@deeeed/metamask-harness 0.50.5 → 0.51.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 +10 -4
- package/README.md +31 -0
- package/adapters/extension/ensure-browser.sh +2 -5
- package/adapters/extension/launch-browser.cjs +8 -10
- package/adapters/extension/lib/macos-focus.cjs +1 -10
- package/adapters/extension/live.sh +2 -0
- package/dist/cli-commands.js +2 -1
- package/dist/cli.js +4 -0
- package/dist/command-contract.js +43 -0
- package/dist/commands/checklist.js +9 -17
- package/dist/commands/prepare.js +236 -0
- package/dist/commands/task-init.js +51 -0
- package/dist/doctor.js +3 -4
- package/dist/mm-harness-cli.js +82 -2
- package/dist/paths.js +4 -0
- package/package.json +5 -5
package/CHANGELOG.md
CHANGED
|
@@ -2,12 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
-
## 0.
|
|
5
|
+
## 0.51.0 - 2026-09-13
|
|
6
6
|
|
|
7
|
-
###
|
|
7
|
+
### Added
|
|
8
|
+
|
|
9
|
+
- `mm-harness task init` writes the task directory (task document, checklist, mark shim, and inputs) from a shared execution template, with the MetaMask surface, harness-gated mark command, and detected platform applied by default.
|
|
10
|
+
- `mm-harness prepare` runs the readiness chain for a checkout (`doctor --fix`, `status`, launch, fixtures, `verify`), stops at the first failure, and records one readiness record at `<artifacts-dir>/sandbox.json` with a per-step report.
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
8
13
|
|
|
9
|
-
-
|
|
10
|
-
-
|
|
14
|
+
- Pin the getting-started iOS simulator to the checkout runtime file, and stop the setup prompt from auto-creating a disposable wallet.
|
|
15
|
+
- Resolve the checklist a mark targets through the agent runtime: a missing `checklist-target.json` now means the default checklist and signal instead of silently disabling the terminal-signal guard, and an invalid one raises its own error.
|
|
16
|
+
- Require `@farmslot/agent-runtime` 0.9.0 and `@farmslot/protocol` 0.26.0.
|
|
11
17
|
|
|
12
18
|
## 0.50.4 - 2026-09-10
|
|
13
19
|
|
package/README.md
CHANGED
|
@@ -121,6 +121,37 @@ Mobile and Extension recipes can bracket explicit UI-smoothness windows with
|
|
|
121
121
|
`app.performance_assert`. Capture is never automatic. See
|
|
122
122
|
[UI smoothness capture](docs/PERFORMANCE-CAPTURE.md).
|
|
123
123
|
|
|
124
|
+
## Work a task
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
# Pick a checklist from the shared catalog.
|
|
128
|
+
mm-harness execution-template list --package-templates <catalog> --json
|
|
129
|
+
|
|
130
|
+
# Write the task directory the agent works from.
|
|
131
|
+
mm-harness task init temp/tasks/fix/TAT-1 \
|
|
132
|
+
--flow fix-bug --run-mode autonomous --template fix-bug/autonomous.mobile \
|
|
133
|
+
--package-templates <catalog> --title "Wrong total on the receipt" --ticket TAT-1
|
|
134
|
+
|
|
135
|
+
# Bring the checkout to a proven-ready state before the first step.
|
|
136
|
+
mm-harness prepare --artifacts-dir temp/tasks/fix/TAT-1/artifacts --json
|
|
137
|
+
|
|
138
|
+
# Mark progress; the task's own ./mark shim routes here.
|
|
139
|
+
mm-harness checklist mark temp/tasks/fix/TAT-1 start
|
|
140
|
+
mm-harness checklist mark temp/tasks/fix/TAT-1 1
|
|
141
|
+
mm-harness checklist mark temp/tasks/fix/TAT-1 complete --mark-last
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
`task init` writes `TASK.md`, `CHECKLIST.md`, the `mark` shim, and `inputs/`.
|
|
145
|
+
`CHECKLIST.md` is the only file whose boxes count as steps. It writes no
|
|
146
|
+
`checklist-target.json`; absent means `CHECKLIST.md` + `SIGNAL.json`.
|
|
147
|
+
|
|
148
|
+
`prepare` runs `doctor --fix`, `status`, `launch --verify`, `fixtures set`, and
|
|
149
|
+
`verify` in order, stops at the first failure, and records one readiness record
|
|
150
|
+
at `<artifacts-dir>/sandbox.json`. Core skips launch and fixtures; Mobile takes
|
|
151
|
+
`ios`/`android` from the checkout's configured target, then from the devices
|
|
152
|
+
`status` reports, and asks when that is ambiguous. Exit 0 means
|
|
153
|
+
ready. See [the task directory](site/task-dir.html).
|
|
154
|
+
|
|
124
155
|
## Discover and prove
|
|
125
156
|
|
|
126
157
|
```bash
|
|
@@ -301,7 +301,7 @@ const {
|
|
|
301
301
|
runtimeIdentityArgs,
|
|
302
302
|
writeRuntimeIdentity,
|
|
303
303
|
} = require(path.join('${SCRIPT_DIR}', 'lib/chrome-args.cjs'));
|
|
304
|
-
const { captureMacFrontmostProcess,
|
|
304
|
+
const { captureMacFrontmostProcess, restoreMacFrontmostProcess } = require(path.join('${SCRIPT_DIR}', 'lib/macos-focus.cjs'));
|
|
305
305
|
|
|
306
306
|
const SLOT_ID = '${SLOT_ID}';
|
|
307
307
|
const AGENT_DIR = '${AGENT_DIR}';
|
|
@@ -345,9 +345,7 @@ const resumeWebpack = () => {
|
|
|
345
345
|
|
|
346
346
|
const chromiumApp = path.dirname(path.dirname(path.dirname(chromium.executablePath())));
|
|
347
347
|
const previousFrontmostPid = captureMacFrontmostProcess();
|
|
348
|
-
const holdOperatorFocus = () => preserveMacFrontmostProcess(previousFrontmostPid);
|
|
349
348
|
execFileSync('open', ['-g', '-n', '-a', chromiumApp, '--args', ...args], { stdio: 'ignore' });
|
|
350
|
-
holdOperatorFocus();
|
|
351
349
|
for (let i = 0; i < 60; i++) {
|
|
352
350
|
try {
|
|
353
351
|
const version = await new Promise((resolve, reject) => {
|
|
@@ -360,7 +358,6 @@ const resumeWebpack = () => {
|
|
|
360
358
|
});
|
|
361
359
|
if (version) break;
|
|
362
360
|
} catch {}
|
|
363
|
-
holdOperatorFocus();
|
|
364
361
|
await new Promise(r => setTimeout(r, 1000));
|
|
365
362
|
}
|
|
366
363
|
const browser = await chromium.connectOverCDP('http://127.0.0.1:' + CDP_PORT);
|
|
@@ -501,7 +498,7 @@ const resumeWebpack = () => {
|
|
|
501
498
|
if (process.env.MM_HARNESS_FOCUS_BROWSER === '1') {
|
|
502
499
|
await page.bringToFront().catch(() => {});
|
|
503
500
|
} else {
|
|
504
|
-
|
|
501
|
+
restoreMacFrontmostProcess(previousFrontmostPid);
|
|
505
502
|
}
|
|
506
503
|
|
|
507
504
|
fs.writeFileSync(path.join(AGENT_DIR, 'extension.id'), extId);
|
|
@@ -42,7 +42,7 @@ const {
|
|
|
42
42
|
validationLaunchQuarantineError,
|
|
43
43
|
writeRuntimeIdentity,
|
|
44
44
|
} = require('./lib/chrome-args.cjs');
|
|
45
|
-
const { captureMacFrontmostProcess,
|
|
45
|
+
const { captureMacFrontmostProcess, restoreMacFrontmostProcess } = require('./lib/macos-focus.cjs');
|
|
46
46
|
|
|
47
47
|
if (process.argv.includes('--help') || process.argv.includes('-h')) {
|
|
48
48
|
console.log(
|
|
@@ -165,7 +165,6 @@ const chromeArgs = [
|
|
|
165
165
|
];
|
|
166
166
|
const logFd = fs.openSync(args['chrome-log'], 'a');
|
|
167
167
|
const previousFrontmostPid = captureMacFrontmostProcess();
|
|
168
|
-
const holdOperatorFocus = () => preserveMacFrontmostProcess(previousFrontmostPid);
|
|
169
168
|
let browserPid;
|
|
170
169
|
try {
|
|
171
170
|
const application = process.platform === 'darwin' ? macApplicationForExecutable(args['chrome-bin']) : null;
|
|
@@ -175,8 +174,7 @@ try {
|
|
|
175
174
|
env: sanitizedChildEnv(),
|
|
176
175
|
stdio: ['ignore', logFd, logFd],
|
|
177
176
|
});
|
|
178
|
-
|
|
179
|
-
browserPid = waitForOwnedCdpPid(cdpPort, args.profile, args['extension-dir'], holdOperatorFocus);
|
|
177
|
+
browserPid = waitForOwnedCdpPid(cdpPort, args.profile, args['extension-dir']);
|
|
180
178
|
if (browserPid === null) {
|
|
181
179
|
throw new Error(`Chrome launched but did not expose CDP on 127.0.0.1:${cdpPort}`);
|
|
182
180
|
}
|
|
@@ -188,8 +186,7 @@ try {
|
|
|
188
186
|
stdio: ['ignore', logFd, logFd],
|
|
189
187
|
});
|
|
190
188
|
child.unref();
|
|
191
|
-
|
|
192
|
-
browserPid = waitForOwnedCdpPid(cdpPort, args.profile, args['extension-dir'], holdOperatorFocus);
|
|
189
|
+
browserPid = waitForOwnedCdpPid(cdpPort, args.profile, args['extension-dir']);
|
|
193
190
|
if (browserPid === null) {
|
|
194
191
|
if (child.pid) terminatePids([child.pid]);
|
|
195
192
|
throw new Error(
|
|
@@ -223,13 +220,15 @@ try {
|
|
|
223
220
|
);
|
|
224
221
|
}
|
|
225
222
|
if (process.platform === 'darwin') {
|
|
226
|
-
const wake = spawn('caffeinate', ['-
|
|
223
|
+
const wake = spawn('caffeinate', ['-dimsu', '-w', String(browserPid)], {
|
|
227
224
|
detached: true,
|
|
228
225
|
stdio: 'ignore',
|
|
229
226
|
});
|
|
230
227
|
wake.unref();
|
|
231
228
|
}
|
|
232
|
-
|
|
229
|
+
if (process.env.MM_HARNESS_FOCUS_BROWSER !== '1') {
|
|
230
|
+
restoreMacFrontmostProcess(previousFrontmostPid);
|
|
231
|
+
}
|
|
233
232
|
|
|
234
233
|
function sanitizedChildEnv() {
|
|
235
234
|
return {
|
|
@@ -265,13 +264,12 @@ function macApplicationForExecutable(executable) {
|
|
|
265
264
|
return null;
|
|
266
265
|
}
|
|
267
266
|
|
|
268
|
-
function waitForOwnedCdpPid(port, profile, extensionDir
|
|
267
|
+
function waitForOwnedCdpPid(port, profile, extensionDir) {
|
|
269
268
|
for (let i = 0; i < 300; i += 1) {
|
|
270
269
|
const pid = cdpListenerPids(port).find((candidate) =>
|
|
271
270
|
processIsOwnedHarnessBrowser(candidate, profile, extensionDir),
|
|
272
271
|
);
|
|
273
272
|
if (pid !== undefined) return pid;
|
|
274
|
-
if (typeof onWait === 'function' && i % 10 === 0) onWait();
|
|
275
273
|
spawnSync('sleep', ['0.1']);
|
|
276
274
|
}
|
|
277
275
|
return null;
|
|
@@ -29,13 +29,4 @@ function restoreMacFrontmostProcess(pid) {
|
|
|
29
29
|
}
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
|
|
33
|
-
if (process.env.MM_HARNESS_FOCUS_BROWSER === '1') return false;
|
|
34
|
-
return restoreMacFrontmostProcess(pid);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
module.exports = {
|
|
38
|
-
captureMacFrontmostProcess,
|
|
39
|
-
preserveMacFrontmostProcess,
|
|
40
|
-
restoreMacFrontmostProcess,
|
|
41
|
-
};
|
|
32
|
+
module.exports = { captureMacFrontmostProcess, restoreMacFrontmostProcess };
|
|
@@ -228,6 +228,7 @@ NODE
|
|
|
228
228
|
quoted_check_infura="$(printf '%q' "$SCRIPT_DIR/check-infura-readiness.cjs")"
|
|
229
229
|
quoted_stamp_title="$(printf '%q' "$SCRIPT_DIR/stamp-runtime-title.cjs")"
|
|
230
230
|
quoted_chrome_launcher="$(printf '%q' "$SCRIPT_DIR/launch-browser.cjs")"
|
|
231
|
+
quoted_build_handoff="$(printf '%q' "$SCRIPT_DIR/build-handoff.cjs")"
|
|
231
232
|
quoted_artifact_state="$(printf '%q' "$SCRIPT_DIR/artifact-runtime-state.cjs")"
|
|
232
233
|
quoted_fixture_state="$(printf '%q' "$FIXTURE_STATE_ABS")"
|
|
233
234
|
quoted_fixture_validation="$(printf '%q' "$FIXTURE_VALIDATION_ABS")"
|
|
@@ -352,6 +353,7 @@ NODE
|
|
|
352
353
|
fi
|
|
353
354
|
prepare_parts+=("$chrome_launch_cmd")
|
|
354
355
|
prepare_parts+=("for i in {1..60}; do curl -fsS --max-time 1 http://127.0.0.1:${CDP_PORT}/json/version >/dev/null 2>&1 && break; sleep 1; done; curl -fsS --max-time 1 http://127.0.0.1:${CDP_PORT}/json/version >/dev/null")
|
|
356
|
+
prepare_parts+=("node ${quoted_build_handoff} --target ${quoted_target} --runtime-dist ${quoted_runtime_dist} --cdp-port ${CDP_PORT}")
|
|
355
357
|
if [ -n "$WALLET_FIXTURE_ABS" ]; then
|
|
356
358
|
prepare_parts+=("bash ${quoted_seed_fixture} seed-cdp --target ${quoted_target} --fixture ${quoted_wallet_fixture} --state ${quoted_fixture_state} --cdp-port ${CDP_PORT} --extension-dir ${quoted_runtime_dist} --extension-id-file ${quoted_extension_id_file} --out ${quoted_fixture_validation}")
|
|
357
359
|
fi
|
package/dist/cli-commands.js
CHANGED
|
@@ -25,7 +25,8 @@ const SPEC = {
|
|
|
25
25
|
{ name: "execution-template", desc: "Discover and materialize shared agent checklists", args: ["list", "materialize", "lint", "new"], flags: ["--dir", "--domain-dir", "--project-worker", "--project-name", "--package-templates", "--package-id", "--flow", "--run-mode", "--platform", "--domain", "--id", "--provenance", "--include-shadowed", "--no-include-shadowed", "--title", "--force", "--json"] },
|
|
26
26
|
{ name: "recipe-quality", desc: "Build the recipe-quality artifact from compact JSON", args: ["build"], flags: ["--input", "--output", "--json"] },
|
|
27
27
|
{ name: "interactive", aliases: ["menu"], desc: "Interactive command menu" },
|
|
28
|
-
{ name: "prepare", desc: "
|
|
28
|
+
{ name: "prepare", desc: "Run the readiness chain and write the readiness record", flags: ["--target", "--platform", "--mobile-platform", "--artifacts-dir", "--runtime-dir", "--json"] },
|
|
29
|
+
{ name: "task", desc: "Create a task directory from a shared execution template", args: ["init"], flags: ["--flow", "--run-mode", "--platform", "--template", "--title", "--task-text", "--task-file", "--ticket", "--source-ref", "--surface", "--project", "--repo", "--attempt-id", "--var", "--task-dir-label", "--mode-preamble", "--addendum-file", "--mark-command", "--dir", "--domain-dir", "--project-worker", "--project-name", "--package-templates", "--package-id", "--domain", "--json"] },
|
|
29
30
|
{ name: "runtime-status", desc: "Structured runtime status JSON", flags: ["--json", "--target", "--cdp-port", "--runtime-dir"] }
|
|
30
31
|
],
|
|
31
32
|
extension: [
|
package/dist/cli.js
CHANGED
|
@@ -22,6 +22,8 @@ import { handleStatus } from "./commands/status.js";
|
|
|
22
22
|
import { handleCheck } from "./commands/check.js";
|
|
23
23
|
import { handleChecklist } from "./commands/checklist.js";
|
|
24
24
|
import { handleExecutionTemplate } from "./commands/execution-template.js";
|
|
25
|
+
import { handlePrepare } from "./commands/prepare.js";
|
|
26
|
+
import { handleTaskInit } from "./commands/task-init.js";
|
|
25
27
|
import { handleLast } from "./commands/last.js";
|
|
26
28
|
import { parseArgs, targetPath } from "./commands/parse-args.js";
|
|
27
29
|
const COMMANDS = {
|
|
@@ -127,6 +129,8 @@ async function main(argv) {
|
|
|
127
129
|
if (command === "check") return handleCheck(argv.slice(1));
|
|
128
130
|
if (command === "checklist") return handleChecklist(argv.slice(1));
|
|
129
131
|
if (command === "execution-template") return handleExecutionTemplate(argv.slice(1));
|
|
132
|
+
if (command === "task") return handleTaskInit(argv.slice(1));
|
|
133
|
+
if (command === "prepare") return handlePrepare(argv.slice(1));
|
|
130
134
|
if (command === "last") return handleLast(parseArgs(argv.slice(1), command));
|
|
131
135
|
const handler = COMMANDS[command];
|
|
132
136
|
if (!handler) throw new Error(`Unknown command: ${command}`);
|
package/dist/command-contract.js
CHANGED
|
@@ -113,6 +113,49 @@ const PUBLIC_COMMAND_CONTRACTS = {
|
|
|
113
113
|
],
|
|
114
114
|
minimumPositionals: 2
|
|
115
115
|
},
|
|
116
|
+
prepare: {
|
|
117
|
+
usage: "mm-harness prepare [--target <checkout>] [--platform mobile|extension|core] [--mobile-platform ios|android] [--artifacts-dir <dir>] [--json]",
|
|
118
|
+
options: options(HELP, JSON, TARGET, {
|
|
119
|
+
"--platform": value(["mobile", "extension", "core"]),
|
|
120
|
+
"--mobile-platform": value(["ios", "android"]),
|
|
121
|
+
"--artifacts-dir": value()
|
|
122
|
+
})
|
|
123
|
+
},
|
|
124
|
+
task: {
|
|
125
|
+
usage: "mm-harness task init <task-dir> --flow f --run-mode m --template id --title t [options]",
|
|
126
|
+
options: options(HELP, JSON, {
|
|
127
|
+
"--flow": value(),
|
|
128
|
+
"--run-mode": value(["autonomous", "interactive", "validation"]),
|
|
129
|
+
"--platform": value(),
|
|
130
|
+
"--template": value(),
|
|
131
|
+
"--title": value(),
|
|
132
|
+
"--task-text": value(),
|
|
133
|
+
"--task-file": value(),
|
|
134
|
+
"--ticket": value(),
|
|
135
|
+
"--source-ref": value(),
|
|
136
|
+
"--surface": value(),
|
|
137
|
+
"--project": value(),
|
|
138
|
+
"--repo": value(),
|
|
139
|
+
"--attempt-id": value(),
|
|
140
|
+
"--var": value(),
|
|
141
|
+
"--task-dir-label": value(),
|
|
142
|
+
"--mode-preamble": value(),
|
|
143
|
+
"--addendum-file": value(),
|
|
144
|
+
"--mark-command": value(),
|
|
145
|
+
"--dir": value(),
|
|
146
|
+
"--domain-dir": value(),
|
|
147
|
+
"--project-worker": value(),
|
|
148
|
+
"--project-name": value(),
|
|
149
|
+
"--package-templates": value(),
|
|
150
|
+
"--package-id": value(),
|
|
151
|
+
"--domain": value()
|
|
152
|
+
}),
|
|
153
|
+
positionals: [
|
|
154
|
+
{ label: "action", choices: ["init"] },
|
|
155
|
+
{ label: "task-dir" }
|
|
156
|
+
],
|
|
157
|
+
minimumPositionals: 2
|
|
158
|
+
},
|
|
116
159
|
"execution-template": {
|
|
117
160
|
usage: "mm-harness execution-template <list|materialize|lint|new> [target] [options]",
|
|
118
161
|
options: options(HELP, JSON, {
|
|
@@ -7,6 +7,9 @@ const require2 = createRequire(import.meta.url);
|
|
|
7
7
|
const MARK_SCRIPT = require2.resolve(
|
|
8
8
|
"@farmslot/agent-runtime/scripts/mark-checklist-step.cjs"
|
|
9
9
|
);
|
|
10
|
+
const { resolveChecklistPaths } = require2(
|
|
11
|
+
"@farmslot/agent-runtime/scripts/checklist-target.cjs"
|
|
12
|
+
);
|
|
10
13
|
const USAGE = "mm-harness checklist <mark <task-dir> <step> | closeout <task-dir>> [options]";
|
|
11
14
|
const CHECKLIST_VALUE_OPTIONS = /* @__PURE__ */ new Set([
|
|
12
15
|
"--reason",
|
|
@@ -98,16 +101,14 @@ function stageLearningPackage(taskDir, step, markArgs) {
|
|
|
98
101
|
);
|
|
99
102
|
}
|
|
100
103
|
function terminalSignalStatus(taskDir) {
|
|
104
|
+
const { signalPath } = resolveChecklistPaths(taskDir);
|
|
105
|
+
let signal;
|
|
101
106
|
try {
|
|
102
|
-
|
|
103
|
-
fs.readFileSync(path.join(taskDir, "checklist-target.json"), "utf8")
|
|
104
|
-
);
|
|
105
|
-
const signalPath = path.resolve(taskDir, target.signal ?? "SIGNAL.json");
|
|
106
|
-
const signal = JSON.parse(fs.readFileSync(signalPath, "utf8"));
|
|
107
|
-
return signal.status === "complete" || signal.status === "blocked" ? signal.status : null;
|
|
107
|
+
signal = JSON.parse(fs.readFileSync(signalPath, "utf8"));
|
|
108
108
|
} catch {
|
|
109
109
|
return null;
|
|
110
110
|
}
|
|
111
|
+
return signal.status === "complete" || signal.status === "blocked" ? signal.status : null;
|
|
111
112
|
}
|
|
112
113
|
function preserveTerminalSignal(taskDir, step) {
|
|
113
114
|
if (step !== "start" && numericMarkStep(step) === null) return false;
|
|
@@ -122,16 +123,6 @@ function numericMarkStep(step) {
|
|
|
122
123
|
const stepNumber = Number(step);
|
|
123
124
|
return Number.isInteger(stepNumber) && stepNumber >= 1 ? stepNumber : null;
|
|
124
125
|
}
|
|
125
|
-
function checklistPath(taskDir) {
|
|
126
|
-
try {
|
|
127
|
-
const target = JSON.parse(
|
|
128
|
-
fs.readFileSync(path.join(taskDir, "checklist-target.json"), "utf8")
|
|
129
|
-
);
|
|
130
|
-
return path.resolve(taskDir, target.checklist ?? "CHECKLIST.md");
|
|
131
|
-
} catch {
|
|
132
|
-
return path.join(taskDir, "CHECKLIST.md");
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
126
|
function checklistStepRequiresDiffPass(checklist, stepNumber) {
|
|
136
127
|
let inFence = false;
|
|
137
128
|
let seen = 0;
|
|
@@ -151,9 +142,10 @@ function checklistStepRequiresDiffPass(checklist, stepNumber) {
|
|
|
151
142
|
function checkDiffGateReady(taskDir, step) {
|
|
152
143
|
const stepNumber = numericMarkStep(step);
|
|
153
144
|
if (stepNumber === null) return true;
|
|
145
|
+
const { taskPath } = resolveChecklistPaths(taskDir);
|
|
154
146
|
let checklist = "";
|
|
155
147
|
try {
|
|
156
|
-
checklist = fs.readFileSync(
|
|
148
|
+
checklist = fs.readFileSync(taskPath, "utf8");
|
|
157
149
|
} catch {
|
|
158
150
|
return true;
|
|
159
151
|
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
READINESS_RECORD
|
|
6
|
+
} from "@farmslot/protocol";
|
|
7
|
+
import { runnerProvenance } from "../doctor.js";
|
|
8
|
+
import { detectAdapter } from "../harness.js";
|
|
9
|
+
import { harnessExecutable, manifestPath, recipeRuntimeDir } from "../paths.js";
|
|
10
|
+
import { optionFlag, optionString, parseArgs, targetPath, usageError } from "./parse-args.js";
|
|
11
|
+
import { EXIT } from "./shared.js";
|
|
12
|
+
const USAGE = "mm-harness prepare [--target <checkout>] [--platform mobile|extension|core] [--mobile-platform ios|android] [--artifacts-dir <dir>] [--json]";
|
|
13
|
+
const REPORT_DIR = "prepare";
|
|
14
|
+
const SKIPPED_AFTER_FAILURE = "previous step failed";
|
|
15
|
+
function reportPathFor(id) {
|
|
16
|
+
return path.join(REPORT_DIR, `${id}.json`);
|
|
17
|
+
}
|
|
18
|
+
function reportedError(stdout) {
|
|
19
|
+
let parsed;
|
|
20
|
+
try {
|
|
21
|
+
parsed = JSON.parse(stdout ?? "");
|
|
22
|
+
} catch {
|
|
23
|
+
return void 0;
|
|
24
|
+
}
|
|
25
|
+
const message = parsed?.error?.message;
|
|
26
|
+
return typeof message === "string" && message ? message : void 0;
|
|
27
|
+
}
|
|
28
|
+
function skippedStep(id, command, reason) {
|
|
29
|
+
return { id, command, status: "skipped", exitCode: 0, reason };
|
|
30
|
+
}
|
|
31
|
+
const STEP_MARK = { pass: "\u2713", fail: "\u2717", skipped: "\u2013" };
|
|
32
|
+
function stepLine(step) {
|
|
33
|
+
return `${STEP_MARK[step.status]} ${step.id}: ${step.status}${step.reason ? ` \u2014 ${step.reason}` : ""}`;
|
|
34
|
+
}
|
|
35
|
+
function runStep(executable, target, artifactsDir, id, args, { json }) {
|
|
36
|
+
const command = [executable, ...args].join(" ");
|
|
37
|
+
const result = spawnSync(executable, [...args], {
|
|
38
|
+
cwd: target,
|
|
39
|
+
encoding: "utf8",
|
|
40
|
+
// The update nudge is an interactive courtesy; it has no place in the
|
|
41
|
+
// stream a recorded readiness step captures.
|
|
42
|
+
env: { ...process.env, MM_HARNESS_NO_UPDATE_CHECK: "1" }
|
|
43
|
+
});
|
|
44
|
+
if (result.error) {
|
|
45
|
+
return { id, command, status: "fail", exitCode: 1, reason: result.error.message };
|
|
46
|
+
}
|
|
47
|
+
const report = reportPathFor(id);
|
|
48
|
+
const file = path.join(artifactsDir, report);
|
|
49
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
50
|
+
fs.writeFileSync(file, result.stdout ?? "");
|
|
51
|
+
const exitCode = result.status ?? 1;
|
|
52
|
+
if (exitCode === 0) {
|
|
53
|
+
return { id, command, status: "pass", exitCode, reportPath: report };
|
|
54
|
+
}
|
|
55
|
+
if (!json && result.stderr) process.stderr.write(result.stderr);
|
|
56
|
+
return {
|
|
57
|
+
id,
|
|
58
|
+
command,
|
|
59
|
+
status: "fail",
|
|
60
|
+
exitCode,
|
|
61
|
+
reportPath: report,
|
|
62
|
+
reason: reportedError(result.stdout) ?? `exited ${exitCode}`
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
function readDevices(artifactsDir) {
|
|
66
|
+
const file = path.join(artifactsDir, reportPathFor("status"));
|
|
67
|
+
let parsed;
|
|
68
|
+
try {
|
|
69
|
+
parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
70
|
+
} catch {
|
|
71
|
+
return [];
|
|
72
|
+
}
|
|
73
|
+
const devices = parsed?.devices;
|
|
74
|
+
return Array.isArray(devices) ? devices : [];
|
|
75
|
+
}
|
|
76
|
+
function readRuntimeContextTarget(target) {
|
|
77
|
+
const file = path.join(target, recipeRuntimeDir(), "agentic-runtime.json");
|
|
78
|
+
let parsed;
|
|
79
|
+
try {
|
|
80
|
+
parsed = JSON.parse(fs.readFileSync(file, "utf8"));
|
|
81
|
+
} catch {
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
84
|
+
const record = parsed ?? {};
|
|
85
|
+
const str = (key) => typeof record[key] === "string" ? record[key] : void 0;
|
|
86
|
+
return { simulator: str("simulator"), simulatorUdid: str("simulatorUdid"), adbSerial: str("adbSerial") };
|
|
87
|
+
}
|
|
88
|
+
function mobilePlatformFromConfiguredTarget(env, context) {
|
|
89
|
+
const ios = Boolean(env.IOS_SIMULATOR || env.SIM_UDID || context.simulator || context.simulatorUdid);
|
|
90
|
+
const android = Boolean(
|
|
91
|
+
env.ADB_SERIAL || env.ANDROID_SERIAL || env.ANDROID_TARGET_DEVICE_NAME || env.ANDROID_DEVICE || context.adbSerial
|
|
92
|
+
);
|
|
93
|
+
if (ios === android) return void 0;
|
|
94
|
+
return ios ? "ios" : "android";
|
|
95
|
+
}
|
|
96
|
+
function mobilePlatformFromDevices(devices) {
|
|
97
|
+
const selected = devices.filter((device) => device?.selected === true);
|
|
98
|
+
const candidates = selected.length > 0 ? selected : devices;
|
|
99
|
+
const platforms = [
|
|
100
|
+
...new Set(
|
|
101
|
+
candidates.map((device) => device?.platform).filter((value) => value === "ios" || value === "android")
|
|
102
|
+
)
|
|
103
|
+
];
|
|
104
|
+
return platforms.length === 1 ? platforms[0] : void 0;
|
|
105
|
+
}
|
|
106
|
+
function mobileTargetAmbiguityError() {
|
|
107
|
+
const choices = [
|
|
108
|
+
"1. iOS mm-harness prepare --platform mobile --mobile-platform ios",
|
|
109
|
+
"2. Android mm-harness prepare --platform mobile --mobile-platform android"
|
|
110
|
+
];
|
|
111
|
+
return {
|
|
112
|
+
code: "PREPARE_MOBILE_TARGET_AMBIGUOUS",
|
|
113
|
+
message: "could not infer the mobile target from the connected devices",
|
|
114
|
+
userAction: `choose the mobile target, then rerun this command:
|
|
115
|
+
${choices.join("\n")}`
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function ambiguousMobileTarget(json) {
|
|
119
|
+
const { code, message, userAction } = mobileTargetAmbiguityError();
|
|
120
|
+
if (json) {
|
|
121
|
+
console.log(
|
|
122
|
+
JSON.stringify(
|
|
123
|
+
{
|
|
124
|
+
schemaVersion: 1,
|
|
125
|
+
command: "prepare",
|
|
126
|
+
status: "fail",
|
|
127
|
+
exitCode: EXIT.runtime,
|
|
128
|
+
error: { code, message, userAction }
|
|
129
|
+
},
|
|
130
|
+
null,
|
|
131
|
+
2
|
|
132
|
+
)
|
|
133
|
+
);
|
|
134
|
+
} else {
|
|
135
|
+
console.error(`\u2717 mm-harness prepare: ${message}`);
|
|
136
|
+
console.error(` Next: ${userAction}`);
|
|
137
|
+
}
|
|
138
|
+
return EXIT.runtime;
|
|
139
|
+
}
|
|
140
|
+
function mobilePlatformOption(value) {
|
|
141
|
+
if (value === void 0) return void 0;
|
|
142
|
+
if (value === "ios") return "ios";
|
|
143
|
+
if (value === "android") return "android";
|
|
144
|
+
throw usageError(
|
|
145
|
+
`--mobile-platform must be ios or android; received '${value}'
|
|
146
|
+
Next: ${USAGE}`
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
function resolvePlatform(requested, target) {
|
|
150
|
+
const platform = requested ?? detectAdapter(target);
|
|
151
|
+
if (platform === "mobile" || platform === "extension" || platform === "core") {
|
|
152
|
+
return platform;
|
|
153
|
+
}
|
|
154
|
+
throw usageError(
|
|
155
|
+
`could not detect the platform of ${target}
|
|
156
|
+
Next: ${USAGE}`
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
async function handlePrepare(argv) {
|
|
160
|
+
const { options } = parseArgs(argv, "prepare");
|
|
161
|
+
const json = optionFlag(options, "json");
|
|
162
|
+
const target = targetPath(options);
|
|
163
|
+
const platform = resolvePlatform(optionString(options, "platform"), target);
|
|
164
|
+
const artifactsDir = path.resolve(
|
|
165
|
+
optionString(options, "artifactsDir") ?? path.join(target, recipeRuntimeDir())
|
|
166
|
+
);
|
|
167
|
+
const executable = harnessExecutable();
|
|
168
|
+
const targetArgs = ["--target", target];
|
|
169
|
+
let mobilePlatform = mobilePlatformOption(optionString(options, "mobilePlatform"));
|
|
170
|
+
const steps = [];
|
|
171
|
+
const failed = () => steps.some((step) => step.status === "fail");
|
|
172
|
+
const addStep = (step) => {
|
|
173
|
+
steps.push(step);
|
|
174
|
+
if (!json) console.log(stepLine(step));
|
|
175
|
+
return step;
|
|
176
|
+
};
|
|
177
|
+
const run = (id, args) => addStep(
|
|
178
|
+
failed() ? skippedStep(id, [executable, ...args].join(" "), SKIPPED_AFTER_FAILURE) : runStep(executable, target, artifactsDir, id, args, { json })
|
|
179
|
+
);
|
|
180
|
+
run("doctor", ["doctor", "--fix", "--json", "--adapter", platform, ...targetArgs]);
|
|
181
|
+
run("status", ["status", "--json", ...targetArgs]);
|
|
182
|
+
if (platform === "mobile" && !mobilePlatform && !failed()) {
|
|
183
|
+
mobilePlatform = mobilePlatformFromConfiguredTarget(process.env, readRuntimeContextTarget(target)) ?? mobilePlatformFromDevices(readDevices(artifactsDir));
|
|
184
|
+
if (!mobilePlatform) return ambiguousMobileTarget(json);
|
|
185
|
+
}
|
|
186
|
+
if (platform === "core") {
|
|
187
|
+
const reason = "core is headless: no app surface to launch or seed";
|
|
188
|
+
addStep(skippedStep("launch", `${executable} launch`, reason));
|
|
189
|
+
addStep(skippedStep("fixtures", `${executable} fixtures set`, reason));
|
|
190
|
+
} else if (platform === "mobile") {
|
|
191
|
+
const device = mobilePlatform ? [mobilePlatform] : [];
|
|
192
|
+
run("launch", ["launch", ...device, "--verify", "--json", ...targetArgs]);
|
|
193
|
+
run("fixtures", [
|
|
194
|
+
"fixtures",
|
|
195
|
+
"set",
|
|
196
|
+
...mobilePlatform ? ["--platform", mobilePlatform] : [],
|
|
197
|
+
"--json",
|
|
198
|
+
...targetArgs
|
|
199
|
+
]);
|
|
200
|
+
} else {
|
|
201
|
+
run("launch", ["launch", "--verify", "--json", ...targetArgs]);
|
|
202
|
+
run("fixtures", ["fixtures", "set", "--json", ...targetArgs]);
|
|
203
|
+
}
|
|
204
|
+
run("verify", ["verify", "--json", "--adapter", platform, ...targetArgs]);
|
|
205
|
+
const provenance = runnerProvenance(manifestPath(platform));
|
|
206
|
+
const record = {
|
|
207
|
+
schemaVersion: 1,
|
|
208
|
+
harness: {
|
|
209
|
+
name: provenance.packageName,
|
|
210
|
+
version: provenance.version,
|
|
211
|
+
source: `${provenance.installKind}/${provenance.packageSource}`,
|
|
212
|
+
executable: provenance.executablePath
|
|
213
|
+
},
|
|
214
|
+
platform,
|
|
215
|
+
...mobilePlatform ? { mobilePlatform } : {},
|
|
216
|
+
steps,
|
|
217
|
+
ready: steps.every((step) => step.status !== "fail"),
|
|
218
|
+
recordedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
219
|
+
};
|
|
220
|
+
const recordPath = path.join(artifactsDir, READINESS_RECORD);
|
|
221
|
+
fs.mkdirSync(artifactsDir, { recursive: true });
|
|
222
|
+
fs.writeFileSync(recordPath, `${JSON.stringify(record, null, 2)}
|
|
223
|
+
`);
|
|
224
|
+
if (json) {
|
|
225
|
+
console.log(JSON.stringify(record, null, 2));
|
|
226
|
+
} else {
|
|
227
|
+
console.log(`readiness record: ${recordPath}`);
|
|
228
|
+
}
|
|
229
|
+
return record.ready ? EXIT.ok : EXIT.runtime;
|
|
230
|
+
}
|
|
231
|
+
export {
|
|
232
|
+
handlePrepare,
|
|
233
|
+
mobilePlatformFromConfiguredTarget,
|
|
234
|
+
mobilePlatformFromDevices,
|
|
235
|
+
mobileTargetAmbiguityError
|
|
236
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { spawnSync } from "node:child_process";
|
|
2
|
+
import { createRequire } from "node:module";
|
|
3
|
+
import { detectAdapter } from "../harness.js";
|
|
4
|
+
import { harnessExecutable } from "../paths.js";
|
|
5
|
+
import { EXIT } from "./shared.js";
|
|
6
|
+
const require2 = createRequire(import.meta.url);
|
|
7
|
+
const USAGE = "mm-harness task init <task-dir> --flow f --run-mode m --template id --title t [options]";
|
|
8
|
+
function hasOption(argv, name) {
|
|
9
|
+
return argv.some((arg) => arg === name || arg.startsWith(`${name}=`));
|
|
10
|
+
}
|
|
11
|
+
function metamaskDefaults(argv) {
|
|
12
|
+
const defaults = [];
|
|
13
|
+
if (!hasOption(argv, "--surface")) defaults.push("--surface", "skill");
|
|
14
|
+
if (!hasOption(argv, "--mark-command")) {
|
|
15
|
+
defaults.push("--mark-command", `${harnessExecutable()} checklist mark`);
|
|
16
|
+
}
|
|
17
|
+
if (!hasOption(argv, "--platform")) {
|
|
18
|
+
const adapter = detectAdapter(process.cwd());
|
|
19
|
+
if (adapter) defaults.push("--platform", adapter);
|
|
20
|
+
}
|
|
21
|
+
return defaults;
|
|
22
|
+
}
|
|
23
|
+
async function handleTaskInit(argv) {
|
|
24
|
+
if (argv[0] !== "init") {
|
|
25
|
+
console.error(`usage: ${USAGE}`);
|
|
26
|
+
return EXIT.usage;
|
|
27
|
+
}
|
|
28
|
+
const forwarded = argv.slice(1);
|
|
29
|
+
let entrypoint;
|
|
30
|
+
try {
|
|
31
|
+
entrypoint = require2.resolve("@farmslot/agent-runtime/scripts/task-init-cli.mjs");
|
|
32
|
+
} catch (error) {
|
|
33
|
+
console.error(
|
|
34
|
+
`mm-harness task init: installed @farmslot/agent-runtime does not expose the task-directory command: ${error instanceof Error ? error.message : String(error)}`
|
|
35
|
+
);
|
|
36
|
+
return EXIT.runtime;
|
|
37
|
+
}
|
|
38
|
+
const result = spawnSync(
|
|
39
|
+
process.execPath,
|
|
40
|
+
[entrypoint, ...forwarded, ...metamaskDefaults(forwarded)],
|
|
41
|
+
{ env: process.env, stdio: "inherit" }
|
|
42
|
+
);
|
|
43
|
+
if (result.error) {
|
|
44
|
+
console.error(`mm-harness task init: ${result.error.message}`);
|
|
45
|
+
return EXIT.runtime;
|
|
46
|
+
}
|
|
47
|
+
return result.status ?? EXIT.runtime;
|
|
48
|
+
}
|
|
49
|
+
export {
|
|
50
|
+
handleTaskInit
|
|
51
|
+
};
|
package/dist/doctor.js
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
} from "../library/actions/mobile/platform/tool-paths.mjs";
|
|
9
9
|
import { mobilePerpsEnvironment } from "./adapters/mobile/perps-env.js";
|
|
10
10
|
import { readRuntimeContextField, resolveRuntimeContextPath } from "./harness.js";
|
|
11
|
-
import { manifestPath, readJson, recipeHarnessRoot, recipeRuntimeDir, runnerDir } from "./paths.js";
|
|
11
|
+
import { harnessExecutable, manifestPath, readJson, recipeHarnessRoot, recipeRuntimeDir, runnerDir } from "./paths.js";
|
|
12
12
|
function requiredDoctorCheckSummary(checks) {
|
|
13
13
|
const required = checks.filter((check) => check.required);
|
|
14
14
|
const failed = required.filter((check) => check.status === "fail").map((check) => check.id);
|
|
@@ -246,9 +246,7 @@ function runnerProvenance(actionManifestPath) {
|
|
|
246
246
|
if (typeof version !== "string" || !version) {
|
|
247
247
|
throw new Error(`Expected package version in ${packagePath}`);
|
|
248
248
|
}
|
|
249
|
-
const executablePath =
|
|
250
|
-
process.env.MM_HARNESS_EXECUTABLE ?? path.join(runnerDir, "bin/mm-harness")
|
|
251
|
-
);
|
|
249
|
+
const executablePath = harnessExecutable();
|
|
252
250
|
const invokedPath = path.resolve(process.env.MM_HARNESS_INVOKED_AS ?? executablePath);
|
|
253
251
|
const installKind = runnerInstallKind(runnerDir, invokedPath, executablePath);
|
|
254
252
|
const packageSource = runnerDir.includes(`${path.sep}node_modules${path.sep}`) ? "node_modules" : "source-checkout";
|
|
@@ -286,5 +284,6 @@ export {
|
|
|
286
284
|
repoShape,
|
|
287
285
|
requiredDoctorCheckSummary,
|
|
288
286
|
runnerInstallKind,
|
|
287
|
+
runnerProvenance,
|
|
289
288
|
runtimeContextSummary
|
|
290
289
|
};
|
package/dist/mm-harness-cli.js
CHANGED
|
@@ -123,7 +123,9 @@ Example:
|
|
|
123
123
|
mark delegates checklist state to @farmslot/agent-runtime. Successful complete
|
|
124
124
|
and no-change marks stage a learning package through @farmslot/handoff.
|
|
125
125
|
closeout exposes that same Handoff boundary directly; --share remains explicit.
|
|
126
|
-
The task directory
|
|
126
|
+
The task directory is the one mm-harness task init wrote. Without a
|
|
127
|
+
checklist-target.json it marks CHECKLIST.md and writes SIGNAL.json; only a
|
|
128
|
+
role switch writes that manifest, to point at its own checklist.
|
|
127
129
|
|
|
128
130
|
Steps: start | 1 | 2 | ... | complete | no-change | blocked
|
|
129
131
|
|
|
@@ -133,6 +135,84 @@ Example:
|
|
|
133
135
|
mm-harness checklist mark temp/tasks/recipe-cook/<task> complete --mark-last
|
|
134
136
|
mm-harness checklist closeout temp/tasks/recipe-cook/<task> --share`
|
|
135
137
|
},
|
|
138
|
+
{
|
|
139
|
+
name: "prepare",
|
|
140
|
+
summary: "Bring this checkout to a proven-ready state and write the readiness record.",
|
|
141
|
+
example: "mm-harness prepare --platform extension",
|
|
142
|
+
helpText: `mm-harness prepare [flags]
|
|
143
|
+
|
|
144
|
+
Run the readiness chain for this checkout and record what it did as one
|
|
145
|
+
protocol readiness record (sandbox.json). Steps run in order and stop at the
|
|
146
|
+
first failure; later steps are recorded as skipped.
|
|
147
|
+
|
|
148
|
+
doctor --fix status launch --verify fixtures set verify
|
|
149
|
+
|
|
150
|
+
Core skips launch and fixtures (headless; verify is its readiness gate).
|
|
151
|
+
Mobile infers ios|android from the connected devices reported by status and
|
|
152
|
+
refuses with the numbered choice when that is ambiguous.
|
|
153
|
+
|
|
154
|
+
--target <path> Checkout path (default: cwd)
|
|
155
|
+
--platform <mobile|extension|core> Force the platform (auto-detected inside a checkout)
|
|
156
|
+
--mobile-platform <ios|android> Mobile target when status cannot decide it
|
|
157
|
+
--artifacts-dir <dir> Where sandbox.json and the per-step reports land
|
|
158
|
+
(default: <target>/temp/recipe/runtime)
|
|
159
|
+
--json Print the readiness record
|
|
160
|
+
|
|
161
|
+
Exit 0 when the record is ready, 1 otherwise.
|
|
162
|
+
|
|
163
|
+
Example:
|
|
164
|
+
mm-harness prepare
|
|
165
|
+
mm-harness prepare --platform mobile --mobile-platform ios --json
|
|
166
|
+
mm-harness prepare --artifacts-dir temp/tasks/fix/TAT-1/artifacts --json`
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "task",
|
|
170
|
+
summary: "Create the task directory an agent works from, out of a shared execution template.",
|
|
171
|
+
example: 'mm-harness task init temp/tasks/fix/TAT-1 --flow fix-bug --run-mode autonomous --template fix-bug/autonomous.mobile --title "Wrong total"',
|
|
172
|
+
helpText: `mm-harness task init <task-dir> [options]
|
|
173
|
+
|
|
174
|
+
Write the task directory from a selected execution template: TASK.md, the
|
|
175
|
+
CHECKLIST.md whose boxes are the steps, a mark shim routed through
|
|
176
|
+
mm-harness checklist mark, and inputs/ (handoff.json, worker-terminal-contract.json).
|
|
177
|
+
checklist-target.json is NOT written \u2014 absent means CHECKLIST.md + SIGNAL.json.
|
|
178
|
+
|
|
179
|
+
Applied when you do not pass them: --surface skill, --mark-command for this
|
|
180
|
+
executable, and --platform from the detected checkout.
|
|
181
|
+
|
|
182
|
+
Task:
|
|
183
|
+
--title <t> Task title (required)
|
|
184
|
+
--task-text <s> | --task-file <p> Task description, inline or from a file
|
|
185
|
+
--ticket <key> Ticket key
|
|
186
|
+
--source-ref <url> Where the task came from
|
|
187
|
+
|
|
188
|
+
Selection (same catalog as execution-template):
|
|
189
|
+
--flow <flow> Exact flow (required)
|
|
190
|
+
--run-mode <mode> autonomous|interactive|validation (required)
|
|
191
|
+
--platform <platform> Exact platform
|
|
192
|
+
--template <id> Exact template id (required)
|
|
193
|
+
--domain <domain> Exact domain
|
|
194
|
+
--dir <path> Custom flow-tree source (repeatable)
|
|
195
|
+
--domain-dir <domain=path> Domain-scoped team source (repeatable)
|
|
196
|
+
--package-templates <path> Canonical package flow-tree root
|
|
197
|
+
--package-id <id> Source label for the package root
|
|
198
|
+
--project-worker <path> Project template directory
|
|
199
|
+
--project-name <name> Source label for project templates
|
|
200
|
+
|
|
201
|
+
Identity and rendering:
|
|
202
|
+
--surface <s> Handoff surface (default: skill)
|
|
203
|
+
--project <name> --repo <owner/name> --attempt-id <id>
|
|
204
|
+
--var KEY=VALUE Template placeholder value (repeatable)
|
|
205
|
+
--task-dir-label <label> Task directory as written into the documents
|
|
206
|
+
--mode-preamble <text> Run-mode preamble for TASK.md
|
|
207
|
+
--addendum-file <path> Extra task-document section
|
|
208
|
+
--mark-command <cmd> Override the recorded mark command
|
|
209
|
+
--json Machine-readable output
|
|
210
|
+
|
|
211
|
+
Example:
|
|
212
|
+
mm-harness task init temp/tasks/fix/TAT-1 --flow fix-bug --run-mode autonomous \\
|
|
213
|
+
--template fix-bug/autonomous.mobile --package-templates <path> \\
|
|
214
|
+
--title "Wrong total on the receipt" --ticket TAT-1 --json`
|
|
215
|
+
},
|
|
136
216
|
{
|
|
137
217
|
name: "execution-template",
|
|
138
218
|
summary: "Discover, validate, and materialize shared agent checklists.",
|
|
@@ -698,7 +778,7 @@ const HELP_GROUPS = [
|
|
|
698
778
|
{
|
|
699
779
|
title: "DAILY LOOP",
|
|
700
780
|
blurb: "what a teammate runs many times a day (auto-ensures the overlay; --heal owns recovery)",
|
|
701
|
-
commands: ["status", "launch", "stop", "logs", "debug", "reload", "fixtures"]
|
|
781
|
+
commands: ["status", "task", "prepare", "launch", "stop", "logs", "debug", "reload", "fixtures"]
|
|
702
782
|
},
|
|
703
783
|
{
|
|
704
784
|
title: "DISCOVER",
|
package/dist/paths.js
CHANGED
|
@@ -11,6 +11,9 @@ function recipeRuntimeDir() {
|
|
|
11
11
|
function recipeHarnessRoot() {
|
|
12
12
|
return validateRelativeRecipePath("RECIPE_HARNESS_ROOT", process.env.RECIPE_HARNESS_ROOT || DEFAULT_RECIPE_HARNESS_ROOT);
|
|
13
13
|
}
|
|
14
|
+
function harnessExecutable() {
|
|
15
|
+
return path.resolve(process.env.MM_HARNESS_EXECUTABLE ?? path.join(runnerDir, "bin/mm-harness"));
|
|
16
|
+
}
|
|
14
17
|
function readPathDefaults() {
|
|
15
18
|
const defaultsPath = path.join(runnerDir, "adapters/shared/path-defaults.json");
|
|
16
19
|
const parsed = JSON.parse(fs.readFileSync(defaultsPath, "utf8"));
|
|
@@ -170,6 +173,7 @@ export {
|
|
|
170
173
|
DEFAULT_RECIPE_RUNTIME_DIR,
|
|
171
174
|
assertAdapter,
|
|
172
175
|
extensionIdPath,
|
|
176
|
+
harnessExecutable,
|
|
173
177
|
importMetaMaskNativeUiProvider,
|
|
174
178
|
importRecipeHarness,
|
|
175
179
|
importRecipeHarnessAppLifecycle,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@deeeed/metamask-harness",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.51.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.9.
|
|
23
|
+
"@farmslot/agent-runtime": "0.9.0",
|
|
24
|
+
"@farmslot/expo-recipe": "0.9.1",
|
|
25
25
|
"@farmslot/handoff": "^0.3.1",
|
|
26
|
-
"@farmslot/protocol": "0.
|
|
27
|
-
"@farmslot/recipe-harness": "0.16.
|
|
26
|
+
"@farmslot/protocol": "0.26.0",
|
|
27
|
+
"@farmslot/recipe-harness": "0.16.2",
|
|
28
28
|
"agent-device": "0.19.3",
|
|
29
29
|
"commander": "^12.0.0",
|
|
30
30
|
"es-module-lexer": "2.3.1",
|