@deeeed/metamask-harness 0.17.5 → 0.19.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 (169) hide show
  1. package/CHANGELOG.md +1059 -0
  2. package/README.md +91 -140
  3. package/adapters/core/inject.sh +1 -6
  4. package/adapters/extension/console-tail.mjs +55 -24
  5. package/adapters/extension/ensure-browser.sh +6 -1
  6. package/adapters/extension/inject.mjs +7 -13
  7. package/adapters/extension/launch-browser.cjs +7 -1
  8. package/adapters/extension/launch-webpack.cjs +83 -0
  9. package/adapters/extension/launch.sh +8 -11
  10. package/adapters/extension/lib/chrome-args.cjs +8 -1
  11. package/adapters/extension/lib/macos-focus.cjs +32 -0
  12. package/adapters/extension/live.sh +12 -21
  13. package/adapters/extension/reattach.sh +1 -0
  14. package/adapters/extension/seed-fixture.sh +4 -12
  15. package/adapters/extension/sidepanel-toggle.sh +4 -1
  16. package/adapters/extension/stamp-runtime-title.cjs +58 -0
  17. package/adapters/extension/start-watch.sh +17 -4
  18. package/adapters/extension/stop-viewers.sh +1 -1
  19. package/adapters/extension/sync-webpack-dist.cjs +107 -0
  20. package/adapters/extension/verify.sh +2 -2
  21. package/adapters/manifest.json +53 -37
  22. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +16 -0
  23. package/adapters/mobile/bridge-runtime/console-forwarder.cjs +1 -1
  24. package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +3 -1
  25. package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +2 -3
  26. package/adapters/mobile/cleanup.sh +16 -176
  27. package/adapters/mobile/inject.sh +91 -671
  28. package/adapters/mobile/launch-console-forwarder.cjs +39 -0
  29. package/adapters/mobile/open-device.sh +45 -7
  30. package/adapters/mobile/start-console-forwarder.sh +70 -0
  31. package/adapters/mobile/start-metro.sh +0 -41
  32. package/adapters/mobile/verify.sh +34 -83
  33. package/adapters/mobile/wait-for-bridge.sh +22 -8
  34. package/adapters/shared/harness-source-fingerprint.mjs +14 -12
  35. package/adapters/shared/resolve-slot-ports-core.mjs +17 -6
  36. package/adapters/shared/resolve-slot-ports.sh +17 -5
  37. package/adapters/shared/update-check-worker.mjs +43 -0
  38. package/bin/mm-harness +21 -2
  39. package/dist/adapters/core/surface.js +4 -1
  40. package/dist/adapters/extension/console-capture.js +95 -0
  41. package/dist/adapters/extension/product-config.js +110 -0
  42. package/dist/adapters/extension/runtime-decision.js +20 -2
  43. package/dist/adapters/extension/surface.js +22 -3
  44. package/dist/adapters/harness-freshness.js +49 -0
  45. package/dist/adapters/mobile/perps-env.js +43 -3
  46. package/dist/adapters/mobile/prepare.js +39 -7
  47. package/dist/adapters/mobile/surface.js +5 -2
  48. package/dist/adapters/resolve-slot-ports.js +2 -2
  49. package/dist/adapters/slot-ports.js +13 -16
  50. package/dist/adapters.js +56 -16
  51. package/dist/checkout-lock.js +27 -2
  52. package/dist/cli-color.js +19 -0
  53. package/dist/cli-commands.js +1 -1
  54. package/dist/cli.js +7 -18
  55. package/dist/command-contract.js +462 -0
  56. package/dist/command-journal.js +263 -0
  57. package/dist/commands/call.js +252 -57
  58. package/dist/commands/check.js +10 -4
  59. package/dist/commands/completion-candidates.js +17 -22
  60. package/dist/commands/device-target.js +27 -12
  61. package/dist/commands/doctor.js +106 -25
  62. package/dist/commands/fixtures.js +142 -35
  63. package/dist/commands/last.js +60 -0
  64. package/dist/commands/launch/extension.js +40 -17
  65. package/dist/commands/launch/index.js +207 -63
  66. package/dist/commands/launch/mobile.js +2 -0
  67. package/dist/commands/list-executables.js +187 -29
  68. package/dist/commands/logs.js +8 -6
  69. package/dist/commands/manifest.js +286 -35
  70. package/dist/commands/parse-args.js +68 -4
  71. package/dist/commands/provision.js +10 -4
  72. package/dist/commands/run-engine.js +772 -264
  73. package/dist/commands/run-report.js +12 -3
  74. package/dist/commands/run.js +378 -85
  75. package/dist/commands/shared.js +76 -4
  76. package/dist/commands/status-probe.js +4 -1
  77. package/dist/commands/status.js +2 -1
  78. package/dist/commands/stop.js +7 -2
  79. package/dist/commands/update.js +16 -28
  80. package/dist/completions-cache.js +1 -1
  81. package/dist/doctor.js +57 -10
  82. package/dist/harness.js +47 -137
  83. package/dist/heal-bounds.js +7 -2
  84. package/dist/json-stream.js +57 -0
  85. package/dist/live-adapter-contract.js +138 -32
  86. package/dist/manifest.js +161 -1
  87. package/dist/mm-harness-cli.js +146 -73
  88. package/dist/paths.js +2 -5
  89. package/dist/recipe-security.js +178 -0
  90. package/dist/run-diagnostics.js +261 -0
  91. package/dist/run-recording.js +1 -1
  92. package/dist/runner.js +140 -9
  93. package/docs/CONTRIBUTING.md +136 -0
  94. package/docs/QA.md +184 -0
  95. package/docs/RECIPES.md +122 -0
  96. package/docs/SECURITY.md +88 -0
  97. package/library/README.md +7 -3
  98. package/library/actions/core/perps/_controller.mjs +43 -56
  99. package/library/actions/core/perps/assert_orders.mjs +6 -7
  100. package/library/actions/core/perps/assert_positions.mjs +6 -7
  101. package/library/actions/core/perps/close_orders.mjs +2 -0
  102. package/library/actions/core/perps/close_positions.mjs +2 -0
  103. package/library/actions/core/perps/ensure_orders.mjs +4 -2
  104. package/library/actions/core/perps/ensure_positions.mjs +4 -2
  105. package/library/actions/core/perps/place_order.mjs +7 -3
  106. package/library/actions/core/perps/read_account.mjs +2 -2
  107. package/library/actions/core/perps/read_orders.mjs +2 -1
  108. package/library/actions/core/perps/read_positions.mjs +2 -1
  109. package/library/actions/core/wallet/list_accounts.mjs +95 -0
  110. package/library/actions/extension/perps/assert_orders.mjs +2 -1
  111. package/library/actions/extension/perps/assert_positions.mjs +2 -1
  112. package/library/actions/extension/perps/perps.mjs +43 -14
  113. package/library/actions/extension/platform/cdp.mjs +1 -0
  114. package/library/actions/extension/wallet/list_accounts.mjs +41 -0
  115. package/library/actions/mobile/perps/assert_orders.mjs +2 -1
  116. package/library/actions/mobile/perps/assert_positions.mjs +2 -1
  117. package/library/actions/mobile/perps/perps.mjs +40 -12
  118. package/library/actions/mobile/wallet/list_accounts.mjs +37 -0
  119. package/library/library.json +1 -1
  120. package/library/manifests/core.action-manifest.json +1214 -396
  121. package/library/manifests/extension.action-manifest.json +1635 -728
  122. package/library/manifests/mobile.action-manifest.json +1740 -727
  123. package/library/recipes/app/lifecycle.android-smoke.mobile.recipe.json +63 -81
  124. package/library/recipes/perps/clean-market-testnet.core.recipe.json +44 -0
  125. package/library/recipes/perps/clean-market-testnet.recipe.json +49 -0
  126. package/library/recipes/perps/lifecycle.recipe.json +136 -180
  127. package/library/recipes/perps/order-lifecycle.core.recipe.json +71 -67
  128. package/library/recipes/perps/performance.background-resume.mobile.recipe.json +51 -67
  129. package/library/recipes/perps/performance.cold-start.mobile.recipe.json +51 -67
  130. package/library/recipes/perps/performance.mobile.recipe.json +37 -51
  131. package/library/recipes/perps/performance.warm-start.mobile.recipe.json +44 -59
  132. package/library/recipes/perps/read-markets.core.recipe.json +29 -31
  133. package/library/recipes/perps/smoke.core.recipe.json +29 -32
  134. package/library/recipes/perps/smoke.extension.recipe.json +41 -44
  135. package/library/recipes/perps/smoke.mobile.recipe.json +42 -44
  136. package/library/recipes/perps/trading-lifecycle.core.recipe.json +69 -65
  137. package/library/recipes/runner/action-validation.extension.recipe.json +312 -398
  138. package/library/recipes/runner/action-validation.mobile.recipe.json +316 -402
  139. package/library/recipes/runner/smoke.core.recipe.json +25 -0
  140. package/library/recipes/runner/smoke.extension.recipe.json +23 -24
  141. package/library/recipes/runner/smoke.mobile.recipe.json +23 -24
  142. package/library/recipes/wallet/smoke.extension.recipe.json +40 -0
  143. package/library/recipes/wallet/smoke.mobile.recipe.json +40 -0
  144. package/package.json +12 -5
  145. package/scripts/completions.sh +8 -11
  146. package/scripts/validate-human-outcomes.mjs +169 -0
  147. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgentStepHud.tsx.patch +0 -185
  148. package/adapters/mobile/overlay/app/dev-tools/AgenticService/AgenticService.ts.patch +0 -1662
  149. package/adapters/mobile/overlay/compat/README.md +0 -28
  150. package/adapters/mobile/overlay/compat/rn81-message-event-source.patch +0 -42
  151. package/adapters/shared/resolve-farmslot-ports-core.mjs +0 -3
  152. package/adapters/shared/resolve-farmslot-ports.mjs +0 -5
  153. package/adapters/shared/resolve-farmslot-ports.sh +0 -7
  154. package/dist/adapters/extension/harness-freshness.js +0 -39
  155. package/dist/commands/flows.js +0 -62
  156. package/docs/ADAPTER-SURFACE.md +0 -119
  157. package/docs/CHEATSHEET.md +0 -61
  158. package/docs/CLI-SPEC.md +0 -1098
  159. package/docs/CODE-MAP.md +0 -62
  160. package/docs/DEBUG-HANDOVER.md +0 -36
  161. package/docs/MENTAL-MODEL.md +0 -295
  162. package/docs/UX-PRINCIPLES.md +0 -64
  163. package/docs/architecture.md +0 -398
  164. package/docs/live-adapter-contract.md +0 -188
  165. package/docs/package-boundaries.md +0 -47
  166. package/docs/perps-flow-catalog.md +0 -235
  167. package/docs/recipe-libraries.md +0 -301
  168. package/docs/runtime-file-conventions.md +0 -36
  169. package/library/flows/perps.flows.json +0 -64
