@deeeed/metamask-harness 0.60.0 → 0.61.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 CHANGED
@@ -2,6 +2,17 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.61.0 - 2026-09-20
6
+
7
+ ### Added
8
+
9
+ - `status --watch` and `status --task` show child checklist units (Farmslot ADR-060): a unit registered on a row is read from `subtasks/index.json` beside the checklist and printed under the row that owns it, with its id, status, `done/total` and source, and its rows indented one level deeper. Piped output logs a registration, each child row tick and a child status move. Everything comes from the protocol helpers (`subtaskPaths`, `isSettledSubtaskStatus`, `enumerateChecklistCheckboxes`), so the harness parses nothing itself.
10
+ - Pins `@farmslot/protocol` 0.30.0, `@farmslot/agent-runtime` 0.13.0, `@farmslot/recipe-harness` 0.18.0 and `@farmslot/expo-recipe` 0.11.0 together, so one protocol copy serves the harness; a worker produces `subtasks/` once its node runs agent-runtime 0.13.0 (`mark sub`).
11
+
12
+ ### Fixed
13
+
14
+ - Mobile `ui.screenshot` records the node's `label` on its artifact-manifest entry (simctl and adb paths); the bridge leaf used to stamp the generic `ui.screenshot screenshot` and drop the label the action schema declares, so packaged evidence carried no caption. The Extension path already carried it.
15
+
5
16
  ## 0.60.0 - 2026-09-19
6
17
 
7
18
  ### Changed
@@ -1494,6 +1505,11 @@ Fresh-install hotfixes found in live validation of 0.5.0.
1494
1505
 
1495
1506
  ## Unreleased
1496
1507
 
1508
+ ### Added
1509
+
1510
+ - `status --watch` and `status --task` show child checklist units (Farmslot ADR-060): a unit registered on a row is read from `subtasks/index.json` beside the checklist and printed under the row that owns it, with its id, status, `done/total` and source, and its rows indented one level deeper. Piped output logs a registration, each child row tick and a child status move. Everything comes from the protocol helpers (`subtaskPaths`, `isSettledSubtaskStatus`, `enumerateChecklistCheckboxes`), so the harness parses nothing itself.
1511
+ - Pins `@farmslot/protocol` 0.30.0, `@farmslot/agent-runtime` 0.13.0, `@farmslot/recipe-harness` 0.18.0 and `@farmslot/expo-recipe` 0.11.0 together, so one protocol copy serves the harness; a worker produces `subtasks/` once its node runs agent-runtime 0.13.0 (`mark sub`).
1512
+
1497
1513
  ### Fixed
1498
1514
 
1499
1515
  - **`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.
@@ -2382,6 +2398,11 @@ Fresh-install hotfixes found in live validation of 0.5.0.
2382
2398
 
2383
2399
  ## Unreleased
2384
2400
 
2401
+ ### Added
2402
+
2403
+ - `status --watch` and `status --task` show child checklist units (Farmslot ADR-060): a unit registered on a row is read from `subtasks/index.json` beside the checklist and printed under the row that owns it, with its id, status, `done/total` and source, and its rows indented one level deeper. Piped output logs a registration, each child row tick and a child status move. Everything comes from the protocol helpers (`subtaskPaths`, `isSettledSubtaskStatus`, `enumerateChecklistCheckboxes`), so the harness parses nothing itself.
2404
+ - Pins `@farmslot/protocol` 0.30.0, `@farmslot/agent-runtime` 0.13.0, `@farmslot/recipe-harness` 0.18.0 and `@farmslot/expo-recipe` 0.11.0 together, so one protocol copy serves the harness; a worker produces `subtasks/` once its node runs agent-runtime 0.13.0 (`mark sub`).
2405
+
2385
2406
  ### Fixed
2386
2407
 
2387
2408
  - **`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.
@@ -130,6 +130,9 @@ function renderTaskView(view, title, explicitTask = false) {
130
130
  }
131
131
  phase = row.phase;
132
132
  lines.push(renderRow(row, view.currentStep, out));
133
+ for (const unit of view.subtasks.filter((entry) => entry.parentStep === row.number)) {
134
+ lines.push(...renderSubtask(unit, out));
135
+ }
133
136
  }
