@deeeed/metamask-harness 0.17.0 → 0.17.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +90 -140
  3. package/adapters/core/cleanup.sh +0 -0
  4. package/adapters/core/inject.sh +0 -0
  5. package/adapters/extension/cleanup.mjs +0 -0
  6. package/adapters/extension/console-tail.mjs +51 -24
  7. package/adapters/extension/ensure-browser.sh +0 -0
  8. package/adapters/extension/inject.mjs +5 -9
  9. package/adapters/extension/launch-browser.cjs +0 -0
  10. package/adapters/extension/launch-webpack.cjs +83 -0
  11. package/adapters/extension/launch.sh +8 -11
  12. package/adapters/extension/live.sh +2 -0
  13. package/adapters/extension/readiness.mjs +0 -0
  14. package/adapters/extension/reattach.sh +1 -0
  15. package/adapters/extension/refresh-build.sh +0 -0
  16. package/adapters/extension/seed-fixture.sh +4 -12
  17. package/adapters/extension/sidepanel-toggle.sh +4 -1
  18. package/adapters/extension/snapshot-dist.sh +0 -0
  19. package/adapters/extension/stamp-runtime-title.cjs +49 -0
  20. package/adapters/extension/start-watch.sh +16 -4
  21. package/adapters/extension/stop-viewers.sh +1 -1
  22. package/adapters/extension/sync-webpack-dist.cjs +105 -0
  23. package/adapters/extension/verify.sh +1 -1
  24. package/adapters/extension/wallet-fixture-state.cjs +0 -0
  25. package/adapters/manifest.json +46 -38
  26. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +0 -0
  27. package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
  28. package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
  29. package/adapters/mobile/bridge-runtime/setup-wallet.sh +0 -0
  30. package/adapters/mobile/cleanup.sh +16 -176
  31. package/adapters/mobile/inject.sh +88 -671
  32. package/adapters/mobile/launch-console-forwarder.cjs +39 -0
  33. package/adapters/mobile/lib/metro-listener.sh +0 -0
  34. package/adapters/mobile/lib/tmux-viewer.sh +0 -0
  35. package/adapters/mobile/open-device.sh +0 -0
  36. package/adapters/mobile/prewarm-bundle.sh +0 -0
  37. package/adapters/mobile/start-console-forwarder.sh +70 -0
  38. package/adapters/mobile/start-metro.sh +0 -41
  39. package/adapters/mobile/stop-metro.sh +0 -0
  40. package/adapters/mobile/verify.sh +4 -61
  41. package/adapters/mobile/wait-for-bridge.sh +0 -0
  42. package/adapters/mobile/yarn-setup.sh +0 -0
  43. package/adapters/shared/activate-repo-node.sh +0 -0
  44. package/adapters/shared/activate-repo-ruby.sh +0 -0
  45. package/adapters/shared/cli-ux.sh +0 -0
  46. package/adapters/shared/ensure-runner-deps.sh +0 -0
  47. package/adapters/shared/harness-path.sh +0 -0
  48. package/adapters/shared/hash-helpers.sh +0 -0
  49. package/adapters/shared/install-repo-deps.sh +0 -0
  50. package/adapters/shared/json-field.sh +0 -0
  51. package/adapters/shared/open-debug.mjs +90 -36
  52. package/adapters/shared/open-log-window.sh +0 -0
  53. package/adapters/shared/reap-checkout-metros.sh +0 -0
  54. package/adapters/shared/resolve-slot-ports-core.mjs +0 -6
  55. package/adapters/shared/resolve-slot-ports.mjs +0 -0
  56. package/adapters/shared/resolve-slot-ports.sh +0 -5
  57. package/adapters/shared/sync-wallet-fixture.sh +0 -0
  58. package/adapters/shared/tmux-session.sh +0 -0
  59. package/adapters/shared/tmux-viewer.sh +0 -0
  60. package/adapters/shared/update-check-worker.mjs +37 -0
  61. package/dist/adapters/extension/console-capture.js +84 -0
  62. package/dist/adapters/extension/runtime-decision.js +14 -2
  63. package/dist/adapters/extension/surface.js +3 -2
  64. package/dist/adapters/mobile/prepare.js +22 -7
  65. package/dist/adapters/mobile/surface.js +1 -1
  66. package/dist/adapters/resolve-slot-ports.js +0 -2
  67. package/dist/adapters/slot-ports.js +8 -0
  68. package/dist/cli.js +3 -11
  69. package/dist/commands/call.js +9 -4
  70. package/dist/commands/debug.js +38 -31
  71. package/dist/commands/fixtures.js +18 -10
  72. package/dist/commands/launch/index.js +10 -0
  73. package/dist/commands/logs.js +8 -6
  74. package/dist/commands/manifest.js +5 -17
  75. package/dist/commands/run-engine.js +1 -32
  76. package/dist/commands/run-report.js +1 -1
  77. package/dist/commands/run.js +50 -3
  78. package/dist/commands/status-probe.js +1 -1
  79. package/dist/commands/update.js +15 -28
  80. package/dist/doctor.js +1 -4
  81. package/dist/harness.js +5 -123
  82. package/dist/live-adapter-contract.js +6 -20
  83. package/dist/mm-harness-cli.js +6 -43
  84. package/dist/paths.js +2 -5
  85. package/dist/run-diagnostics.js +35 -40
  86. package/dist/runtime-context.js +26 -8
  87. package/docs/CHEATSHEET.md +39 -52
  88. package/docs/CLI-ERGONOMICS-HUMAN-QA.md +48 -104
  89. package/docs/CODE-MAP.md +1 -1
  90. package/docs/VIDEO-DEMO-VALIDATION.md +62 -0
  91. package/docs/architecture.md +65 -375
  92. package/docs/live-adapter-contract.md +3 -1
  93. package/library/actions/core/perps/_controller.mjs +10 -55
  94. package/library/manifests/mobile.action-manifest.json +1 -1
  95. package/package.json +2 -2
  96. package/scripts/completions.sh +0 -0
  97. package/scripts/install-completions.sh +0 -0
  98. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -185
  99. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -1662
  100. package/adapters/mobile/overlay/compat/README.md +0 -28
  101. package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +0 -42
  102. package/adapters/shared/resolve-farmslot-ports-core.mjs +0 -3
  103. package/adapters/shared/resolve-farmslot-ports.mjs +0 -5
  104. package/adapters/shared/resolve-farmslot-ports.sh +0 -7
  105. package/docs/CLI-SPEC.md +0 -1142
  106. package/docs/MENTAL-MODEL.md +0 -295
