@deeeed/metamask-harness 0.50.2 → 0.50.3

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 CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.50.3 - 2026-09-09
6
+
7
+ - Recognize enabled form controls through recipe-harness 0.16.1; report Extension startup crashes without reload-based RPC probes or automatic relaunch recovery.
8
+ - Color human recipe help while keeping JSON plain; clarify recipe composition, Extension timing capture, acceptance criteria, and team-library updates.
9
+ - Make architecture topics expandable and correct cheatsheet platform, recording, and recipe examples.
10
+
5
11
  ## 0.50.2 - 2026-09-08
6
12
 
7
13
  - Remove Mobile receipt actions that require unpublished app/SDK APIs; reject stale receipt-based assertion and cleanup inputs before execution.
package/README.md CHANGED
@@ -158,6 +158,10 @@ mm-harness run --list
158
158
  Shared libraries hold durable actions and composable recipes. Task acceptance
159
159
  criteria remain task-local. See [Recipes](docs/RECIPES.md).
160
160
 
161
+ Teams maintain their library checkouts. Update them before starting a task;
162
+ the harness records local revisions and recipe digests but does not fetch or
163
+ check for upstream updates. Keep the selected library unchanged during a run.
164
+
161
165
  ## Recover
162
166
 
163
167
  ```bash
@@ -441,6 +441,16 @@ async function decideExtensionReadiness(target, options = {}) {
441
441
  reasons: ["Build is fresh and the extension runtime is healthy over CDP."],
442
442
  actions: []
443
443
  },
444
+ {
445
+ when: cdp.errorCode === "EXTENSION_BACKGROUND_UNRESPONSIVE",
446
+ decision: "blocked",
447
+ reasonCode: "background-unresponsive",
448
+ reasons: [
449
+ "Extension startup failed; inspect the background error before relaunching or resetting wallet state.",
450
+ ...cdp.findings?.slice(0, 3) ?? []
451
+ ],
452
+ actions: []
453
+ },
444
454
  {
445
455
  when: cdp.errorCode === "EVM_RPC_EXECUTION_FAILED",
446
456
  decision: "blocked",
@@ -492,6 +502,13 @@ async function decideExtensionReadiness(target, options = {}) {
492
502
  reasons: [`Release artifact ${releaseArtifact.expectedVersion} is intact and healthy over CDP.`],
493
503
  actions: []
494
504
  },
505
+ {
506
+ when: cdp.errorCode === "EXTENSION_BACKGROUND_UNRESPONSIVE",
507
+ decision: "blocked",
508
+ reasonCode: "background-unresponsive",
509
+ reasons: ["Extension startup failed; inspect the background error before relaunching or resetting wallet state.", ...cdp.findings?.slice(0, 3) ?? []],
510
+ actions: []
511
+ },
495
512
  {
496
513
  when: cdp.errorCode === "EVM_RPC_EXECUTION_FAILED",
497
514
  decision: "blocked",
@@ -51,7 +51,7 @@ async function assertHealthyExtensionRuntime(options) {
51
51
  pageMode: options.pageMode
52
52
  });
53
53
  if (lastReport.status === "PASS") return lastReport;
54
- if (lastReport.errorCode === "UI_COMPOSITOR_SUSPENDED") {
54
+ if (lastReport.errorCode === "UI_COMPOSITOR_SUSPENDED" || lastReport.errorCode === "EXTENSION_BACKGROUND_UNRESPONSIVE") {
55
55
  throw new Error(formatHealthFailure(lastReport, options.projectRoot));
56
56
  }
57
57
  await sleep(500);
@@ -151,6 +151,23 @@ async function checkExtensionRuntimeHealth(projectRoot, cdpPort, options = {}) {
151
151
  };
152
152
  }
153
153
  const runtime = await evaluateHealth(session, cdpCallTimeoutMs);
154
+ if (runtime.backgroundUnresponsive === true) {
155
+ return {
156
+ status: "FAIL",
157
+ errorCode: "EXTENSION_BACKGROUND_UNRESPONSIVE",
158
+ userAction: `mm-harness logs --target ${shellQuote(projectRoot)} --full # inspect the startup error before restarting or resetting an authorized dev fixture`,
159
+ cdpPort,
160
+ targetUrl: target.url,
161
+ extensionId: safeExtensionId(target),
162
+ extensionPageTargets: extensionTargets.length,
163
+ findings: ["EXTENSION_BACKGROUND_UNRESPONSIVE: Extension UI reports a startup failure or unresponsive background; RPC readiness cannot be established."],
164
+ details: {
165
+ projectRoot,
166
+ targetUrls: targets.map((entry) => entry.url).filter(Boolean),
167
+ runtime
168
+ }
169
+ };
170
+ }
154
171
  if (runtime.hasSubmitRequest !== true) {
155
172
  const providerProbe = await probeUiEthereumProvider(session, cdpCallTimeoutMs);
156
173
  runtime.evmRpcProbeOk = providerProbe.ok;
@@ -160,9 +177,6 @@ async function checkExtensionRuntimeHealth(projectRoot, cdpPort, options = {}) {
160
177
  if (runtime.href && !String(runtime.href).startsWith("chrome-extension://")) {
161
178
  findings.push(`Extension page href is not an extension URL: ${runtime.href}`);
162
179
  }
163
- if (runtime.backgroundUnresponsive === true) {
164
- findings.push("Extension UI reports background connection unresponsive.");
165
- }
166
180
  if (runtime.ethereumConnectionUnavailable === true) {
167
181
  findings.push('Extension UI reports "Unable to connect to Ethereum".');
168
182
  }
@@ -1,5 +1,6 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
+ import { color } from "../cli-color.js";
3
4
  import { detectAdapter } from "../harness.js";
4
5
  import { optionString, parseArgs, targetPath } from "./parse-args.js";
5
6
  const UNSCOPED_NEXT = "cd into a MetaMask checkout or pass --adapter <mobile|extension|core>";
@@ -48,21 +49,22 @@ function recipeHelpPayload(topic, harnessVersion, adapter, target) {
48
49
  };
49
50
  }
50
51
  function renderRecipeHelp(topic, harnessVersion, adapter) {
52
+ const paint = (style, text) => color(style, text, { stream: process.stdout });
51
53
  const lines = [
52
- `mm-harness ${harnessVersion} recipe guide`,
53
- `Context: Recipe Protocol v${topic.recipeProtocolVersion}, adapter ${adapter}`,
54
+ paint("bold", `mm-harness ${harnessVersion} recipe guide`),
55
+ paint("dim", `Context: Recipe Protocol v${topic.recipeProtocolVersion}, adapter ${adapter}`),
54
56
  topic.summary,
55
57
  topic.capabilities,
56
- topic.scopeNotice,
57
- "Visual tutorial: mm-harness tutorial"
58
+ paint("warn", topic.scopeNotice),
59
+ `Visual tutorial: ${paint("cmd", "mm-harness tutorial")}`
58
60
  ];
59
- if (adapter === "unscoped") lines.push(`No MetaMask checkout detected. Next: ${UNSCOPED_NEXT}.`);
61
+ if (adapter === "unscoped") lines.push(paint("warn", `No MetaMask checkout detected. Next: ${UNSCOPED_NEXT}.`));
60
62
  for (const [index, section] of scopedSections(topic, adapter).entries()) {
61
- lines.push("", `${index + 1}. ${section.title}`, ` ${section.instruction}`);
63
+ lines.push("", paint("label", `${index + 1}. ${section.title}`), ` ${section.instruction}`);
62
64
  for (const detail of section.details) lines.push(` - ${detail}`);
63
- for (const command of section.commands) lines.push(` $ ${command}`);
65
+ for (const command of section.commands) lines.push(` $ ${paint("cmd", command)}`);
64
66
  }
65
- lines.push("", `Safety: ${topic.safety}`);
67
+ lines.push("", `${paint("warn", "Safety:")} ${topic.safety}`);
66
68
  return `${lines.join("\n")}
67
69
  `;
68
70
  }
@@ -310,6 +310,16 @@ async function handleLaunchLocked(argv, stream) {
310
310
  exitCode: EXIT.infra
311
311
  });
