From 37ea4ff2c3467b256aa8edcb38e66068d9707399 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Tue, 19 May 2026 16:35:58 +0100 Subject: [PATCH] Move freshness surface to CultNet types --- epiphany-codex-bridge/src/cultnet.rs | 27 ++++++ epiphany-codex-bridge/src/lib.rs | 1 + epiphany-codex-bridge/src/reorient.rs | 14 ++- epiphany-codex-bridge/src/view.rs | 23 ++--- epiphany-core/src/surfaces/freshness.rs | 37 ++++++-- .../epiphany_read_routes.rs | 88 ++++++++++++++++++- 6 files changed, 168 insertions(+), 22 deletions(-) create mode 100644 epiphany-codex-bridge/src/cultnet.rs diff --git a/epiphany-codex-bridge/src/cultnet.rs b/epiphany-codex-bridge/src/cultnet.rs new file mode 100644 index 00000000..fe94435c --- /dev/null +++ b/epiphany-codex-bridge/src/cultnet.rs @@ -0,0 +1,27 @@ +use epiphany_core::EpiphanyGraphFreshness; +use epiphany_core::EpiphanyInvalidationInput; +use epiphany_core::EpiphanyRetrievalFreshness; +pub use epiphany_core::EpiphanyGraphFreshnessStatus; +pub use epiphany_core::EpiphanyInvalidationStatus; +pub use epiphany_core::EpiphanyRetrievalFreshnessStatus; +use serde::Deserialize; +use serde::Serialize; + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub enum EpiphanySurfaceSource { + Stored, + Live, +} + +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct EpiphanyFreshnessSurface { + pub thread_id: String, + pub source: EpiphanySurfaceSource, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub state_revision: Option, + pub retrieval: EpiphanyRetrievalFreshness, + pub graph: EpiphanyGraphFreshness, + pub watcher: EpiphanyInvalidationInput, +} diff --git a/epiphany-codex-bridge/src/lib.rs b/epiphany-codex-bridge/src/lib.rs index 09246335..c4e113ef 100644 --- a/epiphany-codex-bridge/src/lib.rs +++ b/epiphany-codex-bridge/src/lib.rs @@ -1,6 +1,7 @@ pub mod checkpoint; pub mod context; pub mod coordinator; +pub mod cultnet; pub mod error; pub mod invalidation; pub mod jobs; diff --git a/epiphany-codex-bridge/src/reorient.rs b/epiphany-codex-bridge/src/reorient.rs index 7a19a77f..51180558 100644 --- a/epiphany-codex-bridge/src/reorient.rs +++ b/epiphany-codex-bridge/src/reorient.rs @@ -51,18 +51,26 @@ pub fn map_epiphany_freshness( ThreadEpiphanyGraphFreshness, ThreadEpiphanyInvalidationInput, ) { + let freshness = derive_epiphany_freshness_view(state, retrieval_override, watcher_snapshot); + map_core_epiphany_freshness(freshness) +} + +pub fn derive_epiphany_freshness_view( + state: Option<&EpiphanyThreadState>, + retrieval_override: Option<&EpiphanyRetrievalState>, + watcher_snapshot: Option>, +) -> EpiphanyFreshnessView { let watcher = watcher_snapshot.map(|snapshot| EpiphanyFreshnessWatcherInput { available: snapshot.available, workspace_root: snapshot.workspace_root, observed_at_unix_seconds: snapshot.observed_at_unix_seconds, changed_paths: snapshot.changed_paths, }); - let freshness = derive_freshness(EpiphanyFreshnessInput { + derive_freshness(EpiphanyFreshnessInput { state, retrieval_override, watcher, - }); - map_core_epiphany_freshness(freshness) + }) } fn map_core_epiphany_freshness( diff --git a/epiphany-codex-bridge/src/view.rs b/epiphany-codex-bridge/src/view.rs index 650f8a7e..374d0bb9 100644 --- a/epiphany-codex-bridge/src/view.rs +++ b/epiphany-codex-bridge/src/view.rs @@ -20,10 +20,13 @@ use crate::coordinator::map_epiphany_coordinator_view; use crate::coordinator::map_epiphany_crrc_recommendation; use crate::coordinator::map_epiphany_roles; use crate::coordinator::render_epiphany_roles_note; +use crate::cultnet::EpiphanyFreshnessSurface; +use crate::cultnet::EpiphanySurfaceSource; use crate::jobs::map_epiphany_jobs; use crate::launch::EPIPHANY_REORIENT_LAUNCH_BINDING_ID; use crate::pressure::map_epiphany_pressure; use crate::reorient::EpiphanyFreshnessWatcherSnapshot; +use crate::reorient::derive_epiphany_freshness_view; use crate::reorient::map_epiphany_freshness; use crate::reorient::map_epiphany_reorient; use crate::runtime_results::load_epiphany_reorient_result_snapshot; @@ -77,25 +80,25 @@ pub struct EpiphanyFreshnessResponseInput<'a> { pub watcher_snapshot: Option>, } -pub fn map_epiphany_freshness_response( +pub fn derive_epiphany_freshness_surface( input: EpiphanyFreshnessResponseInput<'_>, -) -> ThreadEpiphanyFreshnessResponse { - let (state_revision, retrieval, graph, watcher) = map_epiphany_freshness( +) -> EpiphanyFreshnessSurface { + let freshness = derive_epiphany_freshness_view( input.state, input.retrieval_override, input.watcher_snapshot, ); - ThreadEpiphanyFreshnessResponse { + EpiphanyFreshnessSurface { thread_id: input.thread_id, source: if input.loaded { - ThreadEpiphanyFreshnessSource::Live + EpiphanySurfaceSource::Live } else { - ThreadEpiphanyFreshnessSource::Stored + EpiphanySurfaceSource::Stored }, - state_revision, - retrieval, - graph, - watcher, + state_revision: freshness.state_revision, + retrieval: freshness.retrieval, + graph: freshness.graph, + watcher: freshness.watcher, } } diff --git a/epiphany-core/src/surfaces/freshness.rs b/epiphany-core/src/surfaces/freshness.rs index c1e003ff..7dff03f6 100644 --- a/epiphany-core/src/surfaces/freshness.rs +++ b/epiphany-core/src/surfaces/freshness.rs @@ -1,11 +1,14 @@ use epiphany_state_model::EpiphanyRetrievalState; use epiphany_state_model::EpiphanyRetrievalStatus; use epiphany_state_model::EpiphanyThreadState; +use serde::Deserialize; +use serde::Serialize; use std::collections::HashSet; use std::path::Path; use std::path::PathBuf; -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub enum EpiphanyRetrievalFreshnessStatus { Missing, Ready, @@ -14,51 +17,69 @@ pub enum EpiphanyRetrievalFreshnessStatus { Unavailable, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct EpiphanyRetrievalFreshness { pub status: EpiphanyRetrievalFreshnessStatus, + #[serde(default, skip_serializing_if = "Option::is_none")] pub semantic_available: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub last_indexed_at_unix_seconds: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub indexed_file_count: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub indexed_chunk_count: Option, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub dirty_paths: Vec, pub note: String, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub enum EpiphanyGraphFreshnessStatus { Missing, Ready, Stale, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct EpiphanyGraphFreshness { pub status: EpiphanyGraphFreshnessStatus, + #[serde(default, skip_serializing_if = "Option::is_none")] pub graph_freshness: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub checkpoint_id: Option, pub dirty_path_count: u32, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub dirty_paths: Vec, pub open_question_count: u32, pub open_gap_count: u32, pub note: String, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub enum EpiphanyInvalidationStatus { Unavailable, Clean, Changed, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct EpiphanyInvalidationInput { pub status: EpiphanyInvalidationStatus, + #[serde(default, skip_serializing_if = "Option::is_none")] pub watched_root: Option, + #[serde(default, skip_serializing_if = "Option::is_none")] pub observed_at_unix_seconds: Option, pub changed_path_count: u32, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub changed_paths: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub graph_node_ids: Vec, + #[serde(default, skip_serializing_if = "Vec::is_empty")] pub active_frontier_node_ids: Vec, pub note: String, } @@ -78,8 +99,10 @@ pub struct EpiphanyFreshnessInput<'a> { pub watcher: Option>, } -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)] +#[serde(rename_all = "camelCase")] pub struct EpiphanyFreshnessView { + #[serde(default, skip_serializing_if = "Option::is_none")] pub state_revision: Option, pub retrieval: EpiphanyRetrievalFreshness, pub graph: EpiphanyGraphFreshness, diff --git a/vendor/codex/codex-rs/app-server/src/codex_message_processor/epiphany_read_routes.rs b/vendor/codex/codex-rs/app-server/src/codex_message_processor/epiphany_read_routes.rs index 212ca125..3a3377f3 100644 --- a/vendor/codex/codex-rs/app-server/src/codex_message_processor/epiphany_read_routes.rs +++ b/vendor/codex/codex-rs/app-server/src/codex_message_processor/epiphany_read_routes.rs @@ -2,6 +2,11 @@ use codex_app_server_protocol::*; use codex_core::CodexThread; use codex_protocol::ThreadId; use codex_protocol::protocol::EpiphanyRetrievalState; +use epiphany_codex_bridge::cultnet::EpiphanyFreshnessSurface; +use epiphany_codex_bridge::cultnet::EpiphanyGraphFreshnessStatus; +use epiphany_codex_bridge::cultnet::EpiphanyInvalidationStatus; +use epiphany_codex_bridge::cultnet::EpiphanyRetrievalFreshnessStatus; +use epiphany_codex_bridge::cultnet::EpiphanySurfaceSource; use epiphany_codex_bridge::invalidation::epiphany_freshness_watcher_snapshot; use epiphany_codex_bridge::launch::EPIPHANY_REORIENT_LAUNCH_BINDING_ID; use epiphany_codex_bridge::launch::epiphany_role_binding_id; @@ -20,7 +25,7 @@ use epiphany_codex_bridge::view::epiphany_view_needs_reorientation_inputs; use epiphany_codex_bridge::view::epiphany_view_needs_runtime_store; use epiphany_codex_bridge::view::map_epiphany_context_response; use epiphany_codex_bridge::view::map_epiphany_distill_response; -use epiphany_codex_bridge::view::map_epiphany_freshness_response; +use epiphany_codex_bridge::view::derive_epiphany_freshness_surface; use epiphany_codex_bridge::view::map_epiphany_graph_query_response; use epiphany_codex_bridge::view::map_epiphany_propose_response; use epiphany_codex_bridge::view::map_epiphany_reorient_result_response; @@ -261,7 +266,7 @@ impl CodexMessageProcessor { } else { None }; - let response = map_epiphany_freshness_response(EpiphanyFreshnessResponseInput { + let surface = derive_epiphany_freshness_surface(EpiphanyFreshnessResponseInput { thread_id, loaded: loaded_thread.is_some(), state: thread.epiphany_state.as_ref(), @@ -270,6 +275,7 @@ impl CodexMessageProcessor { .as_ref() .map(epiphany_freshness_watcher_snapshot), }); + let response = thread_epiphany_freshness_response_from_surface(surface); self.outgoing.send_response(request_id, response).await; } @@ -569,6 +575,84 @@ impl CodexMessageProcessor { } } +fn thread_epiphany_freshness_response_from_surface( + surface: EpiphanyFreshnessSurface, +) -> ThreadEpiphanyFreshnessResponse { + ThreadEpiphanyFreshnessResponse { + thread_id: surface.thread_id, + source: match surface.source { + EpiphanySurfaceSource::Stored => ThreadEpiphanyFreshnessSource::Stored, + EpiphanySurfaceSource::Live => ThreadEpiphanyFreshnessSource::Live, + }, + state_revision: surface.state_revision, + retrieval: ThreadEpiphanyRetrievalFreshness { + status: match surface.retrieval.status { + EpiphanyRetrievalFreshnessStatus::Missing => { + ThreadEpiphanyRetrievalFreshnessStatus::Missing + } + EpiphanyRetrievalFreshnessStatus::Ready => { + ThreadEpiphanyRetrievalFreshnessStatus::Ready + } + EpiphanyRetrievalFreshnessStatus::Stale => { + ThreadEpiphanyRetrievalFreshnessStatus::Stale + } + EpiphanyRetrievalFreshnessStatus::Indexing => { + ThreadEpiphanyRetrievalFreshnessStatus::Indexing + } + EpiphanyRetrievalFreshnessStatus::Unavailable => { + ThreadEpiphanyRetrievalFreshnessStatus::Unavailable + } + }, + semantic_available: surface.retrieval.semantic_available, + last_indexed_at_unix_seconds: surface.retrieval.last_indexed_at_unix_seconds, + indexed_file_count: surface.retrieval.indexed_file_count, + indexed_chunk_count: surface.retrieval.indexed_chunk_count, + dirty_paths: surface.retrieval.dirty_paths, + note: surface.retrieval.note, + }, + graph: ThreadEpiphanyGraphFreshness { + status: match surface.graph.status { + EpiphanyGraphFreshnessStatus::Missing => { + ThreadEpiphanyGraphFreshnessStatus::Missing + } + EpiphanyGraphFreshnessStatus::Ready => { + ThreadEpiphanyGraphFreshnessStatus::Ready + } + EpiphanyGraphFreshnessStatus::Stale => { + ThreadEpiphanyGraphFreshnessStatus::Stale + } + }, + graph_freshness: surface.graph.graph_freshness, + checkpoint_id: surface.graph.checkpoint_id, + dirty_path_count: surface.graph.dirty_path_count, + dirty_paths: surface.graph.dirty_paths, + open_question_count: surface.graph.open_question_count, + open_gap_count: surface.graph.open_gap_count, + note: surface.graph.note, + }, + watcher: ThreadEpiphanyInvalidationInput { + status: match surface.watcher.status { + EpiphanyInvalidationStatus::Unavailable => { + ThreadEpiphanyInvalidationStatus::Unavailable + } + EpiphanyInvalidationStatus::Clean => { + ThreadEpiphanyInvalidationStatus::Clean + } + EpiphanyInvalidationStatus::Changed => { + ThreadEpiphanyInvalidationStatus::Changed + } + }, + watched_root: surface.watcher.watched_root, + observed_at_unix_seconds: surface.watcher.observed_at_unix_seconds, + changed_path_count: surface.watcher.changed_path_count, + changed_paths: surface.watcher.changed_paths, + graph_node_ids: surface.watcher.graph_node_ids, + active_frontier_node_ids: surface.watcher.active_frontier_node_ids, + note: surface.watcher.note, + }, + } +} + async fn thread_epiphany_retrieval_state(thread: &CodexThread) -> EpiphanyRetrievalState { let config = thread.config_snapshot().await; let codex_home = thread.codex_home().await;