aboutsummaryrefslogtreecommitdiff
path: root/src/terminal
diff options
context:
space:
mode:
authorMark Jaquith <mark@jaquith.me>2026-09-05 17:08:19 -0400
committerGitHub <noreply@github.com>2026-09-06 00:08:19 +0300
commitec05328a4413e38ebdcb84365938345e9cf47431 (patch)
tree3f280531f2c81d00d2204ca7e7fb0d3b161072a6 /src/terminal
parent6f57f2f62c5d2591647262caf15bc3c962b9c9f8 (diff)
fix: prevent OpenCode child prompts from leaving status blocked (#3669)
Child permission and question reports omitted the session ID, clearing Herdr’s root session identity and preventing later lifecycle reports from clearing the blocked state. Track child-to-parent relationships and report prompt transitions with their root session ID, including for nested children. This preserves identity and lets existing ownership checks reject unrelated sessions. Add regression coverage for prompt routing and lifecycle transitions, and bump the OpenCode integration version to 11.
Diffstat (limited to 'src/terminal')
-rw-r--r--src/terminal/state.rs54
1 files changed, 54 insertions, 0 deletions
diff --git a/src/terminal/state.rs b/src/terminal/state.rs
index ee170198..dc28af10 100644
--- a/src/terminal/state.rs
+++ b/src/terminal/state.rs
@@ -4786,6 +4786,60 @@ mod tests {
}
#[test]
+ fn opencode_child_prompt_reports_with_root_id_preserve_lifecycle_authority() {
+ let mut terminal = test_terminal();
+ let root = crate::agent_resume::AgentSessionRef::id("opencode-root").unwrap();
+ anchor_full_lifecycle_session(
+ &mut terminal,
+ Agent::OpenCode,
+ "herdr:opencode",
+ "opencode",
+ root.clone(),
+ );
+
+ // The plugin projects child permission/question events onto their root.
+ for (seq, state) in [
+ (20, AgentState::Working),
+ (21, AgentState::Blocked),
+ (22, AgentState::Working),
+ (23, AgentState::Idle),
+ ] {
+ let mutation = terminal
+ .set_hook_authority_with_session_ref(
+ "herdr:opencode".into(),
+ "opencode".into(),
+ state,
+ None,
+ Some(root.clone()),
+ Some(seq),
+ )
+ .expect("root-scoped lifecycle report should be accepted");
+ assert!(!mutation.session_ref_changed);
+ assert_eq!(terminal.state, state);
+ assert_eq!(
+ terminal
+ .hook_authority
+ .as_ref()
+ .unwrap()
+ .session_ref
+ .as_ref(),
+ Some(&root)
+ );
+ }
+
+ let foreign_child_prompt = terminal.set_hook_authority_with_session_ref(
+ "herdr:opencode".into(),
+ "opencode".into(),
+ AgentState::Blocked,
+ None,
+ crate::agent_resume::AgentSessionRef::id("opencode-other-root"),
+ Some(24),
+ );
+ assert!(foreign_child_prompt.is_none());
+ assert_eq!(terminal.state, AgentState::Idle);
+ }
+
+ #[test]
fn opencode_tui_selection_reanchors_full_lifecycle_authority() {
let mut terminal = test_terminal();
let old_session = crate::agent_resume::AgentSessionRef::id("opencode-newer").unwrap();