@platless/axom-cli 0.5.9 → 0.6.52
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/README.md +25 -6
- package/dist/axom-patch/cli/README.md +129 -12
- package/dist/axom-patch/cli/account-limits/generate.py +15 -12
- package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +20 -3
- package/dist/axom-patch/cli/claude-account-limits.json +9 -9
- package/dist/axom-patch/cli/claude-replay-lifecycle.json +16 -0
- package/dist/axom-patch/cli/codex-account-limits.json +11 -11
- package/dist/axom-patch/cli/codex-terminal-output.json +16 -0
- package/dist/axom-patch/cli/commandcode-account-limits.json +9 -9
- package/dist/axom-patch/cli/kimi-quota.mjs +12 -0
- package/dist/axom-patch/cli/kimi-subagents.mjs +173 -0
- package/dist/axom-patch/cli/nightly-task.md +55 -13
- package/dist/axom-patch/cli/probes/claude-replay-lifecycle.mjs +31 -0
- package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +33 -0
- package/dist/axom-patch/cli/probes/kimi-subagent-subwork.mjs +82 -0
- package/dist/axom-patch/cli/probes/zcode-env-account.mjs +81 -0
- package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +7 -0
- package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1687 -0
- package/dist/axom-patch/cli/qoder/releases/1.1.75.json +277 -0
- package/dist/axom-patch/cli/qoder-background-terminal.md +13 -6
- package/dist/axom-patch/cli/qoder-foreground-output.md +14 -8
- package/dist/axom-patch/cli/qoder-native-acp.json +227 -0
- package/dist/axom-patch/cli/qoder-steering.md +20 -25
- package/dist/axom-patch/cli/qoder-subagent-subwork.md +17 -68
- package/dist/axom-patch/cli/runtime.json +19 -19
- package/dist/axom-patch/cli/zcode/env-account.mjs +13 -0
- package/dist/axom-patch/cli/zcode/generate-env-account.py +52 -0
- package/dist/axom-patch/cli/zcode-account-limits.json +10 -11
- package/dist/axom-patch/cli/zcode-env-account.json +58 -0
- package/dist/axom-patch/cli/zcode-models.json +9 -10
- package/dist/axom-patch/cli/zcode-subagent-subwork.json +10 -11
- package/dist/cli.js +11 -45
- package/dist/commands/auth.js +4 -1
- package/dist/commands/knowledge.js +47 -0
- package/dist/commands/update.js +126 -3
- package/dist/commands/workstation.js +101 -164
- package/dist/commands/workstationRegistrations.js +116 -0
- package/dist/credentialBroker.js +13 -2
- package/dist/credentials.js +16 -0
- package/dist/install/claude/commands.js +15 -15
- package/dist/keepers/terminal/keeper.mjs +1089 -673
- package/dist/keepers/terminal/manifest.json +4 -4
- package/dist/mcp.js +1 -1
- package/dist/native/darwin-x64/axom-quic +0 -0
- package/dist/workstation/acp/acpAgents.js +113 -102
- package/dist/workstation/acp/acpClient.js +26 -0
- package/dist/workstation/acp/builds/install/npm.js +99 -10
- package/dist/workstation/acp/initializeResponse.js +6 -1
- package/dist/workstation/acp/managedPatches.js +189 -25
- package/dist/workstation/acp/managedRuntime.js +314 -0
- package/dist/workstation/acp/outbox.js +12 -5
- package/dist/workstation/acp/patchAssets.js +40 -0
- package/dist/workstation/acp/permission.js +2 -2
- package/dist/workstation/acp/processor/checkpoint.js +26 -34
- package/dist/workstation/acp/processor/control.js +7 -80
- package/dist/workstation/acp/processor/jsonrpc.js +8 -0
- package/dist/workstation/acp/processor/runtime.js +5 -81
- package/dist/workstation/acp/processor/session.js +54 -82
- package/dist/workstation/acp/processor/state.js +7 -13
- package/dist/workstation/acp/processor/step.js +3 -20
- package/dist/workstation/acp/processor/test/fixtureLog.js +9 -16
- package/dist/workstation/acp/processor/transport/local.js +19 -3
- package/dist/workstation/acp/processor/updates.js +156 -105
- package/dist/workstation/acp/processorSession.js +20 -173
- package/dist/workstation/acp/registry/catalog.js +65 -39
- package/dist/workstation/acp/registry/launch.js +16 -1
- package/dist/workstation/acp/switching.js +0 -3
- package/dist/workstation/acp/test/fakeAgent.js +13 -0
- package/dist/workstation/acp/test/keeperWorld.js +32 -3
- package/dist/workstation/acp/test/raw/sources.js +16 -130
- package/dist/workstation/acp/test/realAdapter.js +1 -47
- package/dist/workstation/acp/test/scriptAgent.js +11 -3
- package/dist/workstation/agentFlow.js +1 -1
- package/dist/workstation/agentSessions.js +114 -166
- package/dist/workstation/channels/agent.js +9 -1
- package/dist/workstation/channels/computer.js +27 -4
- package/dist/workstation/channels/management.js +130 -15
- package/dist/workstation/channels/url.js +1 -1
- package/dist/workstation/config.js +33 -9
- package/dist/workstation/connection/encryptedChannel.js +4 -1
- package/dist/workstation/control/bindingStore.js +204 -0
- package/dist/workstation/control/commands.js +125 -0
- package/dist/workstation/control/daemonIdentity.js +143 -0
- package/dist/workstation/control/deviceCommands.js +191 -0
- package/dist/workstation/control/deviceProtocol.js +68 -0
- package/dist/workstation/control/enrollment.js +106 -0
- package/dist/workstation/control/inspect.js +71 -0
- package/dist/workstation/control/locks.js +110 -0
- package/dist/workstation/control/protocol.js +40 -0
- package/dist/workstation/control/restartGuard.js +46 -0
- package/dist/workstation/control/rootFormat.js +16 -0
- package/dist/workstation/control/runtimeClient.js +152 -0
- package/dist/workstation/control/runtimeHost.js +235 -0
- package/dist/workstation/control/serverIdentity.js +35 -0
- package/dist/workstation/control/updateTransition.js +91 -0
- package/dist/workstation/daemon.js +266 -264
- package/dist/workstation/daemonctl.js +226 -25
- package/dist/workstation/desktopControl/audio.js +99 -0
- package/dist/workstation/desktopControl/automationDevice.js +219 -0
- package/dist/workstation/desktopControl/automationJson.js +74 -0
- package/dist/workstation/desktopControl/automationLocalState.js +23 -0
- package/dist/workstation/desktopControl/automationProtocol.js +253 -0
- package/dist/workstation/desktopControl/automationSchema.generated.js +1 -0
- package/dist/workstation/desktopControl/automationSession.js +81 -0
- package/dist/workstation/desktopControl/automationTransport.js +138 -0
- package/dist/workstation/desktopControl/capabilities.js +93 -0
- package/dist/workstation/desktopControl/captureModes.js +9 -0
- package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
- package/dist/workstation/desktopControl/deviceAdmission.js +229 -0
- package/dist/workstation/desktopControl/deviceOwnership.js +130 -0
- package/dist/workstation/desktopControl/deviceSockets.js +246 -0
- package/dist/workstation/desktopControl/helperProcess.js +602 -0
- package/dist/workstation/desktopControl/hostGrant.js +65 -0
- package/dist/workstation/desktopControl/lobby.js +131 -0
- package/dist/workstation/desktopControl/macProvider.js +156 -0
- package/dist/workstation/desktopControl/media.js +331 -0
- package/dist/workstation/desktopControl/nativeDeviceAdmission.js +207 -0
- package/dist/workstation/desktopControl/nativeProvider.js +1044 -0
- package/dist/workstation/desktopControl/networkEvidence.js +43 -0
- package/dist/workstation/desktopControl/overview.js +58 -0
- package/dist/workstation/desktopControl/ownershipProtocol.js +31 -0
- package/dist/workstation/desktopControl/protocol.js +311 -0
- package/dist/workstation/desktopControl/provider.js +1 -0
- package/dist/workstation/desktopControl/quicMedia.js +240 -0
- package/dist/workstation/desktopControl/rateController.js +226 -0
- package/dist/workstation/desktopControl/supervisor.js +1774 -0
- package/dist/workstation/desktopControl/transport.js +27 -0
- package/dist/workstation/desktopControl/wallpaper.js +98 -0
- package/dist/workstation/exec/policy.js +5 -0
- package/dist/workstation/files/client.js +10 -1
- package/dist/workstation/files/credentials.js +142 -0
- package/dist/workstation/files/host.js +9 -2
- package/dist/workstation/files/text.js +42 -8
- package/dist/workstation/journal.js +5 -2
- package/dist/workstation/keepers/acp/client.js +38 -2
- package/dist/workstation/keepers/kit/buildVersion.js +35 -11
- package/dist/workstation/keepers/kit/bundle.js +21 -17
- package/dist/workstation/keepers/kit/client.js +2 -2
- package/dist/workstation/keepers/kit/launcher.js +123 -7
- package/dist/workstation/keepers/kit/paths.js +2 -1
- package/dist/workstation/keepers/legacy.js +16 -2
- package/dist/workstation/keepers/policy.js +5 -34
- package/dist/workstation/keepers/terminal/client.js +115 -23
- package/dist/workstation/keepers/terminal/exec.js +6 -3
- package/dist/workstation/keepers/terminal/frames.js +73 -0
- package/dist/workstation/keepers/terminal/generations.js +5 -8
- package/dist/workstation/keepers/terminal/gridProtocol.js +16 -0
- package/dist/workstation/keepers/terminal/input.js +146 -0
- package/dist/workstation/keepers/terminal/keeper.js +22 -8
- package/dist/workstation/keepers/terminal/main.js +1 -1
- package/dist/workstation/keepers/terminal/metadata.js +138 -0
- package/dist/workstation/keepers/terminal/protocol.js +4 -28
- package/dist/workstation/keepers/terminal/pty.js +1 -330
- package/dist/workstation/keepers/terminal/screen.js +246 -0
- package/dist/workstation/keepers/terminal/sessions.js +526 -0
- package/dist/workstation/managedInstall.js +57 -0
- package/dist/workstation/oauth/deviceAuthorization.js +31 -17
- package/dist/workstation/protocol/agent.js +2 -15
- package/dist/workstation/protocol/management.js +113 -5
- package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
- package/dist/workstation/rd/artifact.js +120 -0
- package/dist/workstation/rd/control.js +117 -0
- package/dist/workstation/rd/display.js +29 -0
- package/dist/workstation/rd/host.js +213 -0
- package/dist/workstation/rd/migrationReadiness.js +46 -0
- package/dist/workstation/rd/provider.js +256 -0
- package/dist/workstation/rd/sessions.js +391 -0
- package/dist/workstation/rd/state.js +57 -0
- package/dist/workstation/rd/update.js +168 -0
- package/dist/workstation/rd/upkeep.js +80 -0
- package/dist/workstation/register.js +27 -37
- package/dist/workstation/registrations/enrollment.js +250 -0
- package/dist/workstation/registrations/enrollmentPending.js +361 -0
- package/dist/workstation/registrations/migration.js +356 -0
- package/dist/workstation/registrations/model.js +34 -0
- package/dist/workstation/registrations/removal.js +222 -0
- package/dist/workstation/registrations/store.js +737 -0
- package/dist/workstation/runtime/desktopBinding.js +20 -0
- package/dist/workstation/runtime/deviceDesktop.js +193 -0
- package/dist/workstation/runtime/deviceRuntime.js +568 -0
- package/dist/workstation/runtime/deviceServices.js +117 -0
- package/dist/workstation/runtime/events.js +20 -0
- package/dist/workstation/runtime/ownership.js +100 -0
- package/dist/workstation/runtime/registrationRuntime.js +575 -0
- package/dist/workstation/runtime/remoteDesktopAdapter.js +186 -0
- package/dist/workstation/runtime/removalActivity.js +184 -0
- package/dist/workstation/service/installer.js +197 -14
- package/dist/workstation/service/launchd.js +2 -0
- package/dist/workstation/service/systemd.js +1 -0
- package/dist/workstation/storageMetrics.js +118 -0
- package/dist/workstation/systemMetrics.js +47 -92
- package/dist/workstation/toolchain.js +94 -0
- package/dist/workstation/tools/browser.js +28 -4
- package/dist/workstation/tools/client.js +8 -27
- package/dist/workstation/tools/info.js +6 -13
- package/dist/workstation/tools/terminalRouter.js +17 -172
- package/dist/workstation/tools/validation.js +55 -1
- package/dist/workstation/update/archiveEvidence.js +206 -0
- package/dist/workstation/update/artifact.js +2 -1
- package/dist/workstation/update/coordinator.js +34 -13
- package/dist/workstation/update/deviceCompatibility.js +119 -0
- package/dist/workstation/update/deviceCoordinator.js +369 -0
- package/dist/workstation/update/deviceHealth.js +104 -0
- package/dist/workstation/update/deviceHealthProtocol.js +26 -0
- package/dist/workstation/update/deviceRecoveryLifecycle.js +46 -0
- package/dist/workstation/update/deviceRollback.js +78 -0
- package/dist/workstation/update/globalInstall.js +186 -0
- package/dist/workstation/update/legacyBridge.js +414 -0
- package/dist/workstation/update/legacyOwner.js +39 -0
- package/dist/workstation/update/npmInstaller.js +51 -25
- package/dist/workstation/update/privateDurability.js +61 -0
- package/dist/workstation/update/rdRollback.js +145 -0
- package/dist/workstation/update/recoveryPlan.js +124 -0
- package/dist/workstation/update/runUpdate.js +10 -3
- package/dist/workstation/update/taskRegistry.js +161 -3
- package/dist/workstation/update/watchdog.js +29806 -333
- package/dist/workstation/update/watchdogProcess.js +72 -0
- package/package.json +11 -7
- package/skills/axom/SKILL.md +11 -11
- package/skills/axom-definition/SKILL.md +3 -3
- package/skills/axom-platform/SKILL.md +3 -3
- package/dist/axom-patch/cli/probes/qoder-steering.mjs +0 -89
- package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +0 -61
- package/dist/axom-patch/cli/qoder-steering.json +0 -52
- package/dist/axom-patch/cli/qoder-subagent-origin.json +0 -73
- package/dist/commands/guide.js +0 -38
- package/dist/runtime-deps/qoder-sdk/package-lock.json +0 -1189
- package/dist/runtime-deps/qoder-sdk/package.json +0 -8
- package/dist/workstation/acp/delegationMcp.js +0 -24
- package/dist/workstation/acp/processor/sideChannel.js +0 -80
- package/dist/workstation/acp/qoderStartup.js +0 -45
- package/dist/workstation/acp/test/raw/qoderCaptures.js +0 -125
- package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -118
- package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -193
- package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +0 -372
- package/dist/workstation/qoderAdapter/callbackContext.js +0 -76
- package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
- package/dist/workstation/qoderAdapter/contract.js +0 -78
- package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -36
- package/dist/workstation/qoderAdapter/history.js +0 -85
- package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
- package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
- package/dist/workstation/qoderAdapter/main.js +0 -95
- package/dist/workstation/qoderAdapter/mapper.js +0 -117
- package/dist/workstation/qoderAdapter/observerPlugin.js +0 -245
- package/dist/workstation/qoderAdapter/observerReceiver.js +0 -187
- package/dist/workstation/qoderAdapter/permissions.js +0 -61
- package/dist/workstation/qoderAdapter/profile.js +0 -93
- package/dist/workstation/qoderAdapter/sdkDriver.js +0 -48
- package/dist/workstation/qoderAdapter/sdkRuntime.js +0 -93
- package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +0 -145
- package/dist/workstation/qoderAdapter/session.js +0 -319
- package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
- package/dist/workstation/qoderAdapter/subwork.js +0 -85
- package/dist/workstation/qoderAdapter/taskControl.js +0 -38
- package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
- package/dist/workstation/qoderAdapter/usage.js +0 -37
- /package/dist/workstation/{qoderAdapter/types.js → desktopControl/automationPreparation.js} +0 -0
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const counter = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
|
|
3
|
+
const pathSchema = z.strictObject({
|
|
4
|
+
sequence: counter.min(1), pathGeneration: counter.min(1),
|
|
5
|
+
rttUs: counter, minRttUs: counter, bandwidthBps: counter.nullable(),
|
|
6
|
+
cwndBytes: counter, sentPackets: counter, lostPackets: counter,
|
|
7
|
+
congestionEvents: counter, udpTxBytes: counter,
|
|
8
|
+
});
|
|
9
|
+
export function decodeQuicPathStats(bytes, observedAtMs) {
|
|
10
|
+
if (bytes.length > 4096 || !Number.isFinite(observedAtMs) || observedAtMs < 0)
|
|
11
|
+
throw new Error('invalid path stats');
|
|
12
|
+
const stats = pathSchema.parse(JSON.parse(bytes.toString('utf8')));
|
|
13
|
+
if (stats.lostPackets > stats.sentPackets)
|
|
14
|
+
throw new Error('invalid path counters');
|
|
15
|
+
return { ...stats, observedAtMs };
|
|
16
|
+
}
|
|
17
|
+
export class DesktopNetworkEvidence {
|
|
18
|
+
previous;
|
|
19
|
+
state = 'unknown';
|
|
20
|
+
queueMs = 0;
|
|
21
|
+
sample(nowMs, stats, demandBps) {
|
|
22
|
+
if (!stats || stats.observedAtMs > nowMs || nowMs - stats.observedAtMs > Math.max(1500, stats.rttUs / 1000 * 3)) {
|
|
23
|
+
return { state: 'unknown', fresh: false, queueMs: 0 };
|
|
24
|
+
}
|
|
25
|
+
const previous = this.previous;
|
|
26
|
+
const samePath = previous?.id === stats.id && previous.pathGeneration === stats.pathGeneration;
|
|
27
|
+
if (samePath && stats.sequence <= previous.sequence) {
|
|
28
|
+
return { state: this.state, fresh: false, queueMs: this.queueMs, bandwidthBps: previous.bandwidthBps ?? undefined };
|
|
29
|
+
}
|
|
30
|
+
this.previous = stats;
|
|
31
|
+
this.queueMs = Math.max(0, (stats.rttUs - stats.minRttUs) / 1000);
|
|
32
|
+
const queueThresholdMs = Math.max(10, stats.minRttUs / 1000 * 0.1);
|
|
33
|
+
const countersAdvance = samePath && stats.sentPackets >= previous.sentPackets &&
|
|
34
|
+
stats.lostPackets >= previous.lostPackets && stats.congestionEvents >= previous.congestionEvents &&
|
|
35
|
+
stats.udpTxBytes > previous.udpTxBytes;
|
|
36
|
+
const congestion = countersAdvance && stats.congestionEvents > previous.congestionEvents;
|
|
37
|
+
const bandwidth = stats.bandwidthBps ?? undefined;
|
|
38
|
+
const loaded = countersAdvance && bandwidth !== undefined && bandwidth > 0 && demandBps >= bandwidth * 0.85;
|
|
39
|
+
this.state = this.queueMs > queueThresholdMs && (congestion || loaded) ? 'congested'
|
|
40
|
+
: this.queueMs <= queueThresholdMs ? 'usable' : 'unknown';
|
|
41
|
+
return { state: this.state, fresh: true, queueMs: this.queueMs, bandwidthBps: bandwidth };
|
|
42
|
+
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { mediaRequire } from './media.js';
|
|
3
|
+
/** A single initial image, then <=2% of focus target and <=32kbps, including wire headers. */
|
|
4
|
+
export class DesktopOverviewBudget {
|
|
5
|
+
tokens = 32808;
|
|
6
|
+
at = 0;
|
|
7
|
+
lastSent = -Infinity;
|
|
8
|
+
digest;
|
|
9
|
+
initialized = false;
|
|
10
|
+
canCapture(now, targetBps, pressure) {
|
|
11
|
+
this.refill(now, targetBps);
|
|
12
|
+
return !pressure && now - this.lastSent >= 2000 && this.tokens >= 1024;
|
|
13
|
+
}
|
|
14
|
+
get maxSide() { return this.tokens < 8192 ? 320 : 640; }
|
|
15
|
+
eligible(bytes, now, targetBps) {
|
|
16
|
+
this.refill(now, targetBps);
|
|
17
|
+
const digest = createHash('sha256').update(bytes.subarray(16, 28)).update(bytes.subarray(40)).digest('hex');
|
|
18
|
+
if (digest === this.digest || bytes.length > this.tokens || now - this.lastSent < 2000)
|
|
19
|
+
return false;
|
|
20
|
+
return true;
|
|
21
|
+
}
|
|
22
|
+
admit(bytes, now, targetBps) {
|
|
23
|
+
if (!this.eligible(bytes, now, targetBps))
|
|
24
|
+
return false;
|
|
25
|
+
this.tokens -= bytes.length;
|
|
26
|
+
this.lastSent = now;
|
|
27
|
+
this.digest = createHash('sha256').update(bytes.subarray(16, 28)).update(bytes.subarray(40)).digest('hex');
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
disconnected() { this.digest = undefined; }
|
|
31
|
+
refill(now, targetBps) {
|
|
32
|
+
mediaRequire(Number.isFinite(now) && Number.isFinite(targetBps) && targetBps >= 0);
|
|
33
|
+
if (!this.initialized) {
|
|
34
|
+
this.at = now;
|
|
35
|
+
this.initialized = true;
|
|
36
|
+
}
|
|
37
|
+
this.tokens = Math.min(32808, this.tokens + Math.max(0, now - this.at) / 1000 * Math.min(4000, targetBps / 400));
|
|
38
|
+
this.at = now;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
export function overviewPacket(image, sequence, generation) {
|
|
42
|
+
const { width: w, height: h, sourceWidth: sw, sourceHeight: sh, jpeg } = image;
|
|
43
|
+
mediaRequire([w, h, sw, sh, sequence, generation].every(Number.isSafeInteger) && sequence > 0 && generation > 0 &&
|
|
44
|
+
w >= 2 && h >= 2 && w <= 640 && h <= 640 && sw >= w && sh >= h && sw <= 16384 && sh <= 16384 &&
|
|
45
|
+
Math.abs(w / h - sw / sh) <= 2 / h && jpeg.length >= 4 && jpeg.length <= 32768 && jpeg.readUInt16BE(0) === 0xffd8 && jpeg.readUInt16BE(jpeg.length - 2) === 0xffd9);
|
|
46
|
+
const bytes = Buffer.alloc(40 + jpeg.length);
|
|
47
|
+
bytes.write('AXOV');
|
|
48
|
+
bytes[4] = 1;
|
|
49
|
+
bytes.writeBigUInt64BE(BigInt(sequence), 8);
|
|
50
|
+
bytes.writeUInt32BE(sw, 16);
|
|
51
|
+
bytes.writeUInt32BE(sh, 20);
|
|
52
|
+
bytes.writeUInt16BE(w, 24);
|
|
53
|
+
bytes.writeUInt16BE(h, 26);
|
|
54
|
+
bytes.writeUInt32BE(jpeg.length, 28);
|
|
55
|
+
bytes.writeBigUInt64BE(BigInt(generation), 32);
|
|
56
|
+
jpeg.copy(bytes, 40);
|
|
57
|
+
return bytes;
|
|
58
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
const bounded = z.string().min(1).max(128).regex(/^[^\x00-\x1f\x7f]+$/);
|
|
3
|
+
const revision = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
|
|
4
|
+
export const deviceAcquisitionSchema = z.discriminatedUnion('kind', [
|
|
5
|
+
z.strictObject({ kind: z.literal('if_free') }),
|
|
6
|
+
z.strictObject({ kind: z.literal('takeover'), expectedDeviceEpoch: bounded, expectedRevision: revision }),
|
|
7
|
+
]);
|
|
8
|
+
export const deviceOwnershipSnapshotSchema = z.strictObject({ deviceEpoch: bounded, revision,
|
|
9
|
+
state: z.enum(['free', 'draining', 'active']),
|
|
10
|
+
controller: z.strictObject({ registrationId: bounded, controllerId: bounded, kind: bounded }).nullable(),
|
|
11
|
+
});
|
|
12
|
+
export const deviceOwnershipCommandSchema = z.strictObject({ t: z.literal('desktop_ownership'), ownershipProtocol: z.literal(1),
|
|
13
|
+
commandId: z.string().regex(/^[0-9a-f]{32}$/), deviceGeneration: bounded, controllerId: bounded,
|
|
14
|
+
kind: z.enum(['viewer', 'automation']), action: z.enum(['acquire', 'commit', 'release', 'cancel']),
|
|
15
|
+
requestSequence: revision.refine(value => value > 0).optional(), acquireCommandId: z.string().regex(/^[0-9a-f]{32}$/).optional(),
|
|
16
|
+
acquisition: deviceAcquisitionSchema.optional(), deviceEpoch: bounded.optional(), revision: revision.optional(),
|
|
17
|
+
}).refine(value => value.action === 'acquire' ? value.acquisition != null && value.requestSequence != null :
|
|
18
|
+
value.action === 'cancel' ? value.acquireCommandId != null && value.requestSequence != null : value.deviceEpoch != null && value.revision != null);
|
|
19
|
+
const reservationCauseSchema = z.strictObject({ acquireCommandId: z.string().regex(/^[0-9a-f]{32}$/), contenderControllerId: bounded, deviceGeneration: bounded });
|
|
20
|
+
export const deviceOwnershipEventSchema = z.strictObject({ t: z.literal('desktop_ownership_result'), ownershipProtocol: z.literal(1),
|
|
21
|
+
commandId: z.string().regex(/^[0-9a-f]{32}$/), deviceGeneration: bounded, controllerId: bounded,
|
|
22
|
+
status: z.enum(['acquired', 'committed', 'released', 'cancelled', 'busy', 'conflict', 'unsupported', 'lost', 'snapshot']),
|
|
23
|
+
snapshot: deviceOwnershipSnapshotSchema, reservationCause: reservationCauseSchema.optional(),
|
|
24
|
+
}).refine(value => value.reservationCause == null || value.status === 'lost');
|
|
25
|
+
export const matchesDeviceGrant = (left, right) => left.deviceEpoch === right.deviceEpoch && left.revision === right.revision && left.registrationId === right.registrationId &&
|
|
26
|
+
left.controllerId === right.controllerId && left.authorization === right.authorization;
|
|
27
|
+
export function nativeReceiptMatches(handle, commandId, grant, action, receipt) {
|
|
28
|
+
return (action === 'isolate' || handle.current()) && receipt.commandId === commandId && receipt.hostLifetimeId === handle.hostLifetimeId &&
|
|
29
|
+
receipt.action === action && receipt.physical === 'completed' && receipt.admission === (action === 'admit' ? 'active' : 'revoked') &&
|
|
30
|
+
matchesDeviceGrant(grant, receipt.grant);
|
|
31
|
+
}
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { audioConfiguration, audioMode } from './audio.js';
|
|
3
|
+
import { lobbyRequestSchema, lobbyPlanSchema } from './lobby.js';
|
|
4
|
+
import { deviceAcquisitionSchema, deviceOwnershipCommandSchema, deviceOwnershipEventSchema } from './ownershipProtocol.js';
|
|
5
|
+
export const DESKTOP_MAX_JSON_BYTES = 48 * 1024;
|
|
6
|
+
export class DesktopProtocolError extends Error {
|
|
7
|
+
code = 'invalid_request';
|
|
8
|
+
}
|
|
9
|
+
const invalid = () => { throw new DesktopProtocolError('Invalid desktop protocol value'); };
|
|
10
|
+
const int = (min = 0, max = Number.MAX_SAFE_INTEGER) => z.number().int().min(min).max(max);
|
|
11
|
+
const positive = int(1);
|
|
12
|
+
const u32 = int(1, 0xffffffff);
|
|
13
|
+
const id = z.string().regex(/^[0-9a-f]{32}$/);
|
|
14
|
+
const bounded = z.string().min(1).refine(s => Buffer.byteLength(s) <= 128 && !/[\x00-\x1f\x7f]/.test(s));
|
|
15
|
+
const code = z.string().regex(/^[a-z0-9_]{1,128}$/);
|
|
16
|
+
const sourceAxis = int(2, 4096);
|
|
17
|
+
const axis = sourceAxis.refine(n => n % 2 === 0);
|
|
18
|
+
const fps = z.union([z.literal(15), z.literal(30), z.literal(60)]);
|
|
19
|
+
const dpi = int(64, 384);
|
|
20
|
+
const format = z.literal('h264-420-v1');
|
|
21
|
+
const quality = z.enum(['auto', 'text', 'motion']);
|
|
22
|
+
const distinct = (items) => new Set(items.map(i => canonicalDesktopJson(i))).size === items.length;
|
|
23
|
+
const formats = z.array(format).min(1).max(1).refine(distinct);
|
|
24
|
+
const protocols = z.strictObject({ min: int(1, 2147483647), max: int(1, 2147483647) }).refine(p => p.min <= p.max);
|
|
25
|
+
const mode = z.strictObject({ width: axis, height: axis });
|
|
26
|
+
const resizeRange = z.strictObject({ minWidth: axis, minHeight: axis, maxWidth: axis, maxHeight: axis })
|
|
27
|
+
.refine(r => r.minWidth <= r.maxWidth && r.minHeight <= r.maxHeight);
|
|
28
|
+
const display = z.strictObject({ id, label: bounded, width: sourceAxis, height: sourceAxis, revision: u32,
|
|
29
|
+
resize: z.enum(['none', 'modes', 'dynamic']), modes: z.array(mode).max(32).refine(distinct),
|
|
30
|
+
kind: z.enum(['display', 'window']).optional(),
|
|
31
|
+
resizeRange: resizeRange.nullable().optional(), captureViewport: z.boolean().optional(), overview: z.boolean().optional(), capturePixelScale: z.number().min(1).max(16).optional(),
|
|
32
|
+
dpi: dpi.optional(), maxDpi: dpi.optional(), appearanceSync: z.boolean().optional(),
|
|
33
|
+
}).refine(d => (d.capturePixelScale == null || d.captureViewport === true) && (d.resize !== 'modes' || d.modes.length > 0) &&
|
|
34
|
+
(d.dpi == null) === (d.maxDpi == null) && (d.dpi == null || d.dpi <= d.maxDpi) &&
|
|
35
|
+
(d.resize === 'dynamic') === (d.resizeRange != null) &&
|
|
36
|
+
(d.resizeRange == null || d.modes.every(m => m.width >= d.resizeRange.minWidth && m.width <= d.resizeRange.maxWidth &&
|
|
37
|
+
m.height >= d.resizeRange.minHeight && m.height <= d.resizeRange.maxHeight)));
|
|
38
|
+
const encoderFormat = z.strictObject({ format, maxWidth: axis, maxHeight: axis, maxPixels: int(4, 16777216), maxFps: fps, cappedRate: z.boolean() });
|
|
39
|
+
const backend = z.strictObject({ id: bounded, kind: z.enum(['software', 'hardware']),
|
|
40
|
+
formats: z.array(encoderFormat).min(1).max(2).refine(a => distinct(a.map(f => f.format))),
|
|
41
|
+
});
|
|
42
|
+
export const desktopCapabilitiesSchema = z.strictObject({
|
|
43
|
+
deviceOwnershipProtocol: z.literal(1).optional(),
|
|
44
|
+
cursorEmbedded: z.boolean().optional(), cursorVisibility: z.boolean().optional(), lobby: z.boolean().optional(),
|
|
45
|
+
protocols, providerId: bounded, providerVersion: bounded, configured: z.boolean(),
|
|
46
|
+
permission: z.enum(['granted', 'required', 'unknown', 'not_applicable']), health: z.enum(['ready', 'unavailable']), reason: code.nullable(),
|
|
47
|
+
formats, encoderBackends: z.array(backend),
|
|
48
|
+
input: z.strictObject({ pointer: z.boolean(), physicalKeyboard: z.boolean(), unicodeText: z.boolean(), clipboard: z.boolean().optional(), textPaste: z.boolean().optional() }),
|
|
49
|
+
displays: z.array(display).min(1).max(128).refine(a => distinct(a.map(d => d.id))),
|
|
50
|
+
}).refine(c => distinct(c.encoderBackends.map(b => b.id)) && c.encoderBackends.every(b => b.formats.every(f => c.formats.includes(f.format))));
|
|
51
|
+
export const capabilityReportSchema = z.strictObject({ generation: bounded, revision: positive, desktop: desktopCapabilitiesSchema.nullable() });
|
|
52
|
+
export const captureRegion = z.strictObject({ x: int(), y: int(), width: positive, height: positive,
|
|
53
|
+
sourceWidth: sourceAxis, sourceHeight: sourceAxis }).refine(r => r.x + r.width <= r.sourceWidth && r.y + r.height <= r.sourceHeight);
|
|
54
|
+
const focus = z.strictObject({ revision: positive, region: captureRegion });
|
|
55
|
+
const focusRequest = z.strictObject({ commandId: id, controlGeneration: positive, mediaEpoch: u32, displayRevision: u32, focus });
|
|
56
|
+
const selection = z.strictObject({ commandId: id, format, width: axis, height: axis, fpsCeiling: fps, quality, dpi: dpi.optional(), captureRegion: captureRegion.optional(), cursorVisible: z.boolean().optional() });
|
|
57
|
+
const configured = z.strictObject({ commandId: id, deviceGeneration: bounded, controlGeneration: positive, mediaEpoch: u32,
|
|
58
|
+
displayRevision: u32, format, width: axis, height: axis, fpsCeiling: fps, quality, encoderBackend: bounded, rateTargetBps: positive, dpi: dpi.optional(), captureRegion: captureRegion.optional() });
|
|
59
|
+
const ready = z.strictObject({ controlGeneration: positive, mediaEpoch: u32, displayRevision: u32 });
|
|
60
|
+
const command = z.strictObject({ commandId: id });
|
|
61
|
+
const inputBase = { controlGeneration: positive, displayRevision: u32, sequence: positive };
|
|
62
|
+
const point = { x: int(0, 4095), y: int(0, 4095) };
|
|
63
|
+
const text = z.string().refine(s => {
|
|
64
|
+
if (Buffer.byteLength(s) > 32768)
|
|
65
|
+
return false;
|
|
66
|
+
let scalars = 0;
|
|
67
|
+
for (const c of s) {
|
|
68
|
+
const n = c.codePointAt(0);
|
|
69
|
+
if (++scalars > 8192 || n >= 0xd800 && n <= 0xdfff || n < 32 && n !== 9 && n !== 10 || n >= 127 && n <= 159)
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
return true;
|
|
73
|
+
});
|
|
74
|
+
const input = z.discriminatedUnion('action', [
|
|
75
|
+
z.strictObject({ ...inputBase, action: z.literal('pointer'), ...point }),
|
|
76
|
+
z.strictObject({ ...inputBase, action: z.literal('button'), ...point, button: int(1, 3), down: z.boolean() }),
|
|
77
|
+
z.strictObject({ ...inputBase, action: z.literal('wheel'), ...point, wheelX: int(-120, 120), wheelY: int(-120, 120) }),
|
|
78
|
+
z.strictObject({ ...inputBase, action: z.literal('key'), usage: int(4, 231), down: z.boolean() }),
|
|
79
|
+
z.strictObject({ ...inputBase, action: z.literal('release_all') }),
|
|
80
|
+
z.strictObject({ ...inputBase, action: z.literal('text'), operationId: id, paste: z.boolean().optional(), text }),
|
|
81
|
+
]);
|
|
82
|
+
const held = z.strictObject({ controlGeneration: positive, displayRevision: u32, sequence: int(),
|
|
83
|
+
keys: z.array(int(4, 231)).max(228).refine(distinct), buttons: z.array(int(1, 3)).max(3).refine(distinct) });
|
|
84
|
+
const inputResult = z.strictObject({ sequence: positive, outcome: z.enum(['injected', 'rejected', 'unknown']), operationId: id.nullable().optional() })
|
|
85
|
+
.refine(r => r.outcome !== 'unknown' || r.operationId != null);
|
|
86
|
+
const finite = z.number().finite().min(0);
|
|
87
|
+
const feedback = z.strictObject({ epoch: u32, receivedId: int(), decodedId: int(), presentedId: int(), receivedTotalBytes: int(),
|
|
88
|
+
queueDepth: int(0, 2), queueAgeMs: finite, decodeMs: finite, presentMs: finite, exactPresentedSequence: int() }).refine(f => f.receivedId >= f.decodedId && f.decodedId >= f.presentedId);
|
|
89
|
+
const resizeResult = z.strictObject({ commandId: id, width: axis, height: axis, displayRevision: u32, outcome: z.enum(['applied', 'rolled_back', 'failed']), dpi: dpi.optional(), captureRegion: captureRegion.optional() });
|
|
90
|
+
export const desktopErrorCodeSchema = z.enum(['unavailable', 'unknown_capability', 'unsupported_protocol', 'unsupported_client',
|
|
91
|
+
'permission_required', 'busy', 'conflict', 'invalid_request', 'rate_limited', 'startup_timeout', 'encoder_failed', 'decoder_failed',
|
|
92
|
+
'resize_failed', 'transport_failed', 'authorization_lost', 'takeover_lost', 'upgrade_required']);
|
|
93
|
+
const state = z.enum(['REQUESTED', 'PREPARING', 'NEGOTIATING', 'STARTING', 'ACTIVE', 'RECONFIGURING', 'SUSPENDED', 'RECONNECTING', 'FAILED', 'CLOSED']);
|
|
94
|
+
const clipboardChunk = { controlGeneration: positive, requestId: id, revision: int().default(0), offset: int(0, 1048576).default(0),
|
|
95
|
+
total: int(0, 1048576).default(0), data: z.string().max(10924).default('') };
|
|
96
|
+
const validClipboardChunk = (v) => {
|
|
97
|
+
const bytes = Buffer.from(v.data, 'base64');
|
|
98
|
+
return bytes.length <= 8192 && v.offset + bytes.length <= v.total && bytes.toString('base64') === v.data;
|
|
99
|
+
};
|
|
100
|
+
const clipboardRequest = z.strictObject({ ...clipboardChunk, action: z.enum(['read', 'write', 'paste']) })
|
|
101
|
+
.refine(v => validClipboardChunk(v) && (v.action === 'write' || v.data === '' && (v.action !== 'paste' || v.offset === 0 && v.total === 0)));
|
|
102
|
+
const clipboardResult = z.strictObject({ ...clipboardChunk, outcome: z.enum(['ok', 'pending', 'conflict', 'unavailable']) })
|
|
103
|
+
.refine(validClipboardChunk);
|
|
104
|
+
const cacheDependency = z.strictObject({ commandId: id, controlGeneration: positive, mediaEpoch: u32, displayRevision: u32,
|
|
105
|
+
frameId: positive, exactSequence: positive, cacheId: positive, width: int(1, 32), height: int(1, 32) });
|
|
106
|
+
const cacheDependencyResult = z.strictObject({ request: cacheDependency, pixels: z.string().max(5464) }).refine(v => {
|
|
107
|
+
const pixels = Buffer.from(v.pixels, 'base64');
|
|
108
|
+
return pixels.toString('base64') === v.pixels && pixels.length === v.request.width * v.request.height * 4 &&
|
|
109
|
+
pixels.every((byte, i) => i % 4 !== 3 || byte === 255);
|
|
110
|
+
});
|
|
111
|
+
const motionCut = { commandId: id, controlGeneration: positive, mediaEpoch: u32, displayRevision: u32 };
|
|
112
|
+
const motionCutPrepared = { ...motionCut, cutId: positive, pictureThrough: int(0, 0xffffffff), frameThrough: positive };
|
|
113
|
+
const payloadSchemas = {
|
|
114
|
+
audio_select: z.strictObject({ commandId: id, mode: audioMode }),
|
|
115
|
+
audio_ready: z.strictObject({ commandId: id, audioEpoch: u32 }),
|
|
116
|
+
audio_configured: audioConfiguration,
|
|
117
|
+
audio_state: z.strictObject({ commandId: id, state: z.enum(['off', 'buffering', 'playing', 'unavailable', 'suspended']), reason: z.string().regex(/^[a-z0-9_]{1,128}$/).nullable().default(null) }),
|
|
118
|
+
audio_feedback: z.strictObject({ audioEpoch: u32, receivedPackets: int(), latePackets: int(), lostPackets: int(), playedSample: int(), queueAgeMs: z.number().finite().min(0).max(10000), underruns: int() }),
|
|
119
|
+
focus: focusRequest,
|
|
120
|
+
cache_dependency: cacheDependency, cache_dependency_result: cacheDependencyResult,
|
|
121
|
+
focus_result: focusRequest.extend({ outcome: z.enum(['applied', 'failed']) }),
|
|
122
|
+
motion_cut_barrier: z.strictObject(motionCutPrepared),
|
|
123
|
+
motion_cut_request: z.strictObject(motionCut),
|
|
124
|
+
motion_cut_prepare: z.strictObject({ ...motionCut, cutId: positive }),
|
|
125
|
+
motion_cut_prepared: z.strictObject(motionCutPrepared),
|
|
126
|
+
motion_cut_fence: z.strictObject({ ...motionCutPrepared, motionCommittedBytes: int() }),
|
|
127
|
+
motion_cut_ack: z.strictObject({ ...motionCut, cutId: positive }),
|
|
128
|
+
motion_cut_commit: z.strictObject({ ...motionCut, cutId: positive }),
|
|
129
|
+
clipboard_request: clipboardRequest, clipboard_result: clipboardResult,
|
|
130
|
+
offer: desktopCapabilitiesSchema, select: selection, configured, viewer_ready: ready, decoder_ready: ready,
|
|
131
|
+
cursor_visibility: z.strictObject({ commandId: id, visible: z.boolean() }),
|
|
132
|
+
cursor_visibility_result: z.strictObject({ commandId: id, visible: z.boolean(), outcome: z.enum(['applied', 'failed']), mediaEpoch: int().default(0), afterFrameId: int().default(0) }),
|
|
133
|
+
appearance: z.strictObject({ commandId: id, colorScheme: z.enum(['light', 'dark']) }),
|
|
134
|
+
appearance_result: z.strictObject({ commandId: id, colorScheme: z.enum(['light', 'dark']), outcome: z.enum(['applied', 'failed']) }),
|
|
135
|
+
start: command, request_refresh: command, suspend: command, resume: command, stop: command, media_open: command,
|
|
136
|
+
input, held_state: held, input_result: inputResult, feedback,
|
|
137
|
+
credit: z.strictObject({ epoch: u32, motionConsumedBytes: int(), cursorConsumedBytes: int(), exactConsumedBytes: int(), windowBytes: int(196608, 524288) }).refine(v => Number.isSafeInteger(v.motionConsumedBytes + v.cursorConsumedBytes + v.exactConsumedBytes)),
|
|
138
|
+
ping: z.strictObject({ challenge: bounded }), pong: z.strictObject({ challenge: bounded }),
|
|
139
|
+
resize: z.strictObject({ commandId: id, expectedDisplayRevision: u32, width: axis, height: axis, dpi: dpi.optional(), captureRegion: captureRegion.optional() }), resize_result: resizeResult,
|
|
140
|
+
state: z.strictObject({ state, code: desktopErrorCodeSchema.nullable().optional() }),
|
|
141
|
+
error: z.strictObject({ code: desktopErrorCodeSchema, retryable: z.boolean() }),
|
|
142
|
+
};
|
|
143
|
+
export function supportsDesktopMode(display, width, height) {
|
|
144
|
+
if (!Number.isInteger(width) || !Number.isInteger(height) || width < 2 || height < 2 || width > 4096 || height > 4096 || width % 2 || height % 2)
|
|
145
|
+
return false;
|
|
146
|
+
if (width === display.width && height === display.height)
|
|
147
|
+
return true;
|
|
148
|
+
const range = display.resizeRange;
|
|
149
|
+
if (display.resize === 'dynamic')
|
|
150
|
+
return range != null && width >= range.minWidth && width <= range.maxWidth && height >= range.minHeight && height <= range.maxHeight;
|
|
151
|
+
return display.resize === 'modes' && display.modes.some(m => m.width === width && m.height === height);
|
|
152
|
+
}
|
|
153
|
+
const viewer = z.strictObject({ protocols, formats, maxWidth: axis, maxHeight: axis, maxPixels: int(4, 16777216), maxFps: fps, maxDpi: dpi.optional(), appearanceSync: z.boolean().optional(), clipboard: z.boolean().optional(), overview: z.boolean().optional(), cursorControl: z.boolean().optional() });
|
|
154
|
+
const sessionRequest = z.strictObject({ requestId: id, displayId: id, viewer,
|
|
155
|
+
viewport: z.strictObject({ widthDp: z.number().positive().max(16384), heightDp: z.number().positive().max(16384),
|
|
156
|
+
deviceScale: z.number().min(0.5).max(8), orientation: z.enum(['landscape', 'portrait']) }),
|
|
157
|
+
resolution: z.discriminatedUnion('mode', [z.strictObject({ mode: z.literal('fit') }), z.strictObject({ mode: z.literal('fixed'), width: axis, height: axis })]),
|
|
158
|
+
quality, takeover: z.boolean().default(false), lobbyId: id.optional(), planId: id.optional(), mediaTransport: z.enum(['quic', 'wss']).optional(),
|
|
159
|
+
ownershipProtocol: z.literal(1).optional(), expectedOwnershipRevision: int().optional(), expectedDeviceGeneration: bounded.optional(), deviceAcquisition: deviceAcquisitionSchema.optional() })
|
|
160
|
+
.refine(request => (request.lobbyId == null) === (request.planId == null) && (request.lobbyId == null) === (request.mediaTransport == null) &&
|
|
161
|
+
(request.deviceAcquisition?.kind !== 'takeover' || request.takeover && request.ownershipProtocol === 1 && request.expectedOwnershipRevision != null) &&
|
|
162
|
+
(request.expectedOwnershipRevision == null) === (request.expectedDeviceGeneration == null) &&
|
|
163
|
+
(request.expectedOwnershipRevision == null || request.ownershipProtocol === 1 && request.takeover));
|
|
164
|
+
const desktopManagementSchema = z.discriminatedUnion('t', [
|
|
165
|
+
deviceOwnershipCommandSchema,
|
|
166
|
+
deviceOwnershipEventSchema,
|
|
167
|
+
z.strictObject({ t: z.literal('desktop_lobby'), commandId: id, lobbyId: id, deviceGeneration: bounded, request: z.lazy(() => lobbyRequestSchema),
|
|
168
|
+
wssProbeGrant: z.string().regex(/^[A-Za-z0-9_-]{43}$/).nullish(), quicProbeGrant: z.string().regex(/^[A-Za-z0-9_-]{43}$/).nullish(),
|
|
169
|
+
quic: z.strictObject({ port: int(1, 65535), certificate: z.string().min(64).max(2048) }).nullish() }),
|
|
170
|
+
z.strictObject({ t: z.literal('desktop_background'), commandId: id, lobbyId: id, deviceGeneration: bounded,
|
|
171
|
+
planId: id, revision: positive, targetId: id, grant: z.string().regex(/^[A-Za-z0-9_-]{43}$/),
|
|
172
|
+
cachedVersion: z.string().regex(/^[A-Za-z0-9_-]{1,128}$/).nullish(), pixelWidth: int(2, 4096), pixelHeight: int(2, 4096) }),
|
|
173
|
+
z.strictObject({ t: z.literal('desktop_lobby_result'), commandId: id, lobbyId: id, deviceGeneration: bounded,
|
|
174
|
+
plan: z.lazy(() => lobbyPlanSchema).nullable(), code: desktopErrorCodeSchema.nullable() }),
|
|
175
|
+
z.strictObject({ t: z.literal('workstation_capabilities'), ...capabilityReportSchema.shape }),
|
|
176
|
+
z.strictObject({ t: z.literal('workstation_capabilities_request'), generation: bounded }),
|
|
177
|
+
z.strictObject({ t: z.literal('desktop_prepare'), commandId: id, sessionId: id, displayId: id,
|
|
178
|
+
deviceGeneration: bounded, controlGeneration: positive, controlGrant: z.string().regex(/^[A-Za-z0-9_-]{43}$/),
|
|
179
|
+
candidate: z.boolean().optional(), mediaTransport: z.enum(['quic', 'wss']).optional() }),
|
|
180
|
+
z.strictObject({ t: z.literal('desktop_prepare_result'), commandId: id, sessionId: id,
|
|
181
|
+
state: z.enum(['bound', 'failed']), code: desktopErrorCodeSchema.nullable() }),
|
|
182
|
+
z.strictObject({ t: z.literal('desktop_cancel'), commandId: id, sessionId: id, controlGeneration: positive, code: desktopErrorCodeSchema, requireIsolation: z.boolean().optional() }),
|
|
183
|
+
z.strictObject({ t: z.literal('desktop_isolation_result'), commandId: id, sessionId: id, deviceGeneration: bounded, controlGeneration: positive, isolated: z.boolean() }),
|
|
184
|
+
z.strictObject({ t: z.literal('desktop_session_state'), sessionId: id, controlGeneration: positive, state, code: desktopErrorCodeSchema.nullable() }),
|
|
185
|
+
]);
|
|
186
|
+
export function decodeDesktopManagementFrame(source, from) {
|
|
187
|
+
const frame = checked(desktopManagementSchema, source);
|
|
188
|
+
const sender = ['desktop_ownership_result', 'desktop_isolation_result', 'desktop_lobby_result', 'workstation_capabilities', 'desktop_prepare_result', 'desktop_session_state'].includes(frame.t) ? 'device' : 'server';
|
|
189
|
+
if (from && from !== sender)
|
|
190
|
+
return invalid();
|
|
191
|
+
return frame;
|
|
192
|
+
}
|
|
193
|
+
const authors = {
|
|
194
|
+
motion_cut_barrier: ['device', 'server'], motion_cut_request: ['viewer', 'device'], motion_cut_prepare: ['server'], motion_cut_prepared: ['device'], motion_cut_fence: ['server'], motion_cut_ack: ['viewer'], motion_cut_commit: ['server'],
|
|
195
|
+
cursor_visibility: ['viewer'], cursor_visibility_result: ['device'], appearance: ['viewer'], appearance_result: ['device'],
|
|
196
|
+
audio_select: ['viewer'], audio_ready: ['viewer'], audio_feedback: ['viewer'], audio_configured: ['device'], audio_state: ['device'],
|
|
197
|
+
clipboard_result: ['device'], clipboard_request: ['viewer'],
|
|
198
|
+
cache_dependency: ['viewer'], cache_dependency_result: ['device'], focus_result: ['device'], offer: ['device'], configured: ['device'], input_result: ['device'], resize_result: ['device'], media_open: ['device'],
|
|
199
|
+
select: ['viewer'], viewer_ready: ['viewer'], decoder_ready: ['viewer'], input: ['viewer'], held_state: ['viewer'],
|
|
200
|
+
focus: ['viewer'], feedback: ['viewer'], resize: ['viewer'], request_refresh: ['viewer'], suspend: ['viewer'], resume: ['viewer'], stop: ['viewer'],
|
|
201
|
+
start: ['server'], state: ['device', 'server'], error: ['device', 'server'],
|
|
202
|
+
};
|
|
203
|
+
/** JSON.parse runs only after a lexical pass checks duplicate decoded keys and exact integer tokens. */
|
|
204
|
+
export function parseDesktopJson(source, limit = DESKTOP_MAX_JSON_BYTES, integerFields) {
|
|
205
|
+
if (typeof source !== 'string' && source.byteLength > limit)
|
|
206
|
+
invalid();
|
|
207
|
+
const value = typeof source === 'string' ? source : new TextDecoder('utf-8', { fatal: true }).decode(source);
|
|
208
|
+
if (Buffer.byteLength(value) > limit)
|
|
209
|
+
invalid();
|
|
210
|
+
let offset = 0;
|
|
211
|
+
const space = () => { while (/[ \t\r\n]/.test(value[offset] ?? '\0'))
|
|
212
|
+
offset++; };
|
|
213
|
+
const take = (c) => { space(); if (value[offset] !== c)
|
|
214
|
+
return false; offset++; return true; };
|
|
215
|
+
const string = () => {
|
|
216
|
+
space();
|
|
217
|
+
const start = offset;
|
|
218
|
+
if (!take('"'))
|
|
219
|
+
return invalid();
|
|
220
|
+
while (offset < value.length) {
|
|
221
|
+
const c = value[offset++];
|
|
222
|
+
if (c === '\\')
|
|
223
|
+
offset++;
|
|
224
|
+
else if (c === '"')
|
|
225
|
+
return JSON.parse(value.slice(start, offset));
|
|
226
|
+
}
|
|
227
|
+
return invalid();
|
|
228
|
+
};
|
|
229
|
+
const floatFields = new Set(['widthDp', 'heightDp', 'deviceScale', 'capturePixelScale', 'queueAgeMs', 'decodeMs', 'presentMs']);
|
|
230
|
+
const visit = (depth, field = '') => {
|
|
231
|
+
if (depth > 32)
|
|
232
|
+
invalid();
|
|
233
|
+
space();
|
|
234
|
+
if (take('{')) {
|
|
235
|
+
const keys = new Set();
|
|
236
|
+
if (take('}'))
|
|
237
|
+
return;
|
|
238
|
+
do {
|
|
239
|
+
const key = string();
|
|
240
|
+
if (keys.has(key) || !take(':'))
|
|
241
|
+
invalid();
|
|
242
|
+
keys.add(key);
|
|
243
|
+
visit(depth + 1, key);
|
|
244
|
+
} while (take(','));
|
|
245
|
+
if (!take('}'))
|
|
246
|
+
invalid();
|
|
247
|
+
}
|
|
248
|
+
else if (take('[')) {
|
|
249
|
+
if (take(']'))
|
|
250
|
+
return;
|
|
251
|
+
do {
|
|
252
|
+
visit(depth + 1, field);
|
|
253
|
+
} while (take(','));
|
|
254
|
+
if (!take(']'))
|
|
255
|
+
invalid();
|
|
256
|
+
}
|
|
257
|
+
else if (value[offset] === '"') {
|
|
258
|
+
string();
|
|
259
|
+
}
|
|
260
|
+
else {
|
|
261
|
+
const start = offset;
|
|
262
|
+
while (offset < value.length && !/[,\]} \t\r\n]/.test(value[offset]))
|
|
263
|
+
offset++;
|
|
264
|
+
const token = value.slice(start, offset);
|
|
265
|
+
if (/^-?[0-9]/.test(token) && (integerFields ? integerFields.has(field) : !floatFields.has(field))) {
|
|
266
|
+
if (!/^-?(0|[1-9][0-9]*)$/.test(token) || !Number.isSafeInteger(Number(token)))
|
|
267
|
+
invalid();
|
|
268
|
+
}
|
|
269
|
+
if (!token)
|
|
270
|
+
invalid();
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
visit(0);
|
|
274
|
+
space();
|
|
275
|
+
if (offset !== value.length)
|
|
276
|
+
invalid();
|
|
277
|
+
return JSON.parse(value);
|
|
278
|
+
}
|
|
279
|
+
function checked(schema, source) {
|
|
280
|
+
try {
|
|
281
|
+
return schema.parse(parseDesktopJson(source));
|
|
282
|
+
}
|
|
283
|
+
catch {
|
|
284
|
+
return invalid();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
export function decodeCapabilityReport(source) { return checked(capabilityReportSchema, source); }
|
|
288
|
+
export function decodeDesktopSessionRequest(source) { return checked(sessionRequest, source); }
|
|
289
|
+
export function decodeDesktopControl(source, from) {
|
|
290
|
+
try {
|
|
291
|
+
const envelope = z.strictObject({ v: z.literal(1), type: z.string(), sessionId: id, payload: z.unknown() }).parse(parseDesktopJson(source));
|
|
292
|
+
if (!Object.hasOwn(payloadSchemas, envelope.type))
|
|
293
|
+
return invalid();
|
|
294
|
+
const type = envelope.type;
|
|
295
|
+
if (from && authors[type] && !authors[type].includes(from))
|
|
296
|
+
return invalid();
|
|
297
|
+
return { ...envelope, type, payload: payloadSchemas[type].parse(envelope.payload) };
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
return invalid();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
export function encodeDesktopControl(message) {
|
|
304
|
+
const text = JSON.stringify(message);
|
|
305
|
+
decodeDesktopControl(text);
|
|
306
|
+
return text;
|
|
307
|
+
}
|
|
308
|
+
export function canonicalDesktopJson(value) {
|
|
309
|
+
return JSON.stringify(value, (_key, item) => item && typeof item === 'object' && !Array.isArray(item)
|
|
310
|
+
? Object.fromEntries(Object.entries(item).sort(([a], [b]) => a.localeCompare(b))) : item);
|
|
311
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|