Add Epiphany GUI operator shell
This commit is contained in:
parent
d3840fad2e
commit
a2c44a52a3
23 changed files with 8188 additions and 17 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -2,5 +2,10 @@ __pycache__/
|
|||
*.pyc
|
||||
epiphany-core/target/
|
||||
epiphany-core/Cargo.lock
|
||||
apps/epiphany-gui/node_modules/
|
||||
apps/epiphany-gui/dist/
|
||||
apps/epiphany-gui/src-tauri/gen/
|
||||
apps/epiphany-gui/src-tauri/target/
|
||||
.epiphany-gui/
|
||||
.epiphany-smoke/
|
||||
.epiphany-dogfood/
|
||||
|
|
|
|||
30
apps/epiphany-gui/README.md
Normal file
30
apps/epiphany-gui/README.md
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
# Epiphany GUI
|
||||
|
||||
Local Tauri v2 + React operator console for the Epiphany MVP.
|
||||
|
||||
This app is not a source of truth. It reflects the existing app-server and
|
||||
artifact surfaces so the operator can inspect coordinator, CRRC, role, job, and
|
||||
artifact state without working through a terminal.
|
||||
|
||||
Current slice:
|
||||
|
||||
- read-only operator console
|
||||
- calls `tools/epiphany_mvp_status.py --json` through a Tauri command
|
||||
- renders coordinator recommendation, pressure, CRRC/reorientation status, role
|
||||
lanes, role findings, jobs, and artifact bundles
|
||||
|
||||
Run from this directory:
|
||||
|
||||
```powershell
|
||||
npm install
|
||||
npm run dev
|
||||
npm run tauri dev
|
||||
```
|
||||
|
||||
The status bridge expects the local app-server binary used by the existing
|
||||
smoke tools. Build it from `vendor/codex/codex-rs` if needed:
|
||||
|
||||
```powershell
|
||||
$env:CARGO_TARGET_DIR='C:\Users\Meta\.cargo-target-codex'
|
||||
cargo build -p codex-app-server
|
||||
```
|
||||
12
apps/epiphany-gui/index.html
Normal file
12
apps/epiphany-gui/index.html
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Epiphany Operator</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
2139
apps/epiphany-gui/package-lock.json
generated
Normal file
2139
apps/epiphany-gui/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
26
apps/epiphany-gui/package.json
Normal file
26
apps/epiphany-gui/package.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "epiphany-gui",
|
||||
"private": true,
|
||||
"version": "0.1.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite --host 127.0.0.1 --port 1420",
|
||||
"build": "tsc && vite build",
|
||||
"preview": "vite preview --host 127.0.0.1 --port 1421",
|
||||
"tauri": "tauri"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.0.0",
|
||||
"lucide-react": "^0.468.0",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@tauri-apps/cli": "^2.0.0",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"typescript": "^5.6.3",
|
||||
"vite": "^6.0.1"
|
||||
}
|
||||
}
|
||||
4967
apps/epiphany-gui/src-tauri/Cargo.lock
generated
Normal file
4967
apps/epiphany-gui/src-tauri/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
18
apps/epiphany-gui/src-tauri/Cargo.toml
Normal file
18
apps/epiphany-gui/src-tauri/Cargo.toml
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "epiphany-gui"
|
||||
version = "0.1.0"
|
||||
description = "Epiphany local operator console"
|
||||
authors = ["GameCult"]
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "epiphany_gui_lib"
|
||||
crate-type = ["staticlib", "cdylib", "rlib"]
|
||||
|
||||
[build-dependencies]
|
||||
tauri-build = { version = "2", features = [] }
|
||||
|
||||
[dependencies]
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = "1"
|
||||
tauri = { version = "2", features = [] }
|
||||
3
apps/epiphany-gui/src-tauri/build.rs
Normal file
3
apps/epiphany-gui/src-tauri/build.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
tauri_build::build();
|
||||
}
|
||||
7
apps/epiphany-gui/src-tauri/capabilities/default.json
Normal file
7
apps/epiphany-gui/src-tauri/capabilities/default.json
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"$schema": "../gen/schemas/desktop-schema.json",
|
||||
"identifier": "default",
|
||||
"description": "Default permissions for the Epiphany operator console.",
|
||||
"windows": ["main"],
|
||||
"permissions": ["core:default"]
|
||||
}
|
||||
BIN
apps/epiphany-gui/src-tauri/icons/icon.ico
Normal file
BIN
apps/epiphany-gui/src-tauri/icons/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.2 KiB |
207
apps/epiphany-gui/src-tauri/src/lib.rs
Normal file
207
apps/epiphany-gui/src-tauri/src/lib.rs
Normal file
|
|
@ -0,0 +1,207 @@
|
|||
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(Debug, 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 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,
|
||||
})
|
||||
}
|
||||
|
||||
impl Default for StatusRequest {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
thread_id: None,
|
||||
cwd: None,
|
||||
codex_home: None,
|
||||
app_server: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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 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(tauri::generate_context!())
|
||||
.expect("error while running Epiphany operator");
|
||||
}
|
||||
3
apps/epiphany-gui/src-tauri/src/main.rs
Normal file
3
apps/epiphany-gui/src-tauri/src/main.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
epiphany_gui_lib::run();
|
||||
}
|
||||
30
apps/epiphany-gui/src-tauri/tauri.conf.json
Normal file
30
apps/epiphany-gui/src-tauri/tauri.conf.json
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
{
|
||||
"$schema": "https://schema.tauri.app/config/2",
|
||||
"productName": "Epiphany",
|
||||
"version": "0.1.0",
|
||||
"identifier": "dev.epiphany.operator",
|
||||
"build": {
|
||||
"beforeDevCommand": "npm run dev",
|
||||
"devUrl": "http://127.0.0.1:1420",
|
||||
"beforeBuildCommand": "npm run build",
|
||||
"frontendDist": "../dist"
|
||||
},
|
||||
"app": {
|
||||
"windows": [
|
||||
{
|
||||
"title": "Epiphany Operator",
|
||||
"width": 1280,
|
||||
"height": 860,
|
||||
"minWidth": 960,
|
||||
"minHeight": 640
|
||||
}
|
||||
],
|
||||
"security": {
|
||||
"csp": null
|
||||
}
|
||||
},
|
||||
"bundle": {
|
||||
"active": false,
|
||||
"targets": "all"
|
||||
}
|
||||
}
|
||||
253
apps/epiphany-gui/src/App.tsx
Normal file
253
apps/epiphany-gui/src/App.tsx
Normal file
|
|
@ -0,0 +1,253 @@
|
|||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { AlertTriangle, BriefcaseBusiness, ClipboardCheck, Database, FileText, GitBranch, RefreshCw } from "lucide-react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import type { ArtifactBundle, OperatorSnapshot, StatusRequest } from "./types";
|
||||
|
||||
const roleOrder = ["implementation", "modeling", "verification", "reorientation"];
|
||||
|
||||
function text(value: unknown, fallback = "none"): string {
|
||||
if (value === null || value === undefined || value === "") {
|
||||
return fallback;
|
||||
}
|
||||
if (typeof value === "string") {
|
||||
return value;
|
||||
}
|
||||
if (typeof value === "number" || typeof value === "boolean") {
|
||||
return String(value);
|
||||
}
|
||||
return fallback;
|
||||
}
|
||||
|
||||
function listText(value: unknown, fallback = "none"): string {
|
||||
return Array.isArray(value) && value.length > 0 ? value.map(String).join(", ") : fallback;
|
||||
}
|
||||
|
||||
function statusClass(value: unknown): string {
|
||||
const lower = text(value).toLowerCase();
|
||||
if (lower.includes("blocked") || lower.includes("critical") || lower.includes("regather")) return "danger";
|
||||
if (lower.includes("needed") || lower.includes("review") || lower.includes("prepare") || lower.includes("high")) return "warn";
|
||||
if (lower.includes("completed") || lower.includes("ready") || lower.includes("continue") || lower.includes("pass")) return "ok";
|
||||
return "neutral";
|
||||
}
|
||||
|
||||
function useSnapshot() {
|
||||
const [snapshot, setSnapshot] = useState<OperatorSnapshot | null>(null);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [request, setRequest] = useState<StatusRequest>({});
|
||||
|
||||
async function refresh(nextRequest = request) {
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
try {
|
||||
const result = await invoke<OperatorSnapshot>("load_operator_snapshot", { request: nextRequest });
|
||||
setSnapshot(result);
|
||||
} catch (err) {
|
||||
setError(err instanceof Error ? err.message : String(err));
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
void refresh({});
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, []);
|
||||
|
||||
return { snapshot, loading, error, request, setRequest, refresh };
|
||||
}
|
||||
|
||||
export function App() {
|
||||
const { snapshot, loading, error, request, setRequest, refresh } = useSnapshot();
|
||||
const status = snapshot?.status;
|
||||
const scene = status?.scene?.scene ?? {};
|
||||
const pressure = status?.pressure?.pressure ?? {};
|
||||
const reorient = status?.reorient?.decision ?? {};
|
||||
const crrc = status?.crrc?.recommendation ?? {};
|
||||
const coordinator = status?.coordinator ?? {};
|
||||
const roles = useMemo(() => {
|
||||
const lanes = status?.roles?.roles;
|
||||
if (!Array.isArray(lanes)) return [];
|
||||
return [...lanes].sort((a, b) => roleOrder.indexOf(text(a.id)) - roleOrder.indexOf(text(b.id)));
|
||||
}, [status]);
|
||||
const jobs: any[] = Array.isArray(status?.jobs?.jobs) ? status.jobs.jobs : [];
|
||||
const roleResults = status?.roleResults ?? {};
|
||||
const reorientResult = status?.reorientResult ?? {};
|
||||
|
||||
return (
|
||||
<main className="shell">
|
||||
<header className="topbar">
|
||||
<div>
|
||||
<p className="eyebrow">Epiphany MVP</p>
|
||||
<h1>Operator Console</h1>
|
||||
</div>
|
||||
<button className="primaryButton" onClick={() => void refresh()} disabled={loading} title="Refresh status">
|
||||
<RefreshCw size={16} aria-hidden="true" />
|
||||
{loading ? "Refreshing" : "Refresh"}
|
||||
</button>
|
||||
</header>
|
||||
|
||||
<section className="controls" aria-label="Connection">
|
||||
<label>
|
||||
Thread ID
|
||||
<input
|
||||
placeholder="start ephemeral status thread"
|
||||
value={request.threadId ?? ""}
|
||||
onChange={(event) => setRequest({ ...request, threadId: event.target.value || undefined })}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Workspace
|
||||
<input
|
||||
placeholder={snapshot?.repoRoot ?? "repo root"}
|
||||
value={request.cwd ?? ""}
|
||||
onChange={(event) => setRequest({ ...request, cwd: event.target.value || undefined })}
|
||||
/>
|
||||
</label>
|
||||
</section>
|
||||
|
||||
{error && (
|
||||
<section className="notice dangerNotice" role="alert">
|
||||
<AlertTriangle size={18} aria-hidden="true" />
|
||||
<span>{error}</span>
|
||||
</section>
|
||||
)}
|
||||
|
||||
<section className="statusGrid" aria-label="Coordinator summary">
|
||||
<Panel title="Recommendation" icon={<ClipboardCheck size={18} />}>
|
||||
<div className={`actionBanner ${statusClass(coordinator.action ?? crrc.action)}`}>
|
||||
<strong>{text(coordinator.action ?? crrc.action, "unknown")}</strong>
|
||||
<span>{text(coordinator.targetRole ?? crrc.recommendedSceneAction)}</span>
|
||||
</div>
|
||||
<p className="reason">{text(coordinator.reason ?? crrc.reason, "No recommendation loaded yet.")}</p>
|
||||
<dl className="facts">
|
||||
<div><dt>Thread</dt><dd>{text(status?.threadId)}</dd></div>
|
||||
<div><dt>State</dt><dd>{text(scene.stateStatus)} rev {text(scene.revision)}</dd></div>
|
||||
<div><dt>Requires review</dt><dd>{text(coordinator.requiresReview)}</dd></div>
|
||||
</dl>
|
||||
</Panel>
|
||||
|
||||
<Panel title="Continuity" icon={<GitBranch size={18} />}>
|
||||
<dl className="facts">
|
||||
<div><dt>Pressure</dt><dd><Pill tone={statusClass(pressure.level)}>{text(pressure.level)}</Pill></dd></div>
|
||||
<div><dt>Prepare compaction</dt><dd>{text(pressure.shouldPrepareCompaction)}</dd></div>
|
||||
<div><dt>Reorient</dt><dd><Pill tone={statusClass(reorient.action)}>{text(reorient.action)}</Pill></dd></div>
|
||||
<div><dt>Reasons</dt><dd>{listText(reorient.reasons)}</dd></div>
|
||||
</dl>
|
||||
<p className="reason">{text(reorient.nextAction)}</p>
|
||||
</Panel>
|
||||
</section>
|
||||
|
||||
<section className="sectionBand">
|
||||
<SectionHeader title="Role Lanes" icon={<BriefcaseBusiness size={18} />} />
|
||||
<div className="cardGrid">
|
||||
{roles.map((role) => (
|
||||
<article className="laneCard" key={text(role.id)}>
|
||||
<div className="cardTopline">
|
||||
<h3>{text(role.title)}</h3>
|
||||
<Pill tone={statusClass(role.status)}>{text(role.status)}</Pill>
|
||||
</div>
|
||||
<p>{text(role.note)}</p>
|
||||
<span className="owner">{text(role.ownerRole)}</span>
|
||||
</article>
|
||||
))}
|
||||
{roles.length === 0 && <EmptyState label="No role lanes loaded." />}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="sectionBand twoColumn">
|
||||
<div>
|
||||
<SectionHeader title="Findings" icon={<FileText size={18} />} />
|
||||
<div className="stack">
|
||||
<Finding title="Modeling / Checkpoint" result={roleResults.modeling} />
|
||||
<Finding title="Verification / Review" result={roleResults.verification} />
|
||||
<Finding title="Reorientation" result={reorientResult} findingKey="finding" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<SectionHeader title="Jobs" icon={<Database size={18} />} />
|
||||
<div className="stack">
|
||||
{jobs.map((job) => (
|
||||
<article className="jobRow" key={text(job.id)}>
|
||||
<div>
|
||||
<strong>{text(job.id)}</strong>
|
||||
<span>{text(job.kind)} · {text(job.ownerRole)}</span>
|
||||
</div>
|
||||
<Pill tone={statusClass(job.status)}>{text(job.status)}</Pill>
|
||||
</article>
|
||||
))}
|
||||
{jobs.length === 0 && <EmptyState label="No jobs loaded." />}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section className="sectionBand">
|
||||
<SectionHeader title="Artifact Bundles" icon={<FileText size={18} />} />
|
||||
<div className="artifactTable" role="table" aria-label="Artifact bundles">
|
||||
<div className="artifactHeader" role="row">
|
||||
<span>Name</span>
|
||||
<span>Files</span>
|
||||
<span>Path</span>
|
||||
</div>
|
||||
{(snapshot?.artifacts ?? []).map((artifact: ArtifactBundle) => (
|
||||
<div className="artifactRow" role="row" key={artifact.path}>
|
||||
<strong>{artifact.name}</strong>
|
||||
<span>{artifact.files.length}</span>
|
||||
<code title={artifact.path}>{artifact.path}</code>
|
||||
</div>
|
||||
))}
|
||||
{(snapshot?.artifacts ?? []).length === 0 && <EmptyState label="No dogfood artifact bundles found." />}
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
function Panel({ title, icon, children }: { title: string; icon: React.ReactNode; children: React.ReactNode }) {
|
||||
return (
|
||||
<section className="panel">
|
||||
<SectionHeader title={title} icon={icon} />
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
function SectionHeader({ title, icon }: { title: string; icon: React.ReactNode }) {
|
||||
return (
|
||||
<div className="sectionHeader">
|
||||
{icon}
|
||||
<h2>{title}</h2>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function Pill({ tone, children }: { tone: string; children: React.ReactNode }) {
|
||||
return <span className={`pill ${tone}`}>{children}</span>;
|
||||
}
|
||||
|
||||
function Finding({ title, result, findingKey = "finding" }: { title: string; result: any; findingKey?: string }) {
|
||||
const finding = result?.[findingKey];
|
||||
return (
|
||||
<article className="findingCard">
|
||||
<div className="cardTopline">
|
||||
<h3>{title}</h3>
|
||||
<Pill tone={statusClass(result?.status)}>{text(result?.status)}</Pill>
|
||||
</div>
|
||||
{finding ? (
|
||||
<>
|
||||
<p>{text(finding.summary ?? finding.nextSafeMove ?? finding.mode ?? finding.verdict)}</p>
|
||||
<dl className="facts compact">
|
||||
<div><dt>Verdict</dt><dd>{text(finding.verdict ?? finding.mode)}</dd></div>
|
||||
<div><dt>Next</dt><dd>{text(finding.nextSafeMove)}</dd></div>
|
||||
</dl>
|
||||
</>
|
||||
) : (
|
||||
<p>{text(result?.note, "No finding available.")}</p>
|
||||
)}
|
||||
</article>
|
||||
);
|
||||
}
|
||||
|
||||
function EmptyState({ label }: { label: string }) {
|
||||
return <p className="emptyState">{label}</p>;
|
||||
}
|
||||
10
apps/epiphany-gui/src/main.tsx
Normal file
10
apps/epiphany-gui/src/main.tsx
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
import React from "react";
|
||||
import ReactDOM from "react-dom/client";
|
||||
import { App } from "./App";
|
||||
import "./styles.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
|
||||
<React.StrictMode>
|
||||
<App />
|
||||
</React.StrictMode>,
|
||||
);
|
||||
397
apps/epiphany-gui/src/styles.css
Normal file
397
apps/epiphany-gui/src/styles.css
Normal file
|
|
@ -0,0 +1,397 @@
|
|||
:root {
|
||||
color: #172018;
|
||||
background: #f7f8f5;
|
||||
font-family:
|
||||
Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
|
||||
sans-serif;
|
||||
font-synthesis: none;
|
||||
text-rendering: optimizeLegibility;
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
min-width: 320px;
|
||||
min-height: 100vh;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(240, 243, 237, 0.88), rgba(248, 249, 246, 1)),
|
||||
#f7f8f5;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
.shell {
|
||||
width: min(1440px, calc(100vw - 32px));
|
||||
margin: 0 auto;
|
||||
padding: 24px 0 36px;
|
||||
}
|
||||
|
||||
.topbar,
|
||||
.controls,
|
||||
.statusGrid,
|
||||
.twoColumn {
|
||||
display: grid;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
grid-template-columns: 1fr auto;
|
||||
align-items: center;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.eyebrow {
|
||||
margin: 0 0 4px;
|
||||
color: #5f6b61;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(1.6rem, 3vw, 2.35rem);
|
||||
line-height: 1.08;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-size: 0.94rem;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 0.92rem;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.primaryButton {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
min-height: 38px;
|
||||
border: 1px solid #244d3a;
|
||||
border-radius: 6px;
|
||||
padding: 0 14px;
|
||||
color: #f8fff8;
|
||||
background: #244d3a;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.primaryButton:disabled {
|
||||
cursor: wait;
|
||||
opacity: 0.68;
|
||||
}
|
||||
|
||||
.controls {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
label {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
color: #465047;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
min-height: 38px;
|
||||
border: 1px solid #ccd4ca;
|
||||
border-radius: 6px;
|
||||
padding: 0 11px;
|
||||
color: #172018;
|
||||
background: #ffffff;
|
||||
}
|
||||
|
||||
.notice,
|
||||
.panel,
|
||||
.laneCard,
|
||||
.findingCard,
|
||||
.jobRow,
|
||||
.artifactTable {
|
||||
border: 1px solid #d9ded5;
|
||||
border-radius: 8px;
|
||||
background: #ffffff;
|
||||
box-shadow: 0 1px 2px rgba(23, 32, 24, 0.04);
|
||||
}
|
||||
|
||||
.notice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
margin-bottom: 16px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.dangerNotice {
|
||||
border-color: #d9b1a4;
|
||||
color: #6b2418;
|
||||
background: #fff7f4;
|
||||
}
|
||||
|
||||
.statusGrid {
|
||||
grid-template-columns: minmax(0, 1.35fr) minmax(320px, 0.65fr);
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
|
||||
.panel,
|
||||
.sectionBand {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.sectionBand {
|
||||
margin-top: 18px;
|
||||
}
|
||||
|
||||
.sectionHeader {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
min-height: 24px;
|
||||
margin-bottom: 12px;
|
||||
color: #263229;
|
||||
}
|
||||
|
||||
.actionBanner {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
min-height: 46px;
|
||||
border-radius: 8px;
|
||||
padding: 10px 12px;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.actionBanner strong {
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.actionBanner span {
|
||||
color: rgba(23, 32, 24, 0.68);
|
||||
font-size: 0.84rem;
|
||||
}
|
||||
|
||||
.ok {
|
||||
background: #e7f3eb;
|
||||
color: #1d5d39;
|
||||
}
|
||||
|
||||
.warn {
|
||||
background: #fff4d9;
|
||||
color: #76520b;
|
||||
}
|
||||
|
||||
.danger {
|
||||
background: #fde8e1;
|
||||
color: #8d2d1c;
|
||||
}
|
||||
|
||||
.neutral {
|
||||
background: #edf0f2;
|
||||
color: #39454b;
|
||||
}
|
||||
|
||||
.reason {
|
||||
color: #3e493f;
|
||||
line-height: 1.45;
|
||||
}
|
||||
|
||||
.facts {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
margin: 14px 0 0;
|
||||
}
|
||||
|
||||
.facts.compact {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.facts div {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
color: #667168;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin: 4px 0 0;
|
||||
overflow-wrap: anywhere;
|
||||
color: #172018;
|
||||
}
|
||||
|
||||
.pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
min-height: 24px;
|
||||
max-width: 100%;
|
||||
border-radius: 999px;
|
||||
padding: 2px 9px;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.cardGrid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(180px, 1fr));
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.laneCard,
|
||||
.findingCard,
|
||||
.jobRow {
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.laneCard p,
|
||||
.findingCard p {
|
||||
min-height: 42px;
|
||||
margin-top: 10px;
|
||||
color: #435045;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.42;
|
||||
}
|
||||
|
||||
.owner {
|
||||
display: block;
|
||||
margin-top: 12px;
|
||||
color: #68726a;
|
||||
font-size: 0.78rem;
|
||||
}
|
||||
|
||||
.cardTopline,
|
||||
.jobRow,
|
||||
.artifactHeader,
|
||||
.artifactRow {
|
||||
display: grid;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.cardTopline {
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
}
|
||||
|
||||
.twoColumn {
|
||||
grid-template-columns: minmax(0, 1fr) minmax(360px, 0.86fr);
|
||||
}
|
||||
|
||||
.stack {
|
||||
display: grid;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.jobRow {
|
||||
grid-template-columns: minmax(0, 1fr) auto;
|
||||
}
|
||||
|
||||
.jobRow div {
|
||||
display: grid;
|
||||
gap: 3px;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.jobRow span {
|
||||
color: #657069;
|
||||
font-size: 0.82rem;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
|
||||
.artifactTable {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.artifactHeader,
|
||||
.artifactRow {
|
||||
grid-template-columns: minmax(180px, 0.6fr) 72px minmax(220px, 1fr);
|
||||
min-height: 42px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.artifactHeader {
|
||||
color: #606a62;
|
||||
background: #f0f3ef;
|
||||
font-size: 0.74rem;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.artifactRow + .artifactRow {
|
||||
border-top: 1px solid #edf0eb;
|
||||
}
|
||||
|
||||
code {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
color: #36503f;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.emptyState {
|
||||
border: 1px dashed #cbd3c8;
|
||||
border-radius: 8px;
|
||||
padding: 14px;
|
||||
color: #69736b;
|
||||
background: #fbfcfa;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.statusGrid,
|
||||
.twoColumn,
|
||||
.controls {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.cardGrid {
|
||||
grid-template-columns: repeat(2, minmax(180px, 1fr));
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.shell {
|
||||
width: min(100vw - 20px, 1440px);
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
.topbar {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.cardGrid,
|
||||
.facts,
|
||||
.facts.compact,
|
||||
.artifactHeader,
|
||||
.artifactRow {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.artifactHeader {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.artifactRow {
|
||||
gap: 4px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
}
|
||||
23
apps/epiphany-gui/src/types.ts
Normal file
23
apps/epiphany-gui/src/types.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
export interface StatusRequest {
|
||||
threadId?: string;
|
||||
cwd?: string;
|
||||
codexHome?: string;
|
||||
appServer?: string;
|
||||
}
|
||||
|
||||
export interface ArtifactBundle {
|
||||
name: string;
|
||||
path: string;
|
||||
files: string[];
|
||||
summaryPath?: string;
|
||||
finalStatusPath?: string;
|
||||
comparisonPath?: string;
|
||||
modifiedMillis?: number;
|
||||
}
|
||||
|
||||
export interface OperatorSnapshot {
|
||||
generatedAt: string;
|
||||
repoRoot: string;
|
||||
status: any;
|
||||
artifacts: ArtifactBundle[];
|
||||
}
|
||||
21
apps/epiphany-gui/tsconfig.json
Normal file
21
apps/epiphany-gui/tsconfig.json
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["DOM", "DOM.Iterable", "ES2020"],
|
||||
"allowJs": false,
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Node",
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx"
|
||||
},
|
||||
"include": ["src"],
|
||||
"references": []
|
||||
}
|
||||
13
apps/epiphany-gui/vite.config.ts
Normal file
13
apps/epiphany-gui/vite.config.ts
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
|
||||
export default defineConfig({
|
||||
plugins: [react()],
|
||||
clearScreen: false,
|
||||
server: {
|
||||
host: "127.0.0.1",
|
||||
port: 1420,
|
||||
strictPort: true,
|
||||
},
|
||||
envPrefix: ["VITE_", "TAURI_"],
|
||||
});
|
||||
|
|
@ -83,6 +83,7 @@ The landed machine now has:
|
|||
- first auditable Phase 6 dogfood runner through `tools/epiphany_mvp_dogfood.py`, producing local status snapshots, raw app-server transcript, final status artifacts, truthful vanilla-reference output, and comparison notes
|
||||
- first auditable Phase 6 fixed-lane coordinator runner through `tools/epiphany_mvp_coordinator.py`, producing coordinator summary, JSONL steps, rendered snapshots, transcript, stderr, and final next-action artifacts while keeping semantic findings review-gated by default
|
||||
- first auditable Phase 6 live-specialist runner through `tools/epiphany_mvp_live_specialist.py`, proving `roleLaunch -> agent_jobs worker -> report_agent_job_result -> roleResult` without manual backend completion
|
||||
- first Phase 6 GUI operator shell under `apps/epiphany-gui`, a Tauri v2 + React read-only console over the existing status bridge and dogfood artifacts
|
||||
- live Phase 6 reorientation app-server smoke coverage in `tools/epiphany_phase6_reorient_smoke.py`
|
||||
- live Phase 6 reorient-launch app-server smoke coverage in `tools/epiphany_phase6_reorient_launch_smoke.py`
|
||||
- live Phase 6 MVP status smoke coverage in `tools/epiphany_mvp_status_smoke.py`
|
||||
|
|
@ -276,9 +277,10 @@ Initial shape:
|
|||
|
||||
Implementation slices:
|
||||
|
||||
1. **Read-only shell**: scaffold Tauri/React, connect to a running app-server,
|
||||
render the same data as `tools/epiphany_mvp_status.py`, and provide artifact
|
||||
file links.
|
||||
1. **Read-only shell**: scaffold Tauri/React, connect to app-server through the
|
||||
existing MVP status bridge, render the same data as
|
||||
`tools/epiphany_mvp_status.py`, and provide artifact bundle links. This
|
||||
slice is landed under `apps/epiphany-gui`.
|
||||
2. **Bounded operator actions**: add buttons for the existing coordinator,
|
||||
roleLaunch, roleResult, reorientLaunch, reorientResult, and reorientAccept
|
||||
flows, preserving review gates.
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ Do not trust this file for the exact live HEAD. Always check git.
|
|||
- Pre-compaction checkpoint intervention is now landed. On token-count events for loaded Epiphany threads, when pressure reaches the existing `shouldPrepareCompaction` threshold, the harness steers the active turn once with a CRRC checkpoint directive so the agent banks working context before compaction/reorientation. This is still bounded steering, not automatic semantic acceptance, a broad scheduler, or implementation continuation.
|
||||
- `tools/epiphany_mvp_dogfood.py` is the first auditable dogfood runner. It drives a bounded state/role/CRRC/reorientation loop and writes local artifacts under `.epiphany-dogfood/mvp-loop`, including raw app-server transcript, rendered snapshots, final status, vanilla-reference prompt/output, and comparison notes. The runner now writes truthful vanilla/comparison artifacts whether the optional vanilla reference is skipped, fails, or completes.
|
||||
- `tools/epiphany_mvp_live_specialist.py` is the first auditable live-specialist runner. It launches a real role specialist through `thread/epiphany/roleLaunch`, lets the spawned worker report through `report_agent_job_result`, reads it back through `thread/epiphany/roleResult`, and writes local artifacts under `.epiphany-dogfood/live-specialist`.
|
||||
- The MVP GUI plan is now Tauri v2 + React under `apps/epiphany-gui`, talking to existing app-server APIs. It is an operator console over typed state and artifact surfaces, not a new source of truth.
|
||||
- The first MVP GUI shell now exists under `apps/epiphany-gui`. It is a Tauri v2 + React read-only operator console over the existing status bridge and dogfood artifact surfaces, not a new source of truth.
|
||||
- Internal `agent_job:` workers now get the reporting tool independent of the user-facing CSV spawn feature, and ephemeral worker sessions can initialize the sqlite state runtime on demand so specialist reports land in the shared backend.
|
||||
- Durable in-flight investigation checkpointing is now landed in authoritative typed state, writable through `thread/epiphany/update` or accepted `thread/epiphany/promote`, rendered into the prompt, and reflected through scene/context.
|
||||
- The repo is an Epiphany fork of Codex, not a Codex preset.
|
||||
|
|
@ -104,6 +104,7 @@ The current spine:
|
|||
- live MVP coordinator smoke through `tools/epiphany_mvp_coordinator_smoke.py`
|
||||
- live job-control app-server smoke through `tools/epiphany_phase6_job_control_smoke.py`
|
||||
- live specialist MVP pass through `tools/epiphany_mvp_live_specialist.py`
|
||||
- Tauri v2 + React read-only GUI operator shell under `apps/epiphany-gui`
|
||||
|
||||
The exact current control flow is documented in
|
||||
`notes/epiphany-current-algorithmic-map.md`.
|
||||
|
|
@ -299,14 +300,15 @@ The fixed-lane coordinator MVP is testable, and the first pre-compaction CRRC
|
|||
intervention is now wired. Limited safe-boundary CRRC execution still handles
|
||||
compact and fixed reorient-worker launch actions after a turn ends; the
|
||||
token-count hook now handles the earlier danger zone by steering active loaded
|
||||
turns once when `shouldPrepareCompaction` is reached. The next real move is the
|
||||
first Tauri v2 + React operator console slice over the same status,
|
||||
coordinator, role, reorient, job, and artifact surfaces so the user does not
|
||||
have to operate the machine from a terminal. Start read-only, then add bounded
|
||||
buttons for already-existing authority surfaces. CRRC is not a specialist-agent
|
||||
persona; the reorient-worker it may launch is the specialist. Do not turn the
|
||||
coordinator into a broad hidden dispatcher, arbitrary marketplace, alternate
|
||||
job backend, automatic semantic acceptance path, or GUI-as-source-of-truth.
|
||||
turns once when `shouldPrepareCompaction` is reached. The first Tauri v2 +
|
||||
React read-only operator console is also scaffolded. It renders the same status,
|
||||
coordinator, role, reorient, job, and artifact surfaces through the existing MVP
|
||||
status bridge so the user does not have to operate the machine from a terminal.
|
||||
The next real move is a visual/operator smoke, then bounded buttons for
|
||||
already-existing authority surfaces. CRRC is not a specialist-agent persona;
|
||||
the reorient-worker it may launch is the specialist. Do not turn the coordinator
|
||||
into a broad hidden dispatcher, arbitrary marketplace, alternate job backend,
|
||||
automatic semantic acceptance path, or GUI-as-source-of-truth.
|
||||
|
||||
Live `thread/epiphany/scene`, `thread/epiphany/jobs`, `thread/epiphany/roles`,
|
||||
`thread/epiphany/freshness`, `thread/epiphany/context`,
|
||||
|
|
|
|||
|
|
@ -65,3 +65,4 @@
|
|||
{"ts": "2026-04-29T15:15:06+01:00", "type": "implementation", "status": "ok", "note": "Landed the smallest pre-compaction checkpoint intervention: TokenCount handling maps pressure through the existing gauge and, for loaded Epiphany threads at shouldPrepareCompaction, steers the active turn once with a bounded CRRC directive to bank checkpoint/scratch/map/evidence before compaction or reorientation. It does not mutate state directly, accept findings, launch arbitrary specialists, or continue implementation. Focused app-server tests plus coordinator/status/reorient/role smokes passed; a stale scene expected-actions assertion was corrected to include the already-landed coordinator action."}
|
||||
{"ts": "2026-04-29T14:35:07+00:00", "type": "implementation", "status": "ok", "note": "Self-dogfood exposed an artifact-honesty blocker in tools/epiphany_mvp_dogfood.py: the manifest listed vanilla comparison artifacts that were not written. The runner now writes truthful vanilla-reference and comparison artifacts for skipped, failed, or completed reference runs; an explicit --run-vanilla-reference pass completed with a persisted vanilla transcript under .epiphany-dogfood/mvp-loop-selfdogfood-vanilla2-20260429."}
|
||||
{"ts": "2026-04-29T14:55:37+00:00", "type": "design", "status": "ok", "note": "Chose the MVP GUI path: scaffold a local Tauri v2 + React operator console over existing app-server status/coordinator/role/reorient/job/artifact surfaces. The GUI reflects and steers typed Epiphany state but does not become a source of truth; first slice should be read-only, with bounded action buttons added only for already-existing authority surfaces after rendering is proved."}
|
||||
{"ts": "2026-04-29T15:18:49+00:00", "type": "implementation", "status": "ok", "note": "Landed the first MVP GUI shell under apps/epiphany-gui: Tauri v2 + React read-only operator console invoking the existing epiphany_mvp_status.py app-server bridge and listing dogfood artifact bundles. Verified npm install, npm run build, cargo check with CARGO_TARGET_DIR, tauri info, tauri build --debug --no-bundle, and a live status-bridge JSON check."}
|
||||
|
|
|
|||
|
|
@ -12,9 +12,9 @@ objective:
|
|||
explicit typed models of the codebase, active goal, evidence, and change
|
||||
machinery they are working on.
|
||||
current: >
|
||||
Turn the landed Phase 6 coordinator/status/dogfood artifact surfaces into
|
||||
the smallest local Tauri v2 + React operator GUI, while continuing
|
||||
agent-run dogfood of CRRC and pre-compaction behavior.
|
||||
Harden the first local Tauri v2 + React operator GUI over the landed Phase
|
||||
6 coordinator/status/dogfood artifact surfaces, while continuing agent-run
|
||||
dogfood of CRRC and pre-compaction behavior.
|
||||
|
||||
constraints:
|
||||
- No frontier-scale training budget.
|
||||
|
|
@ -76,6 +76,7 @@ accepted_design:
|
|||
- The harness now has an in-flight investigation checkpoint packet in durable typed state; automatic CRRC still needs runtime logic that reads the packet and decides whether to resume or force reorientation and re-gathering after compaction.
|
||||
- GUI/client work must reflect or steer typed state rather than manufacture a second source of truth.
|
||||
- The MVP GUI target is a local Tauri v2 + React operator console over existing app-server APIs. Tauri owns windowing and local lifecycle; React renders the operator surface; app-server and typed Epiphany state remain authoritative.
|
||||
- `apps/epiphany-gui` is the first read-only GUI shell: a Tauri v2 + React app that invokes the existing MVP status bridge, renders coordinator/CRRC/pressure/reorient/role/job/finding status, and lists dogfood artifact bundles without writing Epiphany state.
|
||||
- Durable `job_bindings` now form a thin Epiphany-owned launcher seam: bindings can carry launcher identity, authority scope, backend kind, and backend job id while still supporting the older runtime-agent-job pointer as a compatibility alias.
|
||||
- `thread/epiphany/jobs` is a read-only job/progress reflection: it derives indexing, remap, verification, and specialist slots from authoritative state and retrieval summaries, and can overlay durable launcher bindings onto live runtime `agent_jobs` snapshots through the current backend adapter when a real owner exists; it is not a scheduler, notification stream, or durable runtime job table.
|
||||
- `thread/epiphany/jobsUpdated` is the first live bound-job progress notification: it translates runtime `agent_job_progress:{json}` background events into changed launcher-bound payloads by resolving durable `job_bindings` against live `agent_jobs` snapshots through the current backend adapter, without polling in a loop, mutating state, or scheduling work.
|
||||
|
|
@ -127,6 +128,7 @@ landed_baseline:
|
|||
- Phase 6 coordinator: read-only `thread/epiphany/coordinator` plus `tools/epiphany_mvp_coordinator.py` and smoke coverage for the fixed-lane MVP loop.
|
||||
- Phase 6 safe-boundary CRRC automation: turn-complete automation limited to coordinator-approved compact and fixed reorient-worker launch actions.
|
||||
- Phase 6 pre-compaction checkpoint intervention: token-count pressure hook that steers active turns once when `shouldPrepareCompaction` is reached so working context is banked before compaction.
|
||||
- Phase 6 GUI operator shell: Tauri v2 + React read-only console under `apps/epiphany-gui` over the existing status bridge and artifact bundles.
|
||||
guardrails:
|
||||
- Use `tools/epiphany_phase5_smoke.py` before modifying update, distill, propose, promote, read-projection, or notification behavior.
|
||||
- Use `tools/epiphany_phase6_scene_smoke.py` before modifying scene projection behavior.
|
||||
|
|
@ -162,5 +164,5 @@ rejected_paths:
|
|||
reason: persistent state that accumulates every local truth becomes memory cruft and weakens rehydration
|
||||
|
||||
current_status:
|
||||
summary: Phase 1 durable state, Phase 2 prompt integration, Phase 3 typed read, Phase 4 retrieval/indexing, Phase 5 update/distill/propose/promote/read/notification, and Phase 6 read-only scene, jobs, roles, freshness, targeted context, pressure, reorient, CRRC, coordinator, reorient launch/result/accept, explicit modeling/checkpoint and verification/review role launch/result templates, MVP status, dogfood runner, coordinator runner, live specialist runner, limited safe-boundary CRRC automation, and pre-compaction checkpoint intervention are landed. Native CRRC can steer active loaded Epiphany turns once at `shouldPrepareCompaction`; turn-complete automation can still compact or launch the fixed reorient-worker when the coordinator says it can auto-run. The latest self-dogfood pass exposed and fixed an artifact-honesty blocker in the MVP dogfood runner: comparison and vanilla-reference artifacts are now actually written and the optional real vanilla run produces a persisted read-back transcript. Implementation remains main-agent work; CRRC remains workflow automation, not a specialist persona.
|
||||
next_action: Scaffold `apps/epiphany-gui` as a Tauri v2 + React local operator console over the existing app-server status/coordinator/role/reorient/artifact surfaces. First slice is read-only: render active thread, coordinator recommendation, pressure/CRRC/reorient status, role lanes, job progress, reviewable findings, and artifact bundle links without becoming a source of truth.
|
||||
summary: Phase 1 durable state, Phase 2 prompt integration, Phase 3 typed read, Phase 4 retrieval/indexing, Phase 5 update/distill/propose/promote/read/notification, and Phase 6 read-only scene, jobs, roles, freshness, targeted context, pressure, reorient, CRRC, coordinator, reorient launch/result/accept, explicit modeling/checkpoint and verification/review role launch/result templates, MVP status, dogfood runner, coordinator runner, live specialist runner, limited safe-boundary CRRC automation, pre-compaction checkpoint intervention, and the first read-only Tauri/React GUI shell are landed. Native CRRC can steer active loaded Epiphany turns once at `shouldPrepareCompaction`; turn-complete automation can still compact or launch the fixed reorient-worker when the coordinator says it can auto-run. Implementation remains main-agent work; CRRC remains workflow automation, not a specialist persona; GUI remains an operator surface over typed state, not a source of truth.
|
||||
next_action: Run a visual/operator smoke for `apps/epiphany-gui`, then add bounded action buttons for already-existing authority surfaces: refresh/status snapshot, coordinator pass, fixed modeling/verification launches, fixed reorient-worker launch when recommended, and explicit reorientAccept after review.
|
||||
|
|
|
|||
Reference in a new issue