@deeeed/metamask-harness 0.17.5 → 0.18.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 +872 -0
- package/README.md +94 -140
- package/adapters/extension/console-tail.mjs +55 -24
- package/adapters/extension/ensure-browser.sh +6 -1
- package/adapters/extension/inject.mjs +6 -9
- package/adapters/extension/launch-browser.cjs +7 -1
- package/adapters/extension/launch-webpack.cjs +83 -0
- package/adapters/extension/launch.sh +8 -11
- package/adapters/extension/lib/chrome-args.cjs +8 -1
- package/adapters/extension/lib/macos-focus.cjs +32 -0
- package/adapters/extension/live.sh +12 -21
- package/adapters/extension/reattach.sh +1 -0
- package/adapters/extension/seed-fixture.sh +4 -12
- package/adapters/extension/sidepanel-toggle.sh +4 -1
- package/adapters/extension/stamp-runtime-title.cjs +58 -0
- package/adapters/extension/start-watch.sh +17 -4
- package/adapters/extension/stop-viewers.sh +1 -1
- package/adapters/extension/sync-webpack-dist.cjs +107 -0
- package/adapters/extension/verify.sh +1 -1
- package/adapters/manifest.json +53 -37
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +16 -0
- package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
- package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +3 -1
- package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
- package/adapters/mobile/cleanup.sh +16 -176
- package/adapters/mobile/inject.sh +90 -671
- package/adapters/mobile/launch-console-forwarder.cjs +39 -0
- package/adapters/mobile/open-device.sh +45 -7
- package/adapters/mobile/start-console-forwarder.sh +70 -0
- package/adapters/mobile/start-metro.sh +0 -41
- package/adapters/mobile/verify.sh +19 -64
- package/adapters/mobile/wait-for-bridge.sh +22 -8
- package/adapters/shared/resolve-slot-ports-core.mjs +17 -6
- package/adapters/shared/resolve-slot-ports.sh +17 -5
- package/adapters/shared/update-check-worker.mjs +43 -0
- package/bin/mm-harness +21 -2
- package/dist/adapters/core/surface.js +4 -1
- package/dist/adapters/extension/console-capture.js +95 -0
- package/dist/adapters/extension/product-config.js +110 -0
- package/dist/adapters/extension/runtime-decision.js +20 -2
- package/dist/adapters/extension/surface.js +22 -3
- package/dist/adapters/mobile/perps-env.js +43 -3
- package/dist/adapters/mobile/prepare.js +39 -7
- package/dist/adapters/mobile/surface.js +5 -2
- package/dist/adapters/resolve-slot-ports.js +2 -2
- package/dist/adapters/slot-ports.js +13 -16
- package/dist/adapters.js +56 -16
- package/dist/checkout-lock.js +27 -2
- package/dist/cli-color.js +19 -0
- package/dist/cli-commands.js +1 -1
- package/dist/cli.js +7 -14
- package/dist/command-contract.js +451 -0
- package/dist/command-journal.js +225 -0
- package/dist/commands/call.js +170 -50
- package/dist/commands/check.js +9 -3
- package/dist/commands/completion-candidates.js +20 -13
- package/dist/commands/device-target.js +27 -12
- package/dist/commands/doctor.js +106 -25
- package/dist/commands/fixtures.js +92 -34
- package/dist/commands/flows.js +39 -10
- package/dist/commands/last.js +52 -0
- package/dist/commands/launch/extension.js +38 -15
- package/dist/commands/launch/index.js +207 -63
- package/dist/commands/list-executables.js +151 -29
- package/dist/commands/logs.js +8 -6
- package/dist/commands/manifest.js +270 -35
- package/dist/commands/parse-args.js +13 -1
- package/dist/commands/provision.js +10 -3
- package/dist/commands/run-engine.js +435 -105
- package/dist/commands/run-report.js +12 -3
- package/dist/commands/run.js +355 -50
- package/dist/commands/shared.js +75 -3
- package/dist/commands/status-probe.js +4 -1
- package/dist/commands/status.js +2 -1
- package/dist/commands/stop.js +7 -2
- package/dist/commands/update.js +16 -28
- package/dist/completions-cache.js +1 -1
- package/dist/doctor.js +57 -10
- package/dist/harness.js +47 -137
- package/dist/heal-bounds.js +2 -2
- package/dist/json-stream.js +57 -0
- package/dist/live-adapter-contract.js +138 -32
- package/dist/manifest.js +161 -1
- package/dist/mm-harness-cli.js +135 -54
- package/dist/paths.js +2 -5
- package/dist/recipe-security.js +178 -0
- package/dist/run-diagnostics.js +261 -0
- package/dist/runner.js +117 -8
- package/docs/CONTRIBUTING.md +137 -0
- package/docs/QA.md +185 -0
- package/docs/RECIPES.md +161 -0
- package/docs/SECURITY.md +88 -0
- package/library/README.md +4 -0
- package/library/actions/core/perps/_controller.mjs +10 -55
- package/library/actions/core/perps/read_account.mjs +2 -2
- package/library/actions/core/perps/read_orders.mjs +2 -1
- package/library/actions/core/perps/read_positions.mjs +2 -1
- package/library/actions/core/wallet/list_accounts.mjs +95 -0
- package/library/actions/extension/platform/cdp.mjs +1 -0
- package/library/actions/extension/wallet/list_accounts.mjs +41 -0
- package/library/actions/mobile/wallet/list_accounts.mjs +37 -0
- package/library/manifests/core.action-manifest.json +68 -7
- package/library/manifests/extension.action-manifest.json +53 -0
- package/library/manifests/mobile.action-manifest.json +81 -3
- package/library/recipes/runner/action-validation.extension.recipe.json +8 -1
- package/library/recipes/runner/action-validation.mobile.recipe.json +8 -1
- package/library/recipes/runner/smoke.core.recipe.json +27 -0
- package/library/recipes/wallet/smoke.extension.recipe.json +42 -0
- package/library/recipes/wallet/smoke.mobile.recipe.json +42 -0
- package/package.json +12 -5
- package/scripts/completions.sh +7 -7
- package/scripts/validate-human-outcomes.mjs +169 -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/ADAPTER-SURFACE.md +0 -119
- package/docs/CHEATSHEET.md +0 -61
- package/docs/CLI-SPEC.md +0 -1098
- package/docs/CODE-MAP.md +0 -62
- package/docs/DEBUG-HANDOVER.md +0 -36
- package/docs/MENTAL-MODEL.md +0 -295
- package/docs/UX-PRINCIPLES.md +0 -64
- package/docs/architecture.md +0 -398
- package/docs/live-adapter-contract.md +0 -188
- package/docs/package-boundaries.md +0 -47
- package/docs/perps-flow-catalog.md +0 -235
- package/docs/recipe-libraries.md +0 -301
- package/docs/runtime-file-conventions.md +0 -36
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { spawn } = require('node:child_process');
|
|
7
|
+
|
|
8
|
+
const args = { port: '', out: '', err: '' };
|
|
9
|
+
for (let i = 2; i < process.argv.length; i += 1) {
|
|
10
|
+
const key = process.argv[i];
|
|
11
|
+
if (key === '--port') args.port = process.argv[++i] || '';
|
|
12
|
+
else if (key === '--out') args.out = process.argv[++i] || '';
|
|
13
|
+
else if (key === '--err') args.err = process.argv[++i] || '';
|
|
14
|
+
else if (key === '--help' || key === '-h') {
|
|
15
|
+
process.stdout.write('Usage: launch-console-forwarder.cjs --port <port> --out <app-log> --err <error-log>\n');
|
|
16
|
+
process.exit(0);
|
|
17
|
+
} else {
|
|
18
|
+
process.stderr.write(`launch-console-forwarder: unknown arg: ${key}\n`);
|
|
19
|
+
process.exit(2);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!args.port || !args.out || !args.err) {
|
|
24
|
+
process.stderr.write('launch-console-forwarder: --port, --out, and --err are required\n');
|
|
25
|
+
process.exit(2);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const forwarder = path.join(__dirname, 'bridge-runtime', 'console-forwarder.cjs');
|
|
29
|
+
const errFd = fs.openSync(args.err, 'a');
|
|
30
|
+
try {
|
|
31
|
+
const child = spawn(process.execPath, [forwarder, '--port', args.port, '--out', args.out], {
|
|
32
|
+
detached: true,
|
|
33
|
+
stdio: ['ignore', errFd, errFd],
|
|
34
|
+
});
|
|
35
|
+
child.unref();
|
|
36
|
+
process.stdout.write(`${child.pid}\n`);
|
|
37
|
+
} finally {
|
|
38
|
+
fs.closeSync(errFd);
|
|
39
|
+
}
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
# --simulator <udid|name|"booted"> iOS simulator (default: IOS_SIMULATOR env, else "booted")
|
|
14
14
|
# --adb-serial <serial> Android device serial (default: ADB_SERIAL env)
|
|
15
15
|
# --preflight-mode fast|auto|rebuild-native|clean (default: fast; env MOBILE_PREFLIGHT_MODE)
|
|
16
|
+
# --ui-only iOS only: ensure Simulator.app is visible; do not relaunch the app
|
|
16
17
|
# Outputs:
|
|
17
18
|
# Progress on stderr; exit 0 pass; 1 open failed; 2 bad args.
|
|
18
19
|
#
|
|
@@ -25,6 +26,7 @@ SIMULATOR="${IOS_SIMULATOR:-${SIM_UDID:-booted}}"
|
|
|
25
26
|
ADB_SERIAL_ARG="${ADB_SERIAL:-${ANDROID_SERIAL:-}}"
|
|
26
27
|
PREFLIGHT_MODE="${MOBILE_PREFLIGHT_MODE:-fast}"
|
|
27
28
|
PORT="${WATCHER_PORT:-${METRO_PORT:-8081}}"
|
|
29
|
+
UI_ONLY=false
|
|
28
30
|
|
|
29
31
|
while [ "$#" -gt 0 ]; do
|
|
30
32
|
case "$1" in
|
|
@@ -34,10 +36,11 @@ while [ "$#" -gt 0 ]; do
|
|
|
34
36
|
--adb-serial) ADB_SERIAL_ARG="$2"; shift 2 ;;
|
|
35
37
|
--preflight-mode) PREFLIGHT_MODE="$2"; shift 2 ;;
|
|
36
38
|
--port) PORT="$2"; shift 2 ;;
|
|
39
|
+
--ui-only) UI_ONLY=true; shift ;;
|
|
37
40
|
-h|--help)
|
|
38
41
|
printf 'Usage: open-device.sh [--platform ios|android] [--target <dir>]\n'
|
|
39
42
|
printf ' [--simulator <udid|name|booted>] [--adb-serial <serial>]\n'
|
|
40
|
-
printf ' [--preflight-mode fast|auto|rebuild-native|clean] [--port <n>]\n'
|
|
43
|
+
printf ' [--preflight-mode fast|auto|rebuild-native|clean] [--port <n>] [--ui-only]\n'
|
|
41
44
|
exit 0
|
|
42
45
|
;;
|
|
43
46
|
*) printf 'open-device: unknown arg: %s\n' "$1" >&2; exit 2 ;;
|
|
@@ -48,6 +51,10 @@ case "$PLATFORM" in
|
|
|
48
51
|
ios|android) ;;
|
|
49
52
|
*) printf 'open-device: --platform must be ios or android (got: %s)\n' "$PLATFORM" >&2; exit 2 ;;
|
|
50
53
|
esac
|
|
54
|
+
if $UI_ONLY && [ "$PLATFORM" != "ios" ]; then
|
|
55
|
+
printf 'open-device: --ui-only is supported only for ios.\n' >&2
|
|
56
|
+
exit 2
|
|
57
|
+
fi
|
|
51
58
|
|
|
52
59
|
TARGET="$(cd "$TARGET" && pwd)"
|
|
53
60
|
|
|
@@ -149,19 +156,46 @@ boot_simulator_if_needed() {
|
|
|
149
156
|
xcrun simctl bootstatus "$udid" -b >/dev/null
|
|
150
157
|
}
|
|
151
158
|
|
|
152
|
-
|
|
159
|
+
simulator_ui_running() {
|
|
160
|
+
pgrep -x Simulator >/dev/null 2>&1
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
open_simulator_in_background_if_needed() {
|
|
153
164
|
local target="$1" udid=""
|
|
165
|
+
[ "${MM_HARNESS_SHOW_SIMULATOR:-1}" != "0" ] || return 0
|
|
166
|
+
simulator_ui_running && return 0
|
|
167
|
+
printf 'Opening Simulator UI in the background\n' >&2
|
|
154
168
|
if [ "$target" = "booted" ]; then
|
|
155
|
-
|
|
156
|
-
|
|
169
|
+
open -g -a Simulator >/dev/null 2>&1 || {
|
|
170
|
+
printf 'open-device: failed to open Simulator UI.\n' >&2
|
|
171
|
+
return 1
|
|
172
|
+
}
|
|
157
173
|
else
|
|
158
174
|
udid="$(sim_udid_for_target "$target")"
|
|
159
175
|
if [ -n "$udid" ]; then
|
|
160
|
-
open -a Simulator --args -CurrentDeviceUDID "$udid" >/dev/null 2>&1 ||
|
|
176
|
+
open -g -a Simulator --args -CurrentDeviceUDID "$udid" >/dev/null 2>&1 || {
|
|
177
|
+
printf 'open-device: failed to open Simulator UI for %s.\n' "$target" >&2
|
|
178
|
+
return 1
|
|
179
|
+
}
|
|
161
180
|
else
|
|
162
|
-
open -a Simulator >/dev/null 2>&1 ||
|
|
181
|
+
open -g -a Simulator >/dev/null 2>&1 || {
|
|
182
|
+
printf 'open-device: failed to open Simulator UI.\n' >&2
|
|
183
|
+
return 1
|
|
184
|
+
}
|
|
163
185
|
fi
|
|
164
186
|
fi
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
focus_simulator() {
|
|
190
|
+
local target="$1" udid=""
|
|
191
|
+
if [ "$target" != "booted" ]; then
|
|
192
|
+
udid="$(sim_udid_for_target "$target")"
|
|
193
|
+
fi
|
|
194
|
+
if [ -n "$udid" ]; then
|
|
195
|
+
open -a Simulator --args -CurrentDeviceUDID "$udid" >/dev/null 2>&1 || true
|
|
196
|
+
else
|
|
197
|
+
open -a Simulator >/dev/null 2>&1 || true
|
|
198
|
+
fi
|
|
165
199
|
osascript -e 'tell application "Simulator" to activate' >/dev/null 2>&1 || true
|
|
166
200
|
}
|
|
167
201
|
|
|
@@ -220,8 +254,12 @@ if [ "$PLATFORM" = "ios" ]; then
|
|
|
220
254
|
DEV_CLIENT_SCHEME="${IOS_DEV_CLIENT_SCHEME:-expo-metamask}"
|
|
221
255
|
|
|
222
256
|
boot_simulator_if_needed "$SIM_TARGET" || exit 1
|
|
257
|
+
open_simulator_in_background_if_needed "$SIM_TARGET" || exit 1
|
|
223
258
|
if [ "${MM_HARNESS_FOCUS_SIMULATOR:-0}" = "1" ]; then
|
|
224
|
-
|
|
259
|
+
focus_simulator "$SIM_TARGET"
|
|
260
|
+
fi
|
|
261
|
+
if $UI_ONLY; then
|
|
262
|
+
exit 0
|
|
225
263
|
fi
|
|
226
264
|
|
|
227
265
|
LAUNCHED=false
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Ensure one runner-owned CDP console forwarder is alive for this Mobile slot.
|
|
3
|
+
set -uo pipefail
|
|
4
|
+
|
|
5
|
+
TARGET="$PWD"
|
|
6
|
+
PORT="${WATCHER_PORT:-${METRO_PORT:-8081}}"
|
|
7
|
+
QUIET=false
|
|
8
|
+
|
|
9
|
+
while [ "$#" -gt 0 ]; do
|
|
10
|
+
case "$1" in
|
|
11
|
+
--target) [ "$#" -ge 2 ] || { echo "Missing value for --target" >&2; exit 2; }; TARGET="$2"; shift 2 ;;
|
|
12
|
+
--port) [ "$#" -ge 2 ] || { echo "Missing value for --port" >&2; exit 2; }; PORT="$2"; shift 2 ;;
|
|
13
|
+
--quiet) QUIET=true; shift ;;
|
|
14
|
+
-h|--help)
|
|
15
|
+
printf 'Usage: start-console-forwarder.sh [--target <dir>] [--port <port>] [--quiet]\n'
|
|
16
|
+
exit 0
|
|
17
|
+
;;
|
|
18
|
+
*) printf 'start-console-forwarder: unknown arg: %s\n' "$1" >&2; exit 2 ;;
|
|
19
|
+
esac
|
|
20
|
+
done
|
|
21
|
+
|
|
22
|
+
[ "${METAMASK_RECIPE_CONSOLE_FORWARD:-1}" != "0" ] || exit 0
|
|
23
|
+
TARGET="$(cd "$TARGET" && pwd -P)"
|
|
24
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
25
|
+
# shellcheck disable=SC1091
|
|
26
|
+
. "$SCRIPT_DIR/../shared/harness-path.sh"
|
|
27
|
+
LOG_DIR="$TARGET/$(recipe_runtime_dir)" || exit 1
|
|
28
|
+
FORWARDER="$SCRIPT_DIR/bridge-runtime/console-forwarder.cjs"
|
|
29
|
+
PID_FILE="$LOG_DIR/console-forwarder.pid"
|
|
30
|
+
APP_LOG_FILE="$LOG_DIR/app-console.log"
|
|
31
|
+
|
|
32
|
+
{ [ -f "$FORWARDER" ] && command -v node >/dev/null 2>&1; } || exit 0
|
|
33
|
+
mkdir -p "$LOG_DIR"
|
|
34
|
+
|
|
35
|
+
# One debugger owns a React Native page. Reap any collector for this Metro port,
|
|
36
|
+
# including one left by an older globally installed harness.
|
|
37
|
+
while read -r pid cmd; do
|
|
38
|
+
case "$cmd" in
|
|
39
|
+
*console-forwarder.cjs*" --port $PORT "*|*console-forwarder.cjs*" --port=$PORT "*)
|
|
40
|
+
kill "$pid" 2>/dev/null || true
|
|
41
|
+
;;
|
|
42
|
+
esac
|
|
43
|
+
done < <(ps -axo pid=,command= 2>/dev/null || true)
|
|
44
|
+
|
|
45
|
+
if [ -f "$PID_FILE" ]; then
|
|
46
|
+
old_pid="$(cat "$PID_FILE" 2>/dev/null || true)"
|
|
47
|
+
if [ -n "$old_pid" ]; then
|
|
48
|
+
case "$(ps -p "$old_pid" -o command= 2>/dev/null)" in
|
|
49
|
+
*console-forwarder.cjs*) kill "$old_pid" 2>/dev/null || true ;;
|
|
50
|
+
esac
|
|
51
|
+
fi
|
|
52
|
+
rm -f "$PID_FILE"
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
: > "$APP_LOG_FILE"
|
|
56
|
+
pid="$(node "$SCRIPT_DIR/launch-console-forwarder.cjs" \
|
|
57
|
+
--port "$PORT" \
|
|
58
|
+
--out "$APP_LOG_FILE" \
|
|
59
|
+
--err "$LOG_DIR/console-forwarder.err")" || exit 1
|
|
60
|
+
printf '%s\n' "$pid" > "$PID_FILE"
|
|
61
|
+
|
|
62
|
+
# A successful launch must never claim capture when the collector died at boot.
|
|
63
|
+
sleep 0.1
|
|
64
|
+
if ! kill -0 "$pid" 2>/dev/null; then
|
|
65
|
+
rm -f "$PID_FILE"
|
|
66
|
+
printf 'start-console-forwarder: collector exited during startup; inspect %s\n' \
|
|
67
|
+
"$LOG_DIR/console-forwarder.err" >&2
|
|
68
|
+
exit 1
|
|
69
|
+
fi
|
|
70
|
+
[ "$QUIET" = true ] || printf 'Device console → app-console.log (CDP forwarder pid %s)\n' "$pid" >&2
|
|
@@ -77,45 +77,6 @@ default_metro_workers() {
|
|
|
77
77
|
# shellcheck disable=SC1091
|
|
78
78
|
. "$SCRIPT_DIR/lib/tmux-viewer.sh"
|
|
79
79
|
|
|
80
|
-
# Bridgeless RN gates console→Metro forwarding on console._isPolyfilled, which is
|
|
81
|
-
# false with the Hermes native console — device logs (incl. DevLogger) never reach
|
|
82
|
-
# metro.log. The forwarder attaches over CDP and appends them back. Kill switch:
|
|
83
|
-
# METAMASK_RECIPE_CONSOLE_FORWARD=0.
|
|
84
|
-
start_console_forwarder() {
|
|
85
|
-
local forwarder="$SCRIPT_DIR/bridge-runtime/console-forwarder.cjs"
|
|
86
|
-
local fwd_pid_file="$LOG_DIR/console-forwarder.pid"
|
|
87
|
-
[ "${METAMASK_RECIPE_CONSOLE_FORWARD:-1}" != "0" ] || return 0
|
|
88
|
-
{ [ -f "$forwarder" ] && command -v node >/dev/null 2>&1; } || return 0
|
|
89
|
-
# Only one debugger can own a React Native page. Reap any forwarder already
|
|
90
|
-
# attached to this Metro port, even if it was launched from another checkout or
|
|
91
|
-
# package install path and therefore does not own this runtime dir's pid file.
|
|
92
|
-
local pid cmd
|
|
93
|
-
while read -r pid cmd; do
|
|
94
|
-
case "$cmd" in
|
|
95
|
-
*console-forwarder.cjs*" --port $PORT "*|*console-forwarder.cjs*" --port=$PORT "*)
|
|
96
|
-
kill "$pid" 2>/dev/null || true
|
|
97
|
-
;;
|
|
98
|
-
esac
|
|
99
|
-
done < <(ps -axo pid=,command= 2>/dev/null || true)
|
|
100
|
-
if [ -f "$fwd_pid_file" ]; then
|
|
101
|
-
local old_fwd
|
|
102
|
-
old_fwd="$(cat "$fwd_pid_file" 2>/dev/null || true)"
|
|
103
|
-
# A crash can leave a stale pid file and the OS can recycle the pid for an
|
|
104
|
-
# unrelated process — only kill when the command line is really ours.
|
|
105
|
-
if [ -n "$old_fwd" ]; then
|
|
106
|
-
case "$(ps -p "$old_fwd" -o command= 2>/dev/null)" in
|
|
107
|
-
*console-forwarder.cjs*) kill "$old_fwd" 2>/dev/null ;;
|
|
108
|
-
esac
|
|
109
|
-
fi
|
|
110
|
-
rm -f "$fwd_pid_file"
|
|
111
|
-
fi
|
|
112
|
-
nohup node "$forwarder" --port "$PORT" --out "$LOG_FILE" \
|
|
113
|
-
</dev/null >> "$LOG_DIR/console-forwarder.err" 2>&1 &
|
|
114
|
-
echo "$!" > "$fwd_pid_file"
|
|
115
|
-
disown 2>/dev/null || true
|
|
116
|
-
printf 'Device console → metro.log (CDP forwarder pid %s)\n' "$(cat "$fwd_pid_file")" >&2
|
|
117
|
-
}
|
|
118
|
-
|
|
119
80
|
write_metro_runner() {
|
|
120
81
|
local runner="$1"
|
|
121
82
|
local clear_flag=""
|
|
@@ -179,7 +140,6 @@ if metro_ready; then
|
|
|
179
140
|
rm -f "$PID_FILE"
|
|
180
141
|
elif metro_listener_valid; then
|
|
181
142
|
printf 'Metro already running and valid on port %s\n' "$PORT" >&2
|
|
182
|
-
start_console_forwarder
|
|
183
143
|
exit 0
|
|
184
144
|
else
|
|
185
145
|
stop_metro_listener || exit 1
|
|
@@ -269,7 +229,6 @@ if [ "$READY" = true ]; then
|
|
|
269
229
|
first_ready_pid="${ready_pids%% *}"
|
|
270
230
|
[ -z "$first_ready_pid" ] || printf '%s\n' "$first_ready_pid" > "$PID_FILE"
|
|
271
231
|
printf 'Metro ready on port %s\n' "$PORT" >&2
|
|
272
|
-
start_console_forwarder
|
|
273
232
|
printf ' Next: mm-harness logs (tail Metro)\n' >&2
|
|
274
233
|
exit 0
|
|
275
234
|
fi
|
|
@@ -261,76 +261,19 @@ check_file "package.json"
|
|
|
261
261
|
check_file "app/dev-tools/AgenticService/AgenticService.ts"
|
|
262
262
|
|
|
263
263
|
if ! grep -q "AgenticService.install" "$TARGET/app/core/NavigationService/NavigationService.ts" 2>/dev/null; then
|
|
264
|
-
checks+=("{\"name\":\"NavigationService
|
|
264
|
+
checks+=("{\"name\":\"NavigationService agentic bridge\",\"status\":\"fail\"}")
|
|
265
265
|
status="fail"
|
|
266
266
|
else
|
|
267
|
-
checks+=("{\"name\":\"NavigationService
|
|
267
|
+
checks+=("{\"name\":\"NavigationService agentic bridge\",\"status\":\"pass\"}")
|
|
268
268
|
fi
|
|
269
269
|
|
|
270
270
|
if ! grep -q "AgentStepHud" "$TARGET/app/components/Nav/App/App.tsx" 2>/dev/null; then
|
|
271
|
-
checks+=("{\"name\":\"App AgentStepHud
|
|
271
|
+
checks+=("{\"name\":\"App AgentStepHud\",\"status\":\"fail\"}")
|
|
272
272
|
status="fail"
|
|
273
273
|
else
|
|
274
|
-
checks+=("{\"name\":\"App AgentStepHud
|
|
274
|
+
checks+=("{\"name\":\"App AgentStepHud\",\"status\":\"pass\"}")
|
|
275
275
|
fi
|
|
276
276
|
|
|
277
|
-
# Drift detection (read-only): compare the in-repo AgenticService/HUD against
|
|
278
|
-
# the resolved runner overlay, not a runner-owned copy. The runner is the only
|
|
279
|
-
# MetaMask-aware source of truth for Mobile injection.
|
|
280
|
-
overlay_drift_check="$(
|
|
281
|
-
HARNESS_DIR_FOR_DRIFT="$HARNESS_DIR" TARGET_FOR_DRIFT="$TARGET" node <<'NODE'
|
|
282
|
-
const fs = require('fs');
|
|
283
|
-
const path = require('path');
|
|
284
|
-
const name = 'agentic overlay matches runner (HUD freshness)';
|
|
285
|
-
const harnessDir = process.env.HARNESS_DIR_FOR_DRIFT;
|
|
286
|
-
const targetDir = path.join(process.env.TARGET_FOR_DRIFT, 'app', 'dev-tools', 'AgenticService');
|
|
287
|
-
function readTrimmed(file) {
|
|
288
|
-
try { return fs.readFileSync(file, 'utf8').trim(); } catch { return ''; }
|
|
289
|
-
}
|
|
290
|
-
let runnerDir = readTrimmed(path.join(harnessDir, 'runner', '.runner-source'));
|
|
291
|
-
if (!runnerDir) {
|
|
292
|
-
try {
|
|
293
|
-
const manifest = JSON.parse(fs.readFileSync(path.join(harnessDir, 'manifest.json'), 'utf8'));
|
|
294
|
-
runnerDir = manifest && manifest.source && manifest.source.runnerDir || '';
|
|
295
|
-
} catch {}
|
|
296
|
-
}
|
|
297
|
-
if (!runnerDir) {
|
|
298
|
-
process.stdout.write(JSON.stringify({ name, status: 'warn', detail: 'runner source unavailable; overlay freshness compare skipped' }));
|
|
299
|
-
process.exit(0);
|
|
300
|
-
}
|
|
301
|
-
const overlayDir = path.join(runnerDir, 'adapters', 'mobile', 'overlay', 'app', 'dev-tools', 'AgenticService');
|
|
302
|
-
let checked = 0;
|
|
303
|
-
const drifted = [];
|
|
304
|
-
const missing = [];
|
|
305
|
-
try {
|
|
306
|
-
for (const entry of fs.readdirSync(overlayDir)) {
|
|
307
|
-
if (!entry.endsWith('.patch')) continue;
|
|
308
|
-
if (/\.test\./u.test(entry)) continue;
|
|
309
|
-
const base = entry.slice(0, -'.patch'.length);
|
|
310
|
-
const targetFile = path.join(targetDir, base);
|
|
311
|
-
if (!fs.existsSync(targetFile)) { missing.push(base); continue; }
|
|
312
|
-
checked += 1;
|
|
313
|
-
if (fs.readFileSync(path.join(overlayDir, entry), 'utf8') !== fs.readFileSync(targetFile, 'utf8')) {
|
|
314
|
-
drifted.push(base);
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
} catch (error) {
|
|
318
|
-
process.stdout.write(JSON.stringify({ name, status: 'warn', detail: 'runner overlay compare skipped: ' + String((error && error.message) || error) }));
|
|
319
|
-
process.exit(0);
|
|
320
|
-
}
|
|
321
|
-
if (drifted.length || missing.length) {
|
|
322
|
-
const parts = [];
|
|
323
|
-
if (drifted.length) parts.push('behind runner: ' + drifted.join(', '));
|
|
324
|
-
if (missing.length) parts.push('absent in repo: ' + missing.join(', '));
|
|
325
|
-
process.stdout.write(JSON.stringify({ name, status: 'warn', detail: parts.join('; ') + ' — rerun mm-harness install --force-overlay or mm-harness fixtures sync to refresh the in-repo HUD/AgenticService from the runner' }));
|
|
326
|
-
} else if (checked === 0) {
|
|
327
|
-
process.stdout.write(JSON.stringify({ name, status: 'pass' }));
|
|
328
|
-
} else {
|
|
329
|
-
process.stdout.write(JSON.stringify({ name, status: 'pass' }));
|
|
330
|
-
}
|
|
331
|
-
NODE
|
|
332
|
-
)"
|
|
333
|
-
checks+=("$overlay_drift_check")
|
|
334
277
|
run_with_timeout() {
|
|
335
278
|
local log_path="$1"
|
|
336
279
|
local timeout_s="$2"
|
|
@@ -386,9 +329,21 @@ JSON
|
|
|
386
329
|
|
|
387
330
|
ensure_live_runtime() {
|
|
388
331
|
local device_target="$1"
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
332
|
+
local attempt
|
|
333
|
+
for attempt in 1 2 3 4 5; do
|
|
334
|
+
if live_status_ok "$ARTIFACTS/logs/app-status-precheck.log" "$device_target"; then
|
|
335
|
+
return 0
|
|
336
|
+
fi
|
|
337
|
+
# A freshly booted simulator can expose its Metro bridge just before
|
|
338
|
+
# `simctl list devices booted` publishes the device. Bound that launch→verify
|
|
339
|
+
# race instead of reporting a false DEVICE_NOT_FOUND immediately.
|
|
340
|
+
if [ "$attempt" -lt 5 ] && grep -q '"code"[[:space:]]*:[[:space:]]*"DEVICE_NOT_FOUND"' "$ARTIFACTS/logs/app-status-precheck.log"; then
|
|
341
|
+
echo "Waiting for Mobile device discovery (${attempt}/5): ${device_target:-configured target}" >&2
|
|
342
|
+
sleep 1
|
|
343
|
+
continue
|
|
344
|
+
fi
|
|
345
|
+
break
|
|
346
|
+
done
|
|
392
347
|
if [ "$AUTO_START" = true ]; then
|
|
393
348
|
cat >&2 <<'EOF'
|
|
394
349
|
Mobile auto-start is not allowed from product-local scripts. Start or prepare the app through the runner/slot runtime, then rerun verify with --no-auto-start.
|
|
@@ -24,10 +24,10 @@ REQUIRE_PLATFORM=""
|
|
|
24
24
|
|
|
25
25
|
while [ "$#" -gt 0 ]; do
|
|
26
26
|
case "$1" in
|
|
27
|
-
--target) TARGET="$2"; shift 2 ;;
|
|
28
|
-
--port) PORT="$2"; shift 2 ;;
|
|
29
|
-
--max-polls) MAX_POLLS="$2"; shift 2 ;;
|
|
30
|
-
--platform) REQUIRE_PLATFORM="$2"; shift 2 ;;
|
|
27
|
+
--target) [ "$#" -ge 2 ] || { echo "Missing value for --target" >&2; exit 2; }; TARGET="$2"; shift 2 ;;
|
|
28
|
+
--port) [ "$#" -ge 2 ] || { echo "Missing value for --port" >&2; exit 2; }; PORT="$2"; shift 2 ;;
|
|
29
|
+
--max-polls) [ "$#" -ge 2 ] || { echo "Missing value for --max-polls" >&2; exit 2; }; MAX_POLLS="$2"; shift 2 ;;
|
|
30
|
+
--platform) [ "$#" -ge 2 ] || { echo "Missing value for --platform" >&2; exit 2; }; REQUIRE_PLATFORM="$2"; shift 2 ;;
|
|
31
31
|
-h|--help)
|
|
32
32
|
printf 'Usage: wait-for-bridge.sh [--target <dir>] [--port <n>] [--max-polls <n>] [--platform ios|android]\n'
|
|
33
33
|
exit 0
|
|
@@ -36,7 +36,12 @@ while [ "$#" -gt 0 ]; do
|
|
|
36
36
|
esac
|
|
37
37
|
done
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
case "$MAX_POLLS" in
|
|
40
|
+
''|*[!0-9]*) echo "wait-for-bridge: --max-polls must be an integer of at least 2." >&2; exit 2 ;;
|
|
41
|
+
esac
|
|
42
|
+
[ "$MAX_POLLS" -ge 2 ] || { echo "wait-for-bridge: --max-polls must be at least 2 for stable readiness." >&2; exit 2; }
|
|
43
|
+
|
|
44
|
+
TARGET="$(cd "$TARGET" && pwd -P)"
|
|
40
45
|
|
|
41
46
|
# The matcher reads the required platform from the environment so the value survives
|
|
42
47
|
# into the node subprocess that runs the shared matcher against the status output.
|
|
@@ -128,15 +133,24 @@ try {
|
|
|
128
133
|
' "$MATCH_LIB" "$STATUS_LOG" 2>/dev/null || printf 'none'
|
|
129
134
|
}
|
|
130
135
|
|
|
136
|
+
READY_STREAK=0
|
|
131
137
|
for ATTEMPT in $(seq 1 "$MAX_POLLS"); do
|
|
132
138
|
if bridge_has_route; then
|
|
133
|
-
|
|
134
|
-
|
|
139
|
+
READY_STREAK=$((READY_STREAK + 1))
|
|
140
|
+
# The bridge can appear briefly while React Native is still completing its
|
|
141
|
+
# startup navigation/reload. Require two consecutive healthy snapshots so a
|
|
142
|
+
# following command never inherits that transient gap.
|
|
143
|
+
if [ "$READY_STREAK" -ge 2 ]; then
|
|
144
|
+
printf 'Mobile bridge ready\n' >&2
|
|
145
|
+
exit 0
|
|
146
|
+
fi
|
|
147
|
+
else
|
|
148
|
+
READY_STREAK=0
|
|
135
149
|
fi
|
|
136
150
|
if [ "$ATTEMPT" = "1" ] || [ $((ATTEMPT % 5)) -eq 0 ]; then
|
|
137
151
|
printf 'Waiting for Mobile bridge (%s/%s): %s\n' "$ATTEMPT" "$MAX_POLLS" "$(bridge_wait_reason)" >&2
|
|
138
152
|
fi
|
|
139
|
-
sleep 2
|
|
153
|
+
[ "$ATTEMPT" -ge "$MAX_POLLS" ] || sleep 2
|
|
140
154
|
done
|
|
141
155
|
|
|
142
156
|
cat "$STATUS_LOG" >&2 || true
|
|
@@ -172,10 +172,10 @@ function extensionRuntimeContextPath(repo) {
|
|
|
172
172
|
const repoRoot = path.resolve(repo);
|
|
173
173
|
const envCtx = process.env.RECIPE_RUNTIME_CONTEXT;
|
|
174
174
|
if (envCtx) {
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
175
|
+
// Farmslot may manage runtime context outside the checkout. Treat the
|
|
176
|
+
// explicit environment override as authoritative; containment is not a
|
|
177
|
+
// security boundary and silently dropping it selects the wrong port.
|
|
178
|
+
return path.resolve(envCtx);
|
|
179
179
|
}
|
|
180
180
|
return path.join(repoRoot, recipeRuntimeDir(), 'agentic-runtime.json');
|
|
181
181
|
}
|
|
@@ -198,12 +198,22 @@ export function resolveExtensionRuntimeContext(repo) {
|
|
|
198
198
|
}
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
-
//
|
|
202
|
-
|
|
201
|
+
// Context and pool describe checkout identity and therefore replace stale
|
|
202
|
+
// inherited env values. The suffix formula is only a fallback and is applied
|
|
203
|
+
// separately by callers so an explicitly supplied env port can still win.
|
|
204
|
+
export function formatExtensionIdentityPorts(repo) {
|
|
203
205
|
const realRepo = path.resolve(repo);
|
|
204
206
|
return mergeKvLayers(
|
|
205
207
|
{ source: resolveExtensionRuntimeContext(realRepo), overwrite: true },
|
|
206
208
|
{ source: resolveSlotPortsByRepo(realRepo), overwrite: false },
|
|
209
|
+
);
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Checkout-scoped extension ports: runtime context (authoritative) → pool → suffix formula.
|
|
213
|
+
export function formatExtensionSlotPorts(repo) {
|
|
214
|
+
const realRepo = path.resolve(repo);
|
|
215
|
+
return mergeKvLayers(
|
|
216
|
+
{ source: formatExtensionIdentityPorts(realRepo), overwrite: true },
|
|
207
217
|
{ source: resolveDefaultExtensionPorts(realRepo), overwrite: false },
|
|
208
218
|
);
|
|
209
219
|
}
|
|
@@ -285,6 +295,7 @@ export const cliFns = {
|
|
|
285
295
|
// Back-compat cli key for one release.
|
|
286
296
|
resolve_farmslot_ports_by_repo: resolveSlotPortsByRepo,
|
|
287
297
|
resolve_default_extension_ports: resolveDefaultExtensionPorts,
|
|
298
|
+
resolve_extension_identity_ports: (repo) => formatExtensionIdentityPorts(repo) || null,
|
|
288
299
|
resolve_extension_runtime_context: resolveExtensionRuntimeContext,
|
|
289
300
|
resolve_extension_slot_ports: (repo) => formatExtensionSlotPorts(repo) || null,
|
|
290
301
|
resolve_extension_runtime_ports: (repo) => resolveExtensionRuntimePorts(repo) || null,
|
|
@@ -52,12 +52,13 @@ apply_extension_slot_ports() {
|
|
|
52
52
|
local repo="${1:-.}" line key val resolved="" ctx_file="" runtime_rel="${RECIPE_RUNTIME_DIR:-temp/recipe/runtime}"
|
|
53
53
|
repo="$(cd "$repo" && pwd)"
|
|
54
54
|
ctx_file="$repo/$runtime_rel/agentic-runtime.json"
|
|
55
|
-
if [ -f "$ctx_file" ]; then
|
|
55
|
+
if [ -z "${RECIPE_RUNTIME_CONTEXT:-}" ] && [ -f "$ctx_file" ]; then
|
|
56
56
|
RECIPE_RUNTIME_CONTEXT="$ctx_file"
|
|
57
|
-
else
|
|
58
|
-
unset RECIPE_RUNTIME_CONTEXT
|
|
59
57
|
fi
|
|
60
|
-
|
|
58
|
+
export RECIPE_RUNTIME_CONTEXT
|
|
59
|
+
# Context/pool are authoritative checkout identity. Formula ports are only
|
|
60
|
+
# fallback values and must not replace explicitly supplied env ports.
|
|
61
|
+
resolved="$(_resolve_ports_cli resolve_extension_identity_ports "$repo" 2>/dev/null)" || resolved=""
|
|
61
62
|
while IFS= read -r line; do
|
|
62
63
|
[ -n "$line" ] || continue
|
|
63
64
|
key="${line%%=*}"
|
|
@@ -68,6 +69,17 @@ apply_extension_slot_ports() {
|
|
|
68
69
|
SLOT_ID) RECIPE_SLOT_ID="$val" ;;
|
|
69
70
|
esac
|
|
70
71
|
done <<< "$resolved"
|
|
72
|
+
resolved="$(_resolve_ports_cli resolve_default_extension_ports "$repo" 2>/dev/null)" || return 0
|
|
73
|
+
while IFS= read -r line; do
|
|
74
|
+
[ -n "$line" ] || continue
|
|
75
|
+
key="${line%%=*}"
|
|
76
|
+
val="${line#*=}"
|
|
77
|
+
case "$key" in
|
|
78
|
+
CDP_PORT) [ -n "${CDP_PORT:-}" ] || CDP_PORT="$val" ;;
|
|
79
|
+
WATCHER_PORT) [ -n "${WATCHER_PORT:-}" ] || WATCHER_PORT="$val" ;;
|
|
80
|
+
SLOT_ID) [ -n "${RECIPE_SLOT_ID:-}" ] || RECIPE_SLOT_ID="$val" ;;
|
|
81
|
+
esac
|
|
82
|
+
done <<< "$resolved"
|
|
71
83
|
}
|
|
72
84
|
|
|
73
85
|
apply_resolved_extension_ports() {
|
|
@@ -111,4 +123,4 @@ apply_resolved_mobile_ports() {
|
|
|
111
123
|
;;
|
|
112
124
|
esac
|
|
113
125
|
done <<< "$resolved"
|
|
114
|
-
}
|
|
126
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
const [cacheFile, timeoutText, endpoint] = process.argv.slice(2);
|
|
6
|
+
const timeoutMs = Number.parseInt(timeoutText ?? '', 10);
|
|
7
|
+
|
|
8
|
+
function stableVersion(value) {
|
|
9
|
+
const match = /^(\d{1,6})\.(\d{1,6})\.(\d{1,6})$/u.exec(value);
|
|
10
|
+
if (!match) return null;
|
|
11
|
+
return `${Number(match[1])}.${Number(match[2])}.${Number(match[3])}`;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function main() {
|
|
15
|
+
if (!cacheFile || !endpoint || !Number.isInteger(timeoutMs) || timeoutMs < 1) return;
|
|
16
|
+
const controller = new AbortController();
|
|
17
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
18
|
+
try {
|
|
19
|
+
const response = await fetch(endpoint, { signal: controller.signal });
|
|
20
|
+
if (!response.ok) return;
|
|
21
|
+
const data = await response.json();
|
|
22
|
+
if (!data || typeof data.latest !== 'string' || data.latest.length === 0) return;
|
|
23
|
+
const latest = stableVersion(data.latest);
|
|
24
|
+
if (!latest) return;
|
|
25
|
+
let lastCheck = Date.now();
|
|
26
|
+
try {
|
|
27
|
+
const cached = JSON.parse(fs.readFileSync(cacheFile, 'utf8'));
|
|
28
|
+
if (typeof cached.lastCheck === 'number') lastCheck = cached.lastCheck;
|
|
29
|
+
} catch {
|
|
30
|
+
// The parent may have exited before creating the cache; recreate it below.
|
|
31
|
+
}
|
|
32
|
+
fs.mkdirSync(path.dirname(cacheFile), { recursive: true });
|
|
33
|
+
const temporary = `${cacheFile}.${process.pid}.tmp`;
|
|
34
|
+
fs.writeFileSync(temporary, JSON.stringify({ lastCheck, latest }));
|
|
35
|
+
fs.renameSync(temporary, cacheFile);
|
|
36
|
+
} catch {
|
|
37
|
+
// Passive checks are intentionally silent; the explicit update command teaches failures.
|
|
38
|
+
} finally {
|
|
39
|
+
clearTimeout(timer);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
await main();
|
package/bin/mm-harness
CHANGED
|
@@ -5,6 +5,17 @@
|
|
|
5
5
|
# end-state surface; it resolves node/tsx/dist the same way whether the package is
|
|
6
6
|
# installed as a dependency or run from a source checkout.
|
|
7
7
|
set -euo pipefail
|
|
8
|
+
INVOKED_AS="$0"
|
|
9
|
+
if [[ "$INVOKED_AS" == */* ]]; then
|
|
10
|
+
INVOKED_AS="$(cd "$(dirname "$INVOKED_AS")" && pwd -L)/${INVOKED_AS##*/}"
|
|
11
|
+
else
|
|
12
|
+
RESOLVED_INVOKED_AS="$(command -v "$INVOKED_AS" 2>/dev/null || true)"
|
|
13
|
+
if [[ -z "$RESOLVED_INVOKED_AS" ]]; then
|
|
14
|
+
echo "ERROR: Cannot resolve the invoked mm-harness executable: $INVOKED_AS" >&2
|
|
15
|
+
exit 1
|
|
16
|
+
fi
|
|
17
|
+
INVOKED_AS="$RESOLVED_INVOKED_AS"
|
|
18
|
+
fi
|
|
8
19
|
SOURCE="${BASH_SOURCE[0]}"
|
|
9
20
|
while [ -L "$SOURCE" ]; do
|
|
10
21
|
SOURCE_DIR="$(cd "$(dirname "$SOURCE")" && pwd -P)"
|
|
@@ -26,7 +37,7 @@ RUNNER_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
|
26
37
|
# already points at this script — including via a symlink (both sides are resolved
|
|
27
38
|
# to their real paths before comparing).
|
|
28
39
|
if [ -n "${MM_HARNESS_BIN:-}" ]; then
|
|
29
|
-
self_real="$SCRIPT_DIR/$
|
|
40
|
+
self_real="$SCRIPT_DIR/${SOURCE##*/}"
|
|
30
41
|
# Resolve MM_HARNESS_BIN through any symlink chain so a symlink-to-self is caught.
|
|
31
42
|
mm_bin_src="$MM_HARNESS_BIN"
|
|
32
43
|
while [ -L "$mm_bin_src" ]; do
|
|
@@ -39,7 +50,7 @@ if [ -n "${MM_HARNESS_BIN:-}" ]; then
|
|
|
39
50
|
fi
|
|
40
51
|
done
|
|
41
52
|
bin_real_dir="$(cd "$(dirname "$mm_bin_src")" 2>/dev/null && pwd -P || true)"
|
|
42
|
-
bin_real="${bin_real_dir:+$bin_real_dir/$
|
|
53
|
+
bin_real="${bin_real_dir:+$bin_real_dir/${mm_bin_src##*/}}"
|
|
43
54
|
if [ "$bin_real" != "$self_real" ]; then
|
|
44
55
|
if [ ! -x "$MM_HARNESS_BIN" ]; then
|
|
45
56
|
echo "mm-harness: MM_HARNESS_BIN is not an executable: $MM_HARNESS_BIN" >&2
|
|
@@ -50,6 +61,14 @@ if [ -n "${MM_HARNESS_BIN:-}" ]; then
|
|
|
50
61
|
fi
|
|
51
62
|
fi
|
|
52
63
|
|
|
64
|
+
# Internal executable identity (not part of the public env surface): the CLI
|
|
65
|
+
# embeds this in emitted recovery commands so they run verbatim even when
|
|
66
|
+
# mm-harness is not on PATH (task-local installs). Set AFTER the MM_HARNESS_BIN
|
|
67
|
+
# delegation above so the final executable entered always overwrites any
|
|
68
|
+
# inherited/stale value with its own resolved self path.
|
|
69
|
+
export MM_HARNESS_EXECUTABLE="$SCRIPT_DIR/${SOURCE##*/}"
|
|
70
|
+
export MM_HARNESS_INVOKED_AS="$INVOKED_AS"
|
|
71
|
+
|
|
53
72
|
ENTRY_TS="$RUNNER_DIR/src/mm-harness-cli.ts"
|
|
54
73
|
ENTRY_DIST="$RUNNER_DIR/dist/mm-harness-cli.js"
|
|
55
74
|
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { depsCheck } from "@farmslot/recipe-harness/runtime/deps-readiness";
|
|
3
|
+
import { shellQuote } from "../../commands/parse-args.js";
|
|
3
4
|
const coreSurface = {
|
|
4
5
|
adapter: "core",
|
|
5
6
|
headless: true,
|
|
@@ -14,6 +15,7 @@ const coreSurface = {
|
|
|
14
15
|
reasons: [
|
|
15
16
|
ready ? "Core is headless; dependencies are installed. Run recipes with mm-harness run." : "Core is headless; dependencies are not fully installed."
|
|
16
17
|
],
|
|
18
|
+
nextAction: ready ? void 0 : `cd ${shellQuote(path.resolve(target))} && yarn install --immutable`,
|
|
17
19
|
deps: deps.status
|
|
18
20
|
};
|
|
19
21
|
},
|
|
@@ -49,7 +51,8 @@ const coreSurface = {
|
|
|
49
51
|
},
|
|
50
52
|
hints: {
|
|
51
53
|
launch: "mm-harness run <recipe> # run recipes against the headless core",
|
|
52
|
-
relaunch: "mm-harness verify"
|
|
54
|
+
relaunch: "mm-harness verify",
|
|
55
|
+
runtimeProbeRecovery: (target) => `mm-harness verify --adapter core --target ${shellQuote(path.resolve(target))}`
|
|
53
56
|
}
|
|
54
57
|
};
|
|
55
58
|
export {
|