@mobileaidev/ai-app-bridge 0.2.15 → 0.3.0-rc.2

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 (156) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +7 -0
  3. package/README.md +306 -53
  4. package/bin/ai-app-bridge.js +51 -4504
  5. package/bin/android-permissions.js +152 -0
  6. package/bin/android-uia-xml.js +74 -0
  7. package/bin/artifact-paths.js +127 -1
  8. package/bin/bridge-forward.js +56 -0
  9. package/bin/command-discovery.js +86 -0
  10. package/bin/command-errors.js +60 -0
  11. package/bin/command-registry.js +504 -0
  12. package/bin/command-request.js +14 -0
  13. package/bin/command-router.js +80 -0
  14. package/bin/connection-cache.js +119 -0
  15. package/bin/device-provider.js +2934 -0
  16. package/bin/execution-host.js +596 -0
  17. package/bin/execution-runtime.js +101 -0
  18. package/bin/fact-codec.js +321 -0
  19. package/bin/fact-recorder.js +691 -0
  20. package/bin/fact-store.js +226 -0
  21. package/bin/feedback-probe.js +285 -0
  22. package/bin/intent/install-intent.js +267 -0
  23. package/bin/intent/intent-action-executor.js +129 -0
  24. package/bin/intent/intent-autonomous-adapter.js +46 -0
  25. package/bin/intent/intent-capture-port.js +64 -0
  26. package/bin/intent/intent-entry.js +226 -0
  27. package/bin/intent/intent-errors.js +28 -0
  28. package/bin/intent/intent-evidence-store.js +121 -0
  29. package/bin/intent/intent-lifetime.js +62 -0
  30. package/bin/intent/intent-observation-target.js +33 -0
  31. package/bin/intent/intent-observer.js +194 -0
  32. package/bin/intent/intent-production-adapter.js +334 -0
  33. package/bin/intent/intent-provider.js +27 -0
  34. package/bin/intent/intent-runtime.js +63 -0
  35. package/bin/intent/intent-worker.js +432 -0
  36. package/bin/intent/ios-intent-adapter.js +90 -0
  37. package/bin/intent/permission-intent.js +239 -0
  38. package/bin/intent/web-intent-adapter.js +31 -0
  39. package/bin/ios-device-outcome.js +47 -0
  40. package/bin/ios-execution.js +108 -0
  41. package/bin/ios-provider.js +497 -583
  42. package/bin/ios-runtime-binding.js +54 -0
  43. package/bin/ios-wda-execution.js +70 -0
  44. package/bin/ios-wda-port.js +98 -0
  45. package/bin/ios-wda-project.js +79 -0
  46. package/bin/mcp-server.js +83 -1040
  47. package/bin/mmap-scan-index.js +329 -0
  48. package/bin/observation-collector.js +862 -0
  49. package/bin/runtime-client.js +154 -0
  50. package/bin/runtime-directory.js +107 -0
  51. package/bin/runtime-protocol.js +36 -0
  52. package/bin/script/bounded-script-registry.js +103 -0
  53. package/bin/script/node-runtime-adapter.js +233 -0
  54. package/bin/script/progress-projector.js +63 -0
  55. package/bin/script/python-runtime-adapter.js +111 -0
  56. package/bin/script/rolling-summary.js +134 -0
  57. package/bin/script/script-agent-port.js +40 -0
  58. package/bin/script/script-assert.js +95 -0
  59. package/bin/script/script-capture-port.js +76 -0
  60. package/bin/script/script-catalog.js +85 -0
  61. package/bin/script/script-durable-restore.js +195 -0
  62. package/bin/script/script-entry-code.js +26 -0
  63. package/bin/script/script-entry-route.js +38 -0
  64. package/bin/script/script-entry.js +3 -0
  65. package/bin/script/script-errors.js +29 -0
  66. package/bin/script/script-evidence-store.js +22 -0
  67. package/bin/script/script-format-removed.js +26 -0
  68. package/bin/script/script-host-port.js +397 -0
  69. package/bin/script/script-ledger.js +64 -0
  70. package/bin/script/script-result.js +57 -0
  71. package/bin/script/script-sdk.js +152 -0
  72. package/bin/script/script-sdk.py +153 -0
  73. package/bin/script/script-session-channel.js +127 -0
  74. package/bin/script/script-spec.js +86 -0
  75. package/bin/script/script-supervisor.js +919 -0
  76. package/bin/script/templates/checkpoint-reentry.js +13 -0
  77. package/bin/segment-index.js +481 -0
  78. package/bin/segmented-fact-store.js +1571 -0
  79. package/bin/shared-kernel/android-h5-target.js +10 -0
  80. package/bin/shared-kernel/android-install-execution.js +176 -0
  81. package/bin/shared-kernel/android-sdk-endpoint.js +42 -0
  82. package/bin/shared-kernel/android-shell-execution.js +195 -0
  83. package/bin/shared-kernel/argument-schema.js +117 -0
  84. package/bin/shared-kernel/canonical-path.js +17 -0
  85. package/bin/shared-kernel/device-acknowledgements.js +53 -0
  86. package/bin/shared-kernel/device-completion-history.js +52 -0
  87. package/bin/shared-kernel/device-mutation-lease.js +219 -0
  88. package/bin/shared-kernel/device-ownership-recovery.js +95 -0
  89. package/bin/shared-kernel/device-ownership-store.js +94 -0
  90. package/bin/shared-kernel/evidence-adapters.js +251 -0
  91. package/bin/shared-kernel/evidence-archive.js +329 -0
  92. package/bin/shared-kernel/evidence-recording.js +131 -0
  93. package/bin/shared-kernel/evidence-schema.js +194 -0
  94. package/bin/shared-kernel/evidence-store.js +190 -0
  95. package/bin/shared-kernel/execution-admission.js +22 -0
  96. package/bin/shared-kernel/execution-contracts.js +171 -0
  97. package/bin/shared-kernel/execution-io.js +106 -0
  98. package/bin/shared-kernel/execution-ledger.js +125 -0
  99. package/bin/shared-kernel/execution-scope.js +87 -0
  100. package/bin/shared-kernel/execution-target.js +115 -0
  101. package/bin/shared-kernel/flutter-execution.js +13 -0
  102. package/bin/shared-kernel/flutter-h5-port.js +60 -0
  103. package/bin/shared-kernel/flutter-h5-target.js +9 -0
  104. package/bin/shared-kernel/flutter-target.js +75 -0
  105. package/bin/shared-kernel/h5-execution.js +11 -0
  106. package/bin/shared-kernel/h5-target.js +31 -0
  107. package/bin/shared-kernel/host-fact-store.js +49 -0
  108. package/bin/shared-kernel/ios-h5-target.js +9 -0
  109. package/bin/shared-kernel/ios-native-target.js +71 -0
  110. package/bin/shared-kernel/live-capture-query.js +115 -0
  111. package/bin/shared-kernel/managed-sdk-execution.js +78 -0
  112. package/bin/shared-kernel/native-execution.js +13 -0
  113. package/bin/shared-kernel/native-target.js +156 -0
  114. package/bin/shared-kernel/provider-command-contracts.js +55 -0
  115. package/bin/shared-kernel/recorded-payload-archive.js +195 -0
  116. package/bin/shared-kernel/request-context.js +35 -0
  117. package/bin/shared-kernel/semantic-node.js +55 -0
  118. package/bin/shared-kernel/summary-transformer.js +352 -0
  119. package/bin/shared-kernel/target-lease-protocol.js +47 -0
  120. package/bin/shared-kernel/text-wait.js +111 -0
  121. package/bin/shared-kernel/uia-execution.js +96 -0
  122. package/bin/shared-kernel/uia-protocol.js +214 -0
  123. package/bin/shared-kernel/uia-runtime-port.js +377 -0
  124. package/bin/shared-kernel/uia-target.js +39 -0
  125. package/bin/shared-kernel/web-dom-target.js +44 -0
  126. package/bin/shared-kernel/xml-attributes.js +25 -0
  127. package/bin/target-execution.js +275 -0
  128. package/bin/web/command-schema.js +60 -0
  129. package/bin/web/session-store.js +157 -0
  130. package/bin/web-provider.js +334 -553
  131. package/docs/COMMAND_CONTRACT.md +1563 -0
  132. package/docs/EVIDENCE_ARCHIVE.md +214 -0
  133. package/docs/INTENT_FOREGROUND.md +71 -0
  134. package/docs/INTENT_NATIVE_EDITING.md +79 -0
  135. package/docs/RELEASE.md +59 -0
  136. package/docs/SCRIPT_AUTHORING.md +489 -0
  137. package/node_modules/@mobileaidev/segmented-fact-store-native/LICENSE +201 -0
  138. package/node_modules/@mobileaidev/segmented-fact-store-native/NOTICE +7 -0
  139. package/node_modules/@mobileaidev/segmented-fact-store-native/binding.gyp +36 -0
  140. package/node_modules/@mobileaidev/segmented-fact-store-native/bindings/node/sfs_node.c +597 -0
  141. package/node_modules/@mobileaidev/segmented-fact-store-native/include/sfs.h +178 -0
  142. package/node_modules/@mobileaidev/segmented-fact-store-native/index.js +5 -0
  143. package/node_modules/@mobileaidev/segmented-fact-store-native/package.json +24 -0
  144. package/node_modules/@mobileaidev/segmented-fact-store-native/src/sfs.c +2349 -0
  145. package/package.json +60 -5
  146. package/runtime/ios-wda/AABWDABinding.h +19 -0
  147. package/runtime/ios-wda/AABWDABinding.m +97 -0
  148. package/runtime/ios-wda/AABWDAExecution.h +26 -0
  149. package/runtime/ios-wda/AABWDAExecution.m +172 -0
  150. package/runtime/ios-wda/AABWDAIntegration.h +71 -0
  151. package/runtime/ios-wda/AABWDAManagedRoutes.h +392 -0
  152. package/runtime/ios-wda/AABWDAReceiptStore.h +10 -0
  153. package/runtime/ios-wda/AABWDAReceiptStore.m +116 -0
  154. package/runtime/uia/ai-app-bridge-uia.jar +0 -0
  155. package/runtime/uia/manifest.json +22 -0
  156. package/skills/ai-app-bridge-use/SKILL.md +23 -360
