mirror of
https://github.com/samsonjs/vibetunnel.git
synced 2026-04-19 13:35:54 +00:00
Initial notification channel support
This commit is contained in:
parent
1048e21083
commit
a9d75c2f04
3 changed files with 84 additions and 6 deletions
|
|
@ -38,6 +38,7 @@ fn list_sessions(control_path: &Path) -> Result<(), anyhow::Error> {
|
|||
let session_json_path = path.join("session.json");
|
||||
let stream_out_path = path.join("stream-out");
|
||||
let stdin_path = path.join("stdin");
|
||||
let notification_stream_path = path.join("notification-stream");
|
||||
|
||||
if session_json_path.exists() {
|
||||
let session_data = if let Ok(content) = fs::read_to_string(&session_json_path) {
|
||||
|
|
@ -51,7 +52,8 @@ fn list_sessions(control_path: &Path) -> Result<(), anyhow::Error> {
|
|||
"exit_code": session_info.exit_code,
|
||||
"started_at": session_info.started_at,
|
||||
"stream-out": stream_out_path.canonicalize().unwrap_or(stream_out_path.clone()).to_string_lossy(),
|
||||
"stdin": stdin_path.canonicalize().unwrap_or(stdin_path.clone()).to_string_lossy()
|
||||
"stdin": stdin_path.canonicalize().unwrap_or(stdin_path.clone()).to_string_lossy(),
|
||||
"notification-stream": notification_stream_path.canonicalize().unwrap_or(notification_stream_path.clone()).to_string_lossy().to_string()
|
||||
})
|
||||
} else {
|
||||
// Fallback to old behavior if JSON parsing fails
|
||||
|
|
@ -63,7 +65,8 @@ fn list_sessions(control_path: &Path) -> Result<(), anyhow::Error> {
|
|||
serde_json::json!({
|
||||
"status": status,
|
||||
"stream-out": stream_out_path.canonicalize().unwrap_or(stream_out_path.clone()).to_string_lossy(),
|
||||
"stdin": stdin_path.canonicalize().unwrap_or(stdin_path.clone()).to_string_lossy()
|
||||
"stdin": stdin_path.canonicalize().unwrap_or(stdin_path.clone()).to_string_lossy(),
|
||||
"notification-stream": notification_stream_path.canonicalize().unwrap_or(notification_stream_path.clone()).to_string_lossy().to_string()
|
||||
})
|
||||
}
|
||||
} else {
|
||||
|
|
@ -76,7 +79,8 @@ fn list_sessions(control_path: &Path) -> Result<(), anyhow::Error> {
|
|||
serde_json::json!({
|
||||
"status": status,
|
||||
"stream-out": stream_out_path.canonicalize().unwrap_or(stream_out_path.clone()).to_string_lossy(),
|
||||
"stdin": stdin_path.canonicalize().unwrap_or(stdin_path.clone()).to_string_lossy()
|
||||
"stdin": stdin_path.canonicalize().unwrap_or(stdin_path.clone()).to_string_lossy(),
|
||||
"notification-stream": notification_stream_path.canonicalize().unwrap_or(notification_stream_path.clone()).to_string_lossy().to_string()
|
||||
})
|
||||
};
|
||||
|
||||
|
|
@ -316,6 +320,7 @@ fn main() -> Result<(), anyhow::Error> {
|
|||
// Set up stream-out and stdin paths
|
||||
let stream_out_path = session_path.join("stream-out");
|
||||
let stdin_path = session_path.join("stdin");
|
||||
let notification_stream_path = session_path.join("notification-stream");
|
||||
|
||||
// Create and configure TtySpawn
|
||||
let mut tty_spawn = TtySpawn::new_cmdline(cmdline.iter().map(|s| s.as_os_str()));
|
||||
|
|
@ -328,6 +333,9 @@ fn main() -> Result<(), anyhow::Error> {
|
|||
tty_spawn.session_name(name);
|
||||
}
|
||||
|
||||
// Always enable notification stream
|
||||
tty_spawn.notification_path(¬ification_stream_path)?;
|
||||
|
||||
// Spawn the process
|
||||
let exit_code = tty_spawn.spawn()?;
|
||||
std::process::exit(exit_code);
|
||||
|
|
|
|||
|
|
@ -65,6 +65,13 @@ pub struct AsciinemaEvent {
|
|||
pub data: String,
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
||||
pub struct NotificationEvent {
|
||||
pub timestamp: Timestamp,
|
||||
pub event: String,
|
||||
pub data: serde_json::Value,
|
||||
}
|
||||
|
||||
pub struct StreamWriter {
|
||||
file: std::fs::File,
|
||||
start_time: std::time::Instant,
|
||||
|
|
@ -109,3 +116,23 @@ impl StreamWriter {
|
|||
self.start_time.elapsed().as_secs_f64()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NotificationWriter {
|
||||
file: std::fs::File,
|
||||
}
|
||||
|
||||
impl NotificationWriter {
|
||||
pub fn new(file: std::fs::File) -> Self {
|
||||
Self { file }
|
||||
}
|
||||
|
||||
pub fn write_notification(&mut self, event: NotificationEvent) -> Result<(), std::io::Error> {
|
||||
use std::io::Write;
|
||||
|
||||
let event_json = serde_json::to_string(&event)?;
|
||||
writeln!(self.file, "{}", event_json)?;
|
||||
self.file.flush()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,8 @@ use std::sync::Arc;
|
|||
use tempfile::NamedTempFile;
|
||||
|
||||
use crate::protocol::{
|
||||
AsciinemaEvent, AsciinemaEventType, AsciinemaHeader, SessionInfo, StreamWriter,
|
||||
AsciinemaEvent, AsciinemaEventType, AsciinemaHeader, NotificationEvent, NotificationWriter,
|
||||
SessionInfo, StreamWriter,
|
||||
};
|
||||
use crate::utils;
|
||||
use jiff::Timestamp;
|
||||
|
|
@ -55,6 +56,7 @@ impl TtySpawn {
|
|||
command,
|
||||
stdin_file: None,
|
||||
stream_writer: None,
|
||||
notification_writer: None,
|
||||
session_json_path: None,
|
||||
session_name: None,
|
||||
}),
|
||||
|
|
@ -127,6 +129,18 @@ impl TtySpawn {
|
|||
self
|
||||
}
|
||||
|
||||
/// Sets a path as output file for notifications.
|
||||
pub fn notification_path<P: AsRef<Path>>(&mut self, path: P) -> Result<&mut Self, io::Error> {
|
||||
let file = File::options()
|
||||
.create(true)
|
||||
.append(true)
|
||||
.open(path)?;
|
||||
|
||||
let notification_writer = NotificationWriter::new(file);
|
||||
self.options_mut().notification_writer = Some(notification_writer);
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Spawns the application in the TTY.
|
||||
pub fn spawn(&mut self) -> Result<i32, io::Error> {
|
||||
Ok(spawn(
|
||||
|
|
@ -143,6 +157,7 @@ struct SpawnOptions {
|
|||
command: Vec<OsString>,
|
||||
stdin_file: Option<File>,
|
||||
stream_writer: Option<StreamWriter>,
|
||||
notification_writer: Option<NotificationWriter>,
|
||||
session_json_path: Option<PathBuf>,
|
||||
session_name: Option<String>,
|
||||
}
|
||||
|
|
@ -226,7 +241,7 @@ fn spawn(mut opts: SpawnOptions) -> Result<i32, Errno> {
|
|||
.map(|p| p.to_string_lossy().to_string())
|
||||
.unwrap_or_else(|_| "unknown".to_string());
|
||||
|
||||
let cmdline = opts
|
||||
let cmdline: Vec<String> = opts
|
||||
.command
|
||||
.iter()
|
||||
.map(|s| s.to_string_lossy().to_string())
|
||||
|
|
@ -234,8 +249,22 @@ fn spawn(mut opts: SpawnOptions) -> Result<i32, Errno> {
|
|||
|
||||
let session_name = opts.session_name.unwrap_or(executable_name);
|
||||
|
||||
create_session_info(session_json_path, cmdline, session_name, current_dir)
|
||||
create_session_info(session_json_path, cmdline.clone(), session_name.clone(), current_dir.clone())
|
||||
.map_err(|e| Errno::from_raw(e.raw_os_error().unwrap_or(libc::EIO)))?;
|
||||
|
||||
// Send session started notification
|
||||
if let Some(ref mut notification_writer) = opts.notification_writer {
|
||||
let notification = NotificationEvent {
|
||||
timestamp: Timestamp::now(),
|
||||
event: "session_started".to_string(),
|
||||
data: serde_json::json!({
|
||||
"cmdline": cmdline,
|
||||
"name": session_name,
|
||||
"cwd": current_dir
|
||||
}),
|
||||
};
|
||||
let _ = notification_writer.write_notification(notification);
|
||||
}
|
||||
}
|
||||
// if we can't retrieve the terminal atts we're not directly connected
|
||||
// to a pty in which case we won't do any of the terminal related
|
||||
|
|
@ -293,6 +322,7 @@ fn spawn(mut opts: SpawnOptions) -> Result<i32, Errno> {
|
|||
opts.stdin_file.as_mut(),
|
||||
stderr_pty,
|
||||
true, // flush is always enabled
|
||||
opts.notification_writer.as_mut(),
|
||||
)?;
|
||||
|
||||
// Update session status to exited with exit code
|
||||
|
|
@ -300,6 +330,18 @@ fn spawn(mut opts: SpawnOptions) -> Result<i32, Errno> {
|
|||
let _ = update_session_status(session_json_path, None, "exited", Some(exit_code));
|
||||
}
|
||||
|
||||
// Send session exited notification
|
||||
if let Some(ref mut notification_writer) = opts.notification_writer {
|
||||
let notification = NotificationEvent {
|
||||
timestamp: Timestamp::now(),
|
||||
event: "session_exited".to_string(),
|
||||
data: serde_json::json!({
|
||||
"exit_code": exit_code
|
||||
}),
|
||||
};
|
||||
let _ = notification_writer.write_notification(notification);
|
||||
}
|
||||
|
||||
return Ok(exit_code);
|
||||
}
|
||||
|
||||
|
|
@ -333,6 +375,7 @@ fn communication_loop(
|
|||
in_file: Option<&mut File>,
|
||||
stderr: Option<OwnedFd>,
|
||||
flush: bool,
|
||||
_notification_writer: Option<&mut NotificationWriter>,
|
||||
) -> Result<i32, Errno> {
|
||||
let mut buf = [0; 4096];
|
||||
let mut read_stdin = true;
|
||||
|
|
|
|||
Loading…
Reference in a new issue