@lesliechan721/agent-skill-eval 0.1.0-beta.5 → 0.1.0-beta.51

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.
Files changed (120) hide show
  1. package/dist/bin.js +167 -39
  2. package/dist/bin.js.map +1 -1
  3. package/dist/codex-eval-app-server.d.ts +210 -0
  4. package/dist/codex-eval-app-server.js +1384 -0
  5. package/dist/codex-eval-app-server.js.map +1 -0
  6. package/dist/codex-eval-cases.d.ts +41 -0
  7. package/dist/codex-eval-cases.js +324 -0
  8. package/dist/codex-eval-cases.js.map +1 -0
  9. package/dist/codex-eval-isolation.d.ts +79 -0
  10. package/dist/codex-eval-isolation.js +420 -0
  11. package/dist/codex-eval-isolation.js.map +1 -0
  12. package/dist/codex-eval-process-tree.d.ts +42 -0
  13. package/dist/codex-eval-process-tree.js +860 -0
  14. package/dist/codex-eval-process-tree.js.map +1 -0
  15. package/dist/codex-eval-rollout.d.ts +8 -0
  16. package/dist/codex-eval-rollout.js +85 -0
  17. package/dist/codex-eval-rollout.js.map +1 -0
  18. package/dist/codex-eval-runtime-config.d.ts +43 -0
  19. package/dist/codex-eval-runtime-config.js +607 -0
  20. package/dist/codex-eval-runtime-config.js.map +1 -0
  21. package/dist/codex-trigger-eval.d.ts +85 -0
  22. package/dist/codex-trigger-eval.js +941 -0
  23. package/dist/codex-trigger-eval.js.map +1 -0
  24. package/dist/codex-workflow-evaluation.d.ts +91 -0
  25. package/dist/codex-workflow-evaluation.js +309 -0
  26. package/dist/codex-workflow-evaluation.js.map +1 -0
  27. package/dist/codex-workflow-fixtures.d.ts +37 -0
  28. package/dist/codex-workflow-fixtures.js +144 -0
  29. package/dist/codex-workflow-fixtures.js.map +1 -0
  30. package/dist/codex-workflow.d.ts +224 -0
  31. package/dist/codex-workflow.js +2140 -0
  32. package/dist/codex-workflow.js.map +1 -0
  33. package/dist/credentials.d.ts +5 -0
  34. package/dist/credentials.js +30 -0
  35. package/dist/credentials.js.map +1 -0
  36. package/dist/custom-process.d.ts +60 -0
  37. package/dist/custom-process.js +424 -0
  38. package/dist/custom-process.js.map +1 -0
  39. package/dist/eval-audit.d.ts +38 -0
  40. package/dist/eval-audit.js +71 -0
  41. package/dist/eval-audit.js.map +1 -0
  42. package/dist/eval-file.d.ts +9 -0
  43. package/dist/eval-file.js +15 -0
  44. package/dist/eval-file.js.map +1 -0
  45. package/dist/eval-process-management.d.ts +22 -0
  46. package/dist/eval-process-management.js +90 -0
  47. package/dist/eval-process-management.js.map +1 -0
  48. package/dist/eval-result.d.ts +79 -0
  49. package/dist/eval-result.js +144 -0
  50. package/dist/eval-result.js.map +1 -0
  51. package/dist/eval-run.d.ts +27 -0
  52. package/dist/eval-run.js +566 -0
  53. package/dist/eval-run.js.map +1 -0
  54. package/dist/eval-spec.d.ts +57 -0
  55. package/dist/eval-spec.js +120 -0
  56. package/dist/eval-spec.js.map +1 -0
  57. package/dist/eval-suite-binding.d.ts +4 -0
  58. package/dist/eval-suite-binding.js +19 -0
  59. package/dist/eval-suite-binding.js.map +1 -0
  60. package/dist/index.d.ts +44 -10
  61. package/dist/index.js +22 -6
  62. package/dist/index.js.map +1 -1
  63. package/dist/instruction-v2.d.ts +53 -0
  64. package/dist/instruction-v2.js +169 -0
  65. package/dist/instruction-v2.js.map +1 -0
  66. package/dist/json-schema.js +84 -5
  67. package/dist/json-schema.js.map +1 -1
  68. package/dist/output-path.js +2 -2
  69. package/dist/process-tree.d.ts +53 -1
  70. package/dist/process-tree.js +931 -160
  71. package/dist/process-tree.js.map +1 -1
  72. package/dist/process.d.ts +2 -2
  73. package/dist/process.js +99 -34
  74. package/dist/process.js.map +1 -1
  75. package/dist/provider-doctor.d.ts +46 -0
  76. package/dist/provider-doctor.js +120 -0
  77. package/dist/provider-doctor.js.map +1 -0
  78. package/dist/providers.d.ts +85 -0
  79. package/dist/providers.js +685 -0
  80. package/dist/providers.js.map +1 -0
  81. package/dist/run-paths.d.ts +8 -0
  82. package/dist/run-paths.js +71 -0
  83. package/dist/run-paths.js.map +1 -0
  84. package/dist/runtime-skill.d.ts +6 -0
  85. package/dist/runtime-skill.js +87 -3
  86. package/dist/runtime-skill.js.map +1 -1
  87. package/dist/schemas/digests.json +7 -3
  88. package/dist/schemas/eval-audit.schema.json +64 -0
  89. package/dist/schemas/eval-result.schema.json +232 -0
  90. package/dist/schemas/eval-spec.schema.json +109 -0
  91. package/dist/schemas/instruction-v2-receipt.schema.json +24 -0
  92. package/dist/schemas/instruction-v2-request.schema.json +51 -0
  93. package/dist/schemas/workflow-evaluation.schema.json +38 -0
  94. package/dist/schemas/workflow-fixture-action.schema.json +15 -0
  95. package/dist/schemas.d.ts +1 -3
  96. package/dist/schemas.js +22 -20
  97. package/dist/schemas.js.map +1 -1
  98. package/dist/selected-cases.d.ts +30 -0
  99. package/dist/selected-cases.js +386 -0
  100. package/dist/selected-cases.js.map +1 -0
  101. package/dist/types.d.ts +2 -34
  102. package/dist/windows-job.d.ts +26 -0
  103. package/dist/windows-job.js +97 -0
  104. package/dist/windows-job.js.map +1 -0
  105. package/package.json +22 -3
  106. package/dist/bundle.d.ts +0 -93
  107. package/dist/bundle.js +0 -216
  108. package/dist/bundle.js.map +0 -1
  109. package/dist/capabilities.d.ts +0 -2
  110. package/dist/capabilities.js +0 -38
  111. package/dist/capabilities.js.map +0 -1
  112. package/dist/plan.d.ts +0 -79
  113. package/dist/plan.js +0 -285
  114. package/dist/plan.js.map +0 -1
  115. package/dist/run.d.ts +0 -2
  116. package/dist/run.js +0 -592
  117. package/dist/run.js.map +0 -1
  118. package/dist/schemas/aw-skill-eval-execution-plan.schema.json +0 -220
  119. package/dist/schemas/eval-run-bundle.schema.json +0 -408
  120. package/dist/schemas/eval-runtime-capabilities.schema.json +0 -85