134
137
  const signal = view.signal;
135
138
  if (signal) {
@@ -171,6 +174,21 @@ function changedLines(previous, next) {
171
174
  if (!row.checked || before.get(row.number) === true) continue;
172
175
  lines.push(`checked ${row.number} ${row.label}${row.durationMs !== void 0 ? ` (${formatDuration(row.durationMs)})` : ""}`);
173
176
  }
177
+ const beforeUnits = new Map(previous.subtasks.map((unit) => [unit.id, unit]));
178
+ for (const unit of next.subtasks) {
179
+ const before2 = beforeUnits.get(unit.id);
180
+ if (!before2) {
181
+ lines.push(`subtask ${unit.id} registered on step ${unit.parentStep} (${unit.rows.length} rows)`);
182
+ }
183
+ const beforeRows = new Map((before2?.rows ?? []).map((row) => [row.number, row.checked]));
184
+ for (const row of unit.rows) {
185
+ if (!row.checked || beforeRows.get(row.number) === true) continue;
186
+ lines.push(`subtask ${unit.id} checked ${row.number} ${row.label}`);
187
+ }
188
+ if (before2 && before2.status !== unit.status) {
189
+ lines.push(`subtask ${unit.id} ${unit.status ?? "unknown"}`);
190
+ }
191
+ }
174
192
  if (previous.signal?.step !== next.signal?.step && next.signal?.step) {
175
193
  lines.push(`step ${stepLabel(next.signal.step)}`);
176
194
  }
@@ -198,6 +216,20 @@ function renderRow(row, currentStep, out) {
198
216
  const text = row.checked ? out("dim", label) : out("bold", label);
199
217
  return ` ${marker} ${out("dim", number)} ${text}${timing ? ` ${out("dim", timing)}` : ""}`;
200
218
  }
219
+ function renderSubtask(unit, out) {
220
+ const done = unit.rows.filter((row) => row.checked).length;
221
+ const header = joinParts([
222
+ `${out("label", "subtask")} ${out("accent", unit.id)}`,
223
+ statusWord(out, unit.status),
224
+ `${done}/${unit.rows.length}`,
225
+ unit.source?.ref && out("dim", unit.source.ref)
226
+ ]);
227
+ const current = unit.rows.find((row) => !row.checked);
228
+ return [
229
+ ` ${header}`,
230
+ ...unit.rows.map((row) => ` ${renderRow(row, current?.number, out)}`)
231
+ ];
232
+ }
201
233
  function libraryLabel(library) {
202
234
  const home = os.homedir();
203
235
  const location = home && library.path.startsWith(`${home}/`) ? `~${library.path.slice(home.length)}` : library.path;
package/dist/task-view.js CHANGED
@@ -7,8 +7,12 @@ import {
7
7
  deriveChecklistStepDurations,
8
8
  enumerateChecklistCheckboxes,
9
9
  INTERACTIVE_CHECKLIST_MARKDOWN,
10
+ isSettledSubtaskStatus,
10
11
  isTerminalWorkerSignalStatus,
11
12
  READINESS_RECORD,
13
+ SUBTASK_INDEX_FILE,
14
+ SUBTASKS_DIR,
15
+ subtaskPaths,
12
16
  WORKER_SIGNAL_FILE
13
17
  } from "@farmslot/protocol";
14
18
  import { readCommandJournal } from "./command-journal.js";
@@ -28,7 +32,9 @@ function collectTaskView(target, taskDir, now) {
28
32
  ({ name, root }) => ({ ...name ? { name } : {}, path: root })
29
33
  )
30
34
  };
31
- if (!taskDir) return { ...base, checklist: [], activity: { ongoing: false, reason: "no-task" } };
35
+ if (!taskDir) {
36
+ return { ...base, checklist: [], subtasks: [], activity: { ongoing: false, reason: "no-task" } };
37
+ }
32
38
  const handoff = readJsonObject(path.join(taskDir, HANDOFF_INPUT));
33
39
  const fixtures = readJsonObject(path.join(taskDir, "artifacts", "prepare", "fixtures.json"));
34
40
  const sandbox = readJsonObject(path.join(taskDir, "artifacts", READINESS_RECORD)) ?? readJsonObject(path.join(target, recipeRuntimeDir(), READINESS_RECORD));
@@ -81,6 +87,7 @@ function collectTaskView(target, taskDir, now) {
81
87
  }),