@@ -114,24 +114,21 @@ else
114
114
  status="fail"
115
115
  fi
116
116
 
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.
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.
121
120
  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"
122
124
  for _tv_src in "$SCRIPT_DIR/lib/tmux-viewer.sh" "$SCRIPT_DIR/../shared/tmux-viewer.sh"; do
123
125
  # shellcheck disable=SC1091
124
126
  [ -f "$_tv_src" ] && { . "$_tv_src"; break; }
125
127
  done
126
128
  unset _tv_src
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"
129
+ if command -v tmux_viewer_open >/dev/null 2>&1; then
133
130
  tmux_viewer_open "console" "$CDP_PORT" "$console_log" "$console_runtime" "$console_runtime/console.tmux" \
134
- "exec node $(printf '%q' "$console_tail") --cdp-port ${CDP_PORT} --log $(printf '%q' "$console_log")"
131
+ "exec tail -n +1 -F $(printf '%q' "$console_log")"
135
132
  fi
136
133
  fi
137
134
 
@@ -162,6 +162,7 @@ NODE
162
162
  quoted_seed_fixture="$(printf '%q' "$SCRIPT_DIR/seed-fixture.sh")"
163
163
  quoted_start_watch="$(printf '%q' "$SCRIPT_DIR/start-watch.sh")"
164
164
  quoted_snapshot_dist="$(printf '%q' "$SCRIPT_DIR/snapshot-dist.sh")"
165
+ quoted_stamp_title="$(printf '%q' "$SCRIPT_DIR/stamp-runtime-title.cjs")"
165
166
  quoted_chrome_launcher="$(printf '%q' "$SCRIPT_DIR/launch-browser.cjs")"
166
167
  quoted_fixture_state="$(printf '%q' "$FIXTURE_STATE_ABS")"
167
168
  quoted_fixture_validation="$(printf '%q' "$FIXTURE_VALIDATION_ABS")"
@@ -256,6 +257,7 @@ NODE
256
257
  fi
257
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}")
258
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}")
259
261
  # Optional A/B feature-flag pinning: patch the ephemeral snapshot manifest so
260
262
  # manifest._flags wins over the fetched ClientConfigApi value. No-op unless
