From 8ff139027285b6aa5f0ff64b7356f62d4966daca Mon Sep 17 00:00:00 2001 From: Jeremiah Date: Fri, 24 Jul 2026 10:40:31 -0400 Subject: [PATCH 1/4] fix: update Grok busy-state adapter for 0.2.111 --- .agents/skills/harness-adapters/SKILL.md | 19 +++++++++++----- bin/fm-harness.sh | 2 +- bin/fm-tmux-lib.sh | 6 ++--- bin/fm-watch.sh | 6 ++--- tests/fm-grok-harness.test.sh | 29 +++++++++++++++++++++++- 5 files changed, 48 insertions(+), 14 deletions(-) diff --git a/.agents/skills/harness-adapters/SKILL.md b/.agents/skills/harness-adapters/SKILL.md index e4cdb0263b..1f5673883d 100644 --- a/.agents/skills/harness-adapters/SKILL.md +++ b/.agents/skills/harness-adapters/SKILL.md @@ -132,7 +132,7 @@ Natural language is acceptable if uncertain. - codex: `$`, for example `$no-mistakes`; `/` is claude-only and codex rejects it as "Unrecognized command". - opencode: no separate verified skill invocation beyond normal slash-command behavior; use natural language if the exact skill command is uncertain. - pi: no separate verified skill invocation beyond normal command behavior; use natural language if the exact skill command is uncertain. -- grok: `/`, for example `/no-mistakes` (same form as claude). Verified end to end: grok discovers the user-level `no-mistakes` skill, `/no-mistakes` invokes it, and grok drives a real `no-mistakes axi run`. Like codex's `$`/`/` popups, typing `/` opens grok's slash-autocomplete, so a too-fast Enter selects the popup entry instead of sending, and for an argument-taking command (like `/no-mistakes`'s optional task-first argument) that first Enter only expands the popup selection into an argument-hint placeholder rather than submitting - a genuine second Enter is required (see the grok section below for the 2026-07-03 incident and fix). `fm_tmux_submit_core`'s retried Enter (used by `fm-send` on the tmux backend) already handles this correctly by reading the cursor row; the herdr backend needed a dedicated fix (`fm_backend_herdr_composer_state`, docs/herdr-backend.md) because its prior delta-based verification false-positived on that same popup-close content change. +- grok: `/`, for example `/no-mistakes` (same form as claude). Verified end to end: grok discovers the user-level `no-mistakes` skill, `/no-mistakes` invokes it, and grok drives a real `no-mistakes axi run`. Typing `/` opens grok's slash-autocomplete. Grok 0.2.111 submits the selected no-argument `/no-mistakes` entry with one Enter; commands whose autocomplete selection expands to an argument-hint placeholder still require a second Enter. `fm_tmux_submit_core`'s composer-state retry (used by `fm-send` on the tmux backend) safely handles both shapes; the herdr backend needed a dedicated fix (`fm_backend_herdr_composer_state`, docs/herdr-backend.md) because its prior delta-based verification false-positived on popup-close content changes. ## claude (VERIFIED) @@ -260,7 +260,7 @@ The model arms through `fm_watch_arm_pi`, never a foreground bash arm; the watch `bin/fm-session-start.sh` reports when the live Pi session has not loaded both the turn-end guard and watcher extensions, and points at plain `pi` after project trust as the fix, with `-e` as a trust-free fallback. When a secondmate is launched on Pi, `fm-spawn.sh --secondmate` launches Pi with both `-e .pi/extensions/fm-primary-turnend-guard.ts` and `-e .pi/extensions/fm-primary-pi-watch.ts`, both already present in the secondmate home's git worktree. -## grok (VERIFIED 2026-06-29, grok 0.2.73; slash-submit re-verified 2026-07-03 on 0.2.82; reasoning-effort ceiling re-verified 2026-07-13 on 0.2.99; exit paths re-verified 2026-07-19 on grok 0.2.103) +## grok (VERIFIED 2026-07-24, grok 0.2.111) Grok Build TUI (`grok`), a Claude-Code-compatible CLI from xAI. Launch with a positional prompt: `grok --always-approve "$(cat )"`. @@ -268,14 +268,21 @@ For Grok's supported reasoning-effort values and omission behavior, see the [lau | Fact | Value | |---|---| -| Busy-pane signature | `Ctrl+c:cancel` (the mid-turn cancel hint in grok's keybind bar, shown iff a turn is running; the spinner line is a braille glyph + `… N.Ns` + `[stop]`, e.g. `⠹ Thinking… 1.1s … [stop]`). Idle keybind bar shows only `Shift+Tab:mode │ Ctrl+.:shortcuts`. The ASCII `Ctrl+c:cancel` is the busy regex (avoids locale fragility of matching braille). | -| Exit command | `/exit` typed into the composer exits the TUI cleanly and prints `Resume this session with: grok --resume `; `Ctrl+Q` double-press within 1000ms remains a fallback; `Ctrl+D` is the quit key in VS Code family terminals; `Ctrl+C` is the interrupt, not the exit. | -| Interrupt | single `Ctrl+C` (cancels the current turn; the footer shows `Ctrl+c:cancel` mid-turn). `Esc` only moves focus to the scrollback, it does NOT interrupt. | -| Skill invocation | `/` (e.g. `/no-mistakes`), same as claude. Opens a slash-autocomplete popup, so a too-fast Enter selects the popup entry instead of sending. For an argument-taking command that first Enter does not submit at all - it expands the selection into an argument-hint placeholder in the composer (e.g. `/compact` -> `/compact compaction instructions`, live-verified), leaving real text still sitting there unsubmitted; a genuine second Enter is required. `fm-send`'s retried Enter lands it on BOTH backends, but only because each backend's own submit-verification correctly recognizes that placeholder-filled text as still-pending - see the incident below. | +| Busy-pane signature | `Esc:cancel` (the mid-turn cancel hint in grok's keybind bar, shown iff a turn is running; the spinner line remains a braille glyph + `… N.Ns` + `[stop]`). Idle keybind bar shows only `Shift+Tab:mode │ Ctrl+x:shortcuts`. The ASCII `Esc:cancel` is the busy regex (avoids locale fragility of matching braille). | +| Exit command | `/exit` typed into the composer exits the TUI cleanly and prints `Resume this session with: grok --resume `; `Ctrl+Q` double-press within 1000ms remains a fallback; `Ctrl+D` is the quit key in VS Code family terminals; Escape is the interrupt, not the exit. | +| Interrupt | single Escape cancels the current turn; the footer shows `Esc:cancel` mid-turn and the pane reports `Turn cancelled by user`. | +| Skill invocation | `/` (e.g. `/no-mistakes`), same as claude. Opens a slash-autocomplete popup. On 0.2.111 one Enter submitted the selected no-argument `/no-mistakes` entry, which discovered the user-level skill and began its repository checks. An argument-hint placeholder left in the composer remains pending and receives the adapter's retry Enter. | | Autonomy | `--always-approve` (footer shows `· always-approve`); auto-approves every tool execution, verified to run fully unattended. `--permission-mode bypassPermissions` is the stronger equivalent. | | Env marker | `GROK_AGENT=1`, set for child/tool processes. grok does NOT set `CLAUDECODE` despite Claude compatibility, so the marker is unambiguous. | | Resume | `grok --resume ` (id printed on exit) or `grok -c` / `--continue` (most recent for the cwd); `--fork-session` branches a new session id. | +**2026-07-24 terminal re-verification (grok 0.2.111).** +In a disposable git directory inside an isolated firstmate worktree, `grok --always-approve ''` immediately submitted the positional prompt, displayed `Grok 4.5 (high) · always-approve`, and ran an unattended shell tool whose child recorded `GROK_AGENT=1` and the disposable working directory. +During both thinking and a 30-second shell tool the footer displayed `Esc:cancel`; one Escape produced `Turn cancelled by user`, after which the idle footer no longer contained the cancel hint. +`/exit` cleanly ended the TUI, and `grok --continue --always-approve` in the same directory restored the prior conversation. +The project was a git repository and displayed no trust, login, update, or permission dialog. +Typing `/no-mistakes` displayed the discovered skill in slash autocomplete; one Enter submitted it and Grok began the skill's repository checks. + **Incident (2026-07-03, herdr backend only, grok 0.2.82):** two grok/herdr crewmates were sent `/no-mistakes` via `fm-send`; both left it fully typed but unsubmitted in the composer for minutes (footer still `Enter:send`), and `fm-send` exited 0 with no error. Reproduced live: the herdr adapter's submit-verification at the time treated ANY pane-content change after Enter as "submitted", and the popup-close-with-placeholder-fill described above IS a visible content change even though nothing was actually sent. The tmux backend was never affected - `fm_tmux_composer_state` reads the actual cursor row, correctly sees the placeholder text as still-pending, and its retry loop already sends the needed second Enter. diff --git a/bin/fm-harness.sh b/bin/fm-harness.sh index 3267e92207..bad30a6ca8 100755 --- a/bin/fm-harness.sh +++ b/bin/fm-harness.sh @@ -31,7 +31,7 @@ detect_own() { # Layer 1: environment markers for verified harnesses. [ "${CLAUDECODE:-}" = "1" ] && { echo claude; return; } [ "${PI_CODING_AGENT:-}" = "true" ] && { echo pi; return; } - # grok sets GROK_AGENT=1 for its child/tool processes (verified, grok 0.2.73). + # grok sets GROK_AGENT=1 for its child/tool processes (re-verified, grok 0.2.111). # It does NOT set CLAUDECODE despite being Claude-Code-compatible, so this marker # is unambiguous when firstmate runs natively on grok. [ "${GROK_AGENT:-}" = "1" ] && { echo grok; return; } diff --git a/bin/fm-tmux-lib.sh b/bin/fm-tmux-lib.sh index 889def95bf..21b605aa7d 100755 --- a/bin/fm-tmux-lib.sh +++ b/bin/fm-tmux-lib.sh @@ -61,9 +61,9 @@ . "$(dirname -- "${BASH_SOURCE[0]}")/fm-composer-lib.sh" # Busy footers per harness (mirror fm-watch.sh). claude/codex: "esc to -# interrupt"; opencode: "esc interrupt"; pi: "Working..."; grok: "Ctrl+c:cancel" -# (grok's mid-turn cancel hint, shown iff a turn is running - verified grok 0.2.73). -FM_TMUX_BUSY_REGEX_DEFAULT='esc (to )?interrupt|Working\.\.\.|Ctrl\+c:cancel' +# interrupt"; opencode: "esc interrupt"; pi: "Working..."; grok: "Esc:cancel" +# (grok's mid-turn cancel hint, shown iff a turn is running - verified grok 0.2.111). +FM_TMUX_BUSY_REGEX_DEFAULT='esc (to )?interrupt|Working\.\.\.|Esc:cancel' # fm_tmux_strip_ghost: thin adapter over the shared, fleet-wide ghost extractor # fm_composer_strip_ghost (bin/fm-composer-lib.sh). It drops de-emphasised diff --git a/bin/fm-watch.sh b/bin/fm-watch.sh index 1bb9f05e6c..e13ecfda53 100755 --- a/bin/fm-watch.sh +++ b/bin/fm-watch.sh @@ -102,10 +102,10 @@ SIGNAL_GRACE=${FM_SIGNAL_GRACE:-30} # seconds to linger after a signal so trai # turn-end hook) coalesce into one wake # Busy signatures per harness, OR-ed. Extend via env when new adapters are verified. # claude/codex: "esc to interrupt"; opencode: "esc interrupt"; pi: "Working..."; -# grok: "Ctrl+c:cancel" (the mid-turn cancel hint in grok's keybind bar, shown iff a -# turn is running; absent when idle - verified grok 0.2.73, ASCII to avoid the +# grok: "Esc:cancel" (the mid-turn cancel hint in grok's keybind bar, shown iff a +# turn is running; absent when idle - verified grok 0.2.111, ASCII to avoid the # locale fragility of matching grok's braille spinner glyph directly). -BUSY_REGEX=${FM_BUSY_REGEX:-'esc (to )?interrupt|Working\.\.\.|Ctrl\+c:cancel'} +BUSY_REGEX=${FM_BUSY_REGEX:-'esc (to )?interrupt|Working\.\.\.|Esc:cancel'} # Always-on wake triage: most wakes during a long crew validation are benign (a # working: note or turn-end while a pipeline runs, a no-change heartbeat). Rather # than wake firstmate's LLM for each, this watcher classifies every wake in bash diff --git a/tests/fm-grok-harness.test.sh b/tests/fm-grok-harness.test.sh index edb5001b9a..0401c796b1 100755 --- a/tests/fm-grok-harness.test.sh +++ b/tests/fm-grok-harness.test.sh @@ -1,5 +1,5 @@ #!/usr/bin/env bash -# Behavior tests for Grok-harness hook authentication, teardown cleanup, and session-lock holder detection. +# Behavior tests for Grok-harness busy detection, hook authentication, teardown cleanup, and session-lock holder detection. set -u # shellcheck source=tests/lib.sh @@ -9,6 +9,32 @@ SPAWN="$ROOT/bin/fm-spawn.sh" TEARDOWN="$ROOT/bin/fm-teardown.sh" TMP_ROOT=$(fm_test_tmproot fm-grok-harness) +test_grok_busy_signature_matches_current_footer() { + local regex + # shellcheck source=bin/fm-tmux-lib.sh + . "$ROOT/bin/fm-tmux-lib.sh" + regex=$FM_TMUX_BUSY_REGEX_DEFAULT + printf '%s\n' 'Shift+Tab:mode │ Esc:cancel │ Ctrl+x:shortcuts' \ + | grep -qiE "$regex" \ + || fail "grok 0.2.111 busy footer did not match the shared busy regex" + if printf '%s\n' 'Shift+Tab:mode │ Ctrl+x:shortcuts' | grep -qiE "$regex"; then + fail "grok 0.2.111 idle footer matched the shared busy regex" + fi + assert_grep "Esc:cancel" "$ROOT/bin/fm-watch.sh" \ + "watcher busy regex drifted from the shared grok footer signature" + ( + tmux() { printf '%s\n' 'Shift+Tab:mode │ Esc:cancel │ Ctrl+x:shortcuts'; } + fm_pane_is_busy grok-pane + ) || fail "shared tmux pane classifier did not recognize grok 0.2.111 as busy" + if ( + tmux() { printf '%s\n' 'Shift+Tab:mode │ Ctrl+x:shortcuts'; } + fm_pane_is_busy grok-pane + ); then + fail "shared tmux pane classifier treated grok 0.2.111 idle footer as busy" + fi + pass "grok 0.2.111 Esc:cancel footer distinguishes busy from idle" +} + make_spawn_fakebin() { local dir=$1 fakebin fakebin=$(fm_fakebin "$dir") @@ -137,6 +163,7 @@ SH pass "fm-lock recognizes grok harness processes" } +test_grok_busy_signature_matches_current_footer test_grok_hook_requires_registered_token test_grok_teardown_removes_pointer_and_token test_fm_lock_recognizes_grok_holder From c539c29fb553363c24f8cd81c2dd6b0b2a7acbd0 Mon Sep 17 00:00:00 2001 From: Jeremiah Date: Fri, 24 Jul 2026 10:53:42 -0400 Subject: [PATCH 2/4] no-mistakes(review): Captain, preserve Grok busy markers across versions --- bin/fm-tmux-lib.sh | 7 ++++--- bin/fm-watch.sh | 9 +++++---- docs/configuration.md | 2 +- tests/fm-grok-harness.test.sh | 18 +++++++++++++----- 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/bin/fm-tmux-lib.sh b/bin/fm-tmux-lib.sh index 21b605aa7d..a144230b20 100755 --- a/bin/fm-tmux-lib.sh +++ b/bin/fm-tmux-lib.sh @@ -61,9 +61,10 @@ . "$(dirname -- "${BASH_SOURCE[0]}")/fm-composer-lib.sh" # Busy footers per harness (mirror fm-watch.sh). claude/codex: "esc to -# interrupt"; opencode: "esc interrupt"; pi: "Working..."; grok: "Esc:cancel" -# (grok's mid-turn cancel hint, shown iff a turn is running - verified grok 0.2.111). -FM_TMUX_BUSY_REGEX_DEFAULT='esc (to )?interrupt|Working\.\.\.|Esc:cancel' +# interrupt"; opencode: "esc interrupt"; pi: "Working..."; grok: +# "Ctrl+c:cancel" or "Esc:cancel" (mid-turn cancel hints, shown iff a turn is +# running - verified grok 0.2.73 and 0.2.111). +FM_TMUX_BUSY_REGEX_DEFAULT='esc (to )?interrupt|Working\.\.\.|(Ctrl\+c|Esc):cancel' # fm_tmux_strip_ghost: thin adapter over the shared, fleet-wide ghost extractor # fm_composer_strip_ghost (bin/fm-composer-lib.sh). It drops de-emphasised diff --git a/bin/fm-watch.sh b/bin/fm-watch.sh index e13ecfda53..47c8015869 100755 --- a/bin/fm-watch.sh +++ b/bin/fm-watch.sh @@ -102,10 +102,11 @@ SIGNAL_GRACE=${FM_SIGNAL_GRACE:-30} # seconds to linger after a signal so trai # turn-end hook) coalesce into one wake # Busy signatures per harness, OR-ed. Extend via env when new adapters are verified. # claude/codex: "esc to interrupt"; opencode: "esc interrupt"; pi: "Working..."; -# grok: "Esc:cancel" (the mid-turn cancel hint in grok's keybind bar, shown iff a -# turn is running; absent when idle - verified grok 0.2.111, ASCII to avoid the -# locale fragility of matching grok's braille spinner glyph directly). -BUSY_REGEX=${FM_BUSY_REGEX:-'esc (to )?interrupt|Working\.\.\.|Esc:cancel'} +# grok: "Ctrl+c:cancel" or "Esc:cancel" (mid-turn cancel hints in grok's keybind +# bar, shown iff a turn is running; absent when idle - verified grok 0.2.73 and +# 0.2.111, ASCII to avoid the locale fragility of matching grok's braille spinner +# glyph directly). +BUSY_REGEX=${FM_BUSY_REGEX:-$FM_TMUX_BUSY_REGEX_DEFAULT} # Always-on wake triage: most wakes during a long crew validation are benign (a # working: note or turn-end while a pipeline runs, a no-change heartbeat). Rather # than wake firstmate's LLM for each, this watcher classifies every wake in bash diff --git a/docs/configuration.md b/docs/configuration.md index 168054665c..e8ade8a54c 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -432,7 +432,7 @@ FM_STALE_WORKTREE_LOCK_RETRY_WAIT_SECS= # legacy alias for FM_TREEHOUSE_RETURN FM_FLEET_SYNC_PACKED_REFS_LOCK_RETRIES=3 # fetch retries after fm-fleet-sync.sh hits the orphaned .git/packed-refs.lock signature FM_FLEET_SYNC_PACKED_REFS_LOCK_RETRY_WAIT_SECS=1 # seconds fm-fleet-sync.sh waits before each of those retries FM_FLEET_SYNC_PACKED_REFS_LOCK_AGE_SECS=30 # min mtime age before fm-fleet-sync.sh treats a leftover packed-refs.lock as provably stale -FM_BUSY_REGEX='esc (to )?interrupt|Working\.\.\.|Ctrl\+c:cancel' # busy-pane signatures, shared by watcher, fm-crew-state pane fallback, and tmux helper +FM_BUSY_REGEX='esc (to )?interrupt|Working\.\.\.|(Ctrl\+c|Esc):cancel' # busy-pane signatures, shared by watcher, fm-crew-state pane fallback, and tmux helper FM_COMPOSER_IDLE_RE= # optional empty-composer regex, applied after ghost and border stripping FM_COMPOSER_GHOST_LUMA_MAX=128 # fleet-wide: max perceived luminance (0.299R+0.587G+0.114B, 0-255) for a TRUECOLOR foreground to count as de-emphasised ghost/placeholder text and be stripped; dim/faint (SGR 2) is stripped regardless. Assumes a dark terminal theme (bin/fm-composer-lib.sh's fm_composer_strip_ghost, shared by the tmux and herdr composer readers) GROK_HOME= # optional Grok config home for firstmate's global grok turn-end hook; defaults to ~/.grok diff --git a/tests/fm-grok-harness.test.sh b/tests/fm-grok-harness.test.sh index 0401c796b1..4a0cf88a9c 100755 --- a/tests/fm-grok-harness.test.sh +++ b/tests/fm-grok-harness.test.sh @@ -9,19 +9,27 @@ SPAWN="$ROOT/bin/fm-spawn.sh" TEARDOWN="$ROOT/bin/fm-teardown.sh" TMP_ROOT=$(fm_test_tmproot fm-grok-harness) -test_grok_busy_signature_matches_current_footer() { +test_grok_busy_signatures_match_verified_footers() { local regex # shellcheck source=bin/fm-tmux-lib.sh . "$ROOT/bin/fm-tmux-lib.sh" regex=$FM_TMUX_BUSY_REGEX_DEFAULT + printf '%s\n' 'Shift+Tab:mode │ Ctrl+c:cancel │ Ctrl+x:shortcuts' \ + | grep -qiE "$regex" \ + || fail "grok 0.2.73 busy footer did not match the shared busy regex" printf '%s\n' 'Shift+Tab:mode │ Esc:cancel │ Ctrl+x:shortcuts' \ | grep -qiE "$regex" \ || fail "grok 0.2.111 busy footer did not match the shared busy regex" if printf '%s\n' 'Shift+Tab:mode │ Ctrl+x:shortcuts' | grep -qiE "$regex"; then fail "grok 0.2.111 idle footer matched the shared busy regex" fi - assert_grep "Esc:cancel" "$ROOT/bin/fm-watch.sh" \ - "watcher busy regex drifted from the shared grok footer signature" + assert_grep "BUSY_REGEX=\${FM_BUSY_REGEX:-\$FM_TMUX_BUSY_REGEX_DEFAULT}" \ + "$ROOT/bin/fm-watch.sh" \ + "watcher busy regex drifted from the shared busy regex" + ( + tmux() { printf '%s\n' 'Shift+Tab:mode │ Ctrl+c:cancel │ Ctrl+x:shortcuts'; } + fm_pane_is_busy grok-pane + ) || fail "shared tmux pane classifier did not recognize grok 0.2.73 as busy" ( tmux() { printf '%s\n' 'Shift+Tab:mode │ Esc:cancel │ Ctrl+x:shortcuts'; } fm_pane_is_busy grok-pane @@ -32,7 +40,7 @@ test_grok_busy_signature_matches_current_footer() { ); then fail "shared tmux pane classifier treated grok 0.2.111 idle footer as busy" fi - pass "grok 0.2.111 Esc:cancel footer distinguishes busy from idle" + pass "verified grok busy footers distinguish busy from idle" } make_spawn_fakebin() { @@ -163,7 +171,7 @@ SH pass "fm-lock recognizes grok harness processes" } -test_grok_busy_signature_matches_current_footer +test_grok_busy_signatures_match_verified_footers test_grok_hook_requires_registered_token test_grok_teardown_removes_pointer_and_token test_fm_lock_recognizes_grok_holder From b7c026019a5c6da5810b941971be145d047649c6 Mon Sep 17 00:00:00 2001 From: Jeremiah Date: Fri, 24 Jul 2026 11:16:19 -0400 Subject: [PATCH 3/4] no-mistakes(document): Synchronize Grok busy-signature documentation --- .agents/skills/harness-adapters/SKILL.md | 2 +- bin/fm-supervise-daemon.sh | 3 ++- bin/fm-tmux-lib.sh | 8 ++++---- bin/fm-watch.sh | 3 ++- 4 files changed, 9 insertions(+), 7 deletions(-) diff --git a/.agents/skills/harness-adapters/SKILL.md b/.agents/skills/harness-adapters/SKILL.md index 1f5673883d..e318d75d90 100644 --- a/.agents/skills/harness-adapters/SKILL.md +++ b/.agents/skills/harness-adapters/SKILL.md @@ -268,7 +268,7 @@ For Grok's supported reasoning-effort values and omission behavior, see the [lau | Fact | Value | |---|---| -| Busy-pane signature | `Esc:cancel` (the mid-turn cancel hint in grok's keybind bar, shown iff a turn is running; the spinner line remains a braille glyph + `… N.Ns` + `[stop]`). Idle keybind bar shows only `Shift+Tab:mode │ Ctrl+x:shortcuts`. The ASCII `Esc:cancel` is the busy regex (avoids locale fragility of matching braille). | +| Busy-pane signature | `Esc:cancel` (the mid-turn cancel hint in grok's keybind bar, shown iff a turn is running; the spinner line remains a braille glyph + `… N.Ns` + `[stop]`). Idle keybind bar shows only `Shift+Tab:mode │ Ctrl+x:shortcuts`. Firstmate's shared ASCII busy regex accepts both 0.2.111's `Esc:cancel` and the legacy 0.2.73 `Ctrl+c:cancel` footer, avoiding locale-fragile matching of the braille spinner. | | Exit command | `/exit` typed into the composer exits the TUI cleanly and prints `Resume this session with: grok --resume `; `Ctrl+Q` double-press within 1000ms remains a fallback; `Ctrl+D` is the quit key in VS Code family terminals; Escape is the interrupt, not the exit. | | Interrupt | single Escape cancels the current turn; the footer shows `Esc:cancel` mid-turn and the pane reports `Turn cancelled by user`. | | Skill invocation | `/` (e.g. `/no-mistakes`), same as claude. Opens a slash-autocomplete popup. On 0.2.111 one Enter submitted the selected no-argument `/no-mistakes` entry, which discovered the user-level skill and began its repository checks. An argument-hint placeholder left in the composer remains pending and receives the adapter's retry Enter. | diff --git a/bin/fm-supervise-daemon.sh b/bin/fm-supervise-daemon.sh index 1c5940d211..48b1c3ed6c 100755 --- a/bin/fm-supervise-daemon.sh +++ b/bin/fm-supervise-daemon.sh @@ -96,7 +96,8 @@ # (default 300) # FM_HOUSEKEEPING_TICK seconds between housekeeping passes while # the watcher is mid-cycle (default 15) -# FM_BUSY_REGEX OR-ed busy signatures (mirrors fm-watch.sh) +# FM_BUSY_REGEX OR-ed busy signatures overriding the shared +# fm-tmux-lib.sh default # FM_COMPOSER_IDLE_RE empty-composer regex applied after dim-ghost # and structural border stripping (default: # bare prompt glyphs plus busy footers) diff --git a/bin/fm-tmux-lib.sh b/bin/fm-tmux-lib.sh index a144230b20..0554c76c3b 100755 --- a/bin/fm-tmux-lib.sh +++ b/bin/fm-tmux-lib.sh @@ -46,8 +46,8 @@ # tmux adapter does not paper over a herdr-specific shape. # # Per-harness override: FM_COMPOSER_IDLE_RE matches an empty composer after -# ghost and structural border stripping. FM_BUSY_REGEX overrides the busy -# footer set (mirrors fm-watch.sh / the daemon). +# ghost and structural border stripping. FM_BUSY_REGEX overrides the shared busy +# footer set consumed by fm-watch.sh and the daemon. # # All functions are `set -u` and `set -e` safe (guarded tmux calls, explicit # returns) so they can be sourced into either context. @@ -60,8 +60,8 @@ # shellcheck source=bin/fm-composer-lib.sh . "$(dirname -- "${BASH_SOURCE[0]}")/fm-composer-lib.sh" -# Busy footers per harness (mirror fm-watch.sh). claude/codex: "esc to -# interrupt"; opencode: "esc interrupt"; pi: "Working..."; grok: +# Shared busy footers consumed by fm-watch.sh and the daemon. claude/codex: +# "esc to interrupt"; opencode: "esc interrupt"; pi: "Working..."; grok: # "Ctrl+c:cancel" or "Esc:cancel" (mid-turn cancel hints, shown iff a turn is # running - verified grok 0.2.73 and 0.2.111). FM_TMUX_BUSY_REGEX_DEFAULT='esc (to )?interrupt|Working\.\.\.|(Ctrl\+c|Esc):cancel' diff --git a/bin/fm-watch.sh b/bin/fm-watch.sh index 47c8015869..3319eb1f29 100755 --- a/bin/fm-watch.sh +++ b/bin/fm-watch.sh @@ -100,7 +100,8 @@ CHECK_TIMEOUT=${FM_CHECK_TIMEOUT:-30} # seconds allowed per *.check.sh SIGNAL_GRACE=${FM_SIGNAL_GRACE:-30} # seconds to linger after a signal so trailing # signals (a status write, then the same turn's # turn-end hook) coalesce into one wake -# Busy signatures per harness, OR-ed. Extend via env when new adapters are verified. +# Shared busy signatures from fm-tmux-lib.sh, OR-ed across harnesses. +# Extend via env when new adapters are verified. # claude/codex: "esc to interrupt"; opencode: "esc interrupt"; pi: "Working..."; # grok: "Ctrl+c:cancel" or "Esc:cancel" (mid-turn cancel hints in grok's keybind # bar, shown iff a turn is running; absent when idle - verified grok 0.2.73 and From 117296e5cc5b787c90ef731db93aa4d572c2538d Mon Sep 17 00:00:00 2001 From: Jeremiah Date: Fri, 24 Jul 2026 12:09:22 -0400 Subject: [PATCH 4/4] no-mistakes: apply CI fixes --- tests/fm-lint.test.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/fm-lint.test.sh b/tests/fm-lint.test.sh index 4a1b18d7dc..a23d18ea7b 100755 --- a/tests/fm-lint.test.sh +++ b/tests/fm-lint.test.sh @@ -253,7 +253,7 @@ test_source_graph_boundaries_keep_every_owner() { grep -q '^[[:space:]]*# shellcheck source=bin/' "$file" || continue production_context_tests="${production_context_tests}$(basename "$file")|" done - [ "$production_context_tests" = 'fm-backend-herdr.test.sh|fm-daemon.test.sh|fm-pending-reply.test.sh|fm-secondmate-sync.test.sh|' ] \ + [ "$production_context_tests" = 'fm-backend-herdr.test.sh|fm-daemon.test.sh|fm-grok-harness.test.sh|fm-pending-reply.test.sh|fm-secondmate-sync.test.sh|' ] \ || fail "only callback/variable interop tests may retain production source context: $production_context_tests" pass "dispatcher, adapters, production owner, and tests have explicit lint boundaries" }