Add GUI visual smoke and bounded actions

This commit is contained in:
Meta Vens 2026-04-29 16:50:18 +01:00
commit 21a05368d3
14 changed files with 620 additions and 32 deletions

View file

@ -12,6 +12,8 @@ Current slice:
- 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
- writes explicit status-snapshot and coordinator-plan artifact bundles through
bounded operator buttons
Run from this directory:
@ -21,6 +23,18 @@ npm run dev
npm run tauri dev
```
Visual smoke:
```powershell
npm run smoke:visual
```
The smoke runs the React shell in a browser with the bundled sample operator
snapshot, clicks the bounded operator buttons, and writes:
- `.epiphany-gui/operator-console-smoke-desktop.png`
- `.epiphany-gui/operator-console-smoke-mobile.png`
The status bridge expects the local app-server binary used by the existing
smoke tools. Build it from `vendor/codex/codex-rs` if needed:
@ -28,3 +42,10 @@ smoke tools. Build it from `vendor/codex/codex-rs` if needed:
$env:CARGO_TARGET_DIR='C:\Users\Meta\.cargo-target-codex'
cargo build -p codex-app-server
```
The first bounded buttons are intentionally narrow:
- **Status Snapshot** writes a status JSON/transcript/stderr bundle under
`.epiphany-gui/status-snapshots`.
- **Coordinator Plan** runs `tools/epiphany_mvp_coordinator.py --mode plan` and
writes an auditable bundle under `.epiphany-dogfood`.

View file

@ -18,6 +18,7 @@
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.4",
"playwright": "^1.59.1",
"typescript": "^5.6.3",
"vite": "^6.0.1"
}
@ -1853,6 +1854,53 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/playwright": {
"version": "1.59.1",
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.59.1.tgz",
"integrity": "sha512-C8oWjPR3F81yljW9o5OxcWzfh6avkVwDD2VYdwIGqTkl+OGFISgypqzfu7dOe4QNLL2aqcWBmI3PMtLIK233lw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"playwright-core": "1.59.1"
},
"bin": {
"playwright": "cli.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"fsevents": "2.3.2"
}
},
"node_modules/playwright-core": {
"version": "1.59.1",
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz",
"integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==",
"dev": true,
"license": "Apache-2.0",
"bin": {
"playwright-core": "cli.js"
},
"engines": {
"node": ">=18"
}
},
"node_modules/playwright/node_modules/fsevents": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
"dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true,
"os": [
"darwin"
],
"engines": {
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
}
},
"node_modules/postcss": {
"version": "8.5.12",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.12.tgz",

View file

@ -7,6 +7,7 @@
"dev": "vite --host 127.0.0.1 --port 1420",
"build": "tsc && vite build",
"preview": "vite preview --host 127.0.0.1 --port 1421",
"smoke:visual": "node scripts/visual-smoke.mjs",
"tauri": "tauri"
},
"dependencies": {
@ -20,6 +21,7 @@
"@types/react": "^18.3.12",
"@types/react-dom": "^18.3.1",
"@vitejs/plugin-react": "^4.3.4",
"playwright": "^1.59.1",
"typescript": "^5.6.3",
"vite": "^6.0.1"
}

View file

