@platless/axom-cli 0.5.9 → 0.6.52

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/README.md +25 -6
  2. package/dist/axom-patch/cli/README.md +129 -12
  3. package/dist/axom-patch/cli/account-limits/generate.py +15 -12
  4. package/dist/axom-patch/cli/account-limits/kimi-proxy.mjs +20 -3
  5. package/dist/axom-patch/cli/claude-account-limits.json +9 -9
  6. package/dist/axom-patch/cli/claude-replay-lifecycle.json +16 -0
  7. package/dist/axom-patch/cli/codex-account-limits.json +11 -11
  8. package/dist/axom-patch/cli/codex-terminal-output.json +16 -0
  9. package/dist/axom-patch/cli/commandcode-account-limits.json +9 -9
  10. package/dist/axom-patch/cli/kimi-quota.mjs +12 -0
  11. package/dist/axom-patch/cli/kimi-subagents.mjs +173 -0
  12. package/dist/axom-patch/cli/nightly-task.md +55 -13
  13. package/dist/axom-patch/cli/probes/claude-replay-lifecycle.mjs +31 -0
  14. package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +33 -0
  15. package/dist/axom-patch/cli/probes/kimi-subagent-subwork.mjs +82 -0
  16. package/dist/axom-patch/cli/probes/zcode-env-account.mjs +81 -0
  17. package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +7 -0
  18. package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1687 -0
  19. package/dist/axom-patch/cli/qoder/releases/1.1.75.json +277 -0
  20. package/dist/axom-patch/cli/qoder-background-terminal.md +13 -6
  21. package/dist/axom-patch/cli/qoder-foreground-output.md +14 -8
  22. package/dist/axom-patch/cli/qoder-native-acp.json +227 -0
  23. package/dist/axom-patch/cli/qoder-steering.md +20 -25
  24. package/dist/axom-patch/cli/qoder-subagent-subwork.md +17 -68
  25. package/dist/axom-patch/cli/runtime.json +19 -19
  26. package/dist/axom-patch/cli/zcode/env-account.mjs +13 -0
  27. package/dist/axom-patch/cli/zcode/generate-env-account.py +52 -0
  28. package/dist/axom-patch/cli/zcode-account-limits.json +10 -11
  29. package/dist/axom-patch/cli/zcode-env-account.json +58 -0
  30. package/dist/axom-patch/cli/zcode-models.json +9 -10
  31. package/dist/axom-patch/cli/zcode-subagent-subwork.json +10 -11
  32. package/dist/cli.js +11 -45
  33. package/dist/commands/auth.js +4 -1
  34. package/dist/commands/knowledge.js +47 -0
  35. package/dist/commands/update.js +126 -3
  36. package/dist/commands/workstation.js +101 -164
  37. package/dist/commands/workstationRegistrations.js +116 -0
  38. package/dist/credentialBroker.js +13 -2
  39. package/dist/credentials.js +16 -0
  40. package/dist/install/claude/commands.js +15 -15
  41. package/dist/keepers/terminal/keeper.mjs +1089 -673
  42. package/dist/keepers/terminal/manifest.json +4 -4
  43. package/dist/mcp.js +1 -1
  44. package/dist/native/darwin-x64/axom-quic +0 -0
  45. package/dist/workstation/acp/acpAgents.js +113 -102
  46. package/dist/workstation/acp/acpClient.js +26 -0
  47. package/dist/workstation/acp/builds/install/npm.js +99 -10
  48. package/dist/workstation/acp/initializeResponse.js +6 -1
  49. package/dist/workstation/acp/managedPatches.js +189 -25
  50. package/dist/workstation/acp/managedRuntime.js +314 -0
  51. package/dist/workstation/acp/outbox.js +12 -5
  52. package/dist/workstation/acp/patchAssets.js +40 -0
  53. package/dist/workstation/acp/permission.js +2 -2
  54. package/dist/workstation/acp/processor/checkpoint.js +26 -34
  55. package/dist/workstation/acp/processor/control.js +7 -80
  56. package/dist/workstation/acp/processor/jsonrpc.js +8 -0
  57. package/dist/workstation/acp/processor/runtime.js +5 -81
  58. package/dist/workstation/acp/processor/session.js +54 -82
  59. package/dist/workstation/acp/processor/state.js +7 -13
  60. package/dist/workstation/acp/processor/step.js +3 -20
  61. package/dist/workstation/acp/processor/test/fixtureLog.js +9 -16
  62. package/dist/workstation/acp/processor/transport/local.js +19 -3
  63. package/dist/workstation/acp/processor/updates.js +156 -105
  64. package/dist/workstation/acp/processorSession.js +20 -173
  65. package/dist/workstation/acp/registry/catalog.js +65 -39
  66. package/dist/workstation/acp/registry/launch.js +16 -1
  67. package/dist/workstation/acp/switching.js +0 -3
  68. package/dist/workstation/acp/test/fakeAgent.js +13 -0
  69. package/dist/workstation/acp/test/keeperWorld.js +32 -3
  70. package/dist/workstation/acp/test/raw/sources.js +16 -130
  71. package/dist/workstation/acp/test/realAdapter.js +1 -47
  72. package/dist/workstation/acp/test/scriptAgent.js +11 -3
  73. package/dist/workstation/agentFlow.js +1 -1
  74. package/dist/workstation/agentSessions.js +114 -166
  75. package/dist/workstation/channels/agent.js +9 -1
  76. package/dist/workstation/channels/computer.js +27 -4
  77. package/dist/workstation/channels/management.js +130 -15
  78. package/dist/workstation/channels/url.js +1 -1
  79. package/dist/workstation/config.js +33 -9
  80. package/dist/workstation/connection/encryptedChannel.js +4 -1
  81. package/dist/workstation/control/bindingStore.js +204 -0
  82. package/dist/workstation/control/commands.js +125 -0
  83. package/dist/workstation/control/daemonIdentity.js +143 -0
  84. package/dist/workstation/control/deviceCommands.js +191 -0
  85. package/dist/workstation/control/deviceProtocol.js +68 -0
  86. package/dist/workstation/control/enrollment.js +106 -0
  87. package/dist/workstation/control/inspect.js +71 -0
  88. package/dist/workstation/control/locks.js +110 -0
  89. package/dist/workstation/control/protocol.js +40 -0
  90. package/dist/workstation/control/restartGuard.js +46 -0
  91. package/dist/workstation/control/rootFormat.js +16 -0
  92. package/dist/workstation/control/runtimeClient.js +152 -0
  93. package/dist/workstation/control/runtimeHost.js +235 -0
  94. package/dist/workstation/control/serverIdentity.js +35 -0
  95. package/dist/workstation/control/updateTransition.js +91 -0
  96. package/dist/workstation/daemon.js +266 -264
  97. package/dist/workstation/daemonctl.js +226 -25
  98. package/dist/workstation/desktopControl/audio.js +99 -0
  99. package/dist/workstation/desktopControl/automationDevice.js +219 -0
  100. package/dist/workstation/desktopControl/automationJson.js +74 -0
  101. package/dist/workstation/desktopControl/automationLocalState.js +23 -0
  102. package/dist/workstation/desktopControl/automationProtocol.js +253 -0
  103. package/dist/workstation/desktopControl/automationSchema.generated.js +1 -0
  104. package/dist/workstation/desktopControl/automationSession.js +81 -0
  105. package/dist/workstation/desktopControl/automationTransport.js +138 -0
  106. package/dist/workstation/desktopControl/capabilities.js +93 -0
  107. package/dist/workstation/desktopControl/captureModes.js +9 -0
  108. package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
  109. package/dist/workstation/desktopControl/deviceAdmission.js +229 -0
  110. package/dist/workstation/desktopControl/deviceOwnership.js +130 -0
  111. package/dist/workstation/desktopControl/deviceSockets.js +246 -0
  112. package/dist/workstation/desktopControl/helperProcess.js +602 -0
  113. package/dist/workstation/desktopControl/hostGrant.js +65 -0
  114. package/dist/workstation/desktopControl/lobby.js +131 -0
  115. package/dist/workstation/desktopControl/macProvider.js +156 -0
  116. package/dist/workstation/desktopControl/media.js +331 -0
  117. package/dist/workstation/desktopControl/nativeDeviceAdmission.js +207 -0
  118. package/dist/workstation/desktopControl/nativeProvider.js +1044 -0
  119. package/dist/workstation/desktopControl/networkEvidence.js +43 -0
  120. package/dist/workstation/desktopControl/overview.js +58 -0
  121. package/dist/workstation/desktopControl/ownershipProtocol.js +31 -0
  122. package/dist/workstation/desktopControl/protocol.js +311 -0
  123. package/dist/workstation/desktopControl/provider.js +1 -0
  124. package/dist/workstation/desktopControl/quicMedia.js +240 -0
  125. package/dist/workstation/desktopControl/rateController.js +226 -0
  126. package/dist/workstation/desktopControl/supervisor.js +1774 -0
  127. package/dist/workstation/desktopControl/transport.js +27 -0
  128. package/dist/workstation/desktopControl/wallpaper.js +98 -0
  129. package/dist/workstation/exec/policy.js +5 -0
  130. package/dist/workstation/files/client.js +10 -1
  131. package/dist/workstation/files/credentials.js +142 -0
  132. package/dist/workstation/files/host.js +9 -2
  133. package/dist/workstation/files/text.js +42 -8
  134. package/dist/workstation/journal.js +5 -2
  135. package/dist/workstation/keepers/acp/client.js +38 -2
  136. package/dist/workstation/keepers/kit/buildVersion.js +35 -11
  137. package/dist/workstation/keepers/kit/bundle.js +21 -17
  138. package/dist/workstation/keepers/kit/client.js +2 -2
  139. package/dist/workstation/keepers/kit/launcher.js +123 -7
  140. package/dist/workstation/keepers/kit/paths.js +2 -1
  141. package/dist/workstation/keepers/legacy.js +16 -2
  142. package/dist/workstation/keepers/policy.js +5 -34
  143. package/dist/workstation/keepers/terminal/client.js +115 -23
  144. package/dist/workstation/keepers/terminal/exec.js +6 -3
  145. package/dist/workstation/keepers/terminal/frames.js +73 -0
  146. package/dist/workstation/keepers/terminal/generations.js +5 -8
  147. package/dist/workstation/keepers/terminal/gridProtocol.js +16 -0
  148. package/dist/workstation/keepers/terminal/input.js +146 -0
  149. package/dist/workstation/keepers/terminal/keeper.js +22 -8
  150. package/dist/workstation/keepers/terminal/main.js +1 -1
  151. package/dist/workstation/keepers/terminal/metadata.js +138 -0
  152. package/dist/workstation/keepers/terminal/protocol.js +4 -28
  153. package/dist/workstation/keepers/terminal/pty.js +1 -330
  154. package/dist/workstation/keepers/terminal/screen.js +246 -0
  155. package/dist/workstation/keepers/terminal/sessions.js +526 -0
  156. package/dist/workstation/managedInstall.js +57 -0
  157. package/dist/workstation/oauth/deviceAuthorization.js +31 -17
  158. package/dist/workstation/protocol/agent.js +2 -15
  159. package/dist/workstation/protocol/management.js +113 -5
  160. package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
  161. package/dist/workstation/rd/artifact.js +120 -0
  162. package/dist/workstation/rd/control.js +117 -0
  163. package/dist/workstation/rd/display.js +29 -0
  164. package/dist/workstation/rd/host.js +213 -0
  165. package/dist/workstation/rd/migrationReadiness.js +46 -0
  166. package/dist/workstation/rd/provider.js +256 -0
  167. package/dist/workstation/rd/sessions.js +391 -0
  168. package/dist/workstation/rd/state.js +57 -0
  169. package/dist/workstation/rd/update.js +168 -0
  170. package/dist/workstation/rd/upkeep.js +80 -0
  171. package/dist/workstation/register.js +27 -37
  172. package/dist/workstation/registrations/enrollment.js +250 -0
  173. package/dist/workstation/registrations/enrollmentPending.js +361 -0
  174. package/dist/workstation/registrations/migration.js +356 -0
  175. package/dist/workstation/registrations/model.js +34 -0
  176. package/dist/workstation/registrations/removal.js +222 -0
  177. package/dist/workstation/registrations/store.js +737 -0
  178. package/dist/workstation/runtime/desktopBinding.js +20 -0
  179. package/dist/workstation/runtime/deviceDesktop.js +193 -0
  180. package/dist/workstation/runtime/deviceRuntime.js +568 -0
  181. package/dist/workstation/runtime/deviceServices.js +117 -0
  182. package/dist/workstation/runtime/events.js +20 -0
  183. package/dist/workstation/runtime/ownership.js +100 -0
  184. package/dist/workstation/runtime/registrationRuntime.js +575 -0
  185. package/dist/workstation/runtime/remoteDesktopAdapter.js +186 -0
  186. package/dist/workstation/runtime/removalActivity.js +184 -0
  187. package/dist/workstation/service/installer.js +197 -14
  188. package/dist/workstation/service/launchd.js +2 -0
  189. package/dist/workstation/service/systemd.js +1 -0
  190. package/dist/workstation/storageMetrics.js +118 -0
  191. package/dist/workstation/systemMetrics.js +47 -92
  192. package/dist/workstation/toolchain.js +94 -0
  193. package/dist/workstation/tools/browser.js +28 -4
  194. package/dist/workstation/tools/client.js +8 -27
  195. package/dist/workstation/tools/info.js +6 -13
  196. package/dist/workstation/tools/terminalRouter.js +17 -172
  197. package/dist/workstation/tools/validation.js +55 -1
  198. package/dist/workstation/update/archiveEvidence.js +206 -0
  199. package/dist/workstation/update/artifact.js +2 -1
  200. package/dist/workstation/update/coordinator.js +34 -13
  201. package/dist/workstation/update/deviceCompatibility.js +119 -0
  202. package/dist/workstation/update/deviceCoordinator.js +369 -0
  203. package/dist/workstation/update/deviceHealth.js +104 -0
  204. package/dist/workstation/update/deviceHealthProtocol.js +26 -0
  205. package/dist/workstation/update/deviceRecoveryLifecycle.js +46 -0
  206. package/dist/workstation/update/deviceRollback.js +78 -0
  207. package/dist/workstation/update/globalInstall.js +186 -0
  208. package/dist/workstation/update/legacyBridge.js +414 -0
  209. package/dist/workstation/update/legacyOwner.js +39 -0
  210. package/dist/workstation/update/npmInstaller.js +51 -25
  211. package/dist/workstation/update/privateDurability.js +61 -0
  212. package/dist/workstation/update/rdRollback.js +145 -0
  213. package/dist/workstation/update/recoveryPlan.js +124 -0
  214. package/dist/workstation/update/runUpdate.js +10 -3
  215. package/dist/workstation/update/taskRegistry.js +161 -3
  216. package/dist/workstation/update/watchdog.js +29806 -333
  217. package/dist/workstation/update/watchdogProcess.js +72 -0
  218. package/package.json +11 -7
  219. package/skills/axom/SKILL.md +11 -11
  220. package/skills/axom-definition/SKILL.md +3 -3
  221. package/skills/axom-platform/SKILL.md +3 -3
  222. package/dist/axom-patch/cli/probes/qoder-steering.mjs +0 -89
  223. package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +0 -61
  224. package/dist/axom-patch/cli/qoder-steering.json +0 -52
  225. package/dist/axom-patch/cli/qoder-subagent-origin.json +0 -73
  226. package/dist/commands/guide.js +0 -38
  227. package/dist/runtime-deps/qoder-sdk/package-lock.json +0 -1189
  228. package/dist/runtime-deps/qoder-sdk/package.json +0 -8
  229. package/dist/workstation/acp/delegationMcp.js +0 -24
  230. package/dist/workstation/acp/processor/sideChannel.js +0 -80
  231. package/dist/workstation/acp/qoderStartup.js +0 -45
  232. package/dist/workstation/acp/test/raw/qoderCaptures.js +0 -125
  233. package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -118
  234. package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -193
  235. package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +0 -372
  236. package/dist/workstation/qoderAdapter/callbackContext.js +0 -76
  237. package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
  238. package/dist/workstation/qoderAdapter/contract.js +0 -78
  239. package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -36
  240. package/dist/workstation/qoderAdapter/history.js +0 -85
  241. package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
  242. package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
  243. package/dist/workstation/qoderAdapter/main.js +0 -95
  244. package/dist/workstation/qoderAdapter/mapper.js +0 -117
  245. package/dist/workstation/qoderAdapter/observerPlugin.js +0 -245
  246. package/dist/workstation/qoderAdapter/observerReceiver.js +0 -187
  247. package/dist/workstation/qoderAdapter/permissions.js +0 -61
  248. package/dist/workstation/qoderAdapter/profile.js +0 -93
  249. package/dist/workstation/qoderAdapter/sdkDriver.js +0 -48
  250. package/dist/workstation/qoderAdapter/sdkRuntime.js +0 -93
  251. package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +0 -145
  252. package/dist/workstation/qoderAdapter/session.js +0 -319
  253. package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
  254. package/dist/workstation/qoderAdapter/subwork.js +0 -85
  255. package/dist/workstation/qoderAdapter/taskControl.js +0 -38
  256. package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
  257. package/dist/workstation/qoderAdapter/usage.js +0 -37
  258. /package/dist/workstation/{qoderAdapter/types.js → desktopControl/automationPreparation.js} +0 -0