261
263
  # --remote-flag was passed; the snapshot is re-created each run, so no revert.
File without changes
@@ -103,6 +103,7 @@ 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
106
107
  fi
107
108
 
108
109
  # Resolve the extension id: explicit flag, then the recorded id file, else CDP.
File without changes
@@ -4,15 +4,13 @@
4
4
  #
5
5
  # Purpose:
6
6
  # Resolves which wallet fixture a slot run should use (env override then
7
- # runtime-dir/legacy/port-scoped candidates) and drives the recipe-tree
7
+ # the checkout's canonical runtime fixture) 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 >
14
- # temp/runtime/ > temp/.recipe-validation-<port>/ >
15
- # temp/.agent-validation/.
13
+ # RECIPE_WALLET_FIXTURE env > <runtime-dir>/wallet-fixture.json.
16
14
  # prefill --fixture <json> --target <repo> --state <file> --profile <dir>
17
15
  # --extension-dir <dist> --extension-id-file <file>
18
16
  # Runs fixture-state generate then prefill-profile (pre-launch).
@@ -131,14 +129,8 @@ if [ "$SUBCOMMAND" = resolve ]; then
131
129
  fi
132
130
  resolved="$RECIPE_WALLET_FIXTURE"
133
131
  else
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
132
+ candidate="${RUNTIME_DIR:+$TARGET/$RUNTIME_DIR/wallet-fixture.json}"
133
+ [ -n "$candidate" ] && [ -f "$candidate" ] && resolved="$candidate"
142
134
  fi
143
135
  [ -n "$resolved" ] || resolve_status=missing
144
136
  if [ -n "$SOURCE_OUT" ]; then
@@ -289,7 +289,10 @@ const { chromium } = require('playwright');
289
289
  return { windowId: currentWindow.id };
290
290
  });
291
291
 
292
- await page.locator('#__recipe_open_sidepanel__').click({ timeout: 5000 });
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 });
293
296
  const clickResult = await page
294
297
  .locator('#__recipe_open_sidepanel__')
295
298
  .evaluate((button) => button.dataset.result || '');
File without changes
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const fs = require('node:fs');
5
+ const path = require('node:path');
6
+
7
+ const { readSlotId } = require('./lib/slot-title.cjs');
8
+
9
+ function stampRuntimeTitles({ target, runtimeDist, runtimeDir = process.env.RECIPE_RUNTIME_DIR || 'temp/recipe/runtime' }) {
10
+ const slotId = readSlotId(target, runtimeDir);
11
+ if (!slotId) return { slotId: '', stamped: [] };
12
+ const stamped = [];
13
+ for (const name of ['home.html', 'sidepanel.html']) {
14
+ const file = path.join(runtimeDist, name);
15
+ if (!fs.existsSync(file)) continue;
16
+ const source = fs.readFileSync(file, 'utf8');
17
+ const next = source.replace(/<title>([^<]*)<\/title>/iu, (_match, title) => {
18
+ const base = String(title || 'MetaMask').replace(/^.+?\s+[—–-]\s+/u, '') || 'MetaMask';
19
+ return `<title>${slotId} — ${base}</title>`;
20
+ });
21
+ if (next === source) continue;
22
+ fs.writeFileSync(file, next);
23
+ stamped.push(name);
24
+ }
25
+ return { slotId, stamped };
26
+ }
27
+
28
+ if (require.main === module) {
29
+ const args = {};
30
+ for (let index = 2; index < process.argv.length; index += 1) {
31
+ const key = process.argv[index];
32
+ if (key === '--help' || key === '-h') {
33
+ process.stdout.write('Usage: stamp-runtime-title.cjs --target <path> --runtime-dist <path> [--runtime-dir <path>]\n');
34
+ process.exit(0);
35
+ }
36
+ if (!key.startsWith('--') || index + 1 >= process.argv.length) throw new Error(`invalid argument: ${key}`);
37
+ args[key.slice(2)] = process.argv[++index];
38
+ }
39
+ if (!args.target) throw new Error('--target is required');
40
+ if (!args['runtime-dist']) throw new Error('--runtime-dist is required');
41
+ const result = stampRuntimeTitles({
42
+ target: path.resolve(args.target),
43
+ runtimeDist: path.resolve(args['runtime-dist']),
44
+ runtimeDir: args['runtime-dir'],
45
+ });
46
+ if (result.stamped.length > 0) process.stdout.write(`slot title ${result.slotId}: ${result.stamped.join(', ')}\n`);
47
+ }
48
+
49
+ module.exports = { stampRuntimeTitles };
@@ -185,6 +185,11 @@ if [ -f "$RUNTIME_DIR/recipe-harness-webpack.pid" ]; then
185
185
  [ -z "$killed_harness_pid" ] || kill "$killed_harness_pid" >/dev/null 2>&1 || true
