diff --git a/Cargo.lock b/Cargo.lock index 567f4ec9..60ebafbf 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -515,7 +515,7 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cli-sub-agent" -version = "0.1.199" +version = "0.1.200" dependencies = [ "anyhow", "chrono", @@ -704,7 +704,7 @@ dependencies = [ [[package]] name = "csa-acp" -version = "0.1.199" +version = "0.1.200" dependencies = [ "agent-client-protocol", "anyhow", @@ -724,7 +724,7 @@ dependencies = [ [[package]] name = "csa-config" -version = "0.1.199" +version = "0.1.200" dependencies = [ "anyhow", "chrono", @@ -740,7 +740,7 @@ dependencies = [ [[package]] name = "csa-core" -version = "0.1.199" +version = "0.1.200" dependencies = [ "agent-teams", "chrono", @@ -755,7 +755,7 @@ dependencies = [ [[package]] name = "csa-eval" -version = "0.1.199" +version = "0.1.200" dependencies = [ "anyhow", "chrono", @@ -769,7 +769,7 @@ dependencies = [ [[package]] name = "csa-executor" -version = "0.1.199" +version = "0.1.200" dependencies = [ "agent-teams", "anyhow", @@ -795,7 +795,7 @@ dependencies = [ [[package]] name = "csa-hooks" -version = "0.1.199" +version = "0.1.200" dependencies = [ "anyhow", "chrono", @@ -812,7 +812,7 @@ dependencies = [ [[package]] name = "csa-lock" -version = "0.1.199" +version = "0.1.200" dependencies = [ "anyhow", "chrono", @@ -824,7 +824,7 @@ dependencies = [ [[package]] name = "csa-mcp-hub" -version = "0.1.199" +version = "0.1.200" dependencies = [ "anyhow", "axum", @@ -846,7 +846,7 @@ dependencies = [ [[package]] name = "csa-memory" -version = "0.1.199" +version = "0.1.200" dependencies = [ "anyhow", "async-trait", @@ -864,7 +864,7 @@ dependencies = [ [[package]] name = "csa-process" -version = "0.1.199" +version = "0.1.200" dependencies = [ "anyhow", "chrono", @@ -882,7 +882,7 @@ dependencies = [ [[package]] name = "csa-resource" -version = "0.1.199" +version = "0.1.200" dependencies = [ "anyhow", "csa-core", @@ -898,7 +898,7 @@ dependencies = [ [[package]] name = "csa-scheduler" -version = "0.1.199" +version = "0.1.200" dependencies = [ "anyhow", "chrono", @@ -916,7 +916,7 @@ dependencies = [ [[package]] name = "csa-session" -version = "0.1.199" +version = "0.1.200" dependencies = [ "anyhow", "chrono", @@ -937,7 +937,7 @@ dependencies = [ [[package]] name = "csa-todo" -version = "0.1.199" +version = "0.1.200" dependencies = [ "anyhow", "chrono", @@ -4367,7 +4367,7 @@ dependencies = [ [[package]] name = "weave" -version = "0.1.199" +version = "0.1.200" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 3349af02..e79e494a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -3,7 +3,7 @@ members = ["crates/*"] resolver = "2" [workspace.package] -version = "0.1.199" +version = "0.1.200" edition = "2024" rust-version = "1.88" license = "Apache-2.0" diff --git a/crates/cli-sub-agent/src/session_cmds_daemon.rs b/crates/cli-sub-agent/src/session_cmds_daemon.rs index 89eb77d9..4717eef9 100644 --- a/crates/cli-sub-agent/src/session_cmds_daemon.rs +++ b/crates/cli-sub-agent/src/session_cmds_daemon.rs @@ -43,10 +43,11 @@ fn read_daemon_pid(session_dir: &std::path::Path) -> Option { /// /// Exits 0 when result.toml appears (streams stdout.log), exits 124 on timeout, /// exits 1 if the daemon process died without producing a result. -/// Hardcoded wait timeout in seconds. -const WAIT_TIMEOUT_SECS: u64 = 250; - -pub(crate) fn handle_session_wait(session: String, cd: Option) -> Result { +pub(crate) fn handle_session_wait( + session: String, + cd: Option, + wait_timeout_secs: u64, +) -> Result { let project_root = crate::pipeline::determine_project_root(cd.as_deref())?; let resolved = resolve_session_prefix_with_fallback(&project_root, &session)?; let session_dir = get_session_dir(&project_root, &resolved.session_id)?; @@ -84,10 +85,20 @@ pub(crate) fn handle_session_wait(session: String, cd: Option) -> Result return Ok(1); } - if start.elapsed().as_secs() >= WAIT_TIMEOUT_SECS { + let elapsed = start.elapsed().as_secs(); + if elapsed >= wait_timeout_secs { eprintln!( "Timeout: session {} did not complete within {}s", - resolved.session_id, WAIT_TIMEOUT_SECS + resolved.session_id, wait_timeout_secs, + ); + // Emit structured retry hint for orchestrators / agents. + let cd_arg = cd + .as_ref() + .map(|path| format!(" --cd '{}'", path)) + .unwrap_or_default(); + eprintln!( + "", + resolved.session_id, elapsed, resolved.session_id, cd_arg, ); return Ok(124); } diff --git a/crates/cli-sub-agent/src/session_dispatch.rs b/crates/cli-sub-agent/src/session_dispatch.rs index 8cc173ae..d15be018 100644 --- a/crates/cli-sub-agent/src/session_dispatch.rs +++ b/crates/cli-sub-agent/src/session_dispatch.rs @@ -7,6 +7,7 @@ use anyhow::Result; use crate::cli::SessionCommands; use crate::session_cmds; +use csa_config::DEFAULT_DAEMON_WAIT_SECS; use csa_core::types::OutputFormat; pub(crate) fn dispatch(cmd: SessionCommands, output_format: OutputFormat) -> Result<()> { @@ -90,7 +91,8 @@ pub(crate) fn dispatch(cmd: SessionCommands, output_format: OutputFormat) -> Res session_cmds::handle_session_tool_output(session, index, list, cd)?; } SessionCommands::Wait { session, cd } => { - let exit_code = session_cmds::handle_session_wait(session, cd)?; + let wait_timeout = resolve_daemon_wait_timeout(cd.as_deref()); + let exit_code = session_cmds::handle_session_wait(session, cd, wait_timeout)?; let _ = std::io::stdout().flush(); let _ = std::io::stderr().flush(); std::process::exit(exit_code); @@ -111,3 +113,19 @@ pub(crate) fn dispatch(cmd: SessionCommands, output_format: OutputFormat) -> Res } Ok(()) } + +/// Resolve daemon wait timeout from project/global config, falling back to the +/// compile-time default. +fn resolve_daemon_wait_timeout(cd: Option<&str>) -> u64 { + let project_root = crate::pipeline::determine_project_root(cd).ok(); + if let Some(ref root) = project_root { + match csa_config::ProjectConfig::load(root) { + Ok(Some(config)) => return config.session.daemon_wait_seconds, + Ok(None) => {} // No project config file — use default. + Err(e) => { + tracing::warn!("Failed to load project config for daemon_wait_seconds: {e}") + } + } + } + DEFAULT_DAEMON_WAIT_SECS +} diff --git a/crates/csa-config/src/config.rs b/crates/csa-config/src/config.rs index cd7c42e7..5a0e7b94 100644 --- a/crates/csa-config/src/config.rs +++ b/crates/csa-config/src/config.rs @@ -184,7 +184,9 @@ fn default_recursion_depth() -> u32 { 5 } -pub use super::config_session::{ExecutionConfig, HooksSection, SessionConfig, VcsConfig}; +pub use super::config_session::{ + DEFAULT_DAEMON_WAIT_SECS, ExecutionConfig, HooksSection, SessionConfig, VcsConfig, +}; pub use super::config_tool::{ToolConfig, ToolFilesystemSandboxConfig, ToolRestrictions}; impl ProjectConfig { diff --git a/crates/csa-config/src/config_session.rs b/crates/csa-config/src/config_session.rs index 406580a1..9d50e184 100644 --- a/crates/csa-config/src/config_session.rs +++ b/crates/csa-config/src/config_session.rs @@ -51,6 +51,13 @@ pub struct SessionConfig { /// Only effective when `tool_output_compression` is enabled. #[serde(default = "default_tool_output_threshold_bytes")] pub tool_output_threshold_bytes: u64, + /// Timeout (seconds) for `csa session wait` polling loop. + /// + /// The default of 250s is intentional: it lets the daemon's KV cache stay + /// warm while periodically returning control to the calling orchestrator. + /// The caller is expected to re-invoke `csa session wait` in a loop. + #[serde(default = "default_daemon_wait_seconds")] + pub daemon_wait_seconds: u64, } fn default_seed_max_age_secs() -> u64 { @@ -65,6 +72,13 @@ fn default_tool_output_threshold_bytes() -> u64 { 8192 } +/// Default daemon wait timeout: 250s for KV cache warmth. +pub const DEFAULT_DAEMON_WAIT_SECS: u64 = 250; + +fn default_daemon_wait_seconds() -> u64 { + DEFAULT_DAEMON_WAIT_SECS +} + const DEFAULT_SPOOL_MAX_MB: u32 = 32; const DEFAULT_SPOOL_KEEP_ROTATED: bool = true; @@ -82,6 +96,7 @@ impl Default for SessionConfig { spool_keep_rotated: None, tool_output_compression: false, tool_output_threshold_bytes: default_tool_output_threshold_bytes(), + daemon_wait_seconds: default_daemon_wait_seconds(), } } } @@ -99,6 +114,7 @@ impl SessionConfig { && self.spool_keep_rotated.is_none() && !self.tool_output_compression && self.tool_output_threshold_bytes == default_tool_output_threshold_bytes() + && self.daemon_wait_seconds == default_daemon_wait_seconds() } pub fn resolved_spool_max_mb(&self) -> u32 { diff --git a/crates/csa-config/src/lib.rs b/crates/csa-config/src/lib.rs index d5f1b6d5..3949406f 100644 --- a/crates/csa-config/src/lib.rs +++ b/crates/csa-config/src/lib.rs @@ -24,9 +24,9 @@ pub mod weave_lock; pub use acp::AcpConfig; pub use config::{ - EnforcementMode, ExecutionConfig, HooksSection, ProjectConfig, ProjectMeta, SessionConfig, - TierConfig, TierStrategy, ToolConfig, ToolFilesystemSandboxConfig, ToolResourceProfile, - ToolRestrictions, + DEFAULT_DAEMON_WAIT_SECS, EnforcementMode, ExecutionConfig, HooksSection, ProjectConfig, + ProjectMeta, SessionConfig, TierConfig, TierStrategy, ToolConfig, ToolFilesystemSandboxConfig, + ToolResourceProfile, ToolRestrictions, }; pub use config_filesystem_sandbox::FilesystemSandboxConfig; pub use config_resources::ResourcesConfig;