Move MVP operator reads onto Epiphany view

This commit is contained in:
Meta Vens 2026-05-12 04:50:51 +01:00
commit 7b1e79a765
15 changed files with 1138 additions and 78 deletions

View file

@ -512,7 +512,7 @@ fn collect_coordinator_status(
)?;
let view = client.send(
"thread/epiphany/view",
Some(json!({"threadId": thread_id, "lenses": ["scene", "pressure", "jobs"]})),
Some(json!({"threadId": thread_id, "lenses": ["scene", "pressure", "jobs", "roles", "planning", "reorient", "crrc", "coordinator"]})),
true,
)?;
let scene = json!({
@ -524,26 +524,14 @@ fn collect_coordinator_status(
"source": "live",
"pressure": view.get("pressure").cloned().unwrap_or_else(|| json!(null)),
});
let reorient = client.send(
"thread/epiphany/reorient",
Some(json!({"threadId": thread_id})),
true,
)?;
let reorient = view.get("reorient").cloned().unwrap_or_else(|| json!(null));
let jobs = json!({
"threadId": thread_id,
"source": "live",
"jobs": view.get("jobs").cloned().unwrap_or_else(|| json!([])),
});
let roles = client.send(
"thread/epiphany/roles",
Some(json!({"threadId": thread_id})),
true,
)?;
let planning = client.send(
"thread/epiphany/planning",
Some(json!({"threadId": thread_id})),
true,
)?;
let roles = view.get("roles").cloned().unwrap_or_else(|| json!(null));
let planning = view.get("planning").cloned().unwrap_or_else(|| json!(null));
let role_results = json!({
"imagination": client.send("thread/epiphany/roleResult", Some(json!({"threadId": thread_id, "roleId": "imagination"})), true)?,
"modeling": client.send("thread/epiphany/roleResult", Some(json!({"threadId": thread_id, "roleId": "modeling"})), true)?,
@ -554,16 +542,8 @@ fn collect_coordinator_status(
Some(json!({"threadId": thread_id})),
true,
)?;
let crrc = client.send(
"thread/epiphany/crrc",
Some(json!({"threadId": thread_id})),
true,
)?;
let coordinator = client.send(
"thread/epiphany/coordinator",
Some(json!({"threadId": thread_id})),
true,
)?;
let crrc = view.get("crrc").cloned().unwrap_or_else(|| json!(null));
let coordinator = view.get("coordinator").cloned().unwrap_or_else(|| json!(null));
let root = env::current_dir()?;
let heartbeat_dir = root.join(".epiphany-heartbeats");
let face_dir = root.join(".epiphany-face");

View file

@ -161,7 +161,7 @@ fn run_status(args: &Args) -> Result<Value> {
)?;
let view = client.send(
"thread/epiphany/view",
Some(json!({"threadId": thread_id, "lenses": ["scene", "pressure", "jobs"]})),
Some(json!({"threadId": thread_id, "lenses": ["scene", "pressure", "jobs", "roles", "planning", "reorient", "crrc", "coordinator"]})),
true,
)?;
let scene = json!({
@ -173,26 +173,14 @@ fn run_status(args: &Args) -> Result<Value> {
"source": "live",
"pressure": view.get("pressure").cloned().unwrap_or_else(|| json!(null)),
});
let reorient = client.send(
"thread/epiphany/reorient",
Some(json!({"threadId": thread_id})),
true,
)?;
let reorient = view.get("reorient").cloned().unwrap_or_else(|| json!(null));
let jobs = json!({
"threadId": thread_id,
"source": "live",
"jobs": view.get("jobs").cloned().unwrap_or_else(|| json!([])),
});
let roles = client.send(
"thread/epiphany/roles",
Some(json!({"threadId": thread_id})),
true,
)?;
let planning = client.send(
"thread/epiphany/planning",
Some(json!({"threadId": thread_id})),
true,
)?;
let roles = view.get("roles").cloned().unwrap_or_else(|| json!(null));
let planning = view.get("planning").cloned().unwrap_or_else(|| json!(null));
let role_results = json!({
"imagination": client.send("thread/epiphany/roleResult", Some(json!({"threadId": thread_id, "roleId": "imagination"})), true)?,
"modeling": client.send("thread/epiphany/roleResult", Some(json!({"threadId": thread_id, "roleId": "modeling"})), true)?,
@ -203,16 +191,8 @@ fn run_status(args: &Args) -> Result<Value> {
Some(json!({"threadId": thread_id})),
true,
)?;
let crrc = client.send(
"thread/epiphany/crrc",
Some(json!({"threadId": thread_id})),
true,
)?;
let coordinator = client.send(
"thread/epiphany/coordinator",
Some(json!({"threadId": thread_id})),
true,
)?;
let crrc = view.get("crrc").cloned().unwrap_or_else(|| json!(null));
let coordinator = view.get("coordinator").cloned().unwrap_or_else(|| json!(null));
let root = env::current_dir().context("failed to resolve current directory")?;
let heartbeat_dir = root.join(".epiphany-heartbeats");
let face_dir = root.join(".epiphany-face");

View file

@ -4753,7 +4753,12 @@
"enum": [
"scene",
"jobs",
"pressure"
"roles",
"planning",
"pressure",
"reorient",
"crrc",
"coordinator"
],
"type": "string"
},

View file

@ -17833,14 +17833,278 @@
},
"type": "object"
},
"ThreadEpiphanyViewCoordinator": {
"properties": {
"action": {
"$ref": "#/definitions/v2/ThreadEpiphanyCoordinatorAction"
},
"canAutoRun": {
"type": "boolean"
},
"note": {
"type": "string"
},
"reason": {
"type": "string"
},
"recommendedSceneAction": {
"anyOf": [
{
"$ref": "#/definitions/v2/ThreadEpiphanySceneAction"
},
{
"type": "null"
}
]
},
"requiresReview": {
"type": "boolean"
},
"roles": {
"items": {
"$ref": "#/definitions/v2/ThreadEpiphanyRoleLane"
},
"type": "array"
},
"source": {
"$ref": "#/definitions/v2/ThreadEpiphanyRolesSource"
},
"sourceSignals": {
"$ref": "#/definitions/v2/ThreadEpiphanyCoordinatorSignals"
},
"stateRevision": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"stateStatus": {
"$ref": "#/definitions/v2/ThreadEpiphanyReorientStateStatus"
},
"targetRole": {
"anyOf": [
{
"$ref": "#/definitions/v2/ThreadEpiphanyRoleId"
},
{
"type": "null"
}
]
},
"threadId": {
"type": "string"
}
},
"required": [
"action",
"canAutoRun",
"note",
"reason",
"requiresReview",
"roles",
"source",
"sourceSignals",
"stateStatus",
"threadId"
],
"type": "object"
},
"ThreadEpiphanyViewCrrc": {
"properties": {
"availableActions": {
"items": {
"$ref": "#/definitions/v2/ThreadEpiphanySceneAction"
},
"type": "array"
},
"decision": {
"$ref": "#/definitions/v2/ThreadEpiphanyReorientDecision"
},
"note": {
"type": "string"
},
"pressure": {
"$ref": "#/definitions/v2/ThreadEpiphanyPressure"
},
"recommendation": {
"$ref": "#/definitions/v2/ThreadEpiphanyCrrcRecommendation"
},
"reorientBindingId": {
"type": "string"
},
"reorientFinding": {
"anyOf": [
{
"$ref": "#/definitions/v2/ThreadEpiphanyReorientFinding"
},
{
"type": "null"
}
]
},
"reorientJob": {
"anyOf": [
{
"$ref": "#/definitions/v2/ThreadEpiphanyJob"
},
{
"type": "null"
}
]
},
"reorientResultStatus": {
"$ref": "#/definitions/v2/ThreadEpiphanyReorientResultStatus"
},
"source": {
"$ref": "#/definitions/v2/ThreadEpiphanyReorientSource"
},
"stateRevision": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"stateStatus": {
"$ref": "#/definitions/v2/ThreadEpiphanyReorientStateStatus"
},
"threadId": {
"type": "string"
}
},
"required": [
"decision",
"note",
"pressure",
"recommendation",
"reorientBindingId",
"reorientResultStatus",
"source",
"stateStatus",
"threadId"
],
"type": "object"
},
"ThreadEpiphanyViewLens": {
"enum": [
"scene",
"jobs",
"pressure"
"roles",
"planning",
"pressure",
"reorient",
"crrc",
"coordinator"
],
"type": "string"
},
"ThreadEpiphanyViewPlanning": {
"properties": {
"planning": {
"$ref": "#/definitions/v2/EpiphanyPlanningState"
},
"source": {
"$ref": "#/definitions/v2/ThreadEpiphanyContextSource"
},
"stateRevision": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"stateStatus": {
"$ref": "#/definitions/v2/ThreadEpiphanyContextStateStatus"
},
"summary": {
"$ref": "#/definitions/v2/ThreadEpiphanyPlanningSummary"
},
"threadId": {
"type": "string"
}
},
"required": [
"planning",
"source",
"stateStatus",
"summary",
"threadId"
],
"type": "object"
},
"ThreadEpiphanyViewReorient": {
"properties": {
"decision": {
"$ref": "#/definitions/v2/ThreadEpiphanyReorientDecision"
},
"source": {
"$ref": "#/definitions/v2/ThreadEpiphanyReorientSource"
},
"stateRevision": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"stateStatus": {
"$ref": "#/definitions/v2/ThreadEpiphanyReorientStateStatus"
},
"threadId": {
"type": "string"
}
},
"required": [
"decision",
"source",
"stateStatus",
"threadId"
],
"type": "object"
},
"ThreadEpiphanyViewRoles": {
"properties": {
"note": {
"type": "string"
},
"roles": {
"items": {
"$ref": "#/definitions/v2/ThreadEpiphanyRoleLane"
},
"type": "array"
},
"source": {
"$ref": "#/definitions/v2/ThreadEpiphanyRolesSource"
},
"stateRevision": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"stateStatus": {
"$ref": "#/definitions/v2/ThreadEpiphanyReorientStateStatus"
},
"threadId": {
"type": "string"
}
},
"required": [
"note",
"roles",
"source",
"stateStatus",
"threadId"
],
"type": "object"
},
"ThreadForkParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",

View file

@ -15720,14 +15720,278 @@
},
"type": "object"
},
"ThreadEpiphanyViewCoordinator": {
"properties": {
"action": {
"$ref": "#/definitions/ThreadEpiphanyCoordinatorAction"
},
"canAutoRun": {
"type": "boolean"
},
"note": {
"type": "string"
},
"reason": {
"type": "string"
},
"recommendedSceneAction": {
"anyOf": [
{
"$ref": "#/definitions/ThreadEpiphanySceneAction"
},
{
"type": "null"
}
]
},
"requiresReview": {
"type": "boolean"
},
"roles": {
"items": {
"$ref": "#/definitions/ThreadEpiphanyRoleLane"
},
"type": "array"
},
"source": {
"$ref": "#/definitions/ThreadEpiphanyRolesSource"
},
"sourceSignals": {
"$ref": "#/definitions/ThreadEpiphanyCoordinatorSignals"
},
"stateRevision": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"stateStatus": {
"$ref": "#/definitions/ThreadEpiphanyReorientStateStatus"
},
"targetRole": {
"anyOf": [
{
"$ref": "#/definitions/ThreadEpiphanyRoleId"
},
{
"type": "null"
}
]
},
"threadId": {
"type": "string"
}
},
"required": [
"action",
"canAutoRun",
"note",
"reason",
"requiresReview",
"roles",
"source",
"sourceSignals",
"stateStatus",
"threadId"
],
"type": "object"
},
"ThreadEpiphanyViewCrrc": {
"properties": {
"availableActions": {
"items": {
"$ref": "#/definitions/ThreadEpiphanySceneAction"
},
"type": "array"
},
"decision": {
"$ref": "#/definitions/ThreadEpiphanyReorientDecision"
},
"note": {
"type": "string"
},
"pressure": {
"$ref": "#/definitions/ThreadEpiphanyPressure"
},
"recommendation": {
"$ref": "#/definitions/ThreadEpiphanyCrrcRecommendation"
},
"reorientBindingId": {
"type": "string"
},
"reorientFinding": {
"anyOf": [
{
"$ref": "#/definitions/ThreadEpiphanyReorientFinding"
},
{
"type": "null"
}
]
},
"reorientJob": {
"anyOf": [
{
"$ref": "#/definitions/ThreadEpiphanyJob"
},
{
"type": "null"
}
]
},
"reorientResultStatus": {
"$ref": "#/definitions/ThreadEpiphanyReorientResultStatus"
},
"source": {
"$ref": "#/definitions/ThreadEpiphanyReorientSource"
},
"stateRevision": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"stateStatus": {
"$ref": "#/definitions/ThreadEpiphanyReorientStateStatus"
},
"threadId": {
"type": "string"
}
},
"required": [
"decision",
"note",
"pressure",
"recommendation",
"reorientBindingId",
"reorientResultStatus",
"source",
"stateStatus",
"threadId"
],
"type": "object"
},
"ThreadEpiphanyViewLens": {
"enum": [
"scene",
"jobs",
"pressure"
"roles",
"planning",
"pressure",
"reorient",
"crrc",
"coordinator"
],
"type": "string"
},
"ThreadEpiphanyViewPlanning": {
"properties": {
"planning": {
"$ref": "#/definitions/EpiphanyPlanningState"
},
"source": {
"$ref": "#/definitions/ThreadEpiphanyContextSource"
},
"stateRevision": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"stateStatus": {
"$ref": "#/definitions/ThreadEpiphanyContextStateStatus"
},
"summary": {
"$ref": "#/definitions/ThreadEpiphanyPlanningSummary"
},
"threadId": {
"type": "string"
}
},
"required": [
"planning",
"source",
"stateStatus",
"summary",
"threadId"
],
"type": "object"
},
"ThreadEpiphanyViewReorient": {
"properties": {
"decision": {
"$ref": "#/definitions/ThreadEpiphanyReorientDecision"
},
"source": {
"$ref": "#/definitions/ThreadEpiphanyReorientSource"
},
"stateRevision": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"stateStatus": {
"$ref": "#/definitions/ThreadEpiphanyReorientStateStatus"
},
"threadId": {
"type": "string"
}
},
"required": [
"decision",
"source",
"stateStatus",
"threadId"
],
"type": "object"
},
"ThreadEpiphanyViewRoles": {
"properties": {
"note": {
"type": "string"
},
"roles": {
"items": {
"$ref": "#/definitions/ThreadEpiphanyRoleLane"
},
"type": "array"
},
"source": {
"$ref": "#/definitions/ThreadEpiphanyRolesSource"
},
"stateRevision": {
"format": "uint64",
"minimum": 0.0,
"type": [
"integer",
"null"
]
},
"stateStatus": {
"$ref": "#/definitions/ThreadEpiphanyReorientStateStatus"
},
"threadId": {
"type": "string"
}
},
"required": [
"note",
"roles",
"source",
"stateStatus",
"threadId"
],
"type": "object"
},
"ThreadForkParams": {
"$schema": "http://json-schema.org/draft-07/schema#",
"description": "There are two ways to fork a thread: 1. By thread_id: load the thread from disk by thread_id and fork it into a new thread. 2. By path: load the thread from disk by path and fork it into a new thread.\n\nIf using path, the thread_id param will be ignored.\n\nPrefer using thread_id whenever possible.",

View file

@ -0,0 +1,12 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ThreadEpiphanyCoordinatorAction } from "./ThreadEpiphanyCoordinatorAction";
import type { ThreadEpiphanyCoordinatorSignals } from "./ThreadEpiphanyCoordinatorSignals";
import type { ThreadEpiphanyReorientStateStatus } from "./ThreadEpiphanyReorientStateStatus";
import type { ThreadEpiphanyRoleId } from "./ThreadEpiphanyRoleId";
import type { ThreadEpiphanyRoleLane } from "./ThreadEpiphanyRoleLane";
import type { ThreadEpiphanyRolesSource } from "./ThreadEpiphanyRolesSource";
import type { ThreadEpiphanySceneAction } from "./ThreadEpiphanySceneAction";
export type ThreadEpiphanyViewCoordinator = { threadId: string, source: ThreadEpiphanyRolesSource, stateStatus: ThreadEpiphanyReorientStateStatus, stateRevision?: bigint | null, action: ThreadEpiphanyCoordinatorAction, targetRole?: ThreadEpiphanyRoleId | null, recommendedSceneAction?: ThreadEpiphanySceneAction | null, requiresReview: boolean, canAutoRun: boolean, reason: string, sourceSignals: ThreadEpiphanyCoordinatorSignals, roles: Array<ThreadEpiphanyRoleLane>, note: string, };

View file

@ -0,0 +1,14 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ThreadEpiphanyCrrcRecommendation } from "./ThreadEpiphanyCrrcRecommendation";
import type { ThreadEpiphanyJob } from "./ThreadEpiphanyJob";
import type { ThreadEpiphanyPressure } from "./ThreadEpiphanyPressure";
import type { ThreadEpiphanyReorientDecision } from "./ThreadEpiphanyReorientDecision";
import type { ThreadEpiphanyReorientFinding } from "./ThreadEpiphanyReorientFinding";
import type { ThreadEpiphanyReorientResultStatus } from "./ThreadEpiphanyReorientResultStatus";
import type { ThreadEpiphanyReorientSource } from "./ThreadEpiphanyReorientSource";
import type { ThreadEpiphanyReorientStateStatus } from "./ThreadEpiphanyReorientStateStatus";
import type { ThreadEpiphanySceneAction } from "./ThreadEpiphanySceneAction";
export type ThreadEpiphanyViewCrrc = { threadId: string, source: ThreadEpiphanyReorientSource, stateStatus: ThreadEpiphanyReorientStateStatus, stateRevision?: bigint | null, pressure: ThreadEpiphanyPressure, decision: ThreadEpiphanyReorientDecision, recommendation: ThreadEpiphanyCrrcRecommendation, reorientBindingId: string, reorientResultStatus: ThreadEpiphanyReorientResultStatus, reorientJob?: ThreadEpiphanyJob | null, reorientFinding?: ThreadEpiphanyReorientFinding | null, availableActions?: Array<ThreadEpiphanySceneAction>, note: string, };

