@deeeed/metamask-harness 0.17.3 → 0.17.5

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 (114) hide show
  1. package/CHANGELOG.md +6 -96
  2. package/README.md +136 -96
  3. package/adapters/extension/console-tail.mjs +24 -51
  4. package/adapters/extension/ensure-browser.sh +7 -1
  5. package/adapters/extension/inject.mjs +15 -5
  6. package/adapters/extension/launch-browser.cjs +4 -3
  7. package/adapters/extension/launch.sh +11 -8
  8. package/adapters/extension/lib/chrome-args.cjs +24 -0
  9. package/adapters/extension/live.sh +1 -2
  10. package/adapters/extension/reattach.sh +0 -1
  11. package/adapters/extension/seed-fixture.sh +12 -4
  12. package/adapters/extension/sidepanel-toggle.sh +1 -4
  13. package/adapters/extension/start-watch.sh +4 -17
  14. package/adapters/extension/stop-viewers.sh +1 -1
  15. package/adapters/extension/verify.sh +1 -1
  16. package/adapters/manifest.json +45 -53
  17. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +0 -16
  18. package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
  19. package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +1 -3
  20. package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +3 -2
  21. package/adapters/mobile/cleanup.sh +176 -16
  22. package/adapters/mobile/inject.sh +671 -90
  23. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +185 -0
  24. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +1662 -0
  25. package/adapters/mobile/overlay/compat/README.md +28 -0
  26. package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +42 -0
  27. package/adapters/mobile/start-metro.sh +41 -0
  28. package/adapters/mobile/verify.sh +61 -4
  29. package/adapters/mobile/wait-for-bridge.sh +8 -22
  30. package/adapters/shared/harness-source-fingerprint.mjs +49 -0
  31. package/adapters/shared/install-repo-deps.sh +1 -5
  32. package/adapters/shared/open-debug.mjs +184 -103
  33. package/adapters/shared/resolve-farmslot-ports-core.mjs +3 -0
  34. package/adapters/shared/resolve-farmslot-ports.mjs +5 -0
  35. package/adapters/shared/resolve-farmslot-ports.sh +7 -0
  36. package/adapters/shared/resolve-slot-ports-core.mjs +6 -0
  37. package/adapters/shared/resolve-slot-ports.sh +7 -2
  38. package/bin/mm-harness +0 -7
  39. package/dist/adapters/extension/harness-freshness.js +39 -0
  40. package/dist/adapters/extension/runtime-decision.js +4 -73
  41. package/dist/adapters/extension/surface.js +2 -3
  42. package/dist/adapters/mobile/perps-env.js +3 -43
  43. package/dist/adapters/mobile/prepare.js +7 -22
  44. package/dist/adapters/mobile/source-freshness.js +26 -41
  45. package/dist/adapters/mobile/surface.js +1 -1
  46. package/dist/adapters/resolve-slot-ports.js +2 -0
  47. package/dist/adapters/slot-ports.js +0 -16
  48. package/dist/adapters.js +5 -12
  49. package/dist/cli-commands.js +1 -1
  50. package/dist/cli.js +12 -6
  51. package/dist/commands/call.js +22 -56
  52. package/dist/commands/check.js +3 -9
  53. package/dist/commands/checklist.js +4 -1
  54. package/dist/commands/debug.js +31 -38
  55. package/dist/commands/device-target.js +12 -27
  56. package/dist/commands/doctor.js +23 -77
  57. package/dist/commands/fixtures.js +17 -27
  58. package/dist/commands/launch/extension.js +2 -0
  59. package/dist/commands/launch/index.js +59 -167
  60. package/dist/commands/logs.js +6 -8
  61. package/dist/commands/manifest.js +25 -151
  62. package/dist/commands/parse-args.js +0 -2
  63. package/dist/commands/provision.js +3 -10
  64. package/dist/commands/run-engine.js +69 -71
  65. package/dist/commands/run-report.js +3 -12
  66. package/dist/commands/run.js +35 -245
  67. package/dist/commands/shared.js +3 -55
  68. package/dist/commands/status-probe.js +1 -1
  69. package/dist/commands/status.js +1 -1
  70. package/dist/commands/stop.js +2 -7
  71. package/dist/commands/update.js +28 -16
  72. package/dist/doctor.js +4 -1
  73. package/dist/harness.js +134 -43
  74. package/dist/heal-bounds.js +1 -1
  75. package/dist/live-adapter-contract.js +20 -6
  76. package/dist/mm-harness-cli.js +48 -123
  77. package/dist/paths.js +5 -2
  78. package/dist/runner.js +2 -37
  79. package/dist/runtime-context.js +8 -26
  80. package/docs/ADAPTER-SURFACE.md +112 -19
  81. package/docs/CHEATSHEET.md +52 -39
  82. package/docs/CLI-SPEC.md +1098 -0
  83. package/docs/CODE-MAP.md +1 -1
  84. package/docs/DEBUG-HANDOVER.md +36 -0
  85. package/docs/MENTAL-MODEL.md +295 -0
  86. package/docs/UX-PRINCIPLES.md +1 -3
  87. package/docs/architecture.md +375 -65
  88. package/docs/live-adapter-contract.md +2 -4
  89. package/docs/recipe-libraries.md +275 -45
  90. package/library/README.md +0 -4
  91. package/library/actions/core/perps/_controller.mjs +55 -10
  92. package/library/actions/mobile/platform/bridge.mjs +1 -5
  93. package/library/manifests/core.action-manifest.json +7 -7
  94. package/library/manifests/mobile.action-manifest.json +3 -28
  95. package/package.json +1 -4
  96. package/adapters/extension/launch-webpack.cjs +0 -83
  97. package/adapters/extension/stamp-runtime-title.cjs +0 -49
  98. package/adapters/extension/sync-webpack-dist.cjs +0 -105
  99. package/adapters/mobile/launch-console-forwarder.cjs +0 -39
  100. package/adapters/mobile/start-console-forwarder.sh +0 -70
  101. package/adapters/shared/update-check-worker.mjs +0 -35
  102. package/dist/adapters/extension/console-capture.js +0 -93
  103. package/dist/command-contract.js +0 -441
  104. package/dist/command-journal.js +0 -225
  105. package/dist/commands/last.js +0 -52
  106. package/dist/json-stream.js +0 -57
  107. package/dist/run-diagnostics.js +0 -261
  108. package/docs/CLI-ERGONOMICS-AUDIT.md +0 -32
  109. package/docs/CLI-ERGONOMICS-HUMAN-QA.md +0 -64
  110. package/docs/VIDEO-DEMO-VALIDATION.md +0 -74
  111. package/library/recipes/runner/smoke.core.recipe.json +0 -27
  112. package/library/recipes/wallet/smoke.extension.recipe.json +0 -42
  113. package/library/recipes/wallet/smoke.mobile.recipe.json +0 -42
  114. package/scripts/validate-human-outcomes.mjs +0 -169
