diff --git a/AGENTS.md b/AGENTS.md index 340b397..32e5a80 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -404,6 +404,7 @@ If a secondmate's scope fits, steer that secondmate with one concise instruction The bare `fm-` target resolves through this home's `state/.meta`; pass `session:window` only when intentionally targeting a window outside this firstmate home. A secondmate is itself a firstmate, so a request reaches it in its own chat, which you never read - the return channel that wakes you is its status file. So `fm-send` to a bare `fm-` whose meta is `kind=secondmate` automatically prepends a from-firstmate marker (`bin/fm-marker-lib.sh`); the secondmate recognizes it and returns its answer via its status file, or via a doc under its home plus a status pointer for a detailed response, never only in chat. +For codex secondmates, that marked ordinary-text path also uses the longer pre-Enter settle so the already-typed request is not left unsubmitted by input timing. Expect and read that response on the status/doc path the same way you read any other status signal; do not peek the secondmate's chat for the answer. A captain typing directly into the secondmate's window is unmarked and stays a conversational captain intervention, so do not relay captain-destined chat through this path; the marker is applied only by `fm-send` to a `kind=secondmate` target. Do not spawn a direct crewmate for work that belongs to a secondmate scope unless the secondmate is blocked or the captain explicitly redirects it. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9a55d54..82ac342 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -65,7 +65,7 @@ tests/fm-watcher-lock.test.sh # watcher singleton, lock-race, watch- tests/fm-watch-triage.test.sh # always-on watcher triage: benign absorb, actionable surface, stale wedge threshold, heartbeat backstop, and afk one-shot coherence tests/fm-daemon.test.sh # sub-supervisor classifier, /afk presence-gating, max-defer, composer, and fm-send submit tests tests/fm-send-settle.test.sh # fm-send post-submit settle pause, tuning, disable, and --key bypass tests -tests/fm-send-popup-settle.test.sh # fm-send pre-Enter popup-settle selection for slash commands and codex $skill invocations +tests/fm-send-popup-settle.test.sh # fm-send pre-Enter popup-settle selection for slash commands, codex $skill invocations, and marked codex secondmate text tests/fm-send-secondmate-marker.test.sh # fm-send from-firstmate marker for kind=secondmate targets: marked vs crewmate/explicit/--key, and the exact marker byte sequence tests/fm-wake-daemon-lifecycle-e2e.test.sh # watcher + daemon lifecycle e2e: restart catch-up, batching, dedupe, stale-pane routing, and digest injection tests/fm-composer-ghost.test.sh # dim-ghost stripping, ghost-only composer detection, and escape-free peek tests diff --git a/bin/fm-send.sh b/bin/fm-send.sh index 489c07c..15e3d58 100755 --- a/bin/fm-send.sh +++ b/bin/fm-send.sh @@ -12,8 +12,9 @@ # instead of silently leaving an unsubmitted instruction (incident afk-invx-i5). # The composer/submit logic is shared with the away-mode daemon via # bin/fm-tmux-lib.sh. Tune with FM_SEND_RETRIES (default 3) / FM_SEND_SLEEP (0.4). -# Slash commands, and codex `$...` skill invocations resolved through harness -# meta, get a longer pre-Enter settle so completion popups do not swallow Enter. +# Slash commands, codex `$...` skill invocations resolved through harness meta, +# and marked codex secondmate text get a longer pre-Enter settle so completion or +# input timing does not swallow Enter. # # From-firstmate marker: when the resolved target is a bare `fm-` whose meta # records kind=secondmate, the text is prefixed with the from-firstmate marker @@ -102,13 +103,22 @@ else # `$` case is scoped to codex on purpose: unlike `/`, a leading `$` commonly # starts ordinary text ("$5/month", "$HOME"), so a universal `$` rule would # needlessly slow plain text to claude/opencode/pi. The retried Enter in - # fm_tmux_submit_core still backs the settle up either way. + # fm_tmux_submit_core still backs the settle up either way. A marked ordinary + # message to a codex secondmate also uses the longer settle: live Codex panes + # have swallowed Enter on that path while leaving the already-typed request in + # the composer, and the marker is present only for bare kind=secondmate targets. case "$*" in /*) settle=1.2 ;; \$*) if [ "$TARGET_HARNESS" = codex ]; then settle=1.2; else settle=0.3; fi ;; - *) settle=0.3 ;; + *) + if [ -n "$MARK_PREFIX" ] && [ "$TARGET_HARNESS" = codex ]; then + settle=1.2 + else + settle=0.3 + fi + ;; esac retries=${FM_SEND_RETRIES:-3} sleep_s=${FM_SEND_SLEEP:-0.4} diff --git a/docs/architecture.md b/docs/architecture.md index 148d579..24e4f5d 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -34,7 +34,7 @@ The watcher and daemon share `bin/fm-classify-lib.sh` for captain-relevant statu The always-on watcher also uses that library's provably-working predicate on no-verb signal and non-terminal-stale paths, while the daemon keeps its away-mode stale recheck unchanged. The daemon escalates only captain-relevant events as one batched, single-line digest (prefixed with an in-band sentinel marker so firstmate can tell daemon injections apart from real messages). Its injection path shares `bin/fm-tmux-lib.sh` with `fm-send.sh`, so dim-ghost-aware and border-aware composer detection plus verified submit retry stay consistent; stalled escalation delivery raises `state/.subsuper-inject-wedged` after `FM_MAX_DEFER_SECS` instead of silently deferring forever. -`fm-send.sh` selects a pre-Enter popup-settle for slash commands and for codex `$...` skill invocations using the target's recorded `harness=` meta, then adds its own `FM_SEND_SETTLE` pause after successful text sends so immediate peeks catch the receiving turn starting; the sub-supervisor uses only the shared submit core and does not pay that post-submit pause. +`fm-send.sh` selects a pre-Enter popup-settle for slash commands, codex `$...` skill invocations, and marked ordinary text sent to codex secondmates using the target's recorded `harness=` and `kind=` meta, then adds its own `FM_SEND_SETTLE` pause after successful text sends so immediate peeks catch the receiving turn starting; the sub-supervisor uses only the shared submit core and does not pay that post-submit pause. ## Worktrees, not branches in your checkout diff --git a/docs/scripts.md b/docs/scripts.md index 98617d2..2302416 100644 --- a/docs/scripts.md +++ b/docs/scripts.md @@ -43,7 +43,7 @@ Each file also starts with a short header comment. | `fm-wake-drain.sh` | Atomically drain queued watcher wakes before handling supervision work, then run the watcher-liveness guard | | `fm-wake-lib.sh` | Shared durable wake queue and portable lock helpers sourced by the watcher, drain, arm, guard, and daemon | | `fm-classify-lib.sh` | Shared captain-relevant wake classifier sourced by the watcher and daemon, plus the watcher's provably-working predicate | -| `fm-send.sh` | Send one verified literal line (or `--key Escape`) to a direct-report window; exits non-zero on confirmed swallowed Enter; bare `kind=secondmate` targets are marked as from-firstmate; slash commands and codex `$...` skill invocations get popup-settle before Enter; text sends pause `FM_SEND_SETTLE` seconds after success | +| `fm-send.sh` | Send one verified literal line (or `--key Escape`) to a direct-report window; exits non-zero on confirmed swallowed Enter; bare `kind=secondmate` targets are marked as from-firstmate; slash commands, codex `$...` skill invocations, and marked codex secondmate text get popup-settle before Enter; text sends pause `FM_SEND_SETTLE` seconds after success | | `fm-tmux-lib.sh` | Shared tmux pane primitives for busy detection, dim-ghost-aware and border-aware composer detection, and verified submit retry | | `fm-peek.sh` | Print a bounded tail of a crewmate pane | | `fm-pr-check.sh` | Record `pr=` and GitHub's `pr_head=` when available for a PR-ready task, then arm the watcher's merge poll | diff --git a/tests/fm-send-popup-settle.test.sh b/tests/fm-send-popup-settle.test.sh index fcf0d2b..07ab49d 100755 --- a/tests/fm-send-popup-settle.test.sh +++ b/tests/fm-send-popup-settle.test.sh @@ -15,6 +15,7 @@ # $... explicit -> 0.3 (session:window target has no meta -> harness unknown # -> non-codex safe default) # plain text -> 0.3 (fast path) +# marked codex secondmate plain text -> 1.2 (long settle before submit) # # The popup-settle is the FIRST sleep recorded: fm_tmux_submit_core types the text, # then `sleep "$settle"`, then the Enter-retry loop (sleep 0.4 each) and finally @@ -67,16 +68,16 @@ SH printf '%s\n' "$fb" } -# first_settle