@platless/axom-cli 0.3.13 → 0.6.36

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 (320) hide show
  1. package/README.md +18 -5
  2. package/dist/axom-patch/cli/README.md +119 -9
  3. package/dist/axom-patch/cli/account-limits/README.md +27 -0
  4. package/dist/axom-patch/cli/account-limits/antigravity-proxy.mjs +47 -0
  5. package/dist/axom-patch/cli/account-limits/antigravity-read.mjs +90 -0
  6. package/dist/axom-patch/cli/account-limits/antigravity.mjs +46 -0
  7. package/dist/axom-patch/cli/account-limits/generate.py +123 -0
  8. package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +81 -0
  9. package/dist/axom-patch/cli/account-limits/normalize.mjs +151 -0
  10. package/dist/axom-patch/cli/claude-account-limits.json +37 -0
  11. package/dist/axom-patch/cli/codex-account-limits.json +37 -0
  12. package/dist/axom-patch/cli/codex-terminal-output.json +16 -0
  13. package/dist/axom-patch/cli/commandcode-account-limits.json +30 -0
  14. package/dist/axom-patch/cli/kimi-quota.mjs +12 -0
  15. package/dist/axom-patch/cli/kimi-subagents.mjs +173 -0
  16. package/dist/axom-patch/cli/nightly-task.md +194 -0
  17. package/dist/axom-patch/cli/pi-subagent-subwork.json +23 -0
  18. package/dist/axom-patch/cli/probes/claude-late-async-terminal.mjs +5 -1
  19. package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +33 -0
  20. package/dist/axom-patch/cli/probes/kimi-subagent-subwork.mjs +82 -0
  21. package/dist/axom-patch/cli/probes/pi-subagent-subwork.mjs +211 -0
  22. package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +257 -0
  23. package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1591 -0
  24. package/dist/axom-patch/cli/qoder/releases/1.1.75.json +360 -0
  25. package/dist/axom-patch/cli/qoder-background-terminal.md +13 -5
  26. package/dist/axom-patch/cli/qoder-foreground-output.md +14 -7
  27. package/dist/axom-patch/cli/qoder-native-acp.json +227 -0
  28. package/dist/axom-patch/cli/qoder-steering.md +20 -25
  29. package/dist/axom-patch/cli/qoder-subagent-subwork.md +17 -68
  30. package/dist/axom-patch/cli/runtime.json +39 -15
  31. package/dist/axom-patch/cli/zcode-account-limits.json +30 -0
  32. package/dist/axom-patch/cli/zcode-models.json +37 -0
  33. package/dist/axom-patch/cli/zcode-subagent-subwork.json +72 -0
  34. package/dist/cli.js +4 -9
  35. package/dist/commands/knowledge.js +47 -0
  36. package/dist/commands/update.js +65 -3
  37. package/dist/commands/workstation.js +73 -7
  38. package/dist/install/claude/commands.js +13 -13
  39. package/dist/keepers/acp/keeper.mjs +3083 -0
  40. package/dist/keepers/acp/manifest.json +9 -0
  41. package/dist/keepers/terminal/keeper.mjs +8469 -0
  42. package/dist/keepers/terminal/manifest.json +9 -0
  43. package/dist/keepers/terminal/node_modules/node-pty/lib/conpty_console_list_agent.js +25 -0
  44. package/dist/keepers/terminal/node_modules/node-pty/lib/eventEmitter2.js +47 -0
  45. package/dist/keepers/terminal/node_modules/node-pty/lib/index.js +52 -0
  46. package/dist/keepers/terminal/node_modules/node-pty/lib/interfaces.js +7 -0
  47. package/dist/keepers/terminal/node_modules/node-pty/lib/shared/conout.js +10 -0
  48. package/dist/keepers/terminal/node_modules/node-pty/lib/terminal.js +190 -0
  49. package/dist/keepers/terminal/node_modules/node-pty/lib/types.js +7 -0
  50. package/dist/keepers/terminal/node_modules/node-pty/lib/unixTerminal.js +351 -0
  51. package/dist/keepers/terminal/node_modules/node-pty/lib/utils.js +38 -0
  52. package/dist/keepers/terminal/node_modules/node-pty/lib/windowsConoutConnection.js +131 -0
  53. package/dist/keepers/terminal/node_modules/node-pty/lib/windowsPtyAgent.js +344 -0
  54. package/dist/keepers/terminal/node_modules/node-pty/lib/windowsTerminal.js +209 -0
  55. package/dist/keepers/terminal/node_modules/node-pty/lib/worker/conoutSocketWorker.js +22 -0
  56. package/dist/keepers/terminal/node_modules/node-pty/package.json +66 -0
  57. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/pty.node +0 -0
  58. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-arm64/spawn-helper +0 -0
  59. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/pty.node +0 -0
  60. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/darwin-x64/spawn-helper +0 -0
  61. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-arm64/pty.node +0 -0
  62. package/dist/keepers/terminal/node_modules/node-pty/prebuilds/linux-x64/pty.node +0 -0
  63. package/dist/mcp.js +1 -1
  64. package/dist/native/darwin-arm64/axom-quic +0 -0
  65. package/dist/test/keeperKit.js +67 -0
  66. package/dist/workstation/acp/acpAgents.js +2192 -636
  67. package/dist/workstation/acp/acpClient.js +84 -495
  68. package/dist/workstation/acp/adoption.js +10 -396
  69. package/dist/workstation/acp/binaryInstall.js +100 -27
  70. package/dist/workstation/acp/builds/gc.js +245 -0
  71. package/dist/workstation/acp/builds/install/binary.js +28 -0
  72. package/dist/workstation/acp/builds/install/npm.js +324 -0
  73. package/dist/workstation/acp/builds/install/uv.js +254 -0
  74. package/dist/workstation/acp/builds/migrate.js +195 -0
  75. package/dist/workstation/acp/builds/model.js +128 -0
  76. package/dist/workstation/acp/builds/nodeGate.js +100 -0
  77. package/dist/workstation/acp/builds/store.js +352 -0
  78. package/dist/workstation/acp/commandTableProbe.js +68 -0
  79. package/dist/workstation/acp/configOptions.js +103 -1
  80. package/dist/workstation/acp/envSecrets.js +27 -0
  81. package/dist/workstation/acp/initializeResponse.js +290 -0
  82. package/dist/workstation/acp/installErrors.js +12 -2
  83. package/dist/workstation/acp/installProbe.js +1 -1
  84. package/dist/workstation/acp/managedPatches.js +239 -159
  85. package/dist/workstation/acp/managedRuntime.js +314 -0
  86. package/dist/workstation/acp/outbox.js +330 -0
  87. package/dist/workstation/acp/patchAssets.js +60 -3
  88. package/dist/workstation/acp/permission.js +42 -10
  89. package/dist/workstation/acp/processor/checkpoint.js +78 -0
  90. package/dist/workstation/acp/processor/commands.js +35 -0
  91. package/dist/workstation/acp/processor/control.js +282 -0
  92. package/dist/workstation/acp/processor/emit.js +26 -0
  93. package/dist/workstation/acp/processor/jsonrpc.js +172 -0
  94. package/dist/workstation/acp/processor/memoryLog.js +49 -0
  95. package/dist/workstation/acp/processor/origins.js +48 -0
  96. package/dist/workstation/acp/processor/output.js +67 -0
  97. package/dist/workstation/acp/processor/projection.js +42 -0
  98. package/dist/workstation/acp/processor/record.js +128 -0
  99. package/dist/workstation/acp/processor/runtime.js +105 -0
  100. package/dist/workstation/acp/processor/sdkSchemas.js +61 -0
  101. package/dist/workstation/acp/processor/session.js +496 -0
  102. package/dist/workstation/acp/processor/state.js +67 -0
  103. package/dist/workstation/acp/processor/step.js +442 -0
  104. package/dist/workstation/acp/processor/test/fixtureLog.js +58 -0
  105. package/dist/workstation/acp/processor/test/harness.js +120 -0
  106. package/dist/workstation/acp/processor/test/largeIdEcho.js +50 -0
  107. package/dist/workstation/acp/processor/test/rawAgent.js +70 -0
  108. package/dist/workstation/acp/processor/transport/generations.js +76 -0
  109. package/dist/workstation/acp/processor/transport/keeper.js +130 -0
  110. package/dist/workstation/acp/processor/transport/local.js +269 -0
  111. package/dist/workstation/acp/processor/transport/types.js +78 -0
  112. package/dist/workstation/acp/processor/updates.js +390 -0
  113. package/dist/workstation/acp/processorSession.js +1348 -0
  114. package/dist/workstation/acp/registry/catalog.js +189 -41
  115. package/dist/workstation/acp/registry/launch.js +76 -119
  116. package/dist/workstation/acp/sessionUpdateMapper.js +18 -7
  117. package/dist/workstation/acp/steering.js +58 -0
  118. package/dist/workstation/acp/switching.js +111 -0
  119. package/dist/workstation/acp/terminalLogin.js +16 -42
  120. package/dist/workstation/acp/terminalLoginSpec.js +41 -0
  121. package/dist/workstation/acp/test/adapterFixtures.js +256 -0
  122. package/dist/workstation/acp/test/daemonLife.js +106 -0
  123. package/dist/workstation/acp/test/fakeAgent.js +13 -0
  124. package/dist/workstation/acp/test/keeperWorld.js +295 -0
  125. package/dist/workstation/acp/test/memoryOutbox.js +55 -0
  126. package/dist/workstation/acp/test/raw/fixtures.js +196 -0
  127. package/dist/workstation/acp/test/raw/sanitize.js +344 -0
  128. package/dist/workstation/acp/test/raw/sources.js +135 -0
  129. package/dist/workstation/acp/test/realAdapter.js +331 -0
  130. package/dist/workstation/acp/test/scriptAgent.js +214 -0
  131. package/dist/workstation/acp/test/sessionFrames.js +42 -0
  132. package/dist/workstation/acp/timedCommand.js +4 -2
  133. package/dist/workstation/acp/uvxInstall.js +72 -122
  134. package/dist/workstation/agentFlow.js +103 -0
  135. package/dist/workstation/agentSessions.js +1289 -544
  136. package/dist/workstation/channels/agent.js +7 -5
  137. package/dist/workstation/channels/computer.js +69 -21
  138. package/dist/workstation/channels/computerExecs.js +200 -0
  139. package/dist/workstation/channels/management.js +172 -8
  140. package/dist/workstation/channels/url.js +1 -1
  141. package/dist/workstation/config.js +16 -4
  142. package/dist/workstation/connection/encryptedChannel.js +4 -1
  143. package/dist/workstation/control/bindingStore.js +200 -0
  144. package/dist/workstation/control/commands.js +124 -0
  145. package/dist/workstation/control/daemonIdentity.js +53 -0
  146. package/dist/workstation/control/enrollment.js +106 -0
  147. package/dist/workstation/control/inspect.js +71 -0
  148. package/dist/workstation/control/locks.js +63 -0
  149. package/dist/workstation/control/protocol.js +40 -0
  150. package/dist/workstation/control/restartGuard.js +46 -0
  151. package/dist/workstation/control/runtimeClient.js +97 -0
  152. package/dist/workstation/control/runtimeHost.js +148 -0
  153. package/dist/workstation/control/serverIdentity.js +35 -0
  154. package/dist/workstation/daemon.js +393 -12
  155. package/dist/workstation/daemonctl.js +103 -14
  156. package/dist/workstation/desktopControl/audio.js +99 -0
  157. package/dist/workstation/desktopControl/capabilities.js +92 -0
  158. package/dist/workstation/desktopControl/captureModes.js +9 -0
  159. package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
  160. package/dist/workstation/desktopControl/deviceSockets.js +246 -0
  161. package/dist/workstation/desktopControl/helperProcess.js +353 -0
  162. package/dist/workstation/desktopControl/lobby.js +129 -0
  163. package/dist/workstation/desktopControl/macProvider.js +155 -0
  164. package/dist/workstation/desktopControl/media.js +331 -0
  165. package/dist/workstation/desktopControl/nativeProvider.js +1018 -0
  166. package/dist/workstation/desktopControl/networkEvidence.js +43 -0
  167. package/dist/workstation/desktopControl/overview.js +58 -0
  168. package/dist/workstation/desktopControl/protocol.js +302 -0
  169. package/dist/workstation/desktopControl/quicMedia.js +240 -0
  170. package/dist/workstation/desktopControl/rateController.js +226 -0
  171. package/dist/workstation/desktopControl/supervisor.js +1681 -0
  172. package/dist/workstation/desktopControl/transport.js +27 -0
  173. package/dist/workstation/desktopControl/wallpaper.js +78 -0
  174. package/dist/workstation/exec/attach.js +126 -0
  175. package/dist/workstation/exec/command.js +40 -0
  176. package/dist/workstation/exec/durable.js +517 -0
  177. package/dist/workstation/exec/fallback.js +106 -0
  178. package/dist/workstation/exec/policy.js +837 -0
  179. package/dist/workstation/exec/shaping.js +211 -0
  180. package/dist/workstation/exec.js +3 -112
  181. package/dist/workstation/files/adapters/computerFileFrames.js +51 -0
  182. package/dist/workstation/files/adapters/toolRequests.js +121 -0
  183. package/dist/workstation/files/client.js +455 -0
  184. package/dist/workstation/files/credentials.js +142 -0
  185. package/dist/workstation/files/errors.js +28 -0
  186. package/dist/workstation/files/fence.js +350 -0
  187. package/dist/workstation/files/find/fileIndex.js +153 -0
  188. package/dist/workstation/files/find/fuzzy.js +155 -0
  189. package/dist/workstation/files/host.js +122 -0
  190. package/dist/workstation/files/hostProtocol.js +2 -0
  191. package/dist/workstation/files/media/cache.js +620 -0
  192. package/dist/workstation/files/media/encoding.js +308 -0
  193. package/dist/workstation/files/media/estimator.js +100 -0
  194. package/dist/workstation/files/media/media.js +325 -0
  195. package/dist/workstation/files/media/packedAudio.js +23 -0
  196. package/dist/workstation/files/media/scheduler.js +551 -0
  197. package/dist/workstation/files/media/types.js +5 -0
  198. package/dist/workstation/files/scope.js +45 -0
  199. package/dist/workstation/files/structured.js +284 -0
  200. package/dist/workstation/{fileOps.js → files/text.js} +152 -39
  201. package/dist/workstation/files/uploads.js +351 -0
  202. package/dist/workstation/journal.js +65 -236
  203. package/dist/workstation/keepers/acp/client.js +101 -0
  204. package/dist/workstation/keepers/acp/envelope.js +62 -0
  205. package/dist/workstation/keepers/acp/host.js +419 -0
  206. package/dist/workstation/keepers/acp/main.js +18 -0
  207. package/dist/workstation/keepers/acp/memoryStream.js +121 -0
  208. package/dist/workstation/keepers/acp/params.js +137 -0
  209. package/dist/workstation/keepers/acp/protocol.js +66 -0
  210. package/dist/workstation/keepers/acp/recover.js +120 -0
  211. package/dist/workstation/keepers/acp/session.js +584 -0
  212. package/dist/workstation/keepers/acp/stderr.js +154 -0
  213. package/dist/workstation/keepers/kit/activity.js +103 -0
  214. package/dist/workstation/keepers/kit/bootstrap.js +164 -0
  215. package/dist/workstation/keepers/kit/buildVersion.js +51 -0
  216. package/dist/workstation/keepers/kit/bundle.js +124 -0
  217. package/dist/workstation/keepers/kit/client.js +432 -0
  218. package/dist/workstation/keepers/kit/identity.js +21 -0
  219. package/dist/workstation/keepers/kit/ipc.js +112 -0
  220. package/dist/workstation/keepers/kit/launchConfig.js +8 -0
  221. package/dist/workstation/keepers/kit/launcher.js +289 -0
  222. package/dist/workstation/keepers/kit/lock.js +123 -0
  223. package/dist/workstation/keepers/kit/orphanSweep.js +86 -0
  224. package/dist/workstation/keepers/kit/paths.js +76 -0
  225. package/dist/workstation/keepers/kit/protocol.js +43 -0
  226. package/dist/workstation/keepers/kit/server.js +518 -0
  227. package/dist/workstation/keepers/kit/streamLog.js +697 -0
  228. package/dist/workstation/keepers/legacy.js +296 -0
  229. package/dist/workstation/keepers/policy.js +576 -0
  230. package/dist/workstation/keepers/stop.js +34 -0
  231. package/dist/workstation/keepers/terminal/client.js +213 -0
  232. package/dist/workstation/keepers/terminal/exec.js +981 -0
  233. package/dist/workstation/keepers/terminal/generations.js +129 -0
  234. package/dist/workstation/keepers/terminal/keeper.js +62 -0
  235. package/dist/workstation/keepers/terminal/main.js +43 -0
  236. package/dist/workstation/keepers/terminal/orphanSweep.js +3 -0
  237. package/dist/workstation/keepers/terminal/params.js +27 -0
  238. package/dist/workstation/keepers/terminal/protocol.js +56 -0
  239. package/dist/workstation/keepers/terminal/pty.js +330 -0
  240. package/dist/workstation/managedInstall.js +57 -0
  241. package/dist/workstation/protocol/accountLimits.js +47 -0
  242. package/dist/workstation/protocol/agent.js +50 -19
  243. package/dist/workstation/protocol/common.js +30 -0
  244. package/dist/workstation/protocol/computer.js +15 -4
  245. package/dist/workstation/protocol/management.js +141 -4
  246. package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
  247. package/dist/workstation/rd/artifact.js +119 -0
  248. package/dist/workstation/rd/control.js +119 -0
  249. package/dist/workstation/rd/display.js +29 -0
  250. package/dist/workstation/rd/provider.js +222 -0
  251. package/dist/workstation/rd/sessions.js +391 -0
  252. package/dist/workstation/rd/state.js +45 -0
  253. package/dist/workstation/rd/update.js +171 -0
  254. package/dist/workstation/rd/upkeep.js +38 -0
  255. package/dist/workstation/register.js +27 -37
  256. package/dist/workstation/service/installer.js +104 -13
  257. package/dist/workstation/service/launchd.js +1 -0
  258. package/dist/workstation/storageMetrics.js +118 -0
  259. package/dist/workstation/systemMetrics.js +154 -93
  260. package/dist/workstation/toolchain.js +94 -0
  261. package/dist/workstation/tools/browser.js +28 -4
  262. package/dist/workstation/tools/client.js +9 -1
  263. package/dist/workstation/tools/info.js +50 -0
  264. package/dist/workstation/tools/keeperProcess.js +15 -5
  265. package/dist/workstation/tools/router.js +55 -0
  266. package/dist/workstation/tools/terminalRouter.js +222 -0
  267. package/dist/workstation/tools/validation.js +37 -1
  268. package/dist/workstation/update/artifact.js +2 -1
  269. package/dist/workstation/update/coordinator.js +3 -0
  270. package/dist/workstation/update/globalInstall.js +116 -0
  271. package/dist/workstation/update/npmInstaller.js +49 -23
  272. package/dist/workstation/update/ptyPreflight.js +21 -0
  273. package/dist/workstation/update/rdRollback.js +145 -0
  274. package/dist/workstation/update/runUpdate.js +3 -2
  275. package/dist/workstation/update/taskRegistry.js +105 -11
  276. package/dist/workstation/update/watchdog.js +16790 -334
  277. package/package.json +11 -8
  278. package/skills/axom/SKILL.md +11 -11
  279. package/skills/axom-definition/SKILL.md +3 -3
  280. package/skills/axom-platform/SKILL.md +3 -3
  281. package/dist/axom-patch/cli/probes/qoder-steering.mjs +0 -89
  282. package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +0 -49
  283. package/dist/axom-patch/cli/qoder-steering.json +0 -52
  284. package/dist/axom-patch/cli/qoder-subagent-origin.json +0 -73
  285. package/dist/commands/guide.js +0 -38
  286. package/dist/workstation/acp/acpSession.js +0 -1475
  287. package/dist/workstation/acp/contract.js +0 -499
  288. package/dist/workstation/acp/delegationMcp.js +0 -24
  289. package/dist/workstation/acp/draftInterceptor.js +0 -163
  290. package/dist/workstation/acp/qoderStartup.js +0 -45
  291. package/dist/workstation/acp/sessionScope.js +0 -19
  292. package/dist/workstation/filePaths.js +0 -174
  293. package/dist/workstation/fileWorker.js +0 -81
  294. package/dist/workstation/fileWorkerClient.js +0 -121
  295. package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -106
  296. package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -262
  297. package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
  298. package/dist/workstation/qoderAdapter/contract.js +0 -78
  299. package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -30
  300. package/dist/workstation/qoderAdapter/history.js +0 -87
  301. package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
  302. package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
  303. package/dist/workstation/qoderAdapter/main.js +0 -93
  304. package/dist/workstation/qoderAdapter/mapper.js +0 -117
  305. package/dist/workstation/qoderAdapter/observerPlugin.js +0 -245
  306. package/dist/workstation/qoderAdapter/observerReceiver.js +0 -187
  307. package/dist/workstation/qoderAdapter/permissions.js +0 -61
  308. package/dist/workstation/qoderAdapter/profile.js +0 -93
  309. package/dist/workstation/qoderAdapter/sdkDriver.js +0 -49
  310. package/dist/workstation/qoderAdapter/session.js +0 -319
  311. package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
  312. package/dist/workstation/qoderAdapter/subwork.js +0 -77
  313. package/dist/workstation/qoderAdapter/taskControl.js +0 -38
  314. package/dist/workstation/qoderAdapter/taskEvent.js +0 -26
  315. package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
  316. package/dist/workstation/qoderAdapter/usage.js +0 -37
  317. package/dist/workstation/tools/core.js +0 -50
  318. package/dist/workstation/tools/files.js +0 -525
  319. package/dist/workstation/tools/terminal.js +0 -214
  320. /package/dist/workstation/{qoderAdapter/types.js → desktopControl/provider.js} +0 -0
