Check pi liveness per tmux pane and add "Seen" marks to the control board (#1503)

First step-3 refinement from Jason's daily use. Liveness now lists the
panes of the agent's tmux session and counts it alive only if a pane runs
pi, so killed pi sessions whose tmux session still exists show offline
instead of waiting. A "Seen" button on waiting and error rows stores the
row's lastActivity in <dataRoot>/board/seen.json (clicks only, never
rewritten by a scan, fail closed if corrupt) and drops the row from
"Waiting on you" until the agent writes anything newer; "Unsee" reverses
it. New POST /api/seen route: JSON only, 4 KB limit, 400 on bad input.

Tests: control-board 63/63 (30 new), registry 69/69. Review APPROVED;
receipt docs/plans/reviews/2026-09-12_control-board-step3-seen-marks.md.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-09-12 08:24:49 -05:00
co-authored by Claude Fable 5.1
parent ebedd1281e
commit 88d21defde
12 changed files with 777 additions and 21 deletions
@@ -0,0 +1,69 @@
# Control board step 3, first refinement: real liveness and "Seen" marks
Date: 2026-09-12. Recorded by jarvis (Claude Code, coordinator). Tracking: #1503.
Plan: `docs/plans/2026-09-12_control-board-mvp.md` (Step 3 log).
## What Jason reported
After using the page: "most of the sessions are actually just in a complete
state rather than waiting on me". Also, he had terminated the darkwing and
dewey pi sessions, and both still showed "waiting".
## Cause
1. Liveness only asked tmux whether a session by that name existed. Both
tmux sessions still existed (now running bash and Claude), so the scanner
read the old pi session log as live status.
2. Pi records that the agent finished a reply, not whether that reply was a
question or a completion report. Both landed in "waiting".
## What was built
- `tmuxIsAlive` now lists the panes of the named tmux session and reports
alive only if one runs `pi`. A session that exists but runs something else
is offline. The tmux check is injectable for tests.
- "Seen" marks: `<dataRoot>/board/seen.json` maps `project/agent` to the
`lastActivity` timestamp Jason marked. A marked row keeps its state but
leaves "Waiting on you" until the agent writes anything newer, which
changes the timestamp and clears the mark by itself. "Unsee" clears it by
hand. The file is written only by clicks (atomic, mode 0600); a scan reads
it and never rewrites it; a corrupt file refuses the scan (fail closed).
- Server: `POST /api/seen` with a JSON body; requires
`Content-Type: application/json` (so a page from another site cannot post
it without a CORS preflight, which this server does not answer); body limit
4096 bytes; 400 on bad input; 500 if the rescan fails. Other methods 405.
- Page: Seen / Unsee button beside the state badge, "seen" tag, footer count.
CLI `scan --print` shows `s` for seen rows and a seen count.
## Tests
`node --test packages/control-board/tests/`: 63 pass, 0 fail (30 new, written
by a Claude Sonnet helper against the spec; no src change was needed).
`node --test packages/mosaic/tests/`: 69 pass, 0 fail, unchanged.
## Live check
Real tmux, full fleet, Chrome via DevTools on port 7332. Before: 18 rows
waiting on Jason including darkwing and dewey. After the liveness fix: 16,
both now offline (their tmux sessions run bash). Clicking Seen on zane wrote
`{"fleet/zane": "<timestamp>"}` (mode 600), the row left "Waiting on you"
and showed a seen tag with Unsee in its project table; the API reported
15 waiting and 1 seen. Unsee emptied the file and restored 16. Console: no
messages. Screenshot `/tmp/control-board-seen.png` (not committed). The
board was left with no marks.
## Independent review
Reviewer: code-review agent (Claude Sonnet), read-only. It checked the pi
runtime source to confirm a live pi pane reports `pi` as its command (pi sets
its process title, and tool subprocesses do not take over the pane), traced
the CORS argument for the POST route, the escaping in the new markup, the
single-response paths in the server, and the stale-mark fail-safe. Verdict:
APPROVED with one finding: the README quoted the tmux command without the
`-s -t '=<session>'` flags the code uses. Fixed in README and plan page.
## Boundaries kept
Loopback only, no auth, no daemon, no launcher changes, no new root files,
no run records touched. `seen.json` is Jason's input, not derived data, and
is the only board file a scan does not regenerate.