blob: 69d7f6535720b2fea2234f3d0ac87bd2652d0566 [file]
import assert from 'node:assert/strict';
import { spawn } from 'node:child_process';
import { mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { fileURLToPath } from 'node:url';
import { describe, test } from 'node:test';
import { createSessionStore } from '@maka/storage';
import { validateHarborCellOutput } from '../cell-output.js';
import { mapLegacyMakaHeadlessArgs, runMakaEvalCli } from '../cli.js';
import { openHeadlessStorageForWrite } from '../headless-storage.js';
import { withCapturedProcessIo } from './helpers/capture-process-io.js';
import { readResults } from '../results.js';
import type { TaskEvent } from '../task-contracts.js';
import { taskRunLocator } from '../task-run-identity.js';
const cliPath = fileURLToPath(new URL('../cli.js', import.meta.url));
const repoRoot = fileURLToPath(new URL('../../../../', import.meta.url));
function runCli(args: string[]): Promise<{ code: number | null; stdout: string; stderr: string }> {
return new Promise((resolve) => {
const child = spawn(process.execPath, [cliPath, ...args], {
env: { ...process.env },
});
let stdout = '';
let stderr = '';
child.stdout.on('data', (d: Buffer) => {
stdout += d.toString('utf8');
});
child.stderr.on('data', (d: Buffer) => {
stderr += d.toString('utf8');
});
child.on('close', (code) => resolve({ code, stdout, stderr }));
});
}
// In-process route for ordinary command semantics: the same legacy argv
// mapping and canonical router the bin runs, with stdout/stderr captured at
// the process-stream seam and env overrides applied around the call. The real
// subprocess route above stays for the representative bin-wiring contract.
async function runCliInProcess(
args: string[],
options: { env?: NodeJS.ProcessEnv } = {},
): Promise<{ code: number; stdout: string; stderr: string }> {
const mapped = mapLegacyMakaHeadlessArgs(args);
assert.ok(mapped && mapped.length > 0, 'in-process runner expects a canonical command');
const envOverrides = Object.entries(options.env ?? {});
const savedEnv = envOverrides.map(([key]) => [key, process.env[key]] as const);
for (const [key, value] of envOverrides) process.env[key] = value;
const savedExitCode = process.exitCode;
try {
const {
result: code,
stdout,
stderr,
} = await withCapturedProcessIo(async () => {
try {
return await runMakaEvalCli(mapped);
} catch (error) {
// Mirror the bin's fatal handler: report the error and fail with 1.
process.stderr.write(
`${error instanceof Error ? (error.stack ?? String(error)) : String(error)}\n`,
);
return 1;
}
});
return { code, stdout, stderr };
} finally {
process.exitCode = savedExitCode;
for (const [key, value] of savedEnv) {
if (value === undefined) delete process.env[key];
else process.env[key] = value;
}
}
}
describe('maka-headless CLI', () => {
test('task-run readers report an actionable error for a non-Headless root', async () => {
const root = await mkdtemp(join(tmpdir(), 'maka-headless-unmarked-'));
const exportRoot = join(root, 'export');
try {
for (const args of [
['task', 'inspect', 'missing', '--store', root],
['task', 'export', 'missing', '--store', root, '--out', exportRoot],
]) {
const result = await runCli(args);
assert.equal(result.code, 1);
assert.match(result.stderr, /Pass the <out>\/runs directory created by a task run/);
assert.doesNotMatch(result.stderr, /StorageRootAuthorityError|\n\s+at /);
}
} finally {
await rm(root, { recursive: true, force: true });
}
});
test('rejects a task missing protectedPaths (grading boundary required)', async () => {
const dir = await mkdtemp(join(tmpdir(), 'maka-headless-cli-'));
try {
const spec = {
configs: [
{ id: 'fake-cfg', backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model' },
],
tasks: [
{
id: 't',
instruction: 'go',
workspaceDir: 'fixture',
verification: { command: 'true' },
},
],
};
const specPath = join(dir, 'spec.json');
await writeFile(specPath, JSON.stringify(spec), 'utf8');
const result = await runCliInProcess(['eval', specPath, '--out', join(dir, 'out')]);
assert.equal(result.code, 1);
assert.match(result.stderr, /protectedPaths/);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test('harbor run refuses real backend without explicit isolation', async () => {
const dir = await mkdtemp(join(tmpdir(), 'maka-headless-harbor-cli-'));
try {
await mkdir(join(dir, 'fixture'), { recursive: true });
const result = await runCliInProcess([
'harbor',
'run',
'--backend',
'ai-sdk',
'--instruction',
'solve it',
'--workdir',
join(dir, 'fixture'),
]);
assert.equal(result.code, 1);
assert.match(result.stderr, /requires --isolation harbor-local\|harbor-http/);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test('harbor run preflights host bridge URL and token', async () => {
const dir = await mkdtemp(join(tmpdir(), 'maka-headless-harbor-cli-'));
try {
await mkdir(join(dir, 'fixture'), { recursive: true });
const missingUrl = await runCliInProcess([
'harbor',
'run',
'--backend',
'fake',
'--isolation',
'harbor-http',
'--instruction',
'solve it',
'--workdir',
join(dir, 'fixture'),
]);
assert.equal(missingUrl.code, 1);
assert.match(missingUrl.stderr, /MAKA_HARBOR_TOOL_EXECUTOR_URL is required/);
const missingToken = await runCliInProcess(
[
'harbor',
'run',
'--backend',
'fake',
'--isolation',
'harbor-http',
'--instruction',
'solve it',
'--workdir',
join(dir, 'fixture'),
],
{ env: { MAKA_HARBOR_TOOL_EXECUTOR_URL: 'http://127.0.0.1:1' } },
);
assert.equal(missingToken.code, 1);
assert.match(missingToken.stderr, /MAKA_HARBOR_TOOL_EXECUTOR_TOKEN is required/);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test('harbor run task-run writes external Harbor verifier export without fake verifier authority', async () => {
const dir = await mkdtemp(join(tmpdir(), 'maka-headless-harbor-cli-'));
try {
const fixture = join(dir, 'fixture');
const outDir = join(dir, 'out');
const cellArtifactDir = join(dir, 'cell-artifacts');
await mkdir(fixture, { recursive: true });
await writeFile(join(fixture, 'README.txt'), 'Harbor owns the task workspace.\n', 'utf8');
const result = await runCliInProcess(
[
'harbor',
'run',
'--backend',
'fake',
'--isolation',
'none',
'--instruction',
'solve it',
'--workdir',
fixture,
'--task-id',
'tb-real-backend',
'--task-run-id',
'harbor-run-1',
'--out',
outDir,
'--include-events',
],
{
env: {
MAKA_ECONOMY_TASK_MODE: 'true',
MAKA_CELL_ARTIFACT_DIR: cellArtifactDir,
MAKA_MODEL: 'fake-model',
MAKA_REASONING_EFFORT: 'xhigh',
MAKA_TRIAL_PRICING_SOURCE: 'test-account-plan',
},
},
);
assert.equal(result.code, 0, result.stderr);
const summary = JSON.parse(result.stdout);
assert.equal(summary.taskRunId, 'harbor-run-1');
assert.equal(summary.mode, 'task-run');
assert.equal(summary.scored, false);
assert.equal(summary.authoritative, false);
const cell = validateHarborCellOutput(
JSON.parse(await readFile(join(cellArtifactDir, 'maka-cell-output.json'), 'utf8')),
);
assert.equal(cell.status, 'completed');
assert.equal(cell.promptHash, cell.executionIdentity?.systemPromptHash);
assert.equal(cell.executionIdentity?.llmConnectionSlug, 'fake');
assert.equal(cell.executionIdentity?.model, 'fake-model');
assert.equal(cell.executionIdentity?.reasoningEffort, 'xhigh');
assert.equal(cell.executionIdentity?.pricingProfile, 'test-account-plan');
assert.equal(cell.executionIdentity?.agentTools, false);
assert.equal(cell.toolSummary.actualToolCalls, 0);
assert.equal(cell.steps, 1);
assert.equal(cell.runtimeEventsPath, join(cellArtifactDir, 'runtime-events.jsonl'));
assert.match(await readFile(cell.runtimeEventsPath, 'utf8'), /"role":"system"/);
const durableIdentity = JSON.parse(
await readFile(join(cellArtifactDir, 'maka-cell-execution-identity.json'), 'utf8'),
);
assert.deepEqual(durableIdentity, cell.executionIdentity);
const harborExport = join(outDir, 'exports', taskRunLocator('harbor-run-1'));
const taskRunJson = JSON.parse(await readFile(join(harborExport, 'task-run.json'), 'utf8'));
assert.equal(taskRunJson.policy.economyTask.enabled, true);
assert.equal(taskRunJson.policy.economyTask.triggerSource, 'config');
assert.equal(taskRunJson.verifier.kind, 'terminal_bench');
assert.equal(taskRunJson.verifier.benchmark.instanceId, 'tb-real-backend');
assert.equal(taskRunJson.verifier.benchmark.dataset, 'terminal-bench/terminal-bench-2-1');
assert.equal(taskRunJson.verifier.benchmark.pendingExternalHarborVerifier, true);
assert.equal(taskRunJson.verifier.authority.authoritative, false);
assert.equal(taskRunJson.verifier.authority.label, 'external Harbor verifier pending');
const events = await readFile(join(harborExport, 'events.jsonl'), 'utf8');
assert.doesNotMatch(events, /"testCommand":"false"/);
assert.doesNotMatch(events, /"placeholder":true/);
assert.doesNotMatch(events, /verificationPlaceholder/);
assert.doesNotMatch(events, /unsupported local benchmark placeholder/);
const resultJson = await readFile(join(harborExport, 'result.json'), 'utf8');
assert.doesNotMatch(resultJson, /verificationPlaceholder/);
assert.doesNotMatch(resultJson, /unsupported local benchmark placeholder/);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test('harbor run task-run cell evidence covers every heavy-task invocation', async () => {
const dir = await mkdtemp(join(tmpdir(), 'maka-headless-harbor-heavy-'));
try {
const fixture = join(dir, 'fixture');
const outDir = join(dir, 'out');
const cellArtifactDir = join(dir, 'cell-artifacts');
await mkdir(fixture, { recursive: true });
await writeFile(join(fixture, 'README.txt'), 'Harbor owns the task workspace.\n', 'utf8');
const result = await runCliInProcess(
[
'harbor',
'run',
'--backend',
'fake',
'--isolation',
'none',
'--instruction',
'solve it',
'--workdir',
fixture,
'--task-id',
'tb-heavy-trajectory',
'--task-run-id',
'harbor-heavy-trajectory',
'--out',
outDir,
'--heavy-task',
],
{ env: { MAKA_CELL_ARTIFACT_DIR: cellArtifactDir, MAKA_MODEL: 'fake-model' } },
);
assert.equal(result.code, 0, result.stderr);
const cell = validateHarborCellOutput(
JSON.parse(await readFile(join(cellArtifactDir, 'maka-cell-output.json'), 'utf8')),
);
const runtimeEvents = (await readFile(cell.runtimeEventsPath, 'utf8'))
.trim()
.split('\n')
.map((line) => JSON.parse(line));
const completedModelSteps = runtimeEvents.filter(
(event) => event.role === 'model' && event.partial !== true,
);
assert.equal(cell.steps, 2);
assert.equal(completedModelSteps.length, 2);
const traceEvents = (await readFile(join(cellArtifactDir, 'trace-events.jsonl'), 'utf8'))
.trim()
.split('\n')
.map((line) => JSON.parse(line));
assert.equal(
new Set(traceEvents.map((event) => event.runId).filter(Boolean)).size,
2,
'combined trace must retain both heavy-task runs',
);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test('exits 0 when a run completes but fails verification (valid benchmark data)', async () => {
const dir = await mkdtemp(join(tmpdir(), 'maka-headless-cli-'));
try {
await mkdir(join(dir, 'fixture'), { recursive: true });
const spec = {
configs: [
{ id: 'fake-cfg', backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model' },
],
tasks: [
{
id: 't-fail',
instruction: 'go',
workspaceDir: 'fixture',
verification: { command: 'test -f nope.txt', protectedPaths: [] },
},
],
};
const specPath = join(dir, 'spec.json');
await writeFile(specPath, JSON.stringify(spec), 'utf8');
const result = await runCliInProcess(['eval', specPath, '--out', join(dir, 'out')]);
assert.equal(result.code, 0, result.stderr);
const records = await readResults(join(dir, 'out', 'results.jsonl'));
assert.equal(records[0]?.passed, false);
assert.ok(!records[0]?.error);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test('task run, inspect, and exports tolerate unavailable optional Session evidence', async () => {
const dir = await mkdtemp(join(tmpdir(), 'maka-headless-task-cli-'));
try {
await mkdir(join(dir, 'fixture'), { recursive: true });
await writeFile(join(dir, 'fixture', 'marker.txt'), 'ok', 'utf8');
const spec = {
configs: [
{ id: 'fake-cfg', backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model' },
],
tasks: [
{
id: 'tb-pass',
instruction: 'go',
workspaceDir: 'fixture',
verifier: {
kind: 'terminal_bench',
adapter: 'terminal-bench',
instanceId: 'tb-local',
testCommand: 'test -f marker.txt',
protectedPaths: [],
},
},
],
};
const specPath = join(dir, 'spec.json');
const outDir = join(dir, 'out');
await writeFile(specPath, JSON.stringify(spec), 'utf8');
const run = await runCliInProcess([
'task',
'run',
specPath,
'--task',
'tb-pass',
'--config',
'fake-cfg',
'--task-run-id',
'task-run-1',
'--out',
outDir,
'--include-events',
]);
assert.equal(run.code, 1);
assert.match(run.stdout, /taskRunId: task-run-1/);
const inspect = await runCliInProcess([
'task',
'inspect',
'task-run-1',
'--store',
join(outDir, 'runs'),
'--json',
]);
assert.equal(inspect.code, 0, inspect.stderr);
const inspectDocument = JSON.parse(inspect.stdout);
assert.equal(inspectDocument.schemaVersion, 'maka.task_run_inspect.v1');
assert.equal(inspectDocument.kind, 'task_run');
assert.equal(inspectDocument.taskRun.result.taxonomy, 'verification_failed');
assert.ok(Array.isArray(inspectDocument.attempts));
const humanInspect = await runCliInProcess([
'task',
'inspect',
'task-run-1',
'--store',
join(outDir, 'runs'),
]);
assert.equal(humanInspect.code, 0, humanInspect.stderr);
assert.match(humanInspect.stdout, /TaskRun task-run-1 \[/);
assert.match(humanInspect.stdout, /Task Events task_event:task-run-1/);
const exportDir = join(dir, 'manual-export');
const exported = await runCliInProcess([
'task',
'export',
'task-run-1',
'--store',
join(outDir, 'runs'),
'--out',
exportDir,
'--include-events',
]);
assert.equal(exported.code, 0, exported.stderr);
const taskRunJson = JSON.parse(await readFile(join(exportDir, 'task-run.json'), 'utf8'));
assert.equal(taskRunJson.verifier.kind, 'terminal_bench');
assert.equal(taskRunJson.verifier.benchmark.instanceId, 'tb-local');
assert.equal(taskRunJson.verifier.authority.authoritative, false);
assert.equal(taskRunJson.verifier.benchmark.verificationPlaceholder, true);
assert.match(
await readFile(join(exportDir, 'events.jsonl'), 'utf8'),
/verifier_result_recorded/,
);
const sessionId = inspectDocument.attempts[0]?.agentRuns[0]?.identity?.sessionId;
assert.equal(typeof sessionId, 'string');
const sessions = createSessionStore(join(outDir, 'runs'));
try {
await sessions.remove(sessionId);
} finally {
await sessions.close?.();
}
const aheExportDir = join(dir, 'ahe-export');
const aheExported = await runCliInProcess([
'ahe',
'export',
'task-run-1',
'--store',
join(outDir, 'runs'),
'--repo',
repoRoot,
'--out',
aheExportDir,
'--include-events',
]);
assert.equal(aheExported.code, 0, aheExported.stderr);
assert.match(aheExported.stdout, /harnessResults:/);
const aheResults = JSON.parse(
await readFile(join(aheExportDir, 'harness-results.json'), 'utf8'),
);
assert.equal(aheResults.results[0].status, 'excluded');
assert.equal(aheResults.results[0].scoreAuthority, 'self_check');
assert.equal(aheResults.results[0].taskRunId, 'task-run-1');
assert.match(aheResults.results[0].executionLineageRef.digest, /^sha256:/);
const aheTraceIndex = await readFile(join(aheExportDir, 'trace-index.json'), 'utf8');
const traceLocator = taskRunLocator('task-run-1');
assert.match(aheTraceIndex, new RegExp(`traces/${traceLocator}/result\\.md`));
assert.match(aheTraceIndex, /task-events.jsonl/);
assert.doesNotMatch(aheTraceIndex, /"runtimeEventsJsonl"/);
const aheLineage = JSON.parse(
await readFile(
join(aheExportDir, 'traces', traceLocator, 'execution-lineage.json'),
'utf8',
),
);
assert.equal(aheLineage.rawRuntimeEvents, 'included');
assert.equal(aheLineage.attempts[0].executions.length > 0, true);
assert.match(
aheLineage.attempts[0].executions[0].inspectRef.ref,
/agent-runs\/.+\/inspect.json$/,
);
assert.match(
aheLineage.attempts[0].executions[0].runtimeEventsRef.ref,
/agent-runs\/.+\/runtime-events.jsonl$/,
);
assert.match(
await readFile(
join(aheExportDir, aheLineage.attempts[0].executions[0].inspectRef.ref),
'utf8',
),
/maka.agent_run_inspect.v1/,
);
assert.match(
await readFile(
join(aheExportDir, aheLineage.attempts[0].executions[0].runtimeEventsRef.ref),
'utf8',
),
/"runId"/,
);
const aheMessages = JSON.parse(
await readFile(join(aheExportDir, 'traces', traceLocator, 'messages.json'), 'utf8'),
);
assert.deepEqual(
aheMessages.messages.map((message: { role: string }) => message.role),
['system', 'user', 'assistant'],
);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test('preserves a long TaskRun identity across run, inspect, result, and export', async () => {
const dir = await mkdtemp(join(tmpdir(), 'maka-headless-long-task-run-'));
try {
await mkdir(join(dir, 'fixture'), { recursive: true });
await writeFile(join(dir, 'fixture', 'marker.txt'), 'ok', 'utf8');
const spec = {
configs: [
{ id: 'fake-cfg', backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model' },
],
tasks: [
{
id: 'long-id-task',
instruction: 'complete the task',
workspaceDir: 'fixture',
verification: { command: 'test -f marker.txt', protectedPaths: [] },
},
],
};
const specPath = join(dir, 'spec.json');
const outDir = join(dir, 'out');
const taskRunId = `long-task-run-${'x'.repeat(320)}`;
await writeFile(specPath, JSON.stringify(spec), 'utf8');
const run = await runCliInProcess([
'task',
'run',
specPath,
'--task',
'long-id-task',
'--config',
'fake-cfg',
'--task-run-id',
taskRunId,
'--out',
outDir,
'--include-events',
]);
assert.equal(run.code, 0, run.stderr);
const inspect = await runCliInProcess([
'task',
'inspect',
taskRunId,
'--store',
join(outDir, 'runs'),
'--json',
]);
assert.equal(inspect.code, 0, inspect.stderr);
const inspectDocument = JSON.parse(inspect.stdout);
assert.equal(inspectDocument.taskRun.taskRunId, taskRunId);
assert.equal(inspectDocument.taskRun.result.taxonomy, 'passed');
const records = await readResults(join(outDir, 'results.jsonl'));
assert.equal(records.length, 1);
assert.equal(records[0]?.passed, true);
const exportRoot = join(outDir, 'exports', taskRunLocator(taskRunId));
const exported = JSON.parse(await readFile(join(exportRoot, 'task-run.json'), 'utf8'));
assert.equal(exported.taskRun.taskRunId, taskRunId);
assert.equal(exported.taskRun.result.taxonomy, 'passed');
assert.match(await readFile(join(exportRoot, 'events.jsonl'), 'utf8'), /task_run_completed/);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test('task resume continues a parked needs_approval task run', async () => {
const dir = await mkdtemp(join(tmpdir(), 'maka-headless-task-resume-'));
try {
await mkdir(join(dir, 'fixture'), { recursive: true });
await writeFile(join(dir, 'fixture', 'marker.txt'), 'ok', 'utf8');
const spec = {
configs: [
{ id: 'fake-cfg', backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model' },
],
tasks: [
{
id: 'approval-task',
instruction: 'go',
workspaceDir: 'fixture',
verification: { command: 'test -f marker.txt', protectedPaths: [] },
},
],
};
const specPath = join(dir, 'spec.json');
const outDir = join(dir, 'out');
const taskRunId = 'parked-run';
const firstAttemptId = `${taskRunId}-attempt-1`;
await writeFile(specPath, JSON.stringify(spec), 'utf8');
const initialEvents = [
JSON.stringify({
type: 'task_run_created',
id: 'e1',
taskRunId,
ts: 1,
taskId: 'approval-task',
configId: 'fake-cfg',
}),
JSON.stringify({
type: 'task_run_started',
id: 'e2',
taskRunId,
ts: 2,
startedAt: 2,
sessionId: 'session-1',
agentRunId: 'agent-1',
}),
JSON.stringify({
type: 'task_attempt_started',
id: 'e3',
taskRunId,
ts: 2,
attemptId: firstAttemptId,
startedAt: 2,
sessionId: 'session-1',
agentRunId: 'agent-1',
}),
JSON.stringify({
type: 'task_inbox_item_recorded',
id: 'e4',
taskRunId,
ts: 3,
item: {
schemaVersion: 1,
inboxItemId: 'inbox-1',
taskRunId,
attemptId: firstAttemptId,
kind: 'approval_request',
status: 'open',
title: 'Approval required',
reason: 'Bash requires approval',
createdAt: 3,
relatedRequestId: 'request-1',
},
}),
JSON.stringify({
type: 'task_run_needs_approval',
id: 'e5',
taskRunId,
ts: 3,
attemptId: firstAttemptId,
reason: 'approval',
inboxItemId: 'inbox-1',
}),
].map((line) => JSON.parse(line) as TaskEvent);
const { taskRunStore } = await openHeadlessStorageForWrite(join(outDir, 'runs'));
for (const event of initialEvents) await taskRunStore.appendEvent(taskRunId, event);
const resumed = await runCliInProcess([
'task',
'resume',
taskRunId,
'--spec',
specPath,
'--out',
outDir,
]);
assert.equal(resumed.code, 0, resumed.stderr);
assert.match(resumed.stdout, /resumed: parked-run/);
assert.match(resumed.stdout, /status: completed/);
const inspect = await runCliInProcess([
'task',
'inspect',
taskRunId,
'--store',
join(outDir, 'runs'),
'--json',
]);
assert.equal(inspect.code, 0, inspect.stderr);
const projected = JSON.parse(inspect.stdout);
assert.equal(projected.taskRun.status, 'completed');
assert.equal(projected.taskRun.result.taxonomy, 'passed');
assert.equal(projected.taskRun.attemptCount, 2);
assert.equal(projected.attempts.length, 2);
assert.equal(projected.taskRun.parked, undefined);
const exported = JSON.parse(
await readFile(join(outDir, 'exports', taskRunLocator(taskRunId), 'task-run.json'), 'utf8'),
);
assert.equal(exported.taskRun.status, 'completed');
assert.equal(exported.inbox.items[0].status, 'resolved');
const records = await readResults(join(outDir, 'results.jsonl'));
assert.equal(records.length, 1);
assert.equal(records[0]?.taskId, 'approval-task');
assert.equal(records[0]?.passed, true);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
test('task retry-failed retries retryable failures and skips unsupported adapters', async () => {
const dir = await mkdtemp(join(tmpdir(), 'maka-headless-task-retry-'));
try {
await mkdir(join(dir, 'fixture'), { recursive: true });
const spec = {
configs: [
{ id: 'fake-cfg', backend: 'fake', llmConnectionSlug: 'fake', model: 'fake-model' },
],
tasks: [
{
id: 'pass',
instruction: 'go',
workspaceDir: 'fixture',
verification: { command: 'true', protectedPaths: [] },
},
{
id: 'retry-me',
instruction: 'go',
workspaceDir: 'fixture',
verification: { command: 'true', protectedPaths: [] },
},
{
id: 'unsupported',
instruction: 'go',
workspaceDir: 'fixture',
verification: { command: 'true', protectedPaths: [] },
},
],
};
const specPath = join(dir, 'spec.json');
const priorPath = join(dir, 'prior-results.jsonl');
await writeFile(specPath, JSON.stringify(spec), 'utf8');
await writeFile(
priorPath,
[
JSON.stringify(record('pass', 'fake-cfg', true)),
JSON.stringify(
record('retry-me', 'fake-cfg', false, {
errorClass: 'verification_failed',
exitCode: 1,
}),
),
JSON.stringify(
record('unsupported', 'fake-cfg', false, {
errorClass: 'unsupported_adapter',
excludedReason: 'unsupported_adapter',
error: 'adapter missing',
exitCode: null,
}),
),
'',
].join('\n'),
'utf8',
);
const outDir = join(dir, 'out');
const result = await runCliInProcess([
'task',
'retry-failed',
priorPath,
'--spec',
specPath,
'--out',
outDir,
]);
assert.equal(result.code, 0, result.stderr);
assert.match(result.stdout, /retry retry-me/);
assert.match(result.stdout, /skip unsupported/);
const records = await readResults(join(outDir, 'results.jsonl'));
assert.equal(records.length, 4);
assert.equal(records.filter((r) => r.taskId === 'retry-me').length, 2);
assert.equal(records.at(-1)?.passed, true);
} finally {
await rm(dir, { recursive: true, force: true });
}
});
});
function record(
taskId: string,
configId: string,
passed: boolean,
extra: Record<string, unknown> = {},
) {
return {
taskId,
configId,
sessionId: `s-${taskId}`,
runId: `r-${taskId}`,
status: 'completed',
passed,
exitCode: passed ? 0 : 1,
steps: 1,
durationMs: 1,
startedAt: 1,
finishedAt: 2,
scored: true,
eligible: true,
...extra,
};
}