@deeeed/metamask-harness 0.92.12 → 0.92.15

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 CHANGED
@@ -6,6 +6,18 @@
6
6
 
7
7
  - Refresh Terminal contract goldens for the shipped console-error window, capture-provider metadata and engine evidence inventories.
8
8
 
9
+ ## 0.92.15 - 2026-10-16
10
+
11
+ ### Fixed
12
+
13
+ - Add Extension viewer-only teardown for browser reservations, retaining the browser and webpack while verifying console collectors exit and reporting cleanup failures.
14
+
15
+ ## 0.92.14 - 2026-10-16
16
+
17
+ ### Fixed
18
+
19
+ - Pin the worker dependency set to recipe-runner 0.38.1 so nested recipe nodes produce safe still filenames and publication preserves retained evidence links.
20
+
9
21
  ## 0.92.12 - 2026-10-16
10
22
 
11
23
  ### Fixed
@@ -127,6 +127,7 @@ copyFile(path.join(runnerDir, 'adapters/shared/path-defaults.json'), path.join(h
127
127
  copyFile(path.join(runnerDir, 'adapters/shared/json-field.sh'), path.join(harnessDir, 'scripts/lib/json-field.sh'));
128
128
  copyFile(path.join(runnerDir, 'adapters/shared/tmux-session.sh'), path.join(harnessDir, 'scripts/lib/tmux-session.sh'));
129
129
  copyFile(path.join(runnerDir, 'adapters/shared/tmux-viewer.sh'), path.join(harnessDir, 'scripts/lib/tmux-viewer.sh'));
130
+ copyFile(path.join(runnerDir, 'adapters/shared/process-identity.sh'), path.join(harnessDir, 'scripts/lib/process-identity.sh'));
130
131
  copyFile(path.join(runnerDir, 'adapters/shared/log-tui.mjs'), path.join(harnessDir, 'scripts/lib/log-tui.mjs'));
131
132
  copyAdapterWeb(path.join(harnessDir, 'scripts/node_modules/@farmslot/adapter-web'));
132
133
  copyFile(path.join(runnerDir, 'adapters/extension/lib/product-config.cjs'), path.join(harnessDir, 'scripts/lib/product-config.cjs'));
@@ -28,6 +28,18 @@ for _tv_src in "$SCRIPT_DIR/lib/tmux-viewer.sh" "$SCRIPT_DIR/../shared/tmux-view
28
28
  done
29
29
  unset _tv_src
30
30
 
31
+ _process_lib="$SCRIPT_DIR/lib/process-identity.sh"
32
+ [ -f "$_process_lib" ] || _process_lib="$SCRIPT_DIR/../shared/process-identity.sh"
33
+ # shellcheck disable=SC1090
34
+ . "$_process_lib"
35
+ unset _process_lib
36
+
37
+ _console_collector_owned() {
38
+ [ "$(process_identity "$1")" = "$2" ] &&
39
+ [ "$(ps -ww -p "$1" -o command= 2>/dev/null | sed 's/^[[:space:]]*//')" = "$3" ] &&
40
+ [ "$(process_identity "$1")" = "$2" ]
41
+ }
42
+
31
43
  runtime_dir="$TARGET/$(recipe_runtime_dir)"
32
44
  set +e
33
45
 
@@ -40,16 +52,25 @@ if command -v tmux_viewer_sweep_prefix >/dev/null 2>&1; then
40
52
  tmux_viewer_sweep_prefix "console" "$runtime_dir"
41
53
  fi
42
54
 
43
- ps -axo pid=,command= 2>/dev/null | while read -r pid command; do
55
+ ps -ww -axo pid=,command= 2>/dev/null | while read -r pid command; do
44
56
  [ -n "${pid:-}" ] || continue
45
57
  case "$command" in
46
58
  *console-tail.mjs*) ;;
47
59
  *) continue ;;
48
60
  esac
49
61
  case "$command" in
50
- *"$runtime_dir/"*) kill "$pid" 2>/dev/null || true ;;
62
+ *"$runtime_dir/"*)
63
+ identity="$(process_identity "$pid")"
64
+ if [ -z "$identity" ] && ! kill -0 "$pid" 2>/dev/null; then continue; fi
65
+ stop_verified_process "$pid" "$identity" TERM 0.05 _console_collector_owned "$pid" "$identity" "$command" || exit 1
66
+ ;;
51
67
  esac
52
68
  done
69
+ stop_status=$?
70
+ if [ "$stop_status" -ne 0 ]; then
71
+ echo "Console collector stop was not confirmed. Next: mm-harness status --target \"$TARGET\" --json" >&2
72
+ exit "$stop_status"
73
+ fi
53
74
 
