aboutsummaryrefslogtreecommitdiff
path: root/src/app/actions.rs
diff options
context:
space:
mode:
authorOgulcan Celik <ogulcancelik@gmail.com>2026-07-24 02:49:33 +0300
committerOgulcan Celik <ogulcancelik@gmail.com>2026-07-24 02:50:58 +0300
commite608a75159fc5cca70485d83ffb8ee681c9da385 (patch)
tree5ffdec62d0bee9a798418d6414ecbddae7ff8ae5 /src/app/actions.rs
parent169a4fd90c20a00aec77d684ecec98b9f0f996cc (diff)
fix: make known-agent exit process-owned
refs #1792 refs #1648
Diffstat (limited to 'src/app/actions.rs')
-rw-r--r--src/app/actions.rs104
1 files changed, 77 insertions, 27 deletions
diff --git a/src/app/actions.rs b/src/app/actions.rs
index b7d335b1..ff786846 100644
--- a/src/app/actions.rs
+++ b/src/app/actions.rs
@@ -2793,7 +2793,7 @@ impl AppState {
seq,
..
} => {
- if crate::agent_resume::is_reserved_native_state_source(&source, &agent_label) {
+ if crate::agent_resume::is_official_agent_source(&source, &agent_label) {
Vec::new()
} else {
self.update_terminal_state(pane_id, |terminal| {
@@ -2890,8 +2890,16 @@ impl AppState {
previous_state: change.previous_state,
previous_seen,
previous_presentation: change.previous_presentation.clone(),
- agent_label: change.agent_label.clone(),
- known_agent: change.known_agent,
+ agent_label: if agent_released {
+ change.previous_agent_label.clone()
+ } else {
+ change.agent_label.clone()
+ },
+ known_agent: if agent_released {
+ change.previous_known_agent
+ } else {
+ change.known_agent
+ },
state: change.state,
seen,
presentation: change.presentation.clone(),
@@ -3011,7 +3019,11 @@ impl AppState {
return None;
}
- let agent_label = change.agent_label.clone()?;
+ let agent_label = change
+ .agent_label
+ .clone()
+ .or_else(|| change.previous_agent_label.clone())?;
+ let known_agent = change.known_agent.or(change.previous_known_agent);
let kind = client_notification_kind.unwrap_or(match sound {
Some(crate::sound::Sound::Request) => ToastKind::NeedsAttention,
Some(crate::sound::Sound::Done) | None => ToastKind::Finished,
@@ -3024,7 +3036,7 @@ impl AppState {
pane_id,
workspace_id,
agent_label,
- change.known_agent,
+ known_agent,
kind,
change.state,
);
@@ -3036,7 +3048,7 @@ impl AppState {
pane_id,
workspace_id,
agent_label,
- known_agent: change.known_agent,
+ known_agent,
kind,
state: change.state,
deadline: {
@@ -3071,7 +3083,10 @@ impl AppState {
if terminal_state.state != expected_state {
return None;
}
- if terminal_state.effective_agent_label() != Some(agent_label.as_str()) {
+ if terminal_state
+ .effective_agent_label()
+ .is_some_and(|current| current != agent_label)
+ {
return None;
}
@@ -4978,7 +4993,7 @@ mod tests {
}
#[test]
- fn reserved_native_release_report_does_not_clear_screen_state() {
+ fn official_release_preserves_process_owned_agent_identity() {
let mut state = app_with_workspaces(&["active"]);
let pane_id = *state.workspaces[0].panes.keys().next().unwrap();
let terminal_id = state.workspaces[0]
@@ -4990,24 +5005,51 @@ mod tests {
state.handle_app_event(AppEvent::StateChanged {
pane_id,
- agent: Some(Agent::Claude),
+ agent: Some(Agent::Pi),
state: AgentState::Working,
visible_blocker: false,
visible_working: true,
process_exited: false,
observed_at: std::time::Instant::now(),
});
- state.handle_app_event(AppEvent::HookAgentReleased {
+ let terminal = state.terminals.get_mut(&terminal_id).unwrap();
+ terminal.set_persisted_agent_session(crate::agent_resume::PersistedAgentSession {
+ source: "herdr:pi".into(),
+ agent: "pi".into(),
+ session_ref: crate::agent_resume::AgentSessionRef::path(
+ std::env::current_dir()
+ .unwrap()
+ .join("release-session.jsonl")
+ .display()
+ .to_string(),
+ )
+ .unwrap(),
+ });
+ terminal.set_hook_authority(
+ "herdr:pi".into(),
+ "pi".into(),
+ AgentState::Working,
+ None,
+ Some(1),
+ );
+ terminal.set_agent_name("reviewer".into());
+ state.session_dirty = false;
+
+ let updates = state.handle_app_event(AppEvent::HookAgentReleased {
pane_id,
- source: "herdr:claude".into(),
- agent_label: "claude".into(),
- known_agent: Some(Agent::Claude),
- seq: Some(1),
+ source: "herdr:pi".into(),
+ agent_label: "pi".into(),
+ known_agent: Some(Agent::Pi),
+ seq: Some(2),
});
- let terminal = state.terminals.get(&terminal_id).unwrap();
+ assert!(updates.is_empty());
+ let terminal = &state.terminals[&terminal_id];
assert_eq!(terminal.state, AgentState::Working);
- assert_eq!(terminal.detected_agent, Some(Agent::Claude));
+ assert_eq!(terminal.detected_agent, Some(Agent::Pi));
+ assert_eq!(terminal.agent_name.as_deref(), Some("reviewer"));
+ assert!(terminal.full_lifecycle_hook_authority_active());
+ assert!(!state.session_dirty);
}
#[test]
@@ -5081,7 +5123,7 @@ mod tests {
}
#[test]
- fn releasing_an_agent_alias_marks_the_session_dirty() {
+ fn custom_release_clears_report_owned_agent() {
let mut state = app_with_workspaces(&["active"]);
let pane_id = *state.workspaces[0].panes.keys().next().unwrap();
let terminal_id = state.workspaces[0]
@@ -5089,21 +5131,29 @@ mod tests {
.unwrap()
.attached_terminal_id
.clone();
- let terminal = state.terminals.get_mut(&terminal_id).unwrap();
- terminal.set_detected_state(Some(Agent::Pi), AgentState::Working);
- terminal.set_agent_name("reviewer".into());
- state.session_dirty = false;
+ state
+ .terminals
+ .get_mut(&terminal_id)
+ .unwrap()
+ .set_hook_authority(
+ "custom:agent".into(),
+ "custom-agent".into(),
+ AgentState::Working,
+ None,
+ Some(1),
+ );
state.handle_app_event(AppEvent::HookAgentReleased {
pane_id,
- source: "herdr:pi".into(),
- agent_label: "pi".into(),
- known_agent: Some(Agent::Pi),
- seq: Some(1),
+ source: "custom:agent".into(),
+ agent_label: "custom-agent".into(),
+ known_agent: None,
+ seq: Some(2),
});
- assert!(state.terminals[&terminal_id].agent_name.is_none());
- assert!(state.session_dirty);
+ let terminal = &state.terminals[&terminal_id];
+ assert!(terminal.hook_authority.is_none());
+ assert_eq!(terminal.state, AgentState::Unknown);
}
#[test]