186
186
  rm -f "$RUNTIME_DIR/recipe-harness-webpack.pid"
187
187
  fi
188
+ if [ -f "$RUNTIME_DIR/recipe-harness-webpack-sync.pid" ]; then
189
+ sync_pid="$(cat "$RUNTIME_DIR/recipe-harness-webpack-sync.pid" 2>/dev/null || true)"
190
+ [ -z "$sync_pid" ] || kill "$sync_pid" >/dev/null 2>&1 || true
191
+ rm -f "$RUNTIME_DIR/recipe-harness-webpack-sync.pid"
192
+ fi
188
193
 
189
194
  # Do not start a second writer for dist/chrome. Slot-owned watches use
190
195
  # webpack.pid; harness-owned watches use recipe-harness-webpack.pid.
@@ -219,6 +224,8 @@ fi
219
224
  # unrelated repo and leave this target validating stale dist/chrome output.
220
225
  watch_pid_file="$RUNTIME_DIR/recipe-harness-webpack.pid"
221
226
  watch_log="$RUNTIME_DIR/recipe-harness-webpack.log"
227
+ sync_pid_file="$RUNTIME_DIR/recipe-harness-webpack-sync.pid"
228
+ sync_log="$RUNTIME_DIR/recipe-harness-webpack-sync.log"
222
229
  rm -f "$watch_pid_file"
223
230
  : > "$watch_log"
224
231
  LIB_DIR=""
@@ -229,11 +236,16 @@ unset _lib
229
236
  echo "[recipe-harness] Starting yarn start (watcher-port=${WATCHER_PORT:-default})"
230
237
  echo "[recipe-harness] compact build view - full log: $watch_log (mm-harness logs --full)"
231
238
  started_pid=""
232
- # Launch the watcher detached writing to the log; the tmux window is a read-only tail viewer.
233
239
  path_prefix="${REPO_NODE_PATH_PREFIX:-}"
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"
240
+ launcher="$SCRIPT_DIR/launch-webpack.cjs"
241
+ [ -f "$launcher" ] || { echo "launch-webpack.cjs missing from harness; run: mm-harness install" >&2; exit 1; }
242
+ sync_script="$SCRIPT_DIR/sync-webpack-dist.cjs"
243
+ [ -f "$sync_script" ] || { echo "sync-webpack-dist.cjs missing from harness; run: mm-harness install" >&2; exit 1; }
244
+ 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}")
245
+ [ -z "$WATCHER_PORT" ] || launch_args+=(--port "$WATCHER_PORT")
246
+ [ -z "$path_prefix" ] || launch_args+=(--path-prefix "$path_prefix")
247
+ env -u BUNDLED_DEBUGPY_PATH node "$launcher" "${launch_args[@]}" >/dev/null
248
+ started_pid="$(cat "$watch_pid_file")"
237
249
  # The orchestrator names the session; the harness only populates windows. Resolve
238
250
  # via the run-owned ladder (RECIPE_TMUX_SESSION → agentic-runtime.json session) —
239
251
  # 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"
