Move freshness surface to CultNet types

This commit is contained in:
Meta Vens 2026-05-19 16:35:58 +01:00
commit 37ea4ff2c3
6 changed files with 168 additions and 22 deletions

View file

@ -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<u64>,
pub retrieval: EpiphanyRetrievalFreshness,
pub graph: EpiphanyGraphFreshness,
pub watcher: EpiphanyInvalidationInput,
}

View file

@ -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;

View file

@ -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<EpiphanyFreshnessWatcherSnapshot<'_>>,
) -> 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(

View file

@ -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<EpiphanyFreshnessWatcherSnapshot<'a>>,
}
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,
}
}

View file

@ -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<bool>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub last_indexed_at_unix_seconds: Option<i64>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub indexed_file_count: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub indexed_chunk_count: Option<u32>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub dirty_paths: Vec<PathBuf>,
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<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub checkpoint_id: Option<String>,
pub dirty_path_count: u32,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub dirty_paths: Vec<PathBuf>,
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<PathBuf>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub observed_at_unix_seconds: Option<i64>,
pub changed_path_count: u32,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub changed_paths: Vec<PathBuf>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub graph_node_ids: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub active_frontier_node_ids: Vec<String>,
pub note: String,
}
@ -78,8 +99,10 @@ pub struct EpiphanyFreshnessInput<'a> {
pub watcher: Option<EpiphanyFreshnessWatcherInput<'a>>,
}
#[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<u64>,
pub retrieval: EpiphanyRetrievalFreshness,
pub graph: EpiphanyGraphFreshness,

View file

@ -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;