View file

@ -2,4 +2,4 @@
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
export type ThreadEpiphanyViewLens = "scene" | "jobs" | "pressure";
export type ThreadEpiphanyViewLens = "scene" | "jobs" | "roles" | "planning" | "pressure" | "reorient" | "crrc" | "coordinator";

View file

@ -0,0 +1,9 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { EpiphanyPlanningState } from "../EpiphanyPlanningState";
import type { ThreadEpiphanyContextSource } from "./ThreadEpiphanyContextSource";
import type { ThreadEpiphanyContextStateStatus } from "./ThreadEpiphanyContextStateStatus";
import type { ThreadEpiphanyPlanningSummary } from "./ThreadEpiphanyPlanningSummary";
export type ThreadEpiphanyViewPlanning = { threadId: string, source: ThreadEpiphanyContextSource, stateStatus: ThreadEpiphanyContextStateStatus, stateRevision?: bigint | null, planning: EpiphanyPlanningState, summary: ThreadEpiphanyPlanningSummary, };

View file

@ -0,0 +1,8 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ThreadEpiphanyReorientDecision } from "./ThreadEpiphanyReorientDecision";
import type { ThreadEpiphanyReorientSource } from "./ThreadEpiphanyReorientSource";
import type { ThreadEpiphanyReorientStateStatus } from "./ThreadEpiphanyReorientStateStatus";
export type ThreadEpiphanyViewReorient = { threadId: string, source: ThreadEpiphanyReorientSource, stateStatus: ThreadEpiphanyReorientStateStatus, stateRevision?: bigint | null, decision: ThreadEpiphanyReorientDecision, };

