control board: reply from the board through agent-send.sh (#1505)

Piece 2 of the MVP (#1503). A one-line reply box and Send in the detail
of rows with a live registration; POST /api/reply runs
tools/tmux/agent-send.sh -s <session> -S <host>:control-board
[-L <socket>] -m <text> once for one seat and returns the exit code,
stdout and stderr. The page shows delivered or failed with the tool's
stderr; other rows say "reply needs a registered seat". No send-keys,
queue, retries, history or broadcast; packages/seat and agent-send.sh
untouched. Every message ends with a fixed trailer telling the seat to
answer in its own session (Jason's refinement after the first Gate C
exchange; the board has no pane). Board suite 98/98.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-09-12 11:30:30 -05:00
co-authored by Claude Fable 5.1
parent a62ca1904f
commit 867619dca2
8 changed files with 508 additions and 19 deletions
+1
View File
@@ -223,3 +223,4 @@ are never rewritten or removed; corrections are new entries.
- 2026-09-12 — darkwing (Claude Code, coordinator; formerly jarvis) — Seat registration (#1504), Jason's go on the professor session's brief: new `packages/seat` and `scripts/mosaic` (`launch <seat>` registers then execs the launch script unchanged; `seat task` edits the task); board reads `<dataRoot>/seats/<layout>/<seat>/registration.json` and lets registered task/project/workspace override derived values with a source tag; the four repo launch scripts register themselves; fleet one-liner written for orch-01. Tests: seat 15, board 79 → 89, launch scripts 5, registry 69, config 24. Sonnet review found a seat-name collision across layouts; fixed by keying on layout, second pass APPROVED. Live check on a scratch server against a temp data root. Also found Jason's launch refused on the Pi pin (node_modules 0.84.4 vs pin 0.85.1 since 557aba0f); `npm ci` fixed it, the refusal now names both versions; the pin stays unless Jason decides otherwise.
- 2026-09-12 — darkwing (Claude Code, coordinator) — #1504 follow-up on two defects the professor session reported and this session verified: the darkwing launch test leaked fixture registrations into the real data root (flock spawn without the fixture config; fixed and guarded so a leak fails), and a refused launch left a dead-pid record the board honoured (scanner now probes the pid; stale records are shown but do not override). Fleet launchers marked not planned per Jason. Board 90/90, seat 15/15, launch scripts 5/5. Committed and pushed to refactor.
- 2026-09-12 — darkwing (Claude Code, coordinator) — Control board: model per row (#1503) on Jason's ask: readSession tracks model/provider from the latest model_change entry or assistant turn; page shows the model under the agent name and a Model detail row. Live check on a scratch board: all 42 real rows named a model. Board 91/91. Committed and pushed to refactor.
- 2026-09-12 — darkwing (Claude Code, coordinator) — Control board piece 2, reply-from-board (#1505, brief by Jason): `POST /api/reply` runs `tools/tmux/agent-send.sh` for one registered, live seat and returns the exit code and streams; the page has a reply box in registered live rows' detail with delivered/failed receipts. Live check on a scratch tmux session through the real agent-send.sh: delivered, then a failure receipt with the tool's stderr. Board 98/98. Gate C first exchange ("pizza?") delivered and read back within one scan; the seat's send-back to control-board was refused as expected, so every board message now ends with a fixed trailer (Jason's ruling via the professor session). Committed and pushed to refactor.
@@ -407,3 +407,67 @@ of `gpt-5.6-*` and `glm-5.3`). Board suite 91/91. Jason's board on 7331
needs a restart to pick this up. Sonnet review found the model hover title
escaped an already escaped string (double encoding for names with `&` or
`<`); fixed before commit, then APPROVED on that basis.
**2026-09-12 — Piece 2: reply-from-board (#1505).** Built to the brief
above. `POST /api/reply {agent, text}` rescans, finds the row, and runs
`tools/tmux/agent-send.sh -s <session> -S <host>:control-board -m <text>`,
plus `-L <socket>` when the registration names one, with
`MOSAIC_TMUX_SOCKET` stripped from the tool's environment so the
registration is the only source of the socket. The answer carries
`delivered`, the exit code, stdout and stderr; a non-zero exit is
reported as is. Refusals before the tool runs: bad or empty or long text
400, unknown row 404, no registration or stale registration or no tmux
session 409, tool not startable 500. The page puts a one-line box and
Send in the detail of rows whose registration is live and has a tmux
session; other rows say "reply needs a registered seat". The receipt
reads `delivered <time> to tmux <session>` or `failed (exit N): <stderr>`;
drafts and receipts survive the periodic refresh and the caret is given
back to the box; a delivered reply clears the box and rescans at once.
Not built, per the brief: raw send-keys, queue, retries, history,
broadcast, more than one seat per request; no change to `packages/seat`,
the registration record or `agent-send.sh`.
Live check on a scratch server (port 7332) with a scratch tmux session
`cb-reply-test` running `cat` and a temp data root: `agent-send.sh`
accepted `dragon-lin:control-board` as sender, the text arrived in the
pane as `[dragon-lin:control-board -> dragon-lin:cb-reply-test] ...`, and
the page showed `delivered`. Killing the session also killed the
registered pid, so the row went stale and the box gave way to "reply
needs a registered seat", as intended. With a live pid pointed at a
missing session the receipt read `failed (exit 1): ERROR: invalid
resolved pane identity` and the draft stayed in the box. Tests: a fake
`agent-send.sh` on the server's configured path captures argv and the
environment; cases for `-s`/`-S`/`-m` without `-L`, with `-L`, exit 4 with
stderr, each refusal with the tool never called, a missing tool, and
static checks on the page (box only when `alive !== false` and a tmux
session, JSON POST, escaped receipts). Board suite 98/98 (was 91). Gate C
is Jason's to run against a real waiting seat; his board on 7331 needs a
restart first.
Sonnet review of the piece: one finding, the route comment at the top of
`serve.mjs` had the `/api/reply` line spliced into the middle of the
`/api/seen` description, so it read as if a reply rescanned and returned
the index; the comment was split back into two entries before commit.
Noted but not changed: the body limit is bytes and the text limit is
characters, so a non-ASCII text near 2000 characters is refused as an
oversized body rather than as long text; and a row open in both "Waiting
on you" and its project group has two forms with the same id, so the
refocus after a refresh may land on the other copy.
**2026-09-12 — Gate C, first exchange, and the trailer.** Jason answered
darkwing from his restarted board with "pizza?". The delivery worked
(`[dragon-lin:control-board -> dragon-lin:darkwing] pizza?` in darkwing's
session log) and the row showed the answer as its last message within one
scan, so the return path exists: the board reads the transcript. What
failed was the seat's side: the communications skill told the seat a reply
only exists once `agent-send.sh` runs, the seat aimed at `-s
control-board`, and the tool refused because `control-board` is not a
tmux session ("invalid resolved pane identity"). That refusal is correct;
the board is not made a pane. Fix in the board, relayed as Jason's ruling
by the professor session: every message the board sends ends with a fixed
line, `(control-board: answer in your own session as usual; the board
reads your transcript. Do not agent-send to control-board.)`, exported as
`REPLY_TRAILER`; `-S` stays `<host>:control-board` so the preamble stays
honest about the origin. The fake agent-send test now checks the message
text ends with the trailer; the receipt is unchanged. Committed with the
piece.
+21 -7
View File
@@ -32,13 +32,27 @@ Package: `packages/seat`. Fleet seats stay on their own launchers (Jason's
ruling, 2026-09-12); only `agents/` seats register. A record whose pid is
gone is stale and does not override the derived values.
Next action: step 3 of #1503 continues — daily use with #1504 landed. Jason
launches his seats through the new path (or the launch scripts, which now
register), sets tasks with `seat task`, and reports what is wrong, missing
or confusing; each report becomes a small fix with a test, committed and
pushed to `refactor`. The sequence Jason set stays: seat registration (done),
then reply-from-board, then the WebUI on Dewey's Console design absorbs the
board as its first screen. Close #1503 when Jason says the page answers "who
Gate B passed 2026-09-12 (seat registration shown and used on the board).
Piece 2, reply-from-board (#1505; brief in the plan page, section "Piece
2: reply-from-board", approved by Jason 2026-09-12), is built and pushed
2026-09-12: a text box and Send in the detail of registered, live rows;
`POST /api/reply` runs `tools/tmux/agent-send.sh -s <session> -S
"<host>:control-board" [-L <socket>] -m <text>` and returns the exit code,
stdout and stderr; the page shows `delivered` or `failed` with the stderr.
No send-keys, queue, history or broadcast; no change to `packages/seat` or
`agent-send.sh`. Board suite 98/98.
Gate C's first exchange happened 2026-09-12 ("pizza?" to darkwing:
delivered, read back within one scan; the seat's send-back to
`control-board` was refused as expected, so every board message now ends
with a fixed trailer telling the seat to answer in its own session).
Next action: Jason confirms Gate C on the trailer build (restart the board
on 7331, answer a `waiting` seat, the row turns `working` with the reply
as its newest user message and the seat answers in its own session).
Record the pass or the failure in the plan page and here. Then the WebUI on Dewey's Console design absorbs
the board as its first screen. Close #1503 when Jason says the page answers "who
is waiting on me" without him opening a terminal. Out of scope until he
asks: auth or provider registry, roster schema changes, hooks/plugins, comms,
memory, multiple sessions per seat, stopping seats, new root files. The