1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
# Codex readiness manifest (E6.3) - declarative port of the hardcoded
# `CodexReadinessDetector` / `prompt_ready` in `readiness.rs`.
#
# State vocabulary (consumed by the daemon badge when E2 wires evaluate() in):
# idle -> the CLI is waiting for input (the old `is_ready == true`)
# working -> mid-render / mid-turn, do NOT send input (`is_ready == false`)
# blocked -> a wall the operator must clear first; never false-ready
# No matching rule -> the engine returns None and the caller refuses to guess
# (readiness.rs Open Question #9: no byte-count fallback).
#
# Arbitration reproduces `prompt_ready`'s short-circuit exactly:
# auth_wall (980) > busy (900) > idle_prompt (100)
# so a screen showing both a prompt glyph AND an auth wall / busy marker
# resolves to blocked/working (not-ready), matching the imperative early return.
#
# The bottom_non_empty_lines(3) region IS the old STATUS_REGION_LINES = 3: it
# scopes the wall/busy match to the composer + status bar so a model reply that
# merely mentions "Working" up in scrollback does not pin the agent not-ready
# (the original Codex review P1 fix). idle_prompt reads only the last line,
# matching `last.trim_end().ends_with(glyph)`.
#
# ponytail: glyphs/markers mirror readiness.rs (PROMPT_GLYPHS, BUSY_MARKERS,
# WALL_MARKERS); pin them against a live codex TUI via
# cli/scripts/smoke/capture-readiness-grid.sh when E2 lands. Conservative bias
# holds: a wrong glyph is a false-NOT-ready (daemon waits), never a false-ready.
[[]]
= "auth_wall"
= "blocked"
= 980
= "bottom_non_empty_lines(3)"
= { = [
{ = "Waiting for auth" },
{ = "waiting for auth" },
{ = "Do you trust" },
{ = "Login required" },
] }
[[]]
= "busy"
= "working"
= 900
= "bottom_non_empty_lines(3)"
= { = [
{ = "esc to interrupt" },
{ = "Esc to interrupt" },
{ = "Working" },
{ = "Thinking" },
] }
# Last non-blank line ends with a prompt glyph (❯ › ▌), optional trailing space.
# Literal (single-quoted) TOML string so the backslash reaches the regex engine.
[[]]
= "idle_prompt"
= "idle"
= 100
= "bottom_non_empty_lines(1)"
= { = '[\x{276f}\x{203a}\x{2595}][ \t]*$' }
# Answerable command/edit approvals (x-f498). Codex 0.144.1 renders a borderless
# numbered menu and commits the choice on a bare digit. The menu/footer stay in
# the small fingerprinted answer region while the question is detected anywhere
# on the visible screen, so a heavily wrapped command cannot push it out. The
# marked selector + approval-specific footer keep model-printed or other numbered
# menus from becoming answerable. Gemini remains unchanged until live capture.
[[]]
= "approval_prompt"
= "blocked"
= 986
= "bottom_non_empty_lines(20)"
= { = [
{ = '^\s*\x{203a}\s*[0-9]\.\s' },
{ = "Press enter to confirm or esc to cancel" },
] }
= "whole_recent"
= { = [
{ = "Would you like to run the following command?" },
{ = "Would you like to make the following edits?" },
] }
[]
= '^\s*\x{203a}?\s*(?P<idx>[0-9])\.\s+(?P<label>.+?)\s*$'
= "digit"
# Answerable numbered prompt (x-5103). Codex's trust prompt renders a borderless
# numbered menu ("› 1. Yes, continue" / " 2. No, quit") - the selected row
# carries a "›" (U+203A) marker, unselected rows are digit-only. Validated
# against a live codex TUI (fresh untrusted dir): the bare digit COMMITS the
# choice (no Enter), so send="digit" like claude; digit_enter would bleed a stray
# CR into the next prompt codex stacks immediately after. Priority 985 sits above
# auth_wall (980) so a numbered trust menu wins over a bare wall if both ever
# match; in practice they don't (auth_wall's 3-line region never holds the "Do
# you trust" line, which sits above the options). Gated on the trust question AND
# a MARKED numbered line: the gate's line_regex REQUIRES the "›" selector glyph
# before the digit (unlike the answer regex below, which keeps it optional to
# extract every option). A live menu always draws the selector on its current
# choice; a model reply that merely prints "Do you trust …" plus a plain "1./2."
# list in scrollback has no "›" before those digits, so it can't fake the gate
# and get its own list turned into answer-queue choices that inject "1"/"2" into
# the idle composer (codex review P2). A bare auth wall keeps its auth_wall fallback.
[[]]
= "trust_prompt"
= "blocked"
= 985
= "bottom_non_empty_lines(10)"
= { = [
{ = [ { = "Do you trust" }, { = "do you trust" } ] },
{ = '^\s*\x{203a}\s*[0-9]\.\s' },
] }
[]
= '^\s*\x{203a}?\s*(?P<idx>[0-9])\.\s+(?P<label>.+?)\s*$'
= "digit"