From 1e2895800aa76220b854d836de3a0cdb4bf3336c Mon Sep 17 00:00:00 2001 From: Chris Busillo Date: Wed, 6 May 2026 20:19:22 -0400 Subject: [PATCH] feat(tui): attach Every Code origin to remote inbox sessions --- code-rs/tui/src/remote_inbox/client.rs | 99 ++++++++++++++++++++++++ code-rs/tui/src/remote_inbox/protocol.rs | 57 ++++++++++++++ 2 files changed, 156 insertions(+) diff --git a/code-rs/tui/src/remote_inbox/client.rs b/code-rs/tui/src/remote_inbox/client.rs index 18360056e7d..6f9b0139a78 100644 --- a/code-rs/tui/src/remote_inbox/client.rs +++ b/code-rs/tui/src/remote_inbox/client.rs @@ -49,6 +49,7 @@ use crate::remote_inbox::protocol::RemoteUserMessage; use crate::remote_inbox::protocol::ServerMessage; use crate::remote_inbox::protocol::SessionHeartbeat; use crate::remote_inbox::protocol::SessionHello; +use crate::remote_inbox::protocol::SessionOrigin; use crate::remote_inbox::protocol::SessionStatusEvent; use crate::remote_inbox::protocol::RemoteTurnStep; @@ -68,6 +69,7 @@ pub(crate) struct RemoteInboxSession { pub cwd: String, pub branch: Option, pub pid: u32, + pub origin: Option, } pub(crate) struct RemoteInboxClientHandle { @@ -590,10 +592,33 @@ impl RemoteInboxSession { cwd: cwd.display().to_string(), branch, pid: std::process::id(), + origin: session_origin_from_env(), } } } +fn session_origin_from_env() -> Option { + let kind = env_value("EVERY_CODE_SESSION_ORIGIN") + .or_else(|| env_value("EVERY_CODE_ORIGIN")) + .or_else(|| env_value("LAUNCHPLANE_EVERY_CODE_ORIGIN")) + .or_else(|| env_value("EVERY_CODE_REQUEST_ID").map(|_| "every_code".to_string()))?; + + Some(SessionOrigin { + kind, + request_id: env_value("EVERY_CODE_REQUEST_ID"), + repository: env_value("EVERY_CODE_REPOSITORY"), + issue_number: env_value("EVERY_CODE_ISSUE_NUMBER").and_then(|value| value.parse().ok()), + issue_url: env_value("EVERY_CODE_ISSUE_URL"), + }) +} + +fn env_value(key: &str) -> Option { + std::env::var(key) + .ok() + .map(|value| value.trim().to_string()) + .filter(|value| !value.is_empty()) +} + impl RemoteInboxSession { fn hello(&self, config: &RemoteInboxConfig) -> SessionHello { SessionHello { @@ -608,6 +633,7 @@ impl RemoteInboxSession { cwd: self.cwd.clone(), branch: self.branch.clone(), pid: self.pid, + origin: self.origin.clone(), } } } @@ -1463,12 +1489,15 @@ mod tests { use super::*; use std::pin::Pin; use std::sync::mpsc; + use std::sync::Mutex; use std::task::Context; use std::task::Poll; use futures::Sink; use serde_json::json; + static ENV_MUTEX: Mutex<()> = Mutex::new(()); + #[derive(Default)] struct RecordingSink { messages: Vec, @@ -1511,6 +1540,76 @@ mod tests { cwd: "/tmp/project".to_string(), branch: Some("main".to_string()), pid: 42, + origin: None, + } + } + + #[test] + fn session_origin_is_read_from_every_code_env() { + let _guard = ENV_MUTEX.lock().expect("env mutex"); + let backup = EnvBackup::new(&[ + "EVERY_CODE_SESSION_ORIGIN", + "EVERY_CODE_ORIGIN", + "LAUNCHPLANE_EVERY_CODE_ORIGIN", + "EVERY_CODE_REQUEST_ID", + "EVERY_CODE_REPOSITORY", + "EVERY_CODE_ISSUE_NUMBER", + "EVERY_CODE_ISSUE_URL", + ]); + backup.set("EVERY_CODE_SESSION_ORIGIN", "every_code"); + backup.set("EVERY_CODE_REQUEST_ID", "every-code-cbusillo-syo-67"); + backup.set("EVERY_CODE_REPOSITORY", "cbusillo/sellyouroutboard"); + backup.set("EVERY_CODE_ISSUE_NUMBER", "67"); + backup.set( + "EVERY_CODE_ISSUE_URL", + "https://github.com/cbusillo/sellyouroutboard/issues/67", + ); + + let origin = session_origin_from_env().expect("origin from env"); + + assert_eq!(origin.kind, "every_code"); + assert_eq!( + origin.request_id.as_deref(), + Some("every-code-cbusillo-syo-67") + ); + assert_eq!(origin.repository.as_deref(), Some("cbusillo/sellyouroutboard")); + assert_eq!(origin.issue_number, Some(67)); + assert_eq!( + origin.issue_url.as_deref(), + Some("https://github.com/cbusillo/sellyouroutboard/issues/67") + ); + } + + struct EnvBackup { + values: Vec<(&'static str, Option)>, + } + + impl EnvBackup { + fn new(keys: &[&'static str]) -> Self { + let values = keys + .iter() + .map(|key| { + let previous = std::env::var(key).ok(); + unsafe { std::env::remove_var(key) }; + (*key, previous) + }) + .collect(); + Self { values } + } + + fn set(&self, key: &str, value: &str) { + unsafe { std::env::set_var(key, value) }; + } + } + + impl Drop for EnvBackup { + fn drop(&mut self) { + for (key, value) in &self.values { + match value { + Some(value) => unsafe { std::env::set_var(key, value) }, + None => unsafe { std::env::remove_var(key) }, + } + } } } diff --git a/code-rs/tui/src/remote_inbox/protocol.rs b/code-rs/tui/src/remote_inbox/protocol.rs index 112dc7a8784..5dde28f4d61 100644 --- a/code-rs/tui/src/remote_inbox/protocol.rs +++ b/code-rs/tui/src/remote_inbox/protocol.rs @@ -32,6 +32,21 @@ pub(crate) struct SessionHello { pub cwd: String, pub branch: Option, pub pid: u32, + #[serde(skip_serializing_if = "Option::is_none")] + pub origin: Option, +} + +#[derive(Debug, Clone, Serialize, PartialEq, Eq)] +pub(crate) struct SessionOrigin { + pub kind: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub request_id: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub repository: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub issue_number: Option, + #[serde(skip_serializing_if = "Option::is_none")] + pub issue_url: Option, } #[derive(Debug, Clone, Serialize)] @@ -183,6 +198,7 @@ mod tests { cwd: "/tmp/project".to_string(), branch: Some("main".to_string()), pid: 42, + origin: None, }); let value = serde_json::to_value(message).expect("serialize hello"); @@ -201,6 +217,47 @@ mod tests { ); } + #[test] + fn serializes_hello_origin_metadata() { + let message = ClientMessage::Hello(SessionHello { + session_id: "session-1".to_string(), + session_epoch: "epoch-1".to_string(), + host_id: None, + host_label: "Mac Studio".to_string(), + cwd: "/tmp/project".to_string(), + branch: Some("every-code/issue-67".to_string()), + pid: 42, + origin: Some(SessionOrigin { + kind: "every_code".to_string(), + request_id: Some("every-code-cbusillo-syo-67".to_string()), + repository: Some("cbusillo/sellyouroutboard".to_string()), + issue_number: Some(67), + issue_url: Some("https://github.com/cbusillo/sellyouroutboard/issues/67".to_string()), + }), + }); + + let value = serde_json::to_value(message).expect("serialize hello"); + assert_eq!( + value, + json!({ + "type": "hello", + "session_id": "session-1", + "session_epoch": "epoch-1", + "host_label": "Mac Studio", + "cwd": "/tmp/project", + "branch": "every-code/issue-67", + "pid": 42, + "origin": { + "kind": "every_code", + "request_id": "every-code-cbusillo-syo-67", + "repository": "cbusillo/sellyouroutboard", + "issue_number": 67, + "issue_url": "https://github.com/cbusillo/sellyouroutboard/issues/67", + }, + }) + ); + } + #[test] fn omits_empty_assistant_message_from_status_events() { let message = ClientMessage::TurnComplete(SessionStatusEvent {