@deeeed/metamask-harness 0.87.0 → 0.89.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 +33 -0
- package/adapters/extension/ensure-browser.sh +6 -10
- package/adapters/extension/inject.mjs +1 -0
- package/adapters/extension/launch-browser.cjs +31 -4
- package/adapters/extension/profile-extension-state.cjs +156 -0
- package/adapters/extension/readiness.mjs +21 -2
- package/adapters/manifest.json +8 -0
- package/adapters/mobile/wait-for-bridge.cjs +9 -3
- package/dist/adapters/extension/actions.js +2 -1
- package/dist/adapters/extension/run.js +6 -0
- package/dist/adapters/mobile/android-screenrecord.js +10 -6
- package/dist/adapters/mobile/recording-outcome.js +2 -51
- package/dist/adapters/mobile/run.js +10 -4
- package/dist/adapters/perps-mainnet-cleanup.js +122 -0
- package/dist/engine.js +11 -2
- package/dist/funding-execution-context.js +13 -8
- package/dist/host.js +6 -2
- package/dist/index.js +3 -0
- package/dist/mm-harness-cli.js +4 -2
- package/dist/provider.js +74 -0
- package/dist/recipe-security.js +1 -0
- package/docs/CONTRIBUTING.md +7 -0
- package/docs/RECIPES.md +38 -13
- package/docs/RELEASE-QA-CAPABILITY-MAP.md +5 -1
- package/docs/cli-contract.md +1 -1
- package/library/actions/extension/perps/mutation-receipt.mjs +4 -3
- package/library/actions/extension/perps/perps.mjs +200 -9
- package/library/actions/extension/perps/set_network.mjs +6 -0
- package/library/actions/mobile/perps/perps.mjs +159 -11
- package/library/actions/mobile/perps/place_order.mjs +7 -4
- package/library/actions/shared/perps/mainnet-order-ledger.mjs +378 -0
- package/library/actions/shared/perps/order-policy.mjs +56 -19
- package/library/actions/shared/wallet/fixture-accounts.mjs +9 -3
- package/library/manifests/extension.action-manifest.json +34 -4
- package/library/manifests/mobile.action-manifest.json +18 -5
- package/package.json +14 -9
- package/scripts/validate-release-report.mjs +14 -2
- package/site/assets/help-recipes.json +1 -1
|
@@ -2614,7 +2614,7 @@
|
|
|
2614
2614
|
"execution_capabilities": ["app-mutation", "external-mutation"]
|
|
2615
2615
|
},
|
|
2616
2616
|
"metamask.perps.close_orders": {
|
|
2617
|
-
"description": "mobile Cancel selected live Perps open orders on the app's current Perps network,
|
|
2617
|
+
"description": "mobile Cancel selected live Perps open orders on the app's current Perps network; on mainnet only orders this run placed, anything else is refused before dispatch. Use selector/mode params to cancel matching symbols or all orders. Pre-existing testnet state on an account the task owns is converged, not refused.",
|
|
2618
2618
|
"schema": {
|
|
2619
2619
|
"type": "object",
|
|
2620
2620
|
"properties": {
|
|
@@ -2819,11 +2819,24 @@
|
|
|
2819
2819
|
"mainnet",
|
|
2820
2820
|
"testnet"
|
|
2821
2821
|
],
|
|
2822
|
-
"description": "Perps network the order must run on; checked against the live app. A mainnet order must declare mainnet."
|
|
2822
|
+
"description": "Perps network the order must run on; checked against the live app. A mainnet order must declare mainnet: a market order with close_immediately, or a dev1 limit order away from the market at 1x and at most $15 notional at its limit price, which the run cancels at its end (success, failure or abort) and records in perps-mainnet-orders.json."
|
|
2823
2823
|
},
|
|
2824
2824
|
"close_immediately": {
|
|
2825
2825
|
"type": "boolean",
|
|
2826
|
-
"description": "Close the opened position again inside this action and prove the market flat (market orders only; the market must start flat).
|
|
2826
|
+
"description": "Close the opened position again inside this action and prove the market flat (market orders only; the market must start flat). A mainnet market order needs it, with leverage 1 and notional at most $15 from a fixture account; a mainnet limit order takes none (the run cancels it at its end)."
|
|
2827
|
+
},
|
|
2828
|
+
"order_type": {
|
|
2829
|
+
"type": "string",
|
|
2830
|
+
"enum": ["market", "limit"],
|
|
2831
|
+
"default": "market"
|
|
2832
|
+
},
|
|
2833
|
+
"limit_price": {
|
|
2834
|
+
"type": ["string", "number"],
|
|
2835
|
+
"description": "Limit price; omit it to offset from the current market price by offset_pct."
|
|
2836
|
+
},
|
|
2837
|
+
"offset_pct": {
|
|
2838
|
+
"type": "number",
|
|
2839
|
+
"description": "Limit price offset from the current market price when limit_price is omitted (default -30 for a buy, 30 for a sell)."
|
|
2827
2840
|
}
|
|
2828
2841
|
},
|
|
2829
2842
|
"additionalProperties": false
|
|
@@ -3142,7 +3155,7 @@
|
|
|
3142
3155
|
"execution_capabilities": ["app-mutation", "external-mutation"]
|
|
3143
3156
|
},
|
|
3144
3157
|
"metamask.perps.ensure_orders": {
|
|
3145
|
-
"description": "mobile Converge selected testnet orders to open or absent, then independently assert the final state. Creating an open state uses a resting limit order, which the shared Perps order rule refuses on mainnet; cancelling acts on the app's current Perps network
|
|
3158
|
+
"description": "mobile Converge selected testnet orders to open or absent, then independently assert the final state. Creating an open state uses a resting limit order, which the shared Perps order rule refuses on mainnet; cancelling acts on the app's current Perps network, and on mainnet only on orders this run placed. Pre-existing testnet state on an account the task owns is converged, not refused.",
|
|
3146
3159
|
"schema": {
|
|
3147
3160
|
"type": "object",
|
|
3148
3161
|
"properties": {
|
|
@@ -3511,7 +3524,7 @@
|
|
|
3511
3524
|
"network": {
|
|
3512
3525
|
"type": "string",
|
|
3513
3526
|
"enum": ["testnet", "mainnet"],
|
|
3514
|
-
"description": "Requested Perps network. Omit it to keep the current network unless a named profile supplies one."
|
|
3527
|
+
"description": "Requested Perps network; network=mainnet is the opt-in for mainnet orders. Omit it to keep the current network unless a named profile supplies one."
|
|
3515
3528
|
},
|
|
3516
3529
|
"market": {
|
|
3517
3530
|
"type": "string",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deeeed/metamask-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.89.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mm-harness": "bin/mm-harness"
|
|
@@ -21,15 +21,15 @@
|
|
|
21
21
|
"check:syntax": "find . -name '*.mjs' -print0 | xargs -0 -n1 node --check"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@farmslot/adapter-node": "0.1.
|
|
25
|
-
"@farmslot/adapter-rn": "0.19.
|
|
26
|
-
"@farmslot/adapter-sdk": "0.8.
|
|
27
|
-
"@farmslot/adapter-web": "0.6.
|
|
28
|
-
"@farmslot/agent-runtime": "0.17.
|
|
24
|
+
"@farmslot/adapter-node": "0.1.12",
|
|
25
|
+
"@farmslot/adapter-rn": "0.19.10",
|
|
26
|
+
"@farmslot/adapter-sdk": "0.8.6",
|
|
27
|
+
"@farmslot/adapter-web": "0.6.10",
|
|
28
|
+
"@farmslot/agent-runtime": "0.17.5",
|
|
29
29
|
"@farmslot/handoff": "^0.3.1",
|
|
30
|
-
"@farmslot/protocol": "0.
|
|
31
|
-
"@farmslot/recipe-cli": "0.
|
|
32
|
-
"@farmslot/recipe-runner": "0.
|
|
30
|
+
"@farmslot/protocol": "0.37.0",
|
|
31
|
+
"@farmslot/recipe-cli": "0.13.0",
|
|
32
|
+
"@farmslot/recipe-runner": "0.29.0",
|
|
33
33
|
"agent-device": "0.19.3",
|
|
34
34
|
"commander": "^12.0.0",
|
|
35
35
|
"esbuild": "0.28.1",
|
|
@@ -55,6 +55,11 @@
|
|
|
55
55
|
},
|
|
56
56
|
"main": "./dist/index.js",
|
|
57
57
|
"exports": {
|
|
58
|
+
"./provider": {
|
|
59
|
+
"development": "./src/provider.ts",
|
|
60
|
+
"import": "./dist/provider.js",
|
|
61
|
+
"default": "./dist/provider.js"
|
|
62
|
+
},
|
|
58
63
|
".": {
|
|
59
64
|
"development": "./src/index.ts",
|
|
60
65
|
"import": "./dist/index.js",
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import fs from 'node:fs';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
-
import { validateRecipeArtifactPackage } from '@farmslot/protocol';
|
|
4
|
+
import { recipeTraceEntries, validateRecipeArtifactPackage } from '@farmslot/protocol';
|
|
5
|
+
import { CAPTURE_EVIDENCE_INCOMPLETE, onlyCaptureInterrupted } from '@farmslot/recipe-runner';
|
|
5
6
|
|
|
6
7
|
const options = parseArgs(process.argv.slice(2));
|
|
7
8
|
const inventoryPath = path.resolve(options.inventory);
|
|
@@ -71,6 +72,7 @@ for (const entry of verdictCases) {
|
|
|
71
72
|
errors.push(`${entry.id ?? '<missing-id>'} must include evidence`);
|
|
72
73
|
continue;
|
|
73
74
|
}
|
|
75
|
+
const interruptedRuns = [];
|
|
74
76
|
for (const evidence of entry.evidence) {
|
|
75
77
|
if (typeof evidence !== 'string' || evidence.length === 0) {
|
|
76
78
|
errors.push(`${entry.id ?? '<missing-id>'} contains invalid evidence`);
|
|
@@ -121,7 +123,8 @@ for (const entry of verdictCases) {
|
|
|
121
123
|
}
|
|
122
124
|
documents[required] = requiredDocument.value;
|
|
123
125
|
}
|
|
124
|
-
|
|
126
|
+
const captureInterrupted = onlyCaptureInterrupted(recipeTraceEntries(documents['trace.json']) ?? []);
|
|
127
|
+
if (documents['summary.json'] && entry.status === 'PASS' && documents['summary.json'].status !== 'pass' && !captureInterrupted) {
|
|
125
128
|
errors.push(`${entry.id ?? '<missing-id>'} PASS evidence summary.json must have status pass: ${resolved}`);
|
|
126
129
|
}
|
|
127
130
|
if (requiredDocuments.every((required) => required in documents)) {
|
|
@@ -147,11 +150,20 @@ for (const entry of verdictCases) {
|
|
|
147
150
|
.join(', ');
|
|
148
151
|
errors.push(`${entry.id ?? '<missing-id>'} evidence directory is not a valid Recipe Protocol artifact package: ${resolved} (${codes})`);
|
|
149
152
|
}
|
|
153
|
+
interruptedRuns.push(captureInterrupted);
|
|
150
154
|
}
|
|
151
155
|
} else if (!stat.isFile()) {
|
|
152
156
|
errors.push(`${entry.id ?? '<missing-id>'} evidence must be a file or run directory: ${resolved}`);
|
|
153
157
|
}
|
|
154
158
|
}
|
|
159
|
+
// Retained interrupted attempts must not override a retry's product verdict.
|
|
160
|
+
if (interruptedRuns.length > 0 && interruptedRuns.every(Boolean)) {
|
|
161
|
+
if (entry.status !== 'BLOCKED_ENVIRONMENT') {
|
|
162
|
+
errors.push(`${entry.id ?? '<missing-id>'} evidence failed only because its recording was interrupted (CAPTURE_INTERRUPTED): report BLOCKED_ENVIRONMENT, ${CAPTURE_EVIDENCE_INCOMPLETE}, with the partial video, not ${entry.status}`);
|
|
163
|
+
} else if (!entry.summary.includes(CAPTURE_EVIDENCE_INCOMPLETE)) {
|
|
164
|
+
errors.push(`${entry.id ?? '<missing-id>'} summary must say "${CAPTURE_EVIDENCE_INCOMPLETE}" for a case whose recordings were interrupted`);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
155
167
|
}
|
|
156
168
|
|
|
157
169
|
const counts = Object.fromEntries([...allowedStatuses].map((status) => [
|
|
@@ -201,7 +201,7 @@
|
|
|
201
201
|
"Analytics: verify the collection destination and existing consent before capture. Changing participation or marketing consent requires operator approval and restoration of the prior settings; a recipe's hardcoded opt-in is not permission. Missing collection prerequisites are not missing product events.",
|
|
202
202
|
"Flake risk: wait on observable state instead of sleeping, keep device and runtime identity explicit, and never overwrite a prior run's artifacts.",
|
|
203
203
|
"Mutation: require an independent state assertion and identity-bound receipt. Bind activity to that operation's accepted ID; an old row or nonzero count is insufficient. Opening and closing orders have different IDs. Schedule authorized cleanup in teardown, verify its result, and report any residue. Testnet cleanup closes what the task opened and never silently undoes another task's live proof on a reserved account or market.",
|
|
204
|
-
"Testnet account state: on a testnet account or market the task owns for its run (the slot's fixture account, or one reserved with exclusive_testnet_market=true), converging is an explicit, ownership-scoped setup choice, not a violation. Use the team library's perps.ensure-market-state or perps.clean-market-testnet, or on Mobile and Core metamask.perps.ensure_positions / metamask.perps.ensure_orders (mode=all for strays), record what was closed in the trace, then prove. A pre-existing position or order on an account the task owns is never a reason to block, skip, or refuse, and a fixture that demands an empty account must converge rather than refuse. Extension ensure_* and teardown_state only assert; converge there through an explicitly funded leaf action. Never broaden cleanup onto an account or market the task does not own; obtain an owned one instead of blocking. On mainnet, orders follow the shared Perps order rule (PERPS_MAINNET_ORDER_REFUSED), Core needs CORE_PERPS_ALLOW_MAINNET_WRITES=1 for any write, Mobile
|
|
204
|
+
"Testnet account state: on a testnet account or market the task owns for its run (the slot's fixture account, or one reserved with exclusive_testnet_market=true), converging is an explicit, ownership-scoped setup choice, not a violation. Use the team library's perps.ensure-market-state or perps.clean-market-testnet, or on Mobile and Core metamask.perps.ensure_positions / metamask.perps.ensure_orders (mode=all for strays), record what was closed in the trace, then prove. A pre-existing position or order on an account the task owns is never a reason to block, skip, or refuse, and a fixture that demands an empty account must converge rather than refuse. Extension ensure_* and teardown_state only assert; converge there through an explicitly funded leaf action. Never broaden cleanup onto an account or market the task does not own; obtain an owned one instead of blocking. On mainnet, orders follow the shared Perps order rule (PERPS_MAINNET_ORDER_REFUSED), Core needs CORE_PERPS_ALLOW_MAINNET_WRITES=1 for any write, Mobile close_positions acts on whatever network the app is on, close_orders there cancels only the run's own orders, and Extension refuses mainnet writes other than orders, `close_visible_position` and cancelling the run's own orders in the fixture-policy lane.",
|
|
205
205
|
"Rate limits: RATE_LIMITED means Hyperliquid answered 429 Too Many Requests and bounded retries did not get through; it says nothing about the product. Wait a minute and rerun a read. A placement left outcome-unknown is never rerun before the market's positions, orders and fills are checked.",
|
|
206
206
|
{
|
|
207
207
|
"text": "An accepted order can fill before the UI updates. Preserve its receipt even when the expected resting-order assertion fails; one empty read does not prove cleanup. Check the read API's cache and freshness options before adding polling. Reconcile returned margin against the pre-placement balance. A successful close does not turn the original failed proof into a pass.",
|