Add bounded runtime actor and live PTY pane
CI / rust (push) Successful in 3m26s

This commit is contained in:
2026-08-31 16:32:50 -07:00
parent ab7543b9c6
commit 32d190c6c6
15 changed files with 1111 additions and 39 deletions
+104 -3
View File
@@ -81,6 +81,12 @@ pub enum PaneStatus {
Ready,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum OutputSource {
Deterministic,
External,
}
impl PaneStatus {
#[must_use]
pub const fn label(self) -> &'static str {
@@ -209,6 +215,7 @@ pub struct PaneState {
status_before_input: Option<PaneStatus>,
lines: Vec<String>,
synthetic_line_sequence: u64,
output_source: OutputSource,
}
impl PaneState {
@@ -240,6 +247,10 @@ impl PaneState {
pub const fn synthetic_line_sequence(&self) -> u64 {
self.synthetic_line_sequence
}
#[must_use]
pub const fn output_source(&self) -> OutputSource {
self.output_source
}
}
#[derive(Clone, Debug, Default, Eq, PartialEq)]
@@ -276,6 +287,7 @@ pub enum ShellAction {
CloseCommandPalette,
SetCommandPaletteQuery(String),
SyntheticStreamTick,
AppendExternalOutput { pane: PaneId, lines: Vec<String> },
}
/// Counters prove both candidates replayed the same workload. Framework
@@ -294,6 +306,8 @@ pub struct MeasurementCounters {
/// Total surfaces invalidated by the deterministic six-surface workload.
pub surface_updates: u64,
pub terminal_lines_appended: u64,
pub external_output_batches: u64,
pub external_lines_appended: u64,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
@@ -315,6 +329,16 @@ impl fmt::Display for InvalidTerminalLineLimit {
}
impl std::error::Error for InvalidTerminalLineLimit {}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct InvalidExternalOutputPane;
impl fmt::Display for InvalidExternalOutputPane {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter.write_str("external output requires a terminal pane")
}
}
impl std::error::Error for InvalidExternalOutputPane {}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ShellModel {
panes: [PaneState; GRID_ROWS * GRID_COLUMNS],
@@ -350,6 +374,7 @@ impl ShellModel {
.then_some(PaneStatus::Working),
lines,
synthetic_line_sequence: 0,
output_source: OutputSource::Deterministic,
}
});
Ok(Self {
@@ -363,6 +388,22 @@ impl ShellModel {
})
}
/// Creates the comparison model with exactly one actor-driven terminal.
///
/// # Errors
///
/// Returns [`InvalidExternalOutputPane`] when `pane` is not a terminal.
pub fn with_external_output(pane: PaneId) -> Result<Self, InvalidExternalOutputPane> {
let mut model = Self::default();
let pane_state = &mut model.panes[pane.index()];
if pane_state.kind() != SurfaceKind::Terminal {
return Err(InvalidExternalOutputPane);
}
pane_state.output_source = OutputSource::External;
pane_state.lines.clear();
Ok(model)
}
#[must_use]
pub fn panes(&self) -> &[PaneState; GRID_ROWS * GRID_COLUMNS] {
&self.panes
@@ -457,7 +498,11 @@ impl ShellModel {
self.synthetic_tick += 1;
self.counters.synthetic_ticks += 1;
for pane in &mut self.panes {
if pane.output_source == OutputSource::External {
continue;
}
pane.synthetic_line_sequence += 1;
self.counters.surface_updates += 1;
if pane.kind() != SurfaceKind::Terminal {
continue;
}
@@ -472,10 +517,25 @@ impl ShellModel {
}
appended += 1;
}
self.counters.surface_updates += self.panes.len() as u64;
self.counters.terminal_lines_appended += appended as u64;
changed = true;
}
ShellAction::AppendExternalOutput { pane, lines } => {
let pane = &mut self.panes[pane.index()];
if pane.output_source == OutputSource::External && !lines.is_empty() {
appended = lines.len();
pane.lines.extend(lines);
if pane.lines.len() > self.terminal_line_limit {
pane.lines
.drain(..pane.lines.len() - self.terminal_line_limit);
}
self.counters.surface_updates += 1;
self.counters.terminal_lines_appended += appended as u64;
self.counters.external_output_batches += 1;
self.counters.external_lines_appended += appended as u64;
changed = true;
}
}
}
if changed {
self.revision += 1;
@@ -511,8 +571,8 @@ pub const fn focus_neighbor(pane: PaneId, direction: FocusDirection) -> Option<P
#[cfg(test)]
mod tests {
use super::{
FocusDirection, GRID_COLUMNS, GRID_ROWS, PANES, PaneId, PaneStatus, ShellAction,
ShellModel, SurfaceKind, focus_neighbor,
FocusDirection, GRID_COLUMNS, GRID_ROWS, OutputSource, PANES, PaneId, PaneStatus,
ShellAction, ShellModel, SurfaceKind, focus_neighbor,
};
#[test]
@@ -699,6 +759,47 @@ mod tests {
assert!(ShellModel::with_terminal_line_limit(0).is_err());
}
#[test]
fn one_external_terminal_leaves_five_deterministic_surfaces() {
let mut model = ShellModel::with_external_output(PaneId::CodexRuntime).unwrap();
assert_eq!(
model.pane(PaneId::CodexRuntime).output_source(),
OutputSource::External
);
assert!(model.pane(PaneId::CodexRuntime).lines().is_empty());
let tick = model.dispatch(ShellAction::SyntheticStreamTick);
assert_eq!(tick.terminal_lines_appended, 2);
assert!(model.pane(PaneId::CodexRuntime).lines().is_empty());
assert_eq!(model.counters().surface_updates, 5);
let output = model.dispatch(ShellAction::AppendExternalOutput {
pane: PaneId::CodexRuntime,
lines: vec!["runtime ready".into(), "pty line".into()],
});
assert!(output.changed);
assert_eq!(output.terminal_lines_appended, 2);
assert_eq!(
model.pane(PaneId::CodexRuntime).lines(),
["runtime ready", "pty line"]
);
assert_eq!(model.counters().surface_updates, 6);
assert_eq!(model.counters().external_output_batches, 1);
assert_eq!(model.counters().external_lines_appended, 2);
}
#[test]
fn external_output_rejects_nonterminals_and_deterministic_panes() {
assert!(ShellModel::with_external_output(PaneId::Architecture).is_err());
let mut model = ShellModel::default();
let outcome = model.dispatch(ShellAction::AppendExternalOutput {
pane: PaneId::CodexRuntime,
lines: vec!["must not replace fixture output".into()],
});
assert!(!outcome.changed);
assert_eq!(model.counters().external_output_batches, 0);
}
#[test]
fn identical_action_replays_produce_identical_models_and_counters() {
let actions = [