@deeeed/metamask-harness 0.79.0 → 0.80.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 (210) hide show
  1. package/CHANGELOG.md +47 -0
  2. package/README.md +1 -1
  3. package/adapters/extension/ensure-browser.sh +2 -2
  4. package/adapters/extension/inject.mjs +0 -1
  5. package/adapters/extension/launch-browser.cjs +39 -613
  6. package/adapters/extension/readiness.mjs +2 -5
  7. package/adapters/extension/reattach.sh +2 -2
  8. package/adapters/extension/stamp-runtime-title.cjs +1 -1
  9. package/adapters/manifest.json +2 -34
  10. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +57 -1358
  11. package/adapters/mobile/bridge-runtime/teaching.cjs +20 -0
  12. package/adapters/mobile/inject.sh +16 -0
  13. package/adapters/mobile/launch-console-forwarder.cjs +1 -2
  14. package/adapters/mobile/open-device.sh +2 -2
  15. package/adapters/mobile/reload-app.mjs +4 -4
  16. package/adapters/mobile/reset-app-data.sh +1 -1
  17. package/adapters/mobile/start-console-forwarder.sh +3 -2
  18. package/adapters/mobile/start-metro.sh +17 -12
  19. package/adapters/mobile/wait-for-bridge.cjs +2 -2
  20. package/adapters/shared/harness-source-fingerprint.mjs +13 -8
  21. package/adapters/shared/open-debug.mjs +2 -2
  22. package/adapters/shared/sync-wallet-fixture.sh +8 -5
  23. package/adapters/terminal/cleanup.mjs +2 -1
  24. package/adapters/terminal/inject.mjs +2 -1
  25. package/bin/mm-harness +9 -28
  26. package/dist/adapters/checkout-files.js +16 -0
  27. package/dist/adapters/console-capture.js +14 -1
  28. package/dist/adapters/core/actions.js +4 -3
  29. package/dist/adapters/core/overlay.js +1 -1
  30. package/dist/adapters/core/readiness.js +28 -1
  31. package/dist/adapters/core/run.js +2 -2
  32. package/dist/adapters/core/surface.js +16 -1
  33. package/dist/adapters/extension/browser-pid.js +56 -0
  34. package/dist/adapters/extension/console-capture.js +1 -1
  35. package/dist/adapters/extension/doctor-checks.js +1 -1
  36. package/dist/adapters/extension/ensure-ready.js +5 -4
  37. package/dist/adapters/extension/extension-id.js +1 -1
  38. package/dist/adapters/extension/feature-flags.js +1 -1
  39. package/dist/adapters/extension/fixtures.js +1 -2
  40. package/dist/adapters/extension/launch.js +4 -5
  41. package/dist/adapters/extension/launched-browser.js +1 -2
  42. package/dist/adapters/extension/network-observer.js +1 -1
  43. package/dist/adapters/extension/overlay.js +1 -1
  44. package/dist/adapters/extension/product-config.js +1 -1
  45. package/dist/adapters/extension/readiness.js +1 -1
  46. package/dist/adapters/extension/reload.js +1 -1
  47. package/dist/adapters/extension/run.js +24 -10
  48. package/dist/adapters/extension/runtime-decision.js +2 -1
  49. package/dist/adapters/extension/runtime.js +9 -5
  50. package/dist/adapters/extension/surface.js +15 -2
  51. package/dist/adapters/extension/verify.js +2 -2
  52. package/dist/adapters/harness-freshness.js +2 -1
  53. package/dist/adapters/mobile/android-gfxinfo-performance.js +1 -3
  54. package/dist/adapters/mobile/app-identity.js +10 -0
  55. package/dist/adapters/mobile/doctor-checks.js +1 -1
  56. package/dist/adapters/mobile/fixtures.js +1 -1
  57. package/dist/adapters/mobile/launch.js +27 -28
  58. package/dist/adapters/mobile/lifecycle.js +1 -1
  59. package/dist/adapters/mobile/metro-env.js +9 -59
  60. package/dist/adapters/mobile/performance-observer.js +1 -1
  61. package/dist/adapters/mobile/prepare-target.js +1 -1
  62. package/dist/adapters/mobile/prepare.js +3 -4
  63. package/dist/adapters/mobile/provision.js +1 -1
  64. package/dist/adapters/mobile/readiness.js +1 -1
  65. package/dist/adapters/mobile/recording-target.js +15 -0
  66. package/dist/adapters/mobile/release-artifact-state.js +1 -1
  67. package/dist/adapters/mobile/reload.js +1 -1
  68. package/dist/adapters/mobile/run.js +8 -7
  69. package/dist/adapters/mobile/runtime-decision.js +2 -4
  70. package/dist/adapters/mobile/source-freshness.js +22 -126
  71. package/dist/adapters/mobile/surface.js +26 -3
  72. package/dist/adapters/mobile/ui.js +16 -5
  73. package/dist/adapters/mobile/verify.js +2 -3
  74. package/dist/adapters/performance/cdp-trace.js +1 -1
  75. package/dist/adapters/performance/device-platform.js +1 -1
  76. package/dist/adapters/slot-ports.js +1 -1
  77. package/dist/adapters/surface.js +1 -0
  78. package/dist/adapters/terminal/console-capture.js +1 -1
  79. package/dist/adapters/terminal/launch.js +1 -1
  80. package/dist/adapters/terminal/run.js +25 -2
  81. package/dist/adapters/terminal/surface.js +7 -1
  82. package/dist/adapters.js +8 -4
  83. package/dist/advisor-policy.js +1 -1
  84. package/dist/checkout-view.js +1 -2
  85. package/dist/cli.js +28 -15
  86. package/dist/command-contract.js +1 -26
  87. package/dist/commands/call.js +10 -24
  88. package/dist/commands/check.js +3 -4
  89. package/dist/commands/checklist.js +1 -1
  90. package/dist/commands/completion-candidates.js +2 -10
  91. package/dist/commands/config.js +2 -2
  92. package/dist/commands/core-readiness.js +12 -7
  93. package/dist/commands/debug.js +3 -3
  94. package/dist/commands/device-target.js +5 -5
  95. package/dist/commands/doctor.js +2 -14
  96. package/dist/commands/domain.js +2 -2
  97. package/dist/commands/ensure-ready.js +1 -7
  98. package/dist/commands/fixtures.js +5 -6
  99. package/dist/commands/help.js +3 -4
  100. package/dist/commands/launch/device.js +10 -17
  101. package/dist/commands/launch/extension.js +2 -8
  102. package/dist/commands/launch/mobile.js +1 -1
  103. package/dist/commands/list-executables.js +1 -3
  104. package/dist/commands/logs.js +3 -3
  105. package/dist/commands/manifest.js +1 -12
  106. package/dist/commands/mobile-device-view.js +4 -2
  107. package/dist/commands/overlay.js +110 -0
  108. package/dist/commands/pin.js +1 -1
  109. package/dist/commands/pr-body.js +2 -2
  110. package/dist/commands/prepare.js +4 -9
  111. package/dist/commands/provision.js +3 -12
  112. package/dist/commands/recipe-advice.js +1 -2
  113. package/dist/commands/recipe-quality.js +2 -3
  114. package/dist/commands/resolve-extension.js +1 -7
  115. package/dist/commands/review.js +4 -4
  116. package/dist/commands/run-engine.js +12 -51
  117. package/dist/commands/run.js +4 -25
  118. package/dist/commands/runtime-decision.js +1 -6
  119. package/dist/commands/runtime-health.js +1 -7
  120. package/dist/commands/runtime-launch.js +1 -13
  121. package/dist/commands/self-test.js +2 -6
  122. package/dist/commands/status-watch.js +1 -2
  123. package/dist/commands/status.js +2 -5
  124. package/dist/commands/task-init.js +2 -3
  125. package/dist/commands/tutorial.js +2 -2
  126. package/dist/commands/update.js +1 -1
  127. package/dist/completions-cache.js +1 -1
  128. package/dist/console-classifier.js +19 -0
  129. package/dist/doctor.js +2 -3
  130. package/dist/funding-execution-context.js +15 -1
  131. package/dist/host.js +32 -0
  132. package/dist/index.js +1 -6
  133. package/dist/live-tsx.js +49 -0
  134. package/dist/mm-harness-cli.js +2 -4
  135. package/dist/network-observation.js +4 -4
  136. package/dist/paths.js +34 -64
  137. package/dist/performance-observation.js +2 -2
  138. package/dist/progress.js +1 -1
  139. package/dist/recipe-assistance-context.js +1 -3
  140. package/dist/recipe-security.js +2 -59
  141. package/dist/review/knowledge.js +2 -2
  142. package/dist/runner.js +5 -3
  143. package/dist/runtime-context.js +1 -1
  144. package/dist/task-view.js +2 -3
  145. package/docs/CONTRIBUTING.md +1 -1
  146. package/docs/cli-contract.md +2 -2
  147. package/docs/feature-guard.md +90 -0
  148. package/library/README.md +3 -1
  149. package/library/actions/core/perps/_controller.mjs +153 -130
  150. package/library/actions/core/perps/quote_fees.mjs +58 -0
  151. package/library/actions/core/wallet/_fixture-accounts.mjs +69 -0
  152. package/library/actions/core/wallet/list_accounts.mjs +14 -57
  153. package/library/actions/extension/feature_flags/state.mjs +1 -1
  154. package/library/actions/extension/platform/cdp.mjs +2 -8
  155. package/library/actions/harness-exports.mjs +1 -6
  156. package/library/actions/mobile/app/network-control.mjs +6 -8
  157. package/library/actions/mobile/perps/prepare_local_snapshot_endpoint.mjs +1 -1
  158. package/library/actions/mobile/platform/bridge.mjs +8 -8
  159. package/library/actions/mobile/platform/native-session-name.mjs +21 -1
  160. package/library/actions/mobile/platform/native-session.mjs +3 -4
  161. package/library/actions/mobile/platform/observe-ui.mjs +12 -6
  162. package/library/actions/mobile/ui/native-navigation.mjs +4 -3
  163. package/library/actions/mobile/wallet/native-ui.mjs +3 -2
  164. package/library/actions/shared/analytics/_adapter.mjs +1 -1
  165. package/library/actions/shared/console-findings.mjs +1 -1
  166. package/library/actions/terminal/platform/runtime.mjs +2 -1
  167. package/library/manifests/core.action-manifest.json +83 -13
  168. package/package.json +8 -9
  169. package/adapters/extension/lib/slot-title.cjs +0 -292
  170. package/adapters/mobile/bridge-runtime/console-forwarder.cjs +0 -472
  171. package/adapters/mobile/bridge-runtime/lib/bridge-errors.cjs +0 -125
  172. package/adapters/mobile/bridge-runtime/lib/cdp-broker.cjs +0 -1055
  173. package/adapters/mobile/bridge-runtime/lib/cdp-eval.cjs +0 -110
  174. package/adapters/mobile/bridge-runtime/lib/config.cjs +0 -61
  175. package/adapters/mobile/bridge-runtime/lib/console-format.cjs +0 -53
  176. package/adapters/mobile/bridge-runtime/lib/devtools-proxy.cjs +0 -177
  177. package/adapters/mobile/bridge-runtime/lib/issue-capture.cjs +0 -446
  178. package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +0 -84
  179. package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +0 -403
  180. package/adapters/mobile/bridge-runtime/lib/ws-client.cjs +0 -116
  181. package/adapters/mobile/coalesce-metro-log.cjs +0 -24
  182. package/adapters/mobile/launch-metro.cjs +0 -77
  183. package/adapters/mobile/metro-config.cjs +0 -98
  184. package/adapters/mobile/metro-log-generation.cjs +0 -106
  185. package/dist/adapters/mobile/frame-metrics.js +0 -118
  186. package/dist/adapters/mobile/video-recorder.js +0 -289
  187. package/dist/artifact-files.js +0 -92
  188. package/dist/checkout-lock.js +0 -149
  189. package/dist/cli-color.js +0 -98
  190. package/dist/command-journal.js +0 -308
  191. package/dist/commands/last.js +0 -60
  192. package/dist/commands/launch/index.js +0 -109
  193. package/dist/commands/parse-args.js +0 -279
  194. package/dist/commands/reload.js +0 -41
  195. package/dist/commands/run-report.js +0 -241
  196. package/dist/commands/shared.js +0 -323
  197. package/dist/commands/stop.js +0 -60
  198. package/dist/devices.js +0 -100
  199. package/dist/execution-provenance.js +0 -332
  200. package/dist/harness.js +0 -451
  201. package/dist/heal-bounds.js +0 -137
  202. package/dist/json-stream.js +0 -60
  203. package/dist/leaf-invoke.js +0 -19
  204. package/dist/library-provenance.js +0 -33
  205. package/dist/live-adapter-contract.js +0 -380
  206. package/dist/recording-target.js +0 -120
  207. package/dist/run-diagnostics.js +0 -364
  208. package/dist/run-recording.js +0 -481
  209. package/dist/runtime-proof.js +0 -90
  210. package/library/actions/mobile/platform/tool-paths.mjs +0 -123