@ -0,0 +1,164 @@
{
"generatedAt": "1777475847000",
"repoRoot": "E:\\Projects\\EpiphanyAgent",
"status": {
"threadId": "019dd9d1-045b-7f13-b0e1-38ed89b31495",
"scene": {
"scene": {
"availableActions": [
"index",
"retrieve",
"distill",
"context",
"jobs",
"roles",
"coordinator",
"roleLaunch",
"roleResult",
"jobLaunch",
"freshness",
"pressure",
"reorient",
"crrc",
"update"
],
"source": "live",
"stateStatus": "missing"
}
},
"pressure": {
"pressure": {
"basis": "unknown",
"level": "unknown",
"note": "No token usage telemetry has been recorded for this thread yet.",
"shouldPrepareCompaction": false,
"status": "unknown"
}
},
"reorient": {
"decision": {
"action": "regather",
"checkpointStatus": "missing",
"graphStatus": "missing",
"nextAction": "Re-gather source context before editing.",
"note": "No Epiphany state survived, so there is no authoritative checkpoint to resume.",
"pressureLevel": "unknown",
"reasons": ["missingState", "missingCheckpoint"],
"retrievalStatus": "ready",
"watcherStatus": "clean"
}
},
"jobs": {
"jobs": [
{
"id": "retrieval-index",
"kind": "indexing",
"ownerRole": "epiphany-core",
"progressNote": "Retrieval catalog is ready.",
"scope": "E:\\Projects\\EpiphanyAgent",
"status": "idle"
},
{
"blockingReason": "Epiphany state is missing, so there is no graph to remap.",
"id": "graph-remap",
"kind": "remap",
"ownerRole": "epiphany-harness",
"scope": "architecture/dataflow graphs",
"status": "blocked"
},
{
"blockingReason": "Epiphany state is missing, so there are no invariants to verify.",
"id": "verification",
"kind": "verification",
"ownerRole": "epiphany-harness",
"scope": "invariants/evidence",
"status": "blocked"
}
]
},
"roles": {
"roles": [
{
"id": "implementation",
"note": "Wait for CRRC action: RegatherManually.",
"ownerRole": "coding-agent",
"status": "blocked",
"title": "Implementation"
},
{
"id": "modeling",
"note": "Checkpoint missing: Re-gather source context before editing.",
"ownerRole": "epiphany-modeler",
"recommendedAction": "update",
"status": "needed",
"title": "Modeling / Checkpoint"
},
{
"id": "verification",
"note": "Epiphany state is missing, so there are no invariants to verify.",
"ownerRole": "epiphany-verifier",
"status": "blocked",
"title": "Verification / Review"
},
{
"id": "reorientation",
"note": "Regather verdict, result MissingState, pressure Unknown. No authoritative Epiphany state exists; re-gather source context before editing.",
"ownerRole": "epiphany-reorient",
"status": "needed",
"title": "Reorientation"
}
]
},
"roleResults": {
"modeling": {
"bindingId": "modeling-checkpoint-worker",
"note": "No authoritative Epiphany state exists for this thread.",
"roleId": "modeling",
"status": "missingState"
},
"verification": {
"bindingId": "verification-review-worker",
"note": "No authoritative Epiphany state exists for this thread.",
"roleId": "verification",
"status": "missingState"
}
},
"reorientResult": {
"bindingId": "reorient-worker",
"note": "No authoritative Epiphany state exists for this thread.",
"status": "missingState"
},
"crrc": {
"recommendation": {
"action": "regatherManually",
"reason": "No authoritative Epiphany state exists; re-gather source context before editing."
}
},
"coordinator": {
"action": "prepareCheckpoint",
"canAutoRun": false,
"reason": "Authoritative state or investigation checkpoint is missing; prepare a checkpoint before coordination can continue.",
"recommendedSceneAction": "update",
"requiresReview": false,
"targetRole": "modeling"
}
},
"artifacts": [
{
"name": "mvp-loop-selfdogfood-vanilla2-20260429",
"path": "E:\\Projects\\EpiphanyAgent\\.epiphany-dogfood\\mvp-loop-selfdogfood-vanilla2-20260429",
"files": [
"artifact-manifest.json",
"comparison.md",
"epiphany-dogfood-summary.json",
"epiphany-final-status.json",
"epiphany-transcript.jsonl",
"vanilla-reference.md"
],
"summaryPath": "E:\\Projects\\EpiphanyAgent\\.epiphany-dogfood\\mvp-loop-selfdogfood-vanilla2-20260429\\epiphany-dogfood-summary.json",
"finalStatusPath": "E:\\Projects\\EpiphanyAgent\\.epiphany-dogfood\\mvp-loop-selfdogfood-vanilla2-20260429\\epiphany-final-status.json",
"comparisonPath": "E:\\Projects\\EpiphanyAgent\\.epiphany-dogfood\\mvp-loop-selfdogfood-vanilla2-20260429\\comparison.md",
"modifiedMillis": 1777475847000
}
]
}

View file

