@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
@@ -1,4 +1,5 @@
1
1
  import { manifestPath } from "../../paths.js";
2
+ import { perpsOrderInputFindings } from "../perps-order-inputs.js";
2
3
  const NATIVE_PROVIDER_UI_ACTIONS = /* @__PURE__ */ new Set([
3
4
  "ui.press",
4
5
  "ui.key_press",
@@ -47,7 +48,9 @@ const mobileActions = {
47
48
  }
48
49
  }),
49
50
  wrap: async (adapters) => (await import("./runtime-identity.js")).withMobileNativeRuntimeIdentity(adapters),
50
- lifecycle: async (adapters) => (await import("./lifecycle.js")).mobileSourceAwareLifecycleAdapters(adapters)
51
+ lifecycle: async (adapters) => (await import("./lifecycle.js")).mobileSourceAwareLifecycleAdapters(adapters),
52
+ // ensure_positions and ensure_orders with an open or present state place an order.
53
+ inputFindings: (nodeId, node) => perpsOrderInputFindings(nodeId, node, { ensureChecksOnly: false })
51
54
  };
52
55
  export {
53
56
  mobileActions
@@ -0,0 +1,68 @@
1
+ import path from "node:path";
2
+ import brokerModule from "@farmslot/recipe-runner/cdp-broker";
3
+ import configModule from "@farmslot/adapter-rn/bridge-runtime/lib/config.cjs";
4
+ const { brokerSocketPath, createBrokerClient } = brokerModule;
5
+ const { resolvePort } = configModule;
6
+ async function createMobileNetworkBackend(target, env) {
7
+ const { client } = await connectMobileBroker(target, env);
8
+ return {
9
+ start(params) {
10
+ return client.control("capture-start", params, 1e4);
11
+ },
12
+ async end(id) {
13
+ return asRecord(await client.control("capture-end", { id }, 1e4));
14
+ },
15
+ async close() {
16
+ client.close();
17
+ }
18
+ };
19
+ }
20
+ async function connectMobileBroker(target, env) {
21
+ const runtimeDir = env.RECIPE_RUNTIME_DIR ? path.resolve(target, env.RECIPE_RUNTIME_DIR) : path.join(target, "temp", "recipe", "runtime");
22
+ const socketPath = brokerSocketPath(
23
+ runtimeDir,
24
+ resolvePort(env, target)
25
+ );
26
+ const discoveryClient = await createBrokerClient(socketPath, "", 1e4);
27
+ let targets;
28
+ try {
29
+ targets = await discoveryClient.control(
30
+ "resolve-targets",
31
+ { nameIncludes: mobileTargetPin(env) },
32
+ 1e4
33
+ );
34
+ } finally {
35
+ discoveryClient.close();
36
+ }
37
+ const deviceId = selectMobileBrokerTarget(targets, env);
38
+ const client = await createBrokerClient(socketPath, deviceId, 1e4);
39
+ return { client, deviceId };
40
+ }
41
+ function selectMobileBrokerTarget(value, env) {
42
+ const targets = Array.isArray(value) ? value.map(asRecord).filter((target) => target.deviceId) : [];
43
+ const pin = mobileTargetPin(env);
44
+ const candidates = pin ? targets.filter(
45
+ (target) => String(target.name ?? "").toLowerCase().includes(pin.toLowerCase())
46
+ ) : targets;
47
+ if (candidates.length !== 1) {
48
+ throw new Error(
49
+ `Mobile network observation requires one broker target; found ${candidates.length}.`
50
+ );
51
+ }
52
+ return String(candidates[0].deviceId);
53
+ }
54
+ function mobileTargetPin(env) {
55
+ const platform = String(
56
+ env.RECIPE_RN_EXPLICIT_PLATFORM ?? env.RECIPE_HARNESS_PLATFORM ?? ""
57
+ ).toLowerCase();
58
+ return String(
59
+ platform === "android" ? env.ANDROID_TARGET_DEVICE_NAME ?? env.ANDROID_DEVICE ?? "" : platform === "ios" ? env.IOS_SIMULATOR ?? "" : env.IOS_SIMULATOR ?? env.ANDROID_TARGET_DEVICE_NAME ?? env.ANDROID_DEVICE ?? ""
60
+ ).trim();
61
+ }
62
+ function asRecord(value) {
63
+ return value && typeof value === "object" && !Array.isArray(value) ? value : {};
64
+ }
65
+ export {
66
+ connectMobileBroker,
67
+ createMobileNetworkBackend
68
+ };
@@ -1,9 +1,7 @@
1
1
  import {
2
2
  createCdpTraceCollector
3
3
  } from "../performance/cdp-trace.js";
4
- import {
5
- connectMobileBroker
6
- } from "../../network-observation.js";
4
+ import { connectMobileBroker } from "./network-observer.js";
7
5
  import { createAndroidGfxinfoPerformanceBackend } from "./android-gfxinfo-performance.js";
8
6
  async function createMobilePerformanceBackend(target, env) {
9
7
  if (resolvePlatform(env) === "android") {
@@ -1,4 +1,4 @@
1
- import { isRecord } from "@farmslot/recipe-cli/harness";
1
+ import { EXIT, isRecord } from "@farmslot/recipe-cli/harness";
2
2
  import { spawnSync } from "node:child_process";
3
3
  import path from "node:path";
4
4
  import { IOS_SIMULATOR_RECORDING_TARGET_PREFIX } from "@farmslot/adapter-rn";
@@ -8,6 +8,7 @@ import {
8
8
  resolveAndroidDeviceSerial,
9
9
  resolveMobileIosSimulatorName
10
10
  } from "./recording-target.js";
11
+ import { emitProofFailure } from "../proof-failure.js";
11
12
  import { mobileAppIdentityEnv } from "./app-identity.js";
12
13
  import { requireCurrentMobileReleaseArtifactState } from "./release-artifact-state.js";
13
14
  const EXPLICIT_DEVICE_KEYS = [
@@ -51,7 +52,10 @@ const mobileRun = {
51
52
  ADB_SERIAL: process.env.ADB_SERIAL,
52
53
  ANDROID_SERIAL: process.env.ANDROID_SERIAL,
53
54
  ...mobileAppIdentityEnv(),
54
- METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME: process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME
55
+ METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME: process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME,
56
+ // The platform `--platform`/`--device` pinned; the React Native bridge
57
+ // matches its target against it.
58
+ RECIPE_RN_EXPLICIT_PLATFORM: process.env.RECIPE_RN_EXPLICIT_PLATFORM
55
59
  }),
56
60
  // An installed release artifact is opaque: no harness HUD inside it.
57
61
  autoHud: () => process.env.METAMASK_RECIPE_MOBILE_OPAQUE_RUNTIME !== "1",
@@ -65,12 +69,28 @@ const mobileRun = {
65
69
  // source only matters for a development build the caller did not restart.
66
70
  runtimeCheck({ target, heal, state, json, appRestartAuthored }) {
67
71
  if (requireCurrentMobileReleaseArtifactState(target) !== null || appRestartAuthored) return void 0;
68
- return async () => {
69
- const { ensureMobileProofRuntime } = await import("../../commands/run-engine.js");
70
- return ensureMobileProofRuntime(target, heal, state, json);
71
- };
72
+ return () => ensureMobileProofRuntime(target, heal, state, json);
72
73
  }
73
74
  };
75
+ async function ensureMobileProofRuntime(target, _heal, state, json, deps = {}) {
76
+ const source = await import("./source-freshness.js");
77
+ const check = deps.check ?? (() => source.mobileSourceCheck(target));
78
+ const initial = check();
79
+ if (initial.status === "unavailable") {
80
+ emitProofFailure(json, { recoverable: false, error: initial.error });
81
+ return EXIT.usage;
82
+ }
83
+ if (initial.status === "current") return null;
84
+ const targetArg = JSON.stringify(path.resolve(target));
85
+ const userAction = `mm-harness call app.lifecycle --arg command=restart --adapter mobile --target ${targetArg}`;
86
+ const message = `Mobile loaded source is not current (${initial.status}).`;
87
+ emitProofFailure(json, {
88
+ recoverable: true,
89
+ mutations: state.mutations,
90
+ error: { code: "MOBILE_SOURCE_NOT_LOADED", message, userAction }
91
+ });
92
+ return EXIT.infra;
93
+ }
74
94
  const mobileRecording = {
75
95
  target: (context) => resolveMobileRecordingTarget(context),
76
96
  // An Android device records through the mirror, with screenrecord as the
@@ -127,11 +147,13 @@ const mobileDiagnostics = {
127
147
  };
128
148
  const mobileObservation = {
129
149
  network: {
130
- backend: async (target, env) => (await import("../../network-observation.js")).createMobileNetworkBackend(target, env)
150
+ backend: async (target, env) => (await import("./network-observer.js")).createMobileNetworkBackend(target, env)
131
151
  },
132
152
  performance: {
133
- backend: async (target, env) => (await import("./performance-observer.js")).createMobilePerformanceBackend(target, env),
134
- platform: (env) => devicePlatformFromEnv(env)
153
+ start: async (context) => (await import("../../performance-observation.js")).startRunPerformanceObservation({
154
+ backend: async (target, env) => (await import("./performance-observer.js")).createMobilePerformanceBackend(target, env),
155
+ platform: (env) => devicePlatformFromEnv(env)
156
+ }, context)
135
157
  }
136
158
  };
137
159
  function runMobileIssueCommand(projectRoot, command) {
@@ -150,6 +172,7 @@ function runMobileIssueCommand(projectRoot, command) {
150
172
  }
151
173
  }
152
174
  export {
175
+ ensureMobileProofRuntime,
153
176
  mobileDiagnostics,
154
177
  mobileObservation,
155
178
  mobileRecording,
@@ -0,0 +1,36 @@
1
+ function perpsOrderInputFindings(nodeId, node, { ensureChecksOnly }) {
2
+ const action = node.action;
3
+ const state = typeof node.state === "string" ? node.state.toLowerCase() : void 0;
4
+ const createsOrder = action === "metamask.perps.place_order" || !ensureChecksOnly && (action === "metamask.perps.ensure_positions" || action === "metamask.perps.ensure_orders") && (state === "open" || state === "present");
5
+ if (!createsOrder) return [];
6
+ const findings = [];
7
+ const requireOneOf = (names, message) => {
8
+ const present = names.some((name) => {
9
+ const value = node[name];
10
+ return value !== void 0 && value !== null && value !== "";
11
+ });
12
+ if (present) return;
13
+ findings.push({
14
+ severity: "error",
15
+ code: "recipe.missing_param",
16
+ path: `workflow.nodes.${nodeId}.${names[0]}`,
17
+ message
18
+ });
19
+ };
20
+ requireOneOf(
21
+ ["market", "symbol"],
22
+ `${action} requires market=<symbol> (or symbol=<symbol>) before it may create an order`
23
+ );
24
+ requireOneOf(
25
+ ["side"],
26
+ `${action} requires side=long or side=short before it may create an order`
27
+ );
28
+ requireOneOf(
29
+ ["amount", "notional"],
30
+ `${action} requires amount=<usd> or notional=<usd> before it may create an order`
31
+ );
32
+ return findings;
33
+ }
34
+ export {
35
+ perpsOrderInputFindings
36
+ };
@@ -0,0 +1,98 @@
1
+ import path from "node:path";
2
+ import {
3
+ adapterPlugin,
4
+ adapterSelectionFailureOut,
5
+ ensureAdapterLoaded,
6
+ selectedAdapterId
7
+ } from "@farmslot/recipe-cli/harness";
8
+ import { configPath, declaredLibraryPathEnv, readConfig } from "../review/knowledge.js";
9
+ import { adapterRegistry } from "./registry.js";
10
+ import { bindWebDappPolicy } from "./web-dapp/surface.js";
11
+ function configuredLibrarySources() {
12
+ let libraries;
13
+ try {
14
+ libraries = readConfig(configPath()).libraries;
15
+ } catch {
16
+ return [];
17
+ }
18
+ return Object.entries(libraries ?? {}).map(([name, root]) => ({ name, root: path.resolve(root) }));
19
+ }
20
+ function operatorLibraryEnv() {
21
+ const declared = declaredLibraryPathEnv();
22
+ if (!declared) return process.env;
23
+ const env = { ...process.env };
24
+ if (declared.value === void 0) delete env.RECIPE_LIBRARY_PATH;
25
+ else env.RECIPE_LIBRARY_PATH = declared.value;
26
+ return env;
27
+ }
28
+ async function loadSelectedAdapter(command, args) {
29
+ const passthrough = args.indexOf("--");
30
+ const own = passthrough < 0 ? args : args.slice(0, passthrough);
31
+ const libraries = [];
32
+ own.forEach((arg, index) => {
33
+ if (arg === "--library" && own[index + 1] !== void 0) libraries.push(own[index + 1]);
34
+ else if (arg.startsWith("--library=")) libraries.push(arg.slice("--library=".length));
35
+ });
36
+ const selected = selectedAdapterId(args);
37
+ try {
38
+ await ensureAdapterLoaded(selected, {
39
+ libraries,
40
+ configured: configuredLibrarySources(),
41
+ env: operatorLibraryEnv(),
42
+ adopt: adoptPluginAdapter
43
+ });
44
+ return void 0;
45
+ } catch (error) {
46
+ const refusal = error;
47
+ if (typeof refusal.code !== "string" || typeof refusal.userAction !== "string") throw error;
48
+ if (selected !== void 0 && adapterRegistry.has(selected) && refusal.code !== "ADAPTER_ID_CONFLICT") {
49
+ return void 0;
50
+ }
51
+ return adapterSelectionFailureOut(own.includes("--json"), command, {
52
+ code: refusal.code,
53
+ message: String(refusal.message),
54
+ userAction: refusal.userAction
55
+ });
56
+ }
57
+ }
58
+ const ADAPTER_PLUGIN_ENV = "METAMASK_RECIPE_ADAPTER_PLUGIN";
59
+ function adoptPluginAdapter(adapter) {
60
+ const surface = bindWebDappPolicy(adapter);
61
+ return {
62
+ ...surface,
63
+ run: {
64
+ ...surface.run,
65
+ envKeys: [...surface.run?.envKeys ?? [], ADAPTER_PLUGIN_ENV],
66
+ activateEnv(projectRoot, options) {
67
+ surface.run?.activateEnv?.(projectRoot, options);
68
+ process.env[ADAPTER_PLUGIN_ENV] = `${adapter.id} ${adapterPlugin(adapter.id)?.digest ?? "unknown"}`;
69
+ }
70
+ },
71
+ runwayProvision: surface.runwayProvision ?? {
72
+ async run(target) {
73
+ return {
74
+ schemaVersion: 1,
75
+ command: "provision",
76
+ adapter: adapter.id,
77
+ target: path.resolve(target),
78
+ status: "fail",
79
+ exitCode: 2,
80
+ error: {
81
+ code: "UNSUPPORTED_ADAPTER",
82
+ message: `${adapter.id} has no mobile simulator to provision.`,
83
+ userAction: `mm-harness run <recipe> --adapter ${adapter.id}`
84
+ }
85
+ };
86
+ }
87
+ },
88
+ readiness: surface.readiness ?? { compatibilityMode: () => "unsupported/no bridge" },
89
+ fixtures: surface.fixtures ?? { operations: [] }
90
+ };
91
+ }
92
+ export {
93
+ ADAPTER_PLUGIN_ENV,
94
+ adoptPluginAdapter,
95
+ configuredLibrarySources,
96
+ loadSelectedAdapter,
97
+ operatorLibraryEnv
98
+ };
@@ -0,0 +1,11 @@
1
+ function emitProofFailure(json, failure) {
2
+ if (json) {
3
+ console.log(JSON.stringify({ schemaVersion: 1, status: "fail", ...failure }, null, 2));
4
+ return;
5
+ }
6
+ console.error(`\u2717 ${failure.error.message}`);
7
+ console.error(` Next: ${failure.error.userAction}`);
8
+ }
9
+ export {
10
+ emitProofFailure
11
+ };
@@ -3,7 +3,7 @@ import { adapterRegistry } from "./registry.js";
3
3
  import "./mobile/surface.js";
4
4
  import "./extension/surface.js";
5
5
  import "./core/surface.js";
6
- import "./terminal/surface.js";
6
+ import "./web-dapp/surface.js";
7
7
  function getAdapterSurface(adapter) {
8
8
  return adapterRegistry.get(adapter);
9
9
  }
@@ -1,6 +1,7 @@
1
1
  import { manifestPath } from "../../paths.js";
2
- const terminalActions = {
3
- manifestPath: () => manifestPath("terminal"),
2
+ import { perpsOrderInputFindings } from "../perps-order-inputs.js";
3
+ const webDappActions = {
4
+ manifestPath: () => manifestPath("web-dapp"),
4
5
  semantic: [
5
6
  "app.network_capture",
6
7
  "app.network_assert",
@@ -11,8 +12,8 @@ const terminalActions = {
11
12
  ui: (harness, { hudPolicy }) => ({
12
13
  base: harness.createCdpWebUiTransport({
13
14
  async withPage(input, callback) {
14
- const { withTerminalPage } = await import("../../../library/actions/terminal/platform/page.mjs");
15
- return withTerminalPage(
15
+ const { withAppPage } = await import("../../../library/actions/web-dapp/platform/page.mjs");
16
+ return withAppPage(
16
17
  {
17
18
  action: input.action,
18
19
  node: input.node,
@@ -28,16 +29,18 @@ const terminalActions = {
28
29
  }),
29
30
  async execute(action, node, context, standard) {
30
31
  if (action === "app.hud") {
31
- const { showTerminalHud } = await import("../../../library/actions/terminal/platform/hud.mjs");
32
- return showTerminalHud(
32
+ const { showAppHud } = await import("../../../library/actions/web-dapp/platform/hud.mjs");
33
+ return showAppHud(
33
34
  { action, node, context: { nodeId: context.nodeId, projectRoot: context.projectRoot } },
34
35
  { hudPolicy }
35
36
  );
36
37
  }
37
38
  return standard();
38
39
  }
39
- })
40
+ }),
41
+ // ensure_positions and ensure_orders with an open or present state place an order.
42
+ inputFindings: (nodeId, node) => perpsOrderInputFindings(nodeId, node, { ensureChecksOnly: false })
40
43
  };
41
44
  export {
42
- terminalActions
45
+ webDappActions
43
46
  };
@@ -0,0 +1,63 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { recipeRuntimePath } from "@farmslot/recipe-cli/harness";
4
+ import { verifyCapture } from "../../../library/actions/shared/console-collector.mjs";
5
+ import { ensureConsoleCollector } from "../console-capture.js";
6
+ import { webDappAdapterId } from "../../../adapters/web-dapp/lib/policy.mjs";
7
+ const WEB_DAPP_CONSOLE_FILES = Object.freeze({
8
+ app: "app-console.log",
9
+ extension: "extension-console.log",
10
+ collectorLog: "console-tail.log",
11
+ pidFile: "console-tail.pid"
12
+ });
13
+ function webDappRuntimeFile(target, name) {
14
+ let root = path.resolve(target);
15
+ try {
16
+ root = fs.realpathSync(root);
17
+ } catch {
18
+ }
19
+ return recipeRuntimePath(root, webDappAdapterId(), name);
20
+ }
21
+ function webDappCaptureFiles(target) {
22
+ return {
23
+ extensionLog: webDappRuntimeFile(target, WEB_DAPP_CONSOLE_FILES.extension),
24
+ pageLog: webDappRuntimeFile(target, WEB_DAPP_CONSOLE_FILES.app),
25
+ collectorLog: webDappRuntimeFile(target, WEB_DAPP_CONSOLE_FILES.collectorLog),
26
+ pidFile: webDappRuntimeFile(target, WEB_DAPP_CONSOLE_FILES.pidFile)
27
+ };
28
+ }
29
+ function webDappCdpPort() {
30
+ return process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
31
+ }
32
+ async function ensureWebDappConsoleCapture(target) {
33
+ const cdpPort = webDappCdpPort();
34
+ if (!cdpPort) {
35
+ const id = webDappAdapterId();
36
+ throw new Error(`${id.charAt(0).toUpperCase()}${id.slice(1)} console capture requires the slot CDP port.`);
37
+ }
38
+ await ensureConsoleCollector({
39
+ cdpPort,
40
+ ...webDappCaptureFiles(target),
41
+ waitForAttachment: false,
42
+ cwd: target
43
+ });
44
+ }
45
+ async function verifyWebDappConsoleControl(capture) {
46
+ let browser = null;
47
+ try {
48
+ const value = JSON.parse(fs.readFileSync(webDappRuntimeFile(capture.projectRoot, "browser.json"), "utf8"));
49
+ browser = typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
50
+ } catch {
51
+ browser = null;
52
+ }
53
+ if (typeof browser?.appOrigin !== "string") return { ok: false, detail: "no slot browser state (browser.json) to verify the page console capture against" };
54
+ return verifyCapture({ cdpPort: capture.cdpPort, log: capture.pageLog, target: "page", appOrigin: browser.appOrigin });
55
+ }
56
+ export {
57
+ WEB_DAPP_CONSOLE_FILES,
58
+ ensureWebDappConsoleCapture,
59
+ verifyWebDappConsoleControl,
60
+ webDappCaptureFiles,
61
+ webDappCdpPort,
62
+ webDappRuntimeFile
63
+ };
@@ -0,0 +1,33 @@
1
+ import { POLICY_ENV, webDappPolicy } from "../../../adapters/web-dapp/lib/policy.mjs";
2
+ import { webDappAppPort, webDappCdpPort } from "./surface.js";
3
+ async function webDappDoctorChecks(target) {
4
+ if (!process.env[POLICY_ENV]) {
5
+ return [{
6
+ id: "web-dapp-venue-policy",
7
+ status: "fail",
8
+ required: true,
9
+ message: "web-dapp needs a venue policy: select an adapter that extends it (e.g. --adapter terminal with its recipe library on RECIPE_LIBRARY_PATH)."
10
+ }];
11
+ }
12
+ const policy = webDappPolicy();
13
+ const { webDappReadiness } = await import("../../../adapters/web-dapp/lib/readiness.mjs");
14
+ const { resolveBrowser } = await import("../../../adapters/web-dapp/launch.mjs");
15
+ const report = await webDappReadiness({
16
+ policy,
17
+ target,
18
+ appPort: webDappAppPort(),
19
+ cdpPort: webDappCdpPort(),
20
+ signer: process.env.TERMINAL_SIGNER || "extension",
21
+ account: process.env.TERMINAL_ACCOUNT || "dev1",
22
+ probeBrowser: resolveBrowser
23
+ });
24
+ return report.checks.map((check) => ({
25
+ id: `${policy.adapterId}-${check.id}`,
26
+ status: check.status === "pass" ? "pass" : "fail",
27
+ required: check.required,
28
+ message: check.detail
29
+ }));
30
+ }
31
+ export {
32
+ webDappDoctorChecks
33
+ };
@@ -2,7 +2,8 @@ import { flag, str } from "@farmslot/recipe-cli/harness";
2
2
  import { execFileSync } from "node:child_process";
3
3
  import path from "node:path";
4
4
  import { runnerDir } from "../../paths.js";
5
- const TERMINAL_ONLY_LAUNCH_OPTIONS = Object.freeze({
5
+ import { webDappAdapterId } from "../../../adapters/web-dapp/lib/policy.mjs";
6
+ const WEB_DAPP_ONLY_LAUNCH_OPTIONS = Object.freeze({
6
7
  signer: "--signer",
7
8
  account: "--account",
8
9
  freshProfile: "--fresh-profile",
@@ -12,12 +13,12 @@ const TERMINAL_ONLY_LAUNCH_OPTIONS = Object.freeze({
12
13
  network: "--network",
13
14
  mainnetConfirmation: "--mainnet-confirmation"
14
15
  });
15
- function terminalOnlyLaunchFlags(options) {
16
- return Object.entries(TERMINAL_ONLY_LAUNCH_OPTIONS).filter(([key]) => options[key] !== void 0 && options[key] !== false).map(([, name]) => name);
16
+ function webDappOnlyLaunchFlags(options) {
17
+ return Object.entries(WEB_DAPP_ONLY_LAUNCH_OPTIONS).filter(([key]) => options[key] !== void 0 && options[key] !== false).map(([, name]) => name);
17
18
  }
18
- function parseTerminalLauncherOutput(output) {
19
+ function parseWebDappLauncherOutput(output) {
19
20
  const text = output.trim();
20
- if (!text) return { status: "fail", error: "the terminal launcher printed no summary" };
21
+ if (!text) return { status: "fail", error: `the ${webDappAdapterId()} launcher printed no summary` };
21
22
  try {
22
23
  return JSON.parse(text);
23
24
  } catch {
@@ -25,21 +26,22 @@ function parseTerminalLauncherOutput(output) {
25
26
  try {
26
27
  return JSON.parse(last);
27
28
  } catch {
28
- return { status: "fail", error: `the terminal launcher printed no JSON summary: ${last.slice(0, 200)}` };
29
+ return { status: "fail", error: `the ${webDappAdapterId()} launcher printed no JSON summary: ${last.slice(0, 200)}` };
29
30
  }
30
31
  }
31
32
  }
32
- async function launchTerminal({ options, target, jsonOutput, stream, usage }) {
33
+ async function launchWebDapp({ options, target, jsonOutput, stream, usage }) {
34
+ const id = webDappAdapterId();
33
35
  const cdpPort = str(options, "cdpPort") ?? process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
34
36
  const appPort = str(options, "watcherPort") ?? process.env.TERMINAL_APP_PORT ?? process.env.RECIPE_WATCHER_PORT ?? process.env.WATCHER_PORT;
35
37
  if (!cdpPort || !appPort) {
36
38
  return usage(
37
- "terminal launch needs the slot browser CDP port and the slot dev-server port.",
38
- "mm-harness launch --adapter terminal --cdp-port <port> --watcher-port <app port>"
39
+ `${id} launch needs the slot browser CDP port and the slot dev-server port.`,
40
+ `mm-harness launch --adapter ${id} --cdp-port <port> --watcher-port <app port>`
39
41
  );
40
42
  }
41
43
  const args = [
42
- path.join(runnerDir, "adapters", "terminal", "launch.mjs"),
44
+ path.join(runnerDir, "adapters", "web-dapp", "launch.mjs"),
43
45
  "--target",
44
46
  target,
45
47
  "--cdp-port",
@@ -61,7 +63,7 @@ async function launchTerminal({ options, target, jsonOutput, stream, usage }) {
61
63
  if (network) args.push("--network", network);
62
64
  const mainnetConfirmation = str(options, "mainnetConfirmation");
63
65
  if (mainnetConfirmation) args.push("--mainnet-confirmation", mainnetConfirmation);
64
- stream.phase("launch", { adapter: "terminal", cdpPort, appPort });
66
+ stream.phase("launch", { adapter: id, cdpPort, appPort });
65
67
  let output = "";
66
68
  let status = 0;
67
69
  try {
@@ -70,16 +72,16 @@ async function launchTerminal({ options, target, jsonOutput, stream, usage }) {
70
72
  status = 1;
71
73
  output = String(error.stdout ?? "");
72
74
  }
73
- const result = parseTerminalLauncherOutput(output);
74
- if (jsonOutput) console.log(JSON.stringify({ schemaVersion: 1, command: "launch", adapter: "terminal", target, ...result }, null, 2));
75
- else if (result.status === "pass") console.log(`\u2713 terminal browser ${result.reused ? "reused" : "started"} on CDP ${result.cdpPort} (${result.signer}, ${result.account?.name})`);
76
- else console.error(`\u2717 terminal launch failed: ${result.error ?? "unknown error"}`);
75
+ const result = parseWebDappLauncherOutput(output);
76
+ if (jsonOutput) console.log(JSON.stringify({ schemaVersion: 1, command: "launch", adapter: id, target, ...result }, null, 2));
77
+ else if (result.status === "pass") console.log(`\u2713 ${id} browser ${result.reused ? "reused" : "started"} on CDP ${result.cdpPort} (${result.signer}, ${result.account?.name})`);
78
+ else console.error(`\u2717 ${id} launch failed: ${result.error ?? "unknown error"}`);
77
79
  stream.complete(result.status === "pass" ? "pass" : "fail", status);
78
80
  return result.status === "pass" ? 0 : 1;
79
81
  }
80
82
  export {
81
- TERMINAL_ONLY_LAUNCH_OPTIONS,
82
- launchTerminal,
83
- parseTerminalLauncherOutput,
84
- terminalOnlyLaunchFlags
83
+ WEB_DAPP_ONLY_LAUNCH_OPTIONS,
84
+ launchWebDapp,
85
+ parseWebDappLauncherOutput,
86
+ webDappOnlyLaunchFlags
85
87
  };
@@ -0,0 +1,21 @@
1
+ import path from "node:path";
2
+ import { runnerDir } from "../../paths.js";
3
+ const webDappHarness = {
4
+ install: { entry: "adapters/web-dapp/inject.mjs", fallback: "adapters/web-dapp/inject.mjs", node: true },
5
+ cleanup: { entry: "adapters/web-dapp/cleanup.mjs", fallback: "adapters/web-dapp/cleanup.mjs", node: true },
6
+ verify: () => ({
7
+ command: process.execPath,
8
+ prefixArgs: [path.join(runnerDir, "adapters", "web-dapp", "verify.mjs")]
9
+ }),
10
+ restart: "mm-harness launch"
11
+ };
12
+ const webDappRuntimeContext = {
13
+ forbiddenFields: ["metroPort", "simulator", "simulatorUdid", "adbSerial", "extensionId"],
14
+ resources: ({ existing, defaults, envPort }) => ({
15
+ cdpPort: envPort("RECIPE_CDP_PORT", "CDP_PORT") ?? existing.cdpPort ?? defaults.cdpPort
16
+ })
17
+ };
18
+ export {
19
+ webDappHarness,
20
+ webDappRuntimeContext
21
+ };
@@ -0,0 +1,10 @@
1
+ import { POLICY_ENV, webDappPolicy } from "../../../adapters/web-dapp/lib/policy.mjs";
2
+ const webDappReadiness = {
3
+ compatibilityMode: (target) => process.env[POLICY_ENV] && webDappPolicy().checkout.matches(target) ? "slot browser over CDP (website; no product bridge)" : "unsupported/no bridge",
4
+ liveChecks: async (target) => (await import("./doctor-checks.js")).webDappDoctorChecks(target),
5
+ // Screenshots come from the slot browser over CDP.
6
+ captureProviders: ["cdp"]
7
+ };
8
+ export {
9
+ webDappReadiness
10
+ };
@@ -2,21 +2,21 @@ import { isRecord } from "@farmslot/recipe-cli/harness";
2
2
  import { verifyOwnedCollectorControl } from "../console-capture.js";
3
3
  import { devicePlatformFromEnv } from "../performance/device-platform.js";
4
4
  import {
5
- ensureTerminalConsoleCapture,
6
- terminalCaptureFiles,
7
- terminalCdpPort,
8
- terminalRuntimeFile,
9
- verifyTerminalConsoleControl
5
+ ensureWebDappConsoleCapture,
6
+ webDappCaptureFiles,
7
+ webDappCdpPort,
8
+ webDappRuntimeFile,
9
+ verifyWebDappConsoleControl
10
10
  } from "./console-capture.js";
11
- const terminalDiagnostics = {
11
+ const webDappDiagnostics = {
12
12
  console: {
13
- start: (projectRoot) => ensureTerminalConsoleCapture(projectRoot),
14
- files: (projectRoot) => terminalCaptureFiles(projectRoot),
15
- cdpPort: () => terminalCdpPort(),
16
- verifyControl: (capture) => verifyOwnedCollectorControl(capture, () => verifyTerminalConsoleControl(capture))
13
+ start: (projectRoot) => ensureWebDappConsoleCapture(projectRoot),
14
+ files: (projectRoot) => webDappCaptureFiles(projectRoot),
15
+ cdpPort: () => webDappCdpPort(),
16
+ verifyControl: (capture) => verifyOwnedCollectorControl(capture, () => verifyWebDappConsoleControl(capture))
17
17
  },
18
18
  requestLog: {
19
- path: (projectRoot) => terminalRuntimeFile(projectRoot, "wallet-requests.jsonl"),
19
+ path: (projectRoot) => webDappRuntimeFile(projectRoot, "wallet-requests.jsonl"),
20
20
  findings: (lines) => walletRequestFindings(lines)
21
21
  }
22
22
  };
@@ -38,16 +38,21 @@ function walletRequestFindings(lines) {
38
38
  }
39
39
  return findings;
40
40
  }
41
- const terminalObservation = {
41
+ const webDappObservation = {
42
42
  network: {
43
- backend: async (_target, env, artifactsDir) => (await import("../../network-observation.js")).createExtensionNetworkBackend(env, artifactsDir)
43
+ backend: async (_target, env, artifactsDir) => (await import("../extension/network-observer.js")).createExtensionNetworkBackend(env, artifactsDir)
44
44
  },
45
45
  performance: {
46
- backend: async (target, env, ports) => (await import("../../performance-observation.js")).createExtensionPerformanceObserver(target, env, ports),
47
- platform: (env) => devicePlatformFromEnv(env)
46
+ start: async (context) => {
47
+ const observation = await import("../../performance-observation.js");
48
+ return observation.startRunPerformanceObservation({
49
+ backend: (target, env) => observation.createExtensionPerformanceObserver(target, env),
50
+ platform: (env) => devicePlatformFromEnv(env)
51
+ }, context);
52
+ }
48
53
  }
49
54
  };
50
55
  export {
51
- terminalDiagnostics,
52
- terminalObservation
56
+ webDappDiagnostics,
57
+ webDappObservation
53
58
  };