diff options
Diffstat (limited to 'src/app')
| -rw-r--r-- | src/app/actions.rs | 119 | ||||
| -rw-r--r-- | src/app/api.rs | 3 | ||||
| -rw-r--r-- | src/app/api/panes.rs | 32 |
3 files changed, 125 insertions, 29 deletions
diff --git a/src/app/actions.rs b/src/app/actions.rs index f6c76a36..95e634be 100644 --- a/src/app/actions.rs +++ b/src/app/actions.rs @@ -2041,17 +2041,38 @@ impl AppState { custom_status, seq, session_ref, + } => { + if crate::agent_resume::is_reserved_native_state_source(&source, &agent_label) { + self.update_terminal_state(pane_id, |terminal| { + terminal.set_agent_session_ref(source, agent_label, session_ref, seq) + }) + .into_iter() + .collect() + } else { + self.update_terminal_state(pane_id, |terminal| { + terminal.set_hook_authority_with_session_ref( + source, + agent_label, + state, + message, + custom_status, + session_ref, + seq, + ) + }) + .into_iter() + .collect() + } + } + AppEvent::AgentSessionReported { + pane_id, + source, + agent_label, + seq, + session_ref, } => self .update_terminal_state(pane_id, |terminal| { - terminal.set_hook_authority_with_session_ref( - source, - agent_label, - state, - message, - custom_status, - session_ref, - seq, - ) + terminal.set_agent_session_ref(source, agent_label, session_ref, seq) }) .into_iter() .collect(), @@ -2106,12 +2127,17 @@ impl AppState { agent_label, seq, .. - } => self - .update_terminal_state(pane_id, |terminal| { - terminal.release_agent_with_mutation(&source, &agent_label, seq) - }) - .into_iter() - .collect(), + } => { + if crate::agent_resume::is_reserved_native_state_source(&source, &agent_label) { + Vec::new() + } else { + self.update_terminal_state(pane_id, |terminal| { + terminal.release_agent_with_mutation(&source, &agent_label, seq) + }) + .into_iter() + .collect() + } + } // Intercepted in App::handle_internal_event before reaching this // dispatch; never touches AppState. AppEvent::ClipboardWrite { .. } => Vec::new(), @@ -3603,20 +3629,20 @@ mod tests { } #[test] - fn visible_idle_waits_before_overriding_claude_hook_working() { - let mut state = app_with_workspaces(&["active", "background"]); + fn reserved_native_state_report_does_not_override_screen_state() { + let mut state = app_with_workspaces(&["active"]); state.active = Some(0); state.toast_config.delivery = crate::config::ToastDelivery::Herdr; - let bg_pane_id = *state.workspaces[1].panes.keys().next().unwrap(); - let bg_terminal_id = state.workspaces[1] + let pane_id = *state.workspaces[0].panes.keys().next().unwrap(); + let terminal_id = state.workspaces[0] .panes - .get(&bg_pane_id) + .get(&pane_id) .unwrap() .attached_terminal_id .clone(); state.handle_app_event(AppEvent::StateChanged { - pane_id: bg_pane_id, + pane_id, agent: Some(Agent::Claude), state: AgentState::Working, visible_blocker: false, @@ -3626,17 +3652,22 @@ mod tests { observed_at: std::time::Instant::now(), }); state.handle_app_event(AppEvent::HookStateReported { - pane_id: bg_pane_id, + pane_id, source: "herdr:claude".into(), agent_label: "claude".into(), - state: AgentState::Working, + state: AgentState::Blocked, message: None, custom_status: None, seq: Some(1), - session_ref: None, + session_ref: crate::agent_resume::AgentSessionRef::id("claude-session"), }); + let terminal = state.terminals.get(&terminal_id).unwrap(); + assert_eq!(terminal.state, AgentState::Working); + assert!(terminal.hook_authority.is_none()); + assert!(terminal.persisted_agent_session.is_some()); + state.handle_app_event(AppEvent::StateChanged { - pane_id: bg_pane_id, + pane_id, agent: Some(Agent::Claude), state: AgentState::Idle, visible_blocker: false, @@ -3646,12 +3677,46 @@ mod tests { observed_at: std::time::Instant::now(), }); - let terminal = state.terminals.get(&bg_terminal_id).unwrap(); - assert_eq!(terminal.state, AgentState::Working); + let terminal = state.terminals.get(&terminal_id).unwrap(); + assert_eq!(terminal.state, AgentState::Idle); assert!(state.toast.is_none()); } #[test] + fn reserved_native_release_report_does_not_clear_screen_state() { + let mut state = app_with_workspaces(&["active"]); + let pane_id = *state.workspaces[0].panes.keys().next().unwrap(); + let terminal_id = state.workspaces[0] + .panes + .get(&pane_id) + .unwrap() + .attached_terminal_id + .clone(); + + state.handle_app_event(AppEvent::StateChanged { + pane_id, + agent: Some(Agent::Claude), + state: AgentState::Working, + visible_blocker: false, + visible_idle: false, + visible_working: true, + process_exited: false, + observed_at: std::time::Instant::now(), + }); + state.handle_app_event(AppEvent::HookAgentReleased { + pane_id, + source: "herdr:claude".into(), + agent_label: "claude".into(), + known_agent: Some(Agent::Claude), + seq: Some(1), + }); + + let terminal = state.terminals.get(&terminal_id).unwrap(); + assert_eq!(terminal.state, AgentState::Working); + assert_eq!(terminal.detected_agent, Some(Agent::Claude)); + } + + #[test] fn hidden_session_ref_only_update_marks_session_dirty_without_visible_update() { let mut state = app_with_workspaces(&["active"]); let pane_id = *state.workspaces[0].panes.keys().next().unwrap(); diff --git a/src/app/api.rs b/src/app/api.rs index 1442012b..733fb8b8 100644 --- a/src/app/api.rs +++ b/src/app/api.rs @@ -562,6 +562,9 @@ impl App { Method::PaneReportAgent(params) => { return self.handle_pane_report_agent(request.id, params); } + Method::PaneReportAgentSession(params) => { + return self.handle_pane_report_agent_session(request.id, params); + } Method::PaneReportMetadata(params) => { return self.handle_pane_report_metadata(request.id, params); } diff --git a/src/app/api/panes.rs b/src/app/api/panes.rs index 0902ac39..d027f732 100644 --- a/src/app/api/panes.rs +++ b/src/app/api/panes.rs @@ -3,8 +3,9 @@ use bytes::Bytes; use crate::api::schema::{ EventData, EventEnvelope, EventKind, PaneClearAgentAuthorityParams, PaneListParams, PaneReadParams, PaneReadResult, PaneReleaseAgentParams, PaneRenameParams, - PaneReportAgentParams, PaneReportMetadataParams, PaneSendInputParams, PaneSendKeysParams, - PaneSendTextParams, PaneSplitParams, PaneTarget, ReadFormat, ReadSource, ResponseResult, + PaneReportAgentParams, PaneReportAgentSessionParams, PaneReportMetadataParams, + PaneSendInputParams, PaneSendKeysParams, PaneSendTextParams, PaneSplitParams, PaneTarget, + ReadFormat, ReadSource, ResponseResult, }; use crate::app::{App, Mode}; @@ -200,6 +201,33 @@ impl App { encode_success(id, ResponseResult::Ok {}) } + pub(super) fn handle_pane_report_agent_session( + &mut self, + id: String, + params: PaneReportAgentSessionParams, + ) -> String { + 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); + }; + 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, + seq: params.seq, + }); + + encode_success(id, ResponseResult::Ok {}) + } + pub(super) fn handle_pane_report_metadata( &mut self, id: String, |
