@braedonsaunders/appkit-desk 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/host.d.ts +71 -0
- package/host.d.ts.map +1 -1
- package/host.js +287 -34
- package/host.js.map +1 -1
- package/package.json +1 -1
package/host.d.ts
CHANGED
|
@@ -5,6 +5,10 @@ import type { A11yNode, DeskFrameFormat, DeskHandoverScope, DeskPoint, DeskPoint
|
|
|
5
5
|
export declare const DEFAULT_CAPACITY = 4;
|
|
6
6
|
export declare const DEFAULT_IDLE_SUSPEND_MS: number;
|
|
7
7
|
export declare const DEFAULT_LEASE_MS: number;
|
|
8
|
+
/** The in-guest trim run before a desk suspends — see `trimGuest`. */
|
|
9
|
+
export declare const DEFAULT_TRIM_COMMAND = "/usr/sbin/fstrim";
|
|
10
|
+
export declare const DEFAULT_TRIM_ARGS: readonly string[];
|
|
11
|
+
export declare const DEFAULT_TRIM_TIMEOUT_MS = 60000;
|
|
8
12
|
export declare const DEFAULT_CID_BASE = 100;
|
|
9
13
|
export interface DeskHostOptions {
|
|
10
14
|
/** Root of the golden base image and per-desk assets. */
|
|
@@ -39,6 +43,37 @@ export interface DeskHostOptions {
|
|
|
39
43
|
now?: () => number;
|
|
40
44
|
pathExists?: (path: string) => boolean;
|
|
41
45
|
deviceExists?: (path: string) => boolean;
|
|
46
|
+
/**
|
|
47
|
+
* Delete a destroyed desk's overlay. Injectable so the destroy path is
|
|
48
|
+
* testable without a filesystem; defaults to `rm(path, { force: true })`.
|
|
49
|
+
*/
|
|
50
|
+
removeOverlay?: (path: string) => Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* Bytes that must remain free on the overlay filesystem AFTER admitting a
|
|
53
|
+
* desk. A desk whose overlay cannot be grown fails deep inside the guest —
|
|
54
|
+
* ENOSPC surfaces as a corrupt filesystem or a boot that hangs, which reads
|
|
55
|
+
* as a product bug rather than as a full disk. Refusing the lease up front
|
|
56
|
+
* turns that into one clear error. Zero disables the check.
|
|
57
|
+
*/
|
|
58
|
+
minFreeBytes?: number;
|
|
59
|
+
/**
|
|
60
|
+
* Free bytes on the filesystem holding `path`. Injectable for tests;
|
|
61
|
+
* defaults to `statfs`.
|
|
62
|
+
*/
|
|
63
|
+
freeBytes?: (path: string) => Promise<number>;
|
|
64
|
+
/**
|
|
65
|
+
* Run `fstrim` in the guest just before it suspends, returning the blocks it
|
|
66
|
+
* has freed to the host filesystem. On by default: an overlay that is never
|
|
67
|
+
* trimmed only grows. Set false for a guest without the binary, or one whose
|
|
68
|
+
* filesystem should never be discarded.
|
|
69
|
+
*/
|
|
70
|
+
trimOnSuspend?: boolean;
|
|
71
|
+
/** The in-guest trim binary. */
|
|
72
|
+
trimCommand?: string;
|
|
73
|
+
/** Arguments for the trim binary; defaults to trimming the guest root. */
|
|
74
|
+
trimArgs?: readonly string[];
|
|
75
|
+
/** Bound on the trim; a slow trim must not hold up a suspend indefinitely. */
|
|
76
|
+
trimTimeoutMs?: number;
|
|
42
77
|
}
|
|
43
78
|
export interface DeskStartOptions {
|
|
44
79
|
deskId: string;
|
|
@@ -305,6 +340,42 @@ export interface VerifyDeskHostOptions {
|
|
|
305
340
|
deviceExists?: (path: string) => boolean;
|
|
306
341
|
idFactory?: () => string;
|
|
307
342
|
}
|
|
343
|
+
export interface OverlayReflinkCheck {
|
|
344
|
+
/** Whether `cp --reflink` produced a block-sharing clone in this directory. */
|
|
345
|
+
supported: boolean;
|
|
346
|
+
overlayDirectory: string;
|
|
347
|
+
/** Why the answer is what it is — the `cp` failure when unsupported. */
|
|
348
|
+
detail: string;
|
|
349
|
+
}
|
|
350
|
+
/**
|
|
351
|
+
* Whether the overlay directory's filesystem can make reflink clones.
|
|
352
|
+
*
|
|
353
|
+
* This exists because `cp --reflink=auto` — what `buildDeskLaunchPlan` uses,
|
|
354
|
+
* deliberately, so a desk still boots on a filesystem without reflink — is
|
|
355
|
+
* SILENT when it falls back. On XFS or Btrfs a 20GB base clones instantly and
|
|
356
|
+
* costs kilobytes; on ext4 the identical command copies all 20GB, slowly, per
|
|
357
|
+
* desk. The failure is invisible until a disk sized for reflink sharing fills
|
|
358
|
+
* after two desks, which looks like a capacity bug rather than the filesystem
|
|
359
|
+
* choice it actually is.
|
|
360
|
+
*
|
|
361
|
+
* So the probe uses `--reflink=always`, which REFUSES to fall back, on a tiny
|
|
362
|
+
* throwaway file. Call it at startup and refuse to serve rather than discover
|
|
363
|
+
* this in production — see `assertOverlayReflink`.
|
|
364
|
+
*/
|
|
365
|
+
export declare function verifyOverlayReflink(options: {
|
|
366
|
+
overlayDirectory: string;
|
|
367
|
+
idFactory?: () => string;
|
|
368
|
+
copy?: (source: string, destination: string) => Promise<void>;
|
|
369
|
+
}): Promise<OverlayReflinkCheck>;
|
|
370
|
+
/**
|
|
371
|
+
* Fail loudly at startup on a filesystem without reflink support, naming the
|
|
372
|
+
* cost so the operator can act on it.
|
|
373
|
+
*/
|
|
374
|
+
export declare function assertOverlayReflink(options: {
|
|
375
|
+
overlayDirectory: string;
|
|
376
|
+
idFactory?: () => string;
|
|
377
|
+
copy?: (source: string, destination: string) => Promise<void>;
|
|
378
|
+
}): Promise<OverlayReflinkCheck>;
|
|
308
379
|
export interface DeskHostVerification {
|
|
309
380
|
/** Whether this host can serve desks: KVM present and the guest reachable. */
|
|
310
381
|
supported: boolean;
|
package/host.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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"}
|
package/host.js
CHANGED
|
@@ -9,9 +9,10 @@
|
|
|
9
9
|
* never touches a database and knows nothing about tenants or approvals.
|
|
10
10
|
*/
|
|
11
11
|
import { existsSync } from 'node:fs';
|
|
12
|
-
import {
|
|
12
|
+
import { execFile } from 'node:child_process';
|
|
13
|
+
import { rm, statfs, writeFile } from 'node:fs/promises';
|
|
13
14
|
import { tmpdir } from 'node:os';
|
|
14
|
-
import { join } from 'node:path';
|
|
15
|
+
import { dirname, join } from 'node:path';
|
|
15
16
|
import { randomUUID } from 'node:crypto';
|
|
16
17
|
import { Buffer } from 'node:buffer';
|
|
17
18
|
import { buildDeskLaunchPlan, cleanDeskId, DEFAULT_KVM_PATH, DEFAULT_VMM_PATH, DeskError, } from './plan.js';
|
|
@@ -20,6 +21,10 @@ import { parseCapabilities, parseClipboardText, parseExecResult, parseHandoverBe
|
|
|
20
21
|
export const DEFAULT_CAPACITY = 4;
|
|
21
22
|
export const DEFAULT_IDLE_SUSPEND_MS = 5 * 60_000;
|
|
22
23
|
export const DEFAULT_LEASE_MS = 15 * 60_000;
|
|
24
|
+
/** The in-guest trim run before a desk suspends — see `trimGuest`. */
|
|
25
|
+
export const DEFAULT_TRIM_COMMAND = '/usr/sbin/fstrim';
|
|
26
|
+
export const DEFAULT_TRIM_ARGS = ['/'];
|
|
27
|
+
export const DEFAULT_TRIM_TIMEOUT_MS = 60_000;
|
|
23
28
|
export const DEFAULT_CID_BASE = 100;
|
|
24
29
|
const MAX_BUFFERED_FRAMES = 60;
|
|
25
30
|
/**
|
|
@@ -53,6 +58,13 @@ export function createDeskHost(options) {
|
|
|
53
58
|
const now = options.now ?? Date.now;
|
|
54
59
|
const pathExists = options.pathExists ?? existsSync;
|
|
55
60
|
const deviceExists = options.deviceExists ?? existsSync;
|
|
61
|
+
const removeOverlay = options.removeOverlay ?? ((path) => rm(path, { force: true }));
|
|
62
|
+
const freeBytes = options.freeBytes ?? defaultFreeBytes;
|
|
63
|
+
const minFreeBytes = nonNegativeInteger(options.minFreeBytes ?? 0, 'minFreeBytes');
|
|
64
|
+
const trimOnSuspend = options.trimOnSuspend ?? true;
|
|
65
|
+
const trimCommand = options.trimCommand ?? DEFAULT_TRIM_COMMAND;
|
|
66
|
+
const trimArgs = options.trimArgs ?? DEFAULT_TRIM_ARGS;
|
|
67
|
+
const trimTimeoutMs = positiveInteger(options.trimTimeoutMs ?? DEFAULT_TRIM_TIMEOUT_MS, 'trimTimeoutMs');
|
|
56
68
|
const capacity = positiveInteger(options.capacity ?? DEFAULT_CAPACITY, 'capacity');
|
|
57
69
|
const idleSuspendMs = positiveInteger(options.idleSuspendMs ?? DEFAULT_IDLE_SUSPEND_MS, 'idleSuspendMs');
|
|
58
70
|
const defaultLeaseMs = positiveInteger(options.defaultLeaseMs ?? DEFAULT_LEASE_MS, 'defaultLeaseMs');
|
|
@@ -64,6 +76,14 @@ export function createDeskHost(options) {
|
|
|
64
76
|
const initramfsPath = options.initramfsPath ?? (pathExists(defaultInitramfs) ? defaultInitramfs : undefined);
|
|
65
77
|
const kernelCmdline = options.kernelCmdline;
|
|
66
78
|
const records = new Map();
|
|
79
|
+
/**
|
|
80
|
+
* The last lifecycle transition scheduled for each desk. A desk may only
|
|
81
|
+
* have one VMM child touching its overlay, so boot and teardown are ordered
|
|
82
|
+
* on the desk identity rather than allowed to race through independent API
|
|
83
|
+
* calls. The stored tail always resolves; callers still receive their own
|
|
84
|
+
* operation's result or error.
|
|
85
|
+
*/
|
|
86
|
+
const lifecycleTransitions = new Map();
|
|
67
87
|
const queue = [];
|
|
68
88
|
let occupied = 0;
|
|
69
89
|
let nextCid = positiveInteger(options.cidBase ?? DEFAULT_CID_BASE, 'cidBase');
|
|
@@ -76,6 +96,19 @@ export function createDeskHost(options) {
|
|
|
76
96
|
function iso(timestamp) {
|
|
77
97
|
return new Date(timestamp).toISOString();
|
|
78
98
|
}
|
|
99
|
+
function serializeLifecycle(deskId, transition) {
|
|
100
|
+
const previous = lifecycleTransitions.get(deskId);
|
|
101
|
+
// Preserve the host's existing synchronous admission semantics when this
|
|
102
|
+
// desk has no transition in flight. Only conflicts need the promise tail;
|
|
103
|
+
// unrelated starts must still appear in queue stats before `start` returns.
|
|
104
|
+
const current = previous ? previous.then(transition, transition) : transition();
|
|
105
|
+
const tail = current.then(() => undefined, () => undefined);
|
|
106
|
+
lifecycleTransitions.set(deskId, tail);
|
|
107
|
+
return current.finally(() => {
|
|
108
|
+
if (lifecycleTransitions.get(deskId) === tail)
|
|
109
|
+
lifecycleTransitions.delete(deskId);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
79
112
|
function emit(record, detail) {
|
|
80
113
|
if (record.screen?.handover && HANDOVER_MASKED_KINDS.has(detail.kind))
|
|
81
114
|
return;
|
|
@@ -165,9 +198,62 @@ export function createDeskHost(options) {
|
|
|
165
198
|
at: iso(now()),
|
|
166
199
|
});
|
|
167
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* Stop the machine and keep the disk. This is the SUSPEND half of the
|
|
203
|
+
* lifecycle and it is shared by three callers — `suspend`, `sweep`, and
|
|
204
|
+
* `destroy` — so it must never delete, truncate, or rewrite the overlay.
|
|
205
|
+
*
|
|
206
|
+
* That is not a stylistic preference. For a suspended desk the overlay IS
|
|
207
|
+
* the desk: its home directory, its installed state, its half-written work.
|
|
208
|
+
* A desk that comes back from suspend with a fresh disk has silently lost
|
|
209
|
+
* everything, and the loss surfaces long after the code that caused it.
|
|
210
|
+
* Reclaiming the disk is the exclusive business of `reclaimOverlay`, which
|
|
211
|
+
* is called from exactly one place — `destroy` — where the caller has said
|
|
212
|
+
* the desk should cease to exist.
|
|
213
|
+
*/
|
|
214
|
+
/**
|
|
215
|
+
* Hand the guest's freed blocks back to the host, as the last thing the
|
|
216
|
+
* guest does before it stops.
|
|
217
|
+
*
|
|
218
|
+
* An overlay only ever GROWS otherwise. A block the guest writes is
|
|
219
|
+
* allocated on the host filesystem forever; deleting the file inside the
|
|
220
|
+
* guest frees it in the guest's own filesystem and tells the host nothing,
|
|
221
|
+
* so a long-lived desk ratchets upward until the volume is full even though
|
|
222
|
+
* the guest reports itself half empty. `fstrim` is what closes that loop:
|
|
223
|
+
* it walks the guest filesystem's free space and issues discards, which
|
|
224
|
+
* Cloud Hypervisor's virtio-blk turns into hole-punches on the raw overlay.
|
|
225
|
+
*
|
|
226
|
+
* Suspend is the right moment for it. The image's own `fstrim.timer` fires
|
|
227
|
+
* WEEKLY, and a desk that idle-suspends after minutes is almost never up
|
|
228
|
+
* when that lands — so on a leased-and-parked desk the scheduled trim
|
|
229
|
+
* effectively never runs. Suspend, by contrast, is exactly when the guest
|
|
230
|
+
* has stopped needing its scratch and the host wants the space back.
|
|
231
|
+
*
|
|
232
|
+
* This does not violate the disk contract on `teardown`. `fstrim` discards
|
|
233
|
+
* only blocks the guest's filesystem already considers free; it cannot
|
|
234
|
+
* remove a suspended desk's state, and a desk resumed afterwards sees an
|
|
235
|
+
* identical filesystem. It is also strictly best-effort and bounded: a
|
|
236
|
+
* guest that is wedged, disconnected, or lacks the binary must still
|
|
237
|
+
* suspend promptly, so every failure here is swallowed into `lastError`.
|
|
238
|
+
*/
|
|
239
|
+
async function trimGuest(record, machine) {
|
|
240
|
+
if (!trimOnSuspend)
|
|
241
|
+
return;
|
|
242
|
+
try {
|
|
243
|
+
await machine.request({
|
|
244
|
+
op: 'exec',
|
|
245
|
+
command: trimCommand,
|
|
246
|
+
args: trimArgs,
|
|
247
|
+
timeoutMs: trimTimeoutMs,
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
lastError = `Trim before suspend failed for ${record.deskId}: ${errorMessage(error)}`;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
168
254
|
async function teardown(record) {
|
|
169
255
|
if (record.status !== 'resident')
|
|
170
|
-
return;
|
|
256
|
+
return false;
|
|
171
257
|
await endHandover(record, 'revoked');
|
|
172
258
|
for (const stop of [...record.streamStops])
|
|
173
259
|
stop();
|
|
@@ -193,9 +279,13 @@ export function createDeskHost(options) {
|
|
|
193
279
|
record.timer = null;
|
|
194
280
|
const machine = record.machine;
|
|
195
281
|
record.machine = null;
|
|
196
|
-
|
|
282
|
+
// Do not advertise this desk as suspended until its VMM child is actually
|
|
283
|
+
// down. `serializeLifecycle` prevents a new boot from entering while this
|
|
284
|
+
// transitional state still owns the overlay.
|
|
285
|
+
record.status = 'suspending';
|
|
197
286
|
record.handle = null;
|
|
198
287
|
if (machine) {
|
|
288
|
+
await trimGuest(record, machine);
|
|
199
289
|
try {
|
|
200
290
|
await machine.shutdown();
|
|
201
291
|
}
|
|
@@ -205,7 +295,9 @@ export function createDeskHost(options) {
|
|
|
205
295
|
}
|
|
206
296
|
lastSuspendedAt = iso(now());
|
|
207
297
|
occupied -= 1;
|
|
298
|
+
record.status = 'suspended';
|
|
208
299
|
drainQueue();
|
|
300
|
+
return true;
|
|
209
301
|
}
|
|
210
302
|
function drainQueue() {
|
|
211
303
|
while (occupied < capacity && queue.length > 0) {
|
|
@@ -233,7 +325,37 @@ export function createDeskHost(options) {
|
|
|
233
325
|
queue.push({ run, resolve: resolvePromise, reject: rejectPromise });
|
|
234
326
|
});
|
|
235
327
|
}
|
|
328
|
+
/**
|
|
329
|
+
* Refuse a desk the overlay filesystem cannot afford. A reflink clone costs
|
|
330
|
+
* almost nothing at creation and then grows as the guest writes, so the
|
|
331
|
+
* moment that matters is not the `cp` — it is every write after it. Checking
|
|
332
|
+
* headroom here converts a mid-boot ENOSPC, which the guest reports as a
|
|
333
|
+
* corrupt disk or a hang, into one refusal naming the actual shortage.
|
|
334
|
+
*/
|
|
335
|
+
async function admitDisk(overlayPath) {
|
|
336
|
+
if (minFreeBytes <= 0)
|
|
337
|
+
return;
|
|
338
|
+
let free;
|
|
339
|
+
try {
|
|
340
|
+
free = await freeBytes(overlayPath);
|
|
341
|
+
}
|
|
342
|
+
catch (error) {
|
|
343
|
+
// Fail OPEN on an unreadable filesystem: a desk refused because statfs
|
|
344
|
+
// hiccuped is a worse outcome than one admitted onto a disk that turns
|
|
345
|
+
// out to be full, which at least fails with a real disk error.
|
|
346
|
+
lastError = `Free-space check failed for ${overlayPath}: ${errorMessage(error)}`;
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
if (free < minFreeBytes) {
|
|
350
|
+
throw new DeskError(`Refusing to start a desk: the overlay filesystem holding ${overlayPath} has `
|
|
351
|
+
+ `${free} bytes free, below the ${minFreeBytes}-byte floor. Reclaim destroyed `
|
|
352
|
+
+ 'desks’ overlays or grow the volume before leasing another desk.');
|
|
353
|
+
}
|
|
354
|
+
}
|
|
236
355
|
async function boot(deskId, startOptions) {
|
|
356
|
+
// Before the plan, so a full disk is reported as a full disk rather than
|
|
357
|
+
// as whatever the overlay copy fails with.
|
|
358
|
+
await admitDisk(startOptions.overlayPath);
|
|
237
359
|
const plan = buildDeskLaunchPlan({
|
|
238
360
|
deskId,
|
|
239
361
|
vmmPath: options.vmmPath,
|
|
@@ -380,7 +502,14 @@ export function createDeskHost(options) {
|
|
|
380
502
|
// is about. Suspending it frees the capacity slot and makes the next
|
|
381
503
|
// `resume()` boot a healthy desk.
|
|
382
504
|
lastError = change.reason;
|
|
383
|
-
void
|
|
505
|
+
void serializeLifecycle(record.deskId, async () => {
|
|
506
|
+
// The loss belongs to the epoch that registered this watcher. If an
|
|
507
|
+
// earlier queued transition already replaced that machine, this stale
|
|
508
|
+
// signal must not retire the fresh resident.
|
|
509
|
+
if (record.status !== 'resident' || record.epoch !== epoch)
|
|
510
|
+
return;
|
|
511
|
+
await teardown(record);
|
|
512
|
+
}).catch((error) => {
|
|
384
513
|
lastError = errorMessage(error);
|
|
385
514
|
});
|
|
386
515
|
}
|
|
@@ -864,48 +993,87 @@ export function createDeskHost(options) {
|
|
|
864
993
|
}
|
|
865
994
|
async function start(startOptions) {
|
|
866
995
|
const deskId = cleanDeskId(startOptions.deskId);
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
996
|
+
return serializeLifecycle(deskId, async () => {
|
|
997
|
+
const existing = records.get(deskId);
|
|
998
|
+
if (existing?.status === 'resident' && existing.handle)
|
|
999
|
+
return existing.handle;
|
|
1000
|
+
return admit(() => boot(deskId, { ...startOptions, deskId }));
|
|
1001
|
+
});
|
|
871
1002
|
}
|
|
872
1003
|
async function resume(deskId) {
|
|
873
|
-
const
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
1004
|
+
const id = cleanDeskId(deskId);
|
|
1005
|
+
return serializeLifecycle(id, async () => {
|
|
1006
|
+
const record = records.get(id);
|
|
1007
|
+
if (!record)
|
|
1008
|
+
throw new DeskError(`Unknown desk: ${deskId}`);
|
|
1009
|
+
if (record.status === 'resident' && record.handle)
|
|
1010
|
+
return record.handle;
|
|
1011
|
+
return admit(() => boot(record.deskId, record.startOptions));
|
|
1012
|
+
});
|
|
879
1013
|
}
|
|
880
1014
|
async function suspend(deskId) {
|
|
881
|
-
const
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
1015
|
+
const id = cleanDeskId(deskId);
|
|
1016
|
+
await serializeLifecycle(id, async () => {
|
|
1017
|
+
const record = records.get(id);
|
|
1018
|
+
if (!record)
|
|
1019
|
+
return;
|
|
1020
|
+
await teardown(record);
|
|
1021
|
+
});
|
|
1022
|
+
}
|
|
1023
|
+
/**
|
|
1024
|
+
* Delete a desk's overlay. The ONLY place in this module that removes a
|
|
1025
|
+
* desk's disk, and it is reachable from `destroy` alone — see the contract
|
|
1026
|
+
* on `teardown` for why suspend must never come near it.
|
|
1027
|
+
*
|
|
1028
|
+
* The record is dropped whether or not the unlink succeeds: a desk the
|
|
1029
|
+
* caller has destroyed must not stay leaseable because its file was busy.
|
|
1030
|
+
* A failed unlink is surfaced through `lastError` and leaves the file for
|
|
1031
|
+
* the reaper rather than being retried here.
|
|
1032
|
+
*/
|
|
1033
|
+
async function reclaimOverlay(record) {
|
|
1034
|
+
try {
|
|
1035
|
+
await removeOverlay(record.startOptions.overlayPath);
|
|
1036
|
+
}
|
|
1037
|
+
catch (error) {
|
|
1038
|
+
lastError = `Overlay was not reclaimed for ${record.deskId}: ${errorMessage(error)}`;
|
|
1039
|
+
}
|
|
885
1040
|
}
|
|
886
1041
|
async function destroy(deskId) {
|
|
887
1042
|
const id = cleanDeskId(deskId);
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
1043
|
+
await serializeLifecycle(id, async () => {
|
|
1044
|
+
const record = records.get(id);
|
|
1045
|
+
if (!record)
|
|
1046
|
+
return;
|
|
1047
|
+
await teardown(record);
|
|
1048
|
+
records.delete(id);
|
|
1049
|
+
// Strictly after teardown: the VMM still has the file open until its child
|
|
1050
|
+
// has exited, and unlinking underneath a live guest would leave the desk
|
|
1051
|
+
// writing into an orphaned inode instead of failing loudly.
|
|
1052
|
+
await reclaimOverlay(record);
|
|
1053
|
+
});
|
|
893
1054
|
}
|
|
894
1055
|
async function sweep() {
|
|
895
1056
|
const timestamp = now();
|
|
896
1057
|
let suspendedCount = 0;
|
|
897
1058
|
for (const record of [...records.values()]) {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
1059
|
+
const suspended = await serializeLifecycle(record.deskId, async () => {
|
|
1060
|
+
// A destroy and a later start may have replaced a captured record
|
|
1061
|
+
// before its turn in the sweep. Never apply the old deadline to the
|
|
1062
|
+
// new desk that happens to share its id.
|
|
1063
|
+
if (records.get(record.deskId) !== record || record.status !== 'resident')
|
|
1064
|
+
return false;
|
|
1065
|
+
const handover = record.screen?.handover;
|
|
1066
|
+
if (handover && handover.deadline <= timestamp) {
|
|
1067
|
+
await endHandover(record, 'expired');
|
|
1068
|
+
}
|
|
1069
|
+
if (record.leaseDeadline <= timestamp
|
|
1070
|
+
|| timestamp - record.lastActivityAt >= idleSuspendMs) {
|
|
1071
|
+
return teardown(record);
|
|
1072
|
+
}
|
|
1073
|
+
return false;
|
|
1074
|
+
});
|
|
1075
|
+
if (suspended)
|
|
907
1076
|
suspendedCount += 1;
|
|
908
|
-
}
|
|
909
1077
|
}
|
|
910
1078
|
return suspendedCount;
|
|
911
1079
|
}
|
|
@@ -930,6 +1098,77 @@ export function createDeskHost(options) {
|
|
|
930
1098
|
}
|
|
931
1099
|
return { start, resume, suspend, destroy, stats, sweep };
|
|
932
1100
|
}
|
|
1101
|
+
/**
|
|
1102
|
+
* Whether the overlay directory's filesystem can make reflink clones.
|
|
1103
|
+
*
|
|
1104
|
+
* This exists because `cp --reflink=auto` — what `buildDeskLaunchPlan` uses,
|
|
1105
|
+
* deliberately, so a desk still boots on a filesystem without reflink — is
|
|
1106
|
+
* SILENT when it falls back. On XFS or Btrfs a 20GB base clones instantly and
|
|
1107
|
+
* costs kilobytes; on ext4 the identical command copies all 20GB, slowly, per
|
|
1108
|
+
* desk. The failure is invisible until a disk sized for reflink sharing fills
|
|
1109
|
+
* after two desks, which looks like a capacity bug rather than the filesystem
|
|
1110
|
+
* choice it actually is.
|
|
1111
|
+
*
|
|
1112
|
+
* So the probe uses `--reflink=always`, which REFUSES to fall back, on a tiny
|
|
1113
|
+
* throwaway file. Call it at startup and refuse to serve rather than discover
|
|
1114
|
+
* this in production — see `assertOverlayReflink`.
|
|
1115
|
+
*/
|
|
1116
|
+
export async function verifyOverlayReflink(options) {
|
|
1117
|
+
const overlayDirectory = options.overlayDirectory;
|
|
1118
|
+
const idFactory = options.idFactory ?? randomUUID;
|
|
1119
|
+
const copy = options.copy ?? reflinkAlways;
|
|
1120
|
+
const token = idFactory();
|
|
1121
|
+
const source = join(overlayDirectory, `.reflink-probe-${token}`);
|
|
1122
|
+
const destination = join(overlayDirectory, `.reflink-probe-${token}.clone`);
|
|
1123
|
+
try {
|
|
1124
|
+
try {
|
|
1125
|
+
await writeFile(source, 'reflink probe');
|
|
1126
|
+
}
|
|
1127
|
+
catch (error) {
|
|
1128
|
+
// A directory that cannot be written to is not a filesystem without
|
|
1129
|
+
// reflink — it is a broken deployment, and reporting it as the former
|
|
1130
|
+
// would send an operator to reformat a disk over a permissions bug.
|
|
1131
|
+
throw new DeskError(`The overlay directory ${overlayDirectory} could not be written to, so reflink `
|
|
1132
|
+
+ `support could not be determined: ${errorMessage(error)}`);
|
|
1133
|
+
}
|
|
1134
|
+
await copy(source, destination);
|
|
1135
|
+
return { supported: true, overlayDirectory, detail: 'cp --reflink=always succeeded.' };
|
|
1136
|
+
}
|
|
1137
|
+
catch (error) {
|
|
1138
|
+
if (error instanceof DeskError)
|
|
1139
|
+
throw error;
|
|
1140
|
+
return { supported: false, overlayDirectory, detail: errorMessage(error) };
|
|
1141
|
+
}
|
|
1142
|
+
finally {
|
|
1143
|
+
await rm(source, { force: true }).catch(() => undefined);
|
|
1144
|
+
await rm(destination, { force: true }).catch(() => undefined);
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Fail loudly at startup on a filesystem without reflink support, naming the
|
|
1149
|
+
* cost so the operator can act on it.
|
|
1150
|
+
*/
|
|
1151
|
+
export async function assertOverlayReflink(options) {
|
|
1152
|
+
const check = await verifyOverlayReflink(options);
|
|
1153
|
+
if (!check.supported) {
|
|
1154
|
+
throw new DeskError(`The overlay directory ${check.overlayDirectory} is on a filesystem without reflink `
|
|
1155
|
+
+ 'support, so every desk would FULL-COPY the base image instead of sharing its '
|
|
1156
|
+
+ 'blocks — gigabytes and minutes per desk, and a disk sized for sharing fills after '
|
|
1157
|
+
+ `a handful of them. Put the overlay directory on XFS or Btrfs. Probe said: ${check.detail}`);
|
|
1158
|
+
}
|
|
1159
|
+
return check;
|
|
1160
|
+
}
|
|
1161
|
+
async function reflinkAlways(source, destination) {
|
|
1162
|
+
await new Promise((resolvePromise, rejectPromise) => {
|
|
1163
|
+
execFile('cp', ['--reflink=always', source, destination], (error, _stdout, stderr) => {
|
|
1164
|
+
if (error) {
|
|
1165
|
+
rejectPromise(new Error(stderr.trim() || errorMessage(error)));
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
resolvePromise();
|
|
1169
|
+
});
|
|
1170
|
+
});
|
|
1171
|
+
}
|
|
933
1172
|
/**
|
|
934
1173
|
* Boot-time probe. Distinguishes a host that is unusable — wrong platform,
|
|
935
1174
|
* missing VMM, missing images — which throws, from capabilities that are
|
|
@@ -1032,6 +1271,20 @@ function positiveInteger(value, field) {
|
|
|
1032
1271
|
}
|
|
1033
1272
|
return value;
|
|
1034
1273
|
}
|
|
1274
|
+
function nonNegativeInteger(value, field) {
|
|
1275
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
1276
|
+
throw new DeskError(`${field} must be an integer of zero or more.`);
|
|
1277
|
+
}
|
|
1278
|
+
return value;
|
|
1279
|
+
}
|
|
1280
|
+
/**
|
|
1281
|
+
* Free bytes on the filesystem holding `path`, measured against the directory
|
|
1282
|
+
* so the answer is the same before and after the overlay itself exists.
|
|
1283
|
+
*/
|
|
1284
|
+
async function defaultFreeBytes(path) {
|
|
1285
|
+
const stats = await statfs(dirname(path));
|
|
1286
|
+
return Number(stats.bsize) * Number(stats.bavail);
|
|
1287
|
+
}
|
|
1035
1288
|
function errorMessage(error) {
|
|
1036
1289
|
return error instanceof Error ? error.message : String(error);
|
|
1037
1290
|
}
|
package/host.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The desk host: lifecycle, leases, and the screen service over a backend.\n *\n * One host manages many desks against one capacity budget. A desk is\n * headless by default; the screen is a service started on demand on the\n * machine the agent is already using. The host owns lease accounting (idle\n * suspend, a hard concurrency cap with a FIFO queue beyond it), event\n * emission to the consumer's ports, and the handover masking contract. It\n * never touches a database and knows nothing about tenants or approvals.\n */\nimport { existsSync } from 'node:fs'\nimport { rm } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { join } from 'node:path'\nimport { randomUUID } from 'node:crypto'\nimport { Buffer } from 'node:buffer'\nimport {\n buildDeskLaunchPlan,\n cleanDeskId,\n DEFAULT_KVM_PATH,\n DEFAULT_VMM_PATH,\n DeskError,\n type DeskLauncherIdentity,\n} from './plan'\nimport {\n cloudHypervisorBackend,\n type DeskBackend,\n type DeskConnectionChange,\n type DeskMachine,\n} from './backend'\nimport {\n parseCapabilities,\n parseClipboardText,\n parseExecResult,\n parseHandoverBegun,\n parseJobStarted,\n parseObservation,\n type GuestEventMessage,\n type GuestInput,\n} from './protocol'\nimport type {\n A11yNode,\n DeskEventDetail,\n DeskEventKind,\n DeskFrameFormat,\n DeskHandoverEndReason,\n DeskHandoverScope,\n DeskPoint,\n DeskPointerButton,\n DeskPorts,\n DeskVideoChunkKind,\n WindowInfo,\n} from './events'\n\nexport const DEFAULT_CAPACITY = 4\nexport const DEFAULT_IDLE_SUSPEND_MS = 5 * 60_000\nexport const DEFAULT_LEASE_MS = 15 * 60_000\nexport const DEFAULT_CID_BASE = 100\nconst MAX_BUFFERED_FRAMES = 60\n/**\n * Video chunks a slow consumer may fall behind by before its stream is ended.\n * Larger than the frame bound because a fragment is a fraction of a frame's\n * bytes, and enforced by ENDING rather than by dropping — see the comment at\n * the overrun.\n */\nconst MAX_BUFFERED_VIDEO_CHUNKS = 600\nconst MIN_TIMER_DELAY_MS = 25\n\n/**\n * Event kinds that must never reach the recording port while a handover is\n * active. Human input and everything derived from watching the human act is\n * masked; only the handover boundaries themselves cross.\n */\nconst HANDOVER_MASKED_KINDS: ReadonlySet<DeskEventKind> = new Set([\n 'click',\n 'type',\n 'key',\n 'scroll',\n 'drag',\n 'window_focus',\n 'app_launch',\n])\n\nexport interface DeskHostOptions {\n /** Root of the golden base image and per-desk assets. */\n imageRoot: string\n vmmPath?: string\n kvmPath?: string\n /** Guest kernel; defaults to `<imageRoot>/vmlinux`. */\n kernelPath?: string\n /**\n * Initramfs the guest kernel boots with. A modular distro cloud kernel needs\n * one to mount root; omit only for a kernel with virtio/ext4 built in. When\n * unset it defaults to `<imageRoot>/initrd` if that file exists, else none.\n */\n initramfsPath?: string\n /**\n * Kernel command line every desk boots with. Defaults to\n * `DEFAULT_KERNEL_CMDLINE`; override it for partitioned cloud images whose\n * root is not `/dev/vda` (e.g. `root=/dev/vda3 rw quiet`).\n */\n kernelCmdline?: string\n /** Directory holding vsock and API control sockets. */\n runtimeDir?: string\n launcherIdentity?: DeskLauncherIdentity\n backend?: DeskBackend\n ports: DeskPorts\n /** Hard cap on concurrently resident desks; starts beyond it queue FIFO. */\n capacity?: number\n idleSuspendMs?: number\n defaultLeaseMs?: number\n /** First guest vsock CID handed out; each boot takes the next one. */\n cidBase?: number\n now?: () => number\n pathExists?: (path: string) => boolean\n deviceExists?: (path: string) => boolean\n}\n\nexport interface DeskStartOptions {\n deskId: string\n /** Absolute path of the golden base image this desk boots from. */\n baseImage: string\n /** Absolute path of this desk's copy-on-write overlay. */\n overlayPath: string\n memoryMb?: number\n vcpus?: number\n leaseMs?: number\n network?: { tapDevice?: string; macAddress?: string }\n}\n\nexport interface DeskExecOptions {\n command: string\n args?: readonly string[]\n cwd?: string\n env?: Readonly<Record<string, string>>\n timeoutMs?: number\n /** Survive the call as a background job; the job dies with the lease. */\n keepAlive?: boolean\n}\n\nexport interface DeskExecSnapshot {\n deskId: string\n command: string\n args: readonly string[]\n exitCode: number | null\n signal: string | null\n stdout: string\n stderr: string\n truncated: boolean\n startedAt: string\n finishedAt: string\n}\n\nexport interface DeskJob {\n jobId: string\n deskId: string\n command: string\n args: readonly string[]\n startedAt: string\n status: 'running' | 'exited'\n exitCode: number | null\n signal: string | null\n}\n\nexport interface DeskObservation {\n png: Buffer\n width: number\n height: number\n a11y: A11yNode | null\n windows: readonly WindowInfo[]\n focused: WindowInfo | null\n}\n\nexport interface DeskFrame {\n seq: number\n width: number\n height: number\n data: Buffer\n /**\n * How `data` is encoded. A consumer must not assume PNG — it builds the\n * media type it hands on (a `data:` URL, a decoder) from this.\n */\n format: DeskFrameFormat\n at: string\n}\n\n/**\n * One unit of the live video stream: the init segment, or one media fragment.\n *\n * Video is deliberately NOT modelled as a kind of frame. A frame is a picture\n * that stands alone; a video chunk is meaningless without the init segment\n * before it and, for a media chunk, without the keyframe its group of pictures\n * began at. Pretending otherwise produces a consumer that appends bytes in any\n * order and renders black with no error to explain it.\n */\nexport interface DeskVideoChunk {\n seq: number\n kind: DeskVideoChunkKind\n /** RFC 6381, e.g. `avc1.42C020`. What a MediaSource must be told. */\n codec: string\n width: number\n height: number\n /** True when a `media` chunk begins at a sync sample. Always false on `init`. */\n keyframe: boolean\n data: Buffer\n at: string\n}\n\nexport interface DeskScreenInput {\n move(x: number, y: number): Promise<void>\n click(x: number, y: number, button?: DeskPointerButton): Promise<void>\n type(text: string): Promise<void>\n key(combo: string): Promise<void>\n scroll(x: number, y: number, dx: number, dy: number): Promise<void>\n drag(from: DeskPoint, to: DeskPoint): Promise<void>\n}\n\nexport interface DeskScreenHandle {\n observe(): Promise<DeskObservation>\n /**\n * Coordinate contract: all coordinates are in the pixel space of the most\n * recent view of the screen — an `observe()`, a frame from `frames()`, or a\n * chunk from `video()` — one to one. All three anchor it, because the caller\n * aims at whatever it was last shown, and a live viewer is shown a stream\n * rather than an observation. That holds whatever the encoding is: a lossy\n * frame and an H.264 fragment are both true-size views of the screen, so they\n * anchor exactly as a lossless PNG does. Input before any view, or outside\n * its bounds, throws.\n */\n input: DeskScreenInput\n a11y: { invoke(nodeId: string, action: string): Promise<void> }\n launch(appId: string, args?: readonly string[]): Promise<void>\n clipboard: { read(): Promise<string>; write(text: string): Promise<void> }\n /**\n * The live frame stream. `format` picks how the guest encodes each frame and\n * is a bytes-for-fidelity trade: `jpeg` is roughly an order of magnitude\n * smaller per frame, which is what a human driving the screen needs, while\n * `png` stays exact. Omitted leaves the choice to the guest. `observe()` is\n * unaffected and always lossless.\n *\n * The stream also ends when the desk reconnects to its guest agent: the\n * capture behind it did not survive, so the iterator completes rather than\n * waiting on frames that are never coming. Start another one to resume.\n */\n frames(options?: {\n fps?: number\n width?: number\n height?: number\n format?: DeskFrameFormat\n }): AsyncIterable<DeskFrame>\n /**\n * The live screen as encoded VIDEO rather than as a sequence of pictures.\n *\n * This is what a person driving the desk should watch. `frames()` ships a\n * whole independent image per tick; a video codec ships the DIFFERENCE, and a\n * desktop is mostly still — which is roughly two orders of magnitude fewer\n * bytes on the transport between the guest and here, the one place the live\n * view was actually bounded.\n *\n * The stream anchors the coordinate space exactly as `observe()` and\n * `frames()` do, from the first chunk onwards, because a viewer driving by\n * video is aiming at what the video showed it.\n *\n * Masked like frames: nothing is emitted while a handover is active. And\n * ended like frames when the desk reconnects to its guest agent — a video\n * consumer resumed on a fresh encoder, without its init segment and away\n * from a keyframe, decodes nothing at all and says so nowhere.\n */\n video(options?: {\n fps?: number\n width?: number\n height?: number\n }): AsyncIterable<DeskVideoChunk>\n handover: {\n begin(options: {\n ttlMs: number\n scope?: DeskHandoverScope\n actor?: string\n }): Promise<{ url: string }>\n end(): Promise<void>\n readonly active: boolean\n }\n}\n\nexport interface DeskScreenService {\n start(options: { width: number; height: number }): Promise<DeskScreenHandle>\n stop(): Promise<void>\n readonly running: boolean\n}\n\n/**\n * One leased desk.\n *\n * The handle stays valid across a lost guest connection: the host reconnects\n * underneath it and later calls work, so a guest agent restart or a dropped\n * vsock bridge no longer costs the rest of the lease. Two consequences a\n * caller must actually handle:\n *\n * - A call that was IN FLIGHT when the connection dropped rejects with\n * `DeskRequestFateUnknownError` — it may already have run in the guest.\n * Nothing retries it for you.\n * - A reconnect does not restore guest-side capture state. Live `frames()` and\n * `video()` iterators END, and the coordinate anchor is dropped, so the\n * caller must `observe()` (or take a new frame) before aiming again.\n *\n * When the connection cannot be recovered at all the desk is suspended, so the\n * handle goes dead and `resume()` boots a fresh one — rather than the handle\n * answering \"no longer connected\" for the rest of the lease.\n */\nexport interface DeskHandle {\n readonly deskId: string\n renewLease(ms: number): void\n exec(options: DeskExecOptions & { keepAlive: true }): Promise<DeskJob>\n exec(options: DeskExecOptions): Promise<DeskExecSnapshot>\n /** The keepAlive processes still running on this desk. */\n jobs(): DeskJob[]\n screen: DeskScreenService\n}\n\nexport interface DeskHostStats {\n /** Desks currently resident (booted or booting) against the capacity cap. */\n resident: number\n /** Starts waiting in the FIFO queue for capacity. */\n queued: number\n capacity: number\n suspended: number\n lastStartedAt: string | null\n lastSuspendedAt: string | null\n /**\n * Times a resident desk lost its guest connection mid-lease and got it back.\n * One is a blip; a number that climbs is a desk FLAPPING — a guest agent\n * restarting in a loop, a failing vsock bridge — and that is the difference\n * between \"the desk is slow\" and a diagnosis.\n */\n reconnects: number\n lastReconnectAt: string | null\n /** Which desk reconnected last: enough to go and look at the right one. */\n lastReconnectDeskId: string | null\n lastError: string | null\n}\n\nexport interface DeskHost {\n start(options: DeskStartOptions): Promise<DeskHandle>\n resume(deskId: string): Promise<DeskHandle>\n suspend(deskId: string): Promise<void>\n destroy(deskId: string): Promise<void>\n stats(): DeskHostStats\n /**\n * Enforce every time-based rule now: expire handovers past their TTL and\n * suspend desks past their lease or idle deadline. Returns the number of\n * desks suspended. Timers call this automatically; tests with an injected\n * clock call it directly.\n */\n sweep(): Promise<number>\n}\n\ninterface HandoverState {\n actor: string | null\n scope: DeskHandoverScope\n beganAt: number\n deadline: number\n}\n\ninterface ScreenState {\n running: boolean\n width: number\n height: number\n lastObservation: { width: number; height: number } | null\n lastFocusedId: string | null\n handover: HandoverState | null\n handle: DeskScreenHandle\n}\n\ninterface DeskRecord {\n deskId: string\n startOptions: DeskStartOptions\n status: 'resident' | 'suspended'\n machine: DeskMachine | null\n epoch: number\n leaseDeadline: number\n lastActivityAt: number\n jobs: Map<string, DeskJob>\n screen: ScreenState | null\n unsubscribe: (() => void) | null\n unwatchConnection: (() => void) | null\n handle: DeskHandle | null\n timer: NodeJS.Timeout | null\n streamStops: Set<() => void>\n}\n\ninterface QueueEntry {\n run: () => Promise<DeskHandle>\n resolve: (handle: DeskHandle) => void\n reject: (error: Error) => void\n}\n\nexport function createDeskHost(options: DeskHostOptions): DeskHost {\n const ports = options.ports\n if (typeof ports?.onEvent !== 'function' || typeof ports?.audit !== 'function') {\n throw new DeskError('createDeskHost requires ports with onEvent and audit functions.')\n }\n const backend = options.backend ?? cloudHypervisorBackend\n const now = options.now ?? Date.now\n const pathExists = options.pathExists ?? existsSync\n const deviceExists = options.deviceExists ?? existsSync\n const capacity = positiveInteger(options.capacity ?? DEFAULT_CAPACITY, 'capacity')\n const idleSuspendMs = positiveInteger(options.idleSuspendMs ?? DEFAULT_IDLE_SUSPEND_MS, 'idleSuspendMs')\n const defaultLeaseMs = positiveInteger(options.defaultLeaseMs ?? DEFAULT_LEASE_MS, 'defaultLeaseMs')\n const kernelPath = options.kernelPath ?? join(options.imageRoot, 'vmlinux')\n // Default the initramfs to <imageRoot>/initrd when present so a modular\n // cloud kernel boots without every caller having to name it; an explicit\n // option always wins.\n const defaultInitramfs = join(options.imageRoot, 'initrd')\n const initramfsPath =\n options.initramfsPath ?? (pathExists(defaultInitramfs) ? defaultInitramfs : undefined)\n const kernelCmdline = options.kernelCmdline\n\n const records = new Map<string, DeskRecord>()\n const queue: QueueEntry[] = []\n let occupied = 0\n let nextCid = positiveInteger(options.cidBase ?? DEFAULT_CID_BASE, 'cidBase')\n let lastStartedAt: string | null = null\n let lastSuspendedAt: string | null = null\n let lastError: string | null = null\n let reconnects = 0\n let lastReconnectAt: string | null = null\n let lastReconnectDeskId: string | null = null\n\n function iso(timestamp: number): string {\n return new Date(timestamp).toISOString()\n }\n\n function emit(record: DeskRecord, detail: DeskEventDetail): void {\n if (record.screen?.handover && HANDOVER_MASKED_KINDS.has(detail.kind)) return\n ports.onEvent({ ...detail, deskId: record.deskId, at: iso(now()) })\n }\n\n function touch(record: DeskRecord): void {\n record.lastActivityAt = now()\n }\n\n function requireMachine(record: DeskRecord, epoch: number): DeskMachine {\n if (record.status !== 'resident' || record.epoch !== epoch || !record.machine) {\n throw new DeskError(`Desk ${record.deskId} is not resident; resume it first.`)\n }\n return record.machine\n }\n\n function requireScreen(record: DeskRecord, epoch: number): ScreenState {\n requireMachine(record, epoch)\n const screen = record.screen\n if (!screen?.running) {\n throw new DeskError(`Desk ${record.deskId} has no screen running; call screen.start first.`)\n }\n return screen\n }\n\n function assertCoordinates(record: DeskRecord, points: readonly DeskPoint[]): void {\n const observation = record.screen?.lastObservation\n if (!observation) {\n throw new DeskError(\n 'Coordinate input needs a frame of reference: call observe(), or start '\n + 'frames() and wait for one. Coordinates are the pixel space of the '\n + 'most recent of those, one to one.',\n )\n }\n for (const point of points) {\n if (\n !Number.isInteger(point.x)\n || !Number.isInteger(point.y)\n || point.x < 0\n || point.y < 0\n || point.x >= observation.width\n || point.y >= observation.height\n ) {\n throw new DeskError(\n `Coordinates (${point.x}, ${point.y}) are outside the most recent view `\n + `(${observation.width}x${observation.height}); observe() again before targeting.`,\n )\n }\n }\n }\n\n function armTimer(record: DeskRecord): void {\n if (record.timer) clearTimeout(record.timer)\n record.timer = null\n if (record.status !== 'resident') return\n const deadlines = [record.leaseDeadline, record.lastActivityAt + idleSuspendMs]\n const handover = record.screen?.handover\n if (handover) deadlines.push(handover.deadline)\n const delayMs = Math.max(Math.min(...deadlines) - now(), MIN_TIMER_DELAY_MS)\n record.timer = setTimeout(() => {\n sweep()\n .then(() => {\n if (record.status === 'resident') armTimer(record)\n })\n .catch((error: unknown) => {\n lastError = errorMessage(error)\n })\n }, delayMs)\n record.timer.unref()\n }\n\n async function endHandover(record: DeskRecord, reason: DeskHandoverEndReason): Promise<void> {\n const screen = record.screen\n const handover = screen?.handover\n if (!screen || !handover) return\n screen.handover = null\n if (record.machine) {\n try {\n await record.machine.request({ op: 'handover-end' })\n } catch (error) {\n lastError = errorMessage(error)\n }\n }\n const durationMs = Math.max(0, now() - handover.beganAt)\n emit(record, { kind: 'handover_end', actor: handover.actor, durationMs })\n ports.audit({\n kind: 'handover_end',\n deskId: record.deskId,\n actor: handover.actor,\n durationMs,\n reason,\n at: iso(now()),\n })\n }\n\n async function teardown(record: DeskRecord): Promise<void> {\n if (record.status !== 'resident') return\n await endHandover(record, 'revoked')\n for (const stop of [...record.streamStops]) stop()\n record.streamStops.clear()\n if (record.screen?.running) {\n record.screen.running = false\n emit(record, { kind: 'screen_close' })\n }\n record.screen = null\n for (const job of record.jobs.values()) {\n if (job.status !== 'running') continue\n job.status = 'exited'\n job.signal = 'SIGKILL'\n emit(record, { kind: 'job_exit', jobId: job.jobId, exitCode: null, signal: 'SIGKILL' })\n }\n record.unsubscribe?.()\n record.unsubscribe = null\n record.unwatchConnection?.()\n record.unwatchConnection = null\n if (record.timer) clearTimeout(record.timer)\n record.timer = null\n const machine = record.machine\n record.machine = null\n record.status = 'suspended'\n record.handle = null\n if (machine) {\n try {\n await machine.shutdown()\n } catch (error) {\n lastError = errorMessage(error)\n }\n }\n lastSuspendedAt = iso(now())\n occupied -= 1\n drainQueue()\n }\n\n function drainQueue(): void {\n while (occupied < capacity && queue.length > 0) {\n const entry = queue.shift()\n if (!entry) break\n occupied += 1\n entry.run().then(entry.resolve, (error: unknown) => {\n occupied -= 1\n entry.reject(error instanceof Error ? error : new Error(String(error)))\n drainQueue()\n })\n }\n }\n\n function admit(run: () => Promise<DeskHandle>): Promise<DeskHandle> {\n if (occupied < capacity) {\n occupied += 1\n return run().catch((error: unknown) => {\n occupied -= 1\n drainQueue()\n throw error\n })\n }\n return new Promise<DeskHandle>((resolvePromise, rejectPromise) => {\n queue.push({ run, resolve: resolvePromise, reject: rejectPromise })\n })\n }\n\n async function boot(deskId: string, startOptions: DeskStartOptions): Promise<DeskHandle> {\n const plan = buildDeskLaunchPlan(\n {\n deskId,\n vmmPath: options.vmmPath,\n kvmPath: options.kvmPath,\n kernelPath,\n ...(initramfsPath === undefined ? {} : { initramfsPath }),\n baseImagePath: startOptions.baseImage,\n overlayPath: startOptions.overlayPath,\n memoryMb: startOptions.memoryMb,\n vcpus: startOptions.vcpus,\n vsockCid: nextCid++,\n runtimeDir: options.runtimeDir,\n tapDevice: startOptions.network?.tapDevice,\n macAddress: startOptions.network?.macAddress,\n // Forward the host's cmdline only when set; otherwise the plan falls\n // back to DEFAULT_KERNEL_CMDLINE.\n ...(kernelCmdline === undefined ? {} : { kernelCmdline }),\n launcherIdentity: options.launcherIdentity,\n },\n { pathExists, deviceExists },\n )\n let machine: DeskMachine\n try {\n machine = await backend.boot(plan)\n } catch (error) {\n lastError = errorMessage(error)\n throw error instanceof DeskError\n ? error\n : new DeskError(`Desk ${deskId} failed to boot: ${errorMessage(error)}`)\n }\n\n let record = records.get(deskId)\n if (!record) {\n record = {\n deskId,\n startOptions,\n status: 'resident',\n machine,\n epoch: 1,\n leaseDeadline: 0,\n lastActivityAt: 0,\n jobs: new Map(),\n screen: null,\n unsubscribe: null,\n unwatchConnection: null,\n handle: null,\n timer: null,\n streamStops: new Set(),\n }\n records.set(deskId, record)\n } else {\n record.startOptions = startOptions\n record.status = 'resident'\n record.machine = machine\n record.epoch += 1\n record.jobs = new Map()\n record.screen = null\n record.streamStops = new Set()\n }\n const current = record\n const epoch = current.epoch\n current.leaseDeadline = now() + positiveInteger(startOptions.leaseMs ?? defaultLeaseMs, 'leaseMs')\n current.lastActivityAt = now()\n current.unsubscribe = machine.subscribe((event) => {\n handleGuestEvent(current, epoch, event)\n })\n current.unwatchConnection =\n machine.onConnectionChange?.((change) => {\n handleConnectionChange(current, epoch, change)\n }) ?? null\n current.handle = makeHandle(current, epoch)\n lastStartedAt = iso(now())\n armTimer(current)\n return current.handle\n }\n\n function handleGuestEvent(record: DeskRecord, epoch: number, event: GuestEventMessage): void {\n if (record.status !== 'resident' || record.epoch !== epoch) return\n if (event.event === 'job-exit') {\n const job = record.jobs.get(event.jobId)\n if (!job || job.status !== 'running') return\n job.status = 'exited'\n job.exitCode = event.exitCode\n job.signal = event.signal\n emit(record, {\n kind: 'job_exit',\n jobId: event.jobId,\n exitCode: event.exitCode,\n signal: event.signal,\n })\n return\n }\n if (event.event === 'window-focus') {\n if (record.screen) record.screen.lastFocusedId = event.window.id\n emit(record, { kind: 'window_focus', window: event.window })\n }\n }\n\n /**\n * What a desk losing (and regaining) its guest channel means up here.\n *\n * The backend can get the CHANNEL back; it cannot get the GUEST's state\n * back, and pretending otherwise is how a consumer ends up waiting forever\n * on a stream that will never produce another byte. So a reconnect is\n * treated as a discontinuity that the caller is able to see, and a\n * permanent loss retires the desk instead of leaving a dead one leased.\n */\n function handleConnectionChange(\n record: DeskRecord,\n epoch: number,\n change: DeskConnectionChange,\n ): void {\n if (record.status !== 'resident' || record.epoch !== epoch) return\n if (change.state === 'reconnected') {\n reconnects += 1\n lastReconnectAt = iso(now())\n lastReconnectDeskId = record.deskId\n // Never silent. A reconnect that hides a guest agent restarting is a\n // debugging trap: the desk misbehaves and nothing anywhere says why.\n lastError = `Desk ${record.deskId} reconnected to its guest agent after ${change.downtimeMs}ms (${change.reason}).`\n // The guest's capture state did not necessarily come back with the\n // channel. An agent that restarted has no screen, no frame encoder and\n // no video encoder; even one that survived starts a fresh encoder whose\n // sequence numbers and init segment do not continue the old stream. A\n // consumer resumed mid-stream decodes nothing, silently — so every live\n // stream is ENDED. `frames()`/`video()` complete normally, which is how\n // the consumer notices, and it can start a new one on a guest that is\n // now definitely capturing.\n for (const stop of [...record.streamStops]) stop()\n record.streamStops.clear()\n // The coordinate anchor goes with them: the screen behind it may be\n // gone, or recreated at another size, and a click aimed at the pixel\n // space of a screen that no longer exists lands somewhere nobody chose.\n // Input then refuses until the caller observes again — the existing\n // contract, not a new one.\n if (record.screen) record.screen.lastObservation = null\n // A handover is deliberately NOT ended here. If a human is still on that\n // screen, dropping the mask would start recording them; letting the TTL\n // expire it fails in the safe direction.\n return\n }\n // Lost for good. Left resident, this desk would answer every call with\n // \"no longer connected\" for the rest of the lease and `resume()` would\n // hand back the same dead handle — the exact product failure this release\n // is about. Suspending it frees the capacity slot and makes the next\n // `resume()` boot a healthy desk.\n lastError = change.reason\n void teardown(record).catch((error: unknown) => {\n lastError = errorMessage(error)\n })\n }\n\n function makeHandle(record: DeskRecord, epoch: number): DeskHandle {\n const execImplementation = async (\n execOptions: DeskExecOptions,\n ): Promise<DeskExecSnapshot | DeskJob> => {\n const machine = requireMachine(record, epoch)\n const command = nonEmptyString(execOptions.command, 'command')\n const args = [...(execOptions.args ?? [])]\n const keepAlive = execOptions.keepAlive === true\n if (ports.policy?.allowExec && !ports.policy.allowExec({\n deskId: record.deskId,\n command,\n args,\n keepAlive,\n })) {\n throw new DeskError(`Policy denied exec of ${command} on desk ${record.deskId}.`)\n }\n touch(record)\n const startedAt = iso(now())\n if (keepAlive) {\n const { jobId } = parseJobStarted(await machine.request({\n op: 'job-start',\n command,\n args,\n cwd: execOptions.cwd,\n env: execOptions.env,\n }))\n const job: DeskJob = {\n jobId,\n deskId: record.deskId,\n command,\n args,\n startedAt,\n status: 'running',\n exitCode: null,\n signal: null,\n }\n record.jobs.set(jobId, job)\n emit(record, { kind: 'job_start', jobId, command, args })\n return { ...job }\n }\n const result = parseExecResult(await machine.request(\n {\n op: 'exec',\n command,\n args,\n cwd: execOptions.cwd,\n env: execOptions.env,\n timeoutMs: execOptions.timeoutMs,\n },\n execOptions.timeoutMs === undefined ? {} : { timeoutMs: execOptions.timeoutMs + 5_000 },\n ))\n emit(record, {\n kind: 'shell_command',\n command,\n args,\n cwd: execOptions.cwd ?? null,\n exitCode: result.exitCode,\n signal: result.signal,\n })\n return {\n deskId: record.deskId,\n command,\n args,\n exitCode: result.exitCode,\n signal: result.signal,\n stdout: result.stdout,\n stderr: result.stderr,\n truncated: result.truncated,\n startedAt,\n finishedAt: iso(now()),\n }\n }\n\n const screenService: DeskScreenService = {\n async start({ width, height }) {\n const machine = requireMachine(record, epoch)\n if (record.screen?.running) return record.screen.handle\n const cleanWidth = positiveInteger(width, 'width')\n const cleanHeight = positiveInteger(height, 'height')\n if (ports.policy?.allowScreen && !ports.policy.allowScreen({\n deskId: record.deskId,\n width: cleanWidth,\n height: cleanHeight,\n })) {\n throw new DeskError(`Policy denied opening a screen on desk ${record.deskId}.`)\n }\n touch(record)\n await machine.request({ op: 'screen-start', width: cleanWidth, height: cleanHeight })\n const handle = makeScreenHandle(record, epoch)\n record.screen = {\n running: true,\n width: cleanWidth,\n height: cleanHeight,\n lastObservation: null,\n lastFocusedId: null,\n handover: null,\n handle,\n }\n emit(record, { kind: 'screen_open', width: cleanWidth, height: cleanHeight })\n return handle\n },\n async stop() {\n const machine = requireMachine(record, epoch)\n const screen = record.screen\n if (!screen?.running) return\n await endHandover(record, 'revoked')\n for (const stop of [...record.streamStops]) stop()\n record.streamStops.clear()\n screen.running = false\n record.screen = null\n touch(record)\n await machine.request({ op: 'screen-stop' })\n emit(record, { kind: 'screen_close' })\n },\n get running() {\n return record.screen?.running === true\n },\n }\n\n return {\n deskId: record.deskId,\n renewLease(ms: number): void {\n requireMachine(record, epoch)\n record.leaseDeadline = now() + positiveInteger(ms, 'ms')\n touch(record)\n armTimer(record)\n },\n exec: execImplementation as DeskHandle['exec'],\n jobs(): DeskJob[] {\n return [...record.jobs.values()]\n .filter((job) => job.status === 'running')\n .map((job) => ({ ...job }))\n },\n screen: screenService,\n }\n }\n\n function makeScreenHandle(record: DeskRecord, epoch: number): DeskScreenHandle {\n async function sendInput(detail: DeskEventDetail | null, input: GuestInput): Promise<void> {\n const machine = requireMachine(record, epoch)\n touch(record)\n await machine.request({ op: 'input', input })\n if (detail) emit(record, detail)\n }\n\n return {\n async observe(): Promise<DeskObservation> {\n const machine = requireMachine(record, epoch)\n const screen = requireScreen(record, epoch)\n touch(record)\n const observation = parseObservation(await machine.request({ op: 'observe' }))\n screen.lastObservation = { width: observation.width, height: observation.height }\n if (observation.focused && observation.focused.id !== screen.lastFocusedId) {\n screen.lastFocusedId = observation.focused.id\n emit(record, { kind: 'window_focus', window: observation.focused })\n }\n return {\n png: Buffer.from(observation.png, 'base64'),\n width: observation.width,\n height: observation.height,\n a11y: observation.a11y,\n windows: observation.windows,\n focused: observation.focused,\n }\n },\n input: {\n async move(x, y) {\n requireScreen(record, epoch)\n assertCoordinates(record, [{ x, y }])\n await sendInput(null, { type: 'move', x, y })\n },\n async click(x, y, button = 'left') {\n requireScreen(record, epoch)\n assertCoordinates(record, [{ x, y }])\n await sendInput({ kind: 'click', x, y, button }, { type: 'click', x, y, button })\n },\n async type(text) {\n requireScreen(record, epoch)\n const cleanText = requireStringValue(text, 'text')\n await sendInput({ kind: 'type', text: cleanText }, { type: 'type', text: cleanText })\n },\n async key(combo) {\n requireScreen(record, epoch)\n const cleanCombo = nonEmptyString(combo, 'combo')\n await sendInput({ kind: 'key', combo: cleanCombo }, { type: 'key', combo: cleanCombo })\n },\n async scroll(x, y, dx, dy) {\n requireScreen(record, epoch)\n assertCoordinates(record, [{ x, y }])\n if (!Number.isInteger(dx) || !Number.isInteger(dy)) {\n throw new DeskError('Scroll deltas must be integers.')\n }\n await sendInput({ kind: 'scroll', x, y, dx, dy }, { type: 'scroll', x, y, dx, dy })\n },\n async drag(from, to) {\n requireScreen(record, epoch)\n assertCoordinates(record, [from, to])\n await sendInput({ kind: 'drag', from, to }, { type: 'drag', from, to })\n },\n },\n a11y: {\n async invoke(nodeId, action) {\n const machine = requireMachine(record, epoch)\n requireScreen(record, epoch)\n touch(record)\n await machine.request({\n op: 'a11y-invoke',\n nodeId: nonEmptyString(nodeId, 'nodeId'),\n action: nonEmptyString(action, 'action'),\n })\n },\n },\n async launch(appId, args = []) {\n const machine = requireMachine(record, epoch)\n requireScreen(record, epoch)\n touch(record)\n const cleanAppId = nonEmptyString(appId, 'appId')\n await machine.request({ op: 'launch', appId: cleanAppId, args: [...args] })\n emit(record, { kind: 'app_launch', appId: cleanAppId, args: [...args] })\n },\n clipboard: {\n async read() {\n const machine = requireMachine(record, epoch)\n const screen = requireScreen(record, epoch)\n if (screen.handover) {\n // The human may have just typed a credential that is now on the\n // clipboard; reading it during a handover would carry it into the\n // agent's context and the consumer's record.\n throw new DeskError('The clipboard is masked while a handover is active.')\n }\n touch(record)\n return parseClipboardText(await machine.request({ op: 'clipboard-read' }))\n },\n async write(text) {\n const machine = requireMachine(record, epoch)\n requireScreen(record, epoch)\n touch(record)\n await machine.request({ op: 'clipboard-write', text: requireStringValue(text, 'text') })\n },\n },\n frames(frameOptions = {}) {\n return {\n [Symbol.asyncIterator]: () => {\n const machine = requireMachine(record, epoch)\n const screen = requireScreen(record, epoch)\n const fps = positiveInteger(frameOptions.fps ?? 10, 'fps')\n const width = positiveInteger(frameOptions.width ?? screen.width, 'width')\n const height = positiveInteger(frameOptions.height ?? screen.height, 'height')\n const format = frameOptions.format\n const buffered: DeskFrame[] = []\n let waiting: ((result: IteratorResult<DeskFrame, undefined>) => void) | null = null\n let finished = false\n const unsubscribe = machine.subscribe((event) => {\n if (finished || event.event !== 'frame') return\n // Masking contract: no frame reaches a recording consumer while\n // a handover is active.\n if (record.screen?.handover) return\n const frame: DeskFrame = {\n seq: event.seq,\n width: event.width,\n height: event.height,\n data: Buffer.from(event.data, 'base64'),\n format: event.format,\n at: iso(now()),\n }\n // A frame anchors coordinates exactly as an observation does.\n // The contract is that a click lands in the pixel space of the\n // most recent thing the caller was SHOWN, and a live view is\n // shown the frame stream, not observe(). Without this a viewer\n // that watches frames and clicks what it sees is refused for\n // never having called observe() — technically true and useless.\n if (record.screen) {\n record.screen.lastObservation = { width: frame.width, height: frame.height }\n }\n if (waiting) {\n const resume = waiting\n waiting = null\n resume({ value: frame, done: false })\n } else {\n buffered.push(frame)\n if (buffered.length > MAX_BUFFERED_FRAMES) buffered.shift()\n }\n })\n const finish = (): void => {\n if (finished) return\n finished = true\n unsubscribe()\n record.streamStops.delete(finish)\n if (record.status === 'resident' && record.machine) {\n record.machine.request({ op: 'frames-stop' }).catch((error: unknown) => {\n lastError = errorMessage(error)\n })\n }\n if (waiting) {\n const resume = waiting\n waiting = null\n resume({ value: undefined, done: true })\n }\n }\n record.streamStops.add(finish)\n const started = machine\n .request({ op: 'frames-start', fps, width, height, format })\n .catch((error: unknown) => {\n finish()\n throw error instanceof Error ? error : new Error(String(error))\n })\n return {\n async next(): Promise<IteratorResult<DeskFrame, undefined>> {\n await started\n const bufferedFrame = buffered.shift()\n if (bufferedFrame) return { value: bufferedFrame, done: false }\n if (finished) return { value: undefined, done: true }\n return new Promise((resolvePromise) => {\n waiting = resolvePromise\n })\n },\n async return(): Promise<IteratorResult<DeskFrame, undefined>> {\n finish()\n return { value: undefined, done: true }\n },\n async throw(error?: unknown): Promise<IteratorResult<DeskFrame, undefined>> {\n finish()\n throw error instanceof Error ? error : new Error(String(error))\n },\n }\n },\n }\n },\n video(videoOptions = {}) {\n return {\n [Symbol.asyncIterator]: () => {\n const machine = requireMachine(record, epoch)\n const screen = requireScreen(record, epoch)\n const fps = positiveInteger(videoOptions.fps ?? 30, 'fps')\n const width = positiveInteger(videoOptions.width ?? screen.width, 'width')\n const height = positiveInteger(videoOptions.height ?? screen.height, 'height')\n const buffered: DeskVideoChunk[] = []\n let waiting: ((result: IteratorResult<DeskVideoChunk, undefined>) => void) | null = null\n let finished = false\n const unsubscribe = machine.subscribe((event) => {\n if (finished || event.event !== 'video-chunk') return\n // The same masking contract frames are held to: while a handover\n // is active a person is on that screen, and nothing derived from\n // watching them may reach a recording consumer.\n if (record.screen?.handover) return\n const chunk: DeskVideoChunk = {\n seq: event.seq,\n kind: event.kind,\n codec: event.codec,\n width: event.width,\n height: event.height,\n keyframe: event.keyframe,\n data: Buffer.from(event.data, 'base64'),\n at: iso(now()),\n }\n // A video chunk anchors coordinates exactly as a frame does, and\n // for the same reason: a viewer driving by video is aiming at the\n // pixel space the video showed it. Anchoring on every chunk, not\n // only the first, is what re-asserts the space when the encoder\n // restarts at a new resolution.\n if (record.screen) {\n record.screen.lastObservation = { width: chunk.width, height: chunk.height }\n }\n if (waiting) {\n const resume = waiting\n waiting = null\n resume({ value: chunk, done: false })\n } else {\n buffered.push(chunk)\n // Dropping the OLDEST would drop the init segment, and a\n // consumer without it decodes nothing at all — so an overrun\n // ends the stream instead. A caller that cannot keep up with a\n // video stream is one that should re-subscribe and be resynced\n // from a keyframe, not one that should be handed a hole.\n if (buffered.length > MAX_BUFFERED_VIDEO_CHUNKS) finish()\n }\n })\n const finish = (): void => {\n if (finished) return\n finished = true\n unsubscribe()\n record.streamStops.delete(finish)\n if (record.status === 'resident' && record.machine) {\n record.machine.request({ op: 'video-stop' }).catch((error: unknown) => {\n lastError = errorMessage(error)\n })\n }\n if (waiting) {\n const resume = waiting\n waiting = null\n resume({ value: undefined, done: true })\n }\n }\n record.streamStops.add(finish)\n const started = machine\n .request({ op: 'video-start', fps, width, height })\n .catch((error: unknown) => {\n finish()\n throw error instanceof Error ? error : new Error(String(error))\n })\n return {\n async next(): Promise<IteratorResult<DeskVideoChunk, undefined>> {\n await started\n const bufferedChunk = buffered.shift()\n if (bufferedChunk) return { value: bufferedChunk, done: false }\n if (finished) return { value: undefined, done: true }\n return new Promise((resolvePromise) => {\n waiting = resolvePromise\n })\n },\n async return(): Promise<IteratorResult<DeskVideoChunk, undefined>> {\n finish()\n return { value: undefined, done: true }\n },\n async throw(error?: unknown): Promise<IteratorResult<DeskVideoChunk, undefined>> {\n finish()\n throw error instanceof Error ? error : new Error(String(error))\n },\n }\n },\n }\n },\n handover: {\n async begin({ ttlMs, scope = 'control', actor }) {\n const machine = requireMachine(record, epoch)\n const screen = requireScreen(record, epoch)\n if (screen.handover) {\n throw new DeskError(`A handover is already active on desk ${record.deskId}.`)\n }\n const cleanTtl = positiveInteger(ttlMs, 'ttlMs')\n if (ports.policy?.allowHandover && !ports.policy.allowHandover({\n deskId: record.deskId,\n scope,\n ttlMs: cleanTtl,\n })) {\n throw new DeskError(`Policy denied a handover on desk ${record.deskId}.`)\n }\n touch(record)\n const begun = parseHandoverBegun(\n await machine.request({ op: 'handover-begin', ttlMs: cleanTtl, scope }),\n )\n const startedAt = now()\n screen.handover = {\n actor: actor ?? null,\n scope,\n beganAt: startedAt,\n deadline: startedAt + cleanTtl,\n }\n // The boundary events are the one thing that crosses the mask.\n emit(record, { kind: 'handover_begin', actor: actor ?? null, scope })\n ports.audit({\n kind: 'handover_begin',\n deskId: record.deskId,\n actor: actor ?? null,\n scope,\n ttlMs: cleanTtl,\n at: iso(startedAt),\n })\n armTimer(record)\n return { url: begun.url }\n },\n async end() {\n requireMachine(record, epoch)\n await endHandover(record, 'ended')\n },\n get active() {\n return record.screen?.handover != null\n },\n },\n }\n }\n\n async function start(startOptions: DeskStartOptions): Promise<DeskHandle> {\n const deskId = cleanDeskId(startOptions.deskId)\n const existing = records.get(deskId)\n if (existing?.status === 'resident' && existing.handle) return existing.handle\n return admit(() => boot(deskId, { ...startOptions, deskId }))\n }\n\n async function resume(deskId: string): Promise<DeskHandle> {\n const record = records.get(cleanDeskId(deskId))\n if (!record) throw new DeskError(`Unknown desk: ${deskId}`)\n if (record.status === 'resident' && record.handle) return record.handle\n return admit(() => boot(record.deskId, record.startOptions))\n }\n\n async function suspend(deskId: string): Promise<void> {\n const record = records.get(cleanDeskId(deskId))\n if (!record) return\n await teardown(record)\n }\n\n async function destroy(deskId: string): Promise<void> {\n const id = cleanDeskId(deskId)\n const record = records.get(id)\n if (!record) return\n await teardown(record)\n records.delete(id)\n }\n\n async function sweep(): Promise<number> {\n const timestamp = now()\n let suspendedCount = 0\n for (const record of [...records.values()]) {\n if (record.status !== 'resident') continue\n const handover = record.screen?.handover\n if (handover && handover.deadline <= timestamp) {\n await endHandover(record, 'expired')\n }\n if (\n record.leaseDeadline <= timestamp\n || timestamp - record.lastActivityAt >= idleSuspendMs\n ) {\n await teardown(record)\n suspendedCount += 1\n }\n }\n return suspendedCount\n }\n\n function stats(): DeskHostStats {\n let suspended = 0\n for (const record of records.values()) {\n if (record.status === 'suspended') suspended += 1\n }\n return {\n resident: occupied,\n queued: queue.length,\n capacity,\n suspended,\n lastStartedAt,\n lastSuspendedAt,\n reconnects,\n lastReconnectAt,\n lastReconnectDeskId,\n lastError,\n }\n }\n\n return { start, resume, suspend, destroy, stats, sweep }\n}\n\nexport interface VerifyDeskHostOptions {\n kernelPath: string\n /** Initramfs for the probe VM; needed for a modular kernel, as at boot. */\n initramfsPath?: string\n /**\n * Kernel command line for the probe VM. It must match what real desks boot\n * with: a probe that panics because its root is on another partition\n * reports a host that cannot run desks, which is indistinguishable from a\n * host that genuinely cannot.\n */\n kernelCmdline?: string\n baseImagePath: string\n vmmPath?: string\n kvmPath?: string\n /** Where the throwaway probe overlay is created; defaults to the OS tmpdir. */\n scratchDir?: string\n runtimeDir?: string\n backend?: DeskBackend\n launcherIdentity?: DeskLauncherIdentity\n platform?: NodeJS.Platform\n pathExists?: (path: string) => boolean\n deviceExists?: (path: string) => boolean\n idFactory?: () => string\n}\n\nexport interface DeskHostVerification {\n /** Whether this host can serve desks: KVM present and the guest reachable. */\n supported: boolean\n vmmPath: string\n kvm: boolean\n vsock: boolean\n virtioGpu: boolean\n}\n\n/**\n * Boot-time probe. Distinguishes a host that is unusable — wrong platform,\n * missing VMM, missing images — which throws, from capabilities that are\n * merely absent, which are reported as booleans: `kvm` (no boot possible\n * without it), `vsock` (the guest agent answered), and `virtioGpu` (a screen\n * would have hardware-backed rendering). It boots a throwaway microVM through\n * the backend, asks it two questions, and discards it.\n */\nexport async function verifyDeskHost(options: VerifyDeskHostOptions): Promise<DeskHostVerification> {\n const platform = options.platform ?? process.platform\n const pathExists = options.pathExists ?? existsSync\n const deviceExists = options.deviceExists ?? existsSync\n const idFactory = options.idFactory ?? randomUUID\n const backend = options.backend ?? cloudHypervisorBackend\n const vmmPath = options.vmmPath ?? DEFAULT_VMM_PATH\n const kvmPath = options.kvmPath ?? DEFAULT_KVM_PATH\n\n if (platform !== 'linux') {\n throw new DeskError(`The AppKit desk requires Linux; received ${platform}.`)\n }\n if (!pathExists(vmmPath)) {\n throw new DeskError(`Cloud Hypervisor was not found at ${vmmPath}.`)\n }\n if (!pathExists(options.kernelPath)) {\n throw new DeskError(`Guest kernel does not exist: ${options.kernelPath}`)\n }\n if (!pathExists(options.baseImagePath)) {\n throw new DeskError(`Base image does not exist: ${options.baseImagePath}`)\n }\n if (!deviceExists(kvmPath)) {\n return { supported: false, vmmPath, kvm: false, vsock: false, virtioGpu: false }\n }\n\n const scratchDir = options.scratchDir ?? tmpdir()\n const deskId = `verify-${idFactory()}`\n const overlayPath = join(scratchDir, `${deskId}.raw`)\n const plan = buildDeskLaunchPlan(\n {\n deskId,\n vmmPath,\n kvmPath,\n kernelPath: options.kernelPath,\n ...(options.initramfsPath === undefined ? {} : { initramfsPath: options.initramfsPath }),\n ...(options.kernelCmdline === undefined ? {} : { kernelCmdline: options.kernelCmdline }),\n baseImagePath: options.baseImagePath,\n overlayPath,\n vsockCid: 3,\n runtimeDir: options.runtimeDir,\n launcherIdentity: options.launcherIdentity,\n },\n { pathExists, deviceExists },\n )\n\n let machine: DeskMachine\n try {\n machine = await backend.boot(plan)\n } catch (error) {\n throw new DeskError(`Desk verification could not boot a probe VM: ${errorMessage(error)}`)\n }\n let vsock = false\n let virtioGpu = false\n try {\n try {\n await machine.request({ op: 'ping' })\n vsock = true\n } catch {\n vsock = false\n }\n if (vsock) {\n try {\n virtioGpu = parseCapabilities(await machine.request({ op: 'capabilities' })).virtioGpu\n } catch {\n virtioGpu = false\n }\n }\n } finally {\n try {\n await machine.shutdown()\n } catch {\n // The probe VM is discarded regardless of how cleanly it stops.\n }\n await rm(overlayPath, { force: true })\n }\n return { supported: vsock, vmmPath, kvm: true, vsock, virtioGpu }\n}\n\nfunction nonEmptyString(value: string, field: string): string {\n if (typeof value !== 'string' || value.length === 0) {\n throw new DeskError(`${field} must be a non-empty string.`)\n }\n return value\n}\n\nfunction requireStringValue(value: string, field: string): string {\n if (typeof value !== 'string') {\n throw new DeskError(`${field} must be a string.`)\n }\n return value\n}\n\nfunction positiveInteger(value: number, field: string): number {\n if (!Number.isInteger(value) || value < 1) {\n throw new DeskError(`${field} must be a positive integer.`)\n }\n return value\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"host.js","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The desk host: lifecycle, leases, and the screen service over a backend.\n *\n * One host manages many desks against one capacity budget. A desk is\n * headless by default; the screen is a service started on demand on the\n * machine the agent is already using. The host owns lease accounting (idle\n * suspend, a hard concurrency cap with a FIFO queue beyond it), event\n * emission to the consumer's ports, and the handover masking contract. It\n * never touches a database and knows nothing about tenants or approvals.\n */\nimport { existsSync } from 'node:fs'\nimport { execFile } from 'node:child_process'\nimport { rm, statfs, writeFile } from 'node:fs/promises'\nimport { tmpdir } from 'node:os'\nimport { dirname, join } from 'node:path'\nimport { randomUUID } from 'node:crypto'\nimport { Buffer } from 'node:buffer'\nimport {\n buildDeskLaunchPlan,\n cleanDeskId,\n DEFAULT_KVM_PATH,\n DEFAULT_VMM_PATH,\n DeskError,\n type DeskLauncherIdentity,\n} from './plan'\nimport {\n cloudHypervisorBackend,\n type DeskBackend,\n type DeskConnectionChange,\n type DeskMachine,\n} from './backend'\nimport {\n parseCapabilities,\n parseClipboardText,\n parseExecResult,\n parseHandoverBegun,\n parseJobStarted,\n parseObservation,\n type GuestEventMessage,\n type GuestInput,\n} from './protocol'\nimport type {\n A11yNode,\n DeskEventDetail,\n DeskEventKind,\n DeskFrameFormat,\n DeskHandoverEndReason,\n DeskHandoverScope,\n DeskPoint,\n DeskPointerButton,\n DeskPorts,\n DeskVideoChunkKind,\n WindowInfo,\n} from './events'\n\nexport const DEFAULT_CAPACITY = 4\nexport const DEFAULT_IDLE_SUSPEND_MS = 5 * 60_000\nexport const DEFAULT_LEASE_MS = 15 * 60_000\n/** The in-guest trim run before a desk suspends — see `trimGuest`. */\nexport const DEFAULT_TRIM_COMMAND = '/usr/sbin/fstrim'\nexport const DEFAULT_TRIM_ARGS: readonly string[] = ['/']\nexport const DEFAULT_TRIM_TIMEOUT_MS = 60_000\nexport const DEFAULT_CID_BASE = 100\nconst MAX_BUFFERED_FRAMES = 60\n/**\n * Video chunks a slow consumer may fall behind by before its stream is ended.\n * Larger than the frame bound because a fragment is a fraction of a frame's\n * bytes, and enforced by ENDING rather than by dropping — see the comment at\n * the overrun.\n */\nconst MAX_BUFFERED_VIDEO_CHUNKS = 600\nconst MIN_TIMER_DELAY_MS = 25\n\n/**\n * Event kinds that must never reach the recording port while a handover is\n * active. Human input and everything derived from watching the human act is\n * masked; only the handover boundaries themselves cross.\n */\nconst HANDOVER_MASKED_KINDS: ReadonlySet<DeskEventKind> = new Set([\n 'click',\n 'type',\n 'key',\n 'scroll',\n 'drag',\n 'window_focus',\n 'app_launch',\n])\n\nexport interface DeskHostOptions {\n /** Root of the golden base image and per-desk assets. */\n imageRoot: string\n vmmPath?: string\n kvmPath?: string\n /** Guest kernel; defaults to `<imageRoot>/vmlinux`. */\n kernelPath?: string\n /**\n * Initramfs the guest kernel boots with. A modular distro cloud kernel needs\n * one to mount root; omit only for a kernel with virtio/ext4 built in. When\n * unset it defaults to `<imageRoot>/initrd` if that file exists, else none.\n */\n initramfsPath?: string\n /**\n * Kernel command line every desk boots with. Defaults to\n * `DEFAULT_KERNEL_CMDLINE`; override it for partitioned cloud images whose\n * root is not `/dev/vda` (e.g. `root=/dev/vda3 rw quiet`).\n */\n kernelCmdline?: string\n /** Directory holding vsock and API control sockets. */\n runtimeDir?: string\n launcherIdentity?: DeskLauncherIdentity\n backend?: DeskBackend\n ports: DeskPorts\n /** Hard cap on concurrently resident desks; starts beyond it queue FIFO. */\n capacity?: number\n idleSuspendMs?: number\n defaultLeaseMs?: number\n /** First guest vsock CID handed out; each boot takes the next one. */\n cidBase?: number\n now?: () => number\n pathExists?: (path: string) => boolean\n deviceExists?: (path: string) => boolean\n /**\n * Delete a destroyed desk's overlay. Injectable so the destroy path is\n * testable without a filesystem; defaults to `rm(path, { force: true })`.\n */\n removeOverlay?: (path: string) => Promise<void>\n /**\n * Bytes that must remain free on the overlay filesystem AFTER admitting a\n * desk. A desk whose overlay cannot be grown fails deep inside the guest —\n * ENOSPC surfaces as a corrupt filesystem or a boot that hangs, which reads\n * as a product bug rather than as a full disk. Refusing the lease up front\n * turns that into one clear error. Zero disables the check.\n */\n minFreeBytes?: number\n /**\n * Free bytes on the filesystem holding `path`. Injectable for tests;\n * defaults to `statfs`.\n */\n freeBytes?: (path: string) => Promise<number>\n /**\n * Run `fstrim` in the guest just before it suspends, returning the blocks it\n * has freed to the host filesystem. On by default: an overlay that is never\n * trimmed only grows. Set false for a guest without the binary, or one whose\n * filesystem should never be discarded.\n */\n trimOnSuspend?: boolean\n /** The in-guest trim binary. */\n trimCommand?: string\n /** Arguments for the trim binary; defaults to trimming the guest root. */\n trimArgs?: readonly string[]\n /** Bound on the trim; a slow trim must not hold up a suspend indefinitely. */\n trimTimeoutMs?: number\n}\n\nexport interface DeskStartOptions {\n deskId: string\n /** Absolute path of the golden base image this desk boots from. */\n baseImage: string\n /** Absolute path of this desk's copy-on-write overlay. */\n overlayPath: string\n memoryMb?: number\n vcpus?: number\n leaseMs?: number\n network?: { tapDevice?: string; macAddress?: string }\n}\n\nexport interface DeskExecOptions {\n command: string\n args?: readonly string[]\n cwd?: string\n env?: Readonly<Record<string, string>>\n timeoutMs?: number\n /** Survive the call as a background job; the job dies with the lease. */\n keepAlive?: boolean\n}\n\nexport interface DeskExecSnapshot {\n deskId: string\n command: string\n args: readonly string[]\n exitCode: number | null\n signal: string | null\n stdout: string\n stderr: string\n truncated: boolean\n startedAt: string\n finishedAt: string\n}\n\nexport interface DeskJob {\n jobId: string\n deskId: string\n command: string\n args: readonly string[]\n startedAt: string\n status: 'running' | 'exited'\n exitCode: number | null\n signal: string | null\n}\n\nexport interface DeskObservation {\n png: Buffer\n width: number\n height: number\n a11y: A11yNode | null\n windows: readonly WindowInfo[]\n focused: WindowInfo | null\n}\n\nexport interface DeskFrame {\n seq: number\n width: number\n height: number\n data: Buffer\n /**\n * How `data` is encoded. A consumer must not assume PNG — it builds the\n * media type it hands on (a `data:` URL, a decoder) from this.\n */\n format: DeskFrameFormat\n at: string\n}\n\n/**\n * One unit of the live video stream: the init segment, or one media fragment.\n *\n * Video is deliberately NOT modelled as a kind of frame. A frame is a picture\n * that stands alone; a video chunk is meaningless without the init segment\n * before it and, for a media chunk, without the keyframe its group of pictures\n * began at. Pretending otherwise produces a consumer that appends bytes in any\n * order and renders black with no error to explain it.\n */\nexport interface DeskVideoChunk {\n seq: number\n kind: DeskVideoChunkKind\n /** RFC 6381, e.g. `avc1.42C020`. What a MediaSource must be told. */\n codec: string\n width: number\n height: number\n /** True when a `media` chunk begins at a sync sample. Always false on `init`. */\n keyframe: boolean\n data: Buffer\n at: string\n}\n\nexport interface DeskScreenInput {\n move(x: number, y: number): Promise<void>\n click(x: number, y: number, button?: DeskPointerButton): Promise<void>\n type(text: string): Promise<void>\n key(combo: string): Promise<void>\n scroll(x: number, y: number, dx: number, dy: number): Promise<void>\n drag(from: DeskPoint, to: DeskPoint): Promise<void>\n}\n\nexport interface DeskScreenHandle {\n observe(): Promise<DeskObservation>\n /**\n * Coordinate contract: all coordinates are in the pixel space of the most\n * recent view of the screen — an `observe()`, a frame from `frames()`, or a\n * chunk from `video()` — one to one. All three anchor it, because the caller\n * aims at whatever it was last shown, and a live viewer is shown a stream\n * rather than an observation. That holds whatever the encoding is: a lossy\n * frame and an H.264 fragment are both true-size views of the screen, so they\n * anchor exactly as a lossless PNG does. Input before any view, or outside\n * its bounds, throws.\n */\n input: DeskScreenInput\n a11y: { invoke(nodeId: string, action: string): Promise<void> }\n launch(appId: string, args?: readonly string[]): Promise<void>\n clipboard: { read(): Promise<string>; write(text: string): Promise<void> }\n /**\n * The live frame stream. `format` picks how the guest encodes each frame and\n * is a bytes-for-fidelity trade: `jpeg` is roughly an order of magnitude\n * smaller per frame, which is what a human driving the screen needs, while\n * `png` stays exact. Omitted leaves the choice to the guest. `observe()` is\n * unaffected and always lossless.\n *\n * The stream also ends when the desk reconnects to its guest agent: the\n * capture behind it did not survive, so the iterator completes rather than\n * waiting on frames that are never coming. Start another one to resume.\n */\n frames(options?: {\n fps?: number\n width?: number\n height?: number\n format?: DeskFrameFormat\n }): AsyncIterable<DeskFrame>\n /**\n * The live screen as encoded VIDEO rather than as a sequence of pictures.\n *\n * This is what a person driving the desk should watch. `frames()` ships a\n * whole independent image per tick; a video codec ships the DIFFERENCE, and a\n * desktop is mostly still — which is roughly two orders of magnitude fewer\n * bytes on the transport between the guest and here, the one place the live\n * view was actually bounded.\n *\n * The stream anchors the coordinate space exactly as `observe()` and\n * `frames()` do, from the first chunk onwards, because a viewer driving by\n * video is aiming at what the video showed it.\n *\n * Masked like frames: nothing is emitted while a handover is active. And\n * ended like frames when the desk reconnects to its guest agent — a video\n * consumer resumed on a fresh encoder, without its init segment and away\n * from a keyframe, decodes nothing at all and says so nowhere.\n */\n video(options?: {\n fps?: number\n width?: number\n height?: number\n }): AsyncIterable<DeskVideoChunk>\n handover: {\n begin(options: {\n ttlMs: number\n scope?: DeskHandoverScope\n actor?: string\n }): Promise<{ url: string }>\n end(): Promise<void>\n readonly active: boolean\n }\n}\n\nexport interface DeskScreenService {\n start(options: { width: number; height: number }): Promise<DeskScreenHandle>\n stop(): Promise<void>\n readonly running: boolean\n}\n\n/**\n * One leased desk.\n *\n * The handle stays valid across a lost guest connection: the host reconnects\n * underneath it and later calls work, so a guest agent restart or a dropped\n * vsock bridge no longer costs the rest of the lease. Two consequences a\n * caller must actually handle:\n *\n * - A call that was IN FLIGHT when the connection dropped rejects with\n * `DeskRequestFateUnknownError` — it may already have run in the guest.\n * Nothing retries it for you.\n * - A reconnect does not restore guest-side capture state. Live `frames()` and\n * `video()` iterators END, and the coordinate anchor is dropped, so the\n * caller must `observe()` (or take a new frame) before aiming again.\n *\n * When the connection cannot be recovered at all the desk is suspended, so the\n * handle goes dead and `resume()` boots a fresh one — rather than the handle\n * answering \"no longer connected\" for the rest of the lease.\n */\nexport interface DeskHandle {\n readonly deskId: string\n renewLease(ms: number): void\n exec(options: DeskExecOptions & { keepAlive: true }): Promise<DeskJob>\n exec(options: DeskExecOptions): Promise<DeskExecSnapshot>\n /** The keepAlive processes still running on this desk. */\n jobs(): DeskJob[]\n screen: DeskScreenService\n}\n\nexport interface DeskHostStats {\n /** Desks currently resident (booted or booting) against the capacity cap. */\n resident: number\n /** Starts waiting in the FIFO queue for capacity. */\n queued: number\n capacity: number\n suspended: number\n lastStartedAt: string | null\n lastSuspendedAt: string | null\n /**\n * Times a resident desk lost its guest connection mid-lease and got it back.\n * One is a blip; a number that climbs is a desk FLAPPING — a guest agent\n * restarting in a loop, a failing vsock bridge — and that is the difference\n * between \"the desk is slow\" and a diagnosis.\n */\n reconnects: number\n lastReconnectAt: string | null\n /** Which desk reconnected last: enough to go and look at the right one. */\n lastReconnectDeskId: string | null\n lastError: string | null\n}\n\nexport interface DeskHost {\n start(options: DeskStartOptions): Promise<DeskHandle>\n resume(deskId: string): Promise<DeskHandle>\n suspend(deskId: string): Promise<void>\n destroy(deskId: string): Promise<void>\n stats(): DeskHostStats\n /**\n * Enforce every time-based rule now: expire handovers past their TTL and\n * suspend desks past their lease or idle deadline. Returns the number of\n * desks suspended. Timers call this automatically; tests with an injected\n * clock call it directly.\n */\n sweep(): Promise<number>\n}\n\ninterface HandoverState {\n actor: string | null\n scope: DeskHandoverScope\n beganAt: number\n deadline: number\n}\n\ninterface ScreenState {\n running: boolean\n width: number\n height: number\n lastObservation: { width: number; height: number } | null\n lastFocusedId: string | null\n handover: HandoverState | null\n handle: DeskScreenHandle\n}\n\ninterface DeskRecord {\n deskId: string\n startOptions: DeskStartOptions\n status: 'resident' | 'suspending' | 'suspended'\n machine: DeskMachine | null\n epoch: number\n leaseDeadline: number\n lastActivityAt: number\n jobs: Map<string, DeskJob>\n screen: ScreenState | null\n unsubscribe: (() => void) | null\n unwatchConnection: (() => void) | null\n handle: DeskHandle | null\n timer: NodeJS.Timeout | null\n streamStops: Set<() => void>\n}\n\ninterface QueueEntry {\n run: () => Promise<DeskHandle>\n resolve: (handle: DeskHandle) => void\n reject: (error: Error) => void\n}\n\nexport function createDeskHost(options: DeskHostOptions): DeskHost {\n const ports = options.ports\n if (typeof ports?.onEvent !== 'function' || typeof ports?.audit !== 'function') {\n throw new DeskError('createDeskHost requires ports with onEvent and audit functions.')\n }\n const backend = options.backend ?? cloudHypervisorBackend\n const now = options.now ?? Date.now\n const pathExists = options.pathExists ?? existsSync\n const deviceExists = options.deviceExists ?? existsSync\n const removeOverlay = options.removeOverlay ?? ((path: string) => rm(path, { force: true }))\n const freeBytes = options.freeBytes ?? defaultFreeBytes\n const minFreeBytes = nonNegativeInteger(options.minFreeBytes ?? 0, 'minFreeBytes')\n const trimOnSuspend = options.trimOnSuspend ?? true\n const trimCommand = options.trimCommand ?? DEFAULT_TRIM_COMMAND\n const trimArgs = options.trimArgs ?? DEFAULT_TRIM_ARGS\n const trimTimeoutMs = positiveInteger(options.trimTimeoutMs ?? DEFAULT_TRIM_TIMEOUT_MS, 'trimTimeoutMs')\n const capacity = positiveInteger(options.capacity ?? DEFAULT_CAPACITY, 'capacity')\n const idleSuspendMs = positiveInteger(options.idleSuspendMs ?? DEFAULT_IDLE_SUSPEND_MS, 'idleSuspendMs')\n const defaultLeaseMs = positiveInteger(options.defaultLeaseMs ?? DEFAULT_LEASE_MS, 'defaultLeaseMs')\n const kernelPath = options.kernelPath ?? join(options.imageRoot, 'vmlinux')\n // Default the initramfs to <imageRoot>/initrd when present so a modular\n // cloud kernel boots without every caller having to name it; an explicit\n // option always wins.\n const defaultInitramfs = join(options.imageRoot, 'initrd')\n const initramfsPath =\n options.initramfsPath ?? (pathExists(defaultInitramfs) ? defaultInitramfs : undefined)\n const kernelCmdline = options.kernelCmdline\n\n const records = new Map<string, DeskRecord>()\n /**\n * The last lifecycle transition scheduled for each desk. A desk may only\n * have one VMM child touching its overlay, so boot and teardown are ordered\n * on the desk identity rather than allowed to race through independent API\n * calls. The stored tail always resolves; callers still receive their own\n * operation's result or error.\n */\n const lifecycleTransitions = new Map<string, Promise<void>>()\n const queue: QueueEntry[] = []\n let occupied = 0\n let nextCid = positiveInteger(options.cidBase ?? DEFAULT_CID_BASE, 'cidBase')\n let lastStartedAt: string | null = null\n let lastSuspendedAt: string | null = null\n let lastError: string | null = null\n let reconnects = 0\n let lastReconnectAt: string | null = null\n let lastReconnectDeskId: string | null = null\n\n function iso(timestamp: number): string {\n return new Date(timestamp).toISOString()\n }\n\n function serializeLifecycle<T>(deskId: string, transition: () => Promise<T>): Promise<T> {\n const previous = lifecycleTransitions.get(deskId)\n // Preserve the host's existing synchronous admission semantics when this\n // desk has no transition in flight. Only conflicts need the promise tail;\n // unrelated starts must still appear in queue stats before `start` returns.\n const current = previous ? previous.then(transition, transition) : transition()\n const tail = current.then(\n () => undefined,\n () => undefined,\n )\n lifecycleTransitions.set(deskId, tail)\n return current.finally(() => {\n if (lifecycleTransitions.get(deskId) === tail) lifecycleTransitions.delete(deskId)\n })\n }\n\n function emit(record: DeskRecord, detail: DeskEventDetail): void {\n if (record.screen?.handover && HANDOVER_MASKED_KINDS.has(detail.kind)) return\n ports.onEvent({ ...detail, deskId: record.deskId, at: iso(now()) })\n }\n\n function touch(record: DeskRecord): void {\n record.lastActivityAt = now()\n }\n\n function requireMachine(record: DeskRecord, epoch: number): DeskMachine {\n if (record.status !== 'resident' || record.epoch !== epoch || !record.machine) {\n throw new DeskError(`Desk ${record.deskId} is not resident; resume it first.`)\n }\n return record.machine\n }\n\n function requireScreen(record: DeskRecord, epoch: number): ScreenState {\n requireMachine(record, epoch)\n const screen = record.screen\n if (!screen?.running) {\n throw new DeskError(`Desk ${record.deskId} has no screen running; call screen.start first.`)\n }\n return screen\n }\n\n function assertCoordinates(record: DeskRecord, points: readonly DeskPoint[]): void {\n const observation = record.screen?.lastObservation\n if (!observation) {\n throw new DeskError(\n 'Coordinate input needs a frame of reference: call observe(), or start '\n + 'frames() and wait for one. Coordinates are the pixel space of the '\n + 'most recent of those, one to one.',\n )\n }\n for (const point of points) {\n if (\n !Number.isInteger(point.x)\n || !Number.isInteger(point.y)\n || point.x < 0\n || point.y < 0\n || point.x >= observation.width\n || point.y >= observation.height\n ) {\n throw new DeskError(\n `Coordinates (${point.x}, ${point.y}) are outside the most recent view `\n + `(${observation.width}x${observation.height}); observe() again before targeting.`,\n )\n }\n }\n }\n\n function armTimer(record: DeskRecord): void {\n if (record.timer) clearTimeout(record.timer)\n record.timer = null\n if (record.status !== 'resident') return\n const deadlines = [record.leaseDeadline, record.lastActivityAt + idleSuspendMs]\n const handover = record.screen?.handover\n if (handover) deadlines.push(handover.deadline)\n const delayMs = Math.max(Math.min(...deadlines) - now(), MIN_TIMER_DELAY_MS)\n record.timer = setTimeout(() => {\n sweep()\n .then(() => {\n if (record.status === 'resident') armTimer(record)\n })\n .catch((error: unknown) => {\n lastError = errorMessage(error)\n })\n }, delayMs)\n record.timer.unref()\n }\n\n async function endHandover(record: DeskRecord, reason: DeskHandoverEndReason): Promise<void> {\n const screen = record.screen\n const handover = screen?.handover\n if (!screen || !handover) return\n screen.handover = null\n if (record.machine) {\n try {\n await record.machine.request({ op: 'handover-end' })\n } catch (error) {\n lastError = errorMessage(error)\n }\n }\n const durationMs = Math.max(0, now() - handover.beganAt)\n emit(record, { kind: 'handover_end', actor: handover.actor, durationMs })\n ports.audit({\n kind: 'handover_end',\n deskId: record.deskId,\n actor: handover.actor,\n durationMs,\n reason,\n at: iso(now()),\n })\n }\n\n /**\n * Stop the machine and keep the disk. This is the SUSPEND half of the\n * lifecycle and it is shared by three callers — `suspend`, `sweep`, and\n * `destroy` — so it must never delete, truncate, or rewrite the overlay.\n *\n * That is not a stylistic preference. For a suspended desk the overlay IS\n * the desk: its home directory, its installed state, its half-written work.\n * A desk that comes back from suspend with a fresh disk has silently lost\n * everything, and the loss surfaces long after the code that caused it.\n * Reclaiming the disk is the exclusive business of `reclaimOverlay`, which\n * is called from exactly one place — `destroy` — where the caller has said\n * the desk should cease to exist.\n */\n /**\n * Hand the guest's freed blocks back to the host, as the last thing the\n * guest does before it stops.\n *\n * An overlay only ever GROWS otherwise. A block the guest writes is\n * allocated on the host filesystem forever; deleting the file inside the\n * guest frees it in the guest's own filesystem and tells the host nothing,\n * so a long-lived desk ratchets upward until the volume is full even though\n * the guest reports itself half empty. `fstrim` is what closes that loop:\n * it walks the guest filesystem's free space and issues discards, which\n * Cloud Hypervisor's virtio-blk turns into hole-punches on the raw overlay.\n *\n * Suspend is the right moment for it. The image's own `fstrim.timer` fires\n * WEEKLY, and a desk that idle-suspends after minutes is almost never up\n * when that lands — so on a leased-and-parked desk the scheduled trim\n * effectively never runs. Suspend, by contrast, is exactly when the guest\n * has stopped needing its scratch and the host wants the space back.\n *\n * This does not violate the disk contract on `teardown`. `fstrim` discards\n * only blocks the guest's filesystem already considers free; it cannot\n * remove a suspended desk's state, and a desk resumed afterwards sees an\n * identical filesystem. It is also strictly best-effort and bounded: a\n * guest that is wedged, disconnected, or lacks the binary must still\n * suspend promptly, so every failure here is swallowed into `lastError`.\n */\n async function trimGuest(record: DeskRecord, machine: DeskMachine): Promise<void> {\n if (!trimOnSuspend) return\n try {\n await machine.request({\n op: 'exec',\n command: trimCommand,\n args: trimArgs,\n timeoutMs: trimTimeoutMs,\n })\n } catch (error) {\n lastError = `Trim before suspend failed for ${record.deskId}: ${errorMessage(error)}`\n }\n }\n\n async function teardown(record: DeskRecord): Promise<boolean> {\n if (record.status !== 'resident') return false\n await endHandover(record, 'revoked')\n for (const stop of [...record.streamStops]) stop()\n record.streamStops.clear()\n if (record.screen?.running) {\n record.screen.running = false\n emit(record, { kind: 'screen_close' })\n }\n record.screen = null\n for (const job of record.jobs.values()) {\n if (job.status !== 'running') continue\n job.status = 'exited'\n job.signal = 'SIGKILL'\n emit(record, { kind: 'job_exit', jobId: job.jobId, exitCode: null, signal: 'SIGKILL' })\n }\n record.unsubscribe?.()\n record.unsubscribe = null\n record.unwatchConnection?.()\n record.unwatchConnection = null\n if (record.timer) clearTimeout(record.timer)\n record.timer = null\n const machine = record.machine\n record.machine = null\n // Do not advertise this desk as suspended until its VMM child is actually\n // down. `serializeLifecycle` prevents a new boot from entering while this\n // transitional state still owns the overlay.\n record.status = 'suspending'\n record.handle = null\n if (machine) {\n await trimGuest(record, machine)\n try {\n await machine.shutdown()\n } catch (error) {\n lastError = errorMessage(error)\n }\n }\n lastSuspendedAt = iso(now())\n occupied -= 1\n record.status = 'suspended'\n drainQueue()\n return true\n }\n\n function drainQueue(): void {\n while (occupied < capacity && queue.length > 0) {\n const entry = queue.shift()\n if (!entry) break\n occupied += 1\n entry.run().then(entry.resolve, (error: unknown) => {\n occupied -= 1\n entry.reject(error instanceof Error ? error : new Error(String(error)))\n drainQueue()\n })\n }\n }\n\n function admit(run: () => Promise<DeskHandle>): Promise<DeskHandle> {\n if (occupied < capacity) {\n occupied += 1\n return run().catch((error: unknown) => {\n occupied -= 1\n drainQueue()\n throw error\n })\n }\n return new Promise<DeskHandle>((resolvePromise, rejectPromise) => {\n queue.push({ run, resolve: resolvePromise, reject: rejectPromise })\n })\n }\n\n /**\n * Refuse a desk the overlay filesystem cannot afford. A reflink clone costs\n * almost nothing at creation and then grows as the guest writes, so the\n * moment that matters is not the `cp` — it is every write after it. Checking\n * headroom here converts a mid-boot ENOSPC, which the guest reports as a\n * corrupt disk or a hang, into one refusal naming the actual shortage.\n */\n async function admitDisk(overlayPath: string): Promise<void> {\n if (minFreeBytes <= 0) return\n let free: number\n try {\n free = await freeBytes(overlayPath)\n } catch (error) {\n // Fail OPEN on an unreadable filesystem: a desk refused because statfs\n // hiccuped is a worse outcome than one admitted onto a disk that turns\n // out to be full, which at least fails with a real disk error.\n lastError = `Free-space check failed for ${overlayPath}: ${errorMessage(error)}`\n return\n }\n if (free < minFreeBytes) {\n throw new DeskError(\n `Refusing to start a desk: the overlay filesystem holding ${overlayPath} has `\n + `${free} bytes free, below the ${minFreeBytes}-byte floor. Reclaim destroyed `\n + 'desks’ overlays or grow the volume before leasing another desk.',\n )\n }\n }\n\n async function boot(deskId: string, startOptions: DeskStartOptions): Promise<DeskHandle> {\n // Before the plan, so a full disk is reported as a full disk rather than\n // as whatever the overlay copy fails with.\n await admitDisk(startOptions.overlayPath)\n const plan = buildDeskLaunchPlan(\n {\n deskId,\n vmmPath: options.vmmPath,\n kvmPath: options.kvmPath,\n kernelPath,\n ...(initramfsPath === undefined ? {} : { initramfsPath }),\n baseImagePath: startOptions.baseImage,\n overlayPath: startOptions.overlayPath,\n memoryMb: startOptions.memoryMb,\n vcpus: startOptions.vcpus,\n vsockCid: nextCid++,\n runtimeDir: options.runtimeDir,\n tapDevice: startOptions.network?.tapDevice,\n macAddress: startOptions.network?.macAddress,\n // Forward the host's cmdline only when set; otherwise the plan falls\n // back to DEFAULT_KERNEL_CMDLINE.\n ...(kernelCmdline === undefined ? {} : { kernelCmdline }),\n launcherIdentity: options.launcherIdentity,\n },\n { pathExists, deviceExists },\n )\n let machine: DeskMachine\n try {\n machine = await backend.boot(plan)\n } catch (error) {\n lastError = errorMessage(error)\n throw error instanceof DeskError\n ? error\n : new DeskError(`Desk ${deskId} failed to boot: ${errorMessage(error)}`)\n }\n\n let record = records.get(deskId)\n if (!record) {\n record = {\n deskId,\n startOptions,\n status: 'resident',\n machine,\n epoch: 1,\n leaseDeadline: 0,\n lastActivityAt: 0,\n jobs: new Map(),\n screen: null,\n unsubscribe: null,\n unwatchConnection: null,\n handle: null,\n timer: null,\n streamStops: new Set(),\n }\n records.set(deskId, record)\n } else {\n record.startOptions = startOptions\n record.status = 'resident'\n record.machine = machine\n record.epoch += 1\n record.jobs = new Map()\n record.screen = null\n record.streamStops = new Set()\n }\n const current = record\n const epoch = current.epoch\n current.leaseDeadline = now() + positiveInteger(startOptions.leaseMs ?? defaultLeaseMs, 'leaseMs')\n current.lastActivityAt = now()\n current.unsubscribe = machine.subscribe((event) => {\n handleGuestEvent(current, epoch, event)\n })\n current.unwatchConnection =\n machine.onConnectionChange?.((change) => {\n handleConnectionChange(current, epoch, change)\n }) ?? null\n current.handle = makeHandle(current, epoch)\n lastStartedAt = iso(now())\n armTimer(current)\n return current.handle\n }\n\n function handleGuestEvent(record: DeskRecord, epoch: number, event: GuestEventMessage): void {\n if (record.status !== 'resident' || record.epoch !== epoch) return\n if (event.event === 'job-exit') {\n const job = record.jobs.get(event.jobId)\n if (!job || job.status !== 'running') return\n job.status = 'exited'\n job.exitCode = event.exitCode\n job.signal = event.signal\n emit(record, {\n kind: 'job_exit',\n jobId: event.jobId,\n exitCode: event.exitCode,\n signal: event.signal,\n })\n return\n }\n if (event.event === 'window-focus') {\n if (record.screen) record.screen.lastFocusedId = event.window.id\n emit(record, { kind: 'window_focus', window: event.window })\n }\n }\n\n /**\n * What a desk losing (and regaining) its guest channel means up here.\n *\n * The backend can get the CHANNEL back; it cannot get the GUEST's state\n * back, and pretending otherwise is how a consumer ends up waiting forever\n * on a stream that will never produce another byte. So a reconnect is\n * treated as a discontinuity that the caller is able to see, and a\n * permanent loss retires the desk instead of leaving a dead one leased.\n */\n function handleConnectionChange(\n record: DeskRecord,\n epoch: number,\n change: DeskConnectionChange,\n ): void {\n if (record.status !== 'resident' || record.epoch !== epoch) return\n if (change.state === 'reconnected') {\n reconnects += 1\n lastReconnectAt = iso(now())\n lastReconnectDeskId = record.deskId\n // Never silent. A reconnect that hides a guest agent restarting is a\n // debugging trap: the desk misbehaves and nothing anywhere says why.\n lastError = `Desk ${record.deskId} reconnected to its guest agent after ${change.downtimeMs}ms (${change.reason}).`\n // The guest's capture state did not necessarily come back with the\n // channel. An agent that restarted has no screen, no frame encoder and\n // no video encoder; even one that survived starts a fresh encoder whose\n // sequence numbers and init segment do not continue the old stream. A\n // consumer resumed mid-stream decodes nothing, silently — so every live\n // stream is ENDED. `frames()`/`video()` complete normally, which is how\n // the consumer notices, and it can start a new one on a guest that is\n // now definitely capturing.\n for (const stop of [...record.streamStops]) stop()\n record.streamStops.clear()\n // The coordinate anchor goes with them: the screen behind it may be\n // gone, or recreated at another size, and a click aimed at the pixel\n // space of a screen that no longer exists lands somewhere nobody chose.\n // Input then refuses until the caller observes again — the existing\n // contract, not a new one.\n if (record.screen) record.screen.lastObservation = null\n // A handover is deliberately NOT ended here. If a human is still on that\n // screen, dropping the mask would start recording them; letting the TTL\n // expire it fails in the safe direction.\n return\n }\n // Lost for good. Left resident, this desk would answer every call with\n // \"no longer connected\" for the rest of the lease and `resume()` would\n // hand back the same dead handle — the exact product failure this release\n // is about. Suspending it frees the capacity slot and makes the next\n // `resume()` boot a healthy desk.\n lastError = change.reason\n void serializeLifecycle(record.deskId, async () => {\n // The loss belongs to the epoch that registered this watcher. If an\n // earlier queued transition already replaced that machine, this stale\n // signal must not retire the fresh resident.\n if (record.status !== 'resident' || record.epoch !== epoch) return\n await teardown(record)\n }).catch((error: unknown) => {\n lastError = errorMessage(error)\n })\n }\n\n function makeHandle(record: DeskRecord, epoch: number): DeskHandle {\n const execImplementation = async (\n execOptions: DeskExecOptions,\n ): Promise<DeskExecSnapshot | DeskJob> => {\n const machine = requireMachine(record, epoch)\n const command = nonEmptyString(execOptions.command, 'command')\n const args = [...(execOptions.args ?? [])]\n const keepAlive = execOptions.keepAlive === true\n if (ports.policy?.allowExec && !ports.policy.allowExec({\n deskId: record.deskId,\n command,\n args,\n keepAlive,\n })) {\n throw new DeskError(`Policy denied exec of ${command} on desk ${record.deskId}.`)\n }\n touch(record)\n const startedAt = iso(now())\n if (keepAlive) {\n const { jobId } = parseJobStarted(await machine.request({\n op: 'job-start',\n command,\n args,\n cwd: execOptions.cwd,\n env: execOptions.env,\n }))\n const job: DeskJob = {\n jobId,\n deskId: record.deskId,\n command,\n args,\n startedAt,\n status: 'running',\n exitCode: null,\n signal: null,\n }\n record.jobs.set(jobId, job)\n emit(record, { kind: 'job_start', jobId, command, args })\n return { ...job }\n }\n const result = parseExecResult(await machine.request(\n {\n op: 'exec',\n command,\n args,\n cwd: execOptions.cwd,\n env: execOptions.env,\n timeoutMs: execOptions.timeoutMs,\n },\n execOptions.timeoutMs === undefined ? {} : { timeoutMs: execOptions.timeoutMs + 5_000 },\n ))\n emit(record, {\n kind: 'shell_command',\n command,\n args,\n cwd: execOptions.cwd ?? null,\n exitCode: result.exitCode,\n signal: result.signal,\n })\n return {\n deskId: record.deskId,\n command,\n args,\n exitCode: result.exitCode,\n signal: result.signal,\n stdout: result.stdout,\n stderr: result.stderr,\n truncated: result.truncated,\n startedAt,\n finishedAt: iso(now()),\n }\n }\n\n const screenService: DeskScreenService = {\n async start({ width, height }) {\n const machine = requireMachine(record, epoch)\n if (record.screen?.running) return record.screen.handle\n const cleanWidth = positiveInteger(width, 'width')\n const cleanHeight = positiveInteger(height, 'height')\n if (ports.policy?.allowScreen && !ports.policy.allowScreen({\n deskId: record.deskId,\n width: cleanWidth,\n height: cleanHeight,\n })) {\n throw new DeskError(`Policy denied opening a screen on desk ${record.deskId}.`)\n }\n touch(record)\n await machine.request({ op: 'screen-start', width: cleanWidth, height: cleanHeight })\n const handle = makeScreenHandle(record, epoch)\n record.screen = {\n running: true,\n width: cleanWidth,\n height: cleanHeight,\n lastObservation: null,\n lastFocusedId: null,\n handover: null,\n handle,\n }\n emit(record, { kind: 'screen_open', width: cleanWidth, height: cleanHeight })\n return handle\n },\n async stop() {\n const machine = requireMachine(record, epoch)\n const screen = record.screen\n if (!screen?.running) return\n await endHandover(record, 'revoked')\n for (const stop of [...record.streamStops]) stop()\n record.streamStops.clear()\n screen.running = false\n record.screen = null\n touch(record)\n await machine.request({ op: 'screen-stop' })\n emit(record, { kind: 'screen_close' })\n },\n get running() {\n return record.screen?.running === true\n },\n }\n\n return {\n deskId: record.deskId,\n renewLease(ms: number): void {\n requireMachine(record, epoch)\n record.leaseDeadline = now() + positiveInteger(ms, 'ms')\n touch(record)\n armTimer(record)\n },\n exec: execImplementation as DeskHandle['exec'],\n jobs(): DeskJob[] {\n return [...record.jobs.values()]\n .filter((job) => job.status === 'running')\n .map((job) => ({ ...job }))\n },\n screen: screenService,\n }\n }\n\n function makeScreenHandle(record: DeskRecord, epoch: number): DeskScreenHandle {\n async function sendInput(detail: DeskEventDetail | null, input: GuestInput): Promise<void> {\n const machine = requireMachine(record, epoch)\n touch(record)\n await machine.request({ op: 'input', input })\n if (detail) emit(record, detail)\n }\n\n return {\n async observe(): Promise<DeskObservation> {\n const machine = requireMachine(record, epoch)\n const screen = requireScreen(record, epoch)\n touch(record)\n const observation = parseObservation(await machine.request({ op: 'observe' }))\n screen.lastObservation = { width: observation.width, height: observation.height }\n if (observation.focused && observation.focused.id !== screen.lastFocusedId) {\n screen.lastFocusedId = observation.focused.id\n emit(record, { kind: 'window_focus', window: observation.focused })\n }\n return {\n png: Buffer.from(observation.png, 'base64'),\n width: observation.width,\n height: observation.height,\n a11y: observation.a11y,\n windows: observation.windows,\n focused: observation.focused,\n }\n },\n input: {\n async move(x, y) {\n requireScreen(record, epoch)\n assertCoordinates(record, [{ x, y }])\n await sendInput(null, { type: 'move', x, y })\n },\n async click(x, y, button = 'left') {\n requireScreen(record, epoch)\n assertCoordinates(record, [{ x, y }])\n await sendInput({ kind: 'click', x, y, button }, { type: 'click', x, y, button })\n },\n async type(text) {\n requireScreen(record, epoch)\n const cleanText = requireStringValue(text, 'text')\n await sendInput({ kind: 'type', text: cleanText }, { type: 'type', text: cleanText })\n },\n async key(combo) {\n requireScreen(record, epoch)\n const cleanCombo = nonEmptyString(combo, 'combo')\n await sendInput({ kind: 'key', combo: cleanCombo }, { type: 'key', combo: cleanCombo })\n },\n async scroll(x, y, dx, dy) {\n requireScreen(record, epoch)\n assertCoordinates(record, [{ x, y }])\n if (!Number.isInteger(dx) || !Number.isInteger(dy)) {\n throw new DeskError('Scroll deltas must be integers.')\n }\n await sendInput({ kind: 'scroll', x, y, dx, dy }, { type: 'scroll', x, y, dx, dy })\n },\n async drag(from, to) {\n requireScreen(record, epoch)\n assertCoordinates(record, [from, to])\n await sendInput({ kind: 'drag', from, to }, { type: 'drag', from, to })\n },\n },\n a11y: {\n async invoke(nodeId, action) {\n const machine = requireMachine(record, epoch)\n requireScreen(record, epoch)\n touch(record)\n await machine.request({\n op: 'a11y-invoke',\n nodeId: nonEmptyString(nodeId, 'nodeId'),\n action: nonEmptyString(action, 'action'),\n })\n },\n },\n async launch(appId, args = []) {\n const machine = requireMachine(record, epoch)\n requireScreen(record, epoch)\n touch(record)\n const cleanAppId = nonEmptyString(appId, 'appId')\n await machine.request({ op: 'launch', appId: cleanAppId, args: [...args] })\n emit(record, { kind: 'app_launch', appId: cleanAppId, args: [...args] })\n },\n clipboard: {\n async read() {\n const machine = requireMachine(record, epoch)\n const screen = requireScreen(record, epoch)\n if (screen.handover) {\n // The human may have just typed a credential that is now on the\n // clipboard; reading it during a handover would carry it into the\n // agent's context and the consumer's record.\n throw new DeskError('The clipboard is masked while a handover is active.')\n }\n touch(record)\n return parseClipboardText(await machine.request({ op: 'clipboard-read' }))\n },\n async write(text) {\n const machine = requireMachine(record, epoch)\n requireScreen(record, epoch)\n touch(record)\n await machine.request({ op: 'clipboard-write', text: requireStringValue(text, 'text') })\n },\n },\n frames(frameOptions = {}) {\n return {\n [Symbol.asyncIterator]: () => {\n const machine = requireMachine(record, epoch)\n const screen = requireScreen(record, epoch)\n const fps = positiveInteger(frameOptions.fps ?? 10, 'fps')\n const width = positiveInteger(frameOptions.width ?? screen.width, 'width')\n const height = positiveInteger(frameOptions.height ?? screen.height, 'height')\n const format = frameOptions.format\n const buffered: DeskFrame[] = []\n let waiting: ((result: IteratorResult<DeskFrame, undefined>) => void) | null = null\n let finished = false\n const unsubscribe = machine.subscribe((event) => {\n if (finished || event.event !== 'frame') return\n // Masking contract: no frame reaches a recording consumer while\n // a handover is active.\n if (record.screen?.handover) return\n const frame: DeskFrame = {\n seq: event.seq,\n width: event.width,\n height: event.height,\n data: Buffer.from(event.data, 'base64'),\n format: event.format,\n at: iso(now()),\n }\n // A frame anchors coordinates exactly as an observation does.\n // The contract is that a click lands in the pixel space of the\n // most recent thing the caller was SHOWN, and a live view is\n // shown the frame stream, not observe(). Without this a viewer\n // that watches frames and clicks what it sees is refused for\n // never having called observe() — technically true and useless.\n if (record.screen) {\n record.screen.lastObservation = { width: frame.width, height: frame.height }\n }\n if (waiting) {\n const resume = waiting\n waiting = null\n resume({ value: frame, done: false })\n } else {\n buffered.push(frame)\n if (buffered.length > MAX_BUFFERED_FRAMES) buffered.shift()\n }\n })\n const finish = (): void => {\n if (finished) return\n finished = true\n unsubscribe()\n record.streamStops.delete(finish)\n if (record.status === 'resident' && record.machine) {\n record.machine.request({ op: 'frames-stop' }).catch((error: unknown) => {\n lastError = errorMessage(error)\n })\n }\n if (waiting) {\n const resume = waiting\n waiting = null\n resume({ value: undefined, done: true })\n }\n }\n record.streamStops.add(finish)\n const started = machine\n .request({ op: 'frames-start', fps, width, height, format })\n .catch((error: unknown) => {\n finish()\n throw error instanceof Error ? error : new Error(String(error))\n })\n return {\n async next(): Promise<IteratorResult<DeskFrame, undefined>> {\n await started\n const bufferedFrame = buffered.shift()\n if (bufferedFrame) return { value: bufferedFrame, done: false }\n if (finished) return { value: undefined, done: true }\n return new Promise((resolvePromise) => {\n waiting = resolvePromise\n })\n },\n async return(): Promise<IteratorResult<DeskFrame, undefined>> {\n finish()\n return { value: undefined, done: true }\n },\n async throw(error?: unknown): Promise<IteratorResult<DeskFrame, undefined>> {\n finish()\n throw error instanceof Error ? error : new Error(String(error))\n },\n }\n },\n }\n },\n video(videoOptions = {}) {\n return {\n [Symbol.asyncIterator]: () => {\n const machine = requireMachine(record, epoch)\n const screen = requireScreen(record, epoch)\n const fps = positiveInteger(videoOptions.fps ?? 30, 'fps')\n const width = positiveInteger(videoOptions.width ?? screen.width, 'width')\n const height = positiveInteger(videoOptions.height ?? screen.height, 'height')\n const buffered: DeskVideoChunk[] = []\n let waiting: ((result: IteratorResult<DeskVideoChunk, undefined>) => void) | null = null\n let finished = false\n const unsubscribe = machine.subscribe((event) => {\n if (finished || event.event !== 'video-chunk') return\n // The same masking contract frames are held to: while a handover\n // is active a person is on that screen, and nothing derived from\n // watching them may reach a recording consumer.\n if (record.screen?.handover) return\n const chunk: DeskVideoChunk = {\n seq: event.seq,\n kind: event.kind,\n codec: event.codec,\n width: event.width,\n height: event.height,\n keyframe: event.keyframe,\n data: Buffer.from(event.data, 'base64'),\n at: iso(now()),\n }\n // A video chunk anchors coordinates exactly as a frame does, and\n // for the same reason: a viewer driving by video is aiming at the\n // pixel space the video showed it. Anchoring on every chunk, not\n // only the first, is what re-asserts the space when the encoder\n // restarts at a new resolution.\n if (record.screen) {\n record.screen.lastObservation = { width: chunk.width, height: chunk.height }\n }\n if (waiting) {\n const resume = waiting\n waiting = null\n resume({ value: chunk, done: false })\n } else {\n buffered.push(chunk)\n // Dropping the OLDEST would drop the init segment, and a\n // consumer without it decodes nothing at all — so an overrun\n // ends the stream instead. A caller that cannot keep up with a\n // video stream is one that should re-subscribe and be resynced\n // from a keyframe, not one that should be handed a hole.\n if (buffered.length > MAX_BUFFERED_VIDEO_CHUNKS) finish()\n }\n })\n const finish = (): void => {\n if (finished) return\n finished = true\n unsubscribe()\n record.streamStops.delete(finish)\n if (record.status === 'resident' && record.machine) {\n record.machine.request({ op: 'video-stop' }).catch((error: unknown) => {\n lastError = errorMessage(error)\n })\n }\n if (waiting) {\n const resume = waiting\n waiting = null\n resume({ value: undefined, done: true })\n }\n }\n record.streamStops.add(finish)\n const started = machine\n .request({ op: 'video-start', fps, width, height })\n .catch((error: unknown) => {\n finish()\n throw error instanceof Error ? error : new Error(String(error))\n })\n return {\n async next(): Promise<IteratorResult<DeskVideoChunk, undefined>> {\n await started\n const bufferedChunk = buffered.shift()\n if (bufferedChunk) return { value: bufferedChunk, done: false }\n if (finished) return { value: undefined, done: true }\n return new Promise((resolvePromise) => {\n waiting = resolvePromise\n })\n },\n async return(): Promise<IteratorResult<DeskVideoChunk, undefined>> {\n finish()\n return { value: undefined, done: true }\n },\n async throw(error?: unknown): Promise<IteratorResult<DeskVideoChunk, undefined>> {\n finish()\n throw error instanceof Error ? error : new Error(String(error))\n },\n }\n },\n }\n },\n handover: {\n async begin({ ttlMs, scope = 'control', actor }) {\n const machine = requireMachine(record, epoch)\n const screen = requireScreen(record, epoch)\n if (screen.handover) {\n throw new DeskError(`A handover is already active on desk ${record.deskId}.`)\n }\n const cleanTtl = positiveInteger(ttlMs, 'ttlMs')\n if (ports.policy?.allowHandover && !ports.policy.allowHandover({\n deskId: record.deskId,\n scope,\n ttlMs: cleanTtl,\n })) {\n throw new DeskError(`Policy denied a handover on desk ${record.deskId}.`)\n }\n touch(record)\n const begun = parseHandoverBegun(\n await machine.request({ op: 'handover-begin', ttlMs: cleanTtl, scope }),\n )\n const startedAt = now()\n screen.handover = {\n actor: actor ?? null,\n scope,\n beganAt: startedAt,\n deadline: startedAt + cleanTtl,\n }\n // The boundary events are the one thing that crosses the mask.\n emit(record, { kind: 'handover_begin', actor: actor ?? null, scope })\n ports.audit({\n kind: 'handover_begin',\n deskId: record.deskId,\n actor: actor ?? null,\n scope,\n ttlMs: cleanTtl,\n at: iso(startedAt),\n })\n armTimer(record)\n return { url: begun.url }\n },\n async end() {\n requireMachine(record, epoch)\n await endHandover(record, 'ended')\n },\n get active() {\n return record.screen?.handover != null\n },\n },\n }\n }\n\n async function start(startOptions: DeskStartOptions): Promise<DeskHandle> {\n const deskId = cleanDeskId(startOptions.deskId)\n return serializeLifecycle(deskId, async () => {\n const existing = records.get(deskId)\n if (existing?.status === 'resident' && existing.handle) return existing.handle\n return admit(() => boot(deskId, { ...startOptions, deskId }))\n })\n }\n\n async function resume(deskId: string): Promise<DeskHandle> {\n const id = cleanDeskId(deskId)\n return serializeLifecycle(id, async () => {\n const record = records.get(id)\n if (!record) throw new DeskError(`Unknown desk: ${deskId}`)\n if (record.status === 'resident' && record.handle) return record.handle\n return admit(() => boot(record.deskId, record.startOptions))\n })\n }\n\n async function suspend(deskId: string): Promise<void> {\n const id = cleanDeskId(deskId)\n await serializeLifecycle(id, async () => {\n const record = records.get(id)\n if (!record) return\n await teardown(record)\n })\n }\n\n /**\n * Delete a desk's overlay. The ONLY place in this module that removes a\n * desk's disk, and it is reachable from `destroy` alone — see the contract\n * on `teardown` for why suspend must never come near it.\n *\n * The record is dropped whether or not the unlink succeeds: a desk the\n * caller has destroyed must not stay leaseable because its file was busy.\n * A failed unlink is surfaced through `lastError` and leaves the file for\n * the reaper rather than being retried here.\n */\n async function reclaimOverlay(record: DeskRecord): Promise<void> {\n try {\n await removeOverlay(record.startOptions.overlayPath)\n } catch (error) {\n lastError = `Overlay was not reclaimed for ${record.deskId}: ${errorMessage(error)}`\n }\n }\n\n async function destroy(deskId: string): Promise<void> {\n const id = cleanDeskId(deskId)\n await serializeLifecycle(id, async () => {\n const record = records.get(id)\n if (!record) return\n await teardown(record)\n records.delete(id)\n // Strictly after teardown: the VMM still has the file open until its child\n // has exited, and unlinking underneath a live guest would leave the desk\n // writing into an orphaned inode instead of failing loudly.\n await reclaimOverlay(record)\n })\n }\n\n async function sweep(): Promise<number> {\n const timestamp = now()\n let suspendedCount = 0\n for (const record of [...records.values()]) {\n const suspended = await serializeLifecycle(record.deskId, async () => {\n // A destroy and a later start may have replaced a captured record\n // before its turn in the sweep. Never apply the old deadline to the\n // new desk that happens to share its id.\n if (records.get(record.deskId) !== record || record.status !== 'resident') return false\n const handover = record.screen?.handover\n if (handover && handover.deadline <= timestamp) {\n await endHandover(record, 'expired')\n }\n if (\n record.leaseDeadline <= timestamp\n || timestamp - record.lastActivityAt >= idleSuspendMs\n ) {\n return teardown(record)\n }\n return false\n })\n if (suspended) suspendedCount += 1\n }\n return suspendedCount\n }\n\n function stats(): DeskHostStats {\n let suspended = 0\n for (const record of records.values()) {\n if (record.status === 'suspended') suspended += 1\n }\n return {\n resident: occupied,\n queued: queue.length,\n capacity,\n suspended,\n lastStartedAt,\n lastSuspendedAt,\n reconnects,\n lastReconnectAt,\n lastReconnectDeskId,\n lastError,\n }\n }\n\n return { start, resume, suspend, destroy, stats, sweep }\n}\n\nexport interface VerifyDeskHostOptions {\n kernelPath: string\n /** Initramfs for the probe VM; needed for a modular kernel, as at boot. */\n initramfsPath?: string\n /**\n * Kernel command line for the probe VM. It must match what real desks boot\n * with: a probe that panics because its root is on another partition\n * reports a host that cannot run desks, which is indistinguishable from a\n * host that genuinely cannot.\n */\n kernelCmdline?: string\n baseImagePath: string\n vmmPath?: string\n kvmPath?: string\n /** Where the throwaway probe overlay is created; defaults to the OS tmpdir. */\n scratchDir?: string\n runtimeDir?: string\n backend?: DeskBackend\n launcherIdentity?: DeskLauncherIdentity\n platform?: NodeJS.Platform\n pathExists?: (path: string) => boolean\n deviceExists?: (path: string) => boolean\n idFactory?: () => string\n}\n\nexport interface OverlayReflinkCheck {\n /** Whether `cp --reflink` produced a block-sharing clone in this directory. */\n supported: boolean\n overlayDirectory: string\n /** Why the answer is what it is — the `cp` failure when unsupported. */\n detail: string\n}\n\n/**\n * Whether the overlay directory's filesystem can make reflink clones.\n *\n * This exists because `cp --reflink=auto` — what `buildDeskLaunchPlan` uses,\n * deliberately, so a desk still boots on a filesystem without reflink — is\n * SILENT when it falls back. On XFS or Btrfs a 20GB base clones instantly and\n * costs kilobytes; on ext4 the identical command copies all 20GB, slowly, per\n * desk. The failure is invisible until a disk sized for reflink sharing fills\n * after two desks, which looks like a capacity bug rather than the filesystem\n * choice it actually is.\n *\n * So the probe uses `--reflink=always`, which REFUSES to fall back, on a tiny\n * throwaway file. Call it at startup and refuse to serve rather than discover\n * this in production — see `assertOverlayReflink`.\n */\nexport async function verifyOverlayReflink(options: {\n overlayDirectory: string\n idFactory?: () => string\n copy?: (source: string, destination: string) => Promise<void>\n}): Promise<OverlayReflinkCheck> {\n const overlayDirectory = options.overlayDirectory\n const idFactory = options.idFactory ?? randomUUID\n const copy = options.copy ?? reflinkAlways\n const token = idFactory()\n const source = join(overlayDirectory, `.reflink-probe-${token}`)\n const destination = join(overlayDirectory, `.reflink-probe-${token}.clone`)\n try {\n try {\n await writeFile(source, 'reflink probe')\n } catch (error) {\n // A directory that cannot be written to is not a filesystem without\n // reflink — it is a broken deployment, and reporting it as the former\n // would send an operator to reformat a disk over a permissions bug.\n throw new DeskError(\n `The overlay directory ${overlayDirectory} could not be written to, so reflink `\n + `support could not be determined: ${errorMessage(error)}`,\n )\n }\n await copy(source, destination)\n return { supported: true, overlayDirectory, detail: 'cp --reflink=always succeeded.' }\n } catch (error) {\n if (error instanceof DeskError) throw error\n return { supported: false, overlayDirectory, detail: errorMessage(error) }\n } finally {\n await rm(source, { force: true }).catch(() => undefined)\n await rm(destination, { force: true }).catch(() => undefined)\n }\n}\n\n/**\n * Fail loudly at startup on a filesystem without reflink support, naming the\n * cost so the operator can act on it.\n */\nexport async function assertOverlayReflink(options: {\n overlayDirectory: string\n idFactory?: () => string\n copy?: (source: string, destination: string) => Promise<void>\n}): Promise<OverlayReflinkCheck> {\n const check = await verifyOverlayReflink(options)\n if (!check.supported) {\n throw new DeskError(\n `The overlay directory ${check.overlayDirectory} is on a filesystem without reflink `\n + 'support, so every desk would FULL-COPY the base image instead of sharing its '\n + 'blocks — gigabytes and minutes per desk, and a disk sized for sharing fills after '\n + `a handful of them. Put the overlay directory on XFS or Btrfs. Probe said: ${check.detail}`,\n )\n }\n return check\n}\n\nasync function reflinkAlways(source: string, destination: string): Promise<void> {\n await new Promise<void>((resolvePromise, rejectPromise) => {\n execFile('cp', ['--reflink=always', source, destination], (error, _stdout, stderr) => {\n if (error) {\n rejectPromise(new Error(stderr.trim() || errorMessage(error)))\n return\n }\n resolvePromise()\n })\n })\n}\n\nexport interface DeskHostVerification {\n /** Whether this host can serve desks: KVM present and the guest reachable. */\n supported: boolean\n vmmPath: string\n kvm: boolean\n vsock: boolean\n virtioGpu: boolean\n}\n\n/**\n * Boot-time probe. Distinguishes a host that is unusable — wrong platform,\n * missing VMM, missing images — which throws, from capabilities that are\n * merely absent, which are reported as booleans: `kvm` (no boot possible\n * without it), `vsock` (the guest agent answered), and `virtioGpu` (a screen\n * would have hardware-backed rendering). It boots a throwaway microVM through\n * the backend, asks it two questions, and discards it.\n */\nexport async function verifyDeskHost(options: VerifyDeskHostOptions): Promise<DeskHostVerification> {\n const platform = options.platform ?? process.platform\n const pathExists = options.pathExists ?? existsSync\n const deviceExists = options.deviceExists ?? existsSync\n const idFactory = options.idFactory ?? randomUUID\n const backend = options.backend ?? cloudHypervisorBackend\n const vmmPath = options.vmmPath ?? DEFAULT_VMM_PATH\n const kvmPath = options.kvmPath ?? DEFAULT_KVM_PATH\n\n if (platform !== 'linux') {\n throw new DeskError(`The AppKit desk requires Linux; received ${platform}.`)\n }\n if (!pathExists(vmmPath)) {\n throw new DeskError(`Cloud Hypervisor was not found at ${vmmPath}.`)\n }\n if (!pathExists(options.kernelPath)) {\n throw new DeskError(`Guest kernel does not exist: ${options.kernelPath}`)\n }\n if (!pathExists(options.baseImagePath)) {\n throw new DeskError(`Base image does not exist: ${options.baseImagePath}`)\n }\n if (!deviceExists(kvmPath)) {\n return { supported: false, vmmPath, kvm: false, vsock: false, virtioGpu: false }\n }\n\n const scratchDir = options.scratchDir ?? tmpdir()\n const deskId = `verify-${idFactory()}`\n const overlayPath = join(scratchDir, `${deskId}.raw`)\n const plan = buildDeskLaunchPlan(\n {\n deskId,\n vmmPath,\n kvmPath,\n kernelPath: options.kernelPath,\n ...(options.initramfsPath === undefined ? {} : { initramfsPath: options.initramfsPath }),\n ...(options.kernelCmdline === undefined ? {} : { kernelCmdline: options.kernelCmdline }),\n baseImagePath: options.baseImagePath,\n overlayPath,\n vsockCid: 3,\n runtimeDir: options.runtimeDir,\n launcherIdentity: options.launcherIdentity,\n },\n { pathExists, deviceExists },\n )\n\n let machine: DeskMachine\n try {\n machine = await backend.boot(plan)\n } catch (error) {\n throw new DeskError(`Desk verification could not boot a probe VM: ${errorMessage(error)}`)\n }\n let vsock = false\n let virtioGpu = false\n try {\n try {\n await machine.request({ op: 'ping' })\n vsock = true\n } catch {\n vsock = false\n }\n if (vsock) {\n try {\n virtioGpu = parseCapabilities(await machine.request({ op: 'capabilities' })).virtioGpu\n } catch {\n virtioGpu = false\n }\n }\n } finally {\n try {\n await machine.shutdown()\n } catch {\n // The probe VM is discarded regardless of how cleanly it stops.\n }\n await rm(overlayPath, { force: true })\n }\n return { supported: vsock, vmmPath, kvm: true, vsock, virtioGpu }\n}\n\nfunction nonEmptyString(value: string, field: string): string {\n if (typeof value !== 'string' || value.length === 0) {\n throw new DeskError(`${field} must be a non-empty string.`)\n }\n return value\n}\n\nfunction requireStringValue(value: string, field: string): string {\n if (typeof value !== 'string') {\n throw new DeskError(`${field} must be a string.`)\n }\n return value\n}\n\nfunction positiveInteger(value: number, field: string): number {\n if (!Number.isInteger(value) || value < 1) {\n throw new DeskError(`${field} must be a positive integer.`)\n }\n return value\n}\n\nfunction nonNegativeInteger(value: number, field: string): number {\n if (!Number.isInteger(value) || value < 0) {\n throw new DeskError(`${field} must be an integer of zero or more.`)\n }\n return value\n}\n\n/**\n * Free bytes on the filesystem holding `path`, measured against the directory\n * so the answer is the same before and after the overlay itself exists.\n */\nasync function defaultFreeBytes(path: string): Promise<number> {\n const stats = await statfs(dirname(path))\n return Number(stats.bsize) * Number(stats.bavail)\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@braedonsaunders/appkit-desk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Per-agent Debian microVMs under Cloud Hypervisor — headless machines with an on-demand screen, a framed vsock guest-agent protocol, leases with idle suspend and a bounded queue, and load-bearing handover masking behind consumer-supplied policy and recording ports.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"type": "module",
|