@deeeed/metamask-harness 0.73.0 → 0.75.0

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 (62) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +1 -1
  3. package/adapters/shared/ensure-runner-deps.sh +15 -3
  4. package/dist/action-values.js +15 -0
  5. package/dist/adapters/core/actions.js +20 -0
  6. package/dist/adapters/core/live-script-env.js +36 -0
  7. package/dist/adapters/core/run.js +54 -0
  8. package/dist/adapters/core/surface.js +8 -1
  9. package/dist/adapters/extension/action-inputs.js +206 -0
  10. package/dist/adapters/extension/actions.js +51 -0
  11. package/dist/adapters/extension/console-capture.js +13 -1
  12. package/dist/adapters/extension/launch.js +315 -0
  13. package/dist/adapters/extension/launched-browser.js +51 -0
  14. package/dist/adapters/extension/run.js +105 -0
  15. package/dist/adapters/extension/surface.js +17 -1
  16. package/dist/adapters/mobile/actions.js +54 -0
  17. package/dist/adapters/mobile/launch.js +192 -0
  18. package/dist/adapters/mobile/lifecycle.js +300 -0
  19. package/dist/adapters/mobile/run.js +156 -0
  20. package/dist/adapters/mobile/runtime-identity.js +34 -0
  21. package/dist/adapters/mobile/surface.js +19 -1
  22. package/dist/adapters/mobile/ui.js +701 -0
  23. package/dist/adapters/performance/cdp-trace.js +24 -6
  24. package/dist/adapters/performance/device-platform.js +10 -0
  25. package/dist/adapters/registry.js +17 -0
  26. package/dist/adapters/surface.js +6 -11
  27. package/dist/adapters/terminal/actions.js +43 -0
  28. package/dist/adapters/terminal/console-capture.js +14 -1
  29. package/dist/adapters/terminal/launch.js +85 -0
  30. package/dist/adapters/terminal/run.js +30 -0
  31. package/dist/adapters/terminal/surface.js +9 -1
  32. package/dist/adapters.js +32 -824
  33. package/dist/cli-commands.js +1 -0
  34. package/dist/command-contract.js +3 -0
  35. package/dist/commands/call.js +2 -5
  36. package/dist/commands/launch/device.js +342 -0
  37. package/dist/commands/launch/index.js +9 -942
  38. package/dist/commands/launch/quote.js +7 -0
  39. package/dist/commands/pin.js +226 -0
  40. package/dist/commands/review.js +3 -1
  41. package/dist/commands/run-engine.js +38 -138
  42. package/dist/commands/run-report.js +4 -48
  43. package/dist/commands/run.js +6 -48
  44. package/dist/commands/shared.js +2 -2
  45. package/dist/doctor.js +3 -1
  46. package/dist/execution-provenance.js +3 -2
  47. package/dist/harness-pin.js +10 -0
  48. package/dist/heal-bounds.js +3 -2
  49. package/dist/live-adapter-contract.js +8 -34
  50. package/dist/manifest.js +3 -5
  51. package/dist/metamask-action-validation.js +5 -196
  52. package/dist/mm-harness-cli.js +27 -2
  53. package/dist/network-observation.js +10 -3
  54. package/dist/performance-observation.js +15 -22
  55. package/dist/pr-body/render.js +3 -1
  56. package/dist/recording-target.js +4 -39
  57. package/dist/run-diagnostics.js +21 -60
  58. package/dist/run-recording.js +5 -4
  59. package/dist/runner.js +20 -368
  60. package/docs/CONTRIBUTING.md +20 -3
  61. package/package.json +2 -1
  62. package/scripts/completions.sh +2 -1
package/dist/adapters.js CHANGED
@@ -1,36 +1,14 @@
1
1
  import http from "node:http";
2
2
  import { createHash } from "node:crypto";
3
- import { execFile } from "node:child_process";
4
- import { promisify } from "node:util";
5
3
  import { compatibilityMode, fixtureSummary, repoShape } from "./doctor.js";
6
4
  import {
7
5
  runLiveAdapterScript
8
6
  } from "./live-adapter-contract.js";
9
- import { withExtensionPage } from "../library/actions/extension/platform/cdp.mjs";
10
- import { bridgeCommand, evalAsync, evalSync, MOBILE_BRIDGE_ERROR_CODES, selectBridgeStatusEntry, simulatorScreenshot } from "../library/actions/mobile/platform/bridge.mjs";
11
- import { observeNativeUi } from "../library/actions/mobile/platform/observe-ui.mjs";
12
- import { nativeAgentDeviceStateDir } from "../library/actions/mobile/platform/native-session-name.mjs";
13
- import { resolveMobileToolPath } from "../library/actions/mobile/platform/tool-paths.mjs";
14
7
  import { resolveWalletImportCredentials, validateWalletImportOptions } from "../library/actions/shared/wallet/import-source.mjs";
8
+ import { isRecord, traceText } from "./action-values.js";
9
+ import { getAdapterSurface } from "./adapters/surface.js";
15
10
  import { handleRunNetworkAction } from "./network-observation.js";
16
11
  import { handleRunPerformanceAction } from "./performance-observation.js";
