the platform stops knowing what VNC is

Desktop is a plugin as of today, and this is the half that leaves. sidecar/vnc/, api/desktop/ and
apps/Desktop/ are gone from src/, along with the vocabulary that was the real problem.

sidecar/protocol.ts had a VNC section: vnc:start, vnc:ensure-password, vnc:stop, four reply variants
and two types. sidecar-registry.ts had startVnc, ensureVncPassword, stopVnc, isVncConnected. Four
functions and a dozen message types in the platform, describing a feature that is not the platform's —
the rule CLAUDE.md states most plainly, and its clearest breach.

What replaced them is generic. sendSidecarCommand(kind, cmd) and isSidecarConnected(kind) know that a
command has a type and an id and that a reply carries the same id. A plugin's sidecar speaks whatever
it likes through them and nothing here has to be edited when it adds a message.

The desktop PERMISSION leaves too, and that is the interesting part: it was kind: 'execution', a kind
a plugin permission cannot have and never will, because a permission nobody can delegate is a
contradiction. The plugin expresses the same refusal as ownerOnly: ['/'] — every path under it denied
to anyone but the owner. Verified: /api/desktop, /api/desktop/vnc-password and a path that does not
exist all resolve owner-only. registry.test.ts records the round trip rather than just dropping the
key from its list.

Also gone: the app store's `vnc` entry, which would have offered an older second way to install the
same thing against a process PM2 no longer has, and four commented-out mounts left from 2026-08-13.

The disconnect-scope test used startVnc as its stand-in for "another sidecar's long-running request".
It now uses the generic door, which is the better instrument — that file is about the blast radius of
a disconnect and never had anything to do with VNC.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-21 00:42:19 +00:00
co-authored by Claude Opus 5
parent b1a2d67b76
commit fcdbf02649
20 changed files with 67 additions and 929 deletions
+7 -4
View File
@@ -6,7 +6,7 @@
// direction. Sidecars restart independently by design; that is the whole point of them being PM2 peers.
import { afterEach, describe, expect, test } from 'bun:test';
import { registerSidecar, unregisterSidecar, spawnClaude, startVnc } from './sidecar-registry';
import { registerSidecar, unregisterSidecar, spawnClaude, sendSidecarCommand } from './sidecar-registry';
/** A socket that records what was written and never answers, so requests stay pending. */
function silentSocket() {
@@ -55,7 +55,10 @@ const CLAUDE_PARAMS = {
sessionKey: 'test-session',
};
const VNC_PARAMS = { email: 'owner@example.com', username: 'owner' };
// A stand-in for "some other sidecar's long-running request". This was `startVnc` until the desktop
// plugin took it on 2026-08-21; the generic door is the better instrument anyway, since what this file
// tests is the SCOPE of a disconnect and never had anything to do with VNC.
const otherRequest = (id: string) => sendSidecarCommand('vnc', { type: 'vnc:start', id });
describe('unregisterSidecar', () => {
test('an unrelated sidecar disconnecting leaves other sidecars’ requests in flight', async () => {
@@ -80,7 +83,7 @@ describe('unregisterSidecar', () => {
test('a sidecar’s own disconnect still rejects its own request', async () => {
const vnc = register('vnc', ['vnc']);
const start = startVnc(VNC_PARAMS);
const start = otherRequest('test-start');
const state = settleState(start);
await awaitSend(vnc);
@@ -94,7 +97,7 @@ describe('unregisterSidecar', () => {
register('agent', ['claude']);
const vnc = register('vnc', ['vnc']);
const desktop = startVnc(VNC_PARAMS);
const desktop = otherRequest('test-desktop');
const desktopState = settleState(desktop);
await awaitSend(vnc);