Skip to content

broker: make detect_cli_ready grid-aware#872

Closed
willwashburn wants to merge 8 commits into
mainfrom
will/868-grid-aware-readiness
Closed

broker: make detect_cli_ready grid-aware#872
willwashburn wants to merge 8 commits into
mainfrom
will/868-grid-aware-readiness

Conversation

@willwashburn

Copy link
Copy Markdown
Member

Summary

Stacked on #837. Depends on #837 landing first.

Fixes #868.

  • add a rendered-grid readiness snapshot built from PtySession::screen_text() and PtySession::cursor_position()
  • route production startup readiness through the live VT grid instead of accumulated stripped bytes
  • keep the relaycast boot gate strict: a prompt must appear after boot and be visible in the current grid
  • keep the old accumulated-byte detector as test-only fallback coverage

Testing

  • cargo fmt
  • cargo check
  • cargo test detect_cli_ready
  • cargo test startup_gate
  • cargo test

SlncTrZ and others added 8 commits May 11, 2026 02:29
…ess (steal from ht)

Fixes #800

Signed-off-by: Dinh Truong (SlncTrZ) <46520299+SlncTrZ@users.noreply.github.com>
Port the wait taxonomy from montanaflynn/headless-terminal's
`internal/wait/wait.go` (#800) into a new `src/wait.rs` module:

  - `WaitCondition` enum: Text (substring or regex), Idle, Change,
    Exit, Cursor (kept as a stub — see issue body — until the VT100
    grid lands).
  - `WaitSet` AND-composes multiple conditions; chaining builders
    (`text`, `text_regex`, `idle`, `change`, `exit`, `cursor`).
  - `WaitState` streaming evaluator with `feed`/`feed_raw`/`mark_exited`
    and a `check`/`is_ready` query. Trigger priority mirrors ht
    (Exit > Text > Cursor > Idle > Change).
  - `WaitSet::evaluate(&WaitSnapshot)` for synchronous callers that
    already hold accumulated output and timing.
  - Per-CLI pre-built wait sets in `wait::for_cli` (claude / gemini /
    codex / generic + dispatch) so new readiness call sites have a
    declarative replacement for the per-CLI substring rules in
    `helpers::detect_cli_ready`. Rolling `detect_cli_ready` itself
    onto these builders is a behavior-preserving follow-up.

17 unit tests cover each primitive, AND composition, idle reset on
chunk, regex compile errors, ANSI-stripping feed, trigger priority,
and the per-CLI builders (including claude's onboarding-menu false
positive). Full broker suite: 336 passed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Treat ht as inspiration rather than a hard reference — no need to peg
the module to upstream's naming or evolution.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace the per-CLI substring/regex blocks inside
helpers::detect_cli_ready with calls into the shared wait::for_cli
builders. The per-CLI text rules now live in exactly one place —
src/wait.rs — and detect_cli_ready becomes a thin polling-style
caller that constructs windowed snapshots, dispatches to the right
WaitSet, and keeps the three pieces that don't fit the AND-composed
primitive inline: the explicit `->pty:ready` signal, gemini's
"waiting for auth" negative check, and the byte-count startup
fallback for non-claude / non-gemini CLIs.

Side changes:

- `wait::for_cli::*()` builders are now text-only (no implicit
  `.idle(IDLE_SETTLE)`), so they compose cleanly with both
  polling callers and streaming callers. Streaming callers can
  chain `.idle(...)` directly or use the new
  `wait::for_cli::with_settle(...)` convenience.
- Codex / generic text rules use substring-style regex alternation
  (`(> |\$ |...|❯)`) to preserve detect_cli_ready's historical
  loose substring matching exactly.

All 17 detect_cli_ready tests pass unchanged. Full broker suite:
337 passed (was 336 before this PR; +1 wait::for_cli::with_settle
test).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace "tracked separately" with the actual issue number now that I
have it. No behavior change.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@willwashburn willwashburn requested a review from khaliqgant as a code owner May 17, 2026 16:29
@coderabbitai

coderabbitai Bot commented May 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR implements grid-aware CLI readiness detection by introducing a composable wait-condition system (WaitSet, WaitState, WaitSnapshot) and refactoring prompt-matching logic to query live VT screen content and cursor position rather than best-effort byte-stream reconstruction, then integrating grid snapshots throughout PTY worker startup gating.

Changes

Grid-Aware CLI Readiness Detection

Layer / File(s) Summary
WaitCondition and WaitSet abstractions
src/wait.rs
WaitCondition primitives (text, cursor, idle, change, exit) and WaitSet builder compose multiple conditions; WaitSnapshot provides synchronous evaluation against screen text and optional cursor state.
WaitState streaming and Trigger resolution
src/wait.rs
WaitState accumulates ANSI-stripped output, tracks idle/change/exit/cursor flags, and evaluates snapshots; Trigger enum with deterministic priority resolves which condition fired when multiple are satisfied simultaneously (Exit > Text > Cursor > Idle > Change).
CLI-specific readiness presets
src/wait.rs
The for_cli module provides declarative WaitSet presets (Claude/Gemini/Codex/generic) encoding CLI-specific prompt-matching rules; helpers compose exit gating and "burst then idle" settle windows.
Wait system test suite
src/wait.rs
Comprehensive tests covering text/regex matching (including multiline), idle and change reset semantics, exit gating, cursor position matching/clearing, AND-composition with priority, ANSI stripping, CLI preset correctness, settle chaining, and snapshot/streaming equivalence.
Grid-aware detection helpers
src/helpers.rs
GridReadinessSnapshot wraps visible screen text and optional cursor; detect_cli_ready_with_grid and cli_prompt_ready_with_grid evaluate readiness using live screen content (via WaitSnapshot builders) instead of accumulated stripped bytes; preserves ->pty:ready fast-path.
Grid-aware readiness tests
src/helpers.rs
Tests validate that visible grid screen takes precedence over stale bytes, generic prompts are detected on grid, Claude requires cursor on bare prompt row (rejecting menu states), and Gemini rejects "waiting for auth" while accepting the compose prompt.
PTY worker grid-based startup gating
src/pty_worker.rs
New startup_gate_ready_with_grid and startup_gate_ready_from_pty build grid snapshots from PTY screen text and cursor; all three startup-readiness checkpoints (init handler, PTY output, verification tick) now use grid-backed path; old non-grid variant marked #[cfg(test)] only.
Wait module declaration
src/main.rs
Registers the new wait module to enable compilation and crate-wide access.

Sequence Diagram

sequenceDiagram
  participant PtyWorker as PTY Worker
  participant PtySession as PtySession (screen/cursor)
  participant GridSnapshot as GridReadinessSnapshot
  participant Detection as Detection Function
  participant WaitSnapshot as WaitSnapshot
  participant CliPreset as for_cli Preset
  participant Result

  PtyWorker->>PtySession: capture screen_text()
  PtyWorker->>PtySession: capture cursor_position()
  PtyWorker->>GridSnapshot: build snapshot(screen, cursor)
  PtyWorker->>Detection: detect_cli_ready_with_grid(cli, grid)
  Detection->>WaitSnapshot: builder from grid
  Detection->>CliPreset: dispatch preset (Claude/Gemini/etc.)
  CliPreset->>WaitSnapshot: evaluate conditions (text/cursor)
  WaitSnapshot->>Result: return satisfied / not satisfied
  Detection->>Result: return readiness bool
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

  • #868: This PR directly implements the proposal to make detect_cli_ready grid-aware by introducing GridReadinessSnapshot, grid-backed detection functions, and integrating live screen/cursor queries into the readiness path.
  • #800: The main implementation of the composable WaitSet/WaitCondition system and per-CLI preset builders that were proposed in this issue.

Possibly related PRs

  • AgentWorkforce/relay#836: Enables the grid-aware readiness gating in this PR by providing PtySession with screen_text() and cursor_position() queries backed by the alacritty Term VT grid.

Suggested reviewers

  • khaliqgant

Poem

A rabbit hops through terminal streams,
Where grid-aware prompts gleam,
No byte-stream haze, just visible light—
The cursor knows it's ready! ✨🐰

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'broker: make detect_cli_ready grid-aware' directly and concisely describes the main change: introducing grid-aware readiness detection for detect_cli_ready function.
Description check ✅ Passed The description covers the main objectives with clear references to the linked issue and dependencies, testing commands listed, and key changes outlined. Required sections from template are mostly addressed.
Linked Issues check ✅ Passed All coding requirements from issue #868 are met: grid-aware WaitSnapshot with cursor evaluation, grid-backed detect_cli_ready, per-CLI matching against live screen_text(), and pty_worker integration with grid snapshots.
Out of Scope Changes check ✅ Passed All changes directly support the stated objective: new wait.rs module, GridReadinessSnapshot, grid-aware helpers.rs functions, and pty_worker integration with grid snapshots. No unrelated changes detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch will/868-grid-aware-readiness

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/wait.rs`:
- Around line 174-188: The text_only constructor for WaitSnapshot sets idle_for
= Duration::MAX and change_seen = true which silently satisfies settle/burst
conditions (affecting with_settle(...) and burst_then_idle(...)) for no-grid
callers; change text_only (or replace it with a new constructor) so these
timing/change fields default to an unsatisfied state (e.g., idle_for =
Duration::ZERO or a sentinel representing "not idling" and change_seen = false)
or require callers to supply explicit timing/change state, and update references
to WaitSnapshot::text_only and any tests using it so WaitState semantics remain
consistent for offline/test fallback paths.
- Around line 160-172: WaitSnapshot currently only exposes flattened screen text
and cursor pos (screen, cursor) which can't verify a prompt at the live cell;
add a minimal cell-aware view to the snapshot (e.g. a reference field like grid:
&'a Grid or cells: &'a [CellRow]/&'a [Cell] that exposes per-cell content and
attributes) and update WaitSet::evaluate to populate this new field so callers
can inspect the exact cell under the cursor when checking for prompts rather
than relying on regexes over screen; keep existing fields (screen, cursor,
idle_for, change_seen, exited) and ensure the new field is a lightweight
borrowed reference (no cloning) so wait logic remains grid-backed.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 45f53e25-441c-48e9-bcc5-057486e322e4

📥 Commits

Reviewing files that changed from the base of the PR and between 9cee216 and 00a60bd.

📒 Files selected for processing (4)
  • src/helpers.rs
  • src/main.rs
  • src/pty_worker.rs
  • src/wait.rs

Comment thread src/wait.rs
Comment on lines +160 to +172
/// Snapshot of the world used by [`WaitSet::evaluate`].
pub struct WaitSnapshot<'a> {
/// ANSI-stripped screen text accumulated so far.
pub screen: &'a str,
/// How long since the last output chunk arrived. Used by `Idle`.
pub idle_for: Duration,
/// Whether any output chunk has been observed.
pub change_seen: bool,
/// Whether the process has exited.
pub exited: bool,
/// Current 1-indexed cursor position from the PTY grid, if known.
pub cursor: Option<(u16, u16)>,
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

WaitSnapshot still can't express cell-aware prompt checks.

screen plus (row, col) is not enough to verify that the live cursor is sitting on a bare Claude prompt in the visible grid. As written, the CLI preset still has to fall back to regexes over flattened text, so a standalone / > line elsewhere on screen can satisfy readiness even when the input cursor is not on that row. Please carry a minimal grid/cell view through the snapshot so the wait layer can keep Claude fully grid-backed instead of pushing that logic back into ad hoc helpers.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/wait.rs` around lines 160 - 172, WaitSnapshot currently only exposes
flattened screen text and cursor pos (screen, cursor) which can't verify a
prompt at the live cell; add a minimal cell-aware view to the snapshot (e.g. a
reference field like grid: &'a Grid or cells: &'a [CellRow]/&'a [Cell] that
exposes per-cell content and attributes) and update WaitSet::evaluate to
populate this new field so callers can inspect the exact cell under the cursor
when checking for prompts rather than relying on regexes over screen; keep
existing fields (screen, cursor, idle_for, change_seen, exited) and ensure the
new field is a lightweight borrowed reference (no cloning) so wait logic remains
grid-backed.

Comment thread src/wait.rs
Comment on lines +174 to +188
impl<'a> WaitSnapshot<'a> {
/// Build a text-only snapshot for callers that do not have a VT grid.
///
/// Idle/Change are treated as satisfied so only `Text` conditions
/// participate. `Cursor` conditions remain unsatisfied because no
/// cursor position is available.
pub fn text_only(screen: &'a str) -> Self {
Self {
screen,
idle_for: Duration::MAX,
change_seen: true,
exited: false,
cursor: None,
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

text_only() silently disables settle/burst semantics.

This constructor hardcodes idle_for = Duration::MAX and change_seen = true, so with_settle(...) and burst_then_idle(...) become immediately satisfied for no-grid callers. That makes snapshot evaluation behave differently from WaitState exactly on the offline/test fallback path this PR keeps around. Please default these fields to unsatisfied, or require callers to pass explicit timing/change state when they want snapshot evaluation.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/wait.rs` around lines 174 - 188, The text_only constructor for
WaitSnapshot sets idle_for = Duration::MAX and change_seen = true which silently
satisfies settle/burst conditions (affecting with_settle(...) and
burst_then_idle(...)) for no-grid callers; change text_only (or replace it with
a new constructor) so these timing/change fields default to an unsatisfied state
(e.g., idle_for = Duration::ZERO or a sentinel representing "not idling" and
change_seen = false) or require callers to supply explicit timing/change state,
and update references to WaitSnapshot::text_only and any tests using it so
WaitState semantics remain consistent for offline/test fallback paths.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No issues found across 4 files

Re-trigger cubic

@devin-ai-integration devin-ai-integration Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no potential bugs to report.

View in Devin Review to see 4 additional findings.

Open in Devin Review

@willwashburn

Copy link
Copy Markdown
Member Author

Superseded by the correctly stacked PR targeting #837: SlncTrZ#1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

broker: make detect_cli_ready grid-aware

2 participants