82
88
  sandbox: section({ ready: pickBoolean(sandbox, "ready") }),
83
89
  checklist,
90
+ subtasks: readSubtasks(taskDir),
84
91
  signal: section({
85
92
  status: pickString(signal, "status"),
86
93
  step: pickString(signal, "step"),
@@ -167,6 +174,32 @@ function readChecklistRows(file) {
167
174
  checked: item.checked
168
175
  }));
169
176
  }
177
+ function readSubtasks(taskDir) {
178
+ const index = readJsonObject(path.join(taskDir, SUBTASKS_DIR, SUBTASK_INDEX_FILE));
179
+ if (!index) return [];
180
+ const units = index.units;
181
+ if (!Array.isArray(units)) return [];
182
+ const views = [];
183
+ for (const entry of units) {
184
+ const unit = asObject(entry);
185
+ const id = pickString(unit, "id");
186
+ const parentStep = pickNumber(asObject(unit?.parent), "stepNumber");
187
+ if (id === void 0 || parentStep === void 0) continue;
188
+ const paths = subtaskPaths(id);
189
+ const signal = readJsonObject(path.join(taskDir, paths.signal));
190
+ const status = pickString(signal, "status");
191
+ const source = asObject(unit?.source);
192
+ views.push({
193
+ id,
194
+ parentStep,
195
+ ...status !== void 0 ? { status } : {},
196
+ settled: isSettledSubtaskStatus(status),
197
+ rows: readChecklistRows(path.join(taskDir, paths.checklist)),
198
+ ...section({ kind: pickString(source, "kind"), ref: pickString(source, "ref") }) ? { source: section({ kind: pickString(source, "kind"), ref: pickString(source, "ref") }) } : {}
199
+ });
200
+ }
201
+ return views;
202
+ }
170
203
  function trimLabel(text) {
171
204
  const plain = checklistStepName(text).replace(/\*\*/gu, "").replace(/^\d+\.\s*/u, "").replace(/\s+/gu, " ").trim();
172
205
  if (plain.length <= CHECKLIST_LABEL_LIMIT) return plain;
@@ -927,7 +927,7 @@ export async function simulatorScreenshot(input, relPath) {
927
927
  path: relative,
928
928
  type: 'screenshot',
929
929
  nodeId: input.context.nodeId,
930
- label: input.node?.description || `${input.action} screenshot`,
930
+ label: input.node?.label || input.node?.description || `${input.action} screenshot`,
931
931
  category: 'evidence',
932
932
  mimeType: 'image/png',
933
933
  metadata: {
@@ -976,7 +976,7 @@ async function androidScreenshot(input, relPath) {
976
976
  path: relative,
977
977
  type: 'screenshot',
978
978
  nodeId: input.context.nodeId,
979
- label: input.node?.description || `${input.action} screenshot`,
979
+ label: input.node?.label || input.node?.description || `${input.action} screenshot`,
980
980
  category: 'evidence',
981
981
  mimeType: 'image/png',
982
982
  metadata: {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@deeeed/metamask-harness",
3
- "version": "0.60.0",
3
+ "version": "0.61.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "mm-harness": "bin/mm-harness"
@@ -20,11 +20,11 @@
20
20
  "check:syntax": "find . -name '*.mjs' -print0 | xargs -0 -n1 node --check"
21
21
  },
22
22
  "dependencies": {
23
- "@farmslot/agent-runtime": "0.12.0",
24
- "@farmslot/expo-recipe": "0.10.0",
23
+ "@farmslot/agent-runtime": "0.13.0",
24
+ "@farmslot/expo-recipe": "0.11.0",
25
25
  "@farmslot/handoff": "^0.3.1",
26
- "@farmslot/protocol": "0.29.0",
27
- "@farmslot/recipe-harness": "0.17.0",
26
+ "@farmslot/protocol": "0.30.0",
27
+ "@farmslot/recipe-harness": "0.18.0",
28
28
  "agent-device": "0.19.3",
29
29
  "commander": "^12.0.0",
30
30
  "es-module-lexer": "2.3.1",