@braedonsaunders/appkit-desk 0.4.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/LICENSE +661 -0
- package/README.md +233 -0
- package/backend.d.ts +139 -0
- package/backend.d.ts.map +1 -0
- package/backend.js +583 -0
- package/backend.js.map +1 -0
- package/conformance.d.ts +19 -0
- package/conformance.d.ts.map +1 -0
- package/conformance.js +46 -0
- package/conformance.js.map +1 -0
- package/events.d.ts +190 -0
- package/events.d.ts.map +1 -0
- package/events.js +12 -0
- package/events.js.map +1 -0
- package/frames.d.ts +14 -0
- package/frames.d.ts.map +1 -0
- package/frames.js +24 -0
- package/frames.js.map +1 -0
- package/guest-agent.d.ts +129 -0
- package/guest-agent.d.ts.map +1 -0
- package/guest-agent.js +165 -0
- package/guest-agent.js.map +1 -0
- package/host.d.ts +325 -0
- package/host.d.ts.map +1 -0
- package/host.js +1038 -0
- package/host.js.map +1 -0
- package/index.d.ts +10 -0
- package/index.d.ts.map +1 -0
- package/index.js +10 -0
- package/index.js.map +1 -0
- package/package.json +41 -0
- package/plan.d.ts +117 -0
- package/plan.d.ts.map +1 -0
- package/plan.js +187 -0
- package/plan.js.map +1 -0
- package/portable-home.d.ts +79 -0
- package/portable-home.d.ts.map +1 -0
- package/portable-home.js +193 -0
- package/portable-home.js.map +1 -0
- package/protocol.d.ts +224 -0
- package/protocol.d.ts.map +1 -0
- package/protocol.js +498 -0
- package/protocol.js.map +1 -0
package/guest-agent.js
ADDED
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The in-guest agent: PID 1's helper inside the microVM, speaking the framed
|
|
3
|
+
* JSON protocol over a vsock stream. This is the only new attack surface in
|
|
4
|
+
* the desk design, so it is deliberately small: a bounded frame decoder, a
|
|
5
|
+
* strict parser, a closed dispatch switch, and nothing else. Everything that
|
|
6
|
+
* touches the guest OS — spawning processes, driving the compositor, reading
|
|
7
|
+
* the clipboard — is injected as a handler so the message-handling core stays
|
|
8
|
+
* pure and unit-testable on any platform.
|
|
9
|
+
*
|
|
10
|
+
* The module depends only on `node:` built-ins and this package's own
|
|
11
|
+
* protocol module; it ships in the same package and is started by the guest's
|
|
12
|
+
* init.
|
|
13
|
+
*/
|
|
14
|
+
import { Buffer } from 'node:buffer';
|
|
15
|
+
import { DEFAULT_MAX_FRAME_BYTES, DeskProtocolError, encodeFrame, FrameDecoder, parseGuestRequest, } from './protocol.js';
|
|
16
|
+
export function createGuestAgentCore(handlers, options = {}) {
|
|
17
|
+
const maxFrameBytes = options.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES;
|
|
18
|
+
const decoder = new FrameDecoder({ maxFrameBytes });
|
|
19
|
+
async function respond(value) {
|
|
20
|
+
let request;
|
|
21
|
+
try {
|
|
22
|
+
request = parseGuestRequest(value);
|
|
23
|
+
}
|
|
24
|
+
catch (error) {
|
|
25
|
+
// The frame itself was well-formed JSON, so answering is safe; echo the
|
|
26
|
+
// id when one is recoverable so the host can settle its pending call.
|
|
27
|
+
const id = recoverId(value);
|
|
28
|
+
return { id, ok: false, error: errorMessage(error) };
|
|
29
|
+
}
|
|
30
|
+
try {
|
|
31
|
+
return { id: request.id, ok: true, result: await dispatch(request) };
|
|
32
|
+
}
|
|
33
|
+
catch (error) {
|
|
34
|
+
return { id: request.id, ok: false, error: errorMessage(error) };
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async function dispatch(request) {
|
|
38
|
+
switch (request.op) {
|
|
39
|
+
case 'ping':
|
|
40
|
+
return { pong: true };
|
|
41
|
+
case 'capabilities':
|
|
42
|
+
return handlers.capabilities();
|
|
43
|
+
case 'exec':
|
|
44
|
+
return handlers.exec({
|
|
45
|
+
command: request.command,
|
|
46
|
+
args: request.args ?? [],
|
|
47
|
+
cwd: request.cwd ?? null,
|
|
48
|
+
env: request.env ?? null,
|
|
49
|
+
timeoutMs: request.timeoutMs ?? null,
|
|
50
|
+
});
|
|
51
|
+
case 'job-start':
|
|
52
|
+
return handlers.jobStart({
|
|
53
|
+
command: request.command,
|
|
54
|
+
args: request.args ?? [],
|
|
55
|
+
cwd: request.cwd ?? null,
|
|
56
|
+
env: request.env ?? null,
|
|
57
|
+
});
|
|
58
|
+
case 'job-signal':
|
|
59
|
+
await handlers.jobSignal({ jobId: request.jobId, signal: request.signal });
|
|
60
|
+
return {};
|
|
61
|
+
case 'screen-start':
|
|
62
|
+
await handlers.screenStart({ width: request.width, height: request.height });
|
|
63
|
+
return {};
|
|
64
|
+
case 'screen-stop':
|
|
65
|
+
await handlers.screenStop();
|
|
66
|
+
return {};
|
|
67
|
+
case 'observe':
|
|
68
|
+
return handlers.observe();
|
|
69
|
+
case 'input':
|
|
70
|
+
await handlers.input(request.input);
|
|
71
|
+
return {};
|
|
72
|
+
case 'a11y-invoke':
|
|
73
|
+
await handlers.a11yInvoke({ nodeId: request.nodeId, action: request.action });
|
|
74
|
+
return {};
|
|
75
|
+
case 'launch':
|
|
76
|
+
await handlers.launch({ appId: request.appId, args: request.args ?? [] });
|
|
77
|
+
return {};
|
|
78
|
+
case 'clipboard-read':
|
|
79
|
+
return handlers.clipboardRead();
|
|
80
|
+
case 'clipboard-write':
|
|
81
|
+
await handlers.clipboardWrite({ text: request.text });
|
|
82
|
+
return {};
|
|
83
|
+
case 'frames-start':
|
|
84
|
+
await handlers.framesStart({
|
|
85
|
+
fps: request.fps,
|
|
86
|
+
width: request.width,
|
|
87
|
+
height: request.height,
|
|
88
|
+
format: request.format,
|
|
89
|
+
});
|
|
90
|
+
return {};
|
|
91
|
+
case 'frames-stop':
|
|
92
|
+
await handlers.framesStop();
|
|
93
|
+
return {};
|
|
94
|
+
case 'video-start':
|
|
95
|
+
await handlers.videoStart({
|
|
96
|
+
fps: request.fps,
|
|
97
|
+
width: request.width,
|
|
98
|
+
height: request.height,
|
|
99
|
+
});
|
|
100
|
+
return {};
|
|
101
|
+
case 'video-stop':
|
|
102
|
+
await handlers.videoStop();
|
|
103
|
+
return {};
|
|
104
|
+
case 'handover-begin':
|
|
105
|
+
return handlers.handoverBegin({ ttlMs: request.ttlMs, scope: request.scope });
|
|
106
|
+
case 'handover-end':
|
|
107
|
+
await handlers.handoverEnd();
|
|
108
|
+
return {};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return {
|
|
112
|
+
async handleChunk(chunk) {
|
|
113
|
+
const values = decoder.push(chunk);
|
|
114
|
+
const responses = [];
|
|
115
|
+
for (const value of values) {
|
|
116
|
+
responses.push(encodeFrame(await respond(value), maxFrameBytes));
|
|
117
|
+
}
|
|
118
|
+
return responses;
|
|
119
|
+
},
|
|
120
|
+
encodeEvent(event) {
|
|
121
|
+
return encodeFrame(event, maxFrameBytes);
|
|
122
|
+
},
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Bind the pure core to a real byte stream. This is the entire impure surface
|
|
127
|
+
* of the guest agent: read chunks, write responses, destroy the connection on
|
|
128
|
+
* a framing violation.
|
|
129
|
+
*/
|
|
130
|
+
export function runGuestAgent(options) {
|
|
131
|
+
const { stream, handlers } = options;
|
|
132
|
+
const core = createGuestAgentCore(handlers, { maxFrameBytes: options.maxFrameBytes });
|
|
133
|
+
let queue = Promise.resolve();
|
|
134
|
+
stream.on('data', (chunk) => {
|
|
135
|
+
queue = queue
|
|
136
|
+
.then(async () => {
|
|
137
|
+
for (const frame of await core.handleChunk(chunk))
|
|
138
|
+
stream.write(frame);
|
|
139
|
+
})
|
|
140
|
+
.catch((error) => {
|
|
141
|
+
stream.destroy(error instanceof DeskProtocolError ? error : new DeskProtocolError(errorMessage(error)));
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
const closed = new Promise((resolvePromise) => {
|
|
145
|
+
stream.once('close', () => resolvePromise());
|
|
146
|
+
});
|
|
147
|
+
return {
|
|
148
|
+
sendEvent(event) {
|
|
149
|
+
stream.write(core.encodeEvent(event));
|
|
150
|
+
},
|
|
151
|
+
closed,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
function recoverId(value) {
|
|
155
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
156
|
+
const id = value.id;
|
|
157
|
+
if (typeof id === 'string' && id.length > 0 && id.length <= 128)
|
|
158
|
+
return id;
|
|
159
|
+
}
|
|
160
|
+
return 'unknown';
|
|
161
|
+
}
|
|
162
|
+
function errorMessage(error) {
|
|
163
|
+
return error instanceof Error ? error.message : String(error);
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=guest-agent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"guest-agent.js","sourceRoot":"","sources":["../src/guest-agent.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The in-guest agent: PID 1's helper inside the microVM, speaking the framed\n * JSON protocol over a vsock stream. This is the only new attack surface in\n * the desk design, so it is deliberately small: a bounded frame decoder, a\n * strict parser, a closed dispatch switch, and nothing else. Everything that\n * touches the guest OS — spawning processes, driving the compositor, reading\n * the clipboard — is injected as a handler so the message-handling core stays\n * pure and unit-testable on any platform.\n *\n * The module depends only on `node:` built-ins and this package's own\n * protocol module; it ships in the same package and is started by the guest's\n * init.\n */\nimport { Buffer } from 'node:buffer'\nimport type { Duplex } from 'node:stream'\nimport {\n DEFAULT_MAX_FRAME_BYTES,\n DeskProtocolError,\n encodeFrame,\n FrameDecoder,\n parseGuestRequest,\n type GuestEventMessage,\n type GuestExecResult,\n type GuestObservation,\n type GuestInput,\n type GuestRequest,\n type GuestResponse,\n} from './protocol'\nimport type { DeskFrameFormat, DeskHandoverScope } from './events'\n\nexport interface GuestExecCall {\n command: string\n args: readonly string[]\n cwd: string | null\n env: Readonly<Record<string, string>> | null\n timeoutMs: number | null\n}\n\nexport interface GuestJobStartCall {\n command: string\n args: readonly string[]\n cwd: string | null\n env: Readonly<Record<string, string>> | null\n}\n\n/**\n * The operations the guest environment implements. Handlers own all contact\n * with the OS; the core owns all contact with the wire.\n */\nexport interface GuestAgentHandlers {\n exec(call: GuestExecCall): Promise<GuestExecResult>\n jobStart(call: GuestJobStartCall): Promise<{ jobId: string }>\n jobSignal(call: { jobId: string; signal: 'SIGTERM' | 'SIGKILL' }): Promise<void>\n screenStart(call: { width: number; height: number }): Promise<void>\n screenStop(): Promise<void>\n observe(): Promise<GuestObservation>\n input(action: GuestInput): Promise<void>\n a11yInvoke(call: { nodeId: string; action: string }): Promise<void>\n launch(call: { appId: string; args: readonly string[] }): Promise<void>\n clipboardRead(): Promise<{ text: string }>\n clipboardWrite(call: { text: string }): Promise<void>\n /**\n * `format` absent means the host did not ask, so the guest picks — an older\n * host never sends it and must still get a stream it can decode.\n */\n framesStart(call: {\n fps: number\n width: number\n height: number\n format?: DeskFrameFormat\n }): Promise<void>\n framesStop(): Promise<void>\n /**\n * Start encoding the screen as video. Separate from `framesStart` because it\n * is a different job with a different transport shape: one long-lived encoder\n * whose output only means anything in order, rather than independent pictures.\n */\n videoStart(call: { fps: number; width: number; height: number }): Promise<void>\n videoStop(): Promise<void>\n handoverBegin(call: { ttlMs: number; scope: DeskHandoverScope }): Promise<{ url: string }>\n handoverEnd(): Promise<void>\n capabilities(): Promise<{ virtioGpu: boolean }>\n}\n\nexport interface GuestAgentCore {\n /**\n * Consume one chunk of the byte stream and return the frames to write back,\n * in order. Malformed requests produce error responses; a framing violation\n * (oversized or non-JSON frame) throws, and the caller must drop the\n * connection — a length-prefixed stream cannot be resynchronized once its\n * lengths are untrusted.\n */\n handleChunk(chunk: Buffer): Promise<Buffer[]>\n /** Encode an unsolicited guest-to-host event frame. */\n encodeEvent(event: GuestEventMessage): Buffer\n}\n\nexport function createGuestAgentCore(\n handlers: GuestAgentHandlers,\n options: { maxFrameBytes?: number } = {},\n): GuestAgentCore {\n const maxFrameBytes = options.maxFrameBytes ?? DEFAULT_MAX_FRAME_BYTES\n const decoder = new FrameDecoder({ maxFrameBytes })\n\n async function respond(value: unknown): Promise<GuestResponse> {\n let request: GuestRequest\n try {\n request = parseGuestRequest(value)\n } catch (error) {\n // The frame itself was well-formed JSON, so answering is safe; echo the\n // id when one is recoverable so the host can settle its pending call.\n const id = recoverId(value)\n return { id, ok: false, error: errorMessage(error) }\n }\n try {\n return { id: request.id, ok: true, result: await dispatch(request) }\n } catch (error) {\n return { id: request.id, ok: false, error: errorMessage(error) }\n }\n }\n\n async function dispatch(request: GuestRequest): Promise<unknown> {\n switch (request.op) {\n case 'ping':\n return { pong: true }\n case 'capabilities':\n return handlers.capabilities()\n case 'exec':\n return handlers.exec({\n command: request.command,\n args: request.args ?? [],\n cwd: request.cwd ?? null,\n env: request.env ?? null,\n timeoutMs: request.timeoutMs ?? null,\n })\n case 'job-start':\n return handlers.jobStart({\n command: request.command,\n args: request.args ?? [],\n cwd: request.cwd ?? null,\n env: request.env ?? null,\n })\n case 'job-signal':\n await handlers.jobSignal({ jobId: request.jobId, signal: request.signal })\n return {}\n case 'screen-start':\n await handlers.screenStart({ width: request.width, height: request.height })\n return {}\n case 'screen-stop':\n await handlers.screenStop()\n return {}\n case 'observe':\n return handlers.observe()\n case 'input':\n await handlers.input(request.input)\n return {}\n case 'a11y-invoke':\n await handlers.a11yInvoke({ nodeId: request.nodeId, action: request.action })\n return {}\n case 'launch':\n await handlers.launch({ appId: request.appId, args: request.args ?? [] })\n return {}\n case 'clipboard-read':\n return handlers.clipboardRead()\n case 'clipboard-write':\n await handlers.clipboardWrite({ text: request.text })\n return {}\n case 'frames-start':\n await handlers.framesStart({\n fps: request.fps,\n width: request.width,\n height: request.height,\n format: request.format,\n })\n return {}\n case 'frames-stop':\n await handlers.framesStop()\n return {}\n case 'video-start':\n await handlers.videoStart({\n fps: request.fps,\n width: request.width,\n height: request.height,\n })\n return {}\n case 'video-stop':\n await handlers.videoStop()\n return {}\n case 'handover-begin':\n return handlers.handoverBegin({ ttlMs: request.ttlMs, scope: request.scope })\n case 'handover-end':\n await handlers.handoverEnd()\n return {}\n }\n }\n\n return {\n async handleChunk(chunk: Buffer): Promise<Buffer[]> {\n const values = decoder.push(chunk)\n const responses: Buffer[] = []\n for (const value of values) {\n responses.push(encodeFrame(await respond(value), maxFrameBytes))\n }\n return responses\n },\n encodeEvent(event: GuestEventMessage): Buffer {\n return encodeFrame(event, maxFrameBytes)\n },\n }\n}\n\nexport interface RunningGuestAgent {\n /** Push an unsolicited event (job exit, frame, focus change) to the host. */\n sendEvent(event: GuestEventMessage): void\n /** Resolves when the connection has closed. */\n closed: Promise<void>\n}\n\n/**\n * Bind the pure core to a real byte stream. This is the entire impure surface\n * of the guest agent: read chunks, write responses, destroy the connection on\n * a framing violation.\n */\nexport function runGuestAgent(options: {\n stream: Duplex\n handlers: GuestAgentHandlers\n maxFrameBytes?: number\n}): RunningGuestAgent {\n const { stream, handlers } = options\n const core = createGuestAgentCore(handlers, { maxFrameBytes: options.maxFrameBytes })\n let queue: Promise<void> = Promise.resolve()\n\n stream.on('data', (chunk: Buffer) => {\n queue = queue\n .then(async () => {\n for (const frame of await core.handleChunk(chunk)) stream.write(frame)\n })\n .catch((error: unknown) => {\n stream.destroy(\n error instanceof DeskProtocolError ? error : new DeskProtocolError(errorMessage(error)),\n )\n })\n })\n\n const closed = new Promise<void>((resolvePromise) => {\n stream.once('close', () => resolvePromise())\n })\n\n return {\n sendEvent(event: GuestEventMessage): void {\n stream.write(core.encodeEvent(event))\n },\n closed,\n }\n}\n\nfunction recoverId(value: unknown): string {\n if (typeof value === 'object' && value !== null && !Array.isArray(value)) {\n const id = (value as Record<string, unknown>).id\n if (typeof id === 'string' && id.length > 0 && id.length <= 128) return id\n }\n return 'unknown'\n}\n\nfunction errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n"]}
|
package/host.d.ts
ADDED
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
import { Buffer } from 'node:buffer';
|
|
2
|
+
import { type DeskLauncherIdentity } from './plan.js';
|
|
3
|
+
import { type DeskBackend } from './backend.js';
|
|
4
|
+
import type { A11yNode, DeskFrameFormat, DeskHandoverScope, DeskPoint, DeskPointerButton, DeskPorts, DeskVideoChunkKind, WindowInfo } from './events.js';
|
|
5
|
+
export declare const DEFAULT_CAPACITY = 4;
|
|
6
|
+
export declare const DEFAULT_IDLE_SUSPEND_MS: number;
|
|
7
|
+
export declare const DEFAULT_LEASE_MS: number;
|
|
8
|
+
export declare const DEFAULT_CID_BASE = 100;
|
|
9
|
+
export interface DeskHostOptions {
|
|
10
|
+
/** Root of the golden base image and per-desk assets. */
|
|
11
|
+
imageRoot: string;
|
|
12
|
+
vmmPath?: string;
|
|
13
|
+
kvmPath?: string;
|
|
14
|
+
/** Guest kernel; defaults to `<imageRoot>/vmlinux`. */
|
|
15
|
+
kernelPath?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Initramfs the guest kernel boots with. A modular distro cloud kernel needs
|
|
18
|
+
* one to mount root; omit only for a kernel with virtio/ext4 built in. When
|
|
19
|
+
* unset it defaults to `<imageRoot>/initrd` if that file exists, else none.
|
|
20
|
+
*/
|
|
21
|
+
initramfsPath?: string;
|
|
22
|
+
/**
|
|
23
|
+
* Kernel command line every desk boots with. Defaults to
|
|
24
|
+
* `DEFAULT_KERNEL_CMDLINE`; override it for partitioned cloud images whose
|
|
25
|
+
* root is not `/dev/vda` (e.g. `root=/dev/vda3 rw quiet`).
|
|
26
|
+
*/
|
|
27
|
+
kernelCmdline?: string;
|
|
28
|
+
/** Directory holding vsock and API control sockets. */
|
|
29
|
+
runtimeDir?: string;
|
|
30
|
+
launcherIdentity?: DeskLauncherIdentity;
|
|
31
|
+
backend?: DeskBackend;
|
|
32
|
+
ports: DeskPorts;
|
|
33
|
+
/** Hard cap on concurrently resident desks; starts beyond it queue FIFO. */
|
|
34
|
+
capacity?: number;
|
|
35
|
+
idleSuspendMs?: number;
|
|
36
|
+
defaultLeaseMs?: number;
|
|
37
|
+
/** First guest vsock CID handed out; each boot takes the next one. */
|
|
38
|
+
cidBase?: number;
|
|
39
|
+
now?: () => number;
|
|
40
|
+
pathExists?: (path: string) => boolean;
|
|
41
|
+
deviceExists?: (path: string) => boolean;
|
|
42
|
+
}
|
|
43
|
+
export interface DeskStartOptions {
|
|
44
|
+
deskId: string;
|
|
45
|
+
/** Absolute path of the golden base image this desk boots from. */
|
|
46
|
+
baseImage: string;
|
|
47
|
+
/** Absolute path of this desk's copy-on-write overlay. */
|
|
48
|
+
overlayPath: string;
|
|
49
|
+
memoryMb?: number;
|
|
50
|
+
vcpus?: number;
|
|
51
|
+
leaseMs?: number;
|
|
52
|
+
network?: {
|
|
53
|
+
tapDevice?: string;
|
|
54
|
+
macAddress?: string;
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
export interface DeskExecOptions {
|
|
58
|
+
command: string;
|
|
59
|
+
args?: readonly string[];
|
|
60
|
+
cwd?: string;
|
|
61
|
+
env?: Readonly<Record<string, string>>;
|
|
62
|
+
timeoutMs?: number;
|
|
63
|
+
/** Survive the call as a background job; the job dies with the lease. */
|
|
64
|
+
keepAlive?: boolean;
|
|
65
|
+
}
|
|
66
|
+
export interface DeskExecSnapshot {
|
|
67
|
+
deskId: string;
|
|
68
|
+
command: string;
|
|
69
|
+
args: readonly string[];
|
|
70
|
+
exitCode: number | null;
|
|
71
|
+
signal: string | null;
|
|
72
|
+
stdout: string;
|
|
73
|
+
stderr: string;
|
|
74
|
+
truncated: boolean;
|
|
75
|
+
startedAt: string;
|
|
76
|
+
finishedAt: string;
|
|
77
|
+
}
|
|
78
|
+
export interface DeskJob {
|
|
79
|
+
jobId: string;
|
|
80
|
+
deskId: string;
|
|
81
|
+
command: string;
|
|
82
|
+
args: readonly string[];
|
|
83
|
+
startedAt: string;
|
|
84
|
+
status: 'running' | 'exited';
|
|
85
|
+
exitCode: number | null;
|
|
86
|
+
signal: string | null;
|
|
87
|
+
}
|
|
88
|
+
export interface DeskObservation {
|
|
89
|
+
png: Buffer;
|
|
90
|
+
width: number;
|
|
91
|
+
height: number;
|
|
92
|
+
a11y: A11yNode | null;
|
|
93
|
+
windows: readonly WindowInfo[];
|
|
94
|
+
focused: WindowInfo | null;
|
|
95
|
+
}
|
|
96
|
+
export interface DeskFrame {
|
|
97
|
+
seq: number;
|
|
98
|
+
width: number;
|
|
99
|
+
height: number;
|
|
100
|
+
data: Buffer;
|
|
101
|
+
/**
|
|
102
|
+
* How `data` is encoded. A consumer must not assume PNG — it builds the
|
|
103
|
+
* media type it hands on (a `data:` URL, a decoder) from this.
|
|
104
|
+
*/
|
|
105
|
+
format: DeskFrameFormat;
|
|
106
|
+
at: string;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* One unit of the live video stream: the init segment, or one media fragment.
|
|
110
|
+
*
|
|
111
|
+
* Video is deliberately NOT modelled as a kind of frame. A frame is a picture
|
|
112
|
+
* that stands alone; a video chunk is meaningless without the init segment
|
|
113
|
+
* before it and, for a media chunk, without the keyframe its group of pictures
|
|
114
|
+
* began at. Pretending otherwise produces a consumer that appends bytes in any
|
|
115
|
+
* order and renders black with no error to explain it.
|
|
116
|
+
*/
|
|
117
|
+
export interface DeskVideoChunk {
|
|
118
|
+
seq: number;
|
|
119
|
+
kind: DeskVideoChunkKind;
|
|
120
|
+
/** RFC 6381, e.g. `avc1.42C020`. What a MediaSource must be told. */
|
|
121
|
+
codec: string;
|
|
122
|
+
width: number;
|
|
123
|
+
height: number;
|
|
124
|
+
/** True when a `media` chunk begins at a sync sample. Always false on `init`. */
|
|
125
|
+
keyframe: boolean;
|
|
126
|
+
data: Buffer;
|
|
127
|
+
at: string;
|
|
128
|
+
}
|
|
129
|
+
export interface DeskScreenInput {
|
|
130
|
+
move(x: number, y: number): Promise<void>;
|
|
131
|
+
click(x: number, y: number, button?: DeskPointerButton): Promise<void>;
|
|
132
|
+
type(text: string): Promise<void>;
|
|
133
|
+
key(combo: string): Promise<void>;
|
|
134
|
+
scroll(x: number, y: number, dx: number, dy: number): Promise<void>;
|
|
135
|
+
drag(from: DeskPoint, to: DeskPoint): Promise<void>;
|
|
136
|
+
}
|
|
137
|
+
export interface DeskScreenHandle {
|
|
138
|
+
observe(): Promise<DeskObservation>;
|
|
139
|
+
/**
|
|
140
|
+
* Coordinate contract: all coordinates are in the pixel space of the most
|
|
141
|
+
* recent view of the screen — an `observe()`, a frame from `frames()`, or a
|
|
142
|
+
* chunk from `video()` — one to one. All three anchor it, because the caller
|
|
143
|
+
* aims at whatever it was last shown, and a live viewer is shown a stream
|
|
144
|
+
* rather than an observation. That holds whatever the encoding is: a lossy
|
|
145
|
+
* frame and an H.264 fragment are both true-size views of the screen, so they
|
|
146
|
+
* anchor exactly as a lossless PNG does. Input before any view, or outside
|
|
147
|
+
* its bounds, throws.
|
|
148
|
+
*/
|
|
149
|
+
input: DeskScreenInput;
|
|
150
|
+
a11y: {
|
|
151
|
+
invoke(nodeId: string, action: string): Promise<void>;
|
|
152
|
+
};
|
|
153
|
+
launch(appId: string, args?: readonly string[]): Promise<void>;
|
|
154
|
+
clipboard: {
|
|
155
|
+
read(): Promise<string>;
|
|
156
|
+
write(text: string): Promise<void>;
|
|
157
|
+
};
|
|
158
|
+
/**
|
|
159
|
+
* The live frame stream. `format` picks how the guest encodes each frame and
|
|
160
|
+
* is a bytes-for-fidelity trade: `jpeg` is roughly an order of magnitude
|
|
161
|
+
* smaller per frame, which is what a human driving the screen needs, while
|
|
162
|
+
* `png` stays exact. Omitted leaves the choice to the guest. `observe()` is
|
|
163
|
+
* unaffected and always lossless.
|
|
164
|
+
*
|
|
165
|
+
* The stream also ends when the desk reconnects to its guest agent: the
|
|
166
|
+
* capture behind it did not survive, so the iterator completes rather than
|
|
167
|
+
* waiting on frames that are never coming. Start another one to resume.
|
|
168
|
+
*/
|
|
169
|
+
frames(options?: {
|
|
170
|
+
fps?: number;
|
|
171
|
+
width?: number;
|
|
172
|
+
height?: number;
|
|
173
|
+
format?: DeskFrameFormat;
|
|
174
|
+
}): AsyncIterable<DeskFrame>;
|
|
175
|
+
/**
|
|
176
|
+
* The live screen as encoded VIDEO rather than as a sequence of pictures.
|
|
177
|
+
*
|
|
178
|
+
* This is what a person driving the desk should watch. `frames()` ships a
|
|
179
|
+
* whole independent image per tick; a video codec ships the DIFFERENCE, and a
|
|
180
|
+
* desktop is mostly still — which is roughly two orders of magnitude fewer
|
|
181
|
+
* bytes on the transport between the guest and here, the one place the live
|
|
182
|
+
* view was actually bounded.
|
|
183
|
+
*
|
|
184
|
+
* The stream anchors the coordinate space exactly as `observe()` and
|
|
185
|
+
* `frames()` do, from the first chunk onwards, because a viewer driving by
|
|
186
|
+
* video is aiming at what the video showed it.
|
|
187
|
+
*
|
|
188
|
+
* Masked like frames: nothing is emitted while a handover is active. And
|
|
189
|
+
* ended like frames when the desk reconnects to its guest agent — a video
|
|
190
|
+
* consumer resumed on a fresh encoder, without its init segment and away
|
|
191
|
+
* from a keyframe, decodes nothing at all and says so nowhere.
|
|
192
|
+
*/
|
|
193
|
+
video(options?: {
|
|
194
|
+
fps?: number;
|
|
195
|
+
width?: number;
|
|
196
|
+
height?: number;
|
|
197
|
+
}): AsyncIterable<DeskVideoChunk>;
|
|
198
|
+
handover: {
|
|
199
|
+
begin(options: {
|
|
200
|
+
ttlMs: number;
|
|
201
|
+
scope?: DeskHandoverScope;
|
|
202
|
+
actor?: string;
|
|
203
|
+
}): Promise<{
|
|
204
|
+
url: string;
|
|
205
|
+
}>;
|
|
206
|
+
end(): Promise<void>;
|
|
207
|
+
readonly active: boolean;
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
export interface DeskScreenService {
|
|
211
|
+
start(options: {
|
|
212
|
+
width: number;
|
|
213
|
+
height: number;
|
|
214
|
+
}): Promise<DeskScreenHandle>;
|
|
215
|
+
stop(): Promise<void>;
|
|
216
|
+
readonly running: boolean;
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* One leased desk.
|
|
220
|
+
*
|
|
221
|
+
* The handle stays valid across a lost guest connection: the host reconnects
|
|
222
|
+
* underneath it and later calls work, so a guest agent restart or a dropped
|
|
223
|
+
* vsock bridge no longer costs the rest of the lease. Two consequences a
|
|
224
|
+
* caller must actually handle:
|
|
225
|
+
*
|
|
226
|
+
* - A call that was IN FLIGHT when the connection dropped rejects with
|
|
227
|
+
* `DeskRequestFateUnknownError` — it may already have run in the guest.
|
|
228
|
+
* Nothing retries it for you.
|
|
229
|
+
* - A reconnect does not restore guest-side capture state. Live `frames()` and
|
|
230
|
+
* `video()` iterators END, and the coordinate anchor is dropped, so the
|
|
231
|
+
* caller must `observe()` (or take a new frame) before aiming again.
|
|
232
|
+
*
|
|
233
|
+
* When the connection cannot be recovered at all the desk is suspended, so the
|
|
234
|
+
* handle goes dead and `resume()` boots a fresh one — rather than the handle
|
|
235
|
+
* answering "no longer connected" for the rest of the lease.
|
|
236
|
+
*/
|
|
237
|
+
export interface DeskHandle {
|
|
238
|
+
readonly deskId: string;
|
|
239
|
+
renewLease(ms: number): void;
|
|
240
|
+
exec(options: DeskExecOptions & {
|
|
241
|
+
keepAlive: true;
|
|
242
|
+
}): Promise<DeskJob>;
|
|
243
|
+
exec(options: DeskExecOptions): Promise<DeskExecSnapshot>;
|
|
244
|
+
/** The keepAlive processes still running on this desk. */
|
|
245
|
+
jobs(): DeskJob[];
|
|
246
|
+
screen: DeskScreenService;
|
|
247
|
+
}
|
|
248
|
+
export interface DeskHostStats {
|
|
249
|
+
/** Desks currently resident (booted or booting) against the capacity cap. */
|
|
250
|
+
resident: number;
|
|
251
|
+
/** Starts waiting in the FIFO queue for capacity. */
|
|
252
|
+
queued: number;
|
|
253
|
+
capacity: number;
|
|
254
|
+
suspended: number;
|
|
255
|
+
lastStartedAt: string | null;
|
|
256
|
+
lastSuspendedAt: string | null;
|
|
257
|
+
/**
|
|
258
|
+
* Times a resident desk lost its guest connection mid-lease and got it back.
|
|
259
|
+
* One is a blip; a number that climbs is a desk FLAPPING — a guest agent
|
|
260
|
+
* restarting in a loop, a failing vsock bridge — and that is the difference
|
|
261
|
+
* between "the desk is slow" and a diagnosis.
|
|
262
|
+
*/
|
|
263
|
+
reconnects: number;
|
|
264
|
+
lastReconnectAt: string | null;
|
|
265
|
+
/** Which desk reconnected last: enough to go and look at the right one. */
|
|
266
|
+
lastReconnectDeskId: string | null;
|
|
267
|
+
lastError: string | null;
|
|
268
|
+
}
|
|
269
|
+
export interface DeskHost {
|
|
270
|
+
start(options: DeskStartOptions): Promise<DeskHandle>;
|
|
271
|
+
resume(deskId: string): Promise<DeskHandle>;
|
|
272
|
+
suspend(deskId: string): Promise<void>;
|
|
273
|
+
destroy(deskId: string): Promise<void>;
|
|
274
|
+
stats(): DeskHostStats;
|
|
275
|
+
/**
|
|
276
|
+
* Enforce every time-based rule now: expire handovers past their TTL and
|
|
277
|
+
* suspend desks past their lease or idle deadline. Returns the number of
|
|
278
|
+
* desks suspended. Timers call this automatically; tests with an injected
|
|
279
|
+
* clock call it directly.
|
|
280
|
+
*/
|
|
281
|
+
sweep(): Promise<number>;
|
|
282
|
+
}
|
|
283
|
+
export declare function createDeskHost(options: DeskHostOptions): DeskHost;
|
|
284
|
+
export interface VerifyDeskHostOptions {
|
|
285
|
+
kernelPath: string;
|
|
286
|
+
/** Initramfs for the probe VM; needed for a modular kernel, as at boot. */
|
|
287
|
+
initramfsPath?: string;
|
|
288
|
+
/**
|
|
289
|
+
* Kernel command line for the probe VM. It must match what real desks boot
|
|
290
|
+
* with: a probe that panics because its root is on another partition
|
|
291
|
+
* reports a host that cannot run desks, which is indistinguishable from a
|
|
292
|
+
* host that genuinely cannot.
|
|
293
|
+
*/
|
|
294
|
+
kernelCmdline?: string;
|
|
295
|
+
baseImagePath: string;
|
|
296
|
+
vmmPath?: string;
|
|
297
|
+
kvmPath?: string;
|
|
298
|
+
/** Where the throwaway probe overlay is created; defaults to the OS tmpdir. */
|
|
299
|
+
scratchDir?: string;
|
|
300
|
+
runtimeDir?: string;
|
|
301
|
+
backend?: DeskBackend;
|
|
302
|
+
launcherIdentity?: DeskLauncherIdentity;
|
|
303
|
+
platform?: NodeJS.Platform;
|
|
304
|
+
pathExists?: (path: string) => boolean;
|
|
305
|
+
deviceExists?: (path: string) => boolean;
|
|
306
|
+
idFactory?: () => string;
|
|
307
|
+
}
|
|
308
|
+
export interface DeskHostVerification {
|
|
309
|
+
/** Whether this host can serve desks: KVM present and the guest reachable. */
|
|
310
|
+
supported: boolean;
|
|
311
|
+
vmmPath: string;
|
|
312
|
+
kvm: boolean;
|
|
313
|
+
vsock: boolean;
|
|
314
|
+
virtioGpu: boolean;
|
|
315
|
+
}
|
|
316
|
+
/**
|
|
317
|
+
* Boot-time probe. Distinguishes a host that is unusable — wrong platform,
|
|
318
|
+
* missing VMM, missing images — which throws, from capabilities that are
|
|
319
|
+
* merely absent, which are reported as booleans: `kvm` (no boot possible
|
|
320
|
+
* without it), `vsock` (the guest agent answered), and `virtioGpu` (a screen
|
|
321
|
+
* would have hardware-backed rendering). It boots a throwaway microVM through
|
|
322
|
+
* the backend, asks it two questions, and discards it.
|
|
323
|
+
*/
|
|
324
|
+
export declare function verifyDeskHost(options: VerifyDeskHostOptions): Promise<DeskHostVerification>;
|
|
325
|
+
//# sourceMappingURL=host.d.ts.map
|
package/host.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"host.d.ts","sourceRoot":"","sources":["../src/host.ts"],"names":[],"mappings":"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"}
|