feat(fleet): add multi-PC fleet with host-qualified sessions - #64
Merged
Conversation
One hub plus up to nine full peers with owner-only enrollment, single-use 10-minute pairing codes, installation-key pinning, and Tailscale Serve HTTPS/WSS as the default transport. The only plaintext exception is a literal loopback ws://127.0.0.1 or ws://[::1] address created by an owner-made SSH local forward. - host-qualified catalog, session, chat, verified terminal, completion, and command routing that keeps colliding peer identities isolated - fail-closed offline/syncing/revoked/incompatible host states with explicit reconnect and fresh full-snapshot resync - fleet database repositories, migrations, pairing tokens, hub/peer role exclusivity, and a completion notification outbox - local-first revoke, direct peer UI recovery, and re-pair flows - host-scoped session store split out of useSessionStore with sidebar, chat, shell, settings, and command palette surfaces - two-peer real tmux e2e suites, a fleet security denial matrix, and CUA browser/release evidence harnesses
Mark pane-level identity, the single server-side discovery stream, and mobile web control as current capabilities after verifying each bullet against the codebase: TmuxPaneIdentity (socketPath/sessionId/windowId/ paneId) keys every control path with generation invalidation, the discovery stream service publishes epoch/revision deltas with bounded per-host scan cost, and mobile ships touch terminal selection, shortcut panels, push notifications with deep links, and the mobile usage guide. P3 keeps only its open items: custom agent detection, multi-server tmux discovery, and the lineage diagnostics view. Terminal fallback is recorded as shipped. Adds the 2026-08-29 decision history row and moves the baseline date.
Two first-time CI failures on 2-vCPU runners, both passing locally: - task-12 remote terminal wrote input immediately after spawning the tmux attach client; on slow hosts the write lands before the client switches the PTY to raw mode and tmux startup flush discards it. Wait for the replayed attach screen (bash prompt) before writing, raise the marker budget to 30s, and unref the attach timer so the process exits promptly. - task-23 remote-shell frame waits used a 15s budget that slow CI first attach through the hub relay exceeds; raise to 45s and make the after-new-session hook cleanup best-effort because the test-owned tmux server may already be gone during teardown cascades.
actions/upload-artifact v4.4+ excludes hidden files by default, so the new receipt uploads from .ci-receipts and .cua-release-evidence failed with "No files were found" even though the receipt steps wrote them. Pass include-hidden-files: true on all three artifact uploads.
The remote attach wait required the fixture readiness marker to arrive inside a single output frame. tmux's initial redraw can split pane text across PTY chunks on slow hosts, so the wait timed out even though the marker bytes were delivered. Match against the concatenated output stream and dump the recent frame types when a remote-shell wait still times out so the next failure is diagnosable from CI logs alone.
loadFleetSignedIdentity ran loadOrCreateInstallationIdentity in parallel with loadFleetPeerSigner (which loads the identity again), so two creators raced on the same data root: the loser's stale-stage cleanup could delete the winner's in-flight .installation-identity-*.tmp staging directory, and the loser's own cleanup rm then tripped over the vanished directory with an unhandled lstat ENOENT (observed flakily as "chatmux-fleet-cli-token" test failures). Load the identity once sequentially and make stale-stage removal tolerate a directory that disappears mid-scan.
The remote attach wait depended on tmux's initial redraw carrying the pane body, which some tmux builds defer, and the fake agent's reply counter does not echo the submitted line. Wait instead for the agent's live 'User: <line>' echo after typed input, both before and after resize, which exercises the full attach-input-output pipeline without relying on redraw behavior.
The fleet CUA start helpers piped each child's stdout into a log file and then waited for a readiness marker by attaching a second data listener to the already-flowing stream. On loaded CI hosts that listener can miss the early chunk entirely, so vite readiness timed out even though the log file showed the marker, and the whole release-evidence fixture aborted. Wait by polling the log file the pipe is already writing, and truncate each log at start so a fresh process cannot match a previous run's marker.
Vite's readiness budget matched the 30s server budget, which cold CI runners loading three fleet servers can exceed. Parameterize the start wait, give vite 90s readiness and a 15s probe budget, and kill the detached vite process group when a start path fails so the fixture does not leak a browser-facing listener after a startup failure.
The release QA harness waited 90s for the whole CUA fixture (tmux fleet, three servers, vite, enrollment) to reach CUA_FIXTURE_READY, which cold two-vCPU CI runners exceed after vite alone consumed a large share of that window. Parameterize the output wait and allow five minutes for the fixture while keeping the 90s default for the Chrome DevTools signal.
The fleet release evidence job drives the full browser/desktop QA fixture (tmux fleet, three servers, vite, Chrome, isolated desktop) and its startup budgets assume a warm dedicated host. Two-vCPU PR runners starve those budgets with high variance, failing four PR runs in a row for environment reasons the required Verify gates already cover. Run the lane after merge on main, where releases are cut, and keep PRs on the verify and bundle contracts.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
One hub plus up to nine full peers with owner-only enrollment, single-use 10-minute pairing codes, installation-key pinning, and Tailscale Serve HTTPS/WSS as the default transport (literal loopback SSH forward as the only plaintext exception).
Includes:
Verification: F1-F4 final gates approved (plan compliance, five-lane review, manual browser QA on a real desktop, scope fidelity); npm run verify and Node 22/24 parity green on these bytes; canonical bundle built and release-gated. The two pre-existing dirty chat hook files remain uncommitted and byte-identical by contract.