312
312
  }
313
+ if (adapter === "extension" && attempt.output.includes("EXTENSION_BACKGROUND_UNRESPONSIVE:")) {
314
+ return launchFail(jsonOutput, stream, adapter, mobileTarget, tier, state, target, {
315
+ code: "EXTENSION_BACKGROUND_UNRESPONSIVE",
316
+ message: "Extension startup failed; inspect the background error before restarting or resetting wallet state.",
317
+ recoverable: false,
318
+ userAction: `mm-harness logs --target ${shellQuote(target)} --full`,
319
+ exitCode: EXIT.runtime,
320
+ originalError: attempt.output.trim() || void 0
321
+ });
322
+ }
313
323
  if (adapter === "extension" && attempt.output.includes("EVM_RPC_EXECUTION_FAILED:")) {
314
324
  return launchFail(jsonOutput, stream, adapter, mobileTarget, tier, state, target, {
315
325
  code: "EVM_RPC_EXECUTION_FAILED",
package/docs/QA.md CHANGED
@@ -56,8 +56,8 @@ mm-harness verify
56
56
  - [ ] Bad input fails before runtime work with a stable code and one exact next
57
57
  action.
58
58
  - [ ] `doctor --fix` is idempotent and does not choose a fixture or launch.
59
- - [ ] With `capture-helper` absent, Extension reports CDP screenshots and Mobile
60
- reports `simctl`/ADB screenshots; video is honestly unavailable.
59
+ - [ ] With `capture-helper` absent, Extension reports CDP screenshots and no
60
+ Extension video; Mobile reports its independent simctl/ADB capture readiness.
61
61
 
62
62
  Run the bounded multi-platform preflight when all three checkouts are available:
63
63
 
@@ -177,8 +177,8 @@ Before release:
177
177
  Core scenarios.
178
178
  - [ ] Independent review approves the exact final diff.
179
179
  - [ ] Known limits are explicit: Extension requires product Infura setup;
180
- Mobile requires its normal dev-client/device setup; video requires optional
181
- `capture-helper`; the harness never invents funded fixtures.
180
+ Mobile requires its normal dev-client/device setup; Extension video requires
181
+ `capture-helper`, while Mobile uses platform recorders; the harness never invents funded fixtures.
182
182
  - [ ] Existing [Farmslot](https://farmslot.io) slots remain compatible.
183
183
 
184
184
  When a slot manager is available, repeat `runner.smoke` in one existing managed
package/docs/RECIPES.md CHANGED
@@ -96,6 +96,23 @@ detached-DOM check, and idle-control comparison. A one-screen slope does not
96
96
  prove those broader claims. Check the suite's reference markers against the
97
97
  tested build before running it.
98
98
 
99
+ ### Extension timing observation
100
+
101
+ First inspect `app.performance_capture` and `app.network_capture`. When the
102
+ `recipe-performance` skill is installed, its
103
+ `scripts/capture-extension-window.mjs --help` describes capture around a single
104
+ command. Run that wrapper outside the recipe; a command node cannot start a
105
+ second `mm-harness run/call` while its parent owns the checkout sandbox.
106
+
107
+ For a custom observer blocked by LavaMoat, register a pre-boot script with
108
+ `Page.addScriptToEvaluateOnNewDocument` to retain only the required native API
109
+ reference, such as a bound `requestAnimationFrame`. Initialize it during explicit
110
+ setup, remove the preload after use, and clean up the observer afterward. Do not
111
+ disable scuttling, pause the debugger, or reload inside the measured window.
112
+ Record lifecycle identity and use the application's clock for UI timing; CLI
113
+ duration is a different measurement. Keep endpoint and readiness predicates in
114
+ the team recipe, not a new harness action.
115
+
99
116
  ### Extension Perps release recipes
100
117
 
101
118
  Discover these identifiers with `run --list --adapter extension --json` and
@@ -189,6 +206,10 @@ export RECIPE_LIBRARY_PATH="perps=/path/to/experimental-metamask-recipe-perps"
189
206
 
190
207
  `perps.pro-order-setup` preserves existing orders, positions, and strategies by
191
208
  default. Its Scale, Chase, and TWAP placement callers inherit that policy.
209
+ All three placement helpers require explicit `account` and `notional` inputs.
210
+ Scale also requires `start_price` and `end_price`; Chase requires `max_distance`.
211
+ Choose values for the current market and approved budget. Preserve JSON quotes
212
+ for string parameters, for example `'notional="15"'`; inspect `--describe` first.
192
213
  When a proof requires a clean testnet market, reserve the account/market and
193
214
  compose explicit setup with `trading_state=clean_selected_testnet_market`.
194
215
  This parameter never authorizes cleanup of another task's trades.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.50.2",
3
+ "version": "0.50.3",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"
@@ -24,7 +24,7 @@
24
24
  "@farmslot/expo-recipe": "0.9.0",
25
25
  "@farmslot/handoff": "^0.3.1",
26
26
  "@farmslot/protocol": "0.21.0",
27
- "@farmslot/recipe-harness": "0.16.0",
27
+ "@farmslot/recipe-harness": "0.16.1",
28
28
  "agent-device": "0.19.3",
29
29
  "commander": "^12.0.0",
30
30
  "es-module-lexer": "2.3.1",
@@ -94,6 +94,7 @@
94
94
  "instruction": "Use static call references for unchanged sub-journeys. Separate preparation, action, assertions, and evidence; add workflow.teardown when cleanup must run after success or failure.",
95
95
  "details": [
96
96
  "Starting conditions are ordinary nodes at the beginning of workflow, not a preconditions field. Use idempotent ensure actions and independently assert their postconditions.",
97
+ "Do not invoke mm-harness run/call from a command node on the same checkout: the outer run owns its sandbox. Compose actions or call child recipes instead. External measurement wrappers may invoke one root run, with preparation outside it; never bypass the lock.",
97
98
  "Inspect existing state before creating or cleaning fixtures. Establish only prerequisites required by the claim; a history or display check may already have usable records and need no trade or cleanup. Record the selected records and verify they still satisfy the proof boundary.",
98
99
  "Match history-query ranges to the dates of existing visible records before declaring fixtures missing. Sample only sections needed for change detection; fetch one-time corroborating history once, and count background calls as well as graph nodes.",
99
100
  "Actions translate UI, CDP, or controller operations. Recipes compose them. Product code owns business rules. Teams own specialized journeys in their libraries; the harness retains shared capabilities and a small set of composable examples.",
@@ -188,6 +189,10 @@
188
189
  ]
189
190
  },
190
191
  "Performance: define a measured window, exclude setup, and compare equivalent builds, devices, and lifecycle conditions; action duration alone is not app latency.",
192
+ {
193
+ "text": "For Extension timing, inspect app.performance_capture/app.network_capture and the installed recipe-performance capture-extension-window.mjs helper before writing a collector. If LavaMoat blocks an observation API, capture its reference before initialization via Page.addScriptToEvaluateOnNewDocument during explicit setup, not inside the measured window. Keep domain predicates in the team recipe; see docs/RECIPES.md#extension-timing-observation.",
194
+ "adapters": ["extension"]
195
+ },
191
196
  "Retain samples and declared limits when a measurement fails. For memory growth, compare an equal-duration idle control before attributing it to navigation; do not raise the budget to make the run pass.",
192
197
  "Analytics: verify the collection destination and existing consent before capture. Changing participation or marketing consent requires operator approval and restoration of the prior settings; a recipe's hardcoded opt-in is not permission. Missing collection prerequisites are not missing product events.",
193
198
  "Flake risk: wait on observable state instead of sleeping, keep device and runtime identity explicit, and never overwrite a prior run's artifacts.",