mirror of
https://github.com/herdrdev/herdr.git
synced 2026-09-22 08:01:06 +00:00
* fix: detect codex activity with static titles and queued inputs refs #4092 Codex 0.154.0 can omit the activity bullet when tui.animations is false and replace the literal Working label with a task-specific label. Its terminal title can also remain static during an active turn, so the OSC spinner rule is not sufficient to identify work. The screen fallback required a bullet followed by Working in the last three non-empty lines. It therefore missed both the animation-disabled status line and dynamic labels. Queued follow-ups add a heading, input text, and an edit hint below the activity line, moving that line outside the three-line region. The lower-priority non-empty OSC title rule then reported idle while Codex still showed an active timer and interrupt control. Recognize the elapsed-time and esc-to-interrupt controls in the detection snapshot before the current composer. Allow an optional bullet, a dynamic activity label, and queued follow-ups below that line. Require that no later response, completion, or interruption marker supersedes the live activity. This excludes composer text and stale output without letting an earlier interruption suppress a subsequent active turn. Keep OSC state precedence and transcript-viewer skip behavior unchanged. Update the bundled and distributed manifests together to 2026.09.14.1. Add regression coverage for optional bullets, dynamic labels, queued inputs, resumed work after interruption, and stale or quoted controls. Avoid discarding Regex search caches on every detection poll. The loader previously cloned the entire compiled rule vector, and each Regex clone created a fresh search cache pool despite sharing compiled read-only state. Share the immutable rules through Arc instead. Reload publishes a new array while in-flight readers retain a valid old generation, without holding the global cache lock during matching. Add a cheap contains gate for the required interrupt text before running the multiline expression. Add a deterministic regression for compiled-rule sharing, reload replacement, retained old readers, and concurrent detection. Measure the baseline and optimized detector kernels in three alternating rounds on CPU 24 with NUMA node 0 and CPU 25 with NUMA node 1. Real queued detection falls from about 116 to 22 microseconds, and two evaluations per pane over 15 panes fall from about 3.45 to 0.65 milliseconds on both sockets, about 81 percent less time. No benchmark match results change. The cache improvement requires the updated binary, not just a manifest reload. Validate on the official Herdr 0.9.0 release with Codex 0.154.0 by toggling the old and new manifests during the same active queued task. Confirm working for active turns and idle after completion or interruption. Pass 86 captured-screen and semantic checks through the optimized source binary, 69 manifest/cache tests, CPU-pinned render scaling, and the full native CI suite: 3535 Rust tests passed, with six default skips. Run CI serially with inherited Herdr session context removed; no tests were excluded or expectations relaxed to make CI pass. * fix: detect codex pending follow-ups as working refs #4092 * fix: handle wrapped codex follow-up hints refs #4092 --------- Co-authored-by: Ogulcan Celik <ogulcancelik@gmail.com>
104 lines
3.1 KiB
TOML
104 lines
3.1 KiB
TOML
id = "codex"
|
||
version = "2026.09.14.1"
|
||
min_engine_version = 3
|
||
updated_at = "2026-09-14T00:00: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?"] },
|
||
]
|
||
|
||
[[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
|
||
# Cheap negative filter before the multiline search.
|
||
contains = ["esc to interrupt"]
|
||
# Animations can hide the bullet, and queued inputs can sit below a dynamic activity label.
|
||
# Require the live timer/control suffix with no later response or interruption marker.
|
||
regex = ['(?m)^(?:[•◦][ \t]+)?[^\s›•◦■✗✓─][^\r\n]* \((?:[0-9]+[hm] )*[0-9]+s • esc 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+esc\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']
|
||
|
||
[[rules]]
|
||
id = "osc_title_idle"
|
||
state = "idle"
|
||
priority = 100
|
||
region = "osc_title"
|
||
visible_idle = true
|
||
regex = ['\S']
|
||
not = [
|
||
{ regex = ['(?:^| )[⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏](?: |$)'] },
|
||
{ contains = ["Action Required"] },
|
||
]
|