fix(afk): force-deliver wedged escalations past max-defer#160
Open
rfbr wants to merge 1 commit into
Open
Conversation
The away-mode max-defer escape only RE-TRIED the same guarded escalate_flush, so once a prior injection's Enter was swallowed the daemon's own marker-prefixed digest sat unsubmitted in the supervisor composer. pane_input_pending then read that as "pending input" on every tick, the composer guard deferred delivery forever, and the wedge alarm was invisible in afk (nobody is watching firstmate's pane). A stalled crewmate went un-recovered for ~8h overnight as a result. Past FM_MAX_DEFER_SECS, on a pane that is not genuinely busy, the daemon now FORCE-delivers: it clears the stale self-injected composer text and submits the digest. A genuinely busy pane (agent mid-turn) still defers and raises the loud wedge alarm. The force path is afk-only and afk-gated, so when afk is OFF a human's half-typed line is never clobbered (return-race guard preserved). Clear-before-type also prevents two sentinel-prefixed digests from concatenating into one corrupted turn. - bin/fm-tmux-lib.sh: add fm_tmux_clear_composer (Ctrl-A/K/U line wipe, verify empty, fail-open on unreadable). - bin/fm-supervise-daemon.sh: thread a force flag through escalate_flush -> inject_msg; block 1b force-flushes; busy pane still defers under force. - .agents/skills/afk/SKILL.md: document the force-deliver contract. - tests/fm-afk-wedge.test.sh: regression suite (force-delivers stale self-injection, busy pane still defers, afk-off never clobbers a human line, clear-composer primitive). - tests/fm-daemon.test.sh, tests/wake-helpers.sh: update the max-defer pending-composer case to a busy pane (the case that still defers) and teach the fake tmux to honor clearing keys.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
In away-mode (
/afk), the sub-supervisor daemon detected a crewmate stall within minutes and buffered an escalation, but could not deliver it to firstmate's pane for ~8h — so the stall was never recovered and the whole away period was wasted (state/.subsuper-inject-wedgedreadWEDGED: 29325s undelivered).Root cause
The max-defer escape in
housekeeping()(block 1b) only re-tried the same guardedescalate_flush. It never force-delivered. So once the composer guard started deferring, delivery deferred forever:pane_input_pendingreads that stale self-injected text as "pending input" → the composer guard defers.inject_wedge_alarm.Net: the daemon self-poisons its own input channel and never escapes.
Fix
Past
FM_MAX_DEFER_SECS, on a pane that is not genuinely busy, the daemon now force-delivers: it clears the stale self-injected composer text and submits the digest.esc to interrupt) → still defers and raises the visible wedge alarm; an active turn is never clobbered.Changes
bin/fm-tmux-lib.sh— newfm_tmux_clear_composer(sends Ctrl-A/K/U, verifies empty, fails open on an unreadable pane so the force path never re-wedges).bin/fm-supervise-daemon.sh— thread aforceflag throughescalate_flush→inject_msg; block 1b force-flushes; busy-pane and afk-off behavior unchanged..agents/skills/afk/SKILL.md— document the force-deliver contract.tests/fm-afk-wedge.test.sh— regression suite: force-delivers stale self-injection, busy pane still defers, afk-off never clobbers a human line, and the clear-composer primitive.tests/fm-daemon.test.sh/tests/wake-helpers.sh— the prior "max-defer on a pending composer alarms without typing" case encoded the old behavior; retargeted to a genuinely busy pane (the case that still defers) and taught the fake tmux to honor clearing keys.Testing
This clone has no
no-mistakesgate, so verification was done directly:bash -n bin/*.sh— clean.shellcheck bin/*.sh tests/*.sh— clean (shellcheck 0.10.0).tests/fm-afk-wedge.test.sh(4 cases) — pass.fm-bootstrap/fm-x-modeneedjq;fm-teardownneeds a git fixture), each confirmed failing identically on the clean base.All tests are hermetic (temp dirs + fake tmux); none touch the live
state/of a running firstmate.