@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.
Files changed (258) hide show
  1. package/README.md +25 -6
  2. package/dist/axom-patch/cli/README.md +129 -12
  3. package/dist/axom-patch/cli/account-limits/generate.py +15 -12
  4. package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +20 -3
  5. package/dist/axom-patch/cli/claude-account-limits.json +9 -9
  6. package/dist/axom-patch/cli/claude-replay-lifecycle.json +16 -0
  7. package/dist/axom-patch/cli/codex-account-limits.json +11 -11
  8. package/dist/axom-patch/cli/codex-terminal-output.json +16 -0
  9. package/dist/axom-patch/cli/commandcode-account-limits.json +9 -9
  10. package/dist/axom-patch/cli/kimi-quota.mjs +12 -0
  11. package/dist/axom-patch/cli/kimi-subagents.mjs +173 -0
  12. package/dist/axom-patch/cli/nightly-task.md +55 -13
  13. package/dist/axom-patch/cli/probes/claude-replay-lifecycle.mjs +31 -0
  14. package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +33 -0
  15. package/dist/axom-patch/cli/probes/kimi-subagent-subwork.mjs +82 -0
  16. package/dist/axom-patch/cli/probes/zcode-env-account.mjs +81 -0
  17. package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +7 -0
  18. package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1687 -0
  19. package/dist/axom-patch/cli/qoder/releases/1.1.75.json +277 -0
  20. package/dist/axom-patch/cli/qoder-background-terminal.md +13 -6
  21. package/dist/axom-patch/cli/qoder-foreground-output.md +14 -8
  22. package/dist/axom-patch/cli/qoder-native-acp.json +227 -0
  23. package/dist/axom-patch/cli/qoder-steering.md +20 -25
  24. package/dist/axom-patch/cli/qoder-subagent-subwork.md +17 -68
  25. package/dist/axom-patch/cli/runtime.json +19 -19
  26. package/dist/axom-patch/cli/zcode/env-account.mjs +13 -0
  27. package/dist/axom-patch/cli/zcode/generate-env-account.py +52 -0
  28. package/dist/axom-patch/cli/zcode-account-limits.json +10 -11
  29. package/dist/axom-patch/cli/zcode-env-account.json +58 -0
  30. package/dist/axom-patch/cli/zcode-models.json +9 -10
  31. package/dist/axom-patch/cli/zcode-subagent-subwork.json +10 -11
  32. package/dist/cli.js +11 -45
  33. package/dist/commands/auth.js +4 -1
  34. package/dist/commands/knowledge.js +47 -0
  35. package/dist/commands/update.js +126 -3
  36. package/dist/commands/workstation.js +101 -164
  37. package/dist/commands/workstationRegistrations.js +116 -0
  38. package/dist/credentialBroker.js +13 -2
  39. package/dist/credentials.js +16 -0
  40. package/dist/install/claude/commands.js +15 -15
  41. package/dist/keepers/terminal/keeper.mjs +1089 -673
  42. package/dist/keepers/terminal/manifest.json +4 -4
  43. package/dist/mcp.js +1 -1
  44. package/dist/native/darwin-x64/axom-quic +0 -0
  45. package/dist/workstation/acp/acpAgents.js +113 -102
  46. package/dist/workstation/acp/acpClient.js +26 -0
  47. package/dist/workstation/acp/builds/install/npm.js +99 -10
  48. package/dist/workstation/acp/initializeResponse.js +6 -1
  49. package/dist/workstation/acp/managedPatches.js +189 -25
  50. package/dist/workstation/acp/managedRuntime.js +314 -0
  51. package/dist/workstation/acp/outbox.js +12 -5
  52. package/dist/workstation/acp/patchAssets.js +40 -0
  53. package/dist/workstation/acp/permission.js +2 -2
  54. package/dist/workstation/acp/processor/checkpoint.js +26 -34
  55. package/dist/workstation/acp/processor/control.js +7 -80
  56. package/dist/workstation/acp/processor/jsonrpc.js +8 -0
  57. package/dist/workstation/acp/processor/runtime.js +5 -81
  58. package/dist/workstation/acp/processor/session.js +54 -82
  59. package/dist/workstation/acp/processor/state.js +7 -13
  60. package/dist/workstation/acp/processor/step.js +3 -20
  61. package/dist/workstation/acp/processor/test/fixtureLog.js +9 -16
  62. package/dist/workstation/acp/processor/transport/local.js +19 -3
  63. package/dist/workstation/acp/processor/updates.js +156 -105
  64. package/dist/workstation/acp/processorSession.js +20 -173
  65. package/dist/workstation/acp/registry/catalog.js +65 -39
  66. package/dist/workstation/acp/registry/launch.js +16 -1
  67. package/dist/workstation/acp/switching.js +0 -3
  68. package/dist/workstation/acp/test/fakeAgent.js +13 -0
  69. package/dist/workstation/acp/test/keeperWorld.js +32 -3
  70. package/dist/workstation/acp/test/raw/sources.js +16 -130
  71. package/dist/workstation/acp/test/realAdapter.js +1 -47
  72. package/dist/workstation/acp/test/scriptAgent.js +11 -3
  73. package/dist/workstation/agentFlow.js +1 -1
  74. package/dist/workstation/agentSessions.js +114 -166
  75. package/dist/workstation/channels/agent.js +9 -1
  76. package/dist/workstation/channels/computer.js +27 -4
  77. package/dist/workstation/channels/management.js +130 -15
  78. package/dist/workstation/channels/url.js +1 -1
  79. package/dist/workstation/config.js +33 -9
  80. package/dist/workstation/connection/encryptedChannel.js +4 -1
  81. package/dist/workstation/control/bindingStore.js +204 -0
  82. package/dist/workstation/control/commands.js +125 -0
  83. package/dist/workstation/control/daemonIdentity.js +143 -0
  84. package/dist/workstation/control/deviceCommands.js +191 -0
  85. package/dist/workstation/control/deviceProtocol.js +68 -0
  86. package/dist/workstation/control/enrollment.js +106 -0
  87. package/dist/workstation/control/inspect.js +71 -0
  88. package/dist/workstation/control/locks.js +110 -0
  89. package/dist/workstation/control/protocol.js +40 -0
  90. package/dist/workstation/control/restartGuard.js +46 -0
  91. package/dist/workstation/control/rootFormat.js +16 -0
  92. package/dist/workstation/control/runtimeClient.js +152 -0
  93. package/dist/workstation/control/runtimeHost.js +235 -0
  94. package/dist/workstation/control/serverIdentity.js +35 -0
  95. package/dist/workstation/control/updateTransition.js +91 -0
  96. package/dist/workstation/daemon.js +266 -264
  97. package/dist/workstation/daemonctl.js +226 -25
  98. package/dist/workstation/desktopControl/audio.js +99 -0
  99. package/dist/workstation/desktopControl/automationDevice.js +219 -0
  100. package/dist/workstation/desktopControl/automationJson.js +74 -0
  101. package/dist/workstation/desktopControl/automationLocalState.js +23 -0
  102. package/dist/workstation/desktopControl/automationProtocol.js +253 -0
  103. package/dist/workstation/desktopControl/automationSchema.generated.js +1 -0
  104. package/dist/workstation/desktopControl/automationSession.js +81 -0
  105. package/dist/workstation/desktopControl/automationTransport.js +138 -0
  106. package/dist/workstation/desktopControl/capabilities.js +93 -0
  107. package/dist/workstation/desktopControl/captureModes.js +9 -0
  108. package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
  109. package/dist/workstation/desktopControl/deviceAdmission.js +229 -0
  110. package/dist/workstation/desktopControl/deviceOwnership.js +130 -0
  111. package/dist/workstation/desktopControl/deviceSockets.js +246 -0
  112. package/dist/workstation/desktopControl/helperProcess.js +602 -0
  113. package/dist/workstation/desktopControl/hostGrant.js +65 -0
  114. package/dist/workstation/desktopControl/lobby.js +131 -0
  115. package/dist/workstation/desktopControl/macProvider.js +156 -0
  116. package/dist/workstation/desktopControl/media.js +331 -0
  117. package/dist/workstation/desktopControl/nativeDeviceAdmission.js +207 -0
  118. package/dist/workstation/desktopControl/nativeProvider.js +1044 -0
  119. package/dist/workstation/desktopControl/networkEvidence.js +43 -0
  120. package/dist/workstation/desktopControl/overview.js +58 -0
  121. package/dist/workstation/desktopControl/ownershipProtocol.js +31 -0
  122. package/dist/workstation/desktopControl/protocol.js +311 -0
  123. package/dist/workstation/desktopControl/provider.js +1 -0
  124. package/dist/workstation/desktopControl/quicMedia.js +240 -0
  125. package/dist/workstation/desktopControl/rateController.js +226 -0
  126. package/dist/workstation/desktopControl/supervisor.js +1774 -0
  127. package/dist/workstation/desktopControl/transport.js +27 -0
  128. package/dist/workstation/desktopControl/wallpaper.js +98 -0
  129. package/dist/workstation/exec/policy.js +5 -0
  130. package/dist/workstation/files/client.js +10 -1
  131. package/dist/workstation/files/credentials.js +142 -0
  132. package/dist/workstation/files/host.js +9 -2
  133. package/dist/workstation/files/text.js +42 -8
  134. package/dist/workstation/journal.js +5 -2
  135. package/dist/workstation/keepers/acp/client.js +38 -2
  136. package/dist/workstation/keepers/kit/buildVersion.js +35 -11
  137. package/dist/workstation/keepers/kit/bundle.js +21 -17
  138. package/dist/workstation/keepers/kit/client.js +2 -2
  139. package/dist/workstation/keepers/kit/launcher.js +123 -7
  140. package/dist/workstation/keepers/kit/paths.js +2 -1
  141. package/dist/workstation/keepers/legacy.js +16 -2
  142. package/dist/workstation/keepers/policy.js +5 -34
  143. package/dist/workstation/keepers/terminal/client.js +115 -23
  144. package/dist/workstation/keepers/terminal/exec.js +6 -3
  145. package/dist/workstation/keepers/terminal/frames.js +73 -0
  146. package/dist/workstation/keepers/terminal/generations.js +5 -8
  147. package/dist/workstation/keepers/terminal/gridProtocol.js +16 -0
  148. package/dist/workstation/keepers/terminal/input.js +146 -0
  149. package/dist/workstation/keepers/terminal/keeper.js +22 -8
  150. package/dist/workstation/keepers/terminal/main.js +1 -1
  151. package/dist/workstation/keepers/terminal/metadata.js +138 -0
  152. package/dist/workstation/keepers/terminal/protocol.js +4 -28
  153. package/dist/workstation/keepers/terminal/pty.js +1 -330
  154. package/dist/workstation/keepers/terminal/screen.js +246 -0
  155. package/dist/workstation/keepers/terminal/sessions.js +526 -0
  156. package/dist/workstation/managedInstall.js +57 -0
  157. package/dist/workstation/oauth/deviceAuthorization.js +31 -17
  158. package/dist/workstation/protocol/agent.js +2 -15
  159. package/dist/workstation/protocol/management.js +113 -5
  160. package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
  161. package/dist/workstation/rd/artifact.js +120 -0
  162. package/dist/workstation/rd/control.js +117 -0
  163. package/dist/workstation/rd/display.js +29 -0
  164. package/dist/workstation/rd/host.js +213 -0
  165. package/dist/workstation/rd/migrationReadiness.js +46 -0
  166. package/dist/workstation/rd/provider.js +256 -0
  167. package/dist/workstation/rd/sessions.js +391 -0
  168. package/dist/workstation/rd/state.js +57 -0
  169. package/dist/workstation/rd/update.js +168 -0
  170. package/dist/workstation/rd/upkeep.js +80 -0
  171. package/dist/workstation/register.js +27 -37
  172. package/dist/workstation/registrations/enrollment.js +250 -0
  173. package/dist/workstation/registrations/enrollmentPending.js +361 -0
  174. package/dist/workstation/registrations/migration.js +356 -0
  175. package/dist/workstation/registrations/model.js +34 -0
  176. package/dist/workstation/registrations/removal.js +222 -0
  177. package/dist/workstation/registrations/store.js +737 -0
  178. package/dist/workstation/runtime/desktopBinding.js +20 -0
  179. package/dist/workstation/runtime/deviceDesktop.js +193 -0
  180. package/dist/workstation/runtime/deviceRuntime.js +568 -0
  181. package/dist/workstation/runtime/deviceServices.js +117 -0
  182. package/dist/workstation/runtime/events.js +20 -0
  183. package/dist/workstation/runtime/ownership.js +100 -0
  184. package/dist/workstation/runtime/registrationRuntime.js +575 -0
  185. package/dist/workstation/runtime/remoteDesktopAdapter.js +186 -0
  186. package/dist/workstation/runtime/removalActivity.js +184 -0
  187. package/dist/workstation/service/installer.js +197 -14
  188. package/dist/workstation/service/launchd.js +2 -0
  189. package/dist/workstation/service/systemd.js +1 -0
  190. package/dist/workstation/storageMetrics.js +118 -0
  191. package/dist/workstation/systemMetrics.js +47 -92
  192. package/dist/workstation/toolchain.js +94 -0
  193. package/dist/workstation/tools/browser.js +28 -4
  194. package/dist/workstation/tools/client.js +8 -27
  195. package/dist/workstation/tools/info.js +6 -13
  196. package/dist/workstation/tools/terminalRouter.js +17 -172
  197. package/dist/workstation/tools/validation.js +55 -1
  198. package/dist/workstation/update/archiveEvidence.js +206 -0
  199. package/dist/workstation/update/artifact.js +2 -1
  200. package/dist/workstation/update/coordinator.js +34 -13
  201. package/dist/workstation/update/deviceCompatibility.js +119 -0
  202. package/dist/workstation/update/deviceCoordinator.js +369 -0
  203. package/dist/workstation/update/deviceHealth.js +104 -0
  204. package/dist/workstation/update/deviceHealthProtocol.js +26 -0
  205. package/dist/workstation/update/deviceRecoveryLifecycle.js +46 -0
  206. package/dist/workstation/update/deviceRollback.js +78 -0
  207. package/dist/workstation/update/globalInstall.js +186 -0
  208. package/dist/workstation/update/legacyBridge.js +414 -0
  209. package/dist/workstation/update/legacyOwner.js +39 -0
  210. package/dist/workstation/update/npmInstaller.js +51 -25
  211. package/dist/workstation/update/privateDurability.js +61 -0
  212. package/dist/workstation/update/rdRollback.js +145 -0
  213. package/dist/workstation/update/recoveryPlan.js +124 -0
  214. package/dist/workstation/update/runUpdate.js +10 -3
  215. package/dist/workstation/update/taskRegistry.js +161 -3
  216. package/dist/workstation/update/watchdog.js +29806 -333
  217. package/dist/workstation/update/watchdogProcess.js +72 -0
  218. package/package.json +11 -7
  219. package/skills/axom/SKILL.md +11 -11
  220. package/skills/axom-definition/SKILL.md +3 -3
  221. package/skills/axom-platform/SKILL.md +3 -3
  222. package/dist/axom-patch/cli/probes/qoder-steering.mjs +0 -89
  223. package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +0 -61
  224. package/dist/axom-patch/cli/qoder-steering.json +0 -52
  225. package/dist/axom-patch/cli/qoder-subagent-origin.json +0 -73
  226. package/dist/commands/guide.js +0 -38
  227. package/dist/runtime-deps/qoder-sdk/package-lock.json +0 -1189
  228. package/dist/runtime-deps/qoder-sdk/package.json +0 -8
  229. package/dist/workstation/acp/delegationMcp.js +0 -24
  230. package/dist/workstation/acp/processor/sideChannel.js +0 -80
  231. package/dist/workstation/acp/qoderStartup.js +0 -45
  232. package/dist/workstation/acp/test/raw/qoderCaptures.js +0 -125
  233. package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -118
  234. package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -193
  235. package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +0 -372
  236. package/dist/workstation/qoderAdapter/callbackContext.js +0 -76
  237. package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
  238. package/dist/workstation/qoderAdapter/contract.js +0 -78
  239. package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -36
  240. package/dist/workstation/qoderAdapter/history.js +0 -85
  241. package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
  242. package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
  243. package/dist/workstation/qoderAdapter/main.js +0 -95
  244. package/dist/workstation/qoderAdapter/mapper.js +0 -117
  245. package/dist/workstation/qoderAdapter/observerPlugin.js +0 -245
  246. package/dist/workstation/qoderAdapter/observerReceiver.js +0 -187
  247. package/dist/workstation/qoderAdapter/permissions.js +0 -61
  248. package/dist/workstation/qoderAdapter/profile.js +0 -93
  249. package/dist/workstation/qoderAdapter/sdkDriver.js +0 -48
  250. package/dist/workstation/qoderAdapter/sdkRuntime.js +0 -93
  251. package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +0 -145
  252. package/dist/workstation/qoderAdapter/session.js +0 -319
  253. package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
  254. package/dist/workstation/qoderAdapter/subwork.js +0 -85
  255. package/dist/workstation/qoderAdapter/taskControl.js +0 -38
  256. package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
  257. package/dist/workstation/qoderAdapter/usage.js +0 -37
  258. /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 {};