55
+ rm -f "$runtime_dir/console.tmux" "$runtime_dir/webpack.tmux" "$runtime_dir/console-tail.pid"
56
56
  exit 0
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const { spawnSync } = require('node:child_process');
5
+ const fs = require('node:fs');
6
+ const path = require('node:path');
7
+ const { stampRuntimeTitles } = require('./stamp-runtime-title.cjs');
8
+
9
+ const args = {};
10
+ for (let index = 2; index < process.argv.length; index += 1) {
11
+ const key = process.argv[index];
12
+ if (key === '--help' || key === '-h') {
13
+ process.stdout.write('Usage: sync-webpack-dist.cjs --watcher-pid <pid> --watch-log <path> --target <path> --dist <path> --runtime-dist <path> --pid-file <path>\n');
14
+ process.exit(0);
15
+ }
16
+ if (!key.startsWith('--') || index + 1 >= process.argv.length) throw new Error(`invalid argument: ${key}`);
17
+ args[key.slice(2)] = process.argv[++index];
18
+ }
19
+ for (const required of ['watcher-pid', 'watch-log', 'target', 'dist', 'runtime-dist', 'pid-file']) {
20
+ if (!args[required]) throw new Error(`--${required} is required`);
21
+ }
22
+
23
+ const watcherPid = Number(args['watcher-pid']);
24
+ if (!Number.isInteger(watcherPid) || watcherPid <= 0) throw new Error('--watcher-pid must be a positive integer');
25
+
26
+ const watchLog = path.resolve(args['watch-log']);
27
+ const dist = path.resolve(args.dist);
28
+ const runtimeDist = path.resolve(args['runtime-dist']);
29
+ const pidFile = path.resolve(args['pid-file']);
30
+ const compilePattern = /compiled successfully|compiled with \d+ warning|MetaMask .* compiled|Bundle end: service worker|Bundle end:.*app-init/u;
31
+ let offset = 0;
32
+ let carry = '';
33
+ let timer;
34
+
35
+ function processAlive(pid) {
36
+ try {
37
+ process.kill(pid, 0);
38
+ return true;
39
+ } catch {
40
+ return false;
41
+ }
42
+ }
43
+
44
+ function removeOwnPidFile() {
45
+ try {
46
+ if (fs.readFileSync(pidFile, 'utf8').trim() === String(process.pid)) fs.rmSync(pidFile, { force: true });
47
+ } catch {
48
+ // The launcher may have already removed or replaced it.
49
+ }
50
+ }
51
+
52
+ function syncRuntimeDist() {
53
+ if (!fs.existsSync(path.join(dist, 'manifest.json')) || !fs.existsSync(runtimeDist)) return;
54
+ const result = spawnSync('rsync', ['-a', '--exclude', '_metadata', `${dist}${path.sep}`, `${runtimeDist}${path.sep}`], {
55
+ encoding: 'utf8',
56
+ });
57
+ if (result.status !== 0) {
58
+ process.stderr.write(`runtime-dist sync failed: ${(result.stderr || result.stdout || `rsync exited ${result.status}`).trim()}\n`);
59
+ return;
60
+ }
61
+ stampRuntimeTitles({ target: path.resolve(args.target), runtimeDist });
62
+ process.stdout.write(`runtime-dist updated after webpack compile (${new Date().toISOString()})\n`);
63
+ }
64
+
65
+ function poll() {
66
+ if (!processAlive(watcherPid)) {
67
+ removeOwnPidFile();
68
+ process.exit(0);
69
+ }
70
+ try {
71
+ const size = fs.statSync(watchLog).size;
72
+ if (size < offset) {
73
+ offset = 0;
74
+ carry = '';
75
+ }
76
+ if (size > offset) {
77
+ const length = size - offset;
78
+ const fd = fs.openSync(watchLog, 'r');
79
+ const chunk = Buffer.alloc(length);
80
+ fs.readSync(fd, chunk, 0, length, offset);
81
+ fs.closeSync(fd);
82
+ offset = size;
83
+ const text = carry + chunk.toString('utf8');
84
+ const lines = text.split(/\r?\n/u);
85
+ carry = lines.pop() || '';
86
+ if (lines.some((line) => compilePattern.test(line))) syncRuntimeDist();
87
+ }
88
+ } catch (error) {
89
+ if (error?.code !== 'ENOENT') process.stderr.write(`runtime-dist sync monitor: ${error.message}\n`);
90
+ }
91
+ timer = setTimeout(poll, 250);
92
+ }
93
+
94
+ fs.mkdirSync(path.dirname(pidFile), { recursive: true });
95
+ process.on('SIGTERM', () => {
96
+ clearTimeout(timer);
97
+ removeOwnPidFile();
98
+ process.exit(0);
99
+ });
100
+ process.on('SIGINT', () => {
101
+ clearTimeout(timer);
102
+ removeOwnPidFile();
103
+ process.exit(0);
104
+ });
105
+ poll();
@@ -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 (kept from the legacy extension path.sh).
61
+ # Safe task-local --out resolution.
62
62
  resolve_harness_out() {
63
63
  local target="$1"
64
64
  local out="$2"
File without changes
@@ -18,6 +18,22 @@
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
+ },
21
37
  {
22
38
  "id": "mobile/launch-metro",
23
39
  "entry": "adapters/mobile/launch-metro.cjs",
@@ -62,17 +78,17 @@
62
78
  "id": "mobile/inject",
63
79
  "entry": "adapters/mobile/inject.sh",
64
80
  "kind": "bash",
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"
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"
68
84
  },
