aboutsummaryrefslogtreecommitdiff
path: root/src/app/api/panes.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/api/panes.rs
parent169a4fd90c20a00aec77d684ecec98b9f0f996cc (diff)
fix: make known-agent exit process-owned
refs #1792 refs #1648
Diffstat (limited to 'src/app/api/panes.rs')
-rw-r--r--src/app/api/panes.rs142
1 files changed, 141 insertions, 1 deletions
diff --git a/src/app/api/panes.rs b/src/app/api/panes.rs
index 2da6d308..43206d1e 100644
--- a/src/app/api/panes.rs
+++ b/src/app/api/panes.rs
@@ -1357,9 +1357,21 @@ impl App {
let Some(terminal) = self.state.terminals.get_mut(&terminal_id) else {
return pane_not_found(id, &params.pane_id);
};
+ if terminal.metadata_report_blocked_by_process_exit(
+ &source,
+ agent_label.as_deref(),
+ applies_to_source.as_deref(),
+ ) {
+ return encode_success(id, ResponseResult::Ok {});
+ }
if !terminal.metadata_report_sequence_is_fresh(&source, params.seq) {
return encode_success(id, ResponseResult::Ok {});
}
+ let metadata_agent = crate::terminal::TerminalState::metadata_report_agent(
+ &source,
+ agent_label.as_deref(),
+ applies_to_source.as_deref(),
+ );
if let Some(tokens) = tokens.as_ref() {
if terminal.metadata_tokens.key_count_after_patch(tokens)
> MAX_METADATA_TOKEN_KEYS_PER_RESOURCE
@@ -1373,7 +1385,8 @@ impl App {
);
}
}
- match terminal.accept_metadata_report(&source, params.seq, tokens.is_some()) {
+ match terminal.accept_metadata_report(&source, params.seq, tokens.is_some(), metadata_agent)
+ {
Ok(true) => {}
Ok(false) => return encode_success(id, ResponseResult::Ok {}),
Err(()) => {
@@ -3819,6 +3832,133 @@ mod tests {
}
#[test]
+ fn pane_metadata_ignored_after_process_exit_does_not_poison_sequence() {
+ let (mut app, pane_id) = app_with_test_workspace();
+ let (_, internal_pane_id) = app.parse_pane_id(&pane_id).unwrap();
+ let terminal_id = app.state.workspaces[0]
+ .pane_state(internal_pane_id)
+ .unwrap()
+ .attached_terminal_id
+ .clone();
+ app.state
+ .terminals
+ .get_mut(&terminal_id)
+ .unwrap()
+ .set_detected_state(Some(Agent::Pi), AgentState::Idle);
+
+ let mut initial = metadata_params(pane_id.clone());
+ initial.source = "custom:pi-metadata".into();
+ initial.agent = Some("pi".into());
+ initial.seq = Some(100);
+ let response = app.handle_pane_report_metadata("initial".into(), initial);
+ let _: SuccessResponse = serde_json::from_str(&response).unwrap();
+
+ let mut initial_tokens = metadata_params(pane_id.clone());
+ initial_tokens.source = "custom:pi-tokens".into();
+ initial_tokens.agent = Some("pi".into());
+ initial_tokens.title = None;
+ initial_tokens.tokens =
+ std::collections::HashMap::from([("generation".into(), Some("old".into()))]);
+ initial_tokens.seq = Some(100);
+ let response = app.handle_pane_report_metadata("initial-tokens".into(), initial_tokens);
+ let _: SuccessResponse = serde_json::from_str(&response).unwrap();
+
+ let exit_at = std::time::Instant::now() + std::time::Duration::from_millis(1);
+ app.state
+ .terminals
+ .get_mut(&terminal_id)
+ .unwrap()
+ .set_detected_state_with_screen_signals_at(
+ Some(Agent::Pi),
+ AgentState::Idle,
+ false,
+ false,
+ false,
+ true,
+ exit_at,
+ );
+ app.state
+ .terminals
+ .get_mut(&terminal_id)
+ .unwrap()
+ .set_detected_state_with_screen_signals_at(
+ None,
+ AgentState::Unknown,
+ false,
+ false,
+ false,
+ false,
+ exit_at + std::time::Duration::from_millis(1),
+ );
+
+ let mut stale = metadata_params(pane_id.clone());
+ stale.source = "custom:pi-metadata".into();
+ stale.agent = Some("pi".into());
+ stale.title = Some("stale".into());
+ stale.seq = Some(200);
+ let response = app.handle_pane_report_metadata("stale".into(), stale);
+ let _: SuccessResponse = serde_json::from_str(&response).unwrap();
+
+ let mut official = metadata_params(pane_id.clone());
+ official.source = "herdr:pi".into();
+ official.seq = Some(200);
+ let response = app.handle_pane_report_metadata("official".into(), official);
+ let _: SuccessResponse = serde_json::from_str(&response).unwrap();
+
+ let terminal = &app.state.terminals[&terminal_id];
+ assert!(terminal.metadata_report_sequence_is_fresh("custom:pi-metadata", Some(1)));
+ assert!(terminal.metadata_report_sequence_is_fresh("custom:pi-tokens", Some(1)));
+ assert!(terminal.metadata_report_sequence_is_fresh("herdr:pi", Some(1)));
+
+ app.state
+ .terminals
+ .get_mut(&terminal_id)
+ .unwrap()
+ .set_detected_state_with_screen_signals_at(
+ Some(Agent::Pi),
+ AgentState::Idle,
+ false,
+ false,
+ false,
+ false,
+ exit_at + std::time::Duration::from_millis(2),
+ );
+ let mut fresh = metadata_params(pane_id.clone());
+ fresh.source = "custom:pi-metadata".into();
+ fresh.agent = Some("pi".into());
+ fresh.title = Some("fresh".into());
+ fresh.seq = Some(1);
+ let response = app.handle_pane_report_metadata("fresh".into(), fresh);
+ let _: SuccessResponse = serde_json::from_str(&response).unwrap();
+
+ let mut fresh_tokens = metadata_params(pane_id);
+ fresh_tokens.source = "custom:pi-tokens".into();
+ fresh_tokens.agent = Some("pi".into());
+ fresh_tokens.title = None;
+ fresh_tokens.tokens =
+ std::collections::HashMap::from([("generation".into(), Some("new".into()))]);
+ fresh_tokens.seq = Some(1);
+ let response = app.handle_pane_report_metadata("fresh-tokens".into(), fresh_tokens);
+ let _: SuccessResponse = serde_json::from_str(&response).unwrap();
+
+ let terminal = &app.state.terminals[&terminal_id];
+ assert_eq!(
+ terminal.agent_metadata["custom:pi-metadata"]
+ .title
+ .as_deref(),
+ Some("fresh")
+ );
+ assert_eq!(
+ terminal
+ .metadata_tokens
+ .values()
+ .get("generation")
+ .map(String::as_str),
+ Some("new")
+ );
+ }
+
+ #[test]
fn pane_report_metadata_accepts_documented_source_chars_and_max_ttl() {
let (mut app, pane_id) = app_with_test_workspace();
let mut params = metadata_params(pane_id);