@deeeed/metamask-harness 0.14.7 → 0.15.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 +81 -6
- package/adapters/core/cleanup.sh +0 -0
- package/adapters/core/inject.sh +0 -0
- package/adapters/extension/cleanup.mjs +0 -0
- package/adapters/extension/ensure-browser.sh +4 -2
- package/adapters/extension/inject.mjs +0 -0
- package/adapters/extension/launch-browser.cjs +73 -34
- package/adapters/extension/launch.sh +0 -0
- package/adapters/extension/live.sh +21 -10
- package/adapters/extension/readiness.mjs +0 -0
- package/adapters/extension/reattach.sh +3 -3
- package/adapters/extension/refresh-build.sh +0 -0
- package/adapters/extension/seed-fixture.sh +0 -0
- package/adapters/extension/sidepanel-toggle.sh +10 -4
- package/adapters/extension/snapshot-dist.sh +0 -0
- package/adapters/extension/start-watch.sh +2 -2
- package/adapters/extension/stop-viewers.sh +0 -0
- package/adapters/extension/verify.sh +19 -2
- package/adapters/extension/wallet-fixture-state.cjs +14 -1
- package/adapters/manifest.json +19 -3
- package/adapters/mobile/bridge-runtime/cdp-bridge.cjs +0 -0
- package/adapters/mobile/bridge-runtime/setup-wallet.sh +6 -4
- package/adapters/mobile/cleanup.sh +0 -0
- package/adapters/mobile/inject.sh +0 -0
- package/adapters/mobile/launch-metro.cjs +74 -0
- package/adapters/mobile/lib/metro-listener.sh +0 -0
- package/adapters/mobile/lib/tmux-viewer.sh +4 -4
- package/adapters/mobile/open-device.sh +3 -1
- package/adapters/mobile/prewarm-bundle.sh +0 -0
- package/adapters/mobile/start-metro.sh +12 -18
- package/adapters/mobile/stop-metro.sh +1 -0
- package/adapters/mobile/verify.sh +5 -5
- package/adapters/mobile/wait-for-bridge.sh +0 -0
- package/adapters/mobile/yarn-setup.sh +0 -0
- package/adapters/shared/activate-repo-node.sh +0 -0
- package/adapters/shared/activate-repo-ruby.sh +0 -0
- package/adapters/shared/cli-ux.sh +0 -0
- package/adapters/shared/ensure-runner-deps.sh +0 -0
- package/adapters/shared/harness-path.sh +0 -0
- package/adapters/shared/hash-helpers.sh +0 -0
- package/adapters/shared/install-repo-deps.sh +29 -0
- package/adapters/shared/json-field.sh +0 -0
- package/adapters/shared/open-debug.mjs +35 -4
- package/adapters/shared/open-log-window.sh +1 -1
- package/adapters/shared/reap-checkout-metros.sh +0 -0
- package/adapters/shared/resolve-farmslot-ports.mjs +0 -0
- package/adapters/shared/resolve-farmslot-ports.sh +0 -0
- package/adapters/shared/resolve-slot-ports-core.mjs +72 -1
- package/adapters/shared/resolve-slot-ports.mjs +0 -0
- package/adapters/shared/resolve-slot-ports.sh +21 -17
- package/adapters/shared/sync-wallet-fixture.sh +0 -0
- package/adapters/shared/tmux-session.sh +0 -5
- package/adapters/shared/tmux-viewer.sh +7 -2
- package/dist/adapters/core/surface.js +3 -0
- package/dist/adapters/extension/ensure-ready.js +0 -7
- package/dist/adapters/extension/runtime-decision.js +93 -3
- package/dist/adapters/extension/surface.js +17 -6
- package/dist/adapters/mobile/metro-env.js +67 -0
- package/dist/adapters/mobile/perps-env.js +101 -0
- package/dist/adapters/mobile/prepare.js +32 -14
- package/dist/adapters/mobile/runtime-decision.js +21 -2
- package/dist/adapters/mobile/source-freshness.js +116 -0
- package/dist/adapters/mobile/surface.js +3 -0
- package/dist/adapters/resolve-slot-ports.js +4 -0
- package/dist/adapters/slot-ports.js +131 -49
- package/dist/adapters.js +7 -2
- package/dist/checkout-lock.js +72 -0
- package/dist/cli.js +2 -0
- package/dist/commands/call.js +91 -54
- package/dist/commands/check.js +266 -46
- package/dist/commands/checklist.js +136 -0
- package/dist/commands/debug.js +21 -2
- package/dist/commands/doctor.js +187 -12
- package/dist/commands/fixtures.js +177 -42
- package/dist/commands/launch/extension.js +6 -1
- package/dist/commands/launch/index.js +54 -22
- package/dist/commands/logs.js +24 -4
- package/dist/commands/run-engine.js +223 -7
- package/dist/commands/run.js +67 -50
- package/dist/commands/shared.js +86 -5
- package/dist/commands/stop.js +1 -2
- package/dist/doctor.js +39 -17
- package/dist/harness.js +5 -2
- package/dist/heal-bounds.js +1 -1
- package/dist/mm-harness-cli.js +37 -9
- package/dist/runner.js +35 -5
- package/dist/runtime-context.js +228 -0
- package/docs/CLI-SPEC.md +15 -11
- package/docs/MENTAL-MODEL.md +6 -6
- package/library/actions/extension/perps/perps.mjs +29 -8
- package/library/actions/extension/platform/cdp.mjs +128 -28
- package/library/actions/extension/ui/navigate.mjs +1 -1
- package/library/actions/mobile/perps/perps.mjs +13 -1
- package/library/actions/mobile/platform/bridge.mjs +302 -29
- package/library/actions/mobile/wallet/ensure_unlocked.mjs +7 -2
- package/library/manifests/extension.action-manifest.json +32 -12
- package/library/manifests/mobile.action-manifest.json +25 -3
- package/package.json +5 -5
- package/scripts/completions.sh +7 -4
- package/scripts/install-completions.sh +0 -0
package/dist/commands/shared.js
CHANGED
|
@@ -111,7 +111,8 @@ function spawnInherit(script, args, cwd) {
|
|
|
111
111
|
if (result.status !== null) return result.status;
|
|
112
112
|
return result.signal === "SIGINT" || result.signal === "SIGTERM" ? 0 : 1;
|
|
113
113
|
}
|
|
114
|
-
function spawnScriptStreaming(script, args, cwd,
|
|
114
|
+
function spawnScriptStreaming(script, args, cwd, options) {
|
|
115
|
+
const spawnOptions = options && ("env" in options || "timeoutMs" in options) ? options : { env: options };
|
|
115
116
|
const isNodeScript = script === process.execPath && args.length > 0;
|
|
116
117
|
const stem = isNodeScript ? path.basename(args[0]).replace(/[^A-Za-z0-9]/gu, "_").toUpperCase() : path.basename(script).replace(/[^A-Za-z0-9]/gu, "_").toUpperCase();
|
|
117
118
|
const override = process.env[`MM_HARNESS_SCRIPT_BIN_${stem}`];
|
|
@@ -126,12 +127,58 @@ function spawnScriptStreaming(script, args, cwd, env) {
|
|
|
126
127
|
}
|
|
127
128
|
const { bin: invokeBin, args: spawnArgs } = resolveLeafInvoke(bin, directArgs);
|
|
128
129
|
return new Promise((resolve) => {
|
|
130
|
+
const ownsProcessGroup = process.platform !== "win32" && Number(spawnOptions.timeoutMs) > 0;
|
|
129
131
|
const child = spawn(invokeBin, spawnArgs, {
|
|
130
132
|
cwd,
|
|
131
|
-
env: env ? { ...process.env, ...env } : process.env,
|
|
132
|
-
stdio: ["ignore", "pipe", "pipe"]
|
|
133
|
+
env: spawnOptions.env ? { ...process.env, ...spawnOptions.env } : process.env,
|
|
134
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
135
|
+
detached: ownsProcessGroup
|
|
133
136
|
});
|
|
134
137
|
let output = "";
|
|
138
|
+
let settled = false;
|
|
139
|
+
let didTimeout = false;
|
|
140
|
+
let escalation;
|
|
141
|
+
const parentSignals = ["SIGINT", "SIGTERM"];
|
|
142
|
+
const removeParentSignalHandlers = () => {
|
|
143
|
+
for (const signal of parentSignals) process.removeListener(signal, parentSignalHandlers[signal]);
|
|
144
|
+
};
|
|
145
|
+
const finish = (result) => {
|
|
146
|
+
if (settled) return;
|
|
147
|
+
settled = true;
|
|
148
|
+
if (timer) clearTimeout(timer);
|
|
149
|
+
if (escalation) clearTimeout(escalation);
|
|
150
|
+
removeParentSignalHandlers();
|
|
151
|
+
resolve(result);
|
|
152
|
+
};
|
|
153
|
+
const signalChildTree = (signal) => {
|
|
154
|
+
try {
|
|
155
|
+
if (ownsProcessGroup && child.pid) process.kill(-child.pid, signal);
|
|
156
|
+
else child.kill(signal);
|
|
157
|
+
} catch {
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
const parentSignalHandlers = Object.fromEntries(parentSignals.map((signal) => [signal, () => {
|
|
161
|
+
signalChildTree(signal);
|
|
162
|
+
removeParentSignalHandlers();
|
|
163
|
+
process.kill(process.pid, signal);
|
|
164
|
+
}]));
|
|
165
|
+
if (ownsProcessGroup) {
|
|
166
|
+
for (const signal of parentSignals) process.once(signal, parentSignalHandlers[signal]);
|
|
167
|
+
}
|
|
168
|
+
const timeoutMs = spawnOptions.timeoutMs;
|
|
169
|
+
const timer = Number.isFinite(timeoutMs) && Number(timeoutMs) > 0 ? setTimeout(() => {
|
|
170
|
+
didTimeout = true;
|
|
171
|
+
const message = `leaf timed out after ${String(timeoutMs)}ms: ${path.basename(script)}`;
|
|
172
|
+
output += `${output.endsWith("\n") || output.length === 0 ? "" : "\n"}${message}
|
|
173
|
+
`;
|
|
174
|
+
process.stderr.write(`${message}
|
|
175
|
+
`);
|
|
176
|
+
signalChildTree("SIGTERM");
|
|
177
|
+
escalation = setTimeout(() => {
|
|
178
|
+
signalChildTree("SIGKILL");
|
|
179
|
+
finish({ status: 1, output, timedOut: true, timeoutMs: Number(timeoutMs) });
|
|
180
|
+
}, 1e3);
|
|
181
|
+
}, Number(timeoutMs)) : void 0;
|
|
135
182
|
const tee = (chunk) => {
|
|
136
183
|
const text = chunk.toString("utf8");
|
|
137
184
|
output += text;
|
|
@@ -146,10 +193,20 @@ function spawnScriptStreaming(script, args, cwd, env) {
|
|
|
146
193
|
Next: reinstall mm-harness (npm i -g @deeeed/metamask-harness) \u2014 the shell leaf is missing or not executable`;
|
|
147
194
|
process.stderr.write(`${message}
|
|
148
195
|
`);
|
|
149
|
-
|
|
196
|
+
finish({ status: 1, output: message });
|
|
197
|
+
});
|
|
198
|
+
child.on("exit", () => {
|
|
199
|
+
if (!didTimeout) return;
|
|
200
|
+
signalChildTree("SIGKILL");
|
|
201
|
+
finish({ status: 1, output, timedOut: true, timeoutMs: Number(timeoutMs) });
|
|
150
202
|
});
|
|
151
203
|
child.on("close", (status) => {
|
|
152
|
-
|
|
204
|
+
if (didTimeout) signalChildTree("SIGKILL");
|
|
205
|
+
finish({
|
|
206
|
+
status: didTimeout ? 1 : status ?? 1,
|
|
207
|
+
output,
|
|
208
|
+
...didTimeout ? { timedOut: true, timeoutMs: Number(timeoutMs) } : {}
|
|
209
|
+
});
|
|
153
210
|
});
|
|
154
211
|
});
|
|
155
212
|
}
|
|
@@ -169,9 +226,33 @@ function usageOut(json, command, message, userAction) {
|
|
|
169
226
|
}
|
|
170
227
|
return EXIT.usage;
|
|
171
228
|
}
|
|
229
|
+
function checkoutBusyOut(json, command, message, lockPath) {
|
|
230
|
+
const userAction = `wait for the current owner, or inspect ${lockPath} if its process has exited`;
|
|
231
|
+
if (json) {
|
|
232
|
+
console.log(
|
|
233
|
+
JSON.stringify(
|
|
234
|
+
{
|
|
235
|
+
schemaVersion: 1,
|
|
236
|
+
command,
|
|
237
|
+
status: "fail",
|
|
238
|
+
exitCode: EXIT.bounded,
|
|
239
|
+
recoverable: false,
|
|
240
|
+
error: { code: "SANDBOX_BUSY", message, userAction }
|
|
241
|
+
},
|
|
242
|
+
null,
|
|
243
|
+
2
|
|
244
|
+
)
|
|
245
|
+
);
|
|
246
|
+
} else {
|
|
247
|
+
console.error(`\u2717 mm-harness ${command}: ${message}
|
|
248
|
+
Next: ${userAction}`);
|
|
249
|
+
}
|
|
250
|
+
return EXIT.bounded;
|
|
251
|
+
}
|
|
172
252
|
export {
|
|
173
253
|
ADAPTER_DETECT_NEXT,
|
|
174
254
|
EXIT,
|
|
255
|
+
checkoutBusyOut,
|
|
175
256
|
flag,
|
|
176
257
|
parseFlags,
|
|
177
258
|
resolveAdapter,
|
package/dist/commands/stop.js
CHANGED
|
@@ -12,12 +12,11 @@ async function handleStop(argv) {
|
|
|
12
12
|
const json = optionFlag(options, "json");
|
|
13
13
|
const { adapter, target } = resolveAdapter(options);
|
|
14
14
|
const surface = getAdapterSurface(adapter);
|
|
15
|
+
surface.resolveSlotPorts(target);
|
|
15
16
|
const explicitPort = optionString(options, "port") ?? optionString(options, "watcherPort");
|
|
16
17
|
if (explicitPort) {
|
|
17
18
|
process.env.WATCHER_PORT = explicitPort;
|
|
18
19
|
process.env.METRO_PORT = explicitPort;
|
|
19
|
-
} else {
|
|
20
|
-
surface.resolveSlotPorts(target);
|
|
21
20
|
}
|
|
22
21
|
const stop = surface.devServer.stop(target);
|
|
23
22
|
if (stop.kind === "headless") {
|
package/dist/doctor.js
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { color } from "./cli-color.js";
|
|
4
|
+
import { mobilePerpsEnvironment } from "./adapters/mobile/perps-env.js";
|
|
4
5
|
import { readRuntimeContextField, resolveRuntimeContextPath } from "./harness.js";
|
|
5
6
|
import { manifestPath, readJson, recipeHarnessRoot, recipeRuntimeDir, runnerDir } from "./paths.js";
|
|
7
|
+
function requiredDoctorCheckSummary(checks) {
|
|
8
|
+
const required = checks.filter((check) => check.required);
|
|
9
|
+
const failed = required.filter((check) => check.status === "fail").map((check) => check.id);
|
|
10
|
+
return {
|
|
11
|
+
status: failed.length === 0 ? "pass" : "fail",
|
|
12
|
+
total: required.length,
|
|
13
|
+
passed: required.length - failed.length,
|
|
14
|
+
failed
|
|
15
|
+
};
|
|
16
|
+
}
|
|
6
17
|
function repoShape(target) {
|
|
7
18
|
const exists = (rel) => fs.existsSync(path.join(target, rel));
|
|
8
19
|
const packageInfo = readPackageInfo(target);
|
|
@@ -56,31 +67,36 @@ function readPackageInfo(target) {
|
|
|
56
67
|
};
|
|
57
68
|
}
|
|
58
69
|
}
|
|
59
|
-
function fixtureSummary(target) {
|
|
70
|
+
function fixtureSummary(target, adapter) {
|
|
60
71
|
const candidates = [
|
|
61
72
|
`${recipeRuntimeDir()}/wallet-fixture.json`
|
|
62
73
|
];
|
|
63
74
|
const rel = candidates.find((candidate) => fs.existsSync(path.join(target, candidate)));
|
|
64
75
|
if (!rel) return { status: "missing", path: null };
|
|
76
|
+
return fixtureFileSummary(path.join(target, rel), adapter, rel);
|
|
77
|
+
}
|
|
78
|
+
function fixtureFileSummary(file, adapter, reportedPath = file) {
|
|
65
79
|
try {
|
|
66
|
-
const data = readJsonObject(
|
|
80
|
+
const data = readJsonObject(file);
|
|
81
|
+
const hasAccounts = Array.isArray(data.accounts) && data.accounts.length > 0;
|
|
82
|
+
const hasPassword = typeof data.password === "string" && data.password.length > 0;
|
|
67
83
|
return {
|
|
68
|
-
status:
|
|
69
|
-
path:
|
|
84
|
+
status: hasAccounts && (adapter === "core" || hasPassword) ? "ready" : "incomplete",
|
|
85
|
+
path: reportedPath,
|
|
70
86
|
accountCount: Array.isArray(data.accounts) ? data.accounts.length : 0,
|
|
71
|
-
hasPassword
|
|
87
|
+
hasPassword
|
|
72
88
|
};
|
|
73
89
|
} catch (error) {
|
|
74
|
-
return { status: "invalid", path:
|
|
90
|
+
return { status: "invalid", path: reportedPath, error: error instanceof Error ? error.message : String(error) };
|
|
75
91
|
}
|
|
76
92
|
}
|
|
77
93
|
const RUNTIME_CONTEXT_FIELDS = [
|
|
78
|
-
{ key: "slotId", envVars: ["RECIPE_SLOT_ID"], envVar: "RECIPE_SLOT_ID", customize: "
|
|
79
|
-
{ key: "extensionId", envVars: ["RECIPE_HARNESS_EXTENSION_ID"], envVar: "RECIPE_HARNESS_EXTENSION_ID", customize: "
|
|
80
|
-
{ key: "cdpPort", envVars: ["RECIPE_CDP_PORT", "CDP_PORT"], envVar: "CDP_PORT", customize: "
|
|
81
|
-
{ key: "runtimeStart.approved", envVars: ["RECIPE_RUNTIME_START_APPROVED"], envVar: "RECIPE_RUNTIME_START_APPROVED", customize: "
|
|
82
|
-
{ key: "runtimeStart.command", envVars: [], envVar: null, customize: "
|
|
83
|
-
{ key: "runtimeStart.readyUrl", envVars: ["RECIPE_RUNTIME_READY_URL"], envVar: "RECIPE_RUNTIME_READY_URL", customize: "
|
|
94
|
+
{ key: "slotId", envVars: ["RECIPE_SLOT_ID"], envVar: "RECIPE_SLOT_ID", customize: "doctor --fix creates local identity; Farmslot may override", adapters: ["mobile", "extension", "core"] },
|
|
95
|
+
{ key: "extensionId", envVars: ["RECIPE_HARNESS_EXTENSION_ID"], envVar: "RECIPE_HARNESS_EXTENSION_ID", customize: "resolved from the built extension", adapters: ["extension"] },
|
|
96
|
+
{ key: "cdpPort", envVars: ["RECIPE_CDP_PORT", "CDP_PORT"], envVar: "CDP_PORT", customize: "doctor --fix claims one; --cdp-port overrides", adapters: ["extension"] },
|
|
97
|
+
{ key: "runtimeStart.approved", envVars: ["RECIPE_RUNTIME_START_APPROVED"], envVar: "RECIPE_RUNTIME_START_APPROVED", customize: "optional orchestrator-provided legacy live context", adapters: ["mobile", "extension"] },
|
|
98
|
+
{ key: "runtimeStart.command", envVars: [], envVar: null, customize: "optional orchestrator-provided legacy live context", adapters: ["mobile", "extension"] },
|
|
99
|
+
{ key: "runtimeStart.readyUrl", envVars: ["RECIPE_RUNTIME_READY_URL"], envVar: "RECIPE_RUNTIME_READY_URL", customize: "optional orchestrator-provided legacy live context", adapters: ["mobile", "extension"] }
|
|
84
100
|
];
|
|
85
101
|
function runtimeContextSummary(target, adapter) {
|
|
86
102
|
const contextPath = resolveRuntimeContextPath(target);
|
|
@@ -104,7 +120,7 @@ function renderRuntimeContext(runtimeContext) {
|
|
|
104
120
|
const out = (style, text) => color(style, text, { stream: process.stdout });
|
|
105
121
|
const lines = [];
|
|
106
122
|
lines.push(
|
|
107
|
-
runtimeContext.fileExists ? `${out("label", "runtime-context:")} ${runtimeContext.file} ${out("ok", "(present)")}` : `${out("label", "runtime-context:")} ${runtimeContext.file} ${out("dim", "(absent \u2014
|
|
123
|
+
runtimeContext.fileExists ? `${out("label", "runtime-context:")} ${runtimeContext.file} ${out("ok", "(present)")}` : `${out("label", "runtime-context:")} ${runtimeContext.file} ${out("dim", "(absent \u2014 run mm-harness doctor --fix)")}`
|
|
108
124
|
);
|
|
109
125
|
for (const [key, field] of Object.entries(runtimeContext.fields)) {
|
|
110
126
|
const isSet = field.value !== void 0 && field.value !== null && field.value !== "";
|
|
@@ -118,25 +134,28 @@ function renderRuntimeContext(runtimeContext) {
|
|
|
118
134
|
function createDoctorReport(adapter, target, manifestValidation, actionManifestPath = manifestPath(adapter)) {
|
|
119
135
|
const mode = compatibilityMode(adapter, target);
|
|
120
136
|
const manifestErrors = Number(manifestValidation.summary?.errors ?? 0);
|
|
121
|
-
const status = manifestErrors > 0 ? "fail" : "pass";
|
|
122
137
|
const checks = [
|
|
123
138
|
{
|
|
124
139
|
id: "manifest",
|
|
125
140
|
status: manifestErrors === 0 ? "pass" : "fail",
|
|
141
|
+
required: true,
|
|
126
142
|
message: manifestErrors === 0 ? "Action manifest is valid Recipe v1." : `Action manifest has ${manifestErrors} validation error(s).`
|
|
127
143
|
},
|
|
128
144
|
{
|
|
129
145
|
id: "bridge",
|
|
130
146
|
status: mode === "unsupported/no bridge" ? "fail" : "pass",
|
|
147
|
+
required: false,
|
|
131
148
|
message: mode === "unsupported/no bridge" ? `No ${adapter} bridge is available for this checkout.` : `${adapter} compatibility mode: ${mode}.`
|
|
132
149
|
}
|
|
133
150
|
];
|
|
151
|
+
const requiredChecks = requiredDoctorCheckSummary(checks);
|
|
134
152
|
return {
|
|
135
153
|
schemaVersion: 1,
|
|
136
154
|
protocolVersion: "v1",
|
|
137
155
|
runner_protocol_version: 1,
|
|
138
|
-
status,
|
|
139
|
-
checks
|
|
156
|
+
status: requiredChecks.status,
|
|
157
|
+
checks,
|
|
158
|
+
requiredChecks,
|
|
140
159
|
adapter,
|
|
141
160
|
target,
|
|
142
161
|
runner: {
|
|
@@ -147,7 +166,8 @@ function createDoctorReport(adapter, target, manifestValidation, actionManifestP
|
|
|
147
166
|
},
|
|
148
167
|
compatibilityMode: mode,
|
|
149
168
|
shape: repoShape(target),
|
|
150
|
-
fixture: fixtureSummary(target),
|
|
169
|
+
fixture: fixtureSummary(target, adapter),
|
|
170
|
+
environment: adapter === "mobile" ? { mobilePerps: mobilePerpsEnvironment(target) } : {},
|
|
151
171
|
runtimeContext: runtimeContextSummary(target, adapter),
|
|
152
172
|
manifestValidation: manifestValidation.summary
|
|
153
173
|
};
|
|
@@ -162,8 +182,10 @@ function readJsonObject(file) {
|
|
|
162
182
|
export {
|
|
163
183
|
compatibilityMode,
|
|
164
184
|
createDoctorReport,
|
|
185
|
+
fixtureFileSummary,
|
|
165
186
|
fixtureSummary,
|
|
166
187
|
renderRuntimeContext,
|
|
167
188
|
repoShape,
|
|
189
|
+
requiredDoctorCheckSummary,
|
|
168
190
|
runtimeContextSummary
|
|
169
191
|
};
|
package/dist/harness.js
CHANGED
|
@@ -2,6 +2,7 @@ import { execFileSync, spawnSync } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { resolveLeafInvoke, shellLeafMissing, missingShellLeafMessage } from "./leaf-invoke.js";
|
|
5
|
+
import { resolveExtensionSlotPorts } from "./adapters/slot-ports.js";
|
|
5
6
|
import { recipeHarnessPath, recipeRuntimeDir, runnerDir } from "./paths.js";
|
|
6
7
|
import { prepareMobile } from "./adapters/mobile/prepare.js";
|
|
7
8
|
const HARNESS_ACTIONS = ["install", "verify", "cleanup", "live"];
|
|
@@ -186,11 +187,13 @@ function applyExtensionRuntimeEnv(target, action, args) {
|
|
|
186
187
|
const readyUrl = readRuntimeContextField(contextPath, "runtimeStart.readyUrl");
|
|
187
188
|
if (readyUrl) process.env.RECIPE_RUNTIME_READY_URL = readyUrl;
|
|
188
189
|
}
|
|
190
|
+
} else {
|
|
191
|
+
delete process.env.RECIPE_RUNTIME_CONTEXT;
|
|
189
192
|
}
|
|
193
|
+
resolveExtensionSlotPorts(target);
|
|
190
194
|
let result = [...args];
|
|
191
195
|
if (!hasArg(result, "--cdp-port")) {
|
|
192
|
-
const
|
|
193
|
-
const cdpPort = contextPort ?? process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
|
|
196
|
+
const cdpPort = process.env.RECIPE_CDP_PORT ?? process.env.CDP_PORT;
|
|
194
197
|
if (cdpPort) {
|
|
195
198
|
process.env.RECIPE_CDP_PORT = cdpPort;
|
|
196
199
|
process.env.CDP_PORT = cdpPort;
|
package/dist/heal-bounds.js
CHANGED
|
@@ -103,7 +103,7 @@ function checkHealBounds(target, output, state) {
|
|
|
103
103
|
return {
|
|
104
104
|
code: "SAME_RECOVERY_TWICE",
|
|
105
105
|
exitCode: EXIT.bounded,
|
|
106
|
-
message: "
|
|
106
|
+
message: state.recovered.length > 0 ? "one recovery already succeeded this invocation \u2014 bounded policy refuses a second recovery." : "one recovery was already attempted this invocation \u2014 refusing to loop.",
|
|
107
107
|
originalError
|
|
108
108
|
};
|
|
109
109
|
}
|
package/dist/mm-harness-cli.js
CHANGED
|
@@ -51,6 +51,22 @@ Example:
|
|
|
51
51
|
mm-harness status --json
|
|
52
52
|
mm-harness status --fast`
|
|
53
53
|
},
|
|
54
|
+
{
|
|
55
|
+
name: "checklist",
|
|
56
|
+
summary: "Mark task-local checklist progress through the bundled agent runtime.",
|
|
57
|
+
example: "mm-harness checklist mark <task-dir> start",
|
|
58
|
+
helpText: `mm-harness checklist mark <task-dir> <step> [options]
|
|
59
|
+
|
|
60
|
+
Mark checklist progress through mm-harness's bundled @farmslot/agent-runtime.
|
|
61
|
+
The task directory must contain CHECKLIST.md and checklist-target.json.
|
|
62
|
+
|
|
63
|
+
Steps: start | 1 | 2 | ... | complete | no-change | blocked
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
mm-harness checklist mark temp/tasks/recipe-cook/<task> start
|
|
67
|
+
mm-harness checklist mark temp/tasks/recipe-cook/<task> 1
|
|
68
|
+
mm-harness checklist mark temp/tasks/recipe-cook/<task> complete --mark-last`
|
|
69
|
+
},
|
|
54
70
|
{
|
|
55
71
|
name: "actions",
|
|
56
72
|
summary: "List the action vocabulary + field schemas (--raw dumps the raw action registry JSON).",
|
|
@@ -72,18 +88,18 @@ Example:
|
|
|
72
88
|
},
|
|
73
89
|
{
|
|
74
90
|
name: "stop",
|
|
75
|
-
summary: "Stop
|
|
91
|
+
summary: "Stop this checkout runtime (mobile Metro / Extension watcher + owned browser) and close its log windows.",
|
|
76
92
|
example: "mm-harness stop",
|
|
77
93
|
helpText: `mm-harness stop [flags]
|
|
78
94
|
|
|
79
|
-
Stop the
|
|
95
|
+
Stop the runtime this checkout owns and close its tmux log-tail windows,
|
|
80
96
|
scoped to this checkout so concurrent slots are untouched. Idempotent \u2014
|
|
81
97
|
nothing running is success, not an error. Behavior is per platform:
|
|
82
98
|
mobile stop the port-scoped Metro dev server
|
|
83
|
-
extension stop
|
|
99
|
+
extension stop webpack watcher, owned Chrome/CDP profile processes, viewers, and stale markers
|
|
84
100
|
core headless \u2014 no dev server to stop (teaching error)
|
|
85
101
|
|
|
86
|
-
--port <port>
|
|
102
|
+
--port <port> Metro/webpack port override; Extension CDP remains checkout-context scoped
|
|
87
103
|
--adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
|
|
88
104
|
--target <path> Checkout path (default: cwd)
|
|
89
105
|
--json Machine-readable output
|
|
@@ -101,7 +117,8 @@ Example:
|
|
|
101
117
|
Run one action in isolation as a one-node recipe through the real engine path.
|
|
102
118
|
Fuzzy short-name: 'ensure_unlocked' resolves to 'metamask.wallet.ensure_unlocked'
|
|
103
119
|
if unique; ambiguous = exit 2. Pass action fields as key=value shorthand or
|
|
104
|
-
with --arg k=v
|
|
120
|
+
with --arg k=v; numbers, booleans, arrays, and objects are parsed as JSON-like values.
|
|
121
|
+
Actions differ per adapter \u2014 list this checkout's with:
|
|
105
122
|
mm-harness actions.
|
|
106
123
|
|
|
107
124
|
--list List everything invocable for the adapter (actions + flows); no <action> needed
|
|
@@ -201,11 +218,12 @@ Example:
|
|
|
201
218
|
--adapter <mobile|extension|core> Adapter label (auto-detected when possible)
|
|
202
219
|
--base <ref> Diff base (default: PR base, then remote HEAD, then repo fallback)
|
|
203
220
|
--profile <fast|full> Validation depth (default: fast)
|
|
221
|
+
--fix Fix changed files, then validate them
|
|
204
222
|
--artifacts-dir <dir> Write validation-summary.json/.md and per-check logs
|
|
205
223
|
--json Machine-readable envelope
|
|
206
224
|
|
|
207
225
|
Example:
|
|
208
|
-
mm-harness check diff --profile fast --artifacts-dir artifacts/validation
|
|
226
|
+
mm-harness check diff --fix --profile fast --artifacts-dir artifacts/validation
|
|
209
227
|
mm-harness check diff --profile full --json`
|
|
210
228
|
},
|
|
211
229
|
{
|
|
@@ -306,6 +324,8 @@ Example:
|
|
|
306
324
|
helpText: `mm-harness verify [flags]
|
|
307
325
|
|
|
308
326
|
Check the runtime overlay is present and healthy (no app launch).
|
|
327
|
+
Extension live checks infer the checkout CDP port from runtime context,
|
|
328
|
+
pool, or slot suffix when --cdp-port is omitted.
|
|
309
329
|
|
|
310
330
|
--adapter <mobile|extension|core> Target adapter (auto-detected inside a checkout)
|
|
311
331
|
--target <path> Checkout path (default: cwd)
|
|
@@ -395,6 +415,7 @@ Example:
|
|
|
395
415
|
|
|
396
416
|
--worker Extension service-worker DevTools
|
|
397
417
|
--dev-menu Mobile RN developer menu
|
|
418
|
+
--no-open Resolve target/method/endpoint without opening a UI
|
|
398
419
|
--adapter <mobile|extension> Target adapter (auto-detected inside a checkout)
|
|
399
420
|
--target <path> Checkout path (default: cwd)
|
|
400
421
|
--json Machine-readable output
|
|
@@ -425,11 +446,13 @@ Example:
|
|
|
425
446
|
},
|
|
426
447
|
{
|
|
427
448
|
name: "fixtures",
|
|
428
|
-
summary: "Manage the canonical wallet fixture (wallet DATA only) \u2014 sync / set / generate / finalize.",
|
|
449
|
+
summary: "Manage the canonical wallet fixture (wallet DATA only) \u2014 init / sync / set / generate / finalize.",
|
|
429
450
|
example: "mm-harness fixtures set",
|
|
430
|
-
helpText: `mm-harness fixtures <sync|set|generate|finalize> [flags]
|
|
451
|
+
helpText: `mm-harness fixtures <init|sync|set|generate|finalize> [flags]
|
|
431
452
|
|
|
432
453
|
Manage the ONE canonical wallet fixture per checkout \u2014 wallet DATA only.
|
|
454
|
+
init Create it from --from <path>, or explicitly choose --dev for a
|
|
455
|
+
disposable public test wallet that must never hold real funds.
|
|
433
456
|
sync Refresh the wallet fixture files on the target.
|
|
434
457
|
set Apply the canonical fixture (SRP/password/accounts); the password is
|
|
435
458
|
read FROM the fixture, never typed.
|
|
@@ -441,6 +464,9 @@ Example:
|
|
|
441
464
|
Want different accounts? Edit the fixture file directly:
|
|
442
465
|
<checkout>/temp/recipe/runtime/wallet-fixture.json
|
|
443
466
|
|
|
467
|
+
--from <path> Existing wallet fixture source (init)
|
|
468
|
+
--dev Create a disposable public test wallet (init)
|
|
469
|
+
--force Replace an existing canonical fixture (init)
|
|
444
470
|
--fixture <path> Wallet fixture path (generate/finalize input; agent override for set/sync \u2014 env: RECIPE_WALLET_FIXTURE)
|
|
445
471
|
--out <path> generate: output fixture-state.json; finalize: optional validation report
|
|
446
472
|
--state <path> finalize: the pre-launch fixture-state.json to seed
|
|
@@ -453,6 +479,8 @@ Example:
|
|
|
453
479
|
--json Machine-readable output
|
|
454
480
|
|
|
455
481
|
Example:
|
|
482
|
+
mm-harness fixtures init --from /secure/path/wallet-fixture.json
|
|
483
|
+
mm-harness fixtures init --dev
|
|
456
484
|
mm-harness fixtures sync
|
|
457
485
|
mm-harness fixtures set
|
|
458
486
|
mm-harness fixtures generate --fixture wallet-fixture.json --out fixture-state.json
|
|
@@ -488,7 +516,7 @@ const HELP_GROUPS = [
|
|
|
488
516
|
{
|
|
489
517
|
title: "PROVE",
|
|
490
518
|
blurb: "run recipes and inspect readiness",
|
|
491
|
-
commands: ["run", "doctor", "check", "recipe-quality"]
|
|
519
|
+
commands: ["run", "doctor", "check", "checklist", "recipe-quality"]
|
|
492
520
|
},
|
|
493
521
|
{
|
|
494
522
|
title: "RUNTIME OVERLAY",
|
package/dist/runner.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
import { execSync } from "node:child_process";
|
|
2
2
|
import { createMetaMaskAdapters, createMetaMaskUiTransport } from "./adapters.js";
|
|
3
|
+
import {
|
|
4
|
+
mobileSourceFingerprint,
|
|
5
|
+
recordMobileSourceBaseline
|
|
6
|
+
} from "./adapters/mobile/source-freshness.js";
|
|
3
7
|
import { resolveMetaMaskMobileLifecycleTarget } from "./app-lifecycle.js";
|
|
4
8
|
import { loadMetaMaskExtensionActionManifest, loadMetaMaskMobileActionManifest } from "./manifest.js";
|
|
5
9
|
import { createMetaMaskRecordingTargetProvider } from "./recording-target.js";
|
|
@@ -40,10 +44,13 @@ async function createMetaMaskRunner(adapter, actionManifest, options = {}) {
|
|
|
40
44
|
})
|
|
41
45
|
});
|
|
42
46
|
const existing = new Set([...core, ...ui].map((entry) => entry.action));
|
|
43
|
-
const lifecycle =
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
+
const lifecycle = mobileSourceAwareLifecycleAdapters(
|
|
48
|
+
adapter,
|
|
49
|
+
createAppLifecycleAdapters({
|
|
50
|
+
actions,
|
|
51
|
+
targetProvider: { resolveTarget: resolveMetaMaskMobileLifecycleTarget }
|
|
52
|
+
})
|
|
53
|
+
);
|
|
47
54
|
const custom = createMetaMaskAdapters(adapter).filter(
|
|
48
55
|
(entry) => declaredActions.has(entry.action) && !existing.has(entry.action) && !lifecycle.some((lifecycleEntry) => lifecycleEntry.action === entry.action)
|
|
49
56
|
);
|
|
@@ -69,6 +76,28 @@ async function createMetaMaskRunner(adapter, actionManifest, options = {}) {
|
|
|
69
76
|
}
|
|
70
77
|
});
|
|
71
78
|
}
|
|
79
|
+
function mobileSourceAwareLifecycleAdapters(adapter, lifecycle) {
|
|
80
|
+
if (adapter !== "mobile") return lifecycle;
|
|
81
|
+
return lifecycle.map((entry) => ({
|
|
82
|
+
...entry,
|
|
83
|
+
async execute(node, context) {
|
|
84
|
+
const command = node.command ?? node.event ?? node.state;
|
|
85
|
+
const reloadsSource = command === "launch" || command === "foreground" || command === "restart";
|
|
86
|
+
const source = reloadsSource ? mobileSourceFingerprint(context.projectRoot) : void 0;
|
|
87
|
+
if (source?.status === "unavailable") {
|
|
88
|
+
throw new Error(`${source.error.message} Next: ${source.error.userAction}`);
|
|
89
|
+
}
|
|
90
|
+
const fingerprint = source?.fingerprint;
|
|
91
|
+
const result = await entry.execute(node, context);
|
|
92
|
+
if (fingerprint !== void 0 && !recordMobileSourceBaseline(context.projectRoot, fingerprint)) {
|
|
93
|
+
throw new Error(
|
|
94
|
+
"Mobile source changed while the app was reloading; the loaded-source baseline was not recorded. Next: retry app.lifecycle restart."
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
return result;
|
|
98
|
+
}
|
|
99
|
+
}));
|
|
100
|
+
}
|
|
72
101
|
function runnerProvenance() {
|
|
73
102
|
return {
|
|
74
103
|
source: runnerDir,
|
|
@@ -90,5 +119,6 @@ function runnerGitRef() {
|
|
|
90
119
|
export {
|
|
91
120
|
createMetaMaskExtensionRunner,
|
|
92
121
|
createMetaMaskMobileRunner,
|
|
93
|
-
createMetaMaskRunner
|
|
122
|
+
createMetaMaskRunner,
|
|
123
|
+
mobileSourceAwareLifecycleAdapters
|
|
94
124
|
};
|