@@ -0,0 +1,54 @@
1
+ 'use strict';
2
+
3
+ const { CommandError } = require('./command-errors');
4
+ const { currentExecution } = require('./shared-kernel/execution-scope');
5
+
6
+ const schemaVersion = 'aab.ios-runtime/v1';
7
+ const sdkCommands = new Set(['ios-status', 'ios-tree', 'ios-logs', 'ios-network', 'ios-state', 'ios-events',
8
+ 'ios-h5-dom', 'ios-h5-eval', 'ios-h5-click', 'ios-h5-input', 'ios-h5-scroll', 'ios-flutter-tree', 'ios-flutter-nodes', 'ios-flutter-action',
9
+ 'ios-tap-flutter', 'ios-input-flutter-text', 'ios-scroll-flutter', 'ios-flutter-back', 'ios-flutter-hide-keyboard']);
10
+ const fields = ['schemaVersion', 'bundleId', 'runtimeEpoch', 'processId', 'port'];
11
+
12
+ function bindingFailure(code, message) {
13
+ const dispatched = currentExecution()?.dispatched === true;
14
+ return new CommandError(code, message, { dispatched, ambiguous: dispatched });
15
+ }
16
+
17
+ function runtimeBinding(value, minimumPort = 1) {
18
+ if (!value || value.schemaVersion !== schemaVersion || typeof value.bundleId !== 'string'
19
+ || !/^[A-Za-z0-9_.-]{1,255}$/.test(value.bundleId)
20
+ || typeof value.runtimeEpoch !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(value.runtimeEpoch)
21
+ || !Number.isSafeInteger(value.processId) || value.processId < 1
22
+ || !Number.isInteger(value.port) || value.port < minimumPort || value.port > 65535) {
23
+ throw bindingFailure('invalid_ios_runtime_binding', `Expected a ${schemaVersion} identity with bundleId, runtimeEpoch, processId and port.`);
24
+ }
25
+ return Object.freeze(Object.fromEntries(fields.map(field => [field, value[field]])));
26
+ }
27
+
28
+ function descriptorBinding(value, bundleId) {
29
+ const binding = runtimeBinding(value, value?.ok === false ? 0 : 1);
30
+ if (value.ok !== true) throw bindingFailure('ios_runtime_not_ready',
31
+ `The selected App has not published a ready runtime descriptor${typeof value.error === 'string' ? `: ${value.error}` : '.'}`);
32
+ if (binding.bundleId !== bundleId) throw bindingFailure('ios_runtime_binding_mismatch', 'The App container descriptor does not match the requested bundleId.');
33
+ return binding;
34
+ }
35
+
36
+ function bindingHeaders(binding) {
37
+ return {
38
+ 'X-AAB-Runtime-Schema': binding.schemaVersion,
39
+ 'X-AAB-Bundle-Id': binding.bundleId,
40
+ 'X-AAB-Runtime-Epoch': binding.runtimeEpoch,
41
+ 'X-AAB-Process-Id': String(binding.processId),
42
+ 'X-AAB-Runtime-Port': String(binding.port),
43
+ };
44
+ }
45
+
46
+ function assertRuntimeResponse(response, expected) {
47
+ const actual = runtimeBinding(response?.runtimeBinding);
48
+ if (fields.some(field => actual[field] !== expected[field])) {
49
+ throw bindingFailure('ios_runtime_binding_mismatch', 'The HTTP endpoint does not match the runtime copied from the selected device and App.');
50
+ }
51
+ if (typeof response.ok !== 'boolean') throw bindingFailure('invalid_ios_runtime_response', 'The bound runtime response must declare ok as a boolean.');
52
+ }
53
+
54
+ module.exports = { sdkCommands, descriptorBinding, bindingHeaders, assertRuntimeResponse, bindingFailure };
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ const { randomUUID } = require('node:crypto');
4
+ const { isDeepStrictEqual } = require('node:util');
5
+ const managed = require('./shared-kernel/managed-sdk-execution');
6
+ const { runDeviceEffect } = require('./shared-kernel/device-mutation-lease');
7
+ const { runExecution } = require('./shared-kernel/execution-scope');
8
+ const { iosDeviceKey, lookupCompletion } = require('./ios-execution');
9
+
10
+ const schema = 'aab.wda-execution/v1';
11
+ function proof(result, identity, target) {
12
+ const terminal = managed.terminalReceipt(schema, result, identity) ? result : result?.cancellation?.executionResult;
13
+ if (terminal && !isDeepStrictEqual(terminal.execution?.target, target)) return null;
14
+ return managed.settlementProof('ios-wda', schema, result, identity);
15
+ }
16
+ function completionPort(port) { return { get: route => port.request('GET', route, null) }; }
17
+
18
+ async function executeWDAAction({ port, args, operation, selected, payload = {} }) {
19
+ const identity = { actionId: args.runtimeActionId ?? args.requestId ?? randomUUID(), runtimeEpoch: port.runtimeBinding.runtimeEpoch };
20
+ const target = { ...selected, runnerBundleId: port.runtimeBinding.bundleId, operation };
21
+ const connection = { deviceId: port.device.udid, wdaRunnerBundleId: args.wdaRunnerBundleId,
22
+ ...Object.fromEntries(['wdaUrl', 'devicectl'].filter(key => args[key] !== undefined).map(key => [key, args[key]])) };
23
+ return runDeviceEffect({ kind: 'ios-wda', ...identity, target: connection, executionTarget: target }, async () => {
24
+ const result = await managed.executeManagedAction({ kind: 'wda', schema,
25
+ runtime: { runtimeEpoch: identity.runtimeEpoch, executionSchema: port.status.executionSchema },
26
+ payload: { operation, target: selected, payload }, actionId: identity.actionId, timeoutMs: args.timeoutMs ?? 30000,
27
+ send: async request => {
28
+ const reply = await port.request('POST', '/aab/action', request, selected);
29
+ if (managed.terminalReceipt(schema, reply, identity) && !isDeepStrictEqual(reply.execution.target, target)) {
30
+ return { ok: false, error: 'ios_wda_completion_target_mismatch', settled: false, dispatched: null, ambiguous: true };
31
+ }
32
+ return reply;
33
+ },
34
+ cancel: original => runExecution({ timeoutMs: 4000 }, async () => {
35
+ const cancelled = await port.request('POST', '/aab/execution/cancel', original);
36
+ if (cancelled.ok !== true) return cancelled;
37
+ const found = await lookupCompletion(completionPort(port), 'wda', original, cancelled);
38
+ if (found.executionResult && !isDeepStrictEqual(found.executionResult.execution?.target, target))
39
+ return { ok: false, error: 'ios_wda_completion_target_mismatch', settled: false };
40
+ return found;
41
+ }),
42
+ });
43
+ return { ...result, executionReceipt: proof(result, identity, target) };
44
+ }, result => result.executionReceipt);
45
+ }
46
+
47
+ async function reconcileWDA({ lease, device, args, createPort }) {
48
+ return lease.reconcile(iosDeviceKey(device), async pending => {
49
+ if (pending.kind !== 'ios-wda' || pending.target?.deviceId !== device.udid
50
+ || pending.target?.wdaRunnerBundleId !== args.wdaRunnerBundleId
51
+ || pending.executionTarget?.runnerBundleId !== args.wdaRunnerBundleId
52
+ || typeof pending.actionId !== 'string' || typeof pending.runtimeEpoch !== 'string')
53
+ return { settled: false, error: 'ios_wda_original_identity_required' };
54
+ const identity = { actionId: pending.actionId, runtimeEpoch: pending.runtimeEpoch };
55
+ try {
56
+ const target = { ...pending.target, timeoutMs: args.timeoutMs ?? 30000 };
57
+ for (const key of ['wdaUrl', 'devicectl']) if (args[key] !== undefined) target[key] = args[key];
58
+ const port = await createPort(target);
59
+ const found = await lookupCompletion(completionPort(port), 'wda', identity);
60
+ if (found.ok !== true || found.receipt?.committed !== true
61
+ || !Number.isSafeInteger(found.receipt.sequence) || found.receipt.sequence < 1
62
+ || !/^[a-f0-9]{64}$/.test(found.receipt.sha256))
63
+ return { settled: false, error: found.error || 'ios_wda_durable_completion_required' };
64
+ return proof(found.executionResult, identity, pending.executionTarget)
65
+ || { settled: false, error: 'ios_wda_completion_target_mismatch' };
66
+ } catch (error) { return { settled: false, error: error.code || 'ios_wda_completion_query_failed', message: error.message }; }
67
+ });
68
+ }
69
+
70
+ module.exports = { executeWDAAction, reconcileWDA, completionPort };
@@ -0,0 +1,98 @@
1
+ 'use strict';
2
+
3
+ const { CommandError } = require('./command-errors');
4
+ const { currentExecution, checkExecution } = require('./shared-kernel/execution-scope');
5
+
6
+ const fields = ['schemaVersion', 'bundleId', 'runtimeEpoch', 'processId', 'port'];
7
+ const schemaVersion = 'aab.ios-wda/v1';
8
+ const commands = new Set(['ios-wda-status', 'ios-wda-session', 'ios-uia-tree', 'ios-tap', 'ios-input', 'ios-swipe', 'ios-set-orientation', 'ios-tap-native', 'ios-input-native-text']);
9
+
10
+ function fail(code, message, details) {
11
+ const dispatched = currentExecution()?.dispatched === true;
12
+ return new CommandError(code, message, { dispatched, ambiguous: dispatched, ...(details ? { details } : {}) });
13
+ }
14
+
15
+ function binding(value) {
16
+ if (value?.schemaVersion !== schemaVersion || typeof value?.bundleId !== 'string' || !/^[A-Za-z0-9_.-]{1,255}$/.test(value.bundleId)
17
+ || typeof value?.runtimeEpoch !== 'string' || !/^[A-Za-z0-9_-]{1,128}$/.test(value.runtimeEpoch) || !Number.isSafeInteger(value?.processId)
18
+ || value.processId < 1 || !Number.isInteger(value?.port) || value.port < 1 || value.port > 65535) {
19
+ throw fail('invalid_ios_wda_binding', `Expected ${schemaVersion} Runner identity.`);
20
+ }
21
+ return Object.freeze(Object.fromEntries(fields.map(key => [key, value[key]])));
22
+ }
23
+
24
+ function target(value, session = false) {
25
+ if (typeof value?.bundleId !== 'string' || !value.bundleId || !Number.isSafeInteger(value?.processId) || value.processId < 1
26
+ || (session && (typeof value.sessionId !== 'string' || !value.sessionId))) throw fail('invalid_ios_wda_target', 'WDA must identify the exact foreground App process and session.');
27
+ return Object.freeze({ bundleId: value.bundleId, processId: value.processId, ...(session ? { sessionId: value.sessionId } : {}) });
28
+ }
29
+
30
+ async function openWdaPort(provider, args, device) {
31
+ if (!args.deviceId || !args.wdaRunnerBundleId) throw fail('ios_wda_target_required', 'WDA requires deviceId and the actual wdaRunnerBundleId from ios-setup.');
32
+ device ??= await provider.requireDevice(args);
33
+ if (![device.identifier, device.udid].includes(args.deviceId) || device.developerModeStatus !== 'enabled'
34
+ || device.ddiServicesAvailable !== true || device.tunnelState !== 'connected') throw fail('ios_tunnel_unavailable', 'The selected WDA device must have a connected developer tunnel.');
35
+ const descriptor = await provider.readContainerDescriptor(args, device, args.wdaRunnerBundleId, 'ai_app_bridge_wda.json');
36
+ const runtime = binding(descriptor);
37
+ if (descriptor.ok !== true || runtime.bundleId !== args.wdaRunnerBundleId) throw fail('ios_wda_binding_mismatch', 'The selected Runner container has no matching ready WDA descriptor.');
38
+ let url;
39
+ try {
40
+ const host = device.tunnelIPAddress;
41
+ if (!args.wdaUrl && !host) throw new Error('No developer tunnel IP');
42
+ url = new URL(args.wdaUrl ?? `http://${host.includes(':') ? `[${host}]` : host}:${runtime.port}`);
43
+ if (!['http:', 'https:'].includes(url.protocol) || url.username || url.password || url.search || url.hash) throw new Error('Invalid WDA URL');
44
+ } catch (error) { throw fail('invalid_ios_wda_url', error.message); }
45
+ const baseUrl = url.href.replace(/\/$/, '');
46
+ const headers = { 'X-AAB-WDA-Schema': runtime.schemaVersion, 'X-AAB-WDA-Bundle-Id': runtime.bundleId,
47
+ 'X-AAB-WDA-Runtime-Epoch': runtime.runtimeEpoch, 'X-AAB-WDA-Process-Id': String(runtime.processId), 'X-AAB-WDA-Port': String(runtime.port) };
48
+ let completedMutation = false;
49
+ async function request(method, route, body, selected, mutation = method !== 'GET') {
50
+ const boundHeaders = { ...headers, ...(selected ? { 'X-AAB-WDA-Target-Bundle-Id': selected.bundleId,
51
+ 'X-AAB-WDA-Target-Process-Id': String(selected.processId), ...(selected.sessionId ? { 'X-AAB-WDA-Session-Id': selected.sessionId } : {}) } : {}) };
52
+ let response;
53
+ // A managed event's response arrives from its original XCTest completion.
54
+ // Its wait must use the admitted action budget, not the shorter read budget.
55
+ const timeoutMs = route === '/aab/action' ? body.execution.timeoutMs : args.timeoutMs ?? 5000;
56
+ try { response = await provider.httpRequest(method, `${baseUrl}${route}`, body, { headers: boundHeaders, timeoutMs, mutation }); }
57
+ catch (error) {
58
+ if (!error.response) throw error;
59
+ response = error.response;
60
+ }
61
+ const actual = binding(response?.wdaBinding);
62
+ if (fields.some(key => actual[key] !== runtime[key])) throw fail('ios_wda_binding_mismatch', 'WDA response does not belong to the Runner instance read from the selected device.');
63
+ if (!Object.hasOwn(response, 'value')) throw fail('invalid_ios_wda_response', 'WDA response is missing its value.');
64
+ if (route === '/aab/status' || route === '/aab/action' || route.startsWith('/aab/execution/')) {
65
+ if (typeof response.value?.ok === 'boolean') {
66
+ checkExecution();
67
+ return response.value;
68
+ }
69
+ }
70
+ const error = response.value?.error;
71
+ if (error !== undefined) {
72
+ if (typeof error !== 'string' || !error) throw fail('invalid_ios_wda_response', 'WDA error must be a nonempty string.');
73
+ const rejected = /^ios_wda_|^invalid_ios_wda_/.test(error) && response.value.dispatched === false && response.value.ambiguous === false;
74
+ throw new CommandError(rejected ? error : 'ios_wda_command_failed', response.value.message || error,
75
+ { dispatched: rejected ? completedMutation : currentExecution()?.dispatched === true,
76
+ ambiguous: rejected ? false : currentExecution()?.dispatched === true, details: { wdaError: error, response } });
77
+ }
78
+ checkExecution();
79
+ if (mutation) completedMutation = true;
80
+ return response.value;
81
+ }
82
+ async function session(required = true) {
83
+ const status = await request('GET', '/aab/session', null);
84
+ if (!status || !Object.hasOwn(status, 'session')) throw fail('invalid_ios_wda_session', 'WDA did not return session state.');
85
+ if (!required) return status;
86
+ if (status.session === null) throw fail('ios_wda_session_required', 'Create a session with ios-wda-session before reading or acting.');
87
+ const selected = target(status.session, true);
88
+ if (selected.bundleId !== args.bundleId || selected.sessionId !== args.wdaSessionId) throw fail('ios_wda_session_changed', 'The requested App/session does not match the bound WDA session.');
89
+ return selected;
90
+ }
91
+ // An old unmodified WDA cannot ignore the binding headers and receive a write.
92
+ const status = await request('GET', '/aab/status', null);
93
+ if (status?.ok !== true || status.ready !== true || status.executionSchema !== 'aab.wda-execution/v1')
94
+ throw fail('ios_wda_not_ready', 'The selected WDA Runner must expose a ready managed execution store.');
95
+ return { device, baseUrl, runtimeBinding: runtime, request, session, status };
96
+ }
97
+
98
+ module.exports = { commands, openWdaPort, binding, target };
@@ -0,0 +1,79 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs');
4
+ const path = require('node:path');
5
+ const { createHash } = require('node:crypto');
6
+ const { CommandError } = require('./command-errors');
7
+
8
+ const supportedVersion = '14.1.1';
9
+ const directories = ['Configurations', 'PrivateHeaders', 'Scripts', 'WebDriverAgentLib', 'WebDriverAgentRunner', 'WebDriverAgentTests', 'WebDriverAgent.xcodeproj'];
10
+
11
+ function wdaBuildEnvironment(environment = process.env) {
12
+ const result = { ...environment };
13
+ // A device build must use Xcode's selected SDK, never injected macOS
14
+ // header/library search paths from the Host shell.
15
+ for (const name of ['CPATH', 'C_INCLUDE_PATH', 'CPLUS_INCLUDE_PATH', 'OBJC_INCLUDE_PATH', 'LIBRARY_PATH', 'SDKROOT']) delete result[name];
16
+ return result;
17
+ }
18
+
19
+ function replaceOnce(source, before, after, file) {
20
+ if (source.split(before).length !== 2) throw new CommandError('ios_wda_source_mismatch', `The pinned WDA integration point changed: ${file}.`);
21
+ return source.replace(before, after);
22
+ }
23
+
24
+ function prepareWdaProject({ destination, packageDirectory = path.dirname(require.resolve('appium-webdriveragent/package.json')) }) {
25
+ const manifest = JSON.parse(fs.readFileSync(path.join(packageDirectory, 'package.json'), 'utf8'));
26
+ if (manifest.version !== supportedVersion) throw new CommandError('ios_wda_version_unsupported', `The prepared WDA runtime requires appium-webdriveragent ${supportedVersion}.`);
27
+ fs.mkdirSync(destination, { recursive: true });
28
+ if (fs.readdirSync(destination).length) throw new CommandError('ios_wda_destination_not_empty', 'WDA preparation requires an empty destination.');
29
+ for (const name of directories) fs.cpSync(path.join(packageDirectory, name), path.join(destination, name), { recursive: true });
30
+ fs.copyFileSync(path.join(packageDirectory, 'LICENSE'), path.join(destination, 'LICENSE.upstream'));
31
+ const sourceRoot = path.join(__dirname, '..', 'runtime', 'ios-wda');
32
+ for (const name of ['AABWDABinding.h', 'AABWDABinding.m', 'AABWDAIntegration.h', 'AABWDAManagedRoutes.h',
33
+ 'AABWDAReceiptStore.h', 'AABWDAReceiptStore.m', 'AABWDAExecution.h', 'AABWDAExecution.m']) {
34
+ fs.copyFileSync(path.join(sourceRoot, name), path.join(destination, 'WebDriverAgentLib', 'Routing', name));
35
+ }
36
+ const nativeRoot = path.dirname(require.resolve('@mobileaidev/segmented-fact-store-native/package.json'));
37
+ const nativeCore = { version: JSON.parse(fs.readFileSync(path.join(nativeRoot, 'package.json'))).version, files: [] };
38
+ for (const relative of ['include/sfs.h', 'src/sfs.c']) {
39
+ const bytes = fs.readFileSync(path.join(nativeRoot, relative));
40
+ fs.writeFileSync(path.join(destination, 'WebDriverAgentLib', 'Routing', path.basename(relative)), bytes);
41
+ nativeCore.files.push({ path: relative, sha256: createHash('sha256').update(bytes).digest('hex') });
42
+ }
43
+ const transformations = [];
44
+ function patch(relative, edits) {
45
+ const filename = path.join(destination, relative);
46
+ const original = fs.readFileSync(filename, 'utf8');
47
+ let content = original;
48
+ for (const [before, after] of edits) content = replaceOnce(content, before, after, relative);
49
+ fs.writeFileSync(filename, content);
50
+ const digest = value => createHash('sha256').update(value).digest('hex');
51
+ transformations.push({ path: relative, beforeSha256: digest(original), afterSha256: digest(content) });
52
+ }
53
+ patch('WebDriverAgentLib/Routing/FBWebServer.m', [
54
+ ['#import "XCUIDevice+FBHelpers.h"', '#import "XCUIDevice+FBHelpers.h"\n#import "AABWDAIntegration.h"'],
55
+ ['[self.server setRouteQueue:dispatch_get_main_queue()];', '[self.server setRouteQueue:nil];'],
56
+ [' [self registerRouteHandlers:[self.class collectCommandHandlerClasses]];', ' AABWDARegisterControl(self.server);\n [self registerRouteHandlers:[self.class collectCommandHandlerClasses]];'],
57
+ [' NSString *serverHost = bindingIP', ' NSError *bindingError;\n if (![AABWDABinding.shared publishPort:self.server.port error:&bindingError]) {\n [FBLogger logFmt:@"AAB WDA descriptor publication failed: %@", bindingError];\n }\n\n NSString *serverHost = bindingIP'],
58
+ [' NSDictionary *arguments = [NSJSONSerialization JSONObjectWithData:request.body',
59
+ ' dispatch_sync(dispatch_get_main_queue(), ^{\n NSString *admissionError = AABWDAAdmission(route, request.headers, request.params, request.body);\n if (admissionError) { [AABWDAReject(admissionError) dispatchWithResponse:response]; return; }\n NSDictionary *arguments = [NSJSONSerialization JSONObjectWithData:request.body'],
60
+ [' [strongSelf handleException:exception forResponse:response];\n }\n }];',
61
+ ' [strongSelf handleException:exception forResponse:response];\n }\n });\n }];'],
62
+ [' [response respondWithString:@"Shutting down"];\n [strongSelf.delegate webServerDidRequestShutdown:strongSelf];',
63
+ ' [AABWDAReject(@"ios_wda_route_unsupported") dispatchWithResponse:response];'],
64
+ ]);
65
+ patch('WebDriverAgentLib/Routing/FBResponsePayload.m', [
66
+ ['#import "FBResponsePayload.h"', '#import "FBResponsePayload.h"\n#import "AABWDABinding.h"'],
67
+ [' response[@"sessionId"] = [FBSession activeSession].identifier ?: NSNull.null;', ' response[@"wdaBinding"] = AABWDABinding.shared.identity;\n response[@"sessionId"] = [FBSession activeSession].identifier ?: NSNull.null;'],
68
+ ]);
69
+ patch('WebDriverAgentLib/Categories/XCUIApplication+FBHelpers.m', [
70
+ ['\n info[@"rawIdentifier"] = FBValueOrNull([snapshot.identifier isEqual:@""] ? nil : snapshot.identifier);',
71
+ '\n info[@"rawIdentifier"] = FBValueOrNull([snapshot.identifier isEqual:@""] ? nil : snapshot.identifier);\n info[@"elementId"] = FBValueOrNull([FBXCElementSnapshotWrapper ensureWrapped:snapshot].wdUID);'],
72
+ ]);
73
+ const result = { schemaVersion: 'aab.ios-wda-project/v1', upstreamVersion: supportedVersion,
74
+ projectPath: path.join(destination, 'WebDriverAgent.xcodeproj'), transformations, nativeCore };
75
+ fs.writeFileSync(path.join(destination, 'aab-wda-project.json'), JSON.stringify(result, null, 2) + '\n');
76
+ return result;
77
+ }
78
+
79
+ module.exports = { supportedVersion, prepareWdaProject, wdaBuildEnvironment };