@@ -0,0 +1,2140 @@
1
+ import { createHash } from 'node:crypto';
2
+ import path from 'node:path';
3
+ import process from 'node:process';
4
+ import fs from 'fs-extra';
5
+ import { CodexEvalAttemptControl, EvalAppServerSession, extractAppServerThreadId, assertHostReadContainmentAvailable, hostReadContainmentProfile, } from './codex-eval-app-server.js';
6
+ import { applyCodexEvalProviderEnvironment, createCodexEvalTemporaryRoot, materializeCodexEvalRuntimeHome, redactCodexEvalValue, } from './codex-eval-runtime-config.js';
7
+ import { applyCodexEvalGitIsolation } from './codex-eval-isolation.js';
8
+ import { readCodexEvalRollouts, selectedCodexSkillInstruction } from './codex-eval-rollout.js';
9
+ import { parseInstructionV2ReasoningEffort, parseInstructionV2Receipt, parseInstructionV2Transport, } from './instruction-v2.js';
10
+ import { runProviderProcess } from './process.js';
11
+ import { assertProviderRunPaths } from './run-paths.js';
12
+ import { parseEvalProviderGrade, } from './custom-process.js';
13
+ import { createCodexGrader, parseConversationGraderOutput } from './providers.js';
14
+ import { parseWorkflowEvaluation, normalizeWorkflowChecks, WorkflowEvaluation, } from './codex-workflow-evaluation.js';
15
+ import { runWorkflowFixtureAction, validateWorkflowFixtureActions } from './codex-workflow-fixtures.js';
16
+ const MAX_ADAPTER_OUTPUT_BYTES = 4 * 1024 * 1024;
17
+ const MAX_REQUEST_TEXT_BYTES = 200_000;
18
+ const MAX_ROLE_COUNT = 32;
19
+ const MAX_ROLE_DESCRIPTION_BYTES = 20_000;
20
+ const MAX_V1_EXEC_INPUT_BYTES = MAX_REQUEST_TEXT_BYTES + 16_384;
21
+ const V1_CODE_MODE_SPAWN_MARKER = 'multi_agent_v1__spawn_agent';
22
+ export const CODEX_WORKFLOW_ARTIFACTS = [
23
+ { path: 'workflow-evidence.json', role: 'evidence' },
24
+ { path: 'workflow-trace.json', role: 'trace' },
25
+ { path: 'model-output.txt', role: 'output' },
26
+ ];
27
+ function isRecord(value) {
28
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
29
+ }
30
+ function exactKeys(value, required, optional, label) {
31
+ for (const key of required) {
32
+ if (!Object.hasOwn(value, key))
33
+ throw new Error(`${label} is missing ${key}.`);
34
+ }
35
+ const allowed = new Set([...required, ...optional]);
36
+ const unknown = Object.keys(value).find((key) => !allowed.has(key));
37
+ if (unknown)
38
+ throw new Error(`${label} has unknown field ${unknown}.`);
39
+ }
40
+ function boundedString(value, label, maxBytes) {
41
+ if (typeof value !== 'string' || value.trim() === '') {
42
+ throw new Error(`${label} must be a non-empty string.`);
43
+ }
44
+ if (Buffer.byteLength(value) > maxBytes)
45
+ throw new Error(`${label} is too large.`);
46
+ return value;
47
+ }
48
+ function relativeWorkspacePath(value, label) {
49
+ const result = boundedString(value, label, 4_096);
50
+ if (result.includes('\\')
51
+ || result.includes('\0')
52
+ || path.posix.isAbsolute(result)
53
+ || path.win32.isAbsolute(result)
54
+ || path.posix.normalize(result) !== result
55
+ || result.split('/').some((segment) => segment === '' || segment === '.' || segment === '..'))
56
+ throw new Error(`${label} must be a normalized workspace-relative POSIX path.`);
57
+ return result;
58
+ }
59
+ async function assertWorkspaceRoleFile(workspace, relative) {
60
+ let current = workspace;
61
+ for (const segment of relative.split('/')) {
62
+ current = path.join(current, segment);
63
+ const info = await fs.lstat(current).catch(() => null);
64
+ if (!info)
65
+ throw new Error(`workflow role configFile does not exist: ${relative}`);
66
+ if (info.isSymbolicLink()) {
67
+ throw new Error(`workflow role configFile must not contain symbolic links: ${relative}`);
68
+ }
69
+ }
70
+ if (!(await fs.lstat(current)).isFile()) {
71
+ throw new Error(`workflow role configFile must be a regular file: ${relative}`);
72
+ }
73
+ }
74
+ export async function parseCodexWorkflowRequest(value, allowedModels, workspace, fixtures) {
75
+ if (!isRecord(value))
76
+ throw new Error('workflow prepare stdout must be one JSON object.');
77
+ exactKeys(value, ['version', 'prompt', 'sandbox', 'model', 'developerInstructions', 'roles', 'multiAgent'], ['evaluation', 'transport', 'reasoningEffort'], 'workflow prepare stdout');
78
+ if (value.version !== 1)
79
+ throw new Error('workflow prepare stdout version must be 1.');
80
+ if (value.sandbox !== 'read-only' && value.sandbox !== 'workspace-write') {
81
+ throw new Error('workflow request sandbox must be read-only or workspace-write.');
82
+ }
83
+ if (value.model !== null && (typeof value.model !== 'string' || !allowedModels.includes(value.model))) {
84
+ throw new Error('workflow request model must be null or a member of provider.allowedModels.');
85
+ }
86
+ if (!Array.isArray(value.roles) || value.roles.length > MAX_ROLE_COUNT) {
87
+ throw new Error(`workflow request roles must be an array with at most ${MAX_ROLE_COUNT} entries.`);
88
+ }
89
+ const roles = await Promise.all(value.roles.map(async (entry, index) => {
90
+ if (!isRecord(entry))
91
+ throw new Error(`workflow request roles[${index}] must be an object.`);
92
+ exactKeys(entry, ['name', 'description', 'configFile'], [], `workflow request roles[${index}]`);
93
+ const name = boundedString(entry.name, `workflow request roles[${index}].name`, 256);
94
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]*$/u.test(name)) {
95
+ throw new Error(`workflow request roles[${index}].name must be a safe role name.`);
96
+ }
97
+ const configFile = relativeWorkspacePath(entry.configFile, `workflow request roles[${index}].configFile`);
98
+ await assertWorkspaceRoleFile(workspace, configFile);
99
+ return {
100
+ name,
101
+ description: boundedString(entry.description, `workflow request roles[${index}].description`, MAX_ROLE_DESCRIPTION_BYTES),
102
+ configFile,
103
+ };
104
+ }));
105
+ if (new Set(roles.map((role) => role.name)).size !== roles.length) {
106
+ throw new Error('workflow request roles must contain unique names.');
107
+ }
108
+ if (!isRecord(value.multiAgent))
109
+ throw new Error('workflow request multiAgent must be an object.');
110
+ exactKeys(value.multiAgent, ['protocol', 'maxDepth', 'hideSpawnAgentMetadata', 'exposeSpawnAgentModelOverrides'], [], 'workflow request multiAgent');
111
+ const multiAgent = value.multiAgent;
112
+ if (multiAgent.protocol !== 'v1' && multiAgent.protocol !== 'v2') {
113
+ throw new Error('workflow request multiAgent.protocol must be v1 or v2.');
114
+ }
115
+ if (!Number.isSafeInteger(multiAgent.maxDepth) || multiAgent.maxDepth < 1
116
+ || multiAgent.maxDepth > 4) {
117
+ throw new Error('workflow request multiAgent.maxDepth must be an integer from 1 to 4.');
118
+ }
119
+ if (typeof multiAgent.hideSpawnAgentMetadata !== 'boolean'
120
+ || typeof multiAgent.exposeSpawnAgentModelOverrides !== 'boolean')
121
+ throw new Error('workflow request multiAgent metadata fields must be boolean.');
122
+ if (multiAgent.protocol === 'v1'
123
+ && (multiAgent.hideSpawnAgentMetadata || multiAgent.exposeSpawnAgentModelOverrides))
124
+ throw new Error('workflow request V1 metadata fields must be false.');
125
+ const request = {
126
+ version: 1,
127
+ prompt: boundedString(value.prompt, 'workflow request prompt', MAX_REQUEST_TEXT_BYTES),
128
+ sandbox: value.sandbox,
129
+ model: value.model,
130
+ ...(value.reasoningEffort === undefined ? {} : { reasoningEffort: parseInstructionV2ReasoningEffort(value.reasoningEffort) }),
131
+ developerInstructions: boundedString(value.developerInstructions, 'workflow request developerInstructions', MAX_REQUEST_TEXT_BYTES),
132
+ roles,
133
+ ...(value.evaluation === undefined ? {} : { evaluation: parseWorkflowEvaluation(value.evaluation) }),
134
+ ...(value.transport === undefined ? {} : { transport: await parseInstructionV2Transport(value.transport, workspace) }),
135
+ multiAgent: {
136
+ protocol: multiAgent.protocol,
137
+ maxDepth: multiAgent.maxDepth,
138
+ hideSpawnAgentMetadata: multiAgent.hideSpawnAgentMetadata,
139
+ exposeSpawnAgentModelOverrides: multiAgent.exposeSpawnAgentModelOverrides,
140
+ },
141
+ };
142
+ if (request.transport) {
143
+ const external = request.transport.request;
144
+ if (external.instructions.text !== request.developerInstructions || external.task !== request.prompt
145
+ || external.run.access !== request.sandbox || (external.run.model ?? null) !== request.model
146
+ || external.run.reasoningEffort !== request.reasoningEffort) {
147
+ throw new Error('instruction-v2 request must match workflow instructions, task, access, model, and reasoningEffort.');
148
+ }
149
+ if (request.evaluation?.interactions.length || request.evaluation?.setupPrompt !== undefined) {
150
+ throw new Error('instruction-v2 transport does not support evaluation interactions or setupPrompt.');
151
+ }
152
+ }
153
+ const actions = [
154
+ ...(request.evaluation?.setupActions ?? []),
155
+ ...(request.evaluation?.finalActions ?? []),
156
+ ...(request.evaluation?.interactions.flatMap((interaction) => interaction.action ? [interaction.action] : []) ?? []),
157
+ ];
158
+ if (actions.length) {
159
+ if (!fixtures)
160
+ throw new Error('workflow fixture actions require the Eval-owned fixture root.');
161
+ await validateWorkflowFixtureActions(actions, fixtures);
162
+ }
163
+ return request;
164
+ }
165
+ function parseJsonObject(stdout, label) {
166
+ let value;
167
+ try {
168
+ value = JSON.parse(stdout);
169
+ }
170
+ catch (error) {
171
+ throw new Error(`${label} must be one JSON object: ${error.message}`);
172
+ }
173
+ if (!isRecord(value))
174
+ throw new Error(`${label} must be one JSON object.`);
175
+ return value;
176
+ }
177
+ export function codexWorkflowAdapterEnvironment(sourceEnv, evalRoot, phase) {
178
+ const env = {};
179
+ for (const name of ['PATH', 'LANG', 'LC_ALL']) {
180
+ if (sourceEnv[name] !== undefined)
181
+ env[name] = sourceEnv[name];
182
+ }
183
+ const home = path.join(evalRoot, `.workflow-adapter-${phase}-home`);
184
+ const temporary = path.join(evalRoot, `.workflow-adapter-${phase}-tmp`);
185
+ return applyCodexEvalGitIsolation({
186
+ ...env,
187
+ HOME: home,
188
+ USERPROFILE: home,
189
+ XDG_CACHE_HOME: path.join(home, '.cache'),
190
+ XDG_CONFIG_HOME: path.join(home, '.config'),
191
+ XDG_STATE_HOME: path.join(home, '.state'),
192
+ TMPDIR: temporary,
193
+ TEMP: temporary,
194
+ TMP: temporary,
195
+ NO_COLOR: '1',
196
+ });
197
+ }
198
+ async function runAdapter(options) {
199
+ const env = codexWorkflowAdapterEnvironment(options.sourceEnv, options.evalRoot, options.input.phase);
200
+ await Promise.all([fs.mkdir(env.HOME, { mode: 0o700 }), fs.mkdir(env.TMPDIR, { mode: 0o700 })]);
201
+ try {
202
+ const result = await runProviderProcess({
203
+ executable: options.command[0],
204
+ args: [...options.command.slice(1)],
205
+ cwd: options.evalRoot,
206
+ env,
207
+ stdin: JSON.stringify(options.input),
208
+ timeoutMs: options.timeoutMs,
209
+ maxBufferBytes: MAX_ADAPTER_OUTPUT_BYTES,
210
+ ...(options.signal ? { signal: options.signal } : {}),
211
+ }, {}, {
212
+ containment: options.processManagement.mode === 'unmanaged-confirmed'
213
+ ? 'unmanaged-confirmed'
214
+ : 'verified-observed',
215
+ ...(options.processManagement.mode === 'unmanaged-confirmed'
216
+ ? {}
217
+ : { processTree: options.processTree }),
218
+ });
219
+ if (result.timedOut)
220
+ throw new Error(`workflow ${options.input.phase} timed out.`);
221
+ if (result.cancelled)
222
+ throw new Error(`workflow ${options.input.phase} was aborted.`);
223
+ if (result.maxBufferExceeded)
224
+ throw new Error(`workflow ${options.input.phase} output is too large.`);
225
+ if (options.processManagement.mode !== 'unmanaged-confirmed'
226
+ && !result.gradingEvidenceAccepted) {
227
+ throw new Error(`workflow ${options.input.phase} process containment was not verified.`);
228
+ }
229
+ if (result.signal || result.exitCode !== 0) {
230
+ const diagnostic = result.stderr.trim();
231
+ throw new Error(`workflow ${options.input.phase} failed (exit=${String(result.exitCode)}, `
232
+ + `signal=${String(result.signal)})${diagnostic ? `: ${diagnostic}` : '.'}`);
233
+ }
234
+ return parseJsonObject(result.stdout, `workflow ${options.input.phase} stdout`);
235
+ }
236
+ finally {
237
+ const cleanup = await Promise.allSettled([fs.remove(env.HOME), fs.remove(env.TMPDIR)]);
238
+ if (cleanup.some((result) => result.status === 'rejected')) {
239
+ throw new Error(`workflow ${options.input.phase} could not remove isolated adapter state.`);
240
+ }
241
+ }
242
+ }
243
+ async function snapshotWorkspace(root) {
244
+ const snapshot = new Map();
245
+ async function visit(current) {
246
+ const entries = await fs.readdir(current, { withFileTypes: true });
247
+ for (const entry of entries) {
248
+ const absolute = path.join(current, entry.name);
249
+ const relative = path.relative(root, absolute).split(path.sep).join('/');
250
+ if (entry.isDirectory()) {
251
+ snapshot.set(`${relative}/`, { kind: 'directory', digest: 'directory' });
252
+ await visit(absolute);
253
+ }
254
+ else if (entry.isFile()) {
255
+ snapshot.set(relative, {
256
+ kind: 'file',
257
+ digest: createHash('sha256').update(await fs.readFile(absolute)).digest('hex'),
258
+ });
259
+ }
260
+ else if (entry.isSymbolicLink()) {
261
+ snapshot.set(relative, { kind: 'symlink', digest: await fs.readlink(absolute) });
262
+ }
263
+ else
264
+ snapshot.set(relative, { kind: 'other', digest: 'other' });
265
+ }
266
+ }
267
+ await visit(root);
268
+ return snapshot;
269
+ }
270
+ function workspaceDiff(before, after) {
271
+ const added = [];
272
+ const removed = [];
273
+ const modified = [];
274
+ const paths = [...new Set([...before.keys(), ...after.keys()])].sort();
275
+ for (const entry of paths) {
276
+ if (!before.has(entry))
277
+ added.push(entry);
278
+ else if (!after.has(entry))
279
+ removed.push(entry);
280
+ else if (JSON.stringify(before.get(entry)) !== JSON.stringify(after.get(entry)))
281
+ modified.push(entry);
282
+ }
283
+ return { added, removed, modified };
284
+ }
285
+ function buildEngineEnvironment(sourceEnv, root, codexHome, runtime) {
286
+ const env = {};
287
+ for (const name of [
288
+ 'PATH', 'LANG', 'LC_ALL', 'HTTP_PROXY', 'HTTPS_PROXY', 'ALL_PROXY', 'NO_PROXY',
289
+ 'SSL_CERT_FILE', 'SSL_CERT_DIR', 'NODE_EXTRA_CA_CERTS',
290
+ ]) {
291
+ if (sourceEnv[name] !== undefined)
292
+ env[name] = sourceEnv[name];
293
+ }
294
+ const home = path.join(root, 'home');
295
+ const temporary = path.join(root, 'tmp');
296
+ return applyCodexEvalProviderEnvironment(applyCodexEvalGitIsolation({
297
+ ...env,
298
+ HOME: home,
299
+ USERPROFILE: home,
300
+ CODEX_HOME: codexHome,
301
+ XDG_CACHE_HOME: path.join(root, 'xdg-cache'),
302
+ XDG_CONFIG_HOME: path.join(root, 'xdg-config'),
303
+ XDG_STATE_HOME: path.join(root, 'xdg-state'),
304
+ TMPDIR: temporary,
305
+ TEMP: temporary,
306
+ TMP: temporary,
307
+ NO_COLOR: '1',
308
+ }), runtime);
309
+ }
310
+ function configValue(value) {
311
+ return JSON.stringify(value);
312
+ }
313
+ function workflowConfig(request, workspace) {
314
+ const v2 = request.multiAgent.protocol === 'v2';
315
+ return [
316
+ 'features.multi_agent=true',
317
+ 'features.executed_tool_call_metadata=true',
318
+ `features.multi_agent_v2.enabled=${String(v2)}`,
319
+ `features.multi_agent_v2.hide_spawn_agent_metadata=${String(request.multiAgent.hideSpawnAgentMetadata)}`,
320
+ `features.multi_agent_v2.expose_spawn_agent_model_overrides=${String(request.multiAgent.exposeSpawnAgentModelOverrides)}`,
321
+ `agents.max_depth=${request.multiAgent.maxDepth}`,
322
+ `developer_instructions=${configValue(request.developerInstructions)}`,
323
+ ...(request.reasoningEffort ? [`model_reasoning_effort=${configValue(request.reasoningEffort)}`] : []),
324
+ ...request.roles.flatMap((role) => [
325
+ `agents.${role.name}.description=${configValue(role.description)}`,
326
+ `agents.${role.name}.config_file=${configValue(path.join(workspace, role.configFile))}`,
327
+ ]),
328
+ ];
329
+ }
330
+ export function codexWorkflowFrameThreadId(frame) {
331
+ const params = isRecord(frame.params) ? frame.params : {};
332
+ if (typeof params.threadId === 'string')
333
+ return params.threadId;
334
+ for (const key of ['thread', 'turn', 'item']) {
335
+ const nested = isRecord(params[key]) ? params[key] : {};
336
+ if (typeof nested.threadId === 'string')
337
+ return nested.threadId;
338
+ if (key === 'thread' && typeof nested.id === 'string')
339
+ return nested.id;
340
+ }
341
+ return null;
342
+ }
343
+ export function codexWorkflowFrameIsForRoot(frame, rootThreadId) {
344
+ return codexWorkflowFrameThreadId(frame) === rootThreadId;
345
+ }
346
+ export function codexWorkflowMissingFinalMessageDiagnostic(options) {
347
+ let rootFinalAgentMessageCount = 0;
348
+ let otherThreadFinalAgentMessageCount = 0;
349
+ for (const value of options.frames) {
350
+ if (!isRecord(value) || value.method !== 'item/completed')
351
+ continue;
352
+ const params = isRecord(value.params) ? value.params : {};
353
+ const item = isRecord(params.item) ? params.item : null;
354
+ if (!item || item.type !== 'agentMessage' || item.phase !== 'final_answer')
355
+ continue;
356
+ const threadId = codexWorkflowFrameThreadId(value);
357
+ if (threadId === options.rootThreadId)
358
+ rootFinalAgentMessageCount += 1;
359
+ else if (threadId !== null)
360
+ otherThreadFinalAgentMessageCount += 1;
361
+ }
362
+ return {
363
+ rootTurnStatus: options.turnStatus,
364
+ rootFinalAgentMessageCount,
365
+ otherThreadFinalAgentMessageCount,
366
+ usagePresent: options.usage !== null && options.usage !== undefined,
367
+ frameCount: options.frames.length,
368
+ };
369
+ }
370
+ function hash(value) {
371
+ return `sha256:${createHash('sha256').update(value).digest('hex')}`;
372
+ }
373
+ function parseSerialized(value) {
374
+ if (typeof value !== 'string')
375
+ return value;
376
+ try {
377
+ return JSON.parse(value);
378
+ }
379
+ catch {
380
+ return value;
381
+ }
382
+ }
383
+ function skipWhitespace(source, start) {
384
+ let index = start;
385
+ while (/\s/u.test(source[index] ?? ''))
386
+ index += 1;
387
+ return index;
388
+ }
389
+ function parseJsonStringToken(source, start) {
390
+ if (source[start] !== '"')
391
+ throw new Error('Codex V1 code-mode spawn object has an invalid string.');
392
+ let escaped = false;
393
+ for (let index = start + 1; index < source.length; index += 1) {
394
+ const character = source[index];
395
+ if (escaped) {
396
+ escaped = false;
397
+ continue;
398
+ }
399
+ if (character === '\\') {
400
+ escaped = true;
401
+ continue;
402
+ }
403
+ if (character !== '"')
404
+ continue;
405
+ let value;
406
+ try {
407
+ value = JSON.parse(source.slice(start, index + 1));
408
+ }
409
+ catch {
410
+ throw new Error('Codex V1 code-mode spawn object has an invalid string.');
411
+ }
412
+ if (typeof value !== 'string') {
413
+ throw new Error('Codex V1 code-mode spawn object has an invalid string.');
414
+ }
415
+ return { value, end: index + 1 };
416
+ }
417
+ throw new Error('Codex V1 code-mode spawn object has an unterminated string.');
418
+ }
419
+ function parseTemplateLiteralToken(source, start) {
420
+ if (source[start] !== '`') {
421
+ throw new Error('Codex V1 code-mode spawn object has an invalid template literal.');
422
+ }
423
+ let value = '';
424
+ for (let index = start + 1; index < source.length; index += 1) {
425
+ const character = source[index];
426
+ if (character === '`')
427
+ return { value, end: index + 1 };
428
+ if (character === '$' && source[index + 1] === '{') {
429
+ throw new Error('Codex V1 code-mode spawn template literal interpolation is unsupported.');
430
+ }
431
+ if (character === '\r') {
432
+ if (source[index + 1] === '\n')
433
+ index += 1;
434
+ value += '\n';
435
+ continue;
436
+ }
437
+ if (character !== '\\') {
438
+ value += character;
439
+ continue;
440
+ }
441
+ const escaped = source[index + 1];
442
+ if (escaped === undefined) {
443
+ throw new Error('Codex V1 code-mode spawn object has an unterminated template literal.');
444
+ }
445
+ index += 1;
446
+ if (escaped === '\n')
447
+ continue;
448
+ if (escaped === '\r') {
449
+ if (source[index + 1] === '\n')
450
+ index += 1;
451
+ continue;
452
+ }
453
+ if (escaped === '\u2028' || escaped === '\u2029')
454
+ continue;
455
+ const commonEscapes = {
456
+ b: '\b', f: '\f', n: '\n', r: '\r', t: '\t', v: '\v',
457
+ '0': '\0', '\\': '\\', '`': '`',
458
+ };
459
+ if (Object.hasOwn(commonEscapes, escaped)) {
460
+ if (escaped === '0' && /[0-9]/u.test(source[index + 1] ?? '')) {
461
+ throw new Error('Codex V1 code-mode spawn template literal has an invalid numeric escape.');
462
+ }
463
+ value += commonEscapes[escaped];
464
+ continue;
465
+ }
466
+ if (escaped === 'x') {
467
+ const digits = source.slice(index + 1, index + 3);
468
+ if (!/^[0-9A-Fa-f]{2}$/u.test(digits)) {
469
+ throw new Error('Codex V1 code-mode spawn template literal has an invalid hex escape.');
470
+ }
471
+ value += String.fromCharCode(Number.parseInt(digits, 16));
472
+ index += 2;
473
+ continue;
474
+ }
475
+ if (escaped === 'u') {
476
+ if (source[index + 1] === '{') {
477
+ const close = source.indexOf('}', index + 2);
478
+ const digits = close === -1 ? '' : source.slice(index + 2, close);
479
+ const codePoint = /^[0-9A-Fa-f]{1,6}$/u.test(digits)
480
+ ? Number.parseInt(digits, 16)
481
+ : Number.NaN;
482
+ if (!Number.isInteger(codePoint) || codePoint > 0x10FFFF) {
483
+ throw new Error('Codex V1 code-mode spawn template literal has an invalid Unicode escape.');
484
+ }
485
+ value += String.fromCodePoint(codePoint);
486
+ index = close;
487
+ continue;
488
+ }
489
+ const digits = source.slice(index + 1, index + 5);
490
+ if (!/^[0-9A-Fa-f]{4}$/u.test(digits)) {
491
+ throw new Error('Codex V1 code-mode spawn template literal has an invalid Unicode escape.');
492
+ }
493
+ value += String.fromCharCode(Number.parseInt(digits, 16));
494
+ index += 4;
495
+ continue;
496
+ }
497
+ if (/[1-9]/u.test(escaped)) {
498
+ throw new Error('Codex V1 code-mode spawn template literal has an invalid numeric escape.');
499
+ }
500
+ value += escaped;
501
+ }
502
+ throw new Error('Codex V1 code-mode spawn object has an unterminated template literal.');
503
+ }
504
+ function parseV1SpawnObject(source, start) {
505
+ let index = skipWhitespace(source, start);
506
+ if (source[index] !== '{')
507
+ throw new Error('Codex V1 code-mode spawn arguments must be an object.');
508
+ index += 1;
509
+ const value = {};
510
+ while (true) {
511
+ index = skipWhitespace(source, index);
512
+ if (source[index] === '}')
513
+ return { value, end: index + 1 };
514
+ let key;
515
+ if (source[index] === '"') {
516
+ const parsed = parseJsonStringToken(source, index);
517
+ key = parsed.value;
518
+ index = parsed.end;
519
+ }
520
+ else {
521
+ const match = source.slice(index).match(/^[A-Za-z_$][A-Za-z0-9_$]*/u);
522
+ if (!match)
523
+ throw new Error('Codex V1 code-mode spawn object has an invalid key.');
524
+ key = match[0];
525
+ index += key.length;
526
+ }
527
+ if (Object.hasOwn(value, key)) {
528
+ throw new Error('Codex V1 code-mode spawn object has a duplicate key.');
529
+ }
530
+ index = skipWhitespace(source, index);
531
+ if (source[index] !== ':')
532
+ throw new Error('Codex V1 code-mode spawn object is missing a colon.');
533
+ index = skipWhitespace(source, index + 1);
534
+ if (source[index] === '"') {
535
+ const parsed = parseJsonStringToken(source, index);
536
+ value[key] = parsed.value;
537
+ index = parsed.end;
538
+ }
539
+ else if (source[index] === '`') {
540
+ const parsed = parseTemplateLiteralToken(source, index);
541
+ value[key] = parsed.value;
542
+ index = parsed.end;
543
+ }
544
+ else if (source.startsWith('true', index)) {
545
+ value[key] = true;
546
+ index += 4;
547
+ }
548
+ else if (source.startsWith('false', index)) {
549
+ value[key] = false;
550
+ index += 5;
551
+ }
552
+ else {
553
+ throw new Error('Codex V1 code-mode spawn object has an unsupported value.');
554
+ }
555
+ index = skipWhitespace(source, index);
556
+ if (source[index] === '}')
557
+ return { value, end: index + 1 };
558
+ if (source[index] !== ',')
559
+ throw new Error('Codex V1 code-mode spawn object is missing a comma.');
560
+ index = skipWhitespace(source, index + 1);
561
+ if (source[index] === '}')
562
+ return { value, end: index + 1 };
563
+ }
564
+ }
565
+ function canonicalV1SpawnArguments(value) {
566
+ if (!isRecord(value))
567
+ throw new Error('Codex V1 spawn arguments must be an object.');
568
+ const allowed = new Set(['agent_type', 'message', 'fork_context', 'model', 'reasoning_effort']);
569
+ const unknown = Object.keys(value).find((key) => !allowed.has(key));
570
+ if (unknown)
571
+ throw new Error(`Codex V1 spawn arguments have unknown field ${unknown}.`);
572
+ const agentType = Object.hasOwn(value, 'agent_type')
573
+ ? boundedString(value.agent_type, 'Codex V1 spawn agent_type', 256)
574
+ : undefined;
575
+ const message = boundedString(value.message, 'Codex V1 spawn message', MAX_REQUEST_TEXT_BYTES);
576
+ if (typeof value.fork_context !== 'boolean') {
577
+ throw new Error('Codex V1 spawn fork_context evidence must be boolean.');
578
+ }
579
+ const model = value.model === undefined
580
+ ? undefined
581
+ : boundedString(value.model, 'Codex V1 spawn model', 256);
582
+ const reasoningEffort = value.reasoning_effort === undefined
583
+ ? undefined
584
+ : boundedString(value.reasoning_effort, 'Codex V1 spawn reasoning_effort', 256);
585
+ return {
586
+ ...(agentType === undefined ? {} : { agent_type: agentType }),
587
+ message,
588
+ ...(model === undefined ? {} : { model }),
589
+ ...(reasoningEffort === undefined ? {} : { reasoning_effort: reasoningEffort }),
590
+ fork_context: value.fork_context,
591
+ };
592
+ }
593
+ function v1PrefixWrapperFingerprint(source) {
594
+ const pragma = /^\s*\/\/\s*@exec\b/u.test(source);
595
+ const withoutPragma = source.replace(/^\s*\/\/\s*@exec\b[^\r\n]*(?:\r?\n|$)/u, '');
596
+ const codeFence = /^\s*```[^\r\n]*(?:\r?\n|$)/u.test(withoutPragma);
597
+ const structuralSource = withoutPragma.replace(/^\s*```[^\r\n]*(?:\r?\n|$)/u, '');
598
+ const declaration = structuralSource.match(/^\s*(const|let|var)\b/u)?.[1];
599
+ const declarationPrefix = /^\s*(?:const|let|var)\s+/u;
600
+ const bindingSource = structuralSource.replace(declarationPrefix, '');
601
+ const bindingForm = /^[A-Za-z_$][A-Za-z0-9_$]*\s*=/u.test(bindingSource)
602
+ ? 'identifier'
603
+ : /^\{[^}\r\n]*\}\s*=/u.test(bindingSource)
604
+ ? 'object-destructure'
605
+ : /^\[[^\]\r\n]*\]\s*=/u.test(bindingSource)
606
+ ? 'array-destructure'
607
+ : 'other';
608
+ const assignmentAwait = /^\s*(?:const|let|var)\s+[^\r\n]*?=\s*await\b/u;
609
+ const directTextAwait = /^\s*text\s*\(\s*await\s+tools\.multi_agent_v1__spawn_agent\s*\(/u;
610
+ const callee = structuralSource.match(/^\s*(?:(?:const|let|var)\s+[^\r\n]*?=\s*|text\s*\(\s*)(?:await\s+)?([A-Za-z_$][A-Za-z0-9_$]*)\.([A-Za-z_$][A-Za-z0-9_$]*)\s*\(/u);
611
+ const receiver = callee?.[1] === 'tools' ? 'tools' : 'other';
612
+ const calleeName = callee?.[2];
613
+ const calleeFamily = calleeName === 'multi_agent_v1__spawn_agent'
614
+ ? 'exact-v1-snake'
615
+ : calleeName?.startsWith('multi_agent_v1__') === true
616
+ ? 'v1-namespaced-other'
617
+ : calleeName !== undefined && /(?:spawn_agent|spawnAgent)$/u.test(calleeName)
618
+ ? 'spawn-family-other'
619
+ : 'other';
620
+ return {
621
+ phase: 'prefix',
622
+ pragma: pragma ? 'yes' : 'no',
623
+ codeFence: codeFence ? 'yes' : 'no',
624
+ declaration: declaration === 'const' || declaration === 'let' || declaration === 'var'
625
+ ? declaration
626
+ : 'none',
627
+ bindingForm,
628
+ await: assignmentAwait.test(structuralSource) || directTextAwait.test(structuralSource)
629
+ ? 'yes'
630
+ : 'no',
631
+ receiver,
632
+ calleeFamily,
633
+ exactSpawnCallee: receiver === 'tools' && calleeFamily === 'exact-v1-snake' ? 'yes' : 'no',
634
+ outputForm: directTextAwait.test(structuralSource) ? 'direct-text-await' : 'other',
635
+ };
636
+ }
637
+ function v1SuffixWrapperFingerprint(source, variable) {
638
+ const escapedVariable = variable.replace(/[.*+?^${}()|[\]\\]/gu, '\\$&');
639
+ const textOutput = new RegExp(`^\\s*\\)\\s*;?\\s*text\\s*\\(\\s*${escapedVariable}\\s*\\)\\s*;?`, 'u');
640
+ const returnTextOutput = new RegExp(`^\\s*\\)\\s*;?\\s*return\\s+text\\s*\\(\\s*${escapedVariable}\\s*\\)\\s*;?`, 'u');
641
+ const output = source.match(textOutput) ?? source.match(returnTextOutput);
642
+ return {
643
+ phase: 'suffix',
644
+ spawnCallSemicolon: /^\s*\)\s*;/u.test(source) ? 'yes' : 'no',
645
+ outputForm: textOutput.test(source)
646
+ ? 'text'
647
+ : returnTextOutput.test(source) ? 'return-text' : 'other',
648
+ extraStatements: output !== null && source.slice(output[0].length).trim() !== '' ? 'yes' : 'no',
649
+ };
650
+ }
651
+ function invalidV1Wrapper(fingerprint) {
652
+ return new Error(`Codex V1 code-mode spawn ${fingerprint.phase} is invalid. `
653
+ + `fingerprint=${JSON.stringify(fingerprint)}`);
654
+ }
655
+ function parseV1CodeModeSpawnInput(value) {
656
+ if (typeof value !== 'string' || Buffer.byteLength(value) > MAX_V1_EXEC_INPUT_BYTES) {
657
+ throw new Error('Codex V1 code-mode spawn input must be a bounded string.');
658
+ }
659
+ const prefix = value.match(/^\s*const\s+([A-Za-z_$][A-Za-z0-9_$]*)\s*=\s*(?:await\s+)?tools\.multi_agent_v1__spawn_agent\s*\(\s*/u);
660
+ if (!prefix)
661
+ throw invalidV1Wrapper(v1PrefixWrapperFingerprint(value));
662
+ const parsed = parseV1SpawnObject(value, prefix[0].length);
663
+ const variable = prefix[1];
664
+ const suffix = value.slice(parsed.end);
665
+ const suffixPattern = new RegExp(`^\\s*\\)\\s*;\\s*text\\s*\\(\\s*${variable}\\s*\\)\\s*;?\\s*$`, 'u');
666
+ if (!suffixPattern.test(suffix)) {
667
+ throw invalidV1Wrapper(v1SuffixWrapperFingerprint(suffix, variable));
668
+ }
669
+ return canonicalV1SpawnArguments(parsed.value);
670
+ }
671
+ function isV1CodeModeExecCall(call) {
672
+ return call?.tool === 'exec'
673
+ && isRecord(call.arguments)
674
+ && typeof call.arguments.input === 'string'
675
+ && call.arguments.input.includes(V1_CODE_MODE_SPAWN_MARKER);
676
+ }
677
+ function responseItem(event) {
678
+ return event.type === 'response_item' && isRecord(event.payload) ? event.payload : null;
679
+ }
680
+ function contentText(payload) {
681
+ if (!Array.isArray(payload.content))
682
+ return '';
683
+ return payload.content.filter(isRecord)
684
+ .filter((entry) => entry.type === 'input_text' && typeof entry.text === 'string')
685
+ .map((entry) => entry.text)
686
+ .join('\n');
687
+ }
688
+ function selectedSkillName(event) {
689
+ const selected = selectedCodexSkillInstruction(event);
690
+ return selected?.path.endsWith(`/.agents/skills/${selected.name}/SKILL.md`)
691
+ ? selected.name : null;
692
+ }
693
+ function encryptedContent(payload) {
694
+ if (!Array.isArray(payload.content))
695
+ return null;
696
+ const content = payload.content.filter(isRecord);
697
+ const envelope = content.find((item) => item.type === 'input_text' && typeof item.text === 'string');
698
+ if (!String(envelope?.text ?? '').includes('Message Type: NEW_TASK'))
699
+ return null;
700
+ const entry = content.find((item) => item.type === 'encrypted_content' && typeof item.encrypted_content === 'string');
701
+ return entry ? entry.encrypted_content : null;
702
+ }
703
+ function agentMessagePayload(text) {
704
+ const marker = text.match(/^Payload:\s*$/mu);
705
+ if (!marker || !Number.isInteger(marker.index))
706
+ return null;
707
+ return text
708
+ .slice(marker.index + marker[0].length)
709
+ .replace(/^\r?\n/u, '')
710
+ .trim() || null;
711
+ }
712
+ function sanitizeTraceValue(value, runtime, isolatedPaths, redactSpawnMessages) {
713
+ if (typeof value === 'string') {
714
+ const redactedPath = isolatedPaths
715
+ .filter((entry) => entry.path !== '')
716
+ .sort((left, right) => right.path.length - left.path.length)
717
+ .reduce((text, entry) => text.replaceAll(entry.path, entry.marker), value);
718
+ return redactCodexEvalValue(redactedPath, runtime);
719
+ }
720
+ if (Array.isArray(value)) {
721
+ return value.map((entry) => sanitizeTraceValue(entry, runtime, isolatedPaths, redactSpawnMessages));
722
+ }
723
+ if (!isRecord(value))
724
+ return value;
725
+ if (value.type === 'custom_tool_call'
726
+ && value.name === 'exec'
727
+ && typeof value.input === 'string'
728
+ && value.input.includes(V1_CODE_MODE_SPAWN_MARKER)) {
729
+ let input = '[redacted-v1-code-wrapper]';
730
+ try {
731
+ input = {
732
+ namespace: 'multi_agent_v1',
733
+ tool: 'spawn_agent',
734
+ arguments: parseV1CodeModeSpawnInput(value.input),
735
+ };
736
+ }
737
+ catch {
738
+ // A linked terminal event is validated separately. An unlinked malformed wrapper stays private.
739
+ }
740
+ return sanitizeTraceValue({ ...value, input }, runtime, isolatedPaths, redactSpawnMessages);
741
+ }
742
+ if (value.type === 'message' && value.role === 'developer') {
743
+ const developerText = contentText(value);
744
+ return {
745
+ type: 'message',
746
+ role: 'developer',
747
+ developerContext: {
748
+ present: true,
749
+ sha256: hash(developerText),
750
+ bytes: Buffer.byteLength(developerText),
751
+ },
752
+ };
753
+ }
754
+ if (typeof value.name === 'string'
755
+ && normalizeToolName(value.name).tool === 'spawn_agent'
756
+ && redactSpawnMessages) {
757
+ const argumentsValue = parseSerialized(value.arguments ?? value.input);
758
+ if (isRecord(argumentsValue) && typeof argumentsValue.message === 'string') {
759
+ const argumentKey = value.arguments === undefined ? 'input' : 'arguments';
760
+ return sanitizeTraceValue({
761
+ ...value,
762
+ [argumentKey]: {
763
+ ...argumentsValue,
764
+ message: { kind: 'opaque', sha256: hash(argumentsValue.message) },
765
+ },
766
+ }, runtime, isolatedPaths, redactSpawnMessages);
767
+ }
768
+ }
769
+ const result = {};
770
+ for (const [key, entry] of Object.entries(value)) {
771
+ if (key === 'agentPath' || key === 'agent_path' || key === 'sender_agent_path')
772
+ continue;
773
+ if (key === 'encrypted_content' && typeof entry === 'string') {
774
+ result[key] = { redacted: true, sha256: hash(entry) };
775
+ continue;
776
+ }
777
+ if (/^(?:authorization|credentials?|api[_-]?key|access[_-]?token|refresh[_-]?token|secret|ciphertext)$/iu.test(key)) {
778
+ result[key] = '[redacted]';
779
+ continue;
780
+ }
781
+ result[key] = sanitizeTraceValue(entry, runtime, isolatedPaths, redactSpawnMessages);
782
+ }
783
+ return result;
784
+ }
785
+ function rolloutIdentity(events) {
786
+ for (const event of events) {
787
+ if (event.type !== 'session_meta' || !isRecord(event.payload))
788
+ continue;
789
+ const payload = event.payload;
790
+ if (typeof payload.id !== 'string' || payload.id === '') {
791
+ throw new Error('Codex rollout session_meta is missing its thread id.');
792
+ }
793
+ const source = isRecord(payload.source) ? payload.source : {};
794
+ const subagent = isRecord(source.subagent) ? source.subagent : {};
795
+ const spawn = isRecord(subagent.thread_spawn) ? subagent.thread_spawn : {};
796
+ return {
797
+ threadId: payload.id,
798
+ parentThreadId: typeof spawn.parent_thread_id === 'string' ? spawn.parent_thread_id : null,
799
+ agentPath: typeof spawn.agent_path === 'string' ? spawn.agent_path : null,
800
+ agentRole: typeof spawn.agent_role === 'string' ? spawn.agent_role : null,
801
+ };
802
+ }
803
+ throw new Error('Codex rollout is missing session_meta.');
804
+ }
805
+ function eventTimestamp(event, file, line) {
806
+ return `${typeof event.timestamp === 'string' ? event.timestamp : ''}\0${file}\0${String(line).padStart(8, '0')}`;
807
+ }
808
+ function callFromPayload(options) {
809
+ const { payload, identity } = options;
810
+ if (payload.type !== 'function_call' && payload.type !== 'custom_tool_call')
811
+ return null;
812
+ if (typeof payload.name !== 'string')
813
+ return null;
814
+ const argumentsValue = payload.type === 'custom_tool_call'
815
+ && payload.name === 'exec'
816
+ && typeof payload.input === 'string'
817
+ ? { input: payload.input }
818
+ : parseSerialized(payload.arguments ?? payload.input ?? null);
819
+ return {
820
+ threadId: identity.threadId,
821
+ parentThreadId: identity.parentThreadId,
822
+ callId: typeof payload.call_id === 'string' ? payload.call_id : null,
823
+ namespace: typeof payload.namespace === 'string' ? payload.namespace : null,
824
+ tool: payload.name,
825
+ arguments: argumentsValue,
826
+ output: null,
827
+ status: null,
828
+ order: eventTimestamp(options.event, options.file, options.line),
829
+ };
830
+ }
831
+ function agentMessageCall(options) {
832
+ if (options.payload.type !== 'agent_message')
833
+ return null;
834
+ const text = contentText(options.payload);
835
+ const messageType = text.match(/^Message Type:\s*(MESSAGE|FINAL_ANSWER)\s*$/mu)?.[1];
836
+ if (!messageType)
837
+ return null;
838
+ return {
839
+ threadId: options.identity.threadId,
840
+ parentThreadId: options.identity.parentThreadId,
841
+ callId: null,
842
+ namespace: 'collaboration',
843
+ tool: 'agent_message',
844
+ arguments: null,
845
+ output: {
846
+ messageType,
847
+ status: text.match(/\bstatus=(progress|blocked)\b/u)?.[1] ?? null,
848
+ taskName: text.match(/^Task name:\s*(.+)\s*$/mu)?.[1] ?? null,
849
+ senderAgentPath: text.match(/^Sender:\s*(.+)\s*$/mu)?.[1] ?? null,
850
+ payload: messageType === 'FINAL_ANSWER' ? agentMessagePayload(text) : null,
851
+ },
852
+ status: 'completed',
853
+ order: eventTimestamp(options.event, options.file, options.line),
854
+ };
855
+ }
856
+ function normalizeToolName(value) {
857
+ const separator = value.indexOf('__');
858
+ return separator < 0
859
+ ? { namespace: null, tool: value }
860
+ : { namespace: value.slice(0, separator), tool: value.slice(separator + 2) };
861
+ }
862
+ function normalizeAppServerCollaborationTool(value) {
863
+ switch (value) {
864
+ case 'spawnAgent':
865
+ case 'spawn_agent':
866
+ return 'spawn_agent';
867
+ case 'wait':
868
+ case 'wait_agent':
869
+ return 'wait_agent';
870
+ case 'closeAgent':
871
+ case 'close_agent':
872
+ return 'close_agent';
873
+ default:
874
+ return value;
875
+ }
876
+ }
877
+ function appServerCollaborationCalls(frames, records, descendants) {
878
+ const parentByThread = new Map(records.map((record) => [record.threadId, record.parentThreadId]));
879
+ const recordByThread = new Map(records.map((record) => [record.threadId, record]));
880
+ const rolloutCallsById = new Map();
881
+ for (const call of records.flatMap((record) => record.calls)) {
882
+ if (!call.callId)
883
+ continue;
884
+ const matches = rolloutCallsById.get(call.callId) ?? [];
885
+ matches.push(call);
886
+ rolloutCallsById.set(call.callId, matches);
887
+ }
888
+ const calls = [];
889
+ for (const [index, value] of frames.entries()) {
890
+ if (!isRecord(value) || value.method !== 'item/completed')
891
+ continue;
892
+ const params = isRecord(value.params) ? value.params : {};
893
+ const item = isRecord(params.item) ? params.item : null;
894
+ if (!item
895
+ || (item.type !== 'collabToolCall'
896
+ && item.type !== 'collabAgentToolCall'
897
+ && item.type !== 'collab_tool_call'))
898
+ continue;
899
+ const threadId = typeof item.senderThreadId === 'string'
900
+ ? item.senderThreadId
901
+ : typeof item.sender_thread_id === 'string'
902
+ ? item.sender_thread_id
903
+ : codexWorkflowFrameThreadId(value);
904
+ if (!threadId || !descendants.has(threadId))
905
+ continue;
906
+ const callId = typeof item.id === 'string' && item.id !== '' ? item.id : null;
907
+ const tool = typeof item.tool === 'string' && item.tool !== '' ? item.tool : null;
908
+ const status = typeof item.status === 'string' && item.status !== '' ? item.status : null;
909
+ const receiverThreadIds = Array.isArray(item.receiverThreadIds)
910
+ && item.receiverThreadIds.every((entry) => typeof entry === 'string')
911
+ ? item.receiverThreadIds
912
+ : Array.isArray(item.receiver_thread_ids)
913
+ && item.receiver_thread_ids.every((entry) => typeof entry === 'string')
914
+ ? item.receiver_thread_ids
915
+ : typeof item.receiverThreadId === 'string'
916
+ ? [item.receiverThreadId]
917
+ : typeof item.receiver_thread_id === 'string'
918
+ ? [item.receiver_thread_id]
919
+ : typeof item.newThreadId === 'string'
920
+ ? [item.newThreadId]
921
+ : typeof item.new_thread_id === 'string'
922
+ ? [item.new_thread_id]
923
+ : null;
924
+ const agentsStates = item.agentsStates
925
+ ?? item.agents_states
926
+ ?? item.agentStatus
927
+ ?? item.agent_status;
928
+ if (!callId || !tool || !status || !receiverThreadIds || agentsStates === undefined) {
929
+ throw new Error('Codex app-server collaboration item is missing required terminal evidence.');
930
+ }
931
+ const matchingRolloutCalls = rolloutCallsById.get(callId) ?? [];
932
+ const rolloutCall = matchingRolloutCalls.length === 1 ? matchingRolloutCalls[0] : undefined;
933
+ const rolloutArguments = isRecord(rolloutCall?.arguments) ? rolloutCall.arguments : {};
934
+ const normalizedTool = normalizeAppServerCollaborationTool(tool);
935
+ let callArguments = rolloutCall?.arguments ?? null;
936
+ let effectiveRuntime;
937
+ let callOutput = {
938
+ agentsStates,
939
+ ...(item.result !== undefined ? { result: item.result } : {}),
940
+ ...(item.error !== undefined ? { error: item.error } : {}),
941
+ };
942
+ if (normalizedTool === 'spawn_agent') {
943
+ if (matchingRolloutCalls.length !== 1 || rolloutCall?.tool !== 'spawn_agent') {
944
+ throw new Error('Codex V1 spawn evidence must have one exact rollout call id and tool match.');
945
+ }
946
+ const spawnArguments = canonicalV1SpawnArguments(rolloutCall.arguments);
947
+ if (receiverThreadIds.length !== 1) {
948
+ throw new Error('Codex V1 spawn evidence must identify exactly one receiver thread.');
949
+ }
950
+ const receiverThreadId = receiverThreadIds[0];
951
+ const receiver = recordByThread.get(receiverThreadId);
952
+ if (!receiver
953
+ || receiver.parentThreadId !== threadId
954
+ || !receiver.agentRole
955
+ || !receiver.initialUserMessage)
956
+ throw new Error('Codex V1 spawn evidence is not linked to a plaintext child thread.');
957
+ if (item.prompt !== undefined && item.prompt !== null && item.prompt !== receiver.initialUserMessage) {
958
+ throw new Error('Codex V1 spawn prompt does not match the linked child thread.');
959
+ }
960
+ if (spawnArguments.agent_type !== undefined
961
+ && spawnArguments.agent_type !== receiver.agentRole)
962
+ throw new Error('Codex V1 spawn role does not match the linked child thread.');
963
+ if (spawnArguments.message !== receiver.initialUserMessage)
964
+ throw new Error('Codex V1 spawn message does not match the linked child thread.');
965
+ const stateRecord = isRecord(agentsStates) ? agentsStates : null;
966
+ if (!stateRecord || !Object.hasOwn(stateRecord, receiverThreadId)) {
967
+ throw new Error('Codex V1 spawn evidence is missing the linked child state.');
968
+ }
969
+ const model = item.model;
970
+ const reasoningEffort = item.reasoningEffort
971
+ ?? item.reasoning_effort;
972
+ callArguments = spawnArguments;
973
+ if (typeof model === 'string' && model !== ''
974
+ || typeof reasoningEffort === 'string' && reasoningEffort !== '') {
975
+ effectiveRuntime = {
976
+ ...(typeof model === 'string' && model !== '' ? { model } : {}),
977
+ ...(typeof reasoningEffort === 'string' && reasoningEffort !== ''
978
+ ? { reasoningEffort }
979
+ : {}),
980
+ };
981
+ }
982
+ callOutput = {
983
+ agent_id: receiverThreadId,
984
+ status: stateRecord[receiverThreadId],
985
+ };
986
+ }
987
+ else if (normalizedTool === 'wait_agent') {
988
+ if (!isRecord(agentsStates)) {
989
+ throw new Error('Codex V1 wait evidence agentsStates must be an object.');
990
+ }
991
+ if (receiverThreadIds.some((receiver) => !descendants.has(receiver)
992
+ || !recordByThread.has(receiver)
993
+ || !Object.hasOwn(agentsStates, receiver))) {
994
+ throw new Error('Codex V1 wait evidence is missing a linked receiver state.');
995
+ }
996
+ const timeoutMs = rolloutArguments.timeout_ms;
997
+ if (timeoutMs !== undefined && (!Number.isSafeInteger(timeoutMs) || timeoutMs < 0)) {
998
+ throw new Error('Codex V1 wait timeout_ms evidence must be a non-negative integer.');
999
+ }
1000
+ callArguments = {
1001
+ receiver_thread_ids: receiverThreadIds,
1002
+ ...(timeoutMs === undefined ? {} : { timeout_ms: timeoutMs }),
1003
+ };
1004
+ const rawOutput = isRecord(rolloutCall?.output) ? rolloutCall.output : null;
1005
+ const rawTimedOut = rawOutput?.timed_out === true
1006
+ || rawOutput?.timedOut === true
1007
+ || rawOutput?.status === 'timeout'
1008
+ || rawOutput?.status === 'timed_out';
1009
+ const result = isRecord(item.result) ? item.result : null;
1010
+ const statusByReceiver = {};
1011
+ for (const receiver of receiverThreadIds) {
1012
+ const receiverResult = isRecord(result?.[receiver]) ? result[receiver] : result;
1013
+ statusByReceiver[receiver] = receiverResult && typeof receiverResult.completed === 'string'
1014
+ ? { completed: receiverResult.completed }
1015
+ : agentsStates[receiver];
1016
+ }
1017
+ const inferredTimedOut = status === 'completed'
1018
+ && receiverThreadIds.every((receiver) => agentsStates[receiver] === 'running');
1019
+ callOutput = {
1020
+ ...(rawTimedOut || inferredTimedOut ? { timed_out: true } : {}),
1021
+ status: statusByReceiver,
1022
+ };
1023
+ }
1024
+ else if (normalizedTool === 'close_agent') {
1025
+ if (receiverThreadIds.length === 0
1026
+ || receiverThreadIds.some((receiver) => !descendants.has(receiver)
1027
+ || !recordByThread.has(receiver)))
1028
+ throw new Error('Codex V1 close evidence is missing a linked receiver thread.');
1029
+ callArguments = { receiver_thread_ids: receiverThreadIds };
1030
+ }
1031
+ calls.push({
1032
+ threadId,
1033
+ parentThreadId: parentByThread.get(threadId) ?? null,
1034
+ ...(normalizedTool === 'wait_agent'
1035
+ && receiverThreadIds.length === 1
1036
+ && recordByThread.get(receiverThreadIds[0])?.parentThreadId
1037
+ ? { linkedThreadId: receiverThreadIds[0] }
1038
+ : {}),
1039
+ callId,
1040
+ namespace: 'collaboration',
1041
+ tool: normalizedTool,
1042
+ arguments: callArguments,
1043
+ ...(effectiveRuntime === undefined ? {} : { effectiveRuntime }),
1044
+ output: callOutput,
1045
+ status,
1046
+ order: `0000-app-server\0${String(index).padStart(8, '0')}`,
1047
+ });
1048
+ }
1049
+ return calls;
1050
+ }
1051
+ function v1CodeModeSpawnCall(options) {
1052
+ const event = options.events[options.index];
1053
+ const eventPayload = event.type === 'event_msg' && isRecord(event.payload) ? event.payload : null;
1054
+ const item = eventPayload?.type === 'item_completed' && isRecord(eventPayload.item)
1055
+ ? eventPayload.item
1056
+ : null;
1057
+ if (!item || !['CollabAgentToolCall', 'collabAgentToolCall', 'collab_tool_call'].includes(typeof item.type === 'string' ? item.type : ''))
1058
+ return null;
1059
+ if (normalizeAppServerCollaborationTool(String(item.tool ?? '')) !== 'spawn_agent')
1060
+ return null;
1061
+ const previous = options.events[options.index - 1];
1062
+ const next = options.events[options.index + 1];
1063
+ const exec = previous ? responseItem(previous) : null;
1064
+ const output = next ? responseItem(next) : null;
1065
+ if (!exec
1066
+ || exec.type !== 'custom_tool_call'
1067
+ || exec.name !== 'exec'
1068
+ || typeof exec.call_id !== 'string'
1069
+ || exec.call_id === ''
1070
+ || typeof exec.input !== 'string'
1071
+ || !output
1072
+ || output.type !== 'custom_tool_call_output'
1073
+ || output.call_id !== exec.call_id
1074
+ || typeof item.id !== 'string'
1075
+ || item.id === ''
1076
+ || (item.sender_thread_id ?? item.senderThreadId) !== options.identity.threadId)
1077
+ throw new Error('Codex V1 code-mode spawn event is not linked to its exec wrapper and output.');
1078
+ const argumentsValue = parseV1CodeModeSpawnInput(exec.input);
1079
+ if (typeof item.prompt !== 'string' || item.prompt !== argumentsValue.message) {
1080
+ throw new Error('Codex V1 code-mode spawn prompt conflicts with its wrapper arguments.');
1081
+ }
1082
+ return {
1083
+ threadId: options.identity.threadId,
1084
+ parentThreadId: options.identity.parentThreadId,
1085
+ callId: item.id,
1086
+ namespace: 'multi_agent_v1',
1087
+ tool: 'spawn_agent',
1088
+ arguments: argumentsValue,
1089
+ output: null,
1090
+ status: typeof item.status === 'string' ? item.status : null,
1091
+ order: eventTimestamp(event, options.file, options.index + 1),
1092
+ };
1093
+ }
1094
+ function rolloutCalls(events, identity, file) {
1095
+ const calls = [];
1096
+ const byId = new Map();
1097
+ const pendingOutputs = new Map();
1098
+ for (const [index, event] of events.entries()) {
1099
+ const payload = responseItem(event);
1100
+ if (payload) {
1101
+ const update = agentMessageCall({ payload, identity, file, line: index + 1, event });
1102
+ if (update)
1103
+ calls.push(update);
1104
+ const call = callFromPayload({ payload, identity, file, line: index + 1, event });
1105
+ const v1CodeModeWrapper = isV1CodeModeExecCall(call);
1106
+ if (call && !v1CodeModeWrapper && (!call.callId || !byId.has(call.callId))) {
1107
+ if (call.callId && pendingOutputs.has(call.callId)) {
1108
+ call.output = pendingOutputs.get(call.callId);
1109
+ call.status = 'completed';
1110
+ }
1111
+ calls.push(call);
1112
+ if (call.callId)
1113
+ byId.set(call.callId, call);
1114
+ }
1115
+ if ((payload.type === 'function_call_output' || payload.type === 'custom_tool_call_output')
1116
+ && typeof payload.call_id === 'string') {
1117
+ const output = parseSerialized(payload.output);
1118
+ const target = byId.get(payload.call_id);
1119
+ if (target) {
1120
+ target.output = output;
1121
+ target.status = 'completed';
1122
+ }
1123
+ else
1124
+ pendingOutputs.set(payload.call_id, output);
1125
+ }
1126
+ const metadata = isRecord(payload.internal_chat_message_metadata_passthrough)
1127
+ ? payload.internal_chat_message_metadata_passthrough
1128
+ : {};
1129
+ if (Array.isArray(metadata.executed_tool_calls)) {
1130
+ if (!metadata.executed_tool_calls.every(isRecord)) {
1131
+ throw new Error('Codex executed tool-call metadata contains an invalid entry.');
1132
+ }
1133
+ if (typeof payload.call_id !== 'string' || payload.call_id === '') {
1134
+ throw new Error('Codex executed tool-call metadata is missing its output call id.');
1135
+ }
1136
+ const output = parseSerialized(payload.output);
1137
+ if (output === undefined || output === null) {
1138
+ throw new Error('Codex executed tool-call metadata is missing its linked output.');
1139
+ }
1140
+ for (const [nestedIndex, entry] of metadata.executed_tool_calls.entries()) {
1141
+ if (typeof entry.name !== 'string' || entry.name === '' || !Object.hasOwn(entry, 'arguments')) {
1142
+ throw new Error('Codex executed tool-call metadata is missing its name or arguments.');
1143
+ }
1144
+ const tool = normalizeToolName(entry.name);
1145
+ const direct = byId.get(payload.call_id);
1146
+ if (direct?.tool === tool.tool
1147
+ && (tool.namespace === null || direct.namespace === tool.namespace))
1148
+ continue;
1149
+ calls.push({
1150
+ threadId: identity.threadId,
1151
+ parentThreadId: identity.parentThreadId,
1152
+ callId: metadata.executed_tool_calls.length === 1
1153
+ ? payload.call_id
1154
+ : `${payload.call_id}:executed:${nestedIndex + 1}`,
1155
+ namespace: tool.namespace,
1156
+ tool: tool.tool,
1157
+ arguments: parseSerialized(entry.arguments),
1158
+ output,
1159
+ status: 'completed',
1160
+ order: eventTimestamp(event, file, index + 1),
1161
+ });
1162
+ }
1163
+ }
1164
+ }
1165
+ const v1Spawn = v1CodeModeSpawnCall({ events, index, identity, file });
1166
+ if (v1Spawn)
1167
+ calls.push(v1Spawn);
1168
+ const eventPayload = event.type === 'event_msg' && isRecord(event.payload) ? event.payload : null;
1169
+ const completedItem = eventPayload?.type === 'item_completed' && isRecord(eventPayload.item)
1170
+ ? eventPayload.item
1171
+ : null;
1172
+ if (!completedItem)
1173
+ continue;
1174
+ if (completedItem.type === 'CommandExecution' || completedItem.type === 'command_execution') {
1175
+ const raw = Array.isArray(completedItem.command)
1176
+ ? completedItem.command.at(-1)
1177
+ : completedItem.command;
1178
+ if (typeof raw !== 'string')
1179
+ continue;
1180
+ calls.push({
1181
+ threadId: identity.threadId,
1182
+ parentThreadId: identity.parentThreadId,
1183
+ callId: typeof completedItem.id === 'string' ? `command:${completedItem.id}` : null,
1184
+ namespace: null,
1185
+ tool: 'exec_command',
1186
+ arguments: { cmd: raw },
1187
+ output: {
1188
+ exitCode: Number.isInteger(completedItem.exit_code) ? completedItem.exit_code : null,
1189
+ },
1190
+ status: typeof completedItem.status === 'string' ? completedItem.status : 'completed',
1191
+ order: eventTimestamp(event, file, index + 1),
1192
+ });
1193
+ }
1194
+ }
1195
+ return calls;
1196
+ }
1197
+ function initialMessages(events) {
1198
+ let user = null;
1199
+ let opaque = null;
1200
+ const developer = [];
1201
+ for (const event of events) {
1202
+ const payload = responseItem(event);
1203
+ if (!payload)
1204
+ continue;
1205
+ if (payload.type === 'message' && payload.role === 'developer')
1206
+ developer.push(contentText(payload));
1207
+ if (user === null && payload.type === 'message' && payload.role === 'user') {
1208
+ const candidate = contentText(payload);
1209
+ if (candidate && !/^<environment_context>[\s\S]*<\/environment_context>$/u.test(candidate.trim())) {
1210
+ user = candidate;
1211
+ }
1212
+ }
1213
+ if (opaque === null && payload.type === 'agent_message')
1214
+ opaque = encryptedContent(payload);
1215
+ }
1216
+ return {
1217
+ user,
1218
+ opaque,
1219
+ developer: developer.join('\n'),
1220
+ developerMessageCount: developer.length,
1221
+ };
1222
+ }
1223
+ function opaqueSpawnArguments(calls, protocol) {
1224
+ if (protocol !== 'v2')
1225
+ return;
1226
+ for (const call of calls) {
1227
+ if (call.tool !== 'spawn_agent' || !isRecord(call.arguments))
1228
+ continue;
1229
+ if (typeof call.arguments.message !== 'string')
1230
+ continue;
1231
+ call.arguments = {
1232
+ ...call.arguments,
1233
+ message: { kind: 'opaque', sha256: hash(call.arguments.message) },
1234
+ };
1235
+ }
1236
+ }
1237
+ function linkedOpaqueCall(calls, parentThreadId, digest) {
1238
+ const call = calls.find((entry) => entry.threadId === parentThreadId
1239
+ && entry.tool === 'spawn_agent'
1240
+ && isRecord(entry.arguments)
1241
+ && isRecord(entry.arguments.message)
1242
+ && entry.arguments.message.sha256 === digest);
1243
+ return call?.callId ?? null;
1244
+ }
1245
+ export async function normalizeCodexWorkflowRollouts(options) {
1246
+ const records = [];
1247
+ for (const { file, events } of await readCodexEvalRollouts(options.codexHome)) {
1248
+ const identity = rolloutIdentity(events);
1249
+ const messages = initialMessages(events);
1250
+ records.push({
1251
+ ...identity,
1252
+ initialUserMessage: messages.user,
1253
+ initialOpaqueMessage: messages.opaque,
1254
+ developerText: messages.developer,
1255
+ developerMessageCount: messages.developerMessageCount,
1256
+ calls: rolloutCalls(events, identity, path.relative(options.codexHome, file)),
1257
+ trace: events,
1258
+ });
1259
+ }
1260
+ const root = records.find((record) => record.threadId === options.rootThreadId);
1261
+ if (!root)
1262
+ throw new Error('Codex raw rollout does not contain the app-server root thread.');
1263
+ const descendants = new Set([options.rootThreadId]);
1264
+ let changed = true;
1265
+ while (changed) {
1266
+ changed = false;
1267
+ for (const record of records) {
1268
+ if (record.parentThreadId && descendants.has(record.parentThreadId) && !descendants.has(record.threadId)) {
1269
+ descendants.add(record.threadId);
1270
+ changed = true;
1271
+ }
1272
+ }
1273
+ }
1274
+ const selected = records.filter((record) => descendants.has(record.threadId));
1275
+ if (selected.some((record) => record.developerText === '')) {
1276
+ throw new Error('Codex raw rollout is missing developer context for a workflow thread.');
1277
+ }
1278
+ const rolloutCallList = selected.flatMap((record) => record.calls);
1279
+ const appServerCallList = options.request.multiAgent.protocol === 'v1'
1280
+ ? appServerCollaborationCalls(options.appServerFrames ?? [], records, descendants)
1281
+ : [];
1282
+ const appServerCallIds = new Set(appServerCallList.map((call) => call.callId));
1283
+ const calls = [
1284
+ ...appServerCallList,
1285
+ ...rolloutCallList.filter((call) => !call.callId || !appServerCallIds.has(call.callId)),
1286
+ ];
1287
+ const isolatedPaths = options.isolatedPaths
1288
+ ?? [{ path: options.codexHome, marker: '[codex-home]' }];
1289
+ for (const call of calls) {
1290
+ if (options.request.multiAgent.protocol === 'v1'
1291
+ && isV1CodeModeExecCall(call)
1292
+ && isRecord(call.arguments)
1293
+ && typeof call.arguments.input === 'string') {
1294
+ call.arguments = {
1295
+ input: {
1296
+ namespace: 'multi_agent_v1',
1297
+ tool: 'spawn_agent',
1298
+ arguments: parseV1CodeModeSpawnInput(call.arguments.input),
1299
+ },
1300
+ };
1301
+ }
1302
+ call.arguments = sanitizeTraceValue(call.arguments, options.runtime, isolatedPaths, options.request.multiAgent.protocol === 'v2');
1303
+ call.output = sanitizeTraceValue(call.output, options.runtime, isolatedPaths, options.request.multiAgent.protocol === 'v2');
1304
+ }
1305
+ opaqueSpawnArguments(calls, options.request.multiAgent.protocol);
1306
+ calls.sort((left, right) => left.order.localeCompare(right.order));
1307
+ const collaborationCalls = calls.map(({ order: _order, ...call }, index) => ({
1308
+ ...call,
1309
+ ordinal: index + 1,
1310
+ }));
1311
+ const descendantThreadIds = new Set(selected
1312
+ .filter((record) => record.threadId !== options.rootThreadId)
1313
+ .map((record) => record.threadId));
1314
+ for (const call of collaborationCalls) {
1315
+ if (call.tool !== 'wait_agent' || call.linkedThreadId !== undefined)
1316
+ continue;
1317
+ const receiverIds = new Set();
1318
+ if (isRecord(call.arguments) && Array.isArray(call.arguments.receiver_thread_ids)) {
1319
+ for (const receiver of call.arguments.receiver_thread_ids) {
1320
+ if (typeof receiver === 'string')
1321
+ receiverIds.add(receiver);
1322
+ }
1323
+ }
1324
+ if (isRecord(call.output) && isRecord(call.output.status)) {
1325
+ for (const receiver of Object.keys(call.output.status))
1326
+ receiverIds.add(receiver);
1327
+ }
1328
+ if (receiverIds.size !== 1)
1329
+ continue;
1330
+ const receiver = [...receiverIds][0];
1331
+ if (descendantThreadIds.has(receiver))
1332
+ call.linkedThreadId = receiver;
1333
+ }
1334
+ const descendantByAgentPath = new Map();
1335
+ for (const record of selected) {
1336
+ if (!record.parentThreadId || !record.agentPath)
1337
+ continue;
1338
+ descendantByAgentPath.set(record.agentPath, descendantByAgentPath.has(record.agentPath) ? null : record.threadId);
1339
+ }
1340
+ for (const call of collaborationCalls) {
1341
+ if (call.tool !== 'agent_message' || !isRecord(call.output))
1342
+ continue;
1343
+ const senderAgentPath = call.output.senderAgentPath;
1344
+ delete call.output.senderAgentPath;
1345
+ if (call.threadId !== options.rootThreadId
1346
+ || call.output.messageType !== 'FINAL_ANSWER'
1347
+ || typeof senderAgentPath !== 'string')
1348
+ continue;
1349
+ const linkedThreadId = descendantByAgentPath.get(senderAgentPath);
1350
+ if (linkedThreadId)
1351
+ call.linkedThreadId = linkedThreadId;
1352
+ }
1353
+ const threadGraph = selected.map((record) => {
1354
+ let initialMessage = null;
1355
+ if (record.initialOpaqueMessage) {
1356
+ const digest = hash(record.initialOpaqueMessage);
1357
+ const linkedCallId = record.parentThreadId
1358
+ ? linkedOpaqueCall(calls, record.parentThreadId, digest)
1359
+ : null;
1360
+ if (!linkedCallId)
1361
+ throw new Error('Codex V2 child opaque message is not linked to a spawn call.');
1362
+ initialMessage = { kind: 'opaque-link', sha256: digest, linkedCallId };
1363
+ }
1364
+ else if (record.initialUserMessage) {
1365
+ initialMessage = { kind: 'plaintext', text: record.initialUserMessage };
1366
+ }
1367
+ if (record.parentThreadId
1368
+ && options.request.multiAgent.protocol === 'v1'
1369
+ && initialMessage?.kind !== 'plaintext')
1370
+ throw new Error('Codex V1 child rollout is missing its plaintext initial message.');
1371
+ if (record.parentThreadId
1372
+ && options.request.multiAgent.protocol === 'v2'
1373
+ && options.request.multiAgent.hideSpawnAgentMetadata
1374
+ && initialMessage?.kind !== 'opaque-link')
1375
+ throw new Error('Codex V2 child rollout is missing its opaque spawn link.');
1376
+ return {
1377
+ threadId: record.threadId,
1378
+ parentThreadId: record.parentThreadId,
1379
+ agentRole: record.agentRole,
1380
+ initialMessage,
1381
+ developerContext: {
1382
+ present: true,
1383
+ messageCount: record.developerMessageCount,
1384
+ sha256: hash(record.developerText),
1385
+ hasMultiAgentModeGate: record.developerText.includes('<multi_agent_mode>'),
1386
+ hasNestedAgentCapability: record.developerText.includes('Child agents can also spawn their own sub-agents'),
1387
+ hasCodexExploreDeveloperAuthorization: record.developerText.includes('### Codex Explore 子代理授权'),
1388
+ },
1389
+ };
1390
+ });
1391
+ threadGraph.sort((left, right) => {
1392
+ if (left.threadId === options.rootThreadId)
1393
+ return -1;
1394
+ if (right.threadId === options.rootThreadId)
1395
+ return 1;
1396
+ return left.threadId.localeCompare(right.threadId);
1397
+ });
1398
+ const skillLoads = [];
1399
+ const seenLoads = new Set();
1400
+ for (const record of selected) {
1401
+ for (const event of record.trace) {
1402
+ const skill = selectedSkillName(event);
1403
+ if (!skill)
1404
+ continue;
1405
+ const key = `${record.threadId}\0${skill}`;
1406
+ if (seenLoads.has(key))
1407
+ continue;
1408
+ seenLoads.add(key);
1409
+ skillLoads.push({ threadId: record.threadId, skill, callId: null });
1410
+ }
1411
+ }
1412
+ for (const call of collaborationCalls) {
1413
+ const serialized = JSON.stringify(call.arguments).replaceAll('\\', '/');
1414
+ for (const match of serialized.matchAll(/\.agents\/skills\/([^/"\\]+)\/SKILL\.md/gu)) {
1415
+ const key = `${call.threadId}\0${match[1]}`;
1416
+ if (seenLoads.has(key))
1417
+ continue;
1418
+ seenLoads.add(key);
1419
+ skillLoads.push({ threadId: call.threadId, skill: match[1], callId: call.callId });
1420
+ }
1421
+ }
1422
+ return {
1423
+ threadGraph,
1424
+ collaborationCalls,
1425
+ leafCalls: collaborationCalls.filter((call) => call.threadId !== options.rootThreadId
1426
+ || (call.linkedThreadId !== undefined && descendants.has(call.linkedThreadId))),
1427
+ skillLoads,
1428
+ trace: selected.flatMap((record) => record.trace.map((event) => sanitizeTraceValue(isRecord(event) ? { ...event, threadId: record.threadId, parentThreadId: record.parentThreadId } : event, options.runtime, [
1429
+ ...isolatedPaths,
1430
+ ...selected.flatMap((entry) => entry.agentPath
1431
+ ? [{ path: entry.agentPath, marker: '[agent-path]' }]
1432
+ : []),
1433
+ ], options.request.multiAgent.protocol === 'v2'))),
1434
+ };
1435
+ }
1436
+ function sandboxPolicy(request) {
1437
+ // Opt-in evaluations use one outer OS profile; nesting Seatbelt is unreliable.
1438
+ if (request.evaluation)
1439
+ return { type: 'externalSandbox', networkAccess: 'enabled' };
1440
+ return request.sandbox === 'read-only'
1441
+ ? { type: 'readOnly', networkAccess: true }
1442
+ : {
1443
+ type: 'workspaceWrite',
1444
+ writableRoots: [],
1445
+ networkAccess: true,
1446
+ excludeTmpdirEnvVar: false,
1447
+ excludeSlashTmp: false,
1448
+ };
1449
+ }
1450
+ function workflowFixtureRunner(input, control) {
1451
+ return (action) => runWorkflowFixtureAction({
1452
+ action, workspace: input.input.workspacePath, fixtures: input.input.fixturesPath,
1453
+ output: input.input.outputDir, evalRoot: input.evalRoot, sourceEnv: input.sourceEnv, runtime: input.codexRuntime,
1454
+ timeoutMs: control.remaining('workflow fixture action'), signal: control.signal,
1455
+ processManagement: input.processManagement,
1456
+ ...(input.processTree ? { processTree: input.processTree } : {}),
1457
+ ...(input.protectedEvalRoots ? { protectedEvalRoots: input.protectedEvalRoots } : {}),
1458
+ });
1459
+ }
1460
+ async function readInstructionV2File(root, relative, limit) {
1461
+ relativeWorkspacePath(relative, 'instruction-v2 capture path');
1462
+ let target = root;
1463
+ for (const part of relative.split('/')) {
1464
+ target = path.join(target, part);
1465
+ if ((await fs.lstat(target)).isSymbolicLink())
1466
+ throw new Error('instruction-v2 capture must not contain symbolic links.');
1467
+ }
1468
+ const stat = await fs.lstat(target);
1469
+ if (!stat.isFile() || stat.size > limit)
1470
+ throw new Error('instruction-v2 capture is not a bounded regular file.');
1471
+ return fs.readFile(target, 'utf8');
1472
+ }
1473
+ const instructionV2Transport = {
1474
+ async run(input) {
1475
+ const selected = input.request.transport;
1476
+ if (!path.isAbsolute(input.codexBin))
1477
+ throw new Error('instruction-v2 requires the Engine-resolved absolute Codex executable.');
1478
+ const control = new CodexEvalAttemptControl({
1479
+ label: 'instruction-v2 workflow', timeoutMs: input.timeoutMs,
1480
+ ...(input.signal ? { signal: input.signal } : {}),
1481
+ });
1482
+ const temporaryRoot = await createCodexEvalTemporaryRoot('instruction-v2-workflow-');
1483
+ const runtimeRoot = await fs.realpath(temporaryRoot.root);
1484
+ const codexHome = path.join(runtimeRoot, 'codex-home');
1485
+ const runDirectory = path.join(runtimeRoot, 'run');
1486
+ const env = {
1487
+ ...buildEngineEnvironment(input.sourceEnv, runtimeRoot, codexHome, input.codexRuntime),
1488
+ AW_SUBAGENT_RUN_DIR: runDirectory,
1489
+ AW_SUBAGENT_RESULT_FILE: path.join(runDirectory, 'result.json'),
1490
+ AW_SUBAGENT_TRACE_FILE: path.join(runDirectory, 'codex-events.jsonl'),
1491
+ AW_SUBAGENT_CODEX_BINARY: input.codexBin,
1492
+ };
1493
+ const startedAt = Date.now();
1494
+ let cleaned = false;
1495
+ let judgeNumber = 0;
1496
+ const judgeUsage = [];
1497
+ const grader = input.request.evaluation ? createCodexGrader({
1498
+ sourceEnv: input.sourceEnv, codexRuntime: input.codexRuntime, codexBin: input.codexBin,
1499
+ ...(input.request.model ? { model: input.request.model } : {}), processManagement: input.processManagement,
1500
+ }) : undefined;
1501
+ const evaluation = input.request.evaluation ? new WorkflowEvaluation(input.request.evaluation, input.input.workspacePath, async (prompt) => {
1502
+ const outputDir = path.join(input.input.outputDir, 'workflow-judges', String(++judgeNumber));
1503
+ await fs.ensureDir(outputDir);
1504
+ const result = await grader({ prompt, outputDir, timeoutMs: control.remaining('external semantic check'), signal: control.signal });
1505
+ if (!result.mechanicalPassed)
1506
+ throw new Error('external workflow semantic grader did not complete its read-only checks.');
1507
+ judgeUsage.push(result.usage);
1508
+ return parseConversationGraderOutput(result.output);
1509
+ }, workflowFixtureRunner(input, control)) : undefined;
1510
+ try {
1511
+ await Promise.all([
1512
+ fs.ensureDir(env.HOME), fs.ensureDir(env.TMPDIR), fs.mkdir(runDirectory, { mode: 0o700 }),
1513
+ materializeCodexEvalRuntimeHome(input.codexRuntime, codexHome, { trustedProject: input.input.workspacePath }),
1514
+ ]);
1515
+ await evaluation?.runSetupActions();
1516
+ const before = await snapshotWorkspace(input.input.workspacePath);
1517
+ let containment;
1518
+ if (input.request.evaluation) {
1519
+ containment = hostReadContainmentProfile({
1520
+ forbiddenHostRoots: [input.input.fixturesPath, input.input.outputDir],
1521
+ writableRoots: [runtimeRoot, input.input.workspacePath],
1522
+ unwritableRoots: input.request.sandbox === 'read-only' ? [input.input.workspacePath] : [],
1523
+ }) + '\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');
1524
+ for (let ancestor = path.dirname(input.input.workspacePath); ancestor !== path.dirname(ancestor); ancestor = path.dirname(ancestor)) {
1525
+ containment += `\n(allow file-read-metadata (literal ${JSON.stringify(ancestor)}))`;
1526
+ }
1527
+ await assertHostReadContainmentAvailable(containment);
1528
+ }
1529
+ evaluation?.observe({ method: 'evaluation/externalSubmission', runId: selected.request.run.id }, null);
1530
+ const processResult = await runProviderProcess({
1531
+ executable: containment ? '/usr/bin/sandbox-exec' : selected.command[0],
1532
+ args: containment ? ['-p', containment, ...selected.command] : [...selected.command.slice(1)],
1533
+ cwd: input.input.workspacePath, env, stdin: JSON.stringify(selected.request),
1534
+ timeoutMs: control.remaining('external execution'), maxBufferBytes: MAX_ADAPTER_OUTPUT_BYTES,
1535
+ signal: control.signal,
1536
+ }, {}, {
1537
+ containment: input.processManagement.mode === 'unmanaged-confirmed' ? 'unmanaged-confirmed' : 'verified-observed',
1538
+ ...(input.processManagement.mode === 'unmanaged-confirmed' ? {} : { processTree: input.processTree }),
1539
+ });
1540
+ let receipt = null;
1541
+ let diagnostic = null;
1542
+ try {
1543
+ receipt = parseInstructionV2Receipt(JSON.parse(await readInstructionV2File(runDirectory, 'result.json', 64 * 1024)));
1544
+ }
1545
+ catch (error) {
1546
+ diagnostic = `External result receipt unavailable: ${error.message}`;
1547
+ }
1548
+ const trace = [];
1549
+ if (receipt?.traceFile) {
1550
+ try {
1551
+ const raw = await readInstructionV2File(runDirectory, receipt.traceFile, 8 * 1024 * 1024);
1552
+ const lines = raw.split(/\r?\n/u).filter((line) => line.trim());
1553
+ if (lines.length > 5_000)
1554
+ throw new Error('instruction-v2 trace event limit exceeded.');
1555
+ for (const line of lines)
1556
+ trace.push(JSON.parse(line));
1557
+ }
1558
+ catch (error) {
1559
+ diagnostic ??= `External trace unavailable: ${error.message}`;
1560
+ trace.length = 0;
1561
+ }
1562
+ }
1563
+ const completed = receipt?.outcome === 'completed' && receipt.providerTerminal === 'turn.completed'
1564
+ && receipt.instructionChannel !== 'user-prefix'
1565
+ && receipt.instructionsRevision === selected.request.instructions.revision
1566
+ && processResult.exitCode === 0 && processResult.signal === null
1567
+ && !processResult.timedOut && !processResult.cancelled && !processResult.maxBufferExceeded
1568
+ && receipt.generationStarted !== false && receipt.scopeViolation !== true
1569
+ && (receipt.localExitCode === undefined || receipt.localExitCode === 0)
1570
+ && (input.processManagement.mode === 'unmanaged-confirmed' || processResult.gradingEvidenceAccepted)
1571
+ && processResult.stdout.trim() !== '';
1572
+ const status = completed ? 'completed' : receipt?.outcome === 'failed'
1573
+ && (receipt.providerTerminal === 'turn.failed' || receipt.generationStarted === false) ? 'failed' : 'unknown';
1574
+ if (!completed)
1575
+ diagnostic ??= 'External execution lacks compatible channel, instruction revision, terminal, normal exit, or final text evidence.';
1576
+ const after = await snapshotWorkspace(input.input.workspacePath);
1577
+ await temporaryRoot.cleanup();
1578
+ cleaned = true;
1579
+ const usage = standardUsage(receipt?.usage);
1580
+ const evidence = {
1581
+ version: 1, rootThreadId: receipt?.providerSessionId ?? selected.request.run.id,
1582
+ completion: { status, turnStatus: receipt?.providerTerminal ?? 'unknown', durationMs: Date.now() - startedAt },
1583
+ termination: {
1584
+ reason: status,
1585
+ providerProcessCleanupConfirmed: input.processManagement.mode !== 'unmanaged-confirmed'
1586
+ && processResult.gradingEvidenceAccepted,
1587
+ },
1588
+ usage, finalMessage: processResult.stdout, workspaceDiff: workspaceDiff(before, after),
1589
+ skillLoads: [], threadGraph: [], collaborationCalls: [], leafCalls: [],
1590
+ cleanup: { isolatedCodexHomeRemoved: true },
1591
+ transport: {
1592
+ type: 'instruction-v2', request: selected.request, command: selected.command, receipt,
1593
+ traceAvailable: trace.length > 0,
1594
+ observationLimits: [
1595
+ 'Provider JSONL events are captured as supplied; complete file-access attribution and nested delegation coverage are not established.',
1596
+ '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.',
1597
+ 'Provider trace is read after process exit. Artifact snapshots measure before submission and after cleanup; replayed event order does not supply intermediate snapshots.',
1598
+ ],
1599
+ diagnostic, exitCode: processResult.exitCode, signal: processResult.signal, stderr: processResult.stderr,
1600
+ },
1601
+ };
1602
+ if (completed && evaluation) {
1603
+ for (const frame of trace)
1604
+ evaluation.observe({ method: 'evaluation/externalTraceCapture', capturedAfterExit: true, frame }, evidence.rootThreadId);
1605
+ const messages = [
1606
+ { type: 'response_item', payload: { type: 'message', role: 'user', content: [{ type: 'input_text', text: selected.request.task }] } },
1607
+ ...trace,
1608
+ { type: 'response_item', payload: { type: 'agent_message', text: processResult.stdout } },
1609
+ ];
1610
+ try {
1611
+ evidence.evaluation = await evaluation.finish(evidence, messages);
1612
+ if (!trace.length) {
1613
+ evidence.evaluation.complete = false;
1614
+ evidence.evaluation.checks = evaluation.request.checks.map(({ name }) => ({
1615
+ name, verdict: 'unknown', evidence: 'External provider tool trace is unavailable; behavior assertions cannot be established from final text.',
1616
+ }));
1617
+ }
1618
+ }
1619
+ catch (error) {
1620
+ evidence.evaluation = {
1621
+ complete: false, checks: [], observations: evaluation.observations,
1622
+ snapshots: evaluation.snapshots, decisions: evaluation.decisions,
1623
+ fixtureActions: evaluation.fixtureActions, measurementStart: evaluation.measurementStart,
1624
+ };
1625
+ await writeWorkflowArtifacts(input.input.outputDir, redactCodexEvalValue({ evidence, trace, modelOutput: processResult.stdout, usage }, input.codexRuntime));
1626
+ throw error;
1627
+ }
1628
+ const totals = [usage, ...judgeUsage].map(standardUsage);
1629
+ evidence.usage = totals.every((value) => value !== null) ? {
1630
+ inputTokens: totals.reduce((sum, value) => sum + value.inputTokens, 0),
1631
+ outputTokens: totals.reduce((sum, value) => sum + value.outputTokens, 0),
1632
+ } : null;
1633
+ }
1634
+ return redactCodexEvalValue({ evidence, trace, modelOutput: processResult.stdout, usage: evidence.usage }, input.codexRuntime);
1635
+ }
1636
+ finally {
1637
+ try {
1638
+ if (!cleaned)
1639
+ await temporaryRoot.cleanup();
1640
+ }
1641
+ finally {
1642
+ control.dispose();
1643
+ }
1644
+ }
1645
+ },
1646
+ };
1647
+ const defaultTransport = {
1648
+ async run(input) {
1649
+ const control = new CodexEvalAttemptControl({
1650
+ label: 'codex-workflow',
1651
+ timeoutMs: input.timeoutMs,
1652
+ ...(input.signal ? { signal: input.signal } : {}),
1653
+ });
1654
+ const temporaryRoot = await createCodexEvalTemporaryRoot('codex-workflow-');
1655
+ const runtimeRoot = await fs.realpath(temporaryRoot.root);
1656
+ const codexHome = path.join(runtimeRoot, 'codex-home');
1657
+ const env = buildEngineEnvironment(input.sourceEnv, runtimeRoot, codexHome, input.codexRuntime);
1658
+ env.AW_SUBAGENT_CODEX_BINARY = input.codexBin;
1659
+ let session;
1660
+ let cleanupConfirmed = false;
1661
+ let codexHomeRemoved = false;
1662
+ const startedAt = Date.now();
1663
+ const frames = [];
1664
+ const judgeUsage = [];
1665
+ let judgeNumber = 0;
1666
+ const runGrader = input.request.evaluation ? createCodexGrader({
1667
+ sourceEnv: input.sourceEnv,
1668
+ codexRuntime: input.codexRuntime,
1669
+ codexBin: input.codexBin,
1670
+ ...(input.request.model ? { model: input.request.model } : {}),
1671
+ processManagement: input.processManagement,
1672
+ }) : undefined;
1673
+ const evaluation = input.request.evaluation ? new WorkflowEvaluation(input.request.evaluation, input.input.workspacePath, async (prompt) => {
1674
+ const outputDir = path.join(input.input.outputDir, 'workflow-judges', String(++judgeNumber));
1675
+ await fs.ensureDir(outputDir);
1676
+ const result = await runGrader({
1677
+ prompt, outputDir, timeoutMs: control.remaining('workflow semantic check'), signal: control.signal,
1678
+ });
1679
+ if (!result.mechanicalPassed)
1680
+ throw new Error('workflow semantic grader did not complete its read-only checks.');
1681
+ judgeUsage.push(result.usage);
1682
+ return parseConversationGraderOutput(result.output);
1683
+ }, workflowFixtureRunner(input, control)) : undefined;
1684
+ let removeHandler;
1685
+ let removeFailureHandler;
1686
+ try {
1687
+ await Promise.all([
1688
+ fs.ensureDir(env.HOME),
1689
+ fs.ensureDir(env.TMPDIR),
1690
+ materializeCodexEvalRuntimeHome(input.codexRuntime, codexHome, {
1691
+ trustedProject: input.input.workspacePath,
1692
+ }),
1693
+ ]);
1694
+ await evaluation?.runSetupActions();
1695
+ const before = await snapshotWorkspace(input.input.workspacePath);
1696
+ let containment;
1697
+ if (evaluation) {
1698
+ containment = hostReadContainmentProfile({
1699
+ forbiddenHostRoots: [input.input.fixturesPath, input.input.outputDir],
1700
+ writableRoots: [runtimeRoot, input.input.workspacePath],
1701
+ unwritableRoots: input.request.sandbox === 'read-only' ? [input.input.workspacePath] : [],
1702
+ }) + '\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');
1703
+ for (let ancestor = path.dirname(input.input.workspacePath); ancestor !== path.dirname(ancestor); ancestor = path.dirname(ancestor)) {
1704
+ containment += `\n(allow file-read-metadata (literal ${JSON.stringify(ancestor)}))`;
1705
+ }
1706
+ await assertHostReadContainmentAvailable(containment);
1707
+ }
1708
+ const configOverrides = workflowConfig(input.request, input.input.workspacePath);
1709
+ session = new EvalAppServerSession({
1710
+ codexBin: input.codexBin,
1711
+ cwd: input.input.workspacePath,
1712
+ env,
1713
+ modelProvider: input.codexRuntime.modelProvider,
1714
+ sensitiveEnvironmentNames: input.codexRuntime.sensitiveEnvironmentNames,
1715
+ isolation: 'standard',
1716
+ processManagement: input.processManagement,
1717
+ configOverrides,
1718
+ ...(containment ? { hostReadContainmentProfile: containment } : {}),
1719
+ });
1720
+ await session.initialize(control.rpcTimeout('initialize'));
1721
+ const threadResult = await session.sendRequest('thread/start', {
1722
+ cwd: input.input.workspacePath,
1723
+ approvalPolicy: 'never',
1724
+ sandbox: input.request.sandbox,
1725
+ ephemeral: false,
1726
+ ...(input.request.model ? { model: input.request.model } : {}),
1727
+ }, control.rpcTimeout('thread/start'));
1728
+ const rootThreadId = extractAppServerThreadId(threadResult);
1729
+ let finalMessage = null;
1730
+ let usage = null;
1731
+ let turnStatus = null;
1732
+ let resolveTurn;
1733
+ let rejectTurn;
1734
+ let lastMessageOrdinal = 0;
1735
+ let frameQueue = Promise.resolve();
1736
+ removeFailureHandler = session.onFailure((error) => rejectTurn?.(error));
1737
+ removeHandler = session.onFrame((frame) => {
1738
+ const isolatedPaths = [
1739
+ { path: codexHome, marker: '[codex-home]' },
1740
+ { path: env.HOME, marker: '[isolated-home]' },
1741
+ { path: runtimeRoot, marker: '[runtime-root]' },
1742
+ ];
1743
+ frames.push(sanitizeTraceValue(frame, input.codexRuntime, isolatedPaths, input.request.multiAgent.protocol === 'v2'));
1744
+ const method = typeof frame.method === 'string' ? frame.method : '';
1745
+ const params = isRecord(frame.params) ? frame.params : {};
1746
+ // Capture on receipt, before asynchronous semantic matching. All threads count.
1747
+ const ordinal = evaluation && ['item/completed', 'item/tool/requestUserInput', 'turn/completed'].includes(method)
1748
+ ? evaluation.observe(sanitizeTraceValue(frame, input.codexRuntime, isolatedPaths, input.request.multiAgent.protocol === 'v2'), codexWorkflowFrameThreadId(frame)) : 0;
1749
+ frameQueue = frameQueue.then(async () => {
1750
+ if (method === 'turn/completed') {
1751
+ if (!codexWorkflowFrameIsForRoot(frame, rootThreadId))
1752
+ return;
1753
+ const turn = isRecord(params.turn) ? params.turn : {};
1754
+ turnStatus = typeof turn.status === 'string' ? turn.status : 'unknown';
1755
+ resolveTurn?.();
1756
+ return;
1757
+ }
1758
+ if (!codexWorkflowFrameIsForRoot(frame, rootThreadId))
1759
+ return;
1760
+ if (method === 'item/completed' && isRecord(params.item)) {
1761
+ const item = params.item;
1762
+ if (item.type === 'agentMessage'
1763
+ && item.phase === 'final_answer'
1764
+ && typeof item.text === 'string') {
1765
+ finalMessage = item.text;
1766
+ lastMessageOrdinal = ordinal;
1767
+ }
1768
+ }
1769
+ if (method === 'thread/tokenUsage/updated')
1770
+ usage = params.tokenUsage ?? usage;
1771
+ if (method === 'item/tool/requestUserInput') {
1772
+ if (!evaluation)
1773
+ throw new Error('codex-workflow root requested interactive user input.');
1774
+ if ((typeof frame.id !== 'string' && typeof frame.id !== 'number') || !Array.isArray(params.questions)) {
1775
+ throw new Error('workflow user-input request lacks its actual id or questions.');
1776
+ }
1777
+ const reply = await evaluation.reply({ questions: params.questions }, ordinal);
1778
+ const answers = Object.fromEntries(params.questions.filter(isRecord).map((question) => [
1779
+ String(question.id), { answers: [reply && reply.questionId === question.id
1780
+ ? reply.text : 'No user decision is available for this question. Return control without dependent changes.'] },
1781
+ ]));
1782
+ await session.respond(frame.id, { answers });
1783
+ }
1784
+ if (method === 'error')
1785
+ rejectTurn?.(new Error(`codex-workflow app-server error: ${JSON.stringify(params)}`));
1786
+ });
1787
+ return frameQueue;
1788
+ });
1789
+ let nextPrompt = input.request.evaluation?.setupPrompt ?? input.request.prompt;
1790
+ let setup = input.request.evaluation?.setupPrompt !== undefined;
1791
+ while (true) {
1792
+ const turnDone = new Promise((resolve, reject) => {
1793
+ resolveTurn = resolve;
1794
+ rejectTurn = reject;
1795
+ });
1796
+ void turnDone.catch(() => undefined);
1797
+ finalMessage = null;
1798
+ turnStatus = null;
1799
+ await session.sendRequest('turn/start', {
1800
+ threadId: rootThreadId,
1801
+ input: [{ type: 'text', text: nextPrompt, text_elements: [] }],
1802
+ cwd: input.input.workspacePath,
1803
+ approvalPolicy: 'never',
1804
+ sandboxPolicy: sandboxPolicy(input.request),
1805
+ ...(input.request.model ? { model: input.request.model } : {}),
1806
+ }, control.rpcTimeout('turn/start'));
1807
+ let abortTurnWait;
1808
+ try {
1809
+ await Promise.race([
1810
+ turnDone,
1811
+ new Promise((_resolve, reject) => {
1812
+ abortTurnWait = () => reject(control.stopError('turn wait'));
1813
+ if (control.signal.aborted)
1814
+ abortTurnWait();
1815
+ else
1816
+ control.signal.addEventListener('abort', abortTurnWait, { once: true });
1817
+ }),
1818
+ ]);
1819
+ }
1820
+ finally {
1821
+ if (abortTurnWait)
1822
+ control.signal.removeEventListener('abort', abortTurnWait);
1823
+ }
1824
+ await frameQueue;
1825
+ control.throwIfStopped('turn wait');
1826
+ if (turnStatus !== 'completed') {
1827
+ throw new Error(`codex-workflow root turn ended with status ${String(turnStatus)}.`);
1828
+ }
1829
+ if (!finalMessage) {
1830
+ const diagnostic = codexWorkflowMissingFinalMessageDiagnostic({
1831
+ frames,
1832
+ rootThreadId,
1833
+ turnStatus,
1834
+ usage,
1835
+ });
1836
+ throw new Error(`codex-workflow root turn did not produce a final message: ${JSON.stringify(diagnostic)}.`);
1837
+ }
1838
+ if (setup) {
1839
+ setup = false;
1840
+ evaluation.measurementStart = evaluation.observe({ method: 'evaluation/measurementStart' }, rootThreadId);
1841
+ nextPrompt = input.request.prompt;
1842
+ continue;
1843
+ }
1844
+ if (evaluation?.hasPendingDecision) {
1845
+ const reply = await evaluation.reply({ text: finalMessage }, lastMessageOrdinal);
1846
+ if (reply) {
1847
+ nextPrompt = reply.text;
1848
+ continue;
1849
+ }
1850
+ }
1851
+ break;
1852
+ }
1853
+ const isolatedPaths = [
1854
+ { path: codexHome, marker: '[codex-home]' },
1855
+ { path: env.HOME, marker: '[isolated-home]' },
1856
+ { path: runtimeRoot, marker: '[runtime-root]' },
1857
+ ];
1858
+ const rollout = await normalizeCodexWorkflowRollouts({
1859
+ codexHome,
1860
+ rootThreadId,
1861
+ request: input.request,
1862
+ runtime: input.codexRuntime,
1863
+ appServerFrames: frames,
1864
+ isolatedPaths,
1865
+ });
1866
+ removeHandler();
1867
+ removeHandler = undefined;
1868
+ removeFailureHandler();
1869
+ removeFailureHandler = undefined;
1870
+ await session.cleanup();
1871
+ cleanupConfirmed = true;
1872
+ const after = await snapshotWorkspace(input.input.workspacePath);
1873
+ await temporaryRoot.cleanup();
1874
+ codexHomeRemoved = true;
1875
+ const evidence = {
1876
+ version: 1,
1877
+ rootThreadId,
1878
+ completion: {
1879
+ status: 'completed',
1880
+ turnStatus,
1881
+ durationMs: Date.now() - startedAt,
1882
+ },
1883
+ termination: {
1884
+ reason: 'completed',
1885
+ appServerProcessTreeCleanupConfirmed: true,
1886
+ },
1887
+ usage,
1888
+ finalMessage: finalMessage,
1889
+ workspaceDiff: workspaceDiff(before, after),
1890
+ skillLoads: rollout.skillLoads,
1891
+ threadGraph: rollout.threadGraph,
1892
+ collaborationCalls: rollout.collaborationCalls,
1893
+ leafCalls: rollout.leafCalls,
1894
+ cleanup: { isolatedCodexHomeRemoved: true },
1895
+ launch: {
1896
+ instructionChannel: 'developer', developerInstructions: input.request.developerInstructions,
1897
+ instructionsRevision: createHash('sha256').update(input.request.developerInstructions).digest('hex'),
1898
+ model: input.request.model, reasoningEffort: input.request.reasoningEffort ?? null,
1899
+ sandbox: input.request.sandbox, roles: input.request.roles, configOverrides,
1900
+ hostRoleAdoption: 'unknown',
1901
+ },
1902
+ };
1903
+ if (evaluation) {
1904
+ // Subject processes are stopped before final snapshots and independent grading.
1905
+ try {
1906
+ evidence.evaluation = await evaluation.finish(evidence, rollout.trace);
1907
+ }
1908
+ catch (error) {
1909
+ evidence.evaluation = {
1910
+ complete: false, checks: [], observations: evaluation.observations,
1911
+ snapshots: evaluation.snapshots, decisions: evaluation.decisions,
1912
+ fixtureActions: evaluation.fixtureActions,
1913
+ measurementStart: evaluation.measurementStart,
1914
+ };
1915
+ await writeWorkflowArtifacts(input.input.outputDir, {
1916
+ evidence: session.scrub(redactCodexEvalValue(evidence, input.codexRuntime)),
1917
+ trace: session.scrub(redactCodexEvalValue([...frames, ...rollout.trace], input.codexRuntime)),
1918
+ modelOutput: redactCodexEvalValue(finalMessage, input.codexRuntime),
1919
+ usage: redactCodexEvalValue(evidence.usage, input.codexRuntime),
1920
+ });
1921
+ throw error;
1922
+ }
1923
+ const totals = [usage, ...judgeUsage].map(standardUsage);
1924
+ if (totals.some((value) => value === null))
1925
+ throw new Error('workflow evaluation lacks required judge usage evidence.');
1926
+ evidence.usage = {
1927
+ inputTokens: totals.reduce((sum, value) => sum + value.inputTokens, 0),
1928
+ outputTokens: totals.reduce((sum, value) => sum + value.outputTokens, 0),
1929
+ };
1930
+ }
1931
+ return {
1932
+ evidence: session.scrub(redactCodexEvalValue(evidence, input.codexRuntime)),
1933
+ trace: session.scrub(redactCodexEvalValue([
1934
+ ...frames.map((frame) => sanitizeTraceValue(frame, input.codexRuntime, isolatedPaths, input.request.multiAgent.protocol === 'v2')),
1935
+ ...rollout.trace,
1936
+ ], input.codexRuntime)),
1937
+ modelOutput: redactCodexEvalValue(finalMessage, input.codexRuntime),
1938
+ usage: redactCodexEvalValue(evidence.usage, input.codexRuntime),
1939
+ };
1940
+ }
1941
+ finally {
1942
+ removeHandler?.();
1943
+ removeFailureHandler?.();
1944
+ let cleanupError;
1945
+ if (!cleanupConfirmed) {
1946
+ try {
1947
+ await session?.cleanup();
1948
+ }
1949
+ catch (error) {
1950
+ cleanupError = error;
1951
+ }
1952
+ }
1953
+ if (!codexHomeRemoved) {
1954
+ try {
1955
+ await temporaryRoot.cleanup();
1956
+ }
1957
+ catch (error) {
1958
+ cleanupError ??= error;
1959
+ }
1960
+ }
1961
+ control.dispose();
1962
+ if (cleanupError) {
1963
+ throw new Error(`codex-workflow cleanup failed: ${cleanupError instanceof Error ? cleanupError.message : String(cleanupError)}`);
1964
+ }
1965
+ }
1966
+ },
1967
+ };
1968
+ function standardUsage(value) {
1969
+ if (!isRecord(value))
1970
+ return null;
1971
+ const candidates = [value, ...['total', 'totalUsage', 'usage', 'tokenUsage']
1972
+ .map((key) => value[key]).filter(isRecord)];
1973
+ for (const candidate of candidates) {
1974
+ const input = candidate.inputTokens ?? candidate.input_tokens;
1975
+ const output = candidate.outputTokens ?? candidate.output_tokens;
1976
+ if (Number.isSafeInteger(input) && input >= 0
1977
+ && Number.isSafeInteger(output) && output >= 0)
1978
+ return { inputTokens: input, outputTokens: output };
1979
+ }
1980
+ return null;
1981
+ }
1982
+ async function writeWorkflowArtifacts(outputDir, execution) {
1983
+ await Promise.all([
1984
+ fs.writeFile(path.join(outputDir, CODEX_WORKFLOW_ARTIFACTS[0].path), `${JSON.stringify(execution.evidence, null, 2)}\n`, { flag: 'wx' }),
1985
+ fs.writeFile(path.join(outputDir, CODEX_WORKFLOW_ARTIFACTS[1].path), `${JSON.stringify(execution.trace, null, 2)}\n`, { flag: 'wx' }),
1986
+ fs.writeFile(path.join(outputDir, CODEX_WORKFLOW_ARTIFACTS[2].path), `${execution.modelOutput}\n`, { flag: 'wx' }),
1987
+ ]);
1988
+ }
1989
+ async function artifactDigests(outputDir) {
1990
+ return Promise.all(CODEX_WORKFLOW_ARTIFACTS.map(async (artifact) => hash(await fs.readFile(path.join(outputDir, artifact.path)))));
1991
+ }
1992
+ export async function executeCodexWorkflowProvider(options) {
1993
+ const platform = options.platform ?? options.processTree?.platform ?? process.platform;
1994
+ if (platform !== 'darwin' && platform !== 'linux') {
1995
+ throw new Error(`codex-workflow is unsupported on ${platform}; no model was started.`);
1996
+ }
1997
+ const paths = await assertProviderRunPaths({
1998
+ evalRoot: options.evalRoot,
1999
+ workspacePath: options.input.workspacePath,
2000
+ fixturesPath: options.input.fixturesPath,
2001
+ outputDir: options.input.outputDir,
2002
+ });
2003
+ const sourceEnv = options.sourceEnv ?? process.env;
2004
+ const processManagement = options.processManagement ?? {
2005
+ mode: 'managed',
2006
+ inspectionAvailable: true,
2007
+ userAcknowledged: false,
2008
+ descendantCleanupGuaranteed: false,
2009
+ limitation: 'Process-tree inspection verifies cooperative descendants, but it is not OS-enforced containment and does not guarantee descendant cleanup.',
2010
+ };
2011
+ const control = new CodexEvalAttemptControl({
2012
+ label: 'codex-workflow provider',
2013
+ timeoutMs: options.input.timeoutMs,
2014
+ ...(options.input.signal ? { signal: options.input.signal } : {}),
2015
+ });
2016
+ try {
2017
+ const common = {
2018
+ version: 1,
2019
+ case: options.input.case,
2020
+ variant: options.input.variant,
2021
+ attempt: options.input.attempt,
2022
+ };
2023
+ let prepared;
2024
+ try {
2025
+ prepared = await runAdapter({
2026
+ command: options.provider.command,
2027
+ evalRoot: paths.evalRoot,
2028
+ input: {
2029
+ ...common,
2030
+ phase: 'prepare',
2031
+ paths: {
2032
+ workspace: paths.workspacePath,
2033
+ fixtures: paths.fixturesPath,
2034
+ output: paths.outputDir,
2035
+ },
2036
+ },
2037
+ sourceEnv,
2038
+ timeoutMs: control.remaining('prepare'),
2039
+ signal: control.signal,
2040
+ processTree: options.processTree,
2041
+ processManagement,
2042
+ });
2043
+ }
2044
+ catch (error) {
2045
+ control.throwIfStopped('prepare');
2046
+ throw error;
2047
+ }
2048
+ const request = await parseCodexWorkflowRequest(prepared, options.provider.allowedModels, paths.workspacePath, paths.fixturesPath);
2049
+ if (!request.transport && request.model === null && request.multiAgent.protocol === 'v1') {
2050
+ request.model = 'gpt-5.6-luna';
2051
+ }
2052
+ let execution;
2053
+ try {
2054
+ execution = await (options.transport ?? (request.transport ? instructionV2Transport : defaultTransport)).run({
2055
+ request,
2056
+ input: { ...options.input, ...paths, timeoutMs: control.remaining('Codex execution') },
2057
+ evalRoot: paths.evalRoot,
2058
+ codexRuntime: options.codexRuntime,
2059
+ codexBin: options.codexBin ?? sourceEnv.AW_SKILL_EVAL_CODEX_BIN ?? 'codex',
2060
+ sourceEnv,
2061
+ timeoutMs: control.remaining('Codex execution'),
2062
+ processManagement,
2063
+ ...(options.processTree ? { processTree: options.processTree } : {}),
2064
+ ...(options.protectedEvalRoots ? { protectedEvalRoots: options.protectedEvalRoots } : {}),
2065
+ signal: control.signal,
2066
+ });
2067
+ }
2068
+ catch (error) {
2069
+ control.throwIfStopped('Codex execution');
2070
+ throw error;
2071
+ }
2072
+ const sanitized = redactCodexEvalValue(execution, options.codexRuntime);
2073
+ const usage = standardUsage(sanitized.usage);
2074
+ if (!request.transport && (!usage || !standardUsage(sanitized.evidence.usage))) {
2075
+ throw new Error('codex-workflow completed without required token usage evidence.');
2076
+ }
2077
+ await writeWorkflowArtifacts(paths.outputDir, sanitized);
2078
+ control.throwIfStopped('evidence collection');
2079
+ if (sanitized.evidence.completion.status !== 'completed') {
2080
+ throw new Error(`instruction-v2 workflow execution is ${sanitized.evidence.completion.status}; no grade was accepted.`);
2081
+ }
2082
+ const beforeGrade = await artifactDigests(paths.outputDir);
2083
+ let gradeValue;
2084
+ try {
2085
+ gradeValue = await runAdapter({
2086
+ command: options.provider.command,
2087
+ evalRoot: paths.evalRoot,
2088
+ input: {
2089
+ ...common,
2090
+ phase: 'grade',
2091
+ paths: {
2092
+ workspace: paths.workspacePath,
2093
+ fixtures: paths.fixturesPath,
2094
+ output: paths.outputDir,
2095
+ evidence: path.join(paths.outputDir, CODEX_WORKFLOW_ARTIFACTS[0].path),
2096
+ modelOutput: path.join(paths.outputDir, CODEX_WORKFLOW_ARTIFACTS[2].path),
2097
+ },
2098
+ },
2099
+ sourceEnv,
2100
+ timeoutMs: control.remaining('grade'),
2101
+ signal: control.signal,
2102
+ processTree: options.processTree,
2103
+ processManagement,
2104
+ });
2105
+ }
2106
+ catch (error) {
2107
+ control.throwIfStopped('grade');
2108
+ throw error;
2109
+ }
2110
+ const grade = parseEvalProviderGrade(gradeValue, 'workflow grade stdout');
2111
+ if (request.evaluation) {
2112
+ const results = sanitized.evidence.evaluation;
2113
+ const required = [
2114
+ ...request.evaluation.checks,
2115
+ ...(request.evaluation.interactions.length ? [{ name: 'user-decisions', criterion: 'All required user decisions were delivered.' }] : []),
2116
+ ];
2117
+ const complete = normalizeWorkflowChecks(required, {
2118
+ passed: true, score: null, details: results,
2119
+ }).every((check) => check.verdict === 'pass');
2120
+ if (!complete) {
2121
+ grade.passed = false;
2122
+ grade.score = 0;
2123
+ grade.details = { ...grade.details, workflowEvidenceSatisfied: false };
2124
+ }
2125
+ }
2126
+ const afterGrade = await artifactDigests(paths.outputDir);
2127
+ if (JSON.stringify(beforeGrade) !== JSON.stringify(afterGrade)) {
2128
+ throw new Error('workflow grade modified Engine-owned attempt artifacts.');
2129
+ }
2130
+ return {
2131
+ usage,
2132
+ grade,
2133
+ artifacts: CODEX_WORKFLOW_ARTIFACTS.map((artifact) => ({ ...artifact })),
2134
+ };
2135
+ }
2136
+ finally {
2137
+ control.dispose();
2138
+ }
2139
+ }
2140
+ //# sourceMappingURL=codex-workflow.js.map