aboutsummaryrefslogtreecommitdiff
path: root/src/server
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-27 20:57:00 +0300
committerGitHub <noreply@github.com>2026-09-27 20:57:00 +0300
commit2552d101afe46b8622a7695f38f6cd1766dd1afc (patch)
treee560c2cc8ef02689711af0865a5b541ee3050bf1 /src/server
parent0d1a39dcee6a0f244a036c55935970f40cfa76b8 (diff)
feat: let agents report their own resume command (#4687)
* feat: let agents report their own resume command * fix: keep reported resume commands tied to accepted sessions and current owners
Diffstat (limited to 'src/server')
-rw-r--r--src/server/headless/tests/mod.rs140
1 files changed, 140 insertions, 0 deletions
diff --git a/src/server/headless/tests/mod.rs b/src/server/headless/tests/mod.rs
index 4def3fa4..bbb7bfe6 100644
--- a/src/server/headless/tests/mod.rs
+++ b/src/server/headless/tests/mod.rs
@@ -7289,6 +7289,142 @@ fn completion_guard_api_report(server: &mut HeadlessServer, method: api::schema:
serde_json::from_str::<api::schema::SuccessResponse>(&response).expect("successful report");
}
+#[test]
+fn api_report_agent_stores_valid_resume_argv_and_rejects_invalid() {
+ let (writer, _control_rx, _render_rx) = test_client_writer();
+ let (mut server, pane_id) = completion_guard_server(writer);
+ let public_pane_id = server.app.public_pane_id(0, pane_id).unwrap();
+ let terminal_id = server.app.state.workspaces[0].tabs[0].panes[&pane_id]
+ .attached_terminal_id
+ .clone();
+ let report = |resume_argv: Vec<&str>| {
+ api::schema::Method::PaneReportAgent(api::schema::PaneReportAgentParams {
+ pane_id: public_pane_id.clone(),
+ source: "prime-agent".into(),
+ agent: "prime-agent".into(),
+ state: api::schema::PaneAgentState::Idle,
+ message: None,
+ seq: Some(1),
+ agent_session_id: Some("01a0".into()),
+ agent_session_path: None,
+ resume_argv: Some(resume_argv.into_iter().map(String::from).collect()),
+ })
+ };
+
+ completion_guard_api_report(
+ &mut server,
+ report(vec!["prime-agent", "--resume", "01a0", "--model", "x"]),
+ );
+ assert_eq!(
+ server.app.state.terminals[&terminal_id]
+ .reported_resume()
+ .unwrap()
+ .argv,
+ vec!["prime-agent", "--resume", "01a0", "--model", "x"]
+ );
+
+ completion_guard_api_report(&mut server, report(vec!["prime-agent", "--resume", "dup"]));
+ assert_eq!(
+ server.app.state.terminals[&terminal_id]
+ .reported_resume()
+ .unwrap()
+ .argv[2],
+ "01a0",
+ "a duplicate sequence number must not replace the command"
+ );
+
+ let (respond_to, response_rx) = std::sync::mpsc::channel();
+ server.handle_api_request_with_shutdown_check(api::ApiRequestMessage {
+ request: api::schema::Request {
+ id: "invalid-resume".into(),
+ method: report(vec!["/opt/prime/prime-agent", "--resume", "01a0"]),
+ },
+ respond_to,
+ response_write_complete: None,
+ });
+ let response = response_rx
+ .recv_timeout(Duration::from_millis(100))
+ .unwrap();
+ assert!(response.contains("invalid_resume_argv"), "{response}");
+ assert_eq!(
+ server.app.state.terminals[&terminal_id]
+ .reported_resume()
+ .unwrap()
+ .argv[0],
+ "prime-agent"
+ );
+
+ let (respond_to, response_rx) = std::sync::mpsc::channel();
+ server.handle_api_request_with_shutdown_check(api::ApiRequestMessage {
+ request: api::schema::Request {
+ id: "not-owner".into(),
+ method: api::schema::Method::PaneReportAgentSession(
+ api::schema::PaneReportAgentSessionParams {
+ pane_id: public_pane_id.clone(),
+ source: "custom:intruder".into(),
+ agent: "intruder".into(),
+ seq: None,
+ agent_session_id: None,
+ agent_session_path: None,
+ session_start_source: None,
+ resume_argv: Some(vec!["intruder".into()]),
+ },
+ ),
+ },
+ respond_to,
+ response_write_complete: None,
+ });
+ let response = response_rx
+ .recv_timeout(Duration::from_millis(100))
+ .unwrap();
+ assert!(response.contains("resume_not_accepted"), "{response}");
+ assert_eq!(
+ server.app.state.terminals[&terminal_id]
+ .reported_resume()
+ .unwrap()
+ .agent,
+ "prime-agent"
+ );
+}
+
+#[test]
+fn api_resume_argv_is_ignored_when_its_session_report_is_refused() {
+ let (writer, _control_rx, _render_rx) = test_client_writer();
+ let (mut server, pane_id) = completion_guard_server(writer);
+ let public_pane_id = server.app.public_pane_id(0, pane_id).unwrap();
+ let terminal_id = server.app.state.workspaces[0].tabs[0].panes[&pane_id]
+ .attached_terminal_id
+ .clone();
+ server.handle_internal_event_with_forwarding(AppEvent::AgentProcessDetected {
+ pane_id,
+ agent: crate::detect::Agent::Claude,
+ observed_at: Instant::now(),
+ });
+ let report = |session: &str| {
+ api::schema::Method::PaneReportAgentSession(api::schema::PaneReportAgentSessionParams {
+ pane_id: public_pane_id.clone(),
+ source: "herdr:claude".into(),
+ agent: "claude".into(),
+ seq: None,
+ agent_session_id: Some(session.into()),
+ agent_session_path: None,
+ session_start_source: None,
+ resume_argv: Some(vec!["claude".into(), "--resume".into(), session.into()]),
+ })
+ };
+
+ completion_guard_api_report(&mut server, report("session-a"));
+ completion_guard_api_report(&mut server, report("session-b"));
+
+ let terminal = &server.app.state.terminals[&terminal_id];
+ assert!(terminal
+ .session_ref_is_current(&crate::agent_resume::AgentSessionRef::id("session-a").unwrap()));
+ assert_eq!(
+ terminal.reported_resume().unwrap().argv,
+ vec!["claude", "--resume", "session-a"]
+ );
+}
+
fn completion_guard_notifications(
server: &mut HeadlessServer,
receiver: &std::sync::mpsc::Receiver<Vec<u8>>,
@@ -7347,6 +7483,7 @@ fn completion_guard_api_startup_blocker_respects_suppression() {
seq: Some(seq as u64 + 1),
agent_session_id: None,
agent_session_path: None,
+ resume_argv: None,
}),
);
}
@@ -7410,6 +7547,7 @@ fn completion_guard_api_session_replacement_does_not_notify_finished() {
seq: Some(11),
agent_session_id: None,
agent_session_path: Some(new_session.clone()),
+ resume_argv: None,
session_start_source: Some(reason.into()),
}),
);
@@ -7422,6 +7560,7 @@ fn completion_guard_api_session_replacement_does_not_notify_finished() {
seq: Some(12),
agent_session_id: None,
agent_session_path: Some(new_session.clone()),
+ resume_argv: None,
};
completion_guard_api_report(&mut server, Method::PaneReportAgent(report.clone()));
let terminal = &server.app.state.terminals[&terminal_id];
@@ -7612,6 +7751,7 @@ fn stale_api_agent_report_does_not_forward_done_sound() {
seq: Some(19),
agent_session_id: None,
agent_session_path: None,
+ resume_argv: None,
}),
},
respond_to,