Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/next/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
- Experimental pane graphics now support bounded named layers, acknowledged full-RGBA primary-layer direct file frames on audited local terminals, owned BGRA fallback, exact pixel mouse input, and placement-only resize replay.

### Fixed
- `pane report-agent` and `pane report-agent-session` now accept the pane ID after their options, as shown by CLI help, and support `--option=value` syntax.
- Foreground typing no longer waits behind render cadence consumed by output from panes in hidden tabs. (#2890)
- The Windows ARM64 installer now waits for x64 emulation to release the verified executable before activating the downloaded release. (#2916)
- On Unix, Ctrl-click URL openers are now reaped after they exit, preventing defunct child processes from accumulating on long-running servers. (#2903)
Expand Down
73 changes: 57 additions & 16 deletions src/cli/pane.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]";
Comment thread
coderabbitai[bot] marked this conversation as resolved.

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;
Expand All @@ -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" => {
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -1273,20 +1294,28 @@ 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;
let mut agent_session_id = None;
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" => {
Expand Down Expand Up @@ -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)
Expand Down
70 changes: 70 additions & 0 deletions tests/cli/panes.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading