@platless/axom-cli 0.6.36 → 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 (159) hide show
  1. package/README.md +7 -1
  2. package/dist/axom-patch/cli/README.md +31 -2
  3. package/dist/axom-patch/cli/account-limits/generate.py +2 -2
  4. package/dist/axom-patch/cli/claude-replay-lifecycle.json +16 -0
  5. package/dist/axom-patch/cli/codex-terminal-output.json +2 -2
  6. package/dist/axom-patch/cli/probes/claude-replay-lifecycle.mjs +31 -0
  7. package/dist/axom-patch/cli/probes/codex-terminal-output.mjs +1 -1
  8. package/dist/axom-patch/cli/probes/zcode-env-account.mjs +81 -0
  9. package/dist/axom-patch/cli/probes/zcode-subagent-subwork.mjs +7 -0
  10. package/dist/axom-patch/cli/qoder/dist/axom-acp-compat.mjs +137 -41
  11. package/dist/axom-patch/cli/qoder/releases/1.1.75.json +5 -88
  12. package/dist/axom-patch/cli/qoder-native-acp.json +23 -23
  13. package/dist/axom-patch/cli/runtime.json +3 -1
  14. package/dist/axom-patch/cli/zcode/env-account.mjs +13 -0
  15. package/dist/axom-patch/cli/zcode/generate-env-account.py +52 -0
  16. package/dist/axom-patch/cli/zcode-account-limits.json +7 -7
  17. package/dist/axom-patch/cli/zcode-env-account.json +58 -0
  18. package/dist/axom-patch/cli/zcode-models.json +9 -9
  19. package/dist/axom-patch/cli/zcode-subagent-subwork.json +10 -10
  20. package/dist/cli.js +9 -11
  21. package/dist/commands/auth.js +4 -1
  22. package/dist/commands/update.js +68 -7
  23. package/dist/commands/workstation.js +94 -223
  24. package/dist/commands/workstationRegistrations.js +116 -0
  25. package/dist/credentialBroker.js +13 -2
  26. package/dist/credentials.js +16 -0
  27. package/dist/install/claude/commands.js +2 -2
  28. package/dist/keepers/terminal/keeper.mjs +1089 -673
  29. package/dist/keepers/terminal/manifest.json +4 -4
  30. package/dist/native/darwin-x64/axom-quic +0 -0
  31. package/dist/workstation/acp/acpAgents.js +11 -4
  32. package/dist/workstation/acp/outbox.js +12 -5
  33. package/dist/workstation/acp/permission.js +2 -2
  34. package/dist/workstation/acp/processor/checkpoint.js +4 -3
  35. package/dist/workstation/acp/processor/control.js +2 -2
  36. package/dist/workstation/acp/processor/jsonrpc.js +8 -0
  37. package/dist/workstation/acp/processor/session.js +3 -2
  38. package/dist/workstation/acp/processor/state.js +1 -0
  39. package/dist/workstation/acp/processor/transport/local.js +19 -3
  40. package/dist/workstation/acp/processor/updates.js +25 -0
  41. package/dist/workstation/acp/processorSession.js +12 -1
  42. package/dist/workstation/acp/registry/catalog.js +6 -6
  43. package/dist/workstation/acp/test/keeperWorld.js +4 -3
  44. package/dist/workstation/agentSessions.js +76 -23
  45. package/dist/workstation/channels/agent.js +9 -1
  46. package/dist/workstation/channels/computer.js +27 -4
  47. package/dist/workstation/channels/management.js +36 -5
  48. package/dist/workstation/config.js +18 -6
  49. package/dist/workstation/control/bindingStore.js +4 -0
  50. package/dist/workstation/control/commands.js +1 -0
  51. package/dist/workstation/control/daemonIdentity.js +90 -0
  52. package/dist/workstation/control/deviceCommands.js +191 -0
  53. package/dist/workstation/control/deviceProtocol.js +68 -0
  54. package/dist/workstation/control/locks.js +51 -4
  55. package/dist/workstation/control/rootFormat.js +16 -0
  56. package/dist/workstation/control/runtimeClient.js +60 -5
  57. package/dist/workstation/control/runtimeHost.js +96 -9
  58. package/dist/workstation/control/serverIdentity.js +2 -2
  59. package/dist/workstation/control/updateTransition.js +91 -0
  60. package/dist/workstation/daemon.js +262 -360
  61. package/dist/workstation/daemonctl.js +150 -12
  62. package/dist/workstation/desktopControl/automationDevice.js +219 -0
  63. package/dist/workstation/desktopControl/automationJson.js +74 -0
  64. package/dist/workstation/desktopControl/automationLocalState.js +23 -0
  65. package/dist/workstation/desktopControl/automationPreparation.js +1 -0
  66. package/dist/workstation/desktopControl/automationProtocol.js +253 -0
  67. package/dist/workstation/desktopControl/automationSchema.generated.js +1 -0
  68. package/dist/workstation/desktopControl/automationSession.js +81 -0
  69. package/dist/workstation/desktopControl/automationTransport.js +138 -0
  70. package/dist/workstation/desktopControl/capabilities.js +2 -1
  71. package/dist/workstation/desktopControl/deviceAdmission.js +229 -0
  72. package/dist/workstation/desktopControl/deviceOwnership.js +130 -0
  73. package/dist/workstation/desktopControl/deviceSockets.js +2 -2
  74. package/dist/workstation/desktopControl/helperProcess.js +250 -1
  75. package/dist/workstation/desktopControl/hostGrant.js +65 -0
  76. package/dist/workstation/desktopControl/lobby.js +4 -2
  77. package/dist/workstation/desktopControl/macProvider.js +41 -40
  78. package/dist/workstation/desktopControl/nativeDeviceAdmission.js +207 -0
  79. package/dist/workstation/desktopControl/nativeProvider.js +31 -5
  80. package/dist/workstation/desktopControl/ownershipProtocol.js +31 -0
  81. package/dist/workstation/desktopControl/protocol.js +15 -6
  82. package/dist/workstation/desktopControl/supervisor.js +110 -17
  83. package/dist/workstation/desktopControl/wallpaper.js +26 -6
  84. package/dist/workstation/exec/policy.js +5 -0
  85. package/dist/workstation/files/client.js +10 -1
  86. package/dist/workstation/files/host.js +9 -2
  87. package/dist/workstation/journal.js +5 -2
  88. package/dist/workstation/keepers/acp/client.js +38 -2
  89. package/dist/workstation/keepers/kit/buildVersion.js +35 -11
  90. package/dist/workstation/keepers/kit/client.js +2 -2
  91. package/dist/workstation/keepers/kit/launcher.js +80 -4
  92. package/dist/workstation/keepers/legacy.js +16 -2
  93. package/dist/workstation/keepers/policy.js +5 -34
  94. package/dist/workstation/keepers/terminal/client.js +115 -23
  95. package/dist/workstation/keepers/terminal/exec.js +6 -3
  96. package/dist/workstation/keepers/terminal/frames.js +73 -0
  97. package/dist/workstation/keepers/terminal/generations.js +5 -8
  98. package/dist/workstation/keepers/terminal/gridProtocol.js +16 -0
  99. package/dist/workstation/keepers/terminal/input.js +146 -0
  100. package/dist/workstation/keepers/terminal/keeper.js +22 -8
  101. package/dist/workstation/keepers/terminal/main.js +1 -1
  102. package/dist/workstation/keepers/terminal/metadata.js +138 -0
  103. package/dist/workstation/keepers/terminal/protocol.js +4 -28
  104. package/dist/workstation/keepers/terminal/pty.js +1 -330
  105. package/dist/workstation/keepers/terminal/screen.js +246 -0
  106. package/dist/workstation/keepers/terminal/sessions.js +526 -0
  107. package/dist/workstation/oauth/deviceAuthorization.js +31 -17
  108. package/dist/workstation/protocol/management.js +23 -4
  109. package/dist/workstation/rd/artifact.js +4 -3
  110. package/dist/workstation/rd/control.js +3 -5
  111. package/dist/workstation/rd/host.js +213 -0
  112. package/dist/workstation/rd/migrationReadiness.js +46 -0
  113. package/dist/workstation/rd/provider.js +44 -10
  114. package/dist/workstation/rd/state.js +14 -2
  115. package/dist/workstation/rd/update.js +5 -8
  116. package/dist/workstation/rd/upkeep.js +44 -2
  117. package/dist/workstation/registrations/enrollment.js +250 -0
  118. package/dist/workstation/registrations/enrollmentPending.js +361 -0
  119. package/dist/workstation/registrations/migration.js +356 -0
  120. package/dist/workstation/registrations/model.js +34 -0
  121. package/dist/workstation/registrations/removal.js +222 -0
  122. package/dist/workstation/registrations/store.js +737 -0
  123. package/dist/workstation/runtime/desktopBinding.js +20 -0
  124. package/dist/workstation/runtime/deviceDesktop.js +193 -0
  125. package/dist/workstation/runtime/deviceRuntime.js +568 -0
  126. package/dist/workstation/runtime/deviceServices.js +117 -0
  127. package/dist/workstation/runtime/events.js +20 -0
  128. package/dist/workstation/runtime/ownership.js +100 -0
  129. package/dist/workstation/runtime/registrationRuntime.js +575 -0
  130. package/dist/workstation/runtime/remoteDesktopAdapter.js +186 -0
  131. package/dist/workstation/runtime/removalActivity.js +184 -0
  132. package/dist/workstation/service/installer.js +93 -1
  133. package/dist/workstation/service/launchd.js +1 -0
  134. package/dist/workstation/service/systemd.js +1 -0
  135. package/dist/workstation/systemMetrics.js +3 -0
  136. package/dist/workstation/tools/client.js +8 -27
  137. package/dist/workstation/tools/info.js +6 -13
  138. package/dist/workstation/tools/terminalRouter.js +17 -172
  139. package/dist/workstation/tools/validation.js +55 -1
  140. package/dist/workstation/update/archiveEvidence.js +206 -0
  141. package/dist/workstation/update/coordinator.js +34 -13
  142. package/dist/workstation/update/deviceCompatibility.js +119 -0
  143. package/dist/workstation/update/deviceCoordinator.js +369 -0
  144. package/dist/workstation/update/deviceHealth.js +104 -0
  145. package/dist/workstation/update/deviceHealthProtocol.js +26 -0
  146. package/dist/workstation/update/deviceRecoveryLifecycle.js +46 -0
  147. package/dist/workstation/update/deviceRollback.js +78 -0
  148. package/dist/workstation/update/globalInstall.js +80 -10
  149. package/dist/workstation/update/legacyBridge.js +414 -0
  150. package/dist/workstation/update/legacyOwner.js +39 -0
  151. package/dist/workstation/update/npmInstaller.js +2 -2
  152. package/dist/workstation/update/privateDurability.js +61 -0
  153. package/dist/workstation/update/recoveryPlan.js +124 -0
  154. package/dist/workstation/update/runUpdate.js +7 -1
  155. package/dist/workstation/update/taskRegistry.js +161 -3
  156. package/dist/workstation/update/watchdog.js +27427 -14410
  157. package/dist/workstation/update/watchdogProcess.js +72 -0
  158. package/package.json +7 -3
  159. package/dist/native/darwin-arm64/axom-quic +0 -0
