Move checkpoint latch to bridge

This commit is contained in:
Meta Vens 2026-05-17 20:17:54 +01:00
commit 0dd4e79349
6 changed files with 86 additions and 60 deletions

View file

@ -0,0 +1,61 @@
#[derive(Default)]
pub struct EpiphanyCheckpointInterventionState {
last_intervention_turn_id: Option<String>,
pending_compaction_turn_id: Option<String>,
}
impl EpiphanyCheckpointInterventionState {
pub fn record_intervention(&mut self, turn_id: &str) -> bool {
if self.last_intervention_turn_id.as_deref() == Some(turn_id) {
return false;
}
self.last_intervention_turn_id = Some(turn_id.to_string());
true
}
pub fn mark_pending_compaction(&mut self, turn_id: &str) {
self.pending_compaction_turn_id = Some(turn_id.to_string());
}
pub fn take_pending_compaction(&mut self, turn_id: &str) -> bool {
if self.pending_compaction_turn_id.as_deref() != Some(turn_id) {
return false;
}
self.pending_compaction_turn_id = None;
true
}
pub fn clear_pending_compaction(&mut self) {
self.pending_compaction_turn_id = None;
}
}
#[cfg(test)]
mod tests {
use super::EpiphanyCheckpointInterventionState;
#[test]
fn pending_compaction_is_turn_scoped() {
let mut state = EpiphanyCheckpointInterventionState::default();
assert!(state.record_intervention("turn-a"));
assert!(!state.record_intervention("turn-a"));
assert!(!state.take_pending_compaction("turn-a"));
state.mark_pending_compaction("turn-a");
assert!(!state.take_pending_compaction("turn-b"));
assert!(state.take_pending_compaction("turn-a"));
assert!(!state.take_pending_compaction("turn-a"));
}
#[test]
fn pending_compaction_can_be_cleared() {
let mut state = EpiphanyCheckpointInterventionState::default();
state.mark_pending_compaction("turn-a");
state.clear_pending_compaction();
assert!(!state.take_pending_compaction("turn-a"));
}
}

View file

@ -1,3 +1,4 @@
pub mod checkpoint;
pub mod context;
pub mod coordinator;
pub mod invalidation;

View file

@ -797,6 +797,12 @@ request. `epiphany_automation.rs` still gathers live host facts and executes
the host side effects: submit `Op::Compact`, call `epiphany_launch_job`, and
emit JSON-RPC state-update notifications.
The pre-compaction checkpoint intervention latch has also left app-server
ownership. `epiphany-codex-bridge::checkpoint::EpiphanyCheckpointInterventionState`
owns the one-steer-per-turn and same-turn pending-compaction invariant; vendored
`ThreadState` only stores and delegates the latch while event handling remains
host lifecycle glue.
The native OpenAI auth spine overcut has been corrected. It no longer owns a
clone of Codex keyring/file/env auth or ChatGPT token refresh; it re-exports
vendored `codex-login` and carries the Codex workspace tungstenite patches

Binary file not shown.

File diff suppressed because one or more lines are too long

View file

@ -9,6 +9,7 @@ use codex_core::ThreadConfigSnapshot;
use codex_protocol::ThreadId;
use codex_protocol::protocol::EventMsg;
use codex_utils_absolute_path::AbsolutePathBuf;
use epiphany_codex_bridge::checkpoint::EpiphanyCheckpointInterventionState;
use std::collections::HashMap;
use std::collections::HashSet;
use std::path::PathBuf;
@ -65,8 +66,7 @@ pub(crate) struct ThreadState {
pub(crate) listener_generation: u64,
listener_command_tx: Option<mpsc::UnboundedSender<ThreadListenerCommand>>,
current_turn_history: ThreadHistoryBuilder,
last_epiphany_checkpoint_intervention_turn_id: Option<String>,
pending_epiphany_checkpoint_compaction_turn_id: Option<String>,
epiphany_checkpoint_intervention: EpiphanyCheckpointInterventionState,
listener_thread: Option<Weak<CodexThread>>,
}
@ -99,7 +99,8 @@ impl ThreadState {
}
self.listener_command_tx = None;
self.current_turn_history.reset();
self.pending_epiphany_checkpoint_compaction_turn_id = None;
self.epiphany_checkpoint_intervention
.clear_pending_compaction();
self.listener_thread = None;
}
@ -130,71 +131,29 @@ impl ThreadState {
}
pub(crate) fn record_epiphany_checkpoint_intervention(&mut self, turn_id: &str) -> bool {
if self
.last_epiphany_checkpoint_intervention_turn_id
.as_deref()
== Some(turn_id)
{
return false;
}
self.last_epiphany_checkpoint_intervention_turn_id = Some(turn_id.to_string());
true
self.epiphany_checkpoint_intervention
.record_intervention(turn_id)
}
pub(crate) fn mark_epiphany_checkpoint_intervention_pending_compaction(
&mut self,
turn_id: &str,
) {
self.pending_epiphany_checkpoint_compaction_turn_id = Some(turn_id.to_string());
self.epiphany_checkpoint_intervention
.mark_pending_compaction(turn_id);
}
pub(crate) fn take_epiphany_checkpoint_intervention_pending_compaction(
&mut self,
turn_id: &str,
) -> bool {
if self
.pending_epiphany_checkpoint_compaction_turn_id
.as_deref()
!= Some(turn_id)
{
return false;
}
self.pending_epiphany_checkpoint_compaction_turn_id = None;
true
self.epiphany_checkpoint_intervention
.take_pending_compaction(turn_id)
}
pub(crate) fn clear_epiphany_checkpoint_intervention_pending_compaction(&mut self) {
self.pending_epiphany_checkpoint_compaction_turn_id = None;
}
}
#[cfg(test)]
mod tests {
use super::ThreadState;
#[test]
fn epiphany_checkpoint_intervention_pending_compaction_is_turn_scoped() {
let mut state = ThreadState::default();
assert!(state.record_epiphany_checkpoint_intervention("turn-a"));
assert!(!state.record_epiphany_checkpoint_intervention("turn-a"));
assert!(!state.take_epiphany_checkpoint_intervention_pending_compaction("turn-a"));
state.mark_epiphany_checkpoint_intervention_pending_compaction("turn-a");
assert!(!state.take_epiphany_checkpoint_intervention_pending_compaction("turn-b"));
assert!(state.take_epiphany_checkpoint_intervention_pending_compaction("turn-a"));
assert!(!state.take_epiphany_checkpoint_intervention_pending_compaction("turn-a"));
}
#[test]
fn epiphany_checkpoint_intervention_pending_compaction_can_be_cleared() {
let mut state = ThreadState::default();
state.mark_epiphany_checkpoint_intervention_pending_compaction("turn-a");
state.clear_epiphany_checkpoint_intervention_pending_compaction();
assert!(!state.take_epiphany_checkpoint_intervention_pending_compaction("turn-a"));
self.epiphany_checkpoint_intervention
.clear_pending_compaction();
}
}