@deeeed/metamask-harness 0.78.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 (215) hide show
  1. package/CHANGELOG.md +55 -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/ensure-runner-deps.sh +2 -2
  21. package/adapters/shared/harness-source-fingerprint.mjs +13 -8
  22. package/adapters/shared/open-debug.mjs +2 -2
  23. package/adapters/shared/runner-deps-ready.mjs +1 -1
  24. package/adapters/shared/sync-wallet-fixture.sh +8 -5
  25. package/adapters/terminal/cleanup.mjs +2 -1
  26. package/adapters/terminal/inject.mjs +2 -1
  27. package/adapters/terminal/lib/cdp-client.mjs +1 -1
  28. package/bin/mm-harness +9 -28
  29. package/dist/adapters/checkout-files.js +16 -0
  30. package/dist/adapters/console-capture.js +14 -1
  31. package/dist/adapters/core/actions.js +4 -3
  32. package/dist/adapters/core/overlay.js +1 -1
  33. package/dist/adapters/core/readiness.js +28 -1
  34. package/dist/adapters/core/run.js +3 -3
  35. package/dist/adapters/core/surface.js +22 -5
  36. package/dist/adapters/extension/browser-pid.js +56 -0
  37. package/dist/adapters/extension/console-capture.js +1 -1
  38. package/dist/adapters/extension/doctor-checks.js +1 -1
  39. package/dist/adapters/extension/ensure-ready.js +5 -4
  40. package/dist/adapters/extension/extension-id.js +1 -1
  41. package/dist/adapters/extension/feature-flags.js +1 -1
  42. package/dist/adapters/extension/fixtures.js +1 -2
  43. package/dist/adapters/extension/launch.js +4 -5
  44. package/dist/adapters/extension/launched-browser.js +1 -2
  45. package/dist/adapters/extension/network-observer.js +1 -1
  46. package/dist/adapters/extension/overlay.js +1 -1
  47. package/dist/adapters/extension/product-config.js +1 -1
  48. package/dist/adapters/extension/readiness.js +1 -1
  49. package/dist/adapters/extension/reload.js +1 -1
  50. package/dist/adapters/extension/run.js +24 -10
  51. package/dist/adapters/extension/runtime-decision.js +3 -2
  52. package/dist/adapters/extension/runtime.js +9 -5
  53. package/dist/adapters/extension/surface.js +20 -5
  54. package/dist/adapters/extension/verify.js +2 -2
  55. package/dist/adapters/harness-freshness.js +2 -1
  56. package/dist/adapters/mobile/android-gfxinfo-performance.js +1 -3
  57. package/dist/adapters/mobile/app-identity.js +10 -0
  58. package/dist/adapters/mobile/doctor-checks.js +1 -1
  59. package/dist/adapters/mobile/fixtures.js +1 -1
  60. package/dist/adapters/mobile/launch.js +27 -28
  61. package/dist/adapters/mobile/lifecycle.js +1 -1
  62. package/dist/adapters/mobile/metro-env.js +9 -59
  63. package/dist/adapters/mobile/performance-observer.js +1 -1
  64. package/dist/adapters/mobile/prepare-target.js +1 -1
  65. package/dist/adapters/mobile/prepare.js +4 -5
  66. package/dist/adapters/mobile/provision.js +1 -1
  67. package/dist/adapters/mobile/readiness.js +1 -1
  68. package/dist/adapters/mobile/recording-target.js +15 -0
  69. package/dist/adapters/mobile/release-artifact-state.js +1 -1
  70. package/dist/adapters/mobile/reload.js +1 -1
  71. package/dist/adapters/mobile/run.js +9 -8
  72. package/dist/adapters/mobile/runtime-decision.js +5 -7
  73. package/dist/adapters/mobile/source-freshness.js +22 -126
  74. package/dist/adapters/mobile/surface.js +31 -6
  75. package/dist/adapters/mobile/ui.js +16 -5
  76. package/dist/adapters/mobile/verify.js +2 -3
  77. package/dist/adapters/performance/cdp-trace.js +1 -1
  78. package/dist/adapters/performance/device-platform.js +1 -1
  79. package/dist/adapters/registry.js +2 -14
  80. package/dist/adapters/slot-ports.js +1 -1
  81. package/dist/adapters/surface.js +1 -0
  82. package/dist/adapters/terminal/console-capture.js +1 -1
  83. package/dist/adapters/terminal/launch.js +1 -1
  84. package/dist/adapters/terminal/run.js +25 -2
  85. package/dist/adapters/terminal/surface.js +12 -4
  86. package/dist/adapters.js +8 -4
  87. package/dist/advisor-policy.js +1 -1
  88. package/dist/checkout-view.js +1 -2
  89. package/dist/cli.js +28 -15
  90. package/dist/command-contract.js +1 -26
  91. package/dist/commands/call.js +10 -24
  92. package/dist/commands/check.js +3 -4
  93. package/dist/commands/checklist.js +1 -1
  94. package/dist/commands/completion-candidates.js +2 -10
  95. package/dist/commands/config.js +2 -2
  96. package/dist/commands/core-readiness.js +12 -7
  97. package/dist/commands/debug.js +3 -3
  98. package/dist/commands/device-target.js +5 -5
  99. package/dist/commands/doctor.js +2 -14
  100. package/dist/commands/domain.js +2 -2
  101. package/dist/commands/ensure-ready.js +1 -7
  102. package/dist/commands/fixtures.js +5 -6
  103. package/dist/commands/help.js +3 -4
  104. package/dist/commands/launch/device.js +10 -17
  105. package/dist/commands/launch/extension.js +3 -9
  106. package/dist/commands/launch/mobile.js +1 -1
  107. package/dist/commands/list-executables.js +1 -3
  108. package/dist/commands/logs.js +3 -3
  109. package/dist/commands/manifest.js +1 -12
  110. package/dist/commands/mobile-device-view.js +4 -2
  111. package/dist/commands/overlay.js +110 -0
  112. package/dist/commands/pin.js +1 -1
  113. package/dist/commands/pr-body.js +2 -2
  114. package/dist/commands/prepare.js +6 -11
  115. package/dist/commands/provision.js +3 -12
  116. package/dist/commands/recipe-advice.js +1 -2
  117. package/dist/commands/recipe-quality.js +2 -3
  118. package/dist/commands/resolve-extension.js +1 -7
  119. package/dist/commands/review.js +4 -4
  120. package/dist/commands/run-engine.js +12 -51
  121. package/dist/commands/run.js +4 -25
  122. package/dist/commands/runtime-decision.js +1 -6
  123. package/dist/commands/runtime-health.js +1 -7
  124. package/dist/commands/runtime-launch.js +1 -13
  125. package/dist/commands/self-test.js +2 -6
  126. package/dist/commands/status-watch.js +1 -2
  127. package/dist/commands/status.js +2 -5
  128. package/dist/commands/task-init.js +2 -3
  129. package/dist/commands/tutorial.js +2 -2
  130. package/dist/commands/update.js +1 -1
  131. package/dist/completions-cache.js +1 -1
  132. package/dist/console-classifier.js +19 -0
  133. package/dist/discovery.js +1 -1
  134. package/dist/doctor.js +3 -4
  135. package/dist/funding-execution-context.js +15 -1
  136. package/dist/host.js +32 -0
  137. package/dist/index.js +1 -6
  138. package/dist/live-tsx.js +49 -0
  139. package/dist/mm-harness-cli.js +2 -4
  140. package/dist/network-observation.js +4 -4
  141. package/dist/paths.js +48 -78
  142. package/dist/performance-observation.js +2 -2
  143. package/dist/progress.js +1 -1
  144. package/dist/recipe-assistance-context.js +2 -4
  145. package/dist/recipe-security.js +2 -59
  146. package/dist/review/knowledge.js +3 -3
  147. package/dist/runner.js +5 -3
  148. package/dist/runtime-context.js +1 -1
  149. package/dist/task-view.js +3 -4
  150. package/docs/CONTRIBUTING.md +3 -3
  151. package/docs/cli-contract.md +2 -2
  152. package/docs/feature-guard.md +90 -0
  153. package/library/README.md +3 -1
  154. package/library/actions/core/perps/_controller.mjs +153 -130
  155. package/library/actions/core/perps/quote_fees.mjs +58 -0
  156. package/library/actions/core/wallet/_fixture-accounts.mjs +69 -0
  157. package/library/actions/core/wallet/list_accounts.mjs +14 -57
  158. package/library/actions/extension/feature_flags/state.mjs +1 -1
  159. package/library/actions/extension/platform/cdp.mjs +2 -8
  160. package/library/actions/harness-exports.mjs +1 -6
  161. package/library/actions/mobile/app/network-control.mjs +6 -8
  162. package/library/actions/mobile/perps/prepare_local_snapshot_endpoint.mjs +1 -1
  163. package/library/actions/mobile/platform/bridge.mjs +8 -8
  164. package/library/actions/mobile/platform/native-session-name.mjs +21 -1
  165. package/library/actions/mobile/platform/native-session.mjs +3 -4
  166. package/library/actions/mobile/platform/observe-ui.mjs +12 -6
  167. package/library/actions/mobile/ui/native-navigation.mjs +4 -3
  168. package/library/actions/mobile/wallet/native-ui.mjs +3 -2
  169. package/library/actions/shared/analytics/_adapter.mjs +1 -1
  170. package/library/actions/shared/console-findings.mjs +1 -1
  171. package/library/actions/terminal/platform/runtime.mjs +2 -1
  172. package/library/manifests/core.action-manifest.json +83 -13
  173. package/package.json +8 -8
  174. package/adapters/extension/lib/slot-title.cjs +0 -292
  175. package/adapters/mobile/bridge-runtime/console-forwarder.cjs +0 -472
  176. package/adapters/mobile/bridge-runtime/lib/bridge-errors.cjs +0 -125
  177. package/adapters/mobile/bridge-runtime/lib/cdp-broker.cjs +0 -1055
  178. package/adapters/mobile/bridge-runtime/lib/cdp-eval.cjs +0 -110
  179. package/adapters/mobile/bridge-runtime/lib/config.cjs +0 -61
  180. package/adapters/mobile/bridge-runtime/lib/console-format.cjs +0 -53
  181. package/adapters/mobile/bridge-runtime/lib/devtools-proxy.cjs +0 -177
  182. package/adapters/mobile/bridge-runtime/lib/issue-capture.cjs +0 -446
  183. package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +0 -84
  184. package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +0 -403
  185. package/adapters/mobile/bridge-runtime/lib/ws-client.cjs +0 -116
  186. package/adapters/mobile/coalesce-metro-log.cjs +0 -24
  187. package/adapters/mobile/launch-metro.cjs +0 -77
  188. package/adapters/mobile/metro-config.cjs +0 -98
  189. package/adapters/mobile/metro-log-generation.cjs +0 -106
  190. package/dist/adapters/mobile/frame-metrics.js +0 -118
  191. package/dist/adapters/mobile/video-recorder.js +0 -289
  192. package/dist/artifact-files.js +0 -92
  193. package/dist/checkout-lock.js +0 -149
  194. package/dist/cli-color.js +0 -98
  195. package/dist/command-journal.js +0 -308
  196. package/dist/commands/last.js +0 -60
  197. package/dist/commands/launch/index.js +0 -109
  198. package/dist/commands/parse-args.js +0 -279
  199. package/dist/commands/reload.js +0 -41
  200. package/dist/commands/run-report.js +0 -241
  201. package/dist/commands/shared.js +0 -323
  202. package/dist/commands/stop.js +0 -60
  203. package/dist/devices.js +0 -100
  204. package/dist/execution-provenance.js +0 -332
  205. package/dist/harness.js +0 -451
  206. package/dist/heal-bounds.js +0 -137
  207. package/dist/json-stream.js +0 -60
  208. package/dist/leaf-invoke.js +0 -19
  209. package/dist/library-provenance.js +0 -33
  210. package/dist/live-adapter-contract.js +0 -380
  211. package/dist/recording-target.js +0 -120
  212. package/dist/run-diagnostics.js +0 -364
  213. package/dist/run-recording.js +0 -481
  214. package/dist/runtime-proof.js +0 -90
  215. package/library/actions/mobile/platform/tool-paths.mjs +0 -123
