fix(cli): accept reordered agent report arguments (#2928)

refs #2926

Co-authored-by: akbash-bot <300245827+akbash-bot@users.noreply.github.com>
This commit is contained in:
akbash
2026-08-25 22:38:14 +03:00
committed by GitHub
co-authored by akbash-bot
parent 13df1da983
commit da5357d23f
2 changed files with 127 additions and 16 deletions
+57 -16
View File
@@ -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)
+70
View File
@@ -567,6 +567,76 @@ fn pane_shell_gets_herdr_socket_and_pane_env() {
cleanup_spawned_herdr(herdr, base);
}
#[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