@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,1018 @@
1
+ import { promises as fs, constants } from 'node:fs';
2
+ import { execFile } from 'node:child_process';
3
+ import { promisify } from 'node:util';
4
+ import path from 'node:path';
5
+ import { createHash } from 'node:crypto';
6
+ import { setTimeout as delay } from 'node:timers/promises';
7
+ import { z } from 'zod';
8
+ import { HelperProcess, DesktopQueue, NativeCommandError, bounded } from './helperProcess.js';
9
+ import { DesktopMediaStream, decodeMedia, desktopMediaLane, mediaRequire } from './media.js';
10
+ import { canonicalDesktopJson, decodeCapabilityReport, decodeDesktopControl, supportsDesktopMode, captureRegion } from './protocol.js';
11
+ import { captureSamplingModes } from './captureModes.js';
12
+ import { captureTargetLabel } from './captureTargetLabel.js';
13
+ import { XfceWallpaperReader, decodeWallpaper } from './wallpaper.js';
14
+ const execute = promisify(execFile);
15
+ const totals = ['captured', 'encoded', 'videoPayloadBytes', 'headerBytes', 'rateReopens'];
16
+ class ViewerLifetime {
17
+ epoch = 0;
18
+ nextFrameId = 1;
19
+ controlGeneration = 0;
20
+ deviceGeneration = '';
21
+ inputSequence = 0;
22
+ lastAppliedInputSequence = 0;
23
+ text = new Map();
24
+ pendingText;
25
+ initialReservoirBits = 1_000_000;
26
+ appliedTargetBps = 4_000_000;
27
+ appliedFps = 30;
28
+ prepared = false;
29
+ transportDebtBits = 0;
30
+ transportCosts = new Map();
31
+ offsets = { captured: 0, encoded: 0, videoPayloadBytes: 0, headerBytes: 0, rateReopens: 0 };
32
+ session;
33
+ touched = performance.now();
34
+ reserve() {
35
+ mediaRequire(this.epoch < 0xffffffff && Number.isSafeInteger(this.nextFrameId + 1_000_000));
36
+ const nextFrameId = this.nextFrameId;
37
+ this.nextFrameId += 1_000_000;
38
+ return { epoch: ++this.epoch, nextFrameId, frameIdLimit: this.nextFrameId - 1 };
39
+ }
40
+ bind(binding) {
41
+ if (this.controlGeneration !== binding.controlGeneration || this.deviceGeneration !== binding.deviceGeneration) {
42
+ this.controlGeneration = binding.controlGeneration;
43
+ this.deviceGeneration = binding.deviceGeneration;
44
+ this.inputSequence = 0;
45
+ this.lastAppliedInputSequence = 0;
46
+ this.text.clear();
47
+ this.pendingText = undefined;
48
+ }
49
+ }
50
+ }
51
+ /** No helper/session construction in discovery. Only the image's admitted software is offered. */
52
+ export class NativeProvider {
53
+ options;
54
+ lifetimes = new Map();
55
+ revision = 1;
56
+ geometry;
57
+ geometryTail = Promise.resolve();
58
+ geometryJobs = 0;
59
+ displayId;
60
+ factory;
61
+ windowRevisions = new Map();
62
+ executable;
63
+ constructor(options) {
64
+ this.options = options;
65
+ mediaRequire(path.isAbsolute(options.helperPath) && path.isAbsolute(options.runtimeDir));
66
+ this.displayId = '00000000000000000000000000000001';
67
+ this.executable = options.helperPath;
68
+ this.factory = options.helperFactory ?? (binding => HelperProcess.launch(this.executable, { ...this.environment,
69
+ ...(binding ? { AXOM_KEEPER_TARGET: binding.displayId } : {}) }));
70
+ }
71
+ get environment() {
72
+ return this.options.environment ?? { ...process.env, XDG_RUNTIME_DIR: this.options.runtimeDir };
73
+ }
74
+ get displayControl() {
75
+ const executable = this.environment.AXOM_DESKTOP_DISPLAY_CONTROL ?? '/usr/local/bin/axom-display';
76
+ mediaRequire(path.isAbsolute(executable));
77
+ return executable;
78
+ }
79
+ async readBounded(file, limit = 49152) {
80
+ const handle = await fs.open(file, constants.O_RDONLY | constants.O_NOFOLLOW);
81
+ try {
82
+ const info = await handle.stat();
83
+ mediaRequire(info.isFile() && info.size <= limit);
84
+ const bytes = Buffer.alloc(limit + 1);
85
+ const read = await handle.read(bytes, 0, limit + 1, 0);
86
+ mediaRequire(read.bytesRead <= limit);
87
+ return JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes.subarray(0, read.bytesRead)));
88
+ }
89
+ finally {
90
+ await handle.close();
91
+ }
92
+ }
93
+ withGeometry(run) {
94
+ mediaRequire(this.geometryJobs < 16);
95
+ this.geometryJobs++;
96
+ const next = this.geometryTail.then(run);
97
+ this.geometryTail = next.catch(() => { }).finally(() => { this.geometryJobs--; });
98
+ return next;
99
+ }
100
+ probe() { return this.withGeometry(() => this.probeCurrent()); }
101
+ async wallpaper(targetId) {
102
+ mediaRequire(/^[a-f0-9]{32}$/.test(targetId));
103
+ const capabilities = await this.probe();
104
+ mediaRequire(capabilities?.displays.some(display => display.id === targetId));
105
+ const reader = new XfceWallpaperReader(async (command, args) => {
106
+ const result = await execute(command, args, { timeout: 1500, maxBuffer: 49152,
107
+ env: this.environment });
108
+ return result.stdout;
109
+ });
110
+ const discovered = await reader.discover();
111
+ const result = await execute(this.executable, ['--wallpaper-asset', discovered.file, discovered.background], { timeout: 4000, maxBuffer: 100000, env: this.environment });
112
+ const wallpaper = decodeWallpaper(result.stdout);
113
+ return { asset: { ...wallpaper.asset, placement: discovered.placement,
114
+ background: discovered.background, fidelity: discovered.fidelity === 'approximate' ? 'approximate' : wallpaper.asset.fidelity }, jpeg: wallpaper.jpeg };
115
+ }
116
+ async probeCurrent() {
117
+ if (this.options.enumerate)
118
+ return this.options.enumerate();
119
+ const c = decodeCapabilityReport(JSON.stringify({ generation: 'local', revision: 1,
120
+ desktop: await this.readBounded(this.options.manifestPath ?? '/opt/axom-desktop/provider.json') })).desktop;
121
+ mediaRequire(c && c.encoderBackends.length === 1 && c.encoderBackends[0].id === 'libx264' && c.encoderBackends[0].kind === 'software');
122
+ await fs.access(this.executable, constants.X_OK);
123
+ const identity = z.strictObject({ continuationAbi: z.literal(1), helperSha256: z.string().regex(/^[a-f0-9]{64}$/) }).parse(await this.readBounded(path.join(path.dirname(this.options.manifestPath ?? '/opt/axom-desktop/provider.json'), 'provider-identity.json'), 1024));
124
+ const binary = await fs.open(this.executable, constants.O_RDONLY | constants.O_NOFOLLOW);
125
+ try {
126
+ const info = await binary.stat();
127
+ mediaRequire(info.isFile() && info.size <= 32 * 1024 * 1024);
128
+ const hash = createHash('sha256');
129
+ for await (const bytes of binary.createReadStream({ autoClose: false }))
130
+ hash.update(bytes);
131
+ mediaRequire(hash.digest('hex') === identity.helperSha256);
132
+ }
133
+ finally {
134
+ await binary.close();
135
+ }
136
+ await fs.access(this.environment.XAUTHORITY ?? '/run/axom-desktop/Xauthority', constants.R_OK);
137
+ const runtime = await fs.stat(this.options.runtimeDir);
138
+ mediaRequire(runtime.isDirectory() && runtime.uid === process.getuid?.() && !(runtime.mode & 0o022));
139
+ const geometry = z.object({ width: z.number().int().min(2).max(4096), height: z.number().int().min(2).max(4096),
140
+ dpi: z.number().int().min(64).max(384).optional() }).parse(await this.readBounded(path.join(this.options.runtimeDir, 'geometry.json'), 1024));
141
+ if (c.displays[0].maxDpi !== undefined)
142
+ mediaRequire(geometry.dpi !== undefined && geometry.dpi <= c.displays[0].maxDpi);
143
+ if (this.geometry && (geometry.width !== this.geometry.width || geometry.height !== this.geometry.height || geometry.dpi !== this.geometry.dpi))
144
+ this.nextRevision();
145
+ this.geometry = geometry;
146
+ let failed = false;
147
+ for (const name of ['display-failed', 'input-failed']) {
148
+ try {
149
+ await fs.lstat(path.join(this.options.runtimeDir, name));
150
+ failed = true;
151
+ }
152
+ catch (error) {
153
+ if (error.code !== 'ENOENT')
154
+ throw error;
155
+ }
156
+ }
157
+ const root = c.displays[0];
158
+ const sampling = root.captureViewport === true ? { capturePixelScale: 1, resize: 'dynamic',
159
+ resizeRange: { minWidth: 2, minHeight: 2, maxWidth: geometry.width, maxHeight: geometry.height },
160
+ modes: captureSamplingModes(geometry.width, geometry.height) } : {};
161
+ const displays = [{ ...root, id: this.displayId, ...geometry, ...sampling, revision: this.revision }];
162
+ if (c.providerVersion === 'axom-linux-2' && !failed) {
163
+ const { stdout } = await execute(this.executable, ['--capture-targets'], { env: this.environment, timeout: 2500, maxBuffer: 49152 });
164
+ const axis = z.number().int().min(2).max(4096);
165
+ const targets = z.array(z.strictObject({ id: z.string().regex(/^[0-9a-f]{32}$/), kind: z.literal('window'), label: z.string().max(4096), width: axis, height: axis })).max(127).parse(JSON.parse(stdout));
166
+ for (const target of targets) {
167
+ const old = this.windowRevisions.get(target.id);
168
+ const revision = old ? old.revision + Number(old.width !== target.width || old.height !== target.height) : 1;
169
+ this.windowRevisions.set(target.id, { width: target.width, height: target.height, revision });
170
+ displays.push({ ...target, label: captureTargetLabel(target.label), revision, resize: 'dynamic', captureViewport: true, capturePixelScale: 1, ...(root.overview ? { overview: true } : {}),
171
+ resizeRange: { minWidth: 2, minHeight: 2, maxWidth: target.width & ~1, maxHeight: target.height & ~1 }, modes: captureSamplingModes(target.width, target.height) });
172
+ }
173
+ const live = new Set(targets.map(t => t.id));
174
+ for (const key of this.windowRevisions.keys())
175
+ if (!live.has(key))
176
+ this.windowRevisions.delete(key);
177
+ }
178
+ return { ...c, cursorEmbedded: false, health: failed ? 'unavailable' : 'ready', reason: failed ? 'recovery_unverified' : null, displays };
179
+ }
180
+ nextRevision() { mediaRequire(this.revision < 0xffffffff); return ++this.revision; }
181
+ /** Publish dimensions and revision only after the native transaction verifies them. */
182
+ geometryTransaction(run, sampling = false) {
183
+ return this.withGeometry(async () => {
184
+ mediaRequire(this.revision < 0xffffffff);
185
+ const revision = this.revision + 1, actual = await run(revision);
186
+ mediaRequire(Number.isSafeInteger(actual.displayRevision) && actual.displayRevision >= revision && actual.displayRevision <= 0xffffffff);
187
+ if (!sampling)
188
+ this.geometry = { width: actual.width, height: actual.height, ...(actual.dpi === undefined ? {} : { dpi: actual.dpi }) };
189
+ else if (this.geometry && actual.dpi !== undefined)
190
+ this.geometry = { ...this.geometry, dpi: actual.dpi };
191
+ this.revision = actual.displayRevision;
192
+ return actual;
193
+ });
194
+ }
195
+ async prepare(selection, binding) {
196
+ await this.options.validateSession?.();
197
+ const caps = await this.probe();
198
+ mediaRequire(caps?.health === 'ready' && ['granted', 'not_applicable'].includes(caps.permission));
199
+ if (selection.cursorVisible !== undefined)
200
+ mediaRequire(caps.cursorVisibility === true);
201
+ const display = caps.displays.find(d => d.id === binding.displayId);
202
+ mediaRequire(display);
203
+ // Capture sampling keeps the physical source geometry independent of the
204
+ // encoded output size. Physical-resolution sessions use their configured grid.
205
+ const effectiveRegion = selection.captureRegion ?? (display.captureViewport === true ? {
206
+ x: 0, y: 0, width: display.width, height: display.height,
207
+ sourceWidth: display.width, sourceHeight: display.height,
208
+ } : undefined);
209
+ if (selection.captureRegion !== undefined) {
210
+ const region = captureRegion.parse(selection.captureRegion);
211
+ mediaRequire(display.captureViewport === true && region.sourceWidth === display.width && region.sourceHeight === display.height);
212
+ }
213
+ if (selection.dpi !== undefined)
214
+ mediaRequire(display.maxDpi !== undefined && selection.dpi <= display.maxDpi);
215
+ const format = caps.encoderBackends.find(b => b.id === (this.options.backend ?? 'libx264'))?.formats.find(f => f.format === selection.format && f.cappedRate);
216
+ mediaRequire(format && selection.width <= format.maxWidth && selection.height <= format.maxHeight &&
217
+ selection.width * selection.height <= format.maxPixels && selection.fpsCeiling <= format.maxFps &&
218
+ supportsDesktopMode(display, selection.width, selection.height));
219
+ for (const [id, life] of this.lifetimes)
220
+ if (!life.session && performance.now() - life.touched > 60000)
221
+ this.lifetimes.delete(id);
222
+ let life = this.lifetimes.get(binding.sessionId);
223
+ if (!life) {
224
+ mediaRequire(this.lifetimes.size < 1024);
225
+ life = new ViewerLifetime();
226
+ this.lifetimes.set(binding.sessionId, life);
227
+ }
228
+ if (life.session)
229
+ await life.session.stop();
230
+ life.bind(binding);
231
+ let revision = Math.max(this.revision, display.revision);
232
+ if ((display.captureViewport !== true && (selection.width !== display.width || selection.height !== display.height)) || selection.dpi !== undefined) {
233
+ const physicalWidth = display.captureViewport === true ? display.width : selection.width;
234
+ const physicalHeight = display.captureViewport === true ? display.height : selection.height;
235
+ const actual = await this.geometryTransaction(async (displayRevision) => {
236
+ const { stdout } = await execute(this.displayControl, ['--transaction', `${physicalWidth}x${physicalHeight}${selection.dpi === undefined ? '' : '@' + selection.dpi}`], { env: this.environment, timeout: 4500, maxBuffer: 4096 });
237
+ const result = z.object({ outcome: z.literal('applied'), width: z.literal(physicalWidth), height: z.literal(physicalHeight),
238
+ dpi: selection.dpi === undefined ? z.number().optional() : z.literal(selection.dpi) }).parse(JSON.parse(stdout));
239
+ return { ...result, displayRevision };
240
+ });
241
+ revision = actual.displayRevision;
242
+ }
243
+ const helper = await (this.options.sessionHelperFactory?.(binding) ?? this.factory(binding));
244
+ const identity = { sessionId: binding.sessionId, deviceGeneration: binding.deviceGeneration,
245
+ controlGeneration: binding.controlGeneration, displayRevision: revision };
246
+ const reserved = life.reserve();
247
+ try {
248
+ mediaRequire((await helper.request('verify_cleanup', identity)).result.verified === true);
249
+ const payload = { ...(selection.cursorVisible === undefined ? {} : { cursorVisible: selection.cursorVisible }), ...(effectiveRegion ? { captureRegion: effectiveRegion } : {}), width: selection.width, height: selection.height, fpsCeiling: life.prepared ? Math.min(life.appliedFps, selection.fpsCeiling) : selection.fpsCeiling,
250
+ rateTargetBps: life.appliedTargetBps, quality: selection.quality, backend: this.options.backend ?? 'libx264', leaseMs: 30000, representation: 1,
251
+ ...reserved, ...(life.prepared ? { continuation: { initialReservoirBits: life.initialReservoirBits, inputSequence: life.inputSequence,
252
+ lastAppliedInputSequence: life.lastAppliedInputSequence } } : {}) };
253
+ const reply = await helper.request('prepare', identity, payload);
254
+ const actual = z.strictObject({ backend: z.literal(this.options.backend ?? 'libx264'), width: z.literal(selection.width), height: z.literal(selection.height),
255
+ fpsCeiling: z.literal(payload.fpsCeiling), rateTargetBps: z.literal(payload.rateTargetBps), representation: z.literal(payload.representation) }).parse(reply.result.selection);
256
+ if (!life.prepared)
257
+ life.initialReservoirBits = actual.rateTargetBps / 4;
258
+ life.appliedFps = actual.fpsCeiling;
259
+ life.prepared = true;
260
+ const session = new NativeBackendSession(helper, life, identity, selection, reserved, this, effectiveRegion);
261
+ life.session = session;
262
+ return session;
263
+ }
264
+ catch (error) {
265
+ await helper.stop(identity);
266
+ throw error;
267
+ }
268
+ }
269
+ async recoverDisplay(binding, reserved) {
270
+ return this.geometryTransaction(async () => {
271
+ const helper = await (this.options.sessionHelperFactory?.(binding) ?? this.factory(binding));
272
+ const identity = { sessionId: binding.sessionId, deviceGeneration: binding.deviceGeneration, controlGeneration: binding.controlGeneration,
273
+ displayRevision: reserved.displayRevision };
274
+ try {
275
+ mediaRequire((await helper.request('verify_cleanup', identity)).result.verified === true);
276
+ const { result } = await helper.request('recover_display', identity);
277
+ const actual = z.object({ outcome: z.literal('recovered'), width: z.number().int().min(2).max(4096), height: z.number().int().min(2).max(4096) }).parse(result);
278
+ return { verified: true, width: actual.width, height: actual.height, displayRevision: reserved.displayRevision };
279
+ }
280
+ finally {
281
+ await helper.stop(identity);
282
+ }
283
+ });
284
+ }
285
+ }
286
+ export class NativeBackendSession {
287
+ helper;
288
+ life;
289
+ identity;
290
+ selection;
291
+ reserved;
292
+ provider;
293
+ effectiveRegion;
294
+ get audioSupported() { return this.helper.audioSupported === true; }
295
+ get audioPackets() { return this.helper.audioPackets; }
296
+ async startAudio(audioEpoch, bitrate) {
297
+ return this.serialized(async () => {
298
+ mediaRequire(!this.stopped && this.life.session === this && this.audioSupported);
299
+ const reply = await this.helper.request('audio_start', this.identity, { audioEpoch, bitrate });
300
+ return z.strictObject({ audioEpoch: z.literal(audioEpoch), sampleRate: z.literal(48000), channels: z.literal(2),
301
+ framesPerPacket: z.literal(960), bitrate: z.literal(bitrate), lookaheadFrames: z.number().int().min(0).max(960) }).parse(reply.result);
302
+ });
303
+ }
304
+ async setAudioRate(bitrate) {
305
+ return this.serialized(async () => {
306
+ mediaRequire(!this.stopped && this.life.session === this && this.audioSupported);
307
+ await this.helper.request('audio_rate', this.identity, { bitrate });
308
+ });
309
+ }
310
+ async stopAudio() {
311
+ return this.serialized(async () => {
312
+ if (!this.stopped && this.audioSupported)
313
+ await this.helper.request('audio_stop', this.identity);
314
+ });
315
+ }
316
+ async audioStatus() {
317
+ const reply = await this.helper.request('audio_status', this.identity);
318
+ return z.strictObject({ state: z.enum(['off', 'playing', 'unavailable']) }).parse(reply.result).state;
319
+ }
320
+ runtimeRate = false;
321
+ get runtimeRateReconfigure() { return this.runtimeRate; }
322
+ codecFpsCeiling;
323
+ get runtimeRateFpsCeiling() { return this.codecFpsCeiling; }
324
+ events = new DesktopQueue();
325
+ stopped = false;
326
+ stopPromise;
327
+ started = false;
328
+ captureEpoch = 0;
329
+ configuration;
330
+ stream = new DesktopMediaStream(() => this.effectiveRegion ? { width: this.effectiveRegion.sourceWidth, height: this.effectiveRegion.sourceHeight } : undefined);
331
+ desired;
332
+ applied;
333
+ reservedRate;
334
+ firstFrame = 0;
335
+ presented = false;
336
+ packetConsumer = false;
337
+ lastCounters;
338
+ transportTotal = 0;
339
+ transportRestored = false;
340
+ operationTail = Promise.resolve();
341
+ operationCount = 0;
342
+ boundaryAttempt;
343
+ retiringMedia;
344
+ pendingPackets = new Map();
345
+ pendingPacketBytes = 0;
346
+ readPayloadEpoch = 0;
347
+ readPayloadBytes = 0;
348
+ cutSettledFrame = 0;
349
+ motionCut;
350
+ packetKey(packet) { return `${packet.epoch}:${desktopMediaLane(packet.type)}:${packet.sequence}`; }
351
+ forwardingUnit = false;
352
+ forwardingCursor = false;
353
+ forwardedFrame;
354
+ drainWake;
355
+ constructor(helper, life, identity, selection, reserved, provider, effectiveRegion) {
356
+ this.helper = helper;
357
+ this.life = life;
358
+ this.identity = identity;
359
+ this.selection = selection;
360
+ this.reserved = reserved;
361
+ this.provider = provider;
362
+ this.effectiveRegion = effectiveRegion;
363
+ this.desired = this.applied = { targetBps: life.appliedTargetBps, fpsCeiling: life.prepared ? Math.min(life.appliedFps, selection.fpsCeiling) : selection.fpsCeiling, pauseCapture: false, reopenEpoch: false };
364
+ this.codecFpsCeiling = this.applied.fpsCeiling;
365
+ this.forwardedFrame = helper.counters?.lastAllocatedFrameId ?? 0;
366
+ this.publishConfigured();
367
+ void helper.closed.then(() => { if (!this.stopped)
368
+ this.events.push({ type: 'failed', code: 'encoder_failed' }); });
369
+ }
370
+ get nativeTelemetry() { return this.helper.counters; }
371
+ get epoch() { return this.reserved.epoch; }
372
+ get displayRevision() { return this.identity.displayRevision; }
373
+ focusRevision = 1;
374
+ async focus(focus) {
375
+ return this.serialized(async () => {
376
+ const region = this.effectiveRegion;
377
+ mediaRequire(this.presented && this.selection.captureRegion && region && focus.revision > this.focusRevision &&
378
+ focus.region.sourceWidth === region.sourceWidth && focus.region.sourceHeight === region.sourceHeight);
379
+ const result = await this.command('focus', { epoch: this.epoch, displayRevision: this.displayRevision, focus });
380
+ const checked = decodeDesktopControl(JSON.stringify({ v: 1, type: 'focus_result', sessionId: this.identity.sessionId,
381
+ payload: { commandId: this.selection.commandId, controlGeneration: this.identity.controlGeneration, mediaEpoch: this.epoch,
382
+ displayRevision: this.displayRevision, ...result } }), 'device');
383
+ mediaRequire(checked.type === 'focus_result');
384
+ if (checked.payload.outcome === 'applied') {
385
+ mediaRequire(canonicalDesktopJson(checked.payload.focus) === canonicalDesktopJson(focus));
386
+ this.focusRevision = focus.revision;
387
+ this.effectiveRegion = focus.region;
388
+ }
389
+ return { focus: checked.payload.focus, outcome: checked.payload.outcome };
390
+ });
391
+ }
392
+ get rateState() { return { desired: { ...this.desired }, applied: { ...this.applied } }; }
393
+ async checkHealth() {
394
+ await this.provider.options.validateSession?.();
395
+ await this.operationTail;
396
+ await this.command('status');
397
+ if (this.lastCounters?.inputAvailable === false) {
398
+ this.events.push({ type: 'recovery', result: { kind: 'input', verified: false } });
399
+ throw new NativeCommandError('unavailable');
400
+ }
401
+ }
402
+ get counters() { return Object.fromEntries(totals.map(k => [k, this.life.offsets[k] + (this.lastCounters?.[k] ?? 0)])); }
403
+ configured() {
404
+ // Capture-only options belong to prepare, not the configured wire message.
405
+ const { cursorVisible: _cursorVisible, ...selection } = this.selection;
406
+ return { ...selection, deviceGeneration: this.identity.deviceGeneration, controlGeneration: this.identity.controlGeneration,
407
+ mediaEpoch: this.epoch, displayRevision: this.displayRevision, encoderBackend: this.provider.options.backend ?? 'libx264', rateTargetBps: this.applied.targetBps,
408
+ fpsCeiling: this.codecFpsCeiling };
409
+ }
410
+ publishConfigured() { this.events.push({ type: 'configured', configured: this.configured() }); }
411
+ async command(name, payload = {}) {
412
+ if (['input', 'held_state', 'clipboard', 'focus', 'resize'].includes(name))
413
+ await this.provider.options.validateSession?.();
414
+ mediaRequire(!this.stopped && this.life.session === this);
415
+ const admission = name === 'set_rate' ? { ...payload } : undefined;
416
+ const current = () => {
417
+ if (admission && this.captureEpoch !== admission.epoch)
418
+ admission.pauseCapture = true;
419
+ return !this.stopped && this.life.session === this;
420
+ };
421
+ current();
422
+ const reply = await this.helper.request(name, { ...this.identity }, admission ?? payload, current);
423
+ this.lastCounters = reply.counters;
424
+ this.life.lastAppliedInputSequence = Math.max(this.life.lastAppliedInputSequence, reply.counters.lastInputSequence ?? 0);
425
+ mediaRequire(!this.stopped && this.life.session === this);
426
+ return reply.result;
427
+ }
428
+ async cacheDependency(request) {
429
+ return this.serialized(async () => {
430
+ mediaRequire(request.mediaEpoch === this.epoch && request.controlGeneration === this.identity.controlGeneration && request.displayRevision === this.displayRevision);
431
+ const result = await this.command('cache_dependency', { epoch: request.mediaEpoch, exactSequence: request.exactSequence, cacheId: request.cacheId });
432
+ const dependency = z.strictObject({ cacheId: z.literal(request.cacheId), width: z.literal(request.width), height: z.literal(request.height),
433
+ pixels: z.array(z.number().int().min(0).max(255)).length(request.width * request.height * 4) }).parse(result);
434
+ const pixels = Buffer.from(dependency.pixels);
435
+ mediaRequire(pixels.every((byte, i) => i % 4 !== 3 || byte === 255));
436
+ return pixels.toString('base64');
437
+ });
438
+ }
439
+ async accountTransportCost(cost) {
440
+ return this.serialized(async () => {
441
+ mediaRequire(/^[a-f0-9]{32}$/.test(cost.id) && Number.isSafeInteger(cost.totalBytes) && cost.totalBytes >= 0);
442
+ const previous = this.life.transportCosts.get(cost.id) ?? 0;
443
+ mediaRequire(cost.totalBytes >= previous);
444
+ if (!this.life.transportCosts.has(cost.id)) {
445
+ // rateTick owns one report at a time; retired peers cannot enqueue a
446
+ // later report behind eight subsequent replacements.
447
+ if (this.life.transportCosts.size === 8)
448
+ this.life.transportCosts.delete(this.life.transportCosts.keys().next().value);
449
+ this.life.transportCosts.set(cost.id, 0);
450
+ }
451
+ await this.restoreTransportDebt();
452
+ const total = this.transportTotal + cost.totalBytes - previous;
453
+ mediaRequire(Number.isSafeInteger(total));
454
+ if (total !== this.transportTotal)
455
+ await this.command('transport_cost', { totalBytes: total });
456
+ this.transportTotal = total;
457
+ this.life.transportCosts.set(cost.id, cost.totalBytes);
458
+ });
459
+ }
460
+ async restoreTransportDebt() {
461
+ if (this.transportRestored)
462
+ return;
463
+ this.transportTotal = Math.ceil(this.life.transportDebtBits / 8);
464
+ if (this.transportTotal)
465
+ await this.command('transport_cost', { totalBytes: this.transportTotal });
466
+ this.transportRestored = true;
467
+ }
468
+ async start() {
469
+ await this.restoreTransportDebt();
470
+ const reserved = this.reserved;
471
+ this.captureEpoch = reserved.epoch;
472
+ try {
473
+ if (!this.started) {
474
+ const result = await this.command('start', { viewerReady: true, mediaBound: true });
475
+ this.accepted(result.accepted, this.applied.targetBps, this.applied.fpsCeiling);
476
+ this.started = true;
477
+ }
478
+ else {
479
+ await this.command('set_rate', { ...this.applied, pauseCapture: false, reopenEpoch: false, epoch: this.epoch });
480
+ this.applied.pauseCapture = false;
481
+ }
482
+ mediaRequire(this.reserved === reserved);
483
+ }
484
+ catch (error) {
485
+ if (this.reserved === reserved)
486
+ this.captureEpoch = 0;
487
+ throw error;
488
+ }
489
+ }
490
+ accepted(value, targetBps, fpsCeiling, verified = false) {
491
+ const schema = z.object({ backend: z.literal(this.provider.options.backend ?? 'libx264'), representation: z.literal(1),
492
+ width: z.literal(this.selection.width), height: z.literal(this.selection.height), fpsCeiling: z.union([z.literal(15), z.literal(30), z.literal(60)]),
493
+ targetBps: z.number().int().min(1).max(20000000), maxRateBps: z.number().int().min(1), bufferBits: z.number().int().min(1),
494
+ maxBFrames: z.literal(0), qualifiedCap: z.boolean(), runtimeRateReconfigure: z.boolean().optional(),
495
+ rateAdapter: z.string().optional() });
496
+ const actual = schema.parse(value);
497
+ mediaRequire(actual.targetBps === targetBps && actual.fpsCeiling === fpsCeiling && actual.maxRateBps <= targetBps &&
498
+ actual.bufferBits <= targetBps / 4 && actual.representation === 1);
499
+ const runtimeAdapter = this.provider.options.backend === 'videotoolbox' ? 'videotoolbox-data-rate-runtime-v1' : 'x264-vbv-runtime-v1';
500
+ if (verified) {
501
+ mediaRequire(actual.qualifiedCap);
502
+ this.runtimeRate = actual.runtimeRateReconfigure === true && actual.rateAdapter === runtimeAdapter;
503
+ }
504
+ return actual;
505
+ }
506
+ async credit(value) {
507
+ // Credits unblock boundary drains and must never wait on the boundary operation.
508
+ mediaRequire(!this.stopped && this.life.session === this);
509
+ const binding = { ...this.identity };
510
+ const current = () => !this.stopped && this.life.session === this && this.identity.displayRevision === binding.displayRevision &&
511
+ (this.helper.counters ?? this.lastCounters)?.epoch === value.epoch;
512
+ if (!current())
513
+ return;
514
+ let reply;
515
+ try {
516
+ reply = await this.helper.request('credit', binding, value, current);
517
+ }
518
+ catch (error) {
519
+ if (!current())
520
+ return;
521
+ throw error;
522
+ }
523
+ if (!current())
524
+ return;
525
+ this.lastCounters = reply.counters;
526
+ this.life.lastAppliedInputSequence = Math.max(this.life.lastAppliedInputSequence, reply.counters.lastInputSequence ?? 0);
527
+ }
528
+ /** Settle the pipe packet only after actual bounded transport admission and its ownership fence. */
529
+ mediaForwarded(packet) {
530
+ if (this.stopped || this.life.session !== this)
531
+ return;
532
+ const key = this.packetKey(packet), pending = this.pendingPackets.get(key);
533
+ mediaRequire(pending && pending.epoch === packet.epoch && pending.displayRevision === packet.displayRevision && pending.sequence === packet.sequence && pending.type === packet.type &&
534
+ pending.frameId === packet.frameId && pending.fragmentIndex === packet.fragmentIndex);
535
+ this.pendingPackets.delete(key);
536
+ this.pendingPacketBytes -= pending.payload.length;
537
+ if (packet.fragmentIndex + 1 === packet.fragmentCount) {
538
+ if (packet.type === 2)
539
+ this.forwardingUnit = false;
540
+ if (packet.type === 3)
541
+ this.forwardingCursor = false;
542
+ if (packet.type === 2 || packet.type === 5)
543
+ this.forwardedFrame = Math.max(this.forwardedFrame, packet.frameId);
544
+ }
545
+ this.drainWake?.();
546
+ }
547
+ async prepareMotionCut(value) {
548
+ mediaRequire(value.epoch === this.epoch && Number.isSafeInteger(value.cutId) && value.cutId > 0 && !this.stopped && this.life.session === this);
549
+ if (this.motionCut) {
550
+ if (this.motionCut.fence.epoch === value.epoch && this.motionCut.fence.cutId === value.cutId)
551
+ return this.motionCut.fence;
552
+ mediaRequire(this.motionCut.committed && (this.motionCut.fence.epoch !== value.epoch || value.cutId > this.motionCut.fence.cutId));
553
+ }
554
+ const deadline = performance.now() + 4500;
555
+ for (;;) {
556
+ try {
557
+ const reply = await this.helper.request('prepare_motion_cut', this.identity, value, () => !this.stopped && this.life.session === this && this.epoch === value.epoch);
558
+ mediaRequire(!this.stopped && this.life.session === this && this.epoch === value.epoch && reply.counters.epoch === value.epoch);
559
+ const fence = z.strictObject({ epoch: z.literal(value.epoch), cutId: z.literal(value.cutId), pictureThrough: z.number().int().min(0).max(0xffffffff), frameThrough: z.number().int().min(1).max(Number.MAX_SAFE_INTEGER) }).parse(reply.result);
560
+ this.lastCounters = reply.counters;
561
+ this.motionCut = { fence, pipeThrough: reply.counters.sentPayloadBytes, settled: false, committed: false };
562
+ return fence;
563
+ }
564
+ catch (error) {
565
+ if (!(error instanceof NativeCommandError) || error.code !== 'busy' || performance.now() >= deadline)
566
+ throw error;
567
+ await delay(10);
568
+ }
569
+ }
570
+ }
571
+ mediaDisposed(packet, fence) {
572
+ const cut = this.motionCut;
573
+ mediaRequire(cut && cut.fence.cutId === fence.cutId && cut.fence.epoch === packet.epoch && packet.type === 2 && packet.sequence <= fence.pictureThrough);
574
+ const key = this.packetKey(packet), pending = this.pendingPackets.get(key);
575
+ mediaRequire(pending && pending.type === 2 && pending.epoch === packet.epoch && pending.displayRevision === packet.displayRevision && pending.frameId === packet.frameId && pending.fragmentIndex === packet.fragmentIndex);
576
+ this.pendingPackets.delete(key);
577
+ this.pendingPacketBytes -= pending.payload.length;
578
+ if (packet.fragmentIndex + 1 === packet.fragmentCount)
579
+ this.forwardingUnit = false;
580
+ this.drainWake?.();
581
+ }
582
+ async settleMotionCut(fence) {
583
+ const cut = this.motionCut;
584
+ mediaRequire(cut && cut.fence.cutId === fence.cutId && cut.fence.epoch === fence.epoch);
585
+ if (cut.settled)
586
+ return;
587
+ const deadline = performance.now() + 4500;
588
+ while (this.readPayloadEpoch !== fence.epoch || this.readPayloadBytes < cut.pipeThrough || [...this.pendingPackets.values()].some(p => p.epoch === fence.epoch && p.type === 2 && p.sequence <= fence.pictureThrough)) {
589
+ mediaRequire(!this.stopped && this.life.session === this && this.epoch === fence.epoch);
590
+ const remaining = deadline - performance.now();
591
+ mediaRequire(remaining > 0);
592
+ try {
593
+ await bounded(new Promise(resolve => { this.drainWake = resolve; }), remaining);
594
+ }
595
+ finally {
596
+ this.drainWake = undefined;
597
+ }
598
+ }
599
+ this.stream.applyMotionCut(fence.pictureThrough, fence.frameThrough);
600
+ this.forwardingUnit = false;
601
+ this.cutSettledFrame = Math.max(this.cutSettledFrame, fence.frameThrough);
602
+ cut.settled = true;
603
+ this.drainWake?.();
604
+ }
605
+ async commitMotionCut(value) {
606
+ const cut = this.motionCut;
607
+ mediaRequire(cut && cut.settled && cut.fence.cutId === value.cutId && cut.fence.epoch === value.epoch && this.epoch === value.epoch);
608
+ if (cut.committed)
609
+ return;
610
+ await this.command('commit_motion_cut', value);
611
+ cut.committed = true;
612
+ }
613
+ async drainForwarded(frameId) {
614
+ if (!this.packetConsumer)
615
+ return;
616
+ const deadline = performance.now() + 4500;
617
+ while (Math.max(this.forwardedFrame, this.cutSettledFrame) < frameId || this.forwardingUnit || this.forwardingCursor || this.pendingPackets.size) {
618
+ mediaRequire(!this.stopped && this.life.session === this);
619
+ const remaining = deadline - performance.now();
620
+ mediaRequire(remaining > 0);
621
+ try {
622
+ await bounded(new Promise(resolve => { this.drainWake = resolve; }), remaining);
623
+ }
624
+ finally {
625
+ this.drainWake = undefined;
626
+ }
627
+ }
628
+ mediaRequire(!this.stopped && this.life.session === this);
629
+ }
630
+ /** Stop new captures, but keep complete encoded units flowing on the old media leg. */
631
+ async pauseAndDrain() {
632
+ const payload = { ...this.applied, pauseCapture: true, reopenEpoch: false, epoch: this.epoch };
633
+ const deadline = performance.now() + 4500;
634
+ do {
635
+ await this.command('set_rate', payload);
636
+ if (this.lastCounters?.pendingPayloadBytes === 0)
637
+ return;
638
+ await delay(10);
639
+ } while (!this.stopped && performance.now() < deadline);
640
+ throw new NativeCommandError('transport_failed');
641
+ }
642
+ async decoderReady(ack) {
643
+ mediaRequire(ack.controlGeneration === this.identity.controlGeneration && ack.displayRevision === this.displayRevision &&
644
+ ack.mediaEpoch === this.epoch && this.configuration && ack.representation === this.configuration.representation &&
645
+ JSON.stringify(ack.parameterSets) === JSON.stringify(this.configuration.parameterSets));
646
+ await this.command('decoder_ready', { epoch: this.epoch, representation: ack.representation, parameterSets: ack.parameterSets });
647
+ }
648
+ async firstPresented(ack) {
649
+ mediaRequire(ack.epoch === this.epoch && this.firstFrame > 0 && ack.frameId >= this.firstFrame);
650
+ if (!this.presented) {
651
+ await this.command('first_presented', { epoch: this.epoch, frameId: this.firstFrame });
652
+ this.presented = true;
653
+ }
654
+ }
655
+ async acknowledge(ack) {
656
+ await this.operationTail;
657
+ if (this.stopped || ack.epoch !== this.epoch || !this.presented)
658
+ return;
659
+ await this.command('acknowledge', ack);
660
+ }
661
+ async renewAuthenticatedLease(renewal) {
662
+ await this.operationTail;
663
+ await this.command('renew', renewal);
664
+ }
665
+ async heldState(snapshot) {
666
+ await this.operationTail;
667
+ if (snapshot.controlGeneration !== this.identity.controlGeneration || snapshot.displayRevision !== this.displayRevision || snapshot.sequence < this.life.inputSequence)
668
+ return;
669
+ if (snapshot.sequence > this.life.inputSequence) {
670
+ await this.command('held_state', { ...snapshot, keys: [], buttons: [], sequence: this.life.inputSequence });
671
+ return;
672
+ }
673
+ await this.command('held_state', snapshot);
674
+ }
675
+ async reserveOverview(value) {
676
+ await this.operationTail;
677
+ if (this.stopped || !this.started || value.epoch !== this.epoch)
678
+ return false;
679
+ mediaRequire(Number.isSafeInteger(value.wireBytes) && value.wireBytes >= 44 && value.wireBytes <= 32808);
680
+ const epoch = this.epoch;
681
+ try {
682
+ const result = await this.command('reserve_overview', value);
683
+ mediaRequire(typeof result.admitted === 'boolean');
684
+ return !this.stopped && this.epoch === epoch && result.admitted;
685
+ }
686
+ catch {
687
+ return false;
688
+ }
689
+ }
690
+ async overview(maxSide) {
691
+ if (!this.selection.captureRegion || !this.started || this.stopped)
692
+ return undefined;
693
+ try {
694
+ const reply = await this.command('overview', { maxSide });
695
+ if (typeof reply.jpeg !== 'string')
696
+ return undefined;
697
+ const jpeg = Buffer.from(reply.jpeg, 'base64');
698
+ mediaRequire(jpeg.length <= 32768 && jpeg.toString('base64') === reply.jpeg);
699
+ return { width: Number(reply.width), height: Number(reply.height), sourceWidth: Number(reply.sourceWidth), sourceHeight: Number(reply.sourceHeight), jpeg };
700
+ }
701
+ catch {
702
+ // An optional image must never retire decoder/input authority.
703
+ return undefined;
704
+ }
705
+ }
706
+ async clipboard(request) {
707
+ mediaRequire(request.controlGeneration === this.identity.controlGeneration && !this.stopped);
708
+ const reply = await this.command('clipboard', request);
709
+ const message = decodeDesktopControl(JSON.stringify({ v: 1, type: 'clipboard_result', sessionId: this.identity.sessionId,
710
+ payload: reply }), 'device');
711
+ mediaRequire(message.type === 'clipboard_result');
712
+ return message.payload;
713
+ }
714
+ async input(event) {
715
+ decodeDesktopControl(JSON.stringify({ v: 1, type: 'input', sessionId: this.identity.sessionId, payload: event }), 'viewer');
716
+ const rejected = { sequence: event.sequence, outcome: 'rejected', ...(event.action === 'text' ? { operationId: event.operationId } : {}) };
717
+ if (this.stopped || event.controlGeneration !== this.identity.controlGeneration || event.displayRevision !== this.displayRevision ||
718
+ event.sequence <= this.life.inputSequence)
719
+ return rejected;
720
+ this.life.inputSequence = event.sequence;
721
+ if (event.action === 'text') {
722
+ const previous = this.life.text.get(event.operationId);
723
+ if (previous)
724
+ return { ...previous, sequence: event.sequence };
725
+ if (this.life.text.size >= 1024 || this.life.pendingText)
726
+ return rejected;
727
+ this.life.text.set(event.operationId, { ...rejected, outcome: 'unknown' });
728
+ this.life.pendingText = event.operationId;
729
+ }
730
+ try {
731
+ let nativeEvent = event;
732
+ if (event.action === 'pointer' || event.action === 'button' || event.action === 'wheel') {
733
+ const region = this.selection.captureRegion;
734
+ const width = region?.sourceWidth ?? this.selection.width;
735
+ const height = region?.sourceHeight ?? this.selection.height;
736
+ if (event.x < 0 || event.x >= width || event.y < 0 || event.y >= height)
737
+ return rejected;
738
+ if (!region && this.effectiveRegion) {
739
+ const source = this.effectiveRegion;
740
+ nativeEvent = { ...event, x: Math.floor(event.x * source.sourceWidth / width),
741
+ y: Math.floor(event.y * source.sourceHeight / height) };
742
+ }
743
+ }
744
+ const result = await this.command('input', nativeEvent);
745
+ if (event.action === 'text' && result.outcome === 'pending')
746
+ return await this.textResult(event.operationId);
747
+ const parsed = decodeDesktopControl(JSON.stringify({ v: 1, type: 'input_result', sessionId: this.identity.sessionId, payload: result }), 'device');
748
+ mediaRequire(parsed.type === 'input_result');
749
+ if (event.action === 'text') {
750
+ this.life.text.set(event.operationId, parsed.payload);
751
+ this.life.pendingText = undefined;
752
+ }
753
+ return parsed.payload;
754
+ }
755
+ catch {
756
+ if (event.action === 'text') {
757
+ this.life.pendingText = undefined;
758
+ return this.life.text.get(event.operationId);
759
+ }
760
+ return rejected;
761
+ }
762
+ }
763
+ async textResult(operationId) {
764
+ const saved = this.life.text.get(operationId);
765
+ mediaRequire(saved);
766
+ if (this.life.pendingText !== operationId)
767
+ return saved;
768
+ const deadline = performance.now() + 5000;
769
+ try {
770
+ while (!this.stopped && performance.now() < deadline) {
771
+ const result = await this.command('text_result', { operationId });
772
+ if (result.outcome !== 'pending') {
773
+ const message = decodeDesktopControl(JSON.stringify({ v: 1, type: 'input_result', sessionId: this.identity.sessionId, payload: result }), 'device');
774
+ mediaRequire(message.type === 'input_result');
775
+ this.life.text.set(operationId, message.payload);
776
+ return message.payload;
777
+ }
778
+ await delay(10);
779
+ }
780
+ }
781
+ catch { /* Preserve UNKNOWN and never replay after interruption. */ }
782
+ finally {
783
+ if (this.life.pendingText === operationId)
784
+ this.life.pendingText = undefined;
785
+ }
786
+ return this.life.text.get(operationId);
787
+ }
788
+ serialized(run) {
789
+ // Control requests (up to 32 in the supervisor) share this queue with the
790
+ // rate controller. Theme sync can overlap a resize and a rate update at
791
+ // first presentation; pending work must wait rather than invalidate it.
792
+ mediaRequire(this.operationCount < 64);
793
+ this.operationCount++;
794
+ const result = this.operationTail.then(run);
795
+ this.operationTail = result.catch(() => { }).finally(() => { this.operationCount--; });
796
+ return result;
797
+ }
798
+ async atBoundary(command, payload, reserved) {
799
+ const deadline = performance.now() + (command === 'set_rate' ? 12000 : 4500);
800
+ do {
801
+ const coalesced = command === 'set_rate' ? { ...payload, ...this.desired, pauseCapture: true, reopenEpoch: true, epoch: reserved.epoch } : payload;
802
+ try {
803
+ const old = { epoch: this.epoch, displayRevision: this.displayRevision };
804
+ const attempt = this.command(command, coalesced);
805
+ this.boundaryAttempt = attempt;
806
+ let result;
807
+ try {
808
+ result = await attempt;
809
+ }
810
+ finally {
811
+ if (this.boundaryAttempt === attempt)
812
+ this.boundaryAttempt = undefined;
813
+ }
814
+ // Native has closed old capture admission and completed its private writes.
815
+ // Its allocation fence becomes proof only after the real WS acknowledgements.
816
+ this.retiringMedia = old;
817
+ await this.drainForwarded(this.lastCounters.lastAllocatedFrameId);
818
+ return result;
819
+ }
820
+ catch (error) {
821
+ if (!(error instanceof NativeCommandError) || !['busy', 'rate_limited'].includes(error.code) || performance.now() >= deadline)
822
+ throw error;
823
+ this.events.push({ type: 'rate_deferred', reason: error.code, reserved });
824
+ await delay(25);
825
+ }
826
+ } while (!this.stopped);
827
+ throw new NativeCommandError('transport_failed');
828
+ }
829
+ async setRate(decision) {
830
+ this.desired = { ...decision };
831
+ return this.serialized(async () => {
832
+ const current = { ...this.desired };
833
+ if (current.targetBps === this.applied.targetBps && current.fpsCeiling === this.applied.fpsCeiling)
834
+ current.reopenEpoch = false;
835
+ if (!current.reopenEpoch && this.captureEpoch === this.epoch &&
836
+ current.targetBps === this.applied.targetBps && current.fpsCeiling === this.applied.fpsCeiling &&
837
+ current.pauseCapture === this.applied.pauseCapture)
838
+ return;
839
+ if (!current.reopenEpoch) {
840
+ if (!this.runtimeRate) {
841
+ if (current.targetBps !== this.applied.targetBps || current.fpsCeiling > this.applied.fpsCeiling)
842
+ return;
843
+ await this.command('set_rate', { ...current, epoch: this.epoch });
844
+ this.applied = { ...current, pauseCapture: this.lastCounters.pauseCapture };
845
+ return;
846
+ }
847
+ const changed = current.targetBps !== this.applied.targetBps || current.fpsCeiling !== this.applied.fpsCeiling;
848
+ const result = await this.command('set_rate', { ...current, epoch: this.epoch });
849
+ if (changed) {
850
+ this.accepted(result.accepted, current.targetBps, current.fpsCeiling, true);
851
+ mediaRequire(result.reopened === false && Number.isSafeInteger(result.appliedAtMonotonicMs));
852
+ this.life.appliedTargetBps = current.targetBps;
853
+ this.life.appliedFps = current.fpsCeiling;
854
+ this.events.push({ type: 'rate_applied', decision: current, epoch: this.epoch, actualTargetBps: current.targetBps,
855
+ actualFpsCeiling: current.fpsCeiling, appliedAtMonotonicMs: result.appliedAtMonotonicMs });
856
+ }
857
+ this.applied = { ...current, pauseCapture: this.lastCounters.pauseCapture };
858
+ return;
859
+ }
860
+ this.captureEpoch = 0;
861
+ const reserved = this.reservedRate ??= this.life.reserve();
862
+ // Pause until the new CONFIG is committed by viewer_ready + Server START.
863
+ const result = await this.atBoundary('set_rate', { ...current, pauseCapture: true, epoch: reserved.epoch }, reserved);
864
+ const applied = z.object({ targetBps: z.number().int().min(500000).max(20000000), fpsCeiling: z.union([z.literal(15), z.literal(30), z.literal(60)]) }).parse(result.accepted);
865
+ const accepted = this.accepted(result.accepted, applied.targetBps, applied.fpsCeiling);
866
+ mediaRequire(Number.isSafeInteger(result.appliedAtMonotonicMs));
867
+ this.applied = { ...current, targetBps: accepted.targetBps, fpsCeiling: accepted.fpsCeiling, pauseCapture: true };
868
+ this.codecFpsCeiling = accepted.fpsCeiling;
869
+ this.life.appliedTargetBps = accepted.targetBps;
870
+ this.life.appliedFps = accepted.fpsCeiling;
871
+ this.reserved = reserved;
872
+ this.reservedRate = undefined;
873
+ this.configuration = undefined;
874
+ this.firstFrame = 0;
875
+ this.presented = false;
876
+ this.events.push({ type: 'rate_applied', decision: current, epoch: this.epoch, actualTargetBps: this.applied.targetBps,
877
+ actualFpsCeiling: this.applied.fpsCeiling, appliedAtMonotonicMs: result.appliedAtMonotonicMs });
878
+ this.publishConfigured();
879
+ });
880
+ }
881
+ async refresh() {
882
+ return this.serialized(async () => {
883
+ this.captureEpoch = 0;
884
+ const reserved = this.life.reserve();
885
+ await this.pauseAndDrain();
886
+ await this.atBoundary('refresh', { epoch: reserved.epoch }, reserved);
887
+ this.reserved = reserved;
888
+ this.configuration = undefined;
889
+ this.firstFrame = 0;
890
+ this.presented = false;
891
+ this.publishConfigured();
892
+ });
893
+ }
894
+ async setCursorVisible(visible) {
895
+ return this.serialized(async () => {
896
+ mediaRequire(!this.stopped && this.life.session === this);
897
+ const reply = await this.helper.request('cursor_visibility', this.identity, { visible });
898
+ return z.object({ mediaEpoch: z.number().int().min(1), afterFrameId: z.number().int().min(0) }).parse(reply.result);
899
+ });
900
+ }
901
+ async setAppearance(colorScheme) {
902
+ mediaRequire(colorScheme === 'light' || colorScheme === 'dark');
903
+ return this.serialized(async () => {
904
+ mediaRequire(!this.stopped && this.life.session === this && (await this.provider.probe())?.displays[0].appearanceSync === true);
905
+ const { stdout } = await execute(this.provider.displayControl, ['--appearance', colorScheme], { env: this.provider.environment, timeout: 4500, maxBuffer: 4096 });
906
+ z.object({ colorScheme: z.literal(colorScheme), outcome: z.literal('applied') }).parse(JSON.parse(stdout));
907
+ });
908
+ }
909
+ async resize(mode) {
910
+ return this.serialized(() => this.provider.geometryTransaction(async (revision) => {
911
+ this.captureEpoch = 0;
912
+ const reserved = this.life.reserve();
913
+ await this.pauseAndDrain();
914
+ const effectiveRegion = mode.captureRegion ?? (this.effectiveRegion ? {
915
+ x: 0, y: 0, width: this.effectiveRegion.sourceWidth, height: this.effectiveRegion.sourceHeight,
916
+ sourceWidth: this.effectiveRegion.sourceWidth, sourceHeight: this.effectiveRegion.sourceHeight,
917
+ } : undefined);
918
+ const result = await this.atBoundary('resize', { ...mode, ...(effectiveRegion ? { captureRegion: effectiveRegion } : {}),
919
+ ...reserved, expectedDisplayRevision: this.displayRevision, displayRevision: revision }, reserved);
920
+ const axis = z.number().int().min(2).max(4096).refine(n => n % 2 === 0);
921
+ const actual = z.object({ outcome: z.enum(['applied', 'rolled_back']), width: axis, height: axis, displayRevision: z.literal(revision),
922
+ dpi: z.number().int().min(64).max(384).optional(), captureRegion: captureRegion.optional() }).parse(result);
923
+ mediaRequire(canonicalDesktopJson(actual.captureRegion ?? null) === canonicalDesktopJson(effectiveRegion ?? null));
924
+ this.effectiveRegion = effectiveRegion;
925
+ // A helper-only region must not change the identity acknowledged to the server.
926
+ actual.captureRegion = mode.captureRegion;
927
+ if (mode.dpi !== undefined)
928
+ mediaRequire(actual.dpi !== undefined && (actual.outcome !== 'applied' || actual.dpi === mode.dpi));
929
+ else if (this.selection.dpi === undefined)
930
+ delete actual.dpi;
931
+ this.identity.displayRevision = actual.displayRevision;
932
+ this.reserved = reserved;
933
+ this.focusRevision = 1;
934
+ this.selection = { ...this.selection, captureRegion: actual.captureRegion, width: actual.width, height: actual.height, ...(actual.dpi === undefined ? {} : { dpi: actual.dpi }) };
935
+ this.configuration = undefined;
936
+ this.firstFrame = 0;
937
+ this.presented = false;
938
+ this.publishConfigured();
939
+ return { ...actual, commandId: this.selection.commandId };
940
+ }, mode.captureRegion !== undefined || this.effectiveRegion !== undefined));
941
+ }
942
+ /** Keeps server command identities without extending the public provider interface. */
943
+ configurationCommand(commandId) { this.selection = { ...this.selection, commandId }; }
944
+ get codec() { return this.configuration; }
945
+ get packets() {
946
+ const self = this;
947
+ return { async *[Symbol.asyncIterator]() {
948
+ mediaRequire(!self.packetConsumer);
949
+ self.packetConsumer = true;
950
+ let skipCursor = false;
951
+ for await (const bytes of self.helper.packets) {
952
+ if (self.stopped || self.life.session !== self)
953
+ return;
954
+ const reserved = self.reserved;
955
+ const packet = decodeMedia(bytes), retired = self.retiringMedia;
956
+ const old = retired && packet.epoch === retired.epoch && packet.displayRevision === retired.displayRevision;
957
+ mediaRequire((packet.epoch === self.epoch && packet.displayRevision === self.displayRevision) || (old && (packet.type === 3 || packet.type === 4)));
958
+ self.life.lastAppliedInputSequence = Math.max(self.life.lastAppliedInputSequence, packet.lastInputSequence);
959
+ if (self.readPayloadEpoch !== packet.epoch) {
960
+ self.readPayloadEpoch = packet.epoch;
961
+ self.readPayloadBytes = 0;
962
+ }
963
+ self.readPayloadBytes += packet.payload.length;
964
+ mediaRequire(Number.isSafeInteger(self.readPayloadBytes));
965
+ const unit = self.stream.accept(bytes);
966
+ if (packet.fragmentIndex === 0)
967
+ skipCursor = !!old && (packet.type === 3 || packet.type === 4) && !self.forwardingCursor;
968
+ if (skipCursor)
969
+ continue;
970
+ if (packet.type === 1) {
971
+ self.retiringMedia = undefined;
972
+ // Native can verify SPS only after the authorized first encode. Reuse the
973
+ // existing non-reopening response, retaining the current admission state.
974
+ const result = await self.command('set_rate', { ...self.applied, epoch: self.epoch, reopenEpoch: false });
975
+ if (self.stopped || self.life.session !== self || self.reserved !== reserved)
976
+ continue;
977
+ self.accepted(result.accepted, self.applied.targetBps, self.applied.fpsCeiling, true);
978
+ self.configuration = self.stream.configuration;
979
+ }
980
+ if (unit && !self.firstFrame)
981
+ self.firstFrame = unit.frameId;
982
+ const key = self.packetKey(packet);
983
+ mediaRequire(!self.pendingPackets.has(key) && self.pendingPackets.size < 128 && self.pendingPacketBytes + packet.payload.length <= 524288);
984
+ self.pendingPackets.set(key, packet);
985
+ self.pendingPacketBytes += packet.payload.length;
986
+ if (packet.type === 2 && packet.fragmentCount > 1)
987
+ self.forwardingUnit = true;
988
+ if (packet.type === 3 && packet.fragmentCount > 1)
989
+ self.forwardingCursor = true;
990
+ self.drainWake?.();
991
+ yield bytes;
992
+ }
993
+ } };
994
+ }
995
+ stop() {
996
+ if (this.stopPromise)
997
+ return this.stopPromise;
998
+ this.stopped = true;
999
+ this.drainWake?.();
1000
+ return this.stopPromise = (async () => {
1001
+ if (this.life.pendingText)
1002
+ this.life.pendingText = undefined;
1003
+ await this.helper.stop(this.identity);
1004
+ const last = this.helper.counters ?? this.lastCounters;
1005
+ this.life.transportDebtBits = last?.rateBudget?.debtBits ?? this.life.transportDebtBits;
1006
+ for (const k of totals) {
1007
+ const next = this.life.offsets[k] + (last?.[k] ?? 0);
1008
+ mediaRequire(Number.isSafeInteger(next));
1009
+ this.life.offsets[k] = next;
1010
+ }
1011
+ this.lastCounters = undefined;
1012
+ this.life.touched = performance.now();
1013
+ if (this.life.session === this)
1014
+ this.life.session = undefined;
1015
+ this.events.close();
1016
+ })();
1017
+ }
1018
+ }