pub enum PtyScreenState {
Unknown,
NotAgent {
observed_process: String,
},
OperatorGate {
gate: OperatorGateState,
},
Failing {
reason: String,
},
Ready,
}Expand description
Whether the screen currently painted at a PTY looks like the agent’s own
composer, as classified by the node from the same terminal text a human
would read. This is a screen-content judgement, never a process-liveness
one – SessionStatus already answers “is something running”, and a
consumer must not fold the two into a single “is it running” question:
a process can be Running while its screen sits on an unrelated
installer prompt, and that combination is exactly the case this type
exists to distinguish.
Variants§
Unknown
No observation has been made for this generation yet, or the most
recent observation attempt failed. A caller deciding whether to write
to the PTY blindly must treat this exactly like NotAgent – there is
no “probably fine” reading of “unclassified”. Never optimistic.
NotAgent
The PTY’s foreground process is neither the agent’s own binary nor a
tolerated wrapper that spawns it. observed_process records what was
actually seen there, so an operator reading this gets “an update is
installing” rather than a bare timeout with no explanation.
OperatorGate
The foreground process matches, but the screen itself is showing a
recognized blocking pattern – workspace trust, authentication,
a vendor update, first-run onboarding. This is the state that wants a
human specifically, because resolving it means typing into the pane
rather than dispatching another agent turn. gate is a structured
OperatorGateState, not a bare label – what TYPE of gate
(kind), what it is gating (subject), how it is answered
(input), and which choices were read off the screen (options),
so a consumer can close the gate or ask an operator a real question
instead of only ever surfacing a tag.
Fields
gate: OperatorGateStateFailing
The foreground process matches, but the screen shows the agent came
up wrong or fell over – a crash/stack trace, an expired or rejected
login, a fatal startup error. Kept distinct from OperatorGate on
purpose: a gate is a screen a human resolves BY typing into it, while
nothing typed into this screen fixes it. Collapsing the two would
lose exactly the diagnosis an operator needs – “waiting for you”
versus “broken”. For write-gating it behaves like every other
non-Ready state (refused); the split buys a correct label, not
different gating. reason is a short classifier label, the same
shape OperatorGate::gate used to carry before it became a
structured type, never raw terminal text – nothing in this enum
carries screen contents. Where a provider has a
pty_sidecar adapter bound, structured signals (rate limits arriving
as a ProviderEvent) remain the authority for those specific
conditions; this variant is the text-derived fallback, and the only
signal at all for providers that emit no structured events.
Ready
The foreground process matches AND no known gate or failure pattern
is showing. This is explicitly NOT a claim that the agent is idle,
waiting for input, or will do anything useful with a write – it only
means the screen is not known to be showing something else. Reading
Ready as “safe to act on” beyond that is the over-read this type
exists to prevent. Note the asymmetry with the other four variants:
each of them fires from a single signal (process mismatch, or a
recognized gate/failure pattern alone), while Ready requires both
the process and text signals to agree – a screen the text matcher
does not recognize never reaches Ready on that basis alone.
Implementations§
Source§impl PtyScreenState
impl PtyScreenState
Sourcepub fn admits_blind_write(&self) -> bool
pub fn admits_blind_write(&self) -> bool
True unless this screen was READ as an obstacle. Refuses on the three
states that carry a finding – a gate the operator must answer, a
foreign process, a failure – and admits Ready and Unknown alike.
Unknown admits deliberately. It does not mean “an obstacle we might
have missed”, it means the matcher recognized nothing, and refusing on
it makes ignorance indistinguishable from a finding. Every provider
reaches Ready within a frame or two of spawn on process identity
alone, long before anything is on screen, so Unknown is mostly just
the moment before that – and the screen is no longer where this
system decides what a session is doing. ACP carries that as protocol
state and never consults this predicate at all; a PTY is an operator’s
surface first and a control channel second.
What stays refused is what was actually read: writing a task into a trust prompt or a login screen puts the text nowhere and leaves Enter to pick a menu item blind. That is a finding, and findings still count. Answering such a screen is not blocked and never was – key injection does not come through here.
Sourcepub fn is_valid(&self) -> bool
pub fn is_valid(&self) -> bool
Bounds check matching ForegroundProcess::is_valid_for: the carried
strings must be non-empty, control-character free, and within the
per-field byte caps, since both travel over the wire into an operator
UI and a raw process/gate label is not something to trust unbounded.