@deeeed/metamask-harness 0.79.0 → 0.81.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 (252) hide show
  1. package/CHANGELOG.md +73 -0
  2. package/README.md +1 -1
  3. package/adapters/extension/ensure-browser.sh +2 -2
  4. package/adapters/extension/inject.mjs +0 -1
  5. package/adapters/extension/launch-browser.cjs +39 -613
  6. package/adapters/extension/readiness.mjs +2 -5
  7. package/adapters/extension/reattach.sh +2 -2
  8. package/adapters/extension/stamp-runtime-title.cjs +1 -1
  9. package/adapters/manifest.json +16 -64
  10. package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +57 -1358
  11. package/adapters/mobile/bridge-runtime/teaching.cjs +20 -0
  12. package/adapters/mobile/inject.sh +16 -0
  13. package/adapters/mobile/launch-console-forwarder.cjs +1 -2
  14. package/adapters/mobile/open-device.sh +2 -2
  15. package/adapters/mobile/reload-app.mjs +4 -4
  16. package/adapters/mobile/reset-app-data.sh +1 -1
  17. package/adapters/mobile/start-console-forwarder.sh +3 -2
  18. package/adapters/mobile/start-metro.sh +17 -12
  19. package/adapters/mobile/wait-for-bridge.cjs +2 -2
  20. package/adapters/shared/harness-source-fingerprint.mjs +13 -8
  21. package/adapters/shared/open-debug.mjs +2 -2
  22. package/adapters/shared/sync-wallet-fixture.sh +8 -5
  23. package/adapters/terminal/cleanup.mjs +2 -1
  24. package/adapters/terminal/inject.mjs +2 -1
  25. package/adapters/terminal/launch.mjs +17 -13
  26. package/adapters/terminal/lib/mainnet-guard.mjs +9 -0
  27. package/adapters/terminal/lib/readiness.mjs +20 -12
  28. package/adapters/terminal/policy.mjs +35 -0
  29. package/adapters/terminal/verify.mjs +10 -3
  30. package/adapters/terminal/wallet-host.mjs +43 -225
  31. package/bin/mm-harness +9 -28
  32. package/dist/adapters/checkout-files.js +16 -0
  33. package/dist/adapters/console-capture.js +14 -1
  34. package/dist/adapters/core/actions.js +8 -4
  35. package/dist/adapters/core/overlay.js +1 -1
  36. package/dist/adapters/core/readiness.js +28 -1
  37. package/dist/adapters/core/run.js +2 -2
  38. package/dist/adapters/core/surface.js +16 -1
  39. package/dist/adapters/extension/actions.js +6 -2
  40. package/dist/adapters/extension/browser-pid.js +56 -0
  41. package/dist/adapters/extension/console-capture.js +1 -1
  42. package/dist/adapters/extension/doctor-checks.js +1 -1
  43. package/dist/adapters/extension/ensure-ready.js +5 -4
  44. package/dist/adapters/extension/extension-id.js +1 -1
  45. package/dist/adapters/extension/feature-flags.js +1 -1
  46. package/dist/adapters/extension/fixtures.js +1 -2
  47. package/dist/adapters/extension/launch.js +4 -5
  48. package/dist/adapters/extension/launched-browser.js +1 -2
  49. package/dist/adapters/extension/network-observer.js +14 -1
  50. package/dist/adapters/extension/overlay.js +1 -1
  51. package/dist/adapters/extension/product-config.js +1 -1
  52. package/dist/adapters/extension/readiness.js +1 -1
  53. package/dist/adapters/extension/reload.js +1 -1
  54. package/dist/adapters/extension/run.js +135 -14
  55. package/dist/adapters/extension/runtime-decision.js +2 -1
  56. package/dist/adapters/extension/runtime.js +9 -5
  57. package/dist/adapters/extension/surface.js +15 -2
  58. package/dist/adapters/extension/verify.js +2 -2
  59. package/dist/adapters/harness-freshness.js +2 -1
  60. package/dist/adapters/mobile/actions.js +4 -1
  61. package/dist/adapters/mobile/android-gfxinfo-performance.js +1 -3
  62. package/dist/adapters/mobile/app-identity.js +10 -0
  63. package/dist/adapters/mobile/doctor-checks.js +1 -1
  64. package/dist/adapters/mobile/fixtures.js +1 -1
  65. package/dist/adapters/mobile/launch.js +27 -28
  66. package/dist/adapters/mobile/lifecycle.js +1 -1
  67. package/dist/adapters/mobile/metro-env.js +9 -59
  68. package/dist/adapters/mobile/network-observer.js +68 -0
  69. package/dist/adapters/mobile/performance-observer.js +2 -4
  70. package/dist/adapters/mobile/prepare-target.js +1 -1
  71. package/dist/adapters/mobile/prepare.js +3 -4
  72. package/dist/adapters/mobile/provision.js +1 -1
  73. package/dist/adapters/mobile/readiness.js +1 -1
  74. package/dist/adapters/mobile/recording-target.js +15 -0
  75. package/dist/adapters/mobile/release-artifact-state.js +1 -1
  76. package/dist/adapters/mobile/reload.js +1 -1
  77. package/dist/adapters/mobile/run.js +39 -15
  78. package/dist/adapters/mobile/runtime-decision.js +2 -4
  79. package/dist/adapters/mobile/source-freshness.js +22 -126
  80. package/dist/adapters/mobile/surface.js +26 -3
  81. package/dist/adapters/mobile/ui.js +16 -5
  82. package/dist/adapters/mobile/verify.js +2 -3
  83. package/dist/adapters/performance/cdp-trace.js +1 -1
  84. package/dist/adapters/performance/device-platform.js +1 -1
  85. package/dist/adapters/perps-order-inputs.js +36 -0
  86. package/dist/adapters/proof-failure.js +11 -0
  87. package/dist/adapters/slot-ports.js +1 -1
  88. package/dist/adapters/surface.js +1 -0
  89. package/dist/adapters/terminal/actions.js +4 -1
  90. package/dist/adapters/terminal/console-capture.js +1 -1
  91. package/dist/adapters/terminal/doctor-checks.js +2 -0
  92. package/dist/adapters/terminal/launch.js +4 -1
  93. package/dist/adapters/terminal/overlay.js +8 -1
  94. package/dist/adapters/terminal/run.js +33 -5
  95. package/dist/adapters/terminal/surface.js +9 -1
  96. package/dist/adapters.js +27 -7
  97. package/dist/advisor-policy.js +1 -1
  98. package/dist/checkout-view.js +1 -2
  99. package/dist/cli.js +59 -21
  100. package/dist/command-contract.js +1 -26
  101. package/dist/commands/check.js +33 -10
  102. package/dist/commands/checklist.js +1 -1
  103. package/dist/commands/completion-candidates.js +6 -14
  104. package/dist/commands/config.js +2 -2
  105. package/dist/commands/core-readiness.js +12 -7
  106. package/dist/commands/debug.js +3 -3
  107. package/dist/commands/device-target.js +5 -5
  108. package/dist/commands/doctor.js +2 -14
  109. package/dist/commands/domain.js +2 -2
  110. package/dist/commands/ensure-ready.js +1 -7
  111. package/dist/commands/fixtures.js +5 -6
  112. package/dist/commands/help.js +3 -4
  113. package/dist/commands/launch/device.js +10 -17
  114. package/dist/commands/launch/extension.js +2 -8
  115. package/dist/commands/launch/mobile.js +1 -1
  116. package/dist/commands/logs.js +3 -3
  117. package/dist/commands/mobile-device-view.js +4 -2
  118. package/dist/commands/overlay.js +110 -0
  119. package/dist/commands/pin.js +1 -1
  120. package/dist/commands/pr-body.js +2 -2
  121. package/dist/commands/prepare.js +4 -9
  122. package/dist/commands/provision.js +3 -12
  123. package/dist/commands/recipe-advice.js +1 -2
  124. package/dist/commands/recipe-quality.js +2 -3
  125. package/dist/commands/resolve-extension.js +1 -7
  126. package/dist/commands/review.js +4 -4
  127. package/dist/commands/runtime-decision.js +1 -6
  128. package/dist/commands/runtime-health.js +1 -7
  129. package/dist/commands/runtime-launch.js +1 -13
  130. package/dist/commands/self-test.js +5 -8
  131. package/dist/commands/status-watch.js +1 -2
  132. package/dist/commands/status.js +2 -5
  133. package/dist/commands/task-init.js +2 -3
  134. package/dist/commands/tutorial.js +2 -2
  135. package/dist/commands/update.js +1 -1
  136. package/dist/completions-cache.js +1 -1
  137. package/dist/console-classifier.js +19 -0
  138. package/dist/doctor.js +2 -3
  139. package/dist/engine.js +57 -0
  140. package/dist/funding-execution-context.js +96 -1
  141. package/dist/host.js +32 -0
  142. package/dist/index.js +1 -6
  143. package/dist/live-tsx.js +49 -0
  144. package/dist/mm-harness-cli.js +4 -6
  145. package/dist/paths.js +34 -64
  146. package/dist/performance-observation.js +3 -5
  147. package/dist/progress.js +1 -1
  148. package/dist/recipe-assistance-context.js +6 -9
  149. package/dist/recipe-security.js +2 -59
  150. package/dist/review/knowledge.js +2 -2
  151. package/dist/runner.js +7 -4
  152. package/dist/runtime-context.js +1 -1
  153. package/dist/task-view.js +2 -3
  154. package/docs/CONTRIBUTING.md +5 -3
  155. package/docs/RECIPES.md +31 -3
  156. package/docs/cli-contract.md +2 -2
  157. package/docs/feature-guard.md +90 -0
  158. package/library/README.md +3 -1
  159. package/library/actions/core/perps/_controller.mjs +153 -134
  160. package/library/actions/core/perps/_rewards.mjs +186 -0
  161. package/library/actions/core/perps/close_positions.mjs +1 -1
  162. package/library/actions/core/perps/place_order.mjs +167 -45
  163. package/library/actions/core/perps/quote_fees.mjs +61 -0
  164. package/library/actions/core/wallet/list_accounts.mjs +14 -57
  165. package/library/actions/extension/feature_flags/state.mjs +1 -1
  166. package/library/actions/extension/perps/close_visible_position.mjs +5 -0
  167. package/library/actions/extension/perps/edit_margin.mjs +4 -0
  168. package/library/actions/extension/perps/mutation-receipt.mjs +95 -3
  169. package/library/actions/extension/perps/perps.mjs +320 -3
  170. package/library/actions/extension/perps/place_order.mjs +3 -1
  171. package/library/actions/extension/perps/update_position_tpsl.mjs +4 -0
  172. package/library/actions/extension/platform/cdp.mjs +73 -11
  173. package/library/actions/harness-exports.mjs +1 -6
  174. package/library/actions/mobile/app/network-control.mjs +6 -8
  175. package/library/actions/mobile/perps/perps.mjs +216 -11
  176. package/library/actions/mobile/perps/place_order.mjs +3 -1
  177. package/library/actions/mobile/perps/prepare_local_snapshot_endpoint.mjs +1 -1
  178. package/library/actions/mobile/platform/bridge.mjs +8 -8
  179. package/library/actions/mobile/platform/native-session-name.mjs +21 -1
  180. package/library/actions/mobile/platform/native-session.mjs +3 -4
  181. package/library/actions/mobile/platform/observe-ui.mjs +12 -6
  182. package/library/actions/mobile/ui/native-navigation.mjs +4 -3
  183. package/library/actions/mobile/wallet/native-ui.mjs +3 -2
  184. package/library/actions/shared/analytics/_adapter.mjs +1 -1
  185. package/library/actions/shared/console-findings.mjs +1 -1
  186. package/library/actions/shared/perps/order-policy.mjs +177 -0
  187. package/library/actions/shared/wallet/fixture-accounts.mjs +84 -0
  188. package/library/actions/terminal/app/launch.mjs +3 -2
  189. package/library/actions/terminal/platform/hud.mjs +2 -1
  190. package/library/actions/terminal/platform/page.mjs +2 -1
  191. package/library/actions/terminal/platform/runtime.mjs +2 -1
  192. package/library/actions/terminal/wallet/_signature-log.mjs +40 -161
  193. package/library/actions/terminal/wallet/assert_signatures.mjs +6 -12
  194. package/library/actions/terminal/wallet/read_signatures.mjs +10 -9
  195. package/library/manifests/core.action-manifest.json +87 -13
  196. package/library/manifests/extension.action-manifest.json +4 -0
  197. package/library/manifests/mobile.action-manifest.json +12 -0
  198. package/package.json +8 -9
  199. package/site/assets/help-recipes.json +1 -0
  200. package/adapters/extension/lib/slot-title.cjs +0 -292
  201. package/adapters/mobile/bridge-runtime/console-forwarder.cjs +0 -472
  202. package/adapters/mobile/bridge-runtime/lib/bridge-errors.cjs +0 -125
  203. package/adapters/mobile/bridge-runtime/lib/cdp-broker.cjs +0 -1055
  204. package/adapters/mobile/bridge-runtime/lib/cdp-eval.cjs +0 -110
  205. package/adapters/mobile/bridge-runtime/lib/config.cjs +0 -61
  206. package/adapters/mobile/bridge-runtime/lib/console-format.cjs +0 -53
  207. package/adapters/mobile/bridge-runtime/lib/devtools-proxy.cjs +0 -177
  208. package/adapters/mobile/bridge-runtime/lib/issue-capture.cjs +0 -446
  209. package/adapters/mobile/bridge-runtime/lib/match-bridge-target.cjs +0 -84
  210. package/adapters/mobile/bridge-runtime/lib/target-discovery.cjs +0 -403
  211. package/adapters/mobile/bridge-runtime/lib/ws-client.cjs +0 -116
  212. package/adapters/mobile/coalesce-metro-log.cjs +0 -24
  213. package/adapters/mobile/launch-metro.cjs +0 -77
  214. package/adapters/mobile/metro-config.cjs +0 -98
  215. package/adapters/mobile/metro-log-generation.cjs +0 -106
  216. package/adapters/terminal/lib/origin.mjs +0 -30
  217. package/adapters/terminal/lib/page-script.mjs +0 -246
  218. package/adapters/terminal/lib/strict-wallet.mjs +0 -138
  219. package/dist/adapters/mobile/frame-metrics.js +0 -118
  220. package/dist/adapters/mobile/video-recorder.js +0 -289
  221. package/dist/artifact-files.js +0 -92
  222. package/dist/checkout-lock.js +0 -149
  223. package/dist/cli-color.js +0 -98
  224. package/dist/command-journal.js +0 -308
  225. package/dist/commands/call.js +0 -685
  226. package/dist/commands/last.js +0 -60
  227. package/dist/commands/launch/index.js +0 -109
  228. package/dist/commands/list-executables.js +0 -247
  229. package/dist/commands/manifest.js +0 -488
  230. package/dist/commands/parse-args.js +0 -279
  231. package/dist/commands/reload.js +0 -41
  232. package/dist/commands/run-engine.js +0 -1340
  233. package/dist/commands/run-report.js +0 -241
  234. package/dist/commands/run.js +0 -751
  235. package/dist/commands/shared.js +0 -323
  236. package/dist/commands/stop.js +0 -60
  237. package/dist/devices.js +0 -100
  238. package/dist/discovery.js +0 -15
  239. package/dist/execution-provenance.js +0 -332
  240. package/dist/harness.js +0 -451
  241. package/dist/heal-bounds.js +0 -137
  242. package/dist/json-stream.js +0 -60
  243. package/dist/leaf-invoke.js +0 -19
  244. package/dist/library-provenance.js +0 -33
  245. package/dist/live-adapter-contract.js +0 -380
  246. package/dist/metamask-action-validation.js +0 -68
  247. package/dist/network-observation.js +0 -290
  248. package/dist/recording-target.js +0 -120
  249. package/dist/run-diagnostics.js +0 -364
  250. package/dist/run-recording.js +0 -481
  251. package/dist/runtime-proof.js +0 -90
  252. package/library/actions/mobile/platform/tool-paths.mjs +0 -123
