agent-ecosystem/test/main/services/team/CodexNativeTraceReader.test.ts

239 lines
6.8 KiB
TypeScript

import { mkdtemp, mkdir, rm, writeFile } from 'fs/promises';
import { tmpdir } from 'os';
import path from 'path';
import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { CodexNativeTraceReader } from '../../../../src/main/services/team/taskLogs/stream/CodexNativeTraceReader';
const TRACE_ROOT_SEGMENT = path.join('.member-work-sync', 'runtime-hooks', 'codex-native-traces');
let teamsBasePath: string;
function traceSegment(value: string): string {
return encodeURIComponent(value);
}
async function writeTraceFile(params: {
bucket: 'incoming' | 'processed';
teamName: string;
taskId: string;
runId: string;
records: Array<Record<string, unknown>>;
suffix?: '.jsonl' | '.jsonl.tmp';
}): Promise<string> {
const dir = path.join(
teamsBasePath,
TRACE_ROOT_SEGMENT,
params.bucket,
traceSegment(params.teamName),
traceSegment(params.taskId)
);
await mkdir(dir, { recursive: true });
const absolutePath = path.join(dir, `${params.runId}${params.suffix ?? (params.bucket === 'incoming' ? '.jsonl.tmp' : '.jsonl')}`);
await writeFile(
absolutePath,
`${params.records.map((record) => JSON.stringify(record)).join('\n')}\n`,
'utf8'
);
return absolutePath;
}
function header(overrides: Partial<Record<string, unknown>> = {}): Record<string, unknown> {
return {
schemaVersion: 1,
recordType: 'codex_native_trace_header',
runId: 'run-1',
teamName: 'vector-room-131313',
taskId: '8421e1bb-2f3b-4656-9983-6e0fd4b15963',
ownerName: 'atlas',
provider: 'codex',
cwd: '/repo',
startedAt: '2026-05-01T17:10:07.799Z',
...overrides,
};
}
describe('CodexNativeTraceReader', () => {
beforeEach(async () => {
teamsBasePath = await mkdtemp(path.join(tmpdir(), 'codex-native-trace-reader-'));
});
afterEach(async () => {
await rm(teamsBasePath, { recursive: true, force: true });
});
it('reads projection records and prefers processed trace over duplicate incoming run', async () => {
const teamName = 'vector-room-131313';
const taskId = '8421e1bb-2f3b-4656-9983-6e0fd4b15963';
await writeTraceFile({
bucket: 'incoming',
teamName,
taskId,
runId: 'run-1',
records: [
header(),
{
schemaVersion: 1,
recordType: 'codex_native_stdout_event',
receivedAt: '2026-05-01T17:10:08.000Z',
sourceOrder: 1,
projection: {
kind: 'tool_result',
toolSource: 'native',
rawItemType: 'command_execution',
itemId: 'item_1',
toolName: 'Bash',
input: { command: 'pwd' },
result: { content: 'incoming' },
},
},
],
});
await writeTraceFile({
bucket: 'processed',
teamName,
taskId,
runId: 'run-1',
records: [
header(),
{
schemaVersion: 1,
recordType: 'codex_native_stdout_event',
receivedAt: '2026-05-01T17:10:08.000Z',
sourceOrder: 1,
projection: {
kind: 'tool_result',
toolSource: 'native',
rawItemType: 'command_execution',
itemId: 'item_1',
toolName: 'Bash',
input: { command: 'pwd' },
result: { content: 'processed' },
},
},
],
});
const runs = await new CodexNativeTraceReader(teamsBasePath).readTaskRuns({
teamName,
taskIds: [taskId, '8421e1bb'],
includeIncoming: true,
});
expect(runs).toHaveLength(1);
expect(runs[0]?.partial).toBe(false);
expect(runs[0]?.events[0]?.projection).toMatchObject({
kind: 'tool_result',
toolSource: 'native',
toolName: 'Bash',
result: { content: 'processed' },
});
});
it('falls back to raw Codex command/file events and ignores malformed trailing incoming line', async () => {
const teamName = 'vector-room-131313';
const taskId = '891e1f68-d5b0-40f7-aa48-c378607e0f3b';
const dir = path.join(
teamsBasePath,
TRACE_ROOT_SEGMENT,
'incoming',
traceSegment(teamName),
traceSegment(taskId)
);
await mkdir(dir, { recursive: true });
await writeFile(
path.join(dir, 'run-raw.jsonl.tmp'),
[
JSON.stringify(header({ runId: 'run-raw', taskId, ownerName: 'jack' })),
JSON.stringify({
schemaVersion: 1,
recordType: 'codex_native_stdout_event',
receivedAt: '2026-05-01T17:19:36.000Z',
sourceOrder: 1,
raw: {
type: 'item.completed',
item: {
id: 'item_1',
type: 'command_execution',
command: 'pwd',
aggregated_output: '/repo\n',
exit_code: 2,
status: 'completed',
},
},
}),
JSON.stringify({
schemaVersion: 1,
recordType: 'codex_native_stdout_event',
receivedAt: '2026-05-01T17:19:37.000Z',
sourceOrder: 2,
raw: {
type: 'item.completed',
item: {
id: 'item_2',
type: 'file_change',
changes: [{ path: '/repo/src/a.ts', kind: 'update' }],
status: 'completed',
},
},
}),
'{"schemaVersion":1,"recordType":"codex_native_stdout_event"',
].join('\n'),
'utf8'
);
const runs = await new CodexNativeTraceReader(teamsBasePath).readTaskRuns({
teamName,
taskIds: [taskId, '891e1f68'],
includeIncoming: true,
});
expect(runs).toHaveLength(1);
expect(runs[0]?.partial).toBe(true);
expect(runs[0]?.events.map((event) => event.projection?.toolName)).toEqual(['Bash', 'Edit']);
expect(runs[0]?.events[0]?.projection).toMatchObject({
kind: 'tool_result',
rawItemType: 'command_execution',
result: {
content: '/repo\n',
stdout: '/repo\n',
exitCode: 2,
},
isError: true,
});
});
it('rejects trace files whose header belongs to another team or task', async () => {
const teamName = 'vector-room-131313';
const taskId = '8421e1bb-2f3b-4656-9983-6e0fd4b15963';
await writeTraceFile({
bucket: 'processed',
teamName,
taskId,
runId: 'run-wrong-team',
records: [
header({ teamName: 'another-team', runId: 'run-wrong-team' }),
{
schemaVersion: 1,
recordType: 'codex_native_stdout_event',
receivedAt: '2026-05-01T17:10:08.000Z',
sourceOrder: 1,
projection: {
kind: 'tool_result',
toolSource: 'native',
itemId: 'item_1',
toolName: 'Bash',
},
},
],
});
await expect(
new CodexNativeTraceReader(teamsBasePath).readTaskRuns({
teamName,
taskIds: [taskId],
})
).resolves.toEqual([]);
});
});