@@ -0,0 +1,1681 @@
1
+ import { DesktopMediaAdmissionCancelled } from './quicMedia.js';
2
+ import { DesktopOverviewBudget, overviewPacket } from './overview.js';
3
+ import { randomBytes } from 'node:crypto';
4
+ import { decodeLobbyRequest, planLobby } from './lobby.js';
5
+ import { setTimeout as dependencyDelay } from 'node:timers/promises';
6
+ import { bounded, NativeCommandError } from './helperProcess.js';
7
+ import { decodeMedia, mediaRequire, DesktopMediaFlowWindow, desktopMediaLane, DesktopDeliveryDelay, decodeConfig } from './media.js';
8
+ import { DesktopRateController } from './rateController.js';
9
+ import { AudioRateController, decodeAudio } from './audio.js';
10
+ import { canonicalDesktopJson, encodeDesktopControl, decodeDesktopManagementFrame, DesktopProtocolError } from './protocol.js';
11
+ const absoluteMotion = (message) => message.type === 'input' && message.payload.action === 'pointer';
12
+ const diagnosticCodes = new Set(['invalid_request', 'unavailable', 'conflict', 'busy', 'rate_limited',
13
+ 'authorization_lost', 'encoder_failed', 'resize_failed', 'transport_failed']);
14
+ /** One authority owner and bounded management work queue; receive/heartbeat never await native I/O. */
15
+ export class DesktopSessionSupervisor {
16
+ options;
17
+ generation;
18
+ active;
19
+ candidates = new Map();
20
+ tail = Promise.resolve();
21
+ pending = 0;
22
+ closed = false;
23
+ failedCleanup = false;
24
+ commands = new Map();
25
+ diagnosticId = 0;
26
+ lobbyJobs = 0;
27
+ lobbyContexts = new Map();
28
+ backgroundTail = Promise.resolve();
29
+ backgroundJobs = 0;
30
+ cleanupJobs = 0;
31
+ maintenance;
32
+ constructor(options) {
33
+ this.options = options;
34
+ }
35
+ async whenIdle(operation) {
36
+ if (this.closed || !this.generation || this.failedCleanup || this.maintenance || this.active || this.candidates.size ||
37
+ this.pending || this.cleanupJobs || this.lobbyJobs || this.backgroundJobs ||
38
+ [...this.lobbyContexts.values()].some(context => context.expires > Date.now()))
39
+ return false;
40
+ const maintenance = Promise.resolve().then(operation);
41
+ this.maintenance = maintenance;
42
+ try {
43
+ await maintenance;
44
+ return true;
45
+ }
46
+ finally {
47
+ if (this.maintenance === maintenance)
48
+ this.maintenance = undefined;
49
+ }
50
+ }
51
+ managementReady(generation) {
52
+ if (this.generation === generation)
53
+ return;
54
+ const previous = this.active;
55
+ this.generation = generation;
56
+ this.commands.clear();
57
+ this.clearLobbies();
58
+ if (previous) {
59
+ this.retire(previous);
60
+ void this.enqueue(() => this.cleanup(previous)).catch(() => { });
61
+ }
62
+ for (const candidate of [...this.candidates.values()]) {
63
+ this.retire(candidate);
64
+ void this.cleanup(candidate).catch(() => { });
65
+ }
66
+ }
67
+ managementClosed(generation) {
68
+ if (this.generation !== generation)
69
+ return;
70
+ this.generation = undefined;
71
+ this.clearLobbies();
72
+ const previous = this.active;
73
+ if (previous) {
74
+ this.retire(previous);
75
+ void this.enqueue(() => this.cleanup(previous)).catch(() => { });
76
+ }
77
+ for (const candidate of [...this.candidates.values()]) {
78
+ this.retire(candidate);
79
+ void this.cleanup(candidate).catch(() => { });
80
+ }
81
+ }
82
+ dispatch(frame, generation) {
83
+ if (this.maintenance)
84
+ return this.maintenance.catch(() => { }).then(() => this.dispatch(frame, generation));
85
+ if (generation !== this.generation)
86
+ return Promise.resolve();
87
+ if (frame.t === 'desktop_background')
88
+ return this.background(frame);
89
+ if (frame.t === 'desktop_lobby')
90
+ return this.planLobby(frame);
91
+ if (frame.t === 'desktop_prepare')
92
+ return this.prepare(frame);
93
+ return this.cancel(frame.sessionId, { commandId: frame.commandId, deviceGeneration: generation, controlGeneration: frame.controlGeneration });
94
+ }
95
+ background(frame) {
96
+ if (this.closed || frame.deviceGeneration !== this.generation || this.backgroundJobs >= 16)
97
+ return Promise.resolve();
98
+ this.backgroundJobs++;
99
+ const current = () => !this.closed && frame.deviceGeneration === this.generation &&
100
+ this.lobbyContexts.get(frame.lobbyId)?.commandId === frame.planId &&
101
+ (this.lobbyContexts.get(frame.lobbyId)?.expires ?? 0) > Date.now();
102
+ const next = this.backgroundTail.then(async () => {
103
+ if (!current() || !this.options.sockets.uploadWallpaper)
104
+ return;
105
+ try {
106
+ const wallpaper = this.options.provider.wallpaper ? await bounded(this.options.provider.wallpaper(frame.targetId), 8000) : null;
107
+ if (current())
108
+ await this.options.sockets.uploadWallpaper(frame.grant, wallpaper, wallpaper?.asset.version === frame.cachedVersion);
109
+ }
110
+ catch {
111
+ if (current())
112
+ await this.options.sockets.uploadWallpaper(frame.grant, null).catch(() => { });
113
+ }
114
+ });
115
+ this.backgroundTail = next.catch(() => { }).finally(() => { this.backgroundJobs--; });
116
+ return next;
117
+ }
118
+ async planLobby(frame) {
119
+ if (this.closed || frame.deviceGeneration !== this.generation || this.lobbyJobs >= 8)
120
+ return;
121
+ for (const [key, context] of this.lobbyContexts)
122
+ if (context.expires <= Date.now()) {
123
+ context.abort.abort();
124
+ this.lobbyContexts.delete(key);
125
+ }
126
+ if (!this.lobbyContexts.has(frame.lobbyId) && this.lobbyContexts.size >= 64)
127
+ return;
128
+ this.lobbyContexts.get(frame.lobbyId)?.abort.abort();
129
+ const context = { commandId: frame.commandId, abort: new AbortController(), expires: Date.now() + 30_000 };
130
+ this.lobbyContexts.set(frame.lobbyId, context);
131
+ const current = () => !this.closed && this.generation === frame.deviceGeneration &&
132
+ this.lobbyContexts.get(frame.lobbyId) === context && !context.abort.signal.aborted;
133
+ this.lobbyJobs++;
134
+ try {
135
+ const request = decodeLobbyRequest(JSON.stringify(frame.request));
136
+ const capabilities = await bounded(this.options.provider.probe(), 4000);
137
+ if (!current())
138
+ return;
139
+ mediaRequire(capabilities != null);
140
+ const plan = planLobby(request, capabilities, frame.lobbyId, frame.commandId, this.options.provider.automaticDpi ?? true);
141
+ const probeAbort = new AbortController();
142
+ const abortProbe = () => probeAbort.abort();
143
+ context.abort.signal.addEventListener('abort', abortProbe, { once: true });
144
+ const binding = { commandId: frame.commandId, sessionId: frame.lobbyId, displayId: plan.targetId,
145
+ deviceGeneration: frame.deviceGeneration, controlGeneration: request.revision, controlGrant: '' };
146
+ const verify = async (grant, quic) => {
147
+ let socket;
148
+ try {
149
+ socket = await bounded(this.options.sockets.media({ ...binding, mediaTransport: quic ? 'quic' : 'wss' }, grant, probeAbort.signal, () => current() && !probeAbort.signal.aborted, quic), 2000);
150
+ }
151
+ catch { }
152
+ finally {
153
+ socket?.close();
154
+ }
155
+ };
156
+ const probing = [frame.wssProbeGrant ? verify(frame.wssProbeGrant) : Promise.resolve(),
157
+ frame.quicProbeGrant && frame.quic ? verify(frame.quicProbeGrant, frame.quic) : Promise.resolve()];
158
+ void Promise.all(probing).finally(() => {
159
+ probeAbort.abort();
160
+ context.abort.signal.removeEventListener('abort', abortProbe);
161
+ });
162
+ if (current())
163
+ this.options.report(frame.deviceGeneration, { t: 'desktop_lobby_result', commandId: frame.commandId, lobbyId: frame.lobbyId,
164
+ deviceGeneration: frame.deviceGeneration, plan, code: null });
165
+ }
166
+ catch {
167
+ if (current())
168
+ this.options.report(frame.deviceGeneration, { t: 'desktop_lobby_result', commandId: frame.commandId, lobbyId: frame.lobbyId,
169
+ deviceGeneration: frame.deviceGeneration, plan: null, code: 'unavailable' });
170
+ }
171
+ finally {
172
+ this.lobbyJobs--;
173
+ }
174
+ }
175
+ clearLobbies() {
176
+ for (const context of this.lobbyContexts.values())
177
+ context.abort.abort();
178
+ this.lobbyContexts.clear();
179
+ }
180
+ enqueue(run) {
181
+ mediaRequire(this.pending < 16);
182
+ this.pending++;
183
+ const next = this.tail.then(run);
184
+ this.tail = next.catch(() => { }).finally(() => { this.pending--; });
185
+ return next;
186
+ }
187
+ prepare(command) {
188
+ if (this.maintenance)
189
+ return this.maintenance.catch(() => { }).then(() => this.prepare(command));
190
+ decodeDesktopManagementFrame(JSON.stringify({ ...command, t: 'desktop_prepare' }), 'server');
191
+ if (this.closed || command.deviceGeneration !== this.generation)
192
+ return Promise.resolve();
193
+ mediaRequire(this.pending < 16);
194
+ const key = `${command.deviceGeneration}:${command.commandId}`, serialized = canonicalDesktopJson(command);
195
+ const previousCommand = this.commands.get(key);
196
+ if (previousCommand) {
197
+ mediaRequire(previousCommand === serialized);
198
+ return Promise.resolve();
199
+ }
200
+ mediaRequire(this.commands.size < 1024);
201
+ this.commands.set(key, serialized);
202
+ mediaRequire(!command.candidate || this.candidates.size < 8);
203
+ const previous = command.candidate ? undefined : this.active;
204
+ if (previous)
205
+ this.retire(previous);
206
+ const s = { diagnosticId: ++this.diagnosticId, diagnosticCount: 0, rateDiagnosticCount: 0, lastRateDiagnosticAt: -Infinity,
207
+ performanceDiagnosticCount: 0, lastPerformanceDiagnosticAt: -Infinity, diagnosticKey: randomBytes(16).toString('hex'), motionEncodeMs: 0,
208
+ binding: { ...command }, abort: new AbortController(), retired: false, viewerReady: false, starting: false,
209
+ startedEpoch: 0, decoderReady: false, presented: false, inputQueue: [], inputRunning: false, activeTasks: 0, commands: new Map(),
210
+ controller: new DesktopRateController({ fpsCeiling: 30, maxFps: 30 }), rateBusy: false, capturePaused: false, lastFeedbackBytes: 0,
211
+ activeBytes: 0, queuedAt: 0, lastPacketAt: performance.now(), receivedFrame: 0, firstCompleteFrame: 0, flow: new DesktopMediaFlowWindow(), delivery: new DesktopDeliveryDelay(), packetLoop: false, mediaWake: new Set(), nativeCreditDirty: false, nativeDisposed: { motion: 0, cursor: 0, exact: 0 }, configAdmittedEpoch: 0, recovery: [],
212
+ overviewBudget: new DesktopOverviewBudget(), overviewSequence: 0, overviewInitialPolls: 0, challenges: new Map(), rtts: [], rtt: 0, nextPing: performance.now() + 10000 };
213
+ if (command.candidate)
214
+ this.candidates.set(s.binding.sessionId, s);
215
+ else
216
+ this.active = s;
217
+ const prepare = async () => {
218
+ if (previous)
219
+ await this.cleanup(previous);
220
+ if (!this.current(s))
221
+ return;
222
+ mediaRequire(!this.failedCleanup);
223
+ try {
224
+ // Discovery is side-effect free and independent of the authenticated socket attach.
225
+ const discovered = bounded(this.options.provider.probe(), 4000);
226
+ // Attach can fail first. Observe discovery's rejection until it is awaited below.
227
+ void discovered.catch(() => { });
228
+ const control = await bounded(this.options.sockets.control(s.binding, s.abort.signal, () => this.current(s)), 5000);
229
+ s.control = control;
230
+ if (!this.current(s)) {
231
+ control.close();
232
+ return;
233
+ }
234
+ const caps = await discovered;
235
+ if (!this.current(s))
236
+ return;
237
+ if (caps && !caps.displays.some(display => display.id === command.displayId))
238
+ this.options.refreshCapabilities?.(command.deviceGeneration);
239
+ mediaRequire(caps && caps.health === 'ready' && caps.displays.some(d => d.id === command.displayId));
240
+ this.options.report(command.deviceGeneration, { t: 'desktop_prepare_result', commandId: command.commandId, sessionId: command.sessionId, state: 'bound', code: null });
241
+ await this.send(s, { v: 1, type: 'offer', sessionId: command.sessionId, payload: caps });
242
+ void this.controlLoop(s);
243
+ s.timer = setInterval(() => {
244
+ const backend = s.backend, configured = s.configured;
245
+ void this.rateTick(s).catch(error => {
246
+ if (this.current(s) && s.backend === backend && s.configured === configured) {
247
+ this.diagnostic(s, 'rate_tick', error);
248
+ return this.recover(s);
249
+ }
250
+ });
251
+ }, 100);
252
+ s.timer.unref();
253
+ }
254
+ catch (error) {
255
+ if (this.current(s)) {
256
+ this.diagnostic(s, 'prepare', error);
257
+ this.options.report(command.deviceGeneration, { t: 'desktop_prepare_result', commandId: command.commandId, sessionId: command.sessionId, state: 'failed', code: 'unavailable' });
258
+ await this.fail(s);
259
+ }
260
+ }
261
+ };
262
+ return command.candidate ? prepare() : this.enqueue(prepare);
263
+ }
264
+ cancel(sessionId, identity) {
265
+ const s = this.active?.binding.sessionId === sessionId ? this.active : this.candidates.get(sessionId);
266
+ if (!s || s.binding.sessionId !== sessionId || identity && (identity.deviceGeneration !== s.binding.deviceGeneration || identity.controlGeneration !== s.binding.controlGeneration))
267
+ return Promise.resolve();
268
+ if (identity) {
269
+ const key = `${identity.deviceGeneration}:${identity.commandId}`, value = canonicalDesktopJson({ sessionId, ...identity });
270
+ const old = this.commands.get(key);
271
+ if (old) {
272
+ mediaRequire(old === value);
273
+ return Promise.resolve();
274
+ }
275
+ mediaRequire(this.commands.size < 1024);
276
+ this.commands.set(key, value);
277
+ }
278
+ this.retire(s);
279
+ return this.enqueue(() => this.cleanup(s));
280
+ }
281
+ current(s) {
282
+ return !this.closed && !s.retired &&
283
+ (this.active === s || this.candidates.get(s.binding.sessionId) === s) && this.generation === s.binding.deviceGeneration;
284
+ }
285
+ diagnostic(s, origin, error, packet, command) {
286
+ // At most 16 fixed-shape records per session, before recovery resets its state.
287
+ // Never log error text/stacks, bindings, grants, input coordinates/text or media.
288
+ try {
289
+ if (s.diagnosticCount >= 16)
290
+ return;
291
+ const counters = s.backend?.nativeTelemetry;
292
+ const code = error instanceof NativeCommandError && diagnosticCodes.has(error.code) ? error.code :
293
+ error instanceof DesktopProtocolError ? 'protocol_invalid' : error === undefined ? 'none' : 'unexpected_error';
294
+ console.error(JSON.stringify({ event: 'desktop_supervisor_diagnostic', session: s.diagnosticId,
295
+ ordinal: ++s.diagnosticCount, origin, code, packet, command,
296
+ epoch: s.configured?.mediaEpoch ?? 0, startedEpoch: s.startedEpoch, receivedFrame: s.receivedFrame,
297
+ decoderReady: s.decoderReady, presented: s.presented, recovering: s.recovering === true,
298
+ activeTasks: s.activeTasks, queuedInputs: s.inputQueue.length, oldestInputMs: s.inputQueue.length ? Math.max(0, Math.round(performance.now() - s.inputQueue[0].at)) : 0, mediaAttached: !!s.media, inFlightBytes: s.flow.inFlightBytes,
299
+ queuedMs: s.queuedAt ? Math.max(0, Math.round(performance.now() - s.queuedAt)) : 0,
300
+ native: counters ? { epoch: counters.epoch, captured: counters.captured, encoded: counters.encoded,
301
+ sentPayloadBytes: counters.sentPayloadBytes, pendingPayloadBytes: counters.pendingPayloadBytes,
302
+ inputAvailable: counters.inputAvailable, pauseCapture: counters.pauseCapture } : undefined }));
303
+ }
304
+ catch { /* Diagnostics must not change recovery or cleanup. */ }
305
+ }
306
+ retire(s) {
307
+ if (this.candidates.get(s.binding.sessionId) === s)
308
+ this.candidates.delete(s.binding.sessionId);
309
+ s.retired = true;
310
+ s.inputQueue.length = 0;
311
+ s.abort.abort();
312
+ clearInterval(s.timer);
313
+ s.control?.close();
314
+ s.media?.close();
315
+ this.wakeMedia(s);
316
+ // Close native admission synchronously; waiting behind a dial/prepare must not
317
+ // postpone known-disconnect releases. Replacement still awaits this same result.
318
+ if (s.backend && !s.nativeCleanup) {
319
+ s.nativeCleanup = s.backend.stop();
320
+ void s.nativeCleanup.catch(() => { this.failedCleanup = true; });
321
+ }
322
+ }
323
+ cleanup(s) {
324
+ return s.cleanup ??= (async () => {
325
+ this.cleanupJobs++;
326
+ try {
327
+ this.retire(s);
328
+ try {
329
+ const preparing = s.preparation ? await s.preparation.catch(() => undefined) : undefined;
330
+ await (s.nativeCleanup ?? (s.backend ?? preparing)?.stop());
331
+ }
332
+ catch (error) {
333
+ this.failedCleanup = true;
334
+ throw error;
335
+ }
336
+ if (this.generation === s.binding.deviceGeneration && (s.backend || s.preparation))
337
+ this.options.refreshCapabilities?.(s.binding.deviceGeneration);
338
+ if (this.active === s)
339
+ this.active = undefined;
340
+ }
341
+ finally {
342
+ this.cleanupJobs--;
343
+ }
344
+ })();
345
+ }
346
+ async fail(s) {
347
+ if (!this.current(s))
348
+ return;
349
+ this.options.report(s.binding.deviceGeneration, { t: 'desktop_session_state', sessionId: s.binding.sessionId,
350
+ controlGeneration: s.binding.controlGeneration, state: 'FAILED', code: 'transport_failed' });
351
+ this.retire(s);
352
+ await this.cleanup(s);
353
+ }
354
+ async send(s, message) {
355
+ mediaRequire(this.current(s) && s.control);
356
+ await s.control.send(message);
357
+ }
358
+ async controlLoop(s) {
359
+ try {
360
+ for await (const message of s.control.messages) {
361
+ if (!this.current(s))
362
+ return;
363
+ mediaRequire(message.sessionId === s.binding.sessionId);
364
+ if (message.type === 'input' || message.type === 'held_state' || message.type === 'clipboard_request') {
365
+ this.scheduleInput(s, message);
366
+ continue;
367
+ }
368
+ mediaRequire(s.activeTasks < 32);
369
+ s.activeTasks++;
370
+ // Each request rechecks its captured session after awaits. Text polling/media drain
371
+ // cannot prevent release, held snapshots or authenticated renewal from being read.
372
+ const receiving = message.type === 'audio_select' || message.type === 'audio_ready' || message.type === 'audio_feedback'
373
+ ? (s.audioCommands = (s.audioCommands ?? Promise.resolve()).then(() => this.receive(s, message)))
374
+ : this.receive(s, message);
375
+ void receiving.catch(error => {
376
+ if (this.current(s))
377
+ this.diagnostic(s, 'control_receive', error, undefined, message.type);
378
+ return this.fail(s);
379
+ }).finally(() => { s.activeTasks--; });
380
+ }
381
+ }
382
+ catch (error) {
383
+ if (this.current(s))
384
+ this.diagnostic(s, 'control_stream', error);
385
+ }
386
+ if (this.current(s))
387
+ this.diagnostic(s, 'control_ended');
388
+ await this.fail(s);
389
+ }
390
+ scheduleInput(s, message) {
391
+ const last = s.inputQueue.at(-1)?.message;
392
+ const queued = { message, backend: s.backend, configured: s.configured, at: performance.now() };
393
+ // Replace only adjacent, unsent absolute positions in the same authority.
394
+ // Key/button/wheel/text, held snapshots and clipboard requests are barriers.
395
+ // One native operation is in flight: a delayed IPC reply cannot create an
396
+ // unbounded tail of obsolete positions or reorder a release behind a key.
397
+ if (last && absoluteMotion(last) && absoluteMotion(message) &&
398
+ last.payload.controlGeneration === message.payload.controlGeneration &&
399
+ 'displayRevision' in last.payload && 'displayRevision' in message.payload &&
400
+ last.payload.displayRevision === message.payload.displayRevision) {
401
+ s.inputQueue[s.inputQueue.length - 1] = queued;
402
+ }
403
+ else {
404
+ mediaRequire(s.inputQueue.length < 128);
405
+ s.inputQueue.push(queued);
406
+ }
407
+ if (s.inputRunning)
408
+ return;
409
+ s.inputRunning = true;
410
+ void (async () => {
411
+ try {
412
+ while (this.current(s) && s.inputQueue.length) {
413
+ const queued = s.inputQueue.shift();
414
+ if (queued.backend === s.backend && queued.configured === s.configured)
415
+ await this.receive(s, queued.message);
416
+ }
417
+ }
418
+ catch (error) {
419
+ if (this.current(s)) {
420
+ this.diagnostic(s, 'control_receive', error);
421
+ await this.fail(s);
422
+ }
423
+ }
424
+ finally {
425
+ s.inputRunning = false;
426
+ }
427
+ })();
428
+ }
429
+ async audioPackets(s, backend) {
430
+ if (!backend.audioPackets)
431
+ return;
432
+ try {
433
+ for await (const bytes of backend.audioPackets) {
434
+ if (!this.current(s) || s.backend !== backend)
435
+ return;
436
+ const packet = decodeAudio(bytes);
437
+ if (!s.audioReady || !s.audioConfig || packet.epoch !== s.audioConfig.audioEpoch)
438
+ continue;
439
+ const sendAudio = s.media?.sendAudio ?? s.control?.sendAudio;
440
+ if (sendAudio)
441
+ await bounded(sendAudio(bytes), 80, 'audio_stale');
442
+ }
443
+ }
444
+ catch {
445
+ if (this.current(s) && s.backend === backend && s.audioConfig && s.audioCommand) {
446
+ s.audioConfig = undefined;
447
+ await backend.stopAudio?.().catch(() => { });
448
+ await this.send(s, { v: 1, type: 'audio_state', sessionId: s.binding.sessionId,
449
+ payload: { commandId: s.audioCommand, state: 'unavailable', reason: 'audio_unavailable' } }).catch(() => { });
450
+ }
451
+ }
452
+ finally {
453
+ s.audioLoop = undefined;
454
+ }
455
+ }
456
+ async receive(s, message, resumeAudio = false) {
457
+ if (!this.current(s))
458
+ return;
459
+ if (resumeAudio && (message.type !== 'audio_select' || s.audioCommand !== message.payload.commandId || s.audioMode !== message.payload.mode))
460
+ return;
461
+ // A committed native resize can overtake already-forwarded control on the
462
+ // retained socket. Old geometry has no authority in the new epoch; discard
463
+ // it rather than retiring that healthy epoch or replaying held input there.
464
+ // Unbound/future geometry is also rejected without granting it authority or
465
+ // letting a single rejected command tear down the valid media session.
466
+ if (s.configured && (message.type === 'held_state' || message.type === 'input') &&
467
+ (message.payload.controlGeneration !== s.binding.controlGeneration ||
468
+ message.payload.displayRevision !== s.configured.displayRevision))
469
+ return;
470
+ if (message.type === 'focus' && s.configured &&
471
+ (message.payload.controlGeneration !== s.binding.controlGeneration ||
472
+ message.payload.mediaEpoch !== s.configured.mediaEpoch ||
473
+ message.payload.displayRevision !== s.configured.displayRevision))
474
+ return;
475
+ if (s.recovering && (message.type === 'held_state' || message.type === 'input')) {
476
+ mediaRequire(message.payload.controlGeneration === s.binding.controlGeneration &&
477
+ message.payload.displayRevision === s.configured?.displayRevision);
478
+ // The old helper is being stopped/verified. Valid messages already in flight
479
+ // cannot belong to the replacement and must never be replayed into it.
480
+ return;
481
+ }
482
+ if (message.type !== 'media_grant' && ['viewer_ready', 'decoder_ready', 'feedback', 'credit'].includes(message.type)) {
483
+ if (!s.backend || !s.configured)
484
+ return;
485
+ const epoch = 'epoch' in message.payload ? message.payload.epoch : 'mediaEpoch' in message.payload ? message.payload.mediaEpoch : undefined;
486
+ if (typeof epoch === 'number' && epoch < s.configured.mediaEpoch)
487
+ return;
488
+ }
489
+ if (message.type === 'media_grant') {
490
+ mediaRequire(message.controlGeneration === s.binding.controlGeneration && s.startCommand && !s.media && !s.mediaAttaching);
491
+ const attachment = Symbol('media_attachment'), backend = s.backend, configured = s.configured;
492
+ s.mediaAttaching = attachment;
493
+ s.flow = new DesktopMediaFlowWindow();
494
+ const bindingCurrent = () => this.current(s) && s.backend === backend && s.configured === configured;
495
+ const owns = () => bindingCurrent() && s.mediaAttaching === attachment;
496
+ let accepted;
497
+ const socketCurrent = () => accepted ? this.current(s) && s.media === accepted : owns();
498
+ try {
499
+ const media = await this.options.sockets.media(s.binding, message.grant, s.abort.signal, socketCurrent, message.quic);
500
+ if (!owns()) {
501
+ media.close();
502
+ return;
503
+ }
504
+ accepted = media;
505
+ s.media = media;
506
+ this.wakeMedia(s);
507
+ void media.closed?.then(() => {
508
+ if (this.current(s) && s.media === media) {
509
+ this.diagnostic(s, 'media_closed');
510
+ void this.recover(s);
511
+ }
512
+ });
513
+ if (media.cutEvents)
514
+ void this.cutEvents(s, media).catch(error => { if (this.current(s) && s.media === media) {
515
+ this.diagnostic(s, 'packet_loop', error);
516
+ void this.fail(s);
517
+ } });
518
+ await this.maybeStart(s);
519
+ }
520
+ catch (error) {
521
+ if (owns())
522
+ throw error;
523
+ }
524
+ finally {
525
+ if (s.mediaAttaching === attachment)
526
+ s.mediaAttaching = undefined;
527
+ }
528
+ return;
529
+ }
530
+ if ('commandId' in message.payload && message.type !== 'audio_ready' && !message.type.startsWith('motion_cut_') && !(resumeAudio && message.type === 'audio_select')) {
531
+ const id = message.payload.commandId, body = canonicalDesktopJson(message);
532
+ const previous = s.commands.get(id);
533
+ if (previous) {
534
+ mediaRequire(previous === body);
535
+ return;
536
+ }
537
+ mediaRequire(s.commands.size < 1024);
538
+ s.commands.set(id, body);
539
+ }
540
+ switch (message.type) {
541
+ case 'motion_cut_prepare':
542
+ await this.prepareMotionCut(s, message.payload);
543
+ return;
544
+ case 'motion_cut_commit':
545
+ await this.commitMotionCut(s, message.payload);
546
+ return;
547
+ case 'cursor_visibility': {
548
+ let outcome = 'failed';
549
+ let fence = { mediaEpoch: 0, afterFrameId: 0 };
550
+ try {
551
+ if (s.backend?.setCursorVisible) {
552
+ fence = await s.backend.setCursorVisible(message.payload.visible);
553
+ outcome = 'applied';
554
+ }
555
+ }
556
+ catch { /* Report the failure without claiming visibility changed. */ }
557
+ if (this.current(s))
558
+ await this.send(s, { v: 1, type: 'cursor_visibility_result', sessionId: s.binding.sessionId, payload: { ...message.payload, outcome, ...fence } });
559
+ return;
560
+ }
561
+ case 'appearance': {
562
+ let outcome = 'failed';
563
+ try {
564
+ if (s.backend?.setAppearance) {
565
+ await s.backend.setAppearance(message.payload.colorScheme);
566
+ outcome = 'applied';
567
+ }
568
+ }
569
+ catch { /* An appearance failure must not retire the video/input session. */ }
570
+ if (this.current(s))
571
+ await this.send(s, { v: 1, type: 'appearance_result', sessionId: s.binding.sessionId,
572
+ payload: { ...message.payload, outcome } });
573
+ return;
574
+ }
575
+ case 'select': {
576
+ if (this.candidates.get(s.binding.sessionId) === s)
577
+ await this.enqueue(async () => {
578
+ mediaRequire(this.current(s));
579
+ const previous = this.active;
580
+ if (previous) {
581
+ this.retire(previous);
582
+ await this.cleanup(previous);
583
+ }
584
+ mediaRequire(this.current(s) && !this.failedCleanup);
585
+ this.candidates.delete(s.binding.sessionId);
586
+ this.active = s;
587
+ });
588
+ mediaRequire(!s.backend && !s.selection);
589
+ s.selection = message.payload;
590
+ s.configuredCommand = message.payload.commandId;
591
+ s.controller = new DesktopRateController({ fpsCeiling: message.payload.fpsCeiling, maxFps: message.payload.fpsCeiling });
592
+ s.preparation = this.options.provider.prepare(message.payload, s.binding);
593
+ const backend = await s.preparation;
594
+ if (!this.current(s)) {
595
+ await backend.stop();
596
+ return;
597
+ }
598
+ s.backend = backend;
599
+ void this.events(s, backend);
600
+ return;
601
+ }
602
+ case 'audio_select': {
603
+ const command = message.payload, backend = s.backend;
604
+ s.audioCommand = command.commandId;
605
+ s.audioMode = command.mode;
606
+ s.audioPolicy ??= new AudioRateController();
607
+ const bitrate = s.audioPolicy.select(command.mode);
608
+ try {
609
+ if (command.mode === 'off') {
610
+ s.audioConfig = undefined;
611
+ s.audioFeedback = undefined;
612
+ s.audioReady = false;
613
+ s.control?.stopAudio?.();
614
+ await backend?.stopAudio?.();
615
+ if (this.current(s))
616
+ await this.send(s, { v: 1, type: 'audio_state', sessionId: s.binding.sessionId, payload: { commandId: command.commandId, state: 'off', reason: null } });
617
+ return;
618
+ }
619
+ if (!backend?.audioSupported || !backend.startAudio || !(s.media?.sendAudio ?? s.control?.sendAudio))
620
+ throw new NativeCommandError('audio_unavailable');
621
+ if (!s.media?.sendAudio)
622
+ await s.control?.prepareAudio?.();
623
+ if (!this.current(s) || s.audioCommand !== command.commandId)
624
+ return;
625
+ let configured = s.audioConfig;
626
+ if (configured) {
627
+ await backend.setAudioRate(bitrate);
628
+ configured = { ...configured, commandId: command.commandId, bitrate };
629
+ }
630
+ else {
631
+ s.audioReady = false;
632
+ const audioEpoch = (s.audioEpoch ?? 0) + 1;
633
+ s.audioEpoch = audioEpoch;
634
+ const native = await backend.startAudio(audioEpoch, bitrate);
635
+ configured = { ...native, commandId: command.commandId };
636
+ }
637
+ if (!this.current(s) || s.backend !== backend || s.audioCommand !== command.commandId)
638
+ return;
639
+ s.audioConfig = configured;
640
+ s.audioFeedback = undefined;
641
+ await this.send(s, { v: 1, type: 'audio_configured', sessionId: s.binding.sessionId, payload: configured });
642
+ await this.send(s, { v: 1, type: 'audio_state', sessionId: s.binding.sessionId, payload: { commandId: command.commandId, state: 'buffering', reason: null } });
643
+ s.audioLoop ??= this.audioPackets(s, backend);
644
+ }
645
+ catch {
646
+ s.audioConfig = undefined;
647
+ await backend?.stopAudio?.().catch(() => { });
648
+ if (this.current(s))
649
+ await this.send(s, { v: 1, type: 'audio_state', sessionId: s.binding.sessionId, payload: { commandId: command.commandId, state: 'unavailable', reason: 'audio_unavailable' } });
650
+ }
651
+ return;
652
+ }
653
+ case 'audio_ready': {
654
+ if (s.audioCommand === message.payload.commandId && s.audioConfig?.audioEpoch === message.payload.audioEpoch)
655
+ s.audioReady = true;
656
+ return;
657
+ }
658
+ case 'audio_feedback': {
659
+ const value = message.payload, previous = s.audioFeedback;
660
+ if (!s.audioConfig || value.audioEpoch !== s.audioConfig.audioEpoch || !s.audioPolicy)
661
+ return;
662
+ if (!previous) {
663
+ s.audioFeedback = value;
664
+ return;
665
+ }
666
+ if (value.receivedPackets < previous.receivedPackets || value.lostPackets < previous.lostPackets ||
667
+ value.latePackets < previous.latePackets || value.underruns < previous.underruns)
668
+ return;
669
+ s.audioFeedback = value;
670
+ const received = Math.max(0, value.receivedPackets - previous.receivedPackets), lost = Math.max(0, value.lostPackets - previous.lostPackets);
671
+ const late = Math.max(0, value.latePackets - previous.latePackets), total = received + lost;
672
+ const network = s.controller.diagnostics.networkState;
673
+ const bitrate = s.audioPolicy.observe(performance.now(), { queueMs: value.queueAgeMs, lateRatio: total ? late / total : 0,
674
+ lostRatio: total ? lost / total : 0, underruns: Math.max(0, value.underruns - previous.underruns),
675
+ congested: network === 'congested', headroom: network === 'usable' && total > 0 && lost === 0 && late === 0 });
676
+ if (bitrate !== s.audioConfig.bitrate && s.backend?.setAudioRate) {
677
+ await s.backend.setAudioRate(bitrate);
678
+ s.audioConfig = { ...s.audioConfig, bitrate };
679
+ await this.send(s, { v: 1, type: 'audio_configured', sessionId: s.binding.sessionId, payload: s.audioConfig });
680
+ }
681
+ return;
682
+ }
683
+ case 'viewer_ready': {
684
+ this.ready(s, message.payload);
685
+ s.viewerReady = true;
686
+ await this.maybeStart(s);
687
+ return;
688
+ }
689
+ case 'start': {
690
+ mediaRequire(s.configured && s.viewerReady);
691
+ s.startCommand = message.payload.commandId;
692
+ await this.maybeStart(s);
693
+ return;
694
+ }
695
+ case 'credit': {
696
+ // The relay's initial credit can overtake viewer readiness and START on
697
+ // the retained control socket. This only funds the bound transport hop;
698
+ // returnNativeCredit still requires this epoch's actual capture START.
699
+ mediaRequire(s.configured && message.payload.epoch === s.configured.mediaEpoch &&
700
+ (s.media || s.mediaAttaching));
701
+ s.flow.credit(message.payload);
702
+ s.lastPacketAt = performance.now();
703
+ this.wakeMedia(s);
704
+ this.returnNativeCredit(s);
705
+ return;
706
+ }
707
+ case 'decoder_ready': {
708
+ this.ready(s, message.payload);
709
+ mediaRequire(s.backend && s.codec && s.startedEpoch === s.configured.mediaEpoch && s.media);
710
+ const backend = s.backend, configured = s.configured;
711
+ s.decoderAcknowledgement = backend.decoderReady({ ...message.payload, representation: s.codec.representation, parameterSets: s.codec.parameterSets });
712
+ await s.decoderAcknowledgement;
713
+ if (this.current(s) && s.backend === backend && s.configured === configured)
714
+ s.decoderReady = true;
715
+ return;
716
+ }
717
+ case 'feedback': {
718
+ const f = message.payload;
719
+ // Feedback is an observation, not authority to retire capture or input.
720
+ // A fast receiver can report before send() settles; old observations can
721
+ // also cross a configuration/cut. Leave accepted counters intact and wait
722
+ // for the next current observation instead of black-screening the owner.
723
+ if (!(s.backend && s.configured && f.epoch === s.configured.mediaEpoch &&
724
+ f.presentedId <= s.receivedFrame && f.receivedId <= s.receivedFrame && f.receivedTotalBytes >= s.lastFeedbackBytes &&
725
+ (!s.feedback || f.presentedId >= s.feedback.presentedId && f.decodedId >= s.feedback.decodedId && f.receivedId >= s.feedback.receivedId && f.exactPresentedSequence >= s.feedback.exactPresentedSequence)))
726
+ return;
727
+ const previousExactPresented = s.feedback?.exactPresentedSequence ?? 0;
728
+ const backend = s.backend, configured = s.configured;
729
+ const observedAt = performance.now();
730
+ if (f.receivedTotalBytes > s.lastFeedbackBytes || f.receivedId > (s.feedback?.receivedId ?? 0))
731
+ s.feedbackProgressAt = observedAt;
732
+ if (f.decodedId > (s.feedback?.decodedId ?? 0))
733
+ s.decodeObservedAt = observedAt;
734
+ s.feedback = f;
735
+ s.lastFeedbackBytes = f.receivedTotalBytes;
736
+ s.delivery.received(f.receivedId, performance.now());
737
+ if (s.firstCompleteFrame > 0 && f.presentedId >= s.firstCompleteFrame && !s.presented) {
738
+ const backend = s.backend, configured = s.configured;
739
+ mediaRequire(s.decoderReady);
740
+ const acknowledgement = s.presentationAcknowledgement ??= backend.firstPresented({ epoch: f.epoch, frameId: f.presentedId });
741
+ try {
742
+ await acknowledgement;
743
+ }
744
+ catch (error) {
745
+ if (this.current(s) && s.backend === backend && s.configured === configured)
746
+ throw error;
747
+ return;
748
+ }
749
+ if (this.current(s) && s.backend === backend && s.configured === configured) {
750
+ s.presented = true;
751
+ // The Server has committed this geometry; publishing during negotiation
752
+ // would invalidate the capability snapshot that authorized the attempt.
753
+ this.options.refreshCapabilities?.(s.binding.deviceGeneration);
754
+ }
755
+ }
756
+ if (f.exactPresentedSequence > previousExactPresented && this.current(s) && s.backend === backend &&
757
+ s.configured === configured && s.presented) {
758
+ await backend.acknowledge?.({ epoch: f.epoch, exactSequence: f.exactPresentedSequence });
759
+ }
760
+ return;
761
+ }
762
+ case 'clipboard_request': {
763
+ mediaRequire(s.backend?.clipboard && message.payload.controlGeneration === s.binding.controlGeneration);
764
+ const backend = s.backend, configured = s.configured;
765
+ if (!s.presented) {
766
+ const acknowledgement = s.presentationAcknowledgement;
767
+ mediaRequire(acknowledgement);
768
+ try {
769
+ await acknowledgement;
770
+ }
771
+ catch (error) {
772
+ if (this.current(s) && s.backend === backend && s.configured === configured &&
773
+ s.presentationAcknowledgement === acknowledgement)
774
+ throw error;
775
+ return;
776
+ }
777
+ if (!this.current(s) || s.backend !== backend || s.configured !== configured ||
778
+ s.presentationAcknowledgement !== acknowledgement)
779
+ return;
780
+ mediaRequire(s.presented);
781
+ }
782
+ const result = await backend.clipboard(message.payload);
783
+ if (this.current(s) && s.backend === backend && s.configured === configured)
784
+ await this.send(s, { v: 1, type: 'clipboard_result', sessionId: s.binding.sessionId, payload: result });
785
+ return;
786
+ }
787
+ case 'input': {
788
+ const backend = s.backend, configured = s.configured;
789
+ mediaRequire(backend);
790
+ if (!s.presented && message.payload.action !== 'release_all') {
791
+ // Feedback already arrived on this ordered socket; its native commit
792
+ // may still be awaiting IPC. Never admit before that exact commit.
793
+ const acknowledgement = s.presentationAcknowledgement;
794
+ mediaRequire(acknowledgement);
795
+ try {
796
+ await acknowledgement;
797
+ }
798
+ catch (error) {
799
+ if (this.current(s) && s.backend === backend && s.configured === configured &&
800
+ s.presentationAcknowledgement === acknowledgement)
801
+ throw error;
802
+ return;
803
+ }
804
+ if (!this.current(s) || s.backend !== backend || s.configured !== configured ||
805
+ s.presentationAcknowledgement !== acknowledgement)
806
+ return;
807
+ mediaRequire(s.presented);
808
+ }
809
+ const result = await backend.input(message.payload);
810
+ if (this.current(s) && s.backend === backend && s.configured === configured)
811
+ await this.send(s, { v: 1, type: 'input_result', sessionId: s.binding.sessionId, payload: result });
812
+ return;
813
+ }
814
+ case 'held_state': {
815
+ mediaRequire(s.backend && message.payload.controlGeneration === s.binding.controlGeneration && message.payload.displayRevision === s.configured?.displayRevision);
816
+ await s.backend.heldState(message.payload);
817
+ return;
818
+ }
819
+ case 'pong': {
820
+ const challenge = s.challenges.get(message.payload.challenge);
821
+ if (challenge) {
822
+ if (!challenge.answered) {
823
+ challenge.answered = true;
824
+ s.rtt = Math.max(0, performance.now() - challenge.at);
825
+ s.rtts.push(s.rtt);
826
+ if (s.rtts.length > 60)
827
+ s.rtts.shift();
828
+ }
829
+ }
830
+ else if (s.backend)
831
+ await s.backend.renewAuthenticatedLease({ leaseMs: 30000 });
832
+ return;
833
+ }
834
+ case 'ping': {
835
+ await this.send(s, { ...message, type: 'pong' });
836
+ return;
837
+ }
838
+ case 'cache_dependency': {
839
+ const request = message.payload, backend = s.backend, configured = s.configured;
840
+ mediaRequire(backend?.cacheDependency && backend.reserveOverview && request.controlGeneration === s.binding.controlGeneration &&
841
+ request.mediaEpoch === configured?.mediaEpoch && request.displayRevision === configured.displayRevision);
842
+ let pixels;
843
+ try {
844
+ pixels = await backend.cacheDependency(request);
845
+ }
846
+ catch (error) {
847
+ if (!this.current(s) || s.backend !== backend || s.configured !== configured || backend.epoch !== undefined && backend.epoch !== request.mediaEpoch)
848
+ return;
849
+ throw error;
850
+ }
851
+ const result = { v: 1, type: 'cache_dependency_result', sessionId: s.binding.sessionId, payload: { request, pixels } };
852
+ const wireBytes = Buffer.byteLength(encodeDesktopControl(result)) + 8, deadline = performance.now() + 4000;
853
+ while (this.current(s) && s.backend === backend && s.configured === configured && (backend.epoch === undefined || backend.epoch === request.mediaEpoch)) {
854
+ if (await backend.reserveOverview({ epoch: request.mediaEpoch, wireBytes })) {
855
+ if (this.current(s) && s.backend === backend && s.configured === configured)
856
+ await this.send(s, result);
857
+ return;
858
+ }
859
+ mediaRequire(performance.now() < deadline);
860
+ await dependencyDelay(10);
861
+ }
862
+ return;
863
+ }
864
+ case 'focus': {
865
+ const request = message.payload;
866
+ mediaRequire(s.backend?.focus && request.controlGeneration === s.binding.controlGeneration &&
867
+ request.mediaEpoch === s.configured?.mediaEpoch && request.displayRevision === s.configured.displayRevision);
868
+ const backend = s.backend, configured = s.configured;
869
+ if (!s.presented) {
870
+ const acknowledgement = s.presentationAcknowledgement;
871
+ // Navigation crossing a configuration is obsolete. A valid first-frame
872
+ // acknowledgement already in flight, however, precedes this command.
873
+ if (!acknowledgement)
874
+ return;
875
+ try {
876
+ await acknowledgement;
877
+ }
878
+ catch (error) {
879
+ if (this.current(s) && s.backend === backend && s.configured === configured)
880
+ throw error;
881
+ return;
882
+ }
883
+ if (!this.current(s) || s.backend !== backend || s.configured !== configured ||
884
+ s.presentationAcknowledgement !== acknowledgement || !s.presented)
885
+ return;
886
+ }
887
+ const owns = () => this.current(s) && s.backend === backend && s.configured === configured &&
888
+ (backend.epoch === undefined || backend.epoch === request.mediaEpoch);
889
+ let result;
890
+ try {
891
+ result = await backend.focus(request.focus);
892
+ }
893
+ catch (error) {
894
+ if (owns())
895
+ throw error;
896
+ return;
897
+ }
898
+ if (owns())
899
+ await this.send(s, { v: 1, type: 'focus_result', sessionId: s.binding.sessionId, payload: { ...request, ...result } });
900
+ return;
901
+ }
902
+ case 'resize':
903
+ case 'request_refresh': {
904
+ mediaRequire(s.backend);
905
+ // A viewer command may cross an automatic media_open already in flight.
906
+ // The viewer retains its choice and retries after that epoch is presented.
907
+ if (s.automaticConfiguration)
908
+ return;
909
+ if (!s.presented && s.presentationAcknowledgement) {
910
+ const backend = s.backend, configured = s.configured, acknowledgement = s.presentationAcknowledgement;
911
+ try {
912
+ await acknowledgement;
913
+ }
914
+ catch (error) {
915
+ if (this.current(s) && s.backend === backend && s.configured === configured &&
916
+ s.presentationAcknowledgement === acknowledgement)
917
+ throw error;
918
+ return;
919
+ }
920
+ if (!this.current(s) || s.backend !== backend || s.configured !== configured ||
921
+ s.presentationAcknowledgement !== acknowledgement)
922
+ return;
923
+ }
924
+ if (!s.presented)
925
+ return;
926
+ // The old stream must finish its credited writes before native can commit
927
+ // the new configuration. Input closes now; decoder/START retire at CONFIG.
928
+ s.viewerReady = false;
929
+ s.presented = false;
930
+ s.configuredCommand = message.payload.commandId;
931
+ s.backend?.configurationCommand?.(message.payload.commandId);
932
+ if (message.type === 'resize') {
933
+ mediaRequire(message.payload.expectedDisplayRevision === s.configured?.displayRevision);
934
+ const result = await s.backend.resize({ width: message.payload.width, height: message.payload.height,
935
+ ...(message.payload.dpi === undefined ? {} : { dpi: message.payload.dpi }),
936
+ ...(message.payload.captureRegion === undefined ? {} : { captureRegion: message.payload.captureRegion }) });
937
+ if (this.current(s))
938
+ await this.send(s, { v: 1, type: 'resize_result', sessionId: s.binding.sessionId, payload: { ...result, commandId: message.payload.commandId } });
939
+ }
940
+ else
941
+ await s.backend.refresh();
942
+ return;
943
+ }
944
+ case 'stop':
945
+ case 'suspend':
946
+ case 'resume': {
947
+ this.retire(s);
948
+ await this.cleanup(s);
949
+ return;
950
+ }
951
+ case 'error': {
952
+ this.diagnostic(s, 'viewer_error');
953
+ await this.fail(s);
954
+ return;
955
+ }
956
+ case 'state': {
957
+ if (['FAILED', 'CLOSED', 'SUSPENDED'].includes(message.payload.state)) {
958
+ this.diagnostic(s, 'viewer_state');
959
+ this.retire(s);
960
+ await this.cleanup(s);
961
+ }
962
+ return;
963
+ }
964
+ default: throw new Error('invalid_request');
965
+ }
966
+ }
967
+ ready(s, value) {
968
+ mediaRequire(s.configured && value.controlGeneration === s.binding.controlGeneration && value.mediaEpoch === s.configured.mediaEpoch && value.displayRevision === s.configured.displayRevision);
969
+ }
970
+ async maybeStart(s) {
971
+ if (!this.current(s) || !s.backend || !s.configured || !s.viewerReady || !s.startCommand || !s.media || s.starting || s.startedEpoch === s.configured.mediaEpoch)
972
+ return;
973
+ const configured = s.configured, epoch = configured.mediaEpoch, backend = s.backend;
974
+ if (backend.epoch !== undefined && backend.epoch !== epoch)
975
+ return;
976
+ s.starting = true;
977
+ // START is authorized. The persistent reader can receive CONFIG before
978
+ // the helper acknowledges start, so publish the permitted epoch first.
979
+ s.startedEpoch = epoch;
980
+ try {
981
+ await backend.start();
982
+ if (!this.current(s) || s.backend !== backend || s.configured !== configured)
983
+ return;
984
+ s.startedEpoch = epoch;
985
+ // Independent bounded hop: grant one initial native window, then replenish each
986
+ // fragment only after the actual device WS send callback, never after decode/present.
987
+ this.returnNativeCredit(s);
988
+ if (!this.current(s) || s.backend !== backend || s.configured !== configured || s.startedEpoch !== epoch)
989
+ return;
990
+ s.lastPacketAt = performance.now();
991
+ if (!s.packetLoop) {
992
+ s.packetLoop = true;
993
+ void this.packets(s);
994
+ }
995
+ if (s.audioMode && s.audioMode !== 'off' && s.audioCommand && !s.audioConfig) {
996
+ const mode = s.audioMode, commandId = s.audioCommand;
997
+ s.audioCommands = (s.audioCommands ?? Promise.resolve()).then(() => this.receive(s, { v: 1, type: 'audio_select',
998
+ sessionId: s.binding.sessionId, payload: { commandId, mode } }, true)).catch(() => { });
999
+ }
1000
+ }
1001
+ catch (error) {
1002
+ if (this.current(s) && s.backend === backend && s.configured === configured)
1003
+ throw error;
1004
+ }
1005
+ finally {
1006
+ if (s.backend === backend && s.configured === configured)
1007
+ s.starting = false;
1008
+ }
1009
+ }
1010
+ async events(s, backend) {
1011
+ try {
1012
+ for await (const event of backend.events) {
1013
+ if (!this.current(s) || s.backend !== backend)
1014
+ return;
1015
+ if (event.type === 'configured') {
1016
+ const cfg = event.configured;
1017
+ mediaRequire(cfg.deviceGeneration === s.binding.deviceGeneration && cfg.controlGeneration === s.binding.controlGeneration && cfg.commandId === s.configuredCommand);
1018
+ if (s.automaticConfiguration === cfg.commandId) {
1019
+ await this.send(s, { v: 1, type: 'media_open', sessionId: s.binding.sessionId, payload: { commandId: cfg.commandId } });
1020
+ if (!this.current(s) || s.backend !== backend)
1021
+ return;
1022
+ const oldMedia = s.media;
1023
+ s.media = undefined;
1024
+ oldMedia?.close();
1025
+ s.startCommand = undefined;
1026
+ s.automaticConfiguration = undefined;
1027
+ }
1028
+ s.configured = cfg;
1029
+ s.codec = undefined;
1030
+ s.viewerReady = false;
1031
+ s.decoderReady = false;
1032
+ s.decoderAcknowledgement = undefined;
1033
+ s.presented = false;
1034
+ if (s.selection)
1035
+ s.selection = { ...s.selection, captureRegion: cfg.captureRegion, width: cfg.width, height: cfg.height,
1036
+ ...(cfg.dpi === undefined ? {} : { dpi: cfg.dpi }) };
1037
+ s.startCommand = undefined;
1038
+ s.mediaAttaching = undefined;
1039
+ s.startedEpoch = 0;
1040
+ s.starting = false;
1041
+ s.firstCompleteFrame = 0;
1042
+ s.receivedFrame = 0;
1043
+ s.presentationAcknowledgement = undefined;
1044
+ s.flow = new DesktopMediaFlowWindow();
1045
+ s.configAdmittedEpoch = 0;
1046
+ s.nativeAdvertised = undefined;
1047
+ s.nativeDisposed = { motion: 0, cursor: 0, exact: 0 };
1048
+ s.motionCut = undefined;
1049
+ s.cutRequest = undefined;
1050
+ s.untrustedCut = undefined;
1051
+ this.wakeMedia(s);
1052
+ // Picture and exact presentation watermarks belong to this media epoch.
1053
+ // Keep lastFeedbackBytes: receivedTotalBytes is cumulative for the connection.
1054
+ s.feedback = undefined;
1055
+ s.feedbackProgressAt = undefined;
1056
+ s.decodeObservedAt = undefined;
1057
+ s.motionEncodeObservedAt = undefined;
1058
+ s.motionEncodeKey = undefined;
1059
+ s.motionEncodeMs = 0;
1060
+ s.delivery = new DesktopDeliveryDelay();
1061
+ s.capturePaused = false;
1062
+ s.recovering = false;
1063
+ await this.send(s, { v: 1, type: 'configured', sessionId: s.binding.sessionId, payload: cfg });
1064
+ }
1065
+ else if (event.type === 'failed') {
1066
+ this.diagnostic(s, 'backend_failed', new NativeCommandError(event.code));
1067
+ await this.recover(s);
1068
+ }
1069
+ else if (event.type === 'recovery' && !event.result.verified) {
1070
+ this.diagnostic(s, 'backend_recovery_unverified');
1071
+ await this.fail(s);
1072
+ }
1073
+ }
1074
+ }
1075
+ catch (error) {
1076
+ if (this.current(s) && s.backend === backend) {
1077
+ this.diagnostic(s, 'backend_events', error);
1078
+ await this.fail(s);
1079
+ }
1080
+ }
1081
+ }
1082
+ cutBinding(s, p) {
1083
+ return this.current(s) && p.controlGeneration === s.binding.controlGeneration && p.mediaEpoch === s.configured?.mediaEpoch && p.displayRevision === s.configured.displayRevision && s.startedEpoch === p.mediaEpoch && !s.recovering;
1084
+ }
1085
+ async cutEvents(s, media) {
1086
+ for await (const event of media.cutEvents) {
1087
+ if (!this.current(s) || s.media !== media)
1088
+ return;
1089
+ if (event.epoch < (s.configured?.mediaEpoch ?? 0))
1090
+ continue;
1091
+ mediaRequire(event.epoch === s.configured?.mediaEpoch);
1092
+ if (event.action === 1) {
1093
+ if (s.motionCut?.phase === 'committed' && event.cutId < s.motionCut.payload.cutId)
1094
+ continue;
1095
+ if (s.presented && s.decoderReady && !s.recovering && !s.cutRequest && (!s.motionCut || s.motionCut.phase === 'committed')) {
1096
+ const commandId = randomBytes(16).toString('hex');
1097
+ s.cutRequest = commandId;
1098
+ await this.send(s, { v: 1, type: 'motion_cut_request', sessionId: s.binding.sessionId, payload: { commandId, controlGeneration: s.binding.controlGeneration, mediaEpoch: event.epoch, displayRevision: s.configured.displayRevision } });
1099
+ }
1100
+ }
1101
+ else if (event.action === 2) {
1102
+ // A transport proposal is untrusted until the coordinator's matching control transaction.
1103
+ mediaRequire(!s.untrustedCut || canonicalDesktopJson(s.untrustedCut) === canonicalDesktopJson(event));
1104
+ s.untrustedCut = event;
1105
+ }
1106
+ else if (event.action === 4 || event.action === 5) {
1107
+ const cut = s.motionCut;
1108
+ mediaRequire(cut && cut.fence && event.cutId === cut.payload.cutId && event.epoch === cut.payload.mediaEpoch && event.pictureThrough === cut.fence.pictureThrough && event.frameThrough === cut.fence.frameThrough);
1109
+ if (event.action === 4)
1110
+ cut.acked = true;
1111
+ else {
1112
+ mediaRequire(cut.phase === 'committing' && cut.acked);
1113
+ cut.resumed = true;
1114
+ }
1115
+ this.wakeMedia(s);
1116
+ }
1117
+ else if (event.action === 6)
1118
+ throw new NativeCommandError('transport_failed');
1119
+ }
1120
+ }
1121
+ async prepareMotionCut(s, payload) {
1122
+ mediaRequire(this.cutBinding(s, payload) && s.presented && s.backend?.prepareMotionCut && s.backend.settleMotionCut && s.backend.mediaDisposed && s.backend.commitMotionCut && s.media);
1123
+ let cut = s.motionCut;
1124
+ if (cut && cut.payload.cutId === payload.cutId) {
1125
+ mediaRequire(canonicalDesktopJson(cut.payload) === canonicalDesktopJson(payload));
1126
+ if (cut.preparing)
1127
+ await cut.preparing;
1128
+ }
1129
+ else {
1130
+ mediaRequire(!cut || cut.phase === 'committed' && payload.cutId > cut.payload.cutId);
1131
+ cut = { payload, phase: 'preparing', acked: false, resumed: false };
1132
+ s.motionCut = cut;
1133
+ this.wakeMedia(s);
1134
+ const owner = cut, backend = s.backend, media = s.media;
1135
+ cut.preparing = (async () => {
1136
+ const fence = await backend.prepareMotionCut({ epoch: payload.mediaEpoch, cutId: payload.cutId });
1137
+ mediaRequire(this.cutBinding(s, payload) && s.backend === backend && s.media === media && s.motionCut === owner && fence.epoch === payload.mediaEpoch && fence.cutId === payload.cutId);
1138
+ owner.fence = fence;
1139
+ this.wakeMedia(s);
1140
+ if (media.cutCommand)
1141
+ await media.cutCommand(9, payload.cutId, payload.mediaEpoch, fence.pictureThrough, fence.frameThrough);
1142
+ await backend.settleMotionCut(fence);
1143
+ mediaRequire(this.cutBinding(s, payload) && s.backend === backend && s.media === media && s.motionCut === owner);
1144
+ if (!media.cutCommand) {
1145
+ mediaRequire(media.sendCutBarrier);
1146
+ await media.sendCutBarrier({ v: 1, type: 'motion_cut_barrier', sessionId: s.binding.sessionId, payload: { ...payload, pictureThrough: fence.pictureThrough, frameThrough: fence.frameThrough } });
1147
+ }
1148
+ owner.phase = 'prepared';
1149
+ })();
1150
+ await cut.preparing;
1151
+ cut.preparing = undefined;
1152
+ }
1153
+ mediaRequire(this.cutBinding(s, payload) && cut.fence);
1154
+ await this.send(s, { v: 1, type: 'motion_cut_prepared', sessionId: s.binding.sessionId, payload: { ...payload, pictureThrough: cut.fence.pictureThrough, frameThrough: cut.fence.frameThrough } });
1155
+ }
1156
+ async commitMotionCut(s, payload) {
1157
+ const cut = s.motionCut, backend = s.backend, media = s.media;
1158
+ mediaRequire(this.cutBinding(s, payload) && cut && canonicalDesktopJson(cut.payload) === canonicalDesktopJson(payload) && backend?.commitMotionCut && media);
1159
+ if (cut.phase === 'committed')
1160
+ return;
1161
+ if (cut.committing) {
1162
+ await cut.committing;
1163
+ return;
1164
+ }
1165
+ cut.committing = (async () => {
1166
+ if (cut.preparing)
1167
+ await cut.preparing;
1168
+ mediaRequire(cut.phase === 'prepared');
1169
+ cut.phase = 'committing';
1170
+ if (media.cutCommand) {
1171
+ while (!cut.acked) {
1172
+ mediaRequire(this.cutBinding(s, payload) && s.media === media);
1173
+ await this.waitMedia(s);
1174
+ }
1175
+ await media.cutCommand(11, payload.cutId, payload.mediaEpoch);
1176
+ while (!cut.resumed) {
1177
+ mediaRequire(this.cutBinding(s, payload) && s.media === media);
1178
+ await this.waitMedia(s);
1179
+ }
1180
+ }
1181
+ mediaRequire(this.cutBinding(s, payload) && s.backend === backend && s.media === media);
1182
+ await backend.commitMotionCut({ epoch: payload.mediaEpoch, cutId: payload.cutId });
1183
+ mediaRequire(this.cutBinding(s, payload) && s.backend === backend && s.media === media);
1184
+ media.finishCut?.(payload.cutId);
1185
+ cut.phase = 'committed';
1186
+ s.cutRequest = undefined;
1187
+ s.untrustedCut = undefined;
1188
+ this.wakeMedia(s);
1189
+ })();
1190
+ await cut.committing;
1191
+ }
1192
+ disposeMotion(s, backend, packet, cut) {
1193
+ mediaRequire(cut.fence && packet.type === 2 && packet.epoch === cut.payload.mediaEpoch && packet.sequence <= cut.fence.pictureThrough && backend.mediaDisposed);
1194
+ backend.mediaDisposed(packet, cut.fence);
1195
+ s.nativeDisposed.motion += packet.payload.length;
1196
+ mediaRequire(Number.isSafeInteger(s.nativeDisposed.motion));
1197
+ this.returnNativeCredit(s);
1198
+ this.wakeMedia(s);
1199
+ }
1200
+ wakeMedia(s) { for (const wake of [...s.mediaWake])
1201
+ wake(); }
1202
+ async waitMedia(s) {
1203
+ let wake;
1204
+ const waiting = new Promise(resolve => { wake = resolve; s.mediaWake.add(wake); });
1205
+ try {
1206
+ await waiting;
1207
+ }
1208
+ finally {
1209
+ s.mediaWake.delete(wake);
1210
+ }
1211
+ }
1212
+ returnNativeCredit(s) {
1213
+ s.nativeCreditDirty = true;
1214
+ if (s.nativeCreditRunning)
1215
+ return;
1216
+ const task = (async () => {
1217
+ while (this.current(s) && s.nativeCreditDirty) {
1218
+ s.nativeCreditDirty = false;
1219
+ const backend = s.backend, configured = s.configured, flow = s.flow;
1220
+ if (!backend?.credit || !configured || s.startedEpoch !== configured.mediaEpoch || !flow.hasCredit)
1221
+ continue;
1222
+ const value = { epoch: configured.mediaEpoch, ...flow.appliedCredit };
1223
+ value.motionConsumedBytes += s.nativeDisposed.motion;
1224
+ value.cursorConsumedBytes += s.nativeDisposed.cursor;
1225
+ value.exactConsumedBytes += s.nativeDisposed.exact;
1226
+ if (s.nativeAdvertised && canonicalDesktopJson(s.nativeAdvertised) === canonicalDesktopJson(value))
1227
+ continue;
1228
+ try {
1229
+ await backend.credit(value);
1230
+ }
1231
+ catch (error) {
1232
+ if (this.current(s) && s.backend === backend && s.configured === configured) {
1233
+ this.diagnostic(s, 'packet_loop', error, { stage: 'native_credit', type: 0, sequence: 0, payloadBytes: 0 });
1234
+ await this.recover(s);
1235
+ }
1236
+ return;
1237
+ }
1238
+ if (this.current(s) && s.backend === backend && s.configured === configured && s.flow === flow)
1239
+ s.nativeAdvertised = value;
1240
+ }
1241
+ })();
1242
+ s.nativeCreditRunning = task;
1243
+ void task.finally(() => { if (s.nativeCreditRunning === task) {
1244
+ s.nativeCreditRunning = undefined;
1245
+ if (s.nativeCreditDirty && this.current(s))
1246
+ this.returnNativeCredit(s);
1247
+ } }).catch(() => { });
1248
+ }
1249
+ async packets(s) {
1250
+ const backend = s.backend;
1251
+ const queues = { motion: [], cursor: [], exact: [] };
1252
+ const active = new Map();
1253
+ let queuedBytes = 0, ended = false, failed = false, nextId = 0;
1254
+ const current = () => this.current(s) && s.backend === backend;
1255
+ const queuedCount = () => queues.motion.length + queues.cursor.length + queues.exact.length;
1256
+ const updateAge = () => { if (!current())
1257
+ return; s.queuedAt = Math.min(...[...active.values(), ...queues.motion, ...queues.cursor, ...queues.exact].map(r => r.at), Infinity); if (s.queuedAt === Infinity)
1258
+ s.queuedAt = 0; };
1259
+ const fail = async (error, p, stage = 'native_read') => {
1260
+ if (failed || !current())
1261
+ return;
1262
+ failed = true;
1263
+ ended = true;
1264
+ this.wakeMedia(s);
1265
+ this.diagnostic(s, 'packet_loop', error, { stage, type: p?.type ?? 0, sequence: p?.sequence ?? 0, payloadBytes: p?.payload.length ?? 0 });
1266
+ await this.recover(s);
1267
+ };
1268
+ const worker = async (lane) => {
1269
+ let packet, stage = 'native_read';
1270
+ try {
1271
+ while (current() && !failed) {
1272
+ const record = queues[lane][0];
1273
+ if (!record) {
1274
+ if (ended)
1275
+ return;
1276
+ await this.waitMedia(s);
1277
+ continue;
1278
+ }
1279
+ packet = record.p;
1280
+ if (lane === 'motion' && packet.type === 2) {
1281
+ while (current() && !failed && s.motionCut?.phase === 'preparing' && !s.motionCut.fence)
1282
+ await this.waitMedia(s);
1283
+ const cut = s.motionCut;
1284
+ if (cut?.fence && packet.epoch === cut.payload.mediaEpoch && packet.sequence <= cut.fence.pictureThrough) {
1285
+ queues[lane].shift();
1286
+ queuedBytes -= packet.payload.length;
1287
+ this.disposeMotion(s, backend, packet, cut);
1288
+ updateAge();
1289
+ continue;
1290
+ }
1291
+ }
1292
+ const configured = s.configured, flow = s.flow;
1293
+ mediaRequire(configured && packet.epoch === configured.mediaEpoch && packet.displayRevision === configured.displayRevision && s.startedEpoch === packet.epoch);
1294
+ if (packet.type !== 1) {
1295
+ while (current() && !failed && s.configAdmittedEpoch !== packet.epoch)
1296
+ await this.waitMedia(s);
1297
+ stage = 'decoder_wait';
1298
+ if ((packet.type === 2 || packet.type === 5) && !s.decoderReady && s.decoderAcknowledgement)
1299
+ await s.decoderAcknowledgement;
1300
+ mediaRequire(s.codec && ((packet.type !== 2 && packet.type !== 5) || s.decoderReady));
1301
+ }
1302
+ stage = 'credit_wait';
1303
+ while (current() && !failed && (!s.media || !flow.admit(lane, packet.payload.length)))
1304
+ await this.waitMedia(s);
1305
+ if (!current() || failed)
1306
+ return;
1307
+ const media = s.media;
1308
+ mediaRequire(media && s.configured === configured && s.flow === flow);
1309
+ const id = ++nextId;
1310
+ mediaRequire(flow.reserve(id, lane, packet.payload.length));
1311
+ queues[lane].shift();
1312
+ queuedBytes -= packet.payload.length;
1313
+ active.set(lane, record);
1314
+ updateAge();
1315
+ stage = 'send';
1316
+ try {
1317
+ await media.send(record.bytes);
1318
+ }
1319
+ catch (error) {
1320
+ flow.cancel(id);
1321
+ const cut = s.motionCut;
1322
+ if (error instanceof DesktopMediaAdmissionCancelled && current() && s.media === media && cut?.fence && error.cutId === cut.payload.cutId && error.epoch === packet.epoch && error.pictureSequence === packet.sequence) {
1323
+ this.disposeMotion(s, backend, packet, cut);
1324
+ active.delete(lane);
1325
+ updateAge();
1326
+ continue;
1327
+ }
1328
+ throw error;
1329
+ }
1330
+ flow.commit(id);
1331
+ if (!current() || s.configured !== configured || s.media !== media)
1332
+ return;
1333
+ s.lastPacketAt = performance.now();
1334
+ if (packet.type === 1) {
1335
+ s.configAdmittedEpoch = packet.epoch;
1336
+ this.wakeMedia(s);
1337
+ }
1338
+ if (packet.type === 2 || packet.type === 5) {
1339
+ s.activeBytes += packet.payload.length;
1340
+ if (packet.type === 2 && packet.fragmentIndex + 1 === packet.fragmentCount) {
1341
+ s.receivedFrame = Math.max(s.receivedFrame, packet.frameId);
1342
+ s.firstCompleteFrame ||= packet.frameId;
1343
+ if (!s.capturePaused)
1344
+ s.delivery.sent(packet.frameId, performance.now());
1345
+ }
1346
+ }
1347
+ stage = 'native_forwarded';
1348
+ backend.mediaForwarded?.(packet);
1349
+ active.delete(lane);
1350
+ updateAge();
1351
+ this.returnNativeCredit(s);
1352
+ this.wakeMedia(s);
1353
+ }
1354
+ }
1355
+ catch (error) {
1356
+ await fail(error, packet, stage);
1357
+ }
1358
+ };
1359
+ const workers = ['motion', 'cursor', 'exact'].map(worker);
1360
+ try {
1361
+ for await (const bytes of backend.packets) {
1362
+ if (!current() || failed)
1363
+ break;
1364
+ const p = decodeMedia(bytes);
1365
+ mediaRequire(s.configured && p.epoch === s.configured.mediaEpoch && p.displayRevision === s.configured.displayRevision && s.startedEpoch === p.epoch);
1366
+ if (p.type === 1)
1367
+ s.codec = decodeConfig(p.payload);
1368
+ mediaRequire(queuedBytes + s.flow.inFlightBytes + p.payload.length <= 524288 && queuedCount() + s.flow.recordCount < 128);
1369
+ const lane = desktopMediaLane(p.type);
1370
+ queues[lane].push({ bytes, p, at: performance.now() });
1371
+ queuedBytes += p.payload.length;
1372
+ updateAge();
1373
+ this.wakeMedia(s);
1374
+ }
1375
+ }
1376
+ catch (error) {
1377
+ await fail(error);
1378
+ }
1379
+ finally {
1380
+ ended = true;
1381
+ this.wakeMedia(s);
1382
+ await Promise.all(workers);
1383
+ if (current())
1384
+ s.queuedAt = 0;
1385
+ }
1386
+ }
1387
+ async rateTick(s) {
1388
+ if (s.tickBusy)
1389
+ return;
1390
+ s.tickBusy = true;
1391
+ try {
1392
+ await this.rateTickOwned(s);
1393
+ }
1394
+ finally {
1395
+ s.tickBusy = false;
1396
+ }
1397
+ }
1398
+ async rateTickOwned(s) {
1399
+ if (!this.current(s) || !s.backend || !s.configured || s.startedEpoch !== s.configured.mediaEpoch || s.starting || s.rateBusy || s.motionCut && s.motionCut.phase !== 'committed')
1400
+ return;
1401
+ const backend = s.backend, configured = s.configured;
1402
+ const transportCost = s.media?.transportCost?.();
1403
+ if (transportCost && backend.accountTransportCost)
1404
+ await backend.accountTransportCost(transportCost);
1405
+ const audioCost = s.control?.audioTransportCost?.();
1406
+ if (audioCost && backend.accountTransportCost)
1407
+ await backend.accountTransportCost(audioCost);
1408
+ if (!this.current(s) || s.backend !== backend || s.configured !== configured)
1409
+ return;
1410
+ if (backend.checkHealth && performance.now() >= (s.nextHealthAt ?? 0)) {
1411
+ s.nextHealthAt = performance.now() + 1000;
1412
+ try {
1413
+ await backend.checkHealth();
1414
+ }
1415
+ catch (error) {
1416
+ if (!this.current(s) || s.backend !== backend || s.configured !== configured)
1417
+ return;
1418
+ this.diagnostic(s, 'native_health', error);
1419
+ throw error;
1420
+ }
1421
+ const audioConfig = s.audioConfig;
1422
+ if (audioConfig && backend.audioStatus) {
1423
+ const status = await backend.audioStatus().catch(() => 'unavailable');
1424
+ if (status === 'unavailable' && this.current(s) && s.backend === backend && s.audioConfig === audioConfig) {
1425
+ s.audioConfig = undefined;
1426
+ s.control?.stopAudio?.();
1427
+ await backend.stopAudio?.().catch(() => { });
1428
+ await this.send(s, { v: 1, type: 'audio_state', sessionId: s.binding.sessionId,
1429
+ payload: { commandId: audioConfig.commandId, state: 'unavailable', reason: 'audio_capture_unavailable' } }).catch(() => { });
1430
+ }
1431
+ }
1432
+ }
1433
+ if (!this.current(s) || s.backend !== backend || s.configured !== configured || s.startedEpoch !== configured.mediaEpoch)
1434
+ return;
1435
+ const now = performance.now(), queued = s.queuedAt ? now - s.queuedAt : 0;
1436
+ if (now >= s.nextPing) {
1437
+ s.nextPing = now + 10000;
1438
+ for (const [challenge, value] of s.challenges)
1439
+ if (now - value.at >= 90000)
1440
+ s.challenges.delete(challenge);
1441
+ mediaRequire(s.challenges.size < 10);
1442
+ const challenge = randomBytes(16).toString('hex');
1443
+ s.challenges.set(challenge, { at: now, answered: false });
1444
+ await this.send(s, { v: 1, type: 'ping', sessionId: s.binding.sessionId, payload: { challenge } });
1445
+ if (!this.current(s) || s.backend !== backend || s.configured !== configured || s.startedEpoch !== configured.mediaEpoch)
1446
+ return;
1447
+ }
1448
+ // Credit starvation is bounded backpressure. Feed queue age into rate/admission
1449
+ // control instead of restarting a healthy helper when the remote reader is slow.
1450
+ const baseline = s.rtts.length ? Math.min(...s.rtts) : 0;
1451
+ const applied = backend.rateState?.applied ??
1452
+ { targetBps: configured.rateTargetBps, fpsCeiling: configured.fpsCeiling };
1453
+ const delivery = s.delivery.sample(now), excessRttMs = Math.max(0, s.rtt - baseline);
1454
+ const queueAgeMs = s.feedback?.queueAgeMs ?? 0;
1455
+ const timing = backend.nativeTelemetry?.lastFrameTiming;
1456
+ if (timing && !timing.repair && timing.epoch === configured.mediaEpoch) {
1457
+ const key = `${timing.epoch}:${timing.frameId}`;
1458
+ if (key !== s.motionEncodeKey) {
1459
+ s.motionEncodeKey = key;
1460
+ s.motionEncodeObservedAt = now;
1461
+ const elapsedUs = timing.encodeEndUs - timing.encodeBeginUs;
1462
+ s.motionEncodeMs = Number.isFinite(elapsedUs) && elapsedUs >= 0 ? elapsedUs / 1000 : backend.nativeTelemetry?.lastEncodeMs ?? 0;
1463
+ }
1464
+ }
1465
+ const encodeMs = s.motionEncodeMs;
1466
+ const decodeMs = s.feedback?.decodeMs ?? 0;
1467
+ const network = s.media?.networkSample?.();
1468
+ const decision = s.controller.sample({ nowMs: now, activeBytes: s.activeBytes,
1469
+ rttBaselineMs: baseline, excessRttMs, deliveryDelayMs: delivery.delayMs,
1470
+ deliveryObservedAtMs: s.feedbackProgressAt ?? 0, encodeObservedAtMs: s.motionEncodeObservedAt ?? 0,
1471
+ decodeObservedAtMs: s.decodeObservedAt ?? 0, network,
1472
+ queueAgeMs, queuedSendMs: queued, encodeMs, decodeMs }, backend.runtimeRateReconfigure === true);
1473
+ if (now - s.lastPerformanceDiagnosticAt >= (s.performanceDiagnosticCount < 30 ? 2000 : 10000)) {
1474
+ s.lastPerformanceDiagnosticAt = now;
1475
+ s.performanceDiagnosticCount = Math.min(30, s.performanceDiagnosticCount + 1);
1476
+ try {
1477
+ const native = backend.nativeTelemetry, previous = s.performanceSample;
1478
+ const current = native ? { atMs: now, epoch: configured.mediaEpoch, captured: native.captured, encoded: native.encoded,
1479
+ refinements: native.refinementFrames ?? 0, payloadBytes: native.sentPayloadBytes } : undefined;
1480
+ const comparable = current && previous && current.epoch === previous.epoch && now > previous.atMs &&
1481
+ current.encoded >= previous.encoded && current.refinements >= previous.refinements && current.payloadBytes >= previous.payloadBytes;
1482
+ const seconds = comparable ? (now - previous.atMs) / 1000 : 0;
1483
+ console.error(JSON.stringify({ event: 'desktop_supervisor_performance', session: s.diagnosticId,
1484
+ connectionId: s.diagnosticKey, processId: process.pid, atUnixMs: Date.now(),
1485
+ atMs: Math.round(now), epoch: configured.mediaEpoch, width: configured.width, height: configured.height,
1486
+ appliedTargetBps: applied.targetBps, appliedFpsCeiling: applied.fpsCeiling,
1487
+ queuedMs: Math.round(queued), queueAgeMs, excessRttMs, encodeMs, decodeMs,
1488
+ encodeMetric: 'source_unit_job', decodeMetric: 'submit_to_callback', networkScope: 'sender_to_server', network,
1489
+ presentMs: s.feedback?.presentMs, receiverQueueDepth: s.feedback?.queueDepth,
1490
+ policy: s.controller.diagnostics,
1491
+ captureConsumedFps: comparable && current.captured !== undefined && previous.captured !== undefined && current.captured >= previous.captured
1492
+ ? (current.captured - previous.captured) / seconds : undefined,
1493
+ motionCommittedFps: comparable ? (current.encoded - previous.encoded) / seconds : undefined,
1494
+ exactUpdatesPerSecond: comparable && native?.refinementFrames !== undefined ? (current.refinements - previous.refinements) / seconds : undefined,
1495
+ payloadBps: comparable ? (current.payloadBytes - previous.payloadBytes) * 8 / seconds : undefined,
1496
+ receivedFrame: s.receivedFrame, decodedId: s.feedback?.decodedId, presentedId: s.feedback?.presentedId,
1497
+ exactPresentedSequence: s.feedback?.exactPresentedSequence,
1498
+ native: backend.nativeTelemetry }));
1499
+ s.performanceSample = current;
1500
+ }
1501
+ catch { }
1502
+ }
1503
+ const directPressure = Math.max(excessRttMs, queueAgeMs, queued) > 40 ||
1504
+ Math.max(encodeMs, decodeMs) > 0.8 * 1000 / applied.fpsCeiling;
1505
+ // Independent optional lane; never await snapshot/IO in focus rate or input processing.
1506
+ const initialOverview = s.overviewSequence === 0;
1507
+ if (backend.overview && backend.reserveOverview && s.control?.sendOverview && !s.overviewBusy && now >= (s.overviewNextAt ?? 0) &&
1508
+ s.overviewBudget.canCapture(now, applied.targetBps, !initialOverview && (directPressure || s.capturePaused || s.rateBusy))) {
1509
+ s.overviewBusy = true;
1510
+ s.overviewNextAt = now + 2000;
1511
+ const sourceWidth = configured.captureRegion?.sourceWidth ?? configured.width;
1512
+ const sourceHeight = configured.captureRegion?.sourceHeight ?? configured.height;
1513
+ void (async () => {
1514
+ const image = await backend.overview(Math.min(s.overviewBudget.maxSide, s.overviewMaxSide ?? 640));
1515
+ if (!image) {
1516
+ // Poll only initial asynchronous capture setup promptly; do not add a
1517
+ // two-second scheduling delay after WindowServer has produced its image.
1518
+ if (this.current(s) && s.backend === backend)
1519
+ s.overviewNextAt = performance.now() +
1520
+ (s.overviewSequence === 0 && s.overviewInitialPolls++ < 20 ? 250 : 2000);
1521
+ return;
1522
+ }
1523
+ if (image.sourceWidth !== sourceWidth || image.sourceHeight !== sourceHeight || !this.current(s) || s.backend !== backend ||
1524
+ s.configured !== configured || !initialOverview && (s.queuedAt || s.capturePaused))
1525
+ return;
1526
+ const sequence = s.overviewSequence + 1;
1527
+ const bytes = overviewPacket(image, sequence, s.binding.controlGeneration);
1528
+ if (!s.overviewBudget.eligible(bytes, performance.now(), applied.targetBps))
1529
+ return;
1530
+ if (!await backend.reserveOverview({ epoch: configured.mediaEpoch, wireBytes: bytes.length })) {
1531
+ if (this.current(s) && s.backend === backend && s.configured === configured && initialOverview) {
1532
+ s.overviewNextAt = performance.now() + 250;
1533
+ s.overviewMaxSide = 320;
1534
+ }
1535
+ return;
1536
+ }
1537
+ if (!this.current(s) || s.backend !== backend || s.configured !== configured ||
1538
+ !initialOverview && (s.queuedAt || s.capturePaused))
1539
+ return;
1540
+ if (s.overviewBudget.admit(bytes, performance.now(), applied.targetBps)) {
1541
+ // Rejected captures have not used the initial preview's pressure exemption.
1542
+ s.overviewSequence = sequence;
1543
+ if (!await s.control.sendOverview(bytes))
1544
+ s.overviewBudget.disconnected();
1545
+ }
1546
+ })().catch(() => { }).finally(() => { s.overviewBusy = false; });
1547
+ }
1548
+ if (decision.pauseCapture !== s.capturePaused) {
1549
+ s.capturePaused = decision.pauseCapture;
1550
+ s.delivery.reset();
1551
+ }
1552
+ // Provider qualification, not the generic slow policy, decides whether a
1553
+ // target change needs to retire codec/input authority.
1554
+ if (backend.runtimeRateReconfigure)
1555
+ decision.reopenEpoch = decision.fpsCeiling >
1556
+ (backend.runtimeRateFpsCeiling ?? s.selection?.fpsCeiling ?? configured.fpsCeiling);
1557
+ if (decision.targetBps === applied.targetBps && decision.fpsCeiling === applied.fpsCeiling)
1558
+ decision.reopenEpoch = false;
1559
+ const rateKey = `${decision.targetBps}:${decision.fpsCeiling}:${decision.pauseCapture}:${decision.reopenEpoch}`;
1560
+ if (rateKey !== s.lastRateDiagnostic && now - s.lastRateDiagnosticAt >= 1000) {
1561
+ s.lastRateDiagnostic = rateKey;
1562
+ s.lastRateDiagnosticAt = now;
1563
+ s.rateDiagnosticCount++;
1564
+ try {
1565
+ console.error(JSON.stringify({ event: 'desktop_supervisor_rate_decision', session: s.diagnosticId,
1566
+ connectionId: s.diagnosticKey, processId: process.pid, atUnixMs: Date.now(), policy: s.controller.diagnostics,
1567
+ ordinal: s.rateDiagnosticCount, epoch: configured.mediaEpoch, targetBps: decision.targetBps,
1568
+ fpsCeiling: decision.fpsCeiling, pauseCapture: decision.pauseCapture, reopenEpoch: decision.reopenEpoch,
1569
+ appliedTargetBps: applied.targetBps, appliedFpsCeiling: applied.fpsCeiling,
1570
+ queuedMs: Math.round(queued), queueAgeMs: s.feedback?.queueAgeMs ?? 0, excessRttMs: Math.max(0, s.rtt - baseline),
1571
+ deliveryDelayMs: delivery.delayMs, rawDeliveryExcessMs: delivery.excessMs,
1572
+ deliveryObservations: delivery.observations, deliveryEvidenceKnown: delivery.delayMs !== undefined,
1573
+ presented: s.presented }));
1574
+ }
1575
+ catch { /* Fixed, capped diagnostics do not affect rate decisions. */ }
1576
+ }
1577
+ s.activeBytes = 0;
1578
+ if (!s.presented && decision.reopenEpoch)
1579
+ return;
1580
+ s.rateBusy = true;
1581
+ try {
1582
+ if (decision.reopenEpoch) {
1583
+ if (!this.current(s) || s.backend !== backend || s.configured !== configured || s.startedEpoch !== configured.mediaEpoch)
1584
+ return;
1585
+ const commandId = randomBytes(16).toString('hex');
1586
+ s.configuredCommand = commandId;
1587
+ s.automaticConfiguration = commandId;
1588
+ s.backend?.configurationCommand?.(commandId);
1589
+ }
1590
+ if (this.current(s) && s.backend === backend && s.configured === configured && (decision.reopenEpoch || s.startedEpoch === configured.mediaEpoch))
1591
+ await backend.setRate(decision);
1592
+ }
1593
+ finally {
1594
+ s.rateBusy = false;
1595
+ if (s.recoveryPending) {
1596
+ s.recoveryPending = false;
1597
+ await this.recover(s);
1598
+ }
1599
+ }
1600
+ }
1601
+ async recover(s) {
1602
+ if (!this.current(s))
1603
+ return;
1604
+ // A failed media lane cannot be forgotten merely because the encoder is
1605
+ // settling a rate command. Coalesce it and recover as soon as that owner exits.
1606
+ if (s.rateBusy) {
1607
+ if (!s.recovering)
1608
+ s.recoveryPending = true;
1609
+ return;
1610
+ }
1611
+ const now = performance.now();
1612
+ s.recovery = s.recovery.filter(at => now - at < 30000);
1613
+ if (s.recovery.length >= 2 || !s.selection) {
1614
+ await this.fail(s);
1615
+ return;
1616
+ }
1617
+ s.recovery.push(now);
1618
+ s.rateBusy = true;
1619
+ s.recovering = true;
1620
+ try {
1621
+ const backend = s.backend;
1622
+ s.backend = undefined;
1623
+ s.audioConfig = undefined;
1624
+ s.audioFeedback = undefined;
1625
+ s.audioReady = false;
1626
+ s.control?.stopAudio?.();
1627
+ if (s.audioCommand && s.audioMode && s.audioMode !== 'off')
1628
+ await this.send(s, { v: 1, type: 'audio_state',
1629
+ sessionId: s.binding.sessionId, payload: { commandId: s.audioCommand, state: 'suspended', reason: 'audio_source_restarting' } });
1630
+ s.mediaAttaching = undefined;
1631
+ s.media?.close();
1632
+ s.media = undefined;
1633
+ s.packetLoop = false;
1634
+ s.viewerReady = false;
1635
+ s.decoderReady = false;
1636
+ s.presented = false;
1637
+ s.startedEpoch = 0;
1638
+ s.starting = false;
1639
+ s.startCommand = undefined;
1640
+ await backend?.stop();
1641
+ if (!this.current(s))
1642
+ return;
1643
+ const commandId = randomBytes(16).toString('hex');
1644
+ s.configuredCommand = commandId;
1645
+ await this.send(s, { v: 1, type: 'media_open', sessionId: s.binding.sessionId, payload: { commandId } });
1646
+ if (!this.current(s))
1647
+ return;
1648
+ s.preparation = this.options.provider.prepare({ ...s.selection, commandId }, s.binding);
1649
+ const next = await s.preparation;
1650
+ if (!this.current(s)) {
1651
+ await next.stop();
1652
+ return;
1653
+ }
1654
+ s.backend = next;
1655
+ void this.events(s, next);
1656
+ }
1657
+ catch (error) {
1658
+ if (this.current(s))
1659
+ this.diagnostic(s, 'recovery_failed', error);
1660
+ await this.fail(s);
1661
+ }
1662
+ finally {
1663
+ s.rateBusy = false;
1664
+ }
1665
+ }
1666
+ async close() {
1667
+ this.clearLobbies();
1668
+ const candidates = [...this.candidates.values()];
1669
+ for (const candidate of candidates)
1670
+ this.retire(candidate);
1671
+ const active = this.active;
1672
+ if (active)
1673
+ this.retire(active);
1674
+ this.closed = true;
1675
+ await this.maintenance?.catch(() => { });
1676
+ await this.tail;
1677
+ if (active)
1678
+ await this.cleanup(active);
1679
+ await Promise.all(candidates.map(candidate => this.cleanup(candidate)));
1680
+ }
1681
+ }