@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
@@ -1,704 +1,121 @@
1
1
  #!/usr/bin/env bash
2
- # inject.sh install the mobile harness overlay into a checkout
3
- #
4
- # Purpose:
5
- # Installs the runner delegate + recipe/script snapshots into the target's
6
- # ignored harness dir, and (full mode) the AgenticService app overlay with
7
- # backup/rollback, hash markers, and .git/info/exclude hygiene.
8
- #
9
- # Inputs (flags / env):
10
- # --target <metamask-mobile> (default $PWD)
11
- # --allow-dirty-harness-paths skip dirty/managed-hash refusals
12
- # --force-overlay overwrite a product-owned in-app bridge
13
- # --no-git-exclude skip .git/info/exclude updates
14
- # env RECIPE_HARNESS_ROOT, METAMASK_RUNNER_PROTOCOL_ROOT > FARMSLOT_ROOT >
15
- # <runner>/.farmslot-root (protocol root provenance)
16
- #
17
- # Outputs:
18
- # <harness>/manifest.json, <harness>/runner + scripts snapshots,
19
- # backup dir + managed-hashes.tsv, added-git-exclude record.
20
- # Exit 0 — installed; 1 — refusal (dirty paths/changed managed paths) or
21
- # failed install (rolled back); 2 — bad args.
22
- #
23
- # Never touches: tracked product files unless --force-overlay (full mode
24
- # patches NavigationService.ts/App.tsx with backups); nothing outside the
25
- # target checkout and its .git dir.
2
+ # Install runner metadata and delegates into a current MetaMask Mobile checkout.
26
3
  set -euo pipefail
27
4
 
28
5
  TARGET="$PWD"
29
- ALLOW_DIRTY=false
30
- FORCE_OVERLAY=false
31
6
  GIT_EXCLUDE=true
32
7
  while [ "$#" -gt 0 ]; do
33
8
  case "$1" in
34
- --target) TARGET="$2"; shift 2 ;;
35
- --allow-dirty-harness-paths) ALLOW_DIRTY=true; shift ;;
36
- --force-overlay) FORCE_OVERLAY=true; shift ;;
9
+ --target) [ "$#" -ge 2 ] || { echo "Missing value for --target" >&2; exit 2; }; TARGET="$2"; shift 2 ;;
37
10
  --no-git-exclude) GIT_EXCLUDE=false; shift ;;
38
- -h|--help) echo "Usage: install.sh [--target <metamask-mobile>] [--allow-dirty-harness-paths] [--force-overlay] [--no-git-exclude]"; exit 0 ;;
11
+ -h|--help) echo "Usage: inject.sh [--target <metamask-mobile>] [--no-git-exclude]"; exit 0 ;;
39
12
  *) echo "Unknown arg: $1" >&2; exit 2 ;;
40
13
  esac
41
14
  done
42
15
 
43
16
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
44
- RUNNER_DIR="$(cd "${SCRIPT_DIR}/../.." && pwd -P)"
45
- ADAPTER_DIR="$RUNNER_DIR/adapters/mobile"
46
- # compat-overlays: NOT applied here. adapters/mobile/overlay/compat/ holds
47
- # reversible patches applied only when injecting into HISTORICAL checkouts
48
- # (replay/eval against old product commits, e.g. pre-RN-0.81 mobile); the
49
- # operator/recipe-harness skill applies them manually before the rebuild.
50
- # See adapters/mobile/overlay/compat/README.md.
51
- # shellcheck disable=SC1091
52
- . "$SCRIPT_DIR/../shared/hash-helpers.sh"
53
- # shellcheck disable=SC1091
54
- for _hp in "$SCRIPT_DIR/../shared/harness-path.sh"; do
55
- [ -f "$_hp" ] && { . "$_hp"; break; }
56
- done
57
- unset _hp
58
- if ! command -v harness_root >/dev/null 2>&1; then
59
- echo "mm-harness: shared lib adapters/shared/harness-path.sh not found; reinstall the runner." >&2
60
- exit 1
61
- fi
62
- TARGET="$(cd "$TARGET" && pwd)"
63
- # The install stamps this checkout's HEAD into the manifest as its identity. If we
64
- # cannot resolve it (not a git repo, unborn HEAD, git unavailable) we must not
65
- # install with an unidentifiable target: a downstream consumer could not tell a
66
- # stale manifest (left behind by a previous branch/tag) from a current one. Fail
67
- # loud with escape guidance rather than stamping an "unknown" revision.
68
- # --verify fails cleanly (no stdout) on an unborn HEAD, where plain `rev-parse
69
- # HEAD` would echo the literal "HEAD" and slip past the emptiness check below.
17
+ RUNNER_DIR="$(cd "$SCRIPT_DIR/../.." && pwd -P)"
18
+ . "$SCRIPT_DIR/../shared/harness-path.sh"
19
+ TARGET="$(cd "$TARGET" && pwd -P)"
70
20
  TARGET_REV="$(git -C "$TARGET" rev-parse --verify HEAD 2>/dev/null || true)"
71
21
  if [ -z "$TARGET_REV" ]; then
72
- echo "Refusing mobile recipe harness install: cannot resolve the target checkout HEAD in $TARGET." >&2
73
- echo "Next: ensure the target is a git checkout with at least one commit (git -C \"$TARGET\" rev-parse HEAD succeeds), then retry the install." >&2
22
+ echo "Refusing mobile harness install: cannot resolve checkout HEAD in $TARGET." >&2
23
+ echo "Next: run this command from a current MetaMask Mobile git checkout." >&2
74
24
  exit 1
75
25
  fi
76
- METAMASK_RUNNER_DIR="$RUNNER_DIR"
77
- METAMASK_RUNNER_SOURCE_KIND="runner-self"
78
- METAMASK_RUNNER_REVISION="$(git -C "$RUNNER_DIR" rev-parse HEAD 2>/dev/null || echo unknown)"
79
- METAMASK_RUNNER_PROTOCOL_ROOT="${METAMASK_RUNNER_PROTOCOL_ROOT:-${METAMASK_RUNNER_FARMSLOT_ROOT:-${FARMSLOT_ROOT:-}}}"
80
- if [ -z "$METAMASK_RUNNER_PROTOCOL_ROOT" ] && [ -f "$RUNNER_DIR/.farmslot-root" ]; then
81
- METAMASK_RUNNER_PROTOCOL_ROOT="$(cat "$RUNNER_DIR/.farmslot-root")"
26
+
27
+ if ! git -C "$TARGET" ls-files --error-unmatch app/dev-tools/AgenticService/AgenticService.ts >/dev/null 2>&1 \
28
+ || ! grep -q "AgenticService.install" "$TARGET/app/core/NavigationService/NavigationService.ts" 2>/dev/null; then
29
+ echo "Current MetaMask Mobile agentic bridge is missing from $TARGET." >&2
30
+ echo "Next: update the checkout to a revision containing app/dev-tools/AgenticService, then retry mm-harness install." >&2
31
+ exit 1
82
32
  fi
