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