@@ -0,0 +1,69 @@
1
+ // The checkout's wallet fixture expanded into accounts: a mnemonic entry with
2
+ // count N yields N accounts at address indexes 0..N-1, named dev1..devN from the
3
+ // entry's name. list_accounts shows them; Core Perps actions select from them.
4
+
5
+ import { readFile } from 'node:fs/promises';
6
+ import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
7
+
8
+ import { walletFixturePath } from '../../harness-exports.mjs';
9
+
10
+ function fixtureAccountName(base, index, count) {
11
+ if (count === 1) return base || 'account1';
12
+ const match = /^(.*?)(\d+)$/u.exec(base || 'account1');
13
+ return match
14
+ ? `${match[1]}${Number(match[2]) + index}`
15
+ : `${base || 'account'}${index + 1}`;
16
+ }
17
+
18
+ function mnemonicCount(entry, fixturePath, index) {
19
+ const count = Number(entry.count ?? entry.numberOfAccounts ?? 1);
20
+ if (!Number.isInteger(count) || count < 1 || count > 100) {
21
+ throw new Error(`${fixturePath} accounts[${index}] mnemonic count must be an integer from 1 through 100.`);
22
+ }
23
+ return count;
24
+ }
25
+
26
+ /**
27
+ * Expand fixture entries into `{ name, address, signer }`. `signer` is the viem
28
+ * account holding the key: use it to sign, never serialize it.
29
+ */
30
+ export function deriveFixtureAccounts(fixture, fixturePath) {
31
+ if (!Array.isArray(fixture.accounts) || fixture.accounts.length === 0) {
32
+ throw new Error(`wallet fixture at ${fixturePath} has no accounts array.`);
33
+ }
34
+ return fixture.accounts.flatMap((entry, fixtureIndex) => {
35
+ if (!entry || typeof entry !== 'object' || typeof entry.value !== 'string' || !entry.value.trim()) {
36
+ throw new Error(`${fixturePath} accounts[${fixtureIndex}] must contain a non-empty value.`);
37
+ }
38
+ if (entry.type === 'mnemonic') {
39
+ const count = mnemonicCount(entry, fixturePath, fixtureIndex);
40
+ return Array.from({ length: count }, (_, addressIndex) => {
41
+ const signer = mnemonicToAccount(entry.value.trim(), { addressIndex });
42
+ return { name: fixtureAccountName(entry.name, addressIndex, count), address: signer.address, signer };
43
+ });
44
+ }
45
+ if (entry.type === 'privateKey') {
46
+ const raw = entry.value.trim();
47
+ const normalized = raw.startsWith('0x') ? raw : `0x${raw}`;
48
+ if (!/^0x[0-9a-fA-F]{64}$/u.test(normalized)) {
49
+ throw new Error(`${fixturePath} accounts[${fixtureIndex}] privateKey is not a 32-byte hex key.`);
50
+ }
51
+ const signer = privateKeyToAccount(normalized);
52
+ return [{ name: entry.name || `account${fixtureIndex + 1}`, address: signer.address, signer }];
53
+ }
54
+ throw new Error(`${fixturePath} accounts[${fixtureIndex}].type must be mnemonic or privateKey.`);
55
+ });
56
+ }
57
+
58
+ /** The checkout's fixture accounts, or null when it has no wallet fixture. */
59
+ export async function readFixtureAccounts(projectRoot) {
60
+ const fixturePath = walletFixturePath(projectRoot);
61
+ let raw;
62
+ try {
63
+ raw = await readFile(fixturePath, 'utf8');
64
+ } catch (error) {
65
+ if (error?.code === 'ENOENT') return null;
66
+ throw error;
67
+ }
68
+ return { fixturePath, accounts: deriveFixtureAccounts(JSON.parse(raw), fixturePath) };
69
+ }
@@ -3,72 +3,29 @@
3
3
 
