@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.
Files changed (118) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/adapters/manifest.json +48 -64
  3. package/adapters/{terminal → web-dapp}/cleanup.mjs +9 -9
  4. package/adapters/{terminal → web-dapp}/inject.mjs +14 -14
  5. package/adapters/{terminal → web-dapp}/launch.mjs +27 -24
  6. package/adapters/web-dapp/lib/blocked-hosts.mjs +24 -0
  7. package/adapters/{terminal → web-dapp}/lib/cdp-client.mjs +1 -1
  8. package/adapters/{terminal → web-dapp}/lib/display.mjs +1 -1
  9. package/adapters/{terminal → web-dapp}/lib/hud.mjs +1 -1
  10. package/adapters/web-dapp/lib/policy.mjs +79 -0
  11. package/adapters/{terminal → web-dapp}/lib/processes.mjs +8 -8
  12. package/adapters/{terminal → web-dapp}/lib/readiness.mjs +32 -34
  13. package/adapters/{terminal → web-dapp}/stop.mjs +5 -4
  14. package/adapters/{terminal → web-dapp}/verify.mjs +9 -6
  15. package/adapters/{terminal → web-dapp}/wallet-host.mjs +44 -227
  16. package/dist/adapters/core/actions.js +4 -1
  17. package/dist/adapters/extension/actions.js +6 -2
  18. package/dist/adapters/extension/network-observer.js +13 -0
  19. package/dist/adapters/extension/run.js +115 -31
  20. package/dist/adapters/mobile/actions.js +4 -1
  21. package/dist/adapters/mobile/network-observer.js +68 -0
  22. package/dist/adapters/mobile/performance-observer.js +1 -3
  23. package/dist/adapters/mobile/run.js +32 -9
  24. package/dist/adapters/perps-order-inputs.js +36 -0
  25. package/dist/adapters/plugin-surface.js +98 -0
  26. package/dist/adapters/proof-failure.js +11 -0
  27. package/dist/adapters/surface.js +1 -1
  28. package/dist/adapters/{terminal → web-dapp}/actions.js +11 -8
  29. package/dist/adapters/web-dapp/console-capture.js +63 -0
  30. package/dist/adapters/web-dapp/doctor-checks.js +33 -0
  31. package/dist/adapters/{terminal → web-dapp}/launch.js +21 -19
  32. package/dist/adapters/web-dapp/overlay.js +21 -0
  33. package/dist/adapters/web-dapp/readiness.js +10 -0
  34. package/dist/adapters/{terminal → web-dapp}/run.js +22 -17
  35. package/dist/adapters/{terminal → web-dapp}/stop.js +5 -4
  36. package/dist/adapters/web-dapp/surface.js +149 -0
  37. package/dist/adapters.js +20 -4
  38. package/dist/cli.js +34 -6
  39. package/dist/command-contract.js +29 -7
  40. package/dist/commands/check.js +32 -8
  41. package/dist/commands/completion-candidates.js +5 -5
  42. package/dist/commands/doctor.js +19 -2
  43. package/dist/commands/farmslot-ready.js +1 -0
  44. package/dist/commands/help.js +1 -1
  45. package/dist/commands/launch/device.js +5 -5
  46. package/dist/commands/self-test.js +10 -5
  47. package/dist/engine.js +47 -0
  48. package/dist/funding-execution-context.js +81 -0
  49. package/dist/manifest.js +48 -24
  50. package/dist/mm-harness-cli.js +20 -3
  51. package/dist/performance-observation.js +7 -15
  52. package/dist/recipe-assistance-context.js +6 -7
  53. package/dist/runner.js +28 -8
  54. package/docs/CONTRIBUTING.md +4 -2
  55. package/docs/RECIPES.md +31 -3
  56. package/docs/cli-contract.md +5 -3
  57. package/library/actions/core/perps/_controller.mjs +24 -28
  58. package/library/actions/core/perps/_rewards.mjs +186 -0
  59. package/library/actions/core/perps/close_positions.mjs +1 -1
  60. package/library/actions/core/perps/place_order.mjs +167 -45
  61. package/library/actions/core/perps/quote_fees.mjs +7 -4
  62. package/library/actions/core/wallet/list_accounts.mjs +1 -1
  63. package/library/actions/extension/perps/close_visible_position.mjs +5 -0
  64. package/library/actions/extension/perps/edit_margin.mjs +4 -0
  65. package/library/actions/extension/perps/mutation-receipt.mjs +95 -3
  66. package/library/actions/extension/perps/perps.mjs +320 -3
  67. package/library/actions/extension/perps/place_order.mjs +3 -1
  68. package/library/actions/extension/perps/update_position_tpsl.mjs +4 -0
  69. package/library/actions/extension/platform/cdp.mjs +71 -3
  70. package/library/actions/mobile/perps/perps.mjs +216 -11
  71. package/library/actions/mobile/perps/place_order.mjs +3 -1
  72. package/library/actions/shared/console-collector.mjs +1 -1
  73. package/library/actions/shared/perps/order-policy.mjs +177 -0
  74. package/library/actions/{core/wallet/_fixture-accounts.mjs → shared/wallet/fixture-accounts.mjs} +18 -3
  75. package/library/actions/{terminal → web-dapp}/app/assert_no_console_errors.mjs +4 -4
  76. package/library/actions/{terminal → web-dapp}/app/launch.mjs +8 -7
  77. package/library/actions/{terminal → web-dapp}/platform/hud.mjs +7 -6
  78. package/library/actions/{terminal → web-dapp}/platform/page.mjs +13 -12
  79. package/library/actions/{terminal → web-dapp}/platform/runtime.mjs +16 -12
  80. package/library/actions/web-dapp/ui/navigate.mjs +24 -0
  81. package/library/actions/web-dapp/wallet/_signature-log.mjs +32 -0
  82. package/library/actions/{terminal → web-dapp}/wallet/assert_signatures.mjs +8 -14
  83. package/library/actions/{terminal → web-dapp}/wallet/list_accounts.mjs +2 -2
  84. package/library/actions/web-dapp/wallet/read_signatures.mjs +38 -0
  85. package/library/manifests/core.action-manifest.json +6 -2
  86. package/library/manifests/extension.action-manifest.json +4 -0
  87. package/library/manifests/mobile.action-manifest.json +12 -0
  88. package/library/manifests/{terminal.action-manifest.json → web-dapp.action-manifest.json} +18 -702
  89. package/package.json +6 -6
  90. package/site/assets/help-recipes.json +1 -0
  91. package/adapters/terminal/lib/mainnet-guard.mjs +0 -177
  92. package/adapters/terminal/lib/origin.mjs +0 -30
  93. package/adapters/terminal/lib/page-script.mjs +0 -246
  94. package/adapters/terminal/lib/strict-wallet.mjs +0 -138
  95. package/dist/adapters/terminal/console-capture.js +0 -59
  96. package/dist/adapters/terminal/doctor-checks.js +0 -22
  97. package/dist/adapters/terminal/overlay.js +0 -18
  98. package/dist/adapters/terminal/readiness.js +0 -11
  99. package/dist/adapters/terminal/surface.js +0 -133
  100. package/dist/commands/call.js +0 -671
  101. package/dist/commands/list-executables.js +0 -245
  102. package/dist/commands/manifest.js +0 -477
  103. package/dist/commands/run-engine.js +0 -1301
  104. package/dist/commands/run.js +0 -730
  105. package/dist/discovery.js +0 -15
  106. package/dist/metamask-action-validation.js +0 -68
  107. package/dist/network-observation.js +0 -290
  108. package/library/actions/terminal/perps/_venue.mjs +0 -242
  109. package/library/actions/terminal/perps/assert_orders.mjs +0 -54
  110. package/library/actions/terminal/perps/assert_positions.mjs +0 -40
  111. package/library/actions/terminal/perps/read_orders.mjs +0 -26
  112. package/library/actions/terminal/perps/read_positions.mjs +0 -25
  113. package/library/actions/terminal/perps/read_snapshot.mjs +0 -40
  114. package/library/actions/terminal/perps/revoke_agent.mjs +0 -58
  115. package/library/actions/terminal/perps/teardown_state.mjs +0 -104
  116. package/library/actions/terminal/ui/navigate.mjs +0 -31
  117. package/library/actions/terminal/wallet/_signature-log.mjs +0 -177
  118. 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
