@deeeed/metamask-harness 0.92.17 → 0.92.19

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
@@ -2,19 +2,39 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.92.19 - 2026-10-17
6
+
7
+ ### Fixed
8
+
9
+ - Mobile `metamask.wallet.import` confirms the 8.17 password warning sheet after Create password, and still accepts the password checkbox on earlier releases.
10
+
11
+ ## 0.92.18 - 2026-10-16
12
+
5
13
  ### Changed
6
14
 
7
- - Point task initialization, closeout and PR-body documentation at the shared Farmslot commands.
8
- - Refresh Terminal contract goldens for the shipped console-error window, capture-provider metadata and engine evidence inventories.
15
+ - Consume the published parent acceptance inheritance worker packages so hosted task tooling uses the matching Protocol 0.53.0 contracts.
16
+
17
+ ### Removed
18
+
19
+ - Remove the retired `pr-body` route and duplicate renderer after callers moved to `farmslot recipe artifacts --format markdown`.
20
+
21
+ ### Fixed
22
+
23
+ - Recognize shared host environment lookups in the feature inventory and record the retired private Extension recorder PID handshake.
9
24
 
10
25
  ## 0.92.17 - 2026-10-16
11
26
 
12
27
  ### Changed
13
28
 
14
29
  - Consume the published pipeline-review contracts, native review results and aligned worker dependency set.
30
+ - Point task initialization, closeout and PR-body documentation at the shared Farmslot commands.
15
31
 
16
32
  ## 0.92.16 - 2026-10-16
17
33
 
34
+ ### Changed
35
+
36
+ - Refresh Terminal contract goldens for the shipped console-error window, capture-provider metadata and engine evidence inventories.
37
+
18
38
  ### Fixed
19
39
 
20
40
  - Consume the published hosted-task runtime and aligned dependency set so task tooling retains owning workspaces, declared dependencies and hoisted peers.
@@ -11,7 +11,6 @@ const SPEC = {
11
11
  { name: "help", desc: "Load version-matched recipe guidance (help review composes the review guide)", args: ["review"], flags: ["--json", "--adapter", "--target", "--domain"] },
12
12
  { name: "review", desc: "Materialize a review checklist composed from the base review and a team library", args: ["checklist"], flags: ["--domain", "--since", "--base", "--out", "--adapter", "--target", "--json"] },
13
13
  { name: "domain", desc: "Which team library owns the change (declared value, else owned-paths.json)", flags: ["--domain", "--base", "--adapter", "--target", "--json"] },
14
- { name: "pr-body", desc: "Render the publishable PR body: pr-description.md plus the recipe and run sections built from artifacts", args: ["render", "<task-dir>"], flags: ["--command", "--out", "--json"] },
15
14
  { name: "config", desc: "Per-engineer locations and optional advisor settings", args: ["list", "path", "get", "set", "unset"], flags: ["--json"] },
16
15
  { name: "tutorial", desc: "Open the visual recipe tutorial", flags: ["--json", "--no-open"] },
17
16
  { name: "setup-base", desc: "Bootstrap numbered product checkouts", flags: ["--dir", "--counts", "--only", "--dry-run", "--force", "--json", "--show-config", "--reset-config", "--skip-harness-update"] },
@@ -175,36 +175,6 @@ Example:
175
175
  })
176
176
  }
177
177
  },