@ -0,0 +1,112 @@
import { chromium } from "playwright";
import { mkdir } from "node:fs/promises";
import { resolve } from "node:path";
import { createServer } from "vite";
const root = resolve(import.meta.dirname, "..", "..", "..");
const artifactDir = resolve(root, ".epiphany-gui");
const desktopScreenshotPath = resolve(artifactDir, "operator-console-smoke-desktop.png");
const mobileScreenshotPath = resolve(artifactDir, "operator-console-smoke-mobile.png");
const url = "http://127.0.0.1:1420";
await mkdir(artifactDir, { recursive: true });
const server = await createServer({
root: resolve(root, "apps", "epiphany-gui"),
logLevel: "silent",
server: {
host: "127.0.0.1",
port: 1420,
strictPort: true,
},
});
try {
await server.listen();
await waitForServer(url);
const browser = await chromium.launch({
channel: "msedge",
headless: true,
});
await smokeViewport(browser, { width: 1366, height: 900 }, desktopScreenshotPath);
await smokeViewport(browser, { width: 390, height: 844 }, mobileScreenshotPath);
await browser.close();
console.log(
JSON.stringify(
{
ok: true,
screenshots: [desktopScreenshotPath, mobileScreenshotPath],
},
null,
2,
),
);
} finally {
await server.close();
}
async function smokeViewport(browser, viewport, screenshotPath) {
const page = await browser.newPage({ viewport });
await page.goto(url, { waitUntil: "networkidle" });
await page.getByRole("heading", { name: "Operator Console" }).waitFor();
await page.getByText("prepareCheckpoint").waitFor();
await page.getByText("Role Lanes").waitFor();
await page.getByText("Artifact Bundles").waitFor();
if (viewport.width >= 900) {
await page.getByRole("button", { name: "Status Snapshot" }).click();
await page.getByText("statusSnapshot sample completed.").waitFor();
await page.getByRole("button", { name: "Coordinator Plan" }).click();
await page.getByText("coordinatorPlan sample completed.").waitFor();
}
await page.screenshot({ path: screenshotPath, fullPage: true });
const result = await page.evaluate(() => {
const elements = Array.from(document.querySelectorAll("h1, h2, h3, p, dd, code, button, input, .pill"));
const overlaps = [];
for (let index = 0; index < elements.length; index += 1) {
const left = elements[index];
const a = elements[index].getBoundingClientRect();
if (a.width === 0 || a.height === 0) continue;
for (let other = index + 1; other < elements.length; other += 1) {
const right = elements[other];
if (left.contains(right) || right.contains(left)) continue;
const b = elements[other].getBoundingClientRect();
if (b.width === 0 || b.height === 0) continue;
const x = Math.min(a.right, b.right) - Math.max(a.left, b.left);
const y = Math.min(a.bottom, b.bottom) - Math.max(a.top, b.top);
if (x > 2 && y > 2) {
overlaps.push({
left: left.textContent?.trim() || left.getAttribute("placeholder") || left.tagName,
right: right.textContent?.trim() || right.getAttribute("placeholder") || right.tagName,
});
}
}
}
return {
overlaps,
horizontalOverflow: document.documentElement.scrollWidth > document.documentElement.clientWidth + 1,
};
});
await page.close();
if (result.horizontalOverflow) {
throw new Error(`visual smoke found horizontal overflow at ${viewport.width}x${viewport.height}`);
}
if (result.overlaps.length > 0) {
throw new Error(`visual smoke found overlapping text/control boxes: ${JSON.stringify(result.overlaps)}`);
}
}
async function waitForServer(target) {
const deadline = Date.now() + 30000;
while (Date.now() < deadline) {
try {
const response = await fetch(target);
if (response.ok) return;
} catch {
// Server is still waking up.
}
await new Promise((resolveWait) => setTimeout(resolveWait, 500));
}
throw new Error("Vite server did not start.");
}

View file