69
85
  {
70
86
  "id": "mobile/cleanup",
71
87
  "entry": "adapters/mobile/cleanup.sh",
72
88
  "kind": "bash",
73
- "purpose": "Remove the mobile overlay; restore backed-up product files; remove recorded git excludes.",
89
+ "purpose": "Remove Mobile harness metadata and recorded git excludes without touching product source.",
74
90
  "inputs": "--target --allow-managed-changes; env RECIPE_HARNESS_ROOT",
75
- "outputs": "removes <harness>/ + backups; exit 0/1/2"
91
+ "outputs": "removes <harness>/; exit 0/1/2"
76
92
  },
77
93
  {
78
94
  "id": "mobile/verify",
@@ -114,6 +130,30 @@
114
130
  "inputs": "--target --runtime-dir --runner-bin --summary",
115
131
  "outputs": "<runtime-dir>/recipe-harness-webpack.{pid,log}; optional summary; exit 0/1/2"
116
132
  },
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
+ },
117
157
  {
118
158
  "id": "extension/stop-viewers",
119
159
  "entry": "adapters/extension/stop-viewers.sh",
@@ -378,22 +418,6 @@
378
418
  "inputs": "ESM import from resolve-slot-ports.mjs or resolve-slot-ports.ts",
379
419
  "outputs": "resolveSlotPortsByRepo and related functions"
380
420
  },
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
- },
397
421
  {
398
422
  "id": "lib/recipe-harness-root",
399
423
  "entry": "adapters/shared/recipe-harness-root.mjs",
@@ -406,18 +430,10 @@
406
430
  "id": "lib/resolve-slot-ports-sh",
407
431
  "entry": "adapters/shared/resolve-slot-ports.sh",
408
432
  "kind": "lib",
409
- "purpose": "Bash-compat sourced wrapper over resolve-slot-ports.mjs for stop-metro and legacy callers.",
433
+ "purpose": "Sourced wrapper over resolve-slot-ports.mjs for shell runtime leaves.",
410
434
  "inputs": "source + resolve_* / apply_resolved_* function calls",
411
435
  "outputs": "KEY=VALUE lines on stdout; env vars via apply_resolved_*"
412
436
  },
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
- },
421
437
  {
422
438
  "id": "lib/reap-checkout-metros",
423
439
  "entry": "adapters/shared/reap-checkout-metros.sh",
@@ -470,14 +486,6 @@
470
486
  "purpose": "Open human-facing debugger UIs for extension CDP or mobile Metro runtimes.",
471
487
  "inputs": "--adapter extension|mobile [--cdp-port|--port] [--target page|worker] [--json] [--no-open]",
472
488
  "outputs": "debug URL/report on stdout; exit 0/1"
473
- },
474
- {
475
- "id": "compat-overlays/mobile",
476
- "entry": "adapters/mobile/overlay/compat",
477
- "kind": "compat-overlay",
478
- "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.",
479
- "inputs": "manual `git apply` in the historical product checkout (no in-repo apply logic)",
480
- "outputs": "patched historical checkout (reversible via git apply -R); overlay path recorded in eval evidence"
481
489
  }
482
490
  ]
483
491
  }
File without changes
@@ -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 legacy console→Metro forwarding on
4
+ // React Native (Bridgeless) gates its built-in 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 —
@@ -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 Metro deviceName (backward compat / user-specified).
139
+ // ANDROID_DEVICE — exact user-specified Metro deviceName.
140
140
  const androidTargetName = loadAndroidTargetDeviceName();
141
141
  const androidDevice = loadAndroidDevice();
142
142
  const adbSerial = process.env.ADB_SERIAL || process.env.ANDROID_SERIAL || '';
@@ -205,8 +205,7 @@ async function discoverTarget(port) {
205
205
  }
206
206
  candidates = androidFiltered;
207
207
  } else if (androidDevice) {
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).
208
+ // Exact Metro deviceName match when the user set ANDROID_DEVICE directly.
210
209
  androidFiltered = candidates.filter((t) => t.deviceName === androidDevice);
211
210
  if (androidFiltered.length === 0) {
212
211
  // Pinned by exact Metro name but no candidate matched — fail fast.
File without changes