{"_id":"@braedonsaunders/appkit-desk","_rev":"4-85f0b998b6fd0d19898219fd51666c52","name":"@braedonsaunders/appkit-desk","dist-tags":{"latest":"0.6.0"},"versions":{"0.4.0":{"name":"@braedonsaunders/appkit-desk","version":"0.4.0","keywords":["agents","appkit","application-framework","cloud-hypervisor","desktop","microvm","sandbox","typescript","virtualization"],"author":{"name":"Braedon Saunders"},"license":"AGPL-3.0-or-later","_id":"@braedonsaunders/appkit-desk@0.4.0","maintainers":[{"name":"braedonsaunders","email":"bsaunders@rassaun.com"}],"homepage":"https://github.com/braedonsaunders/appkit/tree/main/packages/desk#readme","bugs":{"url":"https://github.com/braedonsaunders/appkit/issues"},"dist":{"shasum":"a6d5b4bd27c7ef3f2977db78092343fd25824399","tarball":"https://registry.npmjs.org/@braedonsaunders/appkit-desk/-/appkit-desk-0.4.0.tgz","fileCount":43,"integrity":"sha512-6XP/oCVAdIfaM1CXYc2iXK903sWzB4vSIoq7864vgdK3J1COY8+X3+CQhccUChTwSV8arOs4e3QGmfOoZyyTjg==","signatures":[{"sig":"MEQCIByjFg/yUKLXbvU8Mxc5hohb2y2VbrhNjvwxAPpD8DGkAiAY6BKFkWiDYMheuebdmVpwoQnic9ZsviIac8LfEnQDuA==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@braedonsaunders%2fappkit-desk@0.4.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":476315},"main":"./index.js","type":"module","_from":"file:braedonsaunders-appkit-desk-0.4.0.tgz","types":"./index.d.ts","engines":{"node":">=22"},"exports":{".":{"types":"./index.d.ts","import":"./index.js","default":"./index.js"},"./package.json":"./package.json"},"_npmUser":{"name":"braedonsaunders","email":"bsaunders@rassaun.com"},"_resolved":"/tmp/a11030f841b9d6198224544bfc8685a3/braedonsaunders-appkit-desk-0.4.0.tgz","_integrity":"sha512-6XP/oCVAdIfaM1CXYc2iXK903sWzB4vSIoq7864vgdK3J1COY8+X3+CQhccUChTwSV8arOs4e3QGmfOoZyyTjg==","repository":{"url":"git+https://github.com/braedonsaunders/appkit.git","type":"git","directory":"packages/desk"},"_npmVersion":"10.9.8","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 recordi","directories":{},"_nodeVersion":"22.23.2","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/appkit-desk_0.4.0_1787007449043_0.21298232682856932","host":"s3://npm-registry-packages-npm-production"}},"0.5.0":{"name":"@braedonsaunders/appkit-desk","version":"0.5.0","keywords":["agents","appkit","application-framework","cloud-hypervisor","desktop","microvm","sandbox","typescript","virtualization"],"author":{"name":"Braedon Saunders"},"license":"AGPL-3.0-or-later","_id":"@braedonsaunders/appkit-desk@0.5.0","maintainers":[{"name":"braedonsaunders","email":"bsaunders@rassaun.com"}],"homepage":"https://github.com/braedonsaunders/appkit/tree/main/packages/desk#readme","bugs":{"url":"https://github.com/braedonsaunders/appkit/issues"},"dist":{"shasum":"4e96145e7197046a5eb4c9e3158fe0ee4dafa3fa","tarball":"https://registry.npmjs.org/@braedonsaunders/appkit-desk/-/appkit-desk-0.5.0.tgz","fileCount":43,"integrity":"sha512-4g3Hrdg3mSUC39Lmu8HaDE6rsSMjC38s3WuNlKpfQ4KXYeTgpyOA/1eHaJisTmtq69E0GKtzDbKqQ6XAwS+khA==","signatures":[{"sig":"MEUCIEwASnq4GCpE9byEFHTa/brltDsJfqgiGeUvWmtdhUbaAiEAgikKY8z/w5L1YQ5B5tvcTUwuhJapx4r5OWgLATuaqhg=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@braedonsaunders%2fappkit-desk@0.5.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":513873},"main":"./index.js","type":"module","_from":"file:braedonsaunders-appkit-desk-0.5.0.tgz","types":"./index.d.ts","engines":{"node":">=22"},"exports":{".":{"types":"./index.d.ts","import":"./index.js","default":"./index.js"},"./package.json":"./package.json"},"_npmUser":{"name":"braedonsaunders","email":"bsaunders@rassaun.com"},"_resolved":"/tmp/6fdb40d7a274ff494b7c23c257be340a/braedonsaunders-appkit-desk-0.5.0.tgz","_integrity":"sha512-4g3Hrdg3mSUC39Lmu8HaDE6rsSMjC38s3WuNlKpfQ4KXYeTgpyOA/1eHaJisTmtq69E0GKtzDbKqQ6XAwS+khA==","repository":{"url":"git+https://github.com/braedonsaunders/appkit.git","type":"git","directory":"packages/desk"},"_npmVersion":"10.9.8","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 recordi","directories":{},"_nodeVersion":"22.23.2","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/appkit-desk_0.5.0_1787095105121_0.00977238807045544","host":"s3://npm-registry-packages-npm-production"}},"0.5.1":{"name":"@braedonsaunders/appkit-desk","version":"0.5.1","keywords":["agents","appkit","application-framework","cloud-hypervisor","desktop","microvm","sandbox","typescript","virtualization"],"author":{"name":"Braedon Saunders"},"license":"AGPL-3.0-or-later","_id":"@braedonsaunders/appkit-desk@0.5.1","maintainers":[{"name":"braedonsaunders","email":"bsaunders@rassaun.com"}],"homepage":"https://github.com/braedonsaunders/appkit/tree/main/packages/desk#readme","bugs":{"url":"https://github.com/braedonsaunders/appkit/issues"},"dist":{"shasum":"20fe3db030b188830c3711dd083fdd1684f0b575","tarball":"https://registry.npmjs.org/@braedonsaunders/appkit-desk/-/appkit-desk-0.5.1.tgz","fileCount":43,"integrity":"sha512-8hLYoo8c2zfWQrXsVtveEQ0D71bd0HTkNAColwIW5yyCmxuPD+CW4Rt+zlNgUJwuvGTgukrPd4b8omubV56Gjw==","signatures":[{"sig":"MEUCIEvgn6cSAR+ojgZcOtiN81yS9RRlUs41vJR7Bv6v9t1fAiEAx7pJPmlWZcb40n48A48u7Dt2E9DcHCwiwloaZ1InWPI=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@braedonsaunders%2fappkit-desk@0.5.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":518473},"main":"./index.js","type":"module","_from":"file:braedonsaunders-appkit-desk-0.5.1.tgz","types":"./index.d.ts","engines":{"node":">=22"},"exports":{".":{"types":"./index.d.ts","import":"./index.js","default":"./index.js"},"./package.json":"./package.json"},"_npmUser":{"name":"braedonsaunders","email":"bsaunders@rassaun.com"},"_resolved":"/tmp/b44c81d40abadcd7de6631f0bfccbd53/braedonsaunders-appkit-desk-0.5.1.tgz","_integrity":"sha512-8hLYoo8c2zfWQrXsVtveEQ0D71bd0HTkNAColwIW5yyCmxuPD+CW4Rt+zlNgUJwuvGTgukrPd4b8omubV56Gjw==","repository":{"url":"git+https://github.com/braedonsaunders/appkit.git","type":"git","directory":"packages/desk"},"_npmVersion":"10.9.8","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 recordi","directories":{},"_nodeVersion":"22.23.2","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/appkit-desk_0.5.1_1787097447892_0.10198765212233396","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"_id":"@braedonsaunders/appkit-desk@0.6.0","bugs":{"url":"https://github.com/braedonsaunders/appkit/issues"},"dist":{"shasum":"553876bcfae639309067fd0ab74c26e91bef105c","tarball":"https://registry.npmjs.org/@braedonsaunders/appkit-desk/-/appkit-desk-0.6.0.tgz","fileCount":43,"integrity":"sha512-uVQdoZiEku4q87HCuIUXPg+bSTfnhO4gcrbWTcKjEZ3zxPTFofEXARmc+a1hxrq0usBb21b77dFTpF1a8HTiDw==","signatures":[{"sig":"MEUCIBeP5aOTNvyAJZugKdDv8Qe1St6hTD6zbgzwnJSOYQrwAiEAhvplH0FG8YVtMjG5ioAqN3c8kSq30V39IhgPlRMY8NM=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDtGF4hR5WorA4JdcItFgTz8U0X0igjgSsYYrdYzWHt7wIhAMlgTfx1kdhQdiJcUgA0YUXL1DwvVvh+xOOuPrI71Mvb"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@braedonsaunders%2fappkit-desk@0.6.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":562225},"main":"./index.js","name":"@braedonsaunders/appkit-desk","type":"module","_from":"file:braedonsaunders-appkit-desk-0.6.0.tgz","types":"./index.d.ts","author":{"name":"Braedon Saunders"},"engines":{"node":">=22"},"exports":{".":{"types":"./index.d.ts","import":"./index.js","default":"./index.js"},"./package.json":"./package.json"},"license":"AGPL-3.0-or-later","version":"0.6.0","_npmUser":{"name":"braedonsaunders","email":"bsaunders@rassaun.com"},"homepage":"https://github.com/braedonsaunders/appkit/tree/main/packages/desk#readme","keywords":["agents","appkit","application-framework","cloud-hypervisor","desktop","microvm","sandbox","typescript","virtualization"],"_resolved":"/tmp/18de6c2802cbda40b2d8a54d4ef7808d/braedonsaunders-appkit-desk-0.6.0.tgz","_integrity":"sha512-uVQdoZiEku4q87HCuIUXPg+bSTfnhO4gcrbWTcKjEZ3zxPTFofEXARmc+a1hxrq0usBb21b77dFTpF1a8HTiDw==","repository":{"url":"git+https://github.com/braedonsaunders/appkit.git","type":"git","directory":"packages/desk"},"_npmVersion":"10.9.8","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 recordi","directories":{},"maintainers":[{"name":"braedonsaunders","email":"bsaunders@rassaun.com"}],"_nodeVersion":"22.23.2","_hasShrinkwrap":false,"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/appkit-desk_0.6.0_1789136184337_0.8005760494880345"}}},"time":{"created":"2026-08-17T22:57:28.904Z","modified":"2026-09-11T14:16:24.845Z","0.4.0":"2026-08-17T22:57:29.187Z","0.5.0":"2026-08-18T23:18:25.288Z","0.5.1":"2026-08-18T23:57:28.059Z","0.6.0":"2026-09-11T14:16:24.448Z"},"bugs":{"url":"https://github.com/braedonsaunders/appkit/issues"},"author":{"name":"Braedon Saunders"},"license":"AGPL-3.0-or-later","homepage":"https://github.com/braedonsaunders/appkit/tree/main/packages/desk#readme","keywords":["agents","appkit","application-framework","cloud-hypervisor","desktop","microvm","sandbox","typescript","virtualization"],"repository":{"url":"git+https://github.com/braedonsaunders/appkit.git","type":"git","directory":"packages/desk"},"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 recordi","maintainers":[{"name":"braedonsaunders","email":"bsaunders@rassaun.com"}],"readme":"# @braedonsaunders/appkit-desk\n\nPer-agent Debian machines in Cloud Hypervisor microVMs: a terminal, a\nfilesystem, arbitrary software, and — only when genuinely needed — a real\ndesktop, all on one machine with one identity.\n\nA desk boots **headless** (a few hundred megabytes of RAM: kernel, filesystem,\nnetwork, the guest agent). The desktop environment is installed in the base\nimage but is not running. When work genuinely needs a screen, the compositor is\nstarted *on the machine the agent is already using* as a service — one\nfilesystem, one machine, one event stream. This package owns the mechanism and\nexposes ports; the consuming application supplies policy behind those ports and\nowns the record. The package never touches a database and knows nothing about\ntenants, employees, or approvals.\n\n## Choosing a tier\n\nThe desk is the middle and top of a cost ladder. Reach for the cheapest tier\nthat does the job; the expensive tiers are strictly worse at tasks the cheap\ntiers already cover.\n\n| Tier | Surface | When | Cost |\n| --- | --- | --- | --- |\n| Document/tool abilities, in-process script sandboxes | No VM at all | Structured outputs, pure computation | Ordinary model calls |\n| **Headless desk** (this package) | Shell, filesystem, background jobs, persistent disk | Real software, persistent state, downloads | A small resident VM |\n| **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 |\n\nSize the concurrency cap against screen-open desks, not headless ones.\n\n## Booting a desk\n\n```ts\nimport { createDeskHost, isDeskSupported } from '@braedonsaunders/appkit-desk'\n\nif (!isDeskSupported()) {\n  // Fail closed: no KVM or no Cloud Hypervisor means no desk ability at all,\n  // not a degraded one.\n  throw new Error('This host cannot run desks.')\n}\n\nconst host = createDeskHost({\n  imageRoot: '/data/agent-disks',\n  capacity: 8,\n  idleSuspendMs: 5 * 60_000,\n  ports: {\n    policy: { allowExec: ({ command }) => policyFor(command) },\n    onEvent: (event) => ledger.append(event),   // the typed desk event union\n    audit: (entry) => auditLog.append(entry),   // handover boundaries only\n  },\n})\n\nconst desk = await host.start({\n  deskId: 'agent-7',\n  baseImage: '/data/agent-disks/base.img',\n  overlayPath: '/data/agent-disks/overlays/agent-7.img',\n  memoryMb: 384,\n  vcpus: 2,\n})\n\nconst result = await desk.exec({ command: '/usr/bin/git', args: ['clone', repo] })\nconst job = await desk.exec({ command: '/usr/bin/serve', keepAlive: true }) // dies with the lease\n\nconst screen = await desk.screen.start({ width: 1280, height: 900 })\nconst { png, a11y, focused } = await screen.observe() // lossless PNG, on demand\nawait screen.input.click(640, 320)\n\n// The live view is a different job: H.264, for a human driving the screen.\nfor await (const chunk of screen.video({ fps: 30 })) {\n  send(chunk) // init segment first, then one unit per media fragment\n}\n\nawait desk.screen.stop() // back to headless; the machine keeps running\n```\n\nThere are three ways to see the screen and they take different trades.\n\n- **`observe()`** is what a model looks at: a lossless PNG plus windows and the\n  accessibility tree, on demand and infrequent.\n- **`video()`** is what a person drives by. A video codec ships the difference\n  between pictures and a desktop is mostly still, so it costs one to two orders\n  of magnitude fewer bytes than the same screen as stills — and bytes between\n  the guest and the host are what actually bounds a live view. Chunks are\n  ordered: the `init` segment first, then `media` fragments, resumable only at\n  one whose `keyframe` is true.\n- **`frames()`** is for a consumer that needs whole pictures — one feeding an\n  encoder of its own, or one that cannot decode H.264. `format` picks `jpeg`\n  (roughly a tenth the bytes) or `png` (exact).\n\nAll three are the screen's real size and never rescaled, so all three anchor the\ncoordinate space a click is aimed in. `video()` and `frames()` are both masked:\nneither emits anything while a handover is active.\n\nEvery desk boots from one golden **raw** base image plus a per-desk\ncopy-on-write overlay, so patching the base patches every desk on its next boot\nwhile agent installs and home directories persist. The overlay is a plain raw\nfile cloned from the base with `cp --reflink=auto` — an instant, block-sharing\nCoW clone on XFS/Btrfs and a graceful full-copy fallback on ext4. It is\ndeliberately *not* a qcow2 backing overlay: Cloud Hypervisor cannot follow disk\nbacking chains and rejects them (`UnsupportedFeature` /\n`MaxNestingDepthExceeded`), so the disk is passed as `image_type=raw` with no\nchain to follow. `buildDeskLaunchPlan` produces the entire invocation — VMM\nargv, overlay-creation step, vsock socket path, TAP device and MAC — as\ninspectable data before anything is spawned, and it fails closed: a missing\n`/dev/kvm`, VMM binary, kernel, base image, or overlay directory throws rather\nthan producing a plan that cannot boot.\n\n## Leases, idle suspend, and the queue\n\nA desk is resident under a lease. `renewLease(ms)` extends it; activity defers\nthe idle timer; a desk past its lease or idle deadline is suspended — the VM\nstops, the disk persists, applications cold-start on `resume`. Keep-alive jobs\ndie with the lease, and their termination is recorded as `job_exit` so no\nprocess ever runs where an operator cannot see it.\n\nResidency is bounded by a hard capacity cap. Starts beyond the cap queue FIFO\nrather than overcommitting host memory. `host.stats()` reports\n`{ resident, queued, capacity, suspended, lastStartedAt, lastSuspendedAt,\nreconnects, lastReconnectAt, lastReconnectDeskId, lastError }`; queue depth is\nworth alerting on, and so is a `reconnects` count that climbs. With an injected\n`now`, tests drive all of this deterministically through `host.sweep()`.\n\n## A connection is re-established, not assumed\n\nThe vsock channel to a guest is not a fact learned once at boot. It can drop\nmid-lease for reasons that say nothing about whether the desk is usable — the\nguest agent restarts, the bridge drops, the guest wedges for a moment — and\ntreating that as terminal stranded the desk for the rest of its lease with a\nhealthy guest behind it. So the backend reconnects: same retry path, same\n`confirmGuest` ping, bounded window and backoff. It never reconnects after\n`shutdown()` or once the VMM has exited, because then the desk genuinely needs\na fresh boot; when the window runs out the host suspends the desk so `resume()`\nboots a new one instead of handing back a dead handle.\n\nTwo things a caller must handle, because pretending otherwise would be a lie:\na request that was **in flight** when the channel dropped rejects with\n`DeskRequestFateUnknownError` — the guest may already have run it, and silently\nreplaying an `exec` that sent mail is the wrong kind of resilience. And the\nguest's own capture state does not survive its agent restarting, so live\n`frames()`/`video()` iterators **end** on a reconnect and the coordinate anchor\nis cleared; observe (or take a frame) again before aiming.\n\n## The coordinate contract\n\n**Input coordinates are in the pixel space of the most recent `observe()`,\none to one.** Any scaling applied on the way out must be undone on the way in.\nThe package enforces this rather than documenting it and hoping: coordinate\ninput before the first `observe()`, or outside its bounds, throws. Getting\nthis wrong makes every click land slightly off in a way that looks like model\nfailure and is very hard to diagnose.\n\nPerception is pixels-primary: `observe()` always returns a PNG, and\nopportunistically includes the focused application's AT-SPI accessibility tree\n(`a11y`) when one is exposed. `screen.a11y.invoke(nodeId, action)` targets by\nrole and name when a tree exists; the pixel path always works.\n\n## The handover masking contract\n\n`screen.handover.begin({ ttlMs, scope })` returns a URL through which a human\ncan view or control the screen — for example to complete a login the agent\ncannot. The masking rules are load-bearing:\n\n- While a handover is active, **input events never reach the `onEvent`\n  recording port** — no `click`, `type`, `key`, `scroll`, `drag`,\n  `window_focus`, or `app_launch` is emitted, even though the input itself is\n  faithfully forwarded to the guest.\n- **Frames are not emitted** to `frames()` consumers for the duration.\n- **`clipboard.read()` is refused** during a handover, so a freshly typed\n  credential cannot ride the clipboard into the agent's context.\n- Only `handover_begin` and `handover_end` cross the boundary, carrying actor,\n  scope, and duration — never content. Both reach `onEvent` (for the ledger)\n  and the `audit` port.\n\nA handover ends explicitly, at its TTL (`reason: 'expired'`), or when the desk\nis suspended (`reason: 'revoked'`). The failure this prevents is concrete:\nkeystrokes typed by a human during a credential handover leaking into an\nappend-only record that cannot be edited afterward.\n\n## The event union\n\n`onEvent` receives a closed union — `shell_command`, `app_launch`, `click`,\n`type`, `key`, `scroll`, `drag`, `window_focus`, `screen_open`,\n`screen_close`, `handover_begin`, `handover_end`, `job_start`, `job_exit` —\neach stamped with `deskId` and an ISO timestamp. The union is defined here so\nthe consumer's ledger and this package agree on the taxonomy; persistence is\nentirely the consumer's concern.\n\n## The backend port and the guest agent\n\n`DeskBackend` is the seam that keeps everything testable without a hypervisor:\n`boot(plan)` returns a `DeskMachine` — a request/response channel to the\nin-guest agent plus an event subscription. The default is\n`cloudHypervisorBackend`, which creates the overlay, spawns Cloud Hypervisor\nwith the plan's argv, performs the vsock `CONNECT` handshake, and speaks the\nframed protocol. Its process-spawning glue is thin on purpose; the launcher\nand the socket transport are injectable, and CI substitutes in-memory fakes.\n\nThe wire protocol — length-prefixed JSON frames with bounded sizes, strict\nfield validation, and a closed operation set — is pure code in `protocol.ts`,\nshared by both ends. The in-guest agent (`guest-agent.ts`) is the\nsecurity-critical piece: it is the only new attack surface in the design, so\nit is small enough to read in one sitting, does no parsing it does not need,\ndispatches through a closed switch, and treats any framing violation as fatal\nto the connection. Its message-handling core is pure and hard-tested; contact\nwith the guest OS is injected as handlers by the guest's init glue.\n\n`verifyDeskHost()` belongs in service startup: it boots a throwaway microVM\nthrough the backend and distinguishes a host that is unusable (wrong platform,\nmissing VMM or images — throws) from capabilities that are merely absent,\nreported as booleans: `kvm`, `vsock`, and `virtioGpu`.\n\nEvery additional provider runs `verifyDeskBackendConformance()` against a\ndisposable launch plan. The shared check verifies request/capability round trips,\nidempotent terminal shutdown, and refusal of work after shutdown. Provider test\nsuites extend this contract; they do not restate it.\n\n`exportPortableDeskHome()` and `importPortableDeskHome()` are the migration seam\nbetween providers. Sources expose entries and file bytes; exports produce a\nbounded, sorted, content-addressed manifest preserving directories, files,\nsafe relative symlinks, modes, and timestamps. Imports verify every SHA-256,\npath, symlink target, size, and aggregate limit before opening a sink, then\nstage the complete home and make it authoritative with one `commit()`. Any\nfailure calls `rollback()`. This moves an agent's working home, not an operating\nsystem image: installed packages and provider-specific machine state remain\nthe provider's concern.\n\n`createDeskFrameDeduplicator()` assigns exact SHA-256 identities to observations\nwithout retaining their bytes. A repeated identity can reuse the prior audit\nframe and omit another model image; reset it whenever the capture session or\nmasking boundary changes.\n\nDo not fall back to unconfined execution when this package reports an\nunsupported host. A deployment without KVM loses the desk ability entirely —\nthe same fail-closed posture as the rest of AppKit.\n","readmeFilename":"README.md"}