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
|
id = "codex"
version = "2026.09.23.1"
min_engine_version = 3
updated_at = "2026-09-23T22:12:00Z"
[[rules]]
id = "osc_title_blocked"
state = "blocked"
priority = 1100
region = "osc_title"
visible_blocker = true
contains = ["Action Required"]
[[rules]]
id = "osc_title_working"
state = "working"
priority = 1050
region = "osc_title"
visible_working = true
regex = ['(?:^| )[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏](?: |$)']
[[rules]]
id = "transcript_viewer"
state = "unknown"
priority = 1000
region = "after_last_prompt_marker"
skip_state_update = true
contains = ["↑/↓ to scroll", "pgup/pgdn to", "home/end to jump", "q to quit"]
any = [
{ contains = ["esc to edit prev"] },
{ contains = ["esc/← to edit prev"] },
]
[[rules]]
id = "trust_directory"
state = "blocked"
priority = 950
region = "top_non_empty_lines(20)"
visible_blocker = true
all = [
{ regex = ['\A> You are in [^\r\n]+(?:\r?\n|$)'] },
{ regex = ['(?s)Do\s+you\s+trust\s+the\s+contents\s+of\s+this\s+directory\?'] },
]
[[rules]]
id = "startup_update"
state = "blocked"
priority = 950
region = "bottom_non_empty_lines(20)"
visible_blocker = true
contains = ["Update available!", "Update now"]
regex = ['Skip\s+until\s+next\s+version', 'Press enter to continue\s*\z']
[[rules]]
id = "live_strong_blocker"
state = "blocked"
priority = 900
region = "after_last_prompt_marker"
visible_blocker = true
any = [
{ contains = ["press enter to confirm or esc to cancel"] },
{ contains = ["enter to submit answer"] },
{ contains = ["enter to submit all"] },
{ contains = ["allow command?"] },
{ contains = ["All Results", "Filesystem Only", "Plugins"] },
]
[[rules]]
id = "weak_blocker"
state = "blocked"
priority = 600
region = "whole_recent_without_current_prompt_marker"
# Sparkles can replace the space after ›. A later response marker makes that prompt stale.
not = [{ regex = ['(?m)^›[⠁⠂⠄⠈⠐⠠⡀⢀][^\n]*(?:\n(?:[^•■✗✓\n][^\n]*)?)*\z'] }]
any = [
{ contains = ["[y/n]"] },
{ contains = ["yes (y)"] },
{ contains = ["do you want to"], any = [{ contains = ["yes"] }, { contains = ["❯"] }] },
{ contains = ["would you like to"], any = [{ contains = ["yes"] }, { contains = ["❯"] }] },
]
[[rules]]
id = "screen_working_fallback"
state = "working"
priority = 500
region = "before_current_prompt_marker"
visible_working = true
# Support animated and reduced-motion status, including dynamic activity labels.
# The interrupt hint can be remapped, unbound, or hidden, and queued inputs can
# sit below the status. Require the live timer suffix with no later response.
any = [{ contains = [" to interrupt)"] }, { contains = ["s)"] }]
regex = ['(?m)^(?:[•◦][ \t]+)?[^\s›•◦■✗✓─][^\r\n]* \((?:[0-9]+[hm] )*[0-9]+s(?: • [^\r\n]+? to interrupt)?\)(?: · [^\r\n]*)?(?:\r?\n(?:[^•◦›■✗✓─\r\n][^\r\n]*|•[ \t]+(?:Queued\s+follow-up\s+inputs|Messages\s+to\s+be\s+submitted\s+after\s+next\s+tool\s+call(?:\s+\(press\s+[^\r\n]+?\s+to\s+interrupt\s+and\s+send\s+immediately\))?|Messages\s+to\s+be\s+submitted\s+at\s+end\s+of\s+turn)|›[⠁⠂⠄⠈⠐⠠⡀⢀][^\r\n]*)?)*\s*\z']
# A failed reconnect keeps its final elapsed timer but is no longer working.
not = [{ line_regex = ['^(?:[•◦][ \t]+)?Reconnect failed — check the endpoint, then relaunch \([0-9hms ]+\)$'] }]
|