@@ -3,7 +3,7 @@ import {
3
3
  bridgeCommand,
4
4
  MOBILE_BRIDGE_ERROR_CODES
5
5
  } from "../../../library/actions/mobile/platform/bridge.mjs";
6
- import { resolveMobileToolPath } from "../../../library/actions/mobile/platform/tool-paths.mjs";
6
+ import { resolveMobileToolPath } from "@farmslot/adapter-rn";
7
7
  import {
8
8
  MOBILE_LIFECYCLE_DEADLINE_FIELD,
9
9
  MOBILE_LIFECYCLE_PREPARED
@@ -1,68 +1,18 @@
1
- import { createHash } from "node:crypto";
2
- import fs from "node:fs";
3
- import path from "node:path";
4
- import { recipeRuntimePath } from "../../paths.js";
5
- const ENV_INPUTS = [".js.env", ".env", ".env.local"];
6
- const PROCESS_ENV_INPUTS = [
7
- "SEGMENT_PROXY_URL",
8
- "SEGMENT_WRITE_KEY",
9
- "SEGMENT_FLUSH_INTERVAL",
10
- "SEGMENT_FLUSH_EVENT_LIMIT"
11
- ];
1
+ import { metroEnvCheck, metroEnvFingerprint, recordMetroEnvBaseline } from "@farmslot/adapter-rn";
2
+ import { recipeRuntimePath } from "@farmslot/recipe-cli/harness";
3
+ const MOBILE_METRO_ENV_INPUTS = {
4
+ files: [".js.env", ".env", ".env.local"],
5
+ env: ["SEGMENT_PROXY_URL", "SEGMENT_WRITE_KEY", "SEGMENT_FLUSH_INTERVAL", "SEGMENT_FLUSH_EVENT_LIMIT"]
6
+ };
12
7
  const BASELINE_FILE = "metro-env-baseline.json";
13
8
  function mobileMetroEnvFingerprint(target) {
14
- const hash = createHash("sha256");
15
- for (const relative of ENV_INPUTS) {
16
- const absolute = path.join(target, relative);
17
- hash.update(`${relative}\0`);
18
- if (fs.existsSync(absolute)) hash.update(fs.readFileSync(absolute));
19
- else hash.update("<absent>");
20
- hash.update("\0");
21
- }
22
- for (const name of PROCESS_ENV_INPUTS) {
23
- hash.update(`${name}\0${process.env[name] ?? "<absent>"}\0`);
24
- }
25
- return hash.digest("hex");
9
+ return metroEnvFingerprint(target, MOBILE_METRO_ENV_INPUTS);
26
10
  }
27
11
  function mobileMetroEnvCheck(target) {
28
- const fingerprint = mobileMetroEnvFingerprint(target);
29
- const marker = readBaseline(target);
30
- return {
31
- fingerprint,
32
- status: marker === null ? "missing" : marker.fingerprint === fingerprint ? "current" : "changed"
33
- };
12
+ return metroEnvCheck(target, MOBILE_METRO_ENV_INPUTS, recipeRuntimePath(target, BASELINE_FILE));
34
13
  }
35
14
  function recordMobileMetroEnvBaseline(target, expectedFingerprint) {
36
- if (mobileMetroEnvFingerprint(target) !== expectedFingerprint) return false;
37
- const marker = recipeRuntimePath(target, BASELINE_FILE);
38
- const temporary = `${marker}.${process.pid}.${Date.now()}.tmp`;
39
- const baseline = {
40
- schemaVersion: 1,
41
- fingerprint: expectedFingerprint,
42
- recordedAt: (/* @__PURE__ */ new Date()).toISOString()
43
- };
44
- fs.mkdirSync(path.dirname(marker), { recursive: true });
45
- try {
46
- fs.writeFileSync(temporary, `${JSON.stringify(baseline, null, 2)}
47
- `, { mode: 384 });
48
- if (mobileMetroEnvFingerprint(target) !== expectedFingerprint) return false;
49
- fs.renameSync(temporary, marker);
50
- if (mobileMetroEnvFingerprint(target) !== expectedFingerprint) {
51
- if (readBaseline(target)?.fingerprint === expectedFingerprint) fs.rmSync(marker, { force: true });
52
- return false;
53
- }
54
- } finally {
55
- fs.rmSync(temporary, { force: true });
56
- }
57
- return true;
58
- }
59
- function readBaseline(target) {
60
- try {
61
- const parsed = JSON.parse(fs.readFileSync(recipeRuntimePath(target, BASELINE_FILE), "utf8"));
62
- return parsed.schemaVersion === 1 && typeof parsed.fingerprint === "string" ? parsed : null;
63
- } catch {
64
- return null;
65
- }
15
+ return recordMetroEnvBaseline(target, MOBILE_METRO_ENV_INPUTS, recipeRuntimePath(target, BASELINE_FILE), expectedFingerprint);
66
16
  }
67
17
  export {
68
18
  mobileMetroEnvCheck,
@@ -37,7 +37,7 @@ async function markRuntime(client, name) {
37
37
  }
38
38
  function resolvePlatform(env) {
39
39
  const explicit = String(
40
- env.MM_HARNESS_EXPLICIT_PLATFORM ?? env.RECIPE_HARNESS_PLATFORM ?? ""
40
+ env.RECIPE_RN_EXPLICIT_PLATFORM ?? env.RECIPE_HARNESS_PLATFORM ?? ""
41
41
  ).toLowerCase();
42
42
  if (explicit === "android" || explicit === "ios") return explicit;
43
43
  return env.ADB_SERIAL || env.ANDROID_SERIAL || env.ANDROID_DEVICE ? "android" : "ios";
@@ -1,6 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
- import { recipeRuntimeDir } from "../../paths.js";
3
+ import { recipeRuntimeDir } from "@farmslot/recipe-cli/harness";
4
4
  function readRuntimeContextTarget(target) {
5
5
  const file = path.join(target, recipeRuntimeDir(), "agentic-runtime.json");
6
6
  let parsed;
@@ -1,9 +1,8 @@
1
+ import { EXIT, spawnScriptStreaming, shellQuote } from "@farmslot/recipe-cli/harness";
1
2
  import { execFileSync } from "node:child_process";
2
3
  import path from "node:path";
3
- import { recordDepsBaseline } from "@farmslot/recipe-harness/runtime/deps-readiness";
4
- import { resolveAndExportMobileToolPaths } from "../../../library/actions/mobile/platform/tool-paths.mjs";
5
- import { EXIT, spawnScriptStreaming } from "../../commands/shared.js";
6
- import { shellQuote } from "../../commands/parse-args.js";
4
+ import { recordDepsBaseline } from "@farmslot/recipe-runner/runtime/deps-readiness";
5
+ import { resolveAndExportMobileToolPaths } from "@farmslot/adapter-rn";
7
6
  import { runnerDir } from "../../paths.js";
8
7
  import {
9
8
  decideMobileReadiness,
@@ -401,7 +400,7 @@ async function dispatchAction(action, target, platform, json, preflightMode = "f
401
400
  leaf,
402
401
  withWatcherPort(["--target", cwd, ...extra], watcherPort),
403
402
  target,
404
- { MM_HARNESS_METRO_ENV_FINGERPRINT: metroEnvFingerprint ?? mobileMetroEnvCheck(path.resolve(target)).fingerprint }
403
+ { RECIPE_RN_METRO_ENV_FINGERPRINT: metroEnvFingerprint ?? mobileMetroEnvCheck(path.resolve(target)).fingerprint }
405
404
  );
406
405
  }
407
406
  case "start-console-forwarder": {
@@ -1,9 +1,9 @@
1
+ import { recipeRuntimeDir } from "@farmslot/recipe-cli/harness";
1
2
  import { execFileSync } from "node:child_process";
2
3
  import crypto from "node:crypto";
3
4
  import fs from "node:fs";
4
5
  import os from "node:os";
5
6
  import path from "node:path";
6
- import { recipeRuntimeDir } from "../../paths.js";
7
7
  import { resolveSlotPortsByRepo } from "../../../adapters/shared/resolve-slot-ports-core.mjs";
8
8
  const RUNWAY_IOS_METADATA = {
9
9
  artifactName: "ios-app-main-dev-expo",
@@ -1,4 +1,4 @@
1
- import { optionFlag, optionString, parsePort } from "../../commands/parse-args.js";
1
+ import { optionFlag, optionString, parsePort } from "@farmslot/recipe-cli/harness";
2
2
  import { detectOrphanMetros, mobileToolDoctorChecks } from "./doctor-checks.js";
3
3
  import { ensureMobilePerpsEnvironment, mobilePerpsEnvironment } from "./perps-env.js";
4
4
  const mobileReadiness = {
@@ -0,0 +1,15 @@
1
+ function resolveAndroidDeviceSerial(env = process.env) {
2
+ return env.ADB_SERIAL?.trim() || process.env.ADB_SERIAL?.trim() || void 0;
3
+ }
4
+ function resolveMobileIosSimulatorName(env = process.env) {
5
+ const explicit = env.IOS_SIMULATOR?.trim() || env.SIM_UDID?.trim();
6
+ if (explicit) return explicit;
7
+ const slot = env.FARMSLOT_SLOT_ID?.trim() || env.SLOT_ID?.trim();
8
+ if (!slot) return void 0;
9
+ const suffix = slot.includes("-") ? slot.slice(slot.lastIndexOf("-") + 1) : slot;
10
+ return suffix || void 0;
11
+ }
12
+ export {
13
+ resolveAndroidDeviceSerial,
14
+ resolveMobileIosSimulatorName
15
+ };
@@ -1,7 +1,7 @@
1
+ import { recipeRuntimePath } from "@farmslot/recipe-cli/harness";
1
2
  import { randomBytes } from "node:crypto";
2
3
  import fs from "node:fs";
3
4
  import path from "node:path";
4
- import { recipeRuntimePath } from "../../paths.js";
5
5
  import {
6
6
  assertAndroidReleaseArtifactRuntime
7
7
  } from "./release-artifact.js";
@@ -1,5 +1,5 @@
1
+ import { EXIT, spawnScript } from "@farmslot/recipe-cli/harness";
1
2
  import path from "node:path";
2
- import { EXIT, spawnScript } from "../../commands/shared.js";
3
3
  import { runnerDir } from "../../paths.js";
4
4
  function reloadMobile(target, json) {
5
5
  const script = path.join(runnerDir, "adapters/mobile/reload-app.mjs");
@@ -1,17 +1,18 @@
1
+ import { isRecord } from "@farmslot/recipe-cli/harness";
1
2
  import { spawnSync } from "node:child_process";
2
3
  import path from "node:path";
3
- import { isRecord } from "../../commands/parse-args.js";
4
+ import { IOS_SIMULATOR_RECORDING_TARGET_PREFIX } from "@farmslot/adapter-rn";
4
5
  import { runnerDir } from "../../paths.js";
5
6
  import { devicePlatformFromEnv } from "../performance/device-platform.js";
6
7
  import {
7
- IOS_SIMULATOR_RECORDING_TARGET_PREFIX,
8
8
  resolveAndroidDeviceSerial,
9
9
  resolveMobileIosSimulatorName
10
- } from "../../recording-target.js";
10
+ } from "./recording-target.js";
11
+ import { mobileAppIdentityEnv } from "./app-identity.js";
11
12
  import { requireCurrentMobileReleaseArtifactState } from "./release-artifact-state.js";
12
13
  const EXPLICIT_DEVICE_KEYS = [
13
14
  "PLATFORM",
14
- "MM_HARNESS_EXPLICIT_PLATFORM",
15
+ "RECIPE_RN_EXPLICIT_PLATFORM",
15
16
  "IOS_SIMULATOR",
16
17
  "SIM_UDID",
17
18
  "ADB_SERIAL",
@@ -21,7 +22,7 @@ const EXPLICIT_DEVICE_KEYS = [
21
22
  ];
22
23
  const mobileRun = {
23
24
  pinnedEnv() {
24
- if (!process.env.MM_HARNESS_EXPLICIT_PLATFORM) return void 0;
25
+ if (!process.env.RECIPE_RN_EXPLICIT_PLATFORM) return void 0;
25
26
  return Object.fromEntries(EXPLICIT_DEVICE_KEYS.map((key) => [key, process.env[key]]));
26
27
  },
27
28
  envKeys: ["METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME", "ANDROID_PACKAGE_ID", "ADB_SERIAL", "ANDROID_SERIAL"],
@@ -49,7 +50,7 @@ const mobileRun = {
49
50
  ANDROID_TARGET_DEVICE_NAME: process.env.ANDROID_TARGET_DEVICE_NAME,
50
51
  ADB_SERIAL: process.env.ADB_SERIAL,
51
52
  ANDROID_SERIAL: process.env.ANDROID_SERIAL,
52
- ANDROID_PACKAGE_ID: process.env.ANDROID_PACKAGE_ID,
53
+ ...mobileAppIdentityEnv(),
53
54
  METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME: process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME
54
55
  }),
55
56
  // An installed release artifact is opaque: no harness HUD inside it.
@@ -78,9 +79,9 @@ const mobileRecording = {
78
79
  const androidSerial = resolveAndroidDeviceSerial();
79
80
  const iosSimulator = androidSerial ? void 0 : resolveMobileIosSimulatorName();
80
81
  if (!androidSerial && !iosSimulator) return void 0;
81
- const { createAndroidVideoRecorder, createIosSimulatorVideoRecorder } = await import("./video-recorder.js");
82
+ const { createAndroidVideoRecorder, createIosSimulatorVideoRecorder } = await import("@farmslot/adapter-rn");
82
83
  if (androidSerial) {
83
- const { createAndroidMirrorVideoRecorder } = await import("@farmslot/recipe-harness");
84
+ const { createAndroidMirrorVideoRecorder } = await import("@farmslot/recipe-runner");
84
85
  return createAndroidMirrorVideoRecorder({ serial: androidSerial, fallback: createAndroidVideoRecorder(androidSerial) });
85
86
  }
86
87
  return createIosSimulatorVideoRecorder(iosSimulator);
@@ -5,16 +5,14 @@ import path from "node:path";
5
5
  import {
6
6
  depsCheck,
7
7
  recordDepsBaseline
8
- } from "@farmslot/recipe-harness/runtime/deps-readiness";
8
+ } from "@farmslot/recipe-runner/runtime/deps-readiness";
9
9
  import {
10
10
  analyzeBundleLog,
11
11
  supersededErrorCapture
12
- } from "@farmslot/recipe-harness/runtime/log-analysis";
13
- import { probeMetroPackager } from "@farmslot/recipe-harness/runtime/metro-probe";
14
- import { color } from "../../cli-color.js";
15
- import { shellQuote } from "../../commands/parse-args.js";
16
- import { recipeRuntimePath } from "../../paths.js";
17
- import { resolveMobileToolPath } from "../../../library/actions/mobile/platform/tool-paths.mjs";
12
+ } from "@farmslot/recipe-runner/runtime/log-analysis";
13
+ import { probeMetroPackager } from "@farmslot/recipe-runner/runtime/metro-probe";
14
+ import { color, recipeRuntimePath, shellQuote } from "@farmslot/recipe-cli/harness";
15
+ import { resolveMobileToolPath } from "@farmslot/adapter-rn";
18
16
  import { mobileProductMarkers } from "./deps-markers.js";
19
17
  import { mobileMetroEnvCheck } from "./metro-env.js";
20
18
  const BUNDLE_ERR = /Bundling failed|Unable to resolve /u;
@@ -1,135 +1,31 @@
1
- import { execFileSync } from "node:child_process";
2
- import { createHash } from "node:crypto";
3
- import fs from "node:fs";
4
- import path from "node:path";
5
- import { recipeRuntimePath } from "../../paths.js";
6
- const SOURCE_PATHS = [
7
- "app",
8
- "locales",
9
- "index.js",
10
- "shim.js",
11
- "shimPerf.js",
12
- "ReactotronConfig.js",
13
- "wdyr.js",
14
- "app.config.js",
15
- "babel.config.js",
16
- "metro.config.js",
17
- "metro.transform.js",
18
- "package.json"
19
- ];
20
- const ENV_INPUTS = [".js.env", ".env", ".env.local"];
1
+ import { recordSourceBaseline, sourceCheck, sourceFingerprint } from "@farmslot/adapter-rn";
2
+ import { recipeRuntimePath } from "@farmslot/recipe-cli/harness";
3
+ const MOBILE_SOURCE_INPUTS = {
4
+ paths: [
5
+ "app",
6
+ "locales",
7
+ "index.js",
8
+ "shim.js",
9
+ "shimPerf.js",
10
+ "ReactotronConfig.js",
11
+ "wdyr.js",
12
+ "app.config.js",
13
+ "babel.config.js",
14
+ "metro.config.js",
15
+ "metro.transform.js",
16
+ "package.json"
17
+ ],
18
+ envFiles: [".js.env", ".env", ".env.local"]
19
+ };
21
20
  const BASELINE_FILE = "mobile-source-baseline.json";
22
21
  function mobileSourceFingerprint(target) {
23
- const hash = createHash("sha256");
24
- const head = tryGit(target, ["rev-parse", "HEAD"]);
25
- if (head === null) {
26
- hash.update("non-git\0");
27
- for (const relative of SOURCE_PATHS) {
28
- hashPath(hash, target, relative);
29
- }
30
- } else {
31
- hash.update(head);
32
- hash.update(git(target, ["diff", "--no-ext-diff", "--binary", "HEAD", "--", ...SOURCE_PATHS]));
33
- const untracked = git(target, [
34
- "ls-files",
35
- "--others",
36
- "--exclude-standard",
37
- "-z",
38
- "--",
39
- ...SOURCE_PATHS
40
- ]).toString("utf8").split("\0").filter(Boolean).sort();
41
- for (const relative of untracked) {
42
- hash.update(`untracked\0${relative}\0`);
43
- hash.update(fs.readFileSync(path.join(target, relative)));
44
- hash.update("\0");
45
- }
46
- }
47
- for (const relative of ENV_INPUTS) {
48
- const absolute = path.join(target, relative);
49
- hash.update(`env\0${relative}\0`);
50
- hash.update(fs.existsSync(absolute) ? fs.readFileSync(absolute) : "<absent>");
51
- hash.update("\0");
52
- }
53
- return hash.digest("hex");
22
+ return sourceFingerprint(target, MOBILE_SOURCE_INPUTS);
54
23
  }
55
24
  function mobileSourceCheck(target) {
56
- const fingerprint = mobileSourceFingerprint(target);
57
- const baseline = readBaseline(target);
58
- return {
59
- fingerprint,
60
- status: baseline === null ? "missing" : baseline.fingerprint === fingerprint ? "current" : "changed"
61
- };
25
+ return sourceCheck(target, MOBILE_SOURCE_INPUTS, recipeRuntimePath(target, BASELINE_FILE));
62
26
  }
63
27
  function recordMobileSourceBaseline(target, expectedFingerprint) {
64
- if (mobileSourceFingerprint(target) !== expectedFingerprint) return false;
65
- const marker = recipeRuntimePath(target, BASELINE_FILE);
66
- const temporary = `${marker}.${process.pid}.${Date.now()}.tmp`;
67
- const baseline = {
68
- schemaVersion: 1,
69
- fingerprint: expectedFingerprint,
70
- recordedAt: (/* @__PURE__ */ new Date()).toISOString()
71
- };
72
- fs.mkdirSync(path.dirname(marker), { recursive: true });
73
- try {
74
- fs.writeFileSync(temporary, `${JSON.stringify(baseline, null, 2)}
75
- `, { mode: 384 });
76
- if (mobileSourceFingerprint(target) !== expectedFingerprint) return false;
77
- fs.renameSync(temporary, marker);
78
- if (mobileSourceFingerprint(target) !== expectedFingerprint) {
79
- if (readBaseline(target)?.fingerprint === expectedFingerprint) fs.rmSync(marker, { force: true });
80
- return false;
81
- }
82
- } finally {
83
- fs.rmSync(temporary, { force: true });
84
- }
85
- return true;
86
- }
87
- function git(target, args) {
88
- return execFileSync("git", args, {
89
- cwd: target,
90
- encoding: "buffer",
91
- maxBuffer: 128 * 1024 * 1024,
92
- stdio: ["ignore", "pipe", "ignore"]
93
- });
94
- }
95
- function tryGit(target, args) {
96
- try {
97
- return git(target, args);
98
- } catch {
99
- return null;
100
- }
101
- }
102
- function hashPath(hash, root, relative) {
103
- const absolute = path.join(root, relative);
104
- if (!fs.existsSync(absolute)) {
105
- hash.update(`absent\0${relative}\0`);
106
- return;
107
- }
108
- const stat = fs.lstatSync(absolute);
109
- if (stat.isDirectory()) {
110
- hash.update(`directory\0${relative}\0`);
111
- for (const child of fs.readdirSync(absolute).sort()) {
112
- hashPath(hash, root, path.join(relative, child));
113
- }
114
- return;
115
- }
116
- if (stat.isSymbolicLink()) {
117
- hash.update(`symlink\0${relative}\0${fs.readlinkSync(absolute)}\0`);
118
- return;
119
- }
120
- hash.update(`file\0${relative}\0`);
121
- hash.update(fs.readFileSync(absolute));
122
- hash.update("\0");
123
- }
124
- function readBaseline(target) {
125
- try {
126
- const parsed = JSON.parse(
127
- fs.readFileSync(recipeRuntimePath(target, BASELINE_FILE), "utf8")
128
- );
129
- return parsed.schemaVersion === 1 && typeof parsed.fingerprint === "string" ? parsed : null;
130
- } catch {
131
- return null;
132
- }
28
+ return recordSourceBaseline(target, MOBILE_SOURCE_INPUTS, recipeRuntimePath(target, BASELINE_FILE), expectedFingerprint);
133
29
  }
134
30
  export {
135
31
  mobileSourceCheck,
@@ -1,24 +1,26 @@
1
+ import { recipeRuntimePath, shellQuote, writeInteractiveProgress } from "@farmslot/recipe-cli/harness";
1
2
  import { spawnSync } from "node:child_process";
2
3
  import path from "node:path";
3
- import { recipeRuntimePath, runnerDir } from "../../paths.js";
4
- import { shellQuote } from "../../commands/parse-args.js";
4
+ import { defineAdapter } from "@farmslot/adapter-sdk";
5
+ import { runnerDir } from "../../paths.js";
5
6
  import { resolveMobileSlotPorts } from "../slot-ports.js";
6
7
  import { mobileRuntimeStatus } from "./prepare.js";
7
8
  import { hasRunwayProvisionBaseline, provisionRunwayMobile } from "./provision.js";
8
9
  import { adapterRegistry } from "../registry.js";
9
- import { writeInteractiveProgress } from "../../commands/shared.js";
10
10
  import { mobileActions } from "./actions.js";
11
11
  import { mobileHarness, mobilePrepare, mobileRuntimeContext } from "./overlay.js";
12
12
  import { mobileReadiness, mobileRuntimeDecision } from "./readiness.js";
13
13
  import { clearMobileReleaseArtifactState } from "./release-artifact-state.js";
14
14
  import { mobileDiagnostics, mobileObservation, mobileRecording, mobileRun } from "./run.js";
15
15
  import { mobileSourceFingerprint } from "./source-freshness.js";
16
+ import { checkoutHas, checkoutHasDir } from "../checkout-files.js";
16
17
  function mobileRuntimeNextAction(reasonCode, target, platform) {
17
18
  const resolved = shellQuote(path.resolve(target));
18
19
  return reasonCode === "deps-missing" || reasonCode === "deps-partial" || reasonCode === "deps-stale" ? `cd ${resolved} && yarn setup` : `mm-harness launch ${platform} --adapter mobile --target ${resolved}`;
19
20
  }
20
- const mobileSurface = {
21
- adapter: "mobile",
21
+ const mobileSurface = defineAdapter({
22
+ id: "mobile",
23
+ sdkVersion: 1,
22
24
  headless: false,
23
25
  resolveSlotPorts(target) {
24
26
  resolveMobileSlotPorts(target);
@@ -51,6 +53,9 @@ const mobileSurface = {
51
53
  devServer: {
52
54
  label: "metro",
53
55
  describe: () => "Metro dev server",
56
+ // Metro's leaves read the port as METRO_PORT; run and call take it as --metro-port.
57
+ portEnv: ["METRO_PORT"],
58
+ portFlags: ["metroPort"],
54
59
  stop(target) {
55
60
  const leaf = path.join(runnerDir, "adapters", "mobile", "stop-metro.sh");
56
61
  const args = ["--target", target];
@@ -68,6 +73,26 @@ const mobileSurface = {
68
73
  appLogSource(target) {
69
74
  return { label: "app", path: recipeRuntimePath(target, "app-console.log") };
70
75
  },
76
+ detect: {
77
+ remote: (url) => url.includes("metamask-mobile"),
78
+ files: (target) => checkoutHas(target, "scripts/skills-sync.mts") || checkoutHasDir(target, "ios") && checkoutHasDir(target, "android") && checkoutHasDir(target, "app/core")
79
+ },
80
+ // `launch ios|android`, and --platform ios|android, select Mobile.
81
+ targets: ["ios", "android"],
82
+ flags: {
83
+ launch: ["build", "watch", "verify", "runway", "clearMetro"],
84
+ commands: ["clearMetro", "resolveOnly"]
85
+ },
86
+ failurePatterns: {
87
+ captureProtected: {
88
+ pattern: /SCREENSHOT_PROTECTED|screenshot is protected by FLAG_SECURE/iu,
89
+ message: "the focused Android window intentionally blocks screenshots; a black capture cannot be used as evidence.",
90
+ userAction: "unlock or navigate to a non-protected screen, then rerun the screenshot action"
91
+ },
92
+ transportFirst: /CDP_TIMEOUT|Mobile CDP bridge command timed out|NO_TARGET|WS_CLOSED/iu,
93
+ walletState: /fixture|keyring|not seeded|\bsrp\b|password|onboard/iu,
94
+ transport: /metro|bridge|bundle|packager|dev client/iu
95
+ },
71
96
  hints: {
72
97
  launch: "mm-harness launch ios",
73
98
  relaunch: "mm-harness launch ios",
@@ -104,7 +129,7 @@ const mobileSurface = {
104
129
  diagnostics: mobileDiagnostics,
105
130
  observation: mobileObservation,
106
131
  sourceFingerprint: (root) => mobileSourceFingerprint(root)
107
- };
132
+ });
108
133
  adapterRegistry.register(mobileSurface);
109
134
  export {
110
135
  mobileRuntimeNextAction,
@@ -3,9 +3,13 @@ import { promisify } from "node:util";
3
3
  import { bridgeCommand, evalAsync, evalSync, MOBILE_BRIDGE_ERROR_CODES, selectBridgeStatusEntry, simulatorScreenshot } from "../../../library/actions/mobile/platform/bridge.mjs";
4
4
  import { observeNativeUi } from "../../../library/actions/mobile/platform/observe-ui.mjs";
5
5
  import { nativeAgentDeviceStateDir } from "../../../library/actions/mobile/platform/native-session-name.mjs";
6
- import { resolveMobileToolPath } from "../../../library/actions/mobile/platform/tool-paths.mjs";
6
+ import { resolveMobileToolPath } from "@farmslot/adapter-rn";
7
7
  import { isRecord, traceText } from "../../action-values.js";
8
- import { runLiveAdapterScript } from "../../live-adapter-contract.js";
8
+ import { runLiveAdapterScript } from "@farmslot/recipe-cli/harness";
9
+ import { trustedMutationContextExtras } from "../../funding-execution-context.js";
10
+ import { LIBRARY_ACTION_NAMESPACE } from "../../paths.js";
11
+ import { liveTsxCandidates } from "../../live-tsx.js";
12
+ import { mobileAppIdentityEnv } from "./app-identity.js";
9
13
  const execFileAsync = promisify(execFile);
10
14
  function sleep(ms) {
11
15
  return new Promise((resolve) => setTimeout(resolve, ms));
@@ -213,7 +217,10 @@ async function sendMobileBridgeCommand(command, context) {
213
217
  if (!handler) {
214
218
  throw new Error(`React Native bridge command ${command.command} is not implemented by the MetaMask runner transport.`);
215
219
  }
216
- return handler(command.payload, context);
220
+ return handler(command.payload, {
221
+ ...context,
222
+ env: { ...context.env, ...mobileAppIdentityEnv(context.env) }
223
+ });
217
224
  }
218
225
  function mobileUiInput(context, command, payload) {
219
226
  return uiInputFor(context, { action: `ui.${command}`, node: payload });
@@ -235,8 +242,11 @@ async function handleMobileNavigate(payload, context) {
235
242
  const live = await runLiveAdapterScript({
236
243
  platform: "mobile",
237
244
  action: "ui.navigate",
245
+ namespace: LIBRARY_ACTION_NAMESPACE,
238
246
  node: payload,
239
- context
247
+ context,
248
+ contextExtras: trustedMutationContextExtras(context),
249
+ tsxCandidates: liveTsxCandidates()
240
250
  });
241
251
  if (!live) throw new Error("ui.navigate requires library/actions/mobile/ui/navigate.mjs.");
242
252
  return isRecord(live.result) ? { ...live.result, liveAdapter: live.script } : live.result;
@@ -586,7 +596,8 @@ async function executeNativeProviderAction(action, node, context, createTranspor
586
596
  if (platform === "android" && action === "ui.swipe" && isCoordinateTarget(node.target)) {
587
597
  return executeAndroidCoordinateSwipe(node, String(device));
588
598
  }
589
- 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";
599
+ const identity = mobileAppIdentityEnv(context.env);
600
+ const app = platform === "android" ? identity.ANDROID_PACKAGE_ID : identity.IOS_BUNDLE_ID;
590
601
  const session = (platform === "ios" ? `mm-harness-${process.pid}-${device}` : `mm-harness-${process.pid}-${context.nodeId}`).replace(/[^a-zA-Z0-9._-]/gu, "-");
591
602
  const stateDir = nativeAgentDeviceStateDir(context.env ?? {}, context.projectRoot, String(device));
592
603
  let transport;
@@ -1,10 +1,9 @@
1
+ import { recipeHarnessPath, recipeHarnessRoot, recipeRuntimeDir, spawnScriptStreaming, readRuntimeContextField } from "@farmslot/recipe-cli/harness";
1
2
  import { execFileSync } from "node:child_process";
2
3
  import crypto from "node:crypto";
3
4
  import fs from "node:fs";
4
5
  import path from "node:path";
5
- import { spawnScriptStreaming } from "../../commands/shared.js";
6
- import { readRuntimeContextField } from "../../harness.js";
7
- import { recipeHarnessPath, recipeHarnessRoot, recipeRuntimeDir, runnerDir } from "../../paths.js";
6
+ import { runnerDir } from "../../paths.js";
8
7
  const USAGE = "Usage: mm-harness verify --adapter mobile -- [--target <metamask-mobile>] [--artifacts-dir <dir>] [--static-only] [--platform ios|android] [--preflight-mode fast|auto|default|rebuild-native|clean] [--no-auto-start]";
9
8
  const ENV_FILES = [".js.env", ".env", ".env.local"];
10
9
  const PREFLIGHT_MODES = ["fast", "auto", "default", "rebuild-native", "clean"];
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  summarizeFrames
3
- } from "../mobile/frame-metrics.js";
3
+ } from "@farmslot/adapter-rn";
4
4
  import { withTimeout } from "../../async.js";
5
5
  import {
6
6
  summarizeJavaScriptTasks
@@ -1,6 +1,6 @@
1
1
  function devicePlatformFromEnv(env) {
2
2
  const explicit = String(
3
- env.MM_HARNESS_EXPLICIT_PLATFORM ?? env.RECIPE_HARNESS_PLATFORM ?? ""
3
+ env.RECIPE_RN_EXPLICIT_PLATFORM ?? env.RECIPE_HARNESS_PLATFORM ?? ""
4
4
  ).toLowerCase();
5
5
  if (explicit === "android" || explicit === "ios") return explicit;
6
6
  return env.ADB_SERIAL || env.ANDROID_SERIAL || env.ANDROID_DEVICE ? "android" : "ios";
@@ -1,17 +1,5 @@
1
- const surfaces = /* @__PURE__ */ new Map();
2
- const adapterRegistry = {
3
- register(surface) {
4
- if (surfaces.has(surface.adapter)) throw new Error(`adapter '${surface.adapter}' is already registered`);
5
- surfaces.set(surface.adapter, surface);
6
- },
7
- get(id) {
8
- const surface = surfaces.get(id);
9
- if (!surface) throw new Error(`unknown adapter '${id}' (registered: ${adapterRegistry.list().join(", ")})`);
10
- return surface;
11
- },
12
- has: (id) => surfaces.has(id),
13
- list: () => [...surfaces.keys()]
14
- };
1
+ import { createAdapterRegistry } from "@farmslot/adapter-sdk";
2
+ const adapterRegistry = createAdapterRegistry();
15
3
  export {
16
4
  adapterRegistry
17
5
  };
@@ -1,8 +1,8 @@
1
+ import { recipeRuntimeDir } from "@farmslot/recipe-cli/harness";
1
2
  import { execFileSync } from "node:child_process";
2
3
  import fs from "node:fs";
3
4
  import os from "node:os";
4
5
  import path from "node:path";
5
- import { recipeRuntimeDir } from "../paths.js";
6
6
  import {
7
7
  formatExtensionIdentityPorts,
8
8
  resolveDefaultExtensionPorts,
@@ -1,3 +1,4 @@
1
+ import "../host.js";
1
2
  import { adapterRegistry } from "./registry.js";
2
3
  import "./mobile/surface.js";
3
4
  import "./extension/surface.js";
@@ -1,7 +1,7 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { recipeRuntimePath } from "@farmslot/recipe-cli/harness";
3
4
  import { verifyCapture } from "../../../library/actions/shared/console-collector.mjs";
4
- import { recipeRuntimePath } from "../../paths.js";
5
5
  import { ensureConsoleCollector } from "../console-capture.js";
6
6
  const TERMINAL_CONSOLE_FILES = Object.freeze({
7
7
  app: "app-console.log",
@@ -1,6 +1,6 @@
1
+ import { flag, str } from "@farmslot/recipe-cli/harness";
1
2
  import { execFileSync } from "node:child_process";
2
3
  import path from "node:path";
3
- import { flag, str } from "../../commands/shared.js";
4
4
  import { runnerDir } from "../../paths.js";
5
5
  const TERMINAL_ONLY_LAUNCH_OPTIONS = Object.freeze({
6
6
  signer: "--signer",