Repository navigation
The hub: one page for every devteam instance on a machine - #97
Conversation
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…needs no migration; tests never touch real ports Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…eam session kept, exact targets Two projects on one machine shared the default devteam session, so window names clashed and send could type into the other project's worker. tmux_session defaults to devteam-<project> now, saved to run/tmux-session when the kit first creates the session (never by a read-only command). A project whose live sessions already sit in a devteam session keeps it. Every target matches the session exactly (=name): tmux would otherwise take a prefix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…in 4321-4345, held ports skipped Two projects' dashboards fought over 4321. dashboard_port defaults to null: a checkout reuses the port it saved in run/dashboard-port, else takes the one after the highest instance answering in the block (skipping ports held by anything else), else the lowest free one. A pinned port outside the block warns that the hub won't list it. The move checklist points at the hub. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
hello is a light fold (open questions, fleet mode, live sessions, kit version with +local when the running kit differs from main's) that the dashboard refreshes every 30s; /api/hello answers from the last run at once, for the hub's 300ms probe. The dashboard's Host check moves to the top of the handler: before, GET / and /api/data answered any Host, so a page that rebound its own name to 127.0.0.1 could read the whole dashboard. Owner actions were already guarded. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… to each one's own dashboard Every dashboard tries to serve the hub port; the one that gets it scans the 25-port block for /api/hello on each page load and lists what answers (root, name, kit, Inbox, fleet), each linking to the instance's own origin. No state, no proxy, GET only, its own Host only, and a page that can talk only to itself. The others retry, so the hub moves when its dashboard stops. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ache the name per process liveSessions() lists tmux windows, which asks tmuxSession(), so the legacy check recursed until the stack ran out. A window is this project's when it is named after one of its tickets or planners in the log. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ktrees; lost-Enter test uses the project's session A planner's feature may not be in the main log yet, so plan-<feature> is matched by the checkout's worktree of that name. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#S:#W), not the exact-match target Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…, a light hello, a hub that can't be driven From the PR review: - A port is held when anything answers on 127.0.0.1 or ::1 (a dev server on localhost), not only when binding 127.0.0.1 fails. Two dashboards that pick one port: the loser picks again. - The session name carries a hash of the checkout's path, so two clones of one repo never share. A legacy devteam name is saved the moment it is found, so it can't flip when its last window closes. - hello counts sessions with one tmux call and checks the kit's files once per dashboard; the dashboard runs it one at a time with a timeout. - The hub probes the block at most every 2s however often it is asked, refuses another site's requests, retries when the hub port's holder is merely slow, and lists an instance speaking a newer hello by root and port only. - Printed tmux targets are quoted (zsh expands =word) and session targets are exact. - kitVersion: a kit file main doesn't have counts as +local. - Tests: the ports test now tells +1 from lowest-free and a saved port from lowest-free; a squatter on the hub port; a cross-site request; each run owns its own port block. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… opens from any link - A window counts as this project's only when it started under this checkout's .worktrees/ (the kit starts every worker and planner there with -c), never by its name. A name match let another project adopt Stacks' devteam session for good, now that a legacy name is saved on detection. hello counts sessions the same way. - The cross-site refusal guards /instances.json only: the page itself opens from a README link. - kitVersion: untracked kit files from git ls-files --others --exclude-standard (an editor's swap file isn't a local kit). - Docs name the session devteam-<project>-<hash>; the tests' port block stays below the ephemeral range. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A worker window the owner split, with the split left active, reported the split's start path and stopped counting. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ate/var) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Review (upstream loop). The author's PR, so a fresh reviewer agent reviewed it adversarially, in three passes:
|
Spec:
docs/superpowers/specs/2026-11-04-devteam-hub-design.md. Plan:docs/superpowers/plans/2026-11-04-devteam-hub.md.What
tmux_sessiondefaults tonull. The resolved name isdevteam-<project>, saved torun/tmux-sessionwhen the kit first creates the session; read-only commands and CI write nothing. A project whose live sessions already sit in adevteamsession keeps that name. Every tmux target matches the session exactly (=name), because tmux otherwise takes a prefix (devteamwould finddevteam-stacks).settings --get tmux_sessionandwhoamiprint the resolved name.dashboard_portdefaults tonull. A checkout reuses the port it saved inrun/dashboard-port. Otherwise it takes the one after the highest instance answering in 4321–4345, skipping ports held by anything else, and falls back to the lowest free one. A pinned port outside the block warns that the hub won't list it.helloand/api/hello:hellois a light fold (open questions, fleet mode, live sessions, and the kit version, with+localwhen the running kit differs from main's, by blob, offline). The dashboard refreshes it every 30 s, and/api/helloanswers from the last run at once.startHub, and dashboard/hub.html: every dashboard tries to serve127.0.0.1:4320. The one that gets it scans the block for/api/helloon each page load and lists what answers, each card linking to that instance's own origin. The others retry every 30 s, so the hub moves when its dashboard stops.GET /andGET /api/dataanswered anyHost, so a page that rebound its own name to 127.0.0.1 could read the whole dashboard. Owner actions were already guarded.devteamsession), the ports (save and reuse, +1, held ports, pinned outside the block),hellowith the Host guard, and the hub (list, +1,+local, CSP, own Host, GET only, failover). Every test dashboard uses test-owned ports (DEVTEAM_HUB_PORT,DEVTEAM_PORT_BASE), never the machine's.Why
The owner wants to run several devteam projects on one machine and reach them all from one page. Two projects on one machine collided: they shared the
devteamtmux session, sosend <id>could type into the other project's worker and a restart could kill the other's dashboard, and they fought over port 4321, so one could show the other's Inbox.Authority
The hub can't act for the owner. It has no POST route and no proxy, answers only to its own Host, and its page has
connect-src 'self'andframe-ancestors 'none'. Each card is a link to the instance's own origin, where the dashboard's existing owner checks apply. Instance discovery reads/api/hello, which carries counts, not log content. The Host fix narrows what any page can read.Migration
None for a running project. Stacks resolves
devteam, because its workers' windows are there, and, with no dashboard answering in the block when it restarts, takes 4321 and saves it. New projects getdevteam-<project>and the next port. The owner's new bookmark is http://127.0.0.1:4320.Tests
See the PR comments for the local runs on Node 18.20.4, 20.19.6 and 24.14.1. CI runs 18/20/22/24.
🤖 Generated with Claude Code