@deeeed/metamask-harness 0.17.5 → 0.19.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 +1059 -0
- package/README.md +91 -140
- package/adapters/core/inject.sh +1 -6
- package/adapters/extension/console-tail.mjs +55 -24
- package/adapters/extension/ensure-browser.sh +6 -1
- package/adapters/extension/inject.mjs +7 -13
- 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 +2 -2
- 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 +91 -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 +34 -83
- package/adapters/mobile/wait-for-bridge.sh +22 -8
- package/adapters/shared/harness-source-fingerprint.mjs +14 -12
- 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/harness-freshness.js +49 -0
- 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 -18
- package/dist/command-contract.js +462 -0
- package/dist/command-journal.js +263 -0
- package/dist/commands/call.js +252 -57
- package/dist/commands/check.js +10 -4
- package/dist/commands/completion-candidates.js +17 -22
- package/dist/commands/device-target.js +27 -12
- package/dist/commands/doctor.js +106 -25
- package/dist/commands/fixtures.js +142 -35
- package/dist/commands/last.js +60 -0
- package/dist/commands/launch/extension.js +40 -17
- package/dist/commands/launch/index.js +207 -63
- package/dist/commands/launch/mobile.js +2 -0
- package/dist/commands/list-executables.js +187 -29
- package/dist/commands/logs.js +8 -6
- package/dist/commands/manifest.js +286 -35
- package/dist/commands/parse-args.js +68 -4
- package/dist/commands/provision.js +10 -4
- package/dist/commands/run-engine.js +772 -264
- package/dist/commands/run-report.js +12 -3
- package/dist/commands/run.js +378 -85
- package/dist/commands/shared.js +76 -4
- 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 +7 -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 +146 -73
- package/dist/paths.js +2 -5
- package/dist/recipe-security.js +178 -0
- package/dist/run-diagnostics.js +261 -0
- package/dist/run-recording.js +1 -1
- package/dist/runner.js +140 -9
- package/docs/CONTRIBUTING.md +136 -0
- package/docs/QA.md +184 -0
- package/docs/RECIPES.md +122 -0
- package/docs/SECURITY.md +88 -0
- package/library/README.md +7 -3
- package/library/actions/core/perps/_controller.mjs +43 -56
- package/library/actions/core/perps/assert_orders.mjs +6 -7
- package/library/actions/core/perps/assert_positions.mjs +6 -7
- package/library/actions/core/perps/close_orders.mjs +2 -0
- package/library/actions/core/perps/close_positions.mjs +2 -0
- package/library/actions/core/perps/ensure_orders.mjs +4 -2
- package/library/actions/core/perps/ensure_positions.mjs +4 -2
- package/library/actions/core/perps/place_order.mjs +7 -3
- 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/perps/assert_orders.mjs +2 -1
- package/library/actions/extension/perps/assert_positions.mjs +2 -1
- package/library/actions/extension/perps/perps.mjs +43 -14
- package/library/actions/extension/platform/cdp.mjs +1 -0
- package/library/actions/extension/wallet/list_accounts.mjs +41 -0
- package/library/actions/mobile/perps/assert_orders.mjs +2 -1
- package/library/actions/mobile/perps/assert_positions.mjs +2 -1
- package/library/actions/mobile/perps/perps.mjs +40 -12
- package/library/actions/mobile/wallet/list_accounts.mjs +37 -0
- package/library/library.json +1 -1
- package/library/manifests/core.action-manifest.json +1214 -396
- package/library/manifests/extension.action-manifest.json +1635 -728
- package/library/manifests/mobile.action-manifest.json +1740 -727
- package/library/recipes/app/lifecycle.android-smoke.mobile.recipe.json +63 -81
- package/library/recipes/perps/clean-market-testnet.core.recipe.json +44 -0
- package/library/recipes/perps/clean-market-testnet.recipe.json +49 -0
- package/library/recipes/perps/lifecycle.recipe.json +136 -180
- package/library/recipes/perps/order-lifecycle.core.recipe.json +71 -67
- package/library/recipes/perps/performance.background-resume.mobile.recipe.json +51 -67
- package/library/recipes/perps/performance.cold-start.mobile.recipe.json +51 -67
- package/library/recipes/perps/performance.mobile.recipe.json +37 -51
- package/library/recipes/perps/performance.warm-start.mobile.recipe.json +44 -59
- package/library/recipes/perps/read-markets.core.recipe.json +29 -31
- package/library/recipes/perps/smoke.core.recipe.json +29 -32
- package/library/recipes/perps/smoke.extension.recipe.json +41 -44
- package/library/recipes/perps/smoke.mobile.recipe.json +42 -44
- package/library/recipes/perps/trading-lifecycle.core.recipe.json +69 -65
- package/library/recipes/runner/action-validation.extension.recipe.json +312 -398
- package/library/recipes/runner/action-validation.mobile.recipe.json +316 -402
- package/library/recipes/runner/smoke.core.recipe.json +25 -0
- package/library/recipes/runner/smoke.extension.recipe.json +23 -24
- package/library/recipes/runner/smoke.mobile.recipe.json +23 -24
- package/library/recipes/wallet/smoke.extension.recipe.json +40 -0
- package/library/recipes/wallet/smoke.mobile.recipe.json +40 -0
- package/package.json +12 -5
- package/scripts/completions.sh +8 -11
- 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/dist/adapters/extension/harness-freshness.js +0 -39
- package/dist/commands/flows.js +0 -62
- 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
- package/library/flows/perps.flows.json +0 -64
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
formatKvLines,
|
|
3
|
+
formatExtensionIdentityPorts,
|
|
3
4
|
formatExtensionSlotPorts,
|
|
4
5
|
inferSlotSuffix,
|
|
5
6
|
realRepoPath,
|
|
@@ -7,12 +8,12 @@ import {
|
|
|
7
8
|
resolveExtensionRuntimeContext,
|
|
8
9
|
resolveExtensionRuntimePorts,
|
|
9
10
|
resolveSlotPortsByRepo,
|
|
10
|
-
resolveFarmslotPortsByRepo,
|
|
11
11
|
resolveMobileRuntimeContext,
|
|
12
12
|
resolveMobileRuntimePorts,
|
|
13
13
|
resolveMobileSlotDefaults
|
|
14
14
|
} from "../../adapters/shared/resolve-slot-ports-core.mjs";
|
|
15
15
|
export {
|
|
16
|
+
formatExtensionIdentityPorts,
|
|
16
17
|
formatExtensionSlotPorts,
|
|
17
18
|
formatKvLines,
|
|
18
19
|
inferSlotSuffix,
|
|
@@ -20,7 +21,6 @@ export {
|
|
|
20
21
|
resolveDefaultExtensionPorts,
|
|
21
22
|
resolveExtensionRuntimeContext,
|
|
22
23
|
resolveExtensionRuntimePorts,
|
|
23
|
-
resolveFarmslotPortsByRepo,
|
|
24
24
|
resolveMobileRuntimeContext,
|
|
25
25
|
resolveMobileRuntimePorts,
|
|
26
26
|
resolveMobileSlotDefaults,
|
|
@@ -3,8 +3,8 @@ import fs from "node:fs";
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { recipeRuntimeDir } from "../paths.js";
|
|
5
5
|
import {
|
|
6
|
+
formatExtensionIdentityPorts,
|
|
6
7
|
resolveDefaultExtensionPorts,
|
|
7
|
-
resolveExtensionRuntimeContext,
|
|
8
8
|
resolveMobileRuntimeContext,
|
|
9
9
|
resolveSlotPortsByRepo,
|
|
10
10
|
resolveMobileSlotDefaults
|
|
@@ -55,12 +55,10 @@ function resolveMobileSlotPorts(target) {
|
|
|
55
55
|
}
|
|
56
56
|
function resolveExtensionSlotPorts(target) {
|
|
57
57
|
const resolved = path.resolve(target);
|
|
58
|
-
const
|
|
59
|
-
if (
|
|
60
|
-
const
|
|
61
|
-
if (
|
|
62
|
-
const defOut = resolveDefaultExtensionPorts(resolved);
|
|
63
|
-
if (defOut?.trim()) applyKVLines(defOut, false);
|
|
58
|
+
const identity = formatExtensionIdentityPorts(resolved);
|
|
59
|
+
if (identity.trim()) applyKVLines(identity, true);
|
|
60
|
+
const fallback = resolveDefaultExtensionPorts(resolved);
|
|
61
|
+
if (fallback?.trim()) applyKVLines(fallback, false);
|
|
64
62
|
}
|
|
65
63
|
function extensionWatcherPids(target) {
|
|
66
64
|
const resolved = path.resolve(target);
|
|
@@ -78,7 +76,9 @@ function extensionWatcherPids(target) {
|
|
|
78
76
|
const match = /^\s*(\d+)\s+(.*)$/u.exec(line);
|
|
79
77
|
if (!match) continue;
|
|
80
78
|
const [, pidStr, cmd] = match;
|
|
81
|
-
const
|
|
79
|
+
const executable = path.basename(cmd.trim().split(/\s+/u)[0] ?? "");
|
|
80
|
+
const isNodeProcess = executable === "node" || executable === "nodejs" || executable === "yarn";
|
|
81
|
+
const isWatcher = isNodeProcess && (/(?:^|\/)yarn start(?:\s|$)/u.test(cmd) || /(?:^|\/)yarn\.js webpack --watch(?:\s|$)/u.test(cmd) || /development\/webpack\/launch\.ts --watch(?:\s|$)/u.test(cmd));
|
|
82
82
|
if (!isWatcher) continue;
|
|
83
83
|
if (cmd.includes(resolved)) {
|
|
84
84
|
ownedPids.add(Number(pidStr));
|
|
@@ -127,9 +127,8 @@ function stopExtensionWatcher(target) {
|
|
|
127
127
|
function stopExtensionRuntime(target) {
|
|
128
128
|
const resolved = path.resolve(target);
|
|
129
129
|
const runtimeAbs = path.join(resolved, recipeRuntimeDir());
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
);
|
|
130
|
+
const configuredProfile = process.env.CHROME_USER_DATA_DIR || path.join(runtimeAbs, process.env.RECIPE_CHROME_PROFILE_NAME || "chrome-profile");
|
|
131
|
+
const profiles = process.env.CHROME_USER_DATA_DIR ? [path.resolve(configuredProfile)] : [configuredProfile, path.join(runtimeAbs, "chrome-profile-recipe"), path.join(runtimeAbs, "chrome-profile-pw")].map((value) => path.resolve(value));
|
|
133
132
|
let signalled = stopExtensionWatcher(resolved);
|
|
134
133
|
const ownedBrowserPids = /* @__PURE__ */ new Set();
|
|
135
134
|
try {
|
|
@@ -138,9 +137,7 @@ function stopExtensionRuntime(target) {
|
|
|
138
137
|
const match = /^\s*(\d+)\s+(.*)$/u.exec(line);
|
|
139
138
|
if (!match) continue;
|
|
140
139
|
const pid = Number(match[1]);
|
|
141
|
-
if (pid !== process.pid && commandHasExactArg(match[2], "--user-data-dir", profile))
|
|
142
|
-
ownedBrowserPids.add(pid);
|
|
143
|
-
}
|
|
140
|
+
if (pid !== process.pid && profiles.some((profile) => commandHasExactArg(match[2], "--user-data-dir", profile))) ownedBrowserPids.add(pid);
|
|
144
141
|
}
|
|
145
142
|
} catch {
|
|
146
143
|
}
|
|
@@ -195,8 +192,8 @@ function processAlive(pid) {
|
|
|
195
192
|
try {
|
|
196
193
|
process.kill(pid, 0);
|
|
197
194
|
return true;
|
|
198
|
-
} catch {
|
|
199
|
-
return
|
|
195
|
+
} catch (error) {
|
|
196
|
+
return error.code === "EPERM";
|
|
200
197
|
}
|
|
201
198
|
}
|
|
202
199
|
function commandHasExactArg(command, flag, value) {
|
package/dist/adapters.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
2
|
import { compatibilityMode, fixtureSummary, repoShape } from "./doctor.js";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
runLiveAdapterScript
|
|
5
|
+
} from "./live-adapter-contract.js";
|
|
4
6
|
import { withExtensionPage } from "../library/actions/extension/platform/cdp.mjs";
|
|
5
7
|
import { bridgeCommand, evalSync, MOBILE_BRIDGE_ERROR_CODES, selectBridgeStatusEntry, simulatorScreenshot } from "../library/actions/mobile/platform/bridge.mjs";
|
|
6
8
|
function sleep(ms) {
|
|
@@ -32,6 +34,7 @@ const LIVE_ONLY_WALLET_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
32
34
|
"metamask.wallet.setup",
|
|
33
35
|
"metamask.wallet.ensure_unlocked",
|
|
34
36
|
"metamask.wallet.select_account",
|
|
37
|
+
"metamask.wallet.list_accounts",
|
|
35
38
|
"metamask.wallet.read_state"
|
|
36
39
|
]);
|
|
37
40
|
const LIVE_ONLY_APP_ACTIONS = /* @__PURE__ */ new Set(["ui.navigate"]);
|
|
@@ -42,14 +45,20 @@ function liveRuntimeConfigured(platform, node) {
|
|
|
42
45
|
if (platform === "mobile" || platform === "core") return true;
|
|
43
46
|
return Boolean(node.cdp_port ?? process.env.CDP_PORT ?? process.env.RECIPE_CDP_PORT);
|
|
44
47
|
}
|
|
45
|
-
async function runLiveFirst(platform, action, node, context) {
|
|
48
|
+
async function runLiveFirst(platform, action, node, context, forceLive = false, preparedLiveAdapters) {
|
|
46
49
|
if (node.allow_static_placeholder === true) {
|
|
47
50
|
throw new Error(
|
|
48
51
|
`${action} refused allow_static_placeholder. MetaMask runner proof runs must use live adapters and real artifacts.`
|
|
49
52
|
);
|
|
50
53
|
}
|
|
51
|
-
if (!requiresLiveAdapter(platform, action) && !liveRuntimeConfigured(platform, node)) return null;
|
|
52
|
-
const live = await runLiveAdapterScript({
|
|
54
|
+
if (!forceLive && !requiresLiveAdapter(platform, action) && !liveRuntimeConfigured(platform, node)) return null;
|
|
55
|
+
const live = await runLiveAdapterScript({
|
|
56
|
+
platform,
|
|
57
|
+
action,
|
|
58
|
+
node,
|
|
59
|
+
context,
|
|
60
|
+
prepared: preparedLiveAdapters?.get(action)
|
|
61
|
+
});
|
|
53
62
|
if (!live) return null;
|
|
54
63
|
const liveResult = isRecord(live.result) ? live.result : { result: live.result };
|
|
55
64
|
const output = { ...liveResult, liveAdapter: live.script };
|
|
@@ -64,10 +73,17 @@ function liveAdapterPathHint(platform, action) {
|
|
|
64
73
|
}
|
|
65
74
|
return `library/actions/${platform}/${action.replaceAll(".", "/")}.mjs`;
|
|
66
75
|
}
|
|
67
|
-
async function semanticResult(platform, action, node, context) {
|
|
68
|
-
const live = await runLiveFirst(
|
|
76
|
+
async function semanticResult(platform, action, node, context, forceLive = false, preparedLiveAdapters) {
|
|
77
|
+
const live = await runLiveFirst(
|
|
78
|
+
platform,
|
|
79
|
+
action,
|
|
80
|
+
node,
|
|
81
|
+
context,
|
|
82
|
+
forceLive,
|
|
83
|
+
preparedLiveAdapters
|
|
84
|
+
);
|
|
69
85
|
if (live) return live;
|
|
70
|
-
if (requiresLiveAdapter(platform, action)) {
|
|
86
|
+
if (forceLive || requiresLiveAdapter(platform, action)) {
|
|
71
87
|
const expected = liveAdapterPathHint(platform, action);
|
|
72
88
|
throw new Error(
|
|
73
89
|
`${action} requires a live ${platform} adapter that drives a real supported app/API path; no adapter script was found or no live runtime is configured (for example ${expected}). Static placeholders are refused to avoid fabricated proof. Set METAMASK_RECIPE_LIVE_ADAPTER_DIR or add a runner library/actions script.`
|
|
@@ -145,15 +161,16 @@ function probeHttpJson(url, timeoutMs = 1e3) {
|
|
|
145
161
|
});
|
|
146
162
|
});
|
|
147
163
|
}
|
|
148
|
-
function createMetaMaskSemanticAdapters(platform) {
|
|
164
|
+
function createMetaMaskSemanticAdapters(platform, declaredCustomActions = [], preparedLiveAdapters) {
|
|
149
165
|
const walletActions = [
|
|
150
166
|
"metamask.wallet.fixture_status",
|
|
151
167
|
"metamask.wallet.setup",
|
|
152
168
|
"metamask.wallet.ensure_unlocked",
|
|
153
169
|
"metamask.wallet.select_account",
|
|
170
|
+
"metamask.wallet.list_accounts",
|
|
154
171
|
"metamask.wallet.read_state"
|
|
155
172
|
];
|
|
156
|
-
const
|
|
173
|
+
const bundledActions = [
|
|
157
174
|
...walletActions,
|
|
158
175
|
"metamask.perps.read_positions",
|
|
159
176
|
"metamask.perps.ensure_positions",
|
|
@@ -169,10 +186,19 @@ function createMetaMaskSemanticAdapters(platform) {
|
|
|
169
186
|
// read_account is core-only: only the headless core adapter implements it.
|
|
170
187
|
...platform === "core" ? ["metamask.perps.read_account"] : []
|
|
171
188
|
];
|
|
189
|
+
const bundled = new Set(bundledActions);
|
|
190
|
+
const actions = [.../* @__PURE__ */ new Set([...bundledActions, ...declaredCustomActions])];
|
|
172
191
|
return actions.map(
|
|
173
192
|
(action) => simpleAdapter(
|
|
174
193
|
action,
|
|
175
|
-
async (node, context) => semanticResult(
|
|
194
|
+
async (node, context) => semanticResult(
|
|
195
|
+
platform,
|
|
196
|
+
action,
|
|
197
|
+
node,
|
|
198
|
+
context,
|
|
199
|
+
!bundled.has(action),
|
|
200
|
+
preparedLiveAdapters
|
|
201
|
+
)
|
|
176
202
|
)
|
|
177
203
|
);
|
|
178
204
|
}
|
|
@@ -287,8 +313,15 @@ async function handleMobileNavigate(payload, context) {
|
|
|
287
313
|
return isRecord(live.result) ? { ...live.result, liveAdapter: live.script } : live.result;
|
|
288
314
|
}
|
|
289
315
|
async function handleMobilePress(payload, context) {
|
|
290
|
-
const
|
|
291
|
-
|
|
316
|
+
const identifiers = ["test_id", "testID", "selector"].map((field) => optionalScalarText(payload[field], `ui.press.${field}`)).filter((value) => value !== void 0);
|
|
317
|
+
const text = optionalScalarText(payload.text, "ui.press.text");
|
|
318
|
+
if (identifiers.length + Number(text !== void 0) !== 1) {
|
|
319
|
+
throw new Error("ui.press requires exactly one of: test_id, testID, selector, text.");
|
|
320
|
+
}
|
|
321
|
+
const target = text ?? identifiers[0];
|
|
322
|
+
if (target.trim() === "") throw new Error("ui.press target must be non-empty.");
|
|
323
|
+
const input = mobileUiInput(context, "press", payload);
|
|
324
|
+
return text !== void 0 ? bridgeCommand(input, ["press-text", target]) : bridgeCommand(input, ["press-test-id", target]);
|
|
292
325
|
}
|
|
293
326
|
async function handleMobileSetInput(payload, context) {
|
|
294
327
|
const input = mobileUiInput(context, "set_input", payload);
|
|
@@ -432,7 +465,7 @@ function mobileHudStatusLabel(status) {
|
|
|
432
465
|
if (status === "pass") return "pass";
|
|
433
466
|
return "run";
|
|
434
467
|
}
|
|
435
|
-
function createMetaMaskUiTransport(platform, harness) {
|
|
468
|
+
function createMetaMaskUiTransport(platform, harness, preparedLiveAdapters) {
|
|
436
469
|
if (platform === "core") {
|
|
437
470
|
return {
|
|
438
471
|
async execute(action) {
|
|
@@ -461,7 +494,14 @@ function createMetaMaskUiTransport(platform, harness) {
|
|
|
461
494
|
return {
|
|
462
495
|
async execute(action, node, context) {
|
|
463
496
|
if (action === "ui.navigate") {
|
|
464
|
-
const live = await runLiveFirst(
|
|
497
|
+
const live = await runLiveFirst(
|
|
498
|
+
platform,
|
|
499
|
+
action,
|
|
500
|
+
node,
|
|
501
|
+
context,
|
|
502
|
+
false,
|
|
503
|
+
preparedLiveAdapters
|
|
504
|
+
);
|
|
465
505
|
if (live) return live.output;
|
|
466
506
|
throw new Error(`ui.navigate requires library/actions/${platform}/ui/navigate.mjs.`);
|
|
467
507
|
}
|
|
@@ -473,7 +513,7 @@ function normalizeUiWaitNode(node) {
|
|
|
473
513
|
if (node.expected !== void 0 || typeof node.visible !== "boolean") return node;
|
|
474
514
|
return { ...node, expected: node.visible ? "visible" : "absent" };
|
|
475
515
|
}
|
|
476
|
-
function createMetaMaskAdapters(adapter) {
|
|
516
|
+
function createMetaMaskAdapters(adapter, declaredCustomActions = [], preparedLiveAdapters) {
|
|
477
517
|
const platform = adapter;
|
|
478
518
|
const adapters = [
|
|
479
519
|
simpleAdapter("app.status", async (_node, context) => ({
|
|
@@ -525,7 +565,7 @@ function createMetaMaskAdapters(adapter) {
|
|
|
525
565
|
}
|
|
526
566
|
};
|
|
527
567
|
}),
|
|
528
|
-
...createMetaMaskSemanticAdapters(platform)
|
|
568
|
+
...createMetaMaskSemanticAdapters(platform, declaredCustomActions, preparedLiveAdapters)
|
|
529
569
|
];
|
|
530
570
|
return adapters;
|
|
531
571
|
}
|
package/dist/checkout-lock.js
CHANGED
|
@@ -7,7 +7,7 @@ function acquireCheckoutLock(target, operation) {
|
|
|
7
7
|
const lockPath = path.join(repoRoot, recipeRuntimeDir(), "sandbox.lock");
|
|
8
8
|
fs.mkdirSync(path.dirname(lockPath), { recursive: true });
|
|
9
9
|
const inheritedToken = process.env.MM_HARNESS_CHECKOUT_LOCK_TOKEN;
|
|
10
|
-
for (let attempt = 0; attempt <
|
|
10
|
+
for (let attempt = 0; attempt < 4; attempt += 1) {
|
|
11
11
|
try {
|
|
12
12
|
const fd = fs.openSync(lockPath, "wx", 384);
|
|
13
13
|
const token = inheritedToken || randomUUID();
|
|
@@ -46,11 +46,36 @@ function acquireCheckoutLock(target, operation) {
|
|
|
46
46
|
message: `checkout sandbox is busy with ${String(owner.operation ?? "another operation")} (pid ${owner.pid}).`
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
|
-
|
|
49
|
+
claimStaleLock(lockPath, owner);
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
return { path: lockPath, owner: readOwner(lockPath), message: "checkout sandbox lock could not be acquired." };
|
|
53
53
|
}
|
|
54
|
+
function claimStaleLock(lockPath, expectedOwner) {
|
|
55
|
+
const claimedPath = `${lockPath}.stale-${process.pid}-${randomUUID()}`;
|
|
56
|
+
try {
|
|
57
|
+
fs.renameSync(lockPath, claimedPath);
|
|
58
|
+
} catch (error) {
|
|
59
|
+
if (error.code === "ENOENT") return;
|
|
60
|
+
throw error;
|
|
61
|
+
}
|
|
62
|
+
try {
|
|
63
|
+
const claimedOwner = readOwner(claimedPath);
|
|
64
|
+
if (!sameOwner(claimedOwner, expectedOwner) || claimedOwner && typeof claimedOwner.pid === "number" && processAlive(claimedOwner.pid)) {
|
|
65
|
+
try {
|
|
66
|
+
fs.linkSync(claimedPath, lockPath);
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error.code !== "EEXIST") throw error;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
} finally {
|
|
72
|
+
fs.rmSync(claimedPath, { force: true });
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
function sameOwner(left, right) {
|
|
76
|
+
if (!left || !right) return left === right;
|
|
77
|
+
return left.pid === right.pid && left.token === right.token && left.startedAt === right.startedAt && left.repoRoot === right.repoRoot;
|
|
78
|
+
}
|
|
54
79
|
function readOwner(lockPath) {
|
|
55
80
|
try {
|
|
56
81
|
const value = JSON.parse(fs.readFileSync(lockPath, "utf8"));
|
package/dist/cli-color.js
CHANGED
|
@@ -47,6 +47,24 @@ function colorStatusWord(word, { stream = process.stderr } = {}) {
|
|
|
47
47
|
}
|
|
48
48
|
return color("info", word, { stream });
|
|
49
49
|
}
|
|
50
|
+
function colorHumanMessage(message, { stream = process.stderr } = {}) {
|
|
51
|
+
return String(message).split("\n").map((line) => {
|
|
52
|
+
const marker = /^(\s*)(→|✓|✗)\s?(.*)$/u.exec(line);
|
|
53
|
+
if (marker) {
|
|
54
|
+
const style = marker[2] === "\u2713" ? "ok" : marker[2] === "\u2717" ? "err" : "accent";
|
|
55
|
+
return `${marker[1]}${color(style, marker[2], { stream })}${marker[3] ? ` ${marker[3]}` : ""}`;
|
|
56
|
+
}
|
|
57
|
+
const next = /^(\s*)Next:\s*(.*)$/u.exec(line);
|
|
58
|
+
if (next) {
|
|
59
|
+
return `${next[1]}${color("label", "Next:", { stream })}${next[2] ? ` ${color("cmd", next[2], { stream })}` : ""}`;
|
|
60
|
+
}
|
|
61
|
+
const warning = /^(\s*)(WARN(?:ING)?)(:?)[ \t]*(.*)$/iu.exec(line);
|
|
62
|
+
if (warning) {
|
|
63
|
+
return `${warning[1]}${color("warn", `${warning[2]}${warning[3]}`, { stream })}${warning[4] ? ` ${warning[4]}` : ""}`;
|
|
64
|
+
}
|
|
65
|
+
return line;
|
|
66
|
+
}).join("\n");
|
|
67
|
+
}
|
|
50
68
|
function classifyLogEvent(event) {
|
|
51
69
|
const text = String(event || "");
|
|
52
70
|
if (/Module build failed|^ERROR in |BUILD FAILED|compiled with [1-9][0-9]* error/iu.test(text)) {
|
|
@@ -72,6 +90,7 @@ export {
|
|
|
72
90
|
classifyLogEvent,
|
|
73
91
|
color,
|
|
74
92
|
colorEnabled,
|
|
93
|
+
colorHumanMessage,
|
|
75
94
|
colorKv,
|
|
76
95
|
colorLogEvent,
|
|
77
96
|
colorStatusWord,
|
package/dist/cli-commands.js
CHANGED
|
@@ -15,7 +15,7 @@ const SPEC = {
|
|
|
15
15
|
{ name: "logs", aliases: ["tail"], desc: "Compact build events or full log", flags: ["--full", "-f", "--window", "--events", "--source", "--json"] },
|
|
16
16
|
{ name: "debug", aliases: ["devtools", "inspect"], desc: "Open DevTools UI", flags: ["--json", "--no-open"] },
|
|
17
17
|
{ name: "fixtures", desc: "Manage the canonical wallet fixture (sync/set/generate)", args: ["sync", "set", "generate"], flags: ["--fixture", "--out", "--adapter", "--target", "--device", "--json"] },
|
|
18
|
-
{ name: "actions", desc: "List runnable recipe actions", flags: ["--json"] },
|
|
18
|
+
{ name: "actions", desc: "List runnable recipe actions", flags: ["--json", "--categories", "--category", "--action", "--library"] },
|
|
19
19
|
{ name: "doctor", desc: "Check harness/orchestration health", flags: ["--json", "--target", "--adapter", "--runtime-dir", "--expect-live", "--print-ready", "--cdp-port", "--device"] },
|
|
20
20
|
{ name: "run", desc: "Execute a proof recipe (path or library name, e.g. run perps.smoke)", args: ["recipe.json|name"], flags: ["--list", "--device"] },
|
|
21
21
|
{ name: "recipe-quality", desc: "Build the recipe-quality artifact from compact JSON", args: ["build"], flags: ["--input", "--output", "--json"] },
|
package/dist/cli.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { detectAdapter, handleHarness } from "./harness.js";
|
|
2
|
-
import { handleActions
|
|
2
|
+
import { handleActions } from "./commands/manifest.js";
|
|
3
3
|
import { handleDoctor } from "./commands/doctor.js";
|
|
4
4
|
import { handleProvision } from "./commands/provision.js";
|
|
5
5
|
import { handleRuntimeHealth } from "./commands/runtime-health.js";
|
|
@@ -11,7 +11,6 @@ import { handleRun } from "./commands/run.js";
|
|
|
11
11
|
import { handleSelfTest } from "./commands/self-test.js";
|
|
12
12
|
import { handleCall } from "./commands/call.js";
|
|
13
13
|
import { handleStop } from "./commands/stop.js";
|
|
14
|
-
import { handleFlows } from "./commands/flows.js";
|
|
15
14
|
import { handleCompletionCandidates, invalidateCompletionCache } from "./commands/completion-candidates.js";
|
|
16
15
|
import { handleLaunch } from "./commands/launch/index.js";
|
|
17
16
|
import { handleLogs } from "./commands/logs.js";
|
|
@@ -21,10 +20,9 @@ import { handleRecipeQuality } from "./commands/recipe-quality.js";
|
|
|
21
20
|
import { handleStatus } from "./commands/status.js";
|
|
22
21
|
import { handleCheck } from "./commands/check.js";
|
|
23
22
|
import { handleChecklist } from "./commands/checklist.js";
|
|
23
|
+
import { handleLast } from "./commands/last.js";
|
|
24
24
|
import { parseArgs, targetPath } from "./commands/parse-args.js";
|
|
25
|
-
import { runOneNode } from "./commands/run-engine.js";
|
|
26
25
|
const COMMANDS = {
|
|
27
|
-
manifest: handleManifest,
|
|
28
26
|
actions: handleActions,
|
|
29
27
|
doctor: handleDoctor,
|
|
30
28
|
"runtime-health": handleRuntimeHealth,
|
|
@@ -35,7 +33,7 @@ const COMMANDS = {
|
|
|
35
33
|
run: handleRun,
|
|
36
34
|
"self-test": handleSelfTest
|
|
37
35
|
};
|
|
38
|
-
const OVERLAY_COMMANDS = ["install", "verify", "cleanup"
|
|
36
|
+
const OVERLAY_COMMANDS = ["install", "verify", "cleanup"];
|
|
39
37
|
function usage() {
|
|
40
38
|
console.error(`mm-harness \u2014 the MetaMask recipe harness: launch the app, prove behavior, manage the runtime overlay.
|
|
41
39
|
Run it from inside a MetaMask checkout; the platform (mobile | extension | core) is auto-detected.
|
|
@@ -54,17 +52,12 @@ DAILY LOOP \u2014 what a teammate runs many times a day:
|
|
|
54
52
|
PROVE \u2014 run recipes and inspect capabilities:
|
|
55
53
|
run Run a recipe and write evidence (summary/trace/artifacts).
|
|
56
54
|
mm-harness run recipe.json
|
|
57
|
-
flows List/promote recipe library flows (personal/team win over canonical).
|
|
58
|
-
mm-harness flows list
|
|
59
55
|
doctor Readiness check for a checkout (no app launch).
|
|
60
56
|
mm-harness doctor
|
|
61
57
|
check Run bounded repo-local checks for an active diff.
|
|
62
58
|
mm-harness check diff --profile fast --artifacts-dir artifacts/validation
|
|
63
59
|
actions Describe the actions a manifest declares.
|
|
64
60
|
mm-harness actions --adapter mobile
|
|
65
|
-
manifest Print/validate the action manifest for an adapter.
|
|
66
|
-
mm-harness manifest --adapter extension --json
|
|
67
|
-
|
|
68
61
|
RUNTIME OVERLAY \u2014 install/verify/clean the per-checkout runtime overlay:
|
|
69
62
|
provision Install the cached Runway mobile dev client (artifact cache + sim install).
|
|
70
63
|
mm-harness provision runway ios --adapter mobile
|
|
@@ -72,12 +65,8 @@ RUNTIME OVERLAY \u2014 install/verify/clean the per-checkout runtime overlay:
|
|
|
72
65
|
mm-harness install # inside a checkout, auto-detected
|
|
73
66
|
verify Check the overlay/runtime is present and healthy (no launch).
|
|
74
67
|
mm-harness verify
|
|
75
|
-
live Launch/reuse the app, then verify live control end-to-end.
|
|
76
|
-
mm-harness live
|
|
77
68
|
cleanup Remove the installed overlay and restore the checkout.
|
|
78
69
|
mm-harness cleanup
|
|
79
|
-
# Open sub-question (Arthur): keep these top-level (mm-harness install) or group
|
|
80
|
-
# them under an \`overlay\` command (mm-harness overlay install)? Top-level for now.
|
|
81
70
|
|
|
82
71
|
ADVANCED \u2014 internal runtime probes (rarely typed by hand):
|
|
83
72
|
runtime-health runtime-decision runtime-launch
|
|
@@ -88,7 +77,7 @@ ONE bin: mm-harness is the only command. No per-platform binaries \u2014 platfor
|
|
|
88
77
|
auto-detected, the positional target forces it (mm-harness launch ios), and
|
|
89
78
|
platform-specific needs are FLAGS on the same command (e.g. --sidebar, --full-build).
|
|
90
79
|
Human happy path = the bare command; agents add depth via flags (--json, --target, ports).
|
|
91
|
-
See
|
|
80
|
+
See README.md for the workflow and docs/RECIPES.md for proof authoring.
|
|
92
81
|
`);
|
|
93
82
|
}
|
|
94
83
|
async function main(argv) {
|
|
@@ -100,7 +89,6 @@ async function main(argv) {
|
|
|
100
89
|
if (command === "status" || command === "health" || command === "home") {
|
|
101
90
|
return handleStatus(parseArgs(argv.slice(1), "status"));
|
|
102
91
|
}
|
|
103
|
-
if (command === "flows") return handleFlows(argv.slice(1));
|
|
104
92
|
if (command === "call") return handleCall(argv.slice(1));
|
|
105
93
|
if (command === "completion-candidates") return handleCompletionCandidates(argv.slice(1));
|
|
106
94
|
if (command === "provision") return handleProvision(parseArgs(argv.slice(1), command));
|
|
@@ -111,7 +99,7 @@ async function main(argv) {
|
|
|
111
99
|
const target = targetPath(options);
|
|
112
100
|
const adapterRaw = (typeof options["adapter"] === "string" ? options["adapter"] : void 0) ?? (typeof options["platform"] === "string" ? options["platform"] : void 0) ?? detectAdapter(target);
|
|
113
101
|
if (adapterRaw === "mobile" || adapterRaw === "extension" || adapterRaw === "core") {
|
|
114
|
-
await handleHarness(["install", "--platform", adapterRaw, "--target", target]);
|
|
102
|
+
await handleHarness(["install", "--platform", adapterRaw, "--target", target, "--quiet"]);
|
|
115
103
|
}
|
|
116
104
|
} catch {
|
|
117
105
|
}
|
|
@@ -129,10 +117,11 @@ async function main(argv) {
|
|
|
129
117
|
if (command === "stop") return handleStop(argv.slice(1));
|
|
130
118
|
if (command === "logs") return handleLogs(argv.slice(1));
|
|
131
119
|
if (command === "debug") return handleDebug(argv.slice(1));
|
|
132
|
-
if (command === "fixtures") return handleFixtures(argv.slice(1)
|
|
120
|
+
if (command === "fixtures") return handleFixtures(argv.slice(1));
|
|
133
121
|
if (command === "recipe-quality") return handleRecipeQuality(argv.slice(1));
|
|
134
122
|
if (command === "check") return handleCheck(argv.slice(1));
|
|
135
123
|
if (command === "checklist") return handleChecklist(argv.slice(1));
|
|
124
|
+
if (command === "last") return handleLast(parseArgs(argv.slice(1), command));
|
|
136
125
|
const handler = COMMANDS[command];
|
|
137
126
|
if (!handler) throw new Error(`Unknown command: ${command}`);
|
|
138
127
|
return handler(parseArgs(argv.slice(1), command));
|