@lesliechan721/agent-skill-eval 0.1.0-beta.37 → 0.1.0-beta.39
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/dist/codex-eval-app-server.d.ts +2 -0
- package/dist/codex-eval-app-server.js +12 -5
- package/dist/codex-eval-app-server.js.map +1 -1
- package/dist/codex-workflow-evaluation.d.ts +16 -1
- package/dist/codex-workflow-evaluation.js +53 -7
- package/dist/codex-workflow-evaluation.js.map +1 -1
- package/dist/codex-workflow-fixtures.d.ts +37 -0
- package/dist/codex-workflow-fixtures.js +144 -0
- package/dist/codex-workflow-fixtures.js.map +1 -0
- package/dist/codex-workflow.d.ts +32 -4
- package/dist/codex-workflow.js +276 -17
- package/dist/codex-workflow.js.map +1 -1
- package/dist/index.d.ts +5 -0
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/instruction-v2.d.ts +53 -0
- package/dist/instruction-v2.js +169 -0
- package/dist/instruction-v2.js.map +1 -0
- package/dist/json-schema.js +9 -0
- package/dist/json-schema.js.map +1 -1
- package/dist/providers.js +50 -1
- package/dist/providers.js.map +1 -1
- package/dist/schemas/digests.json +5 -1
- package/dist/schemas/instruction-v2-receipt.schema.json +24 -0
- package/dist/schemas/instruction-v2-request.schema.json +51 -0
- package/dist/schemas/workflow-evaluation.schema.json +38 -0
- package/dist/schemas/workflow-fixture-action.schema.json +15 -0
- package/dist/types.d.ts +1 -1
- package/package.json +3 -3
package/dist/codex-workflow.js
CHANGED
|
@@ -5,11 +5,13 @@ import fs from 'fs-extra';
|
|
|
5
5
|
import { CodexEvalAttemptControl, EvalAppServerSession, extractAppServerThreadId, assertHostReadContainmentAvailable, hostReadContainmentProfile, } from './codex-eval-app-server.js';
|
|
6
6
|
import { applyCodexEvalProviderEnvironment, createCodexEvalTemporaryRoot, materializeCodexEvalRuntimeHome, redactCodexEvalValue, } from './codex-eval-runtime-config.js';
|
|
7
7
|
import { applyCodexEvalGitIsolation } from './codex-eval-isolation.js';
|
|
8
|
+
import { parseInstructionV2ReasoningEffort, parseInstructionV2Receipt, parseInstructionV2Transport, } from './instruction-v2.js';
|
|
8
9
|
import { runProviderProcess } from './process.js';
|
|
9
10
|
import { assertProviderRunPaths } from './run-paths.js';
|
|
10
11
|
import { parseEvalProviderGrade, } from './custom-process.js';
|
|
11
12
|
import { createCodexGrader, parseConversationGraderOutput } from './providers.js';
|
|
12
13
|
import { parseWorkflowEvaluation, normalizeWorkflowChecks, WorkflowEvaluation, } from './codex-workflow-evaluation.js';
|
|
14
|
+
import { runWorkflowFixtureAction, validateWorkflowFixtureActions } from './codex-workflow-fixtures.js';
|
|
13
15
|
const MAX_ADAPTER_OUTPUT_BYTES = 4 * 1024 * 1024;
|
|
14
16
|
const MAX_REQUEST_TEXT_BYTES = 200_000;
|
|
15
17
|
const MAX_ROLE_COUNT = 32;
|
|
@@ -70,10 +72,10 @@ async function assertWorkspaceRoleFile(workspace, relative) {
|
|
|
70
72
|
throw new Error(`workflow role configFile must be a regular file: ${relative}`);
|
|
71
73
|
}
|
|
72
74
|
}
|
|
73
|
-
export async function parseCodexWorkflowRequest(value, allowedModels, workspace) {
|
|
75
|
+
export async function parseCodexWorkflowRequest(value, allowedModels, workspace, fixtures) {
|
|
74
76
|
if (!isRecord(value))
|
|
75
77
|
throw new Error('workflow prepare stdout must be one JSON object.');
|
|
76
|
-
exactKeys(value, ['version', 'prompt', 'sandbox', 'model', 'developerInstructions', 'roles', 'multiAgent'], ['evaluation'], 'workflow prepare stdout');
|
|
78
|
+
exactKeys(value, ['version', 'prompt', 'sandbox', 'model', 'developerInstructions', 'roles', 'multiAgent'], ['evaluation', 'transport', 'reasoningEffort'], 'workflow prepare stdout');
|
|
77
79
|
if (value.version !== 1)
|
|
78
80
|
throw new Error('workflow prepare stdout version must be 1.');
|
|
79
81
|
if (value.sandbox !== 'read-only' && value.sandbox !== 'workspace-write') {
|
|
@@ -121,14 +123,16 @@ export async function parseCodexWorkflowRequest(value, allowedModels, workspace)
|
|
|
121
123
|
if (multiAgent.protocol === 'v1'
|
|
122
124
|
&& (multiAgent.hideSpawnAgentMetadata || multiAgent.exposeSpawnAgentModelOverrides))
|
|
123
125
|
throw new Error('workflow request V1 metadata fields must be false.');
|
|
124
|
-
|
|
126
|
+
const request = {
|
|
125
127
|
version: 1,
|
|
126
128
|
prompt: boundedString(value.prompt, 'workflow request prompt', MAX_REQUEST_TEXT_BYTES),
|
|
127
129
|
sandbox: value.sandbox,
|
|
128
130
|
model: value.model,
|
|
131
|
+
...(value.reasoningEffort === undefined ? {} : { reasoningEffort: parseInstructionV2ReasoningEffort(value.reasoningEffort) }),
|
|
129
132
|
developerInstructions: boundedString(value.developerInstructions, 'workflow request developerInstructions', MAX_REQUEST_TEXT_BYTES),
|
|
130
133
|
roles,
|
|
131
134
|
...(value.evaluation === undefined ? {} : { evaluation: parseWorkflowEvaluation(value.evaluation) }),
|
|
135
|
+
...(value.transport === undefined ? {} : { transport: await parseInstructionV2Transport(value.transport, workspace) }),
|
|
132
136
|
multiAgent: {
|
|
133
137
|
protocol: multiAgent.protocol,
|
|
134
138
|
maxDepth: multiAgent.maxDepth,
|
|
@@ -136,6 +140,28 @@ export async function parseCodexWorkflowRequest(value, allowedModels, workspace)
|
|
|
136
140
|
exposeSpawnAgentModelOverrides: multiAgent.exposeSpawnAgentModelOverrides,
|
|
137
141
|
},
|
|
138
142
|
};
|
|
143
|
+
if (request.transport) {
|
|
144
|
+
const external = request.transport.request;
|
|
145
|
+
if (external.instructions.text !== request.developerInstructions || external.task !== request.prompt
|
|
146
|
+
|| external.run.access !== request.sandbox || (external.run.model ?? null) !== request.model
|
|
147
|
+
|| external.run.reasoningEffort !== request.reasoningEffort) {
|
|
148
|
+
throw new Error('instruction-v2 request must match workflow instructions, task, access, model, and reasoningEffort.');
|
|
149
|
+
}
|
|
150
|
+
if (request.evaluation?.interactions.length || request.evaluation?.setupPrompt !== undefined) {
|
|
151
|
+
throw new Error('instruction-v2 transport does not support evaluation interactions or setupPrompt.');
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const actions = [
|
|
155
|
+
...(request.evaluation?.setupActions ?? []),
|
|
156
|
+
...(request.evaluation?.finalActions ?? []),
|
|
157
|
+
...(request.evaluation?.interactions.flatMap((interaction) => interaction.action ? [interaction.action] : []) ?? []),
|
|
158
|
+
];
|
|
159
|
+
if (actions.length) {
|
|
160
|
+
if (!fixtures)
|
|
161
|
+
throw new Error('workflow fixture actions require the Eval-owned fixture root.');
|
|
162
|
+
await validateWorkflowFixtureActions(actions, fixtures);
|
|
163
|
+
}
|
|
164
|
+
return request;
|
|
139
165
|
}
|
|
140
166
|
function parseJsonObject(stdout, label) {
|
|
141
167
|
let value;
|
|
@@ -295,6 +321,7 @@ function workflowConfig(request, workspace) {
|
|
|
295
321
|
`features.multi_agent_v2.expose_spawn_agent_model_overrides=${String(request.multiAgent.exposeSpawnAgentModelOverrides)}`,
|
|
296
322
|
`agents.max_depth=${request.multiAgent.maxDepth}`,
|
|
297
323
|
`developer_instructions=${configValue(request.developerInstructions)}`,
|
|
324
|
+
...(request.reasoningEffort ? [`model_reasoning_effort=${configValue(request.reasoningEffort)}`] : []),
|
|
298
325
|
...request.roles.flatMap((role) => [
|
|
299
326
|
`agents.${role.name}.description=${configValue(role.description)}`,
|
|
300
327
|
`agents.${role.name}.config_file=${configValue(path.join(workspace, role.configFile))}`,
|
|
@@ -1456,7 +1483,7 @@ export async function normalizeCodexWorkflowRollouts(options) {
|
|
|
1456
1483
|
leafCalls: collaborationCalls.filter((call) => call.threadId !== options.rootThreadId
|
|
1457
1484
|
|| (call.linkedThreadId !== undefined && descendants.has(call.linkedThreadId))),
|
|
1458
1485
|
skillLoads,
|
|
1459
|
-
trace: selected.flatMap((record) => record.trace.map((event) => sanitizeTraceValue(event, options.runtime, [
|
|
1486
|
+
trace: selected.flatMap((record) => record.trace.map((event) => sanitizeTraceValue(isRecord(event) ? { ...event, threadId: record.threadId, parentThreadId: record.parentThreadId } : event, options.runtime, [
|
|
1460
1487
|
...isolatedPaths,
|
|
1461
1488
|
...selected.flatMap((entry) => entry.agentPath
|
|
1462
1489
|
? [{ path: entry.agentPath, marker: '[agent-path]' }]
|
|
@@ -1478,6 +1505,203 @@ function sandboxPolicy(request) {
|
|
|
1478
1505
|
excludeSlashTmp: false,
|
|
1479
1506
|
};
|
|
1480
1507
|
}
|
|
1508
|
+
function workflowFixtureRunner(input, control) {
|
|
1509
|
+
return (action) => runWorkflowFixtureAction({
|
|
1510
|
+
action, workspace: input.input.workspacePath, fixtures: input.input.fixturesPath,
|
|
1511
|
+
output: input.input.outputDir, evalRoot: input.evalRoot, sourceEnv: input.sourceEnv, runtime: input.codexRuntime,
|
|
1512
|
+
timeoutMs: control.remaining('workflow fixture action'), signal: control.signal,
|
|
1513
|
+
processManagement: input.processManagement,
|
|
1514
|
+
...(input.processTree ? { processTree: input.processTree } : {}),
|
|
1515
|
+
...(input.protectedEvalRoots ? { protectedEvalRoots: input.protectedEvalRoots } : {}),
|
|
1516
|
+
});
|
|
1517
|
+
}
|
|
1518
|
+
async function readInstructionV2File(root, relative, limit) {
|
|
1519
|
+
relativeWorkspacePath(relative, 'instruction-v2 capture path');
|
|
1520
|
+
let target = root;
|
|
1521
|
+
for (const part of relative.split('/')) {
|
|
1522
|
+
target = path.join(target, part);
|
|
1523
|
+
if ((await fs.lstat(target)).isSymbolicLink())
|
|
1524
|
+
throw new Error('instruction-v2 capture must not contain symbolic links.');
|
|
1525
|
+
}
|
|
1526
|
+
const stat = await fs.lstat(target);
|
|
1527
|
+
if (!stat.isFile() || stat.size > limit)
|
|
1528
|
+
throw new Error('instruction-v2 capture is not a bounded regular file.');
|
|
1529
|
+
return fs.readFile(target, 'utf8');
|
|
1530
|
+
}
|
|
1531
|
+
const instructionV2Transport = {
|
|
1532
|
+
async run(input) {
|
|
1533
|
+
const selected = input.request.transport;
|
|
1534
|
+
if (!path.isAbsolute(input.codexBin))
|
|
1535
|
+
throw new Error('instruction-v2 requires the Engine-resolved absolute Codex executable.');
|
|
1536
|
+
const control = new CodexEvalAttemptControl({
|
|
1537
|
+
label: 'instruction-v2 workflow', timeoutMs: input.timeoutMs,
|
|
1538
|
+
...(input.signal ? { signal: input.signal } : {}),
|
|
1539
|
+
});
|
|
1540
|
+
const temporaryRoot = await createCodexEvalTemporaryRoot('instruction-v2-workflow-');
|
|
1541
|
+
const runtimeRoot = await fs.realpath(temporaryRoot.root);
|
|
1542
|
+
const codexHome = path.join(runtimeRoot, 'codex-home');
|
|
1543
|
+
const runDirectory = path.join(runtimeRoot, 'run');
|
|
1544
|
+
const env = {
|
|
1545
|
+
...buildEngineEnvironment(input.sourceEnv, runtimeRoot, codexHome, input.codexRuntime),
|
|
1546
|
+
AW_SUBAGENT_RUN_DIR: runDirectory,
|
|
1547
|
+
AW_SUBAGENT_RESULT_FILE: path.join(runDirectory, 'result.json'),
|
|
1548
|
+
AW_SUBAGENT_TRACE_FILE: path.join(runDirectory, 'codex-events.jsonl'),
|
|
1549
|
+
AW_SUBAGENT_CODEX_BINARY: input.codexBin,
|
|
1550
|
+
};
|
|
1551
|
+
const startedAt = Date.now();
|
|
1552
|
+
let cleaned = false;
|
|
1553
|
+
let judgeNumber = 0;
|
|
1554
|
+
const judgeUsage = [];
|
|
1555
|
+
const grader = input.request.evaluation ? createCodexGrader({
|
|
1556
|
+
sourceEnv: input.sourceEnv, codexRuntime: input.codexRuntime, codexBin: input.codexBin,
|
|
1557
|
+
...(input.request.model ? { model: input.request.model } : {}), processManagement: input.processManagement,
|
|
1558
|
+
}) : undefined;
|
|
1559
|
+
const evaluation = input.request.evaluation ? new WorkflowEvaluation(input.request.evaluation, input.input.workspacePath, async (prompt) => {
|
|
1560
|
+
const outputDir = path.join(input.input.outputDir, 'workflow-judges', String(++judgeNumber));
|
|
1561
|
+
await fs.ensureDir(outputDir);
|
|
1562
|
+
const result = await grader({ prompt, outputDir, timeoutMs: control.remaining('external semantic check'), signal: control.signal });
|
|
1563
|
+
if (!result.mechanicalPassed)
|
|
1564
|
+
throw new Error('external workflow semantic grader did not complete its read-only checks.');
|
|
1565
|
+
judgeUsage.push(result.usage);
|
|
1566
|
+
return parseConversationGraderOutput(result.output);
|
|
1567
|
+
}, workflowFixtureRunner(input, control)) : undefined;
|
|
1568
|
+
try {
|
|
1569
|
+
await Promise.all([
|
|
1570
|
+
fs.ensureDir(env.HOME), fs.ensureDir(env.TMPDIR), fs.mkdir(runDirectory, { mode: 0o700 }),
|
|
1571
|
+
materializeCodexEvalRuntimeHome(input.codexRuntime, codexHome, { trustedProject: input.input.workspacePath }),
|
|
1572
|
+
]);
|
|
1573
|
+
await evaluation?.runSetupActions();
|
|
1574
|
+
const before = await snapshotWorkspace(input.input.workspacePath);
|
|
1575
|
+
let containment;
|
|
1576
|
+
if (input.request.evaluation) {
|
|
1577
|
+
containment = hostReadContainmentProfile({
|
|
1578
|
+
forbiddenHostRoots: [input.input.fixturesPath, input.input.outputDir],
|
|
1579
|
+
writableRoots: [runtimeRoot, input.input.workspacePath],
|
|
1580
|
+
unwritableRoots: input.request.sandbox === 'read-only' ? [input.input.workspacePath] : [],
|
|
1581
|
+
}) + '\n' + [...new Set([input.evalRoot, ...(input.protectedEvalRoots ?? [])])].map((root) => `(deny file-read* (require-all (subpath ${JSON.stringify(root)}) (require-not (subpath ${JSON.stringify(input.input.workspacePath)}))))`).join('\n');
|
|
1582
|
+
for (let ancestor = path.dirname(input.input.workspacePath); ancestor !== path.dirname(ancestor); ancestor = path.dirname(ancestor)) {
|
|
1583
|
+
containment += `\n(allow file-read-metadata (literal ${JSON.stringify(ancestor)}))`;
|
|
1584
|
+
}
|
|
1585
|
+
await assertHostReadContainmentAvailable(containment);
|
|
1586
|
+
}
|
|
1587
|
+
evaluation?.observe({ method: 'evaluation/externalSubmission', runId: selected.request.run.id }, null);
|
|
1588
|
+
const processResult = await runProviderProcess({
|
|
1589
|
+
executable: containment ? '/usr/bin/sandbox-exec' : selected.command[0],
|
|
1590
|
+
args: containment ? ['-p', containment, ...selected.command] : [...selected.command.slice(1)],
|
|
1591
|
+
cwd: input.input.workspacePath, env, stdin: JSON.stringify(selected.request),
|
|
1592
|
+
timeoutMs: control.remaining('external execution'), maxBufferBytes: MAX_ADAPTER_OUTPUT_BYTES,
|
|
1593
|
+
signal: control.signal,
|
|
1594
|
+
}, {}, {
|
|
1595
|
+
containment: input.processManagement.mode === 'unmanaged-confirmed' ? 'unmanaged-confirmed' : 'verified-observed',
|
|
1596
|
+
...(input.processManagement.mode === 'unmanaged-confirmed' ? {} : { processTree: input.processTree }),
|
|
1597
|
+
});
|
|
1598
|
+
let receipt = null;
|
|
1599
|
+
let diagnostic = null;
|
|
1600
|
+
try {
|
|
1601
|
+
receipt = parseInstructionV2Receipt(JSON.parse(await readInstructionV2File(runDirectory, 'result.json', 64 * 1024)));
|
|
1602
|
+
}
|
|
1603
|
+
catch (error) {
|
|
1604
|
+
diagnostic = `External result receipt unavailable: ${error.message}`;
|
|
1605
|
+
}
|
|
1606
|
+
const trace = [];
|
|
1607
|
+
if (receipt?.traceFile) {
|
|
1608
|
+
try {
|
|
1609
|
+
const raw = await readInstructionV2File(runDirectory, receipt.traceFile, 8 * 1024 * 1024);
|
|
1610
|
+
const lines = raw.split(/\r?\n/u).filter((line) => line.trim());
|
|
1611
|
+
if (lines.length > 5_000)
|
|
1612
|
+
throw new Error('instruction-v2 trace event limit exceeded.');
|
|
1613
|
+
for (const line of lines)
|
|
1614
|
+
trace.push(JSON.parse(line));
|
|
1615
|
+
}
|
|
1616
|
+
catch (error) {
|
|
1617
|
+
diagnostic ??= `External trace unavailable: ${error.message}`;
|
|
1618
|
+
trace.length = 0;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
const completed = receipt?.outcome === 'completed' && receipt.providerTerminal === 'turn.completed'
|
|
1622
|
+
&& receipt.instructionChannel !== 'user-prefix'
|
|
1623
|
+
&& receipt.instructionsRevision === selected.request.instructions.revision
|
|
1624
|
+
&& processResult.exitCode === 0 && processResult.signal === null
|
|
1625
|
+
&& !processResult.timedOut && !processResult.cancelled && !processResult.maxBufferExceeded
|
|
1626
|
+
&& receipt.generationStarted !== false && receipt.scopeViolation !== true
|
|
1627
|
+
&& (receipt.localExitCode === undefined || receipt.localExitCode === 0)
|
|
1628
|
+
&& (input.processManagement.mode === 'unmanaged-confirmed' || processResult.gradingEvidenceAccepted)
|
|
1629
|
+
&& processResult.stdout.trim() !== '';
|
|
1630
|
+
const status = completed ? 'completed' : receipt?.outcome === 'failed'
|
|
1631
|
+
&& (receipt.providerTerminal === 'turn.failed' || receipt.generationStarted === false) ? 'failed' : 'unknown';
|
|
1632
|
+
if (!completed)
|
|
1633
|
+
diagnostic ??= 'External execution lacks compatible channel, instruction revision, terminal, normal exit, or final text evidence.';
|
|
1634
|
+
const after = await snapshotWorkspace(input.input.workspacePath);
|
|
1635
|
+
await temporaryRoot.cleanup();
|
|
1636
|
+
cleaned = true;
|
|
1637
|
+
const usage = standardUsage(receipt?.usage);
|
|
1638
|
+
const evidence = {
|
|
1639
|
+
version: 1, rootThreadId: receipt?.providerSessionId ?? selected.request.run.id,
|
|
1640
|
+
completion: { status, turnStatus: receipt?.providerTerminal ?? 'unknown', durationMs: Date.now() - startedAt },
|
|
1641
|
+
termination: {
|
|
1642
|
+
reason: status,
|
|
1643
|
+
providerProcessCleanupConfirmed: input.processManagement.mode !== 'unmanaged-confirmed'
|
|
1644
|
+
&& processResult.gradingEvidenceAccepted,
|
|
1645
|
+
},
|
|
1646
|
+
usage, finalMessage: processResult.stdout, workspaceDiff: workspaceDiff(before, after),
|
|
1647
|
+
skillLoads: [], threadGraph: [], collaborationCalls: [], leafCalls: [],
|
|
1648
|
+
cleanup: { isolatedCodexHomeRemoved: true },
|
|
1649
|
+
transport: {
|
|
1650
|
+
type: 'instruction-v2', request: selected.request, command: selected.command, receipt,
|
|
1651
|
+
traceAvailable: trace.length > 0,
|
|
1652
|
+
observationLimits: [
|
|
1653
|
+
'Provider JSONL events are captured as supplied; complete file-access attribution and nested delegation coverage are not established.',
|
|
1654
|
+
'A file diff proves observed final content, not absence of intermediate writes. Model statements do not prove read-only behavior, independent history, or OS isolation.',
|
|
1655
|
+
'Provider trace is read after process exit. Artifact snapshots measure before submission and after cleanup; replayed event order does not supply intermediate snapshots.',
|
|
1656
|
+
],
|
|
1657
|
+
diagnostic, exitCode: processResult.exitCode, signal: processResult.signal, stderr: processResult.stderr,
|
|
1658
|
+
},
|
|
1659
|
+
};
|
|
1660
|
+
if (completed && evaluation) {
|
|
1661
|
+
for (const frame of trace)
|
|
1662
|
+
evaluation.observe({ method: 'evaluation/externalTraceCapture', capturedAfterExit: true, frame }, evidence.rootThreadId);
|
|
1663
|
+
const messages = [
|
|
1664
|
+
{ type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: selected.request.task }] } },
|
|
1665
|
+
...trace,
|
|
1666
|
+
{ type: 'response_item', payload: { type: 'agent_message', text: processResult.stdout } },
|
|
1667
|
+
];
|
|
1668
|
+
try {
|
|
1669
|
+
evidence.evaluation = await evaluation.finish(evidence, messages);
|
|
1670
|
+
if (!trace.length) {
|
|
1671
|
+
evidence.evaluation.complete = false;
|
|
1672
|
+
evidence.evaluation.checks = evaluation.request.checks.map(({ name }) => ({
|
|
1673
|
+
name, verdict: 'unknown', evidence: 'External provider tool trace is unavailable; behavior assertions cannot be established from final text.',
|
|
1674
|
+
}));
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
catch (error) {
|
|
1678
|
+
evidence.evaluation = {
|
|
1679
|
+
complete: false, checks: [], observations: evaluation.observations,
|
|
1680
|
+
snapshots: evaluation.snapshots, decisions: evaluation.decisions,
|
|
1681
|
+
fixtureActions: evaluation.fixtureActions, measurementStart: evaluation.measurementStart,
|
|
1682
|
+
};
|
|
1683
|
+
await writeWorkflowArtifacts(input.input.outputDir, redactCodexEvalValue({ evidence, trace, modelOutput: processResult.stdout, usage }, input.codexRuntime));
|
|
1684
|
+
throw error;
|
|
1685
|
+
}
|
|
1686
|
+
const totals = [usage, ...judgeUsage].map(standardUsage);
|
|
1687
|
+
evidence.usage = totals.every((value) => value !== null) ? {
|
|
1688
|
+
inputTokens: totals.reduce((sum, value) => sum + value.inputTokens, 0),
|
|
1689
|
+
outputTokens: totals.reduce((sum, value) => sum + value.outputTokens, 0),
|
|
1690
|
+
} : null;
|
|
1691
|
+
}
|
|
1692
|
+
return redactCodexEvalValue({ evidence, trace, modelOutput: processResult.stdout, usage: evidence.usage }, input.codexRuntime);
|
|
1693
|
+
}
|
|
1694
|
+
finally {
|
|
1695
|
+
try {
|
|
1696
|
+
if (!cleaned)
|
|
1697
|
+
await temporaryRoot.cleanup();
|
|
1698
|
+
}
|
|
1699
|
+
finally {
|
|
1700
|
+
control.dispose();
|
|
1701
|
+
}
|
|
1702
|
+
}
|
|
1703
|
+
},
|
|
1704
|
+
};
|
|
1481
1705
|
const defaultTransport = {
|
|
1482
1706
|
async run(input) {
|
|
1483
1707
|
const control = new CodexEvalAttemptControl({
|
|
@@ -1486,8 +1710,10 @@ const defaultTransport = {
|
|
|
1486
1710
|
...(input.signal ? { signal: input.signal } : {}),
|
|
1487
1711
|
});
|
|
1488
1712
|
const temporaryRoot = await createCodexEvalTemporaryRoot('codex-workflow-');
|
|
1489
|
-
const
|
|
1490
|
-
const
|
|
1713
|
+
const runtimeRoot = await fs.realpath(temporaryRoot.root);
|
|
1714
|
+
const codexHome = path.join(runtimeRoot, 'codex-home');
|
|
1715
|
+
const env = buildEngineEnvironment(input.sourceEnv, runtimeRoot, codexHome, input.codexRuntime);
|
|
1716
|
+
env.AW_SUBAGENT_CODEX_BINARY = input.codexBin;
|
|
1491
1717
|
let session;
|
|
1492
1718
|
let cleanupConfirmed = false;
|
|
1493
1719
|
let codexHomeRemoved = false;
|
|
@@ -1512,7 +1738,7 @@ const defaultTransport = {
|
|
|
1512
1738
|
throw new Error('workflow semantic grader did not complete its read-only checks.');
|
|
1513
1739
|
judgeUsage.push(result.usage);
|
|
1514
1740
|
return parseConversationGraderOutput(result.output);
|
|
1515
|
-
}) : undefined;
|
|
1741
|
+
}, workflowFixtureRunner(input, control)) : undefined;
|
|
1516
1742
|
let removeHandler;
|
|
1517
1743
|
let removeFailureHandler;
|
|
1518
1744
|
try {
|
|
@@ -1523,16 +1749,21 @@ const defaultTransport = {
|
|
|
1523
1749
|
trustedProject: input.input.workspacePath,
|
|
1524
1750
|
}),
|
|
1525
1751
|
]);
|
|
1752
|
+
await evaluation?.runSetupActions();
|
|
1526
1753
|
const before = await snapshotWorkspace(input.input.workspacePath);
|
|
1527
1754
|
let containment;
|
|
1528
1755
|
if (evaluation) {
|
|
1529
1756
|
containment = hostReadContainmentProfile({
|
|
1530
1757
|
forbiddenHostRoots: [input.input.fixturesPath, input.input.outputDir],
|
|
1531
|
-
writableRoots: [
|
|
1758
|
+
writableRoots: [runtimeRoot, input.input.workspacePath],
|
|
1532
1759
|
unwritableRoots: input.request.sandbox === 'read-only' ? [input.input.workspacePath] : [],
|
|
1533
1760
|
}) + '\n' + [...new Set([input.evalRoot, ...(input.protectedEvalRoots ?? [])])].map((root) => `(deny file-read* (require-all (subpath ${JSON.stringify(root)}) (require-not (subpath ${JSON.stringify(input.input.workspacePath)}))))`).join('\n');
|
|
1761
|
+
for (let ancestor = path.dirname(input.input.workspacePath); ancestor !== path.dirname(ancestor); ancestor = path.dirname(ancestor)) {
|
|
1762
|
+
containment += `\n(allow file-read-metadata (literal ${JSON.stringify(ancestor)}))`;
|
|
1763
|
+
}
|
|
1534
1764
|
await assertHostReadContainmentAvailable(containment);
|
|
1535
1765
|
}
|
|
1766
|
+
const configOverrides = workflowConfig(input.request, input.input.workspacePath);
|
|
1536
1767
|
session = new EvalAppServerSession({
|
|
1537
1768
|
codexBin: input.codexBin,
|
|
1538
1769
|
cwd: input.input.workspacePath,
|
|
@@ -1541,7 +1772,7 @@ const defaultTransport = {
|
|
|
1541
1772
|
sensitiveEnvironmentNames: input.codexRuntime.sensitiveEnvironmentNames,
|
|
1542
1773
|
isolation: 'standard',
|
|
1543
1774
|
processManagement: input.processManagement,
|
|
1544
|
-
configOverrides
|
|
1775
|
+
configOverrides,
|
|
1545
1776
|
...(containment ? { hostReadContainmentProfile: containment } : {}),
|
|
1546
1777
|
});
|
|
1547
1778
|
await session.initialize(control.rpcTimeout('initialize'));
|
|
@@ -1565,7 +1796,7 @@ const defaultTransport = {
|
|
|
1565
1796
|
const isolatedPaths = [
|
|
1566
1797
|
{ path: codexHome, marker: '[codex-home]' },
|
|
1567
1798
|
{ path: env.HOME, marker: '[isolated-home]' },
|
|
1568
|
-
{ path:
|
|
1799
|
+
{ path: runtimeRoot, marker: '[runtime-root]' },
|
|
1569
1800
|
];
|
|
1570
1801
|
frames.push(sanitizeTraceValue(frame, input.codexRuntime, isolatedPaths, input.request.multiAgent.protocol === 'v2'));
|
|
1571
1802
|
const method = typeof frame.method === 'string' ? frame.method : '';
|
|
@@ -1680,7 +1911,7 @@ const defaultTransport = {
|
|
|
1680
1911
|
const isolatedPaths = [
|
|
1681
1912
|
{ path: codexHome, marker: '[codex-home]' },
|
|
1682
1913
|
{ path: env.HOME, marker: '[isolated-home]' },
|
|
1683
|
-
{ path:
|
|
1914
|
+
{ path: runtimeRoot, marker: '[runtime-root]' },
|
|
1684
1915
|
];
|
|
1685
1916
|
const rollout = await normalizeCodexWorkflowRollouts({
|
|
1686
1917
|
codexHome,
|
|
@@ -1719,10 +1950,34 @@ const defaultTransport = {
|
|
|
1719
1950
|
collaborationCalls: rollout.collaborationCalls,
|
|
1720
1951
|
leafCalls: rollout.leafCalls,
|
|
1721
1952
|
cleanup: { isolatedCodexHomeRemoved: true },
|
|
1953
|
+
launch: {
|
|
1954
|
+
instructionChannel: 'developer', developerInstructions: input.request.developerInstructions,
|
|
1955
|
+
instructionsRevision: createHash('sha256').update(input.request.developerInstructions).digest('hex'),
|
|
1956
|
+
model: input.request.model, reasoningEffort: input.request.reasoningEffort ?? null,
|
|
1957
|
+
sandbox: input.request.sandbox, roles: input.request.roles, configOverrides,
|
|
1958
|
+
hostRoleAdoption: 'unknown',
|
|
1959
|
+
},
|
|
1722
1960
|
};
|
|
1723
1961
|
if (evaluation) {
|
|
1724
1962
|
// Subject processes are stopped before final snapshots and independent grading.
|
|
1725
|
-
|
|
1963
|
+
try {
|
|
1964
|
+
evidence.evaluation = await evaluation.finish(evidence, rollout.trace);
|
|
1965
|
+
}
|
|
1966
|
+
catch (error) {
|
|
1967
|
+
evidence.evaluation = {
|
|
1968
|
+
complete: false, checks: [], observations: evaluation.observations,
|
|
1969
|
+
snapshots: evaluation.snapshots, decisions: evaluation.decisions,
|
|
1970
|
+
fixtureActions: evaluation.fixtureActions,
|
|
1971
|
+
measurementStart: evaluation.measurementStart,
|
|
1972
|
+
};
|
|
1973
|
+
await writeWorkflowArtifacts(input.input.outputDir, {
|
|
1974
|
+
evidence: session.scrub(redactCodexEvalValue(evidence, input.codexRuntime)),
|
|
1975
|
+
trace: session.scrub(redactCodexEvalValue([...frames, ...rollout.trace], input.codexRuntime)),
|
|
1976
|
+
modelOutput: redactCodexEvalValue(finalMessage, input.codexRuntime),
|
|
1977
|
+
usage: redactCodexEvalValue(evidence.usage, input.codexRuntime),
|
|
1978
|
+
});
|
|
1979
|
+
throw error;
|
|
1980
|
+
}
|
|
1726
1981
|
const totals = [usage, ...judgeUsage].map(standardUsage);
|
|
1727
1982
|
if (totals.some((value) => value === null))
|
|
1728
1983
|
throw new Error('workflow evaluation lacks required judge usage evidence.');
|
|
@@ -1848,13 +2103,13 @@ export async function executeCodexWorkflowProvider(options) {
|
|
|
1848
2103
|
control.throwIfStopped('prepare');
|
|
1849
2104
|
throw error;
|
|
1850
2105
|
}
|
|
1851
|
-
const request = await parseCodexWorkflowRequest(prepared, options.provider.allowedModels, paths.workspacePath);
|
|
1852
|
-
if (request.model === null && request.multiAgent.protocol === 'v1') {
|
|
2106
|
+
const request = await parseCodexWorkflowRequest(prepared, options.provider.allowedModels, paths.workspacePath, paths.fixturesPath);
|
|
2107
|
+
if (!request.transport && request.model === null && request.multiAgent.protocol === 'v1') {
|
|
1853
2108
|
request.model = 'gpt-5.6-luna';
|
|
1854
2109
|
}
|
|
1855
2110
|
let execution;
|
|
1856
2111
|
try {
|
|
1857
|
-
execution = await (options.transport ?? defaultTransport).run({
|
|
2112
|
+
execution = await (options.transport ?? (request.transport ? instructionV2Transport : defaultTransport)).run({
|
|
1858
2113
|
request,
|
|
1859
2114
|
input: { ...options.input, ...paths, timeoutMs: control.remaining('Codex execution') },
|
|
1860
2115
|
evalRoot: paths.evalRoot,
|
|
@@ -1863,6 +2118,7 @@ export async function executeCodexWorkflowProvider(options) {
|
|
|
1863
2118
|
sourceEnv,
|
|
1864
2119
|
timeoutMs: control.remaining('Codex execution'),
|
|
1865
2120
|
processManagement,
|
|
2121
|
+
...(options.processTree ? { processTree: options.processTree } : {}),
|
|
1866
2122
|
...(options.protectedEvalRoots ? { protectedEvalRoots: options.protectedEvalRoots } : {}),
|
|
1867
2123
|
signal: control.signal,
|
|
1868
2124
|
});
|
|
@@ -1871,13 +2127,16 @@ export async function executeCodexWorkflowProvider(options) {
|
|
|
1871
2127
|
control.throwIfStopped('Codex execution');
|
|
1872
2128
|
throw error;
|
|
1873
2129
|
}
|
|
1874
|
-
control.throwIfStopped('evidence collection');
|
|
1875
2130
|
const sanitized = redactCodexEvalValue(execution, options.codexRuntime);
|
|
1876
2131
|
const usage = standardUsage(sanitized.usage);
|
|
1877
|
-
if (!usage || !standardUsage(sanitized.evidence.usage)) {
|
|
2132
|
+
if (!request.transport && (!usage || !standardUsage(sanitized.evidence.usage))) {
|
|
1878
2133
|
throw new Error('codex-workflow completed without required token usage evidence.');
|
|
1879
2134
|
}
|
|
1880
2135
|
await writeWorkflowArtifacts(paths.outputDir, sanitized);
|
|
2136
|
+
control.throwIfStopped('evidence collection');
|
|
2137
|
+
if (sanitized.evidence.completion.status !== 'completed') {
|
|
2138
|
+
throw new Error(`instruction-v2 workflow execution is ${sanitized.evidence.completion.status}; no grade was accepted.`);
|
|
2139
|
+
}
|
|
1881
2140
|
const beforeGrade = await artifactDigests(paths.outputDir);
|
|
1882
2141
|
let gradeValue;
|
|
1883
2142
|
try {
|