54
75
  rm -f "$runtime_dir/console.tmux" "$runtime_dir/webpack.tmux" "$runtime_dir/console-tail.pid"
55
76
  exit 0
@@ -527,6 +527,11 @@
527
527
  "entry": "adapters/shared/reap-checkout-metros.sh",
528
528
  "kind": "lib"
529
529
  },
530
+ {
531
+ "id": "lib/process-identity",
532
+ "entry": "adapters/shared/process-identity.sh",
533
+ "kind": "lib"
534
+ },
530
535
  {
531
536
  "id": "lib/sync-wallet-fixture",
532
537
  "entry": "adapters/shared/sync-wallet-fixture.sh",
@@ -1,8 +1,11 @@
1
1
  #!/usr/bin/env bash
2
2
  # Stop only companions bound to this checkout and its selected simulator.
3
3
 
4
+ # shellcheck disable=SC1091
5
+ . "$(cd "$(dirname "${BASH_SOURCE[0]}")/../../shared" && pwd)/process-identity.sh"
6
+
4
7
  _idb_companion_identity() {
5
- ps -p "$1" -o pid=,pgid=,lstart=,comm= 2>/dev/null
8
+ process_identity "$1"
6
9
  }
7
10
 
8
11
  # 1 is foreign; 2 is unknown and must not authorize a signal.
@@ -21,7 +24,7 @@ _idb_companion_owned() {
21
24
  }
22
25
 
23
26
  stop_checkout_idb_companions() {
24
- local repo="$1" udid="${2:-}" pids pid identity status signal current attempt stopped
27
+ local repo="$1" udid="${2:-}" pids pid identity status
25
28
  if pids="$(pgrep -u "$(id -u)" -x idb_companion 2>/dev/null)"; then :
26
29
  else
27
30
  status=$?
@@ -41,26 +44,8 @@ stop_checkout_idb_companions() {
41
44
  printf 'Companion ownership is unknown. Next: mm-harness status --target "%s"\n' "$repo" >&2
42
45
  return 1
43
46
  fi
44
- stopped=false
45
- for signal in TERM KILL; do
46
- if ! _idb_companion_owned "$repo" "$udid" "$pid" "$identity"; then
47
- printf 'Companion ownership changed. Next: mm-harness status --target "%s"\n' "$repo" >&2
48
- return 1
49
- fi
50
- kill -"$signal" "$pid" 2>/dev/null || true
51
- for attempt in $(seq 1 20); do
52
- current="$(_idb_companion_identity "$pid")"
53
- if [ -z "$current" ] && ! kill -0 "$pid" 2>/dev/null; then stopped=true; break; fi
54
- if [ "$current" != "$identity" ]; then
55
- printf 'Companion identity changed during stop. Next: mm-harness status --target "%s"\n' "$repo" >&2
56
- return 1
57
- fi
58
- sleep 0.1
59
- done
60
- [ "$stopped" = true ] && break
61
- done
62
- if [ "$stopped" != true ]; then
63
- printf 'Companion %s did not stop. Next: mm-harness status --target "%s"\n' "$pid" "$repo" >&2
47
+ if ! stop_verified_process "$pid" "$identity" 'TERM KILL' 0.1 _idb_companion_owned "$repo" "$udid" "$pid" "$identity"; then
48
+ printf 'Companion %s cleanup was not confirmed. Next: mm-harness status --target "%s"\n' "$pid" "$repo" >&2
64
49
  return 1
65
50
  fi
66
51
  printf 'Stopped checkout idb companion %s\n' "$pid" >&2
@@ -0,0 +1,35 @@
1
+ #!/usr/bin/env bash
2
+ # Confirm process identity and caller-owned scope before signalling.
3
+
4
+ process_identity() {
5
+ LC_ALL=C ps -p "$1" -o pid=,pgid=,lstart=,comm= 2>/dev/null
6
+ }
7
+
8
+ stop_verified_process() {
9
+ local pid="$1" identity="$2" signals="$3" interval="$4" signal attempt current
10
+ shift 4
11
+ if [ -z "$identity" ]; then
12
+ if ! kill -0 "$pid" 2>/dev/null; then return 0; fi
13
+ printf 'Process %s identity is unknown; stop refused.\n' "$pid" >&2
14
+ return 1
15
+ fi
16
+ for signal in $signals; do
17
+ if ! "$@"; then
18
+ if [ -z "$(process_identity "$pid")" ] && ! kill -0 "$pid" 2>/dev/null; then return 0; fi
19
+ printf 'Process %s ownership changed before stop.\n' "$pid" >&2
20
+ return 1
21
+ fi
22
+ kill -"$signal" "$pid" 2>/dev/null || true
23
+ for attempt in $(seq 1 20); do
24
+ current="$(process_identity "$pid")"
25
+ if [ -z "$current" ] && ! kill -0 "$pid" 2>/dev/null; then return 0; fi
26
+ if [ "$current" != "$identity" ]; then
27
+ printf 'Process %s identity changed during stop.\n' "$pid" >&2
28
+ return 1
29
+ fi
30
+ sleep "$interval"
31
+ done
32
+ done
33
+ printf 'Process %s did not stop.\n' "$pid" >&2
34
+ return 1
35
+ }
@@ -21,6 +21,16 @@ function watcherStatus(buildLog) {
21
21
  if (buildLog === "no-watch") return "down";
22
22
  return buildLog;
23
23
  }
24
+ function stopExtensionViewers(target) {
25
+ const result = spawnSync("bash", [path.join(runnerDir, "adapters/extension/stop-viewers.sh"), "--target", target], {
26
+ encoding: "utf8",
27
+ timeout: 5e3
28
+ });
29
+ return {
30
+ status: result.error ? 1 : result.status ?? 1,
31
+ detail: result.error?.message || result.stderr.trim() || result.stdout.trim()
32
+ };
33
+ }
24
34
  function extensionRuntimeNextAction(reasonCode, target) {
25
35
  const resolved = shellQuote(path.resolve(target));
26
36
  if (reasonCode === "rpc-execution-failed") return `mm-harness logs --target ${resolved} --full`;
@@ -85,8 +95,16 @@ const extensionSurface = defineAdapter({
85
95
  describe: () => "Extension runtime",
86
96
  stop(target) {
87
97
  const stopped = stopExtensionRuntime(target);
88
- const stopViewers = path.join(runnerDir, "adapters/extension/stop-viewers.sh");
89
- spawnSync("bash", [stopViewers, "--target", target], { stdio: "ignore", timeout: 5e3 });
98
+ const viewers = stopExtensionViewers(target);
99
+ if (viewers.status !== 0) {
100
+ return {
101
+ kind: "stopped",
102
+ status: viewers.status,
103
+ signalled: stopped.signalled,
104
+ summary: `failed to stop Extension viewers for ${target}`,
105
+ output: viewers.detail
106
+ };
107
+ }
90
108
  const summary = stopped.signalled > 0 || stopped.removedMarkers > 0 ? `stopped Extension runtime (${stopped.signalled} process${stopped.signalled === 1 ? "" : "es"}, ${stopped.removedMarkers} stale marker${stopped.removedMarkers === 1 ? "" : "s"}) for ${target}` : `Extension runtime not running for ${target} \u2014 nothing to stop`;
91
109
  return { kind: "stopped", status: 0, summary, signalled: stopped.signalled };
92
110
  },
@@ -154,5 +172,6 @@ const extensionSurface = defineAdapter({
154
172
  adapterRegistry.register(extensionSurface);
155
173
  export {
156
174
  extensionRuntimeNextAction,
157
- extensionSurface
175
+ extensionSurface,
176
+ stopExtensionViewers
158
177
  };
@@ -696,6 +696,21 @@ Example:
696
696
  })
697
697
  }
698
698
  },
699
+ {
700
+ name: "stop-viewers",
701
+ summary: "Stop checkout console collectors and log viewers while retaining the Extension browser and webpack.",
702
+ example: "mm-harness stop-viewers --adapter extension",
703
+ helpText: `mm-harness stop-viewers [flags]
704
+
705
+ Stop this checkout's Extension console collectors and log viewers.
706
+ The browser, profile and development server stay running. Used when an
707
+ Extension farm releases its browser reservation.
708
+
709
+ --adapter extension Target adapter
710
+ --target <path> Checkout path (default: cwd)
711
+ --json Machine-readable output`,
712
+ contract: { options: options(HELP, JSON, TARGET, ADAPTER) }
713
+ },
699
714
  {
700
715
  name: "call",
701
716
  summary: "Run one action in isolation as a one-node recipe through the real engine path (fuzzy short names; key=value inputs; same trace/evidence as run).",
@@ -0,0 +1,31 @@
1
+ import { optionFlag, parseArgs, resolveAdapter, shellQuote, usageOut } from "@farmslot/recipe-cli/harness";
2
+ import { stopExtensionViewers } from "../adapters/extension/surface.js";
3
+ async function handleStopViewers(argv) {
4
+ const parsed = parseArgs(argv);
5
+ const json = optionFlag(parsed.options, "json");
6
+ const { adapter, target } = resolveAdapter(parsed.options);
7
+ if (adapter !== "extension") {
8
+ return usageOut(json, "stop-viewers", "Viewer-only cleanup requires the Extension adapter.", "mm-harness stop-viewers --adapter extension");
9
+ }
10
+ const result = stopExtensionViewers(target);
11
+ const exitCode = result.status;
12
+ const detail = result.detail;
13
+ const userAction = `mm-harness status --target ${shellQuote(target)} --json`;
14
+ if (json) {
15
+ console.log(JSON.stringify({
16
+ schemaVersion: 1,
17
+ command: "stop-viewers",
18
+ adapter,
19
+ target,
20
+ status: exitCode === 0 ? "pass" : "fail",
21
+ exitCode,
22
+ ...exitCode === 0 ? {} : { error: { code: "VIEWER_STOP_FAILED", message: detail || "Checkout viewers did not stop.", userAction } }
23
+ }));
24
+ } else {
25
+ console.error(exitCode === 0 ? `Stopped checkout viewers for ${target}; browser and watcher retained.` : `${detail || "Checkout viewers did not stop."} Next: ${userAction}`);
26
+ }
27
+ return exitCode;
28
+ }
29
+ export {
30
+ handleStopViewers
31
+ };
@@ -20,6 +20,7 @@ import {
20
20
  parseArgs,
21
21
  targetPath
22
22
  } from "@farmslot/recipe-cli/harness";
23
+ import { handleStopViewers } from "./commands/stop.js";
23
24
  import { stopOptions } from "./provider-lifecycle.js";
24
25
  import { adoptPluginAdapter, afterAdapterLoad, configuredLibrarySources } from "./adapters/plugin-surface.js";
25
26
  import { withMainnetOrderAbort } from "./adapters/perps-mainnet-cleanup.js";
@@ -150,6 +151,7 @@ const PUBLIC_RUN = {
150
151
  "execution-template": { run: handleExecutionTemplate },
151
152
  actions: { run: (args) => handleActions(parseArgs(args), { catalog: metaMaskEngine }) },
152
153
  stop: { run: (args) => handleStop(args, stopOptions) },
154
+ "stop-viewers": { run: handleStopViewers },
153
155
  // A signal during run or call still cancels the run's mainnet orders.
154
156
  call: { run: (args) => withMainnetOrderAbort(targetPath(parseArgs(args).options), () => handleCall(args, callOptions)) },
155
157
  run: { run: (args) => withMainnetOrderAbort(targetPath(parseArgs(args).options), () => handleRun(args, runOptions)) },
@@ -188,7 +190,7 @@ const HELP_GROUPS = [
188
190
  {
189
191
  title: "DAILY LOOP",
190
192
  blurb: "what a teammate runs many times a day (auto-ensures the overlay; --heal owns recovery)",
191
- commands: ["status", "task", "prepare", "launch", "stop", "logs", "debug", "reload", "fixtures"]
193
+ commands: ["status", "task", "prepare", "launch", "stop", "stop-viewers", "logs", "debug", "reload", "fixtures"]
192
194
  },
193
195
  {
194
196
  title: "DISCOVER",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.92.12",
3
+ "version": "0.92.15",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"
@@ -21,15 +21,15 @@
21
21
  "check:syntax": "find . -name '*.mjs' -print0 | xargs -0 -n1 node --check"
22
22
  },
23
23
  "dependencies": {
24
- "@farmslot/adapter-node": "0.1.27",
25
- "@farmslot/adapter-rn": "0.22.16",
26
- "@farmslot/adapter-sdk": "0.15.1",
27
- "@farmslot/adapter-web": "0.10.7",
28
- "@farmslot/agent-runtime": "0.25.1",
24
+ "@farmslot/adapter-node": "0.1.30",
25
+ "@farmslot/adapter-rn": "0.22.19",
26
+ "@farmslot/adapter-sdk": "0.16.1",
27
+ "@farmslot/adapter-web": "0.10.10",
28
+ "@farmslot/agent-runtime": "0.25.4",
29
29
  "@farmslot/handoff": "^0.3.1",
30
- "@farmslot/protocol": "0.48.0",
31
- "@farmslot/recipe-cli": "0.20.0",
32
- "@farmslot/recipe-runner": "0.37.1",
30
+ "@farmslot/protocol": "0.50.0",
31
+ "@farmslot/recipe-cli": "0.22.2",
32
+ "@farmslot/recipe-runner": "0.38.1",
33
33
  "agent-device": "0.19.3",
34
34
  "commander": "^12.0.0",
35
35
  "esbuild": "0.28.1",