243 lines
10 KiB
TypeScript
243 lines
10 KiB
TypeScript
import { configureTaskView, taskTraceSummaryFixtureResponse } from "../src/components/microservices/code-queue/src/task-view";
|
|
import { configureTaskOutput } from "../src/components/microservices/code-queue/src/task-output";
|
|
import { configureJudge } from "../src/components/microservices/code-queue/src/judge";
|
|
import type { OaTraceStepSummary } from "../src/components/microservices/code-queue/src/oa-events";
|
|
import type { JsonValue, PromptHistoryItem, QueueTask, QueuedStatusReason } from "../src/components/microservices/code-queue/src/types";
|
|
|
|
type JsonRecord = Record<string, unknown>;
|
|
|
|
function assertCondition(condition: unknown, message: string, detail: JsonRecord = {}): void {
|
|
if (!condition) throw new Error(`${message}: ${JSON.stringify(detail)}`);
|
|
}
|
|
|
|
function asRecord(value: unknown): JsonRecord | null {
|
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as JsonRecord : null;
|
|
}
|
|
|
|
function pageBySeq<T extends { seq: number }>(
|
|
items: T[],
|
|
_url: URL,
|
|
_limit: number,
|
|
): { mode: "tail" | "after" | "before"; afterSeq: number; beforeSeq: number | null; nextAfterSeq: number; previousBeforeSeq: number | null; hasMore: boolean; hasBefore: boolean; chunk: T[] } {
|
|
return {
|
|
mode: "tail",
|
|
afterSeq: 0,
|
|
beforeSeq: null,
|
|
nextAfterSeq: items.at(-1)?.seq ?? 0,
|
|
previousBeforeSeq: null,
|
|
hasMore: false,
|
|
hasBefore: false,
|
|
chunk: items,
|
|
};
|
|
}
|
|
|
|
function configureFixtureTaskView(): void {
|
|
configureTaskOutput({
|
|
config: { maxInMemoryOutputRecords: 1000, outputArchiveDir: "/tmp/code-queue-trace-summary-contract/output" },
|
|
allocateSeq: () => 1000,
|
|
errorToJson: (error: unknown): JsonValue => error instanceof Error ? { message: error.message } : String(error),
|
|
logger: () => undefined,
|
|
markTaskDirty: () => undefined,
|
|
nowIso: () => "2026-05-19T00:10:00.000Z",
|
|
schedulePersistState: () => undefined,
|
|
});
|
|
configureJudge({
|
|
config: {
|
|
minimaxApiKey: "",
|
|
minimaxApiBase: "",
|
|
minimaxModel: "minimax-m1",
|
|
judgeTimeoutMs: 1000,
|
|
judgeRepairAttempts: 0,
|
|
judgeMaxTokens: 1000,
|
|
},
|
|
logger: () => undefined,
|
|
safePreview: (value: string, max = 300) => value.length > max ? `${value.slice(0, max)}...` : value,
|
|
userPromptForDisplay: (prompt: string) => prompt,
|
|
taskFullOutput: (task: QueueTask) => task.output,
|
|
taskReferenceIds: (task: QueueTask) => task.referenceTaskIds,
|
|
extractRecord: (value: unknown) => typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : null,
|
|
extractString: (value: unknown, key: string) => {
|
|
const record = typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : null;
|
|
const item = record?.[key];
|
|
return typeof item === "string" ? item : null;
|
|
},
|
|
promptLineCount: (text: string) => text.length > 0 ? text.split(/\r\n|\r|\n/u).length : 0,
|
|
judgeFailRetryLimit: 3,
|
|
});
|
|
configureTaskView({
|
|
config: { codexHome: "/tmp/code-queue-trace-summary-contract" },
|
|
errorToJson: (error: unknown): JsonValue => error instanceof Error ? { message: error.message } : String(error),
|
|
jsonResponse: (body: unknown, status = 200): Response => Response.json(body, { status }),
|
|
logger: () => undefined,
|
|
mergePromptHistory: (items: PromptHistoryItem[]) => items,
|
|
nowIso: () => "2026-05-19T00:10:00.000Z",
|
|
outputPromptHistory: () => [],
|
|
pageBySeq,
|
|
parseLimit: () => 100,
|
|
parseSeqParam: () => null,
|
|
queueIdOf: (task: QueueTask) => task.queueId,
|
|
queuedStatusReason: (): QueuedStatusReason | null => null,
|
|
queuedTaskPromptEditable: () => false,
|
|
taskQueueEnteredAt: (task: QueueTask) => task.queueEnteredAt,
|
|
});
|
|
}
|
|
|
|
function fixtureTask(): QueueTask {
|
|
const at = "2026-05-19T00:00:00.000Z";
|
|
return {
|
|
id: "codex_trace_contract",
|
|
queueId: "default",
|
|
queueEnteredAt: at,
|
|
prompt: "Trace summary contract fixture",
|
|
basePrompt: "Trace summary contract fixture",
|
|
referenceTaskIds: [],
|
|
referenceInjection: null,
|
|
providerId: "D601",
|
|
cwd: "/workspace",
|
|
model: "gpt-5.5",
|
|
reasoningEffort: null,
|
|
executionMode: "default",
|
|
maxAttempts: 99,
|
|
status: "running",
|
|
createdAt: at,
|
|
updatedAt: "2026-05-19T00:06:30.000Z",
|
|
startedAt: at,
|
|
finishedAt: null,
|
|
readAt: null,
|
|
currentAttempt: 2,
|
|
currentMode: "retry",
|
|
codexThreadId: "thread_trace_contract",
|
|
activeTurnId: "turn_trace_contract",
|
|
finalResponse: "",
|
|
lastError: null,
|
|
lastJudge: { decision: "retry", confidence: 1, reason: "attempt 1 asked for retry", source: "fallback" },
|
|
judgeFailCount: 0,
|
|
promptHistory: [],
|
|
output: [
|
|
{ seq: 1, at, channel: "user", text: "Trace summary contract fixture", method: "enqueue" },
|
|
{ seq: 2, at: "2026-05-19T00:00:10.000Z", channel: "system", text: "attempt 1 / 99", method: "queue" },
|
|
{ seq: 3, at: "2026-05-19T00:01:00.000Z", channel: "command", text: "rg trace-summary src/components/microservices/code-queue/src", method: "item/started", itemId: "call-1" },
|
|
{ seq: 4, at: "2026-05-19T00:02:00.000Z", channel: "system", text: "judge=retry confidence=1 source=fallback: attempt 1 asked for retry", method: "judge" },
|
|
],
|
|
events: [],
|
|
attempts: [
|
|
{
|
|
index: 1,
|
|
mode: "initial",
|
|
startedAt: "2026-05-19T00:00:10.000Z",
|
|
finishedAt: "2026-05-19T00:02:00.000Z",
|
|
terminalStatus: "completed",
|
|
transportClosedBeforeTerminal: false,
|
|
appServerExitCode: 0,
|
|
appServerSignal: null,
|
|
error: null,
|
|
finalResponse: "Attempt 1 response",
|
|
finalResponsePreview: "Attempt 1 response",
|
|
finalResponseChars: 18,
|
|
stderrTail: "",
|
|
judge: { decision: "retry", confidence: 1, reason: "attempt 1 asked for retry", source: "fallback" },
|
|
judgeAt: "2026-05-19T00:02:00.000Z",
|
|
judgeSeq: 4,
|
|
outputStartSeq: 2,
|
|
outputEndSeq: 4,
|
|
},
|
|
],
|
|
cancelRequested: false,
|
|
nextPrompt: null,
|
|
nextMode: null,
|
|
};
|
|
}
|
|
|
|
function attempt2Steps(): OaTraceStepSummary[] {
|
|
return [
|
|
{
|
|
eventSequence: 20,
|
|
seq: 20,
|
|
at: "2026-05-19T00:06:00.000Z",
|
|
kind: "ran",
|
|
title: "Run",
|
|
status: "item/started",
|
|
summaryLines: ["attempt 2 / 99", "pnpm test"],
|
|
rawSeqs: [20],
|
|
scopeId: "task:codex_trace_contract:attempt:2",
|
|
attemptIndex: 2,
|
|
source: "oa-event-flow",
|
|
},
|
|
{
|
|
eventSequence: 21,
|
|
seq: 21,
|
|
at: "2026-05-19T00:06:20.000Z",
|
|
kind: "explored",
|
|
title: "Read",
|
|
status: "item/completed",
|
|
summaryLines: ["src/components/microservices/code-queue/src/task-view.ts"],
|
|
rawSeqs: [21],
|
|
scopeId: "task:codex_trace_contract:attempt:2",
|
|
attemptIndex: 2,
|
|
source: "oa-event-flow",
|
|
},
|
|
];
|
|
}
|
|
|
|
export function runCodeQueueTraceSummaryContract(): JsonRecord {
|
|
configureFixtureTaskView();
|
|
const task = fixtureTask();
|
|
const steps = attempt2Steps();
|
|
const summary = taskTraceSummaryFixtureResponse(task, {
|
|
stats: null,
|
|
taskStats: null,
|
|
allSteps: [
|
|
{
|
|
eventSequence: 1,
|
|
seq: 1,
|
|
at: "2026-05-19T00:00:10.000Z",
|
|
kind: "message",
|
|
title: "Assistant message",
|
|
status: "item/completed",
|
|
summaryLines: ["Attempt 1 judge complete"],
|
|
rawSeqs: [4],
|
|
scopeId: "task:codex_trace_contract",
|
|
attemptIndex: null,
|
|
source: "oa-event-flow",
|
|
},
|
|
...steps,
|
|
],
|
|
attemptSteps: new Map([[2, steps]]),
|
|
}) as JsonRecord;
|
|
const attempts = Array.isArray(summary.attempts) ? summary.attempts.map(asRecord).filter((item): item is JsonRecord => item !== null) : [];
|
|
const attempt2 = attempts.find((attempt) => Number(attempt.index) === 2) ?? null;
|
|
const taskStats = asRecord(summary.traceStats);
|
|
const taskExecution = asRecord(summary.execution);
|
|
const attempt2Stats = asRecord(attempt2?.traceStats);
|
|
const attempt2Execution = asRecord(attempt2?.execution);
|
|
|
|
assertCondition(summary.currentAttempt === 2, "summary must retain currentAttempt=2", summary);
|
|
assertCondition(summary.statsSource === "raw-trace-fallback", "summary must distinguish raw trace fallback from empty STEP", summary);
|
|
assertCondition(summary.traceStatsState === "degraded", "summary must mark OA stats sync degraded", summary);
|
|
assertCondition(summary.traceStatsReason === "oa-event-flow-stats-unavailable-raw-trace-present", "summary must explain degraded OA sync", summary);
|
|
assertCondition(taskStats?.source === "oa-event-flow" && taskStats?.sourceHint === "raw-trace-fallback", "summary must expose countable synthetic stats with source hint", taskStats ?? {});
|
|
assertCondition(taskExecution?.statsSource === "oa-event-flow" && taskExecution?.traceStatsState === "degraded", "execution summary must stay countable while degraded", taskExecution ?? {});
|
|
assertCondition(Number(summary.stepCount ?? 0) > 0, "summary fallback STEP count must be visible", summary);
|
|
assertCondition(attempt2 !== null, "summary must materialize the latest running retry attempt", { attempts });
|
|
assertCondition(Number(attempt2?.stepCount ?? 0) > 0, "attempt 2 must expose live fallback STEP count", attempt2 ?? {});
|
|
assertCondition(attempt2Stats?.source === "oa-event-flow" && attempt2Stats?.sourceHint === "raw-trace-fallback", "attempt 2 fallback stats must remain countable", attempt2Stats ?? {});
|
|
assertCondition(attempt2Execution?.statsSource === "oa-event-flow" && attempt2Execution?.traceStatsState === "degraded", "attempt 2 execution must be countable while degraded", attempt2Execution ?? {});
|
|
|
|
return {
|
|
ok: true,
|
|
checks: [
|
|
{ name: "code-queue:trace-summary-latest-attempt-visible", ok: true },
|
|
{ name: "code-queue:trace-summary-raw-trace-step-fallback", ok: true },
|
|
],
|
|
taskId: task.id,
|
|
stepCount: summary.stepCount,
|
|
statsSource: summary.statsSource,
|
|
traceStatsState: summary.traceStatsState,
|
|
attempt2StepCount: attempt2?.stepCount,
|
|
};
|
|
}
|
|
|
|
if (import.meta.main) {
|
|
process.stdout.write(`${JSON.stringify(runCodeQueueTraceSummaryContract(), null, 2)}\n`);
|
|
}
|