control board: brief for piece 2, reply-from-board, with Gate C (#1503)

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-09-12 11:17:28 -05:00
co-authored by Claude Fable 5.1
parent 0bed9ba3db
commit a62ca1904f
@@ -97,6 +97,63 @@ because the launch gave no `--task`; the next real use is
task instead. Fleet seats stay on the old launchers; only `agents/` seats
register. Next piece in Jason's order: reply-from-board.
## Piece 2: reply-from-board
Approved by Jason 2026-09-12 after Gate B ("seat task update works. I want to
keep things building"). Same shape as #1503/#1504: one piece, one yes/no gate,
small commits with tests on `refactor`. Darkwing opens the issue and updates
`CURRENT.md` when he picks it up.
### What it is
A reply box on the board. Jason types a message on a seat's row and the board
delivers it to that seat's tmux pane through the repository's own channel.
No new transport, no message store, no WebSocket.
- **Page**: a one-line text box and a Send button in each row's detail
(registered rows only, see below). After Send the row shows one of two
receipts: `delivered` with the time, or `failed` with the tool's stderr.
Nothing else changes on the page; the next scan shows the effect (the
seat's newest user message is the text just sent, and a `waiting` row
turns `working`).
- **Server**: `POST /api/reply` with `{ "agent": "<row id>", "text": "..." }`.
The handler resolves the row's tmux session, runs
`tools/tmux/agent-send.sh -s <session> -S "<host>:control-board" -m <text>`
and returns the exit code, stdout and stderr. The exit code is the receipt;
a non-zero code is a failure the page shows verbatim. `-S` labels the
sender so the seat's preamble reads `[<host>:control-board -> …]`. Fleet
sockets pass `-L` from the registration's `tmux.socket` when set.
- **Which rows**: only rows whose registration (#1504) names a tmux session,
and whose registration is not stale. Everything else shows "reply needs a
registered seat" instead of the box. Seat name alone is not an address; the
registration's session is.
- **Never**: raw `tmux send-keys`, a queue, retries, a history panel, a
broadcast, sending to more than one seat per request. The durable record of
what was said is the seat's own session log, which the scanner already reads.
### Gate C (yes/no)
Jason answers a seat that is `waiting` from the board, without opening a
terminal, and within one scan the row is `working` and its Task or last
message reflects the reply. One real exchange is the pass.
### Tests
- Server: a fake `agent-send.sh` on PATH captures its argv; assert `-s`, `-S`,
`-L` (when the registration has a socket) and the message text.
- Refusals with exit codes surfaced: unknown row, empty text, row without
registration, stale registration, tool exit non-zero (stderr returned).
- Page: the box appears only on registered live rows; a failure receipt shows
the stderr text.
- Loopback only stays as is; no auth is added for this piece.
### Boundaries
- No change to `packages/seat` or the registration record.
- No change to `tools/tmux/agent-send.sh`; if it refuses, the refusal is the
finding (ms-communications rule), reported on the page and in the issue.
- Fleet seats are out: they are not registered (Jason, 2026-09-12).
## How the scanner decides state
The scanner reads the newest pi session log for each agent, plus whether