@deeeed/metamask-harness 0.80.0 → 0.82.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 +43 -0
- package/adapters/manifest.json +48 -64
- package/adapters/{terminal → web-dapp}/cleanup.mjs +9 -9
- package/adapters/{terminal → web-dapp}/inject.mjs +14 -14
- package/adapters/{terminal → web-dapp}/launch.mjs +27 -24
- package/adapters/web-dapp/lib/blocked-hosts.mjs +24 -0
- package/adapters/{terminal → web-dapp}/lib/cdp-client.mjs +1 -1
- package/adapters/{terminal → web-dapp}/lib/display.mjs +1 -1
- package/adapters/{terminal → web-dapp}/lib/hud.mjs +1 -1
- package/adapters/web-dapp/lib/policy.mjs +79 -0
- package/adapters/{terminal → web-dapp}/lib/processes.mjs +8 -8
- package/adapters/{terminal → web-dapp}/lib/readiness.mjs +32 -34
- package/adapters/{terminal → web-dapp}/stop.mjs +5 -4
- package/adapters/{terminal → web-dapp}/verify.mjs +9 -6
- package/adapters/{terminal → web-dapp}/wallet-host.mjs +44 -227
- package/dist/adapters/core/actions.js +4 -1
- package/dist/adapters/extension/actions.js +6 -2
- package/dist/adapters/extension/network-observer.js +13 -0
- package/dist/adapters/extension/run.js +115 -31
- package/dist/adapters/mobile/actions.js +4 -1
- package/dist/adapters/mobile/network-observer.js +68 -0
- package/dist/adapters/mobile/performance-observer.js +1 -3
- package/dist/adapters/mobile/run.js +32 -9
- package/dist/adapters/perps-order-inputs.js +36 -0
- package/dist/adapters/plugin-surface.js +98 -0
- package/dist/adapters/proof-failure.js +11 -0
- package/dist/adapters/surface.js +1 -1
- package/dist/adapters/{terminal → web-dapp}/actions.js +11 -8
- package/dist/adapters/web-dapp/console-capture.js +63 -0
- package/dist/adapters/web-dapp/doctor-checks.js +33 -0
- package/dist/adapters/{terminal → web-dapp}/launch.js +21 -19
- package/dist/adapters/web-dapp/overlay.js +21 -0
- package/dist/adapters/web-dapp/readiness.js +10 -0
- package/dist/adapters/{terminal → web-dapp}/run.js +22 -17
- package/dist/adapters/{terminal → web-dapp}/stop.js +5 -4
- package/dist/adapters/web-dapp/surface.js +149 -0
- package/dist/adapters.js +20 -4
- package/dist/cli.js +34 -6
- package/dist/command-contract.js +29 -7
- package/dist/commands/check.js +32 -8
- package/dist/commands/completion-candidates.js +5 -5
- package/dist/commands/doctor.js +19 -2
- package/dist/commands/farmslot-ready.js +1 -0
- package/dist/commands/help.js +1 -1
- package/dist/commands/launch/device.js +5 -5
- package/dist/commands/self-test.js +10 -5
- package/dist/engine.js +47 -0
- package/dist/funding-execution-context.js +81 -0
- package/dist/manifest.js +48 -24
- package/dist/mm-harness-cli.js +20 -3
- package/dist/performance-observation.js +7 -15
- package/dist/recipe-assistance-context.js +6 -7
- package/dist/runner.js +28 -8
- package/docs/CONTRIBUTING.md +4 -2
- package/docs/RECIPES.md +31 -3
- package/docs/cli-contract.md +5 -3
- package/library/actions/core/perps/_controller.mjs +24 -28
- package/library/actions/core/perps/_rewards.mjs +186 -0
- package/library/actions/core/perps/close_positions.mjs +1 -1
- package/library/actions/core/perps/place_order.mjs +167 -45
- package/library/actions/core/perps/quote_fees.mjs +7 -4
- package/library/actions/core/wallet/list_accounts.mjs +1 -1
- package/library/actions/extension/perps/close_visible_position.mjs +5 -0
- package/library/actions/extension/perps/edit_margin.mjs +4 -0
- package/library/actions/extension/perps/mutation-receipt.mjs +95 -3
- package/library/actions/extension/perps/perps.mjs +320 -3
- package/library/actions/extension/perps/place_order.mjs +3 -1
- package/library/actions/extension/perps/update_position_tpsl.mjs +4 -0
- package/library/actions/extension/platform/cdp.mjs +71 -3
- package/library/actions/mobile/perps/perps.mjs +216 -11
- package/library/actions/mobile/perps/place_order.mjs +3 -1
- package/library/actions/shared/console-collector.mjs +1 -1
- package/library/actions/shared/perps/order-policy.mjs +177 -0
- package/library/actions/{core/wallet/_fixture-accounts.mjs → shared/wallet/fixture-accounts.mjs} +18 -3
- package/library/actions/{terminal → web-dapp}/app/assert_no_console_errors.mjs +4 -4
- package/library/actions/{terminal → web-dapp}/app/launch.mjs +8 -7
- package/library/actions/{terminal → web-dapp}/platform/hud.mjs +7 -6
- package/library/actions/{terminal → web-dapp}/platform/page.mjs +13 -12
- package/library/actions/{terminal → web-dapp}/platform/runtime.mjs +16 -12
- package/library/actions/web-dapp/ui/navigate.mjs +24 -0
- package/library/actions/web-dapp/wallet/_signature-log.mjs +32 -0
- package/library/actions/{terminal → web-dapp}/wallet/assert_signatures.mjs +8 -14
- package/library/actions/{terminal → web-dapp}/wallet/list_accounts.mjs +2 -2
- package/library/actions/web-dapp/wallet/read_signatures.mjs +38 -0
- package/library/manifests/core.action-manifest.json +6 -2
- package/library/manifests/extension.action-manifest.json +4 -0
- package/library/manifests/mobile.action-manifest.json +12 -0
- package/library/manifests/{terminal.action-manifest.json → web-dapp.action-manifest.json} +18 -702
- package/package.json +6 -6
- package/site/assets/help-recipes.json +1 -0
- package/adapters/terminal/lib/mainnet-guard.mjs +0 -177
- package/adapters/terminal/lib/origin.mjs +0 -30
- package/adapters/terminal/lib/page-script.mjs +0 -246
- package/adapters/terminal/lib/strict-wallet.mjs +0 -138
- package/dist/adapters/terminal/console-capture.js +0 -59
- package/dist/adapters/terminal/doctor-checks.js +0 -22
- package/dist/adapters/terminal/overlay.js +0 -18
- package/dist/adapters/terminal/readiness.js +0 -11
- package/dist/adapters/terminal/surface.js +0 -133
- package/dist/commands/call.js +0 -671
- package/dist/commands/list-executables.js +0 -245
- package/dist/commands/manifest.js +0 -477
- package/dist/commands/run-engine.js +0 -1301
- package/dist/commands/run.js +0 -730
- package/dist/discovery.js +0 -15
- package/dist/metamask-action-validation.js +0 -68
- package/dist/network-observation.js +0 -290
- package/library/actions/terminal/perps/_venue.mjs +0 -242
- package/library/actions/terminal/perps/assert_orders.mjs +0 -54
- package/library/actions/terminal/perps/assert_positions.mjs +0 -40
- package/library/actions/terminal/perps/read_orders.mjs +0 -26
- package/library/actions/terminal/perps/read_positions.mjs +0 -25
- package/library/actions/terminal/perps/read_snapshot.mjs +0 -40
- package/library/actions/terminal/perps/revoke_agent.mjs +0 -58
- package/library/actions/terminal/perps/teardown_state.mjs +0 -104
- package/library/actions/terminal/ui/navigate.mjs +0 -31
- package/library/actions/terminal/wallet/_signature-log.mjs +0 -177
- package/library/actions/terminal/wallet/read_signatures.mjs +0 -37
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,49 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.82.0 - 2026-10-07
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Adapters a recipe library declares in `recipe-library.json` `adapters` (on `--library`, the `RECIPE_LIBRARY_PATH` you set or else the personal library, or `config set libraries.<name>`; never a task-local library, a sibling checkout or `<target>/.skills-cache`, which serve recipe discovery only) work as `--adapter <id>`: the command loads the plugin only when it selects it (the last `--adapter`, else the last `--platform`), composes it on the adapter it `extends` (a built-in or another plugin; a plugin that extends a built-in also offers the built-in's official actions, library actions and observers), runs its doctor checks after the shared ones (a check that throws is a required failure, `adapter-plugin:<id>:doctor`) and runs its in-code actions. `call <action> --adapter <id> --help` shows a plugin action's fields. `doctor --json` names each loaded plugin with its library and digest (`adapter-plugin:<id>`), and an approved plan (`--approve-plan`) binds that digest: every run on a plugin adapter carries it in `METAMASK_RECIPE_ADAPTER_PLUGIN`, so editing the plugin's module or a file beside it fails the old approval with `RECIPE_APPROVAL_MISMATCH`, whatever actions the recipe runs. A plugin's own manifest may declare official actions and observers for that plugin, but a plugin that extends it inherits only its custom actions: inherited official actions and observers come from the bundled library alone, and a parent manifest outside it that declares one fails with `ACTION_MANIFEST_INVALID` (a JSON envelope under `--json`), like any other action manifest the plugin can't use. A malformed `RECIPE_LIBRARY_PATH` never blocks a built-in adapter. A plugin may import from disk only its module's directory and the library's `actions/` (`RECIPE_SOURCE_INVALID` otherwise); a bare package it imports resolves from mm-harness's own install. Plugins need Node.js 22.15 or later. A refused plugin fails with `ADAPTER_SDK_UNSUPPORTED`, `ADAPTER_PLUGIN_INVALID`, `ADAPTER_PLUGIN_LOAD_FAILED`, `ADAPTER_ID_CONFLICT`, `ADAPTER_EXTENDS_UNKNOWN` or `RECIPE_SOURCE_INVALID`, exit 2. A plugin that extends nothing (or only other plugins) gets no Runway provisioning and no wallet fixture operations; one that extends a built-in keeps the built-in's (the terminal plugin keeps web-dapp's `fixtures init|sync|set`).
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- **BREAKING:** the built-in `terminal` adapter becomes the generic `web-dapp` built-in, and the Web Terminal becomes the `terminal` plugin of the `experimental-metamask-recipe-terminal` library (`recipe-library.json` `adapters.terminal`, `extends: web-dapp`). `--adapter terminal` needs a revision of that library that declares `adapters.terminal` (8b409b6 or later, on its `feat/console-allowlist` branch; the library's `main` does not declare it yet) on `RECIPE_LIBRARY_PATH`, as on the Terminal farm slots. A host without it (an older checkout, or none, as on macpro and mini today) refuses every `--adapter terminal` command: `--adapter must be mobile, extension, core, or web-dapp; received 'terminal'`. Update the library before this harness; the hooks, runtime dir (`temp/recipe/runtime/terminal`), doctor check ids, evidence names and action ids are unchanged.
|
|
14
|
+
- The plugin holds everything Hyperliquid: the venue policy (blocked and served hosts, link hosts, the launch probe, the testnet typed-data refusal, the start page and product routes, the injected wallet's start chain, the checkout dependencies and detection, the dev-server testnet variable), the signature-log rules, and the 7 `metamask.perps.*` venue actions with their manifest. They leave this package: `adapters/terminal/lib/mainnet-guard.mjs`, `adapters/terminal/policy.mjs`, `library/actions/terminal/perps/`.
|
|
15
|
+
- `web-dapp` keeps the generic parts (`adapters/web-dapp/`, `library/actions/web-dapp/`, `library/manifests/web-dapp.action-manifest.json`): launch, stop, verify, inject, cleanup, the wallet host, console capture, `ui.*`, `app.hud`, `metamask.app.*` and `metamask.wallet.*`. It reads the venue policy from the module in `RECIPE_WEB_DAPP_POLICY`, which the harness sets when it adopts an adapter that extends `web-dapp`; the leaf scripts no longer take `--policy-module`.
|
|
16
|
+
- `web-dapp` selected on its own has no venue: launch, verify and runtime status refuse, and doctor fails its required `web-dapp-venue-policy` check. The policy module is checked when it loads (every member web-dapp reads, and `policyVersion: 1`).
|
|
17
|
+
- Without `--adapter`, a Terminal checkout is no longer auto-detected, and `actions --matrix` has no `terminal` column: the loader imports a plugin only when a command selects it (the ADR trust boundary), so the plugin's detection runs once `--adapter terminal` selects it. Every farm hook passes `--adapter terminal`.
|
|
18
|
+
- `check diff` takes any registered or library-declared adapter for `--adapter`/`--platform` (it listed the built-ins).
|
|
19
|
+
- Depend on `@farmslot/recipe-cli` 0.6.0 and `@farmslot/adapter-sdk` 0.5.0 (were 0.5.0 and 0.4.0), which carry recipe-library adapter plugins, adapter-aware trusted-mutation hooks and one environment scope for a run's ports (recipe-cli 0.6.0 also writes the task's acceptance ledger from a recipe's proof targets and warns at `run --plan` on command nodes whose exit code cannot prove the claim), and on `@farmslot/recipe-runner` 0.25.0 and `@farmslot/adapter-rn` 0.17.1 (were 0.24.0 and 0.17.0). `@farmslot/adapter-web` 0.3.1, `@farmslot/agent-runtime` 0.17.0 and `@farmslot/protocol` 0.34.0 are unchanged, and the install still holds one `@farmslot/recipe-runner`.
|
|
20
|
+
- The Extension proof-runtime check no longer pins `--cdp-port`/`--watcher-port` itself: `run` and `call` run it, the observers and the execution in one environment scope, so every platform's runtime check and observers see the run's ports, its dev-server port names (`METRO_PORT`) and its platform environment. The performance observers read their ports from that environment (`PerformanceObserver.backend(target, env)`), and `self-test` opens the scope around its smoke runs.
|
|
21
|
+
|
|
22
|
+
## 0.81.0 - 2026-10-07
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
|
|
26
|
+
- Core's headless `RewardsController:getPerpsDiscountForAccount` now asks the rewards API for the Perps fee discount, with the calls Mobile's RewardsController makes for the selected wallet fixture account: the opt-in check, `mobile-login` signed with personal_sign, then `/vip/fees`, and Mobile's discount math. Like Mobile, it logs in when the opt-in check itself fails, reads a 401 from login as not opted in, and gives each request 10 seconds. An account that never opted in, or an address outside the fixture, gets no discount (`null`). The session token stays in memory, and an error keeps only the HTTP status, never the response body. The backend defaults to UAT, as on Mobile dev builds; `CORE_PERPS_REWARDS_API_URL` selects another. `metamask.perps.quote_fees` reports `rewardsSource: "rewards-api"` with each call it answered; `rewards_discount_bips` still replaces the answer, to prove a discount the non-VIP fixture accounts cannot get.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
|
|
30
|
+
- **BREAKING:** Perps orders follow one rule on Mobile, Extension and Core (F21). Testnet orders need no funding proof, so `run` and `call metamask.perps.place_order --adapter extension` work without the four `--funding-*` files, as on Mobile. A mainnet order is allowed only from a synced fixture account, as a 1x isolated market order with `close_immediately: true` and `network: mainnet` declared, whose worst-case fill (one size step plus max slippage over the request) stays within $15; anything else is refused before dispatch with `PERPS_MAINNET_ORDER_REFUSED`. This newly guards Mobile mainnet orders, which had no check. `place_order` takes `close_immediately` (and Mobile takes `network`): from a flat market (no position or resting order), the action closes the opened position itself, on the account and network the rule allowed, and proves the market flat. Extension background market placements now prove their postcondition on current builds (positions carry a signed size, no side). On the Extension, a run without funding files runs its mutations in a fixture-policy lane with a run-owned execution ledger; the funding files remain the stricter release lane. Core keeps `CORE_PERPS_ALLOW_MAINNET_WRITES=1`. The fixture account derivation behind `metamask.wallet.list_accounts` moved to `library/actions/shared/wallet/fixture-accounts.mjs`, shared with the order rule.
|
|
31
|
+
- Extension screenshots: a capture-helper that times out is killed with SIGKILL together with everything it started (the npm wrapper runs the native binary as a child). It can ignore SIGTERM and linger, making later captures hang. Before the next capture, this user's snapshot helpers orphaned by an earlier run (matched by executable and subcommand, including the configured path) are killed with their children; recording sessions and helpers whose harness is alive are left alone. A CDP fallback screenshot records `fallbackReason` (the capture-helper error) next to `fallbackFrom`, for `run`'s fallback listing in the next `@farmslot/recipe-cli` (deeeed/farmslot#829).
|
|
32
|
+
- Extension `check diff` also runs `yarn verify-locales --quiet` when a file under `app/_locales/**` changed, moved or was deleted, and `yarn circular-deps:check` when any JS/TS file changed, moved or was deleted; otherwise each reports `skip`. `changedFiles` now lists deleted files and both sides of a rename (lint, format and tests still run on existing files only).
|
|
33
|
+
- Help text only: the `help` quality guidance says to assert a test command's literal pass count with `assert_output`, since `jest -t` exits 0 when no test matches.
|
|
34
|
+
- The Web Terminal's venue policy lives in one module, `adapters/terminal/policy.mjs`: the blocked and served Hyperliquid hosts, link hosts, the launch probe, the testnet typed-data refusal, the start page (`/order/BTC`), the injected wallet's start chain (42161), the checkout dependencies and the dev-server testnet variable. The launch, verify, doctor and the wallet host take it as an argument instead of naming the venue themselves; the leaf scripts `adapters/terminal/{launch,verify,wallet-host}.mjs` require `--policy-module <path>`, which the Terminal adapter passes. Behaviour is unchanged.
|
|
35
|
+
- The Web Terminal's web3 part moves to `@farmslot/adapter-web/dapp` (`@farmslot/adapter-web` 0.3.0): the strict test wallet, the page script that logs the app's wallet requests (or injects the provider the wallet host answers), the wallet host's judgement of each request's frame and document, and the reading and assertion of the wallet request log behind `metamask.wallet.read_signatures` / `assert_signatures`. The exact-origin checks move to `@farmslot/adapter-web/origin`. The Terminal passes in its own parts: the Hyperliquid testnet refusal, the injected wallet's MetaMask identity, and the L1 and mainnet rules of the signature assertion. Log entries, refusals and action outputs are unchanged. The page's bindings are now `__farmslotWallet{Log,Request,Resolve}` and its marker `__farmslotDapp` (were `__mmTerminal*`).
|
|
36
|
+
- Depend on `@farmslot/recipe-cli` 0.5.0 and `@farmslot/adapter-sdk` 0.4.0 (were 0.4.1 and 0.3.1), which carry the run and call engine and action discovery. `@farmslot/adapter-web` moves 0.2.0 → 0.3.1 (`./dapp` from 0.3.0, the probe fix in 0.3.1); `@farmslot/adapter-rn` 0.17.0, `@farmslot/recipe-runner` 0.24.0, `@farmslot/agent-runtime` 0.17.0 and `@farmslot/protocol` 0.34.0 are unchanged, so the install still holds one `@farmslot/recipe-runner`.
|
|
37
|
+
- `run` and `call` move to `@farmslot/recipe-cli/harness` with their engine (recipe execution, preflight, bounded healing, recipe library resolution, static validation, network observation) and `describeManifestActions`, the action catalog entries they share with `actions`. mm-harness hands them one engine, `src/engine.ts`: the bundled `metamask` library, `src/manifest.ts`, the MetaMask action risk table, `createMetaMaskRunner`, funded mutations bound from the `--funding-*` flags, and the console classifier. Device targeting and the `fixture.file` plan step stay in mm-harness and are passed as command options. Flags, output and the CLI goldens are unchanged.
|
|
38
|
+
- The Mobile and Extension proof-runtime checks (`MOBILE_SOURCE_NOT_LOADED`, `EXTENSION_RUNTIME_*`) move into `src/adapters/mobile/run.ts` and `src/adapters/extension/run.ts`, and the Mobile and Extension network backends into `src/adapters/{mobile,extension}/network-observer.ts`. Platforms declare network and performance observation through the SDK's `observation`; the performance observer stays in mm-harness and starts through `observation.performance.start`.
|
|
39
|
+
- The Perps order rule (an order needs market, side and amount) runs in each platform's `actions.inputFindings` through `src/adapters/perps-order-inputs.ts`; `AdapterSurface` drops `actions.ensureChecksOnly` (the Extension passes it to the rule) and `observation` (now the SDK's). `src/metamask-action-validation.ts` is gone; the node loop is `@farmslot/recipe-cli`'s `validateActionInputs`.
|
|
40
|
+
- Discovery moves to `@farmslot/recipe-cli/harness` and runs on `src/engine.ts`'s catalog: `actions` (lookup, search, categories, `--raw`, `--matrix`), `run --list`, `run <recipe> --describe` and `call --list`. `src/commands/manifest.ts` and `src/commands/list-executables.ts` are gone. Flags, `--json` output and the CLI goldens are unchanged. Two human-output changes:
|
|
41
|
+
- `actions --action <name>` (one match) and `call <action> --help` print the same action detail: the call form, description, source and adapter, risk, result cases, typed fields, up to two example calls, and the first example as a runnable call with its recipe node. The example calls now carry only the action's fields (no `next=done`, `cases` or `default`), shell-quoted, and use the full name when the short one is ambiguous. `actions --action` used to print a one-line catalog entry; `call --help` gains the risk, result cases and runnable example.
|
|
42
|
+
- The `actions --matrix` columns come from the registered platforms (same four, same order), and a capability no platform declares reads `across Mobile, Extension, Core, or Terminal` (was `across Mobile, Extension, or Core`).
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- Browser probes stop when the resolver is stopped: `@farmslot/adapter-web` 0.3.1 (was 0.3.0) stops the probe browser and removes its profile when the resolver gets a signal (SIGTERM, SIGINT, SIGHUP, SIGQUIT); a probe browser left by a resolver killed with SIGKILL is reaped by the next probe.
|
|
47
|
+
|
|
5
48
|
## 0.80.0 - 2026-10-06
|
|
6
49
|
|
|
7
50
|
### Added
|
package/adapters/manifest.json
CHANGED
|
@@ -584,131 +584,115 @@
|
|
|
584
584
|
"outputs": "numbered checkouts, saved preferences, and run summary"
|
|
585
585
|
},
|
|
586
586
|
{
|
|
587
|
-
"id": "
|
|
588
|
-
"entry": "adapters/
|
|
587
|
+
"id": "web-dapp/launch",
|
|
588
|
+
"entry": "adapters/web-dapp/launch.mjs",
|
|
589
589
|
"kind": "node",
|
|
590
|
-
"purpose": "Start or reuse the
|
|
590
|
+
"purpose": "Start or reuse the web-dapp slot browser (Chrome for Testing unless TERMINAL_CHROME_BIN; MetaMask extension with the fixture imported, or the injected strict wallet) and its wallet host. On macOS the browser starts through LaunchServices with no startup window; in testnet mode the hosts the venue policy blocks are unresolvable for the whole browser.",
|
|
591
591
|
"inputs": "--target --cdp-port --app-port [--signer extension|injected] [--account] [--fresh-profile] [--headless|--headful] [--slow-mo] [--network mainnet --mainnet-confirmation REAL_FUNDS] [--json]; env TERMINAL_CHROME_BIN, TERMINAL_EXTENSION_DIR, TERMINAL_EXTENSION_CHECKOUT, RECIPE_WALLET_FIXTURE, TERMINAL_HEADLESS, TERMINAL_SLOW_MO, TERMINAL_WINDOW, TERMINAL_SCREEN",
|
|
592
|
-
"outputs": "<runtime>/
|
|
592
|
+
"outputs": "<runtime>/<adapter id>/browser.json + pid/log files; JSON on stdout; exit 0/1/2"
|
|
593
593
|
},
|
|
594
594
|
{
|
|
595
|
-
"id": "
|
|
596
|
-
"entry": "adapters/
|
|
595
|
+
"id": "web-dapp/wallet-host",
|
|
596
|
+
"entry": "adapters/web-dapp/wallet-host.mjs",
|
|
597
597
|
"kind": "node",
|
|
598
|
-
"purpose": "Long-running CDP observer: opens the app tab blank, hooks it once per target, navigates it to the app and verifies the log binding before ready; logs every wallet request from the app top frame and every MetaMask confirmation shown; answers the injected strict wallet in injected mode (other frames and blank popups get 4100).",
|
|
598
|
+
"purpose": "Long-running CDP observer: opens the app tab blank, hooks it once per target, navigates it to the app and verifies the log binding before ready; logs every wallet request from the app top frame and every MetaMask confirmation shown; answers the injected strict wallet in injected mode (other frames and blank popups get 4100). The page script, the frame judgement and the strict wallet are @farmslot/adapter-web/dapp's; the venue policy (RECIPE_WEB_DAPP_POLICY) gives the testnet refusal, hosts, probe and start page.",
|
|
599
599
|
"inputs": "--cdp-port --runtime-dir --signer --app-origin [--extension-id] [--target --account --start-chain] [--browser-app <.app>]",
|
|
600
|
-
"outputs": "<runtime>/
|
|
600
|
+
"outputs": "<runtime>/<adapter id>/wallet-requests.jsonl, wallet-host.ready"
|
|
601
601
|
},
|
|
602
602
|
{
|
|
603
|
-
"id": "
|
|
604
|
-
"entry": "adapters/
|
|
603
|
+
"id": "web-dapp/stop",
|
|
604
|
+
"entry": "adapters/web-dapp/stop.mjs",
|
|
605
605
|
"kind": "node",
|
|
606
|
-
"purpose": "Stop the
|
|
606
|
+
"purpose": "Stop the web-dapp slot browser, wallet host and console collector and prove the CDP port is free.",
|
|
607
607
|
"inputs": "--target [--cdp-port]",
|
|
608
608
|
"outputs": "JSON on stdout; exit 0/1"
|
|
609
609
|
},
|
|
610
610
|
{
|
|
611
|
-
"id": "
|
|
612
|
-
"entry": "adapters/
|
|
611
|
+
"id": "web-dapp/inject",
|
|
612
|
+
"entry": "adapters/web-dapp/inject.mjs",
|
|
613
613
|
"kind": "node",
|
|
614
|
-
"purpose": "
|
|
614
|
+
"purpose": "web-dapp install: overlay marker, runtime dir and wallet fixture (0600); refuses a checkout the venue policy does not match; no product writes.",
|
|
615
615
|
"inputs": "--target [--fixture] [--force]; env RECIPE_WALLET_FIXTURE",
|
|
616
|
-
"outputs": "<harness>/
|
|
616
|
+
"outputs": "<harness>/<adapter id>/install.json, <runtime>/wallet-fixture.json; exit 0/1"
|
|
617
617
|
},
|
|
618
618
|
{
|
|
619
|
-
"id": "
|
|
620
|
-
"entry": "adapters/
|
|
619
|
+
"id": "web-dapp/cleanup",
|
|
620
|
+
"entry": "adapters/web-dapp/cleanup.mjs",
|
|
621
621
|
"kind": "node",
|
|
622
|
-
"purpose": "
|
|
622
|
+
"purpose": "web-dapp cleanup: stop browser/host, remove the slot's runtime state, overlay marker and installed fixture; profiles kept unless --reset-profile.",
|
|
623
623
|
"inputs": "--target [--cdp-port] [--reset-profile]",
|
|
624
624
|
"outputs": "JSON on stdout; exit 0/1"
|
|
625
625
|
},
|
|
626
626
|
{
|
|
627
|
-
"id": "
|
|
628
|
-
"entry": "adapters/
|
|
627
|
+
"id": "web-dapp/verify",
|
|
628
|
+
"entry": "adapters/web-dapp/verify.mjs",
|
|
629
629
|
"kind": "node",
|
|
630
|
-
"purpose": "Read-only
|
|
630
|
+
"purpose": "Read-only web-dapp slot readiness: checkout, deps, dev server, forced testnet, fixture, browser start probe, extension copy, CDP port.",
|
|
631
631
|
"inputs": "--target [--cdp-port] [--watcher-port] [--signer] [--account] [--json]",
|
|
632
632
|
"outputs": "readiness report; exit 0 ready / 1 not ready"
|
|
633
633
|
},
|
|
634
634
|
{
|
|
635
|
-
"id": "
|
|
636
|
-
"entry": "adapters/
|
|
635
|
+
"id": "web-dapp/lib-cdp-client",
|
|
636
|
+
"entry": "adapters/web-dapp/lib/cdp-client.mjs",
|
|
637
637
|
"kind": "lib",
|
|
638
638
|
"purpose": "Browser-level CDP client with flattened target sessions for the wallet host.",
|
|
639
639
|
"inputs": "CdpClient.connect(url)",
|
|
640
640
|
"outputs": "send/on"
|
|
641
641
|
},
|
|
642
642
|
{
|
|
643
|
-
"id": "
|
|
644
|
-
"entry": "adapters/
|
|
645
|
-
"kind": "lib",
|
|
646
|
-
"purpose": "Page script source: wallet request logging and the injected strict provider (EIP-6963 io.metamask).",
|
|
647
|
-
"inputs": "pageScriptSource({ signer, appOrigin })",
|
|
648
|
-
"outputs": "script source string"
|
|
649
|
-
},
|
|
650
|
-
{
|
|
651
|
-
"id": "terminal/lib-strict-wallet",
|
|
652
|
-
"entry": "adapters/terminal/lib/strict-wallet.mjs",
|
|
653
|
-
"kind": "lib",
|
|
654
|
-
"purpose": "EIP-1193 strict test wallet enforcing typed-data chainId == active chain.",
|
|
655
|
-
"inputs": "createStrictWallet({ account, chainId })",
|
|
656
|
-
"outputs": "request()"
|
|
657
|
-
},
|
|
658
|
-
{
|
|
659
|
-
"id": "terminal/lib-processes",
|
|
660
|
-
"entry": "adapters/terminal/lib/processes.mjs",
|
|
643
|
+
"id": "web-dapp/lib-processes",
|
|
644
|
+
"entry": "adapters/web-dapp/lib/processes.mjs",
|
|
661
645
|
"kind": "lib",
|
|
662
646
|
"purpose": "Pid-file process stop with owner-marker checks (--mm-harness-owner=<kind>-<id>, exact token; live foreign pids kept and reported, dead ones dropped), process-group termination, CDP port release check.",
|
|
663
|
-
"inputs": "
|
|
647
|
+
"inputs": "stopWebDappBrowser(target, { cdpPort }), stopConsoleCollector(target), ownerMarker(kind, runtimeDir), ownsProcess(kind, pid, runtimeDir)",
|
|
664
648
|
"outputs": "stopped processes"
|
|
665
649
|
},
|
|
666
650
|
{
|
|
667
|
-
"id": "
|
|
668
|
-
"entry": "adapters/
|
|
651
|
+
"id": "web-dapp/lib-readiness",
|
|
652
|
+
"entry": "adapters/web-dapp/lib/readiness.mjs",
|
|
669
653
|
"kind": "lib",
|
|
670
|
-
"purpose": "
|
|
671
|
-
"inputs": "
|
|
654
|
+
"purpose": "web-dapp slot readiness checks shared by verify, doctor and the surface.",
|
|
655
|
+
"inputs": "webDappReadiness({ policy, target, appPort, cdpPort, signer }), assertLaunchNetwork(target, { network, mainnetConfirmation }), devServerTestnetDiagnostic(target, { appPort, variable }) (advisory)",
|
|
672
656
|
"outputs": "checks[]"
|
|
673
657
|
},
|
|
674
658
|
{
|
|
675
|
-
"id": "
|
|
676
|
-
"entry": "adapters/
|
|
659
|
+
"id": "web-dapp/lib-display",
|
|
660
|
+
"entry": "adapters/web-dapp/lib/display.mjs",
|
|
677
661
|
"kind": "lib",
|
|
678
662
|
"purpose": "Headless/headful and slow-mo resolution, per-slot window placement and the profile zoom that keeps the desktop layout.",
|
|
679
663
|
"inputs": "resolveHeadless, resolveSlowMo, windowPlacement, writeProfileZoom",
|
|
680
664
|
"outputs": "browser display args"
|
|
681
665
|
},
|
|
682
666
|
{
|
|
683
|
-
"id": "
|
|
684
|
-
"entry": "adapters/
|
|
667
|
+
"id": "web-dapp/lib-hud",
|
|
668
|
+
"entry": "adapters/web-dapp/lib/hud.mjs",
|
|
685
669
|
"kind": "lib",
|
|
686
670
|
"purpose": "Recipe HUD state (<runtime>/hud.json) and the page expression that draws it on the app origin only.",
|
|
687
671
|
"inputs": "nextHudState, hudRenderExpression",
|
|
688
672
|
"outputs": "HUD state, expression source"
|
|
689
673
|
},
|
|
690
674
|
{
|
|
691
|
-
"id": "
|
|
692
|
-
"entry": "adapters/
|
|
675
|
+
"id": "web-dapp/lib-blocked-hosts",
|
|
676
|
+
"entry": "adapters/web-dapp/lib/blocked-hosts.mjs",
|
|
693
677
|
"kind": "lib",
|
|
694
|
-
"purpose": "
|
|
695
|
-
"inputs": "
|
|
696
|
-
"outputs": "
|
|
678
|
+
"purpose": "Testnet enforcement mechanics: the host-resolver rule that makes the policy's blocked hosts unresolvable, and the canonical host check the wallet host's request block uses.",
|
|
679
|
+
"inputs": "hostResolverRules(hosts), hostOf(url), isBlockedUrl(url, hosts)",
|
|
680
|
+
"outputs": "launch switch, booleans"
|
|
697
681
|
},
|
|
698
682
|
{
|
|
699
|
-
"id": "
|
|
700
|
-
"entry": "adapters/
|
|
683
|
+
"id": "web-dapp/lib-policy",
|
|
684
|
+
"entry": "adapters/web-dapp/lib/policy.mjs",
|
|
701
685
|
"kind": "lib",
|
|
702
|
-
"purpose": "
|
|
703
|
-
"inputs": "
|
|
704
|
-
"outputs": "
|
|
686
|
+
"purpose": "The venue policy of the adapter that extends web-dapp (e.g. the Terminal plugin's), loaded from the module RECIPE_WEB_DAPP_POLICY names; the host sets it when it binds that adapter.",
|
|
687
|
+
"inputs": "env RECIPE_WEB_DAPP_POLICY",
|
|
688
|
+
"outputs": "webDappPolicy(), webDappAdapterId()"
|
|
705
689
|
},
|
|
706
690
|
{
|
|
707
|
-
"id": "
|
|
708
|
-
"entry": "src/adapters/
|
|
691
|
+
"id": "web-dapp/surface",
|
|
692
|
+
"entry": "src/adapters/web-dapp/surface.ts",
|
|
709
693
|
"kind": "module",
|
|
710
|
-
"purpose": "
|
|
711
|
-
"inputs": "
|
|
694
|
+
"purpose": "The web-dapp adapter surface (runtime status, browser stop, logs, hints) and bindWebDappPolicy, which binds the venue policy of the adapter that extends it.",
|
|
695
|
+
"inputs": "webDappSurface",
|
|
712
696
|
"outputs": "AdapterSurface"
|
|
713
697
|
}
|
|
714
698
|
]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// cleanup.mjs — `mm-harness cleanup --adapter
|
|
3
|
-
// and wallet host, then remove the
|
|
2
|
+
// cleanup.mjs — `mm-harness cleanup --adapter <web-dapp adapter>`: stop the slot
|
|
3
|
+
// browser and wallet host, then remove the slot's runtime state and the installed
|
|
4
4
|
// wallet fixture. Browser profiles are kept unless --reset-profile.
|
|
5
5
|
//
|
|
6
6
|
// Inputs: --target <checkout> [--cdp-port <port>] [--reset-profile]
|
|
@@ -13,16 +13,16 @@ import { fileURLToPath } from 'node:url';
|
|
|
13
13
|
|
|
14
14
|
import { recipeHarnessPath } from '@farmslot/recipe-cli/harness';
|
|
15
15
|
import { walletFixturePath } from '../../library/actions/harness-exports.mjs';
|
|
16
|
-
import {
|
|
17
|
-
import { stopConsoleCollector,
|
|
16
|
+
import { webDappPolicy, webDappRuntimeDir } from '../../library/actions/web-dapp/platform/runtime.mjs';
|
|
17
|
+
import { stopConsoleCollector, stopWebDappBrowser } from './lib/processes.mjs';
|
|
18
18
|
|
|
19
19
|
const usage = 'Usage: cleanup.mjs --target <checkout> [--cdp-port <port>] [--reset-profile]';
|
|
20
20
|
|
|
21
|
-
export async function
|
|
21
|
+
export async function cleanupWebDappRuntime({ target, cdpPort, resetProfile = false }) {
|
|
22
22
|
const root = path.resolve(target);
|
|
23
|
-
const stop = await
|
|
23
|
+
const stop = await stopWebDappBrowser(root, { cdpPort });
|
|
24
24
|
await stopConsoleCollector(root);
|
|
25
|
-
const runtime =
|
|
25
|
+
const runtime = webDappRuntimeDir(root);
|
|
26
26
|
const removed = [];
|
|
27
27
|
if (existsSync(runtime)) {
|
|
28
28
|
for (const entry of readdirSync(runtime)) {
|
|
@@ -31,7 +31,7 @@ export async function cleanupTerminalRuntime({ target, cdpPort, resetProfile = f
|
|
|
31
31
|
removed.push(entry);
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
const overlay = recipeHarnessPath(root,
|
|
34
|
+
const overlay = recipeHarnessPath(root, webDappPolicy().adapterId);
|
|
35
35
|
if (existsSync(overlay)) {
|
|
36
36
|
rmSync(overlay, { recursive: true, force: true });
|
|
37
37
|
removed.push(path.relative(root, overlay));
|
|
@@ -56,7 +56,7 @@ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.me
|
|
|
56
56
|
};
|
|
57
57
|
const rawPort = value('--cdp-port') ?? process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
|
|
58
58
|
try {
|
|
59
|
-
const result = await
|
|
59
|
+
const result = await cleanupWebDappRuntime({
|
|
60
60
|
target: value('--target') ?? process.cwd(),
|
|
61
61
|
cdpPort: rawPort ? Number(rawPort) : undefined,
|
|
62
62
|
resetProfile: argv.includes('--reset-profile'),
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// inject.mjs — `mm-harness install --adapter
|
|
3
|
-
// directory and install the wallet fixture the
|
|
2
|
+
// inject.mjs — `mm-harness install --adapter <web-dapp adapter>`: prepare the slot
|
|
3
|
+
// runtime directory and install the wallet fixture the web-dapp actions read.
|
|
4
4
|
//
|
|
5
|
-
// Inputs: --target <
|
|
5
|
+
// Inputs: --target <checkout> [--fixture <wallet-fixture.json>] [--force]
|
|
6
6
|
// env RECIPE_WALLET_FIXTURE (fixture source when --fixture is absent)
|
|
7
|
-
// Outputs: <target>/<harness root>/
|
|
8
|
-
// <target>/<runtime>/
|
|
7
|
+
// Outputs: <target>/<harness root>/<adapter id>/install.json (overlay marker),
|
|
8
|
+
// <target>/<runtime>/<adapter id>/ (0700), <runtime>/wallet-fixture.json (0600);
|
|
9
9
|
// JSON summary on stdout (account names only). Exit 0 installed; 1 failure; 2 usage.
|
|
10
10
|
// Never touches: product source files; never prints key material.
|
|
11
11
|
|
|
@@ -15,16 +15,16 @@ import { fileURLToPath } from 'node:url';
|
|
|
15
15
|
|
|
16
16
|
import { recipeHarnessPath, recipeRuntimeDir } from '@farmslot/recipe-cli/harness';
|
|
17
17
|
import { walletFixturePath } from '../../library/actions/harness-exports.mjs';
|
|
18
|
-
import {
|
|
19
|
-
import { isTerminalCheckout } from './lib/readiness.mjs';
|
|
18
|
+
import { webDappPolicy, webDappRuntimeDir } from '../../library/actions/web-dapp/platform/runtime.mjs';
|
|
20
19
|
|
|
21
|
-
const usage = 'Usage: inject.mjs --target <
|
|
20
|
+
const usage = 'Usage: inject.mjs --target <checkout> [--fixture <wallet-fixture.json>] [--force]';
|
|
22
21
|
|
|
23
|
-
export function
|
|
22
|
+
export function installWebDappRuntime({ target, fixture, force = false }) {
|
|
24
23
|
const root = path.resolve(target);
|
|
25
|
-
|
|
24
|
+
const { adapterId, checkout } = webDappPolicy();
|
|
25
|
+
if (!checkout.matches(root)) throw new Error(`${root} is not a ${checkout.name} checkout (needs ${checkout.needs}).`);
|
|
26
26
|
mkdirSync(path.join(root, recipeRuntimeDir()), { recursive: true, mode: 0o700 });
|
|
27
|
-
mkdirSync(
|
|
27
|
+
mkdirSync(webDappRuntimeDir(root), { recursive: true, mode: 0o700 });
|
|
28
28
|
const destination = walletFixturePath(root);
|
|
29
29
|
let fixtureAction = 'kept';
|
|
30
30
|
if (fixture && (force || !existsSync(destination))) {
|
|
@@ -39,9 +39,9 @@ export function installTerminalRuntime({ target, fixture, force = false }) {
|
|
|
39
39
|
const accounts = existsSync(destination)
|
|
40
40
|
? JSON.parse(readFileSync(destination, 'utf8')).accounts.map((account) => account?.name).filter(Boolean)
|
|
41
41
|
: [];
|
|
42
|
-
const marker = { schemaVersion: 1, adapter:
|
|
42
|
+
const marker = { schemaVersion: 1, adapter: adapterId, installedAt: new Date().toISOString(), fixture: fixtureAction };
|
|
43
43
|
// The overlay directory is the install marker run/call auto-ensure checks.
|
|
44
|
-
const overlay = recipeHarnessPath(root,
|
|
44
|
+
const overlay = recipeHarnessPath(root, adapterId);
|
|
45
45
|
mkdirSync(overlay, { recursive: true, mode: 0o700 });
|
|
46
46
|
writeFileSync(path.join(overlay, 'install.json'), `${JSON.stringify(marker, null, 2)}\n`, { mode: 0o600 });
|
|
47
47
|
return { status: fixtureAction === 'missing' ? 'fail' : 'pass', target: root, fixture: fixtureAction, fixturePath: destination, accounts };
|
|
@@ -58,7 +58,7 @@ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.me
|
|
|
58
58
|
return index >= 0 ? argv[index + 1] : undefined;
|
|
59
59
|
};
|
|
60
60
|
try {
|
|
61
|
-
const result =
|
|
61
|
+
const result = installWebDappRuntime({
|
|
62
62
|
target: value('--target') ?? process.cwd(),
|
|
63
63
|
fixture: value('--fixture') ?? process.env.RECIPE_WALLET_FIXTURE,
|
|
64
64
|
force: argv.includes('--force'),
|
|
@@ -1,25 +1,25 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// launch.mjs — start (or reuse) the
|
|
2
|
+
// launch.mjs — start (or reuse) the web-dapp slot browser and its wallet host.
|
|
3
3
|
//
|
|
4
4
|
// Purpose:
|
|
5
5
|
// One Chromium-family browser per slot, bound to 127.0.0.1:<cdp-port>, with a
|
|
6
|
-
// profile under <runtime
|
|
6
|
+
// profile under <runtime>/<adapter id>/. signer=extension loads the slot's copy of
|
|
7
7
|
// the MetaMask extension and imports the fixture account through the Extension
|
|
8
8
|
// adapter's fixture flow (wallet-fixture-state.cjs generate → prefill-profile →
|
|
9
9
|
// seed-cdp). signer=injected loads no extension; the wallet host provides the
|
|
10
10
|
// strict test wallet. Either way wallet-host.mjs then records wallet requests.
|
|
11
11
|
//
|
|
12
|
-
// Inputs (flags): --target <
|
|
12
|
+
// Inputs (flags): --target <checkout> --cdp-port <port> --app-port <port>
|
|
13
13
|
// [--signer extension|injected] [--account <fixture name>] [--fresh-profile]
|
|
14
14
|
// [--headless | --headful] [--slow-mo <ms>] [--start-chain <id>] [--json]
|
|
15
|
-
// [--network mainnet --mainnet-confirmation REAL_FUNDS] (
|
|
16
|
-
//
|
|
15
|
+
// [--network mainnet --mainnet-confirmation REAL_FUNDS] (testnet otherwise,
|
|
16
|
+
// enforced in the browser from the venue policy)
|
|
17
17
|
// env: TERMINAL_CHROME_BIN, TERMINAL_EXTENSION_DIR,
|
|
18
18
|
// TERMINAL_EXTENSION_CHECKOUT, TERMINAL_EXTENSION_NODE, RECIPE_WALLET_FIXTURE,
|
|
19
19
|
// TERMINAL_HEADLESS, TERMINAL_SLOW_MO, TERMINAL_WINDOW, TERMINAL_SCREEN
|
|
20
20
|
// The browser is headful by default, in a window placed per slot (see
|
|
21
21
|
// lib/display.mjs); --headless or TERMINAL_HEADLESS=1 hides it.
|
|
22
|
-
// Outputs: <runtime>/
|
|
22
|
+
// Outputs: <runtime>/<adapter id>/browser.json (+ pid files, logs); JSON summary on stdout.
|
|
23
23
|
// Exit 0 ready; 1 launch/seed failure; 2 usage.
|
|
24
24
|
// Never prints: mnemonics, private keys, the wallet password.
|
|
25
25
|
|
|
@@ -35,14 +35,15 @@ import {
|
|
|
35
35
|
fixtureEntry,
|
|
36
36
|
loadWalletFixture,
|
|
37
37
|
resolveSigner,
|
|
38
|
-
terminalRuntimeDir,
|
|
39
38
|
viemAccountFromEntry,
|
|
40
|
-
|
|
39
|
+
webDappPolicy,
|
|
40
|
+
webDappRuntimeDir,
|
|
41
|
+
} from '../../library/actions/web-dapp/platform/runtime.mjs';
|
|
41
42
|
import { CdpClient, browserWebSocketUrl } from './lib/cdp-client.mjs';
|
|
42
43
|
import { clearHudState } from './lib/hud.mjs';
|
|
43
44
|
import { browserDisplayArgs, resolveHeadless, resolveSlowMo, windowPlacement, writeProfileZoom } from './lib/display.mjs';
|
|
44
|
-
import { cdpListenerPids, ownedBrowserPids, ownerMarker, ownsProcess, pidAlive,
|
|
45
|
-
import { hostResolverRules
|
|
45
|
+
import { cdpListenerPids, ownedBrowserPids, ownerMarker, ownsProcess, pidAlive, stopWebDappBrowser, terminate } from './lib/processes.mjs';
|
|
46
|
+
import { hostResolverRules } from './lib/blocked-hosts.mjs';
|
|
46
47
|
import { assertLaunchNetwork, devServerTestnetDiagnostic } from './lib/readiness.mjs';
|
|
47
48
|
|
|
48
49
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
@@ -76,7 +77,7 @@ export function parseLaunchArgs(argv) {
|
|
|
76
77
|
}
|
|
77
78
|
|
|
78
79
|
function log(message) {
|
|
79
|
-
process.stderr.write(`[
|
|
80
|
+
process.stderr.write(`[${webDappPolicy().adapterId}/launch] ${message}\n`);
|
|
80
81
|
}
|
|
81
82
|
|
|
82
83
|
function browserStarts(bin) {
|
|
@@ -113,7 +114,7 @@ export async function resolveBrowser(env = process.env, candidates = chromeForTe
|
|
|
113
114
|
if (!existsSync(explicit)) throw new Error(`TERMINAL_CHROME_BIN does not exist: ${explicit}`);
|
|
114
115
|
return { bin: explicit, version: browserStarts(explicit), source: 'TERMINAL_CHROME_BIN' };
|
|
115
116
|
}
|
|
116
|
-
if (env.RECIPE_HARNESS_CHROME_BIN) log(`ignoring RECIPE_HARNESS_CHROME_BIN (${env.RECIPE_HARNESS_CHROME_BIN}) for the
|
|
117
|
+
if (env.RECIPE_HARNESS_CHROME_BIN) log(`ignoring RECIPE_HARNESS_CHROME_BIN (${env.RECIPE_HARNESS_CHROME_BIN}) for the ${webDappPolicy().adapterId}; set TERMINAL_CHROME_BIN to override Chrome for Testing`);
|
|
117
118
|
const bin = candidates.find((candidate) => existsSync(candidate));
|
|
118
119
|
if (bin) return { bin, version: browserStarts(bin), source: 'chrome-for-testing' };
|
|
119
120
|
throw new Error(
|
|
@@ -344,7 +345,9 @@ function writePrivateFile(file, text) {
|
|
|
344
345
|
// which waits for a cold dev-server compile.
|
|
345
346
|
export const LAUNCH_TIMEOUTS = Object.freeze({ browserStartMs: 90000, hostReadyMs: 180000 });
|
|
346
347
|
|
|
347
|
-
|
|
348
|
+
// The venue policy comes from RECIPE_WEB_DAPP_POLICY (webDappPolicy), as for every web-dapp leaf.
|
|
349
|
+
export async function launchWebDappBrowser(args, env = process.env, { timeouts = LAUNCH_TIMEOUTS } = {}) {
|
|
350
|
+
const policy = webDappPolicy(env);
|
|
348
351
|
const target = path.resolve(args.target);
|
|
349
352
|
const cdpPort = Number(args['cdp-port']);
|
|
350
353
|
const appPort = Number(args['app-port']);
|
|
@@ -353,16 +356,16 @@ export async function launchTerminalBrowser(args, env = process.env, { timeouts
|
|
|
353
356
|
// browser: the SDK's mainnet venue hosts are unresolvable for the whole
|
|
354
357
|
// browser, and the wallet host blocks, logs and refuses anything mainnet.
|
|
355
358
|
const network = assertLaunchNetwork(target, { network: args.network, mainnetConfirmation: args['mainnet-confirmation'] });
|
|
356
|
-
const venues = network === 'testnet' ? venueHosts(target) : {
|
|
357
|
-
const mainnetHosts = venues.
|
|
359
|
+
const venues = network === 'testnet' ? policy.venueHosts(target) : { blocked: [], served: [] };
|
|
360
|
+
const mainnetHosts = venues.blocked;
|
|
358
361
|
// What the running browser enforces; a browser enforcing anything else is
|
|
359
362
|
// never reused (its resolver rules are fixed at start).
|
|
360
|
-
const enforcementFingerprint = createHash('sha256').update(JSON.stringify({ network, mainnetHosts, testnetHosts: venues.
|
|
361
|
-
const devServer = network === 'testnet' ? devServerTestnetDiagnostic(target, { appPort }) : null;
|
|
363
|
+
const enforcementFingerprint = createHash('sha256').update(JSON.stringify({ network, mainnetHosts, testnetHosts: venues.served })).digest('hex').slice(0, 16);
|
|
364
|
+
const devServer = network === 'testnet' ? devServerTestnetDiagnostic(target, { appPort, variable: policy.testnetVariable }) : null;
|
|
362
365
|
if (devServer && !(devServer.known && devServer.forced)) {
|
|
363
366
|
log(`WARN dev server testnet check: ${devServer.detail} (advisory; the browser enforces testnet itself)`);
|
|
364
367
|
}
|
|
365
|
-
const runtime =
|
|
368
|
+
const runtime = webDappRuntimeDir(target);
|
|
366
369
|
mkdirSync(runtime, { recursive: true, mode: 0o700 });
|
|
367
370
|
chmodSync(runtime, 0o700);
|
|
368
371
|
const stateFile = path.join(runtime, 'browser.json');
|
|
@@ -392,7 +395,7 @@ export async function launchTerminalBrowser(args, env = process.env, { timeouts
|
|
|
392
395
|
if (previous.slowMoMs !== slowMoMs) writePrivateFile(stateFile, `${JSON.stringify(state, null, 2)}\n`);
|
|
393
396
|
return { ...state, reused: true };
|
|
394
397
|
}
|
|
395
|
-
await
|
|
398
|
+
await stopWebDappBrowser(target, { cdpPort, log });
|
|
396
399
|
|
|
397
400
|
const browser = await resolveBrowser(env);
|
|
398
401
|
const profile = path.join(runtime, `profile-${signer}-${args.account.replace(/[^A-Za-z0-9_-]/gu, '_')}`);
|
|
@@ -541,11 +544,11 @@ export async function launchTerminalBrowser(args, env = process.env, { timeouts
|
|
|
541
544
|
'--app-origin', appOrigin,
|
|
542
545
|
'--target', target,
|
|
543
546
|
'--account', args.account,
|
|
544
|
-
'--start-chain', String(args['start-chain'] ??
|
|
547
|
+
'--start-chain', String(args['start-chain'] ?? policy.startChain),
|
|
545
548
|
'--network', network,
|
|
546
549
|
ownerMarker('wallet-host', runtime),
|
|
547
550
|
];
|
|
548
|
-
if (network === 'testnet') hostArgs.push('--mainnet-hosts', mainnetHosts.join(','), '--testnet-hosts', venues.
|
|
551
|
+
if (network === 'testnet') hostArgs.push('--mainnet-hosts', mainnetHosts.join(','), '--testnet-hosts', venues.served.join(','));
|
|
549
552
|
if (extension?.id) hostArgs.push('--extension-id', extension.id);
|
|
550
553
|
if (guardFocus) hostArgs.push('--observe-focus', '1');
|
|
551
554
|
if (launchMethod === LAUNCH_SERVICES) hostArgs.push('--window', [windowBounds.x, windowBounds.y, windowBounds.width, windowBounds.height].join(','));
|
|
@@ -568,7 +571,7 @@ export async function launchTerminalBrowser(args, env = process.env, { timeouts
|
|
|
568
571
|
signer,
|
|
569
572
|
network,
|
|
570
573
|
networkEnforcement: network === 'testnet'
|
|
571
|
-
? { mode: 'enforced', mainnetHosts, testnetHosts: venues.
|
|
574
|
+
? { mode: 'enforced', mainnetHosts, testnetHosts: venues.served, fingerprint: enforcementFingerprint, layers: ['host-resolver-rules', 'cdp-fetch-block', 'wallet-refusal', 'served-network-check'] }
|
|
572
575
|
: { mode: 'disabled', reason: 'network=mainnet with mainnet_confirmation=REAL_FUNDS', fingerprint: enforcementFingerprint },
|
|
573
576
|
devServerDiagnostic: devServer,
|
|
574
577
|
consoleOffsets,
|
|
@@ -595,7 +598,7 @@ export async function launchTerminalBrowser(args, env = process.env, { timeouts
|
|
|
595
598
|
} catch (error) {
|
|
596
599
|
if (started) {
|
|
597
600
|
log(`launch failed, stopping what it started: ${String(error?.message ?? error).split('\n')[0]}`);
|
|
598
|
-
await
|
|
601
|
+
await stopWebDappBrowser(target, { cdpPort, log }).catch((stopError) => log(`rollback incomplete: ${stopError.message}`));
|
|
599
602
|
for (const pid of ownedBrowserPids(runtime)) await terminate(pid, 3000);
|
|
600
603
|
}
|
|
601
604
|
throw error;
|
|
@@ -618,7 +621,7 @@ if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.me
|
|
|
618
621
|
process.exit(0);
|
|
619
622
|
}
|
|
620
623
|
try {
|
|
621
|
-
const state = await
|
|
624
|
+
const state = await launchWebDappBrowser(args);
|
|
622
625
|
process.stdout.write(`${JSON.stringify({ status: 'pass', ...state }, null, args.json ? 0 : 2)}\n`);
|
|
623
626
|
} catch (error) {
|
|
624
627
|
const message = String(error?.stderr ?? '').trim() || error.message;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// Testnet enforcement mechanics for the web-dapp slot browser: the hosts a
|
|
2
|
+
// venue policy blocks become unresolvable for the whole browser (pages,
|
|
3
|
+
// workers, WebSockets), and the wallet host fails and logs each request it
|
|
4
|
+
// sees to one of them. Which hosts is the venue policy's (venueHosts).
|
|
5
|
+
|
|
6
|
+
// A URL's host as the network sees it: lowercase, no trailing dot. null when
|
|
7
|
+
// it does not parse.
|
|
8
|
+
export function hostOf(url) {
|
|
9
|
+
try {
|
|
10
|
+
return new URL(String(url)).hostname.toLowerCase().replace(/\.$/u, '');
|
|
11
|
+
} catch {
|
|
12
|
+
return null;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isBlockedUrl(url, hosts) {
|
|
17
|
+
const host = hostOf(url);
|
|
18
|
+
return host !== null && hosts.includes(host);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function hostResolverRules(hosts) {
|
|
22
|
+
// The host and its fully qualified form (trailing dot) alike.
|
|
23
|
+
return `--host-resolver-rules=${hosts.flatMap((host) => [`MAP ${host} ~NOTFOUND`, `MAP ${host}. ~NOTFOUND`]).join(', ')}`;
|
|
24
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// Minimal browser-level Chrome DevTools Protocol client with flattened target
|
|
2
|
-
// sessions, for the long-running
|
|
2
|
+
// sessions, for the long-running web-dapp wallet host. Page-level UI actions use
|
|
3
3
|
// @farmslot/recipe-runner/runtime/cdp instead.
|
|
4
4
|
|
|
5
5
|
import WebSocket from 'ws';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// How the slot browser is shown: headful (default) or headless, where its
|
|
2
|
-
// window sits on screen, the page zoom that keeps the
|
|
2
|
+
// window sits on screen, the page zoom that keeps the app's desktop layout
|
|
3
3
|
// inside that window, and the optional slow-mo between UI actions.
|
|
4
4
|
|
|
5
5
|
import { execFileSync } from 'node:child_process';
|