@deeeed/metamask-harness 0.62.0 → 0.63.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 +11 -0
- package/dist/commands/prepare.js +30 -2
- package/dist/commands/status-watch.js +43 -7
- package/dist/commands/status.js +8 -1
- package/dist/devices.js +4 -2
- package/dist/mm-harness-cli.js +4 -2
- package/dist/paths.js +2 -0
- package/dist/task-view.js +45 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.63.0 - 2026-09-23
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- Show the task's acceptance criteria and recorded verdicts in `status`, `status --watch`, and `status --task`, using the Farmslot handoff and acceptance ledger.
|
|
10
|
+
- Show the active preparation step and elapsed time in `status --watch` and `status --task` even when the agent buffers preparation output.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Allow 30 seconds for iOS simulator discovery so a cold CoreSimulator startup does not fail at five seconds.
|
|
15
|
+
|
|
5
16
|
## 0.62.0 - 2026-09-22
|
|
6
17
|
|
|
7
18
|
### Added
|
package/dist/commands/prepare.js
CHANGED
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
import { color, stripAnsi } from "../cli-color.js";
|
|
8
8
|
import { runnerProvenance } from "../doctor.js";
|
|
9
9
|
import { detectAdapter } from "../harness.js";
|
|
10
|
-
import { harnessExecutable, manifestPath, recipeRuntimeDir } from "../paths.js";
|
|
10
|
+
import { harnessExecutable, manifestPath, PREPARE_PROGRESS_ARTIFACT, recipeRuntimeDir } from "../paths.js";
|
|
11
11
|
import { optionFlag, optionString, parseArgs, targetPath, usageError } from "./parse-args.js";
|
|
12
12
|
import { EXIT } from "./shared.js";
|
|
13
13
|
import { formatDuration } from "./status-watch.js";
|
|
@@ -336,13 +336,32 @@ async function handlePrepare(argv) {
|
|
|
336
336
|
const remoteFlagArgs = remoteFlag === void 0 ? [] : ["--remote-flag", remoteFlag];
|
|
337
337
|
let mobilePlatform = mobilePlatformOption(optionString(options, "mobilePlatform"));
|
|
338
338
|
const recordPath = path.join(artifactsDir, READINESS_RECORD);
|
|
339
|
+
const progressPath = path.join(artifactsDir, PREPARE_PROGRESS_ARTIFACT);
|
|
339
340
|
const chain = stepChain(platform);
|
|
340
341
|
const view = json ? void 0 : createStepView(chain, stepHints(chain, lastRunDurations(recordPath)));
|
|
341
342
|
view?.begin();
|
|
342
343
|
const steps = [];
|
|
344
|
+
let activeStep;
|
|
345
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
346
|
+
const writeProgress = () => {
|
|
347
|
+
fs.mkdirSync(path.dirname(progressPath), { recursive: true });
|
|
348
|
+
const temporaryPath = `${progressPath}.${process.pid}.tmp`;
|
|
349
|
+
fs.writeFileSync(temporaryPath, `${JSON.stringify({
|
|
350
|
+
schemaVersion: 1,
|
|
351
|
+
startedAt,
|
|
352
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
353
|
+
total: chain.length,
|
|
354
|
+
steps: steps.filter(({ id }) => chain.includes(id)).map(({ id, status, durationMs }) => ({ id, status, durationMs })),
|
|
355
|
+
...activeStep ? { active: activeStep } : {}
|
|
356
|
+
})}
|
|
357
|
+
`);
|
|
358
|
+
fs.renameSync(temporaryPath, progressPath);
|
|
359
|
+
};
|
|
343
360
|
const failed = () => steps.some((step) => step.status === "fail");
|
|
344
361
|
const addStep = (step) => {
|
|
345
362
|
steps.push(step);
|
|
363
|
+
activeStep = void 0;
|
|
364
|
+
writeProgress();
|
|
346
365
|
view?.finish(step);
|
|
347
366
|
return step;
|
|
348
367
|
};
|
|
@@ -352,10 +371,17 @@ async function handlePrepare(argv) {
|
|
|
352
371
|
}
|
|
353
372
|
const step = await runStep(executable, target, artifactsDir, id, args, {
|
|
354
373
|
json,
|
|
355
|
-
onSpawn: (
|
|
374
|
+
onSpawn: (stepStartedAt) => {
|
|
375
|
+
activeStep = { id, startedAt: new Date(stepStartedAt).toISOString() };
|
|
376
|
+
writeProgress();
|
|
377
|
+
view?.start(id, stepStartedAt);
|
|
378
|
+
}
|
|
356
379
|
});
|
|
357
380
|
return addStep(step);
|
|
358
381
|
};
|
|
382
|
+
const heartbeat = setInterval(() => {
|
|
383
|
+
if (activeStep) writeProgress();
|
|
384
|
+
}, 1e4);
|
|
359
385
|
try {
|
|
360
386
|
await run("doctor", ["doctor", "--fix", "--json", "--adapter", platform, ...targetArgs]);
|
|
361
387
|
await run("status", ["status", "--json", ...targetArgs]);
|
|
@@ -384,6 +410,7 @@ async function handlePrepare(argv) {
|
|
|
384
410
|
}
|
|
385
411
|
await run("verify", ["verify", "--json", "--adapter", platform, ...targetArgs]);
|
|
386
412
|
} finally {
|
|
413
|
+
clearInterval(heartbeat);
|
|
387
414
|
view?.end();
|
|
388
415
|
}
|
|
389
416
|
const provenance = runnerProvenance(manifestPath(platform));
|
|
@@ -404,6 +431,7 @@ async function handlePrepare(argv) {
|
|
|
404
431
|
fs.mkdirSync(artifactsDir, { recursive: true });
|
|
405
432
|
fs.writeFileSync(recordPath, `${JSON.stringify(record, null, 2)}
|
|
406
433
|
`);
|
|
434
|
+
fs.rmSync(progressPath, { force: true });
|
|
407
435
|
if (json) {
|
|
408
436
|
console.log(JSON.stringify(record, null, 2));
|
|
409
437
|
} else {
|
|
@@ -103,9 +103,13 @@ function renderTaskView(view, title, explicitTask = false) {
|
|
|
103
103
|
const contextLine = joinParts([
|
|
104
104
|
view.libraries.length > 0 && `libraries ${out("info", view.libraries.map(libraryLabel).join(" "))}`,
|
|
105
105
|
showTask && view.fixture && `fixture ${out("info", path.basename(view.fixture.fixture ?? "unknown"))} ${statusWord(out, view.fixture.status)}${view.fixture.walletReset ? out("warn", " reset") : ""}`,
|
|
106
|
-
showTask && view.sandbox?.ready !== void 0 && `sandbox ${view.sandbox.ready ? out("ok", "ready") : out("warn", "not ready")}`
|
|
106
|
+
showTask && (view.prepare ? `sandbox ${out("info", "preparing")}` : view.sandbox?.ready !== void 0 && `sandbox ${view.sandbox.ready ? out("ok", "ready") : out("warn", "not ready")}`)
|
|
107
107
|
]);
|
|
108
108
|
if (contextLine) lines.push(contextLine);
|
|
109
|
+
if (showTask && view.acceptance?.length) lines.push("", ...renderAcceptanceLines(view));
|
|
110
|
+
if (showTask && view.prepare) {
|
|
111
|
+
lines.push(`prepare ${out("info", view.prepare.step)} \xB7 ${view.prepare.completed}/${view.prepare.total} steps \xB7 ${formatDuration(view.prepare.elapsedMs)} elapsed`);
|
|
112
|
+
}
|
|
109
113
|
const last = view.lastCommand;
|
|
110
114
|
if (showTask && last) {
|
|
111
115
|
lines.push(joinParts([
|
|
@@ -148,6 +152,19 @@ function renderTaskView(view, title, explicitTask = false) {
|
|
|
148
152
|
}
|
|
149
153
|
return lines;
|
|
150
154
|
}
|
|
155
|
+
function renderAcceptanceLines(view) {
|
|
156
|
+
if (!view.acceptance?.length) return [];
|
|
157
|
+
const out = (style, text) => color(style, text, { stream: process.stdout });
|
|
158
|
+
const proven = view.acceptance.filter((row) => row.status?.verdict === "proven").length;
|
|
159
|
+
return [
|
|
160
|
+
`acceptance criteria ${proven}/${view.acceptance.length} proven`,
|
|
161
|
+
...view.acceptance.map((row) => {
|
|
162
|
+
const verdict = row.status?.verdict ?? "no verdict";
|
|
163
|
+
const style = verdict === "proven" ? "ok" : verdict === "weak" ? "warn" : verdict === "no verdict" ? "dim" : "err";
|
|
164
|
+
return ` ${row.id} [${out(style, verdict)}] ${row.text}`;
|
|
165
|
+
})
|
|
166
|
+
];
|
|
167
|
+
}
|
|
151
168
|
function idleReason(view) {
|
|
152
169
|
if (!view.taskDir) return "no task directory under temp/tasks";
|
|
153
170
|
const { reason, idleMs, branchCarriesTicket } = view.activity;
|
|
@@ -161,15 +178,33 @@ function idleReason(view) {
|
|
|
161
178
|
}
|
|
162
179
|
function changedLines(previous, next) {
|
|
163
180
|
if (!previous || previous.taskDir !== next.taskDir) {
|
|
164
|
-
return [
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
181
|
+
return [
|
|
182
|
+
joinParts([
|
|
183
|
+
path.basename(next.checkout.target),
|
|
184
|
+
next.taskDir ? path.basename(next.taskDir) : "no-task-dir",
|
|
185
|
+
`status=${next.signal?.status ?? "unknown"}`,
|
|
186
|
+
`step=${next.signal?.step ? stepLabel(next.signal.step) : "none"}`
|
|
187
|
+
], " "),
|
|
188
|
+
...(next.acceptance ?? []).map((row) => `${row.id} ${row.status?.verdict ?? "no verdict"} ${row.text}`),
|
|
189
|
+
...next.prepare ? [`prepare ${next.prepare.step} started (${next.prepare.completed}/${next.prepare.total} steps)`] : []
|
|
190
|
+
];
|
|
170
191
|
}
|
|
171
192
|
const lines = [];
|
|
193
|
+
for (const step of next.prepare?.steps ?? []) {
|
|
194
|
+
if (!previous.prepare?.steps.some((prior) => prior.id === step.id && prior.status === step.status)) {
|
|
195
|
+
lines.push(`prepare ${step.id} ${step.status} (${formatDuration(step.durationMs)})`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
if (next.prepare && (previous.prepare?.step !== next.prepare.step || previous.prepare.startedAt !== next.prepare.startedAt)) {
|
|
199
|
+
lines.push(`prepare ${next.prepare.step} started (${next.prepare.completed}/${next.prepare.total} steps)`);
|
|
200
|
+
}
|
|
172
201
|
const before = new Map(previous.checklist.map((row) => [row.number, row.checked]));
|
|
202
|
+
const beforeAcceptance = new Map(previous.acceptance?.map((row) => [row.id, row.status?.verdict]));
|
|
203
|
+
for (const row of next.acceptance ?? []) {
|
|
204
|
+
if (beforeAcceptance.get(row.id) !== row.status?.verdict || !beforeAcceptance.has(row.id)) {
|
|
205
|
+
lines.push(`${row.id} ${row.status?.verdict ?? "no verdict"} ${row.text}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
173
208
|
for (const row of next.checklist) {
|
|
174
209
|
if (!row.checked || before.get(row.number) === true) continue;
|
|
175
210
|
lines.push(`checked ${row.number} ${row.label}${row.durationMs !== void 0 ? ` (${formatDuration(row.durationMs)})` : ""}`);
|
|
@@ -254,6 +289,7 @@ export {
|
|
|
254
289
|
changedLines,
|
|
255
290
|
formatDuration,
|
|
256
291
|
handleStatusTaskView,
|
|
292
|
+
renderAcceptanceLines,
|
|
257
293
|
renderTaskView,
|
|
258
294
|
runStatusWatch
|
|
259
295
|
};
|
package/dist/commands/status.js
CHANGED
|
@@ -20,7 +20,8 @@ import {
|
|
|
20
20
|
renderMobileLiveBlock
|
|
21
21
|
} from "./mobile-device-view.js";
|
|
22
22
|
import { ADAPTER_DETECT_NEXT, EXIT, usageOut } from "./shared.js";
|
|
23
|
-
import { handleStatusTaskView } from "./status-watch.js";
|
|
23
|
+
import { handleStatusTaskView, renderAcceptanceLines } from "./status-watch.js";
|
|
24
|
+
import { collectTaskView, findTaskDir } from "../task-view.js";
|
|
24
25
|
import { optionFlag, optionString, targetPath } from "./parse-args.js";
|
|
25
26
|
async function handleStatus({ options }) {
|
|
26
27
|
const target = targetPath(options);
|
|
@@ -106,6 +107,12 @@ async function handleStatus({ options }) {
|
|
|
106
107
|
const out = (style, text) => color(style, text, { stream: process.stdout });
|
|
107
108
|
console.log(`${out("label", "status")} ${out("bold", adapter)} ${out("dim", target)}`);
|
|
108
109
|
console.log(renderCheckoutView(view));
|
|
110
|
+
if (!fast) {
|
|
111
|
+
const taskView = collectTaskView(target, findTaskDir(target), Date.now());
|
|
112
|
+
if (taskView.activity.ongoing && taskView.acceptance?.length) {
|
|
113
|
+
console.log(renderAcceptanceLines(taskView).join("\n"));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
109
116
|
if (adapter !== "mobile") renderFeatureFlagLine(staticFeatureFlags, out);
|
|
110
117
|
if (deviceView) renderMobileDeviceList(deviceView, out);
|
|
111
118
|
if (!fast && adapter === "mobile" && deviceView !== null && deviceView.allConnectedDevices.length > 0) {
|
package/dist/devices.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { resolveMobileToolPath } from "../library/actions/mobile/platform/tool-paths.mjs";
|
|
3
|
+
const IOS_DEVICE_DISCOVERY_TIMEOUT_MS = 3e4;
|
|
3
4
|
function reportProbeFailure(errors, platform, error) {
|
|
4
5
|
const code = error?.code;
|
|
5
6
|
if (code === "ENOENT") return;
|
|
6
|
-
const
|
|
7
|
+
const timeoutSeconds = platform === "ios" ? IOS_DEVICE_DISCOVERY_TIMEOUT_MS / 1e3 : 5;
|
|
8
|
+
const reason = code === "ETIMEDOUT" ? `timed out after ${timeoutSeconds} seconds` : "failed";
|
|
7
9
|
const probeCode = code ?? error?.status;
|
|
8
10
|
errors.push({
|
|
9
11
|
platform,
|
|
@@ -61,7 +63,7 @@ function listIosSimulators(scope, errors) {
|
|
|
61
63
|
try {
|
|
62
64
|
output = execFileSync("xcrun", ["simctl", "list", "devices", scope, "-j"], {
|
|
63
65
|
encoding: "utf8",
|
|
64
|
-
timeout:
|
|
66
|
+
timeout: IOS_DEVICE_DISCOVERY_TIMEOUT_MS,
|
|
65
67
|
stdio: ["ignore", "pipe", "ignore"]
|
|
66
68
|
});
|
|
67
69
|
} catch (error) {
|
package/dist/mm-harness-cli.js
CHANGED
|
@@ -192,8 +192,10 @@ Example:
|
|
|
192
192
|
The task view reads task files only \u2014 no probe, no launch, no write \u2014 and shows
|
|
193
193
|
the same facts whether a skill or a control plane started the run: slot and
|
|
194
194
|
device isolation, template and flow, fixture and sandbox readiness, then the
|
|
195
|
-
checklist with the time each row took
|
|
196
|
-
running
|
|
195
|
+
checklist with the time each row took, how long the current row has been
|
|
196
|
+
running, and acceptance criteria with recorded verdicts. The home dashboard
|
|
197
|
+
also shows criteria for an ongoing task. --task without --watch prints it once.
|
|
198
|
+
With --json, --watch emits one
|
|
197
199
|
object per change (newline-delimited) instead of redrawing.
|
|
198
200
|
|
|
199
201
|
The checkout view (also under "view" in --json, and printed by doctor) is what
|
package/dist/paths.js
CHANGED
|
@@ -5,6 +5,7 @@ const runnerDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..
|
|
|
5
5
|
const pathDefaults = readPathDefaults();
|
|
6
6
|
const DEFAULT_RECIPE_RUNTIME_DIR = pathDefaults.recipeRuntimeDir;
|
|
7
7
|
const DEFAULT_RECIPE_HARNESS_ROOT = pathDefaults.recipeHarnessRoot;
|
|
8
|
+
const PREPARE_PROGRESS_ARTIFACT = "prepare/progress.json";
|
|
8
9
|
function recipeRuntimeDir() {
|
|
9
10
|
return validateRelativeRecipePath("RECIPE_RUNTIME_DIR", process.env.RECIPE_RUNTIME_DIR || DEFAULT_RECIPE_RUNTIME_DIR);
|
|
10
11
|
}
|
|
@@ -171,6 +172,7 @@ function isMissingPackageError(error, packageName) {
|
|
|
171
172
|
export {
|
|
172
173
|
DEFAULT_RECIPE_HARNESS_ROOT,
|
|
173
174
|
DEFAULT_RECIPE_RUNTIME_DIR,
|
|
175
|
+
PREPARE_PROGRESS_ARTIFACT,
|
|
174
176
|
assertAdapter,
|
|
175
177
|
extensionIdPath,
|
|
176
178
|
harnessExecutable,
|
package/dist/task-view.js
CHANGED
|
@@ -2,6 +2,12 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { HANDOFF_INPUT, latestTaskDir } from "@farmslot/agent-runtime";
|
|
5
|
+
import {
|
|
6
|
+
ACCEPTANCE_STATUS_ARTIFACT,
|
|
7
|
+
acceptanceCriteriaView,
|
|
8
|
+
acceptanceCriterionId,
|
|
9
|
+
validateAcceptanceStatusLedger
|
|
10
|
+
} from "@farmslot/protocol/contracts/acceptance";
|
|
5
11
|
import {
|
|
6
12
|
checklistStepName,
|
|
7
13
|
deriveChecklistStepDurations,
|
|
@@ -17,7 +23,7 @@ import {
|
|
|
17
23
|
} from "@farmslot/protocol";
|
|
18
24
|
import { readCommandJournal } from "./command-journal.js";
|
|
19
25
|
import { resolveRuntimeContextPath } from "./harness.js";
|
|
20
|
-
import { recipeRuntimeDir } from "./paths.js";
|
|
26
|
+
import { PREPARE_PROGRESS_ARTIFACT, recipeRuntimeDir } from "./paths.js";
|
|
21
27
|
import { parseLibraryPathEnv } from "./review/knowledge.js";
|
|
22
28
|
const CHECKLIST_LABEL_LIMIT = 70;
|
|
23
29
|
const TASK_SILENT_AFTER_MS = 10 * 60 * 1e3;
|
|
@@ -42,8 +48,22 @@ function collectTaskView(target, taskDir, now) {
|
|
|
42
48
|
const checklist = readChecklistRows(path.join(taskDir, INTERACTIVE_CHECKLIST_MARKDOWN));
|
|
43
49
|
const template = asObject(handoff?.executionTemplate);
|
|
44
50
|
const task = asObject(handoff?.task);
|
|
51
|
+
const rawCriteria = task?.acceptanceCriteria;
|
|
52
|
+
if (rawCriteria !== void 0 && !Array.isArray(rawCriteria)) {
|
|
53
|
+
throw new Error(`${path.join(taskDir, HANDOFF_INPUT)}: task.acceptanceCriteria must be an array.`);
|
|
54
|
+
}
|
|
55
|
+
const criteria = rawCriteria?.map((text, index) => ({
|
|
56
|
+
id: acceptanceCriterionId(index),
|
|
57
|
+
text: String(text)
|
|
58
|
+
})).filter((criterion) => criterion.text.trim().length > 0) ?? [];
|
|
59
|
+
const rawLedger = readJsonObject(path.join(taskDir, ACCEPTANCE_STATUS_ARTIFACT));
|
|
60
|
+
if (rawLedger) {
|
|
61
|
+
const issues = validateAcceptanceStatusLedger(rawLedger);
|
|
62
|
+
if (issues.length > 0) throw new Error(`${ACCEPTANCE_STATUS_ARTIFACT}: ${issues.join("; ")}`);
|
|
63
|
+
}
|
|
45
64
|
const startedAtIso = pickString(handoff, "startedAt");
|
|
46
65
|
const startedAt = parseTime(startedAtIso);
|
|
66
|
+
const prepare = readPrepareProgress(taskDir, now, startedAt, parseTime(pickString(sandbox, "recordedAt")));
|
|
47
67
|
const timing = applyTiming(checklist, signal, startedAtIso, now);
|
|
48
68
|
const ticket = pickString(task, "ticket");
|
|
49
69
|
const journal = readLastCommand(target, now);
|
|
@@ -56,7 +76,8 @@ function collectTaskView(target, taskDir, now) {
|
|
|
56
76
|
startedAt,
|
|
57
77
|
parseTime(pickString(signal, "timestamp")),
|
|
58
78
|
...readMarkEvents(signal).map((event) => Date.parse(event.checkedAt)),
|
|
59
|
-
commandCounts ? commandAt : void 0
|
|
79
|
+
commandCounts ? commandAt : void 0,
|
|
80
|
+
prepare ? parseTime(prepare.updatedAt) : void 0
|
|
60
81
|
],
|
|
61
82
|
branch: base.checkout.branch,
|
|
62
83
|
ticket,
|
|
@@ -79,6 +100,7 @@ function collectTaskView(target, taskDir, now) {
|
|
|
79
100
|
title: pickString(task, "title"),
|
|
80
101
|
ticket
|
|
81
102
|
}),
|
|
103
|
+
...criteria.length > 0 || rawLedger ? { acceptance: acceptanceCriteriaView(criteria, rawLedger ? rawLedger : null) } : {},
|
|
82
104
|
fixture: section({
|
|
83
105
|
fixture: pickString(fixtures, "fixture"),
|
|
84
106
|
canonicalFixture: pickString(fixtures, "canonicalFixture"),
|
|
@@ -86,6 +108,7 @@ function collectTaskView(target, taskDir, now) {
|
|
|
86
108
|
walletReset: pickBoolean(fixtures, "walletReset")
|
|
87
109
|
}),
|
|
88
110
|
sandbox: section({ ready: pickBoolean(sandbox, "ready") }),
|
|
111
|
+
...prepare ? { prepare } : {},
|
|
89
112
|
checklist,
|
|
90
113
|
subtasks: readSubtasks(taskDir),
|
|
91
114
|
signal: section({
|
|
@@ -99,6 +122,26 @@ function collectTaskView(target, taskDir, now) {
|
|
|
99
122
|
activity
|
|
100
123
|
};
|
|
101
124
|
}
|
|
125
|
+
function readPrepareProgress(taskDir, now, taskStartedAt, recordedAt) {
|
|
126
|
+
const progress = readJsonObject(path.join(taskDir, "artifacts", PREPARE_PROGRESS_ARTIFACT));
|
|
127
|
+
const active = asObject(progress?.active);
|
|
128
|
+
const step = pickString(active, "id");
|
|
129
|
+
const startedAt = pickString(active, "startedAt");
|
|
130
|
+
const stepStartedAt = parseTime(startedAt);
|
|
131
|
+
const runStartedAt = parseTime(pickString(progress, "startedAt"));
|
|
132
|
+
const updatedAtIso = pickString(progress, "updatedAt");
|
|
133
|
+
const updatedAt = parseTime(updatedAtIso);
|
|
134
|
+
const total = pickNumber(progress, "total");
|
|
135
|
+
if (!step || !["doctor", "status", "launch", "fixtures", "verify"].includes(step) || stepStartedAt === void 0 || runStartedAt === void 0 || updatedAt === void 0 || now - updatedAt > 3e4 || total === void 0 || total < 1 || taskStartedAt !== void 0 && runStartedAt < taskStartedAt || recordedAt !== void 0 && recordedAt >= runStartedAt) return void 0;
|
|
136
|
+
const steps = Array.isArray(progress?.steps) ? progress.steps.flatMap((value) => {
|
|
137
|
+
const item = asObject(value);
|
|
138
|
+
const id = pickString(item, "id");
|
|
139
|
+
const status = pickString(item, "status");
|
|
140
|
+
const durationMs = pickNumber(item, "durationMs");
|
|
141
|
+
return id && ["doctor", "status", "launch", "fixtures", "verify"].includes(id) && status && ["pass", "fail", "skipped"].includes(status) && durationMs !== void 0 ? [{ id, status, durationMs }] : [];
|
|
142
|
+
}) : [];
|
|
143
|
+
return { step, startedAt, updatedAt: updatedAtIso, elapsedMs: Math.max(0, now - stepStartedAt), completed: steps.length, total, steps };
|
|
144
|
+
}
|
|
102
145
|
function readLastCommand(target, now) {
|
|
103
146
|
const { record } = readCommandJournal(target);
|
|
104
147
|
if (!record) return void 0;
|