@ -5,7 +5,7 @@ use std::path::{Path, PathBuf};
use std::process::Command;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Debug, Deserialize)]
#[derive(Clone, Debug, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
struct StatusRequest {
thread_id: Option<String>,
@ -14,6 +14,14 @@ struct StatusRequest {
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 {
@ -48,14 +56,17 @@ fn load_operator_snapshot(request: Option<StatusRequest>) -> Result<OperatorSnap
})
}
impl Default for StatusRequest {
fn default() -> Self {
Self {
thread_id: None,
cwd: None,
codex_home: None,
app_server: None,
}
#[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}")),
}
}
@ -113,6 +124,114 @@ fn load_status(repo_root: &Path, request: StatusRequest) -> Result<Value, String
.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() {
@ -201,7 +320,10 @@ fn system_time_millis(value: SystemTime) -> Option<u128> {
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
tauri::Builder::default()
.invoke_handler(tauri::generate_handler![load_operator_snapshot])
.invoke_handler(tauri::generate_handler![
load_operator_snapshot,
run_operator_action
])
.run(tauri::generate_context!())
.expect("error while running Epiphany operator");
}

View file

@ -1,7 +1,7 @@
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";
import { loadOperatorSnapshot, runOperatorAction } from "./operatorApi";
import type { ArtifactBundle, OperatorAction, OperatorActionResult, OperatorSnapshot, StatusRequest } from "./types";
const roleOrder = ["implementation", "modeling", "verification", "reorientation"];
@ -34,13 +34,15 @@ function useSnapshot() {
const [snapshot, setSnapshot] = useState<OperatorSnapshot | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const [actionResult, setActionResult] = useState<OperatorActionResult | null>(null);
const [runningAction, setRunningAction] = useState<OperatorAction | 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 });
const result = await loadOperatorSnapshot(nextRequest);
setSnapshot(result);
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
@ -49,16 +51,31 @@ function useSnapshot() {
}
}
async function runAction(action: OperatorAction) {
setRunningAction(action);
setError(null);
setActionResult(null);
try {
const result = await runOperatorAction(action, request);
setActionResult(result);
await refresh(request);
} catch (err) {
setError(err instanceof Error ? err.message : String(err));
} finally {
setRunningAction(null);
}
}
useEffect(() => {
void refresh({});
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return { snapshot, loading, error, request, setRequest, refresh };
return { snapshot, loading, error, request, setRequest, refresh, actionResult, runningAction, runAction };
}
export function App() {
const { snapshot, loading, error, request, setRequest, refresh } = useSnapshot();
const { snapshot, loading, error, request, setRequest, refresh, actionResult, runningAction, runAction } = useSnapshot();
const status = snapshot?.status;
const scene = status?.scene?.scene ?? {};
const pressure = status?.pressure?.pressure ?? {};
@ -106,6 +123,32 @@ export function App() {
</label>
</section>
<section className="actionStrip" aria-label="Bounded operator actions">
<button
className="secondaryButton"
onClick={() => void runAction("statusSnapshot")}
disabled={runningAction !== null}
title="Write an auditable status snapshot"
>
<FileText size={16} aria-hidden="true" />
{runningAction === "statusSnapshot" ? "Writing" : "Status Snapshot"}
</button>
<button
className="secondaryButton"
onClick={() => void runAction("coordinatorPlan")}
disabled={runningAction !== null}
title="Run a review-gated coordinator plan"
>
<ClipboardCheck size={16} aria-hidden="true" />
{runningAction === "coordinatorPlan" ? "Running" : "Coordinator Plan"}
</button>
{actionResult && (
<p className="actionResult">
{actionResult.summary} <code>{actionResult.artifactPath}</code>
</p>
)}
</section>
{error && (
<section className="notice dangerNotice" role="alert">
<AlertTriangle size={18} aria-hidden="true" />
@ -171,7 +214,7 @@ export function App() {
<article className="jobRow" key={text(job.id)}>
<div>
<strong>{text(job.id)}</strong>
<span>{text(job.kind)} · {text(job.ownerRole)}</span>
<span>{text(job.kind)} - {text(job.ownerRole)}</span>
</div>
<Pill tone={statusClass(job.status)}>{text(job.status)}</Pill>
</article>

View file

@ -0,0 +1,31 @@
import { invoke } from "@tauri-apps/api/core";
import type { OperatorAction, OperatorActionResult, OperatorSnapshot, StatusRequest } from "./types";
export async function loadOperatorSnapshot(request: StatusRequest): Promise<OperatorSnapshot> {
if (hasTauriRuntime()) {
return invoke<OperatorSnapshot>("load_operator_snapshot", { request });
}
const response = await fetch("/operator-snapshot.sample.json", { cache: "no-store" });
if (!response.ok) {
throw new Error(`sample operator snapshot failed: ${response.status}`);
}
return (await response.json()) as OperatorSnapshot;
}
export async function runOperatorAction(action: OperatorAction, request: StatusRequest): Promise<OperatorActionResult> {
if (hasTauriRuntime()) {
return invoke<OperatorActionResult>("run_operator_action", { action, request });
}
await new Promise((resolve) => setTimeout(resolve, 250));
return {
action,
artifactPath: "E:\\Projects\\EpiphanyAgent\\.epiphany-gui\\sample-action",
summary: `${action} sample completed.`,
};
}
function hasTauriRuntime(): boolean {
return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window;
}

View file

@ -75,7 +75,8 @@ h3 {
line-height: 1.2;
}
.primaryButton {
.primaryButton,
.secondaryButton {
display: inline-flex;
align-items: center;
justify-content: center;
@ -89,16 +90,44 @@ h3 {
cursor: pointer;
}
.primaryButton:disabled {
.secondaryButton {
border-color: #b8c4bc;
color: #244d3a;
background: #ffffff;
}
.primaryButton:disabled,
.secondaryButton:disabled {
cursor: wait;
opacity: 0.68;
}
.controls {
grid-template-columns: repeat(2, minmax(0, 1fr));
margin-bottom: 10px;
}
.actionStrip {
display: flex;
align-items: center;
gap: 10px;
flex-wrap: wrap;
margin-bottom: 16px;
}
.actionResult {
display: flex;
align-items: center;
gap: 8px;
min-height: 34px;
color: #3e493f;
font-size: 0.86rem;
}
.actionResult code {
max-width: min(580px, 70vw);
}
label {
display: grid;
gap: 6px;
@ -378,6 +407,11 @@ code {
grid-template-columns: 1fr;
}
.actionStrip,
.secondaryButton {
width: 100%;
}
.cardGrid,
.facts,
.facts.compact,

View file

@ -5,6 +5,14 @@ export interface StatusRequest {
appServer?: string;
}
export type OperatorAction = "statusSnapshot" | "coordinatorPlan";
export interface OperatorActionResult {
action: OperatorAction;
artifactPath: string;
summary: string;
}
export interface ArtifactBundle {
name: string;
path: string;

View file

@ -83,7 +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
- first Phase 6 GUI operator shell under `apps/epiphany-gui`, a Tauri v2 + React console over the existing status bridge and dogfood artifacts, with bounded status-snapshot and coordinator-plan buttons
- 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`
@ -281,9 +281,9 @@ Implementation slices:
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.
2. **Bounded operator actions**: status snapshot and coordinator-plan buttons
are landed. Next add buttons for roleLaunch, roleResult, reorientLaunch,
reorientResult, and reorientAccept flows, preserving review gates.
3. **Dogfood launcher**: wrap `tools/epiphany_mvp_dogfood.py` and
`tools/epiphany_mvp_coordinator.py` as explicit operator actions that write
artifact bundles and stream progress/status into the GUI.

View file

@ -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 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.
- The first MVP GUI shell now exists under `apps/epiphany-gui`. It is a Tauri v2 + React operator console over the existing status bridge and dogfood artifact surfaces, not a new source of truth. It has bounded buttons for status snapshots and coordinator-plan artifacts.
- 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,7 +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`
- Tauri v2 + React GUI operator shell under `apps/epiphany-gui`, including visual smoke and first bounded artifact-writing buttons
The exact current control flow is documented in
`notes/epiphany-current-algorithmic-map.md`.
@ -301,12 +301,14 @@ 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 first Tauri v2 +
React read-only operator console is also scaffolded. It renders the same status,
React 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
The GUI visual smoke covers desktop and mobile screenshots and clicks the first
bounded buttons: status snapshot and coordinator plan. The next real move is
adding the fixed role/reorient launch and explicit reorientAccept buttons while
preserving review gates. 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.

View file

@ -66,3 +66,4 @@
{"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."}
{"ts": "2026-04-29T15:48:44+00:00", "type": "implementation", "status": "ok", "note": "Extended the GUI MVP shell with repeatable visual smoke and first bounded operator actions. apps/epiphany-gui now has a browser fallback sample snapshot, Playwright desktop/mobile visual smoke with screenshots, and Tauri commands/buttons for statusSnapshot and coordinatorPlan artifact bundles. The buttons write auditable artifacts but do not accept findings, promote evidence, or launch arbitrary specialists."}

View file

@ -76,7 +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.
- `apps/epiphany-gui` is the first GUI shell: a Tauri v2 + React app that invokes the existing MVP status bridge, renders coordinator/CRRC/pressure/reorient/role/job/finding status, lists dogfood artifact bundles, and exposes bounded artifact-writing buttons for status snapshot and coordinator plan without accepting semantic findings.
- 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.
@ -128,7 +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.
- Phase 6 GUI operator shell: Tauri v2 + React console under `apps/epiphany-gui` over the existing status bridge and artifact bundles, with visual smoke and first bounded artifact-writing buttons.
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.
@ -164,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, 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.
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 Tauri/React GUI operator 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. The GUI now has desktop/mobile visual smoke plus bounded status-snapshot and coordinator-plan buttons. 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: Add fixed role/reorient operator buttons to `apps/epiphany-gui`: launch modeling/checkpoint, launch verification/review, launch fixed reorient-worker when recommended, read role/reorient results, and explicit reorientAccept after review. Preserve review gates and do not auto-promote evidence or continue implementation.