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
+32 -5
View File
@@ -14,10 +14,15 @@ rewritable; they are not run records and are not evidence.
| working | The agent is in the middle of a turn: thinking or running a tool. |
| waiting | The agent finished its turn. It is your move now. |
| error | The agent's last turn ended in an error, was aborted, or was cut off. Go look at it. |
| offline | There is no live tmux session for this agent right now. |
| offline | There is no live tmux session for this agent right now, or its session exists but no longer runs `pi`. |
| idle | The agent is live but has not had a conversation yet. |
| unknown | The scanner could not ask tmux (missing or not answering). It does not assume the agent is alive. |
Liveness means a pane in the agent's tmux session is actually running `pi`
(`tmux list-panes -s -t '=<session>' -F '#{pane_current_command}'`). A tmux session that still
exists but only runs bash or some other program counts as offline, not
waiting.
## Commands
```
@@ -42,13 +47,24 @@ bind to anything but a loopback address.
- `--liveness tmux|assume-alive` — how to decide if an agent is alive.
`tmux` (default) checks the real tmux session. `assume-alive` treats
every agent as alive, useful for tests or environments without tmux.
- `--print` — (`scan`) also print a one-line-per-agent table to stdout.
- `--print` — (`scan`) also print a one-line-per-agent table to stdout. The
first column is `*` for a row waiting on you, `s` for a seen row, or blank
otherwise. The final summary line reads
`board: <dir> (N sessions, N waiting on you, N seen)`.
- `--port N` — (`serve`) port to listen on. Default `7331`; `0` picks a free port.
- `--host ADDR` — (`serve`) loopback address to bind. Default `127.0.0.1`.
Any non-loopback address is refused.
Routes served: `/` (the page), `/api/board` (rescan, returns `index.json`),
`/healthz`.
Routes served:
- `GET /` — the page.
- `GET /api/board` — rescan, returns `index.json`.
- `POST /api/seen` — mark or unmark a row as seen, then rescan and return
`index.json`. Body must be JSON (`Content-Type: application/json`, no
more than 4096 bytes): `{"project", "agent", "lastActivity", "seen"}`.
`seen` defaults to `true`; pass `false` to unsee. Bad input, a missing
header, or an oversized body gets a `400`.
- `GET /healthz`.
## Exit codes
@@ -66,12 +82,22 @@ node --test packages/control-board/tests/
```
<dataRoot>/board/
index.json # summary: counts, waiting-on-you list, all records
index.json # summary: counts, waiting-on-you list, seen list, all records
seen.json # Jason's "seen" marks (see below)
sessions/
<project>/
<agent>.json # one status record per agent
```
`seen.json` holds `{ "<project>/<agent>": "<lastActivity>" }`. It is written
only when Jason clicks "Seen" or "Unsee" on the page (via `POST
/api/seen`); a scan reads it but never writes it. A mark applies only while
the agent's newest message still has that exact `lastActivity` timestamp —
as soon as the agent writes anything new, `lastActivity` changes, the mark
no longer matches, and the row falls back into "Waiting on you" on its own.
If `seen.json` exists but is not valid JSON (or not an object of string
values), the scan refuses rather than silently dropping every mark.
## Example status record
```json
@@ -80,6 +106,7 @@ node --test packages/control-board/tests/
"project": "mosaic-stack",
"state": "waiting",
"waitingOnYou": true,
"seen": false,
"alive": true,
"tmux": { "socket": null, "session": "darkwing" },
"sessionFile": "/mnt/storage/src/mosaic-stack/.pi/state/darkwing/sessions/2026-09-12.jsonl",