4
4
  import { readFile, writeFile } from 'node:fs/promises';
5
5
  import { pathToFileURL } from 'node:url';
6
- import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
7
6
 
8
7
  import { walletFixturePath } from '../../harness-exports.mjs';
9
-
10
- function fixtureAccountName(base, index, count) {
11
- if (count === 1) return base || 'account1';
12
- const match = /^(.*?)(\d+)$/u.exec(base || 'account1');
13
- return match
14
- ? `${match[1]}${Number(match[2]) + index}`
15
- : `${base || 'account'}${index + 1}`;
16
- }
17
-
18
- function mnemonicCount(entry, fixturePath, index) {
19
- const count = Number(entry.count ?? entry.numberOfAccounts ?? 1);
20
- if (!Number.isInteger(count) || count < 1 || count > 100) {
21
- throw new Error(`${fixturePath} accounts[${index}] mnemonic count must be an integer from 1 through 100.`);
22
- }
23
- return count;
24
- }
25
-
26
- function deriveFixtureAccounts(fixture, fixturePath) {
27
- if (!Array.isArray(fixture.accounts) || fixture.accounts.length === 0) {
28
- throw new Error(`wallet fixture at ${fixturePath} has no accounts array.`);
29
- }
30
- return fixture.accounts.flatMap((entry, fixtureIndex) => {
31
- if (!entry || typeof entry !== 'object' || typeof entry.value !== 'string' || !entry.value.trim()) {
32
- throw new Error(`${fixturePath} accounts[${fixtureIndex}] must contain a non-empty value.`);
33
- }
34
- if (entry.type === 'mnemonic') {
35
- const count = mnemonicCount(entry, fixturePath, fixtureIndex);
36
- return Array.from({ length: count }, (_, addressIndex) => {
37
- const account = mnemonicToAccount(entry.value.trim(), { addressIndex });
38
- return {
39
- id: `fixture:${fixtureAccountName(entry.name, addressIndex, count)}`,
40
- address: account.address,
41
- name: fixtureAccountName(entry.name, addressIndex, count),
42
- type: 'eip155:eoa',
43
- };
44
- });
45
- }
46
- if (entry.type === 'privateKey') {
47
- const raw = entry.value.trim();
48
- const normalized = raw.startsWith('0x') ? raw : `0x${raw}`;
49
- if (!/^0x[0-9a-fA-F]{64}$/u.test(normalized)) {
50
- throw new Error(`${fixturePath} accounts[${fixtureIndex}] privateKey is not a 32-byte hex key.`);
51
- }
52
- const account = privateKeyToAccount(normalized);
53
- const name = entry.name || `account${fixtureIndex + 1}`;
54
- return [{ id: `fixture:${name}`, address: account.address, name, type: 'eip155:eoa' }];
55
- }
56
- throw new Error(`${fixturePath} accounts[${fixtureIndex}].type must be mnemonic or privateKey.`);
57
- });
58
- }
8
+ import { readFixtureAccounts } from './_fixture-accounts.mjs';
59
9
 
