feat(host): the Mac follows its commands over Neon Realtime and says it listens on a slow timer

Merged
CH
10d ago
+525-241

Stack 4 · Mac commands, on #3274. compose-mac-commands.ts follows the account's unclaimed commands through the live layer, in place of one held read after another.

  • Follow: uses liveRows with reseal and poll. Realtime is off (no LUKE_REALTIME), so this is the poll the service holds until the rows move (#4272). Every read names accountId (LIVE_USER_QUERY) and the Mac's kinds (RUNS, shell). When the service answers signed out, the follow ends.
  • Claim: each new command is claimed by id, earliest queued first, and run or refused by its kind, as before #4273. Only what the service grants runs, so two Macs never run one command.
  • Presence, the one timer that remains: macCommands.listening(RUNS) is sent at once and then every 15 s, inside the service's 45 s LISTENER_FRESH. No row change can say the process is alive. Each beat reads the gate first. When it shuts, the follow and the beats end, and running commands are stopped.
  • No fallback: an unanswered read is asked again after 5 s. The client's held read waiting() is removed, because the desktop no longer asks for it. The service still answers it.

compose-mac-commands.test.ts runs against a scripted service with Realtime off: answered polled, and held until the rows move. It covers:

  • the gate;
  • claim order;
  • a browser command refused while the shell one runs;
  • a command queued later heard within about a second;
  • every read naming the account and shell;
  • the 15 s beat, and stopping when the setting is switched off;
  • a signed-out read following nothing and being asked again.

Checked piece by piece: tsc for features and host; vitest over all of host and the client; knip; oxlint; discarded-effect.