diff options
| author | akbash <akbash@herdr.dev> | 2026-08-25 22:38:14 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-08-25 22:38:14 +0300 |
| commit | da5357d23f8450d4609f72d079876d7d439ef5a0 (patch) | |
| tree | 2041bf59bf49475cb8c530afd80430b1dcd04fff | |
| parent | 13df1da9837d895b8e0d6bd50ea9fa37e76f525c (diff) | |
fix(cli): accept reordered agent report arguments (#2928)
refs #2926
Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com>
| -rw-r--r-- | src/cli/pane.rs | 73 | ||||
| -rw-r--r-- | tests/cli/panes.rs | 70 |
2 files changed, 127 insertions, 16 deletions
diff --git a/src/cli/pane.rs b/src/cli/pane.rs index 363a2e05..17d3c144 100644 --- a/src/cli/pane.rs +++ b/src/cli/pane.rs @@ -1164,12 +1164,21 @@ fn parse_pane_wait_output_args(args: &[String]) -> Result<PaneWaitForOutputParam } fn pane_report_agent(args: &[String]) -> std::io::Result<i32> { - let Some(raw_pane_id) = args.first() else { - eprintln!("usage: herdr pane report-agent <pane_id> --source ID --agent LABEL --state idle|working|blocked|unknown [--message TEXT] [--seq N] [--agent-session-id ID] [--agent-session-path PATH]"); - return Ok(2); - }; + const USAGE: &str = "usage: herdr pane report-agent <pane_id> --source ID --agent LABEL --state idle|working|blocked|unknown [--message TEXT] [--seq N] [--agent-session-id ID] [--agent-session-path PATH]"; - let pane_id = super::normalize_pane_id(raw_pane_id); + let args = super::expand_equals_args( + args, + &[ + "--source", + "--agent", + "--state", + "--message", + "--seq", + "--agent-session-id", + "--agent-session-path", + ], + ); + let mut pane_id = None; let mut source = None; let mut agent = None; let mut state = None; @@ -1178,7 +1187,7 @@ fn pane_report_agent(args: &[String]) -> std::io::Result<i32> { let mut agent_session_id = None; let mut agent_session_path = None; - let mut index = 1; + let mut index = 0; while index < args.len() { match args[index].as_str() { "--source" => { @@ -1237,13 +1246,25 @@ fn pane_report_agent(args: &[String]) -> std::io::Result<i32> { agent_session_path = Some(value.clone()); index += 2; } - other => { - eprintln!("unknown option: {other}"); + option if option.starts_with('-') => { + eprintln!("unknown option: {option}"); return Ok(2); } + positional => { + if pane_id.is_some() { + eprintln!("unexpected argument: {positional}"); + return Ok(2); + } + pane_id = Some(super::normalize_pane_id(positional)); + index += 1; + } } } + let Some(pane_id) = pane_id else { + eprintln!("{USAGE}"); + return Ok(2); + }; let Some(source) = source.and_then(|source| { let source = source.trim().to_string(); (!source.is_empty()).then_some(source) @@ -1273,12 +1294,20 @@ fn pane_report_agent(args: &[String]) -> std::io::Result<i32> { } fn pane_report_agent_session(args: &[String]) -> std::io::Result<i32> { - let Some(raw_pane_id) = args.first() else { - eprintln!("usage: herdr pane report-agent-session <pane_id> --source ID --agent LABEL [--seq N] [--agent-session-id ID] [--agent-session-path PATH] [--session-start-source SOURCE]"); - return Ok(2); - }; + const USAGE: &str = "usage: herdr pane report-agent-session <pane_id> --source ID --agent LABEL [--seq N] [--agent-session-id ID] [--agent-session-path PATH] [--session-start-source SOURCE]"; - let pane_id = super::normalize_pane_id(raw_pane_id); + let args = super::expand_equals_args( + args, + &[ + "--source", + "--agent", + "--seq", + "--agent-session-id", + "--agent-session-path", + "--session-start-source", + ], + ); + let mut pane_id = None; let mut source = None; let mut agent = None; let mut seq = None; @@ -1286,7 +1315,7 @@ fn pane_report_agent_session(args: &[String]) -> std::io::Result<i32> { let mut agent_session_path = None; let mut session_start_source = None; - let mut index = 1; + let mut index = 0; while index < args.len() { match args[index].as_str() { "--source" => { @@ -1337,13 +1366,25 @@ fn pane_report_agent_session(args: &[String]) -> std::io::Result<i32> { session_start_source = Some(value.clone()); index += 2; } - other => { - eprintln!("unknown option: {other}"); + option if option.starts_with('-') => { + eprintln!("unknown option: {option}"); return Ok(2); } + positional => { + if pane_id.is_some() { + eprintln!("unexpected argument: {positional}"); + return Ok(2); + } + pane_id = Some(super::normalize_pane_id(positional)); + index += 1; + } } } + let Some(pane_id) = pane_id else { + eprintln!("{USAGE}"); + return Ok(2); + }; let Some(source) = source.and_then(|source| { let source = source.trim().to_string(); (!source.is_empty()).then_some(source) diff --git a/tests/cli/panes.rs b/tests/cli/panes.rs index fb6cd3de..90b2bf79 100644 --- a/tests/cli/panes.rs +++ b/tests/cli/panes.rs @@ -568,6 +568,76 @@ fn pane_shell_gets_herdr_socket_and_pane_env() { } #[test] +fn pane_agent_reports_accept_options_before_pane() { + let base = unique_test_dir(); + let config_home = base.join("config"); + let runtime_dir = base.join("runtime"); + let socket_path = runtime_dir.join("herdr.sock"); + + let herdr = spawn_herdr(&config_home, &runtime_dir, &socket_path); + wait_for_socket(&socket_path, Duration::from_secs(5)); + + let created = send_request( + &socket_path, + &format!( + r#"{{"id":"req_agent_report_1","method":"workspace.create","params":{{"cwd":"{}","focus":true}}}}"#, + base.display() + ), + ); + let pane_id = created["result"]["root_pane"]["pane_id"] + .as_str() + .unwrap() + .to_string(); + + let state_report = run_cli( + &socket_path, + &[ + "pane", + "report-agent", + "--source=custom:cli-test", + "--agent=cli-test", + "--state=working", + "--message=parsing=works", + &pane_id, + ], + ); + assert!( + state_report.status.success(), + "stderr: {}", + String::from_utf8_lossy(&state_report.stderr) + ); + assert!(state_report.stdout.is_empty()); + assert!(state_report.stderr.is_empty()); + + let agent = run_cli_json(&socket_path, &["agent", "get", &pane_id]); + assert_eq!(agent["result"]["agent"]["agent"], "cli-test"); + assert_eq!(agent["result"]["agent"]["agent_status"], "working"); + + let session_report = run_cli( + &socket_path, + &[ + "pane", + "report-agent-session", + "--source=custom:cli-test", + "--agent=cli-test", + "--seq=1", + "--agent-session-id=session=1", + "--session-start-source=startup", + &pane_id, + ], + ); + assert!( + session_report.status.success(), + "stderr: {}", + String::from_utf8_lossy(&session_report.stderr) + ); + assert!(session_report.stdout.is_empty()); + assert!(session_report.stderr.is_empty()); + + cleanup_spawned_herdr(herdr, base); +} + +#[test] fn pane_read_rejects_invalid_value_with_usage_error() { // Invalid option values fail as CLI usage errors before any server // contact: exit 2 with the plain parser message, not the old |
