// What one of the user's own machines is running. import { z } from "zod"; import { PortNumberSchema } from "./device-commands.js"; import { DeviceBridgeHoldSchema, DeviceConflictSchema, DeviceCopySchema, DeviceLowDiskSchema } from "./device-folder.js"; import { parseHostConnection, type DeviceFacts, DeviceFactsSchema, MachineIdentitySchema, MachineIdSchema, userDistrosOf, WslEnvironmentSchema } from "./hosts.js"; import { RollbackTargetSchema, UpdateOutcomeSchema } from "./updates.js"; import { DEV_VERSION } from "../state/versions.js"; // Desktop-sync report shape shared by the agent, daemon and browser, produced only by the agent's own `deviceReport`. // The agent never reports `sandboxes`; the docker half is filled in by whoever reads the report, scoped to the reader's // own pairing. // One sandbox's resource share as docker currently enforces it, read off the container by the machine agent. // `overlayRuntime` is the environment's locked demand; `hostRuntime` is the owner's addition; `privileged`/`gpu` are // docker's enforced truth. // A delta against what runs: absent means leave it, `null` on the two caps means back to the default; at least one key // must be set. What the old `reshape` op carries, which the machine agent carries out as `ic sandbox shape --set` (its // `olderResizePlan`); `set-shape` carries a whole shape instead. export const SandboxResourcesAskFieldsSchema = z.object({ memoryGib: z.int().positive().nullable().optional(), cpus: z.int().positive().nullable().optional(), privileged: z.boolean().optional(), gpu: z.boolean().optional(), }); // A sandbox's shape, WHOLE: the owner's own ask for its share of the machine, every field a value. `null` on a cap is // the contract's default (the share derived from the engine, or every core). The switches are the owner's ask, not // docker's answer: a directive the approved environment demands rides beside it (`overlayRuntime`), and a host // without the NVIDIA runtime drops the GPU. `ic` stores it, validates it against the image's run contract when it is // saved, and prints it in `ic sandbox list --json`; nobody else merges anything into it. export const SandboxShapeSchema = z.object({ memoryGib: z.int().positive().nullable(), cpus: z.int().positive().nullable(), privileged: z.boolean(), gpu: z.boolean(), }); export type SandboxShape = z.infer; // When a `set-shape` takes effect: `now` restarts the sandbox onto it; `nextRestart` saves it in ic's record and // restarts nothing, and the next restart through ic applies it — Restart or Start from any door, an update, a rollback // or a rebuild. Docker restarting the container by itself (after a crash or a reboot) does not: only ic recreates one. export const SandboxShapeWhenSchema = z.enum(["now", "nextRestart"]); export type SandboxShapeWhen = z.infer; export const SandboxResourcesSchema = z.object({ // The cgroup memory ceiling in bytes; absent when docker imposes none (the hosted shape). memoryBytes: z.number().optional(), // The CFS quota as whole cores; absent when the container may use every core (the default). cpus: z.number().optional(), privileged: z.boolean(), gpu: z.boolean(), hostRuntime: z.array(z.string()), overlayRuntime: z.array(z.string()), // The shape the container runs with (the owner's ask it carries). Absent from an agent older than `set-shape`. shape: SandboxShapeSchema.optional(), // The shape saved for the sandbox's next restart through ic, and not yet in force. Absent when nothing is saved. desired: SandboxShapeSchema.optional(), // READ-ONLY, from agents older than `set-shape`: their saved share, a delta against what runs. Nothing new writes // it; kept one release so a newer page can still show what an older agent saved. Remove with `later`. saved: SandboxResourcesAskFieldsSchema.optional(), }); export type SandboxResources = z.infer; // Fields also exported separately for callers that compose them and enforce the at-least-one rule themselves. // Strict like every device input (DeviceSandboxFlowSchema says why): it is only ever sent to a device. export const SandboxResourcesAskSchema = SandboxResourcesAskFieldsSchema.strict().refine((ask) => Object.values(ask).some((value) => value !== undefined), { message: "a reshape must change at least one thing", }); export type SandboxResourcesAsk = z.infer; // One sandbox container on the machine, the docker half, filled in by the reader, never by the sync agent. export const DeviceSandboxSchema = z.object({ slug: z.string(), container: z.string(), // The display name, when the machine has one recorded. Docker knows only the container name. name: z.string().optional(), running: z.boolean(), image: z.string(), // Absent when there is no cloudflared sidecar at all; that is not the same as a sidecar that is down. tunnelRunning: z.boolean().optional(), // Absent when the reader only ran a cheap `docker ps` listing without inspecting the container. resources: SandboxResourcesSchema.optional(), // The update `ic sandbox prepare` built and left waiting for this sandbox; absent when nothing is staged. staged: z.object({ image: z.string(), version: z.string().optional(), channel: z.string().optional() }).optional(), // Everything below is read off an `ic` new enough to know it; an older ic's listing simply lacks it. // What the running image says it is. version: z.string().optional(), // An interrupted swap left the old container set aside with no replacement: the sandbox is down, and `start` (or // any swap) puts it back. `running` is false while this is true. parked: z.boolean().optional(), // A swap has just happened and the previous version is still parked and ready: until this moment the host watches // the new one and goes back by itself if it keeps crashing, never becomes ready, or loses its tunnel. probationUntil: z.number().optional(), // What the host last did about this sandbox's version (the same record the sandbox reads as `lastUpdate`). lastUpdate: UpdateOutcomeSchema.optional(), // Newest first: what `rollback` would return to, then the older versions kept on this machine for `rollback --to`. rollbackTargets: z.array(RollbackTargetSchema).optional(), // The other side of this computer that keeps it (`windows`, `linux`; from 2026-10-05 `platform/env`, e.g. // `linux/archlinux`, so two WSL distros are told apart). On Windows, ic on Windows and ic in WSL drive one Docker // engine and each lists every sandbox on it, but only the side whose ic created a sandbox runs its background rounds // (keeper, updates, backups, probation); absent for this side's own, and from an older ic. keptElsewhere: z.string().optional(), // How a person reads that side ("WSL (archlinux)", "Windows"). keptElsewhereName: z.string().optional(), // The side that keeps it has gone silent (its keeper stopped writing `/history/.ic/keeper.json` for half an hour), // so this side adopted it for its rounds: `keptElsewhere` is left out, and these say whose it was and since when. adoptedFrom: z.string().optional(), keeperSilentSince: z.number().optional(), // (2026-10-10) Stopped by the machine's keeper after a stretch nobody needed it (`ic sandbox sleep`), and started // again by itself when somebody opens it. `running` is false while this is true. asleep: z.boolean().optional(), // (2026-10-16) How this machine reaches it with no network (ic's sandbox/loopback.rs): its 12-hex id from its connect // token (null without one), the host port its loopback listener is published on (null when it publishes none), and // the platform's own id for its row as its last announce said (null before one did). Absent from an older ic, and // from a row whose container docker would not inspect. sandboxId: z.string().nullable().optional(), localPort: z.number().int().nullable().optional(), platformId: z.string().nullable().optional(), }); export type DeviceSandbox = z.infer; // One operation on one sandbox, streamed as lines ending in a `result` or `error` frame. `prepare` builds the pending // update without touching the container. `set-shape` sets its resources and privileges, now or for the next restart; // `forget-shape` drops a shape saved for the next restart. `start`/`restart` apply a saved shape (all through `ic`). // `reshape` is `set-shape`'s older spelling, kept one release. `runner-up`/`runner-remove` act on a runner (this // sandbox's own container); gated by the `sandboxes` switch, not removal, as a runner holds no workspace to lose. export const DeviceSandboxOpSchema = z.enum([ "start", "stop", "restart", "sleep", "prepare", // `prepare` under the unattended rules (`ic sandbox prepare --auto`): nobody pressed anything, so low disk is "not // now" and a pinned sandbox or the version it went back from is left alone. What the update card sends by itself // when it opens; sent only to an agent advertising `background-prepare`. "prepare-background", "update", "rebuild", "rollback", "reshape", "set-shape", "forget-shape", "remove", "logs", // The two ops that redeem a fresh setup code; every other one recreates a container from its own existing env. // They differ only in which row the claim names — an existing sandbox on this machine, or one that has never run // anywhere — which is why each checks the opposite thing about the slug before spending the code. "reconnect", "create", "runner-up", "runner-remove", ]); export type DeviceSandboxOp = z.infer; // STRICT, like every input a device is sent: an agent rejects a field it does not know instead of dropping it and doing // something else (an older agent once dropped `later` and restarted a sandbox it had been asked to leave running). New // behaviour is a new `op`, which an older agent refuses by name, and the daemon refuses an op the agent has not // advertised before sending it (device-reports.ts). export const DeviceSandboxFlowSchema = z.strictObject({ op: DeviceSandboxOpSchema, // Sandbox slug, the runner's name for the runner ops (as `/system/runners`/`ic runner list` know it), or for // `create` the name the claim is about to produce — the one op where the slug is an expectation rather than a // lookup, and the device refuses if anything already answers to it. slug: z.string().min(1), // Approved overlay's sha256, required only by `rebuild`; only content matching it is ever built. hash: z.string().optional(), // `rollback` only: a version or pinned image from the sandbox's `rollbackTargets` to go back to instead of the // previous one. Sent only to an agent that advertises `rollback-to`: an older one refuses the field. to: z.string().min(1).optional(), // `set-shape` only, both required by it: the whole shape, and when it takes effect. // Strict too, unlike the same shape read off a report (where a newer ic's extra field must not fail an older // reader): an order carrying a field this agent does not know is refused rather than carried out without it. shape: z.strictObject(SandboxShapeSchema.shape).optional(), when: SandboxShapeWhenSchema.optional(), // READ-ONLY for one release, from pages served by sandboxes older than `set-shape`: the old `reshape` op's delta, // and `later` to save it for the next restart instead. Nothing new sends either. resources: SandboxResourcesAskSchema.optional(), later: z.boolean().optional(), // `runner-up` only, daemon-filled, never by the caller: the browser never holds the pairing credential. parentUrl: z.string().optional(), pair: z.string().optional().meta({ secret: true }), // Required by `reconnect` and `create`; single-sandbox and short-lived, so a leak only buys one recreate. setupCode: z.string().optional().meta({ secret: true }), // `reconnect`/`create` only, daemon-filled: the platform that minted `setupCode`, the only one that redeems it. platformUrl: z.string().optional(), // `runner-up` only, daemon-filled: `definition` carries no capabilities or secrets; `overlay`/`overlayHash` are // re-verified by hash before build. definition: z.string().optional(), overlay: z.string().optional(), overlayHash: z.string().optional(), }); export type DeviceSandboxFlow = z.infer; // The `ic` argv for the shape and power ops, the one place any caller spells them: the machine agent runs it and the // Devices view prints it for a person to type when the app's route to the machine is shut. The shape travels as the // contract's own object, one `--set =` per field, so no caller maps a field onto a flag of its own: // ic reads the pairs back into the contract's shape and refuses a field it does not know, as every device input does. // `when` is the contract's own word too. A field left out keeps what ic already has for it (the shape saved for the // next restart, else what runs): the Devices view always sends the whole shape, a model's tool call may name one // field. Nothing is judged here; ic checks the shape against the image's run contract. Needs an ic that takes `--set` // (the `set-shape` feature is advertised only then). export type SandboxShapeFields = { readonly [K in keyof SandboxShape]?: SandboxShape[K] | undefined }; export const icShapeArgs = (slug: string, fields: SandboxShapeFields, when: SandboxShapeWhen): string[] => [ "sandbox", "shape", slug, ...Object.entries(fields).flatMap(([field, value]) => (value === undefined ? [] : ["--set", `${field}=${JSON.stringify(value)}`])), "--when", when, ]; export const icForgetShapeArgs = (slug: string): string[] => ["sandbox", "shape", slug, "--forget"]; // Power goes through ic too, so a Start or Restart from any door applies the shape saved for the next restart. export const icPowerArgs = (op: "start" | "stop" | "restart" | "sleep", slug: string): string[] => ["sandbox", op, slug]; // Adds the device id to the flow input; the daemon looks up machines by id. `resumeTurns` is the owner saying, as they // restart the sandbox relaying this, that the turns it cuts should run again once it is back; the daemon keeps it for // its next boot and never forwards it to the machine. A sandbox older than it ignores it, and the cut turns wait. export const DeviceSandboxFlowInputSchema = DeviceSandboxFlowSchema.extend({ id: z.string().min(1), resumeTurns: z.boolean().optional() }); export type DeviceSandboxFlowInput = z.infer; // Streamed operation output, matching `IntenticLineSchema`: `line` frames as printed, then one terminal frame, `result` // or `error`, carrying the machine's own message rather than a code. export const DeviceFlowLineSchema = z.discriminatedUnion("kind", [ z.object({ kind: z.literal("line"), text: z.string() }), z.object({ kind: z.literal("result"), message: z.string() }), z.object({ kind: z.literal("error"), message: z.string() }), ]); export type DeviceFlowLine = z.infer; // Every op stops the resident process mid-command; a stream ending with no terminal frame is normal, not a failure, and // confirmation is the version moving on the next poll. `restart` is just a bare `intentic-machine run`. // `forget-unreachable` drops the machine's links to sandboxes that have answered nothing for long enough to be gone // (peer-dial's LONG_OUTAGE_ATTEMPTS) — never the link carrying the request, which is by definition answering. Never // automatic either: a laptop closed for a fortnight must find its sandboxes again, so nothing expires on its own and // dropping one is always somebody's decision. export const DeviceAgentOpSchema = z.enum(["upgrade", "restart", "forget-unreachable"]); export type DeviceAgentOp = z.infer; export const DeviceAgentFlowSchema = z.strictObject({ op: DeviceAgentOpSchema }); export type DeviceAgentFlow = z.infer; // Adds the device id to the flow input. export const DeviceAgentFlowInputSchema = DeviceAgentFlowSchema.extend({ id: z.string().min(1) }); export type DeviceAgentFlowInput = z.infer; export * from "./device-commands.js"; // What a synced folder reports beyond Mutagen's word, its conflicts and what the device holds back (device-folder.ts). export * from "./device-folder.js"; // One paired sandbox as the local agent holds it; `localDir` is which folder on that device holds this sandbox's /work. export const DevicePairingSchema = z.object({ sandboxId: z.string(), // The account's name for the sandbox, stamped by the editor that reads the report (withSandboxNames); never a machine's. name: z.string().optional(), mode: z.enum(["sync", "mirror"]), // Set only for mode "sync", and only for the sandbox being reported to. localDir: z.string().optional(), // Which sandbox folder `localDir` holds when it is not /work itself: a project pairing's `/work/`, the // owner's own folder, which carries no state backup and no git bridge. Absent means /work. remoteDir: z.string().optional(), // Whether landed work in this project folder is delivered to `localDir` by itself (`deliverProject`). A project // sandbox's folder says "auto"; absent means Bring back by hand, as before. (2026-10-21) The folderless // `projectsHost` pairing of the one sandbox per computer went with it; an older report carrying it parses, key dropped. deliver: z.enum(["auto", "off"]).optional(), // (2026-10-20) The slug this machine's ic knows the sandbox by: how the desktop app starts or forgets a folder's sandbox. slug: z.string().optional(), // Machine-owned switch (agent's own `sync mirror off`); absent reads as "on", not merely unknown. mirroring: z.enum(["on", "off"]).optional(), // Mutagen's own status word, kept verbatim rather than reduced to a traffic light, and how far its copy is. mutagenStatus: z.string().optional(), copy: DeviceCopySchema.optional(), // Whole conflict count, including any Mutagen's own state truncates (`excludedConflicts`). conflicts: z.number().int().nonnegative().optional(), // Conflicted paths, capped at `CONFLICT_PATHS_MAX`; `conflicts` above is the true, uncapped count. conflictedPaths: z.array(DeviceConflictSchema).optional(), // Mutagen's word, and why the agent holds it still, owner first (DeviceSyncHoldSchema); agreeing by its next pass. paused: z.boolean().optional(), heldBy: z.array(z.string()).optional(), // Second session's status (the state-dir backup mirror), reported separately: the two fail independently. backupStatus: z.string().optional(), // (2026-10-18) While the drive holding `localDir` is nearly full, the reading that paused its sync and git bridge; and the // repos whose git history stopped following for a while (device-folder.ts). Each absent while it does not hold. lowDisk: DeviceLowDiskSchema.optional(), bridgeHolds: z.array(DeviceBridgeHoldSchema).optional(), }); export type DevicePairing = z.infer; // One sandbox port and what became of it on this machine's localhost; carries a state rather than just live forwards, // since two sandboxes can claim the same port and the loser needs a reason shown. export const DevicePortStateSchema = z.enum([ // Forwarded: the sandbox's listener answers on this machine's localhost at the same number. "mirrored", // Another paired sandbox already holds the port (first paired wins); `heldBy` names it. "held-by-sandbox", // Something outside this product already binds the port; not ours to name or take. "busy", // The owner told this device to leave this number alone (`sync mirror ignore`); nothing was attempted. "ignored", ]); export type DevicePortState = z.infer; // Why a port the sandbox serves is not on this machine's localhost: the state minus its one success. Named here // because the machine decides it and the browser renders it, so a new reason has to reach both at once. export const PortSkipReasonSchema = DevicePortStateSchema.exclude(["mirrored"]); export type PortSkipReason = z.infer; export const DevicePortSchema = z.object({ port: PortNumberSchema, host: z.enum(["127.0.0.1", "::1"]), // The sandbox serving the port, whose /ports listed it, not whoever ended up holding the local bind. sandboxId: z.string(), state: DevicePortStateSchema, // Set only for "held-by-sandbox": the sandbox id that owns the local bind instead. heldBy: z.string().optional(), // What is listening on the sandbox side (e.g. `node …/vite`), so the user can recognise it. command: z.string().optional(), }); export type DevicePort = z.infer; // The device's one agent process; `running` gates whether the rest of a report is current (a dead loop freezes it). export const DeviceAgentSchema = z.object({ running: z.boolean(), pid: z.number().int().optional(), // Build on disk at `~/.intentic/bin/intentic-machine`; absent means no installed agent at all. installed: z.string().optional(), // Build actually serving, stamped in the pidfile; can lag `installed` indefinitely (see `agentBuildSkew`). build: z.string().optional(), // When the sync loop last moved; absent while nothing is paired (no loop runs). A live process can have a dead loop. lastTickAt: z.number().optional(), }); export type DeviceAgent = z.infer; // Stalled only once the agent's own restart of a loop that stopped moving (machine sync/mirror.ts) is overdue. export const WATCHER_STALL_MS = 20 * 60_000; export const AGENT_STALL_AFTER_MS = WATCHER_STALL_MS + 2 * 60_000; export const agentStalled = (agent: DeviceAgent, now: number): boolean => agent.running && agent.lastTickAt !== undefined && now - agent.lastTickAt > AGENT_STALL_AFTER_MS; export const DeviceReportSchema = z.object({ // The computer this reading is from (MachineIdSchema): what a sync enrollment is stamped with when its agent posts, // and so what joins a machine seen through sync to the same machine seen through its card. Absent from an older agent. machineId: MachineIdSchema.optional(), // OS hostname, for the owner to read; not a join key (a WSL distro inherits the Windows machine's name). hostname: z.string(), os: z.string(), // Present only inside a WSL distro; the same fact rides the connect-time facts, which no scope can withhold. wsl: WslEnvironmentSchema.optional(), pairings: z.array(DevicePairingSchema), ports: z.array(DevicePortSchema), // The one agent this device runs, on disk and in flight, in one block. agent: DeviceAgentSchema, // When the machine took this reading, not when the daemon received it; the UI ages the report against this. capturedAt: z.number(), }); export type DeviceReport = z.infer; // Past this, a reading stops speaking for the machine and only says when it was taken. Read against the moment the // reading was received, never the wall clock: a copy nobody has re-read is old, not evidence the machine went quiet. export const REPORT_QUIET_AFTER_MS = 60_000; export const reportQuiet = (report: DeviceReport, receivedAt: number): boolean => receivedAt - report.capturedAt > REPORT_QUIET_AFTER_MS; // The environment a device's evidence describes, as opposed to the machine hosting it: `wsl:` inside a // distro, `native` for an install on the metal. Connect-time facts are read first, since no scope can withhold them; // facts from an agent too old to carry `hostname` say nothing, and a report without `wsl` is native, as it always // was. Undefined is an absence of evidence, never read as agreement. export const environmentOf = (facts: Pick | undefined, report: DeviceReport | undefined): string | undefined => { const wsl = facts?.wsl ?? report?.wsl; if (wsl !== undefined) { return `wsl:${wsl.distro}`; } return facts?.hostname !== undefined || report !== undefined ? "native" : undefined; }; // Whether two devices positively disagree about which environment they are. False whenever either side has not said, // so this only ever blocks a fold it holds evidence against. export const differentEnvironment = (left: string | undefined, right: string | undefined): boolean => left !== undefined && right !== undefined && left !== right; // Compares running build against installed; silent when the loop is stopped, nothing installed, or installed is a dev // build. An unstamped `running` still counts as skew. export const agentBuildSkew = (agent: DeviceAgent): { readonly running: string | undefined; readonly installed: string } | undefined => { const { build: running, installed } = agent; if (!agent.running || installed === undefined || installed === DEV_VERSION || running === installed) { return undefined; } return { running, installed }; }; // Why a device that is present has no report; each value is a distinct thing for the reader to do about it. export const DeviceGapSchema = z.enum([ // A host capability that is enrolled but has no socket right now. Laptops sleep; this is not a fault. "offline", // Connected, but "Run commands" is off on its capability card, so it won't describe itself: no folders, no // ports, no agent health. Says nothing about its containers, which ride their own switch (`Device.sandboxes`). "scope-off", // The agent is there but gave no report: never posted since enrolling, or refused `report` other than by its switch. "unreported", ]); export type DeviceGap = z.infer; // A machine may be reachable via desktop sync and a host capability at once; the two are one row when both say the same // `machineId` and the same environment, and separate rows whenever either has not said (an agent too old to carry it). // `machine` is the enrollment's name for the box (the ssh key's comment): what reports are filed under and what the // revoke route takes. Two machines sharing a key comment share one enrollment identity. export const DeviceSyncSchema = z.object({ machine: z.string(), // "sync" is files+ports, single-holder per sandbox; "mirror" is ports only, any number of machines. mode: z.enum(["sync", "mirror"]), // When this machine last used its enrollment; absent means never, not merely unknown, and is not healthy. seenAt: z.number().optional(), // The computer and the environment on it that hold this enrollment, as its agent said when enrolling or in a report // since; absent until an agent new enough to say has done so. machineId: MachineIdSchema.optional(), environment: z.string().optional(), }); export type DeviceSync = z.infer; // A public host key as a machine pins it in known_hosts: its type and base64 body, no comment and nothing after. export const SshHostKeySchema = z .string() .regex(/^(?:ssh-ed25519|ssh-rsa|ecdsa-sha2-nistp(?:256|384|521)|sk-ssh-ed25519@openssh\.com|sk-ecdsa-sha2-nistp256@openssh\.com) [A-Za-z0-9+/]+={0,2}$/); // POST /system/authorized-key: a machine's agent enrolling its SSH key for desktop sync, redeeming a pairing token. // Every field past `key` is optional: an older agent sends none of them and is still enrolled. // The key, and the computer and environment enrolling it (the same identity a device redeem sends), so the // enrollment joins that machine's card. export const SyncEnrollmentRequestSchema = MachineIdentitySchema.extend({ // One authorized_keys line; its comment is the name the enrollment goes by (`DeviceSync.machine`). key: z.string(), }); export type SyncEnrollmentRequest = z.infer; // The daemon's answer to an enrollment. Every field is optional for the agent reading it, since each arrived in a later // daemon than the route: without `syncToken` the agent refuses the enrollment, without `mode` it reads "sync", and // without `hostKey` it falls back to recording whatever key the sandbox presents first. export const SyncEnrollmentAnswerSchema = z.object({ ok: z.literal(true).optional(), // Authorizes the ports poll, the machine report and the SSH transport, sent as `x-intentic-sync`. syncToken: z.string().optional(), // What the pairing granted: a member's pairing only ever enrolls "mirror". mode: z.enum(["sync", "mirror"]).optional(), // The public key the sandbox's sshd presents (an SshHostKeySchema line), for the agent to pin under the pairing's // alias. Checked by the agent on its own, so a key it cannot read costs the pin, never the enrollment. hostKey: z.string().optional(), }); export type SyncEnrollmentAnswer = z.infer; export const DeviceSchema = z.object({ // Stable row key: the reported hostname when either door produced one, else the name that door knows it by. key: z.string(), // What to call it on screen, the user's own name for the machine wherever one exists. label: z.string(), // Desktop-sync enrollment with this sandbox; absent when reached only via a host capability. sync: DeviceSyncSchema.optional(), // The host connection's key, when this machine is also a connected device. Absent otherwise. hostId: z.string().optional(), // The card that connection is of, beside the key rather than parsed out of it: the machine's name, a label. Absent // with `hostId`. card: z.string().optional(), // The computer this row is an environment of, from whichever door has said (MachineIdSchema): what `machinesOf` // groups by. Absent when no door has, which leaves the row a computer of its own. machineId: MachineIdSchema.optional(), // Host-capability liveness; absent when there is no host capability, not the same as offline. online: z.boolean().optional(), // Carried beside the report so a device with no report still has an OS; `platform` is the normalised slug, `facts` // its connect-time self-description. platform: z.string().optional(), facts: DeviceFactsSchema.optional(), // Announced at connect; matches `report.agent.build` when known, the only version a report-less device has. agentVersion: z.string().optional(), lastSeen: z.number().optional(), report: DeviceReportSchema.optional(), // The containers this machine holds, read through the host door's own `list_sandboxes` and so behind its own // switch ("Manage sandboxes on this device", or "Run commands"). Beside the report rather than inside it because // the two ride different switches: a card granting sandbox management and nothing else answers this and no // report at all. Absent means nobody could look — a sync-only machine, a refusal, an unread device — never that // the machine holds none. sandboxes: z.array(DeviceSandboxSchema).optional(), // Why `sandboxes` is absent, in the machine's own words, when it answered the listing with a refusal or a failure // (a switch that is off, an `ic` too old to list). Absent when it listed, or when nothing answered at all. What a // card falling back to a command says instead of "connect the computer": that computer IS connected (2026-10-07). sandboxesUnread: z.string().optional(), // Whether its card's "Manage sandboxes on this device" switch is on (HostSummary.manages). Not the same fact as // `sandboxes`: "Run commands" alone lists the containers, and only this switch lets one be made, started or removed, // so a screen offering such a press asks here first. Absent on a sync-only row and from a daemon older than it. manages: z.boolean().optional(), gap: DeviceGapSchema.optional(), }); export type Device = z.infer; // Device keys no machine is drawn for, each list absent when empty: enrollments no card holds, which only the owner withdraws (a land can rewrite // the card manifest for a moment), and connections live at once that report one machine id, kept rather than deduped (hosts/host-dedupe.ts). export const CardlessHostSchema = z.object({ id: z.string(), card: z.string(), environment: z.string() }); export type CardlessHost = z.infer; export const SharedIdentitySchema = z.object({ machineId: z.string(), connections: z.array(CardlessHostSchema) }); export type SharedIdentity = z.infer; const deviceKeyNotes = { cardlessHosts: z.array(CardlessHostSchema).optional(), sharedIdentities: z.array(SharedIdentitySchema).optional() }; export const DevicesListSchema = z.object({ devices: z.array(DeviceSchema), ...deviceKeyNotes }); export type DevicesList = z.infer; // The physical computer a device is an environment of. Windows and every WSL distro on it are one machine with one // Docker engine, one screen and one set of disks, each environment holding its own agent and its own door. export interface Machine { /** The card its doors hang off, else the hostname they share, else a lone uncarded device's own key. */ readonly key: string; readonly label: string; /** Windows first, then distros by the name WSL registered: the side that owns the screen leads. */ readonly environments: readonly Device[]; } export const deviceHostname = (device: Device): string | undefined => device.facts?.hostname ?? device.report?.hostname; // Which side of a machine a device is. Its door id names the environment it connected under (`::wsl:`), // and that is all an environment nobody has reached since this daemon booted can say about itself: liveness resets on // restart, so facts and reports are absent on a side that is merely asleep. export const deviceEnvironment = (device: Device): string | undefined => environmentOf(device.facts, device.report) ?? (device.hostId === undefined ? undefined : parseHostConnection(device.hostId).environment); const WSL_PREFIX = "wsl:"; export const isWslDevice = (device: Device): boolean => deviceEnvironment(device)?.startsWith(WSL_PREFIX) === true; // The distro a device runs, by whatever evidence it holds; absent on a native install and on one that has said nothing. export const deviceDistro = (device: Device): string | undefined => { const environment = deviceEnvironment(device); return environment?.startsWith(WSL_PREFIX) === true ? environment.slice(WSL_PREFIX.length) : undefined; }; // Native first, then distros by the name WSL registered them under rather than by their door id, which is that same // name behind a card's and so would order a PC's distros by which card each was connected through. const byEnvironment = (a: Device, b: Device): number => Number(isWslDevice(a)) - Number(isWslDevice(b)) || (deviceDistro(a) ?? a.label).localeCompare(deviceDistro(b) ?? b.label); // The computer a device is on, by whichever door said: its row, its connect-time facts, its report, its sync enrollment. export const deviceMachineId = (device: Device): string | undefined => device.machineId ?? device.facts?.machineId ?? device.report?.machineId ?? device.sync?.machineId; // What makes two devices one computer: a shared card (the row's own `card`, never parsed back out of its key: one card // is one computer, whatever either door has said about itself), or a shared machine id. Nothing else: a hostname is // shared by a PC and its WSL distros and by two unrelated machines with the same name, so it joined things by accident. const tokensOf = (device: Device): string[] => { const { card } = device; const machine = deviceMachineId(device); return [...(card === undefined ? [] : [`card:${card.toLowerCase()}`]), ...(machine === undefined ? [] : [`machine:${machine}`])]; }; // Components over both joins at once: a device holding a card token and a machine token is the evidence that merges // the machines those tokens named, so a distro connected under a card of its own still lands on the PC it runs on. interface Component { readonly tokens: Set; readonly devices: Device[]; } const componentsOf = (devices: readonly Device[]): Component[] => { const components: Component[] = []; for (const device of devices) { const tokens = tokensOf(device); const [head, ...merged] = components.filter((component) => tokens.some((token) => component.tokens.has(token))); if (head === undefined) { components.push({ tokens: new Set(tokens), devices: [device] }); continue; } for (const other of merged) { other.tokens.forEach((token) => head.tokens.add(token)); head.devices.push(...other.devices); components.splice(components.indexOf(other), 1); } for (const token of tokens) { head.tokens.add(token); } head.devices.push(device); } return components; }; // Addressed by the card of the side that owns the screen, an address that does not change when a second environment // connects — unlike a row key, which is a hostname another environment can take first. An uncarded fold has only the // machine id its doors share, and an uncarded lone device its own key. Called what the owner calls it: the name the // leading environment carries, unless that is nothing but its door id, which reads as a PC named after one of its // own sides. const machineOf = (environments: readonly Device[]): Machine => { const [first, ...rest] = environments; // A component holds the device that made it; the empty case only keeps the type honest. if (first === undefined) { return { key: "", label: "", environments }; } const named = environments.map((device) => device.card).find((key) => key !== undefined) ?? (rest.length === 0 ? undefined : deviceMachineId(first)); const label = first.label === first.hostId ? (first.card ?? first.label) : first.label; return { key: named ?? first.key, label, environments }; }; export const machinesOf = (devices: readonly Device[]): Machine[] => componentsOf(devices).map((component) => machineOf(component.devices.toSorted(byEnvironment))); // Every door whose docker reports a given sandbox slug, in list order. More than one is the ordinary case, not a // conflict: Windows and the WSL distros on it share one engine, so each door answers for the same containers. const doorsRunningSandbox = (devices: readonly Device[], slug: string | undefined): Device[] => slug === undefined || slug === "" ? [] : devices.filter((device) => device.hostId !== undefined && device.online === true && (device.sandboxes ?? []).some((box) => box.slug === slug)); // The connected, online device whose docker reports a given sandbox slug: the machine that sandbox RUNS ON, not any // machine merely paired with it, and `undefined` for a sync-only agent, which reports no containers. Read off the row's // own container list, not the report's: a card granting sandbox management without "Run commands" refuses to describe // itself yet runs every swap this answer leads to, so judging it on the report printed a command one click could do. // Answers with a door, any of which a container verb may take; a command written for a PATH may not (`hostHoldingPath`). export const hostRunningSandbox = (devices: readonly Device[], slug: string | undefined): string | undefined => doorsRunningSandbox(devices, slug)[0]?.hostId; // A drive letter or a UNC share is a Windows path, everything else a unix one. Not a claim about where the folder is // on disk: a path only exists in the dialect of the shell that wrote it, and a distro's checkout has no Windows name // the line naming it would work under. const windowsPath = (path: string): boolean => /^([A-Za-z]:[\\/]|\\\\)/.test(path); // How a line written for a host path reaches the environment that holds it, through one door. `none` carries the // distros that were candidates, since "this PC has two and nothing says which" is a different answer for the reader // than "there is no way in". export type PathReach = | { readonly kind: "direct" } | { readonly kind: "wsl"; readonly distro: string } | { readonly kind: "none"; readonly distros: readonly string[] }; // How this door runs a line written for this path. A door whose own shell speaks the path's dialect runs it directly; // a Windows door onto the PC whose distro holds it CROSSES, because `run_command` takes the crossing as an argument // (`in: "wsl:"`) and builds the argv on the machine. One connected machine is therefore enough, whichever side // of it the owner connected. // `wslDistros` is the gate on purpose: it and `in` shipped in the same agent release, so a machine that lists its // distros is a machine that understands the crossing, and an older one is left to the refusal instead of being sent // an argument it would reject. Two real distros stay a refusal that names them — nothing here knows which one has the // folder, and the distro's own door, once connected, answers directly. // A platform that never said what it is stays a candidate, as every other reading of device evidence does. export const pathReach = (platform: string | undefined, facts: DeviceFacts | undefined, path: string): PathReach => { if (platform === undefined || (platform === "windows") === windowsPath(path)) { return { kind: "direct" }; } const distros = platform === "windows" ? userDistrosOf(facts) : []; const only = distros.length === 1 ? distros[0] : undefined; return only === undefined ? { kind: "none", distros } : { kind: "wsl", distro: only }; }; // A path under a door's own home belongs to that door: the one thing that tells two distros of one PC apart, since // both run sh and both answer for the same containers. const homeHolds = (device: Device, path: string): boolean => { const home = device.facts?.home; return home !== undefined && (path === home || path.startsWith(`${home}/`) || path.startsWith(`${home}\\`)); }; // The door for a command written for a path out there — a `cd` into the dev checkout, a script inside it, the log // beside it. One PC answers through several doors, so "which device runs this sandbox" has more than one true answer // and only the path says which environment can open it: a sh line aimed at /home/… is a parse error on the Windows // side of the very machine the build runs on. // A door that runs the line itself wins over one that has to cross — the same PC, one hop fewer, and no distro to // guess at — and among direct doors, the one whose own home holds the path, which is what tells two distros apart. // Undefined only when no connected door reaches that environment at all, which is the state the copyable command // exists for. export const hostHoldingPath = (devices: readonly Device[], slug: string | undefined, path: string | undefined): string | undefined => { if (path === undefined || path === "") { return undefined; } const doors = doorsRunningSandbox(devices, slug).map((device) => ({ device, reach: pathReach(device.platform, device.facts, path) })); const direct = doors.filter((door) => door.reach.kind === "direct").map((door) => door.device); const crossing = doors.find((door) => door.reach.kind === "wsl")?.device; return (direct.find((device) => homeHolds(device, path)) ?? direct[0] ?? crossing)?.hostId; }; // GET /system/sync: sandbox-level facts only, whether sync is possible, whether anything is enrolled, and raw device // reports. Cheap: the sidebar badge reads this and must never fan out to a device. export const SyncStatusSchema = z.object({ enrolled: z.boolean(), // Whether any machine holds this sandbox's FILES, as opposed to only mirroring its ports — the difference // between "a copy of this work exists elsewhere" and "it does not". Absent means the daemon cannot say, which // readers must take as "do not claim there is no copy", the same way `available` is read. syncing: z.boolean().optional(), // Whether this sandbox can do desktop sync at all; absent means a daemon too old to say, so don't offer it. available: z.boolean().optional(), machines: z.array(DeviceReportSchema).optional(), }); export type SyncStatus = z.infer;