Stop always calls systemctl and treats exit 5 (unit already collected)
as done, so there is no is-active/stop race. Cleanup never masks the
copy error, the play ssh timeout covers the remote worst case, and
tests cover stop exit codes and systemd-run stderr reporting.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Stop is a no-op when the collected player unit is already gone
(raw systemctl stop exits 5 on the Frame; verified 2026-09-29).
- Surface systemd-run stderr when the player can't start.
- Keep the copy error if the cleanup ssh also fails; reject upload
names that the play path can never accept.
- Allow 60 s for play (ffprobe 30 s + systemd-run 15 s remote).
- Docs: four-hour cap is unconditional; no delete action yet; fix a
garbled timing sentence.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
HTTPServer.server_bind calls socket.getfqdn, which stalled past the MCP
backend's 10-second startup window on GitHub's macOS runners.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Any unexpected error while launching clears the launch and reports it, so
the pad can always be started again; the temporary stderr file is made
inside the handled path and a failure reading it is tolerated.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
discard() swallows OSError as well as Failure, so a launch that fails and
can't remove its copy still reports the error and can be started again.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The agent holds its copy from its first status line on and tidies it up
however it ends. Before that (a launch error, or stopped before the agent
ran) the server removes the copy itself. discard() only ever removes
incoming copies, never the iPhone bundle's own. The retry race test waits
for both contenders' decisions instead of sleeping.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- A start turned off while copying removes the copy instead of starting an
agent that would be killed before it could tidy up.
- A local read error while copying removes the partial copy too.
- The retry race test holds the new start until the retry has decided, so
it fails every time without the fix (checked 3/3), not by luck.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review round 12: a failed USB-C tunnel now retries the normal path; an
existing HostKeyAlias wins; --host with --usb is rejected; the Steam
desktop-streaming claim is now 'untested' (Valve documents the desktop
showing when a game loses focus); the Show/cleanup overlap test blocks
for real (it fails without the lock). Verified live: with the cable out,
the route is the normal path.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Each start keeps its copy (holding a lock on it) until it ends, however
it ends, then removes it; a restart can still unpack it.
- The server removes a partial copy when copying fails.
- Other copies are removed only when unlocked and over an hour old.
- Tests: a start racing the need-packages retry (one agent, not two), a
restart that must unpack its copy, a held copy surviving the sweep.
Both regression tests fail without their fix.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Plugged into the Mac, the Frame is a USB network device ("Steam Frame",
usb0 at ~0.9 ms). The tunnel uses it when the Frame's usb0 answers,
with the usual host key; otherwise the normal path. Interleaved runs:
content p50 7 vs 10 ms, click to drawn 17 vs 27 ms, scroll p95 23 vs
31-37 ms. FRAME_MACVIEW_USB=0 turns it off; the card says "over USB-C".
- Bench: --usb, and the route is recorded per run.
- Docs: Steam's own streaming (no SteamVR host on macOS; Remote Play pairs
but streams games, not windows; test blocked); the Frame's USB network;
the 2026-09-28 health-check boot-loop recurrence.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- The need-packages retry only runs if no start() has taken over, so two
agents can't end up running for one session.
- Each copy goes to its own incoming folder; the agent removes its own once
connected (and any a cancelled start left over an hour ago), so a stopped
start can't delete files another is copying or still needs.
- A missing package folder means need-packages, not an error.
- Tests keep fake agents running, so they check ready and which agent owns
the session, plus a bounded retry and the tidy rule.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- iPhone build fails if the bundle can't be made, instead of shipping a
stale archive with an empty version.
- A different build that another device is using right now is left running
and replaced once nobody is, rather than stopped under them.
- If what's installed changed after the server looked, the agent asks for
the packages (need-packages) and the server copies them and starts again.
- The installed-build check sends bytes, so Windows' CRLF can't break it.
- Starting again while a stopped start is still copying launches anew;
concurrent copies use their own temporary names.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review round 11: the cleanup's check and pkill now hold a lock that Show
takes to count itself in, so a new viewer can't start between them.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Review round 10: a Show replacing its own stream could have its new viewer
ended by the cleanup; a viewer reset left the delayed relay pending.
Verified: the relay delivers what's queued, then closes the agent side.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
- Chromium on the Frame outlived its last viewer window (verified: 11
processes left after a run). Once nothing is shown, Stop ends it, unless
Show was pressed again meanwhile; its profile is Frame Control's own.
- Relay --delay: if the agent side fails, close the viewer side too
(review round 9).
- Docs: the final scroll run captured 57 fps; don't blame ScreenCaptureKit.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The keyboard and trackpad no longer fetch KDE Connect from Valve's package
repository on the Frame. The desktop apps and the iPhone app's Frame bundle
carry Valve's arm64 build of kdeconnect 24.02.2-1 and the five libraries it
links (kcontacts, kpeople, modemmanager-qt, pulseaudio-qt, libfakekey),
pinned by SHA-256 in frame/kdeconnect/packages.json and downloaded at build
time from the kdeconnect-frame-24.02.2-1 release, which also holds Valve's
complete source package for each.
On first use the computer copies them over its SSH connection (the iPhone
bundle already has them on the Frame); the agent checks each SHA-256,
unpacks them and stamps which build it is, so later starts copy nothing.
No internet on the Frame, 3.6 MB instead of 8 MB, 18 MB unpacked instead of
82 MB (ModemManager and friends were packaging-only dependencies).
GPL/LGPL compliance: frame/kdeconnect/NOTICE.md names each exact version,
licence and source; per-project licence texts in frame/kdeconnect/LICENSES;
THIRD_PARTY_NOTICES.md; an About and licences dialog in the app.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Third review follow-up (PackageParser.buildClassName). Confirmed the
targetActivity resource id 0x01010202 in Open Saber Plus's manifest.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brings in #4, #5, #8 (fbl100's verified Remmina/VNC mirror), APK
alternatives and the website. docs/streaming.md: Mac in the headset stays
the recommendation (now verified on the Frame); Remmina keeps #8's verified
evidence as the whole-screen fallback. docs/mac-in-headset.md cites #8's
lag finding.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>