@@ -584,131 +584,115 @@
584
584
  "outputs": "numbered checkouts, saved preferences, and run summary"
585
585
  },
586
586
  {
587
- "id": "terminal/launch",
588
- "entry": "adapters/terminal/launch.mjs",
587
+ "id": "web-dapp/launch",
588
+ "entry": "adapters/web-dapp/launch.mjs",
589
589
  "kind": "node",
590
- "purpose": "Start or reuse the Web Terminal 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 venue SDK's mainnet hosts are unresolvable for the whole browser.",
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>/terminal/browser.json + pid/log files; JSON on stdout; exit 0/1/2"
592
+ "outputs": "<runtime>/<adapter id>/browser.json + pid/log files; JSON on stdout; exit 0/1/2"
593
593
  },
594
594
  {
595
- "id": "terminal/wallet-host",
596
- "entry": "adapters/terminal/wallet-host.mjs",
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>/terminal/wallet-requests.jsonl, wallet-host.ready"
600
+ "outputs": "<runtime>/<adapter id>/wallet-requests.jsonl, wallet-host.ready"
601
601
  },
602
602
  {
603
- "id": "terminal/stop",
604
- "entry": "adapters/terminal/stop.mjs",
603
+ "id": "web-dapp/stop",
604
+ "entry": "adapters/web-dapp/stop.mjs",
605
605
  "kind": "node",
606
- "purpose": "Stop the terminal slot browser, wallet host and console collector and prove the CDP port is free.",
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": "terminal/inject",
612
- "entry": "adapters/terminal/inject.mjs",
611
+ "id": "web-dapp/inject",
612
+ "entry": "adapters/web-dapp/inject.mjs",
613
613
  "kind": "node",
614
- "purpose": "Terminal install: overlay marker, runtime dir and wallet fixture (0600); no product writes.",
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>/terminal/install.json, <runtime>/wallet-fixture.json; exit 0/1"
616
+ "outputs": "<harness>/<adapter id>/install.json, <runtime>/wallet-fixture.json; exit 0/1"
617
617
  },
