diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-09-27 20:57:00 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-27 20:57:00 +0300 |
| commit | 2552d101afe46b8622a7695f38f6cd1766dd1afc (patch) | |
| tree | e560c2cc8ef02689711af0865a5b541ee3050bf1 /src/app/api | |
| parent | 0d1a39dcee6a0f244a036c55935970f40cfa76b8 (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/app/api')
| -rw-r--r-- | src/app/api/panes.rs | 132 | ||||
| -rw-r--r-- | src/app/api/plugins/mod.rs | 1 |
2 files changed, 113 insertions, 20 deletions
diff --git a/src/app/api/panes.rs b/src/app/api/panes.rs index 119d9298..e4e03e71 100644 --- a/src/app/api/panes.rs +++ b/src/app/api/panes.rs @@ -1549,28 +1549,44 @@ impl App { id: String, params: PaneReportAgentParams, ) -> String { - let Some((_ws_idx, pane_id)) = self.parse_pane_id(¶ms.pane_id) else { + let Some((ws_idx, pane_id)) = self.parse_pane_id(¶ms.pane_id) else { return pane_not_found(id, ¶ms.pane_id); }; let Some(agent_label) = normalize_reported_agent_label(¶ms.agent) else { return invalid_agent(id); }; + if let Err(message) = validate_optional_resume_argv(params.resume_argv.as_deref()) { + return encode_error(id, "invalid_resume_argv", message); + } + let report_is_newer = self + .pane_terminal(ws_idx, pane_id) + .is_some_and(|terminal| terminal.hook_report_is_newer(¶ms.source, params.seq)); + let session_ref = crate::agent_resume::session_ref_from_report( + ¶ms.source, + &agent_label, + params.agent_session_id, + params.agent_session_path, + ); self.handle_internal_event(crate::events::AppEvent::HookStateReported { pane_id, - session_ref: crate::agent_resume::session_ref_from_report( - ¶ms.source, - &agent_label, - params.agent_session_id, - params.agent_session_path, - ), - source: params.source, - agent_label, + session_ref: session_ref.clone(), + source: params.source.clone(), + agent_label: agent_label.clone(), state: detect_state_from_api(params.state), message: params.message, seq: params.seq, }); - - encode_success(id, ResponseResult::Ok {}) + let applied = + report_is_newer && self.session_report_applied(ws_idx, pane_id, session_ref.as_ref()); + self.report_agent_resume( + id, + ws_idx, + pane_id, + params.source, + agent_label, + params.seq.filter(|_| applied), + applied.then_some(params.resume_argv).flatten(), + ) } pub(super) fn handle_pane_report_agent_session( @@ -1578,28 +1594,100 @@ impl App { id: String, params: PaneReportAgentSessionParams, ) -> String { - let Some((_ws_idx, pane_id)) = self.parse_pane_id(¶ms.pane_id) else { + let Some((ws_idx, pane_id)) = self.parse_pane_id(¶ms.pane_id) else { return pane_not_found(id, ¶ms.pane_id); }; let Some(agent_label) = normalize_reported_agent_label(¶ms.agent) else { return invalid_agent(id); }; + if let Err(message) = validate_optional_resume_argv(params.resume_argv.as_deref()) { + return encode_error(id, "invalid_resume_argv", message); + } + let report_is_newer = self + .pane_terminal(ws_idx, pane_id) + .is_some_and(|terminal| terminal.hook_report_is_newer(¶ms.source, params.seq)); + let session_ref = crate::agent_resume::session_ref_from_report( + ¶ms.source, + &agent_label, + params.agent_session_id, + params.agent_session_path, + ); self.handle_internal_event(crate::events::AppEvent::AgentSessionReported { pane_id, - session_ref: crate::agent_resume::session_ref_from_report( - ¶ms.source, - &agent_label, - params.agent_session_id, - params.agent_session_path, - ), - source: params.source, - agent_label, + session_ref: session_ref.clone(), + source: params.source.clone(), + agent_label: agent_label.clone(), seq: params.seq, session_start_source: crate::agent_resume::normalize_session_start_source( params.session_start_source, ), }); + let applied = + report_is_newer && self.session_report_applied(ws_idx, pane_id, session_ref.as_ref()); + self.report_agent_resume( + id, + ws_idx, + pane_id, + params.source, + agent_label, + params.seq.filter(|_| applied), + applied.then_some(params.resume_argv).flatten(), + ) + } + /// A resume command belongs to the session it was reported with, so it is + /// kept only when Herdr accepted that session. + fn session_report_applied( + &self, + ws_idx: usize, + pane_id: crate::layout::PaneId, + session_ref: Option<&crate::agent_resume::AgentSessionRef>, + ) -> bool { + session_ref.is_none_or(|session_ref| { + self.pane_terminal(ws_idx, pane_id) + .is_some_and(|terminal| terminal.session_ref_is_current(session_ref)) + }) + } + + fn pane_terminal( + &self, + ws_idx: usize, + pane_id: crate::layout::PaneId, + ) -> Option<&crate::terminal::TerminalState> { + let pane = self.state.workspaces.get(ws_idx)?.pane_state(pane_id)?; + self.state.terminals.get(&pane.attached_terminal_id) + } + + fn report_agent_resume( + &mut self, + id: String, + ws_idx: usize, + pane_id: crate::layout::PaneId, + source: String, + agent_label: String, + seq: Option<u64>, + resume_argv: Option<Vec<String>>, + ) -> String { + let Some(argv) = resume_argv else { + return encode_success(id, ResponseResult::Ok {}); + }; + let can_record = self + .pane_terminal(ws_idx, pane_id) + .is_some_and(|terminal| terminal.can_record_reported_resume(&source, &agent_label)); + if !can_record { + return encode_error( + id, + "resume_not_accepted", + "resume_argv requires the reporter to hold the pane; report its state with pane.report_agent first", + ); + } + self.handle_internal_event(crate::events::AppEvent::AgentResumeReported { + pane_id, + source, + agent_label, + seq, + argv, + }); encode_success(id, ResponseResult::Ok {}) } @@ -2214,6 +2302,10 @@ fn invalid_agent(id: String) -> String { encode_error(id, "invalid_agent", "agent label must not be empty") } +fn validate_optional_resume_argv(argv: Option<&[String]>) -> Result<(), String> { + argv.map_or(Ok(()), crate::agent_resume::validate_resume_argv) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src/app/api/plugins/mod.rs b/src/app/api/plugins/mod.rs index 35dbee2f..c9b37bfe 100644 --- a/src/app/api/plugins/mod.rs +++ b/src/app/api/plugins/mod.rs @@ -3328,6 +3328,7 @@ action = "missing" seq: None, agent_session_id: None, agent_session_path: None, + resume_argv: None, }, ); |
