@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/README.md
ADDED
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
# @braedonsaunders/appkit-desk
|
|
2
|
+
|
|
3
|
+
Per-agent Debian machines in Cloud Hypervisor microVMs: a terminal, a
|
|
4
|
+
filesystem, arbitrary software, and — only when genuinely needed — a real
|
|
5
|
+
desktop, all on one machine with one identity.
|
|
6
|
+
|
|
7
|
+
A desk boots **headless** (a few hundred megabytes of RAM: kernel, filesystem,
|
|
8
|
+
network, the guest agent). The desktop environment is installed in the base
|
|
9
|
+
image but is not running. When work genuinely needs a screen, the compositor is
|
|
10
|
+
started *on the machine the agent is already using* as a service — one
|
|
11
|
+
filesystem, one machine, one event stream. This package owns the mechanism and
|
|
12
|
+
exposes ports; the consuming application supplies policy behind those ports and
|
|
13
|
+
owns the record. The package never touches a database and knows nothing about
|
|
14
|
+
tenants, employees, or approvals.
|
|
15
|
+
|
|
16
|
+
## Choosing a tier
|
|
17
|
+
|
|
18
|
+
The desk is the middle and top of a cost ladder. Reach for the cheapest tier
|
|
19
|
+
that does the job; the expensive tiers are strictly worse at tasks the cheap
|
|
20
|
+
tiers already cover.
|
|
21
|
+
|
|
22
|
+
| Tier | Surface | When | Cost |
|
|
23
|
+
| --- | --- | --- | --- |
|
|
24
|
+
| Document/tool abilities, in-process script sandboxes | No VM at all | Structured outputs, pure computation | Ordinary model calls |
|
|
25
|
+
| **Headless desk** (this package) | Shell, filesystem, background jobs, persistent disk | Real software, persistent state, downloads | A small resident VM |
|
|
26
|
+
| **Desk with a screen open** | GUI apps, pixels + opportunistic accessibility tree | Software with no CLI, visual verification | ~1.2GB+ resident, a vision call per unassisted step |
|
|
27
|
+
|
|
28
|
+
Size the concurrency cap against screen-open desks, not headless ones.
|
|
29
|
+
|
|
30
|
+
## Booting a desk
|
|
31
|
+
|
|
32
|
+
```ts
|
|
33
|
+
import { createDeskHost, isDeskSupported } from '@braedonsaunders/appkit-desk'
|
|
34
|
+
|
|
35
|
+
if (!isDeskSupported()) {
|
|
36
|
+
// Fail closed: no KVM or no Cloud Hypervisor means no desk ability at all,
|
|
37
|
+
// not a degraded one.
|
|
38
|
+
throw new Error('This host cannot run desks.')
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const host = createDeskHost({
|
|
42
|
+
imageRoot: '/data/agent-disks',
|
|
43
|
+
capacity: 8,
|
|
44
|
+
idleSuspendMs: 5 * 60_000,
|
|
45
|
+
ports: {
|
|
46
|
+
policy: { allowExec: ({ command }) => policyFor(command) },
|
|
47
|
+
onEvent: (event) => ledger.append(event), // the typed desk event union
|
|
48
|
+
audit: (entry) => auditLog.append(entry), // handover boundaries only
|
|
49
|
+
},
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
const desk = await host.start({
|
|
53
|
+
deskId: 'agent-7',
|
|
54
|
+
baseImage: '/data/agent-disks/base.img',
|
|
55
|
+
overlayPath: '/data/agent-disks/overlays/agent-7.img',
|
|
56
|
+
memoryMb: 384,
|
|
57
|
+
vcpus: 2,
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
const result = await desk.exec({ command: '/usr/bin/git', args: ['clone', repo] })
|
|
61
|
+
const job = await desk.exec({ command: '/usr/bin/serve', keepAlive: true }) // dies with the lease
|
|
62
|
+
|
|
63
|
+
const screen = await desk.screen.start({ width: 1280, height: 900 })
|
|
64
|
+
const { png, a11y, focused } = await screen.observe() // lossless PNG, on demand
|
|
65
|
+
await screen.input.click(640, 320)
|
|
66
|
+
|
|
67
|
+
// The live view is a different job: H.264, for a human driving the screen.
|
|
68
|
+
for await (const chunk of screen.video({ fps: 30 })) {
|
|
69
|
+
send(chunk) // init segment first, then one unit per media fragment
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
await desk.screen.stop() // back to headless; the machine keeps running
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
There are three ways to see the screen and they take different trades.
|
|
76
|
+
|
|
77
|
+
- **`observe()`** is what a model looks at: a lossless PNG plus windows and the
|
|
78
|
+
accessibility tree, on demand and infrequent.
|
|
79
|
+
- **`video()`** is what a person drives by. A video codec ships the difference
|
|
80
|
+
between pictures and a desktop is mostly still, so it costs one to two orders
|
|
81
|
+
of magnitude fewer bytes than the same screen as stills — and bytes between
|
|
82
|
+
the guest and the host are what actually bounds a live view. Chunks are
|
|
83
|
+
ordered: the `init` segment first, then `media` fragments, resumable only at
|
|
84
|
+
one whose `keyframe` is true.
|
|
85
|
+
- **`frames()`** is for a consumer that needs whole pictures — one feeding an
|
|
86
|
+
encoder of its own, or one that cannot decode H.264. `format` picks `jpeg`
|
|
87
|
+
(roughly a tenth the bytes) or `png` (exact).
|
|
88
|
+
|
|
89
|
+
All three are the screen's real size and never rescaled, so all three anchor the
|
|
90
|
+
coordinate space a click is aimed in. `video()` and `frames()` are both masked:
|
|
91
|
+
neither emits anything while a handover is active.
|
|
92
|
+
|
|
93
|
+
Every desk boots from one golden **raw** base image plus a per-desk
|
|
94
|
+
copy-on-write overlay, so patching the base patches every desk on its next boot
|
|
95
|
+
while agent installs and home directories persist. The overlay is a plain raw
|
|
96
|
+
file cloned from the base with `cp --reflink=auto` — an instant, block-sharing
|
|
97
|
+
CoW clone on XFS/Btrfs and a graceful full-copy fallback on ext4. It is
|
|
98
|
+
deliberately *not* a qcow2 backing overlay: Cloud Hypervisor cannot follow disk
|
|
99
|
+
backing chains and rejects them (`UnsupportedFeature` /
|
|
100
|
+
`MaxNestingDepthExceeded`), so the disk is passed as `image_type=raw` with no
|
|
101
|
+
chain to follow. `buildDeskLaunchPlan` produces the entire invocation — VMM
|
|
102
|
+
argv, overlay-creation step, vsock socket path, TAP device and MAC — as
|
|
103
|
+
inspectable data before anything is spawned, and it fails closed: a missing
|
|
104
|
+
`/dev/kvm`, VMM binary, kernel, base image, or overlay directory throws rather
|
|
105
|
+
than producing a plan that cannot boot.
|
|
106
|
+
|
|
107
|
+
## Leases, idle suspend, and the queue
|
|
108
|
+
|
|
109
|
+
A desk is resident under a lease. `renewLease(ms)` extends it; activity defers
|
|
110
|
+
the idle timer; a desk past its lease or idle deadline is suspended — the VM
|
|
111
|
+
stops, the disk persists, applications cold-start on `resume`. Keep-alive jobs
|
|
112
|
+
die with the lease, and their termination is recorded as `job_exit` so no
|
|
113
|
+
process ever runs where an operator cannot see it.
|
|
114
|
+
|
|
115
|
+
Residency is bounded by a hard capacity cap. Starts beyond the cap queue FIFO
|
|
116
|
+
rather than overcommitting host memory. `host.stats()` reports
|
|
117
|
+
`{ resident, queued, capacity, suspended, lastStartedAt, lastSuspendedAt,
|
|
118
|
+
reconnects, lastReconnectAt, lastReconnectDeskId, lastError }`; queue depth is
|
|
119
|
+
worth alerting on, and so is a `reconnects` count that climbs. With an injected
|
|
120
|
+
`now`, tests drive all of this deterministically through `host.sweep()`.
|
|
121
|
+
|
|
122
|
+
## A connection is re-established, not assumed
|
|
123
|
+
|
|
124
|
+
The vsock channel to a guest is not a fact learned once at boot. It can drop
|
|
125
|
+
mid-lease for reasons that say nothing about whether the desk is usable — the
|
|
126
|
+
guest agent restarts, the bridge drops, the guest wedges for a moment — and
|
|
127
|
+
treating that as terminal stranded the desk for the rest of its lease with a
|
|
128
|
+
healthy guest behind it. So the backend reconnects: same retry path, same
|
|
129
|
+
`confirmGuest` ping, bounded window and backoff. It never reconnects after
|
|
130
|
+
`shutdown()` or once the VMM has exited, because then the desk genuinely needs
|
|
131
|
+
a fresh boot; when the window runs out the host suspends the desk so `resume()`
|
|
132
|
+
boots a new one instead of handing back a dead handle.
|
|
133
|
+
|
|
134
|
+
Two things a caller must handle, because pretending otherwise would be a lie:
|
|
135
|
+
a request that was **in flight** when the channel dropped rejects with
|
|
136
|
+
`DeskRequestFateUnknownError` — the guest may already have run it, and silently
|
|
137
|
+
replaying an `exec` that sent mail is the wrong kind of resilience. And the
|
|
138
|
+
guest's own capture state does not survive its agent restarting, so live
|
|
139
|
+
`frames()`/`video()` iterators **end** on a reconnect and the coordinate anchor
|
|
140
|
+
is cleared; observe (or take a frame) again before aiming.
|
|
141
|
+
|
|
142
|
+
## The coordinate contract
|
|
143
|
+
|
|
144
|
+
**Input coordinates are in the pixel space of the most recent `observe()`,
|
|
145
|
+
one to one.** Any scaling applied on the way out must be undone on the way in.
|
|
146
|
+
The package enforces this rather than documenting it and hoping: coordinate
|
|
147
|
+
input before the first `observe()`, or outside its bounds, throws. Getting
|
|
148
|
+
this wrong makes every click land slightly off in a way that looks like model
|
|
149
|
+
failure and is very hard to diagnose.
|
|
150
|
+
|
|
151
|
+
Perception is pixels-primary: `observe()` always returns a PNG, and
|
|
152
|
+
opportunistically includes the focused application's AT-SPI accessibility tree
|
|
153
|
+
(`a11y`) when one is exposed. `screen.a11y.invoke(nodeId, action)` targets by
|
|
154
|
+
role and name when a tree exists; the pixel path always works.
|
|
155
|
+
|
|
156
|
+
## The handover masking contract
|
|
157
|
+
|
|
158
|
+
`screen.handover.begin({ ttlMs, scope })` returns a URL through which a human
|
|
159
|
+
can view or control the screen — for example to complete a login the agent
|
|
160
|
+
cannot. The masking rules are load-bearing:
|
|
161
|
+
|
|
162
|
+
- While a handover is active, **input events never reach the `onEvent`
|
|
163
|
+
recording port** — no `click`, `type`, `key`, `scroll`, `drag`,
|
|
164
|
+
`window_focus`, or `app_launch` is emitted, even though the input itself is
|
|
165
|
+
faithfully forwarded to the guest.
|
|
166
|
+
- **Frames are not emitted** to `frames()` consumers for the duration.
|
|
167
|
+
- **`clipboard.read()` is refused** during a handover, so a freshly typed
|
|
168
|
+
credential cannot ride the clipboard into the agent's context.
|
|
169
|
+
- Only `handover_begin` and `handover_end` cross the boundary, carrying actor,
|
|
170
|
+
scope, and duration — never content. Both reach `onEvent` (for the ledger)
|
|
171
|
+
and the `audit` port.
|
|
172
|
+
|
|
173
|
+
A handover ends explicitly, at its TTL (`reason: 'expired'`), or when the desk
|
|
174
|
+
is suspended (`reason: 'revoked'`). The failure this prevents is concrete:
|
|
175
|
+
keystrokes typed by a human during a credential handover leaking into an
|
|
176
|
+
append-only record that cannot be edited afterward.
|
|
177
|
+
|
|
178
|
+
## The event union
|
|
179
|
+
|
|
180
|
+
`onEvent` receives a closed union — `shell_command`, `app_launch`, `click`,
|
|
181
|
+
`type`, `key`, `scroll`, `drag`, `window_focus`, `screen_open`,
|
|
182
|
+
`screen_close`, `handover_begin`, `handover_end`, `job_start`, `job_exit` —
|
|
183
|
+
each stamped with `deskId` and an ISO timestamp. The union is defined here so
|
|
184
|
+
the consumer's ledger and this package agree on the taxonomy; persistence is
|
|
185
|
+
entirely the consumer's concern.
|
|
186
|
+
|
|
187
|
+
## The backend port and the guest agent
|
|
188
|
+
|
|
189
|
+
`DeskBackend` is the seam that keeps everything testable without a hypervisor:
|
|
190
|
+
`boot(plan)` returns a `DeskMachine` — a request/response channel to the
|
|
191
|
+
in-guest agent plus an event subscription. The default is
|
|
192
|
+
`cloudHypervisorBackend`, which creates the overlay, spawns Cloud Hypervisor
|
|
193
|
+
with the plan's argv, performs the vsock `CONNECT` handshake, and speaks the
|
|
194
|
+
framed protocol. Its process-spawning glue is thin on purpose; the launcher
|
|
195
|
+
and the socket transport are injectable, and CI substitutes in-memory fakes.
|
|
196
|
+
|
|
197
|
+
The wire protocol — length-prefixed JSON frames with bounded sizes, strict
|
|
198
|
+
field validation, and a closed operation set — is pure code in `protocol.ts`,
|
|
199
|
+
shared by both ends. The in-guest agent (`guest-agent.ts`) is the
|
|
200
|
+
security-critical piece: it is the only new attack surface in the design, so
|
|
201
|
+
it is small enough to read in one sitting, does no parsing it does not need,
|
|
202
|
+
dispatches through a closed switch, and treats any framing violation as fatal
|
|
203
|
+
to the connection. Its message-handling core is pure and hard-tested; contact
|
|
204
|
+
with the guest OS is injected as handlers by the guest's init glue.
|
|
205
|
+
|
|
206
|
+
`verifyDeskHost()` belongs in service startup: it boots a throwaway microVM
|
|
207
|
+
through the backend and distinguishes a host that is unusable (wrong platform,
|
|
208
|
+
missing VMM or images — throws) from capabilities that are merely absent,
|
|
209
|
+
reported as booleans: `kvm`, `vsock`, and `virtioGpu`.
|
|
210
|
+
|
|
211
|
+
Every additional provider runs `verifyDeskBackendConformance()` against a
|
|
212
|
+
disposable launch plan. The shared check verifies request/capability round trips,
|
|
213
|
+
idempotent terminal shutdown, and refusal of work after shutdown. Provider test
|
|
214
|
+
suites extend this contract; they do not restate it.
|
|
215
|
+
|
|
216
|
+
`exportPortableDeskHome()` and `importPortableDeskHome()` are the migration seam
|
|
217
|
+
between providers. Sources expose entries and file bytes; exports produce a
|
|
218
|
+
bounded, sorted, content-addressed manifest preserving directories, files,
|
|
219
|
+
safe relative symlinks, modes, and timestamps. Imports verify every SHA-256,
|
|
220
|
+
path, symlink target, size, and aggregate limit before opening a sink, then
|
|
221
|
+
stage the complete home and make it authoritative with one `commit()`. Any
|
|
222
|
+
failure calls `rollback()`. This moves an agent's working home, not an operating
|
|
223
|
+
system image: installed packages and provider-specific machine state remain
|
|
224
|
+
the provider's concern.
|
|
225
|
+
|
|
226
|
+
`createDeskFrameDeduplicator()` assigns exact SHA-256 identities to observations
|
|
227
|
+
without retaining their bytes. A repeated identity can reuse the prior audit
|
|
228
|
+
frame and omit another model image; reset it whenever the capture session or
|
|
229
|
+
masking boundary changes.
|
|
230
|
+
|
|
231
|
+
Do not fall back to unconfined execution when this package reports an
|
|
232
|
+
unsupported host. A deployment without KVM loses the desk ability entirely —
|
|
233
|
+
the same fail-closed posture as the rest of AppKit.
|
package/backend.d.ts
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The backend port and its default Cloud Hypervisor implementation.
|
|
3
|
+
*
|
|
4
|
+
* `DeskBackend` is the seam that keeps the package testable without a
|
|
5
|
+
* hypervisor: the host asks a backend to boot a `DeskLaunchPlan` and gets a
|
|
6
|
+
* `DeskMachine` — a request/response channel to the in-guest agent plus an
|
|
7
|
+
* event subscription. CI and non-KVM developer machines inject an in-memory
|
|
8
|
+
* fake; production uses `cloudHypervisorBackend`, whose process-spawning glue
|
|
9
|
+
* is deliberately thin because everything that can be pure lives in the plan
|
|
10
|
+
* builder and the protocol module.
|
|
11
|
+
*/
|
|
12
|
+
import { type ChildProcess } from 'node:child_process';
|
|
13
|
+
import type { Duplex } from 'node:stream';
|
|
14
|
+
import { DeskError, type DeskLaunchPlan, type DeskLauncherIdentity } from './plan.js';
|
|
15
|
+
import { type GuestCommand, type GuestEventMessage } from './protocol.js';
|
|
16
|
+
/**
|
|
17
|
+
* A request that was in flight when the guest connection dropped.
|
|
18
|
+
*
|
|
19
|
+
* Distinct from a plain `DeskError` because the caller's next move is
|
|
20
|
+
* different: this request may ALREADY HAVE RUN. The frame reached the guest,
|
|
21
|
+
* and whether the guest executed it before the channel went away is not
|
|
22
|
+
* knowable from the host. An `exec` may have sent mail; a `click` may have
|
|
23
|
+
* landed. So the transport refuses to guess — it names the ambiguity and lets
|
|
24
|
+
* the caller decide whether the operation is safe to repeat. Anything that
|
|
25
|
+
* rejects with a plain `DeskError` instead never reached the guest at all.
|
|
26
|
+
*/
|
|
27
|
+
export declare class DeskRequestFateUnknownError extends DeskError {
|
|
28
|
+
readonly name: string;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* What happened to the transport underneath a machine.
|
|
32
|
+
*
|
|
33
|
+
* `reconnected` means the host lost the guest channel mid-lease and got it
|
|
34
|
+
* back; the desk is usable again. It is NOT a promise that the guest is
|
|
35
|
+
* unchanged — see the note on `onConnectionChange`. `lost` means it will not
|
|
36
|
+
* come back on this VM and the desk needs a fresh boot.
|
|
37
|
+
*/
|
|
38
|
+
export type DeskConnectionChange = {
|
|
39
|
+
state: 'reconnected';
|
|
40
|
+
deskId: string;
|
|
41
|
+
/** How the previous connection went away. */
|
|
42
|
+
reason: string;
|
|
43
|
+
/** Wall-clock time the desk spent with no channel to its guest. */
|
|
44
|
+
downtimeMs: number;
|
|
45
|
+
/** Reconnects this machine has survived, including this one. */
|
|
46
|
+
reconnects: number;
|
|
47
|
+
} | {
|
|
48
|
+
state: 'lost';
|
|
49
|
+
deskId: string;
|
|
50
|
+
/** Why it is gone for good: the VMM exited, or the window ran out. */
|
|
51
|
+
reason: string;
|
|
52
|
+
};
|
|
53
|
+
/** A booted microVM as the host sees it: one desk, one guest-agent channel. */
|
|
54
|
+
export interface DeskMachine {
|
|
55
|
+
readonly deskId: string;
|
|
56
|
+
/**
|
|
57
|
+
* Send one request to the in-guest agent and await its correlated result.
|
|
58
|
+
*
|
|
59
|
+
* Rejects with `DeskError` when the guest reports an error, the request
|
|
60
|
+
* times out, or the connection is gone for good — in that last case the
|
|
61
|
+
* request never left the host, so repeating it is safe.
|
|
62
|
+
*
|
|
63
|
+
* A connection lost mid-lease is re-established rather than fatal: a call
|
|
64
|
+
* that arrives while that is happening WAITS for it instead of failing.
|
|
65
|
+
* A call that was already in flight when the channel dropped rejects with
|
|
66
|
+
* `DeskRequestFateUnknownError`, because the host cannot tell whether the
|
|
67
|
+
* guest ran it.
|
|
68
|
+
*/
|
|
69
|
+
request(command: GuestCommand, options?: {
|
|
70
|
+
timeoutMs?: number;
|
|
71
|
+
}): Promise<unknown>;
|
|
72
|
+
/**
|
|
73
|
+
* Subscribe to unsolicited guest events (job exits, frames, focus changes).
|
|
74
|
+
*
|
|
75
|
+
* Subscriptions belong to the machine, not to the socket under it, so they
|
|
76
|
+
* survive a reconnect. What does NOT survive is the guest's own capture
|
|
77
|
+
* state: a guest agent that restarted has no screen, no frame encoder and
|
|
78
|
+
* no video encoder, and a subscriber will simply stop receiving. Watch
|
|
79
|
+
* `onConnectionChange` to notice.
|
|
80
|
+
*/
|
|
81
|
+
subscribe(listener: (event: GuestEventMessage) => void): () => void;
|
|
82
|
+
/**
|
|
83
|
+
* Watch the transport under this machine.
|
|
84
|
+
*
|
|
85
|
+
* Optional: a backend whose channel cannot drop has nothing to report. The
|
|
86
|
+
* default backend reports every reconnect, because a reconnect that leaves
|
|
87
|
+
* no trace turns a guest agent restarting every few minutes into a desk that
|
|
88
|
+
* misbehaves for reasons nobody can find.
|
|
89
|
+
*/
|
|
90
|
+
onConnectionChange?(listener: (change: DeskConnectionChange) => void): () => void;
|
|
91
|
+
/** Stop the VM. Idempotent; resolves once the VMM process has exited. */
|
|
92
|
+
shutdown(): Promise<void>;
|
|
93
|
+
}
|
|
94
|
+
export interface DeskBackend {
|
|
95
|
+
boot(plan: DeskLaunchPlan): Promise<DeskMachine>;
|
|
96
|
+
}
|
|
97
|
+
export type DeskProcessLauncher = (command: string, args: readonly string[], options: {
|
|
98
|
+
identity?: DeskLauncherIdentity;
|
|
99
|
+
}) => ChildProcess;
|
|
100
|
+
export interface CloudHypervisorBackendOptions {
|
|
101
|
+
/** Spawns the VMM and image tooling; injectable for tests. */
|
|
102
|
+
launcher?: DeskProcessLauncher;
|
|
103
|
+
/** Opens the host side of the vsock Unix socket; injectable for tests. */
|
|
104
|
+
connect?: (socketPath: string) => Duplex;
|
|
105
|
+
platform?: NodeJS.Platform;
|
|
106
|
+
/**
|
|
107
|
+
* Clock behind every connect and reconnect deadline. Injectable for the same
|
|
108
|
+
* reason `DeskHostOptions.now` is: a test drives a window to its end by
|
|
109
|
+
* moving the clock, rather than sleeping for it and hoping the machine it
|
|
110
|
+
* runs on is fast enough.
|
|
111
|
+
*/
|
|
112
|
+
now?: () => number;
|
|
113
|
+
idFactory?: () => string;
|
|
114
|
+
requestTimeoutMs?: number;
|
|
115
|
+
connectTimeoutMs?: number;
|
|
116
|
+
connectRetryDelayMs?: number;
|
|
117
|
+
/** Bound on a single handshake attempt; see attemptHandshake. */
|
|
118
|
+
handshakeTimeoutMs?: number;
|
|
119
|
+
/**
|
|
120
|
+
* How long a machine keeps trying to get its guest channel back after an
|
|
121
|
+
* unexpected drop before declaring the desk lost. Bounded on purpose: the
|
|
122
|
+
* VMM is still alive so a reconnect is cheap and usually fast, but retrying
|
|
123
|
+
* forever against a guest that is never coming back is worse than failing.
|
|
124
|
+
*/
|
|
125
|
+
reconnectWindowMs?: number;
|
|
126
|
+
/**
|
|
127
|
+
* First delay between reconnect attempts. It doubles toward a ceiling, so
|
|
128
|
+
* this is the fast first poll rather than the steady rate. Separate from
|
|
129
|
+
* `connectRetryDelayMs`, which is sized for a guest that is still cold
|
|
130
|
+
* booting; here the guest was up a moment ago.
|
|
131
|
+
*/
|
|
132
|
+
reconnectRetryDelayMs?: number;
|
|
133
|
+
killGraceMs?: number;
|
|
134
|
+
guestAgentPort?: number;
|
|
135
|
+
}
|
|
136
|
+
export declare function createCloudHypervisorBackend(options?: CloudHypervisorBackendOptions): DeskBackend;
|
|
137
|
+
/** The default backend: real Cloud Hypervisor over a real vsock socket. */
|
|
138
|
+
export declare const cloudHypervisorBackend: DeskBackend;
|
|
139
|
+
//# sourceMappingURL=backend.d.ts.map
|
package/backend.d.ts.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"backend.d.ts","sourceRoot":"","sources":["../src/backend.ts"],"names":[],"mappings":"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"}
|