83
- export METAMASK_RUNNER_DIR METAMASK_RUNNER_SOURCE_KIND METAMASK_RUNNER_REVISION METAMASK_RUNNER_PROTOCOL_ROOT
84
- CLEANUP_SH="$SCRIPT_DIR/cleanup.sh"
33
+
85
34
  HARNESS_ROOT="$(harness_root)"
86
35
  HARNESS_REL="$HARNESS_ROOT/mobile"
87
-
88
- refuse_symlink_destination() {
89
- local rel="$1"
90
- local path_so_far="$TARGET"
36
+ HARNESS_DIR="$(harness_dir "$TARGET" mobile)"
37
+ for rel in "$HARNESS_REL" "$HARNESS_REL/runner" "$HARNESS_REL/scripts"; do
38
+ current="$TARGET"
91
39
  IFS='/' read -r -a parts <<< "$rel"
92
40
  for part in "${parts[@]}"; do
93
41
  [ -n "$part" ] || continue
94
- path_so_far="$path_so_far/$part"
95
- if [ -L "$path_so_far" ]; then
96
- echo "Refusing mobile recipe harness install: $rel contains symlink component $path_so_far." >&2
97
- return 1
98
- fi
42
+ current="$current/$part"
43
+ [ ! -L "$current" ] || { echo "Refusing mobile harness install: $rel contains symlink $current." >&2; exit 1; }
99
44
  done
100
- }
101
-
102
- HARNESS_DIR="$(harness_dir "$TARGET" mobile)"
103
- # refuse_symlink_destination walks every path component, so the deepest path also
104
- # guards its parents ($HARNESS_REL covers the root segments).
105
- refuse_symlink_destination "$HARNESS_REL"
106
- refuse_symlink_destination "app"
107
- refuse_symlink_destination "app/core"
108
- refuse_symlink_destination "app/dev-tools/AgenticService"
109
- refuse_symlink_destination "app/core/NavigationService"
110
- refuse_symlink_destination "app/components"
111
- refuse_symlink_destination "app/components/Nav"
112
- refuse_symlink_destination "app/components/Nav/App"
113
- if GIT_BACKUP_PATH="$(git -C "$TARGET" rev-parse --git-path recipe-harness/mobile/backup 2>/dev/null)"; then
114
- case "$GIT_BACKUP_PATH" in
115
- /*) BACKUP_DIR="$GIT_BACKUP_PATH" ;;
116
- *) BACKUP_DIR="$TARGET/$GIT_BACKUP_PATH" ;;
117
- esac
118
- else
119
- BACKUP_DIR="$HARNESS_DIR/backup-store"
120
- fi
121
- OLD_BACKUP_DIR="$HARNESS_DIR/backup"
122
- if [ "$BACKUP_DIR" != "$OLD_BACKUP_DIR" ] && [ -f "$OLD_BACKUP_DIR/state.env" ] && [ ! -e "$BACKUP_DIR" ]; then
123
- mkdir -p "$(dirname "$BACKUP_DIR")"
124
- mv "$OLD_BACKUP_DIR" "$BACKUP_DIR"
125
- fi
126
- STATE_FILE="$BACKUP_DIR/state.env"
127
-
128
- install_v1_runner_assets() {
129
- # refuse_symlink_destination walks every path component, so the deepest paths
130
- # also guard their parents ($HARNESS_REL covers the root segments).
131
- refuse_symlink_destination "$HARNESS_REL/runner"
132
- refuse_symlink_destination "$HARNESS_REL/scripts"
133
- refuse_symlink_destination "$HARNESS_REL/action-manifest.json"
134
- mkdir -p "$HARNESS_DIR"
135
- rm -rf "$HARNESS_DIR/runner" "$HARNESS_DIR/scripts"
136
- mkdir -p "$HARNESS_DIR/runner/bin" "$HARNESS_DIR/runner/manifests" "$HARNESS_DIR/runner/recipes"
137
-
138
- # Mobile Metro/Watchman observes the product checkout. Copying the full
139
- # TypeScript runner tree into the watched checkout while Metro is running can trigger large
140
- # file-map recrawls and make the React Native debug bridge unresponsive.
141
- # Keep the target overlay lightweight: install a harness-owned delegate plus
142
- # manifest/recipe snapshots, and execute the reviewed external runner source
143
- # recorded in manifest.json. This preserves the injection contract without
144
- # making the runner repo or the watched product checkout own the runtime.
145
- # Emit shell-safe lines: %q-quote the interpolated paths (like CLEANUP_COMMAND
146
- # below) so a protocol/runner path containing a space — or $()/backtick/
147
- # quote — cannot break the generated wrapper or inject at runtime.
148
- local runner_protocol_root_q runner_exec_q target_q
149
- runner_protocol_root_q="$(printf '%q' "$METAMASK_RUNNER_PROTOCOL_ROOT")"
150
- runner_exec_q="$(printf '%q' "$METAMASK_RUNNER_DIR/bin/mm-harness")"
151
- target_q="$(printf '%q' "$TARGET")"
152
- {
153
- printf '%s\n' '#!/usr/bin/env bash'
154
- printf '%s\n' 'set -euo pipefail'
155
- printf 'TARGET_DIR=%s\n' "$target_q"
156
- printf '%s\n' 'if [ -f "$TARGET_DIR/.js.env" ]; then'
157
- printf '%s\n' ' set +u'
158
- printf '%s\n' ' set -a'
159
- printf '%s\n' ' . "$TARGET_DIR/.js.env"'
160
- printf '%s\n' ' set +a'
161
- printf '%s\n' ' set -u'
162
- printf '%s\n' 'fi'
163
- printf '%s\n' 'detect_ios_simulator() {'
164
- printf '%s\n' ' command -v xcrun >/dev/null 2>&1 || return 0'
165
- printf '%s\n' ' node <<'"'"'NODE'"'"''
166
- printf '%s\n' "const cp = require('node:child_process');"
167
- printf '%s\n' 'let report;'
168
- printf '%s\n' "try { report = JSON.parse(cp.execFileSync('xcrun', ['simctl', 'list', 'devices', 'booted', '-j'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] })); } catch { process.exit(0); }"
169
- printf '%s\n' "const devices = Object.values(report.devices || {}).flat().filter((device) => device && device.state === 'Booted');"
170
- printf '%s\n' "const bundleIds = ['io.metamask', 'io.metamask.flask', 'io.metamask.qa'];"
171
- printf '%s\n' 'const withMetaMask = devices.filter((device) => {'
172
- printf '%s\n' ' try {'
173
- printf '%s\n' " const apps = cp.execFileSync('xcrun', ['simctl', 'listapps', device.udid], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'ignore'] });"
174
- printf '%s\n' ' return bundleIds.some((bundleId) => apps.includes(bundleId));'
175
- printf '%s\n' ' } catch { return false; }'
176
- printf '%s\n' '});'
177
- printf '%s\n' 'const candidates = withMetaMask.length > 0 ? withMetaMask : devices;'
178
- printf '%s\n' "if (candidates.length === 1) process.stdout.write(candidates[0].udid || candidates[0].name || '');"
179
- printf '%s\n' 'NODE'
180
- printf '%s\n' '}'
181
- printf '%s\n' 'if [ -z "${IOS_SIMULATOR:-}" ] && [ -z "${ANDROID_DEVICE:-}" ] && [ -z "${ADB_SERIAL:-}" ] && [ -z "${ANDROID_SERIAL:-}" ]; then'
182
- printf '%s\n' ' detected_simulator="$(detect_ios_simulator || true)"'
183
- printf '%s\n' ' if [ -n "$detected_simulator" ]; then'
184
- printf '%s\n' ' export IOS_SIMULATOR="$detected_simulator"'
185
- printf '%s\n' ' fi'
186
- printf '%s\n' 'fi'
187
- if [ -n "$METAMASK_RUNNER_PROTOCOL_ROOT" ]; then
188
- printf 'export FARMSLOT_ROOT=${FARMSLOT_ROOT:-%s}\n' "$runner_protocol_root_q"
189
- fi
190
- # Resolve the runner at run time (override > global install > install-time
191
- # record); the recorded path is last resort so a moved checkout is not pinned.
192
- printf '%s\n' 'if [ -n "${MM_HARNESS_BIN:-}" ] && [ -x "${MM_HARNESS_BIN}" ]; then exec "${MM_HARNESS_BIN}" "$@"; fi'
193
- printf '%s\n' 'if command -v mm-harness >/dev/null 2>&1; then exec mm-harness "$@"; fi'
194
- printf 'if [ -x %s ]; then exec %s "$@"; fi\n' "$runner_exec_q" "$runner_exec_q"
195
- printf '%s\n' 'echo "mm-harness not found. Next: npm i -g @deeeed/metamask-harness" >&2'
196
- printf '%s\n' 'exit 127'
197
- } > "$HARNESS_DIR/runner/bin/mm-harness"
198
- chmod +x "$HARNESS_DIR/runner/bin/mm-harness"
199
- if [ -n "$METAMASK_RUNNER_PROTOCOL_ROOT" ]; then
200
- printf '%s\n' "$METAMASK_RUNNER_PROTOCOL_ROOT" > "$HARNESS_DIR/runner/.farmslot-root"
201
- fi
202
- printf '%s\n' "$METAMASK_RUNNER_DIR" > "$HARNESS_DIR/runner/.runner-source"
203
- cp "$METAMASK_RUNNER_DIR/library/manifests/mobile.action-manifest.json" "$HARNESS_DIR/action-manifest.json"
204
- cp "$METAMASK_RUNNER_DIR/library/manifests/mobile.action-manifest.json" "$HARNESS_DIR/runner/manifests/mobile.action-manifest.json"
205
- cp "$METAMASK_RUNNER_DIR/library/manifests/extension.action-manifest.json" "$HARNESS_DIR/runner/manifests/extension.action-manifest.json"
206
- if [ -d "$METAMASK_RUNNER_DIR/library/recipes" ]; then
207
- rsync -a --delete "$METAMASK_RUNNER_DIR/library/recipes/" "$HARNESS_DIR/runner/recipes/"
208
- fi
209
- # Canonical recipe-library snapshot: flows/ + library.json so the installed
210
- # harness mirrors the runner's library layout.
211
- if [ -d "$METAMASK_RUNNER_DIR/library/flows" ]; then
212
- mkdir -p "$HARNESS_DIR/runner/flows"
213
- rsync -a --delete "$METAMASK_RUNNER_DIR/library/flows/" "$HARNESS_DIR/runner/flows/"
214
- fi
215
- if [ -f "$METAMASK_RUNNER_DIR/library/library.json" ]; then
216
- cp "$METAMASK_RUNNER_DIR/library/library.json" "$HARNESS_DIR/runner/library.json"
217
- fi
218
- # Installed-layout back-compat (INTENTIONAL): targets keep a flat
219
- # <harness>/scripts/ subtree assembled from the runner trees, so installed
220
- # consumers are unaffected by runner-repo layout changes.
221
- if [ -d "$METAMASK_RUNNER_DIR/adapters/mobile" ]; then
222
- mkdir -p "$HARNESS_DIR/scripts"
223
- # launch and live orchestration is now in-process (src/harness.ts + src/adapters/mobile/prepare.ts).
224
- # Remove any copies left by older installs so stale scripts cannot shadow the in-process path.
225
- rm -f "$HARNESS_DIR/scripts/launch.sh" "$HARNESS_DIR/scripts/live.sh"
226
- cp "$METAMASK_RUNNER_DIR/adapters/mobile/verify.sh" "$HARNESS_DIR/scripts/verify.sh"
227
- mkdir -p "$HARNESS_DIR/scripts/lib"
228
- cp "$METAMASK_RUNNER_DIR/adapters/shared/harness-path.sh" "$HARNESS_DIR/scripts/lib/harness-path.sh"
229
- cp "$METAMASK_RUNNER_DIR/adapters/shared/path-defaults.json" "$HARNESS_DIR/scripts/lib/path-defaults.json"
230
- chmod -R u+rwX,go+rX "$HARNESS_DIR/scripts"
231
- find "$HARNESS_DIR/scripts" -type f \( -name '*.sh' -o -name '*.cjs' -o -name '*.mjs' \) -exec chmod +x {} +
232
- fi
233
- if [ ! -x "$HARNESS_DIR/runner/bin/mm-harness" ]; then
234
- echo "Refusing mobile recipe harness install: failed to make runner executable." >&2
235
- return 1
236
- fi
237
- }
238
-
239
- git_tracks_any_under() {
240
- git -C "$TARGET" ls-files -- "$1" 2>/dev/null | grep -q .
241
- }
242
-
243
- has_product_owned_mobile_harness() {
244
- # If the product repo tracks any first-party harness subtree, installing the
245
- # runner overlay must be metadata-only by default. Requiring every marker to be
246
- # present is unsafe for partial Mobile checkouts: falling through would
247
- # rsync --delete tracked product-owned files without --force-overlay.
248
- git_tracks_any_under "app/dev-tools/AgenticService" && return 0
249
- # Marker fallback for checkouts where the harness was patched but not tracked.
250
- grep -q "AgenticService.install" "$TARGET/app/core/NavigationService/NavigationService.ts" 2>/dev/null \
251
- && grep -q "AgentStepHud" "$TARGET/app/components/Nav/App/App.tsx" 2>/dev/null
252
- }
253
-
254
- add_git_exclude_entry() {
255
- local entry="$1"
256
- local tracking_file="${2:-}"
257
- local git_dir
258
- local exclude_file
259
- if ! git_dir="$(git -C "$TARGET" rev-parse --git-dir 2>/dev/null)"; then
260
- return 0
261
- fi
262
- # Skip if the path is already gitignored (e.g. a temp/-rooted harness under an
263
- # existing temp/ rule) — no redundant info/exclude entry needed.
264
- if git -C "$TARGET" check-ignore -q "${entry%/}" 2>/dev/null; then
265
- return 0
266
- fi
267
- case "$git_dir" in
268
- /*) ;;
269
- *) git_dir="$TARGET/$git_dir" ;;
270
- esac
271
- exclude_file="$git_dir/info/exclude"
272
- mkdir -p "$(dirname "$exclude_file")"
273
- touch "$exclude_file"
274
- if ! grep -qxF "$entry" "$exclude_file"; then
275
- echo "$entry" >> "$exclude_file"
276
- if [ -n "$tracking_file" ]; then
277
- echo "$entry" >> "$tracking_file"
278
- fi
279
- fi
280
- }
281
-
282
- if [ "$FORCE_OVERLAY" = false ] && has_product_owned_mobile_harness; then
283
- install_v1_runner_assets
284
- SOURCE_REV="$(git -C "$RUNNER_DIR" rev-parse HEAD 2>/dev/null || echo unknown)"
285
- CLEANUP_COMMAND="RECIPE_HARNESS_ROOT=$HARNESS_ROOT $(printf '%q' "$CLEANUP_SH") --target $(printf '%q' "$TARGET")"
286
- if [ "$GIT_EXCLUDE" = true ]; then
287
- echo "[recipe-harness] Adding local .git/info/exclude entries (removed on cleanup): $HARNESS_ROOT/"
288
- add_git_exclude_entry "$HARNESS_ROOT/" "$HARNESS_DIR/added-git-exclude"
289
- else
290
- echo "[recipe-harness] --no-git-exclude: skipping .git/info/exclude updates; harness overlay paths may show as untracked in git status."
291
- fi
292
- node -e '
293
- const fs = require("fs");
294
- const [runnerOwnerDir, runnerOwnerRevision, runnerDir, runnerRevision, runnerSourceKind, adapterRuntime, target, scriptDir, manifestPath, harnessRoot, harnessRel, cleanupCommand, targetRevision] = process.argv.slice(1);
295
- const m = {
296
- adapter: "mobile",
297
- installMode: "product-owned",
298
- targetRevision,
299
- installedAt: new Date().toISOString(),
300
- source: {
301
- runnerOwnerDir,
302
- runnerOwnerRevision,
303
- runnerDir,
304
- runnerRevision,
305
- runnerSourceKind,
306
- adapterRuntime,
307
- },
308
- target,
309
- protocolVersion: "v1",
310
- actionManifestPath: harnessRel + "/action-manifest.json",
311
- runnerEntrypoint: harnessRel + "/runner/bin/mm-harness",
312
- runtimeHelpers: {
313
- verify: harnessRel + "/scripts/verify.sh"
314
- },
315
- installedPaths: [],
316
- harnessInstalledPaths: [harnessRel + "/runner", harnessRel + "/scripts", harnessRel + "/action-manifest.json"],
317
- patchedFiles: [],
318
- productOwnedPaths: [
319
- "app/dev-tools/AgenticService",
320
- "app/core/NavigationService/NavigationService.ts",
321
- "app/components/Nav/App/App.tsx"
322
- ],
323
- backupDir: null,
324
- cleanupCommand,
325
- productDiffExcludes: [
326
- ":(exclude)" + harnessRoot
327
- ],
328
- note: "This checkout already contains the Mobile in-app bridge. Runner install only writes recipe-harness metadata and must not overwrite tracked in-app bridge files."
329
- };
330
- fs.writeFileSync(manifestPath, JSON.stringify(m, null, 2) + "\n");
331
- ' "$RUNNER_DIR" "$SOURCE_REV" "$METAMASK_RUNNER_DIR" "$METAMASK_RUNNER_REVISION" "$METAMASK_RUNNER_SOURCE_KIND" "$ADAPTER_DIR" "$TARGET" "$SCRIPT_DIR" "$HARNESS_DIR/manifest.json" "$HARNESS_ROOT" "$HARNESS_REL" "$CLEANUP_COMMAND" "$TARGET_REV"
332
- echo "Installed mobile recipe harness metadata only (in-app bridge detected): $HARNESS_DIR/manifest.json"
333
- exit 0
334
- fi
335
-
336
- # Full-install path: reached when the checkout lacks the bridge, or when
337
- # --force-overlay was passed to deliberately overwrite a product-owned bridge.
338
- if [ "$FORCE_OVERLAY" = true ] && has_product_owned_mobile_harness; then
339
- echo "[recipe-harness] --force-overlay: OVERWRITING tracked in-app bridge files with the runner overlay:"
340
- echo " app/dev-tools/AgenticService, app/core/NavigationService/NavigationService.ts, app/components/Nav/App/App.tsx"
341
- echo "[recipe-harness] Files are backed up and restorable via cleanup. This replaces the checkout's in-app bridge/HUD."
342
- fi
343
- if [ "$GIT_EXCLUDE" = false ]; then
344
- echo "[recipe-harness] --no-git-exclude: skipping .git/info/exclude updates; harness overlay paths may show as untracked in git status."
345
- fi
346
-
347
- INSTALLED=false
348
- INSTALL_MUTATING=false
349
- ROLLBACK_BACKUP_DIR="$BACKUP_DIR"
350
- ROLLBACK_STATE_FILE="$STATE_FILE"
351
- REFRESH_BACKUP_DIR=""
352
- REBASE_BACKUP=false
353
-
354
- verify_installed_paths_unchanged() {
355
- local hash_file="$BACKUP_DIR/managed-hashes.tsv"
356
- if [ ! -f "$hash_file" ]; then
357
- return 0
358
- fi
359
- local rel expected actual conflicts=0 stale_clean=0
360
- while IFS=$'\t' read -r rel expected; do
361
- [ -n "$rel" ] || continue
362
- actual="$(hash_path "$rel")"
363
- if [ "$actual" != "$expected" ]; then
364
- if git -C "$TARGET" rev-parse --git-dir >/dev/null 2>&1 \
365
- && [ -z "$(git -C "$TARGET" status --porcelain -- "$rel")" ]; then
366
- echo "Mobile recipe harness managed path changed after install but is clean in git: $rel" >&2
367
- echo " expected: $expected" >&2
368
- echo " actual: $actual" >&2
369
- stale_clean=1
370
- else
371
- echo "Refusing to refresh mobile recipe harness: managed path changed after install: $rel" >&2
372
- echo " expected: $expected" >&2
373
- echo " actual: $actual" >&2
374
- conflicts=1
375
- fi
376
- fi
377
- done < "$hash_file"
378
- if [ "$conflicts" != "0" ]; then
379
- cat >&2 <<EOF
380
- Reinstall would bless local edits as harness-managed and make cleanup unsafe.
381
- Save/stash product changes or rerun with --allow-dirty-harness-paths if you intentionally want to overwrite and refresh harness-managed state.
382
- EOF
383
- exit 1
384
- fi
385
- if [ "$stale_clean" != "0" ]; then
386
- cat >&2 <<EOF
387
- Managed hashes are stale, but all changed harness-managed paths are clean in git.
388
- This usually means the checkout was reset/rebased after a previous harness install.
389
- Rebaselining the harness backup to the current clean checkout before refreshing.
390
- EOF
391
- REBASE_BACKUP=true
392
- fi
393
- }
394
-
395
- if [ -f "$HARNESS_DIR/manifest.json" ] && [ -f "$STATE_FILE" ]; then
396
- INSTALLED=true
397
- echo "Existing mobile recipe harness found; refreshing injected files from source." >&2
398
- fi
399
-
400
- if [ "$ALLOW_DIRTY" = false ] && [ "$INSTALLED" = true ]; then
401
- verify_installed_paths_unchanged
402
- fi
45
+ done
403
46
 
404
- if [ "$ALLOW_DIRTY" = false ] && [ "$INSTALLED" = false ] && git -C "$TARGET" rev-parse --git-dir >/dev/null 2>&1; then
405
- DIRTY_PATHS="$(git -C "$TARGET" status --porcelain -- app/dev-tools/AgenticService app/core/NavigationService/NavigationService.ts app/components/Nav/App/App.tsx)"
406
- if [ -n "$DIRTY_PATHS" ]; then
407
- cat >&2 <<EOF
408
- Refusing to install mobile recipe harness over dirty harness paths.
409
- Clean, stash, or rerun with --allow-dirty-harness-paths if you intentionally want backup/restore behavior.
47
+ rm -rf "$HARNESS_DIR/runner" "$HARNESS_DIR/scripts"
48
+ mkdir -p "$HARNESS_DIR/runner/bin" "$HARNESS_DIR/runner/manifests" "$HARNESS_DIR/runner/recipes" "$HARNESS_DIR/scripts/lib"
410
49
 
411
- $DIRTY_PATHS
412
- EOF
413
- exit 1
414
- fi
50
+ RUNNER_EXEC_Q="$(printf '%q' "$RUNNER_DIR/bin/mm-harness")"
51
+ TARGET_Q="$(printf '%q' "$TARGET")"
52
+ PROTOCOL_ROOT="${METAMASK_RUNNER_PROTOCOL_ROOT:-}"
53
+ if [ -z "$PROTOCOL_ROOT" ] && [ -f "$RUNNER_DIR/.farmslot-root" ]; then
54
+ PROTOCOL_ROOT="$(cat "$RUNNER_DIR/.farmslot-root")"
415
55
  fi
416
-
417
- mkdir -p "$HARNESS_DIR"
418
-
419
- backup_path() {
420
- local rel="$1"
421
- local var="$2"
422
- local target_path="$TARGET/$rel"
423
- local backup_path="$ACTIVE_BACKUP_DIR/$rel"
424
- if [ -e "$target_path" ]; then
425
- mkdir -p "$(dirname "$backup_path")"
426
- cp -a "$target_path" "$backup_path"
427
- printf '%s=1\n' "$var" >> "$ACTIVE_STATE_FILE"
428
- else
429
- printf '%s=0\n' "$var" >> "$ACTIVE_STATE_FILE"
430
- fi
431
- }
432
-
433
- rollback_path() {
434
- local rel="$1"
435
- local existed="$2"
436
- local target_path="$TARGET/$rel"
437
- local backup_path="$ROLLBACK_BACKUP_DIR/$rel"
438
- if [ "$existed" = "1" ]; then
439
- if [ -e "$backup_path" ]; then
440
- rm -rf "$target_path"
441
- mkdir -p "$(dirname "$target_path")"
442
- cp -a "$backup_path" "$target_path"
443
- else
444
- echo "Rollback warning: missing backup for $rel at $backup_path" >&2
445
- fi
446
- else
447
- rm -rf "$target_path"
448
- fi
449
- }
450
-
451
- rollback_git_exclude() {
452
- [ -f "$ROLLBACK_BACKUP_DIR/added-git-exclude" ] || return 0
453
- local git_dir exclude_file tmp_file entry
454
- git_dir="$(git -C "$TARGET" rev-parse --git-dir 2>/dev/null || true)"
455
- [ -n "$git_dir" ] || return 0
456
- case "$git_dir" in
457
- /*) ;;
458
- *) git_dir="$TARGET/$git_dir" ;;
459
- esac
56
+ PROTOCOL_ROOT_Q="$(printf '%q' "$PROTOCOL_ROOT")"
57
+ {
58
+ printf '%s\n' '#!/usr/bin/env bash' 'set -euo pipefail'
59
+ printf 'TARGET_DIR=%s\n' "$TARGET_Q"
60
+ printf '%s\n' 'if [ -f "$TARGET_DIR/.js.env" ]; then set +u; set -a; . "$TARGET_DIR/.js.env"; set +a; set -u; fi'
61
+ if [ -n "$PROTOCOL_ROOT" ]; then
62
+ printf 'export METAMASK_RUNNER_PROTOCOL_ROOT=${METAMASK_RUNNER_PROTOCOL_ROOT:-%s}\n' "$PROTOCOL_ROOT_Q"
63
+ fi
64
+ printf '%s\n' 'if [ -n "${MM_HARNESS_BIN:-}" ] && [ -x "$MM_HARNESS_BIN" ]; then exec "$MM_HARNESS_BIN" "$@"; fi'
65
+ printf '%s\n' 'if command -v mm-harness >/dev/null 2>&1; then exec mm-harness "$@"; fi'
66
+ printf 'if [ -x %s ]; then exec %s "$@"; fi\n' "$RUNNER_EXEC_Q" "$RUNNER_EXEC_Q"
67
+ printf '%s\n' 'echo "mm-harness not found. Next: npm i -g @deeeed/metamask-harness@latest" >&2' 'exit 127'
68
+ } > "$HARNESS_DIR/runner/bin/mm-harness"
69
+ chmod +x "$HARNESS_DIR/runner/bin/mm-harness"
70
+
71
+ [ -z "$PROTOCOL_ROOT" ] || printf '%s\n' "$PROTOCOL_ROOT" > "$HARNESS_DIR/runner/.farmslot-root"
72
+ printf '%s\n' "$RUNNER_DIR" > "$HARNESS_DIR/runner/.runner-source"
73
+ cp "$RUNNER_DIR/library/manifests/mobile.action-manifest.json" "$HARNESS_DIR/action-manifest.json"
74
+ cp "$RUNNER_DIR/library/manifests/mobile.action-manifest.json" "$HARNESS_DIR/runner/manifests/mobile.action-manifest.json"
75
+ cp "$RUNNER_DIR/library/manifests/extension.action-manifest.json" "$HARNESS_DIR/runner/manifests/extension.action-manifest.json"
76
+ rsync -a --delete "$RUNNER_DIR/library/recipes/" "$HARNESS_DIR/runner/recipes/"
77
+ cp "$RUNNER_DIR/library/library.json" "$HARNESS_DIR/runner/library.json"
78
+ mkdir -p "$HARNESS_DIR/runner/flows"
79
+ rsync -a --delete "$RUNNER_DIR/library/flows/" "$HARNESS_DIR/runner/flows/"
80
+ cp "$RUNNER_DIR/adapters/mobile/verify.sh" "$HARNESS_DIR/scripts/verify.sh"
81
+ cp "$RUNNER_DIR/adapters/shared/harness-path.sh" "$HARNESS_DIR/scripts/lib/harness-path.sh"
82
+ cp "$RUNNER_DIR/adapters/shared/path-defaults.json" "$HARNESS_DIR/scripts/lib/path-defaults.json"
83
+ chmod -R u+rwX,go+rX "$HARNESS_DIR"
84
+
85
+ add_git_exclude() {
86
+ local entry="$1" git_dir exclude_file
87
+ git -C "$TARGET" check-ignore -q "${entry%/}" 2>/dev/null && return 0
88
+ git_dir="$(git -C "$TARGET" rev-parse --git-dir)"
89
+ case "$git_dir" in /*) ;; *) git_dir="$TARGET/$git_dir" ;; esac
460
90
  exclude_file="$git_dir/info/exclude"
461
- [ -f "$exclude_file" ] || return 0
462
- tmp_file="$(mktemp)"
463
- cp "$exclude_file" "$tmp_file"
464
- while IFS= read -r entry; do
465
- [ -n "$entry" ] || continue
466
- grep -vxF "$entry" "$tmp_file" > "$tmp_file.next" || true
467
- mv "$tmp_file.next" "$tmp_file"
468
- done < "$ROLLBACK_BACKUP_DIR/added-git-exclude"
469
- mv "$tmp_file" "$exclude_file"
91
+ mkdir -p "$(dirname "$exclude_file")"; touch "$exclude_file"
92
+ grep -qxF "$entry" "$exclude_file" || { echo "$entry" >> "$exclude_file"; echo "$entry" >> "$HARNESS_DIR/added-git-exclude"; }
470
93
  }
471
-
472
- rollback_failed_install() {
473
- local code="${1:-1}"
474
- if [ "$INSTALL_MUTATING" != true ]; then
475
- exit "$code"
476
- fi
477
-
478
- set +e
479
- echo "Mobile recipe harness install failed; restoring backed-up product files." >&2
480
- if [ -f "$ROLLBACK_STATE_FILE" ]; then
481
- while IFS= read -r _line || [ -n "$_line" ]; do
482
- [[ "$_line" =~ ^[[:space:]]*(#|$) ]] && continue
483
- _key="${_line%%=*}"; _val="${_line#*=}"
484
- case "$_key" in
485
- AGENTIC_SERVICE_EXISTED|NAVIGATION_SERVICE_EXISTED|APP_TSX_EXISTED) ;;
486
- *) continue ;;
487
- esac
488
- export "$_key=$_val"
489
- done < "$ROLLBACK_STATE_FILE"
490
- unset _line _key _val
491
- rollback_path "app/dev-tools/AgenticService" "${AGENTIC_SERVICE_EXISTED:-0}"
492
- rollback_path "app/core/NavigationService/NavigationService.ts" "${NAVIGATION_SERVICE_EXISTED:-0}"
493
- rollback_path "app/components/Nav/App/App.tsx" "${APP_TSX_EXISTED:-0}"
494
- rollback_git_exclude
495
- else
496
- echo "Rollback warning: no backup state found at $ROLLBACK_STATE_FILE" >&2
497
- fi
498
- if [ "$INSTALLED" = true ]; then
499
- [ -n "$REFRESH_BACKUP_DIR" ] && rm -rf "$REFRESH_BACKUP_DIR"
500
- else
501
- rm -rf "$HARNESS_DIR"
502
- rm -rf "$BACKUP_DIR"
503
- fi
504
- exit "$code"
505
- }
506
-
507
- trap 'rollback_failed_install $?' ERR
508
-
509
- if [ ! -f "$STATE_FILE" ] || [ "$REBASE_BACKUP" = true ]; then
510
- TMP_BACKUP_DIR="$(mktemp -d "$HARNESS_DIR/backup.tmp.XXXXXX")"
511
- ACTIVE_BACKUP_DIR="$TMP_BACKUP_DIR"
512
- ACTIVE_STATE_FILE="$TMP_BACKUP_DIR/state.env"
513
- : > "$ACTIVE_STATE_FILE"
514
- backup_path "app/dev-tools/AgenticService" "AGENTIC_SERVICE_EXISTED"
515
- backup_path "app/core/NavigationService/NavigationService.ts" "NAVIGATION_SERVICE_EXISTED"
516
- backup_path "app/components/Nav/App/App.tsx" "APP_TSX_EXISTED"
517
- if [ "$REBASE_BACKUP" = true ] && [ -e "$BACKUP_DIR" ]; then
518
- ARCHIVE_DIR="$HARNESS_DIR/backup-stale.$(date -u +%Y%m%dT%H%M%SZ)"
519
- mkdir -p "$(dirname "$ARCHIVE_DIR")"
520
- mv "$BACKUP_DIR" "$ARCHIVE_DIR"
521
- echo "Archived stale mobile harness backup: $ARCHIVE_DIR" >&2
522
- else
523
- rm -rf "$BACKUP_DIR"
524
- fi
525
- mkdir -p "$(dirname "$BACKUP_DIR")"
526
- mv "$TMP_BACKUP_DIR" "$BACKUP_DIR"
527
- STATE_FILE="$BACKUP_DIR/state.env"
528
- ROLLBACK_BACKUP_DIR="$BACKUP_DIR"
529
- ROLLBACK_STATE_FILE="$STATE_FILE"
530
- else
531
- REFRESH_BACKUP_DIR="$(mktemp -d "$HARNESS_DIR/refresh-backup.tmp.XXXXXX")"
532
- ACTIVE_BACKUP_DIR="$REFRESH_BACKUP_DIR"
533
- ACTIVE_STATE_FILE="$REFRESH_BACKUP_DIR/state.env"
534
- : > "$ACTIVE_STATE_FILE"
535
- backup_path "app/dev-tools/AgenticService" "AGENTIC_SERVICE_EXISTED"
536
- backup_path "app/core/NavigationService/NavigationService.ts" "NAVIGATION_SERVICE_EXISTED"
537
- backup_path "app/components/Nav/App/App.tsx" "APP_TSX_EXISTED"
538
- ROLLBACK_BACKUP_DIR="$REFRESH_BACKUP_DIR"
539
- ROLLBACK_STATE_FILE="$REFRESH_BACKUP_DIR/state.env"
540
- fi
541
-
542
- INSTALL_MUTATING=true
543
-
544
- install_v1_runner_assets
545
-
546
- mkdir -p "$TARGET/app/dev-tools/AgenticService"
547
- while IFS= read -r -d '' overlay_file; do
548
- rel="${overlay_file#$ADAPTER_DIR/overlay/app/dev-tools/AgenticService/}"
549
- dest_rel="${rel%.patch}"
550
- dest="$TARGET/app/dev-tools/AgenticService/$dest_rel"
551
- mkdir -p "$(dirname "$dest")"
552
- # Skip when the checkout already has identical content (e.g. the committed
553
- # in-app seam) so install stays a no-op and produces no spurious diff.
554
- cmp -s "$overlay_file" "$dest" || cp "$overlay_file" "$dest"
555
- # Exclude overlay *.test.* files: they are source tests for the overlay
556
- # templates and must not ship into the product checkout, where the product's
557
- # jest could pick them up on local runs.
558
- done < <(find "$ADAPTER_DIR/overlay/app/dev-tools/AgenticService" -type f ! -name '*.test.*' -print0)
559
-
560
- node - "$TARGET" <<'NODE'
561
- const fs = require('fs');
562
- const path = require('path');
563
-
564
- const target = process.argv[2];
565
-
566
- function patchNavigation() {
567
- const file = path.join(target, 'app/core/NavigationService/NavigationService.ts');
568
- if (!fs.existsSync(file)) throw new Error(`missing ${file}`);
569
- const original = fs.readFileSync(file, 'utf8');
570
- let src = original;
571
- const devToolsImport = "import('../../dev-tools/AgenticService/AgenticService')";
572
- if (src.includes('AgenticService.install')) {
573
- const upgraded = src
574
- .replaceAll("import('../AgenticService/AgenticService')", devToolsImport)
575
- .replaceAll("import('../../core/AgenticService/AgenticService')", devToolsImport);
576
- if (upgraded !== src) {
577
- fs.writeFileSync(file, upgraded);
578
- return 'upgraded-import';
579
- }
580
- return 'already-present';
581
- }
582
- const marker = ' this.#navigation = this.#createReactAwareNavigation(navRef);\n';
583
- const insert = `${marker}\n if (__DEV__) {\n import('../../dev-tools/AgenticService/AgenticService').then(\n ({ default: AgenticService }) => {\n AgenticService.install(navRef, this.#navigation);\n },\n );\n }\n`;
584
- if (!src.includes(marker)) {
585
- throw new Error(`cannot patch NavigationService.ts: marker not found`);
586
- }
587
- src = src.replace(marker, insert);
588
- if (src === original) return 'already-present';
589
- fs.writeFileSync(file, src);
590
- return 'patched';
591
- }
592
-
593
- function patchApp() {
594
- const file = path.join(target, 'app/components/Nav/App/App.tsx');
595
- if (!fs.existsSync(file)) throw new Error(`missing ${file}`);
596
- const original = fs.readFileSync(file, 'utf8');
597
- let src = original;
598
- let importStatus = 'already-present';
599
- if (src.includes('core/AgenticService/AgentStepHud') && !src.includes('dev-tools/AgenticService/AgentStepHud')) {
600
- src = src.replaceAll("../../../core/AgenticService/AgentStepHud", "../../../dev-tools/AgenticService/AgentStepHud");
601
- importStatus = 'upgraded-import';
602
- }
603
- if (!src.includes('dev-tools/AgenticService/AgentStepHud')) {
604
- const marker = "import PerpsWebSocketHealthToast";
605
- const line = "import AgentStepHud from '../../../dev-tools/AgenticService/AgentStepHud';\n";
606
- if (src.includes(marker)) {
607
- src = src.replace(marker, `${line}${marker}`);
608
- } else {
609
- throw new Error(`cannot patch App.tsx: import marker not found`);
610
- }
611
- importStatus = 'patched';
612
- }
613
- let renderStatus = 'already-present';
614
- if (!src.includes('<AgentStepHud')) {
615
- const marker = /^(\s*)<ControllerEventToastBridge\b[^\n]*\/>/m;
616
- const match = src.match(marker);
617
- if (!match) {
618
- throw new Error(`cannot patch App.tsx: render marker not found`);
619
- }
620
- src = src.replace(marker, `${match[1]}{__DEV__ && <AgentStepHud />}\n${match[0]}`);
621
- renderStatus = 'patched';
622
- }
623
- if (src !== original) fs.writeFileSync(file, src);
624
- return `${importStatus},${renderStatus}`;
625
- }
626
-
627
- console.log(JSON.stringify({
628
- navigation: patchNavigation(),
629
- app: patchApp(),
630
- }));
631
- NODE
632
-
633
94
  if [ "$GIT_EXCLUDE" = true ]; then
634
- echo "[recipe-harness] Adding local .git/info/exclude entries (removed on cleanup): $HARNESS_ROOT/, app/dev-tools/AgenticService/"
635
- add_git_exclude_entry "$HARNESS_ROOT/" "$BACKUP_DIR/added-git-exclude"
636
- add_git_exclude_entry "app/dev-tools/AgenticService/" "$BACKUP_DIR/added-git-exclude"
95
+ add_git_exclude "$HARNESS_ROOT/"
637
96
  fi
638
97
 
639
- write_managed_hashes() {
640
- local hash_file="$BACKUP_DIR/managed-hashes.tsv"
641
- local rel
642
- : > "$hash_file"
643
- for rel in \
644
- "app/dev-tools/AgenticService" \
645
- "app/core/NavigationService/NavigationService.ts" \
646
- "app/components/Nav/App/App.tsx"; do
647
- printf '%s\t%s\n' "$rel" "$(hash_path "$rel")" >> "$hash_file"
648
- done
649
- }
650
-
651
- write_managed_hashes
652
- INSTALL_MUTATING=false
653
- trap - ERR
654
- [ -n "$REFRESH_BACKUP_DIR" ] && rm -rf "$REFRESH_BACKUP_DIR"
655
-
656
- SOURCE_REV="$(git -C "$RUNNER_DIR" rev-parse HEAD 2>/dev/null || echo unknown)"
657
- CLEANUP_COMMAND="RECIPE_HARNESS_ROOT=$HARNESS_ROOT $(printf '%q' "$CLEANUP_SH") --target $(printf '%q' "$TARGET")"
658
- node -e '
659
- const fs = require("fs");
660
- const [runnerOwnerDir, runnerOwnerRevision, runnerDir, runnerRevision, runnerSourceKind, adapterRuntime, target, backupDir, scriptDir, manifestPath, mobileAgenticSource, harnessRoot, harnessRel, cleanupCommand, targetRevision] = process.argv.slice(1);
661
- const m = {
662
- adapter: "mobile",
663
- installMode: "overlay",
664
- targetRevision,
665
- installedAt: new Date().toISOString(),
666
- source: {
667
- runnerOwnerDir,
668
- runnerOwnerRevision,
669
- runnerDir,
670
- runnerRevision,
671
- runnerSourceKind,
672
- adapterRuntime,
673
- },
674
- target,
675
- protocolVersion: "v1",
676
- actionManifestPath: harnessRel + "/action-manifest.json",
677
- runnerEntrypoint: harnessRel + "/runner/bin/mm-harness",
678
- // launch/live orchestration is in-process (mm-harness launch); only verify
679
- // remains a shell helper. install_v1_runner_assets deletes scripts/launch.sh
680
- // and scripts/live.sh, so advertising them here would be a manifest that
681
- // points at files this same install removed. Keep this in sync with the
682
- // product-owned block above (verify only).
683
- runtimeHelpers: {
684
- verify: harnessRel + "/scripts/verify.sh"
685
- },
686
- installedPaths: [harnessRel + "/runner", harnessRel + "/scripts", harnessRel + "/action-manifest.json", "app/dev-tools/AgenticService"],
687
- patchedFiles: ["app/core/NavigationService/NavigationService.ts", "app/components/Nav/App/App.tsx"],
688
- backupDir,
689
- managedHashes: backupDir + "/managed-hashes.tsv",
690
- cleanupCommand,
691
- productDiffExcludes: [
692
- ":(exclude)" + harnessRoot,
693
- ":(exclude)app/dev-tools/AgenticService"
694
- ]
695
- };
696
- fs.writeFileSync(manifestPath, JSON.stringify(m, null, 2) + "\n");
697
- ' "$RUNNER_DIR" "$SOURCE_REV" "$METAMASK_RUNNER_DIR" "$METAMASK_RUNNER_REVISION" "$METAMASK_RUNNER_SOURCE_KIND" "$ADAPTER_DIR" "$TARGET" "$BACKUP_DIR" "$SCRIPT_DIR" "$HARNESS_DIR/manifest.json" "${MOBILE_AGENTIC_SOURCE:-}" "$HARNESS_ROOT" "$HARNESS_REL" "$CLEANUP_COMMAND" "$TARGET_REV"
698
-
699
- ENSURE_DEPS="$SCRIPT_DIR/../shared/ensure-runner-deps.sh"
700
- if [ -f "$ENSURE_DEPS" ]; then
701
- bash "$ENSURE_DEPS" "$RUNNER_DIR"
702
- fi
98
+ RUNNER_REV="$(git -C "$RUNNER_DIR" rev-parse HEAD 2>/dev/null || echo unknown)"
99
+ CLEANUP_Q="$(printf '%q' "$SCRIPT_DIR/cleanup.sh")"
100
+ TARGET_CLEANUP_Q="$(printf '%q' "$TARGET")"
101
+ node - "$HARNESS_DIR/manifest.json" <<NODE
102
+ const fs = require('node:fs');
103
+ fs.writeFileSync(process.argv[2], JSON.stringify({
104
+ adapter: 'mobile',
105
+ installMode: 'product-owned',
106
+ targetRevision: '$TARGET_REV',
107
+ installedAt: new Date().toISOString(),
108
+ source: { runnerDir: '$RUNNER_DIR', runnerRevision: '$RUNNER_REV' },
109
+ target: '$TARGET',
110
+ protocolVersion: 'v1',
111
+ actionManifestPath: '$HARNESS_REL/action-manifest.json',
112
+ runnerEntrypoint: '$HARNESS_REL/runner/bin/mm-harness',
113
+ runtimeHelpers: { verify: '$HARNESS_REL/scripts/verify.sh' },
114
+ harnessInstalledPaths: ['$HARNESS_REL/runner', '$HARNESS_REL/scripts', '$HARNESS_REL/action-manifest.json'],
115
+ productOwnedPaths: ['app/dev-tools/AgenticService', 'app/core/NavigationService/NavigationService.ts'],
116
+ cleanupCommand: 'RECIPE_HARNESS_ROOT=$HARNESS_ROOT $CLEANUP_Q --target $TARGET_CLEANUP_Q',
117
+ productDiffExcludes: [':(exclude)$HARNESS_ROOT'],
118
+ }, null, 2) + '\n');
119
+ NODE
703
120
 
704
- echo "Installed mobile recipe harness: $HARNESS_DIR/manifest.json"
121
+ echo "Installed mobile harness metadata: $HARNESS_DIR/manifest.json"