178
- {
179
- name: "pr-body",
180
- summary: "Render the publishable PR body: the authored description plus the recipe and run log sections built from artifacts.",
181
- example: "mm-harness pr-body render temp/tasks/dev/TAT-1",
182
- helpText: `mm-harness pr-body render <task-dir> [flags]
183
-
184
- Reads <task-dir>/artifacts/pr-description.md (the PR body the worker wrote in
185
- the repository PR template shape) and inserts the machine sections rendered
186
- from artifacts: "Validation Recipe" from artifacts/recipe.json, "Validation
187
- Logs" from artifacts/recipe-run/report.md, and "Recipe Workflow" from
188
- artifacts/workflow.mmd when it exists. Any such section the worker pasted by
189
- hand is replaced. The sections go before the first checklist section, else at
190
- the end. Fences are always longer than any backtick run in the content and
191
- close on their own line. Writes <task-dir>/artifacts/pr-body.md.
192
-
193
- --command <text> Recipe invocation to show above the run log
194
- --out <file> Write elsewhere than artifacts/pr-body.md
195
- --json Machine-readable result
196
-
197
- Example:
198
- mm-harness pr-body render temp/tasks/dev/TAT-1 --json`,
199
- contract: {
200
- options: options(HELP, JSON, {
201
- "--command": value(),
202
- "--out": value()
203
- }),
204
- positionals: [{ label: "action", choices: ["render"] }, { label: "task-dir" }],
205
- minimumPositionals: 2
206
- }
207
- },
208
178
  {
209
179
  name: "config",
210
180
  summary: "Per-engineer locations and optional recipe advisor settings.",
@@ -48,7 +48,6 @@ import { handleHelp } from "./commands/help.js";
48
48
  import { handleLogs } from "./commands/logs.js";
49
49
  import { overlayCommandOptions } from "./commands/overlay.js";
50
50
  import { handlePin } from "./commands/pin.js";
51
- import { handlePrBody } from "./commands/pr-body.js";
52
51
  import { handlePrepare } from "./commands/prepare.js";
53
52
  import { handleProvision } from "./commands/provision.js";
54
53
  import { handleRecipeQuality } from "./commands/recipe-quality.js";
@@ -140,7 +139,6 @@ const PUBLIC_RUN = {
140
139
  help: { run: (args) => handleHelp(args, { packageRoot, harnessVersion: hostVersion() }), nudge: false },
141
140
  review: { run: handleReview },
142
141
  domain: { run: handleDomain },
143
- "pr-body": { run: handlePrBody },
144
142
  config: { run: handleConfig },
145
143
  tutorial: { run: (args) => handleTutorial(args, { harnessVersion: hostVersion() }), exit: "now", nudge: false },
146
144
  "setup-base": { run: handleSetupBase, raw: true, exit: "now", nudge: false },
@@ -200,7 +198,7 @@ const HELP_GROUPS = [
200
198
  {
201
199
  title: "PROVE",
202
200
  blurb: "run recipes and inspect readiness",
203
- commands: ["run", "last", "doctor", "check", "checklist", "review", "pr-body", "recipe-quality"]
201
+ commands: ["run", "last", "doctor", "check", "checklist", "review", "recipe-quality"]
204
202
  },
205
203
  {
206
204
  title: "RUNTIME OVERLAY",
@@ -62,7 +62,7 @@ Callers scanned (2026-10-02):
62
62
  | Command | Caller | Consumes | G: |
63
63
  |---|---|---|---|
64
64
  | `checklist mark <taskDir> {start\|<n>\|complete [--mark-last]\|blocked --reason <s>\|no-change --reason <s>\|--help}` | `{{TASK_DIR}}/mark` shim (`mark_cmd`) | exit code; `SIGNAL.json`, `CHECKLIST.md` | `task/mark-*` |
65
- | `pr-body render <taskDir> --json` (cwd = task dir) | gateway `pr-body-render.ts` (`pr_body_cmd`) | on failure `JSON.parse(stdout).error`, else stderr; then `artifacts/pr-body.md` | `task/pr-body-render-*` |
65
+ | `farmslot recipe artifacts <taskDir> --format markdown --out <file> --json` | migrated pack hooks and PR-description skills | successful exit, structured `error.message`, explicit `out`; no implicit output file | shared recipe-cli artifact contracts; retained Markdown baselines |
66
66
  | `review checklist --out <file> [--domain <d>] [--since <sha>]` | domain fixtures, review templates | the written file | `task/review-checklist` |
67
67
  | `check diff …`, `recipe-quality build …` | templates | exit code | not covered: run repo lint/test tooling |
68
68
 
@@ -0,0 +1,17 @@
1
+ // Submit the Mobile recovery-phrase import password on both onboarding variants.
2
+ // Releases before 8.17 gate Create password behind a checkbox; 8.17 (mobile #37398) shows the
3
+ // same warning as a bottom sheet after Create password and imports only once it is confirmed.
4
+ const IMPORT_PASSWORD_TEST_IDS = {
5
+ passwordTerms: 'password-understand-box',
6
+ createPassword: 'submit-button',
7
+ passwordWarningConfirm: 'password-reset-warning-sheet-confirm-button',
8
+ };
9
+
10
+ // `driver.pressIfPresent(testId)` presses a visible control and resolves false when it is absent.
11
+ export async function submitImportPassword(driver, timeoutMs) {
12
+ const acceptedCheckbox = await driver.pressIfPresent(IMPORT_PASSWORD_TEST_IDS.passwordTerms);
13
+ await driver.press(IMPORT_PASSWORD_TEST_IDS.createPassword, timeoutMs);
14
+ if (!acceptedCheckbox) {
15
+ await driver.press(IMPORT_PASSWORD_TEST_IDS.passwordWarningConfirm, timeoutMs);
16
+ }
17
+ }
@@ -13,6 +13,7 @@ import {
13
13
  } from '../platform/bridge.mjs';
14
14
  import { setMobileConsent } from '../analytics/consent-settings.mjs';
15
15
  import { ensureUnlocked } from './ensure_unlocked.mjs';
16
+ import { submitImportPassword } from './import-password.mjs';
16
17
  import { importWalletThroughNativeUi, isOpaqueMobileRuntime } from './native-ui.mjs';
17
18
 
18
19
  const TEST_IDS = {
@@ -22,8 +23,6 @@ const TEST_IDS = {
22
23
  continue: 'import-from-seed-screen-continue-button-id',
23
24
  password: 'create-password-first-input-field',
24
25
  confirmPassword: 'create-password-second-input-field',
25
- passwordTerms: 'password-understand-box',
26
- createPassword: 'submit-button',
27
26
  metricsCheckbox: 'optin-metrics-metrics-checkbox',
28
27
  metricsContinue: 'optin-metrics-continue-button-id',
29
28
  interestSkip: 'onboarding-interest-skip-button',
@@ -173,8 +172,10 @@ async function importThroughUi(input, timeoutMs, options) {
173
172
  await press(input, TEST_IDS.continue, timeoutMs);
174
173
  await setSecretInput(input, TEST_IDS.password, credentials.password, timeoutMs);
175
174
  await setSecretInput(input, TEST_IDS.confirmPassword, credentials.password, timeoutMs);
176
- await press(input, TEST_IDS.passwordTerms, timeoutMs);
177
- await press(input, TEST_IDS.createPassword, timeoutMs);
175
+ await submitImportPassword({
176
+ pressIfPresent: (testId) => pressIfPresent(input, testId),
177
+ press: (testId, ms) => press(input, testId, ms),
178
+ }, timeoutMs);
178
179
 
179
180
  const postPassword = await waitForPostPasswordScreen(input, timeoutMs);
180
181
  if (postPassword.route === 'OptinMetrics') {
@@ -7,6 +7,7 @@ import {
7
7
  nativeAppId,
8
8
  nativeSessionName,
9
9
  } from '../platform/native-session-name.mjs';
10
+ import { submitImportPassword } from './import-password.mjs';
10
11
 
11
12
  const TEST_IDS = {
12
13
  existingWallet: 'wallet-setup-screen-have-an-existing-wallet-button-id',
@@ -15,8 +16,6 @@ const TEST_IDS = {
15
16
  continue: 'import-from-seed-screen-continue-button-id',
16
17
  password: 'create-password-first-input-field',
17
18
  confirmPassword: 'create-password-second-input-field',
18
- passwordTerms: 'password-understand-box',
19
- createPassword: 'submit-button',
20
19
  metricsCheckbox: 'optin-metrics-metrics-checkbox',
21
20
  metricsContinue: 'optin-metrics-continue-button-id',
22
21
  interestSkip: 'onboarding-interest-skip-button',
@@ -139,8 +138,14 @@ export async function importWalletThroughNativeUi(input, credentials, options) {
139
138
  await native.fill(TEST_IDS.password, credentials.password, options.timeoutMs);
140
139
  await native.fill(TEST_IDS.confirmPassword, credentials.password, options.timeoutMs);
141
140
  await native.dismissKeyboard();
142
- await native.press(TEST_IDS.passwordTerms, options.timeoutMs);
143
- await native.press(TEST_IDS.createPassword, options.timeoutMs);
141
+ await submitImportPassword({
142
+ async pressIfPresent(testId) {
143
+ if (!await native.exists(testId, 3000)) return false;
144
+ await native.press(testId, options.timeoutMs);
145
+ return true;
146
+ },
147
+ press: (testId, timeoutMs) => native.press(testId, timeoutMs),
148
+ }, options.timeoutMs);
144
149
  }
145
150
  const onboarding = await native.completeOnboarding(options);
146
151
  return {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.92.17",
3
+ "version": "0.92.19",
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.32",
25
- "@farmslot/adapter-rn": "0.22.21",
26
- "@farmslot/adapter-sdk": "0.17.0",
27
- "@farmslot/adapter-web": "0.10.12",
28
- "@farmslot/agent-runtime": "0.27.0",
24
+ "@farmslot/adapter-node": "0.1.33",
25
+ "@farmslot/adapter-rn": "0.22.22",
26
+ "@farmslot/adapter-sdk": "0.17.1",
27
+ "@farmslot/adapter-web": "0.10.13",
28
+ "@farmslot/agent-runtime": "0.28.0",
29
29
  "@farmslot/handoff": "^0.3.1",
30
- "@farmslot/protocol": "0.52.0",
31
- "@farmslot/recipe-cli": "0.22.4",
32
- "@farmslot/recipe-runner": "0.38.3",
30
+ "@farmslot/protocol": "0.53.0",
31
+ "@farmslot/recipe-cli": "0.22.5",
32
+ "@farmslot/recipe-runner": "0.38.4",
33
33
  "agent-device": "0.19.3",
34
34
  "commander": "^12.0.0",
35
35
  "esbuild": "0.28.1",
@@ -1,31 +0,0 @@
1
- import { optionFlag, optionString, parseArgs, usageError } from "@farmslot/recipe-cli/harness";
2
- import fs from "node:fs";
3
- import path from "node:path";
4
- import { BODY_FILE, renderPrBody } from "../pr-body/render.js";
5
- const USAGE = "Usage: mm-harness pr-body render <task-dir> [--command <text>] [--out <file>] [--json]";
6
- async function handlePrBody(argv) {
7
- const parsed = parseArgs(argv);
8
- const [action, taskArg] = parsed.positional;
9
- if (action !== "render" || !taskArg) throw usageError(USAGE);
10
- const taskDir = path.resolve(taskArg);
11
- if (!fs.existsSync(path.join(taskDir, "artifacts"))) {
12
- throw usageError(`${taskDir} has no artifacts/ directory; pass the task directory mm-harness task init wrote.`);
13
- }
14
- const command = optionString(parsed.options, "command");
15
- const result = renderPrBody({ taskDir, ...command ? { command } : {} });
16
- const out = path.resolve(optionString(parsed.options, "out") ?? path.join(taskDir, "artifacts", BODY_FILE));
17
- fs.mkdirSync(path.dirname(out), { recursive: true });
18
- fs.writeFileSync(out, result.body);
19
- if (optionFlag(parsed.options, "json")) {
20
- process.stdout.write(`${JSON.stringify({ command: "pr-body", subcommand: "render", status: "ok", taskDir, out, ...result, body: void 0 }, null, 2)}
21
- `);
22
- } else {
23
- process.stdout.write(`pr body: ${out}
24
- ${result.sections.join("\n ")}
25
- `);
26
- }
27
- return 0;
28
- }
29
- export {
30
- handlePrBody
31
- };
@@ -1,144 +0,0 @@
1
- import fs from "node:fs";
2
- import { createRequire } from "node:module";
3
- import path from "node:path";
4
- const { readRecipeAttemptSelection } = createRequire(import.meta.url)(
5
- "@farmslot/agent-runtime/scripts/retained-recipe-attempt.cjs"
6
- );
7
- const PROSE_FILE = "pr-description.md";
8
- const BODY_FILE = "pr-body.md";
9
- const RECIPE_HEADING = "## **Validation Recipe**";
10
- const LOGS_HEADING = "## **Validation Logs**";
11
- const WORKFLOW_HEADING = "## **Recipe Workflow**";
12
- function headingKey(heading) {
13
- return heading.replace(/^#+\s*/, "").replace(/[*_`]/g, "").replace(/\s+/g, " ").trim().toLowerCase();
14
- }
15
- const MACHINE_KEYS = new Set([RECIPE_HEADING, LOGS_HEADING, WORKFLOW_HEADING].map(headingKey));
16
- function levelTwoHeadingLines(lines) {
17
- const found = [];
18
- lines.forEach((line, index) => {
19
- if (/^ {0,3}##(?!#)\s+\S/.test(line)) found.push(index);
20
- });
21
- return found;
22
- }
23
- function fenceFor(content) {
24
- let longest = 0;
25
- for (const run of content.match(/`+/g) ?? []) longest = Math.max(longest, run.length);
26
- return "`".repeat(Math.max(3, longest + 1));
27
- }
28
- function fenced(content, info = "") {
29
- const fence = fenceFor(content);
30
- return `${fence}${info}
31
- ${content.replace(/\n+$/, "")}
32
- ${fence}`;
33
- }
34
- function escapeHtml(text) {
35
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
36
- }
37
- function countRecipeNodes(recipe) {
38
- if (!recipe || typeof recipe !== "object") return 0;
39
- const record = recipe;
40
- if (Array.isArray(record.nodes)) return record.nodes.length;
41
- if (Array.isArray(record.steps)) return record.steps.length;
42
- const workflow = record.workflow;
43
- if (!workflow || typeof workflow !== "object") return 0;
44
- const graph = workflow.nodes;
45
- if (graph && typeof graph === "object" && !Array.isArray(graph)) return Object.keys(graph).length;
46
- return Object.values(workflow).reduce(
47
- (total, value) => total + (Array.isArray(value) ? value.length : 0),
48
- 0
49
- );
50
- }
51
- function recipeSection(file) {
52
- const recipe = JSON.parse(fs.readFileSync(file, "utf8"));
53
- const nodes = countRecipeNodes(recipe);
54
- const record = recipe && typeof recipe === "object" ? recipe : {};
55
- const title = typeof record.title === "string" ? record.title : typeof record.description === "string" ? record.description : "";
56
- const summary = escapeHtml(`recipe.json (${nodes} nodes${title ? ` \u2014 ${title.slice(0, 120)}` : ""})`);
57
- return {
58
- nodes,
59
- body: [`<details><summary>${summary}</summary>`, "", fenced(JSON.stringify(recipe, null, 2), "json"), "</details>"].join("\n")
60
- };
61
- }
62
- function logsSection(file, command) {
63
- const report = fs.readFileSync(file, "utf8");
64
- const status = /^Status:\s*(\S+)/m.exec(report)?.[1];
65
- const nodes = /^Nodes:\s*(.+?)\s*$/m.exec(report)?.[1];
66
- const summary = escapeHtml(status || nodes ? `Full output (${[nodes, status].filter(Boolean).join(", ")})` : "Full output");
67
- const lines = [];
68
- if (command) lines.push("Command:", "", fenced(command, "bash"), "");
69
- lines.push(`<details><summary>${summary}</summary>`, "", fenced(report), "</details>");
70
- return lines.join("\n");
71
- }
72
- function trimEnd(lines) {
73
- let end = lines.length;
74
- while (end > 0 && !lines[end - 1].trim()) end -= 1;
75
- return lines.slice(0, end);
76
- }
77
- function renderPrBody(input) {
78
- const artifacts = path.join(input.taskDir, "artifacts");
79
- const prosePath = path.join(artifacts, PROSE_FILE);
80
- if (!fs.existsSync(prosePath)) {
81
- throw new Error(`Missing ${path.relative(input.taskDir, prosePath)}: write the PR description there first.`);
82
- }
83
- const recipePath = path.join(artifacts, "recipe.json");
84
- const selected = readRecipeAttemptSelection(input.taskDir);
85
- const reportRoot = selected?.relativeArtifactRoot ?? "recipe-run";
86
- const reportPath = path.join(artifacts, reportRoot, "report.md");
87
- const workflowPath = path.join(artifacts, "workflow.mmd");
88
- const machine = [];
89
- let recipe = null;
90
- if (fs.existsSync(recipePath)) {
91
- const rendered = recipeSection(recipePath);
92
- recipe = { nodes: rendered.nodes, path: path.relative(input.taskDir, recipePath) };
93
- machine.push([RECIPE_HEADING, rendered.body]);
94
- } else {
95
- machine.push([RECIPE_HEADING, "Not applicable: this task has no `artifacts/recipe.json`."]);
96
- }
97
- const hasReport = fs.existsSync(reportPath);
98
- machine.push([
99
- LOGS_HEADING,
100
- hasReport ? logsSection(reportPath, input.command) : `Not applicable: no recipe run report under \`artifacts/${reportRoot}/\`.`
101
- ]);
102
- const hasWorkflow = fs.existsSync(workflowPath);
103
- if (hasWorkflow) {
104
- machine.push([
105
- WORKFLOW_HEADING,
106
- ["<details><summary>workflow.mmd</summary>", "", fenced(fs.readFileSync(workflowPath, "utf8"), "mermaid"), "</details>"].join("\n")
107
- ]);
108
- }
109
- const lines = fs.readFileSync(prosePath, "utf8").replace(/\r\n?/g, "\n").split("\n");
110
- const headings = levelTwoHeadingLines(lines);
111
- const keep = new Array(lines.length).fill(true);
112
- let insertAt = lines.length;
113
- headings.forEach((start, at) => {
114
- const end = headings[at + 1] ?? lines.length;
115
- const key = headingKey(lines[start]);
116
- if (MACHINE_KEYS.has(key)) keep.fill(false, start, end);
117
- else if (insertAt === lines.length && /checklist/.test(key)) insertAt = start;
118
- });
119
- const before = lines.slice(0, insertAt).filter((_, index) => keep[index]);
120
- const after = lines.slice(insertAt).filter((_, index) => keep[insertAt + index]);
121
- const generated = machine.flatMap(([heading, body2]) => [heading, "", body2, ""]);
122
- const body = [...trimEnd(before), "", ...generated, ...after].join("\n").replace(/\n{3,}/g, "\n\n").replace(/\n*$/, "\n");
123
- return {
124
- body,
125
- sections: machine.map(([heading]) => heading),
126
- recipe,
127
- run: hasReport ? { path: path.relative(input.taskDir, reportPath) } : null,
128
- workflow: hasWorkflow ? { path: path.relative(input.taskDir, workflowPath) } : null
129
- };
130
- }
131
- export {
132
- BODY_FILE,
133
- LOGS_HEADING,
134
- PROSE_FILE,
135
- RECIPE_HEADING,
136
- WORKFLOW_HEADING,
137
- countRecipeNodes,
138
- escapeHtml,
139
- fenceFor,
140
- fenced,
141
- headingKey,
142
- levelTwoHeadingLines,
143
- renderPrBody
144
- };