@deeeed/metamask-harness 0.5.0 → 0.6.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 +51 -20
- package/adapters/core/inject.sh +1 -0
- package/adapters/extension/cleanup.mjs +1 -1
- package/adapters/extension/inject.mjs +4 -1
- package/adapters/extension/launch-browser.cjs +18 -4
- package/adapters/extension/live.sh +14 -2
- package/adapters/extension/refresh-build.sh +2 -2
- package/adapters/extension/sidepanel-toggle.sh +33 -12
- package/adapters/extension/start-watch.sh +2 -2
- package/adapters/extension/wallet-fixture-state.cjs +38 -24
- package/adapters/manifest.json +32 -3
- package/adapters/mobile/inject.sh +1 -0
- package/adapters/mobile/start-metro.sh +1 -1
- package/adapters/mobile/stop-metro.sh +20 -0
- package/adapters/mobile/verify.sh +1 -1
- package/adapters/shared/activate-repo-node.sh +1 -1
- package/adapters/shared/cli-ux.sh +44 -25
- package/adapters/shared/ensure-runner-deps.sh +30 -0
- package/adapters/shared/log-tui.mjs +4 -4
- package/adapters/shared/open-debug.mjs +1 -1
- package/adapters/shared/reap-checkout-metros.sh +53 -0
- package/adapters/shared/recipe-harness-root.mjs +23 -0
- package/adapters/shared/resolve-farmslot-ports-core.mjs +205 -0
- package/adapters/shared/resolve-farmslot-ports.mjs +20 -0
- package/adapters/shared/resolve-farmslot-ports.sh +19 -126
- package/bin/mm-harness +45 -4
- package/dist/adapters/extension/runtime.js +3 -1
- package/dist/adapters/mobile/provision.js +34 -1
- package/dist/adapters/mobile/runtime-decision.js +3 -1
- package/dist/adapters/resolve-farmslot-ports.js +22 -0
- package/dist/adapters/slot-ports.js +18 -29
- package/dist/cli.js +32 -1463
- package/dist/commands/call.js +183 -0
- package/dist/commands/completion-candidates.js +58 -0
- package/dist/commands/doctor.js +101 -0
- package/dist/commands/ensure-ready.js +24 -0
- package/dist/commands/flows.js +62 -0
- package/dist/commands/launch/extension.js +40 -0
- package/dist/commands/{launch.js → launch/index.js} +15 -47
- package/dist/commands/launch/mobile.js +10 -0
- package/dist/commands/manifest.js +72 -0
- package/dist/commands/parse-args.js +189 -0
- package/dist/commands/provision.js +136 -0
- package/dist/commands/resolve-extension.js +23 -0
- package/dist/commands/run-engine.js +341 -0
- package/dist/commands/run.js +217 -0
- package/dist/commands/runtime-decision.js +58 -0
- package/dist/commands/runtime-health.js +25 -0
- package/dist/commands/runtime-launch.js +139 -0
- package/dist/commands/self-test.js +52 -0
- package/dist/commands/stop.js +52 -0
- package/dist/harness.js +8 -48
- package/dist/mm-harness-cli.js +13 -8
- package/docs/CLI-SPEC.md +1 -1
- package/docs/CODE-MAP.md +62 -0
- package/library/README.md +14 -0
- package/library/actions/core/perps/_controller.mjs +1 -1
- package/library/actions/extension/platform/cdp.mjs +2 -2
- package/library/actions/extension/wallet/ensure_unlocked.mjs +7 -1
- package/library/actions/harness-exports.mjs +27 -0
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +14 -2
- package/library/actions/mobile/wallet/setup.mjs +1 -1
- package/package.json +6 -7
- package/src/adapters/core/surface.ts +0 -71
- package/src/adapters/extension/ensure-ready.ts +0 -185
- package/src/adapters/extension/extension-id.ts +0 -107
- package/src/adapters/extension/runtime-decision.ts +0 -445
- package/src/adapters/extension/runtime.ts +0 -407
- package/src/adapters/extension/surface.ts +0 -88
- package/src/adapters/mobile/deps-markers.ts +0 -21
- package/src/adapters/mobile/prepare.ts +0 -246
- package/src/adapters/mobile/provision.ts +0 -594
- package/src/adapters/mobile/runtime-decision.ts +0 -459
- package/src/adapters/mobile/surface.ts +0 -71
- package/src/adapters/slot-ports.ts +0 -165
- package/src/adapters/surface.ts +0 -117
- package/src/adapters.ts +0 -601
- package/src/cli-color.ts +0 -92
- package/src/cli-commands.ts +0 -250
- package/src/cli-version.ts +0 -141
- package/src/cli.ts +0 -2091
- package/src/commands/debug.ts +0 -65
- package/src/commands/fixtures.ts +0 -198
- package/src/commands/launch.ts +0 -470
- package/src/commands/logs.ts +0 -99
- package/src/commands/shared.ts +0 -235
- package/src/commands/update.ts +0 -316
- package/src/completions-cache.ts +0 -86
- package/src/doctor.ts +0 -215
- package/src/harness.ts +0 -797
- package/src/heal-bounds.ts +0 -198
- package/src/index.ts +0 -15
- package/src/leaf-invoke.ts +0 -28
- package/src/live-adapter-contract.ts +0 -274
- package/src/manifest.ts +0 -47
- package/src/mm-harness-cli.ts +0 -655
- package/src/paths.ts +0 -198
- package/src/progress.ts +0 -117
- package/src/recording-target.ts +0 -147
- package/src/run-recording.ts +0 -329
- package/src/runner.ts +0 -108
- package/src/types.ts +0 -57
package/CHANGELOG.md
CHANGED
|
@@ -1,13 +1,60 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
-
## 0.
|
|
3
|
+
## 0.6.0 - 2026-07-06
|
|
4
4
|
|
|
5
|
-
Minor release:
|
|
5
|
+
Minor release: CLI hardening (decomposed, single-parser, dist-only) plus a
|
|
6
|
+
fresh-slot validation wave — every fix proven live on untouched -2 slots and
|
|
7
|
+
independently cross-reviewed.
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- **CLI decomposition + dist-only publish** — `src/cli.ts` is parse+dispatch only;
|
|
11
|
+
handlers under `src/commands/<verb>.ts`; one commander parser; the npm tarball
|
|
12
|
+
ships compiled `dist/` without `src/` and `tsx` is a devDependency (plain-node,
|
|
13
|
+
smaller, faster). Source checkouts keep the tsx dev path (`MM_HARNESS_BIN`).
|
|
14
|
+
- `verify` auto-ensures its own overlay (no refusal on a fresh checkout); every
|
|
15
|
+
engine execution resolves the slot's ports/device first (call/run/fixtures get
|
|
16
|
+
the same slot isolation launch has).
|
|
6
17
|
|
|
18
|
+
### Added
|
|
19
|
+
- **`stop` reaps leaked Metro bundlers by checkout** (port-agnostic, path-
|
|
20
|
+
boundary matched), `doctor` surfaces any orphan and teaches the reap.
|
|
21
|
+
- **`launch --url <dapp>`** (extension) opens the dapp in the main tab beside the
|
|
22
|
+
MetaMask sidepanel; `--stop-only` profile release before seed; stable per-checkout
|
|
23
|
+
Chrome profile + runtime-dist; post-open tab cleanup.
|
|
24
|
+
- DX: run-mode + stale-`dist` warning in `bin`; overlay-delegation notice; docs/CODE-MAP.md.
|
|
25
|
+
|
|
26
|
+
### Fixed
|
|
27
|
+
- `launch` targets the slot's own simulator (never the `booted` alias) and boots it;
|
|
28
|
+
slot-port resolution layers rungs so a partial rung never nulls the port; provision
|
|
29
|
+
resolves the simulator from the pool and boots before install.
|
|
30
|
+
- `call` works from published installs (dist bridge, no type-strip crash);
|
|
31
|
+
`ensure_unlocked` fails teaching `fixtures set` on an un-onboarded wallet instead
|
|
32
|
+
of vacuously passing (mobile + extension); `provision --json` usage errors emit the
|
|
33
|
+
standard envelope; deps-not-ready teaches a command that works on a bare checkout.
|
|
34
|
+
## 0.5.1 - 2026-07-05
|
|
35
|
+
|
|
36
|
+
Fresh-install hotfixes found in live validation of 0.5.0.
|
|
37
|
+
|
|
38
|
+
### Fixed
|
|
39
|
+
- **First launch on a provisioned slot targets the slot's own simulator** — the mobile resolution ladder now reads the `runway-provision.json` baseline (simulator name/udid, watcher port, slot id) when `agentic-runtime.json` doesn't exist yet, and boots that simulator. Previously a provisioned-but-unprepared slot degraded to the simctl `booted` alias, missed the installed dev client, and guessed the wrong Metro port.
|
|
40
|
+
- **`call` works from published installs** — library actions load harness helpers through a dist-preferring bridge instead of importing `src/index.ts` (node refuses to type-strip `.ts` under `node_modules`). The bridge is deliberately narrow (paths + run-recording) to avoid an import cycle through `adapters.ts`.
|
|
41
|
+
- **deps-not-ready guidance matches the caller's state** — on a checkout without node_modules the taught command now leads with `yarn install --immutable` (yarn cannot run scripts at all on a bare checkout), and always ends with the launch re-run.
|
|
42
|
+
|
|
43
|
+
## Unreleased
|
|
44
|
+
|
|
45
|
+
### Fixed
|
|
46
|
+
- **`provision --json` adapter-detection failures emit the standard error envelope** — usage errors now match `doctor`/`launch` (envelope on stdout with `error.userAction`; stderr stays human-only). Previously `--json` callers got empty stdout.
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
- **CLI decomposition** — `src/cli.ts` is parse+dispatch only; handlers live under `src/commands/<verb>.ts`; `provision` routes through `src/commands/provision.ts` (not overlay harness dispatch); launch composition splits under `src/commands/launch/`; commander owns per-command `--help` (no hand-rolled interception).
|
|
50
|
+
- **Published tarball is dist-only** — `files` no longer ships `src/`; `tsx` is a devDependency; `bin/mm-harness` and overlay leaves resolve `dist/*.js` (teaching error if a published install lacks `dist/`). Library actions keep importing `library/actions/harness-exports.mjs` (dist-preferring narrow bridge from 0.5.1); package exports remain for external consumers (`dist/index.js` published; `src/index.ts` when `--conditions=development` and dist is absent).
|
|
51
|
+
- **resolve-farmslot-ports is TypeScript-first** — pool/context/formula port resolution lives in self-contained `adapters/shared/resolve-farmslot-ports-core.mjs` (works from runner and injected overlay copies); `src/adapters/resolve-farmslot-ports.ts` re-exports it for `slot-ports.ts`. `resolve-farmslot-ports.mjs` is the node leaf; `resolve-farmslot-ports.sh` remains a thin bash-compat wrapper for sourced callers.
|
|
52
|
+
|
|
53
|
+
## 0.5.0 - 2026-07-05
|
|
54
|
+
Minor release: thin Runway provisioning (harness-owned slot provisioning with a shared artifact cache) plus compiled-dist packaging.
|
|
7
55
|
### Added
|
|
8
56
|
- **`mm-harness provision runway`** — provisions a mobile slot with exactly: resolve the latest Runway (expo-dev-build) artifact, download it ONCE into a shared machine cache (`~/.cache/metamask-harness/runway/<runId>/`, sha256+size integrity, corrupt entries re-downloaded, never a slot failure), create the simulator if missing, install the dev client on it. Zero yarn/deps/Metro — those stay dispatch-time (lazy, presence-authoritative readiness unchanged). `install --runway` is the overlay-path equivalent (covered by its own contract test). Baseline recorded so doctor reports provisioned-but-deps-pending as a normal state.
|
|
9
57
|
- **Compiled `dist/` for published installs** — the npm tarball ships esbuild-compiled JS; `npm i -g` installs run the dist entry (~0.21s cold vs ~0.37s tsx). Source checkouts without `dist/` keep the tsx dev path; `bin/mm-harness` prefers dist when present. Packaging contract test (pack → global install → dist entry runs).
|
|
10
|
-
|
|
11
58
|
### Changed
|
|
12
59
|
- Runway provisioning callers (farm installer, mobile-farm `runway` prepare profile) delegate to the harness; the profile no longer runs a deps phase and a contract test enforces that.
|
|
13
60
|
|
|
@@ -43,17 +90,7 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
|
|
|
43
90
|
- **Colored grouped help with DEV OVERRIDE banner and SLOT line**; `mmdev-harness` completions; outcome-stating launch summaries with a `[dev]` marker.
|
|
44
91
|
- **docs/UX-PRINCIPLES.md** — the CLI interaction contract (human surface rules; `--json` envelope purity is exempt and inviolable).
|
|
45
92
|
|
|
46
|
-
## Unreleased
|
|
47
|
-
|
|
48
|
-
### Added
|
|
49
|
-
- Add `mm-harness provision` for thin mobile Runway artifact provisioning with a shared integrity-checked cache and simulator install path.
|
|
50
|
-
- Add `mm-harness provision runway ios --resolve-only` to return Runway run/revision/artifact metadata without touching simulator, cache, download, install, or baseline state.
|
|
51
|
-
- **Dist publish path** — `npm run build` compiles `src/` to `dist/`; `npm pack` / global install runs the compiled CLI entry (`dist/mm-harness-cli.js`) while a source checkout without `dist/` keeps the tsx+src dev path. Contracts: `packaging-dist-install`, `packaging-dev-checkout`.
|
|
52
|
-
|
|
53
93
|
### Fixed
|
|
54
|
-
- `mm-harness provision runway ios` now routes through the same Runway provisioning surface as `install --runway`, so overlay dispatch accepts the compatibility command instead of rejecting it as an unsupported harness action.
|
|
55
|
-
- Runway provisioning now accepts farm context flags (`--slot`, `--watcher-port`, `--runtime-dir`), records slot/watcher/runtime-dir baselines correctly, ignores stale provision baselines unless the app is still installed on the simulator, and emits rerunnable `Next:` commands from the actual invocation.
|
|
56
|
-
- Runway iOS artifact extraction now handles the live nested zip layout and app bundle names other than `MetaMask.app` while keeping the cached app directory integrity hash/size contract.
|
|
57
94
|
- **Mobile preflight no longer re-runs `yarn setup` on every launch** — the deps readiness decision escalated an mtime hint to `stale` and ran the FULL `yarn setup` (submodules + inpage bridge + jetify + pods) inside the launch phase on every invocation in orchestrated (farmslot) contexts. `depsCheck` reports `stale` for a no-baseline checkout whenever a manifest is newer than the install markers; the orchestrator's git phase refreshes tracked-file mtimes on every sync while its deps phase leaves `node_modules` untouched, so "manifest newer than markers" is normal and not proof of drift — yet it duplicated the orchestrator's deps work and turned a fast preflight into 15+ minutes (the behavior that also looked like a hang before 0.3.8's streaming). **Presence is now authoritative:** without a recorded baseline an mtime-only `stale` is trusted as `current` (with a one-line stderr warning), so a churned-but-present checkout launches with zero `yarn` in both tiers. Genuine drift is still caught by the recorded-baseline fingerprint. `mm-harness launch` (quick tier / no `--build`) also dropped `preflightMode` before deciding, so the fast contract never took effect; it is threaded through now.
|
|
58
95
|
- **Fast tier never installs; genuine gaps teach the orchestrator** — in the fast tier (quick launch / orchestrated), deps that genuinely need work (absent markers, an absent required package, a fingerprint drift, or Metro unable to resolve a module) return a teaching block naming the orchestrator deps/prepare phase (plus the standalone `yarn setup:expo` / `launch --build` remedies) instead of an implicit setup — deps are the orchestrator's contract. The standalone/full tier still installs.
|
|
59
96
|
- **Deps baseline recorded the instant a setup leaf succeeds** — `prepareMobile` records the deps fingerprint baseline immediately after any successful `yarn-setup`, unconditionally (previously only a later `record: true` re-decide did, gated on the original decision being `install` and on `RECIPE_UP_INSTALL_ATTEMPTED`). Genuine-drift detection is fingerprint-based and self-sustaining after one good install.
|
|
@@ -139,7 +176,7 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
|
|
|
139
176
|
- Core run teaches the correct next step when no account is set.
|
|
140
177
|
- Resolved CodeQL alerts (insecure temp dir, file-access-to-http).
|
|
141
178
|
|
|
142
|
-
##
|
|
179
|
+
## 0.2.0 - 2026-07-03
|
|
143
180
|
|
|
144
181
|
### Added
|
|
145
182
|
|
|
@@ -150,12 +187,6 @@ Minor release: the adapter-surface architecture plus the day's feature wave (0.3
|
|
|
150
187
|
### Changed
|
|
151
188
|
|
|
152
189
|
- **Teaching errors are type-enforced**: `usageOut` now requires a `userAction` escape argument, so a teaching error without a reachable next step no longer compiles. The repo-type detection failure teaches BOTH escapes (`cd into a checkout / pass --target <path>` and `--adapter <mobile|extension|core>`) consistently across `doctor`, `launch`, `verify`, and the overlay path.
|
|
153
|
-
- **Repository reorganized into `src/`, `adapters/`, `library/` domains**: all TypeScript now lives under `src/` (shared CLI/core plus typed per-adapter logic in `src/adapters/<adapter>/`); everything that executes against or is injected into the app runtime lives under `adapters/` (`adapters/<adapter>/` per-app glue, `adapters/shared/` cross-adapter OS-glue, `adapters/porcelain/` composed entrypoints, `adapters/*/overlay/` the only injected payload); `library/` holds composable vocabulary only. The `runner/` and `orchestration/` trees no longer exist. The surface doctor moved to `scripts/adapter-surface-doctor.mjs` with its index at `adapters/manifest.json`. Placement rules (R1–R11) are documented in `AGENTS.md`. This is a path-only reorganization; runtime behavior is unchanged.
|
|
154
|
-
|
|
155
|
-
## 0.2.0 - 2026-07-03
|
|
156
|
-
|
|
157
|
-
### Changed
|
|
158
|
-
|
|
159
190
|
- **Docs realigned to the shipped single-bin surface**: `README.md`, `docs/harness-cli.md` (reduced to a pointer at `CLI-SPEC.md`), `docs/recipe-libraries.md`, `docs/extension-runtime-commands.md`, `docs/package-boundaries.md`, and `docs/live-adapter-contract.md` no longer teach the retired `metamask-recipe`/`mm-recipe`/`mme-recipe` names or "planned/stub" daily-loop claims — every command example is `mm-harness`. (`docs/MENTAL-MODEL.md` and `docs/CLI-SPEC.md` retain the old names by design: they are the before→after migration contract.)
|
|
160
191
|
- **Published package trimmed to runtime needs**: the `files` field drops dev-only helpers (`scripts/check.mjs`, `scripts/quality/`, `scripts/link-local-farmslot.mjs`, `scripts/validate-action-e2e-artifacts.mjs`) and repo-dev docs (`AGENTS.md`, `CLAUDE.md`), keeping `bin`, `runner`, `orchestration`, `library`, the runtime completion scripts, `docs`, `README.md`, and `CHANGELOG.md`. The `files` field now also negates `scripts/README.md` (dev-only) so the published tarball no longer ships it alongside the two runtime completion scripts.
|
|
161
192
|
- **Comment-hygiene guard extended**: `scripts/quality/comment-hygiene.mjs` now also fails on presentation vocabulary (`verb`/`daily`) in comments and object-property keys (previously only filenames and declarations), enforced with word boundaries so `verbatim`/`verbose` and user-facing help titles like `DAILY LOOP` stay exempt. Source comments and the `verb: 'rebuild'` progress field were reworded/renamed to `command`.
|
package/adapters/core/inject.sh
CHANGED
|
@@ -88,6 +88,7 @@ install_v1_runner_assets() {
|
|
|
88
88
|
if [ -n "$METAMASK_RUNNER_PROTOCOL_ROOT" ]; then
|
|
89
89
|
printf 'export FARMSLOT_ROOT=${FARMSLOT_ROOT:-%s}\n' "$runner_protocol_root_q"
|
|
90
90
|
fi
|
|
91
|
+
printf 'if [ -n "${MM_HARNESS_BIN:-}" ] && [ "$MM_HARNESS_BIN" != %s ]; then echo "mm-harness overlay: %s: %s (reinstall to repoint: mm-harness install)" >&2; fi\n' "$runner_exec_q" 'delegating to the runner pinned at install time' "$runner_exec_q"
|
|
91
92
|
printf 'exec %s "$@"\n' "$runner_exec_q"
|
|
92
93
|
} > "$HARNESS_DIR/runner/bin/mm-harness"
|
|
93
94
|
chmod +x "$HARNESS_DIR/runner/bin/mm-harness"
|
|
@@ -18,7 +18,7 @@ import fs from 'node:fs';
|
|
|
18
18
|
import path from 'node:path';
|
|
19
19
|
import { spawnSync } from 'node:child_process';
|
|
20
20
|
import process from 'node:process';
|
|
21
|
-
import { recipeHarnessRoot } from '
|
|
21
|
+
import { recipeHarnessRoot } from '../shared/recipe-harness-root.mjs';
|
|
22
22
|
|
|
23
23
|
function usage() { console.error('Usage: cleanup-extension-harness.mjs [--target <metamask-extension>]'); }
|
|
24
24
|
let target = process.cwd();
|
|
@@ -23,7 +23,7 @@ import fs from 'node:fs';
|
|
|
23
23
|
import path from 'node:path';
|
|
24
24
|
import { spawnSync } from 'node:child_process';
|
|
25
25
|
import process from 'node:process';
|
|
26
|
-
import { recipeHarnessRoot } from '
|
|
26
|
+
import { recipeHarnessRoot } from '../shared/recipe-harness-root.mjs';
|
|
27
27
|
|
|
28
28
|
function usage() {
|
|
29
29
|
console.error('Usage: inject-extension-harness.mjs [--target <metamask-extension>] [--no-git-exclude]');
|
|
@@ -72,6 +72,7 @@ const delegate = [
|
|
|
72
72
|
'#!/usr/bin/env bash',
|
|
73
73
|
'set -euo pipefail',
|
|
74
74
|
protocolRoot ? `export FARMSLOT_ROOT=\${FARMSLOT_ROOT:-${shellQuote(protocolRoot)}}` : null,
|
|
75
|
+
`if [ -n "\${MM_HARNESS_BIN:-}" ] && [ "\$MM_HARNESS_BIN" != ${shellQuote(path.join(runnerDir, 'bin/mm-harness'))} ]; then echo "mm-harness overlay: delegating to the runner pinned at install time: ${shellQuote(path.join(runnerDir, 'bin/mm-harness'))} (reinstall to repoint: mm-harness install)" >&2; fi`,
|
|
75
76
|
`exec ${shellQuote(path.join(runnerDir, 'bin/mm-harness'))} "$@"`,
|
|
76
77
|
].filter(Boolean).join('\n') + '\n';
|
|
77
78
|
fs.writeFileSync(path.join(harnessDir, 'runner/bin/mm-harness'), delegate, { mode: 0o755 });
|
|
@@ -108,6 +109,8 @@ copyFile(path.join(runnerDir, 'adapters/extension/ensure-browser.sh'), path.join
|
|
|
108
109
|
copyFile(path.join(runnerDir, 'adapters/shared/harness-path.sh'), path.join(harnessDir, 'scripts/lib/harness-path.sh'));
|
|
109
110
|
copyFile(path.join(runnerDir, 'adapters/shared/activate-repo-node.sh'), path.join(harnessDir, 'scripts/lib/activate-repo-node.sh'));
|
|
110
111
|
copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports.sh'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports.sh'));
|
|
112
|
+
copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports.mjs'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports.mjs'));
|
|
113
|
+
copyFile(path.join(runnerDir, 'adapters/shared/resolve-farmslot-ports-core.mjs'), path.join(harnessDir, 'scripts/lib/resolve-farmslot-ports-core.mjs'));
|
|
111
114
|
copyFile(path.join(runnerDir, 'adapters/shared/path-defaults.json'), path.join(harnessDir, 'scripts/lib/path-defaults.json'));
|
|
112
115
|
copyFile(path.join(runnerDir, 'adapters/shared/json-field.sh'), path.join(harnessDir, 'scripts/lib/json-field.sh'));
|
|
113
116
|
copyFile(path.join(runnerDir, 'adapters/shared/log-tui.mjs'), path.join(harnessDir, 'scripts/lib/log-tui.mjs'));
|
|
@@ -39,9 +39,13 @@ if (!Number.isInteger(cdpPort) || cdpPort <= 0) {
|
|
|
39
39
|
for (const key of ['chrome-bin', 'profile', 'extension-dir', 'chrome-log', 'chrome-pid']) {
|
|
40
40
|
if (!args[key]) throw new Error(`Missing --${key}`);
|
|
41
41
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
// --stop-only releases the profile before the dist snapshot exists on a first
|
|
43
|
+
// run, so launch-input validation applies only to a real launch.
|
|
44
|
+
if (args['stop-only'] === undefined) {
|
|
45
|
+
if (!fs.existsSync(args['chrome-bin'])) throw new Error(`Chrome binary not found: ${args['chrome-bin']}`);
|
|
46
|
+
if (!fs.existsSync(path.join(args['extension-dir'], 'manifest.json'))) {
|
|
47
|
+
throw new Error(`Extension dist manifest not found: ${path.join(args['extension-dir'], 'manifest.json')}`);
|
|
48
|
+
}
|
|
45
49
|
}
|
|
46
50
|
|
|
47
51
|
// Isolation guard: a standalone launch must use a per-checkout profile, never a
|
|
@@ -72,6 +76,14 @@ if (previousPid !== null && processLoadsProfile(previousPid, args.profile)) owne
|
|
|
72
76
|
terminatePids([...ownedPids]);
|
|
73
77
|
removeProfileSingletonLocks(args.profile);
|
|
74
78
|
|
|
79
|
+
// --stop-only: release the profile (owned Chrome terminated, singleton locks
|
|
80
|
+
// cleared) without launching. The prefill step needs the profile's LevelDB
|
|
81
|
+
// unlocked BEFORE it writes; a prior run's Chrome would hold the lock and the
|
|
82
|
+
// write fails with "Database is not open".
|
|
83
|
+
if (args['stop-only'] !== undefined) {
|
|
84
|
+
process.exit(0);
|
|
85
|
+
}
|
|
86
|
+
|
|
75
87
|
const logFd = fs.openSync(args['chrome-log'], 'a');
|
|
76
88
|
let child;
|
|
77
89
|
try {
|
|
@@ -88,7 +100,9 @@ try {
|
|
|
88
100
|
'--disable-features=ExtensionContentVerification,DisableLoadExtensionCommandLineSwitch',
|
|
89
101
|
`--disable-extensions-except=${args['extension-dir']}`,
|
|
90
102
|
`--load-extension=${args['extension-dir']}`,
|
|
91
|
-
'
|
|
103
|
+
// Initial tab: the caller's dapp URL when provided (sidepanel demos pair the
|
|
104
|
+
// extension sidepanel with a dapp in the main tab), else the extensions page.
|
|
105
|
+
args['start-url'] || 'chrome://extensions/',
|
|
92
106
|
], {
|
|
93
107
|
detached: true,
|
|
94
108
|
env: {
|
|
@@ -50,6 +50,7 @@ while [ "$#" -gt 0 ]; do
|
|
|
50
50
|
--artifacts-dir) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; ARTIFACTS="$2"; shift 2 ;;
|
|
51
51
|
--prepare-cmd) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; PREPARE_CMD="$2"; shift 2 ;;
|
|
52
52
|
--launch-existing-dist) LAUNCH_EXISTING_DIST=true; shift ;;
|
|
53
|
+
--start-url) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; START_URL="$2"; shift 2 ;;
|
|
53
54
|
--start-watch|--start-test-watch) START_WATCH=true; LAUNCH_EXISTING_DIST=true; shift ;;
|
|
54
55
|
--dist-dir) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; DIST_DIR="$2"; shift 2 ;;
|
|
55
56
|
--chrome-user-data-dir) [ "$#" -ge 2 ] || { echo "Missing value for $1" >&2; exit 2; }; CHROME_USER_DATA_DIR="$2"; shift 2 ;;
|
|
@@ -114,8 +115,15 @@ fi
|
|
|
114
115
|
|
|
115
116
|
if $LAUNCH_EXISTING_DIST && [ -z "$PREPARE_CMD" ]; then
|
|
116
117
|
DIST_ABS="$TARGET/$DIST_DIR"
|
|
117
|
-
|
|
118
|
-
|
|
118
|
+
# Stable beside the profile: the reused profile registers the unpacked
|
|
119
|
+
# extension by PATH; a per-run snapshot path gets pruned and the next launch
|
|
120
|
+
# loads nothing ("No EIP-6963 Provider Detected").
|
|
121
|
+
RUNTIME_DIST_ABS="$TARGET/temp/recipe/runtime/runtime-dist"
|
|
122
|
+
# Stable per-checkout profile: successive runs recognize their own Chrome
|
|
123
|
+
# (ownership guard matches on --user-data-dir), take it over cleanly, and the
|
|
124
|
+
# seeded wallet persists across relaunches. A per-run profile made every
|
|
125
|
+
# relaunch "foreign" and let live/ pruning delete profiles still in use.
|
|
126
|
+
PROFILE_ABS="${CHROME_USER_DATA_DIR:-$TARGET/temp/recipe/runtime/chrome-profile}"
|
|
119
127
|
FIXTURE_STATE_ABS="$ARTIFACTS/fixture-state.json"
|
|
120
128
|
FIXTURE_VALIDATION_ABS="$ARTIFACTS/logs/fixture-account-parity.json"
|
|
121
129
|
# Wallet fixture resolution chain + provenance live in seed-fixture.sh.
|
|
@@ -233,6 +241,7 @@ NODE
|
|
|
233
241
|
if $START_WATCH; then
|
|
234
242
|
prepare_parts+=("bash ${quoted_start_watch} --runtime-dir ${quoted_runtime_dir} --runner-bin ${quoted_runner}")
|
|
235
243
|
fi
|
|
244
|
+
prepare_parts+=("node ${quoted_chrome_launcher} --stop-only 1 --chrome-bin ${quoted_chrome} --profile ${quoted_profile} --cdp-port ${CDP_PORT} --extension-dir ${quoted_runtime_dist} --chrome-log ${quoted_chrome_log} --chrome-pid ${quoted_chrome_pid}")
|
|
236
245
|
prepare_parts+=("bash ${quoted_snapshot_dist} --dist ${quoted_dist} --runtime-dist ${quoted_runtime_dist}")
|
|
237
246
|
# Optional A/B feature-flag pinning: patch the ephemeral snapshot manifest so
|
|
238
247
|
# manifest._flags wins over the fetched ClientConfigApi value. No-op unless
|
|
@@ -249,6 +258,9 @@ NODE
|
|
|
249
258
|
prepare_parts+=("bash ${quoted_seed_fixture} prefill --target ${quoted_target} --fixture ${quoted_wallet_fixture} --state ${quoted_fixture_state} --profile ${quoted_profile} --extension-dir ${quoted_runtime_dist} --extension-id-file ${quoted_extension_id_file}")
|
|
250
259
|
fi
|
|
251
260
|
chrome_launch_cmd="node ${quoted_chrome_launcher} --chrome-bin ${quoted_chrome} --profile ${quoted_profile} --cdp-port ${CDP_PORT} --extension-dir ${quoted_runtime_dist} --chrome-log ${quoted_chrome_log} --chrome-pid ${quoted_chrome_pid}"
|
|
261
|
+
if [ -n "${START_URL:-}" ]; then
|
|
262
|
+
chrome_launch_cmd="$chrome_launch_cmd --start-url $(printf '%q' "$START_URL")"
|
|
263
|
+
fi
|
|
252
264
|
prepare_parts+=("$chrome_launch_cmd")
|
|
253
265
|
prepare_parts+=("for i in {1..60}; do curl -fsS --max-time 1 http://127.0.0.1:${CDP_PORT}/json/version >/dev/null 2>&1 && break; sleep 1; done; curl -fsS --max-time 1 http://127.0.0.1:${CDP_PORT}/json/version >/dev/null")
|
|
254
266
|
if [ -n "$WALLET_FIXTURE_ABS" ]; then
|
|
@@ -97,7 +97,7 @@ elif command -v activate_repo_node >/dev/null 2>&1; then
|
|
|
97
97
|
exit 1
|
|
98
98
|
}
|
|
99
99
|
else
|
|
100
|
-
echo "[refresh] activate-repo-node.sh missing from harness; run:
|
|
100
|
+
echo "[refresh] activate-repo-node.sh missing from harness; run: mm-harness install" >&2
|
|
101
101
|
exit 1
|
|
102
102
|
fi
|
|
103
103
|
|
|
@@ -186,5 +186,5 @@ kill "$BUILD_PID" 2>/dev/null || true
|
|
|
186
186
|
wait "$BUILD_PID" 2>/dev/null || true
|
|
187
187
|
trap - EXIT
|
|
188
188
|
|
|
189
|
-
echo "[refresh] Build refreshed and frozen — run:
|
|
189
|
+
echo "[refresh] Build refreshed and frozen — run: mm-harness launch"
|
|
190
190
|
echo "[refresh] Log kept at: $LOG"
|
|
@@ -98,10 +98,12 @@ resolve_ext_id() {
|
|
|
98
98
|
printf '%s\n' "$EXT_ID"
|
|
99
99
|
return
|
|
100
100
|
fi
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
101
|
+
for idf in "$AGENT_DIR/extension.id" "$REPO/temp/recipe/runtime/extension.id"; do
|
|
102
|
+
if [ -f "$idf" ]; then
|
|
103
|
+
tr -d '[:space:]' < "$idf"
|
|
104
|
+
return
|
|
105
|
+
fi
|
|
106
|
+
done
|
|
105
107
|
json_list | python3 -c "import json,re,sys; d=json.load(sys.stdin); ids=[]; [ids.extend(re.findall(r'^chrome-extension://([^/]+)/', t.get('url',''))) for t in d]; print(ids[0] if ids else '')"
|
|
106
108
|
}
|
|
107
109
|
|
|
@@ -239,15 +241,34 @@ const { chromium } = require('playwright');
|
|
|
239
241
|
document.getElementById('__recipe_open_sidepanel__')?.remove();
|
|
240
242
|
});
|
|
241
243
|
|
|
242
|
-
|
|
244
|
+
// The wallet now lives in the sidepanel: leftover extension home tabs would
|
|
245
|
+
// show it twice and steal the foreground from the dapp tab. Close every
|
|
246
|
+
// non-sidepanel extension page (not just one this helper created), then
|
|
247
|
+
// bring the first http(s) tab back to front.
|
|
248
|
+
for (const candidate of context.pages()) {
|
|
249
|
+
if (
|
|
250
|
+
candidate.url().startsWith(`chrome-extension://${extId}/`) &&
|
|
251
|
+
!candidate.url().includes('/sidepanel.html')
|
|
252
|
+
) {
|
|
253
|
+
try {
|
|
254
|
+
await candidate.close();
|
|
255
|
+
} catch (error) {
|
|
256
|
+
console.warn(
|
|
257
|
+
`[sidepanel] extension page close failed after successful open: ${
|
|
258
|
+
error && error.message ? error.message : error
|
|
259
|
+
}`,
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
const dappPage = context
|
|
265
|
+
.pages()
|
|
266
|
+
.find((candidate) => candidate.url().startsWith('http://') || candidate.url().startsWith('https://'));
|
|
267
|
+
if (dappPage) {
|
|
243
268
|
try {
|
|
244
|
-
await
|
|
245
|
-
} catch
|
|
246
|
-
|
|
247
|
-
`[sidepanel] helper page close failed after successful open: ${
|
|
248
|
-
error && error.message ? error.message : error
|
|
249
|
-
}`,
|
|
250
|
-
);
|
|
269
|
+
await dappPage.bringToFront();
|
|
270
|
+
} catch {
|
|
271
|
+
// focus is cosmetic; the panel is already open
|
|
251
272
|
}
|
|
252
273
|
}
|
|
253
274
|
await browser.close();
|
|
@@ -82,7 +82,7 @@ elif command -v activate_repo_node >/dev/null 2>&1; then
|
|
|
82
82
|
exit 1
|
|
83
83
|
}
|
|
84
84
|
else
|
|
85
|
-
echo "[recipe-harness] activate-repo-node.sh missing from harness; run:
|
|
85
|
+
echo "[recipe-harness] activate-repo-node.sh missing from harness; run: mm-harness install" >&2
|
|
86
86
|
exit 1
|
|
87
87
|
fi
|
|
88
88
|
if [ -z "$WATCHER_PORT" ]; then
|
|
@@ -233,7 +233,7 @@ for _lib in "$SCRIPT_DIR/lib" "$SCRIPT_DIR/../shared"; do
|
|
|
233
233
|
done
|
|
234
234
|
unset _lib
|
|
235
235
|
echo "[recipe-harness] Starting yarn start (watcher-port=${WATCHER_PORT:-default})"
|
|
236
|
-
echo "[recipe-harness] compact build view - full log: $watch_log (
|
|
236
|
+
echo "[recipe-harness] compact build view - full log: $watch_log (mm-harness logs --full)"
|
|
237
237
|
started_pid=""
|
|
238
238
|
# Launch the watcher detached writing to the log; the tmux window is a read-only tail viewer.
|
|
239
239
|
path_prefix="${REPO_NODE_PATH_PREFIX:-}"
|
|
@@ -668,8 +668,8 @@ async function detectExtension(context, extensionDir, extensionIdFile) {
|
|
|
668
668
|
const page = await context.newPage();
|
|
669
669
|
try {
|
|
670
670
|
await page.goto(`chrome-extension://${candidate.id}/home.html`, {
|
|
671
|
-
waitUntil: '
|
|
672
|
-
timeout:
|
|
671
|
+
waitUntil: 'domcontentloaded',
|
|
672
|
+
timeout: 30000,
|
|
673
673
|
});
|
|
674
674
|
if (page.url().startsWith('chrome-error://')) {
|
|
675
675
|
throw new Error('candidate resolved to chrome-error page');
|
|
@@ -726,34 +726,48 @@ async function waitForWalletScreen(page) {
|
|
|
726
726
|
return { state: 'unknown', selector: null };
|
|
727
727
|
}
|
|
728
728
|
|
|
729
|
+
async function attemptUnlock(page, password) {
|
|
730
|
+
// Selector ladder: current testids first, then the generic selectors the CDP
|
|
731
|
+
// unlock action uses (proven against the same build). A freshly seeded vault
|
|
732
|
+
// can also still be initializing, so a single attempt is not conclusive —
|
|
733
|
+
// the caller retries.
|
|
734
|
+
const filled = await page.evaluate((pw) => {
|
|
735
|
+
const input =
|
|
736
|
+
document.querySelector('[data-testid="unlock-password"]') ??
|
|
737
|
+
document.querySelector('input[type="password"]');
|
|
738
|
+
if (!input) return false;
|
|
739
|
+
const setter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, 'value').set;
|
|
740
|
+
setter.call(input, pw);
|
|
741
|
+
input.dispatchEvent(new Event('input', { bubbles: true }));
|
|
742
|
+
input.dispatchEvent(new Event('change', { bubbles: true }));
|
|
743
|
+
return true;
|
|
744
|
+
}, password);
|
|
745
|
+
if (!filled) return false;
|
|
746
|
+
return page.evaluate(() => {
|
|
747
|
+
const button =
|
|
748
|
+
document.querySelector('[data-testid="unlock-submit"]') ??
|
|
749
|
+
document.querySelector('button[type="submit"]') ??
|
|
750
|
+
Array.from(document.querySelectorAll('button')).find((candidate) => /unlock/iu.test(candidate.innerText || candidate.textContent || ''));
|
|
751
|
+
if (!button) return false;
|
|
752
|
+
button.click();
|
|
753
|
+
return true;
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
|
|
729
757
|
async function unlockIfNeeded(page, password) {
|
|
730
758
|
let state = await waitForWalletScreen(page);
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
await page
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
if (!message.includes('Timeout')) throw error;
|
|
738
|
-
const clicked = await page.evaluate(() => {
|
|
739
|
-
const button = document.querySelector('[data-testid="unlock-submit"]');
|
|
740
|
-
if (!button) return false;
|
|
741
|
-
button.click();
|
|
742
|
-
return true;
|
|
743
|
-
});
|
|
744
|
-
if (!clicked) throw new Error(`Unlock submit timed out and DOM fallback could not find the button: ${message}`);
|
|
745
|
-
}
|
|
746
|
-
const deadline = Date.now() + 45000;
|
|
747
|
-
while (Date.now() < deadline) {
|
|
748
|
-
state = await waitForWalletScreen(page);
|
|
749
|
-
if (state.state === 'unlocked') {
|
|
750
|
-
break;
|
|
751
|
-
}
|
|
759
|
+
const deadline = Date.now() + 90000;
|
|
760
|
+
while (state.state !== 'unlocked' && state.state !== 'onboarding' && Date.now() < deadline) {
|
|
761
|
+
if (state.state === 'locked') {
|
|
762
|
+
await attemptUnlock(page, password);
|
|
763
|
+
await page.waitForTimeout(1500);
|
|
764
|
+
} else {
|
|
752
765
|
await page.waitForTimeout(750);
|
|
753
766
|
}
|
|
767
|
+
state = await waitForWalletScreen(page);
|
|
754
768
|
}
|
|
755
769
|
if (state.state !== 'unlocked') {
|
|
756
|
-
throw new Error(`Wallet did not reach unlocked home screen after fixture seeding (state=${state.state})`);
|
|
770
|
+
throw new Error(`Wallet did not reach unlocked home screen after fixture seeding (state=${state.state}). Next: mm-harness call ensure_unlocked # then relaunch`);
|
|
757
771
|
}
|
|
758
772
|
return state;
|
|
759
773
|
}
|
package/adapters/manifest.json
CHANGED
|
@@ -308,12 +308,41 @@
|
|
|
308
308
|
},
|
|
309
309
|
{
|
|
310
310
|
"id": "lib/resolve-farmslot-ports",
|
|
311
|
-
"entry": "adapters/shared/resolve-farmslot-ports.
|
|
312
|
-
"kind": "
|
|
311
|
+
"entry": "adapters/shared/resolve-farmslot-ports.mjs",
|
|
312
|
+
"kind": "node",
|
|
313
313
|
"purpose": "Resolve CDP + watcher ports from farmslot pool JSON by repo path, else 6660+N / 9010+N.",
|
|
314
|
-
"inputs": "resolve_extension_runtime_ports <repo>",
|
|
314
|
+
"inputs": "resolve-farmslot-ports.mjs resolve_extension_runtime_ports <repo>",
|
|
315
315
|
"outputs": "CDP_PORT / WATCHER_PORT / SLOT_ID lines on stdout"
|
|
316
316
|
},
|
|
317
|
+
{
|
|
318
|
+
"id": "lib/resolve-farmslot-ports-core",
|
|
319
|
+
"entry": "adapters/shared/resolve-farmslot-ports-core.mjs",
|
|
320
|
+
"kind": "lib",
|
|
321
|
+
"purpose": "Self-contained pool/context/formula port resolution imported by the node leaf and TS surface.",
|
|
322
|
+
"inputs": "ESM import from resolve-farmslot-ports.mjs or resolve-farmslot-ports.ts",
|
|
323
|
+
"outputs": "resolveFarmslotPortsByRepo and related functions"
|
|
324
|
+
},
|
|
325
|
+
{
|
|
326
|
+
"id": "lib/recipe-harness-root",
|
|
327
|
+
"entry": "adapters/shared/recipe-harness-root.mjs",
|
|
328
|
+
"kind": "lib",
|
|
329
|
+
"purpose": "Resolve RECIPE_HARNESS_ROOT for overlay node leaves without importing src/paths.ts.",
|
|
330
|
+
"inputs": "ESM import; env RECIPE_HARNESS_ROOT optional override",
|
|
331
|
+
"outputs": "recipeHarnessRoot() relative path string"
|
|
332
|
+
},
|
|
333
|
+
{
|
|
334
|
+
"id": "lib/resolve-farmslot-ports-sh",
|
|
335
|
+
"entry": "adapters/shared/resolve-farmslot-ports.sh",
|
|
336
|
+
"kind": "lib",
|
|
337
|
+
"purpose": "Bash-compat sourced wrapper over resolve-farmslot-ports.mjs for stop-metro and legacy callers.",
|
|
338
|
+
"inputs": "source + resolve_* / apply_resolved_* function calls",
|
|
339
|
+
"outputs": "KEY=VALUE lines on stdout; env vars via apply_resolved_*"
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
"id": "lib/reap-checkout-metros",
|
|
343
|
+
"entry": "adapters/shared/reap-checkout-metros.sh",
|
|
344
|
+
"kind": "lib"
|
|
345
|
+
},
|
|
317
346
|
{
|
|
318
347
|
"id": "lib/sync-wallet-fixture",
|
|
319
348
|
"entry": "adapters/shared/sync-wallet-fixture.sh",
|
|
@@ -187,6 +187,7 @@ install_v1_runner_assets() {
|
|
|
187
187
|
if [ -n "$METAMASK_RUNNER_PROTOCOL_ROOT" ]; then
|
|
188
188
|
printf 'export FARMSLOT_ROOT=${FARMSLOT_ROOT:-%s}\n' "$runner_protocol_root_q"
|
|
189
189
|
fi
|
|
190
|
+
printf 'if [ -n "${MM_HARNESS_BIN:-}" ] && [ "$MM_HARNESS_BIN" != %s ]; then echo "mm-harness overlay: %s: %s (reinstall to repoint: mm-harness install)" >&2; fi\n' "$runner_exec_q" 'delegating to the runner pinned at install time' "$runner_exec_q"
|
|
190
191
|
printf 'exec %s "$@"\n' "$runner_exec_q"
|
|
191
192
|
} > "$HARNESS_DIR/runner/bin/mm-harness"
|
|
192
193
|
chmod +x "$HARNESS_DIR/runner/bin/mm-harness"
|
|
@@ -102,7 +102,7 @@ CLEAR_SUFFIX=""
|
|
|
102
102
|
|
|
103
103
|
printf 'Starting Metro on port %s (workers=%s%s)\n' \
|
|
104
104
|
"$PORT" "$METRO_WORKERS" "${CLEAR:+, clear}" >&2
|
|
105
|
-
printf '(Full log: %s —
|
|
105
|
+
printf '(Full log: %s — mm-harness logs | mm-harness logs --full)\n' "$LOG_FILE" >&2
|
|
106
106
|
|
|
107
107
|
# Metro runs detached, writing to the log. The tmux window is a read-only tail.
|
|
108
108
|
(
|
|
@@ -54,8 +54,23 @@ if [ -n "$pids" ]; then
|
|
|
54
54
|
else
|
|
55
55
|
printf 'Metro not running on port %s — nothing to stop\n' "$PORT" >&2
|
|
56
56
|
fi
|
|
57
|
+
|
|
58
|
+
# The stop decision is made; everything below is best-effort cleanup (pid file,
|
|
59
|
+
# leaked-bundler sweep, tmux window). None of it may fail the command — the
|
|
60
|
+
# idempotent-stop contract is already satisfied. Drop set -e for the tail so an
|
|
61
|
+
# incidental non-zero (platform-dependent) can never turn success into exit 1.
|
|
62
|
+
set +e
|
|
57
63
|
rm -f "$PID_FILE"
|
|
58
64
|
|
|
65
|
+
# Port-scoped stop above misses bundlers a prior launch left on a DIFFERENT port
|
|
66
|
+
# (port drift / missing pid file). Sweep every Metro bound to this checkout so
|
|
67
|
+
# stop fully cleans up — the leak that stacked bundlers across relaunches.
|
|
68
|
+
# Guarded: the reap lib may be absent in a minimal install; skip rather than abort.
|
|
69
|
+
if [ -f "$SCRIPT_DIR/../shared/reap-checkout-metros.sh" ]; then
|
|
70
|
+
. "$SCRIPT_DIR/../shared/reap-checkout-metros.sh"
|
|
71
|
+
reap_checkout_metros "$TARGET" || true
|
|
72
|
+
fi
|
|
73
|
+
|
|
59
74
|
# Close the read-only log-tail window start-metro opened, if it is still there.
|
|
60
75
|
if [ -f "$TMUX_FILE" ] && command -v tmux >/dev/null 2>&1; then
|
|
61
76
|
win="$(cat "$TMUX_FILE" 2>/dev/null || true)"
|
|
@@ -64,3 +79,8 @@ if [ -f "$TMUX_FILE" ] && command -v tmux >/dev/null 2>&1; then
|
|
|
64
79
|
fi
|
|
65
80
|
fi
|
|
66
81
|
rm -f "$TMUX_FILE"
|
|
82
|
+
|
|
83
|
+
# Reaching here = cleanup done (nothing needed stopping, or it stopped and
|
|
84
|
+
# windows/pids were cleared). The idempotent-stop contract is success; do not
|
|
85
|
+
# leak an incidental non-zero from the last command on any platform.
|
|
86
|
+
exit 0
|
|
@@ -296,7 +296,7 @@ if (checked === 0) {
|
|
|
296
296
|
const parts = [];
|
|
297
297
|
if (drifted.length) parts.push('behind runner: ' + drifted.join(', '));
|
|
298
298
|
if (missing.length) parts.push('absent in repo: ' + missing.join(', '));
|
|
299
|
-
process.stdout.write(JSON.stringify({ name, status: 'warn', detail: parts.join('; ') + ' — rerun mm-harness install --force-overlay or
|
|
299
|
+
process.stdout.write(JSON.stringify({ name, status: 'warn', detail: parts.join('; ') + ' — rerun mm-harness install --force-overlay or mm-harness fixtures sync to refresh the in-repo HUD/AgenticService from the runner' }));
|
|
300
300
|
} else {
|
|
301
301
|
process.stdout.write(JSON.stringify({ name, status: 'pass' }));
|
|
302
302
|
}
|
|
@@ -134,7 +134,7 @@ activate_repo_node() {
|
|
|
134
134
|
require_repo_node() {
|
|
135
135
|
local target="${1:-.}"
|
|
136
136
|
if ! command -v activate_repo_node >/dev/null 2>&1; then
|
|
137
|
-
echo "[env] activate-repo-node.sh is not sourced; reinstall the harness (
|
|
137
|
+
echo "[env] activate-repo-node.sh is not sourced; reinstall the harness (mm-harness install)" >&2
|
|
138
138
|
return 1
|
|
139
139
|
fi
|
|
140
140
|
activate_repo_node "$target" || {
|