Add mosaic launch <seat> with seat registration for the control board (#1504)

New package packages/seat and wrapper scripts/mosaic. `launch <seat>` writes
<dataRoot>/seats/<layout>/<seat>/registration.json and then execs the seat's
launch.sh unchanged; `seat task <seat> <text>` edits the task only. The board
reads registrations, matches by sessions directory, and lets a registered
task, project or workspace override the derived value with a source tag.
The four repository launch scripts register themselves unless already
registered or run with --check. Fleet launchers untouched; one-liner on the
plan page.

Review found the record path keyed by seat name alone (repo and fleet
"darkwing" would collide); fixed by keying on layout. Also: the Pi pin
refusal now names installed and required versions.

Tests: seat 15, control-board 89, launch scripts 5, registry 69, config 24.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
This commit is contained in:
2026-09-12 10:56:17 -05:00
co-authored by Claude Fable 5.1
parent 01d9a19612
commit 69f99323c7
25 changed files with 1766 additions and 46 deletions
+82 -2
View File
@@ -114,6 +114,10 @@ Two kinds of agents are scanned today:
- One file per agent: `<dataRoot>/board/sessions/<project>/<agent>.json`
- One summary file for the whole board: `<dataRoot>/board/index.json`
- One registration per seat started through `mosaic launch`:
`<dataRoot>/seats/<layout>/<seat>/registration.json` (#1504). Written by the
launcher, read by the board, rewritten on relaunch. Not under `board/`
because the board never writes it.
- `dataRoot` comes from `~/.config/mosaic-dev/config.json`. If that config
is missing or broken, the scanner refuses to run. It does not guess a
fallback location.
@@ -144,12 +148,18 @@ node --test packages/control-board/tests/
## Boundaries
- No changes to any launcher script.
- No changes to any launcher script. Corrected 2026-09-12 by Jason's go on
the `mosaic launch` brief (#1504): the four repository launch scripts
(`agents/{darkwing,dewey,filbert,rocko}/launch.sh`) gained a guard that
re-enters them through `scripts/mosaic launch` for registration. The
fleet launchers under `~/.mosaic` are still untouched; their one-line
change is written in the Step 3 log for orch-01 to apply.
- No new files at the repository root.
- No secrets read, stored, or printed.
- No daemon yet. In step 1 the scanner is run by hand. In step 2 the page
may trigger it on refresh; nothing runs it on a schedule.
- No changes to `packages/mosaic`.
- No changes to `packages/mosaic`. Still true: seat registration went into
a new package, `packages/seat`, not the hardened registry.
- This plan does not authorize push or merge beyond whatever the existing
refactor-branch plan already allows.
@@ -210,3 +220,73 @@ used" hold above: the selection is made, on evidence from today's use. It does
not start WebUI build work. Sequence stays: seat registration via
`mosaic launch` (in progress), then reply-from-board, then the WebUI on the
Console design absorbs the board as its first screen.
**2026-09-12 — Seat registration: `mosaic launch <seat>` (#1504).** Gate A
passed on Jason's go, relayed by the professor session with a brief. New
package `packages/seat` and wrapper `scripts/mosaic`:
- `scripts/mosaic launch <seat|seat-dir> [--task ..] [-- args]` resolves
`agents/<seat>/launch.sh` (repo layout) or a seat directory (fleet layout,
`<seatDir>/.pi`), writes `<dataRoot>/seats/<layout>/<seat>/registration.json`, then
replaces itself with the launch script (`process.execve`), so the pid, the
terminal, the exit code and the tmux foreground command are the script's
own. The record: seat, project, task, workspace, tmux, harness, startedAt,
pid, plus sessionsDir (the board matches on it, not on the name, because
"darkwing" exists in both layouts), seatDir, launchScript, layout,
updatedAt, version. Rewritten on every launch.
- `scripts/mosaic seat task <seat> <text>` changes the task only; refuses
when the seat was never launched this way.
- Scanner: registrations are read on every scan; a registered task, project
or workspace wins and `taskSource`, `activeProjectSource`,
`workspaceSource` read `registration`; empty or null registered fields
leave the derived value; rows without a registration are unchanged. A
malformed record is listed in `registrationErrors` and skipped. Page: a
small "registered" tag next to the value and a Registered line in the
detail (start, harness, pid, tmux, layout).
- The four repository launch scripts (`agents/{darkwing,dewey,filbert,
rocko}/launch.sh`) gained a five-line guard: unless
`MOSAIC_LAUNCH_REGISTERED` is set or `--check` is among the arguments,
re-enter through `scripts/mosaic launch --harness pi|claude-code`. So the
plain `agents/darkwing/launch.sh` registers too. rocko registers but is
still not on the board: it has no pi session log to scan. Gap, not a bug.
- Fleet launchers under `~/.mosaic` are not edited from this repository.
One-line change for orch-01 to apply in
`~/.mosaic/fleet/bin/launch-seat.sh`, after `SEAT_DIR=` is set (the
per-seat `launch.sh` files are shims and need nothing):
`[[ -n "${MOSAIC_LAUNCH_REGISTERED:-}" || "$CHECK" -eq 1 ]] || exec /mnt/storage/src/mosaic-stack/scripts/mosaic launch --harness pi "$SEAT_DIR" -- "$@"`
Fleet records carry project and workspace null (roster has no project
field; all fleet seats run in `~/.mosaic`), so those rows keep their
derived values until `--project`/`--workspace` are passed or the roster
gains a field.
- Sonnet review found one defect before commit: the record path was keyed
by seat name alone, so the repo and fleet "darkwing" would have overwritten
each other. Fixed by keying on layout and seat
(`seats/<layout>/<seat>/`); `seat task` refuses a bare name registered in
more than one layout unless `--layout` is given. Second review pass
approved. The reviewer also noted `process.execve` is marked experimental
in Node; recorded, accepted (Node 24+ is the floor and the API is the only
way to keep the seat's pid).
- Name clash noted: the npm-global `mosaic` CLI (estate tooling) has no
`launch` or `seat` command; the repository wrapper is run by path.
Tests: seat package 15 (record write via a fake launch.sh, args and exit
passthrough, relaunch rewrite, `--task`, tmux null, tmuxContext with a fake
exec, task update and its refusals, malformed records, samePath through a
symlink, recursion guard); control board 79 → 89 (override precedence,
empty registration, no registration parity, loadRegistrations errors,
matchRegistration by realpath and the same-name-different-layout case, scan
on disk, server round trip, page source checks); the two launch-script
suites now run each real `launch.sh` in a fixture that is its own data root
and assert the record and the `MOSAIC_LAUNCH_REGISTERED` value Pi/Claude
saw (5/5); registry 69/69; config 24/24. Live check: the real
`agents/darkwing/launch.sh` run through the launcher against a temporary
data root, then a scratch board on 7332 reading that root: the darkwing
row shows the registered task, "registered" on task and project, and the
Registered detail line.
Found on the way: Jason's own launch refused with "local Pi must match
package.json (0.85.1)". Commit 557aba0f (#1499) moved the pin without an
`npm ci` in this checkout (node_modules held 0.84.4; the global pi was
already 0.85.1, which hid it). Fixed by `npm ci`; the refusal now names
both versions. Jason questioned the pin itself; it is canon (AGENTS.md
"Version pin") and stays unless he decides otherwise.
+21 -7
View File
@@ -22,13 +22,27 @@ First step-3 refinement landed 2026-09-12: liveness follows the tmux pane
"Waiting on you" until the agent writes again
(`docs/plans/reviews/2026-09-12_control-board-step3-seen-marks.md`).
Next action: step 3 of #1503 — daily use. Jason opens the page while he works
and reports what is wrong, missing or confusing; each report becomes a small
fix on this package with a test, committed and pushed to `refactor`. 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, hooks/plugins, comms,
memory, launcher changes, non-Pi harnesses, new root files. The registry line
(increment 3, headless identity-env leak) stays parked; #1500 is closed.
Gate A (task, active project, workspace per row) passed on 2026-09-12 and
Jason's go on the professor session's brief opened #1504: seat registration.
`scripts/mosaic launch <seat>` runs a seat's launch script unchanged and
leaves one record at `<dataRoot>/seats/<layout>/<seat>/registration.json` that the
board reads instead of guessing; `scripts/mosaic seat task <seat> <text>`
changes the task. The four repository launch scripts register themselves.
Package: `packages/seat`. Fleet launchers under `~/.mosaic` are not edited
from this repository; the one-line change for them is in the plan page.
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
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
registry line (increment 3, headless identity-env leak) stays parked; #1500
is closed.
## Completed checkpoint: #1500 increment 2 (historical)