app
Codex
Public Made by Adomby adom
Codex in Adom Hydrogen: ecosystem setup, dock dashboard and live engineering demos.
main
John Lauer
Add Rust Codex Desktop bridge with native app tools, auth reuse and plugin lifecycle
7aae933
1mo ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
use serde_json::{json, Value};
use std::{
io::{Read, Write},
process::{Command, Stdio},
thread,
time::{Duration, Instant},
};
pub fn hidden(command: &mut Command) -> &mut Command {
#[cfg(windows)]
{
use std::os::windows::process::CommandExt;
command.creation_flags(0x08000000);
}
command
}
// Drain both pipes while waiting: a full stderr pipe must never wedge a job.
pub fn run(mut command: Command, input: Option<&[u8]>, timeout: Duration) -> Result<Value, String> {
hidden(&mut command)
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped());
let mut child = command.spawn().map_err(|e| e.to_string())?;
let mut stdin = child.stdin.take().unwrap();
if let Some(input) = input {
stdin.write_all(input).map_err(|e| e.to_string())?;
}
drop(stdin);
let stdout = child.stdout.take().unwrap();
let stderr = child.stderr.take().unwrap();
fn drain(mut stream: impl Read) -> Vec<u8> {
let mut saved = Vec::new();
let mut buf = [0; 8192];
while let Ok(n) = stream.read(&mut buf) {
if n == 0 {
break;
}
let keep = n.min(2_000_000usize.saturating_sub(saved.len()));
saved.extend_from_slice(&buf[..keep]);
}
saved
}
let (out_tx, out_rx) = std::sync::mpsc::channel();
let (err_tx, err_rx) = std::sync::mpsc::channel();
thread::spawn(move || {
let _ = out_tx.send(drain(stdout));
});
thread::spawn(move || {
let _ = err_tx.send(drain(stderr));
});
let started = Instant::now();
let mut timed_out = false;
let status = loop {
if let Some(status) = child.try_wait().map_err(|e| e.to_string())? {
break status;
}
if started.elapsed() > timeout {
timed_out = true;
let _ = child.kill();
break child.wait().map_err(|e| e.to_string())?;
}
thread::sleep(Duration::from_millis(100));
};
// A launched descendant can inherit pipes after the direct child exits.
let out = out_rx.recv_timeout(Duration::from_secs(2));
let err = err_rx.recv_timeout(Duration::from_secs(2));
let incomplete = out.is_err() || err.is_err();
Ok(
json!({"success":status.success()&&!timed_out&&!incomplete,"exitCode":status.code(),
"timedOut":timed_out,"outputIncomplete":incomplete,"stdout":String::from_utf8_lossy(&out.unwrap_or_default()),
"stderr":String::from_utf8_lossy(&err.unwrap_or_default())}),
)
}
pub fn powershell(script: &str, timeout: Duration) -> Result<Value, String> {
use base64::Engine;
let bytes: Vec<u8> = script.encode_utf16().flat_map(u16::to_le_bytes).collect();
let mut cmd = Command::new("powershell.exe");
cmd.args([
"-NoLogo",
"-NoProfile",
"-NonInteractive",
"-EncodedCommand",
&base64::engine::general_purpose::STANDARD.encode(bytes),
]);
run(cmd, None, timeout)
}
pub fn ps_quote(s: &str) -> String {
format!("'{}'", s.replace('\'', "''"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn quotes_remain_literal() {
assert_eq!(ps_quote("a'$(whoami);b"), "'a''$(whoami);b'");
}
#[cfg(unix)]
#[test]
fn drains_both_streams_and_reports_failure() {
let mut c = Command::new("sh");
c.args(["-c", "printf result; printf problem >&2; exit 7"]);
let r = run(c, None, Duration::from_secs(2)).unwrap();
assert_eq!(r["exitCode"], 7);
assert_eq!(r["success"], false);
assert_eq!(r["stdout"], "result");
assert_eq!(r["stderr"], "problem");
}
}