View file

@ -0,0 +1,8 @@
// GENERATED CODE! DO NOT MODIFY BY HAND!
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { ThreadEpiphanyReorientStateStatus } from "./ThreadEpiphanyReorientStateStatus";
import type { ThreadEpiphanyRoleLane } from "./ThreadEpiphanyRoleLane";
import type { ThreadEpiphanyRolesSource } from "./ThreadEpiphanyRolesSource";
export type ThreadEpiphanyViewRoles = { threadId: string, source: ThreadEpiphanyRolesSource, stateStatus: ThreadEpiphanyReorientStateStatus, stateRevision?: bigint | null, roles: Array<ThreadEpiphanyRoleLane>, note: string, };

View file

@ -387,7 +387,12 @@ export type { ThreadEpiphanyStateUpdatedField } from "./ThreadEpiphanyStateUpdat
export type { ThreadEpiphanyStateUpdatedNotification } from "./ThreadEpiphanyStateUpdatedNotification";
export type { ThreadEpiphanyStateUpdatedSource } from "./ThreadEpiphanyStateUpdatedSource";
export type { ThreadEpiphanyUpdatePatch } from "./ThreadEpiphanyUpdatePatch";
export type { ThreadEpiphanyViewCoordinator } from "./ThreadEpiphanyViewCoordinator";
export type { ThreadEpiphanyViewCrrc } from "./ThreadEpiphanyViewCrrc";
export type { ThreadEpiphanyViewLens } from "./ThreadEpiphanyViewLens";
export type { ThreadEpiphanyViewPlanning } from "./ThreadEpiphanyViewPlanning";
export type { ThreadEpiphanyViewReorient } from "./ThreadEpiphanyViewReorient";
export type { ThreadEpiphanyViewRoles } from "./ThreadEpiphanyViewRoles";
export type { ThreadForkParams } from "./ThreadForkParams";
export type { ThreadForkResponse } from "./ThreadForkResponse";
export type { ThreadInjectItemsParams } from "./ThreadInjectItemsParams";

