This repository has been archived on 2026-10-29. You can view files and clone it, but you cannot make any changes to its state, such as pushing and creating new issues, pull requests or comments.
Epiphany/apps/epiphany-gui/src-tauri/src/lib.rs
2026-04-29 16:50:18 +01:00

329 lines
10 KiB
Rust

use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::fs;
use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
struct StatusRequest {
thread_id: Option<String>,
cwd: Option<String>,
codex_home: Option<String>,
app_server: Option<String>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct OperatorActionResult {
action: String,
artifact_path: String,
summary: String,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct ArtifactBundle {
name: String,
path: String,
files: Vec<String>,
summary_path: Option<String>,
final_status_path: Option<String>,
comparison_path: Option<String>,
modified_millis: Option<u128>,
}
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
struct OperatorSnapshot {
generated_at: String,
repo_root: String,
status: Value,
artifacts: Vec<ArtifactBundle>,
}
#[tauri::command]
fn load_operator_snapshot(request: Option<StatusRequest>) -> Result<OperatorSnapshot, String> {
let repo_root = repo_root()?;
let status = load_status(&repo_root, request.unwrap_or_default())?;
let artifacts = list_artifacts(&repo_root)?;
Ok(OperatorSnapshot {
generated_at: unix_millis().to_string(),
repo_root: repo_root.display().to_string(),
status,
artifacts,
})
}
#[tauri::command]
fn run_operator_action(
action: String,
request: Option<StatusRequest>,
) -> Result<OperatorActionResult, String> {
let repo_root = repo_root()?;
let request = request.unwrap_or_default();
match action.as_str() {
"statusSnapshot" => run_status_snapshot(&repo_root, request),
"coordinatorPlan" => run_coordinator_plan(&repo_root, request),
_ => Err(format!("unknown operator action: {action}")),
}
}
fn load_status(repo_root: &Path, request: StatusRequest) -> Result<Value, String> {
let python = find_python()?;
let status_script = repo_root.join("tools").join("epiphany_mvp_status.py");
let workspace = request
.cwd
.map(PathBuf::from)
.unwrap_or_else(|| repo_root.to_path_buf());
let codex_home = request
.codex_home
.map(PathBuf::from)
.unwrap_or_else(|| repo_root.join(".epiphany-gui").join("codex-home"));
let transcript = repo_root
.join(".epiphany-gui")
.join("status-transcript.jsonl");
let stderr = repo_root
.join(".epiphany-gui")
.join("status-server.stderr.log");
let mut command = Command::new(python);
command
.current_dir(repo_root)
.arg(status_script)
.arg("--json")
.arg("--cwd")
.arg(workspace)
.arg("--codex-home")
.arg(codex_home)
.arg("--transcript")
.arg(transcript)
.arg("--stderr")
.arg(stderr);
if let Some(thread_id) = request.thread_id {
command.arg("--thread-id").arg(thread_id);
}
if let Some(app_server) = request.app_server {
command.arg("--app-server").arg(app_server);
}
let output = command
.output()
.map_err(|err| format!("failed to run Epiphany status bridge: {err}"))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!(
"Epiphany status bridge exited with {}: {}",
output.status, stderr
));
}
serde_json::from_slice(&output.stdout)
.map_err(|err| format!("failed to parse Epiphany status JSON: {err}"))
}
fn run_status_snapshot(
repo_root: &Path,
request: StatusRequest,
) -> Result<OperatorActionResult, String> {
let python = find_python()?;
let artifact_root = repo_root
.join(".epiphany-gui")
.join("status-snapshots")
.join(unix_millis().to_string());
fs::create_dir_all(&artifact_root)
.map_err(|err| format!("failed to create status artifact dir: {err}"))?;
let result_path = artifact_root.join("status.json");
let transcript_path = artifact_root.join("transcript.jsonl");
let stderr_path = artifact_root.join("server.stderr.log");
let workspace = request
.cwd
.map(PathBuf::from)
.unwrap_or_else(|| repo_root.to_path_buf());
let codex_home = request
.codex_home
.map(PathBuf::from)
.unwrap_or_else(|| repo_root.join(".epiphany-gui").join("codex-home"));
let mut command = Command::new(python);
command
.current_dir(repo_root)
.arg(repo_root.join("tools").join("epiphany_mvp_status.py"))
.arg("--json")
.arg("--cwd")
.arg(workspace)
.arg("--codex-home")
.arg(codex_home)
.arg("--result")
.arg(&result_path)
.arg("--transcript")
.arg(transcript_path)
.arg("--stderr")
.arg(stderr_path);
if let Some(thread_id) = request.thread_id {
command.arg("--thread-id").arg(thread_id);
}
if let Some(app_server) = request.app_server {
command.arg("--app-server").arg(app_server);
}
run_command(command, "status snapshot")?;
Ok(OperatorActionResult {
action: "statusSnapshot".to_string(),
artifact_path: artifact_root.display().to_string(),
summary: "Status snapshot written.".to_string(),
})
}
fn run_coordinator_plan(
repo_root: &Path,
request: StatusRequest,
) -> Result<OperatorActionResult, String> {
let python = find_python()?;
let artifact_dir = repo_root
.join(".epiphany-dogfood")
.join(format!("gui-coordinator-plan-{}", unix_millis()));
let workspace = request
.cwd
.map(PathBuf::from)
.unwrap_or_else(|| repo_root.to_path_buf());
let codex_home = request
.codex_home
.map(PathBuf::from)
.unwrap_or_else(|| repo_root.join(".epiphany-gui").join("codex-home"));
let mut command = Command::new(python);
command
.current_dir(repo_root)
.arg(repo_root.join("tools").join("epiphany_mvp_coordinator.py"))
.arg("--mode")
.arg("plan")
.arg("--max-steps")
.arg("2")
.arg("--cwd")
.arg(workspace)
.arg("--codex-home")
.arg(codex_home)
.arg("--artifact-dir")
.arg(&artifact_dir);
if let Some(thread_id) = request.thread_id {
command.arg("--thread-id").arg(thread_id);
}
if let Some(app_server) = request.app_server {
command.arg("--app-server").arg(app_server);
}
run_command(command, "coordinator plan")?;
Ok(OperatorActionResult {
action: "coordinatorPlan".to_string(),
artifact_path: artifact_dir.display().to_string(),
summary: "Coordinator plan artifact written.".to_string(),
})
}
fn run_command(mut command: Command, label: &str) -> Result<(), String> {
let output = command
.output()
.map_err(|err| format!("failed to run {label}: {err}"))?;
if output.status.success() {
return Ok(());
}
let stderr = String::from_utf8_lossy(&output.stderr);
Err(format!("{label} exited with {}: {}", output.status, stderr))
}
fn list_artifacts(repo_root: &Path) -> Result<Vec<ArtifactBundle>, String> {
let root = repo_root.join(".epiphany-dogfood");
if !root.exists() {
return Ok(Vec::new());
}
let mut bundles = Vec::new();
for entry in fs::read_dir(&root).map_err(|err| format!("failed to read artifacts: {err}"))? {
let entry = entry.map_err(|err| format!("failed to read artifact entry: {err}"))?;
let path = entry.path();
if !path.is_dir() {
continue;
}
let mut files = Vec::new();
for file in
fs::read_dir(&path).map_err(|err| format!("failed to read artifact bundle: {err}"))?
{
let file = file.map_err(|err| format!("failed to read artifact file: {err}"))?;
if file.path().is_file() {
files.push(file.file_name().to_string_lossy().to_string());
}
}
files.sort();
let modified_millis = entry
.metadata()
.ok()
.and_then(|metadata| metadata.modified().ok())
.and_then(system_time_millis);
bundles.push(ArtifactBundle {
name: entry.file_name().to_string_lossy().to_string(),
path: path.display().to_string(),
summary_path: existing_path(&path, "epiphany-dogfood-summary.json"),
final_status_path: existing_path(&path, "epiphany-final-status.json"),
comparison_path: existing_path(&path, "comparison.md"),
files,
modified_millis,
});
}
bundles.sort_by(|a, b| b.modified_millis.cmp(&a.modified_millis));
Ok(bundles)
}
fn existing_path(root: &Path, name: &str) -> Option<String> {
let path = root.join(name);
path.exists().then(|| path.display().to_string())
}
fn repo_root() -> Result<PathBuf, String> {
let manifest_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR"));
manifest_dir
.parent()
.and_then(Path::parent)
.and_then(Path::parent)
.map(Path::to_path_buf)
.ok_or_else(|| "failed to derive repository root from CARGO_MANIFEST_DIR".to_string())
}
fn find_python() -> Result<PathBuf, String> {
if let Ok(value) = std::env::var("EPIPHANY_PYTHON") {
let path = PathBuf::from(value);
if path.exists() {
return Ok(path);
}
}
let bundled = PathBuf::from(
r"C:\Users\Meta\.cache\codex-runtimes\codex-primary-runtime\dependencies\python\python.exe",
);
if bundled.exists() {
return Ok(bundled);
}
Ok(PathBuf::from("python"))
}
fn unix_millis() -> u128 {
system_time_millis(SystemTime::now()).unwrap_or_default()
}
fn system_time_millis(value: SystemTime) -> Option<u128> {
value
.duration_since(UNIX_EPOCH)
.ok()
.map(|duration| duration.as_millis())
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![
load_operator_snapshot,
run_operator_action
])
.run(tauri::generate_context!())
.expect("error while running Epiphany operator");
}