@@ -0,0 +1,602 @@
1
+ import { spawn, execFile } from 'node:child_process';
2
+ import { promisify } from 'node:util';
3
+ import { setTimeout as delay } from 'node:timers/promises';
4
+ import { z } from 'zod';
5
+ import { DesktopProtocolError, parseDesktopJson } from './protocol.js';
6
+ import { decodeMedia, mediaRequire } from './media.js';
7
+ import { decodeAudio } from './audio.js';
8
+ import { randomBytes as automationRandomBytes } from 'node:crypto';
9
+ import { parseAutomationJson } from './automationJson.js';
10
+ import { AutomationTransport } from './automationTransport.js';
11
+ import { AutomationSession } from './automationSession.js';
12
+ import { decodeAutomationFailureDetail } from './automationProtocol.js';
13
+ const execute = promisify(execFile);
14
+ const commands = ['focus', 'cache_dependency', 'transport_cost', 'prepare_motion_cut', 'commit_motion_cut', 'prepare', 'start', 'decoder_ready', 'first_presented', 'acknowledge', 'credit', 'renew', 'input', 'clipboard', 'overview', 'reserve_overview', 'resize',
15
+ 'held_state', 'text_result', 'recover_display', 'set_rate', 'refresh', 'stop', 'verify_cleanup', 'probe', 'status'];
16
+ const safe = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
17
+ const handshakeSchema = z.strictObject({ ipc: z.literal(1), providerVersion: z.literal('axom-linux-1'),
18
+ representations: z.tuple([z.literal(1)]),
19
+ inputAbi: z.literal(1), rateAbi: z.literal(1), continuationAbi: z.literal(1), motionRecoveryAbi: z.literal(1), runtimeRateReconfigure: z.literal(false),
20
+ rateLimits: z.strictObject({ minBps: z.literal(500000), maxBps: z.literal(20000000), stepBps: z.literal(4000), maxFps: z.literal(60) }),
21
+ supportedCommands: z.array(z.string()).refine(a => commands.every(c => a.includes(c))) });
22
+ export function validateNativeHandshake(value, platform = 'linux') {
23
+ (platform === 'linux' ? handshakeSchema : handshakeSchema.extend({ providerVersion: z.literal('axom-macos-1'),
24
+ rateLimits: handshakeSchema.shape.rateLimits.extend({ maxFps: z.literal(30) }) })).parse(value);
25
+ }
26
+ /** One waiter, pulled reads: a blocked media consumer never blocks the control reader. */
27
+ export class NativeByteReader {
28
+ max;
29
+ iterator;
30
+ bytes = Buffer.alloc(0);
31
+ constructor(stream, max = 65600) {
32
+ this.max = max;
33
+ this.iterator = stream[Symbol.asyncIterator]();
34
+ }
35
+ async exact(size) {
36
+ mediaRequire(Number.isInteger(size) && size > 0 && size <= this.max);
37
+ while (this.bytes.length < size) {
38
+ const next = await this.iterator.next();
39
+ if (next.done)
40
+ throw new DesktopProtocolError('Native stream ended');
41
+ mediaRequire(Buffer.isBuffer(next.value) && this.bytes.length + next.value.length <= this.max + 65536);
42
+ this.bytes = Buffer.concat([this.bytes, next.value]);
43
+ }
44
+ const value = this.bytes.subarray(0, size);
45
+ this.bytes = this.bytes.subarray(size);
46
+ return value;
47
+ }
48
+ }
49
+ export class NativeControlReader {
50
+ reader;
51
+ constructor(stream) { this.reader = new NativeByteReader(stream, 49152); }
52
+ async read() {
53
+ const size = (await this.reader.exact(4)).readUInt32BE();
54
+ mediaRequire(size > 0 && size <= 49152);
55
+ // lastEncodeMs is the sole fractional native counter.
56
+ const bytes = await this.reader.exact(size);
57
+ return parseDesktopJson(bytes, 49152, nativeIntegerFields);
58
+ }
59
+ }
60
+ const nativeIntegerFields = new Set(['ipc', 'id', 'epoch', 'width', 'height', 'dpi', 'fpsCeiling', 'targetBps', 'rateTargetBps',
61
+ 'motionRecoveryAbi', 'cutId', 'pictureThrough', 'frameThrough', 'continuationAbi', 'inputAbi', 'rateAbi', 'representations', 'representation', 'minBps', 'maxBps', 'stepBps', 'maxFps',
62
+ 'maxRateBps', 'bufferBits', 'maxBFrames', 'nextFrameId', 'frameId', 'leaseMs', 'motionConsumedBytes', 'cursorConsumedBytes', 'exactConsumedBytes', 'windowBytes',
63
+ 'hrdBitRate', 'hrdCpbSize', 'spsWidth', 'spsHeight', 'spsMaxReorderFrames',
64
+ 'rcModeValue', 'blockLevelRateControl', 'spsMaxRateBps', 'spsBufferBits',
65
+ 'controlGeneration', 'displayRevision', 'sequence', 'lastAllocatedFrameId', 'frameIdLimit', 'captured', 'encoded', 'sentPayloadBytes',
66
+ 'videoPayloadBytes', 'headerBytes', 'rateReopens', 'refinementFrames', 'motionContinuations', 'pendingPayloadBytes', 'rateCooldownUntilMs', 'nowMs', 'initialAtMs',
67
+ 'cacheId', 'exactSequence', 'initialReservoirBits', 'capacityBits', 'availableBits', 'integratedTargetBits', 'transportBytes', 'debtBits', 'appliedAtMonotonicMs', 'lastInputSequence', 'lastAppliedInputSequence', 'wireBytes',
68
+ 'sourceCaptureUs', 'captureBeginUs', 'captureEndUs', 'captureCompareBeginUs', 'captureCompareEndUs', 'captureCopyBeginUs', 'captureCopyEndUs', 'encodeBeginUs', 'encodeEndUs', 'committedUs']);
69
+ const budgetSchema = z.strictObject({ nowMs: safe, initialAtMs: safe, initialReservoirBits: safe, capacityBits: safe,
70
+ availableBits: safe, integratedTargetBits: safe, transportBytes: safe, debtBits: safe });
71
+ const frameTimingSchema = z.strictObject({ clock: z.literal('steady_clock'), epoch: safe.min(1), frameId: safe.min(1), repair: z.boolean(),
72
+ sourceCaptureUs: safe, captureBeginUs: safe, captureEndUs: safe, captureCompareBeginUs: safe.default(0), captureCompareEndUs: safe.default(0),
73
+ captureCopyBeginUs: safe.default(0), captureCopyEndUs: safe.default(0), encodeBeginUs: safe, encodeEndUs: safe, committedUs: safe,
74
+ }).refine(timing => timing.encodeBeginUs <= timing.encodeEndUs && timing.encodeEndUs <= timing.committedUs &&
75
+ (timing.repair ? timing.sourceCaptureUs === 0 && timing.captureBeginUs === 0 && timing.captureEndUs === 0 :
76
+ timing.captureBeginUs <= timing.captureEndUs && timing.captureEndUs <= timing.encodeBeginUs &&
77
+ (timing.captureCompareBeginUs === 0 && timing.captureCompareEndUs === 0 && timing.captureCopyBeginUs === 0 && timing.captureCopyEndUs === 0 ||
78
+ timing.captureCompareBeginUs <= timing.captureCompareEndUs &&
79
+ timing.captureCompareEndUs <= timing.captureCopyBeginUs && timing.captureCopyBeginUs <= timing.captureCopyEndUs &&
80
+ timing.captureCopyEndUs <= timing.captureEndUs)), 'Invalid native frame timing');
81
+ const countersSchema = z.strictObject({ epoch: safe, lastAllocatedFrameId: safe, frameIdLimit: safe, captured: safe, encoded: safe,
82
+ sentPayloadBytes: safe, videoPayloadBytes: safe, headerBytes: safe, rateReopens: safe, targetBps: safe, fpsCeiling: safe,
83
+ pauseCapture: z.boolean(), lastEncodeMs: z.number().finite().min(0), lastFrameTiming: frameTimingSchema.nullable(), refinementFrames: safe.optional(), motionContinuations: safe.optional(), pendingPayloadBytes: safe, rateCooldownUntilMs: safe,
84
+ rateBudget: budgetSchema.optional(), inputAvailable: z.boolean(), lastInputSequence: safe });
85
+ const replySchema = z.union([
86
+ z.strictObject({ ipc: z.literal(1), id: safe, ok: z.literal(true), result: z.record(z.string(), z.unknown()), counters: countersSchema }),
87
+ z.strictObject({ ipc: z.literal(1), id: safe, ok: z.literal(false), code: z.enum(['invalid_request', 'unavailable', 'conflict', 'busy',
88
+ 'rate_limited', 'authorization_lost', 'encoder_failed']), counters: countersSchema }),
89
+ ]);
90
+ export class NativeCommandError extends Error {
91
+ code;
92
+ counters;
93
+ detail;
94
+ constructor(code, counters, detail = null) {
95
+ super(code);
96
+ this.code = code;
97
+ this.counters = counters;
98
+ this.detail = detail;
99
+ }
100
+ }
101
+ /** Local queue retirement, created only before allocating an ID or writing bytes. */
102
+ class NativeCommandCancelled extends NativeCommandError {
103
+ constructor() { super('cancelled'); }
104
+ }
105
+ export async function bounded(promise, ms, code = 'transport_failed') {
106
+ let timer;
107
+ try {
108
+ return await Promise.race([promise, new Promise((_, reject) => { timer = setTimeout(() => reject(new NativeCommandError(code)), ms); })]);
109
+ }
110
+ finally {
111
+ clearTimeout(timer);
112
+ }
113
+ }
114
+ export class HelperProcess {
115
+ executable;
116
+ env;
117
+ child;
118
+ reader;
119
+ media;
120
+ audio;
121
+ audioSupported = false;
122
+ id = 0;
123
+ queued = 0;
124
+ tail = Promise.resolve();
125
+ stopped = false;
126
+ termination;
127
+ pending = new Map();
128
+ active = new Set();
129
+ stopPromise;
130
+ exited = false;
131
+ closed;
132
+ counters;
133
+ constructor(executable, env) {
134
+ this.executable = executable;
135
+ this.env = env;
136
+ this.child = spawn(executable, ['--media-fd', '3'], { stdio: ['pipe', 'pipe', 'ignore', 'pipe', 'pipe'], env: { ...env, AXOM_KEEPER_AUDIO_FD: '4' }, detached: false });
137
+ this.child.stdin.on('error', () => { });
138
+ this.reader = new NativeControlReader(this.child.stdout);
139
+ this.media = new NativeByteReader(this.child.stdio[3]);
140
+ this.audio = new NativeByteReader(this.child.stdio[4]);
141
+ this.closed = new Promise(resolve => {
142
+ this.child.once('exit', () => { this.exited = true; this.rejectPending(new NativeCommandError('transport_failed')); resolve(); });
143
+ this.child.once('error', () => { this.exited = true; this.rejectPending(new NativeCommandError('transport_failed')); resolve(); });
144
+ });
145
+ }
146
+ static async launch(executable, env = process.env, platform = 'linux') {
147
+ const h = new HelperProcess(executable, env);
148
+ try {
149
+ const handshake = await bounded(h.reader.read(), 3000, 'unavailable');
150
+ validateNativeHandshake(handshake, platform);
151
+ h.audioSupported = handshake.supportedCommands.includes('audio_start');
152
+ void h.readReplies();
153
+ return h;
154
+ }
155
+ catch {
156
+ await h.terminate();
157
+ throw new NativeCommandError('unavailable');
158
+ }
159
+ }
160
+ get pid() { return this.child.pid; }
161
+ async readReplies() {
162
+ try {
163
+ while (!this.stopped && !this.exited) {
164
+ const reply = replySchema.parse(await this.reader.read());
165
+ const first = this.pending.entries().next();
166
+ mediaRequire(!first.done && reply.id === first.value[0]);
167
+ const [id, pending] = first.value;
168
+ this.pending.delete(id);
169
+ clearTimeout(pending.timer);
170
+ this.counters = reply.counters;
171
+ if (reply.ok)
172
+ pending.resolve({ result: reply.result, counters: reply.counters });
173
+ else
174
+ pending.reject(new NativeCommandError(reply.code, reply.counters));
175
+ }
176
+ }
177
+ catch (error) {
178
+ if (!this.stopped)
179
+ this.fail(error);
180
+ }
181
+ }
182
+ rejectPending(error) {
183
+ for (const pending of this.pending.values()) {
184
+ clearTimeout(pending.timer);
185
+ pending.reject(error);
186
+ }
187
+ this.pending.clear();
188
+ }
189
+ fail(error) { this.rejectPending(error); void this.terminate(); }
190
+ request(command, binding, payload = {}, current = () => true) {
191
+ if (this.stopped || this.exited || ![...commands, 'cursor_visibility', 'audio_start', 'audio_rate', 'audio_stop', 'audio_status'].includes(command) || this.queued >= 32)
192
+ return Promise.reject(new NativeCommandError('transport_failed'));
193
+ const barrier = !['credit', 'status', 'input', 'transport_cost', 'cache_dependency'].includes(command);
194
+ const prior = barrier ? [...this.active] : [];
195
+ const identity = { ...binding }, epoch = this.counters?.epoch;
196
+ let data;
197
+ try {
198
+ data = structuredClone(payload);
199
+ }
200
+ catch (error) {
201
+ this.fail(error);
202
+ return Promise.reject(error);
203
+ }
204
+ this.queued++;
205
+ const queuedAt = performance.now();
206
+ let startedAt = queuedAt;
207
+ let response;
208
+ const issued = this.tail.then(async () => {
209
+ // A barrier drains prior validated replies and their owner continuations,
210
+ // then excludes every later write until its own reply has completed.
211
+ if (barrier)
212
+ await Promise.allSettled(prior);
213
+ startedAt = performance.now();
214
+ if (this.stopped || this.exited || !Number.isSafeInteger(this.id + 1))
215
+ throw new NativeCommandError('transport_failed');
216
+ if (!current() || command === 'input' && epoch !== undefined && this.counters?.epoch !== epoch)
217
+ throw new NativeCommandCancelled();
218
+ // The owner may withdraw capture authority while this rate command waits.
219
+ // All other payload fields retain their enqueue-time snapshot.
220
+ if (command === 'set_rate')
221
+ Object.assign(data, { pauseCapture: payload.pauseCapture });
222
+ const id = ++this.id, bytes = Buffer.from(JSON.stringify({ ipc: 1, id, command, binding: identity, payload: data }));
223
+ mediaRequire(bytes.length > 0 && bytes.length <= 49152);
224
+ response = new Promise((resolve, reject) => {
225
+ const timer = setTimeout(() => this.fail(new NativeCommandError('transport_failed')), 5000);
226
+ this.pending.set(id, { resolve, reject, timer });
227
+ });
228
+ // A reply can precede the write callback; retain its failure without an
229
+ // unhandled rejection while write ownership is still being confirmed.
230
+ void response.catch(() => { });
231
+ const prefix = Buffer.alloc(4);
232
+ prefix.writeUInt32BE(bytes.length);
233
+ await bounded(write(this.child.stdin, Buffer.concat([prefix, bytes])), 5000);
234
+ });
235
+ const run = issued.then(() => response);
236
+ const report = (error) => {
237
+ if (error instanceof NativeCommandCancelled)
238
+ return;
239
+ if (!(error instanceof NativeCommandError) || !['busy', 'rate_limited'].includes(error.code)) {
240
+ console.error(JSON.stringify({ event: 'desktop_native_command_failed', command,
241
+ code: error instanceof NativeCommandError ? error.code : 'transport_failed', elapsedMs: Math.round(performance.now() - startedAt), queuedMs: Math.round(startedAt - queuedAt), queued: this.queued }));
242
+ }
243
+ if (!(error instanceof NativeCommandError) || !error.counters)
244
+ this.fail(error);
245
+ };
246
+ const completed = run.catch(error => { report(error); throw error; }).finally(() => {
247
+ this.queued--;
248
+ this.active.delete(completed);
249
+ });
250
+ this.active.add(completed);
251
+ // Observe issuance errors before allowing the next queued write. Reply
252
+ // failures are reported by completed; the reader independently fails all
253
+ // in-flight work on malformed or unknown replies.
254
+ this.tail = (barrier ? completed : issued).catch(error => {
255
+ if (!(error instanceof NativeCommandCancelled) && (!(error instanceof NativeCommandError) || !error.counters))
256
+ this.fail(error);
257
+ });
258
+ return completed;
259
+ }
260
+ get packets() {
261
+ const self = this;
262
+ return { async *[Symbol.asyncIterator]() {
263
+ while (!self.stopped && !self.exited) {
264
+ const header = await self.media.exact(64), size = header.readUInt32BE(48);
265
+ mediaRequire(size > 0 && size <= 65536);
266
+ const bytes = Buffer.concat([header, await self.media.exact(size)]);
267
+ decodeMedia(bytes);
268
+ yield bytes;
269
+ }
270
+ } };
271
+ }
272
+ get audioPackets() {
273
+ const self = this;
274
+ return { async *[Symbol.asyncIterator]() {
275
+ while (!self.stopped && !self.exited) {
276
+ const header = await self.audio.exact(40), size = header.readUInt16BE(36);
277
+ mediaRequire(size > 0 && size <= 1000);
278
+ const bytes = Buffer.concat([header, await self.audio.exact(size)]);
279
+ decodeAudio(bytes);
280
+ yield bytes;
281
+ }
282
+ } };
283
+ }
284
+ stop(binding) {
285
+ return this.stopPromise ??= (async () => {
286
+ if (!this.exited) {
287
+ try {
288
+ await bounded(this.request('stop', binding), 750);
289
+ }
290
+ catch { /* EOF cleanup remains authoritative. */ }
291
+ }
292
+ await this.terminate();
293
+ await HelperProcess.verifyCleanup(this.executable, this.env);
294
+ })();
295
+ }
296
+ terminate() {
297
+ return this.termination ??= (async () => {
298
+ this.stopped = true;
299
+ this.rejectPending(new NativeCommandError('transport_failed'));
300
+ if (!this.exited) {
301
+ this.child.kill('SIGTERM');
302
+ try {
303
+ await bounded(this.closed, 1000);
304
+ }
305
+ catch {
306
+ this.child.kill('SIGKILL');
307
+ await bounded(this.closed, 2000);
308
+ }
309
+ }
310
+ this.child.stdin?.destroy();
311
+ this.child.stdout?.destroy();
312
+ this.child.stdio[3]?.destroy();
313
+ this.child.stdio[4]?.destroy();
314
+ })();
315
+ }
316
+ static async verifyCleanup(executable, env = process.env) {
317
+ const deadline = performance.now() + 3000;
318
+ do {
319
+ try {
320
+ await execute(executable, ['--verify-input-cleanup'], { env, timeout: 750, maxBuffer: 4096 });
321
+ return;
322
+ }
323
+ catch {
324
+ await delay(25);
325
+ }
326
+ } while (performance.now() < deadline);
327
+ throw new NativeCommandError('unavailable');
328
+ }
329
+ }
330
+ function write(stream, bytes) {
331
+ return new Promise((resolve, reject) => stream.write(bytes, error => error ? reject(error) : resolve()));
332
+ }
333
+ export async function launchAutomationHelper(executable, env, binding) {
334
+ if (!binding.native || !binding.preparation)
335
+ throw new NativeCommandError('trusted_preparation_unavailable');
336
+ const token = automationRandomBytes(32).toString('hex');
337
+ const child = spawn(executable, ['--automation-fd', '3'], { stdio: ['pipe', 'pipe', 'ignore', 'pipe'],
338
+ env: { ...env, AXOM_AUTOMATION_TOKEN: token, AXOM_AUTOMATION_FEATURES: 'capture_wait_v1' } });
339
+ child.stdin.on('error', () => { });
340
+ const control = new NativeByteReader(child.stdout, 49152), binary = new NativeByteReader(child.stdio[3], 8388608 + 49156);
341
+ let ended = false, controlId = 0, writeCount = 0, writeTail = Promise.resolve();
342
+ const renewals = new Map();
343
+ const preparations = new Map();
344
+ const statuses = new Map();
345
+ const readControls = new Map();
346
+ const send = (text) => {
347
+ if (ended || writeCount >= 64)
348
+ return Promise.reject(new NativeCommandError('resource_limit'));
349
+ const body = Buffer.from(text);
350
+ if (!body.length || body.length > 49152)
351
+ return Promise.reject(new NativeCommandError('invalid_argument'));
352
+ const prefix = Buffer.alloc(4);
353
+ prefix.writeUInt32BE(body.length);
354
+ writeCount++;
355
+ const pending = writeTail.then(() => { if (ended)
356
+ throw new NativeCommandError('closed'); return bounded(write(child.stdin, Buffer.concat([prefix, body])), 1000); });
357
+ writeTail = pending.catch(() => { }).finally(() => { writeCount--; });
358
+ return pending;
359
+ };
360
+ const transport = new AutomationTransport(send, requestId => { void send(JSON.stringify({ command: 'cancel', requestId })).catch(() => { }); });
361
+ const retire = () => {
362
+ if (ended)
363
+ return;
364
+ ended = true;
365
+ transport.close('transport_failed');
366
+ for (const renewal of renewals.values()) {
367
+ clearTimeout(renewal.timer);
368
+ renewal.reject(new NativeCommandError('closed'));
369
+ }
370
+ renewals.clear();
371
+ for (const pending of preparations.values()) {
372
+ clearTimeout(pending.timer);
373
+ pending.detach();
374
+ pending.reject(new NativeCommandError('closed'));
375
+ }
376
+ preparations.clear();
377
+ for (const pending of statuses.values()) {
378
+ clearTimeout(pending.timer);
379
+ pending.reject(new NativeCommandError('closed'));
380
+ }
381
+ statuses.clear();
382
+ for (const pending of readControls.values()) {
383
+ clearTimeout(pending.timer);
384
+ pending.reject(new NativeCommandError('closed'));
385
+ }
386
+ readControls.clear();
387
+ };
388
+ const closed = new Promise(resolve => { child.once('exit', () => { retire(); resolve(); }); child.once('error', () => { retire(); resolve(); }); });
389
+ const readControl = async () => {
390
+ const size = (await control.exact(4)).readUInt32BE();
391
+ return parseAutomationJson(await control.exact(size), 49152).value;
392
+ };
393
+ const stop = async () => {
394
+ try {
395
+ if (!ended)
396
+ await send(JSON.stringify({ command: 'shutdown' }));
397
+ }
398
+ catch { }
399
+ retire();
400
+ child.kill('SIGTERM');
401
+ try {
402
+ await bounded(closed, 1000);
403
+ }
404
+ catch {
405
+ child.kill('SIGKILL');
406
+ await bounded(closed, 2000);
407
+ }
408
+ child.stdin?.destroy();
409
+ child.stdout?.destroy();
410
+ child.stdio[3].destroy();
411
+ };
412
+ try {
413
+ const handshake = await bounded(readControl(), 3000, 'unsupported_automation_protocol');
414
+ if (handshake.automationProtocol !== 1 || !Array.isArray(handshake.operations) || !handshake.operations.includes('capture'))
415
+ throw new NativeCommandError('unsupported_automation_protocol');
416
+ await send(JSON.stringify({ command: 'bind', token, context: { ...binding.native, route: binding.route, executionId: binding.executionId,
417
+ preparation: { ...binding.preparation, selector: null, mode: null } } }));
418
+ const attached = await bounded(readControl(), 3000, 'unsupported_automation_protocol');
419
+ if (attached.control !== 'bound' || !Array.isArray(attached.operations))
420
+ throw new NativeCommandError('unsupported_automation_protocol');
421
+ void (async () => {
422
+ while (!ended) {
423
+ const message = await readControl();
424
+ const readPending = readControls.get(message.id);
425
+ if (readPending && ['status_only', 'isolate', 'prepare_failed'].includes(message.control)) {
426
+ readControls.delete(message.id);
427
+ clearTimeout(readPending.timer);
428
+ if (message.control === 'prepare_failed')
429
+ readPending.reject(new NativeCommandError(message.code));
430
+ else if (message.control !== readPending.command || message.result?.physical !== 'completed' || message.result?.revoked !== true)
431
+ readPending.reject(new NativeCommandError('isolation_unconfirmed'));
432
+ else
433
+ readPending.resolve(message.result);
434
+ }
435
+ else if (message.control === 'lease_admitted') {
436
+ const renewal = renewals.get(message.id);
437
+ if (!renewal)
438
+ continue;
439
+ renewals.delete(message.id);
440
+ clearTimeout(renewal.timer);
441
+ renewal.resolve({ admitted: true, physical: message.physical });
442
+ }
443
+ else if (message.control === 'prepared' || message.control === 'prepare_failed') {
444
+ const pending = preparations.get(message.id);
445
+ if (!pending)
446
+ continue;
447
+ preparations.delete(message.id);
448
+ clearTimeout(pending.timer);
449
+ pending.detach();
450
+ if (message.control === 'prepared')
451
+ pending.resolve(message.attachment);
452
+ else
453
+ pending.reject(new NativeCommandError(message.code, undefined, decodeAutomationFailureDetail(message.detail)));
454
+ }
455
+ else if (message.control === 'control_status') {
456
+ const pending = statuses.get(message.id);
457
+ if (!pending)
458
+ continue;
459
+ statuses.delete(message.id);
460
+ clearTimeout(pending.timer);
461
+ pending.resolve({ physical: message.physical, revoked: message.revoked });
462
+ }
463
+ else if (message.control !== 'close_admitted') {
464
+ const accepted = transport.acceptResponse(JSON.stringify(message));
465
+ if (!accepted.ok && accepted.code !== 'unknown_request')
466
+ throw new NativeCommandError('invalid_response');
467
+ }
468
+ }
469
+ })().catch(() => { retire(); child.kill('SIGTERM'); });
470
+ void (async () => {
471
+ while (!ended) {
472
+ const prefix = await binary.exact(4), length = prefix.readUInt32BE();
473
+ if (length < 1 || length > 49152)
474
+ throw new NativeCommandError('invalid_binary');
475
+ const header = await binary.exact(length), value = parseAutomationJson(header, 49152).value;
476
+ if (!Number.isInteger(value.bytesLength) || value.bytesLength < 1 || value.bytesLength > 8388608)
477
+ throw new NativeCommandError('invalid_binary');
478
+ transport.acceptBinary(Buffer.concat([prefix, header, await binary.exact(value.bytesLength)]));
479
+ }
480
+ })().catch(() => { retire(); child.kill('SIGTERM'); });
481
+ return new AutomationSession(transport, binding, { operations: attached.operations.filter((operation) => handshake.operations.includes(operation)), close: stop,
482
+ readControl: async (command, fields) => {
483
+ if (readControls.size >= 8 || ended)
484
+ throw new NativeCommandError('resource_limit');
485
+ const requestId = `read-control-${++controlId}`;
486
+ const pending = new Promise((resolve, reject) => {
487
+ const timer = setTimeout(() => { readControls.delete(requestId); reject(new NativeCommandError('timed_out')); }, 5000);
488
+ readControls.set(requestId, { command, resolve, reject, timer });
489
+ });
490
+ void send(JSON.stringify({ command, requestId, ...fields })).catch(error => {
491
+ const entry = readControls.get(requestId);
492
+ if (entry) {
493
+ readControls.delete(requestId);
494
+ clearTimeout(entry.timer);
495
+ entry.reject(error);
496
+ }
497
+ });
498
+ return pending;
499
+ },
500
+ prepare: async (preparation, options = {}) => {
501
+ if (preparations.size + renewals.size + statuses.size >= 8 || ended)
502
+ throw new NativeCommandError('resource_limit');
503
+ if (options.signal?.aborted)
504
+ throw new NativeCommandError('cancelled');
505
+ const requestId = `attachment-${++controlId}`;
506
+ const pending = new Promise((resolve, reject) => {
507
+ const abort = () => {
508
+ const entry = preparations.get(requestId);
509
+ if (!entry)
510
+ return;
511
+ preparations.delete(requestId);
512
+ clearTimeout(entry.timer);
513
+ entry.detach();
514
+ reject(new NativeCommandError('cancelled'));
515
+ void send(JSON.stringify({ command: 'cancel', requestId })).catch(() => { });
516
+ };
517
+ const timer = setTimeout(() => { abort(); }, options.timeoutMs ?? 65000);
518
+ preparations.set(requestId, { resolve, reject, timer, detach: () => options.signal?.removeEventListener('abort', abort) });
519
+ options.signal?.addEventListener('abort', abort, { once: true });
520
+ });
521
+ void send(JSON.stringify({ command: 'prepare', requestId, preparation })).catch(error => {
522
+ const entry = preparations.get(requestId);
523
+ if (entry) {
524
+ preparations.delete(requestId);
525
+ clearTimeout(entry.timer);
526
+ entry.detach();
527
+ entry.reject(error);
528
+ }
529
+ });
530
+ return pending;
531
+ },
532
+ status: async () => {
533
+ if (preparations.size + renewals.size + statuses.size >= 8 || ended)
534
+ throw new NativeCommandError('resource_limit');
535
+ const id = ++controlId;
536
+ const pending = new Promise((resolve, reject) => {
537
+ const timer = setTimeout(() => { statuses.delete(id); reject(new NativeCommandError('timed_out')); }, 1000);
538
+ statuses.set(id, { resolve, reject, timer });
539
+ });
540
+ void send(JSON.stringify({ command: 'control_status', id })).catch(error => { const entry = statuses.get(id); if (entry) {
541
+ clearTimeout(entry.timer);
542
+ statuses.delete(id);
543
+ entry.reject(error);
544
+ } });
545
+ return pending;
546
+ },
547
+ renew: async (leaseMs) => {
548
+ if (preparations.size + renewals.size + statuses.size >= 8 || ended)
549
+ throw new NativeCommandError('resource_limit');
550
+ const id = ++controlId;
551
+ const pending = new Promise((resolve, reject) => {
552
+ const timer = setTimeout(() => { renewals.delete(id); reject(new NativeCommandError('timed_out')); }, 1000);
553
+ renewals.set(id, { resolve, reject, timer });
554
+ });
555
+ try {
556
+ await send(JSON.stringify({ command: 'renew', id, leaseMs }));
557
+ return await pending;
558
+ }
559
+ catch (error) {
560
+ const renewal = renewals.get(id);
561
+ if (renewal) {
562
+ clearTimeout(renewal.timer);
563
+ renewals.delete(id);
564
+ renewal.resolve({ admitted: true, physical: 'failed' });
565
+ }
566
+ throw error;
567
+ }
568
+ } });
569
+ }
570
+ catch (error) {
571
+ await stop();
572
+ throw error instanceof NativeCommandError ? error : new NativeCommandError('unsupported_automation_protocol');
573
+ }
574
+ }
575
+ /** A single bounded session/event consumer; overflow retires the session. */
576
+ export class DesktopQueue {
577
+ limit;
578
+ values = [];
579
+ wake;
580
+ ended = false;
581
+ constructor(limit = 64) {
582
+ this.limit = limit;
583
+ }
584
+ push(value) {
585
+ if (this.ended)
586
+ return;
587
+ if (this.wake) {
588
+ const wake = this.wake;
589
+ this.wake = undefined;
590
+ wake({ value, done: false });
591
+ }
592
+ else {
593
+ mediaRequire(this.values.length < this.limit);
594
+ this.values.push(value);
595
+ }
596
+ }
597
+ close() { this.ended = true; this.values = []; this.wake?.({ value: undefined, done: true }); this.wake = undefined; }
598
+ [Symbol.asyncIterator]() {
599
+ return { next: () => this.values.length ? Promise.resolve({ value: this.values.shift(), done: false }) :
600
+ this.ended ? Promise.resolve({ value: undefined, done: true }) : new Promise(resolve => { mediaRequire(!this.wake); this.wake = resolve; }) };
601
+ }
602
+ }
@@ -0,0 +1,65 @@
1
+ import { promises as fs, constants } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { createHash } from 'node:crypto';
4
+ import { z } from 'zod';
5
+ export const deviceHostGrantSchema = z.strictObject({ schemaVersion: z.literal(2), hostGeneration: z.string().min(1).max(128),
6
+ executable: z.string().startsWith('/').max(4096), sha256: z.string().regex(/^[a-f0-9]{64}$/),
7
+ socket: z.string().startsWith('/').max(103), token: z.string().regex(/^[a-f0-9]{64}$/) });
8
+ async function verifiedSocketPath(socket, checks) {
9
+ const realpath = checks.realpath ?? ((file) => fs.realpath(file)), canonical = await realpath(socket);
10
+ if (canonical === socket)
11
+ return true;
12
+ // Older macOS producers stored /tmp while the OS resolves its root-owned system alias.
13
+ // Exact prefix substitution rejects every additional symlink or path normalization.
14
+ if ((checks.platform ?? process.platform) !== 'darwin' || !socket.startsWith('/tmp/') || canonical !== '/private' + socket)
15
+ return false;
16
+ try {
17
+ const lstat = checks.systemLstat ?? ((file) => fs.lstat(file));
18
+ const link = await lstat('/tmp'), target = await lstat('/private/tmp');
19
+ const destination = await (checks.systemReadlink ?? ((file) => fs.readlink(file)))('/tmp');
20
+ return link.isSymbolicLink() && link.uid === 0 && target.isDirectory() && target.uid === 0 && !!(target.mode & 0o1000) &&
21
+ (destination === 'private/tmp' || destination === '/private/tmp') && await realpath('/tmp') === '/private/tmp' && await realpath('/private/tmp') === '/private/tmp';
22
+ }
23
+ catch {
24
+ return false;
25
+ }
26
+ }
27
+ export async function readDeviceHostDescriptor(fileName) {
28
+ const parent = await fs.lstat(path.dirname(fileName));
29
+ if (!parent.isDirectory() || parent.uid !== process.getuid?.() || parent.mode & 0o077)
30
+ throw new Error('unsafe_native_host');
31
+ const file = await fs.open(fileName, constants.O_RDONLY | constants.O_NOFOLLOW);
32
+ let grant;
33
+ try {
34
+ const stat = await file.stat();
35
+ if (!stat.isFile() || stat.uid !== process.getuid?.() || stat.mode & 0o077 || stat.size > 16384)
36
+ throw new Error('unsafe_native_host');
37
+ grant = deviceHostGrantSchema.parse(JSON.parse(await file.readFile('utf8')));
38
+ }
39
+ finally {
40
+ await file.close();
41
+ }
42
+ return grant;
43
+ }
44
+ export async function readDeviceHostGrant(fileName, allowRootExecutable = false, checks = {}) {
45
+ const grant = await readDeviceHostDescriptor(fileName);
46
+ const parent = await fs.lstat(path.dirname(grant.socket)), socket = await fs.lstat(grant.socket);
47
+ if (!parent.isDirectory() || parent.uid !== process.getuid?.() || parent.mode & 0o077 || !socket.isSocket() || socket.uid !== process.getuid?.() || socket.mode & 0o077 ||
48
+ !await verifiedSocketPath(grant.socket, checks) || await (checks.realpath ?? ((file) => fs.realpath(file)))(grant.executable) !== grant.executable)
49
+ throw new Error('unsafe_native_host');
50
+ const executable = await fs.open(grant.executable, constants.O_RDONLY | constants.O_NOFOLLOW);
51
+ try {
52
+ const stat = await executable.stat();
53
+ if (!stat.isFile() || stat.uid !== process.getuid?.() && !(allowRootExecutable && stat.uid === 0) || stat.mode & 0o022 || stat.size > 32 * 1024 * 1024)
54
+ throw new Error('unsafe_native_host');
55
+ const hash = createHash('sha256');
56
+ for await (const bytes of executable.createReadStream({ autoClose: false }))
57
+ hash.update(bytes);
58
+ if (hash.digest('hex') !== grant.sha256)
59
+ throw new Error('unsafe_native_host');
60
+ }
61
+ finally {
62
+ await executable.close();
63
+ }
64
+ return grant;
65
+ }