@deeeed/metamask-harness 0.2.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 (158) hide show
  1. package/CHANGELOG.md +161 -0
  2. package/README.md +140 -0
  3. package/bin/mm-harness +99 -0
  4. package/docs/CHEATSHEET.md +61 -0
  5. package/docs/CLI-SPEC.md +915 -0
  6. package/docs/MENTAL-MODEL.md +295 -0
  7. package/docs/architecture.md +367 -0
  8. package/docs/extension-runtime-commands.md +60 -0
  9. package/docs/harness-cli.md +43 -0
  10. package/docs/live-adapter-contract.md +188 -0
  11. package/docs/package-boundaries.md +47 -0
  12. package/docs/perps-flow-catalog.md +235 -0
  13. package/docs/recipe-libraries.md +95 -0
  14. package/docs/runtime-file-conventions.md +36 -0
  15. package/library/actions/core/perps/_controller.mjs +727 -0
  16. package/library/actions/core/perps/assert_orders.mjs +53 -0
  17. package/library/actions/core/perps/assert_positions.mjs +52 -0
  18. package/library/actions/core/perps/close_orders.mjs +97 -0
  19. package/library/actions/core/perps/close_positions.mjs +118 -0
  20. package/library/actions/core/perps/ensure_orders.mjs +40 -0
  21. package/library/actions/core/perps/ensure_positions.mjs +37 -0
  22. package/library/actions/core/perps/place_order.mjs +201 -0
  23. package/library/actions/core/perps/read_account.mjs +30 -0
  24. package/library/actions/core/perps/read_orders.mjs +27 -0
  25. package/library/actions/core/perps/read_positions.mjs +27 -0
  26. package/library/actions/core/perps/start_state.mjs +92 -0
  27. package/library/actions/core/perps/teardown_state.mjs +86 -0
  28. package/library/actions/extension/perps/assert_orders.mjs +11 -0
  29. package/library/actions/extension/perps/assert_positions.mjs +11 -0
  30. package/library/actions/extension/perps/close_orders.mjs +8 -0
  31. package/library/actions/extension/perps/close_positions.mjs +8 -0
  32. package/library/actions/extension/perps/ensure_orders.mjs +4 -0
  33. package/library/actions/extension/perps/ensure_positions.mjs +4 -0
  34. package/library/actions/extension/perps/perps.mjs +730 -0
  35. package/library/actions/extension/perps/place_order.mjs +7 -0
  36. package/library/actions/extension/perps/read_orders.mjs +4 -0
  37. package/library/actions/extension/perps/read_positions.mjs +3 -0
  38. package/library/actions/extension/platform/cdp.mjs +541 -0
  39. package/library/actions/extension/ui/navigate.mjs +44 -0
  40. package/library/actions/extension/wallet/ensure_unlocked.mjs +36 -0
  41. package/library/actions/extension/wallet/read_state.mjs +27 -0
  42. package/library/actions/extension/wallet/select_account.mjs +48 -0
  43. package/library/actions/extension/wallet/setup.mjs +35 -0
  44. package/library/actions/mobile/app-overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +185 -0
  45. package/library/actions/mobile/app-overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +1662 -0
  46. package/library/actions/mobile/bridge-runtime/cdp-bridge.cjs +686 -0
  47. package/library/actions/mobile/bridge-runtime/lib/cdp-eval.cjs +110 -0
  48. package/library/actions/mobile/bridge-runtime/lib/config.cjs +39 -0
  49. package/library/actions/mobile/bridge-runtime/lib/issue-capture.cjs +446 -0
  50. package/library/actions/mobile/bridge-runtime/lib/target-discovery.cjs +204 -0
  51. package/library/actions/mobile/bridge-runtime/lib/ws-client.cjs +108 -0
  52. package/library/actions/mobile/bridge-runtime/setup-wallet.sh +442 -0
  53. package/library/actions/mobile/perps/assert_orders.mjs +11 -0
  54. package/library/actions/mobile/perps/assert_positions.mjs +11 -0
  55. package/library/actions/mobile/perps/close_orders.mjs +8 -0
  56. package/library/actions/mobile/perps/close_positions.mjs +8 -0
  57. package/library/actions/mobile/perps/ensure_orders.mjs +4 -0
  58. package/library/actions/mobile/perps/ensure_positions.mjs +4 -0
  59. package/library/actions/mobile/perps/perps.mjs +709 -0
  60. package/library/actions/mobile/perps/place_order.mjs +7 -0
  61. package/library/actions/mobile/perps/read_orders.mjs +4 -0
  62. package/library/actions/mobile/perps/read_positions.mjs +3 -0
  63. package/library/actions/mobile/platform/bridge.mjs +283 -0
  64. package/library/actions/mobile/ui/navigate.mjs +38 -0
  65. package/library/actions/mobile/wallet/ensure_unlocked.mjs +107 -0
  66. package/library/actions/mobile/wallet/home.mjs +35 -0
  67. package/library/actions/mobile/wallet/read_state.mjs +40 -0
  68. package/library/actions/mobile/wallet/select_account.mjs +48 -0
  69. package/library/actions/mobile/wallet/setup.mjs +220 -0
  70. package/library/flows/perps.flows.json +64 -0
  71. package/library/library.json +7 -0
  72. package/library/manifests/core.action-manifest.json +1282 -0
  73. package/library/manifests/extension.action-manifest.json +1749 -0
  74. package/library/manifests/mobile.action-manifest.json +1753 -0
  75. package/library/recipes/action-validation.extension.recipe.json +417 -0
  76. package/library/recipes/action-validation.mobile.recipe.json +422 -0
  77. package/library/recipes/order-lifecycle.core.recipe.json +78 -0
  78. package/library/recipes/perps-lifecycle.recipe.json +194 -0
  79. package/library/recipes/read-markets.core.recipe.json +38 -0
  80. package/library/recipes/smoke.extension.recipe.json +31 -0
  81. package/library/recipes/smoke.mobile.recipe.json +31 -0
  82. package/library/recipes/trading-lifecycle.core.recipe.json +76 -0
  83. package/orchestration/compat-overlays/README.md +19 -0
  84. package/orchestration/compat-overlays/mobile/README.md +13 -0
  85. package/orchestration/compat-overlays/mobile/rn81-message-event-source.patch +42 -0
  86. package/orchestration/core/cleanup.sh +37 -0
  87. package/orchestration/core/inject.sh +154 -0
  88. package/orchestration/doctor.mjs +72 -0
  89. package/orchestration/extension/cleanup.mjs +60 -0
  90. package/orchestration/extension/console-tail.mjs +228 -0
  91. package/orchestration/extension/ensure-browser.sh +416 -0
  92. package/orchestration/extension/ensure-ready.ts +185 -0
  93. package/orchestration/extension/extension-id.ts +107 -0
  94. package/orchestration/extension/inject.mjs +266 -0
  95. package/orchestration/extension/launch-browser.cjs +216 -0
  96. package/orchestration/extension/launch.sh +175 -0
  97. package/orchestration/extension/live.sh +320 -0
  98. package/orchestration/extension/pin-remote-flags.cjs +45 -0
  99. package/orchestration/extension/readiness.mjs +414 -0
  100. package/orchestration/extension/refresh-build.sh +190 -0
  101. package/orchestration/extension/runtime-decision.ts +445 -0
  102. package/orchestration/extension/runtime.ts +407 -0
  103. package/orchestration/extension/seed-fixture.sh +177 -0
  104. package/orchestration/extension/sidepanel-toggle.sh +291 -0
  105. package/orchestration/extension/snapshot-dist.sh +84 -0
  106. package/orchestration/extension/start-watch.sh +339 -0
  107. package/orchestration/extension/wallet-fixture-state.cjs +1086 -0
  108. package/orchestration/lib/activate-repo-node.sh +144 -0
  109. package/orchestration/lib/cli-color.mjs +84 -0
  110. package/orchestration/lib/cli-commands.mjs +243 -0
  111. package/orchestration/lib/cli-home.mjs +354 -0
  112. package/orchestration/lib/cli-ux.sh +252 -0
  113. package/orchestration/lib/cli-version.mjs +123 -0
  114. package/orchestration/lib/ensure-runner-deps.sh +56 -0
  115. package/orchestration/lib/harness-path.sh +55 -0
  116. package/orchestration/lib/hash-helpers.sh +44 -0
  117. package/orchestration/lib/json-field.sh +23 -0
  118. package/orchestration/lib/log-tui.mjs +304 -0
  119. package/orchestration/lib/open-debug.mjs +317 -0
  120. package/orchestration/lib/path-defaults.json +4 -0
  121. package/orchestration/lib/progress.mjs +107 -0
  122. package/orchestration/lib/recipe-paths.mjs +26 -0
  123. package/orchestration/lib/resolve-farmslot-ports.sh +144 -0
  124. package/orchestration/manifest.json +358 -0
  125. package/orchestration/mobile/cleanup.sh +192 -0
  126. package/orchestration/mobile/deps-markers.ts +21 -0
  127. package/orchestration/mobile/inject.sh +681 -0
  128. package/orchestration/mobile/launch.sh +137 -0
  129. package/orchestration/mobile/live.sh +125 -0
  130. package/orchestration/mobile/runtime-decision.ts +292 -0
  131. package/orchestration/porcelain/metamask-recipe +99 -0
  132. package/orchestration/porcelain/mm-recipe +1591 -0
  133. package/orchestration/porcelain/mme-recipe +1181 -0
  134. package/package.json +59 -0
  135. package/runner/extension/verify.sh +511 -0
  136. package/runner/mobile/verify.sh +501 -0
  137. package/runner/src/adapters.ts +601 -0
  138. package/runner/src/cli.ts +1820 -0
  139. package/runner/src/commands/debug.ts +44 -0
  140. package/runner/src/commands/fixtures.ts +99 -0
  141. package/runner/src/commands/launch.ts +397 -0
  142. package/runner/src/commands/logs.ts +60 -0
  143. package/runner/src/commands/shared.ts +138 -0
  144. package/runner/src/completions-cache.ts +86 -0
  145. package/runner/src/doctor.ts +203 -0
  146. package/runner/src/harness.ts +516 -0
  147. package/runner/src/heal-bounds.ts +179 -0
  148. package/runner/src/index.ts +6 -0
  149. package/runner/src/live-adapter-contract.ts +274 -0
  150. package/runner/src/manifest.ts +47 -0
  151. package/runner/src/mm-harness-cli.ts +488 -0
  152. package/runner/src/paths.ts +198 -0
  153. package/runner/src/recording-target.ts +147 -0
  154. package/runner/src/run-recording.ts +329 -0
  155. package/runner/src/runner.ts +108 -0
  156. package/runner/src/types.ts +57 -0
  157. package/scripts/completions.sh +125 -0
  158. package/scripts/install-completions.sh +62 -0
