@deeeed/metamask-harness 0.77.0 → 0.79.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.
- package/CHANGELOG.md +16 -0
- package/README.md +8 -5
- package/adapters/extension/build-handoff.cjs +2 -2
- package/adapters/extension/ensure-browser.sh +10 -12
- package/adapters/extension/inject.mjs +10 -7
- package/adapters/extension/launch-browser.cjs +7 -10
- package/adapters/extension/live.sh +1 -2
- package/adapters/extension/reattach.sh +3 -3
- package/adapters/extension/sidepanel-toggle.sh +4 -4
- package/adapters/extension/wallet-fixture-state.cjs +2 -2
- package/adapters/manifest.json +0 -24
- package/adapters/shared/ensure-runner-deps.sh +2 -2
- package/adapters/shared/harness-source-fingerprint.mjs +16 -2
- package/adapters/shared/runner-deps-ready.mjs +1 -1
- package/adapters/terminal/launch.mjs +2 -2
- package/adapters/terminal/lib/cdp-client.mjs +1 -1
- package/adapters/terminal/wallet-host.mjs +1 -1
- package/bin/mm-harness +2 -2
- package/dist/adapters/core/run.js +1 -1
- package/dist/adapters/core/surface.js +6 -4
- package/dist/adapters/extension/doctor-checks.js +2 -2
- package/dist/adapters/extension/launched-browser.js +1 -1
- package/dist/adapters/extension/runtime-decision.js +1 -1
- package/dist/adapters/extension/runtime.js +4 -4
- package/dist/adapters/extension/surface.js +5 -3
- package/dist/adapters/extension/validation-process-ownership.js +1 -1
- package/dist/adapters/mobile/prepare.js +1 -1
- package/dist/adapters/mobile/run.js +1 -1
- package/dist/adapters/mobile/runtime-decision.js +3 -3
- package/dist/adapters/mobile/surface.js +5 -3
- package/dist/adapters/mobile/video-recorder.js +1 -1
- package/dist/adapters/registry.js +2 -14
- package/dist/adapters/terminal/surface.js +5 -3
- package/dist/checkout-lock.js +1 -1
- package/dist/command-journal.js +1 -1
- package/dist/commands/launch/extension.js +2 -2
- package/dist/commands/prepare.js +2 -2
- package/dist/commands/shared.js +1 -1
- package/dist/discovery.js +1 -1
- package/dist/doctor.js +1 -1
- package/dist/harness.js +3 -3
- package/dist/paths.js +15 -15
- package/dist/recipe-assistance-context.js +1 -1
- package/dist/review/knowledge.js +1 -1
- package/dist/run-recording.js +1 -1
- package/dist/task-view.js +1 -1
- package/docs/CONTRIBUTING.md +2 -2
- package/docs/cli-contract.md +6 -17
- package/library/actions/extension/platform/cdp.mjs +5 -7
- package/library/actions/terminal/platform/page.mjs +2 -2
- package/package.json +6 -4
- package/adapters/extension/lib/chrome-args.cjs +0 -365
- package/adapters/extension/lib/extension-id.cjs +0 -36
- package/adapters/extension/lib/macos-focus.cjs +0 -108
- package/adapters/extension/lib/playwright-cdp.cjs +0 -50
- package/adapters/extension/lib/validation-launch-supervisor.cjs +0 -292
- package/adapters/extension/lib/validation-process-ownership.cjs +0 -125
- package/adapters/shared/browser-cdp.cjs +0 -542
- package/adapters/shared/browser-resolver.cjs +0 -1007
|
@@ -80,7 +80,7 @@ const mobileRecording = {
|
|
|
80
80
|
if (!androidSerial && !iosSimulator) return void 0;
|
|
81
81
|
const { createAndroidVideoRecorder, createIosSimulatorVideoRecorder } = await import("./video-recorder.js");
|
|
82
82
|
if (androidSerial) {
|
|
83
|
-
const { createAndroidMirrorVideoRecorder } = await import("@farmslot/recipe-
|
|
83
|
+
const { createAndroidMirrorVideoRecorder } = await import("@farmslot/recipe-runner");
|
|
84
84
|
return createAndroidMirrorVideoRecorder({ serial: androidSerial, fallback: createAndroidVideoRecorder(androidSerial) });
|
|
85
85
|
}
|
|
86
86
|
return createIosSimulatorVideoRecorder(iosSimulator);
|
|
@@ -5,12 +5,12 @@ import path from "node:path";
|
|
|
5
5
|
import {
|
|
6
6
|
depsCheck,
|
|
7
7
|
recordDepsBaseline
|
|
8
|
-
} from "@farmslot/recipe-
|
|
8
|
+
} from "@farmslot/recipe-runner/runtime/deps-readiness";
|
|
9
9
|
import {
|
|
10
10
|
analyzeBundleLog,
|
|
11
11
|
supersededErrorCapture
|
|
12
|
-
} from "@farmslot/recipe-
|
|
13
|
-
import { probeMetroPackager } from "@farmslot/recipe-
|
|
12
|
+
} from "@farmslot/recipe-runner/runtime/log-analysis";
|
|
13
|
+
import { probeMetroPackager } from "@farmslot/recipe-runner/runtime/metro-probe";
|
|
14
14
|
import { color } from "../../cli-color.js";
|
|
15
15
|
import { shellQuote } from "../../commands/parse-args.js";
|
|
16
16
|
import { recipeRuntimePath } from "../../paths.js";
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { defineAdapter } from "@farmslot/adapter-sdk";
|
|
3
4
|
import { recipeRuntimePath, runnerDir } from "../../paths.js";
|
|
4
5
|
import { shellQuote } from "../../commands/parse-args.js";
|
|
5
6
|
import { resolveMobileSlotPorts } from "../slot-ports.js";
|
|
@@ -17,8 +18,9 @@ function mobileRuntimeNextAction(reasonCode, target, platform) {
|
|
|
17
18
|
const resolved = shellQuote(path.resolve(target));
|
|
18
19
|
return reasonCode === "deps-missing" || reasonCode === "deps-partial" || reasonCode === "deps-stale" ? `cd ${resolved} && yarn setup` : `mm-harness launch ${platform} --adapter mobile --target ${resolved}`;
|
|
19
20
|
}
|
|
20
|
-
const mobileSurface = {
|
|
21
|
-
|
|
21
|
+
const mobileSurface = defineAdapter({
|
|
22
|
+
id: "mobile",
|
|
23
|
+
sdkVersion: 1,
|
|
22
24
|
headless: false,
|
|
23
25
|
resolveSlotPorts(target) {
|
|
24
26
|
resolveMobileSlotPorts(target);
|
|
@@ -104,7 +106,7 @@ const mobileSurface = {
|
|
|
104
106
|
diagnostics: mobileDiagnostics,
|
|
105
107
|
observation: mobileObservation,
|
|
106
108
|
sourceFingerprint: (root) => mobileSourceFingerprint(root)
|
|
107
|
-
};
|
|
109
|
+
});
|
|
108
110
|
adapterRegistry.register(mobileSurface);
|
|
109
111
|
export {
|
|
110
112
|
mobileRuntimeNextAction,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { optionalVideoTiming } from "@farmslot/recipe-
|
|
4
|
+
import { optionalVideoTiming } from "@farmslot/recipe-runner";
|
|
5
5
|
import {
|
|
6
6
|
ANDROID_RECORDING_TARGET_PREFIX,
|
|
7
7
|
IOS_SIMULATOR_RECORDING_TARGET_PREFIX
|
|
@@ -1,17 +1,5 @@
|
|
|
1
|
-
|
|
2
|
-
const adapterRegistry =
|
|
3
|
-
register(surface) {
|
|
4
|
-
if (surfaces.has(surface.adapter)) throw new Error(`adapter '${surface.adapter}' is already registered`);
|
|
5
|
-
surfaces.set(surface.adapter, surface);
|
|
6
|
-
},
|
|
7
|
-
get(id) {
|
|
8
|
-
const surface = surfaces.get(id);
|
|
9
|
-
if (!surface) throw new Error(`unknown adapter '${id}' (registered: ${adapterRegistry.list().join(", ")})`);
|
|
10
|
-
return surface;
|
|
11
|
-
},
|
|
12
|
-
has: (id) => surfaces.has(id),
|
|
13
|
-
list: () => [...surfaces.keys()]
|
|
14
|
-
};
|
|
1
|
+
import { createAdapterRegistry } from "@farmslot/adapter-sdk";
|
|
2
|
+
const adapterRegistry = createAdapterRegistry();
|
|
15
3
|
export {
|
|
16
4
|
adapterRegistry
|
|
17
5
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
+
import { defineAdapter } from "@farmslot/adapter-sdk";
|
|
2
3
|
import { shellQuote } from "../../commands/parse-args.js";
|
|
3
4
|
import fs from "node:fs";
|
|
4
5
|
import { TERMINAL_CONSOLE_FILES, terminalRuntimeFile } from "./console-capture.js";
|
|
@@ -29,8 +30,9 @@ function terminalSignerFor(target) {
|
|
|
29
30
|
}
|
|
30
31
|
return process.env.TERMINAL_SIGNER || "extension";
|
|
31
32
|
}
|
|
32
|
-
const terminalSurface = {
|
|
33
|
-
|
|
33
|
+
const terminalSurface = defineAdapter({
|
|
34
|
+
id: "terminal",
|
|
35
|
+
sdkVersion: 1,
|
|
34
36
|
headless: false,
|
|
35
37
|
resolveSlotPorts() {
|
|
36
38
|
},
|
|
@@ -116,7 +118,7 @@ const terminalSurface = {
|
|
|
116
118
|
},
|
|
117
119
|
diagnostics: terminalDiagnostics,
|
|
118
120
|
observation: terminalObservation
|
|
119
|
-
};
|
|
121
|
+
});
|
|
120
122
|
adapterRegistry.register(terminalSurface);
|
|
121
123
|
export {
|
|
122
124
|
terminalAppPort,
|
package/dist/checkout-lock.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ensureRuntimeDirectory, processIdentity } from "@farmslot/recipe-
|
|
1
|
+
import { ensureRuntimeDirectory, processIdentity } from "@farmslot/recipe-runner/runtime/operation";
|
|
2
2
|
import { randomUUID } from "node:crypto";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
package/dist/command-journal.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { OperationRecord, readOperations, processIdentity, ensureRuntimeDirectory } from "@farmslot/recipe-
|
|
4
|
+
import { OperationRecord, readOperations, processIdentity, ensureRuntimeDirectory } from "@farmslot/recipe-runner/runtime/operation";
|
|
5
5
|
import { latestTaskDir } from "@farmslot/agent-runtime";
|
|
6
6
|
import { recipeRuntimeDir } from "./paths.js";
|
|
7
7
|
const COMMAND_JOURNAL_FILE = "last-command.json";
|
|
@@ -6,7 +6,7 @@ import path from "node:path";
|
|
|
6
6
|
import {
|
|
7
7
|
depsCheck,
|
|
8
8
|
recordDepsBaseline
|
|
9
|
-
} from "@farmslot/recipe-
|
|
9
|
+
} from "@farmslot/recipe-runner/runtime/deps-readiness";
|
|
10
10
|
import { colorHumanMessage } from "../../cli-color.js";
|
|
11
11
|
import {
|
|
12
12
|
importRecipeHarnessRuntimeCdp,
|
|
@@ -34,7 +34,7 @@ import { readExtensionPinnedFlags } from "../../adapters/extension/feature-flags
|
|
|
34
34
|
import { sameFeatureFlags } from "../../feature-flags.js";
|
|
35
35
|
const { CdpSession } = await importRecipeHarnessRuntimeCdp();
|
|
36
36
|
const { RUNTIME_IDENTITY_FILENAME, RUNTIME_NONCE_PREFIX } = createRequire(import.meta.url)(
|
|
37
|
-
|
|
37
|
+
"@farmslot/adapter-web/chrome-args"
|
|
38
38
|
);
|
|
39
39
|
function extensionDepsBlock(target) {
|
|
40
40
|
if (!fs.existsSync(path.join(target, "package.json"))) return null;
|
package/dist/commands/prepare.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { acquireCheckoutLock, trackCheckoutChild } from "../checkout-lock.js";
|
|
2
2
|
import { recordCommandStage } from "../command-journal.js";
|
|
3
3
|
import { spawn } from "node:child_process";
|
|
4
|
-
import { OperationOutputTail } from "@farmslot/recipe-
|
|
4
|
+
import { OperationOutputTail } from "@farmslot/recipe-runner/runtime/operation";
|
|
5
5
|
import fs from "node:fs";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import {
|
|
@@ -302,7 +302,7 @@ async function handlePrepareLocked(argv) {
|
|
|
302
302
|
const json = optionFlag(options, "json");
|
|
303
303
|
const target = targetPath(options);
|
|
304
304
|
const surface = resolvePlatform(optionString(options, "platform"), target);
|
|
305
|
-
const platform = surface.
|
|
305
|
+
const platform = surface.id;
|
|
306
306
|
const artifactsDir = path.resolve(
|
|
307
307
|
optionString(options, "artifactsDir") ?? path.join(target, recipeRuntimeDir())
|
|
308
308
|
);
|
package/dist/commands/shared.js
CHANGED
|
@@ -2,7 +2,7 @@ import { trackCheckoutChild } from "../checkout-lock.js";
|
|
|
2
2
|
import { recordCommandOutput, recordCommandStage } from "../command-journal.js";
|
|
3
3
|
import { spawn, spawnSync } from "node:child_process";
|
|
4
4
|
import { StringDecoder } from "node:string_decoder";
|
|
5
|
-
import { OperationOutputTail } from "@farmslot/recipe-
|
|
5
|
+
import { OperationOutputTail } from "@farmslot/recipe-runner/runtime/operation";
|
|
6
6
|
import path from "node:path";
|
|
7
7
|
import { detectAdapter } from "../harness.js";
|
|
8
8
|
import { colorHumanMessage } from "../cli-color.js";
|
package/dist/discovery.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { assessRecipe } from "@farmslot/recipe-cli";
|
|
2
2
|
import {
|
|
3
3
|
loadRecipeLibraries
|
|
4
|
-
} from "@farmslot/recipe-
|
|
4
|
+
} from "@farmslot/recipe-runner";
|
|
5
5
|
import { resolveActionManifest } from "./manifest.js";
|
|
6
6
|
async function runnableLibraryRecipes(adapter, sources) {
|
|
7
7
|
const resolution = await loadRecipeLibraries(sources, { adapter });
|
package/dist/doctor.js
CHANGED
package/dist/harness.js
CHANGED
|
@@ -41,7 +41,7 @@ function failureHint(surface, action) {
|
|
|
41
41
|
const restart = surface.harness.restart;
|
|
42
42
|
if (!restart) {
|
|
43
43
|
if (action === "verify") {
|
|
44
|
-
return `Install the ${surface.
|
|
44
|
+
return `Install the ${surface.id} harness first: mm-harness install --platform ${surface.id}`;
|
|
45
45
|
}
|
|
46
46
|
return "Read the error above for the specific cause, then re-run this command.";
|
|
47
47
|
}
|
|
@@ -192,7 +192,7 @@ function installedRunnerSource(target, adapter) {
|
|
|
192
192
|
function resolveHarnessDispatch(surface, action, target) {
|
|
193
193
|
if (action === "verify") return surface.harness.verify(target);
|
|
194
194
|
const leaf = action === "install" ? surface.harness.install : surface.harness.cleanup;
|
|
195
|
-
const base = action === "cleanup" && surface.harness.cleanup.fromInstalledRunner ? installedRunnerSource(target, surface.
|
|
195
|
+
const base = action === "cleanup" && surface.harness.cleanup.fromInstalledRunner ? installedRunnerSource(target, surface.id) ?? runnerDir : runnerDir;
|
|
196
196
|
const entry = resolveEntry(base, [leaf.entry, leaf.fallback], "file");
|
|
197
197
|
return leaf.node ? { command: process.execPath, prefixArgs: [entry] } : { command: entry, prefixArgs: [] };
|
|
198
198
|
}
|
|
@@ -330,7 +330,7 @@ async function handleHarness(argv) {
|
|
|
330
330
|
}
|
|
331
331
|
function reportHarnessOutcome(action, surface, target, exitCode, autoDetected, json, quiet, start) {
|
|
332
332
|
const seconds = ((Date.now() - start) / 1e3).toFixed(1);
|
|
333
|
-
const adapter = surface.
|
|
333
|
+
const adapter = surface.id;
|
|
334
334
|
if (quiet) {
|
|
335
335
|
return exitCode;
|
|
336
336
|
} else if (json) {
|
package/dist/paths.js
CHANGED
|
@@ -86,7 +86,7 @@ function findProtocolRoot(start) {
|
|
|
86
86
|
return void 0;
|
|
87
87
|
}
|
|
88
88
|
function isProtocolRoot(candidate) {
|
|
89
|
-
return fs.existsSync(path.join(candidate, "packages/recipe-
|
|
89
|
+
return fs.existsSync(path.join(candidate, "packages/recipe-runner/package.json")) && fs.existsSync(path.join(candidate, "packages/protocol/package.json"));
|
|
90
90
|
}
|
|
91
91
|
function assertAdapter(adapter) {
|
|
92
92
|
const ids = adapterRegistry.list();
|
|
@@ -110,32 +110,32 @@ function readJson(file) {
|
|
|
110
110
|
}
|
|
111
111
|
async function importRecipeHarness() {
|
|
112
112
|
return importProtocolPackage(
|
|
113
|
-
"@farmslot/recipe-
|
|
114
|
-
"packages/recipe-
|
|
113
|
+
"@farmslot/recipe-runner",
|
|
114
|
+
"packages/recipe-runner/src/index.ts"
|
|
115
115
|
);
|
|
116
116
|
}
|
|
117
117
|
async function importRecipeHarnessRuntimeCdp() {
|
|
118
118
|
return importProtocolPackage(
|
|
119
|
-
"@farmslot/recipe-
|
|
120
|
-
"packages/recipe-
|
|
119
|
+
"@farmslot/recipe-runner/runtime/cdp",
|
|
120
|
+
"packages/recipe-runner/src/runtime/cdp.ts"
|
|
121
121
|
);
|
|
122
122
|
}
|
|
123
123
|
async function importRecipeHarnessAppLifecycle() {
|
|
124
124
|
return importProtocolPackage(
|
|
125
|
-
"@farmslot/recipe-
|
|
126
|
-
"packages/recipe-
|
|
125
|
+
"@farmslot/recipe-runner/adapters/app-lifecycle",
|
|
126
|
+
"packages/recipe-runner/src/adapters/app-lifecycle.ts"
|
|
127
127
|
);
|
|
128
128
|
}
|
|
129
129
|
async function importRecipeHarnessRuntimeBrowserExtension() {
|
|
130
130
|
return importProtocolPackage(
|
|
131
|
-
"@farmslot/recipe-
|
|
132
|
-
"packages/recipe-
|
|
131
|
+
"@farmslot/recipe-runner/runtime/browser-extension",
|
|
132
|
+
"packages/recipe-runner/src/runtime/browser-extension.ts"
|
|
133
133
|
);
|
|
134
134
|
}
|
|
135
135
|
async function importRecipeHarnessRuntimeReactNativeBridge() {
|
|
136
136
|
return importProtocolPackage(
|
|
137
|
-
"@farmslot/recipe-
|
|
138
|
-
"packages/recipe-
|
|
137
|
+
"@farmslot/recipe-runner/runtime/react-native-bridge",
|
|
138
|
+
"packages/recipe-runner/src/runtime/react-native-bridge.ts"
|
|
139
139
|
);
|
|
140
140
|
}
|
|
141
141
|
async function importMetaMaskNativeUiProvider() {
|
|
@@ -144,10 +144,10 @@ async function importMetaMaskNativeUiProvider() {
|
|
|
144
144
|
"packages/expo-recipe/src/index.ts"
|
|
145
145
|
);
|
|
146
146
|
}
|
|
147
|
-
async function
|
|
147
|
+
async function importRecipeRunnerCli() {
|
|
148
148
|
return importProtocolPackage(
|
|
149
|
-
"@farmslot/recipe-
|
|
150
|
-
"packages/recipe-
|
|
149
|
+
"@farmslot/recipe-runner/cli",
|
|
150
|
+
"packages/recipe-runner/src/cli/index.ts"
|
|
151
151
|
);
|
|
152
152
|
}
|
|
153
153
|
async function importRecipeProtocol() {
|
|
@@ -185,11 +185,11 @@ export {
|
|
|
185
185
|
importMetaMaskNativeUiProvider,
|
|
186
186
|
importRecipeHarness,
|
|
187
187
|
importRecipeHarnessAppLifecycle,
|
|
188
|
-
importRecipeHarnessCli,
|
|
189
188
|
importRecipeHarnessRuntimeBrowserExtension,
|
|
190
189
|
importRecipeHarnessRuntimeCdp,
|
|
191
190
|
importRecipeHarnessRuntimeReactNativeBridge,
|
|
192
191
|
importRecipeProtocol,
|
|
192
|
+
importRecipeRunnerCli,
|
|
193
193
|
manifestPath,
|
|
194
194
|
readJson,
|
|
195
195
|
recipeHarnessPath,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
|
-
import { loadRecipeLibraries, resolveRecipeDependencies, rootResolutionRef } from "@farmslot/recipe-
|
|
3
|
+
import { loadRecipeLibraries, resolveRecipeDependencies, rootResolutionRef } from "@farmslot/recipe-runner";
|
|
4
4
|
import { isSensitiveKey } from "./command-journal.js";
|
|
5
5
|
import { resolveActionManifest } from "./manifest.js";
|
|
6
6
|
import { describeManifestActions } from "./commands/manifest.js";
|
package/dist/review/knowledge.js
CHANGED
|
@@ -221,7 +221,7 @@ async function hydrateLibraryPathEnv(context) {
|
|
|
221
221
|
if (discovered.length === 0) return;
|
|
222
222
|
const entries = discovered.map((library) => `${library.name}=${library.root}`);
|
|
223
223
|
if (!existing) {
|
|
224
|
-
const { personalRecipeLibraryRoot } = await import("@farmslot/recipe-
|
|
224
|
+
const { personalRecipeLibraryRoot } = await import("@farmslot/recipe-runner");
|
|
225
225
|
const personal = personalRecipeLibraryRoot(env);
|
|
226
226
|
if (isDir(personal)) entries.push(`personal=${personal}`);
|
|
227
227
|
}
|
package/dist/run-recording.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { spawn, spawnSync } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { optionalVideoTiming, readCaptureHelperTiming, writeRecordingTimeline } from "@farmslot/recipe-
|
|
4
|
+
import { optionalVideoTiming, readCaptureHelperTiming, writeRecordingTimeline } from "@farmslot/recipe-runner";
|
|
5
5
|
import { getAdapterSurface } from "./adapters/surface.js";
|
|
6
6
|
import {
|
|
7
7
|
captureHelperSupportsRecordSessionSnapshots,
|
package/dist/task-view.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { readOperations, processIdentity } from "@farmslot/recipe-
|
|
1
|
+
import { readOperations, processIdentity } from "@farmslot/recipe-runner/runtime/operation";
|
|
2
2
|
import { spawnSync } from "node:child_process";
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import path from "node:path";
|
package/docs/CONTRIBUTING.md
CHANGED
|
@@ -13,7 +13,7 @@ bin/mm-harness
|
|
|
13
13
|
-> @farmslot/agent-runtime checklist state
|
|
14
14
|
-> @farmslot/handoff scrubbed learning capture/share
|
|
15
15
|
-> @farmslot/recipe-cli recipe discovery: libraries, runnable checks, composition
|
|
16
|
-
-> @farmslot/recipe-
|
|
16
|
+
-> @farmslot/recipe-runner generic execution, UI transports, evidence
|
|
17
17
|
-> @farmslot/protocol schemas
|
|
18
18
|
-> adapters/ focused host/browser/device leaves
|
|
19
19
|
-> library/ MetaMask actions and recipes
|
|
@@ -26,7 +26,7 @@ bin/mm-harness
|
|
|
26
26
|
| `@farmslot/agent-runtime` | task-local checklist state and terminal contracts |
|
|
27
27
|
| `@farmslot/handoff` | learning assembly, scrubbing, validation, approval, and publication |
|
|
28
28
|
| `@farmslot/recipe-cli` | recipe discovery: library resolution, runnable checks, composition |
|
|
29
|
-
| `@farmslot/recipe-
|
|
29
|
+
| `@farmslot/recipe-runner` | generic execution, recovery, traces, artifacts, `ui.*` |
|
|
30
30
|
| `mm-harness` | MetaMask runtime control, diagnostics, durable domain actions |
|
|
31
31
|
| skills/checklists | task workflow and proof expectations |
|
|
32
32
|
| product checkout | app behavior and explicit debug hooks |
|
package/docs/cli-contract.md
CHANGED
|
@@ -90,22 +90,11 @@ These are real farm bugs. The goldens freeze the current rejections, so fixing e
|
|
|
90
90
|
| Mobile templates `dev.md`, `dev-interactive.md`, `fix-bug.md`, `review-pr.md` | `mark complete --status blocked --outcome partial --reason …` exits 2 | `mark blocked --reason "<why>"` | `task/mark-invalid-status-outcome`, `task/mark-blocked` |
|
|
91
91
|
| Docs | `launch --build-lavamoat` (only `runtime-launch` has it); `run --target <recipe-file>` | `runtime-launch --build-lavamoat`; `run <recipe-file> --target <repo>` | — |
|
|
92
92
|
|
|
93
|
-
## Terminal adapter
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
- [ ] `actions --raw --adapter terminal --json`
|
|
99
|
-
- [ ] `doctor --adapter terminal --target <repo> --cdp-port <p> --watcher-port <p> --json`
|
|
100
|
-
- [ ] `install --adapter terminal --target <repo>`
|
|
101
|
-
- [ ] `verify --adapter terminal --target <repo> -- --cdp-port <p> --watcher-port <p> --account <a>`
|
|
102
|
-
- [ ] `cleanup --adapter terminal --target <repo> -- --cdp-port <p>`
|
|
103
|
-
- [ ] `launch --adapter terminal --target <repo> --cdp-port <p> --watcher-port <p> --signer extension --account <a>` (use the stub browser from #298's `tests/fixtures/terminal-stub-browser.mjs`)
|
|
104
|
-
- [ ] `stop --adapter terminal --target <repo>` with `RECIPE_CDP_PORT`
|
|
105
|
-
- [ ] `run terminal.perps.<name> k=v… --adapter terminal --target <t> --cdp-port <p> --watcher-port <p> --plan` (recipe-terminal `checks/plan-recipes.mjs`)
|
|
106
|
-
- [ ] runtime layout of `temp/recipe/runtime/terminal/` (`browser.pid` is watched by the farm)
|
|
107
|
-
- [ ] discovery: add `terminal` to the per-adapter loop in `goldens-discovery.test.sh` (`actions --raw`, `actions`, `run --list`, `call --list`)
|
|
108
|
-
- [ ] recipe-terminal `console-guard.mjs` imports the harness's `library/actions/shared/console-findings.mjs` and `tests/fixtures/console-allowlist-vectors.json`: freeze both paths and the exported surface
|
|
93
|
+
## Terminal adapter
|
|
94
|
+
|
|
95
|
+
`goldens-terminal` freezes the `va-mmcx-terminal-farm` hooks with their exact argv (`recipe_action_manifest`, `recipe_doctor`, `recipe_harness_{install,verify,cleanup}`, `recipe_run`, the browser `boot`/`shutdown` resources and `teardown`), against a fresh fixture slot: no slot dev server, no Extension dist, and the farm's wallet fixture outside the slot, so `install` copies it in. `launch` is frozen on its cold path: it refuses at the missing dist before any browser starts. The slot's CDP port is the one port no port holder reserves: a farm slot with no browser has it free, and the Terminal's port checks probe it. `goldens-discovery` covers `terminal` in its per-adapter loop, so `terminal/actions-raw` and `discovery/actions-raw-terminal` freeze the same manifest and change together.
|
|
96
|
+
|
|
97
|
+
Outside this boundary: a successful `launch` and the `temp/recipe/runtime/terminal/` layout (`browser.pid`), recipes and actions from the farm's `RECIPE_LIBRARY_PATH` libraries, `run terminal.perps.<name> --plan`, and the recipe-terminal `console-guard.mjs` import paths.
|
|
109
98
|
|
|
110
99
|
## Running the goldens
|
|
111
100
|
|
|
@@ -152,7 +141,7 @@ The goldens need the harness to be a git checkout: `execution-provenance.json` `
|
|
|
152
141
|
1. Absolute paths: the sandbox becomes `<SANDBOX>`, the repo `<REPO>`, the real `$HOME` `<HOME>`, the node binary and its prefix `<NODE>`/`<NODE_PREFIX>`, and `os.tmpdir()` `<TMP>`. Both `/var` and `/private/var` spellings are covered. Ports from `gd_reserve_ports` become `<PORT:NAME>` in port positions only: numbers under `*port` keys, and in text after `:`, `port `, `port=` or `PORT=`, or a string that is exactly the port (`^<port>$`; `60000 ms` is left alone).
|
|
153
142
|
2. UUIDs become `<UUID>`. Git object ids (40 hex, or a short hex value under a `*head`/`*gitRef`/`*commit` key) become `<GIT_REF>`. ISO-8601 timestamps become `<TIMESTAMP>`, and compact stamps in names `<STAMP>`. Path-derived slot ids `local-<adapter>-<8 hex>` become `local-<adapter>-<HASH>`.
|
|
154
143
|
3. The package version becomes `<HARNESS_VERSION>`. Any other `x.y.z` under a key ending in `version` becomes `<VERSION>`.
|
|
155
|
-
4. Numbers under volatile keys: `*pid` becomes `<PID>`, `*port` `<PORT>`, `*At`/`*Time`/`*timestamp`/`mtime*` `<TIMESTAMP>`, and `*Ms`/`duration*`/`elapsed*` `<DURATION>`. Values inside `schema` and `examples` subtrees (static manifest content) are never rewritten.
|
|
144
|
+
4. Numbers under volatile keys: `*pid` becomes `<PID>`, `inode` `<INODE>`, `*port` `<PORT>`, `*At`/`*Time`/`*timestamp`/`mtime*` `<TIMESTAMP>`, and `*Ms`/`duration*`/`elapsed*` `<DURATION>`. Values inside `schema` and `examples` subtrees (static manifest content) are never rewritten.
|
|
156
145
|
5. `invocationDigest` becomes `<DIGEST>`: it hashes `recipe-invocation.json`, which carries sandbox paths and timestamps and is frozen itself in normalised form. `runner.sourceFingerprint` becomes `<FINGERPRINT>`: it hashes the harness source tree, so any harness edit changes it. A `status` holding `git status --porcelain` output (provenance `runner.status` and `libraries[].status`, empty when clean) becomes `<GIT_STATUS>`. Content-only digests (recipe and dependency digests) stay frozen.
|
|
157
146
|
6. Durations in text (`0.3s`, `123ms`) become `<DURATION>`.
|
|
158
147
|
|
|
@@ -19,7 +19,6 @@ import { closeSync, openSync } from 'node:fs';
|
|
|
19
19
|
import { spawn } from 'node:child_process';
|
|
20
20
|
import { createRequire } from 'node:module';
|
|
21
21
|
import path from 'node:path';
|
|
22
|
-
import { fileURLToPath } from 'node:url';
|
|
23
22
|
|
|
24
23
|
import {
|
|
25
24
|
importRecipeHarnessRuntimeBrowserExtension,
|
|
@@ -370,11 +369,10 @@ function autolaunchEnabled(input) {
|
|
|
370
369
|
}
|
|
371
370
|
|
|
372
371
|
const runnerRequire = createRequire(import.meta.url);
|
|
373
|
-
const
|
|
374
|
-
const
|
|
375
|
-
const
|
|
376
|
-
const
|
|
377
|
-
const macosFocus = runnerRequire(path.join(runnerRoot, 'adapters/extension/lib/macos-focus.cjs'));
|
|
372
|
+
const browserResolver = runnerRequire('@farmslot/adapter-web/browser-resolver');
|
|
373
|
+
const { stopProfileProcessesSync } = runnerRequire('@farmslot/adapter-web/validation-process-ownership');
|
|
374
|
+
const browserCdp = runnerRequire('@farmslot/adapter-web/browser-cdp');
|
|
375
|
+
const macosFocus = runnerRequire('@farmslot/adapter-web/macos-focus');
|
|
378
376
|
|
|
379
377
|
// On macOS a browser that opens a window at startup activates itself, so the
|
|
380
378
|
// autolaunched browser starts without one and gets its pages over CDP in the
|
|
@@ -382,7 +380,7 @@ const macosFocus = runnerRequire(path.join(runnerRoot, 'adapters/extension/lib/m
|
|
|
382
380
|
const startsWithoutWindow = () => process.platform === 'darwin';
|
|
383
381
|
|
|
384
382
|
// One bounded browser-level CDP operation (the shared helpers in
|
|
385
|
-
// browser-cdp
|
|
383
|
+
// @farmslot/adapter-web/browser-cdp never take the operator's focus).
|
|
386
384
|
async function withBrowserCdp(port, fn) {
|
|
387
385
|
const deadline = Date.now() + 15000;
|
|
388
386
|
const remaining = () => Math.max(1, deadline - Date.now());
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// Select the CDP page a terminal UI action drives: the app tab (default) or the
|
|
2
2
|
// MetaMask wallet surface (side panel / notification / popup) that shows the
|
|
3
3
|
// pending dapp request. Pages are never brought to front unless
|
|
4
|
-
//
|
|
4
|
+
// FARMSLOT_FOCUS_BROWSER=1, as in the Extension adapter: Page.bringToFront
|
|
5
5
|
// activates the browser on macOS and takes the operator's keyboard focus. The
|
|
6
6
|
// app tab is the only tab of its window (the wallet host closes the others),
|
|
7
7
|
// so it stays the window's active tab and keeps painting without it.
|
|
@@ -199,7 +199,7 @@ export function slowMoFor(browserState, env = process.env) {
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
export function focusBrowserRequested(env = process.env) {
|
|
202
|
-
return env.
|
|
202
|
+
return env.FARMSLOT_FOCUS_BROWSER === '1';
|
|
203
203
|
}
|
|
204
204
|
|
|
205
205
|
// Defence in depth before a wallet confirmation: the app port is still served
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deeeed/metamask-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.79.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"mm-harness": "bin/mm-harness"
|
|
@@ -21,12 +21,14 @@
|
|
|
21
21
|
"check:syntax": "find . -name '*.mjs' -print0 | xargs -0 -n1 node --check"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
+
"@farmslot/adapter-sdk": "0.1.0",
|
|
25
|
+
"@farmslot/adapter-web": "0.1.0",
|
|
24
26
|
"@farmslot/agent-runtime": "0.16.0",
|
|
25
|
-
"@farmslot/expo-recipe": "0.
|
|
27
|
+
"@farmslot/expo-recipe": "0.14.0",
|
|
26
28
|
"@farmslot/handoff": "^0.3.1",
|
|
27
29
|
"@farmslot/protocol": "0.34.0",
|
|
28
|
-
"@farmslot/recipe-cli": "0.
|
|
29
|
-
"@farmslot/recipe-
|
|
30
|
+
"@farmslot/recipe-cli": "0.2.0",
|
|
31
|
+
"@farmslot/recipe-runner": "0.23.0",
|
|
30
32
|
"agent-device": "0.19.3",
|
|
31
33
|
"commander": "^12.0.0",
|
|
32
34
|
"es-module-lexer": "2.3.1",
|