Files
stack/packages/control-board/README.md
T
jason.woltjeandClaude Fable 5.1 ebedd1281e Add control board web page and local server (#1503)
Step 2 of the control board MVP (MOSAIC-STACK-D-001): `serve` command starts
a loopback-only local server that serves one self-contained page and re-runs
the status scanner on each /api/board request. The page lists sessions
waiting on Jason first (errors on top), then one table per project with
plain-word states, ages, last messages, expandable detail rows, per-project
hide-offline, and a 10-second auto-refresh with pause.

Tests: control-board 33/33 (10 new: loopback rules, host refusal, all routes,
per-request rescan, 500 path, CLI refusals, live serve, page escaping guard);
registry 69/69 unchanged. Receipt:
docs/plans/reviews/2026-09-12_control-board-step2-review.md.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
2026-09-12 07:58:38 -05:00

3.7 KiB

control-board

Step 1 of the control board MVP (docs/plans/2026-09-12_control-board-mvp.md). This package scans your running pi agent sessions and writes one small JSON status file per agent, plus one summary file, so a later web page can show them all in one place. It reads pi session logs and checks tmux liveness. It does not launch, stop, or talk to any agent. Board files are derived and rewritable; they are not run records and are not evidence.

States

State Plain-words meaning
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.
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.

Commands

node src/cli.mjs scan  [--config PATH] [--repo PATH] [--fleet PATH|none] [--liveness tmux|assume-alive] [--print]
node src/cli.mjs serve [same flags] [--port N] [--host 127.0.0.1]

scan runs once and writes the status files. serve starts a small local web server: open http://127.0.0.1:7331/ in a browser. The page fetches /api/board every 10 seconds; each fetch re-runs the scan, so the page is never staler than that timer. There is no login, so the server refuses to bind to anything but a loopback address.

  • --config PATH — path to the system config file. Defaults to ~/.config/mosaic-dev/config.json. This file must exist and name an absolute dataRoot, or the scanner refuses to run.
  • --repo PATH — path to a project checkout to scan for repo agents (.pi/state/<agent>/sessions). Defaults to the current directory.
  • --fleet PATH|none — path to the fleet agents directory (<fleet>/<agent>/.pi/agent/sessions, tmux socket mosaic-fleet). Defaults to ~/.mosaic/fleet/agents. Pass none to skip fleet agents.
  • --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.
  • --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.

Exit codes

  • 0 — scan completed and status files were written, or the server stopped cleanly.
  • 2 — refused: bad config, missing/invalid dataRoot, or bad arguments. The message on stderr says why.

Tests

node --test packages/control-board/tests/

Output layout

<dataRoot>/board/
  index.json                       # summary: counts, waiting-on-you list, all records
  sessions/
    <project>/
      <agent>.json                 # one status record per agent

Example status record

{
  "agent": "darkwing",
  "project": "mosaic-stack",
  "state": "waiting",
  "waitingOnYou": true,
  "alive": true,
  "tmux": { "socket": null, "session": "darkwing" },
  "sessionFile": "/mnt/storage/src/mosaic-stack/.pi/state/darkwing/sessions/2026-09-12.jsonl",
  "sessionId": "01a06e48-0718-71f2-a889-c263c4800fb9",
  "cwd": "/mnt/storage/src/mosaic-stack",
  "lastActivity": "2026-09-12T15:04:33.000Z",
  "ageSeconds": 42,
  "lastAssistantText": "Ready for the next step whenever you are.",
  "lastError": null,
  "skippedLines": 0,
  "scannedAt": "2026-09-12T15:05:15.000Z"
}