@@ -0,0 +1,339 @@
1
+ #!/usr/bin/env bash
2
+ # start-watch.sh — harness-owned webpack watcher with dual-writer prevention
3
+ # scripts/extension/live.sh)
4
+ #
5
+ # Purpose:
6
+ # Clean-build path for one extension checkout: clears the webpack cache,
7
+ # starts (or reuses) the harness-owned `yarn start` watcher, and waits for
8
+ # a compile success marker. Refuses to run if a slot-owned watcher is alive.
9
+ #
10
+ # Inputs (flags / env):
11
+ # --target <metamask-extension> (default $PWD; becomes the working dir)
12
+ # --runtime-dir <rel> (default from orchestration/lib path-defaults)
13
+ # --watcher-port <port> (default env WATCHER_PORT/RECIPE_WATCHER_PORT,
14
+ # runtime json, or slot suffix e.g. metamask-extension-1 => 9011)
15
+ # --runner-bin <path> (optional; records deps/cache baseline best-effort)
16
+ # --summary <file> (optional; writes the standard summary.json shape)
17
+ #
18
+ # Outputs:
19
+ # <runtime-dir>/recipe-harness-webpack.{pid,log}; optional --summary file
20
+ # {feature,status,inputs,outputs,generatedAt}.
21
+ # Exit 0 — compiled; 1 — slot watcher owns the checkout, build failed, or
22
+ # compile-marker timeout (240x2s); 2 — bad args.
23
+ #
24
+ # Never touches: the slot-owned webpack.pid watcher process (refusal only,
25
+ # removes the pid file only when stale); product source files.
26
+ set -euo pipefail
27
+
28
+ TARGET="$PWD"
29
+ RUNTIME_DIR=""
30
+ WATCHER_PORT="${WATCHER_PORT:-${RECIPE_WATCHER_PORT:-}}"
31
+ RUNNER_BIN=""
32
+ SUMMARY=""
33
+ require_value() { [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; }
34
+ while [ "$#" -gt 0 ]; do
35
+ case "$1" in
36
+ --target) require_value "$@"; TARGET="$2"; shift 2 ;;
37
+ --runtime-dir) require_value "$@"; RUNTIME_DIR="$2"; shift 2 ;;
38
+ --watcher-port) require_value "$@"; WATCHER_PORT="$2"; shift 2 ;;
39
+ --runner-bin) require_value "$@"; RUNNER_BIN="$2"; shift 2 ;;
40
+ --summary) require_value "$@"; SUMMARY="$2"; shift 2 ;;
41
+ -h|--help)
42
+ echo "Usage: start-watch.sh [--target <metamask-extension>] [--runtime-dir <rel>] [--watcher-port <port>] [--runner-bin <path>] [--summary <file>]"
43
+ exit 0
44
+ ;;
45
+ *) echo "Unknown arg: $1" >&2; exit 2 ;;
46
+ esac
47
+ done
48
+
49
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
50
+ # shellcheck disable=SC1091
51
+ for _arn in "$SCRIPT_DIR/lib/activate-repo-node.sh" "$SCRIPT_DIR/../lib/activate-repo-node.sh"; do
52
+ [ -f "$_arn" ] && { . "$_arn"; break; }
53
+ done
54
+ unset _arn
55
+ if [ -z "$RUNTIME_DIR" ]; then
56
+ # shellcheck disable=SC1091
57
+ for _hp in "$SCRIPT_DIR/lib/harness-path.sh" "$SCRIPT_DIR/../lib/harness-path.sh"; do
58
+ [ -f "$_hp" ] && { . "$_hp"; break; }
59
+ done
60
+ unset _hp
61
+ if ! command -v recipe_runtime_dir >/dev/null 2>&1; then
62
+ echo "start-watch: shared lib orchestration/lib/harness-path.sh not found; pass --runtime-dir." >&2
63
+ exit 1
64
+ fi
65
+ RUNTIME_DIR="$(recipe_runtime_dir)"
66
+ fi
67
+
68
+ cd "$TARGET"
69
+ REPO_NODE_PATH_PREFIX=""
70
+ for _arn in "$SCRIPT_DIR/lib/activate-repo-node.sh" "$SCRIPT_DIR/../lib/activate-repo-node.sh"; do
71
+ [ -f "$_arn" ] && { # shellcheck disable=SC1090
72
+ . "$_arn"
73
+ break
74
+ }
75
+ done
76
+ unset _arn
77
+ if command -v require_repo_node >/dev/null 2>&1; then
78
+ require_repo_node "$TARGET"
79
+ elif command -v activate_repo_node >/dev/null 2>&1; then
80
+ activate_repo_node "$TARGET" || {
81
+ echo "[recipe-harness] refusing to start yarn start without a pinned Node for $TARGET" >&2
82
+ exit 1
83
+ }
84
+ else
85
+ echo "[recipe-harness] activate-repo-node.sh missing from harness; run: recipe sync" >&2
86
+ exit 1
87
+ fi
88
+ if [ -z "$WATCHER_PORT" ]; then
89
+ WATCHER_PORT="$(node - "$PWD" "$RUNTIME_DIR" <<'NODE'
90
+ const fs = require('fs');
91
+ const path = require('path');
92
+ const [target, runtimeDir] = process.argv.slice(2);
93
+ for (const name of ['recipe-runtime.json', 'agentic-runtime.json']) {
94
+ const file = path.join(target, runtimeDir, name);
95
+ if (!fs.existsSync(file)) continue;
96
+ try {
97
+ const data = JSON.parse(fs.readFileSync(file, 'utf8'));
98
+ const value = data.watcherPort ?? data.devServerPort;
99
+ if (value !== undefined && value !== null && value !== '') {
100
+ process.stdout.write(String(value));
101
+ process.exit(0);
102
+ }
103
+ } catch {
104
+ process.exit(1);
105
+ }
106
+ }
107
+ const match = path.basename(target).match(/-(\d+)$/u);
108
+ if (match) process.stdout.write(`901${match[1]}`);
109
+ NODE
110
+ )"
111
+ fi
112
+ if [ -n "$WATCHER_PORT" ] && ! [[ "$WATCHER_PORT" =~ ^[0-9]+$ ]]; then
113
+ echo "start-watch: --watcher-port must be numeric (got: $WATCHER_PORT)" >&2
114
+ exit 2
115
+ fi
116
+ export WATCHER_PORT RECIPE_WATCHER_PORT="$WATCHER_PORT"
117
+
118
+ status=fail
119
+ finish() {
120
+ if [ -n "$SUMMARY" ]; then
121
+ mkdir -p "$(dirname "$SUMMARY")"
122
+ STATUS_FOR_SUMMARY="$status" TARGET_FOR_SUMMARY="$PWD" RUNTIME_DIR_FOR_SUMMARY="$RUNTIME_DIR" WATCHER_PORT_FOR_SUMMARY="$WATCHER_PORT" SUMMARY_PATH="$SUMMARY" node <<'NODE' || true
123
+ const fs = require('fs');
124
+ fs.writeFileSync(process.env.SUMMARY_PATH, `${JSON.stringify({
125
+ feature: 'extension/start-watch',
126
+ status: process.env.STATUS_FOR_SUMMARY,
127
+ inputs: {
128
+ target: process.env.TARGET_FOR_SUMMARY,
129
+ runtimeDir: process.env.RUNTIME_DIR_FOR_SUMMARY,
130
+ watcherPort: process.env.WATCHER_PORT_FOR_SUMMARY ? Number(process.env.WATCHER_PORT_FOR_SUMMARY) : null,
131
+ },
132
+ outputs: {
133
+ watchPidFile: `${process.env.RUNTIME_DIR_FOR_SUMMARY}/recipe-harness-webpack.pid`,
134
+ watchLog: `${process.env.RUNTIME_DIR_FOR_SUMMARY}/recipe-harness-webpack.log`,
135
+ },
136
+ generatedAt: new Date().toISOString(),
137
+ }, null, 2)}\n`);
138
+ NODE
139
+ fi
140
+ }
141
+ trap finish EXIT
142
+
143
+ mkdir -p "$RUNTIME_DIR"
144
+
145
+ watcher_pids() {
146
+ ps -axo pid=,command= | while read -r current_pid command; do
147
+ [ -n "${current_pid:-}" ] || continue
148
+ case "$command" in
149
+ *"yarn start"*|*"webpack --watch"*|*"development/webpack/launch.ts --watch"*) ;;
150
+ *) continue ;;
151
+ esac
152
+ if printf '%s' "$command" | grep -Fq "$PWD"; then
153
+ printf '%s\n' "$current_pid"
154
+ continue
155
+ fi
156
+ if command -v lsof >/dev/null 2>&1; then
157
+ if lsof -a -p "$current_pid" -d cwd -Fn 2>/dev/null | sed -n 's/^n//p' | grep -Fxq "$PWD"; then
158
+ printf '%s\n' "$current_pid"
159
+ fi
160
+ fi
161
+ done | sed '/^$/d'
162
+ true
163
+ }
164
+
165
+ write_canonical_watch_pid() {
166
+ local live_pids live_pid
167
+ live_pids="$(watcher_pids || true)"
168
+ if [ -z "$live_pids" ] && [ -n "${started_pid:-}" ] && kill -0 "$started_pid" >/dev/null 2>&1; then
169
+ live_pids="$started_pid"
170
+ fi
171
+ [ -n "$live_pids" ] || return 1
172
+ live_pid="$(printf '%s\n' "$live_pids" | head -1)"
173
+ printf '%s\n' "$live_pid" > "$watch_pid_file"
174
+ printf '%s\n' "$live_pid" > "$slot_watch_pid_file"
175
+ }
176
+
177
+ slot_number() {
178
+ local base
179
+ base="$(basename "$PWD")"
180
+ [[ "$base" =~ -([0-9]+)$ ]] && printf '%s\n' "${BASH_REMATCH[1]}"
181
+ }
182
+
183
+ # Explicit clean-build path: clear the target-scoped webpack cache before
184
+ # starting the harness-owned watcher. Faster incremental refresh stays on
185
+ # `mme-recipe refresh`; this script is for proving a clean build works.
186
+ echo '[recipe-harness] clean build: clearing webpack cache + harness watcher'
187
+ rm -rf node_modules/.cache/webpack
188
+ killed_harness_pid=""
189
+ if [ -f "$RUNTIME_DIR/recipe-harness-webpack.pid" ]; then
190
+ killed_harness_pid="$(cat "$RUNTIME_DIR/recipe-harness-webpack.pid" 2>/dev/null || true)"
191
+ [ -z "$killed_harness_pid" ] || kill "$killed_harness_pid" >/dev/null 2>&1 || true
192
+ rm -f "$RUNTIME_DIR/recipe-harness-webpack.pid"
193
+ fi
194
+
195
+ # Do not start a second writer for dist/chrome. Slot-owned watches use
196
+ # webpack.pid; harness-owned watches use recipe-harness-webpack.pid.
197
+ slot_watch_pid_file="$RUNTIME_DIR/webpack.pid"
198
+ if [ -f "$slot_watch_pid_file" ]; then
199
+ slot_watch_pid=$(cat "$slot_watch_pid_file" 2>/dev/null || true)
200
+ else
201
+ slot_watch_pid=
202
+ fi
203
+ if [ -n "$slot_watch_pid" ] && [ "$slot_watch_pid" = "$killed_harness_pid" ]; then
204
+ rm -f "$slot_watch_pid_file"
205
+ slot_watch_pid=
206
+ fi
207
+ if [ -n "$slot_watch_pid" ] && kill -0 "$slot_watch_pid" >/dev/null 2>&1; then
208
+ echo "[recipe-harness] Refusing to start yarn start: slot webpack watch already owns this checkout (pid $slot_watch_pid). Stop it first or reuse the live browser." >&2
209
+ exit 1
210
+ fi
211
+ [ -z "$slot_watch_pid" ] || rm -f "$slot_watch_pid_file"
212
+
213
+ existing_watch_pids="$(watcher_pids || true)"
214
+ if [ -n "$existing_watch_pids" ]; then
215
+ echo "[recipe-harness] Stopping stale target-scoped watcher(s): $(printf '%s' "$existing_watch_pids" | tr '\n' ' ')"
216
+ printf '%s\n' "$existing_watch_pids" | xargs kill 2>/dev/null || true
217
+ sleep 2
218
+ existing_watch_pids="$(watcher_pids || true)"
219
+ if [ -n "$existing_watch_pids" ]; then
220
+ printf '%s\n' "$existing_watch_pids" | xargs kill -9 2>/dev/null || true
221
+ fi
222
+ fi
223
+
224
+ # Scope watcher reuse to this checkout. A machine-global pgrep can match an
225
+ # unrelated repo and leave this target validating stale dist/chrome output.
226
+ watch_pid_file="$RUNTIME_DIR/recipe-harness-webpack.pid"
227
+ watch_log="$RUNTIME_DIR/recipe-harness-webpack.log"
228
+ rm -f "$watch_pid_file"
229
+ : > "$watch_log"
230
+ LIB_DIR=""
231
+ for _lib in "$SCRIPT_DIR/lib" "$SCRIPT_DIR/../lib"; do
232
+ [ -f "$_lib/log-tui.mjs" ] && LIB_DIR="$_lib" && break
233
+ done
234
+ unset _lib
235
+ echo "[recipe-harness] Starting yarn start (watcher-port=${WATCHER_PORT:-default})"
236
+ echo "[recipe-harness] compact build view - full log: $watch_log (recipe logs --full)"
237
+ started_pid=""
238
+ # Launch the watcher detached writing to the log; the tmux window is a read-only tail viewer.
239
+ path_prefix="${REPO_NODE_PATH_PREFIX:-}"
240
+ nohup env -u BUNDLED_DEBUGPY_PATH ${path_prefix:+PATH="${path_prefix}${PATH}"} ${WATCHER_PORT:+PORT="$WATCHER_PORT"} yarn start > "$watch_log" 2>&1 &
241
+ started_pid=$!
242
+ printf '%s\n' "$started_pid" > "$watch_pid_file"
243
+ tmux_session="${RECIPE_TMUX_SESSION:-}"
244
+ if [ -z "$tmux_session" ]; then
245
+ slot="$(slot_number || true)"
246
+ [ -z "$slot" ] || tmux_session="mme-$slot"
247
+ fi
248
+ if [ -n "$tmux_session" ] && command -v tmux >/dev/null 2>&1 && tmux has-session -t "$tmux_session" 2>/dev/null; then
249
+ tmux_window="webpack-${WATCHER_PORT:-default}"
250
+ while tmux list-windows -t "$tmux_session" -F '#{window_name}' 2>/dev/null | grep -Fxq "$tmux_window"; do
251
+ tmux kill-window -t "${tmux_session}:${tmux_window}" 2>/dev/null || break
252
+ done
253
+ log_q="$(printf '%q' "$watch_log")"
254
+ tmux new-window -d -t "$tmux_session" -n "$tmux_window" "exec tail -n +1 -F $log_q"
255
+ fi
256
+ ln -sf "$(basename "$watch_log")" "$RUNTIME_DIR/webpack.log"
257
+
258
+ watch_tail_pid=""
259
+ if [ "${RECIPE_LOG_UI:-compact}" = "full" ]; then
260
+ tail -n +1 -F "$watch_log" &
261
+ watch_tail_pid=$!
262
+ elif [ -n "$LIB_DIR" ] && [ "${RECIPE_LOG_TUI_PARENT:-}" != "1" ]; then
263
+ node "$LIB_DIR/log-tui.mjs" watch \
264
+ --log "$watch_log" \
265
+ --events "${RECIPE_LOG_EVENTS:-10}" \
266
+ --mode "${RECIPE_LOG_UI:-compact}" \
267
+ --label "webpack build" \
268
+ --success-pattern 'compiled successfully|compiled with [0-9]+ warning|MetaMask .* compiled|Bundle end: service worker|Bundle end:.*app-init' &
269
+ watch_tail_pid=$!
270
+ fi
271
+ compiled=false
272
+ for i in {1..240}; do
273
+ if [ -n "$watch_tail_pid" ] && ! kill -0 "$watch_tail_pid" >/dev/null 2>&1; then
274
+ wait "$watch_tail_pid" 2>/dev/null || true
275
+ if grep -Eq 'compiled successfully|compiled with [0-9]+ warning|MetaMask .* compiled|Bundle end: service worker|Bundle end:.*app-init' "$watch_log" 2>/dev/null; then
276
+ compiled=true
277
+ break
278
+ fi
279
+ echo '[recipe-harness] compact build watcher exited before compile marker' >&2
280
+ [ -n "$LIB_DIR" ] && node "$LIB_DIR/log-tui.mjs" events --log "$watch_log" --events "${RECIPE_LOG_EVENTS:-10}" >&2 || true
281
+ rm -f "$watch_pid_file" "$slot_watch_pid_file"
282
+ exit 1
283
+ fi
284
+ if ! write_canonical_watch_pid; then
285
+ if [ "$i" -le 10 ]; then
286
+ sleep 2
287
+ continue
288
+ fi
289
+ [ -n "$watch_tail_pid" ] && kill "$watch_tail_pid" >/dev/null 2>&1 || true
290
+ [ -n "$watch_tail_pid" ] && wait "$watch_tail_pid" 2>/dev/null || true
291
+ echo '[recipe-harness] yarn start exited before webpack emitted a compile marker:' >&2
292
+ if [ -n "$LIB_DIR" ]; then
293
+ node "$LIB_DIR/log-tui.mjs" events --log "$watch_log" --events 15 >&2 || true
294
+ else
295
+ tail -40 "$watch_log" >&2 || true
296
+ fi
297
+ rm -f "$watch_pid_file" "$slot_watch_pid_file"
298
+ exit 1
299
+ fi
300
+ if grep -Eq 'Module build failed|^ERROR in |compiled with [1-9][0-9]* error' "$watch_log" 2>/dev/null; then
301
+ [ -n "$watch_tail_pid" ] && kill "$watch_tail_pid" >/dev/null 2>&1 || true
302
+ [ -n "$watch_tail_pid" ] && wait "$watch_tail_pid" 2>/dev/null || true
303
+ echo '[recipe-harness] webpack BUILD FAILED (not waiting for timeout):' >&2
304
+ [ -n "$LIB_DIR" ] && node "$LIB_DIR/log-tui.mjs" events --log "$watch_log" --events 15 >&2 || true
305
+ echo '[recipe-harness] full log:' "$watch_log" >&2
306
+ echo '[recipe-harness] If it is a stale-cache ENOENT it should have been auto-cleared; a recurring error here is a real source/build issue to fix.' >&2
307
+ exit 1
308
+ fi
309
+ if grep -Eq 'compiled successfully|compiled with [0-9]+ warning|MetaMask .* compiled|Bundle end: service worker|Bundle end:.*app-init' "$watch_log" 2>/dev/null; then
310
+ compiled=true
311
+ [ -n "$watch_tail_pid" ] && kill "$watch_tail_pid" >/dev/null 2>&1 || true
312
+ [ -n "$watch_tail_pid" ] && wait "$watch_tail_pid" 2>/dev/null || true
313
+ break
314
+ fi
315
+ sleep 2
316
+ done
317
+ if [ "$compiled" != true ]; then
318
+ [ -n "$watch_tail_pid" ] && kill "$watch_tail_pid" >/dev/null 2>&1 || true
319
+ [ -n "$watch_tail_pid" ] && wait "$watch_tail_pid" 2>/dev/null || true
320
+ echo 'Timed out waiting for target-scoped yarn start compilation marker' >&2
321
+ if [ -n "$LIB_DIR" ]; then
322
+ node "$LIB_DIR/log-tui.mjs" events --log "$watch_log" --events 15 >&2 || true
323
+ else
324
+ tail -40 "$watch_log" >&2 || true
325
+ fi
326
+ exit 1
327
+ fi
328
+ if ! write_canonical_watch_pid; then
329
+ echo '[recipe-harness] webpack emitted a compile marker, but no live target-scoped watcher remained:' >&2
330
+ tail -80 "$watch_log" >&2 || true
331
+ rm -f "$watch_pid_file" "$slot_watch_pid_file"
332
+ exit 1
333
+ fi
334
+ echo '[recipe-harness] yarn start compiled successfully'
335
+ if [ -n "$RUNNER_BIN" ]; then
336
+ "$RUNNER_BIN" runtime-decision --adapter extension --target . --record-baseline --json >/dev/null 2>&1 || true
337
+ echo '[recipe-harness] recorded deps/cache baseline (runtime-decision --record-baseline)'
338
+ fi
339
+ status=pass