@@ -0,0 +1,41 @@
1
+ import { runAdapter, withExtensionPage } from '../platform/cdp.mjs';
2
+
3
+ runAdapter((input) => withExtensionPage(input, async (page) => {
4
+ const scope = input.node?.scope == null ? 'named' : String(input.node.scope);
5
+ if (scope !== 'selected' && scope !== 'named' && scope !== 'evm' && scope !== 'all') {
6
+ throw new Error('metamask.wallet.list_accounts scope must be selected, named, evm, or all.');
7
+ }
8
+ const result = await page.evaluate(`(() => {
9
+ const store = globalThis.stateHooks?.store;
10
+ if (!store || typeof store.getState !== 'function') {
11
+ throw new Error('stateHooks.store.getState is unavailable.');
12
+ }
13
+ const internal = store.getState()?.metamask?.internalAccounts || {};
14
+ const selectedId = internal.selectedAccount || null;
15
+ const scope = ${JSON.stringify(scope)};
16
+ const accounts = Object.entries(internal.accounts || {})
17
+ .map(([id, account]) => ({
18
+ id,
19
+ address: account?.address || null,
20
+ name: account?.metadata?.name || null,
21
+ type: account?.type || null,
22
+ selected: id === selectedId
23
+ }))
24
+ .filter((account) => {
25
+ if (scope === 'selected') return account.selected;
26
+ if (scope === 'all') return true;
27
+ if (scope === 'evm') return /^0x[0-9a-f]{40}$/iu.test(account.address || '');
28
+ return account.selected || Boolean(String(account.name || '').trim());
29
+ });
30
+ return { accounts };
31
+ })()`);
32
+ return {
33
+ action: input.action,
34
+ source: 'extension-stateHooks-store',
35
+ scope,
36
+ ...result,
37
+ count: result.accounts.length,
38
+ redacted: true,
39
+ proofPath: 'extension-wallet-accounts',
40
+ };
41
+ }));
@@ -2,7 +2,8 @@ import { runAdapter } from '../platform/bridge.mjs';
2
2
  import { assertOrders } from './perps.mjs';