17
- const execFileAsync = promisify(execFile);
18
- const NATIVE_PROVIDER_UI_ACTIONS = /* @__PURE__ */ new Set([
19
- "ui.press",
20
- "ui.key_press",
21
- "ui.set_input",
22
- "ui.scroll",
23
- "ui.swipe",
24
- "ui.pan",
25
- "ui.drag",
26
- "ui.long_press",
27
- "ui.wait_for",
28
- "ui.screenshot",
29
- "ui.capture_surface"
30
- ]);
31
- function sleep(ms) {
32
- return new Promise((resolve) => setTimeout(resolve, ms));
33
- }
34
12
  function simpleAdapter(action, executor) {
35
13
  return {
36
14
  action,
@@ -93,9 +71,6 @@ const LIVE_ONLY_ACTIONS = /* @__PURE__ */ new Set([
93
71
  "metamask.perps.teardown_state",
94
72
  "metamask.perps.capture_performance"
95
73
  ]);
96
- const CORE_ONLY_PERPS_ACTIONS = /* @__PURE__ */ new Set([
97
- "metamask.perps.read_account"
98
- ]);
99
74
  const LIVE_ONLY_WALLET_ACTIONS = /* @__PURE__ */ new Set([
100
75
  "metamask.wallet.setup",
101
76
  "metamask.wallet.import",
@@ -118,10 +93,10 @@ const LIVE_ONLY_APP_ACTIONS = /* @__PURE__ */ new Set([
118
93
  "app.performance_assert"
119
94
  ]);
120
95
  function requiresLiveAdapter(platform, action) {
121
- return LIVE_ONLY_ACTIONS.has(action) || platform === "core" && CORE_ONLY_PERPS_ACTIONS.has(action) || LIVE_ONLY_WALLET_ACTIONS.has(action) || LIVE_ONLY_FEATURE_FLAG_ACTIONS.has(action) || LIVE_ONLY_APP_ACTIONS.has(action);
96
+ return LIVE_ONLY_ACTIONS.has(action) || Boolean(getAdapterSurface(platform).actions.liveOnly?.includes(action)) || LIVE_ONLY_WALLET_ACTIONS.has(action) || LIVE_ONLY_FEATURE_FLAG_ACTIONS.has(action) || LIVE_ONLY_APP_ACTIONS.has(action);
122
97
  }
123
98
  function liveRuntimeConfigured(platform, node) {
124
- if (platform === "mobile" || platform === "core" || platform === "terminal") return true;
99
+ if (!getAdapterSurface(platform).actions.liveNeedsCdpPort) return true;
125
100
  return Boolean(node.cdp_port ?? process.env.CDP_PORT ?? process.env.RECIPE_CDP_PORT);
126
101
  }
127
102
  async function runLiveFirst(platform, action, node, context, forceLive = false, preparedLiveAdapters) {
@@ -204,35 +179,6 @@ async function semanticResult(platform, action, node, context, forceLive = false
204
179
  }
205
180
  };
206
181
  }
207
- function isRecord(value) {
208
- return Boolean(value && typeof value === "object" && !Array.isArray(value));
209
- }
210
- function optionalScalarText(value, label) {
211
- if (value === void 0 || value === null) return void 0;
212
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
213
- return String(value);
214
- }
215
- throw new Error(`${label} must be a string, number, or boolean.`);
216
- }
217
- function scalarText(value, label, fallback) {
218
- return optionalScalarText(value, label) ?? fallback;
219
- }
220
- function firstScalarText(record, keys, label, fallback) {
221
- for (const key of keys) {
222
- const value = optionalScalarText(record[key], `${label}.${key}`);
223
- if (value !== void 0) return value;
224
- }
225
- if (fallback !== void 0) return fallback;
226
- throw new Error(`${label} requires one of: ${keys.join(", ")}.`);
227
- }
228
- function traceText(value) {
229
- if (value === void 0 || value === null) return "";
230
- if (value instanceof Error) return value.message;
231
- if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
232
- return String(value);
233
- }
234
- return JSON.stringify(value);
235
- }
236
182
  function probeHttpJson(url, timeoutMs = 1e3) {
237
183
  return new Promise((resolve) => {
238
184
  const request = http.get(url, { timeout: timeoutMs }, (response) => {
@@ -280,67 +226,35 @@ function createMetaMaskSemanticAdapters(platform, declaredCustomActions = [], pr
280
226
  ];
281
227
  const bundledActions = [
282
228
  ...walletActions,
283
- ...platform !== "core" ? [
284
- "app.network_capture",
285
- "app.network_assert",
286
- "app.performance_capture",
287
- "app.performance_assert"
288
- ] : [],
289
229
  "metamask.assets.read_visible_state",
290
230
  "metamask.assets.open_details",
291
231
  "metamask.assets.read_details",
292
232
  "metamask.assets.verify_sorting",
293
233
  "metamask.assets.set_token_visibility",
294
234
  "metamask.assets.import_custom_token",
295
- ...platform === "extension" ? [
296
- "metamask.assets.prepare_send",
297
- "metamask.assets.review_send",
298
- "metamask.assets.set_custom_gas",
299
- "metamask.assets.select_network_scope"
300
- ] : [],
301
235
  "metamask.wallet.lock",
302
236
  "metamask.networks.read_visible_state",
303
237
  "metamask.networks.add_custom",
304
- ...platform === "extension" ? ["metamask.networks.add_chainlist"] : [],
305
238
  "metamask.networks.remove_custom",
306
239
  "metamask.perps.read_visible_state",
307
240
  "metamask.perps.search_markets",
308
241
  "metamask.perps.set_market_favorite",
309
- ...platform === "extension" ? ["metamask.perps.compare_provider_market"] : [],
310
- ...platform === "extension" ? ["metamask.perps.read_eligibility"] : [],
311
- ...platform === "extension" ? [
312
- "metamask.perps.open_position_action",
313
- "metamask.perps.edit_margin",
314
- "metamask.perps.update_position_tpsl",
315
- "metamask.perps.assert_visible_consistency"
316
- ] : [],
317
242
  "metamask.swap_bridge.read_visible_state",
318
243
  "metamask.swap_bridge.select_assets",
319
244
  "metamask.swap_bridge.set_amount",
320
245
  "metamask.swap_bridge.set_max_amount",
321
246
  "metamask.swap_bridge.set_slippage",
322
- ...platform === "mobile" ? ["metamask.swap_bridge.submit_transaction"] : [],
323
- ...platform === "extension" ? ["metamask.settings.set_basic_functionality"] : [],
324
247
  "metamask.perps.read_positions",
325
248
  "metamask.perps.ensure_positions",
326
249
  "metamask.perps.assert_positions",
327
250
  "metamask.perps.place_order",
328
- ...platform === "mobile" || platform === "core" ? ["metamask.perps.close_positions"] : [],
329
251
  "metamask.perps.read_orders",
330
252
  "metamask.perps.close_orders",
331
253
  "metamask.perps.ensure_orders",
332
254
  "metamask.perps.assert_orders",
333
- ...platform === "mobile" || platform === "core" ? ["metamask.perps.start_state"] : [],
334
255
  "metamask.perps.teardown_state",
335
256
  "metamask.perps.capture_performance",
336
- ...platform === "extension" || platform === "core" ? ["metamask.perps.read_snapshot"] : [],
337
- ...platform === "mobile" ? [
338
- "metamask.perps.clear_performance_caches",
339
- "metamask.perps.measure_homepage_visible",
340
- "metamask.perps.prepare_local_snapshot_endpoint"
341
- ] : [],
342
- // read_account is core-only: only the headless core adapter implements it.
343
- ...platform === "core" ? ["metamask.perps.read_account"] : []
257
+ ...getAdapterSurface(platform).actions.semantic
344
258
  ];
345
259
  const bundled = new Set(bundledActions);
346
260
  const actions = [.../* @__PURE__ */ new Set([...bundledActions, ...declaredCustomActions])];
@@ -358,734 +272,41 @@ function createMetaMaskSemanticAdapters(platform, declaredCustomActions = [], pr
358
272
  )
359
273
  );
360
274
  }
361
- function uiInputFor(context, input) {
362
- return {
363
- action: input.action,
364
- node: input.node,
365
- context: {
366
- nodeId: context.nodeId,
367
- projectRoot: context.projectRoot,
368
- artifactsDir: context.artifactsDir,
369
- env: context.env
370
- }
371
- };
372
- }
373
- function mobileProbeOutput(status, input, projectRoot) {
374
- const entries = Array.isArray(status) ? status : [status];
375
- const selected = selectBridgeStatusEntry(status, input) ?? null;
376
- const route = isRecord(selected) && isRecord(selected.route) ? selected.route : null;
377
- return {
378
- reachable: isRecord(selected) && selected.agenticPresent === true && Boolean(route),
379
- bridge: mobileBridgePath(projectRoot),
380
- targetCount: entries.filter((entry) => isRecord(entry)).length,
381
- deviceName: isRecord(selected) && typeof selected.deviceName === "string" ? selected.deviceName : null,
382
- routeName: route && typeof route.name === "string" ? route.name : null,
383
- accountPresent: isRecord(selected) && isRecord(selected.account)
384
- };
385
- }
386
- function mobileBridgePath(_projectRoot) {
387
- return process.env.METAMASK_RECIPE_MOBILE_BRIDGE_SCRIPT || "runner:adapters/mobile/bridge-runtime/cdp-bridge.cjs";
388
- }
389
- function resolveMobileWaitTarget(payload) {
390
- const testId = optionalScalarText(
391
- payload.test_id ?? payload.testID,
392
- "Mobile ui.wait_for.test_id"
393
- );
394
- const expectedText = optionalScalarText(payload.text, "ui.wait_for.text");
395
- if (!testId && !expectedText) {
396
- throw new Error(
397
- "Mobile ui.wait_for requires one of: test_id, testID, text."
398
- );
399
- }
400
- const expected = scalarText(
401
- payload.expected,
402
- "ui.wait_for.expected",
403
- "present"
404
- ).toLowerCase();
405
- const textMatch = scalarText(
406
- payload.text_match ?? payload.textMatch,
407
- "ui.wait_for.text_match",
408
- "contains"
409
- ).toLowerCase();
410
- if (!["contains", "exact"].includes(textMatch)) {
411
- throw new Error("ui.wait_for.text_match must be contains or exact.");
412
- }
413
- if (!testId && textMatch === "exact") {
414
- throw new Error(
415
- "Mobile text-only ui.wait_for supports text_match=contains; exact matching also requires test_id/testID."
416
- );
417
- }
418
- return {
419
- testId,
420
- expectedText,
421
- expected,
422
- textMatch,
423
- visibility: expected === "visible" || expected === "hidden" ? "viewport" : "tree"
424
- };
425
- }
426
- const RETRYABLE_MOBILE_WAIT_CODES = /* @__PURE__ */ new Set([
427
- MOBILE_BRIDGE_ERROR_CODES.NO_TARGET,
428
- MOBILE_BRIDGE_ERROR_CODES.CDP_TIMEOUT,
429
- MOBILE_BRIDGE_ERROR_CODES.WS_CLOSED,
430
- MOBILE_BRIDGE_ERROR_CODES.METRO_UNREACHABLE
431
- ]);
432
- function isRetryableMobileWaitError(error) {
433
- return Boolean(
434
- isRecord(error) && typeof error.code === "string" && RETRYABLE_MOBILE_WAIT_CODES.has(error.code)
435
- );
436
- }
437
- async function waitForMobileTarget(input, payload) {
438
- const timeoutMs = Number(payload.timeout_ms ?? payload.timeoutMs ?? 1e4);
439
- const deadline = Date.now() + timeoutMs;
440
- const { testId, expectedText, expected, textMatch, visibility } = resolveMobileWaitTarget(payload);
441
- const query = {
442
- ...testId ? { testId } : {},
443
- ...expectedText && textMatch === "contains" ? { textContains: expectedText } : {},
444
- visibility
445
- };
446
- const queryExpression = `(function(){
447
- const api = globalThis.__AGENTIC__;
448
- if (!api || typeof api.queryUiTarget !== 'function') {
449
- return Promise.resolve(JSON.stringify({ unsupported: true }));
450
- }
451
- return api.queryUiTarget(${JSON.stringify(query)}).then(function(result){
452
- return JSON.stringify(result);
453
- });
454
- })()`;
455
- const textExpression = testId ? `(function(){
456
- const api = globalThis.__AGENTIC__;
457
- if (!api?.getTextByTestId) return null;
458
- return api.getTextByTestId(${JSON.stringify(testId)});
459
- })()` : null;
460
- let lastResult = null;
461
- let lastText = null;
462
- let lastBridgeError = null;
463
- const expectsAbsent = expected === "absent" || expected === "hidden" || expected === "not_present";
464
- while (Date.now() <= deadline) {
465
- const remainingMs = Math.max(1, deadline - Date.now());
466
- const probeTimeoutMs = Math.min(5e3, remainingMs);
467
- const probeInput = {
468
- ...input,
469
- node: {
470
- ...input.node,
471
- bridge_timeout_ms: probeTimeoutMs,
472
- cdp_timeout_ms: probeTimeoutMs
473
- }
474
- };
475
- let result;
476
- try {
477
- result = await evalAsync(probeInput, queryExpression);
478
- } catch (error) {
479
- if (!isRetryableMobileWaitError(error)) throw error;
480
- lastBridgeError = error;
481
- await sleep(250);
482
- continue;
483
- }
484
- if (!isRecord(result) || result.unsupported === true) {
485
- throw new Error(
486
- "Mobile ui.wait_for requires the current __AGENTIC__.queryUiTarget bridge for text-only or viewport-visible assertions."
487
- );
488
- }
489
- lastResult = result;
490
- const present = result.present === true;
491
- const visible = result.visible === true;
492
- let textMatched = !expectedText || textMatch === "contains";
493
- if (expectedText && textMatch === "exact" && textExpression) {
494
- try {
495
- lastText = await evalSync(probeInput, textExpression);
496
- } catch (error) {
497
- if (!isRetryableMobileWaitError(error)) throw error;
498
- lastBridgeError = error;
499
- await sleep(250);
500
- continue;
501
- }
502
- textMatched = traceText(lastText) === expectedText;
503
- }
504
- const matched = expected === "visible" ? visible && textMatched : expected === "hidden" ? !visible : expectsAbsent ? !present : present && textMatched;
505
- if (matched) {
506
- return {
507
- matched: true,
508
- ...testId ? { testId } : {},
509
- ...expectedText ? { text: expectedText, textMatch } : {},
510
- expected,
511
- present,
512
- visible,
513
- visibility,
514
- ...isRecord(result.rect) ? { rect: result.rect } : {}
515
- };
516
- }
517
- await sleep(250);
518
- }
519
- const target = [
520
- testId ? `testID ${JSON.stringify(testId)}` : "",
521
- expectedText ? `text ${textMatch} ${JSON.stringify(expectedText)}` : ""
522
- ].filter(Boolean).join(" with ");
523
- const exactReason = textMatch === "exact" ? `; last text=${JSON.stringify(lastText)}` : "";
524
- const bridgeReason = lastBridgeError ? `; last bridge error=${traceText(lastBridgeError)}` : "";
525
- throw new Error(
526
- `Timed out waiting for mobile ${target} to be ${expected}; last result=${JSON.stringify(lastResult)}${exactReason}${bridgeReason}.`
527
- );
528
- }
529
- const MOBILE_BRIDGE_HANDLERS = {
530
- screenshot: handleMobileScreenshot,
531
- status: handleMobileStatus,
532
- navigate: handleMobileNavigate,
533
- press: handleMobilePress,
534
- keyPress: handleMobileKeyPress,
535
- setInput: handleMobileSetInput,
536
- scroll: handleMobileScroll,
537
- waitFor: handleMobileWaitFor,
538
- hud: handleMobileHud,
539
- observeUi: handleMobileObserveUi
540
- };
541
- function createMetaMaskMobileBridge() {
542
- return {
543
- async send(command, context) {
544
- const handler = MOBILE_BRIDGE_HANDLERS[command.command];
545
- if (!handler) {
546
- throw new Error(`React Native bridge command ${command.command} is not implemented by the MetaMask runner transport.`);
547
- }
548
- return handler(command.payload, context);
549
- }
550
- };
551
- }
552
- function mobileUiInput(context, command, payload) {
553
- return uiInputFor(context, { action: `ui.${command}`, node: payload });
554
- }
555
- async function handleMobileScreenshot(payload, context) {
556
- const input = mobileUiInput(context, "screenshot", payload);
557
- const relPath = scalarText(payload.path, "ui.screenshot.path", `screenshots/${context.nodeId}.png`);
558
- const artifact = await simulatorScreenshot(input, relPath);
559
- context.registerArtifact(artifact);
560
- return { captured: true, path: artifact.path, artifact };
561
- }
562
- async function handleMobileStatus(payload, context) {
563
- return bridgeCommand(mobileUiInput(context, "status", payload), ["status"]);
564
- }
565
- async function handleMobileObserveUi(payload, context) {
566
- return observeNativeUi(payload, context);
567
- }
568
- async function handleMobileNavigate(payload, context) {
569
- const live = await runLiveAdapterScript({
570
- platform: "mobile",
571
- action: "ui.navigate",
572
- node: payload,
573
- context
574
- });
575
- if (!live) throw new Error("ui.navigate requires library/actions/mobile/ui/navigate.mjs.");
576
- return isRecord(live.result) ? { ...live.result, liveAdapter: live.script } : live.result;
577
- }
578
- async function handleMobilePress(payload, context) {
579
- const identifiers = ["test_id", "testID", "selector"].map((field) => optionalScalarText(payload[field], `ui.press.${field}`)).filter((value) => value !== void 0);
580
- const text = optionalScalarText(payload.text, "ui.press.text");
581
- if (identifiers.length + Number(text !== void 0) !== 1) {
582
- throw new Error("ui.press requires exactly one of: test_id, testID, selector, text.");
583
- }
584
- const target = text ?? identifiers[0];
585
- if (target.trim() === "") throw new Error("ui.press target must be non-empty.");
586
- const longPress = payload.long_press === true;
587
- if (longPress && text !== void 0) {
588
- throw new Error("ui.press long_press=true requires test_id, testID, or selector targeting.");
589
- }
590
- const input = mobileUiInput(context, "press", payload);
591
- if (text !== void 0) return bridgeCommand(input, ["press-text", target]);
592
- return bridgeCommand(input, [longPress ? "long-press-test-id" : "press-test-id", target]);
593
- }
594
- async function handleMobileKeyPress(payload, context) {
595
- const key = scalarText(payload.key, "ui.key_press.key", "Enter");
596
- const input = mobileUiInput(context, "key_press", payload);
597
- return bridgeCommand(input, ["key-press", key]);
598
- }
599
- async function handleMobileSetInput(payload, context) {
600
- const input = mobileUiInput(context, "set_input", payload);
601
- const testId = firstScalarText(payload, ["test_id", "testID"], "ui.set_input");
602
- const value = scalarText(payload.value ?? payload.text, "ui.set_input.value", "");
603
- const result = await bridgeCommand(input, ["set-input", testId, value]);
604
- if (isRecord(result) && result.ok === false) {
605
- throw new Error(`ui.set_input failed for mobile testID ${testId}: ${traceText(result.error ?? result)}`);
606
- }
607
- return isRecord(result) ? result : { result, testId, value };
608
- }
609
- async function handleMobileScroll(payload, context) {
610
- const input = mobileUiInput(context, "scroll", payload);
611
- const testId = optionalScalarText(payload.test_id ?? payload.testID, "ui.scroll.test_id");
612
- const offset = scalarText(payload.offset ?? payload.delta_y ?? payload.deltaY, "ui.scroll.offset", "600");
613
- const intoView = payload.scroll_into_view === true || payload.into_view === true;
614
- const measureFromTestId = optionalScalarText(
615
- payload.measure_from_test_id ?? payload.measureFromTestId,
616
- "ui.scroll.measure_from_test_id"
617
- );
618
- const measureTargetTestId = optionalScalarText(
619
- payload.measure_target_test_id ?? payload.measureTargetTestId,
620
- "ui.scroll.measure_target_test_id"
621
- );
622
- const measureTargetText = optionalScalarText(
623
- payload.measure_target_text ?? payload.measureTargetText,
624
- "ui.scroll.measure_target_text"
625
- );
626
- const measureTargetCount = Number(Boolean(measureTargetTestId)) + Number(Boolean(measureTargetText));
627
- const measureRequiredPresentTestId = optionalScalarText(
628
- payload.measure_required_present_test_id ?? payload.measureRequiredPresentTestId,
629
- "ui.scroll.measure_required_present_test_id"
630
- );
631
- if (Boolean(measureFromTestId) !== (measureTargetCount === 1)) {
632
- throw new Error(
633
- "ui.scroll visible-transition measurement requires measure_from_test_id and exactly one of measure_target_test_id or measure_target_text."
634
- );
635
- }
636
- if (measureFromTestId && measureTargetCount === 1) {
637
- if (intoView) {
638
- throw new Error(
639
- "ui.scroll visible-transition measurement uses an explicit offset and does not support scroll_into_view."
640
- );
641
- }
642
- return measureMobileScrollTransition(input, {
643
- animated: payload.animated === true,
644
- fromTestId: measureFromTestId,
645
- offset: Number(offset),
646
- ...measureTargetTestId ? { targetTestId: measureTargetTestId } : {},
647
- ...measureTargetText ? { targetText: measureTargetText } : {},
648
- ...measureRequiredPresentTestId ? { requiredPresentTestId: measureRequiredPresentTestId } : {},
649
- timeoutMs: Math.max(
650
- 1,
651
- Number(payload.timeout_ms ?? payload.timeoutMs ?? 3e4)
652
- ),
653
- ...testId ? { scrollTestId: testId } : {}
654
- });
655
- }
656
- if (testId && intoView) {
657
- const before = await queryMobileViewportTarget(input, testId);
658
- if (before.visible === true) {
659
- return {
660
- ok: true,
661
- testId,
662
- intoView: true,
663
- alreadyVisible: true,
664
- rect: before.rect
665
- };
666
- }
667
- }
668
- const args = testId ? [
669
- "scroll-view",
670
- "--test-id",
671
- testId,
672
- "--offset",
673
- offset,
674
- ...intoView ? ["--into-view"] : [],
675
- animatedFlag(payload)
676
- ] : ["scroll-view", "--offset", offset, animatedFlag(payload)];
677
- const timeoutMs = Math.max(0, Number(payload.timeout_ms ?? payload.timeoutMs ?? 5e3));
678
- const deadline = Date.now() + timeoutMs;
679
- let result;
680
- while (true) {
681
- try {
682
- result = await bridgeCommand(input, args);
683
- break;
684
- } catch (error) {
685
- if (!testId || Date.now() >= deadline) throw error;
686
- await sleep(100);
687
- }
688
- }
689
- if (testId && intoView) {
690
- const after = await waitForMobileViewportTarget(
691
- input,
692
- testId,
693
- timeoutMs
694
- );
695
- if (after.visible !== true) {
696
- throw new Error(
697
- `ui.scroll did not bring mobile testID ${JSON.stringify(testId)} into the viewport; last result=${JSON.stringify(after)}.`
698
- );
699
- }
700
- }
701
- return {
702
- ...isRecord(result) ? result : { result },
703
- intoView
704
- };
705
- }
706
- async function measureMobileScrollTransition(input, options) {
707
- const result = await bridgeCommand(
708
- {
709
- ...input,
710
- node: {
711
- ...input.node,
712
- bridge_timeout_ms: options.timeoutMs + 5e3,
713
- cdp_timeout_ms: options.timeoutMs + 5e3
714
- }
715
- },
716
- ["measure-scroll-transition", JSON.stringify(options)]
717
- );
718
- if (!isRecord(result) || result.unsupported === true) {
719
- throw new Error(
720
- "ui.scroll visible-transition measurement requires queryUiTarget and scrollView on the current mobile bridge."
721
- );
722
- }
723
- if (result.ok !== true) {
724
- throw new Error(
725
- `ui.scroll visible-transition measurement failed: ${traceText(result.error ?? result)}.`
726
- );
727
- }
728
- return {
729
- ...result,
730
- measureFromTestId: options.fromTestId,
731
- ...options.targetTestId ? { measureTargetTestId: options.targetTestId } : {},
732
- ...options.targetText ? { measureTargetText: options.targetText } : {},
733
- ...options.requiredPresentTestId ? { measureRequiredPresentTestId: options.requiredPresentTestId } : {}
734
- };
735
- }
736
- async function queryMobileViewportTarget(input, testId) {
737
- const expression = `(function(){
738
- const api = globalThis.__AGENTIC__;
739
- if (!api || typeof api.queryUiTarget !== 'function') {
740
- return Promise.resolve(JSON.stringify({ unsupported: true }));
741
- }
742
- return api.queryUiTarget(${JSON.stringify({ testId, visibility: "viewport" })}).then(function(result){
743
- return JSON.stringify(result);
744
- });
745
- })()`;
746
- const result = await evalAsync(input, expression);
747
- if (!isRecord(result) || result.unsupported === true) {
748
- throw new Error(
749
- "Mobile ui.scroll scroll_into_view requires the current __AGENTIC__.queryUiTarget bridge."
750
- );
751
- }
752
- return result;
753
- }
754
- async function waitForMobileViewportTarget(input, testId, timeoutMs) {
755
- const deadline = Date.now() + Math.max(0, Math.min(timeoutMs, 5e3));
756
- let result = await queryMobileViewportTarget(input, testId);
757
- while (result.visible !== true && Date.now() < deadline) {
758
- await sleep(100);
759
- result = await queryMobileViewportTarget(input, testId);
760
- }
761
- return result;
762
- }
763
- async function handleMobileWaitFor(payload, context) {
764
- return waitForMobileTarget(mobileUiInput(context, "waitFor", payload), payload);
765
- }
766
- async function handleMobileHud(payload, context) {
767
- const automaticProgress = typeof payload.action_name === "string" || context.nodeId === "recipe-complete" && isRecord(payload.progress) && payload.progress.complete === true;
768
- const captureProgress = payload.action_name === "ui.screenshot" || payload.action_name === "ui.capture_surface";
769
- const input = mobileUiInput(
770
- context,
771
- "hud",
772
- automaticProgress ? {
773
- ...payload,
774
- bridge_timeout_ms: captureProgress ? 1e4 : 2e3,
775
- cdp_timeout_ms: captureProgress ? 1e4 : 2e3
776
- } : payload
777
- );
778
- if (payload.clear === true) {
779
- try {
780
- return await bridgeCommand(input, ["hide-step"]);
781
- } catch (error) {
782
- return mobileHudSkippedOrThrow(error);
783
- }
784
- }
785
- const hud = mobileHudPayload(payload, context);
786
- try {
787
- const result = await bridgeCommand(input, [
788
- automaticProgress && !captureProgress ? "show-step-json-deferred" : "show-step-json",
789
- JSON.stringify(hud.step)
790
- ]);
791
- return { hud: true, nodeId: hud.nodeId, status: hud.status, result };
792
- } catch (error) {
793
- return mobileHudSkippedOrThrow(error, { nodeId: hud.nodeId, status: hud.status });
794
- }
795
- }
796
- async function hideMobileHudOnTeardown(projectRoot, env = {}) {
797
- if (env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME === "1" || process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME === "1") return;
798
- if (process.env.METAMASK_RECIPE_AUTO_HUD === "0" || process.env.METAMASK_RECIPE_AUTO_HUD === "false") return;
799
- const input = {
800
- node: { bridge_timeout_ms: 8e3, cdp_timeout_ms: 5e3 },
801
- context: { nodeId: "teardown", projectRoot, artifactsDir: projectRoot, env }
802
- };
803
- try {
804
- await bridgeCommand(input, ["hide-step-deferred"]);
805
- } catch {
806
- }
807
- }
808
- function mobileHudSkippedOrThrow(error, extra = {}) {
809
- const reason = traceText(error);
810
- const code = isRecord(error) && typeof error.code === "string" ? error.code : void 0;
811
- if (isMobileHudLifecycleSkip(reason, code)) {
812
- process.stderr.write(`app.hud skipped (bridge target down or transitioning): ${reason}
813
- `);
814
- return {
815
- hud: false,
816
- skipped: true,
817
- warning: "app.hud skipped because the mobile bridge target is down during lifecycle transition.",
818
- ...extra,
819
- reason
820
- };
821
- }
822
- throw new Error(`app.hud bridge command failed outside a lifecycle/no-target transition: ${reason}`);
823
- }
824
- const HUD_LIFECYCLE_SKIP_CODES = /* @__PURE__ */ new Set([
825
- MOBILE_BRIDGE_ERROR_CODES.NO_TARGET,
826
- MOBILE_BRIDGE_ERROR_CODES.CDP_TIMEOUT,
827
- MOBILE_BRIDGE_ERROR_CODES.WS_CLOSED,
828
- MOBILE_BRIDGE_ERROR_CODES.METRO_UNREACHABLE
829
- ]);
830
- function isMobileHudLifecycleSkip(reason, code) {
831
- if (code && HUD_LIFECYCLE_SKIP_CODES.has(code)) return true;
832
- const haystack = reason.toLowerCase();
833
- return [
834
- "no responding bridge target",
835
- "pinned android device",
836
- "bridge probe failed",
837
- "econnrefused",
838
- "timed out",
839
- "no react native bridge target",
840
- // Metro down mid-run: HUD cannot paint, so skip rather than abort (the real
841
- // action fails on its own). Matches the METRO_UNREACHABLE code branch above.
842
- "cannot reach metro",
843
- // Dev-client foreground/reload kills the Hermes page mid-flight: pending
844
- // CDP calls surface as a message timeout or a closed socket.
845
- "cdp message timeout",
846
- "websocket closed"
847
- ].some((needle) => haystack.includes(needle));
848
- }
849
- function animatedFlag(payload) {
850
- return payload.animated === true ? "--animated" : "--no-animated";
851
- }
852
- function mobileHudPayload(payload, context) {
853
- const nodeId = scalarText(payload.node_id ?? payload.nodeId, "app.hud.node_id", context.nodeId);
854
- const status = scalarText(payload.status, "app.hud.status", "running");
855
- const text = optionalScalarText(payload.intent ?? payload.text ?? payload.detail, "app.hud.intent");
856
- if (!text || !text.trim()) {
857
- throw new Error("app.hud requires intent/text/detail; automatic recipe progress should provide node intent.");
858
- }
859
- const detail = scalarText(payload.detail, "app.hud.detail", "");
860
- const error = scalarText(payload.error, "app.hud.error", "");
861
- const progress = mobileHudProgress(payload.progress);
862
- const display = isRecord(payload.display) ? payload.display : {};
863
- const showDebug = display.showDebug === true;
864
- const showDetail = display.showDetail === true;
865
- const proofTarget = payload.proofTarget ?? payload.proof_target;
866
- const displayId = [mobileHudStatusLabel(status), progress.text].filter(Boolean).join(" ");
867
- return {
868
- nodeId,
869
- status,
870
- step: {
871
- id: displayId || nodeId,
872
- nodeId,
873
- status,
874
- intent: text,
875
- progress: progress.value,
876
- detail: showDetail && detail !== text ? detail : void 0,
877
- error: error || void 0,
878
- debug: showDebug ? {
879
- nodeId,
880
- proofTarget
881
- } : void 0
882
- }
883
- };
884
- }
885
- function mobileHudProgress(progress) {
886
- if (!isRecord(progress)) return { text: "" };
887
- if (typeof progress.current !== "number" || typeof progress.total !== "number") return { text: "" };
888
- return {
889
- text: `${progress.current}/${progress.total}`,
890
- value: { current: progress.current, total: progress.total }
891
- };
892
- }
893
- function mobileHudStatusLabel(status) {
894
- if (status === "fail") return "fail";
895
- if (status === "pass") return "pass";
896
- return "run";
897
- }
898
275
  function createMetaMaskUiTransport(platform, harness, preparedLiveAdapters, { hudPolicy = "auto" } = {}) {
899
- if (platform === "core") {
276
+ const ui = getAdapterSurface(platform).actions.ui;
277
+ if (!ui) {
900
278
  return {
901
279
  async execute(action) {
902
280
  throw new Error(
903
- `core adapter is headless and cannot execute UI action ${action}. Next: mm-harness actions --matrix`
281
+ `${platform} adapter is headless and cannot execute UI action ${action}. Next: mm-harness actions --matrix`
904
282
  );
905
283
  }
906
284
  };
907
285
  }
908
- const base = platform === "mobile" ? harness.createReactNativeBridgeUiTransport({ bridge: createMetaMaskMobileBridge() }) : platform === "terminal" ? harness.createCdpWebUiTransport({
909
- async withPage(input, callback) {
910
- const { withTerminalPage } = await import("../library/actions/terminal/platform/page.mjs");
911
- return withTerminalPage(
912
- {
913
- action: input.action,
914
- node: input.node,
915
- context: {
916
- nodeId: input.context.nodeId,
917
- projectRoot: input.context.projectRoot,
918
- artifactsDir: input.context.artifactsDir
919
- }
920
- },
921
- callback
922
- );
923
- }
924
- }) : harness.createCdpWebUiTransport({
925
- async withPage(input, callback) {
926
- return withExtensionPage(
927
- {
928
- action: input.action,
929
- node: input.node,
930
- context: {
931
- nodeId: input.context.nodeId,
932
- projectRoot: input.context.projectRoot,
933
- artifactsDir: input.context.artifactsDir
934
- }
935
- },
936
- callback
286
+ const { base, execute } = ui(harness, { hudPolicy });
287
+ async function standard(action, node, context) {
288
+ if (action === "ui.navigate") {
289
+ const live = await runLiveFirst(
290
+ platform,
291
+ action,
292
+ node,
293
+ context,
294
+ false,
295
+ preparedLiveAdapters
937
296
  );
297
+ if (live) return live.output;
298
+ throw new Error(`ui.navigate requires library/actions/${platform}/ui/navigate.mjs.`);
938
299
  }
939
- });
300
+ return base.execute(action, action === "ui.wait_for" ? normalizeUiWaitNode(node) : node, context);
301
+ }
940
302
  return {
941
303
  ...base.observe ? { observe: base.observe.bind(base) } : {},
942
304
  async execute(action, node, context) {
943
- if (platform === "terminal" && action === "app.hud") {
944
- const { showTerminalHud } = await import("../library/actions/terminal/platform/hud.mjs");
945
- return showTerminalHud(
946
- { action, node, context: { nodeId: context.nodeId, projectRoot: context.projectRoot } },
947
- { hudPolicy }
948
- );
949
- }
950
- if (action === "ui.navigate") {
951
- const live = await runLiveFirst(
952
- platform,
953
- action,
954
- node,
955
- context,
956
- false,
957
- preparedLiveAdapters
958
- );
959
- if (live) return live.output;
960
- throw new Error(`ui.navigate requires library/actions/${platform}/ui/navigate.mjs.`);
961
- }
962
- if (platform === "mobile" && NATIVE_PROVIDER_UI_ACTIONS.has(action) && (context.env?.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME === "1" || process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME === "1" || ["ui.swipe", "ui.pan", "ui.drag", "ui.long_press", "ui.capture_surface"].includes(action))) {
963
- return executeNativeProviderAction(action, node, context, harness.createNativeUiTransport);
964
- }
965
- return base.execute(action, action === "ui.wait_for" ? normalizeUiWaitNode(node) : node, context);
966
- }
967
- };
968
- }
969
- async function executeNativeProviderAction(action, node, context, createTransport) {
970
- if (!createTransport) {
971
- throw new Error(
972
- `${action} requires the MetaMask native UI provider. Next: npm install -g @deeeed/metamask-harness@latest`
973
- );
974
- }
975
- const platform = mobileNativePlatform(context.env);
976
- const device = platform === "android" ? context.env.ADB_SERIAL ?? context.env.ANDROID_SERIAL ?? process.env.ADB_SERIAL ?? process.env.ANDROID_SERIAL : context.env.SIM_UDID ?? context.env.IOS_SIMULATOR ?? process.env.SIM_UDID ?? process.env.IOS_SIMULATOR;
977
- if (!device) {
978
- throw new Error(
979
- `${action} requires one selected ${platform} device. Next: mm-harness status --adapter mobile --all-devices --json`
980
- );
981
- }
982
- if (platform === "android" && action === "ui.swipe" && isCoordinateTarget(node.target)) {
983
- return executeAndroidCoordinateSwipe(node, String(device));
984
- }
985
- const app = platform === "android" ? context.env.ANDROID_PACKAGE_ID ?? process.env.ANDROID_PACKAGE_ID ?? "io.metamask" : context.env.IOS_BUNDLE_ID ?? process.env.IOS_BUNDLE_ID ?? "io.metamask.MetaMask";
986
- const session = (platform === "ios" ? `mm-harness-${process.pid}-${device}` : `mm-harness-${process.pid}-${context.nodeId}`).replace(/[^a-zA-Z0-9._-]/gu, "-");
987
- const stateDir = nativeAgentDeviceStateDir(context.env ?? {}, context.projectRoot, String(device));
988
- let transport;
989
- try {
990
- transport = await createTransport({ platform, device, app, session, stateDir });
991
- } catch (error) {
992
- throw new Error(
993
- `${action} could not start the MetaMask native UI provider. Next: npm install -g @deeeed/metamask-harness@latest`,
994
- { cause: error }
995
- );
996
- }
997
- let executionError;
998
- let output;
999
- try {
1000
- output = await transport.execute(action, node, context);
1001
- } catch (error) {
1002
- const message = error instanceof Error ? error.message : String(error);
1003
- executionError = /^Native recipe actions require the optional agent-device provider:/u.test(message) ? new Error(
1004
- `${action} could not start the MetaMask native UI provider. Next: npm install -g @deeeed/metamask-harness@latest`,
1005
- { cause: error }
1006
- ) : error;
1007
- }
1008
- let closeError;
1009
- try {
1010
- await transport.close?.();
1011
- } catch (error) {
1012
- closeError = error;
1013
- }
1014
- if (executionError && closeError) {
1015
- const message = executionError instanceof Error ? executionError.message : String(executionError);
1016
- throw new AggregateError([executionError, closeError], message);
1017
- }
1018
- if (executionError) throw executionError;
1019
- if (closeError) throw closeError;
1020
- return output;
1021
- }
1022
- function isCoordinateTarget(value) {
1023
- return isRecord(value) && typeof value.x === "number" && Number.isFinite(value.x) && typeof value.y === "number" && Number.isFinite(value.y);
1024
- }
1025
- async function executeAndroidCoordinateSwipe(node, device) {
1026
- const target = node.target;
1027
- const direction = String(node.direction ?? "").toLowerCase();
1028
- if (!["up", "down", "left", "right"].includes(direction)) {
1029
- throw new Error("ui.swipe.direction must be up, down, left, or right.");
1030
- }
1031
- const distance = Number(node.distance ?? 300);
1032
- const durationMs = Number(node.duration_ms ?? 300);
1033
- if (!Number.isFinite(distance) || distance <= 0) {
1034
- throw new Error("ui.swipe.distance must be a positive number.");
1035
- }
1036
- if (!Number.isFinite(durationMs) || durationMs <= 0) {
1037
- throw new Error("ui.swipe.duration_ms must be a positive number.");
1038
- }
1039
- const start = { x: Math.round(target.x), y: Math.round(target.y) };
1040
- const end = {
1041
- x: Math.round(start.x + (direction === "left" ? -distance : direction === "right" ? distance : 0)),
1042
- y: Math.round(start.y + (direction === "up" ? -distance : direction === "down" ? distance : 0))
1043
- };
1044
- const adbPath = resolveMobileToolPath("adb", { required: true });
1045
- try {
1046
- await execFileAsync(
1047
- adbPath,
1048
- [
1049
- "-s",
1050
- device,
1051
- "shell",
1052
- "input",
1053
- "swipe",
1054
- String(start.x),
1055
- String(start.y),
1056
- String(end.x),
1057
- String(end.y),
1058
- String(Math.round(durationMs))
1059
- ],
1060
- { encoding: "utf8", timeout: Math.round(durationMs) + 1e4 }
1061
- );
1062
- } catch (error) {
1063
- throw new Error(
1064
- `Android ui.swipe failed on ${device}: ${error instanceof Error ? error.message : String(error)}`
1065
- );
1066
- }
1067
- const settleMs = node.settle === false ? 0 : 300;
1068
- if (settleMs > 0) await sleep(settleMs);
1069
- return {
1070
- action: "ui.swipe",
1071
- resolvedStart: start,
1072
- resolvedEnd: end,
1073
- backend: "adb-input-swipe",
1074
- segments: 1,
1075
- settlement: {
1076
- method: "bounded-post-gesture",
1077
- waitedMs: settleMs,
1078
- lifecycleNeutral: true
305
+ if (execute) return execute(action, node, context, () => standard(action, node, context));
306
+ return standard(action, node, context);
1079
307
  }
1080
308
  };
1081
309
  }
1082
- function mobileNativePlatform(env) {
1083
- if (env.PLATFORM === "android" || env.PLATFORM === "ios") return env.PLATFORM;
1084
- if (env.ADB_SERIAL || env.ANDROID_SERIAL) return "android";
1085
- if (env.SIM_UDID || env.IOS_SIMULATOR) return "ios";
1086
- if (process.env.PLATFORM === "android" || process.env.PLATFORM === "ios") return process.env.PLATFORM;
1087
- return process.env.ADB_SERIAL || process.env.ANDROID_SERIAL ? "android" : "ios";
1088
- }
1089
310
  function normalizeUiWaitNode(node) {
1090
311
  if (node.expected !== void 0 || typeof node.visible !== "boolean") return node;
1091
312
  return { ...node, expected: node.visible ? "visible" : "absent" };
@@ -1106,17 +327,13 @@ function createMetaMaskAdapters(adapter, declaredCustomActions = [], preparedLiv
1106
327
  const metroPort = node.metro_port ?? node.watcher_port ?? process.env.WATCHER_PORT ?? null;
1107
328
  const timeoutMs = Number(node.probe_timeout_ms ?? node.cdp_timeout_ms ?? node.timeout_ms ?? process.env.CDP_TIMEOUT ?? 1e4);
1108
329
  const required = node.require_reachable === true || node.required === true;
1109
- if (platform === "mobile" && required) {
1110
- const uiInput = { action: "cdp.target", nodeId: context.nodeId, node };
1111
- const status = await bridgeCommand(uiInputFor(context, uiInput), ["status"]);
1112
- const probe2 = mobileProbeOutput(status, uiInput, context.projectRoot);
1113
- if (!probe2.reachable) {
1114
- throw new Error("cdp.target required a reachable mobile React Native bridge, but bridge status did not expose a selected app route.");
1115
- }
330
+ const target = getAdapterSurface(platform).actions.cdpTarget;
331
+ if (required && target.requiredProbe) {
332
+ const probe2 = await target.requiredProbe(node, context);
1116
333
  return {
1117
334
  output: {
1118
335
  platform,
1119
- transport: "react-native-debug-bridge",
336
+ transport: target.transport,
1120
337
  cdpPort,
1121
338
  metroPort,
1122
339
  timeoutMs,
@@ -1124,8 +341,8 @@ function createMetaMaskAdapters(adapter, declaredCustomActions = [], preparedLiv
1124
341
  }
1125
342
  };
1126
343
  }
1127
- const targetPort = platform === "mobile" ? metroPort ?? cdpPort : cdpPort;
1128
- const url = targetProbeUrl(platform, targetPort);
344
+ const targetPort = target.watcherPortFirst ? metroPort ?? cdpPort : cdpPort;
345
+ const url = targetPort ? `http://127.0.0.1:${traceText(targetPort)}/${target.probePath}` : null;
1129
346
  const probe = url ? await probeHttpJson(url, timeoutMs) : { reachable: null, statusCode: null, error: "no port declared" };
1130
347
  if (required && !probe.reachable) {
1131
348
  throw new Error(`cdp.target required a reachable ${platform} runtime at ${url ?? "<no port>"}: ${traceText(probe.error) || "not reachable"}`);
@@ -1133,7 +350,7 @@ function createMetaMaskAdapters(adapter, declaredCustomActions = [], preparedLiv
1133
350
  return {
1134
351
  output: {
1135
352
  platform,
1136
- transport: platform === "mobile" ? "react-native-debug-bridge" : platform === "terminal" ? "chrome-cdp" : "chrome-extension-cdp",
353
+ transport: target.transport,
1137
354
  cdpPort,
1138
355
  metroPort,
1139
356
  probeUrl: url,
@@ -1146,17 +363,8 @@ function createMetaMaskAdapters(adapter, declaredCustomActions = [], preparedLiv
1146
363
  ];
1147
364
  return adapters;
1148
365
  }
1149
- function targetProbeUrl(platform, targetPort) {
1150
- if (!targetPort) return null;
1151
- const pathSuffix = platform === "mobile" ? "json/list" : "json/version";
1152
- return `http://127.0.0.1:${traceText(targetPort)}/${pathSuffix}`;
1153
- }
1154
366
  export {
1155
367
  createMetaMaskAdapters,
1156
368
  createMetaMaskUiTransport,
1157
- hideMobileHudOnTeardown,
1158
- isMobileHudLifecycleSkip,
1159
- isRetryableMobileWaitError,
1160
- normalizeUiWaitNode,
1161
- resolveMobileWaitTarget
369
+ normalizeUiWaitNode
1162
370
  };