@@ -24,6 +24,7 @@ import path from 'node:path';
24
24
  import { spawnSync } from 'node:child_process';
25
25
  import process from 'node:process';
26
26
  import { recipeHarnessRoot } from '../shared/recipe-harness-root.mjs';
27
+ import { harnessSourceFingerprint } from '../shared/harness-source-fingerprint.mjs';
27
28
 
28
29
  function usage() {
29
30
  console.error('Usage: inject-extension-harness.mjs [--target <metamask-extension>] [--no-git-exclude]');
@@ -94,16 +95,16 @@ copyDir(path.join(runnerDir, 'library/recipes'), path.join(harnessDir, 'runner/r
94
95
  copyFile(path.join(runnerDir, 'library/library.json'), path.join(harnessDir, 'runner/library.json'));
95
96
  fs.mkdirSync(path.join(harnessDir, 'runner/flows'), { recursive: true });
96
97
  copyDir(path.join(runnerDir, 'library/flows'), path.join(harnessDir, 'runner/flows'));
97
- // The installed overlay has a flat scripts/ runtime API independent of the
98
- // package's source layout.
98
+ // Installed-layout back-compat (INTENTIONAL): targets keep a flat
99
+ // <harness>/scripts/ subtree assembled from the runner trees, so installed
100
+ // consumers are unaffected by runner-repo layout changes.
99
101
  copyFile(path.join(runnerDir, 'adapters/extension/launch-browser.cjs'), path.join(harnessDir, 'scripts/launch-browser.cjs'));
102
+ // installed back-compat alias: callers of the pre-rename installed name
103
+ copyFile(path.join(runnerDir, 'adapters/extension/launch-browser.cjs'), path.join(harnessDir, 'scripts/launch-chrome-detached.cjs'));
100
104
  copyFile(path.join(runnerDir, 'adapters/extension/console-tail.mjs'), path.join(harnessDir, 'scripts/console-tail.mjs'));
101
105
  copyFile(path.join(runnerDir, 'adapters/extension/launch.sh'), path.join(harnessDir, 'scripts/launch.sh'));
102
106
  copyFile(path.join(runnerDir, 'adapters/extension/live.sh'), path.join(harnessDir, 'scripts/live.sh'));
103
107
  copyFile(path.join(runnerDir, 'adapters/extension/start-watch.sh'), path.join(harnessDir, 'scripts/start-watch.sh'));
104
- copyFile(path.join(runnerDir, 'adapters/extension/launch-webpack.cjs'), path.join(harnessDir, 'scripts/launch-webpack.cjs'));
105
- copyFile(path.join(runnerDir, 'adapters/extension/sync-webpack-dist.cjs'), path.join(harnessDir, 'scripts/sync-webpack-dist.cjs'));
106
- copyFile(path.join(runnerDir, 'adapters/extension/stamp-runtime-title.cjs'), path.join(harnessDir, 'scripts/stamp-runtime-title.cjs'));
107
108
  copyFile(path.join(runnerDir, 'adapters/extension/stop-viewers.sh'), path.join(harnessDir, 'scripts/stop-viewers.sh'));
108
109
  copyFile(path.join(runnerDir, 'adapters/extension/snapshot-dist.sh'), path.join(harnessDir, 'scripts/snapshot-dist.sh'));
109
110
  copyFile(path.join(runnerDir, 'adapters/extension/pin-remote-flags.cjs'), path.join(harnessDir, 'scripts/pin-remote-flags.cjs'));
@@ -120,6 +121,10 @@ copyFile(path.join(runnerDir, 'adapters/shared/activate-repo-node.sh'), path.joi
120
121
  copyFile(path.join(runnerDir, 'adapters/shared/resolve-slot-ports.sh'), path.join(harnessDir, 'scripts/lib/resolve-slot-ports.sh'));
121
122
  copyFile(path.join(runnerDir, 'adapters/shared/resolve-slot-ports.mjs'), path.join(harnessDir, 'scripts/lib/resolve-slot-ports.mjs'));
122
123
  copyFile(path.join(runnerDir, 'adapters/shared/resolve-slot-ports-core.mjs'), path.join(harnessDir, 'scripts/lib/resolve-slot-ports-core.mjs'));
124
+ // Back-compat shims (one release) so an overlay consumer using the old leaf names still resolves.
125
+ copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports.sh'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports.sh'));
126
+ copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports.mjs'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports.mjs'));
127
+ copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports-core.mjs'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports-core.mjs'));
123
128
  copyFile(path.join(runnerDir, 'adapters/shared/path-defaults.json'), path.join(harnessDir, 'scripts/lib/path-defaults.json'));
