fix(skills): evidence-based prompt delivery (send_keys_safe) to end prompt-lock (FW-W2)
- Add send_keys_safe + quiescence/dialog-detection helpers to lib.sh: keys are sent on pane evidence, never fixed timers; distinct exit codes 1-4; dialogs are never blindly Enter-ed - inject_instructions delegates to send_keys_safe; create --submit-job publishes a terminal error event on delivery failure (no zombie jobs) - wait_for_tui_ready: drop dialog-ambiguous tokens, treat open dialogs as not-ready, return 1 on timeout instead of proceeding - delegate-job wrapper: replace copy-pasted raw paste/C-m block with lib.sh send_keys_safe (restores single source of truth) - resume: conditional signature-gated dialog handling replaces blind Enter/Down/Enter; stop --graceful delivers exitkey safely, fallback chain unchanged - create SKILL: passive capture-pane probe instead of stray Enter - Mark FW-W2 resolved; add 3-agent analysis/plan reports
This commit is contained in:
+82
@@ -0,0 +1,82 @@
|
||||
# Prompt-Lock & Input Delivery Failure — Code-Level Analysis Report
|
||||
|
||||
- **Reviewer**: Reviewer Cline (`canary-projects-multi-agent-mux-reviewer-cline`, role: reviewer)
|
||||
- **Date**: 2026-07-11
|
||||
- **Brief**: `.mam/reports/brief-prompt-lock-fix.md`
|
||||
- **Scope**: Code-level analysis of the "prompt typing lock / input delivery failure" in the TMUX-based multi-agent environment; concrete prevention helper + migration plan.
|
||||
|
||||
---
|
||||
|
||||
## 1. The Problem (recap)
|
||||
|
||||
When the orchestrator issues commands via `tmux send-keys` to a target agent TUI:
|
||||
1. Text gets printed inside the prompt input box but is **never submitted** (Enter ignored) or the cursor freezes.
|
||||
2. Root causes: blessed UI renderer thread bottleneck during heavy output; dialog popups (Approve/Reject permission prompts) stealing input focus; OAuth/list-selection dialog blocks intercepting keystrokes.
|
||||
|
||||
The core failure mode is: **`send-keys` delivers keystrokes to whatever currently has focus.** If a permission dialog, an OAuth browser-prompt, or a list-selection popup is open, the keystrokes go to the dialog (or are swallowed), not the main input box — so the prompt text appears but Enter does nothing, or the cursor appears frozen.
|
||||
|
||||
---
|
||||
|
||||
## 2. Exact Code Locations — Every `send-keys` / Input-Delivery Site
|
||||
|
||||
I grepped the entire `.agents/` tree. There are **6 input-delivery sites**; only 2 use a helper, the rest are raw `tmux send-keys`.
|
||||
|
||||
### Site A — `lib.sh:1086-1100` (`inject_instructions`) — HELPER, central
|
||||
```bash
|
||||
inject_instructions() {
|
||||
local sess="$1" instructions="$2" job_id="${3:-onboard}"
|
||||
local local_tmux="tmux"
|
||||
if [ -n "${TMUX_SERVER_NAME:-}" ] && [ "$TMUX_SERVER_NAME" != "default" ]; then
|
||||
local_tmux="tmux -L $TMUX_SERVER_NAME"
|
||||
fi
|
||||
$local_tmux set-buffer -b "job_buf_$job_id" "$instructions"
|
||||
$local_tmux paste-buffer -b "job_buf_$job_id" -t "$sess"
|
||||
sleep 0.5
|
||||
$local_tmux send-keys -t "$sess" C-m # ← Enter, no focus guard
|
||||
$local_tmux delete-buffer -b "job_buf_$job_id"
|
||||
}
|
||||
```
|
||||
**Vulnerability**: No focus recovery. If a permission/dialog popup is open when `C-m` fires, Enter goes to the dialog. The fixed `sleep 0.5` is too short under heavy renderer load (the brief's "renderer thread bottleneck"). No delivery verification.
|
||||
|
||||
### Site B — `multi-agent-mux-delegate-job:364-369` — DUPLICATE of Site A, raw
|
||||
```bash
|
||||
$_tmux set-buffer -b "job_buf_$job_id" "$instructions"
|
||||
$_tmux paste-buffer -b "job_buf_$job_id" -t "$sess"
|
||||
sleep 0.5
|
||||
$_tmux send-keys -t "$sess" C-m
|
||||
$_tmux delete-buffer -b "job_buf_$job_id"
|
||||
```
|
||||
**Vulnerability**: Identical logic to Site A, copy-pasted (violates lib.sh's "single source of truth" mandate, lib.sh header §4.1). Same no-focus-guard + too-short-sleep defects. This is the delegate-job path — the *primary* way the orchestrator hands work to agents, so it's the highest-traffic vulnerable site.
|
||||
|
||||
### Site C — `resume/SKILL.md:150-156` — RAW, dialog auto-handle (MOST VULNERABLE)
|
||||
```bash
|
||||
# auto-handle trust / bypass dialogs
|
||||
sleep 5
|
||||
tmux send-keys -t "$SESSION_NAME" Enter 2>/dev/null || true
|
||||
sleep 3
|
||||
tmux send-keys -t "$SESSION_NAME" Down 2>/dev/null || true
|
||||
sleep 0.3
|
||||
tmux send-keys -t "$SESSION_NAME" Enter 2>/dev/null || true
|
||||
```
|
||||
**Vulnerability**: This is the *exact* lock symptom from the brief. It fires blind `Enter`/`Down`/`Enter` on fixed sleeps to auto-dismiss a trust dialog. Problems: (a) no check that a dialog actually exists — if the TUI rendered late and focus is still the main input, these keystrokes type garbage into the prompt; (b) if a *different* dialog (OAuth, list-select) appeared instead of the expected trust prompt, `Down`+`Enter` selects the wrong option; (c) `2>/dev/null || true` swallows all errors silently — the operator never learns delivery failed; (d) no `wait_for_tui_ready` gate before sending.
|
||||
|
||||
### Site D — `stop_session.sh:192` (`graceful_stop`) — RAW
|
||||
```bash
|
||||
tmux send-keys -t "$SESSION_NAME" "$exitkey" Enter 2>/dev/null || true
|
||||
```
|
||||
**Vulnerability**: Sends `/exit` + Enter without focus recovery. If a permission dialog is open, `/exit` is typed into the dialog (harmless there) but Enter may dismiss the dialog with an unintended choice, and the agent never receives the exit command. The graceful chain *does* have a proper fallback (kill-session → SIGTERM → SIGKILL with `has-session` checks, lines 194-205), so this site is low-severity — but it still benefits from focus recovery.
|
||||
|
||||
### Site E — `create/SKILL.md:215` — RAW, probe (documentation example)
|
||||
```bash
|
||||
tmux send-keys -t "$SESSION_NAME" "" Enter
|
||||
```
|
||||
**Vulnerability**: This is in a verification snippet (sends empty + Enter). Low impact — it's an optional manual probe, not an automated path. But it sets a bad example for users.
|
||||
|
||||
### Site F — `create_session.sh:384` — USES HELPER (Site A)
|
||||
```bash
|
||||
inject_instructions "$SESSION_NAME" "$instructions" "$DELEGATE_JOB_ID"
|
||||
```
|
||||
**Status**: This is the correct pattern — it routes through `inject_instructions`. It's only as safe as Site A. Also note: `create_session.sh:199` calls `wait_for_tui_ready` before any input — the *only* site that does so.
|
||||
|
||||
### Existing mitigation already present (good, underused)
|
||||
`lib.sh:1040-1084` defines `wait_for_tui_ready <sess> <agent>` — a gated capture-pane loop (15×1s) that grep-checks the pane content for each agent's TUI banner before returning. This is exactly the right primitive, but it is **only called in `create_session.sh:199`**. Resume, stop, and delegate-job never gate on TUI readiness.
|
||||
Reference in New Issue
Block a user