Fix exit event format in terminal sessions

- Change exit event from nested JSON to direct array format: ["exit", exit_code, session_id]
- Add StreamEvent::Exit variant to handle exit events properly in parsing
- Add write_raw_json method to StreamWriter for direct JSON output

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Mario Zechner 2025-06-18 22:03:59 +02:00
parent ce4b67a258
commit 8553de6ae3
3 changed files with 33 additions and 13 deletions

View file

@ -398,6 +398,16 @@ impl StreamWriter {
Ok(())
}
pub fn write_raw_json(&mut self, json_value: &serde_json::Value) -> Result<(), Error> {
use std::io::Write;
let json_string = serde_json::to_string(json_value)?;
writeln!(self.file, "{json_string}")?;
self.file.flush()?;
Ok(())
}
pub fn elapsed_time(&self) -> f64 {
self.start_time.elapsed().as_secs_f64()
}
@ -427,6 +437,7 @@ impl NotificationWriter {
pub enum StreamEvent {
Header(AsciinemaHeader),
Terminal(AsciinemaEvent),
Exit { exit_code: i32, session_id: String },
Error { message: String },
End,
}
@ -454,6 +465,14 @@ impl serde::Serialize for StreamEvent {
match self {
Self::Header(header) => header.serialize(serializer),
Self::Terminal(event) => event.serialize(serializer),
Self::Exit { exit_code, session_id } => {
use serde::ser::SerializeTuple;
let mut tuple = serializer.serialize_tuple(3)?;
tuple.serialize_element("exit")?;
tuple.serialize_element(exit_code)?;
tuple.serialize_element(session_id)?;
tuple.end()
}
Self::Error { message } => {
let error_event = ErrorEvent {
event_type: "error".to_string(),
@ -488,6 +507,15 @@ impl<'de> serde::Deserialize<'de> for StreamEvent {
// Try to parse as an event array [timestamp, type, data]
if let Some(arr) = value.as_array() {
if arr.len() >= 3 {
// Check for exit event: ["exit", exit_code, session_id]
if let Some(first) = arr[0].as_str() {
if first == "exit" {
let exit_code = arr[1].as_i64().unwrap_or(0) as i32;
let session_id = arr[2].as_str().unwrap_or("unknown").to_string();
return Ok(Self::Exit { exit_code, session_id });
}
}
let event: AsciinemaEvent = serde_json::from_value(value).map_err(|e| {
de::Error::custom(format!("Failed to parse terminal event: {e}"))
})?;

View file

@ -405,7 +405,7 @@ fn handle_pty_session(
eprintln!("PTY closed (EOF), updating session status");
// Send exit event to stream before updating session status
let exit_event = json!(["exit", 0, session_id]);
let exit_event = json!(["exit", "0", session_id]);
writeln!(writer, "{exit_event}")?;
writer.flush()?;

View file

@ -538,12 +538,8 @@ fn spawn(mut opts: SpawnOptions) -> Result<i32, Error> {
.and_then(|p| p.file_stem())
.and_then(|s| s.to_str())
.unwrap_or("unknown");
let exit_event = AsciinemaEvent {
time: stream_writer.elapsed_time(),
event_type: AsciinemaEventType::Output,
data: serde_json::json!(["exit", exit_code, session_id]).to_string(),
};
let _ = stream_writer.write_event(exit_event);
let exit_event = serde_json::json!(["exit", exit_code, session_id]);
let _ = stream_writer.write_raw_json(&exit_event);
}
// Update session status to exited with exit code
@ -915,12 +911,8 @@ fn monitor_detached_session(
.and_then(|p| p.file_stem())
.and_then(|s| s.to_str())
.unwrap_or("unknown");
let exit_event = AsciinemaEvent {
time: stream_writer.elapsed_time(),
event_type: AsciinemaEventType::Output,
data: serde_json::json!(["exit", 0, session_id]).to_string(),
};
let _ = stream_writer.write_event(exit_event);
let exit_event = serde_json::json!(["exit", 0, session_id]);
let _ = stream_writer.write_raw_json(&exit_event);
}
// Update session status to exited