From edd271d78f21b97179a8969e9db45c44a7d1e011 Mon Sep 17 00:00:00 2001 From: Christopher Sardegna Date: Sat, 10 Oct 2026 12:45:42 -0700 Subject: [PATCH] refactor: clean up OMP summary adapter and related tests; remove unused functions and update documentation --- src/harness/summary.rs | 115 ++------ src/harness/summary_tests.rs | 292 ++++---------------- tests/corpus/README.md | 23 +- tests/corpus/preview_omp_approval.bin | 40 --- tests/corpus/preview_omp_body_hint.bin | 39 --- tests/corpus/preview_omp_idle.bin | 44 +-- tests/corpus/preview_omp_idle_titled.bin | 44 +-- tests/corpus/preview_omp_working.bin | 46 +-- tests/corpus/preview_omp_working_titled.bin | 21 ++ 9 files changed, 147 insertions(+), 517 deletions(-) delete mode 100644 tests/corpus/preview_omp_approval.bin delete mode 100644 tests/corpus/preview_omp_body_hint.bin create mode 100644 tests/corpus/preview_omp_working_titled.bin diff --git a/src/harness/summary.rs b/src/harness/summary.rs index caeec72..2687a2c 100644 --- a/src/harness/summary.rs +++ b/src/harness/summary.rs @@ -20,17 +20,16 @@ //! matcher, distinguish live status from that content: //! //! 1. Locate the chrome region structurally (claude's separator-pair input -//! box, codex's composer, grok's bordered input box, omp's two-row input -//! box) and limit status candidates relative to it. +//! box, codex's composer, grok's bordered input box) and limit status +//! candidates relative to it. //! 2. Return `None` when the expected structure is absent or inconsistent. -//! 3. Preserve CLI-generated ellipsis truncation. For omp, also require the -//! trailing interrupt hint; reject wrapped rows without it. +//! 3. Preserve CLI-generated ellipsis truncation. //! //! Remove spinner glyphs, elapsed counters, throughput data, and key hints during //! normalization; preserve the CLI's status text. The only synthesized status is -//! `awaiting approval`, for approval menus: claude's dialog, codex's modal, and omp's -//! selector. Supported screen structures are recorded in the corpus fixtures in -//! `tests/corpus`. +//! `awaiting approval`, for approval menus (claude's dialog and codex's modal) and +//! for omp's `!` title, which is that harness's only blocked signal. Supported +//! screen structures are recorded in the corpus fixtures in `tests/corpus`. use crate::preview::SummaryAdapter; @@ -754,36 +753,25 @@ fn grok_border_label(row: &str) -> Option { // ------------------------------------------------------------------- omp -- -/// Interrupt-hint suffixes accepted on an anchored status row. -const OMP_HINTS: &[&str] = &["⟦esc⟧", "⟨esc⟩"]; - -/// Selector cursors accepted by [`omp_approve_row`]. Require an exact remainder after -/// the ASCII `>` cursor to exclude quoted prose. -const OMP_CURSORS: &[&str] = &["❯", "\u{f054}", ">"]; - -/// omp inline-UI adapter. Use a two-row `╭…╮`/`╰…╯` input box to locate the nearest -/// painted status row above it. Without the box, check for the approval selector -/// displayed in its place. -/// -/// Locate status with Unicode box corners; ASCII `+` and `-` also occur in -/// transcript tables and rules. Accept the ASCII approval selector when the -/// input box is absent. Do not extract a model label from omp's status line: -/// users can customize that text, so it does not identify a stable model. +/// omp title-only adapter. omp's state reaches the preview through its OSC 0 +/// title, never through its screen: the chrome is user-themeable along three +/// axes (composer shape, symbol preset, status-line segments), so no row +/// structure is stable enough to pin. The title emitter is app-level and +/// unthemed: `π >` idle, `π > label` idle with a session name, `π ⠋…⠏ label` +/// working, `π ! label` blocked. pub struct OmpSummary; impl SummaryAdapter for OmpSummary { - fn live_preview(&self, rows: &[String]) -> Option<(String, &'static str)> { - match omp_input_box(rows) { - Some(top) => omp_spinner_status(rows, top), - // Consider the approval selector only with the input box gone. - None => omp_approval(rows), - } + /// Always `None`: omp has no structural tier. A matcher pinned to one + /// theme stops working silently on the next, so none is kept. + fn live_preview(&self, _rows: &[String]) -> Option<(String, &'static str)> { + None } /// Normalize omp's `π {separator} {label}` and `π: {label}` titles. Extract a - /// nonempty label after `>` or `π:`, normalize braille frames to `⠋`, and retain - /// `!` as the waiting marker. Return `None` for unsupported shapes and empty idle - /// or disabled labels. + /// nonempty label after `>` or `π:`, normalize braille frames to `⠋`, and map + /// `!` to [`AWAITING_APPROVAL`] with or without a label. Return `None` for + /// unsupported shapes and empty idle or disabled labels. fn normalize_title(&self, title: &str) -> Option { if let Some(label) = title.strip_prefix("π: ") { return (!label.is_empty()).then(|| label.to_string()); @@ -797,9 +785,10 @@ impl SummaryAdapter for OmpSummary { }; match sep { '>' => (!label.is_empty()).then(|| label.to_string()), - // `!` and the frame stay: without a label they are the state. - '!' if label.is_empty() => Some("!".to_string()), - '!' => Some(format!("! {label}")), + // `!` is omp's blocked state; the label is the task, not the + // status, so it is dropped for parity with the other harnesses. + '!' => Some(AWAITING_APPROVAL.to_string()), + // The frame stays: without a label it is the state. f if braille_frame(f) && label.is_empty() => Some("⠋".to_string()), f if braille_frame(f) => Some(format!("⠋ {label}")), _ => None, @@ -807,64 +796,6 @@ impl SummaryAdapter for OmpSummary { } } -/// Inspect the bottom-most `╰…╯` row and return its predecessor only when that row is a -/// `╭…╮` border. Require adjacent borders to exclude preview boxes containing a -/// command. -fn omp_input_box(rows: &[String]) -> Option { - let bottom = rows.iter().rposition(|r| box_edge(r, '╰', '╯'))?; - box_edge(rows[..bottom].last()?, '╭', '╮').then(|| bottom - 1) -} - -/// The status row: the first painted row above the input box, shaped `{frame} {phrase} -/// {hint}` one column in. Everything between the frame and the hint is the model's own -/// streamed intent phrase (`Listing directory contents`; `Working…` when the model -/// streams nothing) and is returned verbatim, the CLI's own truncating `…` included. -/// Reject a wrapped row with its hint on the next line: do not extract half a phrase. -fn omp_spinner_status(rows: &[String], top: usize) -> Option<(String, &'static str)> { - let probe = rows[..top].iter().rev().find(|r| !r.is_empty())?; - let rest = after_frame(probe.trim_start(), braille_frame)?; - let text = OMP_HINTS - .iter() - .find_map(|h| rest.strip_suffix(h))? - .strip_suffix(' ')?; - text.chars() - .next()? - .is_alphanumeric() - .then(|| (text.to_string(), "omp:spinner")) -} - -/// omp's approval selector, reached only with the input box gone: an -/// `Allow tool: {name}` head within six rows above the selected `Approve` row, -/// and `Deny` as the next painted row below it. The selection must occupy one -/// of the final nine rows. Prose quoted above a live input box never reaches -/// this matcher. -fn omp_approval(rows: &[String]) -> Option<(String, &'static str)> { - let last = rows.iter().rposition(|r| !r.is_empty())?; - let i = (last.saturating_sub(8)..=last).find(|&i| omp_approve_row(&rows[i]))?; - if rows[i + 1..].iter().find(|r| !r.is_empty())?.trim() != "Deny" { - return None; - } - rows[i.saturating_sub(6)..i] - .iter() - .any(|r| omp_allow_head(r)) - .then(|| (AWAITING_APPROVAL.to_string(), "omp:approval-menu")) -} - -/// Match a selector cursor, a space, and exactly `Approve`. The ASCII `>` also -/// introduces quoted lines, so accepting an arbitrary suffix could match prose. -fn omp_approve_row(row: &str) -> bool { - let t = row.trim(); - OMP_CURSORS - .iter() - .any(|c| t.strip_prefix(c) == Some(" Approve")) -} - -/// The selector's head row: `Allow tool: {name}`. Require the prefix's trailing space: -/// a trimmed row cannot end in a space, so a bare `Allow tool:` is rejected. -fn omp_allow_head(row: &str) -> bool { - row.trim().starts_with("Allow tool: ") -} - #[cfg(test)] #[path = "summary_tests.rs"] mod tests; diff --git a/src/harness/summary_tests.rs b/src/harness/summary_tests.rs index 1f236f3..e1ca8b8 100644 --- a/src/harness/summary_tests.rs +++ b/src/harness/summary_tests.rs @@ -31,14 +31,6 @@ fn grok_screen>(above: &[S]) -> Vec { rows } -/// Place the supplied rows above the two-row omp input box. -fn omp_screen>(above: &[S]) -> Vec { - let mut rows: Vec = above.iter().map(|s| s.as_ref().to_string()).collect(); - rows.push("╭── π > ⬢ model · ◒ high > ◫ 12.2%/131K ▶──╮".to_string()); - rows.push("╰─ ─╯".to_string()); - rows -} - /// Resolve a corpus fixture at the corpus row count and `cols` columns; /// return its text, source, and rule. fn corpus( @@ -126,10 +118,18 @@ fn select_routes_to_the_matching_adapter() { select("grok").unwrap().live_preview(&grok).unwrap().1, "grok:spinner" ); - let omp = omp_screen(&[" ⠴ Listing directory contents ⟦esc⟧", ""]); + // omp routes to a title-only adapter: its 18.8.7 working screen anchors + // nothing, and its state arrives through the title. + let omp = select("omp").unwrap(); + let working = rs(&[ + " ⎋ List files in current directory", + " ⠇ 4s > ◒ Nemotron 3 Ultra (free) > 🗑 …/work ▶─1.0%───╎┃─────1M─", + "╰─", + ]); + assert_eq!(omp.live_preview(&working), None); assert_eq!( - select("omp").unwrap().live_preview(&omp).unwrap().1, - "omp:spinner" + omp.normalize_title("π ⠇ List files in current directory"), + Some("⠋ List files in current directory".to_string()) ); } @@ -1206,164 +1206,9 @@ fn grok_still_running_shapes() { ); } -/// Build an omp approval screen with `head` naming the tool. -fn omp_selector(head: &str) -> Vec { - omp_selector_row(" ❯ Approve", head) -} - -/// Build an approval selector with a configurable selected row. -fn omp_selector_row(approve: &str, head: &str) -> Vec { - let rule = "─".repeat(120); - rs(&[ - " ⠴ Listing directory contents ⟦esc⟧", - "", - &rule, - "", - head, - " Command: ls -la", - "", - approve, - " Deny", - "", - " up/down navigate enter select esc cancel", - "", - &rule, - ]) -} - -/// Preserve the intent phrase verbatim across both anchoring bracket themes, the CLI's -/// own truncating ellipsis included. -#[test] -fn omp_status_row_extracts_the_intent_phrase() { - let probe = |row: &str| OmpSummary.live_preview(&omp_screen(&[row, ""])); - for hint in ["⟦esc⟧", "⟨esc⟩"] { - assert_eq!( - probe(&format!(" ⠴ Listing directory contents {hint}")), - Some(("Listing directory contents".to_string(), "omp:spinner")), - "{hint:?}" - ); - } - // omp's default phrase, used when the model streams no intent of its own. - assert_eq!( - probe(" ⠹ Working… ⟦esc⟧"), - Some(("Working…".to_string(), "omp:spinner")) - ); - // ASCII frames and hints do not satisfy the anchored status grammar. - assert_eq!(probe(" - Working… [esc]"), None); - assert_eq!(probe(" ⠹ Working… [esc]"), None); - assert_eq!( - probe(" ⠋ Reading the fixture corpus rea… ⟦esc⟧"), - Some(("Reading the fixture corpus rea…".to_string(), "omp:spinner")), - "a phrase the CLI truncated keeps its own ellipsis" - ); - // The model text is a user-configured status-line segment: never read. - assert_eq!( - OmpSummary.model_label(&omp_screen(&[" ⠴ Listing directory contents ⟦esc⟧", ""])), - None - ); -} - -/// The status row needs its frame, its separating space, a phrase, and the -/// interrupt hint. A row whose hint wrapped onto the next line fails here -/// rather than surfacing half a phrase. -#[test] -fn omp_status_row_requires_the_whole_skeleton() { - let probe = |row: &str| OmpSummary.live_preview(&omp_screen(&[row, ""])); - for row in [ - " ⠴ Listing directory contents", - " ⠴Listing directory contents ⟦esc⟧", - " ⠴ ⟦esc⟧", - " ⠴ · queued ⟦esc⟧", - " Listing directory contents ⟦esc⟧", - " Press ⟦esc⟧ to interrupt", - ] { - assert_eq!(probe(row), None, "{row:?}"); - } -} - -/// The pin is the row above the input box, not a substring search: a -/// status-shaped row parked in the transcript never anchors, and the -/// tool-call preview box's matching corners are not the input box. -#[test] -fn omp_pins_the_status_row_to_the_input_box() { - let quoted = omp_screen(&[ - " ⠴ Listing directory contents ⟦esc⟧", - "", - " That row is chrome, not transcript.", - "", - ]); - assert_eq!(OmpSummary.live_preview("ed), None); - - // The preview box fences a `│`-headed command row between the same - // corners, so the pair is not adjacent and the pin fails. - let preview_box = rs(&[ - " ⠴ Listing directory contents ⟦esc⟧", - "", - "╭──────────────────╮", - "│ $ ls -la │", - "╰──────────────────╯", - ]); - assert_eq!(OmpSummary.live_preview(&preview_box), None); -} - -/// Approval requires an `Allow tool:` head, a selected `Approve` row, and the -/// `Deny` sibling below it. A spinner above the selector does not win. -#[test] -fn omp_approval_requires_the_selector_shape() { - assert_eq!( - OmpSummary.live_preview(&omp_selector(" Allow tool: bash")), - Some(("awaiting approval".to_string(), "omp:approval-menu")) - ); - for head in [" Allow tool: ", " Reviewing the plan"] { - assert_eq!( - OmpSummary.live_preview(&omp_selector(head)), - None, - "{head:?}" - ); - } - - // `Deny` must be the next painted row below the selection. - let lone = rs(&[" Allow tool: bash", "", " ❯ Approve", "", " esc cancel"]); - assert_eq!(OmpSummary.live_preview(&lone), None); - - // The selection must occupy one of the final nine rows. - let mut buried = omp_selector(" Allow tool: bash"); - buried.extend(std::iter::repeat_n(" tool output".to_string(), 6)); - assert_eq!(OmpSummary.live_preview(&buried), None); - - // A live input box below the quoted selector routes to spinner matching. - // The selector's trailing rule is the nearest painted row and fails. - let mut quoted = omp_selector(" Allow tool: bash"); - quoted.push(String::new()); - assert_eq!(OmpSummary.live_preview(&omp_screen("ed)), None); -} - -/// Every supported selector cursor reports a blocked task. -#[test] -fn omp_approval_accepts_every_cursor_preset() { - for cursor in ['❯', '\u{f054}', '>'] { - assert_eq!( - OmpSummary.live_preview(&omp_selector_row( - &format!(" {cursor} Approve"), - " Allow tool: bash" - )), - Some(("awaiting approval".to_string(), "omp:approval-menu")), - "{cursor:?}" - ); - } - // The selected row must contain `Approve` exactly after the cursor: `>` is also - // used for quoted lines; accept only this exact shape. - for approve in [" > Approve now", " >Approve", " > approve", " * Approve"] { - assert_eq!( - OmpSummary.live_preview(&omp_selector_row(approve, " Allow tool: bash")), - None, - "{approve:?}" - ); - } -} - /// omp title normalization strips idle and disabled-state prefixes, folds -/// working frames to `⠋`, preserves `!`, and rejects unsupported shapes. +/// working frames to `⠋`, maps `!` to the blocked status, and rejects +/// unsupported shapes. #[test] fn omp_title_separators_decode_state_and_label() { assert_eq!( @@ -1385,12 +1230,15 @@ fn omp_title_separators_decode_state_and_label() { "a label-less frame is still the working state" ); - // The waiting marker is preserved with or without a label. - assert_eq!( - OmpSummary.normalize_title("π ! Fix the flaky test"), - Some("! Fix the flaky test".to_string()) - ); - assert_eq!(OmpSummary.normalize_title("π !"), Some("!".to_string())); + // `!` is the blocked state, with or without a label: a blocked omp reads + // like a blocked claude, codex, or grok. + for title in ["π ! List files in current directory", "π !"] { + assert_eq!( + OmpSummary.normalize_title(title), + Some(AWAITING_APPROVAL.to_string()), + "{title:?}" + ); + } // The disabled-state form yields its label. assert_eq!( @@ -1412,19 +1260,6 @@ fn omp_title_separators_decode_state_and_label() { } } -/// ASCII box glyphs do not anchor status: transcript tables and rules use the -/// same glyphs. -#[test] -fn omp_ascii_box_glyphs_do_not_anchor() { - let ascii_box = rs(&[ - " - Listing directory contents [esc]", - "", - "+-- pi > model . high > 12.2%/131K --+", - "+- -+", - ]); - assert_eq!(OmpSummary.live_preview(&ascii_box), None); -} - // ------------------------------------------------------- corpus replay -- /// Each offset is a cumulative PTY byte checkpoint, not a standalone repaint. @@ -1596,9 +1431,9 @@ fn corpus_codex_0158_stdin_requires_a_second_approval() { } /// Positive per-state fixtures at capture geometry (40×120): exact -/// normalized text, Anchor provenance, and the matcher id. +/// normalized text and provenance, with the matcher id for Anchor cases. #[test] -fn corpus_positive_states_anchor_exactly() { +fn corpus_positive_states_resolve_exactly() { struct Case( &'static str, &'static [u8], @@ -1709,28 +1544,31 @@ fn corpus_positive_states_anchor_exactly() { "Grok 4.5 (xhigh) · 1 subagent still running", "grok:still-running", ), - Case( - "preview_omp_working", - include_bytes!("../../tests/corpus/preview_omp_working.bin"), + ]; + // Title-tier positives: a retained primary title the adapter recognizes, + // on a screen that anchors nothing. + let titles: [(&str, &[u8], &dyn SummaryAdapter, &str); 2] = [ + ( + "preview_omp_idle_titled", + include_bytes!("../../tests/corpus/preview_omp_idle_titled.bin"), &OmpSummary, - "Listing directory contents", - "omp:spinner", + "fix the parser", ), - Case( - "preview_omp_approval", - include_bytes!("../../tests/corpus/preview_omp_approval.bin"), + ( + "preview_omp_working_titled", + include_bytes!("../../tests/corpus/preview_omp_working_titled.bin"), &OmpSummary, - "awaiting approval", - "omp:approval-menu", + "⠋ List files in current directory", ), ]; // Fixture names start with the program word: require a positive case for - // every registered harness. + // every registered harness, in whichever tier carries its state. for a in crate::harness::AGENTS { let program = a.harness.shape().0; let prefix = format!("preview_{program}_"); assert!( - cases.iter().any(|Case(name, ..)| name.starts_with(&prefix)), + cases.iter().any(|Case(name, ..)| name.starts_with(&prefix)) + || titles.iter().any(|(name, ..)| name.starts_with(&prefix)), "a new adapter needs a positive corpus fixture named {prefix}*" ); } @@ -1738,6 +1576,10 @@ fn corpus_positive_states_anchor_exactly() { let got = corpus(bytes, adapter, 120); assert_eq!(got, anchor(text, rule), "{name}"); } + for (name, bytes, adapter, text) in titles { + let got = corpus(bytes, adapter, 120); + assert_eq!(got, titled(text), "{name}"); + } } /// Idle and post-turn screens have no anchor and resolve to the @@ -1772,28 +1614,24 @@ fn corpus_idle_states_fall_through() { } } -/// Without a title announce, the omp idle screen falls through to its input -/// row in the floor tier. +/// Without a title announce, omp's 18.8.7 screens fall through to the +/// composer gutter in the floor tier: nothing anchors on the idle band, and +/// nothing anchors on the working row or band either. #[test] -fn corpus_omp_idle_falls_through_to_the_floor() { - let got = corpus( - include_bytes!("../../tests/corpus/preview_omp_idle.bin"), - &OmpSummary, - 120, - ); - assert_eq!(got, floor(&format!("╰─{}─╯", " ".repeat(116)))); -} - -/// An omp idle screen with a retained `π >