@@ -1,751 +0,0 @@
1
- import fs from "node:fs";
2
- import path from "node:path";
3
- import { importRecipeHarness, walletFixturePath } from "../paths.js";
4
- import { color } from "../cli-color.js";
5
- import { recipeRunning } from "../heal-bounds.js";
6
- import { checkoutBusyOut, EXIT, usageOut, writeInteractiveProgress } from "./shared.js";
7
- import {
8
- optionFlag,
9
- optionString,
10
- resolveAdapter,
11
- runtimeOptionsFromCli,
12
- isRecord,
13
- parseRecipeParamAssignments,
14
- shellQuote,
15
- targetPath,
16
- usageError
17
- } from "./parse-args.js";
18
- import {
19
- countRecipeNodes,
20
- emitHealViolation,
21
- executeWithHealBounds,
22
- preflightRecipe,
23
- prepareHeal,
24
- persistRunEffects,
25
- runRecipe,
26
- validateRunRecipeStatic
27
- } from "./run-engine.js";
28
- import { handleDescribeRecipe, handleListExecutables } from "./list-executables.js";
29
- import { applyDeviceTargeting } from "./device-target.js";
30
- import { getAdapterSurface } from "../adapters/surface.js";
31
- import {
32
- indexProductProvenanceArtifact,
33
- writeRunReport
34
- } from "./run-report.js";
35
- import { acquireCheckoutLock } from "../checkout-lock.js";
36
- import { JsonStreamWriter } from "../json-stream.js";
37
- import { formatRunDiagnosticsForHuman, readRunDiagnosticsDocument } from "../run-diagnostics.js";
38
- import { recipeTrustFailure } from "../recipe-security.js";
39
- import { ProvenanceDriftError } from "../execution-provenance.js";
40
- import { recordCommandEvidence } from "../command-journal.js";
41
- import { actionLibraryContextArgs, resolveActionCapabilityMatrix } from "./manifest.js";
42
- import { missingActionCapabilities } from "@farmslot/recipe-cli";
43
- import {
44
- startRunNetworkObservation
45
- } from "../network-observation.js";
46
- import {
47
- startRunPerformanceObservation
48
- } from "../performance-observation.js";
49
- async function validationCapabilityRefusals(adapter, findings, librarySources) {
50
- const actionNames = findings.flatMap((finding) => {
51
- if (finding.code !== "recipe.action_not_declared_by_manifest") return [];
52
- const match = /^Recipe action ([^ ]+) is not declared by the runner action manifest[.]?$/u.exec(finding.message);
53
- return match?.[1] ? [match[1]] : [];
54
- });
55
- if (actionNames.length === 0) return [];
56
- return missingActionCapabilities(
57
- adapter,
58
- actionNames,
59
- await resolveActionCapabilityMatrix(librarySources)
60
- );
61
- }
62
- function capabilityValidationUserAction(adapter, refusals, libraryContextArgs) {
63
- const capabilities = refusals.map(
64
- (refusal) => `"${refusal.capability}" [${refusal.satisfyingAdapters.join(", ")}]`
65
- ).join("; ");
66
- return `Missing action capabilities for ${adapter}, with satisfying adapters: ${capabilities}. Inspect: mm-harness actions --matrix${libraryContextArgs} --json; then rerun from a checkout matching a satisfying adapter, splitting the recipe if no single adapter satisfies every capability`;
67
- }
68
- async function handleRun(parsed) {
69
- const stream = new JsonStreamWriter("run", optionFlag(parsed.options, "jsonStream"));
70
- const restoreStdout = stream.isolateStdout();
71
- const target = targetPath(parsed.options);
72
- try {
73
- const exitCode = await handleRunInner(parsed, stream);
74
- stream.complete(exitCode === EXIT.ok ? "pass" : "fail", exitCode);
75
- return exitCode;
76
- } catch (error) {
77
- if (error instanceof ProvenanceDriftError) {
78
- const failure = {
79
- code: error.code,
80
- message: error.message,
81
- userAction: error.userAction,
82
- provenancePath: error.provenancePath,
83
- drift: error.drift
84
- };
85
- if (stream.enabled) {
86
- stream.error(failure);
87
- stream.complete("fail", error.exitCode);
88
- } else if (optionFlag(parsed.options, "json")) {
89
- console.log(JSON.stringify({
90
- schemaVersion: 1,
91
- command: "run",
92
- status: "fail",
93
- error: failure,
94
- exitCode: error.exitCode
95
- }, null, 2));
96
- } else {
97
- console.error(`\u2717 mm-harness run: ${error.message}`);
98
- console.error(` Next: ${error.userAction}`);
99
- }
100
- return error.exitCode;
101
- }
102
- const trustFailure = recipeTrustFailure(error);
103
- if (trustFailure) {
104
- if (stream.enabled) {
105
- stream.error(trustFailure);
106
- stream.complete("fail", EXIT.validation);
107
- } else if (optionFlag(parsed.options, "json")) {
108
- console.log(JSON.stringify({
109
- schemaVersion: 1,
110
- command: "run",
111
- status: "fail",
112
- error: trustFailure,
113
- exitCode: EXIT.validation
114
- }, null, 2));
115
- } else {
116
- console.error(`\u2717 mm-harness run: ${trustFailure.message}`);
117
- const blocked = trustFailure.details?.blocked;
118
- if (blocked?.length) {
119
- console.error(" Restricted plan nodes:");
120
- for (const node of blocked.slice(0, 10)) {
121
- const implementation = node.implementation?.digest ? ` implementation=${node.implementation.kind ?? "custom"}@${node.implementation.digest}` : "";
122
- console.error(
123
- ` - ${node.nodeId}: ${node.action} [${node.capabilities.join(", ")}] source=${node.source}${implementation}`
124
- );
125
- }
126
- if (blocked.length > 10) console.error(` - \u2026 ${blocked.length - 10} more (use --json)`);
127
- }
128
- console.error(` Next: ${trustFailure.userAction}`);
129
- }
130
- return EXIT.validation;
131
- }
132
- const exitCode = error !== null && typeof error === "object" && "exitCode" in error && typeof error.exitCode === "number" ? error.exitCode : EXIT.runtime;
133
- stream.error({
134
- code: exitCode === EXIT.usage ? "CLI_USAGE_ERROR" : "RUN_FAILED",
135
- message: error instanceof Error ? error.message : String(error),
136
- userAction: `mm-harness doctor --target ${shellQuote(target)} --json`
137
- });
138
- stream.complete("fail", exitCode);
139
- throw error;
140
- } finally {
141
- restoreStdout();
142
- }
143
- }
144
- async function handleRunInner({ positional, options }, stream) {
145
- if (!optionFlag(options, "list") && ["domain", "source", "sort"].some((name) => optionString(options, name) !== void 0)) {
146
- throw usageError("--domain, --source, and --sort require run --list.");
147
- }
148
- if (optionFlag(options, "describe") && (optionFlag(options, "list") || optionFlag(options, "plan"))) {
149
- const message = "--describe cannot be combined with --list or --plan.";
150
- const userAction = "choose one: mm-harness run --list, mm-harness run <recipe> --describe, or mm-harness run <recipe> --plan";
151
- if (stream.enabled) {
152
- stream.error({ code: "CLI_USAGE_ERROR", message, userAction });
153
- return EXIT.usage;
154
- }
155
- return usageOut(optionFlag(options, "json"), "run", message, userAction);
156
- }
157
- if (optionFlag(options, "list")) {
158
- if (stream.enabled) {
159
- const message = "--json-stream is for recipe execution; use run --list --json for discovery.";
160
- stream.error({ code: "JSON_STREAM_UNSUPPORTED_MODE", message, userAction: "mm-harness run --list --json" });
161
- return EXIT.usage;
162
- }
163
- return handleListExecutables("run", options);
164
- }
165
- const targetRecipe = positional[0];
166
- if (!targetRecipe) throw usageError("run requires <recipe.json>.");
167
- const paramAssignments = positional.slice(1);
168
- if (optionFlag(options, "describe")) {
169
- if (paramAssignments.length > 0) {
170
- throw usageError("run --describe does not accept parameter values; inspect the declaration first.");
171
- }
172
- if (stream.enabled) {
173
- const message = "--json-stream is for recipe execution; use --describe --json for discovery.";
174
- stream.error({
175
- code: "JSON_STREAM_UNSUPPORTED_MODE",
176
- message,
177
- userAction: `mm-harness run ${shellQuote(targetRecipe)} --describe --json`
178
- });
179
- return EXIT.usage;
180
- }
181
- return handleDescribeRecipe(targetRecipe, options);
182
- }
183
- const params = parseRecipeParamAssignments(paramAssignments);
184
- if (optionFlag(options, "plan")) return handleRunPlan(targetRecipe, params, options, stream);
185
- const { adapter, target } = resolveAdapter(options);
186
- const json = optionFlag(options, "json");
187
- const jsonOutput = json && !stream.enabled;
188
- const machine = json || stream.enabled;
189
- stream.phase("resolve", { adapter, target, recipe: targetRecipe });
190
- if (!fs.existsSync(target)) {
191
- const message = `target does not exist: ${target}`;
192
- const userAction = "pass --target <metamask-checkout> pointing to an existing checkout";
193
- if (stream.enabled) {
194
- stream.error({ code: "TARGET_NOT_FOUND", message, userAction });
195
- return EXIT.usage;
196
- }
197
- return usageOut(jsonOutput, "run", message, userAction);
198
- }
199
- writeInteractiveProgress(machine, `\u2192 recipe run \u2014 validating ${targetRecipe} \xB7 ${adapter}`);
200
- getAdapterSurface(adapter).resolveSlotPorts(target);
201
- const dtResult = applyDeviceTargeting("run", adapter, options, { gate: true, rerun: "" });
202
- if ("code" in dtResult) {
203
- return emitRunUsageError(jsonOutput, stream, adapter, targetRecipe, dtResult.code, dtResult.message, dtResult.userAction);
204
- }
205
- if (recipeRunning(target)) return emitRunRecipeRunning(jsonOutput, stream, target);
206
- stream.phase("validate");
207
- const validated = await validateRunRecipeStatic(targetRecipe, adapter, options, params);
208
- if (validated.usageError) {
209
- const userAction = runUsageRecovery(
210
- validated.usageError.code,
211
- validated.usageError.message,
212
- adapter,
213
- validated.recipeFile
214
- );
215
- return emitRunUsageError(
216
- jsonOutput,
217
- stream,
218
- adapter,
219
- validated.recipeFile,
220
- validated.usageError.code,
221
- validated.usageError.message,
222
- userAction
223
- );
224
- }
225
- if (validated.errorCount > 0) {
226
- const capabilityRefusals = optionString(options, "actionManifest") ? [] : await validationCapabilityRefusals(
227
- adapter,
228
- validated.findings,
229
- validated.librarySources
230
- );
231
- return emitRunValidationError(
232
- jsonOutput,
233
- stream,
234
- adapter,
235
- validated.recipeFile,
236
- validated.findings,
237
- validated.errorCount,
238
- capabilityRefusals,
239
- actionLibraryContextArgs(validated.librarySources)
240
- );
241
- }
242
- const depsBlock = await getAdapterSurface(adapter).run?.dependencyBlock?.(target, {
243
- recipe: validated.recipe,
244
- librarySources: validated.librarySources
245
- }) ?? null;
246
- if (depsBlock) {
247
- return emitRunUsageError(jsonOutput, stream, adapter, validated.recipeFile, depsBlock.code, depsBlock.message, depsBlock.userAction);
248
- }
249
- let artifactsDir;
250
- try {
251
- artifactsDir = resolveRunArtifactsDir(target, optionString(options, "artifactsDir"));
252
- } catch (error) {
253
- const message = error instanceof Error ? error.message : String(error);
254
- return emitRunUsageError(
255
- jsonOutput,
256
- stream,
257
- adapter,
258
- validated.recipeFile,
259
- "ARTIFACT_DIR_INVALID",
260
- message,
261
- "set RECIPE_TASK_DIR/FARMSLOT_TASK_DIR inside the checkout or pass --artifacts-dir <path>"
262
- );
263
- }
264
- recordCommandEvidence(artifactsDir);
265
- const librarySources = validated.librarySources;
266
- let networkObservation;
267
- let performanceObservation;
268
- const runtimeOptions = {
269
- ...runtimeOptionsFromCli(options),
270
- params,
271
- ...librarySources ? { librarySources } : {},
272
- stdoutIsMachineContract: machine,
273
- onActionEvent: ({ nodeId, action, status }) => {
274
- stream.node(nodeId, action, status);
275
- networkObservation?.onActionEvent({ nodeId, action, status });
276
- performanceObservation?.onActionEvent({ nodeId, action, status });
277
- }
278
- };
279
- stream.phase("authorize");
280
- let preflightedExecution = await preflightRecipe(
281
- adapter,
282
- validated.recipeFile,
283
- artifactsDir,
284
- target,
285
- optionString(options, "actionManifest"),
286
- runtimeOptions
287
- );
288
- const lock = acquireCheckoutLock(target, "run");
289
- if ("message" in lock) {
290
- const userAction = `wait for the current owner, or inspect ${lock.path} if its process has exited`;
291
- stream.error({ code: "SANDBOX_BUSY", message: lock.message, userAction });
292
- return checkoutBusyOut(jsonOutput, "run", lock.message, lock.path);
293
- }
294
- try {
295
- const prepared = await prepareHeal(adapter, target, options, machine, {
296
- onPhase: (phase, fields) => stream.phase(phase, fields)
297
- });
298
- if (typeof prepared === "number") {
299
- stream.error({
300
- code: "RUN_PREPARE_FAILED",
301
- message: "runtime preparation failed; inspect stderr for the exact probe",
302
- userAction: `mm-harness doctor --fix --adapter ${adapter} --target ${shellQuote(target)} --json`
303
- });
304
- return prepared;
305
- }
306
- const { state } = prepared;
307
- preflightedExecution = await preflightRecipe(
308
- adapter,
309
- validated.recipeFile,
310
- artifactsDir,
311
- target,
312
- optionString(options, "actionManifest"),
313
- runtimeOptions
314
- );
315
- networkObservation = await startRunNetworkObservation(
316
- adapter,
317
- target,
318
- artifactsDir,
319
- process.env,
320
- {
321
- cdpPort: runtimeOptions.cdpPort,
322
- watcherPort: runtimeOptions.watcherPort
323
- }
324
- );
325
- performanceObservation = startRunPerformanceObservation(
326
- adapter,
327
- target,
328
- artifactsDir,
329
- process.env,
330
- {
331
- cdpPort: runtimeOptions.cdpPort,
332
- watcherPort: runtimeOptions.watcherPort
333
- }
334
- );
335
- stream.phase("execute");
336
- let executionResult;
337
- try {
338
- executionResult = await executeWithHealBounds(
339
- // validated.recipeFile, not the raw arg: the arg may be a library recipe NAME
340
- // that only the resolver knows how to turn into a file.
341
- () => {
342
- const execution = preflightedExecution;
343
- preflightedExecution = void 0;
344
- return runRecipe(
345
- adapter,
346
- validated.recipeFile,
347
- artifactsDir,
348
- target,
349
- optionString(options, "actionManifest"),
350
- runtimeOptions,
351
- execution,
352
- state
353
- );
354
- },
355
- target,
356
- state
357
- );
358
- } catch (error) {
359
- await networkObservation?.finalize().catch(() => void 0);
360
- networkObservation = void 0;
361
- await performanceObservation?.finalize().catch(() => void 0);
362
- performanceObservation = void 0;
363
- throw error;
364
- }
365
- const { result, violation } = executionResult;
366
- await networkObservation?.finalize(result.artifactManifestPath);
367
- networkObservation = void 0;
368
- await performanceObservation?.finalize(result.artifactManifestPath);
369
- performanceObservation = void 0;
370
- for (const mutation of state.mutations) stream.mutation(mutation);
371
- for (const recovery of state.recovered) stream.recovery(recovery);
372
- persistRunEffects(result.summaryPath, result.artifactManifestPath, state);
373
- indexProductProvenanceArtifact(
374
- result.artifactManifestPath,
375
- target,
376
- adapter,
377
- result.browser ?? null
378
- );
379
- if (violation !== null) {
380
- const userAction = violation.userAction ?? `inspect ${shellQuote(result.summaryPath)} and ${shellQuote(result.tracePath)}; fix the application or recipe failure before retrying`;
381
- stream.error({
382
- code: violation.code,
383
- message: violation.message,
384
- userAction,
385
- originalError: violation.originalError ?? null
386
- });
387
- return emitHealViolation(jsonOutput, "run", result, violation, state, adapter);
388
- }
389
- const report = writeRunReport(result);
390
- const exitCode = result.status === "pass" ? EXIT.ok : EXIT.runtime;
391
- const failureUserAction = `mm-harness last --target ${shellQuote(target)} --json`;
392
- if (stream.enabled) {
393
- if (result.status === "fail") {
394
- stream.error({
395
- code: "RECIPE_EXECUTION_FAILED",
396
- message: "recipe execution failed; inspect the persisted result and evidence paths",
397
- userAction: failureUserAction
398
- });
399
- }
400
- stream.complete(result.status, exitCode, {
401
- adapter,
402
- reportPath: report.path,
403
- summaryPath: result.summaryPath,
404
- tracePath: result.tracePath,
405
- artifactManifestPath: result.artifactManifestPath,
406
- recovered: state.recovered,
407
- mutations: state.mutations
408
- });
409
- } else if (json) {
410
- console.log(
411
- JSON.stringify(
412
- {
413
- schemaVersion: 1,
414
- command: "run",
415
- adapter,
416
- status: result.status,
417
- exitCode,
418
- recovered: state.recovered,
419
- mutations: state.mutations,
420
- reportPath: report.path,
421
- result,
422
- ...result.status === "fail" ? { error: { code: "RECIPE_EXECUTION_FAILED", message: "recipe execution failed; inspect the persisted result and evidence paths", userAction: failureUserAction } } : {}
423
- },
424
- null,
425
- 2
426
- )
427
- );
428
- } else {
429
- const out = (style, text) => color(style, text, { stream: process.stdout });
430
- console.log(`${out(result.status === "pass" ? "ok" : "err", result.status.toUpperCase())} ${out("bold", "recipe run")} ${out("dim", `[${adapter}]`)}`);
431
- if (report.preview.length > 0) {
432
- console.log(out("label", "summary:"));
433
- for (const line of report.preview) console.log(` ${formatPreviewLine(line, out)}`);
434
- }
435
- const artifacts = runArtifactInventory(result.artifactManifestPath);
436
- console.log(out("label", `artifacts (${artifacts.length}):`));
437
- for (const artifact of artifacts) {
438
- console.log(` ${out("dim", `${artifact.label}:`)} ${out("path", artifact.absolutePath)}`);
439
- }
440
- if (result.status === "fail") {
441
- console.error(` Next: ${failureUserAction}`);
442
- }
443
- console.log(out("label", "diagnostics:"));
444
- const diagnostics = readRunDiagnosticsDocument(result.diagnosticsPath);
445
- for (const line of formatRunDiagnosticsForHuman(diagnostics, adapter)) {
446
- console.log(` ${formatDiagnosticLine(line, out)}`);
447
- }
448
- }
449
- return exitCode;
450
- } finally {
451
- lock.release();
452
- }
453
- }
454
- function formatDiagnosticLine(line, out) {
455
- const match = /^(CLEAN|REVIEW|UNAVAILABLE|N\/A|WARNING|ERROR|EXCEPTION)(.*)$/u.exec(line);
456
- if (!match) return line;
457
- const status = match[1];
458
- const style = status === "CLEAN" ? "ok" : status === "ERROR" || status === "EXCEPTION" ? "err" : "warn";
459
- return `${out(style, status)}${match[2]}`;
460
- }
461
- function resolveRunArtifactsDir(target, explicit) {
462
- if (explicit !== void 0) return path.resolve(explicit);
463
- const taskDir = process.env.RECIPE_TASK_DIR || process.env.FARMSLOT_TASK_DIR;
464
- if (taskDir) {
465
- const resolvedTask = path.resolve(target, taskDir);
466
- const relative = path.relative(path.resolve(target), resolvedTask);
467
- if (!relative.startsWith(`..${path.sep}`) && relative !== "..") {
468
- return path.join(resolvedTask, "artifacts");
469
- }
470
- throw new Error(`task directory must be inside the target checkout: ${taskDir}`);
471
- }
472
- const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[-:.TZ]/gu, "");
473
- return path.join(target, "temp", "recipe", "runs", `${stamp}-${process.pid}`);
474
- }
475
- function runArtifactInventory(manifestPathValue) {
476
- const manifestPath = path.resolve(String(manifestPathValue));
477
- const root = path.dirname(manifestPath);
478
- let manifest;
479
- try {
480
- manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
481
- } catch {
482
- return [{ absolutePath: manifestPath, basename: path.basename(manifestPath), label: "Artifact manifest" }];
483
- }
484
- const entries = isRecord(manifest) && Array.isArray(manifest.artifacts) ? manifest.artifacts : [];
485
- const artifacts = [];
486
- for (const entry of entries) {
487
- if (!isRecord(entry) || typeof entry.path !== "string" || entry.path.length === 0) continue;
488
- const absolutePath = path.resolve(root, entry.path);
489
- const basename = path.basename(absolutePath);
490
- const label = typeof entry.label === "string" && entry.label.length > 0 ? entry.label : basename;
491
- artifacts.push({ absolutePath, basename, label });
492
- }
493
- artifacts.push({ absolutePath: manifestPath, basename: path.basename(manifestPath), label: "Artifact manifest" });
494
- const priority = /* @__PURE__ */ new Map([
495
- ["diagnostics.json", 0],
496
- ["report.md", 1],
497
- ["summary.json", 2],
498
- ["trace.json", 3],
499
- ["recipe.json", 4],
500
- ["artifact-manifest.json", 100]
501
- ]);
502
- return artifacts.filter((artifact, index, all) => all.findIndex((candidate) => candidate.absolutePath === artifact.absolutePath) === index).sort((left, right) => (priority.get(left.basename) ?? 50) - (priority.get(right.basename) ?? 50));
503
- }
504
- function formatPreviewLine(line, out) {
505
- const match = /^(PASS|FAIL)\s+(.+)$/u.exec(line);
506
- if (!match) return line;
507
- const status = match[1];
508
- const rest = match[2];
509
- return `${out(status === "PASS" ? "ok" : "err", status)} ${rest}`;
510
- }
511
- async function handleRunPlan(recipeArg, params, options, stream) {
512
- const json = optionFlag(options, "json");
513
- const jsonOutput = json && !stream.enabled;
514
- const { adapter, target } = resolveAdapter(options);
515
- stream.phase("resolve", { adapter, target, recipe: recipeArg });
516
- stream.phase("validate");
517
- const validated = await validateRunRecipeStatic(recipeArg, adapter, options, params);
518
- if (validated.usageError) {
519
- const userAction = runUsageRecovery(
520
- validated.usageError.code,
521
- validated.usageError.message,
522
- adapter,
523
- validated.recipeFile
524
- );
525
- return emitPlanUsageError(
526
- jsonOutput,
527
- stream,
528
- adapter,
529
- validated.recipeFile,
530
- validated.usageError.code,
531
- validated.usageError.message,
532
- userAction
533
- );
534
- }
535
- const {
536
- recipe,
537
- recipeFile,
538
- findings,
539
- errorCount,
540
- manifestOk,
541
- schemaValid,
542
- effectiveParams,
543
- librarySources
544
- } = validated;
545
- const status = errorCount === 0 ? "pass" : "fail";
546
- const capabilityRefusals = status === "fail" && !optionString(options, "actionManifest") ? await validationCapabilityRefusals(adapter, findings, librarySources) : [];
547
- const failureUserAction = capabilityRefusals.length > 0 ? capabilityValidationUserAction(
548
- adapter,
549
- capabilityRefusals,
550
- actionLibraryContextArgs(librarySources)
551
- ) : runPlanProbe(adapter, recipeFile);
552
- const nodeCount = countRecipeNodes(recipe);
553
- const artifactsDir = optionString(options, "artifactsDir");
554
- const plan = [
555
- { step: "resolve.recipe", confidence: "static", status: "ok", detail: recipeFile },
556
- { step: "resolve.adapter", confidence: "static", status: "ok", detail: adapter },
557
- {
558
- step: "resolve.artifactsDir",
559
- confidence: "static",
560
- status: "ok",
561
- detail: artifactsDir ? path.resolve(artifactsDir) : "(resolved to the slot artifacts dir at run time)"
562
- },
563
- {
564
- step: "validate.manifest",
565
- confidence: "static",
566
- status: manifestOk ? "ok" : "error",
567
- detail: manifestOk ? "action manifest is well-formed" : "action manifest failed validation"
568
- },
569
- {
570
- step: "validate.schema",
571
- confidence: "static",
572
- status: manifestOk && schemaValid ? "ok" : "error",
573
- detail: "recipe document schema + action existence/platform vs the adapter manifest"
574
- },
575
- ...optionFlag(options, "proof") ? [{
576
- step: "validate.proofBindings",
577
- confidence: "static",
578
- status: findings.some((finding) => finding.code.startsWith("proof.")) ? "error" : errorCount ? "planned" : "ok",
579
- detail: "checks possible behavioral assertion bindings only; runtime order, branches, results and proof artifact indexing remain unverified"
580
- }] : [],
581
- {
582
- step: "fixture.file",
583
- confidence: "static",
584
- status: "ok",
585
- detail: fs.existsSync(walletFixturePath(target)) ? `present: ${walletFixturePath(target)}` : `absent: ${walletFixturePath(target)} (fixtures set to seed)`
586
- },
587
- {
588
- step: "overlay.ensure",
589
- confidence: "conditional",
590
- status: "planned",
591
- detail: "would auto-ensure the runtime overlay if missing (install phase)"
592
- },
593
- ...getAdapterSurface(adapter).headless ? [] : [
594
- {
595
- step: "launch.app",
596
- confidence: "conditional",
597
- status: "planned",
598
- detail: "would launch/attach the app + heal transport (Metro/Chrome/CDP) before executing"
599
- }
600
- ],
601
- {
602
- step: "execute.nodes",
603
- confidence: "conditional",
604
- status: "planned",
605
- detail: nodeCount === void 0 ? "would execute the recipe nodes" : `would execute ${nodeCount} recipe node(s)`
606
- }
607
- ];
608
- const { redactRecipeParams } = await importRecipeHarness();
609
- const payload = {
610
- schemaVersion: 1,
611
- command: "run",
612
- mode: "plan",
613
- status,
614
- adapter,
615
- recipe: recipeFile,
616
- params: redactRecipeParams(effectiveParams, isRecord(recipe) ? recipe.paramsSchema : void 0).params,
617
- findings,
618
- plan
619
- };
620
- if (status === "fail") {
621
- payload.error = {
622
- code: "RECIPE_VALIDATION_FAILED",
623
- message: `recipe validation found ${errorCount} error(s)`,
624
- ...capabilityRefusals.length > 0 ? { missingCapabilities: capabilityRefusals } : {},
625
- userAction: failureUserAction
626
- };
627
- }
628
- if (stream.enabled) {
629
- if (status === "fail") stream.error(payload.error);
630
- stream.complete(status, status === "pass" ? EXIT.ok : EXIT.validation, { mode: "plan", adapter, recipe: recipeFile, findings, plan });
631
- } else if (json) {
632
- console.log(JSON.stringify(payload, null, 2));
633
- } else {
634
- console.log(`plan ${status} \u2014 ${adapter} \u2014 ${recipeFile}`);
635
- for (const item of plan) {
636
- const mark = item.status === "error" ? "\u2717" : item.status === "ok" ? "\u2713" : "\xB7";
637
- console.log(` ${mark} [${item.confidence}] ${item.step}: ${item.detail}`);
638
- }
639
- if (findings.length) {
640
- console.log("findings:");
641
- for (const finding of findings) {
642
- console.log(` ${finding.severity === "error" ? "\u2717" : "\u26A0"} ${finding.code} ${finding.path} \u2014 ${finding.message}`);
643
- }
644
- }
645
- if (status === "fail") console.error(` Next: ${failureUserAction}`);
646
- }
647
- return status === "pass" ? EXIT.ok : EXIT.validation;
648
- }
649
- function emitPlanUsageError(json, stream, adapter, recipeFile, code, message, userAction) {
650
- stream.error({ code, message, userAction, mode: "plan", adapter, recipe: recipeFile });
651
- if (json) {
652
- console.log(
653
- JSON.stringify(
654
- { schemaVersion: 1, command: "run", mode: "plan", status: "fail", adapter, recipe: recipeFile, error: { code, message, userAction } },
655
- null,
656
- 2
657
- )
658
- );
659
- } else {
660
- console.error(`\u2717 run --plan: ${message}
661
- Next: ${userAction}`);
662
- }
663
- return EXIT.usage;
664
- }
665
- function emitRunRecipeRunning(json, stream, target, detail) {
666
- const message = detail ?? "a recipe is currently running \u2014 refusing to start while another recipe executes.";
667
- const userAction = `inspect the checkout state with: mm-harness status --target ${shellQuote(target)} --json; retry after the active recipe finishes`;
668
- stream.error({ code: "RECIPE_RUNNING", message, userAction, recoverable: false });
669
- if (json) {
670
- console.log(JSON.stringify({ schemaVersion: 1, status: "fail", recoverable: false, error: { code: "RECIPE_RUNNING", message, userAction } }, null, 2));
671
- } else {
672
- console.error(`\u2717 mm-harness: ${message}
673
- Next: ${userAction}`);
674
- }
675
- return EXIT.bounded;
676
- }
677
- function emitRunUsageError(json, stream, adapter, recipeFile, code, message, userAction) {
678
- stream.error({ code, message, userAction });
679
- if (json) {
680
- console.log(
681
- JSON.stringify(
682
- { schemaVersion: 1, command: "run", adapter, status: "fail", exitCode: EXIT.usage, recipe: recipeFile, error: { code, message, userAction } },
683
- null,
684
- 2
685
- )
686
- );
687
- } else {
688
- console.error(`\u2717 run: ${message}`);
689
- console.error(` Next: ${userAction}`);
690
- }
691
- return EXIT.usage;
692
- }
693
- function emitRunValidationError(json, stream, adapter, recipeFile, findings, errorCount, capabilityRefusals, libraryContextArgs) {
694
- const message = `recipe validation found ${errorCount} error(s)`;
695
- const userAction = capabilityRefusals.length > 0 ? capabilityValidationUserAction(adapter, capabilityRefusals, libraryContextArgs) : runPlanProbe(adapter, recipeFile);
696
- const capabilityDetails = capabilityRefusals.length > 0 ? { missingCapabilities: capabilityRefusals } : {};
697
- stream.error({
698
- code: "RECIPE_VALIDATION_FAILED",
699
- message,
700
- userAction,
701
- findings,
702
- ...capabilityDetails
703
- });
704
- if (json) {
705
- console.log(
706
- JSON.stringify(
707
- {
708
- schemaVersion: 1,
709
- command: "run",
710
- adapter,
711
- status: "fail",
712
- exitCode: EXIT.validation,
713
- recovered: [],
714
- mutations: [],
715
- recipe: recipeFile,
716
- findings,
717
- error: {
718
- code: "RECIPE_VALIDATION_FAILED",
719
- message,
720
- userAction,
721
- ...capabilityDetails
722
- }
723
- },
724
- null,
725
- 2
726
- )
727
- );
728
- } else {
729
- console.error(`\u2717 run: ${message}`);
730
- for (const finding of findings) {
731
- if (finding.severity === "error") console.error(` ${finding.code} ${finding.path} \u2014 ${finding.message}`);
732
- }
733
- console.error(` Next: ${userAction}`);
734
- }
735
- return EXIT.validation;
736
- }
737
- function runPlanProbe(adapter, recipeFile) {
738
- return `mm-harness run --plan ${shellQuote(recipeFile)} --adapter ${adapter} --json`;
739
- }
740
- function runUsageRecovery(code, message, adapter, recipeFile) {
741
- const actionCommand = /This is an action, not a recipe\. Use: (mm-harness call .+)\.$/u.exec(message)?.[1];
742
- if (actionCommand) return actionCommand;
743
- if (code === "RECIPE_NOT_FOUND") return `mm-harness run --list --adapter ${adapter} --json`;
744
- if (code === "RECIPE_UNPARSEABLE") {
745
- return `fix the JSON syntax in ${shellQuote(recipeFile)}, then retry: ${runPlanProbe(adapter, recipeFile)}`;
746
- }
747
- return runPlanProbe(adapter, recipeFile);
748
- }
749
- export {
750
- handleRun
751
- };