vnc sidecar: per-user desktop sessions via sidecar architecture

replaces the single hardcoded systemd VNC service with a dynamic
sidecar that manages per-user VNC sessions on demand. any authenticated
user can now access their own desktop, not just Super Admin.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-06 10:49:53 +00:00
co-authored by Claude Opus 4.6
parent a0d9ea63f9
commit daf5580c39
12 changed files with 467 additions and 190 deletions
+29 -17
View File
@@ -1,5 +1,4 @@
import type { MessageCost, PiEvent } from '../api/pi/types';
import type { Job, EnqueueParams, JobProgress } from '../queue/types';
// ── Envelope ──
@@ -23,17 +22,16 @@ export type SidecarCommand =
| { type: 'pi:abort'; id: string; sessionId: string; requestId: string }
| { type: 'pi:kill'; id: string; sessionId: string }
| { type: 'pi:set-thinking'; id: string; sessionId: string; level: string }
// Queue
| { type: 'queue:enqueue'; id: string; params: EnqueueParams }
| { type: 'queue:cancel'; id: string; jobId: string }
| { type: 'queue:list'; id: string }
| { type: 'queue:get'; id: string; jobId: string };
// VNC
| { type: 'vnc:start'; id: string; params: VncStartParams }
| { type: 'vnc:stop'; id: string; email: string }
| { type: 'vnc:status'; id: string; email: string };
// ── Responses/Events (sidecar → API server) ──
export type SidecarEvent =
| { type: 'pong'; id: string }
| { type: 'state:sync'; id: string; state: SidecarState }
| { type: 'state:sync'; id: string; state: ClaudeState }
| { type: 'proxy:secret'; id: string; secret: string }
// Claude Code
| { type: 'claude:spawned'; id: string; sessionKey: string }
@@ -47,22 +45,19 @@ export type SidecarEvent =
| { type: 'pi:event'; sessionId: string; event: PiEvent }
| { type: 'pi:error'; id: string; error: string }
| { type: 'pi:killed'; id: string }
// Queue
| { type: 'queue:enqueued'; id: string; job: Job }
| { type: 'queue:cancelled'; id: string; job: Job | null }
| { type: 'queue:list'; id: string; jobs: Job[] }
| { type: 'queue:get'; id: string; job: Job | null }
| { type: 'queue:error'; id: string; error: string }
// VNC
| { type: 'vnc:started'; id: string; port: number; display: number }
| { type: 'vnc:stopped'; id: string }
| { type: 'vnc:status'; id: string; session: VncSessionInfo | null }
| { type: 'vnc:error'; id: string; error: string }
// Generic
| { type: 'error'; id?: string; error: string };
// ── Shared state snapshot ──
// ── Claude sidecar state ──
export type SidecarState = {
export type ClaudeState = {
proxySecret: string;
claudeSessions: Record<string, string>; // sessionKey → Claude Code session_id
piSessions: PiSessionInfo[];
uptime: number;
};
export type PiSessionInfo = {
@@ -114,6 +109,23 @@ export type PiSpawnParams = {
sessionFile?: string;
};
// ── VNC types ──
export type VncStartParams = {
email: string;
username: string;
role: string | null;
resolution?: string;
};
export type VncSessionInfo = {
email: string;
display: number;
port: number;
pid: number;
alive: boolean;
};
// ── PTY types ──
export type PtyInitConfig = {