@platless/axom-cli 0.5.9 → 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 (163) hide show
  1. package/README.md +18 -5
  2. package/dist/axom-patch/cli/README.md +100 -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/codex-account-limits.json +11 -11
  7. package/dist/axom-patch/cli/codex-terminal-output.json +16 -0
  8. package/dist/axom-patch/cli/commandcode-account-limits.json +9 -9
  9. package/dist/axom-patch/cli/kimi-quota.mjs +12 -0
  10. package/dist/axom-patch/cli/kimi-subagents.mjs +173 -0
  11. package/dist/axom-patch/cli/nightly-task.md +55 -13
  12. package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +33 -0
  13. package/dist/axom-patch/cli/probes/kimi-subagent-subwork.mjs +82 -0
  14. package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +1591 -0
  15. package/dist/axom-patch/cli/qoder/releases/1.1.75.json +360 -0
  16. package/dist/axom-patch/cli/qoder-background-terminal.md +13 -6
  17. package/dist/axom-patch/cli/qoder-foreground-output.md +14 -8
  18. package/dist/axom-patch/cli/qoder-native-acp.json +227 -0
  19. package/dist/axom-patch/cli/qoder-steering.md +20 -25
  20. package/dist/axom-patch/cli/qoder-subagent-subwork.md +17 -68
  21. package/dist/axom-patch/cli/runtime.json +16 -18
  22. package/dist/axom-patch/cli/zcode-account-limits.json +10 -11
  23. package/dist/axom-patch/cli/zcode-models.json +1 -2
  24. package/dist/axom-patch/cli/zcode-subagent-subwork.json +9 -10
  25. package/dist/cli.js +4 -36
  26. package/dist/commands/knowledge.js +47 -0
  27. package/dist/commands/update.js +65 -3
  28. package/dist/commands/workstation.js +73 -7
  29. package/dist/install/claude/commands.js +13 -13
  30. package/dist/mcp.js +1 -1
  31. package/dist/native/darwin-arm64/axom-quic +0 -0
  32. package/dist/workstation/acp/acpAgents.js +102 -98
  33. package/dist/workstation/acp/acpClient.js +26 -0
  34. package/dist/workstation/acp/builds/install/npm.js +99 -10
  35. package/dist/workstation/acp/initializeResponse.js +6 -1
  36. package/dist/workstation/acp/managedPatches.js +189 -25
  37. package/dist/workstation/acp/managedRuntime.js +314 -0
  38. package/dist/workstation/acp/patchAssets.js +40 -0
  39. package/dist/workstation/acp/processor/checkpoint.js +25 -34
  40. package/dist/workstation/acp/processor/control.js +5 -78
  41. package/dist/workstation/acp/processor/runtime.js +5 -81
  42. package/dist/workstation/acp/processor/session.js +51 -80
  43. package/dist/workstation/acp/processor/state.js +6 -13
  44. package/dist/workstation/acp/processor/step.js +3 -20
  45. package/dist/workstation/acp/processor/test/fixtureLog.js +9 -16
  46. package/dist/workstation/acp/processor/updates.js +131 -105
  47. package/dist/workstation/acp/processorSession.js +8 -172
  48. package/dist/workstation/acp/registry/catalog.js +65 -39
  49. package/dist/workstation/acp/registry/launch.js +16 -1
  50. package/dist/workstation/acp/switching.js +0 -3
  51. package/dist/workstation/acp/test/fakeAgent.js +13 -0
  52. package/dist/workstation/acp/test/keeperWorld.js +28 -0
  53. package/dist/workstation/acp/test/raw/sources.js +16 -130
  54. package/dist/workstation/acp/test/realAdapter.js +1 -47
  55. package/dist/workstation/acp/test/scriptAgent.js +11 -3
  56. package/dist/workstation/agentFlow.js +1 -1
  57. package/dist/workstation/agentSessions.js +38 -143
  58. package/dist/workstation/channels/management.js +95 -11
  59. package/dist/workstation/channels/url.js +1 -1
  60. package/dist/workstation/config.js +16 -4
  61. package/dist/workstation/connection/encryptedChannel.js +4 -1
  62. package/dist/workstation/control/bindingStore.js +200 -0
  63. package/dist/workstation/control/commands.js +124 -0
  64. package/dist/workstation/control/daemonIdentity.js +53 -0
  65. package/dist/workstation/control/enrollment.js +106 -0
  66. package/dist/workstation/control/inspect.js +71 -0
  67. package/dist/workstation/control/locks.js +63 -0
  68. package/dist/workstation/control/protocol.js +40 -0
  69. package/dist/workstation/control/restartGuard.js +46 -0
  70. package/dist/workstation/control/runtimeClient.js +97 -0
  71. package/dist/workstation/control/runtimeHost.js +148 -0
  72. package/dist/workstation/control/serverIdentity.js +35 -0
  73. package/dist/workstation/daemon.js +106 -6
  74. package/dist/workstation/daemonctl.js +77 -14
  75. package/dist/workstation/desktopControl/audio.js +99 -0
  76. package/dist/workstation/desktopControl/capabilities.js +92 -0
  77. package/dist/workstation/desktopControl/captureModes.js +9 -0
  78. package/dist/workstation/desktopControl/captureTargetLabel.js +9 -0
  79. package/dist/workstation/desktopControl/deviceSockets.js +246 -0
  80. package/dist/workstation/desktopControl/helperProcess.js +353 -0
  81. package/dist/workstation/desktopControl/lobby.js +129 -0
  82. package/dist/workstation/desktopControl/macProvider.js +155 -0
  83. package/dist/workstation/desktopControl/media.js +331 -0
  84. package/dist/workstation/desktopControl/nativeProvider.js +1018 -0
  85. package/dist/workstation/desktopControl/networkEvidence.js +43 -0
  86. package/dist/workstation/desktopControl/overview.js +58 -0
  87. package/dist/workstation/desktopControl/protocol.js +302 -0
  88. package/dist/workstation/desktopControl/quicMedia.js +240 -0
  89. package/dist/workstation/desktopControl/rateController.js +226 -0
  90. package/dist/workstation/desktopControl/supervisor.js +1681 -0
  91. package/dist/workstation/desktopControl/transport.js +27 -0
  92. package/dist/workstation/desktopControl/wallpaper.js +78 -0
  93. package/dist/workstation/files/credentials.js +142 -0
  94. package/dist/workstation/files/text.js +42 -8
  95. package/dist/workstation/keepers/kit/bundle.js +21 -17
  96. package/dist/workstation/keepers/kit/launcher.js +47 -7
  97. package/dist/workstation/keepers/kit/paths.js +2 -1
  98. package/dist/workstation/managedInstall.js +57 -0
  99. package/dist/workstation/protocol/agent.js +2 -15
  100. package/dist/workstation/protocol/management.js +93 -4
  101. package/dist/workstation/protocol/runtimeDiagnostics.js +15 -0
  102. package/dist/workstation/rd/artifact.js +119 -0
  103. package/dist/workstation/rd/control.js +119 -0
  104. package/dist/workstation/rd/display.js +29 -0
  105. package/dist/workstation/rd/provider.js +222 -0
  106. package/dist/workstation/rd/sessions.js +391 -0
  107. package/dist/workstation/rd/state.js +45 -0
  108. package/dist/workstation/rd/update.js +171 -0
  109. package/dist/workstation/rd/upkeep.js +38 -0
  110. package/dist/workstation/register.js +27 -37
  111. package/dist/workstation/service/installer.js +104 -13
  112. package/dist/workstation/service/launchd.js +1 -0
  113. package/dist/workstation/storageMetrics.js +118 -0
  114. package/dist/workstation/systemMetrics.js +44 -92
  115. package/dist/workstation/toolchain.js +94 -0
  116. package/dist/workstation/tools/browser.js +28 -4
  117. package/dist/workstation/update/artifact.js +2 -1
  118. package/dist/workstation/update/globalInstall.js +116 -0
  119. package/dist/workstation/update/npmInstaller.js +49 -23
  120. package/dist/workstation/update/rdRollback.js +145 -0
  121. package/dist/workstation/update/runUpdate.js +3 -2
  122. package/dist/workstation/update/watchdog.js +16790 -334
  123. package/package.json +5 -5
  124. package/skills/axom/SKILL.md +11 -11
  125. package/skills/axom-definition/SKILL.md +3 -3
  126. package/skills/axom-platform/SKILL.md +3 -3
  127. package/dist/axom-patch/cli/probes/qoder-steering.mjs +0 -89
  128. package/dist/axom-patch/cli/probes/qoder-subagent-origin.mjs +0 -61
  129. package/dist/axom-patch/cli/qoder-steering.json +0 -52
  130. package/dist/axom-patch/cli/qoder-subagent-origin.json +0 -73
  131. package/dist/commands/guide.js +0 -38
  132. package/dist/runtime-deps/qoder-sdk/package-lock.json +0 -1189
  133. package/dist/runtime-deps/qoder-sdk/package.json +0 -8
  134. package/dist/workstation/acp/delegationMcp.js +0 -24
  135. package/dist/workstation/acp/processor/sideChannel.js +0 -80
  136. package/dist/workstation/acp/qoderStartup.js +0 -45
  137. package/dist/workstation/acp/test/raw/qoderCaptures.js +0 -125
  138. package/dist/workstation/qoderAdapter/backgroundProcess.js +0 -118
  139. package/dist/workstation/qoderAdapter/backgroundTerminal.js +0 -193
  140. package/dist/workstation/qoderAdapter/backgroundTerminalFold.js +0 -372
  141. package/dist/workstation/qoderAdapter/callbackContext.js +0 -76
  142. package/dist/workstation/qoderAdapter/completedContinuation.js +0 -139
  143. package/dist/workstation/qoderAdapter/contract.js +0 -78
  144. package/dist/workstation/qoderAdapter/foregroundOutput.js +0 -36
  145. package/dist/workstation/qoderAdapter/history.js +0 -85
  146. package/dist/workstation/qoderAdapter/identityIndex.js +0 -191
  147. package/dist/workstation/qoderAdapter/inputQueue.js +0 -42
  148. package/dist/workstation/qoderAdapter/main.js +0 -95
  149. package/dist/workstation/qoderAdapter/mapper.js +0 -117
  150. package/dist/workstation/qoderAdapter/observerPlugin.js +0 -245
  151. package/dist/workstation/qoderAdapter/observerReceiver.js +0 -187
  152. package/dist/workstation/qoderAdapter/permissions.js +0 -61
  153. package/dist/workstation/qoderAdapter/profile.js +0 -93
  154. package/dist/workstation/qoderAdapter/sdkDriver.js +0 -48
  155. package/dist/workstation/qoderAdapter/sdkRuntime.js +0 -93
  156. package/dist/workstation/qoderAdapter/sdkRuntimeInstall.js +0 -145
  157. package/dist/workstation/qoderAdapter/session.js +0 -319
  158. package/dist/workstation/qoderAdapter/subagentMeta.js +0 -28
  159. package/dist/workstation/qoderAdapter/subwork.js +0 -85
  160. package/dist/workstation/qoderAdapter/taskControl.js +0 -38
  161. package/dist/workstation/qoderAdapter/taskLedger.js +0 -163
  162. package/dist/workstation/qoderAdapter/usage.js +0 -37
  163. /package/dist/workstation/{qoderAdapter/types.js → desktopControl/provider.js} +0 -0
