@deeeed/metamask-harness 0.56.0 → 0.58.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 +20 -0
- package/adapters/manifest.json +3 -3
- package/adapters/mobile/inject.sh +4 -7
- package/dist/adapters/mobile/verify.js +549 -0
- package/dist/commands/checklist.js +6 -16
- package/dist/commands/run-engine.js +2 -2
- package/dist/commands/status-watch.js +13 -2
- package/dist/execution-provenance.js +16 -28
- package/dist/harness.js +36 -8
- package/dist/task-view.js +79 -76
- package/package.json +5 -5
- package/adapters/mobile/verify.sh +0 -557
|
@@ -2,6 +2,8 @@ import { spawnSync } from "node:child_process";
|
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import { createRequire } from "node:module";
|
|
4
4
|
import path from "node:path";
|
|
5
|
+
import { HANDOFF_INPUT } from "@farmslot/agent-runtime";
|
|
6
|
+
import { enumerateChecklistCheckboxes, isTerminalWorkerSignalStatus } from "@farmslot/protocol";
|
|
5
7
|
import { EXIT } from "./shared.js";
|
|
6
8
|
const require2 = createRequire(import.meta.url);
|
|
7
9
|
const MARK_SCRIPT = require2.resolve(
|
|
@@ -88,7 +90,7 @@ function runCloseout(taskDir, args, stdio) {
|
|
|
88
90
|
}
|
|
89
91
|
}
|
|
90
92
|
function stageLearningPackage(taskDir, step, markArgs) {
|
|
91
|
-
if (!["complete", "no-change"].includes(step ?? "") || markArgs.includes("--skip-learnings") || !fs.existsSync(path.join(taskDir,
|
|
93
|
+
if (!["complete", "no-change"].includes(step ?? "") || markArgs.includes("--skip-learnings") || !fs.existsSync(path.join(taskDir, HANDOFF_INPUT))) {
|
|
92
94
|
return;
|
|
93
95
|
}
|
|
94
96
|
try {
|
|
@@ -108,7 +110,7 @@ function terminalSignalStatus(taskDir) {
|
|
|
108
110
|
} catch {
|
|
109
111
|
return null;
|
|
110
112
|
}
|
|
111
|
-
return signal.status
|
|
113
|
+
return isTerminalWorkerSignalStatus(signal.status) ? signal.status ?? null : null;
|
|
112
114
|
}
|
|
113
115
|
function preserveTerminalSignal(taskDir, step) {
|
|
114
116
|
if (step !== "start" && numericMarkStep(step) === null) return false;
|
|
@@ -124,20 +126,8 @@ function numericMarkStep(step) {
|
|
|
124
126
|
return Number.isInteger(stepNumber) && stepNumber >= 1 ? stepNumber : null;
|
|
125
127
|
}
|
|
126
128
|
function checklistStepRequiresDiffPass(checklist, stepNumber) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
for (const line of checklist.split(/\n/u)) {
|
|
130
|
-
if (/^\s*```/u.test(line)) {
|
|
131
|
-
inFence = !inFence;
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
if (inFence) continue;
|
|
135
|
-
const match = line.match(/^\s*[-*]\s+\[(?: |x|X)\]\s+(.*)$/u);
|
|
136
|
-
if (!match) continue;
|
|
137
|
-
seen += 1;
|
|
138
|
-
if (seen === stepNumber) return /mm-harness check diff/u.test(match[1] ?? "");
|
|
139
|
-
}
|
|
140
|
-
return false;
|
|
129
|
+
const step = enumerateChecklistCheckboxes(checklist).find((item) => item.stepNumber === stepNumber);
|
|
130
|
+
return step !== void 0 && /mm-harness check diff/u.test(step.rawLabel);
|
|
141
131
|
}
|
|
142
132
|
function checkDiffGateReady(taskDir, step) {
|
|
143
133
|
const stepNumber = numericMarkStep(step);
|
|
@@ -89,7 +89,7 @@ async function runRecipe(adapter, recipe, artifactsDir, projectRoot, actionManif
|
|
|
89
89
|
preExecute
|
|
90
90
|
);
|
|
91
91
|
if (preExecuteDrift.length > 0) {
|
|
92
|
-
const provenancePath2 = writeExecutionProvenance(
|
|
92
|
+
const provenancePath2 = await writeExecutionProvenance(
|
|
93
93
|
absoluteArtifactsDir,
|
|
94
94
|
provenanceSnapshots,
|
|
95
95
|
preExecuteDrift
|
|
@@ -134,7 +134,7 @@ async function runRecipe(adapter, recipe, artifactsDir, projectRoot, actionManif
|
|
|
134
134
|
const end = await captureExecutionProvenance(provenanceInput, "end");
|
|
135
135
|
provenanceSnapshots.push(end);
|
|
136
136
|
const endDrift = executionProvenanceDrift(provenanceSnapshots[0], end);
|
|
137
|
-
const provenancePath = writeExecutionProvenance(
|
|
137
|
+
const provenancePath = await writeExecutionProvenance(
|
|
138
138
|
absoluteArtifactsDir,
|
|
139
139
|
provenanceSnapshots,
|
|
140
140
|
endDrift,
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import os from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { isTerminalWorkerSignalStatus } from "@farmslot/protocol";
|
|
3
4
|
import { color } from "../cli-color.js";
|
|
4
5
|
import {
|
|
5
6
|
CHECKLIST_LABEL_LIMIT,
|
|
6
7
|
collectTaskView,
|
|
7
8
|
findTaskDir,
|
|
8
|
-
isTerminalStatus,
|
|
9
9
|
TASK_SILENT_AFTER_MS
|
|
10
10
|
} from "../task-view.js";
|
|
11
11
|
import { EXIT } from "./shared.js";
|
|
@@ -59,7 +59,7 @@ async function runStatusWatch(options, deps) {
|
|
|
59
59
|
`);
|
|
60
60
|
}
|
|
61
61
|
previous = view;
|
|
62
|
-
const terminal =
|
|
62
|
+
const terminal = isTerminalWorkerSignalStatus(view.signal?.status);
|
|
63
63
|
if (view.activity.ongoing) sawInProgress = true;
|
|
64
64
|
if (!options.watch || terminal && (sawInProgress || options.taskDir !== void 0)) return EXIT.ok;
|
|
65
65
|
await deps.wait(REFRESH_MS);
|
|
@@ -106,6 +106,14 @@ function renderTaskView(view, title, explicitTask = false) {
|
|
|
106
106
|
showTask && 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
|
+
const last = view.lastCommand;
|
|
110
|
+
if (showTask && last) {
|
|
111
|
+
lines.push(joinParts([
|
|
112
|
+
`last command ${out("info", joinParts([last.command, last.subject], " "))}`,
|
|
113
|
+
last.verdict && statusWord(out, last.verdict),
|
|
114
|
+
last.ageMs !== void 0 && `${formatDuration(last.ageMs)} ago`
|
|
115
|
+
]));
|
|
116
|
+
}
|
|
109
117
|
lines.push("");
|
|
110
118
|
if (!view.activity.ongoing) {
|
|
111
119
|
lines.push(out("warn", "no ongoing task detected"));
|
|
@@ -169,6 +177,9 @@ function changedLines(previous, next) {
|
|
|
169
177
|
if (previous.signal?.status !== next.signal?.status) {
|
|
170
178
|
lines.push(`status ${next.signal?.status ?? "unknown"}`);
|
|
171
179
|
}
|
|
180
|
+
if (next.lastCommand && previous.lastCommand?.at !== next.lastCommand.at) {
|
|
181
|
+
lines.push(`command ${joinParts([next.lastCommand.command, next.lastCommand.subject], " ")}${next.lastCommand.verdict ? ` ${next.lastCommand.verdict}` : ""}`);
|
|
182
|
+
}
|
|
172
183
|
return lines;
|
|
173
184
|
}
|
|
174
185
|
function formatDuration(ms) {
|
|
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import fs, { constants as fsConstants } from "node:fs";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import { mobileSourceFingerprint } from "./adapters/mobile/source-freshness.js";
|
|
6
|
+
import { indexArtifactManifest } from "./artifact-files.js";
|
|
6
7
|
import { importRecipeProtocol, runnerDir } from "./paths.js";
|
|
7
8
|
const gitContexts = /* @__PURE__ */ new Map();
|
|
8
9
|
let protocolPromise;
|
|
@@ -102,7 +103,7 @@ function executionProvenanceDrift(start, current) {
|
|
|
102
103
|
}
|
|
103
104
|
return drift;
|
|
104
105
|
}
|
|
105
|
-
function writeExecutionProvenance(artifactsDir, snapshots, drift, artifactManifestPath) {
|
|
106
|
+
async function writeExecutionProvenance(artifactsDir, snapshots, drift, artifactManifestPath) {
|
|
106
107
|
const provenancePath = path.join(path.resolve(artifactsDir), "execution-provenance.json");
|
|
107
108
|
const record = {
|
|
108
109
|
schemaVersion: 1,
|
|
@@ -113,7 +114,20 @@ function writeExecutionProvenance(artifactsDir, snapshots, drift, artifactManife
|
|
|
113
114
|
fs.mkdirSync(path.dirname(provenancePath), { recursive: true });
|
|
114
115
|
fs.writeFileSync(provenancePath, `${JSON.stringify(record, null, 2)}
|
|
115
116
|
`);
|
|
116
|
-
if (artifactManifestPath)
|
|
117
|
+
if (artifactManifestPath) {
|
|
118
|
+
await indexArtifactManifest(artifactManifestPath, [
|
|
119
|
+
{
|
|
120
|
+
path: "execution-provenance.json",
|
|
121
|
+
type: "json",
|
|
122
|
+
label: "Execution provenance",
|
|
123
|
+
category: "system",
|
|
124
|
+
metadata: {
|
|
125
|
+
valid: record.valid,
|
|
126
|
+
errorCode: record.valid ? null : "PROVENANCE_DRIFT"
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
]);
|
|
130
|
+
}
|
|
117
131
|
return provenancePath;
|
|
118
132
|
}
|
|
119
133
|
function recipeFileDigest(protocol, recipePath) {
|
|
@@ -309,32 +323,6 @@ function compareSource(drift, phase, prefix, start, current) {
|
|
|
309
323
|
function compare(drift, phase, field, start, current) {
|
|
310
324
|
if (start !== current) drift.push({ phase, field, start, current });
|
|
311
325
|
}
|
|
312
|
-
function indexProvenanceArtifact(artifactManifestPath, record) {
|
|
313
|
-
let manifest;
|
|
314
|
-
try {
|
|
315
|
-
manifest = JSON.parse(fs.readFileSync(artifactManifestPath, "utf8"));
|
|
316
|
-
} catch {
|
|
317
|
-
return;
|
|
318
|
-
}
|
|
319
|
-
if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)) return;
|
|
320
|
-
const document = manifest;
|
|
321
|
-
const artifacts = Array.isArray(document.artifacts) ? document.artifacts : [];
|
|
322
|
-
document.artifacts = [
|
|
323
|
-
...artifacts.filter((artifact) => !artifact || typeof artifact !== "object" || Array.isArray(artifact) || artifact.path !== "execution-provenance.json"),
|
|
324
|
-
{
|
|
325
|
-
path: "execution-provenance.json",
|
|
326
|
-
type: "json",
|
|
327
|
-
label: "Execution provenance",
|
|
328
|
-
category: "system",
|
|
329
|
-
metadata: {
|
|
330
|
-
valid: record.valid,
|
|
331
|
-
errorCode: record.valid ? null : "PROVENANCE_DRIFT"
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
];
|
|
335
|
-
fs.writeFileSync(artifactManifestPath, `${JSON.stringify(document, null, 2)}
|
|
336
|
-
`);
|
|
337
|
-
}
|
|
338
326
|
export {
|
|
339
327
|
ProvenanceDriftError,
|
|
340
328
|
captureExecutionProvenance,
|
package/dist/harness.js
CHANGED
|
@@ -259,6 +259,9 @@ Next: mm-harness install --platform core --target ${target}`
|
|
|
259
259
|
}
|
|
260
260
|
return { command: delegate, prefixArgs: ["doctor", "--adapter", "core"] };
|
|
261
261
|
}
|
|
262
|
+
if (adapter === "mobile" && action === "verify") {
|
|
263
|
+
return { inProcess: "mobile-verify" };
|
|
264
|
+
}
|
|
262
265
|
const installedScript = path.join(recipeHarnessPath(target, adapter), "scripts", `${action}.sh`);
|
|
263
266
|
if (fs.existsSync(installedScript)) {
|
|
264
267
|
return { command: installedScript, prefixArgs: [] };
|
|
@@ -336,6 +339,29 @@ async function handleHarness(argv) {
|
|
|
336
339
|
console.error(colorHumanMessage(`\u2192 ${harnessAction} (${adapter}${detected}) \u2014 target: ${target}`));
|
|
337
340
|
}
|
|
338
341
|
const start = Date.now();
|
|
342
|
+
if ("inProcess" in dispatch) {
|
|
343
|
+
const { runMobileVerify } = await import("./adapters/mobile/verify.js");
|
|
344
|
+
let exitCode;
|
|
345
|
+
try {
|
|
346
|
+
exitCode = await runMobileVerify(forwardArgs, {
|
|
347
|
+
out: quiet ? () => {
|
|
348
|
+
} : (line) => {
|
|
349
|
+
(json ? process.stderr : process.stdout).write(`${line}
|
|
350
|
+
`);
|
|
351
|
+
},
|
|
352
|
+
err: quiet ? () => {
|
|
353
|
+
} : (line) => {
|
|
354
|
+
process.stderr.write(`${line}
|
|
355
|
+
`);
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
} catch (error) {
|
|
359
|
+
if (!quiet) process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
360
|
+
`);
|
|
361
|
+
exitCode = 1;
|
|
362
|
+
}
|
|
363
|
+
return reportHarnessOutcome(harnessAction, adapter, target, exitCode, autoDetected, json, quiet, start);
|
|
364
|
+
}
|
|
339
365
|
if (shellLeafMissing(dispatch.command)) {
|
|
340
366
|
const message = missingShellLeafMessage(dispatch.command);
|
|
341
367
|
if (quiet) {
|
|
@@ -363,7 +389,6 @@ async function handleHarness(argv) {
|
|
|
363
389
|
stdio: quiet ? "ignore" : json ? ["inherit", 2, "inherit"] : "inherit",
|
|
364
390
|
env: process.env
|
|
365
391
|
});
|
|
366
|
-
const seconds = ((Date.now() - start) / 1e3).toFixed(1);
|
|
367
392
|
if (result.error) {
|
|
368
393
|
if (quiet) {
|
|
369
394
|
return 1;
|
|
@@ -381,27 +406,30 @@ async function handleHarness(argv) {
|
|
|
381
406
|
}
|
|
382
407
|
return 1;
|
|
383
408
|
}
|
|
384
|
-
|
|
409
|
+
return reportHarnessOutcome(harnessAction, adapter, target, result.status ?? 1, autoDetected, json, quiet, start);
|
|
410
|
+
}
|
|
411
|
+
function reportHarnessOutcome(action, adapter, target, exitCode, autoDetected, json, quiet, start) {
|
|
412
|
+
const seconds = ((Date.now() - start) / 1e3).toFixed(1);
|
|
385
413
|
if (quiet) {
|
|
386
414
|
return exitCode;
|
|
387
415
|
} else if (json) {
|
|
388
416
|
console.log(
|
|
389
417
|
harnessSummary(
|
|
390
|
-
|
|
418
|
+
action,
|
|
391
419
|
adapter,
|
|
392
420
|
target,
|
|
393
421
|
exitCode === 0 ? "pass" : "fail",
|
|
394
422
|
exitCode,
|
|
395
423
|
autoDetected,
|
|
396
|
-
exitCode === 0 ? void 0 : { code: "HARNESS_FAILED", message: `${
|
|
424
|
+
exitCode === 0 ? void 0 : { code: "HARNESS_FAILED", message: `${action} ${adapter} failed (exit ${exitCode})`, userAction: failureHint(adapter, action) }
|
|
397
425
|
)
|
|
398
426
|
);
|
|
399
427
|
} else if (exitCode === 0) {
|
|
400
|
-
console.error(colorHumanMessage(`\u2713 ${
|
|
401
|
-
Next: ${successNext(adapter,
|
|
428
|
+
console.error(colorHumanMessage(`\u2713 ${action} ${adapter} passed (${seconds}s)
|
|
429
|
+
Next: ${successNext(adapter, action, target)}`));
|
|
402
430
|
} else {
|
|
403
|
-
console.error(colorHumanMessage(`\u2717 ${
|
|
404
|
-
${failureHint(adapter,
|
|
431
|
+
console.error(colorHumanMessage(`\u2717 ${action} ${adapter} failed (exit ${exitCode}, ${seconds}s)
|
|
432
|
+
${failureHint(adapter, action)}`));
|
|
405
433
|
}
|
|
406
434
|
return exitCode;
|
|
407
435
|
}
|
package/dist/task-view.js
CHANGED
|
@@ -1,34 +1,24 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
2
|
import fs from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
|
+
import { HANDOFF_INPUT, latestTaskDir } from "@farmslot/agent-runtime";
|
|
5
|
+
import {
|
|
6
|
+
checklistStepName,
|
|
7
|
+
deriveChecklistStepDurations,
|
|
8
|
+
enumerateChecklistCheckboxes,
|
|
9
|
+
INTERACTIVE_CHECKLIST_MARKDOWN,
|
|
10
|
+
isTerminalWorkerSignalStatus,
|
|
11
|
+
READINESS_RECORD,
|
|
12
|
+
WORKER_SIGNAL_FILE
|
|
13
|
+
} from "@farmslot/protocol";
|
|
14
|
+
import { readCommandJournal } from "./command-journal.js";
|
|
4
15
|
import { resolveRuntimeContextPath } from "./harness.js";
|
|
16
|
+
import { recipeRuntimeDir } from "./paths.js";
|
|
5
17
|
import { parseLibraryPathEnv } from "./review/knowledge.js";
|
|
6
|
-
const TASK_SEARCH_DEPTH = 3;
|
|
7
18
|
const CHECKLIST_LABEL_LIMIT = 70;
|
|
8
|
-
const CHECKLIST_ROW = /^\s*-\s\[([ xX])\]\s*(\d+)\.\s*(.*)$/u;
|
|
9
|
-
const CHECKLIST_PHASE = /^##\s+(.+?)\s*$/u;
|
|
10
19
|
const TASK_SILENT_AFTER_MS = 10 * 60 * 1e3;
|
|
11
20
|
function findTaskDir(target) {
|
|
12
|
-
|
|
13
|
-
let bySignal;
|
|
14
|
-
let byChecklist;
|
|
15
|
-
const consider = (best, dir, file) => {
|
|
16
|
-
const stat = fs.statSync(path.join(dir, file), { throwIfNoEntry: false });
|
|
17
|
-
if (!stat?.isFile()) return best;
|
|
18
|
-
return best && best.mtimeMs >= stat.mtimeMs ? best : { dir, mtimeMs: stat.mtimeMs };
|
|
19
|
-
};
|
|
20
|
-
const visit = (dir, depth) => {
|
|
21
|
-
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
22
|
-
if (!entry.isDirectory()) continue;
|
|
23
|
-
const child = path.join(dir, entry.name);
|
|
24
|
-
bySignal = consider(bySignal, child, "SIGNAL.json");
|
|
25
|
-
byChecklist = consider(byChecklist, child, "CHECKLIST.md");
|
|
26
|
-
if (depth < TASK_SEARCH_DEPTH) visit(child, depth + 1);
|
|
27
|
-
}
|
|
28
|
-
};
|
|
29
|
-
if (!fs.statSync(root, { throwIfNoEntry: false })?.isDirectory()) return void 0;
|
|
30
|
-
visit(root, 1);
|
|
31
|
-
return (bySignal ?? byChecklist)?.dir;
|
|
21
|
+
return latestTaskDir(path.join(target, "temp", "tasks"));
|
|
32
22
|
}
|
|
33
23
|
function collectTaskView(target, taskDir, now) {
|
|
34
24
|
const base = {
|
|
@@ -39,20 +29,29 @@ function collectTaskView(target, taskDir, now) {
|
|
|
39
29
|
)
|
|
40
30
|
};
|
|
41
31
|
if (!taskDir) return { ...base, checklist: [], activity: { ongoing: false, reason: "no-task" } };
|
|
42
|
-
const handoff = readJsonObject(path.join(taskDir,
|
|
43
|
-
const provenance = readJsonObject(path.join(taskDir, "artifacts", "harness-provenance.json"));
|
|
32
|
+
const handoff = readJsonObject(path.join(taskDir, HANDOFF_INPUT));
|
|
44
33
|
const fixtures = readJsonObject(path.join(taskDir, "artifacts", "prepare", "fixtures.json"));
|
|
45
|
-
const sandbox = readJsonObject(path.join(taskDir, "artifacts",
|
|
46
|
-
const signal = readJsonObject(path.join(taskDir,
|
|
47
|
-
const checklist = readChecklistRows(path.join(taskDir,
|
|
34
|
+
const sandbox = readJsonObject(path.join(taskDir, "artifacts", READINESS_RECORD)) ?? readJsonObject(path.join(target, recipeRuntimeDir(), READINESS_RECORD));
|
|
35
|
+
const signal = readJsonObject(path.join(taskDir, WORKER_SIGNAL_FILE));
|
|
36
|
+
const checklist = readChecklistRows(path.join(taskDir, INTERACTIVE_CHECKLIST_MARKDOWN));
|
|
48
37
|
const template = asObject(handoff?.executionTemplate);
|
|
49
38
|
const task = asObject(handoff?.task);
|
|
50
|
-
const
|
|
51
|
-
const
|
|
39
|
+
const startedAtIso = pickString(handoff, "startedAt");
|
|
40
|
+
const startedAt = parseTime(startedAtIso);
|
|
41
|
+
const timing = applyTiming(checklist, signal, startedAtIso, now);
|
|
52
42
|
const ticket = pickString(task, "ticket");
|
|
43
|
+
const journal = readLastCommand(target, now);
|
|
44
|
+
const commandAt = parseTime(journal?.at);
|
|
45
|
+
const commandCounts = commandAt !== void 0 && (startedAt === void 0 || commandAt >= startedAt);
|
|
46
|
+
const lastCommand = commandCounts ? journal : void 0;
|
|
53
47
|
const activity = taskActivity({
|
|
54
48
|
status: pickString(signal, "status"),
|
|
55
|
-
lastWrites: [
|
|
49
|
+
lastWrites: [
|
|
50
|
+
startedAt,
|
|
51
|
+
parseTime(pickString(signal, "timestamp")),
|
|
52
|
+
...readMarkEvents(signal).map((event) => Date.parse(event.checkedAt)),
|
|
53
|
+
commandCounts ? commandAt : void 0
|
|
54
|
+
],
|
|
56
55
|
branch: base.checkout.branch,
|
|
57
56
|
ticket,
|
|
58
57
|
now
|
|
@@ -60,9 +59,10 @@ function collectTaskView(target, taskDir, now) {
|
|
|
60
59
|
return {
|
|
61
60
|
...base,
|
|
62
61
|
taskDir,
|
|
62
|
+
// The readiness record is the only place a harness records itself for a task.
|
|
63
63
|
harness: section({
|
|
64
|
-
version: pickString(asObject(
|
|
65
|
-
sourceKind: pickString(asObject(
|
|
64
|
+
version: pickString(asObject(sandbox?.harness), "version"),
|
|
65
|
+
sourceKind: pickString(asObject(sandbox?.harness), "source")
|
|
66
66
|
}),
|
|
67
67
|
template: section({
|
|
68
68
|
id: pickString(template, "id"),
|
|
@@ -87,10 +87,26 @@ function collectTaskView(target, taskDir, now) {
|
|
|
87
87
|
timestamp: pickString(signal, "timestamp"),
|
|
88
88
|
attemptId: pickString(signal, "attemptId")
|
|
89
89
|
}),
|
|
90
|
+
...lastCommand ? { lastCommand } : {},
|
|
90
91
|
...timing,
|
|
91
92
|
activity
|
|
92
93
|
};
|
|
93
94
|
}
|
|
95
|
+
function readLastCommand(target, now) {
|
|
96
|
+
const { record } = readCommandJournal(target);
|
|
97
|
+
if (!record) return void 0;
|
|
98
|
+
const first = record.args[0];
|
|
99
|
+
const subject = first !== void 0 && !first.startsWith("-") ? first : void 0;
|
|
100
|
+
const at = record.finishedAt || record.startedAt;
|
|
101
|
+
const atMs = parseTime(at);
|
|
102
|
+
return {
|
|
103
|
+
command: record.command,
|
|
104
|
+
...subject ? { subject } : {},
|
|
105
|
+
verdict: record.verdict,
|
|
106
|
+
...at ? { at } : {},
|
|
107
|
+
...atMs !== void 0 ? { ageMs: Math.max(0, now - atMs) } : {}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
94
110
|
function taskActivity(input) {
|
|
95
111
|
const writes = input.lastWrites.filter((value) => value !== void 0);
|
|
96
112
|
const idleMs = writes.length > 0 ? input.now - Math.max(...writes) : void 0;
|
|
@@ -100,71 +116,59 @@ function taskActivity(input) {
|
|
|
100
116
|
...branchCarriesTicket !== void 0 ? { branchCarriesTicket } : {}
|
|
101
117
|
};
|
|
102
118
|
if (input.status === void 0 && writes.length === 0) return { ongoing: false, reason: "no-task", ...extra };
|
|
103
|
-
if (
|
|
119
|
+
if (isTerminalWorkerSignalStatus(input.status)) return { ongoing: false, reason: "finished", ...extra };
|
|
104
120
|
if (idleMs !== void 0 && idleMs > TASK_SILENT_AFTER_MS) return { ongoing: false, reason: "silent", ...extra };
|
|
105
121
|
return { ongoing: true, ...extra };
|
|
106
122
|
}
|
|
107
|
-
function
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
123
|
+
function applyTiming(rows, signal, startedAtIso, now) {
|
|
124
|
+
const events = readMarkEvents(signal);
|
|
125
|
+
const durations = new Map(
|
|
126
|
+
deriveChecklistStepDurations({ schemaVersion: 1, events }, startedAtIso).map((step) => [
|
|
127
|
+
step.stepNumber,
|
|
128
|
+
step.durationMs
|
|
129
|
+
])
|
|
130
|
+
);
|
|
131
|
+
const marks = new Map(events.map((event) => [event.stepNumber, Date.parse(event.checkedAt)]));
|
|
115
132
|
for (const row of rows) {
|
|
116
|
-
const
|
|
117
|
-
if (
|
|
118
|
-
if (previousAt !== void 0) row.durationMs = checkedAt - previousAt;
|
|
119
|
-
if (firstAt === void 0) firstAt = checkedAt;
|
|
120
|
-
previousAt = checkedAt;
|
|
121
|
-
lastAt = checkedAt;
|
|
133
|
+
const durationMs = durations.get(row.number);
|
|
134
|
+
if (durationMs !== void 0) row.durationMs = durationMs;
|
|
122
135
|
}
|
|
136
|
+
const instants = [...marks.values()];
|
|
137
|
+
const lastAt = instants.length > 0 ? Math.max(...instants) : void 0;
|
|
138
|
+
const firstAt = instants.length > 0 ? Math.min(...instants) : void 0;
|
|
123
139
|
const current = rows.find((row) => !row.checked);
|
|
124
140
|
if (current && lastAt !== void 0) current.runningMs = now - lastAt;
|
|
125
|
-
const origin =
|
|
141
|
+
const origin = parseTime(startedAtIso) ?? firstAt;
|
|
126
142
|
return {
|
|
127
143
|
...current ? { currentStep: current.number } : {},
|
|
128
144
|
...origin !== void 0 ? { elapsedMs: now - origin } : {}
|
|
129
145
|
};
|
|
130
146
|
}
|
|
131
|
-
function
|
|
132
|
-
const marks = /* @__PURE__ */ new Map();
|
|
147
|
+
function readMarkEvents(signal) {
|
|
133
148
|
const events = asObject(signal?.checklistTiming)?.events;
|
|
134
|
-
if (!Array.isArray(events)) return
|
|
149
|
+
if (!Array.isArray(events)) return [];
|
|
150
|
+
const marks = [];
|
|
135
151
|
for (const entry of events) {
|
|
136
152
|
const event = asObject(entry);
|
|
137
153
|
const stepNumber = pickNumber(event, "stepNumber");
|
|
138
|
-
const checkedAt =
|
|
139
|
-
if (stepNumber === void 0 || checkedAt === void 0) continue;
|
|
140
|
-
marks.
|
|
154
|
+
const checkedAt = pickString(event, "checkedAt");
|
|
155
|
+
if (stepNumber === void 0 || parseTime(checkedAt) === void 0) continue;
|
|
156
|
+
marks.push({ stepNumber, label: "", checkedAt });
|
|
141
157
|
}
|
|
142
158
|
return marks;
|
|
143
159
|
}
|
|
144
160
|
function readChecklistRows(file) {
|
|
145
161
|
const text = readTextFile(file);
|
|
146
162
|
if (text === void 0) return [];
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
continue;
|
|
154
|
-
}
|
|
155
|
-
const match = CHECKLIST_ROW.exec(line);
|
|
156
|
-
if (!match) continue;
|
|
157
|
-
rows.push({
|
|
158
|
-
...phase ? { phase } : {},
|
|
159
|
-
number: Number(match[2]),
|
|
160
|
-
label: trimLabel(match[3]),
|
|
161
|
-
checked: match[1].toLowerCase() === "x"
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
return rows;
|
|
163
|
+
return enumerateChecklistCheckboxes(text).map((item) => ({
|
|
164
|
+
...item.phase ? { phase: item.phase } : {},
|
|
165
|
+
number: item.stepNumber,
|
|
166
|
+
label: trimLabel(item.rawLabel),
|
|
167
|
+
checked: item.checked
|
|
168
|
+
}));
|
|
165
169
|
}
|
|
166
170
|
function trimLabel(text) {
|
|
167
|
-
const plain = text.replace(/\*\*/gu, "").replace(/\s+/gu, " ").trim();
|
|
171
|
+
const plain = checklistStepName(text).replace(/\*\*/gu, "").replace(/^\d+\.\s*/u, "").replace(/\s+/gu, " ").trim();
|
|
168
172
|
if (plain.length <= CHECKLIST_LABEL_LIMIT) return plain;
|
|
169
173
|
return `${plain.slice(0, CHECKLIST_LABEL_LIMIT).trimEnd()}\u2026`;
|
|
170
174
|
}
|
|
@@ -242,6 +246,5 @@ export {
|
|
|
242
246
|
CHECKLIST_LABEL_LIMIT,
|
|
243
247
|
TASK_SILENT_AFTER_MS,
|
|
244
248
|
collectTaskView,
|
|
245
|
-
findTaskDir
|
|
246
|
-
isTerminalStatus
|
|
249
|
+
findTaskDir
|
|
247
250
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deeeed/metamask-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.58.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.
|
|
24
|
-
"@farmslot/expo-recipe": "0.
|
|
23
|
+
"@farmslot/agent-runtime": "0.12.0",
|
|
24
|
+
"@farmslot/expo-recipe": "0.10.0",
|
|
25
25
|
"@farmslot/handoff": "^0.3.1",
|
|
26
|
-
"@farmslot/protocol": "0.
|
|
27
|
-
"@farmslot/recipe-harness": "0.
|
|
26
|
+
"@farmslot/protocol": "0.29.0",
|
|
27
|
+
"@farmslot/recipe-harness": "0.17.0",
|
|
28
28
|
"agent-device": "0.19.3",
|
|
29
29
|
"commander": "^12.0.0",
|
|
30
30
|
"es-module-lexer": "2.3.1",
|