618
618
  {
619
- "id": "terminal/cleanup",
620
- "entry": "adapters/terminal/cleanup.mjs",
619
+ "id": "web-dapp/cleanup",
620
+ "entry": "adapters/web-dapp/cleanup.mjs",
621
621
  "kind": "node",
622
- "purpose": "Terminal cleanup: stop browser/host, remove terminal runtime state, overlay marker and installed fixture; profiles kept unless --reset-profile.",
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": "terminal/verify",
628
- "entry": "adapters/terminal/verify.mjs",
627
+ "id": "web-dapp/verify",
628
+ "entry": "adapters/web-dapp/verify.mjs",
629
629
  "kind": "node",
630
- "purpose": "Read-only terminal slot readiness: checkout, deps, dev server, forced testnet, fixture, browser start probe, extension copy, CDP port.",
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": "terminal/lib-cdp-client",
636
- "entry": "adapters/terminal/lib/cdp-client.mjs",
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": "terminal/lib-page-script",
644
- "entry": "adapters/terminal/lib/page-script.mjs",
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": "stopTerminalBrowser(target, { cdpPort }), stopConsoleCollector(target), ownerMarker(kind, runtimeDir), ownsProcess(kind, pid, runtimeDir)",
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": "terminal/lib-readiness",
668
- "entry": "adapters/terminal/lib/readiness.mjs",
651
+ "id": "web-dapp/lib-readiness",
652
+ "entry": "adapters/web-dapp/lib/readiness.mjs",
669
653
  "kind": "lib",
670
- "purpose": "Terminal slot readiness checks shared by verify, doctor and the surface.",
671
- "inputs": "terminalReadiness({ target, appPort, cdpPort, signer }), assertLaunchNetwork(target, { network, mainnetConfirmation }), devServerTestnetDiagnostic(target, { appPort }) (advisory)",
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": "terminal/lib-display",
676
- "entry": "adapters/terminal/lib/display.mjs",
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": "terminal/lib-hud",
684
- "entry": "adapters/terminal/lib/hud.mjs",
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": "terminal/lib-origin",
692
- "entry": "adapters/terminal/lib/origin.mjs",
675
+ "id": "web-dapp/lib-blocked-hosts",
676
+ "entry": "adapters/web-dapp/lib/blocked-hosts.mjs",
693
677
  "kind": "lib",
694
- "purpose": "Exact app-origin and app top-frame checks for targets, wallet requests and log entries.",
695
- "inputs": "isAppUrl(url, appOrigin), isAppTopFrameContext(context, { targetId, appOrigin })",
696
- "outputs": "boolean"
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": "terminal/lib-mainnet-guard",
700
- "entry": "adapters/terminal/lib/mainnet-guard.mjs",
683
+ "id": "web-dapp/lib-policy",
684
+ "entry": "adapters/web-dapp/lib/policy.mjs",
701
685
  "kind": "lib",
702
- "purpose": "Testnet enforcement: the venue endpoint hosts (from the venue SDK, @metamask/perps-controller and the app source in the checkout), the browser's host-resolver block, and the mainnet typed-data classifier used by the page script and the wallet host.",
703
- "inputs": "venueHosts(checkout), hostResolverRules(hosts), isMainnetVenueUrl(url, hosts), mainnetTypedDataReason(data)",
704
- "outputs": "host list, launch switch, refusal reason"
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": "terminal/surface",
708
- "entry": "src/adapters/terminal/surface.ts",
691
+ "id": "web-dapp/surface",
692
+ "entry": "src/adapters/web-dapp/surface.ts",
709
693
  "kind": "module",
710
- "purpose": "Terminal adapter surface (runtime status, browser stop, logs, hints).",
711
- "inputs": "terminalSurface",
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 terminal`: stop the slot browser
3
- // and wallet host, then remove the terminal runtime state and the installed
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 { terminalRuntimeDir } from '../../library/actions/terminal/platform/runtime.mjs';
17
- import { stopConsoleCollector, stopTerminalBrowser } from './lib/processes.mjs';
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 cleanupTerminalRuntime({ target, cdpPort, resetProfile = false }) {
21
+ export async function cleanupWebDappRuntime({ target, cdpPort, resetProfile = false }) {
22
22
  const root = path.resolve(target);
23
- const stop = await stopTerminalBrowser(root, { cdpPort });
23
+ const stop = await stopWebDappBrowser(root, { cdpPort });
24
24
  await stopConsoleCollector(root);
25
- const runtime = terminalRuntimeDir(root);
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, 'terminal');
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 cleanupTerminalRuntime({
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 terminal`: prepare the slot runtime
3
- // directory and install the wallet fixture the terminal actions read.
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 <terminal checkout> [--fixture <wallet-fixture.json>] [--force]
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>/terminal/install.json (overlay marker),
8
- // <target>/<runtime>/terminal/ (0700), <runtime>/wallet-fixture.json (0600);
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 { terminalRuntimeDir } from '../../library/actions/terminal/platform/runtime.mjs';
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 <terminal checkout> [--fixture <wallet-fixture.json>] [--force]';
20
+ const usage = 'Usage: inject.mjs --target <checkout> [--fixture <wallet-fixture.json>] [--force]';
22
21
 
23
- export function installTerminalRuntime({ target, fixture, force = false }) {
22
+ export function installWebDappRuntime({ target, fixture, force = false }) {
24
23
  const root = path.resolve(target);
25
- if (!isTerminalCheckout(root)) throw new Error(`${root} is not a Web Terminal checkout (needs next + src/features/perpetuals).`);
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(terminalRuntimeDir(root), { recursive: true, mode: 0o700 });
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: 'terminal', installedAt: new Date().toISOString(), fixture: fixtureAction };
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, 'terminal');
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 = installTerminalRuntime({
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 terminal slot browser and its wallet host.
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>/terminal/. signer=extension loads the slot's copy of
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 <terminal checkout> --cdp-port <port> --app-port <port>
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] (required unless the slot
16
- // dev server is known to force Hyperliquid testnet)
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>/terminal/browser.json (+ pid files, logs); JSON summary on stdout.
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
- } from '../../library/actions/terminal/platform/runtime.mjs';
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, stopTerminalBrowser, terminate } from './lib/processes.mjs';
45
- import { hostResolverRules, venueHosts } from './lib/mainnet-guard.mjs';
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(`[terminal/launch] ${message}\n`);
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 terminal; set TERMINAL_CHROME_BIN to override Chrome for Testing`);
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
- export async function launchTerminalBrowser(args, env = process.env, { timeouts = LAUNCH_TIMEOUTS } = {}) {
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) : { mainnet: [], testnet: [] };
357
- const mainnetHosts = venues.mainnet;
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.testnet })).digest('hex').slice(0, 16);
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 = terminalRuntimeDir(target);
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 stopTerminalBrowser(target, { cdpPort, log });
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'] ?? 42161),
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.testnet.join(','));
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.testnet, fingerprint: enforcementFingerprint, layers: ['host-resolver-rules', 'cdp-fetch-block', 'wallet-refusal', 'served-network-check'] }
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 stopTerminalBrowser(target, { cdpPort, log }).catch((stopError) => log(`rollback incomplete: ${stopError.message}`));
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 launchTerminalBrowser(args);
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 terminal wallet host. Page-level UI actions use
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 terminal's desktop layout
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';
@@ -1,4 +1,4 @@
1
- // Recipe HUD for the terminal app tab: state handling and the page expression
1
+ // Recipe HUD for the web-dapp app tab: state handling and the page expression
2
2
  // that draws it.
3
3
  //
4
4
  // The overlay is an <mm-harness-hud> element under <html> (outside <body>) with