60
10
  async function listAccounts(input) {
61
11
  const scope = input.node?.scope == null ? 'named' : String(input.node.scope);
62
12
  if (scope !== 'selected' && scope !== 'named' && scope !== 'evm' && scope !== 'all') {
63
13
  throw new Error('metamask.wallet.list_accounts scope must be selected, named, evm, or all.');
64
14
  }
65
- const fixturePath = walletFixturePath(input.context.projectRoot);
66
- const fixture = JSON.parse(await readFile(fixturePath, 'utf8'));
15
+ const loaded = await readFixtureAccounts(input.context.projectRoot);
16
+ if (!loaded) {
17
+ throw new Error(
18
+ `no wallet fixture at ${walletFixturePath(input.context.projectRoot)}.\nNext: run mm-harness fixtures set`,
19
+ );
20
+ }
67
21
  const selectedName = String(input.node?.account_name ?? input.node?.account ?? 'dev1');
68
- const accounts = deriveFixtureAccounts(fixture, fixturePath)
69
- .map((account) => ({
70
- ...account,
71
- selected: account.name === selectedName,
22
+ const accounts = loaded.accounts
23
+ .map(({ name, address }) => ({
24
+ id: `fixture:${name}`,
25
+ address,
26
+ name,
27
+ type: 'eip155:eoa',
28
+ selected: name === selectedName,
72
29
  }))
73
30
  .filter((account) => {
74
31
  if (scope === 'selected') return account.selected;
@@ -15,7 +15,7 @@
15
15
  import fs from 'node:fs';
16
16
  import path from 'node:path';
17
17
 
18
- import { recipeRuntimeDir } from '../../harness-exports.mjs';
18
+ import { recipeRuntimeDir } from '@farmslot/recipe-cli/harness';
19
19
 
20
20
  export const EXTENSION_SET_UNSUPPORTED_REASON =
21
21
  'The extension exposes no runtime remote feature-flag override RPC: RemoteFeatureFlagController.setFlagOverride is never registered in metamask-controller.js getApi(), so stateHooks.submitRequestToBackground cannot reach it. Overrides are pinned into the per-run dist snapshot manifest at launch.';
@@ -20,14 +20,8 @@ import { spawn } from 'node:child_process';
20
20
  import { createRequire } from 'node:module';
21
21
  import path from 'node:path';
22
22
 
23
- import {
24
- importRecipeHarnessRuntimeBrowserExtension,
25
- importRecipeHarnessRuntimeCdp,
26
- extensionIdPath,
27
- recipeHarnessPath,
28
- walletFixturePath,
29
- captureActiveRecipeRecordingSnapshot,
30
- } from '../../harness-exports.mjs';
23
+ import { captureActiveRecipeRecordingSnapshot, recipeHarnessPath } from '@farmslot/recipe-cli/harness';
24
+ import { importRecipeHarnessRuntimeBrowserExtension, importRecipeHarnessRuntimeCdp, extensionIdPath, walletFixturePath } from '../../harness-exports.mjs';
31
25
 
32
26
  // Resolve the Farmslot harness through normal package dependencies by default.
33
27
  // Local Farmslot source is only a dev override handled by the harness paths module.
@@ -1,4 +1,4 @@
1
- // Bridge from library actions to the harness path/recording helpers. Published
1
+ // Bridge from library actions to the harness path helpers. Published
2
2
  // installs load the compiled dist (node cannot type-strip .ts under
3
3
  // node_modules); source checkouts without dist fall back to src. Deliberately
4
4
  // narrow — importing the package entry here would close an import cycle
@@ -14,20 +14,15 @@ async function load(distRelative, srcRelative) {
14
14
  }
15
15
 
16
16
  const paths = await load('dist/paths.js', 'src/paths.ts');
17
- const recording = await load('dist/run-recording.js', 'src/run-recording.ts');
18
17
  const featureFlags = await load('dist/feature-flags.js', 'src/feature-flags.ts');
19
18
 
20
19
  export const {
21
20
  importRecipeHarnessRuntimeBrowserExtension,
22
21
  importRecipeHarnessRuntimeCdp,
23
22
  extensionIdPath,
24
- recipeHarnessPath,
25
- recipeRuntimeDir,
26
23
  walletFixturePath,
27
24
  } = paths;
28
25
 
29
- export const { captureActiveRecipeRecordingSnapshot } = recording;
30
-
31
26
  // One normalisation for --remote-flag specs, fixture objects, and the
32
27
  // doctor/status summary line. See src/feature-flags.ts.
33
28
  export const {
@@ -3,7 +3,9 @@
3
3
  import { execFile } from 'node:child_process';
4
4
  import { promisify } from 'node:util';
5
5
 
6
- import { resolveMobileToolPath } from '../platform/tool-paths.mjs';
6
+ import { resolveMobileToolPath } from '@farmslot/adapter-rn';
7
+
8
+ import { nativeAppId } from '../platform/native-session-name.mjs';
7
9
 
8
10
  const execFileAsync = promisify(execFile);
9
11
 
@@ -31,13 +33,9 @@ function selectedAndroidSerial(input) {
31
33
  }
32
34
 
33
35
  function selectedAndroidPackage(input) {
34
- return String(
35
- input.node?.package_id ??
36
- input.node?.app_id ??
37
- input.context?.env?.ANDROID_PACKAGE_ID ??
38
- process.env.ANDROID_PACKAGE_ID ??
39
- 'io.metamask',
40
- ).trim();
36
+ const explicit = input.node?.package_id ?? input.node?.app_id;
37
+ if (explicit != null) return String(explicit).trim();
38
+ return nativeAppId({ ...process.env, ...(input.context?.env ?? {}) }, 'android');
41
39
  }
42
40
 
43
41
  async function defaultRun(file, args) {
@@ -4,7 +4,7 @@ import { execFile } from 'node:child_process';
4
4
  import { promisify } from 'node:util';
5
5
 
6
6
  import { runAdapter } from '../platform/bridge.mjs';
7
- import { resolveMobileToolPath } from '../platform/tool-paths.mjs';
7
+ import { resolveMobileToolPath } from '@farmslot/adapter-rn';
8
8
 
9
9
  const execFileAsync = promisify(execFile);
10
10
 
@@ -8,10 +8,10 @@ import { promisify } from 'node:util';
8
8
  import os from 'node:os';
9
9
  import path from 'node:path';
10
10
  import { fileURLToPath } from 'node:url';
11
- import bridgeErrors from '../../../../adapters/mobile/bridge-runtime/lib/bridge-errors.cjs';
12
- import brokerModule from '../../../../adapters/mobile/bridge-runtime/lib/cdp-broker.cjs';
13
- import configModule from '../../../../adapters/mobile/bridge-runtime/lib/config.cjs';
14
- import { resolveMobileToolPath } from './tool-paths.mjs';
11
+ import bridgeErrors from '@farmslot/adapter-rn/bridge-runtime/lib/bridge-errors.cjs';
12
+ import brokerModule from '@farmslot/recipe-runner/cdp-broker';
13
+ import configModule from '@farmslot/adapter-rn/bridge-runtime/lib/config.cjs';
14
+ import { resolveMobileToolPath } from '@farmslot/adapter-rn';
15
15
 
16
16
  const {
17
17
  BRIDGE_ERROR_CODES,
@@ -155,8 +155,8 @@ export async function bridgeEnv(input) {
155
155
  ...(input.context?.env || {}),
156
156
  CDP_TIMEOUT: String(input.node?.cdp_timeout_ms ?? process.env.CDP_TIMEOUT ?? '10000'),
157
157
  };
158
- if (adbPath) env.MM_HARNESS_ADB_PATH = adbPath;
159
- if (idbPath) env.MM_HARNESS_IDB_PATH = idbPath;
158
+ if (adbPath) env.RECIPE_RN_ADB_PATH = adbPath;
159
+ if (idbPath) env.RECIPE_RN_IDB_PATH = idbPath;
160
160
  const target = resolveMobileTarget(input);
161
161
  const requestedPlatform = target.requestedPlatform;
162
162
  const watcherPort = target.watcherPort;
@@ -238,8 +238,8 @@ export function resolveMobileTarget(input) {
238
238
  input.node?.sim_udid ?? contextEnv.SIM_UDID ?? process.env.SIM_UDID;
239
239
  const configuredPlatform = String(
240
240
  input.node?.platform ??
241
- contextEnv.MM_HARNESS_EXPLICIT_PLATFORM ??
242
- process.env.MM_HARNESS_EXPLICIT_PLATFORM ??
241
+ contextEnv.RECIPE_RN_EXPLICIT_PLATFORM ??
242
+ process.env.RECIPE_RN_EXPLICIT_PLATFORM ??
243
243
  contextEnv.PLATFORM ??
244
244
  process.env.PLATFORM ??
245
245
  '',
@@ -1,7 +1,27 @@
1
1
  // Derive native session names and device-specific state directories, honoring configured overrides.
2
2
  import path from 'node:path';
3
3
 
4
- export function nativeSessionName(env, device, app = 'io.metamask') {
4
+ // The host adapter supplies the app under test in the action env
5
+ // (ANDROID_PACKAGE_ID / IOS_BUNDLE_ID, display name in
6
+ // METAMASK_RECIPE_MOBILE_APP_LABEL); the library has no default app.
7
+ export function nativeAppId(env, platform) {
8
+ const key = platform === 'android' ? 'ANDROID_PACKAGE_ID' : 'IOS_BUNDLE_ID';
9
+ const app = String(env[key] ?? '').trim();
10
+ if (!app) {
11
+ throw new Error(`Mobile native UI needs the app identity in ${key}. Next: run through mm-harness run or call, or set ${key}.`);
12
+ }
13
+ return app;
14
+ }
15
+
16
+ export function nativeAppLabel(env) {
17
+ const label = String(env.METAMASK_RECIPE_MOBILE_APP_LABEL ?? '').trim();
18
+ if (!label) {
19
+ throw new Error('Mobile native UI needs the app display name in METAMASK_RECIPE_MOBILE_APP_LABEL. Next: run through mm-harness run or call, or set METAMASK_RECIPE_MOBILE_APP_LABEL.');
20
+ }
21
+ return label;
22
+ }
23
+
24
+ export function nativeSessionName(env, device, app) {
5
25
  const configured = String(env.METAMASK_RECIPE_MOBILE_SESSION ?? '').trim();
6
26
  const source = configured || `mm-harness-${app}-${device}`;
7
27
  return source.replace(/[^A-Za-z0-9._-]/gu, '-').slice(0, 120);
@@ -1,6 +1,6 @@
1
1
  // Create device-targeted native UI sessions for actions and accessibility snapshots.
2
2
  // Opening an instrumented iOS session hides the accessibility-masking HUD.
3
- import { createAgentDeviceUiTransport } from '@farmslot/expo-recipe';
3
+ import { createAgentDeviceUiTransport } from '@farmslot/adapter-rn';
4
4
  import { createAgentDeviceClient } from 'agent-device';
5
5
 
6
6
  import { resolveMobileTarget } from './bridge.mjs';
@@ -8,6 +8,7 @@ import { hideAccessibilityMaskingHud } from './observe-ui.mjs';
8
8
 
9
9
  import {
10
10
  nativeAgentDeviceStateDir,
11
+ nativeAppId,
11
12
  nativeSessionName,
12
13
  } from './native-session-name.mjs';
13
14
 
@@ -19,9 +20,7 @@ export function createNativeSession(input, domain, sessionSuffix = '') {
19
20
  ? target.adbSerial ?? ''
20
21
  : input.node?.sim_udid ?? env.SIM_UDID ?? target.iosSimulator ?? '').trim();
21
22
  if (!device) throw new Error(`Mobile ${domain} native actions require one explicit ${platform} device. Next: pass --device <device>.`);
22
- const app = String(platform === 'android'
23
- ? env.ANDROID_PACKAGE_ID ?? 'io.metamask'
24
- : env.IOS_BUNDLE_ID ?? 'io.metamask.MetaMask');
23
+ const app = nativeAppId(env, platform);
25
24
  const baseSession = nativeSessionName(env, device, app);
26
25
  const session = sessionSuffix
27
26
  ? `${baseSession}-${String(sessionSuffix).replace(/[^A-Za-z0-9._-]/gu, '-')}`.slice(0, 120)
@@ -7,9 +7,11 @@ import { createAgentDeviceClient } from 'agent-device';
7
7
  import { bridgeCommand } from './bridge.mjs';
8
8
  import {
9
9
  nativeAgentDeviceStateDir,
10
+ nativeAppId,
11
+ nativeAppLabel,
10
12
  nativeSessionName,
11
13
  } from './native-session-name.mjs';
12
- import { mobileToolRecovery, resolveMobileToolPath } from './tool-paths.mjs';
14
+ import { mobileToolRecovery, resolveMobileToolPath } from '@farmslot/adapter-rn';
13
15
 
14
16
  const execFileAsync = promisify(execFile);
15
17
  const VISIBLE_LIMIT = 200;
@@ -93,8 +95,13 @@ async function readNativeHierarchy(payload, context) {
93
95
  env.METAMASK_RECIPE_PROJECT_ROOT = String(context.projectRoot);
94
96
  }
95
97
  const platform = resolvePlatform(node, env);
96
- if (platform === 'android') return readAndroidHierarchy(node, env);
97
- return readIosHierarchy(node, env);
98
+ const appLabel = nativeAppLabel(env);
99
+ const hierarchy = platform === 'android'
100
+ ? await readAndroidHierarchy(node, env)
101
+ : await readIosHierarchy(node, env);
102
+ // No screen name and no labelled item: name the screen after the app.
103
+ hierarchy.screen.name ??= appLabel;
104
+ return hierarchy;
98
105
  }
99
106
 
100
107
  async function readIosHierarchy(node, env) {
@@ -166,7 +173,7 @@ async function readAndroidHierarchy(node, env) {
166
173
  }
167
174
 
168
175
  async function readAndroidAgentDeviceHierarchy(serial, env) {
169
- const app = text(env.ANDROID_PACKAGE_ID) ?? 'io.metamask';
176
+ const app = nativeAppId(env, 'android');
170
177
  const session = nativeSessionName(env, serial, app);
171
178
  const stateDir = nativeAgentDeviceStateDir(env, env.METAMASK_RECIPE_PROJECT_ROOT ?? process.cwd(), serial);
172
179
  const client = createAgentDeviceClient({
@@ -257,8 +264,7 @@ function normalizedHierarchy(
257
264
  const items = nodes.slice(0, VISIBLE_LIMIT);
258
265
  const hiddenOrOffscreen = hiddenNodes.slice(0, HIDDEN_LIMIT);
259
266
  const name = metadata.screenName ??
260
- items.find((item) => item.label)?.label ??
261
- 'MetaMask';
267
+ items.find((item) => item.label)?.label;
262
268
  return {
263
269
  screen: {
264
270
  provider,
@@ -1,9 +1,10 @@
1
1
  // Navigate Android Home, Swap, and Perps through native controls and verify visible destinations.
2
2
  // Switch Perps Lite or Pro mode through visible controls on Android and iOS.
3
- import { createAgentDeviceUiTransport } from '@farmslot/expo-recipe';
3
+ import { createAgentDeviceUiTransport } from '@farmslot/adapter-rn';
4
4
  import { createAgentDeviceClient } from 'agent-device';
5
5
  import {
6
6
  nativeAgentDeviceStateDir,
7
+ nativeAppId,
7
8
  nativeSessionName,
8
9
  } from '../platform/native-session-name.mjs';
9
10
  import { bridgeCommand } from '../platform/bridge.mjs';
@@ -51,7 +52,7 @@ export async function navigateOpaqueMobile(input, destination) {
51
52
  if (!device) {
52
53
  throw new Error('Opaque Mobile navigation requires one explicit Android device serial.');
53
54
  }
54
- const app = String(env.ANDROID_PACKAGE_ID ?? 'io.metamask');
55
+ const app = nativeAppId(env, 'android');
55
56
  const session = nativeSessionName(env, device, app);
56
57
  const stateDir = nativeAgentDeviceStateDir(env, input.context.projectRoot, device);
57
58
  const client = createAgentDeviceClient({
@@ -184,7 +185,7 @@ export async function ensureVisibleAndroidPerpsMode(input, perpsMode) {
184
185
  'Android Perps mode requires one explicit device serial.',
185
186
  );
186
187
  }
187
- const app = String(env.ANDROID_PACKAGE_ID ?? 'io.metamask');
188
+ const app = nativeAppId(env, 'android');
188
189
  const session = nativeSessionName(env, device, app);
189
190
  const stateDir = nativeAgentDeviceStateDir(env, input.context.projectRoot, device);
190
191
  const client = createAgentDeviceClient({
@@ -1,9 +1,10 @@
1
1
  // Drive Android wallet onboarding, lock, unlock, and reset through native accessibility.
2
2
  // Wallet observations can navigate home and open the address list to verify the selected account.
3
- import { createAgentDeviceUiTransport } from '@farmslot/expo-recipe';
3
+ import { createAgentDeviceUiTransport } from '@farmslot/adapter-rn';
4
4
  import { createAgentDeviceClient } from 'agent-device';
5
5
  import {
6
6
  nativeAgentDeviceStateDir,
7
+ nativeAppId,
7
8
  nativeSessionName,
8
9
  } from '../platform/native-session-name.mjs';
9
10
 
@@ -249,7 +250,7 @@ function nativeSession(input) {
249
250
  const env = { ...process.env, ...(input.context?.env ?? {}) };
250
251
  const device = String(env.ADB_SERIAL ?? env.ANDROID_SERIAL ?? '').trim();
251
252
  if (!device) throw new Error('Opaque Mobile UI execution requires one explicit Android device serial.');
252
- const app = String(env.ANDROID_PACKAGE_ID ?? 'io.metamask');
253
+ const app = nativeAppId(env, 'android');
253
254
  const session = nativeSessionName(env, device, app);
254
255
  const stateDir = nativeAgentDeviceStateDir(env, input.context.projectRoot, device);
255
256
  const client = createAgentDeviceClient({
@@ -1,6 +1,6 @@
1
1
  // Minimal adapter IO for the shared analytics actions.
2
2
  //
3
- // Mirrors the live-adapter contract in src/live-adapter-contract.ts. Neither
3
+ // Mirrors the live-adapter contract in @farmslot/recipe-cli/harness. Neither
4
4
  // existing runAdapter fits: extension's lives under actions/extension/platform
5
5
  // and core's carries PerpsController teardown and expect_error handling. A
6
6
  // `shared/` action importing either would couple every platform to one of them,
@@ -1,6 +1,6 @@
1
1
  // The one console matcher: classify application console events and apply a
2
2
  // recipe-library allowlist of known noise. Used by run diagnostics
3
- // (src/run-diagnostics.ts), the assert_no_console_errors action, the
3
+ // (src/console-classifier.ts), the assert_no_console_errors action, the
4
4
  // allowlist generator, and the recipe library's checks/console-guard.mjs
5
5
  // (which imports this file), so every caller judges an event the same way.
6
6
  //
@@ -9,7 +9,8 @@ import { pathToFileURL } from 'node:url';
9
9
 
10
10
  import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts';
11
11
 
12
- import { recipeRuntimeDir, walletFixturePath } from '../../harness-exports.mjs';
12
+ import { recipeRuntimeDir } from '@farmslot/recipe-cli/harness';
13
+ import { walletFixturePath } from '../../harness-exports.mjs';
13
14
 
14
15
  export const SIGNER_MODES = Object.freeze(['extension', 'injected']);
15
16
  export const DEFAULT_ACCOUNT = 'dev1';