3
3
 
4
4
  function expectedOpen(input) {
5
- const state = String(input.node?.state ?? input.node?.orders ?? 'open').toLowerCase();
5
+ if (input.node?.state == null) throw new Error('metamask.perps.assert_orders requires state=open or state=none.');
6
+ const state = String(input.node.state).toLowerCase();
6
7
  if (state === 'open' || state === 'present') return true;
7
8
  if (state === 'none' || state === 'closed' || state === 'absent') return false;
8
9
  throw new Error(`metamask.perps.assert_orders received unsupported state: ${state}`);
@@ -2,7 +2,8 @@ import { runAdapter } from '../platform/bridge.mjs';
2
2
  import { assertPositions } from './perps.mjs';
3
3
 
4
4
  function expectedOpen(input) {
5
- const state = String(input.node?.state ?? input.node?.position ?? 'open').toLowerCase();
5
+ if (input.node?.state == null) throw new Error('metamask.perps.assert_positions requires state=open or state=none.');
6
+ const state = String(input.node.state).toLowerCase();
6
7
  if (state === 'open' || state === 'present') return true;
7
8
  if (state === 'none' || state === 'closed' || state === 'absent') return false;
8
9
  throw new Error(`metamask.perps.assert_positions received unsupported state: ${state}`);
@@ -34,20 +34,39 @@ function configuredSymbols(input, items) {
34
34
  const explicit = input.node?.markets ?? input.node?.symbols ?? selector.markets ?? selector.symbols;
35
35
  if (Array.isArray(explicit) && explicit.length > 0) return uniqueSymbols(explicit.map(normalizeMarketSymbol));
36
36
  if (typeof explicit === 'string' && explicit.length > 0) return uniqueSymbols(explicit.split(',').map((part) => normalizeMarketSymbol(part.trim())).filter(Boolean));
37
- return [marketSymbol(input)];
37
+ const single = input.node?.market ?? input.node?.symbol ?? selector.market ?? selector.symbol;
38
+ return single ? [normalizeMarketSymbol(single)] : [];
38
39
  }
39
40
 
40
- function selectedItems(input, items) {
41
- const symbols = new Set(configuredSymbols(input, items));
41
+ export function selectedItems(input, items) {
42
+ const requested = configuredSymbols(input, items);
43
+ const symbols = requested.length > 0 ? new Set(requested) : null;
42
44
  const selector = input.node?.selector && typeof input.node.selector === 'object' ? input.node.selector : {};
43
45
  const side = input.node?.side ?? selector.side;
44
46
  return items.filter((item) => {
45
- if (!symbols.has(symbolForItem(item))) return false;
47
+ if (symbols && !symbols.has(symbolForItem(item))) return false;
46
48
  if (side && sideForItem(item) && sideForItem(item) !== String(side).toLowerCase()) return false;
47
49
  return true;
48
50
  });
49
51
  }
50
52
 
53
+ function requireExplicitSelection(input) {
54
+ const node = input.node ?? {};
55
+ const selector = node.selector && typeof node.selector === 'object' ? node.selector : {};
56
+ const values = [node.market, node.symbol, node.markets, node.symbols, selector.market, selector.symbol, selector.markets, selector.symbols];
57
+ const selected = values.some((value) => Array.isArray(value) ? value.length > 0 : value != null && String(value).trim() !== '');
58
+ if (!selected && node.mode !== 'all' && selector.mode !== 'all') {
59
+ throw new Error(`${input.action} requires market=<symbol> or mode=all.`);
60
+ }
61
+ }
62
+
63
+ function requirePlaceOrderInputs(input) {
64
+ const node = input.node ?? {};
65
+ if (node.market == null && node.symbol == null) throw new Error(`${input.action} requires market=<symbol>.`);
66
+ if (node.side == null) throw new Error(`${input.action} requires side=long or side=short.`);
67
+ if (node.amount == null && node.notional == null) throw new Error(`${input.action} requires amount=<usd> or notional=<usd>.`);
68
+ }
69
+
51
70
  function uniqueSymbols(symbols) {
52
71
  return Array.from(new Set(symbols.filter(Boolean)));
53
72
  }
@@ -250,20 +269,22 @@ export async function readOrders(input) {
250
269
  }
251
270
 
252
271
  export async function assertPositions(input, expectedOpen) {
272
+ requireExplicitSelection(input);
253
273
  const positions = await readPositions(input);
254
274
  const matching = selectedItems(input, positions);
255
275
  const hasPosition = matching.length > 0;
256
- if (expectedOpen && !hasPosition) throw new Error(`Expected selected Perps position(s), but none matched ${JSON.stringify(input.node)}.`);
257
- if (!expectedOpen && hasPosition) throw new Error(`Expected no selected Perps positions, but ${matching.length} matched ${JSON.stringify(input.node)}.`);
276
+ if (expectedOpen && !hasPosition) throw new Error('Expected at least one matching open Perps position, but found none.');
277
+ if (!expectedOpen && hasPosition) throw new Error(`Expected no matching open Perps positions, but found ${matching.length}.`);
258
278
  return { action: input.action, expectedOpen, matchingCount: matching.length, positions: matching.map(redactPosition), source: 'mobile-perps-controller' };
259
279
  }
260
280
 
261
281
  export async function assertOrders(input, expectedOpen) {
282
+ requireExplicitSelection(input);
262
283
  const orders = await readOpenOrders(input);
263
284
  const matching = selectedItems(input, orders);
264
285
  const hasOrders = matching.length > 0;
265
- if (expectedOpen && !hasOrders) throw new Error(`Expected selected Perps order(s), but none matched ${JSON.stringify(input.node)}.`);
266
- if (!expectedOpen && hasOrders) throw new Error(`Expected no selected Perps orders, but ${matching.length} matched ${JSON.stringify(input.node)}.`);
286
+ if (expectedOpen && !hasOrders) throw new Error('Expected at least one matching open Perps order, but found none.');
287
+ if (!expectedOpen && hasOrders) throw new Error(`Expected no matching open Perps orders, but found ${matching.length}.`);
267
288
  return { action: input.action, expectedOpen, matchingCount: matching.length, orders: matching.map(redactOrder), source: 'mobile-perps-controller-open-orders' };
268
289
  }
269
290
 
@@ -315,6 +336,7 @@ async function closePositionItem(input, position) {
315
336
  }
316
337
 
317
338
  export async function closePositions(input) {
339
+ requireExplicitSelection(input);
318
340
  const before = await readPositions(input);
319
341
  const matching = selectedItems(input, before);
320
342
  const symbols = uniqueSymbols(matching.map(symbolForItem));
@@ -336,6 +358,7 @@ export async function closePositions(input) {
336
358
  }
337
359
 
338
360
  export async function closeOrders(input) {
361
+ requireExplicitSelection(input);
339
362
  const before = await readOpenOrders(input);
340
363
  const matching = selectedItems(input, before);
341
364
  const symbols = uniqueSymbols(matching.map(symbolForItem));
@@ -380,9 +403,10 @@ export async function closeOrders(input) {
380
403
  }
381
404
 
382
405
  export async function placeOrder(input) {
406
+ requirePlaceOrderInputs(input);
383
407
  const symbol = marketSymbol(input);
384
- const side = String(input.node?.side ?? 'long').toLowerCase();
385
- const amount = String(input.node?.amount ?? input.node?.notional ?? '11');
408
+ const side = String(input.node.side).toLowerCase();
409
+ const amount = String(input.node.amount ?? input.node.notional);
386
410
  const size = String(input.node?.size ?? '0.0001');
387
411
  const leverage = Number(input.node?.leverage ?? 3);
388
412
  const maxSlippageBps = Number(input.node?.max_slippage_bps ?? input.node?.maxSlippageBps ?? 300);
@@ -401,7 +425,9 @@ export async function placeOrder(input) {
401
425
  }
402
426
 
403
427
  export async function ensurePositions(input) {
404
- const state = String(input.node?.state ?? input.node?.position ?? 'none').toLowerCase();
428
+ if (input.node?.state == null) throw new Error('metamask.perps.ensure_positions requires state=open or state=none.');
429
+ requireExplicitSelection(input);
430
+ const state = String(input.node.state).toLowerCase();
405
431
  if (state === 'none' || state === 'closed' || state === 'absent') {
406
432
  const close = await closePositions(input);
407
433
  return { ...(await assertPositions(input, false)), close };
@@ -415,7 +441,9 @@ export async function ensurePositions(input) {
415
441
  }
416
442
 
417
443
  export async function ensureOrders(input) {
418
- const state = String(input.node?.state ?? input.node?.orders ?? 'none').toLowerCase();
444
+ if (input.node?.state == null) throw new Error('metamask.perps.ensure_orders requires state=open or state=none.');
445
+ requireExplicitSelection(input);
446
+ const state = String(input.node.state).toLowerCase();
419
447
  if (state === 'none' || state === 'closed' || state === 'absent') {
420
448
  const close = await closeOrders(input);
421
449
  return { ...(await assertOrders(input, false)), close };
@@ -0,0 +1,37 @@
1
+ import { bridgeCommand, runAdapter } from '../platform/bridge.mjs';
2
+
3
+ runAdapter(async (input) => {
4
+ const scope = input.node?.scope == null ? 'named' : String(input.node.scope);
5
+ if (scope !== 'selected' && scope !== 'named' && scope !== 'evm' && scope !== 'all') {
6
+ throw new Error('metamask.wallet.list_accounts scope must be selected, named, evm, or all.');
7
+ }
8
+ const rawAccounts = await bridgeCommand(input, ['list-accounts']);
9
+ if (!Array.isArray(rawAccounts)) {
10
+ throw new Error(`metamask.wallet.list_accounts expected an array, got ${typeof rawAccounts}.`);
11
+ }
12
+ const selected = await bridgeCommand(input, ['get-selected-account']);
13
+ const selectedAddress = String(selected?.address ?? selected ?? '').toLowerCase();
14
+ const accounts = rawAccounts
15
+ .map((account) => ({
16
+ id: account?.id ?? null,
17
+ address: account?.address ?? null,
18
+ name: account?.name ?? null,
19
+ type: account?.type ?? null,
20
+ selected: Boolean(selectedAddress && String(account?.address ?? '').toLowerCase() === selectedAddress),
21
+ }))
22
+ .filter((account) => {
23
+ if (scope === 'selected') return account.selected;
24
+ if (scope === 'all') return true;
25
+ if (scope === 'evm') return /^0x[0-9a-f]{40}$/iu.test(String(account.address ?? ''));
26
+ return account.selected || Boolean(String(account.name ?? '').trim());
27
+ });
28
+ return {
29
+ action: input.action,
30
+ source: 'mobile-accounts-controller',
31
+ scope,
32
+ accounts,
33
+ count: accounts.length,
34
+ redacted: true,
35
+ proofPath: 'agentic-wallet-accounts',
36
+ };
37
+ });
@@ -3,5 +3,5 @@
3
3
  "schema_version": 1,
4
4
  "name": "metamask",
5
5
  "owner": "mm-harness-runner",
6
- "description": "Canonical MetaMask recipe library: minimal, stable flows every recipe can compose. Team and personal libraries layer on top and win resolution by default."
6
+ "description": "Canonical MetaMask library of reusable, parameterized recipes. Team and personal libraries layer on top and win resolution by default."
7
7
  }