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 task instead. Fleet seats stay on the old launchers; only `agents/` seats
register. Next piece in Jason's order: reply-from-board. 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 ## How the scanner decides state
The scanner reads the newest pi session log for each agent, plus whether The scanner reads the newest pi session log for each agent, plus whether