* 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>
refs #3937
Root cause:
Activating another endpoint applies a snapshot with a different
endpoint-qualified graphics scope. This triggers reset_endpoint_projection,
which clears agent_scroll together with endpoint-local state even though
the agent list aggregates all machines. The selected agent receives focus,
but the list jumps back to the top.
Fix:
Save the client-owned agent scroll offset and restore it only when
activation changes endpoints. Keep same-endpoint reboot resets,
workspace/tab scroll resets, input cleanup and existing list bounds
clamping unchanged. This adds constant-time work on endpoint activation,
with no protocol, persisted-state or pane-scaled rendering changes.
Validation:
The new regression fails on the original implementation with scroll 0
instead of 6. Tests cover repeated bidirectional agent clicks, stable
visible rows, shrinking lists and same-machine reboots. All 242 client
shell tests pass.
Real SSH A/B testing against the same two disposable 0.9.0 servers
reproduces the reset with both the stable and current-master clients.
The patched client preserves the visible rows through six cross-machine
switches and two same-machine control clicks.
Native just ci passes: 3535 Rust tests, six default skips, plus lint,
maintenance, architecture and integration-asset checks. All seven docs
contract tests pass. Windows cross-lint was not run because its SDK is
not installed; native macOS and Windows UI behavior was not tested.
* test(windows): deflake media player invalid media assertion
The Windows sound test required MediaPlayer's MediaFailed event for invalid
media. Some runners never raise it, so the script exits through its playback
timer and the test saw "sound playback timed out" instead, panicking on the
MediaFailed assertion.
Accept either terminal error and keep the timer short under test via
HERDR_SOUND_TIMEOUT_SECONDS so a missed MediaFailed no longer waits out the
production 15 second bound.
* test(windows): keep media player timeout override test-only
Generate the short-timer player script only in tests instead of reading a
process environment override. This keeps the production script's fixed 15
second bound unchanged, and the script-content test now proves the test
variant rewrites the timer.
* ci: migrate remaining pinned actions to node24
actions/cache@v4 and mlugg/setup-zig@v2.2.1 still target Node 20, which
GitHub removes from runners on 2026-09-23. Bump actions/cache to v6.1.0 and
replace mlugg/setup-zig with vercel-labs/setup-zig@v1.0.2.
vercel-labs/setup-zig takes only `version`, so the use-cache/cache-size-limit
tuning and Zig global-cache reuse are dropped. See the PR for alternatives.
* ci: restore zig caching for vercel setup-zig
vercel-labs/setup-zig has no cache controls, so redirect the Zig global cache
into the workspace .zig-cache and run the Zig build cache step on Linux too.
Drop this commit if we switch to a setup action that owns Zig caching, e.g.
step-security/setup-zig or xyzzylabs/setup-zig.
Terminals such as foot send F1-F4 as parameterized CSI-tilde sequences (for
example F3 as `\x1b[13;1:1~`). `parse_xterm_modified_special_sequence` mapped
codes 15-24 but not 11-14, so these presses were dropped before keybindings
saw them. Map 11-14 to F1-F4, matching the unmodified `\x1b[11~`..`\x1b[14~`
aliases already accepted.
refs #1809
Codepoints 57364-57375 (F1-F12) were missing from the kitty-protocol codepoint table, so terminals that send function keys as full CSI-u sequences (e.g. Ghostty, which enables the kitty keyboard protocol by default) had those keys silently dropped. Confirmed live against a running herdr session: injecting the raw F3/F4 codepoint sequences did nothing before this fix and correctly triggered previous_tab/next_tab after.
refs #1809
Co-authored-by: JJ Liebig <jonathan.liebig@gmail.com>
A process-exit observation is not proof the agent is gone: the same
observation can be wrong while the agent keeps running, and the name is
the only handle its owner has on the pane. Detection uncertainty already
preserves the name, so releasing it on the observation alone contradicts
that contract and leaves a live agent unreachable by name with a rename
as the only recovery.
Free the name at the point the agent actually leaves the pane - a
recorded exit with no agent detected any more - so a wrong observation
costs nothing and a real exit still releases the name for reuse.
refs #3225
Co-authored-by: JJ Liebig <jonathan.liebig@gmail.com>
Windows login-mode PowerShell panes are now launched directly, but the PowerShell prompt-based cwd integration was still gated to non-login mode, leaving those panes without repository-owned cwd reporting after Set-Location. Enable the existing integration for login-mode PowerShell and build the integration args on the shared direct-shell path.
Follow-up to #4060.
* fix(windows): honor default_shell in login shell mode
Login-mode panes used portable-pty's default-program builder, which resolves to %ComSpec% (cmd.exe) on Windows and ignores the SHELL env override, so [terminal].default_shell was silently ignored. Build the launch command from the configured shell instead and append the shell's own login flag for POSIX-style shells that support one.
refs #1445
* fix(windows): add csh and tcsh to login shell flags
refs #1445
Windows skipped the xterm mouse-reporting reset because clear_host_mouse_reporting was a no-op there. Standalone Git Bash (mintty) keeps SGR mouse reporting enabled after detach, so mouse motion leaks escape sequences into the shell. Emit the reset sequence on Windows too and include the X10 mode.
refs #3748
Windows OpenSSH resolves drive-letter Include paths, but Git for Windows' MSYS OpenSSH does not, so the managed remote SSH config silently ignored the user's ~/.ssh/config when herdr ran under Git Bash. Include the user config through ~/.ssh/config, which both implementations expand to the same file.
refs #3947
The clear-only metadata test used a 1 ms ttl, so on loaded Windows runners set_agent_metadata could see the entry as expired, drop it, and recreate it with a None ttl before next_agent_metadata_expiry() ran. Expiry is forced explicitly later in the test, so use a long ttl to keep the no-extension assertion independent of wall-clock scheduling.
Live handoff refused any session with more than 64 panes. The pane count
was checked twice against MAX_FDS_PER_HANDOFF, and the transfer itself put
every pane's pty master into one SCM_RIGHTS control message, so the guard
was the only thing keeping the send inside the kernel's per-message limit.
A session past the limit could only be updated by closing panes or by a
normal restart, which ends every pane process.
Send the descriptors in batches of 64 instead and drop both guards. The
receiving side accumulates across recvmsg calls until the expected count
arrives, bounds every SCM_RIGHTS payload it reads by the control bytes the
kernel returned, rejects a batch that carries more descriptors than it
asked for, and closes the descriptors it already holds on any failure. A
session of 64 panes or fewer still produces one batch, so the bytes on the
wire are unchanged and HANDOFF_VERSION stays at 1.
refs #3393
Co-authored-by: kataokatsuki <kataokatsuki@users.noreply.github.com>
Co-authored-by: JJ Liebig <jonathan.liebig@gmail.com>
Windows CI runs these documentation contract fixtures under variable load. A degraded runner pushed the preview and versions snapshot integration tests past their 30s ceiling, while a healthy run finishes in seconds. Keep 30s elsewhere and give Windows a 120s ceiling.
The polling peer drains at production's 2ms Windows read cadence, so a 1 MiB
frame took ~5.5s locally and exceeded the test's 10s flush deadline under CI
load. Use a 256 KiB frame, still well above the 64 KiB batch limit, and a 30s
deadline so the assertion no longer depends on CI scheduling.