@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 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":"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"}
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 { rm } from 'node:fs/promises';
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
- record.status = 'suspended';
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 teardown(record).catch((error) => {
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
- const existing = records.get(deskId);
868
- if (existing?.status === 'resident' && existing.handle)
869
- return existing.handle;
870
- return admit(() => boot(deskId, { ...startOptions, deskId }));
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 record = records.get(cleanDeskId(deskId));
874
- if (!record)
875
- throw new DeskError(`Unknown desk: ${deskId}`);
876
- if (record.status === 'resident' && record.handle)
877
- return record.handle;
878
- return admit(() => boot(record.deskId, record.startOptions));
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 record = records.get(cleanDeskId(deskId));
882
- if (!record)
883
- return;
884
- await teardown(record);
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
- const record = records.get(id);
889
- if (!record)
890
- return;
891
- await teardown(record);
892
- records.delete(id);
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
- if (record.status !== 'resident')
899
- continue;
900
- const handover = record.screen?.handover;
901
- if (handover && handover.deadline <= timestamp) {
902
- await endHandover(record, 'expired');
903
- }
904
- if (record.leaseDeadline <= timestamp
905
- || timestamp - record.lastActivityAt >= idleSuspendMs) {
906
- await teardown(record);
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.4.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",