@deeeed/metamask-harness 0.84.0 → 0.85.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 +34 -0
- package/adapters/extension/console-tail.mjs +11 -7
- package/adapters/extension/ensure-browser.sh +17 -18
- package/adapters/mobile/start-console-forwarder.sh +8 -0
- package/adapters/shared/resolve-slot-ports-core.mjs +48 -6
- package/dist/adapters/extension/actions.js +5 -0
- package/dist/adapters/extension/console-capture.js +18 -0
- package/dist/adapters/extension/fixtures.js +2 -0
- package/dist/adapters/extension/overlay.js +5 -4
- package/dist/adapters/extension/runtime.js +2 -0
- package/dist/adapters/mobile/launch.js +19 -2
- package/dist/adapters/mobile/provision.js +4 -2
- package/dist/adapters/mobile/run.js +2 -1
- package/dist/adapters/mobile/verify.js +15 -7
- package/dist/adapters/web-dapp/actions.js +7 -5
- package/dist/adapters.js +5 -3
- package/dist/command-contract.js +28 -19
- package/dist/commands/check.js +5 -7
- package/dist/commands/debug.js +2 -2
- package/dist/commands/doctor.js +2 -3
- package/dist/commands/domain.js +4 -3
- package/dist/commands/fixtures.js +3 -3
- package/dist/commands/help.js +6 -5
- package/dist/commands/launch/extension.js +9 -7
- package/dist/commands/logs.js +2 -2
- package/dist/commands/prepare.js +4 -4
- package/dist/commands/provision.js +2 -2
- package/dist/commands/review.js +8 -10
- package/dist/commands/runtime-launch.js +2 -0
- package/dist/commands/status.js +4 -2
- package/dist/commands/task-init.js +2 -3
- package/dist/console-classifier.js +3 -2
- package/dist/doctor.js +6 -2
- package/dist/mm-harness-cli.js +19 -6
- package/dist/paths.js +9 -4
- package/dist/recipe-security.js +1 -1
- package/dist/review/knowledge.js +10 -2
- package/dist/runner.js +18 -3
- package/docs/CONTRIBUTING.md +8 -0
- package/docs/RECIPES.md +14 -1
- package/docs/cli-contract.md +7 -6
- package/library/actions/extension/app/assert_no_console_errors.mjs +95 -0
- package/library/actions/extension/platform/cdp.mjs +26 -0
- package/library/actions/mobile/app/assert_no_console_errors.mjs +129 -0
- package/library/actions/shared/console-assert.mjs +174 -0
- package/library/actions/shared/console-collector.mjs +10 -3
- package/library/actions/shared/console-findings.mjs +108 -7
- package/library/actions/web-dapp/app/assert_no_console_errors.mjs +10 -54
- package/library/actions/web-dapp/platform/hud.mjs +2 -9
- package/library/actions/web-dapp/platform/page.mjs +62 -16
- package/library/actions/web-dapp/platform/runtime.mjs +1 -1
- package/library/diagnostics/console-allowlist.json +13 -0
- package/library/manifests/extension.action-manifest.json +36 -0
- package/library/manifests/mobile.action-manifest.json +36 -0
- package/library/manifests/web-dapp.action-manifest.json +1 -1
- package/library/recipes/core/health/check.recipe.json +40 -0
- package/library/recipes/extension/health/check.recipe.json +71 -0
- package/library/recipes/mobile/health/check.recipe.json +71 -0
- package/package.json +9 -9
- package/site/assets/help-recipes.json +2 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,40 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.85.0 - 2026-10-09
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `health.check` recipes for Mobile, Extension and Core: a read-only readiness check to run before ticket work. A failed precondition stops at a node named after its error (for example `WALLET_LOCKED`, `NOT_ON_HOME` or, on Mobile and Extension, `CONSOLE_ERRORS`), whose intent gives the recovery command. `mm-harness help` lists it under the target step.
|
|
10
|
+
- `metamask.app.assert_no_console_errors` on Mobile and Extension, as on `web-dapp`: it fails on a console error or exception that no console allowlist covers, judging the events since the launch began by their own timestamps, and fails as unavailable unless the capture is proven (the slot's console forwarder or collector is running and a control line logged in the app or MetaMask reaches the log). `launch` records when it starts, and console lines replayed from before then (by an app runtime or browser the launch kept) are left out. Mobile reads `app-console.log`, which every launch now restarts empty along with the forwarder's replay cursor, and judges only the device the bridge selects; Extension reads `extension-console.log` from the offset `launch` records, and its collector now stamps each line with the event's own time (UTC, with milliseconds). Known app noise is accepted only when it also appears on the app's unchanged base build: the bundled library ships `diagnostics/console-allowlist.json` (the Extension unlock page's `UNSAFE_componentWillMount` warning), read ahead of other libraries' by these actions and by run diagnostics, and the classifier carries pattern rules for base-reproduced errors one exact entry cannot cover: Mobile's SES lockdown intrinsic reports and its notifications startup failure (`createOnChainTriggers`), whose message carries the bundle line; Extension's `getBearerToken` while locked (MetaMask/metamask-extension#46688) and the Money query adapter's `mutations` TypeError (TAT-4063), whose stacks name build-specific chunks. Allowlist entries can name the `app` source Mobile lines carry, and Mobile console lines match without the forwarder's time and device prefix.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- **BREAKING:** `run --hud` takes `show` or `hide`, default `show`. `auto` is gone: the CLI refuses it as an invalid choice (`--hud must be show or hide; received 'auto'.`); drop the flag to get the default. The recipe HUD, each node's intent, now draws on every adapter that implements `app.hud`, headless browsers included. A headless web-dapp (Terminal) browser gets it where `auto` skipped it; Mobile and Extension already drew it by default. A Mobile release artifact still runs without it, since it has no dev bridge to draw with. `METAMASK_RECIPE_AUTO_HUD=0` stays the internal off switch for self-test, verify and the test sandboxes, and `--hud show` wins over it.
|
|
15
|
+
- **BREAKING:** web-dapp `ui.screenshot` keeps the HUD in the capture (it hid it while capturing), so Terminal screenshot evidence shows the node's intent as Mobile and Extension evidence does. `--hud hide` is the way to a HUD-free screenshot.
|
|
16
|
+
- A `surface=wallet` web-dapp `ui.screenshot` (the MetaMask confirmation window or side panel) shows the running node's intent too: the HUD is a bar docked to that MetaMask page's bottom edge for the capture only, which reserves its own height so the request and the footer buttons move up rather than being covered (pointer-events none), and is removed after it.
|
|
17
|
+
- A run with the HUD off (`--hud hide`, a `call`, an untrusted source) first clears the HUD an earlier run left on the slot, so its screenshots never show a previous recipe's intent: web-dapp deletes `hud.json` (the wallet host redraws from it) and the drawn HUD on the app tabs; Extension removes the runner HUD from its pages. Nothing is launched to do it, `METAMASK_RECIPE_AUTO_HUD=0` skips it, and a failed clear prints one `WARN:` line on stderr.
|
|
18
|
+
- Inside a checkout, every command resolves its adapter, target and slot once, through recipe-cli's context resolver, so `status`, `doctor`, `run` and the rest need no `--adapter`. The order is: flag, then the checkout's runtime binding, then its slot, then a unique detect match among the built-ins and the operator's library plugins (matched by their declared `detect`, before any plugin code loads).
|
|
19
|
+
- A checkout that more than one adapter matches stops with `ADAPTER_AMBIGUOUS` (exit 2) and lists the candidates. With no match, a command keeps its "could not detect the MetaMask repo type" error.
|
|
20
|
+
- `status`, `doctor`, `run`, `call` and discovery (`actions`, `run --list`/`--describe`, `call --list`) carry `context` in their `--json` envelopes (each value and its source), as does a command's own `USAGE` error once its context has resolved. Grammar refusals (raised before resolution), the task view (`status --task`/`--watch`) and the other commands' envelopes, `launch` on every adapter included, are unchanged. Human output adds one `context:` line on stderr when the adapter was not given by a flag.
|
|
21
|
+
- Terminal detection needs a recipe-terminal library that declares `detect` (recipe-terminal #8, 8a44aee); with an older library, pass `--adapter terminal`.
|
|
22
|
+
- `status` accepts `--adapter` and `--platform` (it refused them with `CLI_UNKNOWN_OPTION`).
|
|
23
|
+
- The slot comes from mm-harness's own pool resolution (the configured Farmslot root, else the nearest Farmslot checkout's `pool/`) unless `FARMSLOT_POOL_DIR` or `FARMSLOT_ROOT` names one, so a slot checkout beside a Farmslot checkout resolves its slot, and its ports fill `--cdp-port`/`--watcher-port`, with no env.
|
|
24
|
+
- Commands no longer detect the adapter themselves: status, doctor, check, provision, prepare, help, task init, `verify`'s overlay, debug, logs, fixtures, review and domain all read the resolved one. `check` still refuses `--platform ios|android`.
|
|
25
|
+
- `review` and `domain` scope to the resolved adapter when review covers it (mobile, extension, core, terminal), else run unscoped. Only an explicit `--adapter` that names another adapter is refused.
|
|
26
|
+
- Depend on `@farmslot/recipe-cli` 0.11.0 and `@farmslot/adapter-sdk` 0.8.0 (were 0.10.0 and 0.7.1) for the context resolver (deeeed/farmslot#864), on `@farmslot/recipe-runner` 0.28.0, `@farmslot/protocol` 0.35.0 and `@farmslot/agent-runtime` 0.17.1 (were 0.27.0, 0.34.0 and 0.17.0), and on `@farmslot/adapter-web` 0.6.4, `@farmslot/adapter-rn` 0.19.3 and `@farmslot/adapter-node` 0.1.6 (were 0.6.2, 0.19.1 and 0.1.4). With them, a web-dapp launch stops waiting for CDP when its browser dies (deeeed/farmslot#865). The install holds one `@farmslot/adapter-sdk` and one `@farmslot/recipe-runner`.
|
|
27
|
+
- Help lists the selectable adapters (the registered ones, then those the operator's libraries and the invocation's `--library` entries declare) wherever it names `--adapter` or `--platform`, instead of a fixed `mobile|extension|core|terminal`.
|
|
28
|
+
- **BREAKING:** Mobile `verify` takes `--watcher-port` (the public `verify` grammar accepted it; the Mobile leaf refused it) and reads `RECIPE_WATCHER_PORT`: the Metro port is `--watcher-port`, then `RECIPE_WATCHER_PORT`, then `WATCHER_PORT`, then the slot's `.js.env`. With no port env set, a checkout the slot pool maps gets the pool slot's port through `WATCHER_PORT`, so that port now wins over the checkout's `.js.env` `WATCHER_PORT`.
|
|
29
|
+
- **BREAKING:** A `RECIPE_RUNTIME_CONTEXT` that belongs to another checkout no longer sets the Extension slot's ports (`CDP_PORT`, `WATCHER_PORT`, `SLOT_ID`), Extension `verify`'s `RECIPE_SLOT_ID` and `RECIPE_HARNESS_EXTENSION_ID` (it reads the checkout's own context instead), or the Mobile provision slot (simulator, slot id). It counts when its `repoRoot` is the checkout's Git top level (so a clone nested inside another checkout never takes that checkout's context), or, without a `repoRoot`, when the file sits inside the checkout: the rule recipe-cli applies to the runtime binding. A context outside every checkout that names no `repoRoot` used to be authoritative; an orchestrator that keeps the file elsewhere sets `repoRoot`.
|
|
30
|
+
- Contributors: `bash tests/contract/run.sh` runs half the cores' worth of tests at once (`-j N`), each in its own process group, `TMPDIR` and output file, and prints each test's duration and the 15 slowest. A test gets 60 s (`CONTRACT_TEST_TIMEOUT`, or `# contract: timeout=<s>` in its header) and fails as `timed out`; what it leaves running is killed and named on a `LEAK` line; a test that rebuilds `dist/` declares `# contract: serial`. An unfiltered run over `CONTRACT_BUDGET` (180 s) fails, and a stopped run first lets each running test clean up (its EXIT trap reaps the leaves its CLI detached), then kills every process group left in the test's tree. A process a test leaves behind can no longer stall the run.
|
|
31
|
+
- Contributors: `yarn test:goldens` runs its families through `run.sh` and fails over `GOLDENS_BUDGET` (120 s); a golden case gets 60 s (`GOLDENS_CASE_TIMEOUT`, was 600 s). Docs: CONTRIBUTING and cli-contract describe both.
|
|
32
|
+
- Contributors: `AGENTS.md` has a `## Test speed` section: the default and CI suite must take 5 minutes or less (the contract and goldens runs check their own budgets; build and unit are not checked), and a new or changed test uses no fixed waits of 1 s or more, moves to `tests/live/` when it takes over 5 s (a golden case over 30 s), tears down every background process, and never rebuilds `dist/` under concurrent readers.
|
|
33
|
+
- Site: the platform map (`site/platform.html`) is a static page instead of the zoomable canvas. It shows the five layers (Libraries, MetaMask preset, Adapters, CLI, Engine) with their packages, one `mm-harness run` traced through them, and who changes what. It renders from `file://` (no `fetch`), so `site/assets/platform.json` is removed.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- Extension reopen (`adapters/extension/ensure-browser.sh`) no longer builds its `node -e` script or its `python3` profile cleanup from the slot id, checkout, runtime, profile, extension and runner paths, CDP port, webpack pid or wallet fixture path. The scripts are fixed text and get those values as argv. Before, a checkout path with an apostrophe (`/tmp/Arthur's checkout`) broke the reopen script's syntax and silently skipped the profile cleanup, and a crafted value could change the code the reopen ran.
|
|
38
|
+
|
|
5
39
|
## 0.84.0 - 2026-10-08
|
|
6
40
|
|
|
7
41
|
### Changed
|
|
@@ -90,10 +90,14 @@ function out(event, destination = null) {
|
|
|
90
90
|
}
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
93
|
+
// A record's time: the UTC time of day of the event's own timestamp (CDP
|
|
94
|
+
// Runtime.Timestamp, ms since the epoch), as the Mobile console forwarder
|
|
95
|
+
// writes it, or of the write when the event carries none. Events the browser
|
|
96
|
+
// replays when a collector attaches keep their own time, so
|
|
97
|
+
// metamask.app.assert_no_console_errors can leave out those from before a
|
|
98
|
+
// launch.
|
|
99
|
+
function stamp(timestamp) {
|
|
100
|
+
return new Date(Number.isFinite(timestamp) ? timestamp : Date.now()).toISOString().slice(11, 23);
|
|
97
101
|
}
|
|
98
102
|
|
|
99
103
|
function shortLabel(target) {
|
|
@@ -160,7 +164,7 @@ function emitConsole(label, params, destination) {
|
|
|
160
164
|
const level = String(params.type || 'log').toUpperCase();
|
|
161
165
|
const text = (params.args || []).map(previewArg).join(' ');
|
|
162
166
|
const [first, ...rest] = text.split('\n');
|
|
163
|
-
out(`${stamp()} [${label}] ${level} ${first}${atSuffix(frameText(params.stackTrace?.callFrames?.[0]))}${rest.length ? `\n${rest.join('\n')}` : ''}`, destination);
|
|
167
|
+
out(`${stamp(params.timestamp)} [${label}] ${level} ${first}${atSuffix(frameText(params.stackTrace?.callFrames?.[0]))}${rest.length ? `\n${rest.join('\n')}` : ''}`, destination);
|
|
164
168
|
}
|
|
165
169
|
|
|
166
170
|
function emitException(label, params, destination) {
|
|
@@ -172,7 +176,7 @@ function emitException(label, params, destination) {
|
|
|
172
176
|
'uncaught exception';
|
|
173
177
|
const frame = frameText(d.stackTrace?.callFrames?.[0]) || frameText({ url: d.url, lineNumber: d.lineNumber, columnNumber: d.columnNumber });
|
|
174
178
|
const [first, ...rest] = String(text).split('\n');
|
|
175
|
-
out(`${stamp()} [${label}] EXCEPTION ${first}${atSuffix(frame)}${rest.length ? `\n${rest.join('\n')}` : ''}`, destination);
|
|
179
|
+
out(`${stamp(params.timestamp)} [${label}] EXCEPTION ${first}${atSuffix(frame)}${rest.length ? `\n${rest.join('\n')}` : ''}`, destination);
|
|
176
180
|
}
|
|
177
181
|
|
|
178
182
|
// Browser log entries (network failures, interventions, deprecations) are
|
|
@@ -186,7 +190,7 @@ function emitLogEntry(label, params, destination) {
|
|
|
186
190
|
// A Log entry's location is its URL as is: a line number appended to a URL
|
|
187
191
|
// could not be told apart from the URL's own text.
|
|
188
192
|
const where = e.url ? e.url : frameText(e.stackTrace?.callFrames?.[0]);
|
|
189
|
-
out(`${stamp()} [${label}] [browser:${source}] ${level} ${e.text || ''}${atSuffix(where)}`, destination);
|
|
193
|
+
out(`${stamp(e.timestamp)} [${label}] [browser:${source}] ${level} ${e.text || ''}${atSuffix(where)}`, destination);
|
|
190
194
|
}
|
|
191
195
|
|
|
192
196
|
function handleMessage(label, destination, data) {
|
|
@@ -235,9 +235,10 @@ echo "[cleanup] Reusing profile ${PROFILE}"
|
|
|
235
235
|
# shutdowns; --load-extension re-advertises the path but does not flip it back,
|
|
236
236
|
# leaving MV3 service workers inert).
|
|
237
237
|
if [ -f "${PROFILE}/Default/Preferences" ]; then
|
|
238
|
-
python3 -
|
|
238
|
+
python3 - "${PROFILE}/Default/Preferences" 2>/dev/null <<'PY' && echo "[cleanup] Cleared crash recovery state + re-enabled extensions"
|
|
239
239
|
import json
|
|
240
|
-
|
|
240
|
+
import sys
|
|
241
|
+
pf = sys.argv[1]
|
|
241
242
|
prefs = json.load(open(pf))
|
|
242
243
|
prefs['profile'] = prefs.get('profile', {})
|
|
243
244
|
prefs['profile']['exit_type'] = 'Normal'
|
|
@@ -248,7 +249,7 @@ for ext_id, ext_prefs in ext_settings.items():
|
|
|
248
249
|
ext_prefs['state'] = 1
|
|
249
250
|
ext_prefs.pop('disable_reasons', None)
|
|
250
251
|
json.dump(prefs, open(pf, 'w'))
|
|
251
|
-
|
|
252
|
+
PY
|
|
252
253
|
fi
|
|
253
254
|
|
|
254
255
|
# Readiness gate: wait until webpack emits a loadable background entry before
|
|
@@ -289,10 +290,13 @@ echo "[reopen] ${SLOT_ID} — CDP:${CDP_PORT:-off}"
|
|
|
289
290
|
cd "${REPO}"
|
|
290
291
|
|
|
291
292
|
trap - EXIT
|
|
292
|
-
|
|
293
|
+
# The reopen script is fixed text; paths, ids and ports reach it as argv, never as source.
|
|
294
|
+
IFS= read -r -d '' REOPEN_SCRIPT <<'NODE' || true
|
|
295
|
+
const [SCRIPT_DIR, SLOT_ID, AGENT_DIR, EXTENSION, REPO, RUNNER_BIN, PROFILE, cdpPortArg, webpackPidArg, WALLET_FIXTURE] =
|
|
296
|
+
process.argv.slice(1);
|
|
293
297
|
const path = require('path');
|
|
294
298
|
const fs = require('fs');
|
|
295
|
-
const adapterWeb = (name) => require('module').createRequire(path.join(
|
|
299
|
+
const adapterWeb = (name) => require('module').createRequire(path.join(SCRIPT_DIR, 'package.json'))('@farmslot/adapter-web/' + name);
|
|
296
300
|
const http = require('http');
|
|
297
301
|
const { execFileSync } = require('child_process');
|
|
298
302
|
let chromium; try { chromium = require('@playwright/test').chromium; } catch { chromium = require('playwright').chromium; }
|
|
@@ -318,15 +322,9 @@ const {
|
|
|
318
322
|
resolveBrowser,
|
|
319
323
|
} = adapterWeb('browser-resolver');
|
|
320
324
|
|
|
321
|
-
const
|
|
322
|
-
const
|
|
323
|
-
const
|
|
324
|
-
const REPO = '${REPO}';
|
|
325
|
-
const RUNNER_BIN = '${RUNNER_BIN}';
|
|
326
|
-
const PROFILE = '${PROFILE}';
|
|
327
|
-
const CDP_PORT = '${CDP_PORT}' || null;
|
|
328
|
-
const WEBPACK_PID = '${WEBPACK_PID}' || null;
|
|
329
|
-
const walletFixture = JSON.parse(fs.readFileSync('${WALLET_FIXTURE}', 'utf8'));
|
|
325
|
+
const CDP_PORT = cdpPortArg || null;
|
|
326
|
+
const WEBPACK_PID = webpackPidArg || null;
|
|
327
|
+
const walletFixture = JSON.parse(fs.readFileSync(WALLET_FIXTURE, 'utf8'));
|
|
330
328
|
const PASSWORD = walletFixture.password;
|
|
331
329
|
|
|
332
330
|
if (!PASSWORD) {
|
|
@@ -522,8 +520,8 @@ const releaseFocusHold = () => {
|
|
|
522
520
|
await evaluatePageViaCdp(page, applyPersistentSlotTitle, { slotId: SLOT_ID, defaultTitle: 'MetaMask' });
|
|
523
521
|
|
|
524
522
|
// Wait for runtime state to settle, then unlock if needed.
|
|
525
|
-
const unlockSelector = '[data-testid
|
|
526
|
-
const homeSelector = '[data-testid
|
|
523
|
+
const unlockSelector = '[data-testid="unlock-password"]';
|
|
524
|
+
const homeSelector = '[data-testid="account-menu-icon"]';
|
|
527
525
|
let detectedState = 'unknown';
|
|
528
526
|
for (let i = 0; i < 15; i++) {
|
|
529
527
|
if (await page.locator(homeSelector).count()) {
|
|
@@ -544,7 +542,7 @@ const releaseFocusHold = () => {
|
|
|
544
542
|
if (detectedState === 'locked') {
|
|
545
543
|
console.log('[reopen] Unlocking wallet...');
|
|
546
544
|
await page.fill(unlockSelector, PASSWORD);
|
|
547
|
-
await page.click('[data-testid
|
|
545
|
+
await page.click('[data-testid="unlock-submit"]');
|
|
548
546
|
await page.waitForSelector(homeSelector, { timeout: 30000 });
|
|
549
547
|
console.log('[reopen] Unlocked');
|
|
550
548
|
} else if (detectedState === 'onboarding') {
|
|
@@ -611,4 +609,5 @@ const releaseFocusHold = () => {
|
|
|
611
609
|
console.error(e);
|
|
612
610
|
process.exit(1);
|
|
613
611
|
});
|
|
614
|
-
|
|
612
|
+
NODE
|
|
613
|
+
exec node -e "$REOPEN_SCRIPT" "$SCRIPT_DIR" "$SLOT_ID" "$AGENT_DIR" "$EXTENSION" "$REPO" "$RUNNER_BIN" "$PROFILE" "$CDP_PORT" "$WEBPACK_PID" "$WALLET_FIXTURE"
|
|
@@ -72,7 +72,15 @@ if [ -f "$PID_FILE" ]; then
|
|
|
72
72
|
rm -f "$PID_FILE"
|
|
73
73
|
fi
|
|
74
74
|
|
|
75
|
+
# Each launch starts a fresh log. The replay cursor goes with it: it marks
|
|
76
|
+
# lines as written that are no longer in the file, such as the relaunched
|
|
77
|
+
# app's first lines the previous forwarder wrote, so the new forwarder replays
|
|
78
|
+
# the app runtime's console buffer into the new log instead of skipping them.
|
|
79
|
+
# A runtime the launch kept replays lines from before the launch too;
|
|
80
|
+
# metamask.app.assert_no_console_errors leaves those out by their own time
|
|
81
|
+
# against the mark each launch attempt records (app-console.launch.json).
|
|
75
82
|
: > "$APP_LOG_FILE"
|
|
83
|
+
rm -f "$APP_LOG_FILE.forwarder-state.json"
|
|
76
84
|
pid="$(node "$SCRIPT_DIR/launch-console-forwarder.cjs" \
|
|
77
85
|
--port "$PORT" \
|
|
78
86
|
--out "$APP_LOG_FILE" \
|
|
@@ -198,15 +198,57 @@ function mergeKvLayers(...layers) {
|
|
|
198
198
|
return Object.keys(result).length ? formatKvLines(result) : '';
|
|
199
199
|
}
|
|
200
200
|
|
|
201
|
+
// Whether a runtime context belongs to the checkout at `repo` (recipe-cli's
|
|
202
|
+
// binding rule): its repoRoot is the checkout's Git top level, so a clone
|
|
203
|
+
// nested inside another checkout never takes that checkout's context; without
|
|
204
|
+
// a repoRoot, the file sits inside that top level. An orchestrator may keep the
|
|
205
|
+
// file elsewhere as long as it names the checkout. Twin of recipe-cli's
|
|
206
|
+
// runtimeContextOwned: this module stays dependency-free for the shell paths
|
|
207
|
+
// that load it, so the rule is repeated here rather than imported.
|
|
208
|
+
export function ownsRuntimeContext(file, repo) {
|
|
209
|
+
let repoRoot;
|
|
210
|
+
try {
|
|
211
|
+
const value = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
212
|
+
repoRoot = typeof value?.repoRoot === 'string' ? value.repoRoot : undefined;
|
|
213
|
+
} catch {
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
if (repoRoot === undefined) return within(gitTopLevel(repo), file);
|
|
217
|
+
try {
|
|
218
|
+
return fs.realpathSync(repoRoot) === fs.realpathSync(gitTopLevel(repo));
|
|
219
|
+
} catch {
|
|
220
|
+
// A path that does not exist owns nothing.
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function gitTopLevel(dir) {
|
|
226
|
+
try {
|
|
227
|
+
return execFileSync('git', ['-C', dir, 'rev-parse', '--show-toplevel'], {
|
|
228
|
+
encoding: 'utf8',
|
|
229
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
230
|
+
}).trim();
|
|
231
|
+
} catch {
|
|
232
|
+
// Not a Git checkout: the directory is its own root.
|
|
233
|
+
return dir;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
function within(outer, inner) {
|
|
238
|
+
try {
|
|
239
|
+
const relative = path.relative(fs.realpathSync(outer), fs.realpathSync(inner));
|
|
240
|
+
return !(relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative));
|
|
241
|
+
} catch {
|
|
242
|
+
// A path that does not exist owns nothing.
|
|
243
|
+
return false;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
201
247
|
function extensionRuntimeContextPath(repo) {
|
|
202
248
|
const repoRoot = path.resolve(repo);
|
|
203
249
|
const envCtx = process.env.RECIPE_RUNTIME_CONTEXT;
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
// explicit environment override as authoritative; containment is not a
|
|
207
|
-
// security boundary and silently dropping it selects the wrong port.
|
|
208
|
-
return path.resolve(envCtx);
|
|
209
|
-
}
|
|
250
|
+
// Another checkout's context would set this checkout's ports.
|
|
251
|
+
if (envCtx && ownsRuntimeContext(path.resolve(envCtx), repoRoot)) return path.resolve(envCtx);
|
|
210
252
|
return path.join(repoRoot, recipeRuntimeDir(), 'agentic-runtime.json');
|
|
211
253
|
}
|
|
212
254
|
|
|
@@ -43,6 +43,11 @@ const extensionActions = {
|
|
|
43
43
|
}
|
|
44
44
|
})
|
|
45
45
|
}),
|
|
46
|
+
// The HUD lives only in the extension pages' DOM.
|
|
47
|
+
async clearHud() {
|
|
48
|
+
const { clearExtensionHud } = await import("../../../library/actions/extension/platform/cdp.mjs");
|
|
49
|
+
await clearExtensionHud();
|
|
50
|
+
},
|
|
46
51
|
trustedMutation: true,
|
|
47
52
|
// ensure_positions and ensure_orders only check state here.
|
|
48
53
|
inputFindings: (nodeId, node) => [
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import { recipeRuntimePath } from "@farmslot/recipe-cli/harness";
|
|
3
4
|
import { verifyCapture } from "../../../library/actions/shared/console-collector.mjs";
|
|
4
5
|
import { ensureConsoleCollector } from "../console-capture.js";
|
|
@@ -10,6 +11,21 @@ function extensionCaptureFiles(target) {
|
|
|
10
11
|
pidFile: recipeRuntimePath(target, "console-tail.pid")
|
|
11
12
|
};
|
|
12
13
|
}
|
|
14
|
+
const EXTENSION_CONSOLE_LAUNCH_FILE = "extension-console.launch.json";
|
|
15
|
+
function recordConsoleLaunchIn(runtimeDir) {
|
|
16
|
+
const log = path.join(runtimeDir, "extension-console.log");
|
|
17
|
+
const stat = fs.existsSync(log) ? fs.statSync(log) : null;
|
|
18
|
+
fs.mkdirSync(runtimeDir, { recursive: true });
|
|
19
|
+
const launchedAtMs = Date.now();
|
|
20
|
+
fs.writeFileSync(
|
|
21
|
+
path.join(runtimeDir, EXTENSION_CONSOLE_LAUNCH_FILE),
|
|
22
|
+
`${JSON.stringify({ offset: stat?.size ?? 0, inode: stat?.ino ?? null, launchedAtMs, recordedAt: new Date(launchedAtMs).toISOString() })}
|
|
23
|
+
`
|
|
24
|
+
);
|
|
25
|
+
}
|
|
26
|
+
function recordExtensionConsoleLaunch(target) {
|
|
27
|
+
recordConsoleLaunchIn(path.dirname(extensionCaptureFiles(target).extensionLog));
|
|
28
|
+
}
|
|
13
29
|
function extensionCdpPort() {
|
|
14
30
|
return process.env.CDP_PORT ?? process.env.RECIPE_CDP_PORT;
|
|
15
31
|
}
|
|
@@ -37,8 +53,10 @@ async function verifyExtensionConsoleControl(capture) {
|
|
|
37
53
|
return verifyCapture({ cdpPort: capture.cdpPort, log: capture.extensionLog, target: "extension", extensionId });
|
|
38
54
|
}
|
|
39
55
|
export {
|
|
56
|
+
EXTENSION_CONSOLE_LAUNCH_FILE,
|
|
40
57
|
ensureExtensionConsoleCapture,
|
|
41
58
|
extensionCaptureFiles,
|
|
42
59
|
extensionCdpPort,
|
|
60
|
+
recordExtensionConsoleLaunch,
|
|
43
61
|
verifyExtensionConsoleControl
|
|
44
62
|
};
|
|
@@ -4,6 +4,7 @@ import { colorHumanMessage, EXIT, shellQuote, spawnScriptStreaming } from "@farm
|
|
|
4
4
|
import { FIXTURE_STREAM_TIMEOUT_MS } from "../../commands/fixtures.js";
|
|
5
5
|
import { sameFeatureFlags } from "../../feature-flags.js";
|
|
6
6
|
import { runnerDir } from "../../paths.js";
|
|
7
|
+
import { recordExtensionConsoleLaunch } from "./console-capture.js";
|
|
7
8
|
import { readExtensionPinnedFlags } from "./feature-flags.js";
|
|
8
9
|
import { releaseArtifactState } from "./runtime-decision.js";
|
|
9
10
|
async function applyExtensionFixture({
|
|
@@ -96,6 +97,7 @@ async function applyExtensionFixture({
|
|
|
96
97
|
}
|
|
97
98
|
process.stderr.write(`${colorHumanMessage(`\u2192 fixtures ${action} extension \u2014 resetting and seeding the slot-owned profile from the existing build\u2026`)}
|
|
98
99
|
`);
|
|
100
|
+
recordExtensionConsoleLaunch(target);
|
|
99
101
|
const liveScript = path.join(runnerDir, "adapters/extension/live.sh");
|
|
100
102
|
const args = ["--target", target, "--launch-existing-dist", "--launch-only", "--reset-profile"];
|
|
101
103
|
const flagKeys = Object.keys(fixtureFlags);
|
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import { readRuntimeContextField, resolveRuntimeContextPath } from "@farmslot/recipe-cli/harness";
|
|
1
|
+
import { readRuntimeContextField, recipeRuntimeDir, resolveRuntimeContextPath } from "@farmslot/recipe-cli/harness";
|
|
2
2
|
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import { ownsRuntimeContext } from "../../../adapters/shared/resolve-slot-ports-core.mjs";
|
|
3
5
|
import { resolveExtensionSlotPorts } from "../slot-ports.js";
|
|
4
6
|
function hasArg(args, needle) {
|
|
5
7
|
return args.some((arg) => arg === needle || arg.startsWith(`${needle}=`));
|
|
6
8
|
}
|
|
7
9
|
function applyExtensionRuntimeEnv(target, args) {
|
|
8
|
-
const contextPath = resolveRuntimeContextPath(target);
|
|
9
|
-
|
|
10
|
-
if (contextExists) {
|
|
10
|
+
const contextPath = [resolveRuntimeContextPath(target), path.join(target, recipeRuntimeDir(), "agentic-runtime.json")].find((file) => fs.existsSync(file) && ownsRuntimeContext(file, target));
|
|
11
|
+
if (contextPath) {
|
|
11
12
|
process.env.RECIPE_RUNTIME_CONTEXT = contextPath;
|
|
12
13
|
if (!process.env.RECIPE_SLOT_ID) {
|
|
13
14
|
const slotId = readRuntimeContextField(contextPath, "slotId");
|
|
@@ -4,6 +4,7 @@ import fs from "node:fs";
|
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { importRecipeHarnessRuntimeBrowserExtension, importRecipeHarnessRuntimeCdp, resolveFarmslotPoolDir, runnerDir } from "../../paths.js";
|
|
7
|
+
import { recordExtensionConsoleLaunch } from "./console-capture.js";
|
|
7
8
|
import { poolFilesForHost } from "../resolve-slot-ports.js";
|
|
8
9
|
import { macosSessionLocked, SESSION_LOCKED_MESSAGE, SESSION_LOCKED_USER_ACTION } from "../../../adapters/shared/session-lock.mjs";
|
|
9
10
|
import { profileProcessPids } from "./validation-process-ownership.js";
|
|
@@ -666,6 +667,7 @@ async function launchLockedValidationBrowser(options) {
|
|
|
666
667
|
fs.mkdirSync(validationProfileRoot, { recursive: true });
|
|
667
668
|
fs.mkdirSync(validationArtifactsRoot, { recursive: true });
|
|
668
669
|
assertNoQuarantinedValidationProfiles(validationProfileRoot);
|
|
670
|
+
recordExtensionConsoleLaunch(options.projectRoot);
|
|
669
671
|
await stopOwnedCdpListener(options.cdpPort, [
|
|
670
672
|
validationRuntimePath,
|
|
671
673
|
path.join(options.projectRoot, recipeRuntimeDir())
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
2
3
|
import { recipeRuntimePath, EXIT, str, writeInteractiveProgress } from "@farmslot/recipe-cli/harness";
|
|
3
4
|
import { applyDeviceTargeting } from "../../commands/device-target.js";
|
|
4
5
|
import { launchMobile } from "../../commands/launch/mobile.js";
|
|
@@ -49,6 +50,14 @@ function metroLaunchEvidence(target) {
|
|
|
49
50
|
if (descriptor !== void 0) fs.closeSync(descriptor);
|
|
50
51
|
}
|
|
51
52
|
}
|
|
53
|
+
function recordMobileConsoleLaunch(target) {
|
|
54
|
+
const file = recipeRuntimePath(target, "app-console.launch.json");
|
|
55
|
+
const launchedAtMs = Date.now();
|
|
56
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
57
|
+
fs.writeFileSync(file, `${JSON.stringify({ launchedAtMs, recordedAt: new Date(launchedAtMs).toISOString() })}
|
|
58
|
+
`);
|
|
59
|
+
}
|
|
60
|
+
const markedLaunches = /* @__PURE__ */ new WeakSet();
|
|
52
61
|
const mobileLaunchProfile = {
|
|
53
62
|
refuseFlags({ platformTarget }) {
|
|
54
63
|
if (!platformTarget) {
|
|
@@ -104,7 +113,14 @@ const mobileLaunchProfile = {
|
|
|
104
113
|
},
|
|
105
114
|
nativeInputs: ["ios", "android", "package.json", "yarn.lock"],
|
|
106
115
|
rebuildCommand: ({ platformTarget }) => `mm-harness launch ${platformTarget} --build`,
|
|
107
|
-
compose: (
|
|
116
|
+
compose: (launch, tier, restartApp, clearMetro) => {
|
|
117
|
+
const { target, platformTarget, machine, remoteFlagRequest } = launch;
|
|
118
|
+
if (restartApp || !markedLaunches.has(launch)) {
|
|
119
|
+
markedLaunches.add(launch);
|
|
120
|
+
recordMobileConsoleLaunch(target);
|
|
121
|
+
}
|
|
122
|
+
return launchMobile(target, platformTarget, tier, machine, restartApp, clearMetro, remoteFlagRequest?.flags);
|
|
123
|
+
},
|
|
108
124
|
classify(_launch, output) {
|
|
109
125
|
if (output.includes("open-device: Android device is locked.")) {
|
|
110
126
|
return {
|
|
@@ -187,5 +203,6 @@ const mobileLaunchProfile = {
|
|
|
187
203
|
evidence: metroLaunchEvidence
|
|
188
204
|
};
|
|
189
205
|
export {
|
|
190
|
-
mobileLaunchProfile
|
|
206
|
+
mobileLaunchProfile,
|
|
207
|
+
recordMobileConsoleLaunch
|
|
191
208
|
};
|
|
@@ -4,7 +4,7 @@ import crypto from "node:crypto";
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import os from "node:os";
|
|
6
6
|
import path from "node:path";
|
|
7
|
-
import { resolveSlotPortsByRepo } from "../../../adapters/shared/resolve-slot-ports-core.mjs";
|
|
7
|
+
import { ownsRuntimeContext, resolveSlotPortsByRepo } from "../../../adapters/shared/resolve-slot-ports-core.mjs";
|
|
8
8
|
const RUNWAY_IOS_METADATA = {
|
|
9
9
|
artifactName: "ios-app-main-dev-expo",
|
|
10
10
|
workflow: "expo-dev-build.yml",
|
|
@@ -177,8 +177,10 @@ function fail(target, platform, code, message, rerunCommand, slot) {
|
|
|
177
177
|
};
|
|
178
178
|
}
|
|
179
179
|
function readSlotContext(target, runtimeDir) {
|
|
180
|
+
const envContext = process.env.RECIPE_RUNTIME_CONTEXT;
|
|
180
181
|
const candidates = [
|
|
181
|
-
|
|
182
|
+
// Another checkout's context would name its slot and simulator.
|
|
183
|
+
envContext && ownsRuntimeContext(envContext, target) ? envContext : void 0,
|
|
182
184
|
runtimeContextPath(target, runtimeDir),
|
|
183
185
|
path.join(target, recipeRuntimeDir(), "agentic-runtime.json"),
|
|
184
186
|
path.join(target, "temp/recipe/runtime/agentic-runtime.json"),
|
|
@@ -57,7 +57,8 @@ const mobileRun = {
|
|
|
57
57
|
// matches its target against it.
|
|
58
58
|
RECIPE_RN_EXPLICIT_PLATFORM: process.env.RECIPE_RN_EXPLICIT_PLATFORM
|
|
59
59
|
}),
|
|
60
|
-
// An installed release artifact is opaque: no
|
|
60
|
+
// An installed release artifact is opaque: it has no dev bridge and no
|
|
61
|
+
// AgentStepHud, so there is nothing to draw the HUD with.
|
|
61
62
|
autoHud: () => process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME !== "1",
|
|
62
63
|
// Always clear a HUD step the engine left on-device (a failed run strands
|
|
63
64
|
// the FAIL banner). Best-effort — never masks the run's real outcome.
|
|
@@ -4,7 +4,7 @@ import crypto from "node:crypto";
|
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { runnerDir } from "../../paths.js";
|
|
7
|
-
const USAGE = "Usage: mm-harness verify --adapter mobile -- [--target <metamask-mobile>] [--artifacts-dir <dir>] [--static-only] [--platform ios|android] [--preflight-mode fast|auto|default|rebuild-native|clean] [--no-auto-start]";
|
|
7
|
+
const USAGE = "Usage: mm-harness verify --adapter mobile -- [--target <metamask-mobile>] [--artifacts-dir <dir>] [--static-only] [--platform ios|android] [--watcher-port <port>] [--preflight-mode fast|auto|default|rebuild-native|clean] [--no-auto-start]";
|
|
8
8
|
const ENV_FILES = [".js.env", ".env", ".env.local"];
|
|
9
9
|
const PREFLIGHT_MODES = ["fast", "auto", "default", "rebuild-native", "clean"];
|
|
10
10
|
const AUTO_START_TRUE = ["1", "true", "TRUE", "True", "yes", "YES", "Yes", "on", "ON", "On"];
|
|
@@ -36,6 +36,7 @@ function parseMobileVerifyArgs(argv, env = process.env) {
|
|
|
36
36
|
let platform = env.RECIPE_HARNESS_PLATFORM || env.PLATFORM || "ios";
|
|
37
37
|
let preflightMode = env.RECIPE_HARNESS_MOBILE_PREFLIGHT_MODE || "fast";
|
|
38
38
|
let autoStart = env.RECIPE_HARNESS_MOBILE_AUTO_START ?? "false";
|
|
39
|
+
let watcherPort;
|
|
39
40
|
for (let i = 0; i < argv.length; i += 1) {
|
|
40
41
|
const arg = argv[i];
|
|
41
42
|
const needsValue = () => {
|
|
@@ -65,6 +66,12 @@ function parseMobileVerifyArgs(argv, env = process.env) {
|
|
|
65
66
|
platform = value;
|
|
66
67
|
break;
|
|
67
68
|
}
|
|
69
|
+
case "--watcher-port": {
|
|
70
|
+
const value = needsValue();
|
|
71
|
+
if (value === void 0) return { kind: "error", message: "Missing value for --watcher-port", exitCode: 2 };
|
|
72
|
+
watcherPort = value;
|
|
73
|
+
break;
|
|
74
|
+
}
|
|
68
75
|
case "--preflight-mode": {
|
|
69
76
|
const value = needsValue();
|
|
70
77
|
if (value === void 0) return { kind: "error", message: "Missing value for --preflight-mode", exitCode: 2 };
|
|
@@ -101,7 +108,8 @@ function parseMobileVerifyArgs(argv, env = process.env) {
|
|
|
101
108
|
staticOnly,
|
|
102
109
|
platform,
|
|
103
110
|
preflightMode,
|
|
104
|
-
autoStart: AUTO_START_TRUE.includes(autoStart)
|
|
111
|
+
autoStart: AUTO_START_TRUE.includes(autoStart),
|
|
112
|
+
...watcherPort === void 0 ? {} : { watcherPort }
|
|
105
113
|
}
|
|
106
114
|
};
|
|
107
115
|
}
|
|
@@ -141,8 +149,8 @@ function buildMobileVerifySummary(input) {
|
|
|
141
149
|
generatedAt: input.generatedAt
|
|
142
150
|
};
|
|
143
151
|
}
|
|
144
|
-
function resolveWatcherPort(target, env = process.env) {
|
|
145
|
-
const explicit = env.WATCHER_PORT || "";
|
|
152
|
+
function resolveWatcherPort(target, env = process.env, flag) {
|
|
153
|
+
const explicit = flag || env.RECIPE_WATCHER_PORT || env.WATCHER_PORT || "";
|
|
146
154
|
if (explicit) return explicit;
|
|
147
155
|
for (const file of ENV_FILES) {
|
|
148
156
|
const full = path.join(target, file);
|
|
@@ -299,7 +307,7 @@ async function runMobileVerify(argv, io = defaultIo) {
|
|
|
299
307
|
io.err(parsed.message);
|
|
300
308
|
return parsed.exitCode;
|
|
301
309
|
}
|
|
302
|
-
const { staticOnly, platform, preflightMode, autoStart } = parsed.options;
|
|
310
|
+
const { staticOnly, platform, preflightMode, autoStart, watcherPort } = parsed.options;
|
|
303
311
|
const target = path.resolve(parsed.options.target);
|
|
304
312
|
if (!fs.existsSync(target)) {
|
|
305
313
|
io.err(`Refusing mobile verify: target checkout does not exist: ${target}`);
|
|
@@ -344,9 +352,9 @@ async function runMobileVerify(argv, io = defaultIo) {
|
|
|
344
352
|
let fixtureStatus = null;
|
|
345
353
|
let portHolder = null;
|
|
346
354
|
if (!staticOnly) {
|
|
347
|
-
const port = resolveWatcherPort(target);
|
|
355
|
+
const port = resolveWatcherPort(target, process.env, watcherPort);
|
|
348
356
|
if (!isNumericWatcherPort(port)) {
|
|
349
|
-
io.err(`Refusing mobile verify: resolved watcher port is not numeric: '${port}' (check WATCHER_PORT)`);
|
|
357
|
+
io.err(`Refusing mobile verify: resolved watcher port is not numeric: '${port}' (check --watcher-port, RECIPE_WATCHER_PORT or WATCHER_PORT)`);
|
|
350
358
|
return 2;
|
|
351
359
|
}
|
|
352
360
|
let deviceTarget = mobileDeviceTarget(target, platform, process.env);
|
|
@@ -9,7 +9,7 @@ const webDappActions = {
|
|
|
9
9
|
"app.performance_assert"
|
|
10
10
|
],
|
|
11
11
|
cdpTarget: { transport: "chrome-cdp", probePath: "json/version" },
|
|
12
|
-
ui: (harness
|
|
12
|
+
ui: (harness) => ({
|
|
13
13
|
base: harness.createCdpWebUiTransport({
|
|
14
14
|
async withPage(input, callback) {
|
|
15
15
|
const { withAppPage } = await import("../../../library/actions/web-dapp/platform/page.mjs");
|
|
@@ -30,14 +30,16 @@ const webDappActions = {
|
|
|
30
30
|
async execute(action, node, context, standard) {
|
|
31
31
|
if (action === "app.hud") {
|
|
32
32
|
const { showAppHud } = await import("../../../library/actions/web-dapp/platform/hud.mjs");
|
|
33
|
-
return showAppHud(
|
|
34
|
-
{ action, node, context: { nodeId: context.nodeId, projectRoot: context.projectRoot } },
|
|
35
|
-
{ hudPolicy }
|
|
36
|
-
);
|
|
33
|
+
return showAppHud({ action, node, context: { nodeId: context.nodeId, projectRoot: context.projectRoot } });
|
|
37
34
|
}
|
|
38
35
|
return standard();
|
|
39
36
|
}
|
|
40
37
|
}),
|
|
38
|
+
// Deletes hud.json (the wallet host redraws from it) and the HUD on the app tabs.
|
|
39
|
+
async clearHud(projectRoot) {
|
|
40
|
+
const { showAppHud } = await import("../../../library/actions/web-dapp/platform/hud.mjs");
|
|
41
|
+
await showAppHud({ node: { clear: true }, context: { nodeId: "hud-clear", projectRoot } });
|
|
42
|
+
},
|
|
41
43
|
// ensure_positions and ensure_orders with an open or present state place an order.
|
|
42
44
|
inputFindings: (nodeId, node) => perpsOrderInputFindings(nodeId, node, { ensureChecksOnly: false })
|
|
43
45
|
};
|
package/dist/adapters.js
CHANGED
|
@@ -90,7 +90,9 @@ const LIVE_ONLY_APP_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
90
90
|
"app.network_capture",
|
|
91
91
|
"app.network_assert",
|
|
92
92
|
"app.performance_capture",
|
|
93
|
-
"app.performance_assert"
|
|
93
|
+
"app.performance_assert",
|
|
94
|
+
// Proves a capture of the running app; a static result would pass on no evidence.
|
|
95
|
+
"metamask.app.assert_no_console_errors"
|
|
94
96
|
]);
|
|
95
97
|
function requiresLiveAdapter(platform, action) {
|
|
96
98
|
return LIVE_ONLY_ACTIONS.has(action) || Boolean(getAdapterSurface(platform).actions.liveOnly?.includes(action)) || LIVE_ONLY_WALLET_ACTIONS.has(action) || LIVE_ONLY_FEATURE_FLAG_ACTIONS.has(action) || LIVE_ONLY_APP_ACTIONS.has(action);
|
|
@@ -290,7 +292,7 @@ function createMetaMaskSemanticAdapters(platform, declaredCustomActions = [], pr
|
|
|
290
292
|
)
|
|
291
293
|
);
|
|
292
294
|
}
|
|
293
|
-
function createMetaMaskUiTransport(platform, harness, preparedLiveAdapters
|
|
295
|
+
function createMetaMaskUiTransport(platform, harness, preparedLiveAdapters) {
|
|
294
296
|
const ui = getAdapterSurface(platform).actions.ui;
|
|
295
297
|
if (!ui) {
|
|
296
298
|
return {
|
|
@@ -301,7 +303,7 @@ function createMetaMaskUiTransport(platform, harness, preparedLiveAdapters, { hu
|
|
|
301
303
|
}
|
|
302
304
|
};
|
|
303
305
|
}
|
|
304
|
-
const { base, execute } = ui(harness, { hudPolicy });
|
|
306
|
+
const { base, execute } = ui(harness, { hudPolicy: "show" });
|
|
305
307
|
async function standard(action, node, context) {
|
|
306
308
|
if (action === "ui.navigate") {
|
|
307
309
|
const live = await runLiveFirst(
|