aboutsummaryrefslogtreecommitdiff
path: root/src/app/api
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/app/api
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/app/api')
-rw-r--r--src/app/api/panes.rs132
-rw-r--r--src/app/api/plugins/mod.rs1
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(&params.pane_id) else {
+ let Some((ws_idx, pane_id)) = self.parse_pane_id(&params.pane_id) else {
return pane_not_found(id, &params.pane_id);
};
let Some(agent_label) = normalize_reported_agent_label(&params.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(&params.source, params.seq));
+ let session_ref = crate::agent_resume::session_ref_from_report(
+ &params.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(
- &params.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(&params.pane_id) else {
+ let Some((ws_idx, pane_id)) = self.parse_pane_id(&params.pane_id) else {
return pane_not_found(id, &params.pane_id);
};
let Some(agent_label) = normalize_reported_agent_label(&params.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(&params.source, params.seq));
+ let session_ref = crate::agent_resume::session_ref_from_report(
+ &params.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(
- &params.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,
},
);