@@ -0,0 +1,43 @@
1
+ import { z } from 'zod';
2
+ const counter = z.number().int().min(0).max(Number.MAX_SAFE_INTEGER);
3
+ const pathSchema = z.strictObject({
4
+ sequence: counter.min(1), pathGeneration: counter.min(1),
5
+ rttUs: counter, minRttUs: counter, bandwidthBps: counter.nullable(),
6
+ cwndBytes: counter, sentPackets: counter, lostPackets: counter,
7
+ congestionEvents: counter, udpTxBytes: counter,
8
+ });
9
+ export function decodeQuicPathStats(bytes, observedAtMs) {
10
+ if (bytes.length > 4096 || !Number.isFinite(observedAtMs) || observedAtMs < 0)
11
+ throw new Error('invalid path stats');
12
+ const stats = pathSchema.parse(JSON.parse(bytes.toString('utf8')));
13
+ if (stats.lostPackets > stats.sentPackets)
14
+ throw new Error('invalid path counters');
15
+ return { ...stats, observedAtMs };
16
+ }
17
+ export class DesktopNetworkEvidence {
18
+ previous;
19
+ state = 'unknown';
20
+ queueMs = 0;
21
+ sample(nowMs, stats, demandBps) {
22
+ if (!stats || stats.observedAtMs > nowMs || nowMs - stats.observedAtMs > Math.max(1500, stats.rttUs / 1000 * 3)) {
23
+ return { state: 'unknown', fresh: false, queueMs: 0 };
24
+ }
25
+ const previous = this.previous;
26
+ const samePath = previous?.id === stats.id && previous.pathGeneration === stats.pathGeneration;
27
+ if (samePath && stats.sequence <= previous.sequence) {
28
+ return { state: this.state, fresh: false, queueMs: this.queueMs, bandwidthBps: previous.bandwidthBps ?? undefined };
29
+ }
30
+ this.previous = stats;
31
+ this.queueMs = Math.max(0, (stats.rttUs - stats.minRttUs) / 1000);
32
+ const queueThresholdMs = Math.max(10, stats.minRttUs / 1000 * 0.1);
33
+ const countersAdvance = samePath && stats.sentPackets >= previous.sentPackets &&
34
+ stats.lostPackets >= previous.lostPackets && stats.congestionEvents >= previous.congestionEvents &&
35
+ stats.udpTxBytes > previous.udpTxBytes;
36
+ const congestion = countersAdvance && stats.congestionEvents > previous.congestionEvents;
37
+ const bandwidth = stats.bandwidthBps ?? undefined;
38
+ const loaded = countersAdvance && bandwidth !== undefined && bandwidth > 0 && demandBps >= bandwidth * 0.85;
39
+ this.state = this.queueMs > queueThresholdMs && (congestion || loaded) ? 'congested'
40
+ : this.queueMs <= queueThresholdMs ? 'usable' : 'unknown';
41
+ return { state: this.state, fresh: true, queueMs: this.queueMs, bandwidthBps: bandwidth };
42
+ }
43
+ }
@@ -0,0 +1,58 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { mediaRequire } from './media.js';
3
+ /** A single initial image, then <=2% of focus target and <=32kbps, including wire headers. */
4
+ export class DesktopOverviewBudget {
5
+ tokens = 32808;
6
+ at = 0;
7
+ lastSent = -Infinity;
8
+ digest;
9
+ initialized = false;
10
+ canCapture(now, targetBps, pressure) {
11
+ this.refill(now, targetBps);
12
+ return !pressure && now - this.lastSent >= 2000 && this.tokens >= 1024;
13
+ }
14
+ get maxSide() { return this.tokens < 8192 ? 320 : 640; }
15
+ eligible(bytes, now, targetBps) {
16
+ this.refill(now, targetBps);
17
+ const digest = createHash('sha256').update(bytes.subarray(16, 28)).update(bytes.subarray(40)).digest('hex');
18
+ if (digest === this.digest || bytes.length > this.tokens || now - this.lastSent < 2000)
19
+ return false;
20
+ return true;
21
+ }
22
+ admit(bytes, now, targetBps) {
23
+ if (!this.eligible(bytes, now, targetBps))
24
+ return false;
25
+ this.tokens -= bytes.length;
26
+ this.lastSent = now;
27
+ this.digest = createHash('sha256').update(bytes.subarray(16, 28)).update(bytes.subarray(40)).digest('hex');
28
+ return true;
29
+ }
30
+ disconnected() { this.digest = undefined; }
31
+ refill(now, targetBps) {
32
+ mediaRequire(Number.isFinite(now) && Number.isFinite(targetBps) && targetBps >= 0);
33
+ if (!this.initialized) {
34
+ this.at = now;
35
+ this.initialized = true;
36
+ }
37
+ this.tokens = Math.min(32808, this.tokens + Math.max(0, now - this.at) / 1000 * Math.min(4000, targetBps / 400));
38
+ this.at = now;
39
+ }
40
+ }
41
+ export function overviewPacket(image, sequence, generation) {
42
+ const { width: w, height: h, sourceWidth: sw, sourceHeight: sh, jpeg } = image;
43
+ mediaRequire([w, h, sw, sh, sequence, generation].every(Number.isSafeInteger) && sequence > 0 && generation > 0 &&
44
+ w >= 2 && h >= 2 && w <= 640 && h <= 640 && sw >= w && sh >= h && sw <= 16384 && sh <= 16384 &&
45
+ Math.abs(w / h - sw / sh) <= 2 / h && jpeg.length >= 4 && jpeg.length <= 32768 && jpeg.readUInt16BE(0) === 0xffd8 && jpeg.readUInt16BE(jpeg.length - 2) === 0xffd9);
46
+ const bytes = Buffer.alloc(40 + jpeg.length);
47
+ bytes.write('AXOV');
48
+ bytes[4] = 1;
49
+ bytes.writeBigUInt64BE(BigInt(sequence), 8);
50
+ bytes.writeUInt32BE(sw, 16);
51
+ bytes.writeUInt32BE(sh, 20);
52
+ bytes.writeUInt16BE(w, 24);
53
+ bytes.writeUInt16BE(h, 26);
54
+ bytes.writeUInt32BE(jpeg.length, 28);
55
+ bytes.writeBigUInt64BE(BigInt(generation), 32);
56
+ jpeg.copy(bytes, 40);
57
+ return bytes;
58
+ }
@@ -0,0 +1,302 @@
1
+ import { z } from 'zod';
2
+ import { audioConfiguration, audioMode } from './audio.js';
3
+ import { lobbyRequestSchema, lobbyPlanSchema } from './lobby.js';
4
+ export const DESKTOP_MAX_JSON_BYTES = 48 * 1024;
5
+ export class DesktopProtocolError extends Error {
6
+ code = 'invalid_request';
7
+ }
8
+ const invalid = () => { throw new DesktopProtocolError('Invalid desktop protocol value'); };
9
+ const int = (min = 0, max = Number.MAX_SAFE_INTEGER) => z.number().int().min(min).max(max);
10
+ const positive = int(1);
11
+ const u32 = int(1, 0xffffffff);
12
+ const id = z.string().regex(/^[0-9a-f]{32}$/);
13
+ const bounded = z.string().min(1).refine(s => Buffer.byteLength(s) <= 128 && !/[\x00-\x1f\x7f]/.test(s));
14
+ const code = z.string().regex(/^[a-z0-9_]{1,128}$/);
15
+ const sourceAxis = int(2, 4096);
16
+ const axis = sourceAxis.refine(n => n % 2 === 0);
17
+ const fps = z.union([z.literal(15), z.literal(30), z.literal(60)]);
18
+ const dpi = int(64, 384);
19
+ const format = z.literal('h264-420-v1');
20
+ const quality = z.enum(['auto', 'text', 'motion']);
21
+ const distinct = (items) => new Set(items.map(i => canonicalDesktopJson(i))).size === items.length;
22
+ const formats = z.array(format).min(1).max(1).refine(distinct);
23
+ const protocols = z.strictObject({ min: int(1, 2147483647), max: int(1, 2147483647) }).refine(p => p.min <= p.max);
24
+ const mode = z.strictObject({ width: axis, height: axis });
25
+ const resizeRange = z.strictObject({ minWidth: axis, minHeight: axis, maxWidth: axis, maxHeight: axis })
26
+ .refine(r => r.minWidth <= r.maxWidth && r.minHeight <= r.maxHeight);
27
+ const display = z.strictObject({ id, label: bounded, width: sourceAxis, height: sourceAxis, revision: u32,
28
+ resize: z.enum(['none', 'modes', 'dynamic']), modes: z.array(mode).max(32).refine(distinct),
29
+ kind: z.enum(['display', 'window']).optional(),
30
+ resizeRange: resizeRange.nullable().optional(), captureViewport: z.boolean().optional(), overview: z.boolean().optional(), capturePixelScale: z.number().min(1).max(16).optional(),
31
+ dpi: dpi.optional(), maxDpi: dpi.optional(), appearanceSync: z.boolean().optional(),
32
+ }).refine(d => (d.capturePixelScale == null || d.captureViewport === true) && (d.resize !== 'modes' || d.modes.length > 0) &&
33
+ (d.dpi == null) === (d.maxDpi == null) && (d.dpi == null || d.dpi <= d.maxDpi) &&
34
+ (d.resize === 'dynamic') === (d.resizeRange != null) &&
35
+ (d.resizeRange == null || d.modes.every(m => m.width >= d.resizeRange.minWidth && m.width <= d.resizeRange.maxWidth &&
36
+ m.height >= d.resizeRange.minHeight && m.height <= d.resizeRange.maxHeight)));
37
+ const encoderFormat = z.strictObject({ format, maxWidth: axis, maxHeight: axis, maxPixels: int(4, 16777216), maxFps: fps, cappedRate: z.boolean() });
38
+ const backend = z.strictObject({ id: bounded, kind: z.enum(['software', 'hardware']),
39
+ formats: z.array(encoderFormat).min(1).max(2).refine(a => distinct(a.map(f => f.format))),
40
+ });
41
+ export const desktopCapabilitiesSchema = z.strictObject({
42
+ cursorEmbedded: z.boolean().optional(), cursorVisibility: z.boolean().optional(), lobby: z.boolean().optional(),
43
+ protocols, providerId: bounded, providerVersion: bounded, configured: z.boolean(),
44
+ permission: z.enum(['granted', 'required', 'unknown', 'not_applicable']), health: z.enum(['ready', 'unavailable']), reason: code.nullable(),
45
+ formats, encoderBackends: z.array(backend),
46
+ input: z.strictObject({ pointer: z.boolean(), physicalKeyboard: z.boolean(), unicodeText: z.boolean(), clipboard: z.boolean().optional(), textPaste: z.boolean().optional() }),
47
+ displays: z.array(display).min(1).max(128).refine(a => distinct(a.map(d => d.id))),
48
+ }).refine(c => distinct(c.encoderBackends.map(b => b.id)) && c.encoderBackends.every(b => b.formats.every(f => c.formats.includes(f.format))));
49
+ export const capabilityReportSchema = z.strictObject({ generation: bounded, revision: positive, desktop: desktopCapabilitiesSchema.nullable() });
50
+ export const captureRegion = z.strictObject({ x: int(), y: int(), width: positive, height: positive,
51
+ sourceWidth: sourceAxis, sourceHeight: sourceAxis }).refine(r => r.x + r.width <= r.sourceWidth && r.y + r.height <= r.sourceHeight);
52
+ const focus = z.strictObject({ revision: positive, region: captureRegion });
53
+ const focusRequest = z.strictObject({ commandId: id, controlGeneration: positive, mediaEpoch: u32, displayRevision: u32, focus });
54
+ const selection = z.strictObject({ commandId: id, format, width: axis, height: axis, fpsCeiling: fps, quality, dpi: dpi.optional(), captureRegion: captureRegion.optional(), cursorVisible: z.boolean().optional() });
55
+ const configured = z.strictObject({ commandId: id, deviceGeneration: bounded, controlGeneration: positive, mediaEpoch: u32,
56
+ displayRevision: u32, format, width: axis, height: axis, fpsCeiling: fps, quality, encoderBackend: bounded, rateTargetBps: positive, dpi: dpi.optional(), captureRegion: captureRegion.optional() });
57
+ const ready = z.strictObject({ controlGeneration: positive, mediaEpoch: u32, displayRevision: u32 });
58
+ const command = z.strictObject({ commandId: id });
59
+ const inputBase = { controlGeneration: positive, displayRevision: u32, sequence: positive };
60
+ const point = { x: int(0, 4095), y: int(0, 4095) };
61
+ const text = z.string().refine(s => {
62
+ if (Buffer.byteLength(s) > 32768)
63
+ return false;
64
+ let scalars = 0;
65
+ for (const c of s) {
66
+ const n = c.codePointAt(0);
67
+ if (++scalars > 8192 || n >= 0xd800 && n <= 0xdfff || n < 32 && n !== 9 && n !== 10 || n >= 127 && n <= 159)
68
+ return false;
69
+ }
70
+ return true;
71
+ });
72
+ const input = z.discriminatedUnion('action', [
73
+ z.strictObject({ ...inputBase, action: z.literal('pointer'), ...point }),
74
+ z.strictObject({ ...inputBase, action: z.literal('button'), ...point, button: int(1, 3), down: z.boolean() }),
75
+ z.strictObject({ ...inputBase, action: z.literal('wheel'), ...point, wheelX: int(-120, 120), wheelY: int(-120, 120) }),
76
+ z.strictObject({ ...inputBase, action: z.literal('key'), usage: int(4, 231), down: z.boolean() }),
77
+ z.strictObject({ ...inputBase, action: z.literal('release_all') }),
78
+ z.strictObject({ ...inputBase, action: z.literal('text'), operationId: id, paste: z.boolean().optional(), text }),
79
+ ]);
80
+ const held = z.strictObject({ controlGeneration: positive, displayRevision: u32, sequence: int(),
81
+ keys: z.array(int(4, 231)).max(228).refine(distinct), buttons: z.array(int(1, 3)).max(3).refine(distinct) });
82
+ const inputResult = z.strictObject({ sequence: positive, outcome: z.enum(['injected', 'rejected', 'unknown']), operationId: id.nullable().optional() })
83
+ .refine(r => r.outcome !== 'unknown' || r.operationId != null);
84
+ const finite = z.number().finite().min(0);
85
+ const feedback = z.strictObject({ epoch: u32, receivedId: int(), decodedId: int(), presentedId: int(), receivedTotalBytes: int(),
86
+ queueDepth: int(0, 2), queueAgeMs: finite, decodeMs: finite, presentMs: finite, exactPresentedSequence: int() }).refine(f => f.receivedId >= f.decodedId && f.decodedId >= f.presentedId);
87
+ const resizeResult = z.strictObject({ commandId: id, width: axis, height: axis, displayRevision: u32, outcome: z.enum(['applied', 'rolled_back', 'failed']), dpi: dpi.optional(), captureRegion: captureRegion.optional() });
88
+ export const desktopErrorCodeSchema = z.enum(['unavailable', 'unknown_capability', 'unsupported_protocol', 'unsupported_client',
89
+ 'permission_required', 'busy', 'conflict', 'invalid_request', 'rate_limited', 'startup_timeout', 'encoder_failed', 'decoder_failed',
90
+ 'resize_failed', 'transport_failed', 'authorization_lost', 'takeover_lost']);
91
+ const state = z.enum(['REQUESTED', 'PREPARING', 'NEGOTIATING', 'STARTING', 'ACTIVE', 'RECONFIGURING', 'SUSPENDED', 'RECONNECTING', 'FAILED', 'CLOSED']);
92
+ const clipboardChunk = { controlGeneration: positive, requestId: id, revision: int().default(0), offset: int(0, 1048576).default(0),
93
+ total: int(0, 1048576).default(0), data: z.string().max(10924).default('') };
94
+ const validClipboardChunk = (v) => {
95
+ const bytes = Buffer.from(v.data, 'base64');
96
+ return bytes.length <= 8192 && v.offset + bytes.length <= v.total && bytes.toString('base64') === v.data;
97
+ };
98
+ const clipboardRequest = z.strictObject({ ...clipboardChunk, action: z.enum(['read', 'write', 'paste']) })
99
+ .refine(v => validClipboardChunk(v) && (v.action === 'write' || v.data === '' && (v.action !== 'paste' || v.offset === 0 && v.total === 0)));
100
+ const clipboardResult = z.strictObject({ ...clipboardChunk, outcome: z.enum(['ok', 'pending', 'conflict', 'unavailable']) })
101
+ .refine(validClipboardChunk);
102
+ const cacheDependency = z.strictObject({ commandId: id, controlGeneration: positive, mediaEpoch: u32, displayRevision: u32,
103
+ frameId: positive, exactSequence: positive, cacheId: positive, width: int(1, 32), height: int(1, 32) });
104
+ const cacheDependencyResult = z.strictObject({ request: cacheDependency, pixels: z.string().max(5464) }).refine(v => {
105
+ const pixels = Buffer.from(v.pixels, 'base64');
106
+ return pixels.toString('base64') === v.pixels && pixels.length === v.request.width * v.request.height * 4 &&
107
+ pixels.every((byte, i) => i % 4 !== 3 || byte === 255);
108
+ });
109
+ const motionCut = { commandId: id, controlGeneration: positive, mediaEpoch: u32, displayRevision: u32 };
110
+ const motionCutPrepared = { ...motionCut, cutId: positive, pictureThrough: int(0, 0xffffffff), frameThrough: positive };
111
+ const payloadSchemas = {
112
+ audio_select: z.strictObject({ commandId: id, mode: audioMode }),
113
+ audio_ready: z.strictObject({ commandId: id, audioEpoch: u32 }),
114
+ audio_configured: audioConfiguration,
115
+ audio_state: z.strictObject({ commandId: id, state: z.enum(['off', 'buffering', 'playing', 'unavailable', 'suspended']), reason: z.string().regex(/^[a-z0-9_]{1,128}$/).nullable().default(null) }),
116
+ audio_feedback: z.strictObject({ audioEpoch: u32, receivedPackets: int(), latePackets: int(), lostPackets: int(), playedSample: int(), queueAgeMs: z.number().finite().min(0).max(10000), underruns: int() }),
117
+ focus: focusRequest,
118
+ cache_dependency: cacheDependency, cache_dependency_result: cacheDependencyResult,
119
+ focus_result: focusRequest.extend({ outcome: z.enum(['applied', 'failed']) }),
120
+ motion_cut_barrier: z.strictObject(motionCutPrepared),
121
+ motion_cut_request: z.strictObject(motionCut),
122
+ motion_cut_prepare: z.strictObject({ ...motionCut, cutId: positive }),
123
+ motion_cut_prepared: z.strictObject(motionCutPrepared),
124
+ motion_cut_fence: z.strictObject({ ...motionCutPrepared, motionCommittedBytes: int() }),
125
+ motion_cut_ack: z.strictObject({ ...motionCut, cutId: positive }),
126
+ motion_cut_commit: z.strictObject({ ...motionCut, cutId: positive }),
127
+ clipboard_request: clipboardRequest, clipboard_result: clipboardResult,
128
+ offer: desktopCapabilitiesSchema, select: selection, configured, viewer_ready: ready, decoder_ready: ready,
129
+ cursor_visibility: z.strictObject({ commandId: id, visible: z.boolean() }),
130
+ cursor_visibility_result: z.strictObject({ commandId: id, visible: z.boolean(), outcome: z.enum(['applied', 'failed']), mediaEpoch: int().default(0), afterFrameId: int().default(0) }),
131
+ appearance: z.strictObject({ commandId: id, colorScheme: z.enum(['light', 'dark']) }),
132
+ appearance_result: z.strictObject({ commandId: id, colorScheme: z.enum(['light', 'dark']), outcome: z.enum(['applied', 'failed']) }),
133
+ start: command, request_refresh: command, suspend: command, resume: command, stop: command, media_open: command,
134
+ input, held_state: held, input_result: inputResult, feedback,
135
+ credit: z.strictObject({ epoch: u32, motionConsumedBytes: int(), cursorConsumedBytes: int(), exactConsumedBytes: int(), windowBytes: int(196608, 524288) }).refine(v => Number.isSafeInteger(v.motionConsumedBytes + v.cursorConsumedBytes + v.exactConsumedBytes)),
136
+ ping: z.strictObject({ challenge: bounded }), pong: z.strictObject({ challenge: bounded }),
137
+ resize: z.strictObject({ commandId: id, expectedDisplayRevision: u32, width: axis, height: axis, dpi: dpi.optional(), captureRegion: captureRegion.optional() }), resize_result: resizeResult,
138
+ state: z.strictObject({ state, code: desktopErrorCodeSchema.nullable().optional() }),
139
+ error: z.strictObject({ code: desktopErrorCodeSchema, retryable: z.boolean() }),
140
+ };
141
+ export function supportsDesktopMode(display, width, height) {
142
+ if (!Number.isInteger(width) || !Number.isInteger(height) || width < 2 || height < 2 || width > 4096 || height > 4096 || width % 2 || height % 2)
143
+ return false;
144
+ if (width === display.width && height === display.height)
145
+ return true;
146
+ const range = display.resizeRange;
147
+ if (display.resize === 'dynamic')
148
+ return range != null && width >= range.minWidth && width <= range.maxWidth && height >= range.minHeight && height <= range.maxHeight;
149
+ return display.resize === 'modes' && display.modes.some(m => m.width === width && m.height === height);
150
+ }
151
+ const viewer = z.strictObject({ protocols, formats, maxWidth: axis, maxHeight: axis, maxPixels: int(4, 16777216), maxFps: fps, maxDpi: dpi.optional(), appearanceSync: z.boolean().optional(), clipboard: z.boolean().optional(), overview: z.boolean().optional(), cursorControl: z.boolean().optional() });
152
+ const sessionRequest = z.strictObject({ requestId: id, displayId: id, viewer,
153
+ viewport: z.strictObject({ widthDp: z.number().positive().max(16384), heightDp: z.number().positive().max(16384),
154
+ deviceScale: z.number().min(0.5).max(8), orientation: z.enum(['landscape', 'portrait']) }),
155
+ resolution: z.discriminatedUnion('mode', [z.strictObject({ mode: z.literal('fit') }), z.strictObject({ mode: z.literal('fixed'), width: axis, height: axis })]),
156
+ quality, takeover: z.boolean().default(false), lobbyId: id.optional(), planId: id.optional(), mediaTransport: z.enum(['quic', 'wss']).optional() })
157
+ .refine(request => (request.lobbyId == null) === (request.planId == null) && (request.lobbyId == null) === (request.mediaTransport == null));
158
+ const desktopManagementSchema = z.discriminatedUnion('t', [
159
+ z.strictObject({ t: z.literal('desktop_lobby'), commandId: id, lobbyId: id, deviceGeneration: bounded, request: z.lazy(() => lobbyRequestSchema),
160
+ wssProbeGrant: z.string().regex(/^[A-Za-z0-9_-]{43}$/).nullish(), quicProbeGrant: z.string().regex(/^[A-Za-z0-9_-]{43}$/).nullish(),
161
+ quic: z.strictObject({ port: int(1, 65535), certificate: z.string().min(64).max(2048) }).nullish() }),
162
+ z.strictObject({ t: z.literal('desktop_background'), commandId: id, lobbyId: id, deviceGeneration: bounded,
163
+ planId: id, revision: positive, targetId: id, grant: z.string().regex(/^[A-Za-z0-9_-]{43}$/),
164
+ cachedVersion: z.string().regex(/^[A-Za-z0-9_-]{1,128}$/).nullish() }),
165
+ z.strictObject({ t: z.literal('desktop_lobby_result'), commandId: id, lobbyId: id, deviceGeneration: bounded,
166
+ plan: z.lazy(() => lobbyPlanSchema).nullable(), code: desktopErrorCodeSchema.nullable() }),
167
+ z.strictObject({ t: z.literal('workstation_capabilities'), ...capabilityReportSchema.shape }),
168
+ z.strictObject({ t: z.literal('workstation_capabilities_request'), generation: bounded }),
169
+ z.strictObject({ t: z.literal('desktop_prepare'), commandId: id, sessionId: id, displayId: id,
170
+ deviceGeneration: bounded, controlGeneration: positive, controlGrant: z.string().regex(/^[A-Za-z0-9_-]{43}$/),
171
+ candidate: z.boolean().optional(), mediaTransport: z.enum(['quic', 'wss']).optional() }),
172
+ z.strictObject({ t: z.literal('desktop_prepare_result'), commandId: id, sessionId: id,
173
+ state: z.enum(['bound', 'failed']), code: desktopErrorCodeSchema.nullable() }),
174
+ z.strictObject({ t: z.literal('desktop_cancel'), commandId: id, sessionId: id, controlGeneration: positive, code: desktopErrorCodeSchema }),
175
+ z.strictObject({ t: z.literal('desktop_session_state'), sessionId: id, controlGeneration: positive, state, code: desktopErrorCodeSchema.nullable() }),
176
+ ]);
177
+ export function decodeDesktopManagementFrame(source, from) {
178
+ const frame = checked(desktopManagementSchema, source);
179
+ const sender = ['desktop_lobby_result', 'workstation_capabilities', 'desktop_prepare_result', 'desktop_session_state'].includes(frame.t) ? 'device' : 'server';
180
+ if (from && from !== sender)
181
+ return invalid();
182
+ return frame;
183
+ }
184
+ const authors = {
185
+ motion_cut_barrier: ['device', 'server'], motion_cut_request: ['viewer', 'device'], motion_cut_prepare: ['server'], motion_cut_prepared: ['device'], motion_cut_fence: ['server'], motion_cut_ack: ['viewer'], motion_cut_commit: ['server'],
186
+ cursor_visibility: ['viewer'], cursor_visibility_result: ['device'], appearance: ['viewer'], appearance_result: ['device'],
187
+ audio_select: ['viewer'], audio_ready: ['viewer'], audio_feedback: ['viewer'], audio_configured: ['device'], audio_state: ['device'],
188
+ clipboard_result: ['device'], clipboard_request: ['viewer'],
189
+ cache_dependency: ['viewer'], cache_dependency_result: ['device'], focus_result: ['device'], offer: ['device'], configured: ['device'], input_result: ['device'], resize_result: ['device'], media_open: ['device'],
190
+ select: ['viewer'], viewer_ready: ['viewer'], decoder_ready: ['viewer'], input: ['viewer'], held_state: ['viewer'],
191
+ focus: ['viewer'], feedback: ['viewer'], resize: ['viewer'], request_refresh: ['viewer'], suspend: ['viewer'], resume: ['viewer'], stop: ['viewer'],
192
+ start: ['server'], state: ['device', 'server'], error: ['device', 'server'],
193
+ };
194
+ /** JSON.parse runs only after a lexical pass checks duplicate decoded keys and exact integer tokens. */
195
+ export function parseDesktopJson(source, limit = DESKTOP_MAX_JSON_BYTES, integerFields) {
196
+ if (typeof source !== 'string' && source.byteLength > limit)
197
+ invalid();
198
+ const value = typeof source === 'string' ? source : new TextDecoder('utf-8', { fatal: true }).decode(source);
199
+ if (Buffer.byteLength(value) > limit)
200
+ invalid();
201
+ let offset = 0;
202
+ const space = () => { while (/[ \t\r\n]/.test(value[offset] ?? '\0'))
203
+ offset++; };
204
+ const take = (c) => { space(); if (value[offset] !== c)
205
+ return false; offset++; return true; };
206
+ const string = () => {
207
+ space();
208
+ const start = offset;
209
+ if (!take('"'))
210
+ return invalid();
211
+ while (offset < value.length) {
212
+ const c = value[offset++];
213
+ if (c === '\\')
214
+ offset++;
215
+ else if (c === '"')
216
+ return JSON.parse(value.slice(start, offset));
217
+ }
218
+ return invalid();
219
+ };
220
+ const floatFields = new Set(['widthDp', 'heightDp', 'deviceScale', 'capturePixelScale', 'queueAgeMs', 'decodeMs', 'presentMs']);
221
+ const visit = (depth, field = '') => {
222
+ if (depth > 32)
223
+ invalid();
224
+ space();
225
+ if (take('{')) {
226
+ const keys = new Set();
227
+ if (take('}'))
228
+ return;
229
+ do {
230
+ const key = string();
231
+ if (keys.has(key) || !take(':'))
232
+ invalid();
233
+ keys.add(key);
234
+ visit(depth + 1, key);
235
+ } while (take(','));
236
+ if (!take('}'))
237
+ invalid();
238
+ }
239
+ else if (take('[')) {
240
+ if (take(']'))
241
+ return;
242
+ do {
243
+ visit(depth + 1, field);
244
+ } while (take(','));
245
+ if (!take(']'))
246
+ invalid();
247
+ }
248
+ else if (value[offset] === '"') {
249
+ string();
250
+ }
251
+ else {
252
+ const start = offset;
253
+ while (offset < value.length && !/[,\]} \t\r\n]/.test(value[offset]))
254
+ offset++;
255
+ const token = value.slice(start, offset);
256
+ if (/^-?[0-9]/.test(token) && (integerFields ? integerFields.has(field) : !floatFields.has(field))) {
257
+ if (!/^-?(0|[1-9][0-9]*)$/.test(token) || !Number.isSafeInteger(Number(token)))
258
+ invalid();
259
+ }
260
+ if (!token)
261
+ invalid();
262
+ }
263
+ };
264
+ visit(0);
265
+ space();
266
+ if (offset !== value.length)
267
+ invalid();
268
+ return JSON.parse(value);
269
+ }
270
+ function checked(schema, source) {
271
+ try {
272
+ return schema.parse(parseDesktopJson(source));
273
+ }
274
+ catch {
275
+ return invalid();
276
+ }
277
+ }
278
+ export function decodeCapabilityReport(source) { return checked(capabilityReportSchema, source); }
279
+ export function decodeDesktopSessionRequest(source) { return checked(sessionRequest, source); }
280
+ export function decodeDesktopControl(source, from) {
281
+ try {
282
+ const envelope = z.strictObject({ v: z.literal(1), type: z.string(), sessionId: id, payload: z.unknown() }).parse(parseDesktopJson(source));
283
+ if (!Object.hasOwn(payloadSchemas, envelope.type))
284
+ return invalid();
285
+ const type = envelope.type;
286
+ if (from && authors[type] && !authors[type].includes(from))
287
+ return invalid();
288
+ return { ...envelope, type, payload: payloadSchemas[type].parse(envelope.payload) };
289
+ }
290
+ catch {
291
+ return invalid();
292
+ }
293
+ }
294
+ export function encodeDesktopControl(message) {
295
+ const text = JSON.stringify(message);
296
+ decodeDesktopControl(text);
297
+ return text;
298
+ }
299
+ export function canonicalDesktopJson(value) {
300
+ return JSON.stringify(value, (_key, item) => item && typeof item === 'object' && !Array.isArray(item)
301
+ ? Object.fromEntries(Object.entries(item).sort(([a], [b]) => a.localeCompare(b))) : item);
302
+ }
@@ -0,0 +1,240 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { access } from 'node:fs/promises';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { once } from 'node:events';
5
+ import { homedir } from 'node:os';
6
+ import path from 'node:path';
7
+ import { bounded, DesktopQueue, NativeCommandError } from './helperProcess.js';
8
+ import { mediaRequire } from './media.js';
9
+ import { decodeQuicPathStats } from './networkEvidence.js';
10
+ const safeU64 = (b, offset) => { const n = b.readBigUInt64BE(offset); mediaRequire(n <= BigInt(Number.MAX_SAFE_INTEGER)); return Number(n); };
11
+ export function decodeQuicCutEvent(bytes) {
12
+ mediaRequire(bytes.length === 48 && bytes[0] === 1 && bytes[1] >= 1 && bytes[1] <= 6 && bytes[2] <= 7 && bytes[3] === 0 && bytes.readUInt32BE(20) === 0);
13
+ const value = { action: bytes[1], reason: bytes[2], epoch: bytes.readUInt32BE(4), cutId: safeU64(bytes, 8), pictureThrough: bytes.readUInt32BE(16), frameThrough: safeU64(bytes, 24), wireThrough: safeU64(bytes, 32), motionCommittedBytes: safeU64(bytes, 40) };
14
+ mediaRequire(value.epoch > 0 && (value.action === 1 || value.cutId > 0));
15
+ return value;
16
+ }
17
+ export class DesktopMediaAdmissionCancelled extends NativeCommandError {
18
+ cutId;
19
+ epoch;
20
+ pictureSequence;
21
+ constructor(cutId, epoch, pictureSequence) {
22
+ super('transport_failed');
23
+ this.cutId = cutId;
24
+ this.epoch = epoch;
25
+ this.pictureSequence = pictureSequence;
26
+ }
27
+ }
28
+ export const isQuicMediaQueueEvent = (kind) => kind === 1 || kind === 2 || kind === 3 || kind === 4;
29
+ /** At most one unresolved ticket per lane, sharing the caller's existing bounded reservation. */
30
+ export class DesktopQuicAdmission {
31
+ peer;
32
+ write;
33
+ pending = new Map();
34
+ nextId = 0n;
35
+ totals = new Map([[0, [0, 0, 0]]]);
36
+ epoch = 0;
37
+ dead = false;
38
+ cut;
39
+ constructor(peer, write) {
40
+ this.peer = peer;
41
+ this.write = write;
42
+ }
43
+ ownCut(id, epoch, pictureThrough) { mediaRequire(Number.isSafeInteger(id) && id > 0 && epoch > 0 && !this.cut); this.cut = { id, epoch, pictureThrough }; }
44
+ finishCut(id) { mediaRequire(this.cut?.id === id); this.cut = undefined; }
45
+ async send(kind, bytes) {
46
+ mediaRequire(!this.dead && (kind === 2 || kind === 3) && bytes.length > 0 && bytes.length <= 65600);
47
+ let lane = 255, epoch = 0, sequence = 0, payloadBytes = 0;
48
+ if (kind === 3) {
49
+ mediaRequire(bytes.length >= 65 && bytes.subarray(0, 5).equals(Buffer.from('AXDS\x01')));
50
+ lane = bytes[5] <= 2 ? 0 : bytes[5] <= 4 ? 1 : 2;
51
+ epoch = bytes.readUInt32BE(12);
52
+ sequence = bytes.readUInt32BE(20);
53
+ payloadBytes = bytes.length - 64;
54
+ mediaRequire(bytes[5] >= 1 && bytes[5] <= 5 && epoch > 0 && sequence > 0);
55
+ if (bytes[5] === 1) {
56
+ mediaRequire(epoch > this.epoch);
57
+ const oldest = [...this.totals.keys()].filter(e => e !== 0 && e !== this.epoch);
58
+ for (const old of oldest) {
59
+ mediaRequire(![...this.pending.values()].some(p => p.epoch === old));
60
+ this.totals.delete(old);
61
+ }
62
+ this.epoch = epoch;
63
+ if (!this.totals.has(epoch))
64
+ this.totals.set(epoch, [0, 0, 0]);
65
+ }
66
+ else {
67
+ mediaRequire(epoch === this.epoch || this.epoch === 0);
68
+ if (!this.totals.has(epoch))
69
+ this.totals.set(epoch, [0, 0, 0]);
70
+ }
71
+ }
72
+ mediaRequire(![...this.pending.values()].some(p => p.lane === lane));
73
+ const id = ++this.nextId;
74
+ mediaRequire(id <= BigInt(Number.MAX_SAFE_INTEGER));
75
+ const header = Buffer.alloc(21);
76
+ header.writeUInt32BE(bytes.length + 17);
77
+ header[4] = kind;
78
+ header.writeBigUInt64BE(this.peer, 5);
79
+ header.writeBigUInt64BE(id, 13);
80
+ return new Promise((resolve, reject) => {
81
+ const item = { id, kind, lane, epoch, sequence, payloadBytes, frame: Buffer.concat([header, bytes]), resolve, reject, busy: false, deadline: setTimeout(() => { this.close(new NativeCommandError('transport_failed')); }, 5000) };
82
+ this.pending.set(id, item);
83
+ void this.write(item.frame).catch(e => this.close(e));
84
+ });
85
+ }
86
+ accept(peer, bytes) {
87
+ mediaRequire(peer === this.peer && bytes.length === 32 && bytes.readUInt16BE(10) === 0 && bytes.readUInt32BE(20) === 0);
88
+ const id = bytes.readBigUInt64BE(), p = this.pending.get(id);
89
+ mediaRequire(p && bytes[8] >= 1 && bytes[8] <= 3 && bytes[9] === p.lane && bytes.readUInt32BE(12) === p.epoch && bytes.readUInt32BE(16) === p.sequence);
90
+ const total = safeU64(bytes, 24), previous = p.lane === 255 ? 0 : this.totals.get(p.epoch)[p.lane];
91
+ mediaRequire(total === previous + (bytes[8] === 1 ? p.payloadBytes : 0));
92
+ if (bytes[8] === 3) {
93
+ mediaRequire(!p.busy);
94
+ p.busy = true;
95
+ p.timer = setTimeout(() => { p.busy = false; if (this.pending.get(id) === p)
96
+ void this.write(p.frame).catch(e => this.close(e)); }, 10);
97
+ return;
98
+ }
99
+ if (bytes[8] === 2)
100
+ mediaRequire(p.lane === 0 && this.cut && p.epoch === this.cut.epoch && p.sequence <= this.cut.pictureThrough);
101
+ this.pending.delete(id);
102
+ clearTimeout(p.deadline);
103
+ clearTimeout(p.timer);
104
+ if (bytes[8] === 1) {
105
+ if (p.lane !== 255)
106
+ this.totals.get(p.epoch)[p.lane] = total;
107
+ p.resolve();
108
+ }
109
+ else
110
+ p.reject(new DesktopMediaAdmissionCancelled(this.cut.id, p.epoch, p.sequence));
111
+ }
112
+ close(error = new NativeCommandError('transport_failed')) { this.dead = true; for (const p of this.pending.values()) {
113
+ clearTimeout(p.deadline);
114
+ clearTimeout(p.timer);
115
+ p.reject(error);
116
+ } this.pending.clear(); }
117
+ }
118
+ /** The subprocess is a platform build of the same Rust engine embedded by native viewers. */
119
+ export async function openQuicMedia(server, offer, signal, current) {
120
+ const packaged = fileURLToPath(new URL(`../../native/${process.platform}-${process.arch}/axom-quic`, import.meta.url));
121
+ const candidates = [...(process.env.AXOM_QUIC_BINARY ? [process.env.AXOM_QUIC_BINARY] : []), packaged, path.join(homedir(), '.axom/workstation/quic', `${process.platform}-${process.arch}`, 'axom-quic'), '/usr/local/bin/axom-quic'];
122
+ const executable = await (async () => { for (const file of candidates) {
123
+ try {
124
+ await access(file);
125
+ return file;
126
+ }
127
+ catch { }
128
+ } return undefined; })();
129
+ if (!executable)
130
+ return undefined;
131
+ const child = spawn(executable, [], { stdio: ['pipe', 'pipe', 'ignore'] });
132
+ const queue = new DesktopQueue();
133
+ const events = queue[Symbol.asyncIterator]();
134
+ let buffer = Buffer.alloc(0), peer = 0n, dead = false, transportBytes = 0;
135
+ let pathStats;
136
+ let admission;
137
+ const cuts = new DesktopQueue(16);
138
+ const close = () => { if (dead)
139
+ return; dead = true; child.stdin.destroy(); child.kill(); admission?.close(); queue.close(); cuts.close(); signal.removeEventListener('abort', close); };
140
+ const closed = new Promise(resolve => child.once('close', () => { close(); resolve(); }));
141
+ signal.addEventListener('abort', close, { once: true });
142
+ child.once('error', close);
143
+ child.stdin.on('error', close);
144
+ if (signal.aborted)
145
+ close();
146
+ child.stdout.on('data', (bytes) => {
147
+ try {
148
+ mediaRequire(buffer.length + bytes.length <= 2 * 65613);
149
+ buffer = Buffer.concat([buffer, bytes]);
150
+ while (buffer.length >= 4) {
151
+ const length = buffer.readUInt32BE();
152
+ mediaRequire(length >= 9 && length <= 65609);
153
+ if (buffer.length < length + 4)
154
+ break;
155
+ const frame = buffer.subarray(4, 4 + length);
156
+ buffer = buffer.subarray(4 + length);
157
+ const kind = frame[0], eventPeer = frame.readBigUInt64BE(1), payload = Buffer.from(frame.subarray(9));
158
+ if (kind === 1) {
159
+ mediaRequire(peer === 0n && eventPeer > 0n);
160
+ peer = eventPeer;
161
+ }
162
+ if (kind === 9) {
163
+ mediaRequire(eventPeer === peer && payload.length === 8);
164
+ const total = safeU64(payload, 0);
165
+ mediaRequire(total >= transportBytes);
166
+ transportBytes = total;
167
+ }
168
+ else if (kind === 10) {
169
+ mediaRequire(eventPeer === peer);
170
+ const next = decodeQuicPathStats(payload, performance.now());
171
+ mediaRequire(!pathStats || next.sequence > pathStats.sequence && next.pathGeneration >= pathStats.pathGeneration);
172
+ pathStats = next;
173
+ }
174
+ else if (kind === 8) {
175
+ mediaRequire(admission);
176
+ admission.accept(eventPeer, payload);
177
+ }
178
+ else if (kind === 7) {
179
+ mediaRequire(eventPeer === peer);
180
+ cuts.push(decodeQuicCutEvent(payload));
181
+ }
182
+ else if (isQuicMediaQueueEvent(kind))
183
+ queue.push({ kind, peer: eventPeer, bytes: payload });
184
+ // Native event extensions belong to their own lanes. Never let an
185
+ // unrecognized event become an AXDS media packet and retire the RD session.
186
+ }
187
+ }
188
+ catch {
189
+ close();
190
+ }
191
+ });
192
+ const write = async (bytes) => { mediaRequire(current() && !signal.aborted && !dead); if (!child.stdin.write(bytes))
193
+ await bounded(once(child.stdin, 'drain'), 2000); };
194
+ try {
195
+ const config = Buffer.from(JSON.stringify({ mode: 'client', host: new URL(server).hostname.replace(/^\[|\]$/g, ''), port: offer.port, certificate: offer.certificate }));
196
+ const header = Buffer.alloc(4);
197
+ header.writeUInt32BE(config.length);
198
+ await write(Buffer.concat([header, config]));
199
+ const ready = await bounded(events.next(), 1800);
200
+ mediaRequire(!ready.done && ready.value.kind === 1);
201
+ peer = ready.value.peer;
202
+ admission = new DesktopQuicAdmission(peer, write);
203
+ }
204
+ catch (error) {
205
+ close();
206
+ if (signal.aborted)
207
+ throw error;
208
+ return undefined;
209
+ }
210
+ const send = (kind, bytes) => admission.send(kind, bytes);
211
+ const sendAudio = async (bytes) => {
212
+ mediaRequire(bytes.length >= 41 && bytes.length <= 1040 && bytes.subarray(0, 4).equals(Buffer.from('AXDA')));
213
+ if (!current() || signal.aborted || dead || child.stdin.writableLength > 4096)
214
+ return false;
215
+ const frame = Buffer.alloc(21);
216
+ frame.writeUInt32BE(17 + bytes.length);
217
+ frame[4] = 12;
218
+ frame.writeBigUInt64BE(peer, 5);
219
+ child.stdin.write(Buffer.concat([frame, bytes]));
220
+ return true;
221
+ };
222
+ const cutCommand = async (kind, cutId, epoch, pictureThrough = 0, frameThrough = 0) => {
223
+ mediaRequire(Number.isSafeInteger(cutId) && cutId > 0 && Number.isInteger(epoch) && epoch > 0);
224
+ const payload = Buffer.alloc(kind === 9 ? 16 : 4);
225
+ payload.writeUInt32BE(epoch);
226
+ if (kind === 9) {
227
+ payload.writeUInt32BE(pictureThrough, 4);
228
+ payload.writeBigUInt64BE(BigInt(frameThrough), 8);
229
+ admission.ownCut(cutId, epoch, pictureThrough);
230
+ }
231
+ const frame = Buffer.alloc(21);
232
+ frame.writeUInt32BE(17 + payload.length);
233
+ frame[4] = kind;
234
+ frame.writeBigUInt64BE(peer, 5);
235
+ frame.writeBigUInt64BE(BigInt(cutId), 13);
236
+ await write(Buffer.concat([frame, payload]));
237
+ };
238
+ return { send, sendAudio, close, closed, transportCost: () => transportBytes, pathStats: () => pathStats, cutEvents: cuts, cutCommand, finishCut: (id) => admission.finishCut(id), receive: async (timeoutMs = 0) => { const event = timeoutMs ? await bounded(events.next(), timeoutMs) : await events.next(); if (event.done || event.value.kind === 4)
239
+ throw new NativeCommandError('transport_failed'); return event.value; } };
240
+ }