124
129
  copyFile(path.join(runnerDir, 'adapters/shared/json-field.sh'), path.join(harnessDir, 'scripts/lib/json-field.sh'));
125
130
  copyFile(path.join(runnerDir, 'adapters/shared/tmux-session.sh'), path.join(harnessDir, 'scripts/lib/tmux-session.sh'));
@@ -127,6 +132,7 @@ copyFile(path.join(runnerDir, 'adapters/shared/tmux-viewer.sh'), path.join(harne
127
132
  copyFile(path.join(runnerDir, 'adapters/shared/log-tui.mjs'), path.join(harnessDir, 'scripts/lib/log-tui.mjs'));
128
133
  copyFile(path.join(runnerDir, 'adapters/extension/lib/extension-id.cjs'), path.join(harnessDir, 'scripts/lib/extension-id.cjs'));
129
134
  copyFile(path.join(runnerDir, 'adapters/extension/lib/slot-title.cjs'), path.join(harnessDir, 'scripts/lib/slot-title.cjs'));
135
+ copyFile(path.join(runnerDir, 'adapters/extension/lib/chrome-args.cjs'), path.join(harnessDir, 'scripts/lib/chrome-args.cjs'));
130
136
  makeExecutableTree(path.join(harnessDir, 'scripts'));
131
137
  fs.writeFileSync(path.join(harnessDir, 'installed-scripts.sha256'), `${dirContentHash(path.join(harnessDir, 'scripts'))}\n`);
132
138
 
@@ -136,6 +142,10 @@ const manifest = {
136
142
  adapter: 'extension',
137
143
  installedAt: new Date().toISOString(),
138
144
  runnerPackageVersion,
145
+ // Fingerprint of the runner source that produced this overlay. The launch-time
146
+ // freshness guard re-injects when the runner's fingerprint no longer matches,
147
+ // so a slot never silently runs a stale injected launcher.
148
+ sourceFingerprint: harnessSourceFingerprint(runnerDir),
139
149
  source: {
140
150
  runnerDir,
141
151
  runnerRevision,
@@ -24,6 +24,7 @@ const os = require('node:os');
24
24
  const path = require('node:path');
25
25
  const { execFileSync, spawn, spawnSync } = require('node:child_process');
26
26
  const { extensionIdFromExtensionDir } = require('./lib/extension-id.cjs');
27
+ const { remoteDebuggingArgs } = require('./lib/chrome-args.cjs');
27
28
 
28
29
  if (process.argv.includes('--help') || process.argv.includes('-h')) {
29
30
  console.log(
@@ -92,8 +93,9 @@ if (args['stop-only'] !== undefined) {
92
93
  const initialUrl = args['start-url'] || extensionHomeUrl(args['extension-dir']) || 'chrome://extensions/';
93
94
  const chromeArgs = [
94
95
  `--user-data-dir=${args.profile}`,
95
- '--remote-debugging-address=127.0.0.1',
96
- `--remote-debugging-port=${cdpPort}`,
96
+ // Debug-port trio (address + port + scoped allow-origins) from the shared module,
97
+ // so the DevTools origin allow-list can never drift away from the port.
98
+ ...remoteDebuggingArgs(cdpPort),
97
99
  '--no-first-run',
98
100
  '--disable-first-run-ui',
99
101
  '--disable-default-apps',
@@ -113,7 +115,6 @@ let browserPid;
113
115
  try {
114
116
  const application = process.platform === 'darwin' ? macApplicationForExecutable(args['chrome-bin']) : null;
115
117
  if (application) {
116
- // Launch Services does not attach Chrome's stdio, so chrome.log may stay empty; CDP ownership is the readiness signal.
117
118
  execFileSync('open', ['-g', '-n', '-a', application, '--args', ...chromeArgs], {
118
119
  env: sanitizedChildEnv(),
119
120
  stdio: ['ignore', logFd, logFd],
@@ -114,21 +114,24 @@ else
114
114
  status="fail"
115
115
  fi
116
116
 
117
- # The public CLI owns console collection after every successful launch path.
118
- # This leaf only adds an optional viewer when an explicitly run-owned tmux
119
- # session exists; the viewer tails the file and never owns the collector.
117
+ # Live extension-console viewer tab (default-on): tail the running extension's
118
+ # service worker + UI-page console in a tmux window, mirroring the Metro/webpack
119
+ # tabs. Skipped silently when there is no tmux session or CDP (headless/CI), so
120
+ # it never affects the launch result.
120
121
  if [ "$status" = "pass" ] && [ -n "$CDP_PORT" ] && command -v tmux >/dev/null 2>&1; then
121
- console_runtime="$TARGET/$( (cd "$TARGET" && recipe_runtime_dir) 2>/dev/null || echo temp/recipe/runtime)"
122
- mkdir -p "$console_runtime"
123
- console_log="$console_runtime/extension-console.log"
124
122
  for _tv_src in "$SCRIPT_DIR/lib/tmux-viewer.sh" "$SCRIPT_DIR/../shared/tmux-viewer.sh"; do
125
123
  # shellcheck disable=SC1091
126
124
  [ -f "$_tv_src" ] && { . "$_tv_src"; break; }
127
125
  done
128
126
  unset _tv_src
129
- if command -v tmux_viewer_open >/dev/null 2>&1; then
127
+ console_tail="$SCRIPT_DIR/console-tail.mjs"
128
+ if command -v tmux_viewer_open >/dev/null 2>&1 && [ -f "$console_tail" ]; then
129
+ console_runtime="$TARGET/$( (cd "$TARGET" && recipe_runtime_dir) 2>/dev/null || echo temp/recipe/runtime)"
130
+ mkdir -p "$console_runtime"
131
+ console_log="$console_runtime/extension-console.log"
132
+ : > "$console_log"
130
133
  tmux_viewer_open "console" "$CDP_PORT" "$console_log" "$console_runtime" "$console_runtime/console.tmux" \
131
- "exec tail -n +1 -F $(printf '%q' "$console_log")"
134
+ "exec node $(printf '%q' "$console_tail") --cdp-port ${CDP_PORT} --log $(printf '%q' "$console_log")"
132
135
  fi
133
136
  fi
134
137
 
@@ -0,0 +1,24 @@
1
+ 'use strict';
2
+ // chrome-args.cjs — single source of truth for the remote-debugging launch flags
3
+ // shared by BOTH extension launchers: the fresh spawn (launch-browser.cjs) and the
4
+ // reopen path (ensure-browser.sh → reopen-browser.sh). Keeping the debug-port trio
5
+ // here means `--remote-allow-origins` can never drift away from the port it scopes:
6
+ // the two are declared once and always travel together. Without the allow-origins
7
+ // entry Chrome 403s every browser DevTools WebSocket on the debug port and
8
+ // `mm-harness debug` cannot attach a visible console.
9
+
10
+ // The exact origin allowed is the locally-served bundled DevTools frontend, which
11
+ // Chrome serves at http://127.0.0.1:<cdpPort>. Scoped to that origin — never `*`.
12
+ function remoteDebuggingArgs(cdpPort) {
13
+ const port = Number(cdpPort);
14
+ if (!Number.isInteger(port) || port <= 0) {
15
+ throw new Error(`remoteDebuggingArgs: invalid cdp port: ${cdpPort}`);
16
+ }
17
+ return [
18
+ '--remote-debugging-address=127.0.0.1',
19
+ `--remote-debugging-port=${port}`,
20
+ `--remote-allow-origins=http://127.0.0.1:${port}`,
21
+ ];
22
+ }
23
+
24
+ module.exports = { remoteDebuggingArgs };
@@ -100,6 +100,7 @@ if [ -z "$VERIFY_SH" ]; then
100
100
  echo "mm-harness: extension verify.sh not found next to $SCRIPT_DIR; reinstall the runner." >&2
101
101
  exit 1
102
102
  fi
103
+ TARGET="$(cd "$TARGET" && pwd)"
103
104
  RUNTIME_DIR="$(recipe_runtime_dir)"
104
105
  RUNTIME_DIST_DIR="${RECIPE_RUNTIME_DIST_DIR:-runtime-dist}"
105
106
  CHROME_PROFILE_NAME="${RECIPE_CHROME_PROFILE_NAME:-chrome-profile}"
@@ -162,7 +163,6 @@ NODE
162
163
  quoted_seed_fixture="$(printf '%q' "$SCRIPT_DIR/seed-fixture.sh")"
163
164
  quoted_start_watch="$(printf '%q' "$SCRIPT_DIR/start-watch.sh")"
164
165
  quoted_snapshot_dist="$(printf '%q' "$SCRIPT_DIR/snapshot-dist.sh")"
165
- quoted_stamp_title="$(printf '%q' "$SCRIPT_DIR/stamp-runtime-title.cjs")"
166
166
  quoted_chrome_launcher="$(printf '%q' "$SCRIPT_DIR/launch-browser.cjs")"
167
167
  quoted_fixture_state="$(printf '%q' "$FIXTURE_STATE_ABS")"
168
168
  quoted_fixture_validation="$(printf '%q' "$FIXTURE_VALIDATION_ABS")"
@@ -257,7 +257,6 @@ NODE
257
257
  fi
258
258
  prepare_parts+=("node ${quoted_chrome_launcher} --stop-only 1 --reset-profile 1 --chrome-bin ${quoted_chrome} --profile ${quoted_profile} --cdp-port ${CDP_PORT} --extension-dir ${quoted_runtime_dist} --chrome-log ${quoted_chrome_log} --chrome-pid ${quoted_chrome_pid}")
259
259
  prepare_parts+=("bash ${quoted_snapshot_dist} --dist ${quoted_dist} --runtime-dist ${quoted_runtime_dist}")
260
- prepare_parts+=("node ${quoted_stamp_title} --target ${quoted_target} --runtime-dist ${quoted_runtime_dist} --runtime-dir ${quoted_runtime_dir}")
261
260
  # Optional A/B feature-flag pinning: patch the ephemeral snapshot manifest so
262
261
  # manifest._flags wins over the fetched ClientConfigApi value. No-op unless
263
262
  # --remote-flag was passed; the snapshot is re-created each run, so no revert.
@@ -103,7 +103,6 @@ echo "[reattach] reusing loaded runtime-dist: $RUNTIME_DIST_ABS" >&2
103
103
  if [ -d "$DIST_ABS" ] && [ -d "$RUNTIME_DIST_ABS" ]; then
104
104
  echo "[reattach] refreshing loaded runtime-dist in place" >&2
105
105
  rsync -a --exclude _metadata "$DIST_ABS/" "$RUNTIME_DIST_ABS/" >&2
106
- node "$SCRIPT_DIR/stamp-runtime-title.cjs" --target "$TARGET" --runtime-dist "$RUNTIME_DIST_ABS" --runtime-dir "$RUNTIME_DIR" >&2
107
106
  fi
108
107
 
109
108
  # Resolve the extension id: explicit flag, then the recorded id file, else CDP.
@@ -4,13 +4,15 @@
4
4
  #
5
5
  # Purpose:
6
6
  # Resolves which wallet fixture a slot run should use (env override then
7
- # the checkout's canonical runtime fixture) and drives the recipe-tree
7
+ # runtime-dir/legacy/port-scoped candidates) and drives the recipe-tree
8
8
  # wallet-fixture-state.cjs phases around the browser launch.
9
9
  #
10
10
  # Subcommands (flags):
11
11
  # resolve --target <repo> --cdp-port <port> [--source-out <file>]
12
12
  # Prints the fixture path (empty if none). Chain:
13
- # RECIPE_WALLET_FIXTURE env > <runtime-dir>/wallet-fixture.json.
13
+ # RECIPE_WALLET_FIXTURE env > <runtime-dir>/wallet-fixture.json >
14
+ # temp/runtime/ > temp/.recipe-validation-<port>/ >
15
+ # temp/.agent-validation/.
14
16
  # prefill --fixture <json> --target <repo> --state <file> --profile <dir>
15
17
  # --extension-dir <dist> --extension-id-file <file>
16
18
  # Runs fixture-state generate then prefill-profile (pre-launch).
@@ -129,8 +131,14 @@ if [ "$SUBCOMMAND" = resolve ]; then
129
131
  fi
130
132
  resolved="$RECIPE_WALLET_FIXTURE"
131
133
  else
132
- candidate="${RUNTIME_DIR:+$TARGET/$RUNTIME_DIR/wallet-fixture.json}"
133
- [ -n "$candidate" ] && [ -f "$candidate" ] && resolved="$candidate"
134
+ for candidate in \
135
+ ${RUNTIME_DIR:+"$TARGET/$RUNTIME_DIR/wallet-fixture.json"} \
136
+ "$TARGET/temp/runtime/wallet-fixture.json" \
137
+ ${CDP_PORT:+"$TARGET/temp/.recipe-validation-${CDP_PORT}/wallet-fixture.json"} \
138
+ "$TARGET/temp/.agent-validation/wallet-fixture.json"
139
+ do
140
+ [ -f "$candidate" ] && { resolved="$candidate"; break; }
141
+ done
134
142
  fi
135
143
  [ -n "$resolved" ] || resolve_status=missing
136
144
  if [ -n "$SOURCE_OUT" ]; then
@@ -289,10 +289,7 @@ const { chromium } = require('playwright');
289
289
  return { windowId: currentWindow.id };
290
290
  });
291
291
 
292
- // This harness-owned button exists only to provide Chrome's trusted user
293
- // gesture. Its visual layout is irrelevant and can be occluded by the app's
294
- // full-screen loading surface, so do not wait on Playwright actionability.
295
- await page.locator('#__recipe_open_sidepanel__').click({ timeout: 5000, force: true });
292
+ await page.locator('#__recipe_open_sidepanel__').click({ timeout: 5000 });
296
293
  const clickResult = await page
297
294
  .locator('#__recipe_open_sidepanel__')
298
295
  .evaluate((button) => button.dataset.result || '');
@@ -182,15 +182,9 @@ rm -rf node_modules/.cache/webpack
182
182
  killed_harness_pid=""
183
183
  if [ -f "$RUNTIME_DIR/recipe-harness-webpack.pid" ]; then
184
184
  killed_harness_pid="$(cat "$RUNTIME_DIR/recipe-harness-webpack.pid" 2>/dev/null || true)"
185
- [ -z "$killed_harness_pid" ] || kill -- "-$killed_harness_pid" >/dev/null 2>&1 || true
186
185
  [ -z "$killed_harness_pid" ] || kill "$killed_harness_pid" >/dev/null 2>&1 || true
187
186
  rm -f "$RUNTIME_DIR/recipe-harness-webpack.pid"
188
187
  fi
189
- if [ -f "$RUNTIME_DIR/recipe-harness-webpack-sync.pid" ]; then
190
- sync_pid="$(cat "$RUNTIME_DIR/recipe-harness-webpack-sync.pid" 2>/dev/null || true)"
191
- [ -z "$sync_pid" ] || kill "$sync_pid" >/dev/null 2>&1 || true
192
- rm -f "$RUNTIME_DIR/recipe-harness-webpack-sync.pid"
193
- fi
194
188
 
195
189
  # Do not start a second writer for dist/chrome. Slot-owned watches use
196
190
  # webpack.pid; harness-owned watches use recipe-harness-webpack.pid.
@@ -225,8 +219,6 @@ fi
225
219
  # unrelated repo and leave this target validating stale dist/chrome output.
226
220
  watch_pid_file="$RUNTIME_DIR/recipe-harness-webpack.pid"
227
221
  watch_log="$RUNTIME_DIR/recipe-harness-webpack.log"
228
- sync_pid_file="$RUNTIME_DIR/recipe-harness-webpack-sync.pid"
229
- sync_log="$RUNTIME_DIR/recipe-harness-webpack-sync.log"
230
222
  rm -f "$watch_pid_file"
231
223
  : > "$watch_log"
232
224
  LIB_DIR=""
@@ -237,16 +229,11 @@ unset _lib
237
229
  echo "[recipe-harness] Starting yarn start (watcher-port=${WATCHER_PORT:-default})"
238
230
  echo "[recipe-harness] compact build view - full log: $watch_log (mm-harness logs --full)"
239
231
  started_pid=""
232
+ # Launch the watcher detached writing to the log; the tmux window is a read-only tail viewer.
240
233
  path_prefix="${REPO_NODE_PATH_PREFIX:-}"
241
- launcher="$SCRIPT_DIR/launch-webpack.cjs"
242
- [ -f "$launcher" ] || { echo "launch-webpack.cjs missing from harness; run: mm-harness install" >&2; exit 1; }
243
- sync_script="$SCRIPT_DIR/sync-webpack-dist.cjs"
244
- [ -f "$sync_script" ] || { echo "sync-webpack-dist.cjs missing from harness; run: mm-harness install" >&2; exit 1; }
245
- launch_args=(--target "$PWD" --log "$watch_log" --pid-file "$watch_pid_file" --sync-script "$sync_script" --sync-pid-file "$sync_pid_file" --sync-log "$sync_log" --dist "$PWD/dist/chrome" --runtime-dist "$PWD/$RUNTIME_DIR/${RECIPE_RUNTIME_DIST_DIR:-runtime-dist}")
246
- [ -z "$WATCHER_PORT" ] || launch_args+=(--port "$WATCHER_PORT")
247
- [ -z "$path_prefix" ] || launch_args+=(--path-prefix "$path_prefix")
248
- env -u BUNDLED_DEBUGPY_PATH node "$launcher" "${launch_args[@]}" >/dev/null
249
- started_pid="$(cat "$watch_pid_file")"
234
+ nohup env -u BUNDLED_DEBUGPY_PATH ${path_prefix:+PATH="${path_prefix}${PATH}"} ${WATCHER_PORT:+PORT="$WATCHER_PORT"} yarn start > "$watch_log" 2>&1 &
235
+ started_pid=$!
236
+ printf '%s\n' "$started_pid" > "$watch_pid_file"
250
237
  # The orchestrator names the session; the harness only populates windows. Resolve
251
238
  # via the run-owned ladder (RECIPE_TMUX_SESSION → agentic-runtime.json session) —
252
239
  # no current-session fallback or slot-number derivation.
@@ -52,5 +52,5 @@ ps -axo pid=,command= 2>/dev/null | while read -r pid command; do
52
52
  esac
53
53
  done
54
54
 
55
- rm -f "$runtime_dir/console.tmux" "$runtime_dir/webpack.tmux" "$runtime_dir/console-tail.pid"
55
+ rm -f "$runtime_dir/console.tmux" "$runtime_dir/webpack.tmux"
56
56
  exit 0
@@ -58,7 +58,7 @@ if ! command -v harness_root >/dev/null 2>&1; then
58
58
  exit 1
59
59
  fi
60
60
 
61
- # Safe task-local --out resolution.
61
+ # Safe task-local --out resolution (kept from the legacy extension path.sh).
62
62
  resolve_harness_out() {
63
63
  local target="$1"
64
64
  local out="$2"
@@ -18,22 +18,6 @@
18
18
  "inputs": "--target --port --clear; env WATCHER_PORT, METRO_PORT, MOBILE_METRO_REQUIRED_ENV, METRO_MAX_WORKERS",
19
19
  "outputs": "Metro PID file + log; exit 0 ready / 1 timeout / 2 bad args"
20
20
  },
21
- {
22
- "id": "mobile/start-console-forwarder",
23
- "entry": "adapters/mobile/start-console-forwarder.sh",
24
- "kind": "bash",
25
- "purpose": "Ensure one runner-owned Mobile application console collector is alive after bridge readiness.",
26
- "inputs": "--target --port --quiet; env WATCHER_PORT, METRO_PORT, METAMASK_RECIPE_CONSOLE_FORWARD",
27
- "outputs": "app-console.log + collector PID file; exit 0 ready / 1 startup failed / 2 bad args"
28
- },
29
- {
30
- "id": "mobile/launch-console-forwarder",
31
- "entry": "adapters/mobile/launch-console-forwarder.cjs",
32
- "kind": "node",
33
- "purpose": "Detach the Mobile application console collector from command-runner process-group cleanup.",
34
- "inputs": "--port --out --err",
35
- "outputs": "collector PID on stdout; exit 0 launched / 1 spawn failed / 2 bad args"
36
- },
37
21
  {
38
22
  "id": "mobile/launch-metro",
39
23
  "entry": "adapters/mobile/launch-metro.cjs",
@@ -78,17 +62,17 @@
78
62
  "id": "mobile/inject",
79
63
  "entry": "adapters/mobile/inject.sh",
80
64
  "kind": "bash",
81
- "purpose": "Install Mobile runner metadata, delegates, and snapshots without modifying product source.",
82
- "inputs": "--target --no-git-exclude; env RECIPE_HARNESS_ROOT, METAMASK_RUNNER_PROTOCOL_ROOT",
83
- "outputs": "<harness>/manifest.json + runner/ + scripts/; exit 0/1/2"
65
+ "purpose": "Install the mobile harness overlay (delegate + snapshots; full mode adds the app bridge with backups).",
66
+ "inputs": "--target --allow-dirty-harness-paths --force-overlay --no-git-exclude; env RECIPE_HARNESS_ROOT, METAMASK_RUNNER_PROTOCOL_ROOT>FARMSLOT_ROOT",
67
+ "outputs": "<harness>/manifest.json + runner/ + scripts/ + backup store; exit 0/1/2"
84
68
  },
85
69
  {
86
70
  "id": "mobile/cleanup",
87
71
  "entry": "adapters/mobile/cleanup.sh",
88
72
  "kind": "bash",
89
- "purpose": "Remove Mobile harness metadata and recorded git excludes without touching product source.",
90
- "inputs": "--target; env RECIPE_HARNESS_ROOT",
91
- "outputs": "removes <harness>/; exit 0/1/2"
73
+ "purpose": "Remove the mobile overlay; restore backed-up product files; remove recorded git excludes.",
74
+ "inputs": "--target --allow-managed-changes; env RECIPE_HARNESS_ROOT",
75
+ "outputs": "removes <harness>/ + backups; exit 0/1/2"
92
76
  },
93
77
  {
94
78
  "id": "mobile/verify",
@@ -130,30 +114,6 @@
130
114
  "inputs": "--target --runtime-dir --runner-bin --summary",
131
115
  "outputs": "<runtime-dir>/recipe-harness-webpack.{pid,log}; optional summary; exit 0/1/2"
132
116
  },
133
- {
134
- "id": "extension/launch-webpack",
135
- "entry": "adapters/extension/launch-webpack.cjs",
136
- "kind": "node",
137
- "purpose": "Detach the normal yarn start watcher from the launching CLI process while preserving its target, environment, and log.",
138
- "inputs": "--target --log --pid-file --sync-script --sync-pid-file --sync-log --dist --runtime-dist; optional --port --path-prefix",
139
- "outputs": "watcher and runtime-sync pids; webpack and sync logs; exit 0/1"
140
- },
141
- {
142
- "id": "extension/sync-webpack-dist",
143
- "entry": "adapters/extension/sync-webpack-dist.cjs",
144
- "kind": "node",
145
- "purpose": "After each successful webpack compile, update the loaded isolated runtime in place so an ordinary browser refresh sees the edit.",
146
- "inputs": "--watcher-pid --watch-log --target --dist --runtime-dist --pid-file",
147
- "outputs": "runtime-dist updated in place after successful compile markers; exits with its watcher"
148
- },
149
- {
150
- "id": "extension/stamp-runtime-title",
151
- "entry": "adapters/extension/stamp-runtime-title.cjs",
152
- "kind": "node",
153
- "purpose": "Stamp the slot id into isolated Extension page titles so window identity survives browser refreshes.",
154
- "inputs": "--target --runtime-dist; optional --runtime-dir",
155
- "outputs": "home.html and sidepanel.html titles updated in the isolated runtime only; exit 0/1"
156
- },
157
117
  {
158
118
  "id": "extension/stop-viewers",
159
119
  "entry": "adapters/extension/stop-viewers.sh",
@@ -418,6 +378,22 @@
418
378
  "inputs": "ESM import from resolve-slot-ports.mjs or resolve-slot-ports.ts",
419
379
  "outputs": "resolveSlotPortsByRepo and related functions"
420
380
  },
381
+ {
382
+ "id": "lib/resolve-farmslot-ports",
383
+ "entry": "adapters/shared/resolve-farmslot-ports.mjs",
384
+ "kind": "node",
385
+ "purpose": "Back-compat shim (one release): delegates to resolve-slot-ports.mjs.",
386
+ "inputs": "resolve-farmslot-ports.mjs <fn> <repo>",
387
+ "outputs": "same as resolve-slot-ports.mjs"
388
+ },
389
+ {
390
+ "id": "lib/resolve-farmslot-ports-core",
391
+ "entry": "adapters/shared/resolve-farmslot-ports-core.mjs",
392
+ "kind": "lib",
393
+ "purpose": "Back-compat shim (one release): re-exports resolve-slot-ports-core.mjs.",
394
+ "inputs": "ESM import of the old path",
395
+ "outputs": "re-exports resolveSlotPortsByRepo (and the resolveFarmslotPortsByRepo alias)"
396
+ },
421
397
  {
422
398
  "id": "lib/recipe-harness-root",
423
399
  "entry": "adapters/shared/recipe-harness-root.mjs",
@@ -430,10 +406,18 @@
430
406
  "id": "lib/resolve-slot-ports-sh",
431
407
  "entry": "adapters/shared/resolve-slot-ports.sh",
432
408
  "kind": "lib",
433
- "purpose": "Sourced wrapper over resolve-slot-ports.mjs for shell runtime leaves.",
409
+ "purpose": "Bash-compat sourced wrapper over resolve-slot-ports.mjs for stop-metro and legacy callers.",
434
410
  "inputs": "source + resolve_* / apply_resolved_* function calls",
435
411
  "outputs": "KEY=VALUE lines on stdout; env vars via apply_resolved_*"
436
412
  },
413
+ {
414
+ "id": "lib/resolve-farmslot-ports-sh",
415
+ "entry": "adapters/shared/resolve-farmslot-ports.sh",
416
+ "kind": "lib",
417
+ "purpose": "Back-compat shim (one release): sources resolve-slot-ports.sh (keeps the resolve_farmslot_ports_by_repo alias).",
418
+ "inputs": "source",
419
+ "outputs": "same sourced functions as resolve-slot-ports.sh"
420
+ },
437
421
  {
438
422
  "id": "lib/reap-checkout-metros",
439
423
  "entry": "adapters/shared/reap-checkout-metros.sh",
@@ -488,12 +472,20 @@
488
472
  "outputs": "debug URL/report on stdout; exit 0/1"
489
473
  },
490
474
  {
491
- "id": "lib/update-check-worker",
492
- "entry": "adapters/shared/update-check-worker.mjs",
493
- "kind": "node",
494
- "purpose": "Detached, silent registry probe used by the daily passive update notice.",
495
- "inputs": "<cache-file> <timeout-ms> [registry-endpoint]",
496
- "outputs": "atomic update-cache refresh; always silent and best-effort"
475
+ "id": "lib/harness-source-fingerprint",
476
+ "entry": "adapters/shared/harness-source-fingerprint.mjs",
477
+ "kind": "lib",
478
+ "purpose": "Compute the deterministic source fingerprint stamped into injected extension harness manifests.",
479
+ "inputs": "imported by injection and launch freshness checks",
480
+ "outputs": "stable SHA-256 fingerprint"
481
+ },
482
+ {
483
+ "id": "compat-overlays/mobile",
484
+ "entry": "adapters/mobile/overlay/compat",
485
+ "kind": "compat-overlay",
486
+ "purpose": "Historical-commit replay support: reversible source overlays (e.g. rn81-message-event-source.patch) applied manually/via the recipe-harness skill so pre-RN-0.81 mobile checkouts boot and bridge under the current toolchain.",
487
+ "inputs": "manual `git apply` in the historical product checkout (no in-repo apply logic)",
488
+ "outputs": "patched historical checkout (reversible via git apply -R); overlay path recorded in eval evidence"
497
489
  }
498
490
  ]
499
491
  }
@@ -254,21 +254,6 @@ const COMMANDS = {
254
254
  return { ...result, testId, deviceName };
255
255
  },
256
256
 
257
- async 'press-text'(client, args, { deviceName } = {}) {
258
- const text = args[0];
259
- if (!text) {
260
- throw new Error('Usage: press-text <text>');
261
- }
262
- const expr = `(function() {
263
- if (typeof globalThis.__AGENTIC__?.pressText !== 'function') {
264
- return { ok: false, error: 'The app bridge does not expose pressText' };
265
- }
266
- return globalThis.__AGENTIC__.pressText(${JSON.stringify(text)});
267
- })()`;
268
- const result = await cdpEval(client, expr);
269
- return { ...result, text, deviceName };
270
- },
271
-
272
257
  async 'scroll-view'(client, args, { deviceName } = {}) {
273
258
  let testId;
274
259
  let offset = 300;
@@ -693,7 +678,6 @@ Commands:
693
678
  get-selected-account Get the currently selected account
694
679
  switch-account <address> Switch to account by address
695
680
  press-test-id <testId> Press a component by its testID prop
696
- press-text <text> Press a component containing visible text
697
681
  scroll-view [--test-id <id>] [--offset <n>] [--animated]
698
682
  Scroll a ScrollView/FlatList
699
683
  set-input <testId> <value> Set text input value by testID (calls onChangeText)
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  // console-forwarder — stream device console lines into metro.log.
3
3
  //
4
- // React Native (Bridgeless) gates its built-in console→Metro forwarding on
4
+ // React Native (Bridgeless) gates its legacy console→Metro forwarding on
5
5
  // `console._isPolyfilled` (setUpDeveloperTools.js, T214991636); with the Hermes
6
6
  // native console that gate is false, so app logs (incl. DevLogger) never reach
7
7
  // Metro's log. The supported contract for log consumption in modern RN is CDP —
@@ -50,9 +50,7 @@ function matchesBridgeTarget(target, env) {
50
50
  // (an in-app agentic bridge is live), not merely a registered debug target.
51
51
  function hasMatchingRoute(value, env) {
52
52
  const targets = Array.isArray(value) ? value : [value];
53
- return targets.some(
54
- (t) => matchesBridgeTarget(t, env) && t && t.agenticPresent === true && Boolean(t.route),
55
- );
53
+ return targets.some((t) => matchesBridgeTarget(t, env) && t && t.route);
56
54
  }
57
55
 
58
56
  // Human summary of the targets that answered (platform / deviceName), for teaching on
@@ -136,7 +136,7 @@ async function discoverTarget(port) {
136
136
  // ANDROID_TARGET_DEVICE_NAME — Metro-compatible model prefix resolved by bridge.mjs
137
137
  // from the adb serial via `adb -s <serial> shell getprop ro.product.model`.
138
138
  // Example: "Pixel 6" matches Metro deviceName "Pixel 6 - 16 - API 36".
139
- // ANDROID_DEVICE — exact user-specified Metro deviceName.
139
+ // ANDROID_DEVICE — exact Metro deviceName (backward compat / user-specified).
140
140
  const androidTargetName = loadAndroidTargetDeviceName();
141
141
  const androidDevice = loadAndroidDevice();
142
142
  const adbSerial = process.env.ADB_SERIAL || process.env.ANDROID_SERIAL || '';
@@ -205,7 +205,8 @@ async function discoverTarget(port) {
205
205
  }
206
206
  candidates = androidFiltered;
207
207
  } else if (androidDevice) {
208
- // Exact Metro deviceName match when the user set ANDROID_DEVICE directly.
208
+ // Exact Metro deviceName match (user set ANDROID_DEVICE to the Metro name, or
209
+ // legacy path where ANDROID_DEVICE was not resolved from serial).
209
210
  androidFiltered = candidates.filter((t) => t.deviceName === androidDevice);
210
211
  if (androidFiltered.length === 0) {
211
212
  // Pinned by exact Metro name but no candidate matched — fail fast.