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:
@@ -9,7 +9,6 @@ import type {
|
||||
LiveClaudeSession,
|
||||
LiveOpenCodeSession,
|
||||
OpenCodeRunParams,
|
||||
VncStartParams,
|
||||
} from './sidecar/protocol';
|
||||
import type { SidecarRegistration } from './sidecar/registration-protocol';
|
||||
import type { TurnMessage } from './api/chat/types';
|
||||
@@ -170,10 +169,43 @@ export function on(eventType: string, handler: EventHandler): () => void {
|
||||
}
|
||||
|
||||
// ── Command sending ──
|
||||
//
|
||||
// ── The generic door, for plugins ──
|
||||
//
|
||||
// `sendCommand` below is private and its `SidecarCommand` union names every message the PLATFORM knows
|
||||
// about. That was fine while every sidecar was the platform's. A plugin's sidecar speaks a vocabulary
|
||||
// this file must not learn — the desktop extraction is what made that concrete: `startVnc`,
|
||||
// `ensureVncPassword` and a dozen `vnc:*` variants lived here, in the platform, describing a plugin.
|
||||
//
|
||||
// So a plugin drives its own sidecar through `sendSidecarCommand`, which knows only that a command has a
|
||||
// type and an id and that a reply carries the same id. The vocabulary stays in the plugin, and nothing
|
||||
// here has to be edited when a plugin adds a message.
|
||||
|
||||
const DEFAULT_TIMEOUT_MS = 30_000;
|
||||
const LONG_TIMEOUT_MS = 6 * 60 * 1000;
|
||||
|
||||
/** A message to a sidecar. `id` is what the reply is matched on; everything else is the plugin's. */
|
||||
export type PluginSidecarCommand = { type: string; id: string } & Record<string, unknown>;
|
||||
|
||||
/**
|
||||
* Send a command to whichever sidecar handles `kind`, and wait for the reply with the same id.
|
||||
*
|
||||
* The plugin-facing half of `sendCommand`. Rejects when no sidecar handles that kind — which for a
|
||||
* plugin means its own process is not running, and is a better error than a timeout.
|
||||
*/
|
||||
export function sendSidecarCommand<T = unknown>(
|
||||
kind: string,
|
||||
cmd: PluginSidecarCommand,
|
||||
timeoutMs = DEFAULT_TIMEOUT_MS,
|
||||
): Promise<T> {
|
||||
return sendCommand(kind, cmd as unknown as SidecarCommand, timeoutMs) as Promise<T>;
|
||||
}
|
||||
|
||||
/** Is a sidecar handling `kind` connected right now? */
|
||||
export function isSidecarConnected(kind: string): boolean {
|
||||
return !!findSidecarHandling(kind);
|
||||
}
|
||||
|
||||
function sendCommand(kind: string, cmd: SidecarCommand, timeoutMs = DEFAULT_TIMEOUT_MS): Promise<any> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const sc = findSidecarHandling(kind);
|
||||
@@ -482,28 +514,7 @@ export function onOpenCodeSession(handler: (sessionKey: string, sessionId: strin
|
||||
// Nothing here any more. The pty sidecar serves its own listener; `/api/terminal/*` is a proxy and
|
||||
// `/api/terminal/ws` a byte relay, both keyed off the `pty:server` port like every other HTTP sidecar.
|
||||
|
||||
// ── VNC ──
|
||||
|
||||
export async function startVnc(params: VncStartParams): Promise<{ port: number; display: number }> {
|
||||
const res = await sendCommand('vnc', { type: 'vnc:start', id: nextId(), params });
|
||||
if (res.type === 'vnc:started') return { port: res.port, display: res.display };
|
||||
if (res.type === 'vnc:error') throw new Error(res.error);
|
||||
throw new Error('Unexpected response');
|
||||
}
|
||||
|
||||
// Provision the VNC password without starting a server. The desktop UI needs it before it can open
|
||||
// the WebSocket that would start one, so asking vnc:start here would be circular.
|
||||
export async function ensureVncPassword(email: string): Promise<string> {
|
||||
const res = await sendCommand('vnc', { type: 'vnc:ensure-password', id: nextId(), email });
|
||||
if (res.type === 'vnc:password') return res.password;
|
||||
if (res.type === 'vnc:error') throw new Error(res.error);
|
||||
throw new Error('Unexpected response from VNC sidecar');
|
||||
}
|
||||
|
||||
export function stopVnc(email: string): void {
|
||||
sendFire('vnc', { type: 'vnc:stop', id: nextId(), email });
|
||||
}
|
||||
|
||||
export function isVncConnected(): boolean {
|
||||
return findSidecarHandling('vnc') !== undefined;
|
||||
}
|
||||
// The VNC helpers that were here — startVnc, ensureVncPassword, stopVnc, isVncConnected — left with the
|
||||
// desktop plugin on 2026-08-21. They were the clearest example of the platform knowing what a plugin was
|
||||
// about: four functions and a dozen `vnc:*` message types describing a feature that is not the
|
||||
// platform's. What replaced them is `sendSidecarCommand` above, which is generic.
|
||||
|
||||
Reference in New Issue
Block a user