@deeeed/metamask-harness 0.39.0 → 0.40.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 +46 -0
- package/adapters/extension/lib/playwright-cdp.cjs +17 -1
- package/adapters/extension/sidepanel-toggle.sh +135 -13
- package/dist/adapters/mobile/release-artifact.js +65 -0
- package/dist/adapters.js +8 -7
- package/dist/command-contract.js +10 -2
- package/dist/commands/call.js +7 -4
- package/dist/commands/launch/index.js +55 -3
- package/dist/commands/parse-args.js +6 -0
- package/dist/commands/run-engine.js +75 -39
- package/dist/funding-execution-context.js +1386 -0
- package/dist/live-adapter-contract.js +3 -1
- package/dist/metamask-action-validation.js +170 -3
- package/dist/mm-harness-cli.js +8 -2
- package/dist/recipe-security.js +5 -12
- package/dist/runner.js +102 -6
- package/docs/RECIPES.md +74 -1
- package/docs/RELEASE-QA-CAPABILITY-MAP.md +6 -5
- package/library/actions/extension/networks/add_chainlist.mjs +4 -0
- package/library/actions/extension/networks/add_custom.mjs +4 -0
- package/library/actions/extension/networks/custom_network.mjs +48 -1
- package/library/actions/extension/perps/assert_visible_consistency.mjs +366 -0
- package/library/actions/extension/perps/close_visible_position.mjs +299 -37
- package/library/actions/extension/perps/compare_provider_market.mjs +77 -0
- package/library/actions/extension/perps/edit_margin.mjs +285 -44
- package/library/actions/extension/perps/mutation-receipt.mjs +781 -0
- package/library/actions/extension/perps/perps.mjs +1029 -363
- package/library/actions/extension/perps/read_funds_confirmation.mjs +126 -13
- package/library/actions/extension/perps/read_snapshot.mjs +124 -3
- package/library/actions/extension/perps/read_visible_state.mjs +63 -5
- package/library/actions/extension/perps/select_activity_filter.mjs +81 -18
- package/library/actions/extension/perps/select_market_filter.mjs +48 -4
- package/library/actions/extension/perps/update_position_tpsl.mjs +251 -69
- package/library/actions/extension/perps/visible-mutation-identity.mjs +164 -0
- package/library/actions/extension/platform/cdp.mjs +88 -9
- package/library/actions/extension/settings/set_basic_functionality.mjs +35 -7
- package/library/actions/extension/ui/navigate.mjs +45 -5
- package/library/actions/extension/wallet/import.mjs +70 -29
- package/library/actions/extension/wallet/lock.mjs +62 -5
- package/library/actions/extension/wallet/visible-session.mjs +218 -0
- package/library/actions/mobile/ui/native-navigation.mjs +45 -0
- package/library/actions/mobile/wallet/import.mjs +17 -10
- package/library/actions/mobile/wallet/native-ui.mjs +21 -1
- package/library/actions/shared/perps/visible-state.mjs +25 -5
- package/library/manifests/extension.action-manifest.json +1696 -1271
- package/library/recipes/extension/assets/release-custom-network-token-persistence.recipe.json +1 -0
- package/library/recipes/{perps → extension/perps}/release-activity-filters.recipe.json +5 -5
- package/library/recipes/{perps → extension/perps}/release-funds-flows.recipe.json +3 -3
- package/library/recipes/extension/perps/release-live-limit-order.recipe.json +24 -19
- package/library/recipes/extension/perps/release-market-filters.recipe.json +97 -0
- package/library/recipes/extension/perps/release-order-entry.recipe.json +4 -1
- package/library/recipes/extension/perps/release-order-validation.recipe.json +53 -14
- package/library/recipes/extension/perps/release-visible-consistency.recipe.json +47 -0
- package/library/recipes/extension/perps/source-dev-snapshot-consistency.recipe.json +76 -0
- package/library/recipes/perps/release-live-market-order.recipe.json +30 -13
- package/library/recipes/perps/release-market-details.recipe.json +1 -1
- package/library/recipes/swap-bridge/release-quote.recipe.json +1 -0
- package/library/recipes/wallet/import.recipe.json +4 -14
- package/package.json +1 -1
- package/library/actions/extension/assets/finish_send.mjs +0 -128
- package/library/actions/extension/swap_bridge/submit_transaction.mjs +0 -363
- package/library/actions/extension/ui/activate_and_observe.mjs +0 -96
- package/library/recipes/assets/release-custom-gas-send.recipe.json +0 -88
- package/library/recipes/assets/release-native-max-cancel.recipe.json +0 -70
- package/library/recipes/assets/release-native-max-send.recipe.json +0 -78
- package/library/recipes/assets/release-own-account-send.recipe.json +0 -80
- package/library/recipes/extension/runner/action-validation.recipe.json +0 -343
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,52 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.40.0 - 2026-08-16
|
|
6
|
+
|
|
7
|
+
- Remove Extension's public `metamask.perps.close_positions` and `metamask.perps.start_state` surfaces and its mutating `runner.action-validation` recipe; retain only state assertions it can prove without implicit readiness or unreceipted convergence.
|
|
8
|
+
- Make guarded Extension custom-network setup wait for an unobscured account-menu hit point before opening network management.
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Add explicit Extension fullscreen/sidepanel launch selection and auto/show/hide recipe HUD policy.
|
|
13
|
+
- Bind external-mutation runs to independently validated release, onboarding, balance-evidence, request, and preflight bytes plus the same-run visible wallet/runtime identity.
|
|
14
|
+
- Add a packaged Perps market-filter release recipe with a deterministic market-list start state and every nonconditional category control.
|
|
15
|
+
- Add official-compatible visible Extension Perps market-list/activity consistency assertions, plus source-dev-only background snapshot sampling and activity-to-history identity expectations.
|
|
16
|
+
- Bind every live Extension action result to the selected CDP target and current browser runtime session.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Canonicalize durable Extension Perps release journeys under one discovered recipe ID each, with activity and funds flows scoped to Extension and explicit official/source-development and live-mutation boundaries.
|
|
21
|
+
- Parameterize live values in the Extension Perps order-validation recipe and narrow existing market-details, activity-filter, and funds-preview claims to their asserted evidence.
|
|
22
|
+
- Require an explicit flat market for non-submitting Extension order-form recipes and make live-price movement an opt-in source-development snapshot assertion.
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Trust only the current Perps-only funding registry plus the named frozen G005 digest; reject duplicate or aggregate-over-cap mutation operations and budgets, mutation-capable balance graphs, root-ref dependency shadowing, and non-visible or non-Home balance observations.
|
|
27
|
+
- Bind trusted funding and onboarding evidence to exact emitted call graphs, successful node/action order, and reachable root PASS terminals while preserving Mobile's adapter-owned external-mutation consent boundary.
|
|
28
|
+
- Stop advertising shared Perps clean-market convergence on Extension; its state helpers now expose only navigation and assertion inputs and reject ignored creation or cleanup parameters during planning.
|
|
29
|
+
- Remove unsupported Extension send completion, Swap/Bridge submission, and generic activate-and-observe capabilities plus four non-executable Assets submit recipes; retain read-only, prepare, review, quote, and Mobile-owned live Swap planning.
|
|
30
|
+
- Bind funded Extension Perps margin and TP/SL updates to the exact visible position size, enforce order caps against requested leveraged exposure before receipt publication, and default funded placements to one-times leverage.
|
|
31
|
+
- Fail closed on visible and background Perps financial mutations without an identity-bound durable receipt, current visible funding minimum, runner-owned artifact/runtime, and exact dispatch-boundary provider/network; terminally record known non-dispatch instead of retrying the same claim.
|
|
32
|
+
- Require approved visible wallet method and credential provenance plus a hash-bound emitted recipe, pass summary, artifact manifest, and exact reachable root-terminal PASS in funding evidence.
|
|
33
|
+
- Require every funded visible position edit or close to recheck current funding, provider/network, and exact target state after receipt publication and immediately before its first pointer dispatch.
|
|
34
|
+
- Invalidate funded-mutation wallet identity proof after account selection, reset, lock, unlock, setup, or any state-changing generic UI action until a complete fresh visible wallet import proof succeeds.
|
|
35
|
+
- Keep Extension Perps state wrappers observation-only so they cannot place, close, or cancel implicitly outside a leaf action's funding authorization.
|
|
36
|
+
- Preserve a final Perps navigation observation poll and require Side Panel convergence to one MetaMask product surface beside the explicitly requested dapp.
|
|
37
|
+
- Require the canonical non-submitting Swap quote recipe to prove a positive reviewable quote instead of accepting a no-route state.
|
|
38
|
+
- Refuse cleanup-bound custom-network setup when the target network already exists, preserving pre-existing wallet configuration.
|
|
39
|
+
- Bind Android release-artifact action results to the verified native app process session and redact Mobile wallet-import identity proofs to a matched digest.
|
|
40
|
+
- Distinguish obscured Perps controls from harmless edge intersections in visible layout proofs and include confirmation controls on funds surfaces.
|
|
41
|
+
- Reject duplicate rendered market, watchlist, or activity identities during optional Extension consistency proofs.
|
|
42
|
+
- Read Perps funds confirmation titles from their visible wallet-initiated header and support exact amount, token, network, percentage-control, and disabled-state assertions.
|
|
43
|
+
- Wait for Basic Functionality consent to enable its confirmation before submitting the visible settings change.
|
|
44
|
+
- Reach the Extension global wallet menu through the visible Home control before locking from headerless product routes.
|
|
45
|
+
- Keep Extension surface transitions singular and route the sidepanel launcher's obscuration hit test through hardened raw CDP when its visible notification badge overlaps the button.
|
|
46
|
+
- Namespace funded Perps claims by exact request and preflight bytes, persist identity-bound receipts before live pointer dispatch, and restrict over-cap order recovery to exact cleanup-only cancellation without upgrading placement success.
|
|
47
|
+
- Relaunch intact Extension release artifacts without source dependency repair, and support exact receipt-bound close-only Perps cleanup with primary-card and balance reconciliation proof.
|
|
48
|
+
- Validate Extension Perps activity filters against their singular route query while requiring the requested filter label to remain visible.
|
|
49
|
+
- Make Extension `wallet.import method=ui` idempotent on an onboarded profile by visibly unlocking and selecting the exact derived account without fixture or background-state probes.
|
|
50
|
+
|
|
5
51
|
## 0.39.0 - 2026-08-14
|
|
6
52
|
|
|
7
53
|
### Added
|
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const { createRequire } = require('node:module');
|
|
5
|
+
|
|
6
|
+
function loadChromium() {
|
|
7
|
+
const runtimeRequire = createRequire(path.join(process.cwd(), 'package.json'));
|
|
8
|
+
try {
|
|
9
|
+
return runtimeRequire('@playwright/test').chromium;
|
|
10
|
+
} catch {
|
|
11
|
+
return runtimeRequire('playwright').chromium;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
async function connectBrowserViaCdp(endpoint) {
|
|
16
|
+
return loadChromium().connectOverCDP(endpoint);
|
|
17
|
+
}
|
|
18
|
+
|
|
3
19
|
function buildEvaluationExpression(callbackOrExpression, argument) {
|
|
4
20
|
if (typeof callbackOrExpression === 'string') return callbackOrExpression;
|
|
5
21
|
if (typeof callbackOrExpression !== 'function') {
|
|
@@ -31,4 +47,4 @@ async function evaluatePageViaCdp(page, callbackOrExpression, argument) {
|
|
|
31
47
|
}
|
|
32
48
|
}
|
|
33
49
|
|
|
34
|
-
module.exports = { buildEvaluationExpression, evaluatePageViaCdp };
|
|
50
|
+
module.exports = { buildEvaluationExpression, connectBrowserViaCdp, evaluatePageViaCdp };
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
#
|
|
8
8
|
# Inputs: subcommand (status|open|close|toggle|cycle, default status);
|
|
9
9
|
# --cdp-port <port> (required), --ext-id <id>, --agent-dir <dir>,
|
|
10
|
-
# --settle-ms <ms>; env REPO, CDP_PORT, EXT_ID,
|
|
10
|
+
# --settle-ms <ms>, --dapp-url <url>; env REPO, CDP_PORT, EXT_ID,
|
|
11
|
+
# SETTLE_MS, AGENT_DIR, EXTENSION_START_URL.
|
|
11
12
|
# Outputs: status/progress lines on stdout.
|
|
12
13
|
# Exit 0 — action done; 1 — repo/args missing or action failed;
|
|
13
14
|
# 2 — non-numeric port / CDP unreachable.
|
|
@@ -30,7 +31,7 @@ set -euo pipefail
|
|
|
30
31
|
|
|
31
32
|
ACTION="${1:-status}"
|
|
32
33
|
if [ "$ACTION" = "-h" ] || [ "$ACTION" = "--help" ]; then
|
|
33
|
-
echo "Usage: sidepanel-toggle.sh <status|open|close|toggle|cycle> --cdp-port <port> [--ext-id <id>] [--agent-dir <dir>] [--settle-ms <ms>]"
|
|
34
|
+
echo "Usage: sidepanel-toggle.sh <status|open|close|toggle|cycle> --cdp-port <port> [--ext-id <id>] [--agent-dir <dir>] [--settle-ms <ms>] [--dapp-url <url>]"
|
|
34
35
|
exit 0
|
|
35
36
|
fi
|
|
36
37
|
shift || true
|
|
@@ -68,6 +69,7 @@ CDP_PORT="${CDP_PORT:-}"
|
|
|
68
69
|
EXT_ID="${EXT_ID:-}"
|
|
69
70
|
SETTLE_MS="${SETTLE_MS:-10000}"
|
|
70
71
|
AGENT_DIR="${AGENT_DIR:-$SCRIPT_DIR}"
|
|
72
|
+
DAPP_URL="${EXTENSION_START_URL:-https://metamask.github.io/test-dapp/}"
|
|
71
73
|
|
|
72
74
|
while [[ $# -gt 0 ]]; do
|
|
73
75
|
case "$1" in
|
|
@@ -75,8 +77,9 @@ while [[ $# -gt 0 ]]; do
|
|
|
75
77
|
--ext-id) EXT_ID="$2"; shift 2 ;;
|
|
76
78
|
--agent-dir) AGENT_DIR="$2"; shift 2 ;;
|
|
77
79
|
--settle-ms) SETTLE_MS="$2"; shift 2 ;;
|
|
80
|
+
--dapp-url) DAPP_URL="$2"; shift 2 ;;
|
|
78
81
|
-h|--help)
|
|
79
|
-
echo "Usage: sidepanel-toggle.sh <status|open|close|toggle|cycle> --cdp-port <port> [--ext-id <id>] [--agent-dir <dir>] [--settle-ms <ms>]"
|
|
82
|
+
echo "Usage: sidepanel-toggle.sh <status|open|close|toggle|cycle> --cdp-port <port> [--ext-id <id>] [--agent-dir <dir>] [--settle-ms <ms>] [--dapp-url <url>]"
|
|
80
83
|
exit 0
|
|
81
84
|
;;
|
|
82
85
|
*) echo "unknown arg: $1" >&2; exit 1 ;;
|
|
@@ -91,6 +94,10 @@ if ! [[ "$CDP_PORT" =~ ^[0-9]+$ ]]; then
|
|
|
91
94
|
echo "FAIL: CDP port \"$CDP_PORT\" must be numeric" >&2
|
|
92
95
|
exit 2
|
|
93
96
|
fi
|
|
97
|
+
case "$DAPP_URL" in
|
|
98
|
+
http://*|https://*) ;;
|
|
99
|
+
*) echo "FAIL: --dapp-url must use http or https" >&2; exit 1 ;;
|
|
100
|
+
esac
|
|
94
101
|
if ! curl -s -m 3 "http://127.0.0.1:${CDP_PORT}/json/version" >/dev/null 2>&1; then
|
|
95
102
|
echo "FAIL: CDP not reachable on port ${CDP_PORT}" >&2
|
|
96
103
|
exit 2
|
|
@@ -105,7 +112,44 @@ focus_browser() {
|
|
|
105
112
|
}
|
|
106
113
|
|
|
107
114
|
find_sidepanel_id() {
|
|
108
|
-
|
|
115
|
+
local ext_id
|
|
116
|
+
ext_id="$(resolve_ext_id)"
|
|
117
|
+
json_list | EXT_ID="$ext_id" python3 -c "import json,os,sys; d=json.load(sys.stdin); ext=os.environ.get('EXT_ID',''); ids=[t.get('id','') for t in d if t.get('type')=='page' and t.get('url','').startswith('chrome-extension://'+ext+'/sidepanel.html')]; print(ids[0] if ids else '')"
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
find_dapp_id() {
|
|
121
|
+
json_list | DAPP_URL="$DAPP_URL" python3 -c "import json,os,sys; d=json.load(sys.stdin); wanted=os.environ['DAPP_URL'].rstrip('/'); ids=[t.get('id','') for t in d if t.get('type')=='page' and t.get('url','').rstrip('/')==wanted]; print(ids[0] if ids else '')"
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
surface_counts() {
|
|
125
|
+
local ext_id
|
|
126
|
+
ext_id="$(resolve_ext_id)"
|
|
127
|
+
json_list | EXT_ID="$ext_id" DAPP_URL="$DAPP_URL" python3 -c "import json,os,sys; d=json.load(sys.stdin); ext=os.environ.get('EXT_ID',''); wanted=os.environ['DAPP_URL'].rstrip('/'); pages=[t for t in d if t.get('type')=='page']; homes=sum(t.get('url','').startswith('chrome-extension://'+ext+'/home.html') for t in pages); panels=sum(t.get('url','').startswith('chrome-extension://'+ext+'/sidepanel.html') for t in pages); dapps=sum(t.get('url','').rstrip('/')==wanted for t in pages); products=sum(t.get('url','').startswith('chrome-extension://'+ext+'/') for t in pages); print(homes,panels,dapps,products)"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
ensure_dapp_tab() {
|
|
131
|
+
[ -n "$(find_dapp_id)" ] && return 0
|
|
132
|
+
local encoded
|
|
133
|
+
encoded="$(DAPP_URL="$DAPP_URL" python3 -c "import os,urllib.parse; print(urllib.parse.quote(os.environ['DAPP_URL'], safe=''))")"
|
|
134
|
+
curl -fsS -X PUT "http://127.0.0.1:${CDP_PORT}/json/new?${encoded}" >/dev/null
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
ensure_home_page() {
|
|
138
|
+
local ext_id encoded home_ids first
|
|
139
|
+
ext_id="$(resolve_ext_id)"
|
|
140
|
+
[ -n "$ext_id" ] || { echo "FAIL: could not resolve extension id for CDP ${CDP_PORT}" >&2; return 1; }
|
|
141
|
+
home_ids="$(json_list | EXT_ID="$ext_id" python3 -c "import json,os,sys; d=json.load(sys.stdin); ext=os.environ.get('EXT_ID',''); print('\\n'.join(t.get('id','') for t in d if t.get('type')=='page' and t.get('url','').startswith('chrome-extension://'+ext+'/home.html')))" )"
|
|
142
|
+
if [ -n "$home_ids" ]; then
|
|
143
|
+
first=1
|
|
144
|
+
while IFS= read -r target_id; do
|
|
145
|
+
[ -n "$target_id" ] || continue
|
|
146
|
+
if [ "$first" -eq 1 ]; then first=0; continue; fi
|
|
147
|
+
curl -fsS "http://127.0.0.1:${CDP_PORT}/json/close/${target_id}" >/dev/null
|
|
148
|
+
done <<< "$home_ids"
|
|
149
|
+
return 0
|
|
150
|
+
fi
|
|
151
|
+
encoded="$(EXT_ID="$ext_id" python3 -c "import os,urllib.parse; print(urllib.parse.quote('chrome-extension://'+os.environ['EXT_ID']+'/home.html#/', safe=''))")"
|
|
152
|
+
curl -fsS -X PUT "http://127.0.0.1:${CDP_PORT}/json/new?${encoded}" >/dev/null
|
|
109
153
|
}
|
|
110
154
|
|
|
111
155
|
resolve_ext_id() {
|
|
@@ -172,6 +216,20 @@ wait_for_sidepanel() {
|
|
|
172
216
|
done
|
|
173
217
|
}
|
|
174
218
|
|
|
219
|
+
wait_for_surface_invariant() {
|
|
220
|
+
local expected="$1" start deadline now home_count panel_count dapp_count product_count
|
|
221
|
+
start="$(now_ms)"
|
|
222
|
+
deadline=$((start + SETTLE_MS))
|
|
223
|
+
while :; do
|
|
224
|
+
read -r home_count panel_count dapp_count product_count <<< "$(surface_counts)"
|
|
225
|
+
if [ "$expected" = "fullscreen" ] && [ "$home_count" -eq 1 ] && [ "$panel_count" -eq 0 ] && [ "$product_count" -eq 1 ]; then return 0; fi
|
|
226
|
+
if [ "$expected" = "sidepanel" ] && [ "$home_count" -eq 0 ] && [ "$panel_count" -eq 1 ] && [ "$dapp_count" -eq 1 ] && [ "$product_count" -eq 1 ]; then return 0; fi
|
|
227
|
+
now="$(now_ms)"
|
|
228
|
+
[ "$now" -lt "$deadline" ] || return 1
|
|
229
|
+
sleep 0.2
|
|
230
|
+
done
|
|
231
|
+
}
|
|
232
|
+
|
|
175
233
|
print_targets() {
|
|
176
234
|
json_list | python3 -c "import json,sys; d=json.load(sys.stdin); [print(f' {t.get(\"type\",\"?\")[:18]:18s} {t.get(\"url\",\"\")[:100]}') for t in d]"
|
|
177
235
|
}
|
|
@@ -179,7 +237,7 @@ print_targets() {
|
|
|
179
237
|
activate_dapp_tab() {
|
|
180
238
|
local target_id
|
|
181
239
|
target_id="$(
|
|
182
|
-
json_list | python3 -c "import json,sys; d=json.load(sys.stdin); pages=[t for t in d if t.get('type')=='page']; target=next((t for t in pages if t.get('url','').
|
|
240
|
+
json_list | DAPP_URL="$DAPP_URL" python3 -c "import json,os,sys; d=json.load(sys.stdin); wanted=os.environ['DAPP_URL'].rstrip('/'); pages=[t for t in d if t.get('type')=='page']; target=next((t for t in pages if t.get('url','').rstrip('/')==wanted), None); print(target.get('id','') if target else '')"
|
|
183
241
|
)"
|
|
184
242
|
if [ -n "$target_id" ] && focus_browser; then
|
|
185
243
|
curl -s "http://127.0.0.1:${CDP_PORT}/json/activate/${target_id}" >/dev/null || true
|
|
@@ -191,7 +249,7 @@ cleanup_sidepanel_tabs() {
|
|
|
191
249
|
ext_id="$(resolve_ext_id)"
|
|
192
250
|
[ -n "$ext_id" ] || return 0
|
|
193
251
|
close_ids="$(
|
|
194
|
-
json_list | EXT_ID="$ext_id" python3 -c "import json,os,sys; d=json.load(sys.stdin); ext=os.environ.get('EXT_ID',''); has_panel=any(t.get('type')=='page' and t.get('url','').startswith('chrome-extension://'+ext+'/
|
|
252
|
+
json_list | EXT_ID="$ext_id" DAPP_URL="$DAPP_URL" python3 -c "import json,os,sys; d=json.load(sys.stdin); ext=os.environ.get('EXT_ID',''); wanted=os.environ['DAPP_URL'].rstrip('/'); has_panel=any(t.get('type')=='page' and t.get('url','').startswith('chrome-extension://'+ext+'/sidepanel.html') for t in d); has_dapp=any(t.get('type')=='page' and t.get('url','').rstrip('/')==wanted for t in d); print('\\n'.join(t.get('id','') for t in d if has_panel and has_dapp and t.get('type')=='page' and t.get('url','').startswith('chrome-extension://'+ext+'/home.html')))"
|
|
195
253
|
)"
|
|
196
254
|
if [ -n "$close_ids" ]; then
|
|
197
255
|
while IFS= read -r target_id; do
|
|
@@ -202,6 +260,18 @@ cleanup_sidepanel_tabs() {
|
|
|
202
260
|
activate_dapp_tab
|
|
203
261
|
}
|
|
204
262
|
|
|
263
|
+
dedupe_sidepanel_tabs() {
|
|
264
|
+
local ext_id sidepanel_ids first
|
|
265
|
+
ext_id="$(resolve_ext_id)"
|
|
266
|
+
sidepanel_ids="$(json_list | EXT_ID="$ext_id" python3 -c "import json,os,sys; d=json.load(sys.stdin); ext=os.environ.get('EXT_ID',''); print('\\n'.join(t.get('id','') for t in d if t.get('type')=='page' and t.get('url','').startswith('chrome-extension://'+ext+'/sidepanel.html')))" )"
|
|
267
|
+
first=1
|
|
268
|
+
while IFS= read -r target_id; do
|
|
269
|
+
[ -n "$target_id" ] || continue
|
|
270
|
+
if [ "$first" -eq 1 ]; then first=0; continue; fi
|
|
271
|
+
curl -fsS "http://127.0.0.1:${CDP_PORT}/json/close/${target_id}" >/dev/null
|
|
272
|
+
done <<< "$sidepanel_ids"
|
|
273
|
+
}
|
|
274
|
+
|
|
205
275
|
status_sidepanel() {
|
|
206
276
|
local sp
|
|
207
277
|
sp="$(find_sidepanel_id)"
|
|
@@ -217,15 +287,22 @@ close_sidepanel() {
|
|
|
217
287
|
sp="$(find_sidepanel_id)"
|
|
218
288
|
if [ -z "$sp" ]; then
|
|
219
289
|
echo "[sidepanel] already closed"
|
|
290
|
+
ensure_home_page
|
|
291
|
+
wait_for_surface_invariant fullscreen || { echo "FAIL: fullscreen surface did not converge to one Home target" >&2; return 1; }
|
|
220
292
|
return 0
|
|
221
293
|
fi
|
|
222
294
|
curl -s "http://127.0.0.1:${CDP_PORT}/json/close/${sp}" >/dev/null
|
|
295
|
+
ensure_home_page
|
|
296
|
+
wait_for_surface_invariant fullscreen || { echo "FAIL: fullscreen surface did not converge to one Home target" >&2; return 1; }
|
|
223
297
|
echo "[sidepanel] closed target ${sp}"
|
|
224
298
|
}
|
|
225
299
|
|
|
226
300
|
open_sidepanel() {
|
|
301
|
+
ensure_dapp_tab
|
|
227
302
|
if [ -n "$(find_sidepanel_id)" ]; then
|
|
303
|
+
dedupe_sidepanel_tabs
|
|
228
304
|
cleanup_sidepanel_tabs
|
|
305
|
+
wait_for_surface_invariant sidepanel || { echo "FAIL: sidepanel surface did not converge to one panel and a dapp host" >&2; return 1; }
|
|
229
306
|
echo "[sidepanel] already open"
|
|
230
307
|
return 0
|
|
231
308
|
fi
|
|
@@ -239,13 +316,54 @@ open_sidepanel() {
|
|
|
239
316
|
exit 4
|
|
240
317
|
fi
|
|
241
318
|
|
|
242
|
-
|
|
243
|
-
const
|
|
319
|
+
CDP_PORT="$CDP_PORT" EXT_ID="$ext_id" SCRIPT_DIR="$SCRIPT_DIR" node <<'NODE'
|
|
320
|
+
const path = require('node:path');
|
|
321
|
+
const {
|
|
322
|
+
connectBrowserViaCdp,
|
|
323
|
+
evaluatePageViaCdp,
|
|
324
|
+
} = require(path.join(process.env.SCRIPT_DIR, 'lib/playwright-cdp.cjs'));
|
|
325
|
+
|
|
326
|
+
async function clickVisibleSafeHit(page, selector, successSelector, timeoutMs) {
|
|
327
|
+
const deadline = Date.now() + timeoutMs;
|
|
328
|
+
while (Date.now() < deadline) {
|
|
329
|
+
if (await page.locator(successSelector).isVisible().catch(() => false)) return;
|
|
330
|
+
const candidate = page.locator(selector);
|
|
331
|
+
const count = await candidate.count();
|
|
332
|
+
if (count > 1) throw new Error(`expected one visible ${selector} control, found ${count}`);
|
|
333
|
+
if (count === 1 && await candidate.isVisible().catch(() => false)) {
|
|
334
|
+
try {
|
|
335
|
+
const point = await evaluatePageViaCdp(page, (candidateSelector) => {
|
|
336
|
+
const matches = [...document.querySelectorAll(candidateSelector)];
|
|
337
|
+
if (matches.length !== 1) return null;
|
|
338
|
+
const [element] = matches;
|
|
339
|
+
const rect = element.getBoundingClientRect();
|
|
340
|
+
const points = [
|
|
341
|
+
[rect.left + rect.width / 2, rect.top + rect.height / 2],
|
|
342
|
+
[rect.left + rect.width / 4, rect.top + rect.height / 2],
|
|
343
|
+
[rect.left + rect.width * 3 / 4, rect.top + rect.height / 2],
|
|
344
|
+
[rect.left + rect.width / 2, rect.top + rect.height / 4],
|
|
345
|
+
[rect.left + rect.width / 2, rect.top + rect.height * 3 / 4],
|
|
346
|
+
];
|
|
347
|
+
const safe = points.find(([x, y]) => {
|
|
348
|
+
const hit = document.elementFromPoint(x, y);
|
|
349
|
+
return hit === element || Boolean(hit && element.contains(hit));
|
|
350
|
+
});
|
|
351
|
+
return safe ? { x: safe[0], y: safe[1] } : null;
|
|
352
|
+
}, selector);
|
|
353
|
+
if (point) await page.mouse.click(point.x, point.y);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
if (!/detached|not attached|execution context was destroyed/iu.test(String(error?.message || error))) throw error;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
await page.waitForTimeout(50);
|
|
359
|
+
}
|
|
360
|
+
throw new Error(`visible ${selector} control did not activate within ${timeoutMs}ms`);
|
|
361
|
+
}
|
|
244
362
|
|
|
245
363
|
(async () => {
|
|
246
364
|
const port = process.env.CDP_PORT;
|
|
247
365
|
const extId = process.env.EXT_ID;
|
|
248
|
-
const browser = await
|
|
366
|
+
const browser = await connectBrowserViaCdp(`http://127.0.0.1:${port}`);
|
|
249
367
|
const context = browser.contexts()[0];
|
|
250
368
|
const page = await context.newPage();
|
|
251
369
|
try {
|
|
@@ -256,11 +374,13 @@ const { chromium } = require('playwright');
|
|
|
256
374
|
if (process.env.MM_HARNESS_FOCUS_BROWSER === '1') {
|
|
257
375
|
await page.bringToFront();
|
|
258
376
|
}
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
377
|
+
await clickVisibleSafeHit(
|
|
378
|
+
page,
|
|
379
|
+
'[data-testid="account-options-menu-button"]',
|
|
380
|
+
'[data-testid="global-menu-toggle-view"]',
|
|
381
|
+
5000,
|
|
382
|
+
);
|
|
262
383
|
const toggle = page.locator('[data-testid="global-menu-toggle-view"]');
|
|
263
|
-
await toggle.waitFor({ state: 'visible', timeout: 10000 });
|
|
264
384
|
await toggle.click({ timeout: 5000 });
|
|
265
385
|
} finally {
|
|
266
386
|
if (!page.isClosed()) await page.close();
|
|
@@ -277,7 +397,9 @@ const { chromium } = require('playwright');
|
|
|
277
397
|
NODE
|
|
278
398
|
|
|
279
399
|
if wait_for_sidepanel; then
|
|
400
|
+
dedupe_sidepanel_tabs
|
|
280
401
|
cleanup_sidepanel_tabs
|
|
402
|
+
wait_for_surface_invariant sidepanel || { echo "FAIL: sidepanel surface did not converge to one panel and a dapp host" >&2; return 1; }
|
|
281
403
|
echo "[sidepanel] opened"
|
|
282
404
|
return 0
|
|
283
405
|
fi
|
|
@@ -89,6 +89,70 @@ function assertAndroidReleaseArtifactRuntime(identity, deps = defaultDeps) {
|
|
|
89
89
|
const adb = resolveAdb(deps);
|
|
90
90
|
assertInstalledAndroidReleaseArtifact(identity, deps, adb);
|
|
91
91
|
}
|
|
92
|
+
function readAndroidNativeRuntimeIdentity(identity, deps = defaultDeps) {
|
|
93
|
+
assertAndroidReleaseArtifactRuntime(identity, deps);
|
|
94
|
+
const adb = resolveAdb(deps);
|
|
95
|
+
const buildFingerprint = runChecked(deps, adb, [
|
|
96
|
+
"-s",
|
|
97
|
+
identity.deviceSerial,
|
|
98
|
+
"shell",
|
|
99
|
+
"getprop",
|
|
100
|
+
"ro.build.fingerprint"
|
|
101
|
+
]).trim();
|
|
102
|
+
if (!/^[A-Za-z0-9._+:/=-]{1,512}$/u.test(buildFingerprint)) {
|
|
103
|
+
throw new Error(`Android device ${identity.deviceSerial} returned an invalid build fingerprint.`);
|
|
104
|
+
}
|
|
105
|
+
const bootId = runChecked(deps, adb, [
|
|
106
|
+
"-s",
|
|
107
|
+
identity.deviceSerial,
|
|
108
|
+
"shell",
|
|
109
|
+
"cat",
|
|
110
|
+
"/proc/sys/kernel/random/boot_id"
|
|
111
|
+
]).trim().toLowerCase();
|
|
112
|
+
if (!/^[a-f0-9]{8}-(?:[a-f0-9]{4}-){3}[a-f0-9]{12}$/u.test(bootId)) {
|
|
113
|
+
throw new Error(`Android device ${identity.deviceSerial} returned an invalid boot ID.`);
|
|
114
|
+
}
|
|
115
|
+
const pids = runChecked(deps, adb, [
|
|
116
|
+
"-s",
|
|
117
|
+
identity.deviceSerial,
|
|
118
|
+
"shell",
|
|
119
|
+
"pidof",
|
|
120
|
+
identity.packageId
|
|
121
|
+
]).trim().split(/\s+/u).filter(Boolean);
|
|
122
|
+
if (pids.length !== 1 || !/^\d+$/u.test(pids[0])) {
|
|
123
|
+
throw new Error(`Android package ${identity.packageId} must have exactly one running app process on ${identity.deviceSerial}.`);
|
|
124
|
+
}
|
|
125
|
+
const pid = pids[0];
|
|
126
|
+
const processStat = runChecked(deps, adb, [
|
|
127
|
+
"-s",
|
|
128
|
+
identity.deviceSerial,
|
|
129
|
+
"shell",
|
|
130
|
+
"cat",
|
|
131
|
+
`/proc/${pid}/stat`
|
|
132
|
+
]).trim();
|
|
133
|
+
const close = processStat.lastIndexOf(") ");
|
|
134
|
+
const processFields = close === -1 ? [] : processStat.slice(close + 2).trim().split(/\s+/u);
|
|
135
|
+
const startTimeTicks = processFields[19] ?? "";
|
|
136
|
+
if (!/^\d+$/u.test(startTimeTicks)) {
|
|
137
|
+
throw new Error(`Android package ${identity.packageId} returned an invalid process start time on ${identity.deviceSerial}.`);
|
|
138
|
+
}
|
|
139
|
+
const payload = {
|
|
140
|
+
schemaVersion: 1,
|
|
141
|
+
platform: "android",
|
|
142
|
+
deviceSerial: identity.deviceSerial,
|
|
143
|
+
buildFingerprint,
|
|
144
|
+
bootId,
|
|
145
|
+
packageId: identity.packageId,
|
|
146
|
+
versionName: identity.versionName,
|
|
147
|
+
versionCode: identity.versionCode,
|
|
148
|
+
sha256: identity.sha256,
|
|
149
|
+
appProcess: { pid, startTimeTicks }
|
|
150
|
+
};
|
|
151
|
+
return {
|
|
152
|
+
...payload,
|
|
153
|
+
runtimeSessionId: createHash("sha256").update(JSON.stringify(payload)).digest("hex")
|
|
154
|
+
};
|
|
155
|
+
}
|
|
92
156
|
async function relaunchAndroidReleaseArtifactRuntime(identity, deps = defaultDeps) {
|
|
93
157
|
assertAndroidReleaseArtifactRuntime(identity, deps);
|
|
94
158
|
const adb = resolveAdb(deps);
|
|
@@ -291,5 +355,6 @@ const defaultDeps = {
|
|
|
291
355
|
export {
|
|
292
356
|
assertAndroidReleaseArtifactRuntime,
|
|
293
357
|
installAndroidReleaseArtifact,
|
|
358
|
+
readAndroidNativeRuntimeIdentity,
|
|
294
359
|
relaunchAndroidReleaseArtifactRuntime
|
|
295
360
|
};
|
package/dist/adapters.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import http from "node:http";
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
2
3
|
import { execFile } from "node:child_process";
|
|
3
4
|
import { promisify } from "node:util";
|
|
4
5
|
import { compatibilityMode, fixtureSummary, repoShape } from "./doctor.js";
|
|
@@ -45,7 +46,6 @@ const LIVE_ONLY_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
45
46
|
"metamask.assets.import_custom_token",
|
|
46
47
|
"metamask.assets.prepare_send",
|
|
47
48
|
"metamask.assets.review_send",
|
|
48
|
-
"metamask.assets.finish_send",
|
|
49
49
|
"metamask.assets.set_custom_gas",
|
|
50
50
|
"metamask.assets.select_network_scope",
|
|
51
51
|
"metamask.wallet.lock",
|
|
@@ -66,6 +66,7 @@ const LIVE_ONLY_ACTIONS = /* @__PURE__ */ new Set([
|
|
|
66
66
|
"metamask.perps.edit_margin",
|
|
67
67
|
"metamask.perps.update_position_tpsl",
|
|
68
68
|
"metamask.perps.read_funds_confirmation",
|
|
69
|
+
"metamask.perps.assert_visible_consistency",
|
|
69
70
|
"metamask.swap_bridge.read_visible_state",
|
|
70
71
|
"metamask.swap_bridge.select_assets",
|
|
71
72
|
"metamask.swap_bridge.set_amount",
|
|
@@ -164,7 +165,7 @@ async function semanticResult(platform, action, node, context, forceLive = false
|
|
|
164
165
|
...output,
|
|
165
166
|
credentialSource: credentials.source,
|
|
166
167
|
credentialSourceName: credentials.sourceName,
|
|
167
|
-
|
|
168
|
+
expectedIdentitySha256: createHash("sha256").update(credentials.expectedAddress).digest("hex")
|
|
168
169
|
}
|
|
169
170
|
};
|
|
170
171
|
}
|
|
@@ -261,7 +262,6 @@ function createMetaMaskSemanticAdapters(platform, declaredCustomActions = [], pr
|
|
|
261
262
|
...platform === "extension" ? [
|
|
262
263
|
"metamask.assets.prepare_send",
|
|
263
264
|
"metamask.assets.review_send",
|
|
264
|
-
"metamask.assets.finish_send",
|
|
265
265
|
"metamask.assets.set_custom_gas",
|
|
266
266
|
"metamask.assets.select_network_scope"
|
|
267
267
|
] : [],
|
|
@@ -278,25 +278,26 @@ function createMetaMaskSemanticAdapters(platform, declaredCustomActions = [], pr
|
|
|
278
278
|
...platform === "extension" ? [
|
|
279
279
|
"metamask.perps.open_position_action",
|
|
280
280
|
"metamask.perps.edit_margin",
|
|
281
|
-
"metamask.perps.update_position_tpsl"
|
|
281
|
+
"metamask.perps.update_position_tpsl",
|
|
282
|
+
"metamask.perps.assert_visible_consistency"
|
|
282
283
|
] : [],
|
|
283
284
|
"metamask.swap_bridge.read_visible_state",
|
|
284
285
|
"metamask.swap_bridge.select_assets",
|
|
285
286
|
"metamask.swap_bridge.set_amount",
|
|
286
287
|
"metamask.swap_bridge.set_max_amount",
|
|
287
288
|
"metamask.swap_bridge.set_slippage",
|
|
288
|
-
"metamask.swap_bridge.submit_transaction",
|
|
289
|
+
...platform === "mobile" ? ["metamask.swap_bridge.submit_transaction"] : [],
|
|
289
290
|
...platform === "extension" ? ["metamask.settings.set_basic_functionality"] : [],
|
|
290
291
|
"metamask.perps.read_positions",
|
|
291
292
|
"metamask.perps.ensure_positions",
|
|
292
293
|
"metamask.perps.assert_positions",
|
|
293
294
|
"metamask.perps.place_order",
|
|
294
|
-
"metamask.perps.close_positions",
|
|
295
|
+
...platform === "mobile" || platform === "core" ? ["metamask.perps.close_positions"] : [],
|
|
295
296
|
"metamask.perps.read_orders",
|
|
296
297
|
"metamask.perps.close_orders",
|
|
297
298
|
"metamask.perps.ensure_orders",
|
|
298
299
|
"metamask.perps.assert_orders",
|
|
299
|
-
"metamask.perps.start_state",
|
|
300
|
+
...platform === "mobile" || platform === "core" ? ["metamask.perps.start_state"] : [],
|
|
300
301
|
"metamask.perps.teardown_state",
|
|
301
302
|
"metamask.perps.capture_performance",
|
|
302
303
|
...platform === "extension" || platform === "core" ? ["metamask.perps.read_snapshot"] : [],
|
package/dist/command-contract.js
CHANGED
|
@@ -31,6 +31,12 @@ const RECIPE_RUNTIME = {
|
|
|
31
31
|
"--source-digest": value(),
|
|
32
32
|
"--approve-plan": value()
|
|
33
33
|
};
|
|
34
|
+
const RUN_FUNDING = {
|
|
35
|
+
"--funding-request": value(),
|
|
36
|
+
"--funding-preflight": value(),
|
|
37
|
+
"--funding-release-proof": value(),
|
|
38
|
+
"--funding-balance-proof": value()
|
|
39
|
+
};
|
|
34
40
|
const RUNWAY = {
|
|
35
41
|
"--branch": value(),
|
|
36
42
|
"--default-branch": value(),
|
|
@@ -158,10 +164,11 @@ const PUBLIC_COMMAND_CONTRACTS = {
|
|
|
158
164
|
variadic: { label: "key=value", pattern: /^[^=\s]+=.*/u }
|
|
159
165
|
},
|
|
160
166
|
run: {
|
|
161
|
-
options: options(HELP, JSON, JSON_STREAM, TARGET, ADAPTER, MOBILE_PLATFORM, DEVICE, RUNTIME_PORTS, RECIPE_RUNTIME, {
|
|
167
|
+
options: options(HELP, JSON, JSON_STREAM, TARGET, ADAPTER, MOBILE_PLATFORM, DEVICE, RUNTIME_PORTS, RECIPE_RUNTIME, RUN_FUNDING, {
|
|
162
168
|
"--list": bool(),
|
|
163
169
|
"--plan": bool(),
|
|
164
|
-
"--describe": bool()
|
|
170
|
+
"--describe": bool(),
|
|
171
|
+
"--hud": value(["auto", "show", "hide"])
|
|
165
172
|
}),
|
|
166
173
|
positionals: [{ label: "recipe" }],
|
|
167
174
|
minimumPositionals: 1,
|
|
@@ -232,6 +239,7 @@ const PUBLIC_COMMAND_CONTRACTS = {
|
|
|
232
239
|
"--verify": bool(),
|
|
233
240
|
"--runway": bool(),
|
|
234
241
|
"--watch": bool(),
|
|
242
|
+
"--surface": value(["fullscreen", "sidepanel"]),
|
|
235
243
|
"--sidepanel": bool(),
|
|
236
244
|
"--fullscreen": bool(),
|
|
237
245
|
"--url": value(),
|
package/dist/commands/call.js
CHANGED
|
@@ -208,16 +208,19 @@ async function handleCall(argv) {
|
|
|
208
208
|
const artifactsDir = optionString(options, "artifactsDir") ?? defaultCallArtifactsDir(target, resolvedAction, args);
|
|
209
209
|
recordCommandEvidence(artifactsDir);
|
|
210
210
|
const requestedRuntimeOptions = runtimeOptionsFromCli(options);
|
|
211
|
+
const inheritedSource = process.env.FARMSLOT_RECIPE_SOURCE_TRUST || process.env.FARMSLOT_RECIPE_SOURCE_KIND || process.env.FARMSLOT_RECIPE_SOURCE_NAME || process.env.FARMSLOT_RECIPE_SOURCE_DIGEST;
|
|
211
212
|
const callRuntimeOptions = {
|
|
212
213
|
...requestedRuntimeOptions,
|
|
213
214
|
...librarySources ? { librarySources } : {},
|
|
214
215
|
autoHud: false,
|
|
215
216
|
suppressLibraryResolutionLogs: true,
|
|
216
217
|
stdoutIsMachineContract: json,
|
|
217
|
-
source: requestedRuntimeOptions.source
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
...requestedRuntimeOptions.source ? { source: requestedRuntimeOptions.source } : inheritedSource ? {} : {
|
|
219
|
+
source: {
|
|
220
|
+
kind: "operator",
|
|
221
|
+
trust: "trusted",
|
|
222
|
+
name: "mm-harness call"
|
|
223
|
+
}
|
|
221
224
|
}
|
|
222
225
|
};
|
|
223
226
|
let preflightedExecution;
|
|
@@ -37,6 +37,10 @@ import { acquireCheckoutLock } from "../../checkout-lock.js";
|
|
|
37
37
|
import { JsonStreamWriter } from "../../json-stream.js";
|
|
38
38
|
import { ensureExtensionConsoleCapture } from "../../adapters/extension/console-capture.js";
|
|
39
39
|
import { extensionProductConfigBlock } from "../../adapters/extension/product-config.js";
|
|
40
|
+
import {
|
|
41
|
+
releaseArtifactState,
|
|
42
|
+
runtimeDistCheck
|
|
43
|
+
} from "../../adapters/extension/runtime-decision.js";
|
|
40
44
|
const LAUNCH_BOOLEANS = /* @__PURE__ */ new Set([
|
|
41
45
|
"build",
|
|
42
46
|
"watch",
|
|
@@ -124,13 +128,41 @@ async function handleLaunchLocked(argv, stream) {
|
|
|
124
128
|
const wantWatch = flag(options, "watch");
|
|
125
129
|
const wantRunway = flag(options, "runway");
|
|
126
130
|
const wantClearMetro = flag(options, "clearMetro");
|
|
131
|
+
const requestedSurface = str(options, "surface");
|
|
132
|
+
const legacySidepanel = flag(options, "sidepanel");
|
|
133
|
+
const legacyFullscreen = flag(options, "fullscreen");
|
|
127
134
|
if (wantRunway && adapter !== "mobile") {
|
|
128
135
|
return launchUsage(jsonOutput, stream, "runway is mobile-only.", "drop --runway for the extension");
|
|
129
136
|
}
|
|
130
137
|
if (wantClearMetro && adapter !== "mobile") {
|
|
131
138
|
return launchUsage(jsonOutput, stream, "--clear-metro is mobile-only.", "drop --clear-metro for the extension");
|
|
132
139
|
}
|
|
133
|
-
|
|
140
|
+
if (requestedSurface !== void 0 && requestedSurface !== "fullscreen" && requestedSurface !== "sidepanel") {
|
|
141
|
+
return launchUsage(
|
|
142
|
+
jsonOutput,
|
|
143
|
+
stream,
|
|
144
|
+
`--surface must be fullscreen or sidepanel (got: ${requestedSurface}).`,
|
|
145
|
+
"pass --surface fullscreen or --surface sidepanel"
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
if (legacySidepanel && legacyFullscreen) {
|
|
149
|
+
return launchUsage(
|
|
150
|
+
jsonOutput,
|
|
151
|
+
stream,
|
|
152
|
+
"--sidepanel and --fullscreen conflict.",
|
|
153
|
+
"pass exactly one Extension surface"
|
|
154
|
+
);
|
|
155
|
+
}
|
|
156
|
+
const legacySurface = legacySidepanel ? "sidepanel" : legacyFullscreen ? "fullscreen" : void 0;
|
|
157
|
+
if (requestedSurface && legacySurface && requestedSurface !== legacySurface) {
|
|
158
|
+
return launchUsage(
|
|
159
|
+
jsonOutput,
|
|
160
|
+
stream,
|
|
161
|
+
`--surface ${requestedSurface} conflicts with --${legacySurface}.`,
|
|
162
|
+
"pass one consistent Extension surface"
|
|
163
|
+
);
|
|
164
|
+
}
|
|
165
|
+
const displayMode = requestedSurface ?? legacySurface ?? "fullscreen";
|
|
134
166
|
for (const portFlag of ["cdpPort", "watcherPort"]) {
|
|
135
167
|
const value = str(options, portFlag);
|
|
136
168
|
if (value !== void 0 && !/^\d+$/u.test(value)) {
|
|
@@ -189,7 +221,11 @@ async function handleLaunchLocked(argv, stream) {
|
|
|
189
221
|
}
|
|
190
222
|
stream.phase("healthcheck");
|
|
191
223
|
if (adapter === "extension") {
|
|
192
|
-
const
|
|
224
|
+
const artifact = releaseArtifactState(target);
|
|
225
|
+
const reusable = !wantWatch && tier !== "build" && await extensionRuntimeReusable(target, artifact);
|
|
226
|
+
const verifiedReleaseReuse = artifact.status === "valid" && reusable;
|
|
227
|
+
const verifiedReleaseLaunch = !wantWatch && tier !== "build" && artifact.status === "valid" && runtimeDistCheck(target, artifact).status === "fresh";
|
|
228
|
+
const willBuild = !reusable;
|
|
193
229
|
if (willBuild) {
|
|
194
230
|
const productConfigBlock = extensionProductConfigBlock(target);
|
|
195
231
|
if (productConfigBlock) {
|
|
@@ -202,7 +238,7 @@ async function handleLaunchLocked(argv, stream) {
|
|
|
202
238
|
});
|
|
203
239
|
}
|
|
204
240
|
}
|
|
205
|
-
const depsBlock = extensionDepsBlock(target);
|
|
241
|
+
const depsBlock = verifiedReleaseReuse || verifiedReleaseLaunch ? null : extensionDepsBlock(target);
|
|
206
242
|
if (depsBlock) {
|
|
207
243
|
if (heal === "off") {
|
|
208
244
|
return launchUsage(jsonOutput, stream, depsBlock.message, depsBlock.userAction);
|
|
@@ -498,6 +534,22 @@ async function executeComposition(adapter, mobileTarget, tier, wantWatch, target
|
|
|
498
534
|
return launchExtension(target, tier, wantWatch, displayMode);
|
|
499
535
|
}
|
|
500
536
|
async function finishLaunch(jsonOutput, machine, stream, adapter, mobileTarget, tier, displayMode, target, state, wantWatch, wantVerify, sidepanelRecoveryAllowed = true) {
|
|
537
|
+
if (adapter === "extension" && displayMode === "fullscreen") {
|
|
538
|
+
const sidepanelSh = path.join(runnerDir, "adapters/extension/sidepanel-toggle.sh");
|
|
539
|
+
const sidepanelArgs = ["close"];
|
|
540
|
+
if (process.env.CDP_PORT) sidepanelArgs.push("--cdp-port", process.env.CDP_PORT);
|
|
541
|
+
const fullscreen = spawnScript(sidepanelSh, sidepanelArgs, target, machine, { REPO: target });
|
|
542
|
+
if (fullscreen.status !== 0) {
|
|
543
|
+
return launchFail(jsonOutput, stream, adapter, mobileTarget, tier, state, target, {
|
|
544
|
+
code: "FULLSCREEN_OPEN_FAILED",
|
|
545
|
+
message: "app launched but restoring the fullscreen Extension surface failed.",
|
|
546
|
+
recoverable: false,
|
|
547
|
+
userAction: `bash adapters/extension/sidepanel-toggle.sh close --cdp-port ${process.env.CDP_PORT ?? "<CDP_PORT>"}`,
|
|
548
|
+
exitCode: EXIT.runtime,
|
|
549
|
+
originalError: fullscreen.output.trim() || void 0
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
}
|
|
501
553
|
if (adapter === "extension" && displayMode === "sidepanel") {
|
|
502
554
|
const sidepanelSh = path.join(runnerDir, "adapters/extension/sidepanel-toggle.sh");
|
|
503
555
|
const sidepanelArgs = ["open"];
|