View file

@ -1914,6 +1914,8 @@ mod tests {
lenses: vec![v2::ThreadEpiphanyViewLens::Pressure],
scene: None,
jobs: Vec::new(),
roles: None,
planning: None,
pressure: Some(v2::ThreadEpiphanyPressure {
status: v2::ThreadEpiphanyPressureStatus::Ready,
level: v2::ThreadEpiphanyPressureLevel::Low,
@ -1927,6 +1929,9 @@ mod tests {
note: "Context window usage is below the compaction prep threshold."
.to_string(),
}),
reorient: None,
crrc: None,
coordinator: None,
},
};

View file

@ -3946,7 +3946,12 @@ pub struct ThreadEpiphanySceneResponse {
pub enum ThreadEpiphanyViewLens {
Scene,
Jobs,
Roles,
Planning,
Pressure,
Reorient,
Crrc,
Coordinator,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, JsonSchema, TS)]
@ -3958,23 +3963,6 @@ pub struct ThreadEpiphanyViewParams {
pub lenses: Vec<ThreadEpiphanyViewLens>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadEpiphanyViewResponse {
pub thread_id: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub lenses: Vec<ThreadEpiphanyViewLens>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub scene: Option<ThreadEpiphanyScene>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub jobs: Vec<ThreadEpiphanyJob>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub pressure: Option<ThreadEpiphanyPressure>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
@ -5203,6 +5191,130 @@ pub struct ThreadEpiphanyCrrcRecommendation {
pub reason: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadEpiphanyViewResponse {
pub thread_id: String,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub lenses: Vec<ThreadEpiphanyViewLens>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub scene: Option<ThreadEpiphanyScene>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub jobs: Vec<ThreadEpiphanyJob>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub roles: Option<ThreadEpiphanyViewRoles>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub planning: Option<ThreadEpiphanyViewPlanning>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub pressure: Option<ThreadEpiphanyPressure>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub reorient: Option<ThreadEpiphanyViewReorient>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub crrc: Option<ThreadEpiphanyViewCrrc>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub coordinator: Option<ThreadEpiphanyViewCoordinator>,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadEpiphanyViewRoles {
pub thread_id: String,
pub source: ThreadEpiphanyRolesSource,
pub state_status: ThreadEpiphanyReorientStateStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub state_revision: Option<u64>,
pub roles: Vec<ThreadEpiphanyRoleLane>,
pub note: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadEpiphanyViewPlanning {
pub thread_id: String,
pub source: ThreadEpiphanyContextSource,
pub state_status: ThreadEpiphanyContextStateStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub state_revision: Option<u64>,
pub planning: CoreEpiphanyPlanningState,
pub summary: ThreadEpiphanyPlanningSummary,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadEpiphanyViewReorient {
pub thread_id: String,
pub source: ThreadEpiphanyReorientSource,
pub state_status: ThreadEpiphanyReorientStateStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub state_revision: Option<u64>,
pub decision: ThreadEpiphanyReorientDecision,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadEpiphanyViewCrrc {
pub thread_id: String,
pub source: ThreadEpiphanyReorientSource,
pub state_status: ThreadEpiphanyReorientStateStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub state_revision: Option<u64>,
pub pressure: ThreadEpiphanyPressure,
pub decision: ThreadEpiphanyReorientDecision,
pub recommendation: ThreadEpiphanyCrrcRecommendation,
pub reorient_binding_id: String,
pub reorient_result_status: ThreadEpiphanyReorientResultStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub reorient_job: Option<ThreadEpiphanyJob>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub reorient_finding: Option<ThreadEpiphanyReorientFinding>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub available_actions: Vec<ThreadEpiphanySceneAction>,
pub note: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]
pub struct ThreadEpiphanyViewCoordinator {
pub thread_id: String,
pub source: ThreadEpiphanyRolesSource,
pub state_status: ThreadEpiphanyReorientStateStatus,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub state_revision: Option<u64>,
pub action: ThreadEpiphanyCoordinatorAction,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub target_role: Option<ThreadEpiphanyRoleId>,
#[serde(default, skip_serializing_if = "Option::is_none")]
#[ts(optional = nullable)]
pub recommended_scene_action: Option<ThreadEpiphanySceneAction>,
pub requires_review: bool,
pub can_auto_run: bool,
pub reason: String,
pub source_signals: ThreadEpiphanyCoordinatorSignals,
pub roles: Vec<ThreadEpiphanyRoleLane>,
pub note: String,
}
#[derive(Serialize, Deserialize, Debug, Clone, Copy, PartialEq, Eq, JsonSchema, TS)]
#[serde(rename_all = "camelCase")]
#[ts(export_to = "v2/")]

View file

@ -266,9 +266,14 @@ use codex_app_server_protocol::ThreadEpiphanyStateUpdatedSource;
use codex_app_server_protocol::ThreadEpiphanyUpdateParams;
use codex_app_server_protocol::ThreadEpiphanyUpdatePatch;
use codex_app_server_protocol::ThreadEpiphanyUpdateResponse;
use codex_app_server_protocol::ThreadEpiphanyViewCoordinator;
use codex_app_server_protocol::ThreadEpiphanyViewCrrc;
use codex_app_server_protocol::ThreadEpiphanyViewLens;
use codex_app_server_protocol::ThreadEpiphanyViewParams;
use codex_app_server_protocol::ThreadEpiphanyViewPlanning;
use codex_app_server_protocol::ThreadEpiphanyViewReorient;
use codex_app_server_protocol::ThreadEpiphanyViewResponse;
use codex_app_server_protocol::ThreadEpiphanyViewRoles;
use codex_app_server_protocol::ThreadForkParams;
use codex_app_server_protocol::ThreadForkResponse;
use codex_app_server_protocol::ThreadIncrementElicitationParams;
@ -4307,7 +4312,12 @@ impl CodexMessageProcessor {
vec![
ThreadEpiphanyViewLens::Scene,
ThreadEpiphanyViewLens::Jobs,
ThreadEpiphanyViewLens::Roles,
ThreadEpiphanyViewLens::Planning,
ThreadEpiphanyViewLens::Pressure,
ThreadEpiphanyViewLens::Reorient,
ThreadEpiphanyViewLens::Crrc,
ThreadEpiphanyViewLens::Coordinator,
]
} else {
lenses
@ -4336,7 +4346,17 @@ impl CodexMessageProcessor {
}
};
let retrieval_override = if lenses.contains(&ThreadEpiphanyViewLens::Jobs)
let needs_jobs = lenses.contains(&ThreadEpiphanyViewLens::Jobs)
|| lenses.contains(&ThreadEpiphanyViewLens::Roles)
|| lenses.contains(&ThreadEpiphanyViewLens::Crrc)
|| lenses.contains(&ThreadEpiphanyViewLens::Coordinator);
let needs_reorientation_inputs = lenses.contains(&ThreadEpiphanyViewLens::Roles)
|| lenses.contains(&ThreadEpiphanyViewLens::Reorient)
|| lenses.contains(&ThreadEpiphanyViewLens::Crrc)
|| lenses.contains(&ThreadEpiphanyViewLens::Coordinator);
let needs_pressure =
lenses.contains(&ThreadEpiphanyViewLens::Pressure) || needs_reorientation_inputs;
let retrieval_override = if (needs_jobs || needs_reorientation_inputs)
&& thread
.epiphany_state
.as_ref()
@ -4351,9 +4371,288 @@ impl CodexMessageProcessor {
} else {
None
};
let token_usage_info = if lenses.contains(&ThreadEpiphanyViewLens::Pressure) {
let watcher_snapshot = if needs_reorientation_inputs {
if let Some(loaded_thread) = loaded_thread.as_ref() {
let config_snapshot = loaded_thread.config_snapshot().await;
self.epiphany_invalidation_manager
.ensure_thread_watch(&thread_id, &config_snapshot.cwd)
.await;
Some(
self.epiphany_invalidation_manager
.snapshot(&thread_id)
.await,
)
} else {
None
}
} else {
None
};
let token_usage_info = if needs_pressure {
if let Some(loaded_thread) = loaded_thread.as_ref() {
loaded_thread.token_usage_info().await
} else {
match thread.path.as_deref() {
Some(path) => latest_token_usage_info_from_rollout_path(path).await,
None => None,
}
}
} else {
None
};
let pressure = needs_pressure.then(|| map_epiphany_pressure(token_usage_info.as_ref()));
let freshness = needs_reorientation_inputs.then(|| {
map_epiphany_freshness(
thread.epiphany_state.as_ref(),
retrieval_override.as_ref(),
watcher_snapshot.as_ref(),
)
});
let (state_revision, reorient_state_status, reorient_decision) =
if let (Some((state_revision, retrieval, graph, watcher)), Some(pressure)) =
(freshness.as_ref(), pressure.as_ref())
{
let (state_status, decision) = map_epiphany_reorient(
thread.epiphany_state.as_ref(),
pressure,
retrieval,
graph,
watcher,
);
(*state_revision, state_status, Some(decision))
} else {
(None, ThreadEpiphanyReorientStateStatus::Missing, None)
};
let jobs = if needs_jobs {
map_epiphany_jobs(thread.epiphany_state.as_ref(), retrieval_override.as_ref())
} else {
Vec::new()
};
let runtime_store_path = if lenses.contains(&ThreadEpiphanyViewLens::Roles)
|| lenses.contains(&ThreadEpiphanyViewLens::Crrc)
|| lenses.contains(&ThreadEpiphanyViewLens::Coordinator)
{
if let Some(loaded_thread) = loaded_thread.as_ref() {
Some(loaded_thread.epiphany_runtime_spine_store_path().await)
} else {
None
}
} else {
None
};
let reorient_job = jobs
.iter()
.find(|job| job.id == EPIPHANY_REORIENT_LAUNCH_BINDING_ID)
.cloned();
let (reorient_result_status, reorient_finding, reorient_result_note) = if runtime_store_path
.is_some()
|| lenses.contains(&ThreadEpiphanyViewLens::Crrc)
|| lenses.contains(&ThreadEpiphanyViewLens::Coordinator)
{
load_epiphany_reorient_result_snapshot(
thread.epiphany_state.as_ref(),
runtime_store_path.as_deref(),
EPIPHANY_REORIENT_LAUNCH_BINDING_ID,
)
.await
} else {
(
ThreadEpiphanyReorientResultStatus::MissingState,
None,
"Reorient result was not requested.".to_string(),
)
};
let checkpoint_present = thread
.epiphany_state
.as_ref()
.and_then(|state| state.investigation_checkpoint.as_ref())
.is_some();
let reorient_finding_accepted = reorient_finding.as_ref().is_some_and(|finding| {
thread
.epiphany_state
.as_ref()
.is_some_and(|state| epiphany_reorient_finding_already_accepted(state, finding))
});
let recommendation = if let (Some(pressure), Some(decision)) =
(pressure.as_ref(), reorient_decision.as_ref())
{
Some(map_epiphany_crrc_recommendation(
loaded,
reorient_state_status,
pressure,
decision,
reorient_result_status,
checkpoint_present,
reorient_finding.is_some(),
reorient_finding_accepted,
))
} else {
None
};
let roles = if let (Some(pressure), Some(decision), Some(recommendation)) = (
pressure.as_ref(),
reorient_decision.as_ref(),
recommendation.as_ref(),
) {
Some(map_epiphany_roles(
thread.epiphany_state.as_ref(),
&jobs,
decision,
pressure,
recommendation,
reorient_result_status,
reorient_job.as_ref(),
))
} else {
None
};
let coordinator_response = if lenses.contains(&ThreadEpiphanyViewLens::Coordinator) {
if let (Some(pressure), Some(recommendation), Some(roles)) =
(pressure.as_ref(), recommendation.as_ref(), roles.clone())
{
let (modeling_result_status, modeling_finding, _) =
if let Some(state) = thread.epiphany_state.as_ref() {
load_epiphany_role_result_snapshot(
state,
runtime_store_path.as_deref(),
ThreadEpiphanyRoleId::Modeling,
EPIPHANY_MODELING_ROLE_BINDING_ID,
)
.await
} else {
(
ThreadEpiphanyRoleResultStatus::MissingState,
None,
"No authoritative Epiphany state exists for this thread.".to_string(),
)
};
let modeling_result_accepted = modeling_finding.as_ref().is_some_and(|finding| {
thread
.epiphany_state
.as_ref()
.is_some_and(|state| epiphany_role_finding_already_accepted(state, finding))
});
let modeling_result_reviewable = modeling_finding
.as_ref()
.is_some_and(epiphany_modeling_finding_has_reviewable_state_patch);
let (verification_result_status, verification_finding, _) =
if let Some(state) = thread.epiphany_state.as_ref() {
load_epiphany_role_result_snapshot(
state,
runtime_store_path.as_deref(),
ThreadEpiphanyRoleId::Verification,
EPIPHANY_VERIFICATION_ROLE_BINDING_ID,
)
.await
} else {
(
ThreadEpiphanyRoleResultStatus::MissingState,
None,
"No authoritative Epiphany state exists for this thread.".to_string(),
)
};
let verification_result_accepted =
verification_finding.as_ref().is_some_and(|finding| {
thread.epiphany_state.as_ref().is_some_and(|state| {
epiphany_role_finding_already_accepted(state, finding)
})
});
let verification_result_covers_current_modeling =
thread.epiphany_state.as_ref().is_none_or(|state| {
epiphany_verification_finding_covers_current_modeling(
state,
modeling_result_accepted,
modeling_finding.as_ref(),
verification_finding.as_ref(),
)
});
let modeling_result_accepted_after_verification =
thread.epiphany_state.as_ref().is_some_and(|state| {
role_finding_accepted_after(
state,
modeling_finding.as_ref(),
verification_finding.as_ref(),
)
});
let implementation_evidence_after_verification =
thread.epiphany_state.as_ref().is_some_and(|state| {
implementation_evidence_after_role_finding(
state,
verification_finding.as_ref(),
)
});
let verification_result_cites_implementation_evidence =
thread.epiphany_state.as_ref().is_some_and(|state| {
epiphany_role_finding_cites_implementation_evidence(
state,
verification_finding.as_ref(),
)
});
let verification_result_allows_implementation = verification_result_accepted
&& verification_finding
.as_ref()
.is_some_and(epiphany_verification_finding_allows_implementation);
let verification_result_needs_evidence = verification_result_accepted
&& verification_finding
.as_ref()
.is_some_and(epiphany_verification_finding_needs_evidence);
let source_signals = ThreadEpiphanyCoordinatorSignals {
pressure_level: pressure.level,
should_prepare_compaction: pressure.should_prepare_compaction,
reorient_action: reorient_decision
.as_ref()
.map(|decision| decision.action)
.unwrap_or(ThreadEpiphanyReorientAction::Resume),
crrc_action: recommendation.action,
modeling_result_status,
verification_result_status,
reorient_result_status,
};
let coordinator = map_epiphany_coordinator(
reorient_state_status,
checkpoint_present,
pressure,
recommendation,
&roles,
&source_signals,
modeling_result_accepted,
modeling_result_reviewable,
modeling_result_accepted_after_verification,
implementation_evidence_after_verification,
verification_result_cites_implementation_evidence,
verification_result_covers_current_modeling,
verification_result_accepted,
verification_result_allows_implementation,
verification_result_needs_evidence,
reorient_finding_accepted,
);
let note = render_epiphany_coordinator_note(
recommendation.action,
pressure.level,
modeling_result_status,
verification_result_status,
reorient_result_status,
coordinator.action,
);
Some(ThreadEpiphanyViewCoordinator {
thread_id: thread_id.clone(),
source: if loaded {
ThreadEpiphanyRolesSource::Live
} else {
ThreadEpiphanyRolesSource::Stored
},
state_status: reorient_state_status,
state_revision,
action: coordinator.action,
target_role: coordinator.target_role,
recommended_scene_action: coordinator.recommended_scene_action,
requires_review: coordinator.requires_review,
can_auto_run: coordinator.can_auto_run,
reason: coordinator.reason,
source_signals,
roles,
note,
})
} else {
None
}
@ -4362,18 +4661,113 @@ impl CodexMessageProcessor {
};
let response = ThreadEpiphanyViewResponse {
thread_id,
thread_id: thread_id.clone(),
scene: lenses
.contains(&ThreadEpiphanyViewLens::Scene)
.then(|| map_epiphany_scene(thread.epiphany_state.as_ref(), loaded)),
jobs: if lenses.contains(&ThreadEpiphanyViewLens::Jobs) {
map_epiphany_jobs(thread.epiphany_state.as_ref(), retrieval_override.as_ref())
jobs.clone()
} else {
Vec::new()
},
roles: lenses.contains(&ThreadEpiphanyViewLens::Roles).then(|| {
let roles = roles.clone().unwrap_or_default();
ThreadEpiphanyViewRoles {
thread_id: thread_id.clone(),
source: if loaded {
ThreadEpiphanyRolesSource::Live
} else {
ThreadEpiphanyRolesSource::Stored
},
state_status: reorient_state_status,
state_revision,
note: render_epiphany_roles_note(
&roles,
reorient_state_status,
recommendation
.as_ref()
.map(|recommendation| recommendation.action)
.unwrap_or(ThreadEpiphanyCrrcAction::Continue),
),
roles,
}
}),
planning: lenses.contains(&ThreadEpiphanyViewLens::Planning).then(|| {
let (state_status, state_revision, planning, summary) =
map_epiphany_planning(thread.epiphany_state.as_ref());
ThreadEpiphanyViewPlanning {
thread_id: thread_id.clone(),
source: if loaded {
ThreadEpiphanyContextSource::Live
} else {
ThreadEpiphanyContextSource::Stored
},
state_status,
state_revision,
planning,
summary,
}
}),
pressure: lenses
.contains(&ThreadEpiphanyViewLens::Pressure)
.then(|| map_epiphany_pressure(token_usage_info.as_ref())),
.then(|| pressure.clone())
.flatten(),
reorient: lenses
.contains(&ThreadEpiphanyViewLens::Reorient)
.then(|| {
reorient_decision
.clone()
.map(|decision| ThreadEpiphanyViewReorient {
thread_id: thread_id.clone(),
source: if loaded {
ThreadEpiphanyReorientSource::Live
} else {
ThreadEpiphanyReorientSource::Stored
},
state_status: reorient_state_status,
state_revision,
decision,
})
})
.flatten(),
crrc: lenses
.contains(&ThreadEpiphanyViewLens::Crrc)
.then(|| {
let pressure = pressure.clone()?;
let decision = reorient_decision.clone()?;
let recommendation = recommendation.clone()?;
let available_actions = epiphany_scene_available_actions(
loaded,
thread.epiphany_state.is_some(),
checkpoint_present,
reorient_job.is_some(),
);
let note = format!(
"{} Result status: {:?}. {}",
recommendation.reason, reorient_result_status, reorient_result_note
);
Some(ThreadEpiphanyViewCrrc {
thread_id: thread_id.clone(),
source: if loaded {
ThreadEpiphanyReorientSource::Live
} else {
ThreadEpiphanyReorientSource::Stored
},
state_status: reorient_state_status,
state_revision,
pressure,
decision,
recommendation,
reorient_binding_id: EPIPHANY_REORIENT_LAUNCH_BINDING_ID.to_string(),
reorient_result_status,
reorient_job: reorient_job.clone(),
reorient_finding: reorient_finding.clone(),
available_actions,
note,
})
})
.flatten(),
coordinator: coordinator_response,
lenses,
};
self.outgoing.send_response(request_id, response).await;