@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.
- package/CHANGELOG.md +34 -0
- package/README.md +90 -140
- package/adapters/core/cleanup.sh +0 -0
- package/adapters/core/inject.sh +0 -0
- package/adapters/extension/cleanup.mjs +0 -0
- package/adapters/extension/console-tail.mjs +51 -24
- package/adapters/extension/ensure-browser.sh +0 -0
- package/adapters/extension/inject.mjs +5 -9
- package/adapters/extension/launch-browser.cjs +0 -0
- package/adapters/extension/launch-webpack.cjs +83 -0
- package/adapters/extension/launch.sh +8 -11
- package/adapters/extension/live.sh +2 -0
- package/adapters/extension/readiness.mjs +0 -0
- package/adapters/extension/reattach.sh +1 -0
- package/adapters/extension/refresh-build.sh +0 -0
- package/adapters/extension/seed-fixture.sh +4 -12
- package/adapters/extension/sidepanel-toggle.sh +4 -1
- package/adapters/extension/snapshot-dist.sh +0 -0
- package/adapters/extension/stamp-runtime-title.cjs +49 -0
- package/adapters/extension/start-watch.sh +16 -4
- package/adapters/extension/stop-viewers.sh +1 -1
- package/adapters/extension/sync-webpack-dist.cjs +105 -0
- package/adapters/extension/verify.sh +1 -1
- package/adapters/extension/wallet-fixture-state.cjs +0 -0
- package/adapters/manifest.json +46 -38
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +0 -0
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
- package/adapters/mobile/bridge-runtime/setup-wallet.sh +0 -0
- package/adapters/mobile/cleanup.sh +16 -176
- package/adapters/mobile/inject.sh +88 -671
- package/adapters/mobile/launch-console-forwarder.cjs +39 -0
- package/adapters/mobile/lib/metro-listener.sh +0 -0
- package/adapters/mobile/lib/tmux-viewer.sh +0 -0
- package/adapters/mobile/open-device.sh +0 -0
- package/adapters/mobile/prewarm-bundle.sh +0 -0
- package/adapters/mobile/start-console-forwarder.sh +70 -0
- package/adapters/mobile/start-metro.sh +0 -41
- package/adapters/mobile/stop-metro.sh +0 -0
- package/adapters/mobile/verify.sh +4 -61
- package/adapters/mobile/wait-for-bridge.sh +0 -0
- package/adapters/mobile/yarn-setup.sh +0 -0
- package/adapters/shared/activate-repo-node.sh +0 -0
- package/adapters/shared/activate-repo-ruby.sh +0 -0
- package/adapters/shared/cli-ux.sh +0 -0
- package/adapters/shared/ensure-runner-deps.sh +0 -0
- package/adapters/shared/harness-path.sh +0 -0
- package/adapters/shared/hash-helpers.sh +0 -0
- package/adapters/shared/install-repo-deps.sh +0 -0
- package/adapters/shared/json-field.sh +0 -0
- package/adapters/shared/open-debug.mjs +90 -36
- package/adapters/shared/open-log-window.sh +0 -0
- package/adapters/shared/reap-checkout-metros.sh +0 -0
- package/adapters/shared/resolve-slot-ports-core.mjs +0 -6
- package/adapters/shared/resolve-slot-ports.mjs +0 -0
- package/adapters/shared/resolve-slot-ports.sh +0 -5
- package/adapters/shared/sync-wallet-fixture.sh +0 -0
- package/adapters/shared/tmux-session.sh +0 -0
- package/adapters/shared/tmux-viewer.sh +0 -0
- package/adapters/shared/update-check-worker.mjs +37 -0
- package/dist/adapters/extension/console-capture.js +84 -0
- package/dist/adapters/extension/runtime-decision.js +14 -2
- package/dist/adapters/extension/surface.js +3 -2
- package/dist/adapters/mobile/prepare.js +22 -7
- package/dist/adapters/mobile/surface.js +1 -1
- package/dist/adapters/resolve-slot-ports.js +0 -2
- package/dist/adapters/slot-ports.js +8 -0
- package/dist/cli.js +3 -11
- package/dist/commands/call.js +9 -4
- package/dist/commands/debug.js +38 -31
- package/dist/commands/fixtures.js +18 -10
- package/dist/commands/launch/index.js +10 -0
- package/dist/commands/logs.js +8 -6
- package/dist/commands/manifest.js +5 -17
- package/dist/commands/run-engine.js +1 -32
- package/dist/commands/run-report.js +1 -1
- package/dist/commands/run.js +50 -3
- package/dist/commands/status-probe.js +1 -1
- package/dist/commands/update.js +15 -28
- package/dist/doctor.js +1 -4
- package/dist/harness.js +5 -123
- package/dist/live-adapter-contract.js +6 -20
- package/dist/mm-harness-cli.js +6 -43
- package/dist/paths.js +2 -5
- package/dist/run-diagnostics.js +35 -40
- package/dist/runtime-context.js +26 -8
- package/docs/CHEATSHEET.md +39 -52
- package/docs/CLI-ERGONOMICS-HUMAN-QA.md +48 -104
- package/docs/CODE-MAP.md +1 -1
- package/docs/VIDEO-DEMO-VALIDATION.md +62 -0
- package/docs/architecture.md +65 -375
- package/docs/live-adapter-contract.md +3 -1
- package/library/actions/core/perps/_controller.mjs +10 -55
- package/library/manifests/mobile.action-manifest.json +1 -1
- package/package.json +2 -2
- package/scripts/completions.sh +0 -0
- package/scripts/install-completions.sh +0 -0
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -185
- package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -1662
- package/adapters/mobile/overlay/compat/README.md +0 -28
- package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +0 -42
- package/adapters/shared/resolve-farmslot-ports-core.mjs +0 -3
- package/adapters/shared/resolve-farmslot-ports.mjs +0 -5
- package/adapters/shared/resolve-farmslot-ports.sh +0 -7
- package/docs/CLI-SPEC.md +0 -1142
- package/docs/MENTAL-MODEL.md +0 -295
|
@@ -1,704 +1,121 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
#
|
|
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:
|
|
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 "$
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
73
|
-
echo "Next:
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
84
|
-
CLEANUP_SH="$SCRIPT_DIR/cleanup.sh"
|
|
33
|
+
|
|
85
34
|
HARNESS_ROOT="$(harness_root)"
|
|
86
35
|
HARNESS_REL="$HARNESS_ROOT/mobile"
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
95
|
-
|
|
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
|
-
|
|
405
|
-
|
|
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
|
-
$
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
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
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
if [ -
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
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
|
-
|
|
462
|
-
|
|
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
|
-
|
|
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
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
}
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
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
|
|
121
|
+
echo "Installed mobile harness metadata: $HARNESS_DIR/manifest.json"
|