Add GUI visual smoke and bounded actions
This commit is contained in:
parent
a2c44a52a3
commit
21a05368d3
14 changed files with 620 additions and 32 deletions
|
|
@ -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`.
|
||||
|
|
|
|||
48
apps/epiphany-gui/package-lock.json
generated
48
apps/epiphany-gui/package-lock.json
generated
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
|
|||
164
apps/epiphany-gui/public/operator-snapshot.sample.json
Normal file
164
apps/epiphany-gui/public/operator-snapshot.sample.json
Normal 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
|
||||
}
|
||||
]
|
||||
}
|
||||
112
apps/epiphany-gui/scripts/visual-smoke.mjs
Normal file
112
apps/epiphany-gui/scripts/visual-smoke.mjs
Normal 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.");
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
31
apps/epiphany-gui/src/operatorApi.ts
Normal file
31
apps/epiphany-gui/src/operatorApi.ts
Normal 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;
|
||||
}
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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."}
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Reference in a new issue