@@ -0,0 +1,253 @@
1
+ import { parseAutomationJson } from './automationJson.js';
2
+ import { automationSchema } from './automationSchema.generated.js';
3
+ export { automationSchema };
4
+ export function decodeAutomationFailureDetail(value) {
5
+ if (value == null)
6
+ return null;
7
+ shape(value, automationSchema.types.FailureDetail);
8
+ return structuredClone(value);
9
+ }
10
+ const maxJson = 48 * 1024;
11
+ const routeKeys = ['requestId', 'consumerId', 'deviceGeneration', 'controlGeneration', 'inputDomainId'];
12
+ const envelopeKeys = ['automationProtocol', ...routeKeys, 'method', 'payload'];
13
+ function requireValue(value) { if (!value)
14
+ throw new Error('invalid_automation_protocol'); }
15
+ function object(value) {
16
+ requireValue(value !== null && typeof value === 'object' && !Array.isArray(value));
17
+ return value;
18
+ }
19
+ function equal(left, right) {
20
+ if (left === right)
21
+ return true;
22
+ if (left === null || right === null || typeof left !== 'object' || typeof right !== 'object')
23
+ return false;
24
+ const keys = Object.keys(left);
25
+ return keys.length === Object.keys(right).length && keys.every(key => Object.hasOwn(right, key) && equal(left[key], right[key]));
26
+ }
27
+ function integerToken(numbers, path) {
28
+ if (numbers)
29
+ requireValue(/^-?(0|[1-9][0-9]*)$/.test(numbers.get(JSON.stringify(path)) ?? ''));
30
+ }
31
+ function shape(value, rule, numbers, path = []) {
32
+ if (rule.ref) {
33
+ shape(value, automationSchema.types[rule.ref], numbers, path);
34
+ semantics(rule.ref, value);
35
+ return;
36
+ }
37
+ if (rule.nullable) {
38
+ if (value !== null)
39
+ shape(value, rule.nullable, numbers, path);
40
+ return;
41
+ }
42
+ if (rule.enum) {
43
+ requireValue(rule.enum.includes(value));
44
+ if (typeof rule.enum[0] === 'number')
45
+ integerToken(numbers, path);
46
+ return;
47
+ }
48
+ if (rule.tag) {
49
+ const data = object(value), variant = rule.variants[data[rule.tag]];
50
+ requireValue(variant);
51
+ const fields = { ...data };
52
+ delete fields[rule.tag];
53
+ shape(fields, variant, numbers, path);
54
+ return;
55
+ }
56
+ if (rule.fields) {
57
+ const data = object(value), optional = rule.optional;
58
+ requireValue(Object.keys(data).every(key => Object.hasOwn(rule.fields, key)));
59
+ for (const [key, field] of Object.entries(rule.fields)) {
60
+ if (Object.hasOwn(data, key))
61
+ shape(data[key], field, numbers, [...path, key]);
62
+ else
63
+ requireValue(optional.includes(key));
64
+ }
65
+ return;
66
+ }
67
+ if (rule.items) {
68
+ requireValue(Array.isArray(value) && value.length >= rule.min && value.length <= rule.max);
69
+ if (rule.unique)
70
+ requireValue(new Set(value.map(item => JSON.stringify(item))).size === value.length);
71
+ value.forEach((item, index) => shape(item, rule.items, numbers, [...path, index]));
72
+ return;
73
+ }
74
+ if (rule.string) {
75
+ requireValue(typeof value === 'string' && value.length >= (rule.min ?? 0) && value.length <= rule.max && !value.includes('\u0000'));
76
+ requireValue(rule.blank || [...value].some(character => !automationSchema.whitespaceRanges.some((range) => character.codePointAt(0) >= range[0] && character.codePointAt(0) <= range[1])));
77
+ return;
78
+ }
79
+ if (rule.boolean) {
80
+ requireValue(typeof value === 'boolean');
81
+ return;
82
+ }
83
+ requireValue(typeof value === 'number' && Number.isFinite(value));
84
+ if (rule.integer) {
85
+ requireValue(Number.isSafeInteger(value));
86
+ integerToken(numbers, path);
87
+ }
88
+ if (rule.min !== undefined)
89
+ requireValue(value >= rule.min);
90
+ if (rule.max !== undefined)
91
+ requireValue(value <= rule.max);
92
+ if (rule.positive)
93
+ requireValue(value > 0);
94
+ }
95
+ function semantics(name, value) {
96
+ if (name === 'Acquisition' && value.type === 'takeover')
97
+ requireValue((value.expectedDeviceEpoch == null) === (value.expectedDeviceRevision == null));
98
+ if (name === 'WaitResult') {
99
+ requireValue((value.status === 'met') === (value.conditionState === 'met'));
100
+ requireValue(value.conditionState !== 'unavailable' || value.image === null);
101
+ }
102
+ if (name === 'Rect')
103
+ requireValue(Number.isFinite(value.x + value.width) && Number.isFinite(value.y + value.height));
104
+ if (name === 'Target') {
105
+ requireValue(Boolean(value.display) !== Boolean(value.window));
106
+ requireValue((value.selectionReason === 'EXPLICIT_DISPLAY') === Boolean(value.display));
107
+ }
108
+ if (name === 'Metadata') {
109
+ const binding = value.binding, target = value.target, transform = value.coordinates.imageToSourceTransform, rect = value.sourceRect;
110
+ requireValue(target.id === binding.targetId && target.workstation === binding.workstation && target.desktopId === binding.desktopId && target.inputDomain === binding.inputDomainId);
111
+ requireValue(Math.abs(transform.originX - rect.x) <= 0.001 && Math.abs(transform.originY - rect.y) <= 0.001 &&
112
+ Math.abs(value.width * transform.scaleX - rect.width) <= 0.001 && Math.abs(value.height * transform.scaleY - rect.height) <= 0.001);
113
+ }
114
+ if (name === 'Transfer') {
115
+ const header = value.header, metadata = value.metadata, binding = metadata.binding;
116
+ requireValue(header.consumerId === binding.consumerId && header.deviceGeneration === binding.revisions.deviceGeneration &&
117
+ header.controlGeneration === binding.revisions.controlGeneration && header.inputDomainId === binding.inputDomainId &&
118
+ header.captureSerial === binding.captureSerial && header.mimeType === metadata.mimeType);
119
+ }
120
+ if (name === 'ActRequest')
121
+ requireValue(value.actions.reduce((sum, action) => sum + (action.type === 'wait' ? action.durationMs : action.type === 'drag' ? (action.durationMs ?? 500) : 0), 0) <= 10000);
122
+ if (name === 'ActionResult') {
123
+ requireValue(value.postImage == null || value.captureError == null);
124
+ requireValue(value.receipts.length <= (value.actionCount ?? value.receipts.length));
125
+ value.receipts.forEach((receipt, index) => {
126
+ requireValue(receipt.index === index && (index === value.receipts.length - 1 || receipt.outcome === 'injected'));
127
+ });
128
+ }
129
+ }
130
+ function envelope(value, numbers) {
131
+ requireValue(equal(Object.keys(value).sort(), [...envelopeKeys].sort()) && value.automationProtocol === 1);
132
+ shape(value.automationProtocol, { enum: [1] }, numbers, ['automationProtocol']);
133
+ for (const key of routeKeys)
134
+ shape(value[key], key === 'controlGeneration' ? { integer: true, min: 0, max: Number.MAX_SAFE_INTEGER } : automationSchema.types.Identity, numbers, [key]);
135
+ requireValue(typeof value.method === 'string' && Object.hasOwn(automationSchema.requests, value.method));
136
+ }
137
+ export function decodeAutomationRequest(source) {
138
+ const parsed = parseAutomationJson(source, maxJson), value = object(parsed.value);
139
+ envelope(value, parsed.numbers);
140
+ shape(value.payload, automationSchema.requests[value.method], parsed.numbers, ['payload']);
141
+ if (value.method === 'open')
142
+ requireValue((value.payload.mode ?? 'observer') === 'controller' || (value.payload.acquisition?.type ?? 'if_free') === 'if_free');
143
+ if (value.method === 'open' && value.payload.acquisition?.type === 'takeover')
144
+ requireValue(value.payload.acquisition.expectedDeviceGeneration === value.deviceGeneration);
145
+ if (value.method === 'act')
146
+ requireValue(value.payload.request.observationId === value.payload.admission.delivery.binding.observationId);
147
+ return value;
148
+ }
149
+ export function encodeAutomationRequest(value) {
150
+ const text = JSON.stringify(value);
151
+ decodeAutomationRequest(text);
152
+ return text;
153
+ }
154
+ export function validateAutomationRequestFor(value, route, owner, observation, hostDelivery = false) {
155
+ decodeAutomationRequest(JSON.stringify(value));
156
+ requireValue(route && ['consumerId', 'workstation', 'desktopId', 'inputDomainId', 'deviceGeneration', 'controlGeneration'].every(key => route[key] !== undefined));
157
+ for (const key of ['consumerId', 'deviceGeneration', 'controlGeneration', 'inputDomainId'])
158
+ requireValue(value[key] === route[key]);
159
+ const payload = value.payload;
160
+ if (payload.workstation !== undefined)
161
+ requireValue(payload.workstation === route.workstation);
162
+ const handle = payload.handle ?? payload.capture?.handle ?? payload.request?.handle;
163
+ if (handle)
164
+ requireValue(owner && equal(owner.handle, handle) && equal(owner.route, route));
165
+ const binding = value.method === 'act' ? payload.admission.delivery.binding : value.method === 'delivery_ack' ? payload.binding : undefined;
166
+ if (binding) {
167
+ requireValue(equal(binding, observation));
168
+ validateObservationRoute(binding, route);
169
+ if (value.method === 'act')
170
+ requireValue(owner?.targetId === binding.targetId && payload.request.observationId === binding.observationId);
171
+ else
172
+ requireValue(hostDelivery);
173
+ }
174
+ }
175
+ function validateObservationRoute(binding, route) {
176
+ for (const key of ['consumerId', 'workstation', 'desktopId', 'inputDomainId'])
177
+ requireValue(binding[key] === route[key]);
178
+ requireValue(binding.revisions.deviceGeneration === route.deviceGeneration && binding.revisions.controlGeneration === route.controlGeneration);
179
+ }
180
+ function transfers(value) {
181
+ if (value.payload.type !== 'success')
182
+ return [];
183
+ if (value.method === 'capture')
184
+ return [value.payload.data];
185
+ if (value.method === 'capture_wait_v1' && value.payload.data.image)
186
+ return [value.payload.data.image];
187
+ if ((value.method === 'act' || value.method === 'status') && value.payload.data.postImage)
188
+ return [value.payload.data.postImage];
189
+ return [];
190
+ }
191
+ export function decodeAutomationResponse(source, request) {
192
+ decodeAutomationRequest(JSON.stringify(request));
193
+ const parsed = parseAutomationJson(source, maxJson), value = object(parsed.value);
194
+ envelope(value, parsed.numbers);
195
+ for (const key of ['automationProtocol', ...routeKeys, 'method'])
196
+ requireValue(value[key] === request[key]);
197
+ if (value.payload?.type === 'failure')
198
+ shape(value.payload, automationSchema.types.Failure, parsed.numbers, ['payload']);
199
+ else {
200
+ requireValue(equal(Object.keys(object(value.payload)).sort(), ['data', 'type']) && value.payload.type === 'success');
201
+ shape(value.payload.data, automationSchema.responses[value.method], parsed.numbers, ['payload', 'data']);
202
+ if (value.method === 'open')
203
+ requireValue(value.payload.data.mode !== 'controller' || value.payload.data.lease !== null);
204
+ }
205
+ for (const transfer of transfers(value))
206
+ for (const key of ['automationProtocol', ...routeKeys])
207
+ requireValue(transfer.header[key] === request[key]);
208
+ return value;
209
+ }
210
+ export function acceptsSequence(previous, incoming) {
211
+ return Number.isSafeInteger(previous) && previous >= 0 && Number.isSafeInteger(incoming) && incoming > previous;
212
+ }
213
+ export function encodeAutomationBinary(header, body) {
214
+ shape(header, automationSchema.types.Header);
215
+ requireValue(body.byteLength === header.bytesLength);
216
+ const text = new TextEncoder().encode(JSON.stringify(header));
217
+ requireValue(text.length <= maxJson);
218
+ const frame = new Uint8Array(4 + text.length + body.length);
219
+ new DataView(frame.buffer).setUint32(0, text.length, false);
220
+ frame.set(text, 4);
221
+ frame.set(body, 4 + text.length);
222
+ return frame;
223
+ }
224
+ export function decodeAutomationBinaryHeader(source) {
225
+ const parsed = parseAutomationJson(source, maxJson), header = object(parsed.value);
226
+ shape(header, automationSchema.types.Header, parsed.numbers);
227
+ return header;
228
+ }
229
+ export class AutomationBinaryReceiver {
230
+ consumed = false;
231
+ header;
232
+ constructor(request, transfer, route) {
233
+ decodeAutomationRequest(JSON.stringify(request));
234
+ requireValue(['capture', 'capture_wait_v1', 'act', 'status'].includes(request.method));
235
+ shape(transfer, automationSchema.types.Transfer);
236
+ this.header = JSON.parse(JSON.stringify(transfer.header));
237
+ for (const key of ['automationProtocol', ...routeKeys])
238
+ requireValue(this.header[key] === request[key]);
239
+ validateObservationRoute(transfer.metadata.binding, route);
240
+ for (const key of ['consumerId', 'deviceGeneration', 'controlGeneration', 'inputDomainId'])
241
+ requireValue(request[key] === route[key]);
242
+ }
243
+ accept(frame) {
244
+ requireValue(!this.consumed && frame.length >= 4 && frame.length <= 4 + maxJson + 8388608);
245
+ const length = new DataView(frame.buffer, frame.byteOffset, frame.byteLength).getUint32(0, false);
246
+ requireValue(length > 0 && length <= maxJson && frame.length === 4 + length + this.header.bytesLength);
247
+ const parsed = parseAutomationJson(frame.subarray(4, 4 + length), maxJson), header = object(parsed.value);
248
+ shape(header, automationSchema.types.Header, parsed.numbers);
249
+ requireValue(equal(header, this.header));
250
+ this.consumed = true;
251
+ return frame.slice(4 + length);
252
+ }
253
+ }
@@ -0,0 +1 @@
1
+ export const automationSchema = { "whitespaceRanges": [[9, 13], [28, 32], [160, 160], [5760, 5760], [8192, 8202], [8232, 8233], [8239, 8239], [8287, 8287], [12288, 12288]], "types": { "Identity": { "string": true, "max": 256, "blank": false }, "SelectorText": { "string": true, "max": 4096, "blank": false }, "Handle": { "fields": { "id": { "ref": "Identity" } }, "optional": [] }, "Point": { "fields": { "x": { "number": true }, "y": { "number": true } }, "optional": [] }, "Rect": { "fields": { "x": { "number": true }, "y": { "number": true }, "width": { "number": true, "positive": true }, "height": { "number": true, "positive": true } }, "optional": [] }, "Transform": { "fields": { "originX": { "number": true }, "originY": { "number": true }, "scaleX": { "number": true, "positive": true }, "scaleY": { "number": true, "positive": true } }, "optional": [] }, "Revisions": { "fields": { "deviceGeneration": { "ref": "Identity" }, "controlGeneration": { "integer": true, "min": 0, "max": 9007199254740991 }, "targetRevision": { "integer": true, "min": 0, "max": 9007199254740991 }, "geometryRevision": { "integer": true, "min": 0, "max": 9007199254740991 } }, "optional": [] }, "Binding": { "fields": { "consumerId": { "ref": "Identity" }, "workstation": { "ref": "Identity" }, "desktopId": { "ref": "Identity" }, "targetId": { "ref": "Identity" }, "revisions": { "ref": "Revisions" }, "observationId": { "ref": "Identity" }, "captureSerial": { "integer": true, "min": 0, "max": 9007199254740991 }, "captureToken": { "ref": "Identity" }, "inputDomainId": { "ref": "Identity" } }, "optional": [] }, "Delivery": { "fields": { "deliveryId": { "ref": "Identity" }, "binding": { "ref": "Binding" }, "surface": { "enum": ["PROVIDER_REQUEST", "SDK_RETURN"] }, "surfaceRequestId": { "ref": "Identity" } }, "optional": [] }, "Admission": { "fields": { "token": { "ref": "Identity" }, "delivery": { "ref": "Delivery" } }, "optional": [] }, "ApplicationSelector": { "tag": "type", "variants": { "name": { "fields": { "value": { "ref": "SelectorText" } }, "optional": [] }, "bundle_id": { "fields": { "value": { "ref": "SelectorText" } }, "optional": [] }, "path": { "fields": { "value": { "ref": "SelectorText" } }, "optional": [] } } }, "TargetSelector": { "tag": "type", "variants": { "display": { "fields": { "displayId": { "ref": "Identity" } }, "optional": [] }, "window": { "fields": { "windowId": { "ref": "Identity" } }, "optional": [] }, "application": { "fields": { "selector": { "ref": "ApplicationSelector" } }, "optional": [] } } }, "Application": { "fields": { "id": { "ref": "Identity" }, "name": { "ref": "SelectorText" }, "installationPath": { "ref": "SelectorText" }, "bundleId": { "nullable": { "ref": "SelectorText" } }, "desktopEntryId": { "nullable": { "ref": "SelectorText" } } }, "optional": ["bundleId", "desktopEntryId"] }, "Process": { "fields": { "pid": { "integer": true, "min": 1, "max": 9007199254740991 }, "instanceId": { "ref": "Identity" } }, "optional": [] }, "Display": { "fields": { "id": { "ref": "Identity" }, "sourceRect": { "ref": "Rect" } }, "optional": [] }, "Window": { "fields": { "id": { "ref": "Identity" }, "title": { "string": true, "max": 16384, "blank": true }, "applicationId": { "nullable": { "ref": "Identity" } }, "process": { "nullable": { "ref": "Process" } }, "capturable": { "boolean": true }, "focused": { "boolean": true }, "main": { "boolean": true }, "sourceRect": { "nullable": { "ref": "Rect" } } }, "optional": ["sourceRect"] }, "Resolution": { "fields": { "application": { "ref": "Application" }, "state": { "enum": ["INSTALLED", "LAUNCH_PENDING", "RUNNING_WITHOUT_WINDOWS", "RUNNING_WITH_WINDOWS"] }, "processes": { "items": { "ref": "Process" }, "min": 0, "max": 64, "unique": false }, "windows": { "items": { "ref": "Window" }, "min": 0, "max": 64, "unique": false } }, "optional": [] }, "Target": { "fields": { "id": { "ref": "Identity" }, "workstation": { "ref": "Identity" }, "desktopId": { "ref": "Identity" }, "inputDomain": { "ref": "Identity" }, "selectionReason": { "enum": ["EXPLICIT_DISPLAY", "EXPLICIT_WINDOW", "FOCUSED_APPLICATION_WINDOW", "MAIN_APPLICATION_WINDOW", "UNIQUE_CAPTURABLE_WINDOW"] }, "display": { "nullable": { "ref": "Display" } }, "window": { "nullable": { "ref": "Window" } }, "application": { "nullable": { "ref": "Application" } }, "process": { "nullable": { "ref": "Process" } } }, "optional": ["display", "window", "application", "process"] }, "Coordinates": { "fields": { "sourceSpace": { "enum": ["DESKTOP_GLOBAL", "WINDOW_LOCAL"] }, "inputUnit": { "enum": ["NATIVE_PIXEL", "LOGICAL_POINT"] }, "imageToSourceTransform": { "ref": "Transform" }, "sourceToInputTransform": { "ref": "Transform" }, "inputSpace": { "enum": ["DESKTOP_GLOBAL", "WINDOW_LOCAL"] }, "sourceUnit": { "enum": ["NATIVE_PIXEL"] } }, "optional": ["inputSpace", "sourceUnit"] }, "Fidelity": { "fields": { "captureMode": { "enum": ["NATIVE_SURFACE", "VISIBLE_CROP"] }, "stable": { "nullable": { "boolean": true } }, "limitations": { "items": { "enum": ["OCCLUDED", "MINIMIZED", "OTHER_SPACE", "CURSOR_EXCLUDED", "STABILITY_UNKNOWN"] }, "min": 0, "max": 64, "unique": false } }, "optional": [] }, "Metadata": { "fields": { "binding": { "ref": "Binding" }, "mimeType": { "enum": ["image/png", "image/jpeg"] }, "width": { "integer": true, "min": 1, "max": 4096 }, "height": { "integer": true, "min": 1, "max": 4096 }, "sourceRect": { "ref": "Rect" }, "coordinates": { "ref": "Coordinates" }, "capturedAtEpochMs": { "integer": true, "min": 0, "max": 9007199254740991 }, "afterActionId": { "nullable": { "ref": "Identity" } }, "appliedInputSequence": { "nullable": { "integer": true, "min": 0, "max": 9007199254740991 } }, "fidelity": { "ref": "Fidelity" }, "target": { "ref": "Target" }, "parentObservationId": { "nullable": { "ref": "Identity" } }, "cursor": { "nullable": { "fields": { "sourcePosition": { "ref": "Point" }, "includedInImage": { "boolean": true } }, "optional": [] } }, "focus": { "nullable": { "fields": { "windowId": { "nullable": { "ref": "Identity" } }, "applicationId": { "nullable": { "ref": "Identity" } }, "process": { "nullable": { "ref": "Process" } } }, "optional": [] } }, "inputEligible": { "boolean": true } }, "optional": ["parentObservationId", "cursor", "focus", "inputEligible"] }, "Header": { "fields": { "automationProtocol": { "enum": [1] }, "requestId": { "ref": "Identity" }, "consumerId": { "ref": "Identity" }, "deviceGeneration": { "ref": "Identity" }, "controlGeneration": { "integer": true, "min": 0, "max": 9007199254740991 }, "inputDomainId": { "ref": "Identity" }, "transferId": { "ref": "Identity" }, "captureSerial": { "integer": true, "min": 0, "max": 9007199254740991 }, "bytesLength": { "integer": true, "min": 1, "max": 8388608 }, "mimeType": { "enum": ["image/png", "image/jpeg"] } }, "optional": [] }, "Transfer": { "fields": { "header": { "ref": "Header" }, "metadata": { "ref": "Metadata" } }, "optional": [] }, "Acquisition": { "tag": "type", "variants": { "if_free": { "fields": {}, "optional": [] }, "takeover": { "fields": { "expectedOwnershipRevision": { "integer": true, "min": 0, "max": 9007199254740991 }, "expectedDeviceGeneration": { "ref": "Identity" }, "expectedDeviceEpoch": { "ref": "Identity" }, "expectedDeviceRevision": { "integer": true, "min": 0, "max": 9007199254740991 } }, "optional": ["expectedDeviceEpoch", "expectedDeviceRevision"] } } }, "Action": { "tag": "type", "variants": { "move": { "fields": { "point": { "ref": "Point" } }, "optional": [] }, "click": { "fields": { "point": { "ref": "Point" }, "button": { "enum": ["LEFT", "MIDDLE", "RIGHT"] }, "count": { "integer": true, "min": 1, "max": 2 } }, "optional": ["button", "count"] }, "drag": { "fields": { "path": { "items": { "ref": "Point" }, "min": 2, "max": 256, "unique": false }, "button": { "enum": ["LEFT", "MIDDLE", "RIGHT"] }, "durationMs": { "integer": true, "min": 1, "max": 10000 } }, "optional": ["button", "durationMs"] }, "scroll": { "fields": { "deltaX": { "number": true, "min": -10000, "max": 10000 }, "deltaY": { "number": true, "min": -10000, "max": 10000 }, "unit": { "enum": ["PIXEL", "LINE", "PAGE"] }, "point": { "nullable": { "ref": "Point" } } }, "optional": ["point"] }, "key_chord": { "fields": { "keys": { "items": { "string": true, "max": 64, "blank": false }, "min": 1, "max": 8, "unique": true } }, "optional": [] }, "type_text": { "fields": { "text": { "string": true, "max": 16384, "blank": true, "min": 1 }, "mode": { "enum": ["UNICODE", "PASTE"] } }, "optional": ["mode"] }, "focus_window": { "fields": { "windowId": { "ref": "Identity" } }, "optional": [] }, "wait": { "fields": { "durationMs": { "integer": true, "min": 0, "max": 2000 } }, "optional": [] }, "release_all": { "fields": {}, "optional": [] } } }, "ActRequest": { "fields": { "handle": { "ref": "Handle" }, "observationId": { "ref": "Identity" }, "requestId": { "ref": "Identity" }, "actions": { "items": { "ref": "Action" }, "min": 1, "max": 16, "unique": false }, "postObservation": { "boolean": true } }, "optional": ["postObservation"] }, "Controller": { "fields": { "consumerId": { "ref": "Identity" }, "userId": { "nullable": { "ref": "Identity" } }, "ownershipRevision": { "integer": true, "min": 0, "max": 9007199254740991 }, "controlGeneration": { "integer": true, "min": 0, "max": 9007199254740991 } }, "optional": [] }, "Diagnostic": { "fields": { "reason": { "enum": ["capture_timeout", "capture_not_after_action", "too_large", "target_changed", "unsupported_automation_protocol", "delivery_not_acknowledged", "observation_expired", "foreign_handle", "launch_pending", "cancelled", "request_digest_mismatch", "budget_exhausted"] }, "description": { "nullable": { "string": true, "max": 16384, "blank": true } } }, "optional": ["description"] }, "Receipt": { "fields": { "index": { "integer": true, "min": 0, "max": 15 }, "outcome": { "enum": ["accepted", "executing", "injected", "rejected", "unknown"] }, "sequence": { "nullable": { "integer": true, "min": 0, "max": 9007199254740991 } }, "code": { "nullable": { "string": true, "max": 16384, "blank": true } }, "diagnostic": { "nullable": { "ref": "Diagnostic" } } }, "optional": ["diagnostic"] }, "ActionResult": { "fields": { "requestId": { "ref": "Identity" }, "receipts": { "items": { "ref": "Receipt" }, "min": 0, "max": 16, "unique": false }, "postImage": { "nullable": { "ref": "Transfer" } }, "captureError": { "nullable": { "string": true, "max": 16384, "blank": true } }, "captureDiagnostic": { "nullable": { "ref": "Diagnostic" } }, "actionCount": { "integer": true, "min": 0, "max": 16 } }, "optional": ["captureDiagnostic", "actionCount"] }, "LaunchQuery": { "fields": { "workstation": { "ref": "Identity" }, "requestId": { "ref": "Identity" } }, "optional": [] }, "Launch": { "fields": { "identity": { "ref": "LaunchQuery" }, "state": { "enum": ["ACCEPTED", "LAUNCHING", "WINDOW_NOT_READY", "READY", "FAILED", "UNKNOWN", "CANCELLED", "TIMED_OUT"] }, "application": { "nullable": { "ref": "Application" } }, "process": { "nullable": { "ref": "Process" } }, "windows": { "items": { "ref": "Window" }, "min": 0, "max": 64, "unique": false }, "canonicalArgsDigest": { "nullable": { "ref": "Identity" } }, "diagnostic": { "nullable": { "ref": "Diagnostic" } } }, "optional": ["canonicalArgsDigest", "diagnostic"] }, "Candidate": { "tag": "type", "variants": { "display": { "fields": { "display": { "ref": "Display" } }, "optional": [] }, "window": { "fields": { "window": { "ref": "Window" } }, "optional": [] }, "application": { "fields": { "application": { "ref": "Resolution" } }, "optional": [] } } }, "FailureDetail": { "fields": { "diagnostic": { "nullable": { "ref": "Diagnostic" } }, "candidates": { "items": { "ref": "Candidate" }, "min": 0, "max": 64, "unique": false }, "launch": { "nullable": { "ref": "Launch" } }, "controller": { "nullable": { "ref": "Controller" } } }, "optional": ["diagnostic", "candidates", "launch", "controller"] }, "Failure": { "fields": { "type": { "enum": ["failure"] }, "code": { "enum": ["invalid_argument", "forbidden", "unsupported", "not_found", "ambiguous_target", "window_not_ready", "launch_failed", "control_lost", "stale_observation", "outcome_unknown", "resource_limit", "approval_required", "service_unavailable", "conflict", "capture_failed"] }, "message": { "string": true, "max": 16384, "blank": true }, "detail": { "nullable": { "ref": "FailureDetail" } } }, "optional": ["detail"] }, "WaitCondition": { "tag": "type", "variants": { "target_capturable": { "fields": {}, "optional": [] }, "input_applied": { "fields": { "actionId": { "ref": "Identity" } }, "optional": [] }, "changed_from": { "fields": { "observationId": { "ref": "Identity" }, "roi": { "nullable": { "ref": "Rect" } } }, "optional": [] }, "stable": { "fields": { "roi": { "nullable": { "ref": "Rect" } }, "stableMs": { "integer": true, "min": 100, "max": 10000 } }, "optional": [] } } }, "WaitResult": { "fields": { "status": { "enum": ["met", "timeout"] }, "conditionState": { "enum": ["met", "not_met", "unavailable"] }, "elapsedMs": { "integer": true, "min": 0, "max": 60000 }, "image": { "nullable": { "ref": "Transfer" } } }, "optional": [] } }, "requests": { "discover": { "fields": { "workstation": { "ref": "Identity" }, "kinds": { "items": { "enum": ["DISPLAY", "WINDOW", "APPLICATION"] }, "min": 1, "max": 3, "unique": true }, "application": { "nullable": { "ref": "ApplicationSelector" } }, "limit": { "integer": true, "min": 1, "max": 64 }, "cursor": { "nullable": { "string": true, "max": 16384, "blank": true } } }, "optional": ["kinds", "application", "limit", "cursor"] }, "application_resolve": { "fields": { "workstation": { "ref": "Identity" }, "selector": { "ref": "ApplicationSelector" } }, "optional": [] }, "application_ensure": { "fields": { "workstation": { "ref": "Identity" }, "application": { "ref": "Application" }, "requestId": { "ref": "Identity" }, "timeoutMs": { "integer": true, "min": 1, "max": 60000 } }, "optional": ["timeoutMs"] }, "application_status": { "ref": "LaunchQuery" }, "open": { "fields": { "workstation": { "ref": "Identity" }, "target": { "ref": "TargetSelector" }, "mode": { "enum": ["observer", "controller"] }, "acquisition": { "ref": "Acquisition" }, "launchIfNeeded": { "boolean": true }, "requestId": { "ref": "Identity" }, "timeoutMs": { "integer": true, "min": 1, "max": 60000 } }, "optional": ["mode", "acquisition", "launchIfNeeded", "timeoutMs"] }, "capture": { "fields": { "handle": { "ref": "Handle" }, "crop": { "nullable": { "ref": "Rect" } }, "afterActionId": { "nullable": { "ref": "Identity" } }, "timeoutMs": { "integer": true, "min": 1, "max": 60000 }, "maxImageSide": { "integer": true, "min": 1, "max": 4096 }, "maxImageBytes": { "integer": true, "min": 1, "max": 8388608 } }, "optional": ["crop", "afterActionId", "timeoutMs", "maxImageSide", "maxImageBytes"] }, "act": { "fields": { "request": { "ref": "ActRequest" }, "admission": { "ref": "Admission" } }, "optional": [] }, "status": { "fields": { "handle": { "ref": "Handle" }, "requestId": { "ref": "Identity" } }, "optional": [] }, "close": { "fields": { "handle": { "ref": "Handle" } }, "optional": [] }, "delivery_ack": { "ref": "Delivery" }, "capture_wait_v1": { "fields": { "capture": { "fields": { "handle": { "ref": "Handle" }, "crop": { "nullable": { "ref": "Rect" } }, "afterActionId": { "nullable": { "ref": "Identity" } }, "timeoutMs": { "integer": true, "min": 1, "max": 60000 }, "maxImageSide": { "integer": true, "min": 1, "max": 4096 }, "maxImageBytes": { "integer": true, "min": 1, "max": 8388608 } }, "optional": ["crop", "afterActionId", "timeoutMs", "maxImageSide", "maxImageBytes"] }, "condition": { "ref": "WaitCondition" } }, "optional": [] } }, "responses": { "discover": { "fields": { "workstation": { "ref": "Identity" }, "desktopId": { "ref": "Identity" }, "inputDomain": { "ref": "Identity" }, "candidates": { "items": { "ref": "Candidate" }, "min": 0, "max": 64, "unique": false }, "capabilities": { "fields": { "capture": { "boolean": true }, "input": { "boolean": true }, "applicationLaunch": { "boolean": true }, "clipboard": { "boolean": true }, "operations": { "items": { "string": true, "max": 256, "blank": false }, "min": 0, "max": 64, "unique": true } }, "optional": [] }, "controller": { "nullable": { "ref": "Controller" } }, "nextCursor": { "nullable": { "string": true, "max": 16384, "blank": true } }, "deviceOwnership": { "nullable": { "fields": { "deviceEpoch": { "ref": "Identity" }, "revision": { "integer": true, "min": 0, "max": 9007199254740991 }, "state": { "enum": ["free", "draining", "active"] }, "controller": { "nullable": { "fields": { "registrationId": { "ref": "Identity" }, "controllerId": { "ref": "Identity" }, "kind": { "ref": "Identity" } }, "optional": [] } } }, "optional": [] } } }, "optional": ["nextCursor", "deviceOwnership"] }, "application_resolve": { "ref": "Resolution" }, "application_ensure": { "ref": "Launch" }, "application_status": { "ref": "Launch" }, "open": { "fields": { "handle": { "ref": "Handle" }, "target": { "ref": "Target" }, "mode": { "enum": ["observer", "controller"] }, "revisions": { "ref": "Revisions" }, "lease": { "nullable": { "fields": { "id": { "ref": "Identity" }, "expiresAtEpochMs": { "integer": true, "min": 0, "max": 9007199254740991 } }, "optional": [] } } }, "optional": [] }, "capture": { "ref": "Transfer" }, "act": { "ref": "ActionResult" }, "status": { "ref": "ActionResult" }, "close": { "fields": { "handle": { "ref": "Handle" }, "alreadyClosed": { "boolean": true } }, "optional": [] }, "delivery_ack": { "ref": "Admission" }, "capture_wait_v1": { "ref": "WaitResult" } } };
@@ -0,0 +1,81 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { automationSchema } from './automationProtocol.js';
3
+ export class AutomationSession {
4
+ transport;
5
+ control;
6
+ operations;
7
+ binding;
8
+ statusRequest;
9
+ constructor(transport, binding, control) {
10
+ this.transport = transport;
11
+ this.control = control;
12
+ const route = binding.route;
13
+ if (!['consumerId', 'workstation', 'desktopId', 'inputDomainId', 'deviceGeneration'].every(key => typeof route[key] === 'string' && route[key].length > 0) ||
14
+ !Number.isSafeInteger(route.controlGeneration) || route.controlGeneration < 0 || !binding.executionId)
15
+ throw new Error('live_route_unavailable');
16
+ this.binding = structuredClone(binding);
17
+ this.operations = [...control.operations];
18
+ }
19
+ prepareAttachment(preparation, options) {
20
+ if (this.statusRequest !== undefined)
21
+ return Promise.reject(new Error('control_lost'));
22
+ if (!this.control.prepare)
23
+ return Promise.reject(new Error('unsupported_automation_attachment'));
24
+ return this.control.prepare(structuredClone(preparation), options);
25
+ }
26
+ controlStatus() {
27
+ return this.control.status ? this.control.status() : Promise.reject(new Error('unsupported_automation_control_status'));
28
+ }
29
+ call(method, payload, options) {
30
+ if (this.statusRequest !== undefined && (method !== 'application_status' || payload.requestId !== this.statusRequest))
31
+ return Promise.reject(new Error('control_lost'));
32
+ if (!Object.hasOwn(automationSchema.requests, method) || !this.operations.includes(method))
33
+ return Promise.reject(new Error('unsupported'));
34
+ const route = this.binding.route;
35
+ return this.transport.request({ automationProtocol: 1, requestId: randomBytes(16).toString('hex'), consumerId: route.consumerId,
36
+ deviceGeneration: route.deviceGeneration, controlGeneration: route.controlGeneration, inputDomainId: route.inputDomainId, method, payload }, options);
37
+ }
38
+ execute(request, options) {
39
+ if (this.statusRequest !== undefined && (request.method !== 'application_status' || request.payload.requestId !== this.statusRequest))
40
+ return Promise.reject(new Error('control_lost'));
41
+ if (!this.operations.includes(request.method))
42
+ return Promise.reject(new Error('unsupported'));
43
+ if (['consumerId', 'deviceGeneration', 'controlGeneration', 'inputDomainId'].some(key => request[key] !== this.binding.route[key]))
44
+ return Promise.reject(new Error('stale_generation'));
45
+ if (request.method === 'close')
46
+ this.transport.cancelPending();
47
+ return this.transport.request(request, options);
48
+ }
49
+ discover(payload, options) { return this.call('discover', payload, options); }
50
+ applicationResolve(payload, options) { return this.call('application_resolve', payload, options); }
51
+ applicationEnsure(payload, options) { return this.call('application_ensure', payload, options); }
52
+ applicationStatus(payload, options) { return this.call('application_status', payload, options); }
53
+ open(payload, options) { return this.call('open', payload, options); }
54
+ capture(payload, options) { return this.call('capture', payload, options); }
55
+ act(payload, options) { return this.call('act', payload, options); }
56
+ status(payload, options) { return this.call('status', payload, options); }
57
+ close(payload, options) { this.transport.cancelPending(); return this.call('close', payload, options); }
58
+ deliveryAck(payload, options) { return this.call('delivery_ack', payload, options); }
59
+ async renewAuthenticatedLease(leaseMs) {
60
+ if (this.statusRequest !== undefined)
61
+ return { admitted: true, physical: 'failed' };
62
+ if (!Number.isInteger(leaseMs) || leaseMs < 1 || leaseMs > 30000)
63
+ return Promise.reject(new Error('invalid_argument'));
64
+ return await this.control.renew(leaseMs) ?? { admitted: true, physical: 'pending' };
65
+ }
66
+ async dispose() { this.transport.close(); await this.control.close(); }
67
+ async detachApplicationStatus(requestId, digest) {
68
+ if (this.statusRequest !== undefined && this.statusRequest !== requestId)
69
+ throw new Error('conflict');
70
+ this.statusRequest = requestId;
71
+ if (!this.control.readControl)
72
+ throw new Error('unsupported');
73
+ return this.control.readControl('status_only', { launchRequestId: requestId, canonicalArgsDigest: digest });
74
+ }
75
+ async isolate() {
76
+ this.statusRequest = this.statusRequest ?? '';
77
+ if (!this.control.readControl)
78
+ throw new Error('unsupported');
79
+ return this.control.readControl('isolate', {});
80
+ }
81
+ }
@@ -0,0 +1,138 @@
1
+ import { parseAutomationJson } from './automationJson.js';
2
+ import { AutomationBinaryReceiver, decodeAutomationResponse, decodeAutomationBinaryHeader, encodeAutomationBinary, encodeAutomationRequest } from './automationProtocol.js';
3
+ export class AutomationTransport {
4
+ send;
5
+ cancel;
6
+ pending = new Map();
7
+ ended = false;
8
+ imageBytes = 0;
9
+ constructor(send, cancel = () => { }) {
10
+ this.send = send;
11
+ this.cancel = cancel;
12
+ }
13
+ pendingCount() { return this.pending.size; }
14
+ request(value, options = {}) {
15
+ if (this.ended)
16
+ return Promise.reject(new Error('closed'));
17
+ if (this.pending.size >= 64)
18
+ return Promise.reject(new Error('resource_limit'));
19
+ if (options.signal?.aborted)
20
+ return Promise.reject(new Error('cancelled'));
21
+ const request = structuredClone(value), text = encodeAutomationRequest(request);
22
+ if (this.pending.has(request.requestId))
23
+ return Promise.reject(new Error('conflict'));
24
+ const timeout = options.timeoutMs ?? 65000;
25
+ if (!Number.isInteger(timeout) || timeout < 1 || timeout > 65000)
26
+ return Promise.reject(new Error('invalid_argument'));
27
+ const promise = new Promise((resolve, reject) => {
28
+ const abort = () => this.retire(request.requestId, 'cancelled');
29
+ const timer = setTimeout(() => this.retire(request.requestId, 'timed_out'), timeout);
30
+ this.pending.set(request.requestId, { request, resolve, reject, timer, bytes: 0,
31
+ detach: () => options.signal?.removeEventListener('abort', abort) });
32
+ options.signal?.addEventListener('abort', abort, { once: true });
33
+ });
34
+ void this.send(text).catch(() => this.retire(request.requestId, 'transport_failed'));
35
+ return promise;
36
+ }
37
+ acceptResponse(text) {
38
+ try {
39
+ const raw = parseAutomationJson(text, 49152).value;
40
+ const pending = this.pending.get(raw.requestId);
41
+ if (!pending || pending.response)
42
+ return { ok: false, code: 'unknown_request' };
43
+ const response = decodeAutomationResponse(text, pending.request);
44
+ const transfer = response.payload.type === 'success' ? response.method === 'capture' ? response.payload.data :
45
+ response.method === 'capture_wait_v1' ? response.payload.data.image : ['act', 'status'].includes(response.method) ? response.payload.data.postImage : undefined : undefined;
46
+ if (transfer) {
47
+ const size = transfer.header.bytesLength;
48
+ if (this.imageBytes - pending.bytes + size > 8388608) {
49
+ this.retire(raw.requestId, 'resource_limit');
50
+ return { ok: false, code: 'resource_limit' };
51
+ }
52
+ pending.receiver = new AutomationBinaryReceiver(pending.request, transfer, {
53
+ ...pending.request, workstation: transfer.metadata.binding.workstation, desktopId: transfer.metadata.binding.desktopId,
54
+ });
55
+ this.imageBytes += size - pending.bytes;
56
+ pending.bytes = size;
57
+ pending.response = response;
58
+ if (pending.early) {
59
+ const early = pending.early;
60
+ pending.early = undefined;
61
+ this.acceptBinary(early);
62
+ }
63
+ }
64
+ else {
65
+ this.remove(raw.requestId)?.resolve({ response });
66
+ }
67
+ return { ok: true };
68
+ }
69
+ catch {
70
+ return { ok: false, code: 'invalid_response' };
71
+ }
72
+ }
73
+ acceptImage(header, bytes) {
74
+ if (bytes.byteLength !== (header.bytesLength ?? header.byteLength))
75
+ return { ok: false, code: 'invalid_length' };
76
+ try {
77
+ return this.acceptBinary(encodeAutomationBinary(header, bytes));
78
+ }
79
+ catch {
80
+ return { ok: false, code: 'invalid_binary' };
81
+ }
82
+ }
83
+ acceptBinary(frame) {
84
+ try {
85
+ if (frame.length < 4 || frame.length > 8388608 + 49156)
86
+ throw new Error();
87
+ const length = new DataView(frame.buffer, frame.byteOffset, frame.byteLength).getUint32(0, false);
88
+ if (length < 1 || length > 49152 || frame.length < 4 + length)
89
+ throw new Error();
90
+ const header = decodeAutomationBinaryHeader(frame.subarray(4, 4 + length));
91
+ const pending = this.pending.get(header.requestId);
92
+ if (!pending)
93
+ return { ok: false, code: 'unknown_request' };
94
+ if (!pending.receiver || !pending.response) {
95
+ if (!['capture', 'capture_wait_v1', 'act', 'status'].includes(pending.request.method) || frame.length !== 4 + length + header.bytesLength || pending.early ||
96
+ ['consumerId', 'deviceGeneration', 'controlGeneration', 'inputDomainId'].some(key => header[key] !== pending.request[key]))
97
+ return { ok: false, code: 'invalid_binary' };
98
+ if (this.imageBytes + header.bytesLength > 8388608)
99
+ return { ok: false, code: 'resource_limit' };
100
+ pending.early = Buffer.from(frame);
101
+ pending.bytes = header.bytesLength;
102
+ this.imageBytes += pending.bytes;
103
+ return { ok: true };
104
+ }
105
+ const image = Buffer.from(pending.receiver.accept(frame));
106
+ this.remove(header.requestId)?.resolve({ response: pending.response, image });
107
+ return { ok: true };
108
+ }
109
+ catch {
110
+ return { ok: false, code: 'invalid_binary' };
111
+ }
112
+ }
113
+ remove(requestId) {
114
+ const pending = this.pending.get(requestId);
115
+ if (pending) {
116
+ this.pending.delete(requestId);
117
+ clearTimeout(pending.timer);
118
+ pending.detach();
119
+ this.imageBytes -= pending.bytes;
120
+ }
121
+ return pending;
122
+ }
123
+ retire(requestId, code) {
124
+ const pending = this.remove(requestId);
125
+ if (pending) {
126
+ pending.reject(new Error(code));
127
+ try {
128
+ this.cancel(requestId);
129
+ }
130
+ catch { }
131
+ }
132
+ }
133
+ invalidateGeneration(_generation) { this.close('stale_generation'); }
134
+ cancelPending() { for (const requestId of this.pending.keys())
135
+ this.retire(requestId, 'closed'); }
136
+ close(code = 'closed') { this.ended = true; for (const requestId of this.pending.keys())
137
+ this.retire(requestId, code); }
138
+ }
@@ -20,7 +20,8 @@ export async function capabilitiesForWire(options) {
20
20
  Promise.resolve().then(() => options.provider.probe()),
21
21
  new Promise(resolve => { timer = setTimeout(() => resolve(unavailable()), cap); timer.unref?.(); }),
22
22
  ]);
23
- const report = decodeCapabilityReport(JSON.stringify({ generation, revision: 1, desktop: desktop && { ...desktop, lobby: true } }));
23
+ const compatible = desktop?.deviceOwnershipProtocol === 1 && options.selectedWire < 10 ? null : desktop;
24
+ const report = decodeCapabilityReport(JSON.stringify({ generation, revision: 1, desktop: compatible && { ...compatible, lobby: true } }));
24
25
  decodeDesktopManagementFrame(JSON.stringify({ t: 'workstation_capabilities', ...report }));
25
26
  return report;
26
27
  }