@deeeed/metamask-harness 0.76.0 → 0.78.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 +14 -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/harness-source-fingerprint.mjs +16 -2
- package/adapters/terminal/launch.mjs +2 -2
- package/adapters/terminal/wallet-host.mjs +1 -1
- package/dist/adapters/extension/doctor-checks.js +2 -2
- package/dist/adapters/extension/launched-browser.js +1 -1
- package/dist/adapters/extension/runtime.js +4 -4
- package/dist/adapters/extension/validation-process-ownership.js +1 -1
- package/dist/commands/call.js +3 -7
- package/dist/commands/launch/extension.js +1 -1
- package/dist/commands/list-executables.js +12 -22
- package/dist/commands/manifest.js +11 -133
- package/dist/commands/run.js +2 -5
- 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 +3 -2
- 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
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.78.0 - 2026-10-05
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- The browser process and CDP helpers moved to `@farmslot/adapter-web` (new dependency): `browser-resolver`, `browser-cdp`, `chrome-args`, `extension-id`, `macos-focus`, `playwright-cdp`, `validation-process-ownership` and `validation-launch-supervisor`. Their old paths under `adapters/shared/` and `adapters/extension/lib/` are gone; adapters, library actions, the Terminal launcher and `src/` require the package. Behaviour is unchanged.
|
|
10
|
+
- **Upgrade note: stop running slot browsers before upgrading**: run `mm-harness stop --adapter extension --target <checkout>` once for each Extension checkout with a running browser. The browser identity names are Farmslot's now and the old runtime nonce is no longer accepted, so a browser started by an earlier version may not be recognised as owned (one found by its slot profile still is): `--farmslot-runtime-nonce=`, `.farmslot-detached-launch-unproven`, `/tmp/farmslot-browser-validation-<uid>`, and the probe cache `~/.cache/farmslot/browser-probe.json` (the old `~/.cache/mm-harness/browser-probe.json` and its lock directories are no longer read and can be deleted). The focus knobs are `FARMSLOT_FOCUS_HOLD` and `FARMSLOT_FOCUS_BROWSER` (were `MM_HARNESS_FOCUS_HOLD` / `MM_HARNESS_FOCUS_BROWSER`).
|
|
11
|
+
- The Extension overlay vendors `@farmslot/adapter-web` into `<harness>/scripts/node_modules/` instead of flattening copies into `scripts/lib/`, and the harness source fingerprint covers the package sources, so a package upgrade refreshes installed overlays.
|
|
12
|
+
|
|
13
|
+
## 0.77.0 - 2026-10-04
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- The action catalog runs on `@farmslot/recipe-cli` 0.1.2: `actions` (lookup, search, related actions, categories, `--matrix`), `call` name lookup and `call --list` short names, and the missing-capability refusals in `call` and `run` use its catalog helpers instead of mm-harness's own copies. mm-harness still resolves the action manifest with its own policy, and flags and output are unchanged.
|
|
18
|
+
|
|
5
19
|
## 0.76.0 - 2026-10-04
|
|
6
20
|
|
|
7
21
|
### Changed
|
package/README.md
CHANGED
|
@@ -134,7 +134,7 @@ disabled, so the launch warns and records them with the run evidence.
|
|
|
134
134
|
|
|
135
135
|
In `auto`, a launch with a new profile probe-launches Chrome for Testing the
|
|
136
136
|
way the launcher starts it (headful, through Launch Services on macOS). The
|
|
137
|
-
verdict lives in `~/.cache/
|
|
137
|
+
verdict lives in `~/.cache/farmslot/browser-probe.json`: a crash is kept
|
|
138
138
|
until the OS build or browser changes; a probe that only timed out is retried
|
|
139
139
|
after 30 minutes. Concurrent launches wait for one probe per browser build
|
|
140
140
|
(a `<cache>.<key>.locks/` directory of numbered tickets next to the cache; a
|
|
@@ -144,7 +144,7 @@ file one duplicate probe is possible). Tickets are never removed by the
|
|
|
144
144
|
harness: each probe adds two small files, and a crashed holder's unreleased
|
|
145
145
|
ticket stays (it no longer blocks once its process is gone). To reclaim the
|
|
146
146
|
space, remove the lock directories while no launch or doctor is running:
|
|
147
|
-
`rm -r ~/.cache/
|
|
147
|
+
`rm -r ~/.cache/farmslot/browser-probe.json.*.locks`.
|
|
148
148
|
On a cold cache the probe can take up to about 80 seconds and
|
|
149
149
|
shows a small (about 480×360) browser window; `doctor --adapter extension`
|
|
150
150
|
runs the same probe.
|
|
@@ -205,8 +205,11 @@ On macOS the slot browser starts in the background with no startup window,
|
|
|
205
205
|
and its first window opens over CDP as a background window. After the launch
|
|
206
206
|
settles there is one focus check: only if the slot browser's own process is
|
|
207
207
|
frontmost does the previously focused app get the front back, once
|
|
208
|
-
(`
|
|
209
|
-
after launch can therefore be undone once.
|
|
208
|
+
(`FARMSLOT_FOCUS_HOLD=0` turns this off). A click on the slot window right
|
|
209
|
+
after launch can therefore be undone once. `FARMSLOT_FOCUS_HOLD` and
|
|
210
|
+
`FARMSLOT_FOCUS_BROWSER` belong to `@farmslot/adapter-web`; the settle delay
|
|
211
|
+
before that check, `MM_HARNESS_FOCUS_SETTLE_MS`, belongs to this harness's
|
|
212
|
+
launchers.
|
|
210
213
|
|
|
211
214
|
```bash
|
|
212
215
|
# Roll back to Chrome for Testing with a fresh slot profile (a profile Google
|
|
@@ -214,7 +217,7 @@ after launch can therefore be undone once.
|
|
|
214
217
|
# RECIPE_HARNESS_BROWSER=cft set for later launches too: on a plain auto launch
|
|
215
218
|
# the profile follows Chrome for Testing only until a probe proves it dies.
|
|
216
219
|
RECIPE_HARNESS_BROWSER=cft mm-harness fixtures set
|
|
217
|
-
rm ~/.cache/
|
|
220
|
+
rm ~/.cache/farmslot/browser-probe.json # forget probe verdicts
|
|
218
221
|
```
|
|
219
222
|
|
|
220
223
|
### Remote feature flags
|
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
const fs = require('node:fs');
|
|
5
5
|
const path = require('node:path');
|
|
6
6
|
const { spawnSync } = require('node:child_process');
|
|
7
|
-
const { connectBrowserViaCdp, evaluatePageViaCdp } = require('
|
|
8
|
-
const { RUNTIME_IDENTITY_FILENAME, RUNTIME_NONCE_PREFIX } = require('
|
|
7
|
+
const { connectBrowserViaCdp, evaluatePageViaCdp } = require('@farmslot/adapter-web/playwright-cdp');
|
|
8
|
+
const { RUNTIME_IDENTITY_FILENAME, RUNTIME_NONCE_PREFIX } = require('@farmslot/adapter-web/chrome-args');
|
|
9
9
|
const { stampRuntimeTitles } = require('./stamp-runtime-title.cjs');
|
|
10
10
|
const { carryHarnessPin } = require('./pin-remote-flags.cjs');
|
|
11
11
|
|
|
@@ -205,10 +205,10 @@ kill_profile_processes() {
|
|
|
205
205
|
|
|
206
206
|
# Invalidate the old browser identity before signaling it. A concurrent quick
|
|
207
207
|
# launch must fall through to process ownership while replacement is underway.
|
|
208
|
-
node - "$SCRIPT_DIR
|
|
209
|
-
const [
|
|
208
|
+
node - "$SCRIPT_DIR" "$AGENT_DIR" <<'NODE'
|
|
209
|
+
const [scriptDir, runtimeDir] = process.argv.slice(2);
|
|
210
210
|
try {
|
|
211
|
-
require(
|
|
211
|
+
require('node:module').createRequire(`${scriptDir}/`)('@farmslot/adapter-web/chrome-args').removeRuntimeIdentity(runtimeDir);
|
|
212
212
|
} catch (error) {
|
|
213
213
|
const detail = error instanceof Error ? error.message : String(error);
|
|
214
214
|
throw new Error(
|
|
@@ -292,6 +292,7 @@ trap - EXIT
|
|
|
292
292
|
exec node -e "
|
|
293
293
|
const path = require('path');
|
|
294
294
|
const fs = require('fs');
|
|
295
|
+
const adapterWeb = (name) => require('module').createRequire(path.join('${SCRIPT_DIR}', 'package.json'))('@farmslot/adapter-web/' + name);
|
|
295
296
|
const http = require('http');
|
|
296
297
|
const { execFileSync } = require('child_process');
|
|
297
298
|
let chromium; try { chromium = require('@playwright/test').chromium; } catch { chromium = require('playwright').chromium; }
|
|
@@ -303,9 +304,9 @@ const {
|
|
|
303
304
|
removeRuntimeIdentity,
|
|
304
305
|
runtimeIdentityArgs,
|
|
305
306
|
writeRuntimeIdentity,
|
|
306
|
-
} =
|
|
307
|
-
const { captureMacFrontmost, macBackgroundOpenArgs, restoreFrontmostIfOurs } =
|
|
308
|
-
const { stopProfileProcessesSync } =
|
|
307
|
+
} = adapterWeb('chrome-args');
|
|
308
|
+
const { captureMacFrontmost, macBackgroundOpenArgs, restoreFrontmostIfOurs } = adapterWeb('macos-focus');
|
|
309
|
+
const { stopProfileProcessesSync } = adapterWeb('validation-process-ownership');
|
|
309
310
|
const {
|
|
310
311
|
CDP_LOAD_UNPACKED,
|
|
311
312
|
HEADFUL,
|
|
@@ -315,10 +316,7 @@ const {
|
|
|
315
316
|
waitForCdpOwner,
|
|
316
317
|
playwrightChromiumExecutable,
|
|
317
318
|
resolveBrowser,
|
|
318
|
-
} =
|
|
319
|
-
path.join('${SCRIPT_DIR}', 'lib/browser-resolver.cjs'),
|
|
320
|
-
path.join('${SCRIPT_DIR}', '../shared/browser-resolver.cjs'),
|
|
321
|
-
].find((candidate) => fs.existsSync(candidate)));
|
|
319
|
+
} = adapterWeb('browser-resolver');
|
|
322
320
|
|
|
323
321
|
const SLOT_ID = '${SLOT_ID}';
|
|
324
322
|
const AGENT_DIR = '${AGENT_DIR}';
|
|
@@ -520,7 +518,7 @@ const releaseFocusHold = () => {
|
|
|
520
518
|
|
|
521
519
|
// Set window title to slot ID (persists across SPA navigations)
|
|
522
520
|
const { applyPersistentSlotTitle } = require(path.join('${SCRIPT_DIR}', 'lib/slot-title.cjs'));
|
|
523
|
-
const { evaluatePageViaCdp } =
|
|
521
|
+
const { evaluatePageViaCdp } = adapterWeb('playwright-cdp');
|
|
524
522
|
await evaluatePageViaCdp(page, applyPersistentSlotTitle, SLOT_ID);
|
|
525
523
|
|
|
526
524
|
// Wait for runtime state to settle, then unlock if needed.
|
|
@@ -573,7 +571,7 @@ const releaseFocusHold = () => {
|
|
|
573
571
|
await p.close().catch(() => {});
|
|
574
572
|
}
|
|
575
573
|
}
|
|
576
|
-
if (process.env.
|
|
574
|
+
if (process.env.FARMSLOT_FOCUS_BROWSER === '1') {
|
|
577
575
|
await page.bringToFront().catch(() => {});
|
|
578
576
|
} else {
|
|
579
577
|
const settleMs = Number(process.env.MM_HARNESS_FOCUS_SETTLE_MS);
|
|
@@ -20,6 +20,7 @@
|
|
|
20
20
|
// Never touches: product source files; anything outside the target's
|
|
21
21
|
// harness dir and the target's .git/info/exclude.
|
|
22
22
|
import fs from 'node:fs';
|
|
23
|
+
import { createRequire } from 'node:module';
|
|
23
24
|
import path from 'node:path';
|
|
24
25
|
import { spawnSync } from 'node:child_process';
|
|
25
26
|
import process from 'node:process';
|
|
@@ -126,15 +127,9 @@ copyFile(path.join(runnerDir, 'adapters/shared/json-field.sh'), path.join(harnes
|
|
|
126
127
|
copyFile(path.join(runnerDir, 'adapters/shared/tmux-session.sh'), path.join(harnessDir, 'scripts/lib/tmux-session.sh'));
|
|
127
128
|
copyFile(path.join(runnerDir, 'adapters/shared/tmux-viewer.sh'), path.join(harnessDir, 'scripts/lib/tmux-viewer.sh'));
|
|
128
129
|
copyFile(path.join(runnerDir, 'adapters/shared/log-tui.mjs'), path.join(harnessDir, 'scripts/lib/log-tui.mjs'));
|
|
129
|
-
|
|
130
|
-
copyFile(path.join(runnerDir, 'adapters/shared/browser-cdp.cjs'), path.join(harnessDir, 'scripts/lib/browser-cdp.cjs'));
|
|
131
|
-
copyFile(path.join(runnerDir, 'adapters/extension/lib/extension-id.cjs'), path.join(harnessDir, 'scripts/lib/extension-id.cjs'));
|
|
132
|
-
copyFile(path.join(runnerDir, 'adapters/extension/lib/playwright-cdp.cjs'), path.join(harnessDir, 'scripts/lib/playwright-cdp.cjs'));
|
|
130
|
+
copyAdapterWeb(path.join(harnessDir, 'scripts/node_modules/@farmslot/adapter-web'));
|
|
133
131
|
copyFile(path.join(runnerDir, 'adapters/extension/lib/slot-title.cjs'), path.join(harnessDir, 'scripts/lib/slot-title.cjs'));
|
|
134
|
-
copyFile(path.join(runnerDir, 'adapters/extension/lib/chrome-args.cjs'), path.join(harnessDir, 'scripts/lib/chrome-args.cjs'));
|
|
135
|
-
copyFile(path.join(runnerDir, 'adapters/extension/lib/macos-focus.cjs'), path.join(harnessDir, 'scripts/lib/macos-focus.cjs'));
|
|
136
132
|
copyFile(path.join(runnerDir, 'adapters/extension/lib/product-config.cjs'), path.join(harnessDir, 'scripts/lib/product-config.cjs'));
|
|
137
|
-
copyFile(path.join(runnerDir, 'adapters/extension/lib/validation-process-ownership.cjs'), path.join(harnessDir, 'scripts/lib/validation-process-ownership.cjs'));
|
|
138
133
|
makeExecutableTree(path.join(harnessDir, 'scripts'));
|
|
139
134
|
fs.writeFileSync(path.join(harnessDir, 'installed-scripts.sha256'), `${dirContentHash(path.join(harnessDir, 'scripts'))}\n`);
|
|
140
135
|
|
|
@@ -205,6 +200,14 @@ function refuseSymlinkDestination(root, rel) {
|
|
|
205
200
|
}
|
|
206
201
|
}
|
|
207
202
|
|
|
203
|
+
// Overlay scripts run from the target checkout, which cannot resolve the runner's
|
|
204
|
+
// packages; vendor @farmslot/adapter-web where they resolve it (scripts/node_modules).
|
|
205
|
+
function copyAdapterWeb(dest) {
|
|
206
|
+
const packageJson = createRequire(path.join(runnerDir, 'package.json')).resolve('@farmslot/adapter-web/package.json');
|
|
207
|
+
copyFile(packageJson, path.join(dest, 'package.json'));
|
|
208
|
+
copyDir(path.join(path.dirname(packageJson), 'src'), path.join(dest, 'src'));
|
|
209
|
+
}
|
|
210
|
+
|
|
208
211
|
function copyFile(src, dest) {
|
|
209
212
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
210
213
|
fs.copyFileSync(src, dest);
|
|
@@ -27,14 +27,11 @@ const fs = require('node:fs');
|
|
|
27
27
|
const os = require('node:os');
|
|
28
28
|
const path = require('node:path');
|
|
29
29
|
const { execFileSync, spawn, spawnSync } = require('node:child_process');
|
|
30
|
-
const { extensionIdFromExtensionDir } = require('
|
|
31
|
-
// The
|
|
32
|
-
const browserResolverPath =
|
|
33
|
-
path.join(__dirname, 'lib/browser-resolver.cjs'),
|
|
34
|
-
path.join(__dirname, '../shared/browser-resolver.cjs'),
|
|
35
|
-
].find((candidate) => fs.existsSync(candidate));
|
|
30
|
+
const { extensionIdFromExtensionDir } = require('@farmslot/adapter-web/extension-id');
|
|
31
|
+
// The resolver CLI runs as a bounded child for CDP calls the launcher must not hang on.
|
|
32
|
+
const browserResolverPath = require.resolve('@farmslot/adapter-web/browser-resolver');
|
|
36
33
|
const { CDP_LOAD_UNPACKED, describeBrowser, extensionLaunchArgs, waitForCdpOwner } = require(browserResolverPath);
|
|
37
|
-
const { profileProcessPids, stopProfileProcessesSync } = require('
|
|
34
|
+
const { profileProcessPids, stopProfileProcessesSync } = require('@farmslot/adapter-web/validation-process-ownership');
|
|
38
35
|
const { acquireBuildHandoff } = require('./build-handoff.cjs');
|
|
39
36
|
const {
|
|
40
37
|
automationRuntimeArgs,
|
|
@@ -51,12 +48,12 @@ const {
|
|
|
51
48
|
readValidationPortQuarantine,
|
|
52
49
|
validationLaunchQuarantineError,
|
|
53
50
|
writeRuntimeIdentity,
|
|
54
|
-
} = require('
|
|
51
|
+
} = require('@farmslot/adapter-web/chrome-args');
|
|
55
52
|
const {
|
|
56
53
|
captureMacFrontmost,
|
|
57
54
|
macBackgroundOpenArgs,
|
|
58
55
|
restoreFrontmostIfOurs,
|
|
59
|
-
} = require('
|
|
56
|
+
} = require('@farmslot/adapter-web/macos-focus');
|
|
60
57
|
|
|
61
58
|
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
62
59
|
console.log(
|
|
@@ -138,7 +135,7 @@ if (args['stop-only'] !== undefined) {
|
|
|
138
135
|
|
|
139
136
|
const requestedProfileQuarantined = hasDetachedLaunchUnproven(args.profile);
|
|
140
137
|
const portQuarantine = readValidationPortQuarantine(cdpPort);
|
|
141
|
-
const validationPortLease = process.env.
|
|
138
|
+
const validationPortLease = process.env.FARMSLOT_VALIDATION_PORT_LEASE;
|
|
142
139
|
const activeValidationLease = Boolean(
|
|
143
140
|
portQuarantine?.profile === path.resolve(args.profile) &&
|
|
144
141
|
portQuarantine.lease === validationPortLease &&
|
|
@@ -245,8 +245,7 @@ NODE
|
|
|
245
245
|
# launcher knows how to load the extension and the run evidence names it.
|
|
246
246
|
# launch-browser.cjs starts a macOS .app through Launch Services, headful,
|
|
247
247
|
# so the probe does too.
|
|
248
|
-
BROWSER_RESOLVER="$
|
|
249
|
-
[ -f "$BROWSER_RESOLVER" ] || BROWSER_RESOLVER="$SCRIPT_DIR/../shared/browser-resolver.cjs"
|
|
248
|
+
BROWSER_RESOLVER="$(node -e 'process.stdout.write(require("node:module").createRequire(`${process.argv[1]}/`).resolve("@farmslot/adapter-web/browser-resolver"))' "$SCRIPT_DIR")"
|
|
250
249
|
# A preserved profile keeps the browser recorded for it (auto never flips a
|
|
251
250
|
# profile between builds); the resolver bounds itself (probe + watchdog).
|
|
252
251
|
CHROME_PROBE_LOG="$ARTIFACTS/logs/chrome-probe.log"
|
|
@@ -119,9 +119,9 @@ CDP_PORT="$CDP_PORT" EXT_ID="$EXT_ID" TARGET="$TARGET" SCRIPT_DIR="$SCRIPT_DIR"
|
|
|
119
119
|
let chromium; try { chromium = require('@playwright/test').chromium; } catch { chromium = require('playwright').chromium; }
|
|
120
120
|
const fs = require('node:fs');
|
|
121
121
|
const path = require('node:path');
|
|
122
|
-
const { extensionIdFromManifestFile } = require(
|
|
122
|
+
const { extensionIdFromManifestFile } = require('node:module').createRequire(`${process.env.SCRIPT_DIR}/`)('@farmslot/adapter-web/extension-id');
|
|
123
123
|
const { readSlotId, applyPersistentSlotTitle } = require(path.join(process.env.SCRIPT_DIR, 'lib/slot-title.cjs'));
|
|
124
|
-
const { evaluatePageViaCdp } = require(
|
|
124
|
+
const { evaluatePageViaCdp } = require('node:module').createRequire(`${process.env.SCRIPT_DIR}/`)('@farmslot/adapter-web/playwright-cdp');
|
|
125
125
|
|
|
126
126
|
const port = process.env.CDP_PORT;
|
|
127
127
|
const target = process.env.TARGET;
|
|
@@ -294,7 +294,7 @@ async function closePages(context, predicate) {
|
|
|
294
294
|
if (!startUrl && (url.startsWith('http://') || url.startsWith('https://'))) return true;
|
|
295
295
|
return false;
|
|
296
296
|
});
|
|
297
|
-
if (process.env.
|
|
297
|
+
if (process.env.FARMSLOT_FOCUS_BROWSER === '1') {
|
|
298
298
|
await home.bringToFront().catch(() => {});
|
|
299
299
|
}
|
|
300
300
|
}
|
|
@@ -108,7 +108,7 @@ json_list() {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
focus_browser() {
|
|
111
|
-
[ "${
|
|
111
|
+
[ "${FARMSLOT_FOCUS_BROWSER:-}" = "1" ]
|
|
112
112
|
}
|
|
113
113
|
|
|
114
114
|
find_sidepanel_id() {
|
|
@@ -162,7 +162,7 @@ resolve_ext_id() {
|
|
|
162
162
|
manifest_id="$(
|
|
163
163
|
SCRIPT_DIR="$SCRIPT_DIR" REPO="$REPO" RUNTIME_DIR="$RUNTIME_DIR" RUNTIME_DIST_DIR="$RUNTIME_DIST_DIR" node <<'NODE' 2>/dev/null || true
|
|
164
164
|
const path = require('node:path');
|
|
165
|
-
const { extensionIdFromManifestFile } = require(
|
|
165
|
+
const { extensionIdFromManifestFile } = require('node:module').createRequire(`${process.env.SCRIPT_DIR}/`)('@farmslot/adapter-web/extension-id');
|
|
166
166
|
const id = extensionIdFromManifestFile(path.join(process.env.REPO, process.env.RUNTIME_DIR, process.env.RUNTIME_DIST_DIR, 'manifest.json'));
|
|
167
167
|
if (id) process.stdout.write(id);
|
|
168
168
|
NODE
|
|
@@ -321,7 +321,7 @@ const path = require('node:path');
|
|
|
321
321
|
const {
|
|
322
322
|
connectBrowserViaCdp,
|
|
323
323
|
evaluatePageViaCdp,
|
|
324
|
-
} = require(
|
|
324
|
+
} = require('node:module').createRequire(`${process.env.SCRIPT_DIR}/`)('@farmslot/adapter-web/playwright-cdp');
|
|
325
325
|
|
|
326
326
|
async function clickVisibleSafeHit(page, selector, successSelector, timeoutMs) {
|
|
327
327
|
const deadline = Date.now() + timeoutMs;
|
|
@@ -371,7 +371,7 @@ async function clickVisibleSafeHit(page, selector, successSelector, timeoutMs) {
|
|
|
371
371
|
waitUntil: 'domcontentloaded',
|
|
372
372
|
timeout: 15000,
|
|
373
373
|
});
|
|
374
|
-
if (process.env.
|
|
374
|
+
if (process.env.FARMSLOT_FOCUS_BROWSER === '1') {
|
|
375
375
|
await page.bringToFront();
|
|
376
376
|
}
|
|
377
377
|
await clickVisibleSafeHit(
|
|
@@ -20,8 +20,8 @@ const crypto = require('node:crypto');
|
|
|
20
20
|
const fs = require('node:fs');
|
|
21
21
|
const http = require('node:http');
|
|
22
22
|
const path = require('node:path');
|
|
23
|
-
const { extensionIdFromManifestKey } = require('
|
|
24
|
-
const { evaluatePageViaCdp } = require('
|
|
23
|
+
const { extensionIdFromManifestKey } = require('@farmslot/adapter-web/extension-id');
|
|
24
|
+
const { evaluatePageViaCdp } = require('@farmslot/adapter-web/playwright-cdp');
|
|
25
25
|
|
|
26
26
|
const EOA_METHODS = [
|
|
27
27
|
'personal_sign',
|
package/adapters/manifest.json
CHANGED
|
@@ -282,14 +282,6 @@
|
|
|
282
282
|
"inputs": "--cdp-port --chrome-bin --profile --extension-dir --chrome-log --chrome-pid [--browser-resolution]",
|
|
283
283
|
"outputs": "pid file + append-log + <runtime>/browser-resolution.json; exit 0 / non-zero"
|
|
284
284
|
},
|
|
285
|
-
{
|
|
286
|
-
"id": "extension/validation-process-ownership",
|
|
287
|
-
"entry": "adapters/extension/lib/validation-process-ownership.cjs",
|
|
288
|
-
"kind": "lib",
|
|
289
|
-
"purpose": "Resolve browser processes owned by one isolated Extension validation profile.",
|
|
290
|
-
"inputs": "profile path",
|
|
291
|
-
"outputs": "owned process identifiers"
|
|
292
|
-
},
|
|
293
285
|
{
|
|
294
286
|
"id": "extension/refresh-build",
|
|
295
287
|
"entry": "adapters/extension/refresh-build.sh",
|
|
@@ -506,22 +498,6 @@
|
|
|
506
498
|
"inputs": "--target <checkout>",
|
|
507
499
|
"outputs": "corepack yarn install output; exit 0/1/2"
|
|
508
500
|
},
|
|
509
|
-
{
|
|
510
|
-
"id": "lib/browser-resolver",
|
|
511
|
-
"entry": "adapters/shared/browser-resolver.cjs",
|
|
512
|
-
"kind": "node",
|
|
513
|
-
"purpose": "Choose the extension browser (RECIPE_HARNESS_BROWSER=auto|cft|chrome|<path>; probe-launch Chrome for Testing, cached per machine and version, branded Chrome fallback) and load an unpacked extension over CDP for branded Chrome.",
|
|
514
|
-
"inputs": "resolve --target <checkout> [--launch-method spawn|launch-services] [--display headless|headful] [--recorded <file>] [--out <file>] [--json] | load-unpacked --port <cdp> --path <dist> --profile <dir> [--expect-id <id>] [--open-url <url>]; env RECIPE_HARNESS_BROWSER, RECIPE_HARNESS_CHROME_BIN, RECIPE_HARNESS_CHROME_CANDIDATES, RECIPE_HARNESS_BROWSER_CACHE",
|
|
515
|
-
"outputs": "executable path or resolution JSON on stdout; probe cache under ~/.cache/mm-harness; exit 0/1"
|
|
516
|
-
},
|
|
517
|
-
{
|
|
518
|
-
"id": "lib/browser-cdp",
|
|
519
|
-
"entry": "adapters/shared/browser-cdp.cjs",
|
|
520
|
-
"kind": "lib",
|
|
521
|
-
"purpose": "Browser-level CDP client with per-command deadlines; prove a CDP port belongs to a slot profile, load an unpacked extension (Extensions.loadUnpacked), audit for other user extensions.",
|
|
522
|
-
"inputs": "CDP port, extension dist, owning profile",
|
|
523
|
-
"outputs": "loaded extension id or an error; never loads into a browser it does not own"
|
|
524
|
-
},
|
|
525
501
|
{
|
|
526
502
|
"id": "lib/resolve-slot-ports",
|
|
527
503
|
"entry": "adapters/shared/resolve-slot-ports.mjs",
|
|
@@ -7,11 +7,13 @@
|
|
|
7
7
|
// what makes the runner the single source of truth: edit a runner adapter file and
|
|
8
8
|
// the next launch refreshes every slot from it.
|
|
9
9
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
10
|
+
// Source files count by size + mtime, so it is cheap enough to run on every launch;
|
|
11
|
+
// the only file read is @farmslot/adapter-web's package.json, for its version. No
|
|
12
|
+
// Date.now()/random is used (both would break determinism).
|
|
12
13
|
import fs from 'node:fs';
|
|
13
14
|
import path from 'node:path';
|
|
14
15
|
import crypto from 'node:crypto';
|
|
16
|
+
import { createRequire } from 'node:module';
|
|
15
17
|
import { pathToFileURL } from 'node:url';
|
|
16
18
|
|
|
17
19
|
export function harnessSourceFingerprint(runnerDir, adapter = 'extension') {
|
|
@@ -23,6 +25,18 @@ export function harnessSourceFingerprint(runnerDir, adapter = 'extension') {
|
|
|
23
25
|
for (const rel of sourceDirs) {
|
|
24
26
|
collect(path.join(runnerDir, rel), runnerDir, entries);
|
|
25
27
|
}
|
|
28
|
+
// inject.mjs copies the browser helpers from @farmslot/adapter-web, so its sources
|
|
29
|
+
// are overlay inputs too; label them by package so the install location does not count.
|
|
30
|
+
if (adapter === 'extension') {
|
|
31
|
+
const packageJson = createRequire(path.join(runnerDir, 'package.json')).resolve('@farmslot/adapter-web/package.json');
|
|
32
|
+
const packageRoot = path.dirname(packageJson);
|
|
33
|
+
const packageEntries = [];
|
|
34
|
+
collect(path.join(packageRoot, 'src'), packageRoot, packageEntries);
|
|
35
|
+
entries.push(...packageEntries.map((entry) => `@farmslot/adapter-web/${entry}`));
|
|
36
|
+
// npm tarballs carry fixed mtimes, so an upgrade can keep every size and mtime:
|
|
37
|
+
// the version marks it.
|
|
38
|
+
entries.push(`@farmslot/adapter-web@${JSON.parse(fs.readFileSync(packageJson, 'utf8')).version}`);
|
|
39
|
+
}
|
|
26
40
|
entries.sort();
|
|
27
41
|
return crypto.createHash('sha256').update(entries.join('\n')).digest('hex');
|
|
28
42
|
}
|
|
@@ -46,7 +46,7 @@ import { hostResolverRules, venueHosts } from './lib/mainnet-guard.mjs';
|
|
|
46
46
|
import { assertLaunchNetwork, devServerTestnetDiagnostic } from './lib/readiness.mjs';
|
|
47
47
|
|
|
48
48
|
const SCRIPT_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
49
|
-
const macosFocus = createRequire(import.meta.url)('
|
|
49
|
+
const macosFocus = createRequire(import.meta.url)('@farmslot/adapter-web/macos-focus');
|
|
50
50
|
const HARNESS_ROOT = path.resolve(SCRIPT_DIR, '../..');
|
|
51
51
|
const FIXTURE_STATE_SCRIPT = path.join(HARNESS_ROOT, 'adapters/extension/wallet-fixture-state.cjs');
|
|
52
52
|
|
|
@@ -217,7 +217,7 @@ const FOCUS_OUTCOMES = {
|
|
|
217
217
|
off: () => 'focus handling is off, or no app was captured before the launch',
|
|
218
218
|
};
|
|
219
219
|
|
|
220
|
-
// The one focus check after a launch (
|
|
220
|
+
// The one focus check after a launch (@farmslot/adapter-web/macos-focus): the app in
|
|
221
221
|
// front before the launch gets the front back, once, by its pid, only if one
|
|
222
222
|
// of this slot's browser processes (by owner marker, never by bundle) is in
|
|
223
223
|
// front.
|
|
@@ -36,7 +36,7 @@ import { isAppTopFrameContext, isAppUrl as isExactAppUrl } from './lib/origin.mj
|
|
|
36
36
|
import { LOG_BINDING, REQUEST_BINDING, RESOLVE_FN, pageScriptSource } from './lib/page-script.mjs';
|
|
37
37
|
import { createStrictWallet } from './lib/strict-wallet.mjs';
|
|
38
38
|
|
|
39
|
-
const { captureMacFrontmost, macFocusDisabled } = createRequire(import.meta.url)('
|
|
39
|
+
const { captureMacFrontmost, macFocusDisabled } = createRequire(import.meta.url)('@farmslot/adapter-web/macos-focus');
|
|
40
40
|
|
|
41
41
|
function usage() {
|
|
42
42
|
return 'Usage: wallet-host.mjs --cdp-port <port> --runtime-dir <dir> --signer <extension|injected> --app-origin <url> [--network testnet|mainnet] [--mainnet-hosts <h1,h2>] [--testnet-hosts <h1,h2>] [--extension-id <id>] [--target <checkout> --account <name> --start-chain <id>] [--observe-focus 1] [--window x,y,w,h] [--start-path <path>] [--mm-harness-owner=<marker>]';
|
|
@@ -3,7 +3,7 @@ import { randomUUID } from "node:crypto";
|
|
|
3
3
|
import fs from "node:fs";
|
|
4
4
|
import { createRequire } from "node:module";
|
|
5
5
|
import path from "node:path";
|
|
6
|
-
import { recipeRuntimeDir
|
|
6
|
+
import { recipeRuntimeDir } from "../../paths.js";
|
|
7
7
|
function extensionBrowserDoctorCheck(target) {
|
|
8
8
|
const requireFromTarget = createRequire(path.join(path.resolve(target), "package.json"));
|
|
9
9
|
for (const packageName of ["@playwright/test", "playwright"]) {
|
|
@@ -31,7 +31,7 @@ function extensionBrowserDoctorCheck(target) {
|
|
|
31
31
|
};
|
|
32
32
|
}
|
|
33
33
|
function extensionBrowserResolutionDoctorCheck(target) {
|
|
34
|
-
const resolver =
|
|
34
|
+
const resolver = createRequire(import.meta.url).resolve("@farmslot/adapter-web/browser-resolver");
|
|
35
35
|
const runtime = path.join(path.resolve(target), recipeRuntimeDir());
|
|
36
36
|
const record = path.join(runtime, "browser-resolution.json");
|
|
37
37
|
let profile = process.env.CHROME_USER_DATA_DIR || path.join(runtime, process.env.RECIPE_CHROME_PROFILE_NAME || "chrome-profile");
|
|
@@ -11,7 +11,7 @@ function readRecord(file) {
|
|
|
11
11
|
return null;
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
-
const browserCdp = createRequire(import.meta.url)("
|
|
14
|
+
const browserCdp = createRequire(import.meta.url)("@farmslot/adapter-web/browser-cdp");
|
|
15
15
|
function verifyLaunch(record, cdpPort) {
|
|
16
16
|
const launch = isRecord(record.launch) ? record.launch : {};
|
|
17
17
|
const pid = Number(launch.pid);
|
|
@@ -6,8 +6,8 @@ import { importRecipeHarnessRuntimeBrowserExtension, importRecipeHarnessRuntimeC
|
|
|
6
6
|
import { poolFilesForHost } from "../resolve-slot-ports.js";
|
|
7
7
|
import { profileProcessPids } from "./validation-process-ownership.js";
|
|
8
8
|
const require2 = createRequire(import.meta.url);
|
|
9
|
-
const { detachedLaunchUnprovenPath, hasDetachedLaunchUnproven, shellQuote } = require2(
|
|
10
|
-
"
|
|
9
|
+
const { RUNTIME_NONCE_PREFIX, detachedLaunchUnprovenPath, hasDetachedLaunchUnproven, shellQuote } = require2(
|
|
10
|
+
"@farmslot/adapter-web/chrome-args"
|
|
11
11
|
);
|
|
12
12
|
const {
|
|
13
13
|
CdpSession,
|
|
@@ -697,7 +697,7 @@ ${result.stderr}`);
|
|
|
697
697
|
};
|
|
698
698
|
}
|
|
699
699
|
async function startValidationLaunchSupervisor(cdpPort) {
|
|
700
|
-
const supervisorPath =
|
|
700
|
+
const supervisorPath = require2.resolve("@farmslot/adapter-web/validation-launch-supervisor");
|
|
701
701
|
const child = spawn(process.execPath, [supervisorPath, String(cdpPort)], {
|
|
702
702
|
cwd: runnerDir,
|
|
703
703
|
env: process.env,
|
|
@@ -972,7 +972,7 @@ async function browserEchoesRuntimeNonce(cdpPort, nonce) {
|
|
|
972
972
|
2e3
|
|
973
973
|
);
|
|
974
974
|
if (!Array.isArray(result.arguments)) return false;
|
|
975
|
-
const expected =
|
|
975
|
+
const expected = `${RUNTIME_NONCE_PREFIX}${nonce}`;
|
|
976
976
|
return result.arguments.filter((argument) => argument === expected).length === 1;
|
|
977
977
|
} catch {
|
|
978
978
|
return false;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createRequire } from "node:module";
|
|
2
2
|
const require2 = createRequire(import.meta.url);
|
|
3
3
|
const processOwnership = require2(
|
|
4
|
-
"
|
|
4
|
+
"@farmslot/adapter-web/validation-process-ownership"
|
|
5
5
|
);
|
|
6
6
|
const { profileProcessPids, stopProfileProcesses, stopProfileProcessesSync } = processOwnership;
|
|
7
7
|
export {
|
package/dist/commands/call.js
CHANGED
|
@@ -10,10 +10,9 @@ import {
|
|
|
10
10
|
actionLibraryContextArgs,
|
|
11
11
|
actionExampleCommand,
|
|
12
12
|
describeManifestActions,
|
|
13
|
-
|
|
14
|
-
resolveActionCapabilityMatrix,
|
|
15
|
-
resolveActionCapabilityRefusal
|
|
13
|
+
resolveActionCapabilityMatrix
|
|
16
14
|
} from "./manifest.js";
|
|
15
|
+
import { fuzzyResolveActions, resolveActionCapabilityRefusal } from "@farmslot/recipe-cli";
|
|
17
16
|
import {
|
|
18
17
|
parseArgs,
|
|
19
18
|
isRecord,
|
|
@@ -667,10 +666,7 @@ function normalizeFlagName(flag) {
|
|
|
667
666
|
return flag.replace(/^--/u, "");
|
|
668
667
|
}
|
|
669
668
|
function resolveActionName(shortName, names) {
|
|
670
|
-
|
|
671
|
-
const finalSegment = (name) => name.split(".").pop() ?? name;
|
|
672
|
-
const exactSegment = names.filter((name) => finalSegment(name) === shortName);
|
|
673
|
-
const tier = exactSegment.length > 0 ? exactSegment : names.filter((name) => finalSegment(name).includes(shortName));
|
|
669
|
+
const tier = fuzzyResolveActions(names.map((name) => ({ name })), shortName).map(({ name }) => name);
|
|
674
670
|
if (tier.length === 1) return { status: "ok", resolved: tier[0], candidates: tier };
|
|
675
671
|
if (tier.length > 1) return { status: "ambiguous", resolved: "", candidates: tier };
|
|
676
672
|
return { status: "unknown", resolved: "", candidates: [] };
|
|
@@ -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;
|
|
@@ -6,12 +6,12 @@ import { color } from "../cli-color.js";
|
|
|
6
6
|
import { EXIT } from "./shared.js";
|
|
7
7
|
import { optionFlag, optionString, optionStrings, resolveAdapter, shellQuoteArg } from "./parse-args.js";
|
|
8
8
|
import { actionDomain, describeManifestActions, renderHumanActionCatalog } from "./manifest.js";
|
|
9
|
+
import { shortActionNames } from "@farmslot/recipe-cli";
|
|
9
10
|
import {
|
|
10
11
|
describeRunnableRecipe,
|
|
11
12
|
listRunnableRecipes,
|
|
12
13
|
resolveMetaMaskLibrarySources
|
|
13
14
|
} from "./run-engine.js";
|
|
14
|
-
const finalSegment = (name) => name.split(".").pop() ?? name;
|
|
15
15
|
const recipeDomain = (name) => name.includes(".") ? name.split(".")[0].replaceAll("_", "-") : "general";
|
|
16
16
|
function catalogDescription(description) {
|
|
17
17
|
if (!description) return "";
|
|
@@ -91,27 +91,17 @@ ${out(sort === "domain" ? domainStyles.get(group) : "bold", label)} ${out("dim",
|
|
|
91
91
|
const protocolNames = new Set(getRecipeActionManifestActionNames(manifest));
|
|
92
92
|
const available = describeManifestActions(manifest, actionSources).filter((entry) => protocolNames.has(entry.name)).sort((left, right) => left.name.localeCompare(right.name));
|
|
93
93
|
const described = select(available, actionDomain);
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
name: entry.name,
|
|
106
|
-
short: usableShort,
|
|
107
|
-
description: entry.description,
|
|
108
|
-
fields: entry.fields,
|
|
109
|
-
source: entry.source,
|
|
110
|
-
sourceTier: entry.sourceTier,
|
|
111
|
-
sourceManifest: entry.sourceManifest,
|
|
112
|
-
shadows: entry.shadows
|
|
113
|
-
};
|
|
114
|
-
});
|
|
94
|
+
const shortByName = shortActionNames(available.map((entry) => entry.name));
|
|
95
|
+
const actions = described.map((entry) => ({
|
|
96
|
+
name: entry.name,
|
|
97
|
+
short: shortByName.get(entry.name) ?? null,
|
|
98
|
+
description: entry.description,
|
|
99
|
+
fields: entry.fields,
|
|
100
|
+
source: entry.source,
|
|
101
|
+
sourceTier: entry.sourceTier,
|
|
102
|
+
sourceManifest: entry.sourceManifest,
|
|
103
|
+
shadows: entry.shadows
|
|
104
|
+
}));
|
|
115
105
|
if (json) {
|
|
116
106
|
console.log(JSON.stringify({ schemaVersion: 1, command, action: "list", adapter, actions }, null, 2));
|
|
117
107
|
return EXIT.ok;
|