Files
pikasTech-unidesk/src/components/microservices/code-queue/src/task-view.ts
T
2026-05-17 10:05:47 +00:00

2537 lines
106 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// 重构前 index.ts 只读参考:commit 6a04144d3f5103014f75b637d7e6bc2f45bf007fblob 56e590c1a6b5ca7ad128bf2c992f60e46c355a58;可用 `git show 6a04144d3f5103014f75b637d7e6bc2f45bf007f:src/components/microservices/code-queue/src/index.ts` 查看。
import { existsSync, readdirSync, readFileSync, statSync, type Dirent } from "node:fs";
import { resolve } from "node:path";
import type {
AttemptSummary,
FeedbackPromptRecord,
JudgeDecision,
JudgeResult,
JsonValue,
LiveOutput,
PromptHistoryItem,
QueueTask,
QueuedStatusReason,
RuntimeConfig,
SessionCommandOutput,
SessionFileChange,
TranscriptKind,
TranscriptLine,
} from "./types";
import { codeAgentPortForModel, codeAgentPortInfo, codeExecutionModeInfo, extractRecord } from "./code-agent/common";
import { currentTaskPromptMarker, resolvedReferenceContextTitle, stripCodeQueueEnvironmentHint, userPromptForDisplay } from "./prompts";
import { outputArchiveSignature, taskFullOutput } from "./task-output";
import { retryPrompt } from "./judge";
import { readOaTraceStepsForTask, type OaTraceStepSummary } from "./oa-events";
export interface TaskViewContext {
config: Pick<RuntimeConfig, "codexHome">;
errorToJson: (error: unknown) => JsonValue;
jsonResponse: (body: unknown, status?: number) => Response;
logger: (level: "debug" | "info" | "warn" | "error", message: string, data?: JsonValue) => void;
mergePromptHistory: (items: PromptHistoryItem[]) => PromptHistoryItem[];
nowIso: () => string;
outputPromptHistory: (task: QueueTask) => PromptHistoryItem[];
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[];
};
parseLimit: (url: URL) => number;
parseSeqParam: (url: URL, name: string, defaultValue: number | null) => number | null;
queueIdOf: (task: QueueTask) => string;
queuedStatusReason: (task: QueueTask) => QueuedStatusReason | null;
queuedTaskPromptEditable: (task: QueueTask) => boolean;
taskQueueEnteredAt: (task: QueueTask) => string;
}
function isCodexToolLifecycleOutput(output: Pick<LiveOutput, "channel" | "method" | "itemId" | "text">): boolean {
if (output.channel !== "tool" || typeof output.itemId !== "string" || output.itemId.length === 0) return false;
const method = String(output.method || "");
if (method !== "item/started" && method !== "item/completed") return false;
return /\b(?:webSearch|mcpToolCall|dynamicToolCall)\b/u.test(String(output.text || ""));
}
function codexToolLifecycleStartedBeforeIn(outputs: Pick<LiveOutput, "channel" | "method" | "itemId" | "text">[], output: Pick<LiveOutput, "channel" | "method" | "itemId" | "text">): boolean {
if (!isCodexToolLifecycleOutput(output)) return false;
return outputs.some((item) => item !== output && isCodexToolLifecycleOutput(item) && item.itemId === output.itemId && item.method === "item/started");
}
const judgeFailRetryLimit = 3;
const transcriptCache = new Map<string, { signature: string; previewTranscript?: TranscriptLine[]; fullTranscript?: TranscriptLine[] }>();
const codexSessionPathCache = new Map<string, string>();
const codexSessionFileChangeCache = new Map<string, { signature: string; changes: Map<string, SessionFileChange> }>();
const codexSessionCommandOutputCache = new Map<string, { signature: string; outputs: Map<string, SessionCommandOutput> }>();
let context: TaskViewContext | null = null;
export function configureTaskView(runtimeContext: TaskViewContext): void {
context = runtimeContext;
}
function ctx(): TaskViewContext {
if (context === null) throw new Error("task-view module is not configured");
return context;
}
function taskTimestamp(value: string | null): string | null {
if (value === null || value.length === 0) return null;
const time = Date.parse(value);
return Number.isFinite(time) ? new Date(time).toISOString() : null;
}
function terminalTask(task: QueueTask): boolean {
return task.status === "succeeded" || task.status === "failed" || task.status === "canceled";
}
function terminalTaskUnread(task: QueueTask): boolean {
return terminalTask(task) && task.readAt === null;
}
function queuedStatusPayload(task: QueueTask): { queuedReason: QueuedStatusReason | null; queuedReasonLabel: string | null } {
const reason = ctx().queuedStatusReason(task);
return {
queuedReason: reason,
queuedReasonLabel: reason?.label ?? null,
};
}
function durationMsBetween(startAt: string | null | undefined, endAt: string | null | undefined): number | null {
const start = typeof startAt === "string" ? Date.parse(startAt) : NaN;
const end = typeof endAt === "string" ? Date.parse(endAt) : NaN;
if (!Number.isFinite(start) || !Number.isFinite(end)) return null;
return Math.max(0, end - start);
}
function safePreview(value: string, max = 900): string {
const compact = value.replace(/\s+/gu, " ").trim();
return compact.length > max ? `${compact.slice(0, max)}...` : compact;
}
function prefixPreview(value: string, max = 900): string {
const trimmed = value.trim();
return trimmed.length > max ? `${trimmed.slice(0, max)}...` : trimmed;
}
function linePreview(text: string, maxLines: number, maxChars: number): { text: string; omittedLines: number } {
const clean = text.replace(/\u001b\[[0-9;]*m/gu, "").trimEnd();
if (clean.length === 0) return { text: "", omittedLines: 0 };
const lines = clean.split(/\r?\n/u);
const kept: string[] = [];
let chars = 0;
for (const line of lines) {
if (kept.length >= maxLines || chars + line.length > maxChars) break;
kept.push(line);
chars += line.length + 1;
}
return { text: kept.join("\n"), omittedLines: Math.max(0, lines.length - kept.length) };
}
function completeTraceText(text: string): { text: string; omittedLines: number } {
return { text: text.replace(/\u001b\[[0-9;]*m/gu, "").trimEnd(), omittedLines: 0 };
}
function editedOutputPreview(text: string): { text: string; omittedLines: number } {
return linePreview(compactTranscriptBody(text), 120, 24_000);
}
function codexSessionDateDir(value: string | null | undefined): string | null {
if (typeof value !== "string" || value.length === 0) return null;
const date = new Date(value);
if (Number.isNaN(date.getTime())) return null;
return resolve(
ctx().config.codexHome,
"sessions",
String(date.getUTCFullYear()),
String(date.getUTCMonth() + 1).padStart(2, "0"),
String(date.getUTCDate()).padStart(2, "0"),
);
}
function codexSessionSignature(path: string): string | null {
try {
const stat = statSync(path);
return `${stat.size}:${Math.floor(stat.mtimeMs)}`;
} catch {
return null;
}
}
function findCodexSessionFile(task: QueueTask): string | null {
const threadId = task.codexThreadId;
if (threadId === null || threadId.length === 0) return null;
const cached = codexSessionPathCache.get(threadId);
if (cached !== undefined && existsSync(cached)) return cached;
const roots = Array.from(new Set([
codexSessionDateDir(task.startedAt),
codexSessionDateDir(task.createdAt),
codexSessionDateDir(task.updatedAt),
resolve(ctx().config.codexHome, "sessions"),
].filter((value): value is string => value !== null && existsSync(value))));
const matches: string[] = [];
let scanned = 0;
const scan = (dir: string, depth: number): void => {
if (depth < 0 || scanned > 4000) return;
scanned += 1;
let entries: Dirent[];
try {
entries = readdirSync(dir, { withFileTypes: true });
} catch {
return;
}
for (const entry of entries) {
const path = resolve(dir, entry.name);
if (entry.isDirectory()) {
scan(path, depth - 1);
} else if (entry.isFile() && entry.name.endsWith(".jsonl") && entry.name.includes(threadId)) {
matches.push(path);
}
}
};
for (const root of roots) scan(root, root.endsWith("/sessions") ? 4 : 1);
matches.sort((left, right) => (statMtimeMs(right) - statMtimeMs(left)) || right.localeCompare(left));
const match = matches[0] ?? null;
if (match !== null) codexSessionPathCache.set(threadId, match);
return match;
}
function statMtimeMs(path: string): number {
try {
return statSync(path).mtimeMs;
} catch {
return 0;
}
}
function parseCodexToolOutputText(raw: string): string {
const text = String(raw || "");
try {
const parsed = JSON.parse(text) as Record<string, unknown>;
if (typeof parsed.output === "string") return parsed.output;
} catch {
// Keep the raw tool output if it is not the JSON wrapper used by Codex.
}
return text;
}
function recordStringField(record: Record<string, unknown> | null, keys: string[]): string {
if (record === null) return "";
for (const key of keys) {
const value = record[key];
if (typeof value === "string") return value;
}
return "";
}
function recordNumberField(record: Record<string, unknown> | null, keys: string[]): number | null {
if (record === null) return null;
for (const key of keys) {
const value = Number(record[key]);
if (Number.isFinite(value)) return value;
}
return null;
}
function parseToolOutputStreams(raw: string): { stdout: string; stderr: string; output: string; exitCode: number | null } {
const text = String(raw || "");
try {
const parsed = JSON.parse(text) as unknown;
const record = extractRecord(parsed);
const stdout = recordStringField(record, ["stdout", "stdoutText"]);
const stderr = recordStringField(record, ["stderr", "stderrText"]);
const output = recordStringField(record, ["output", "text"]);
const exitCode = recordNumberField(record, ["exitCode", "code", "status"]);
if (stdout.length > 0 || stderr.length > 0 || output.length > 0) {
return { stdout: stdout || output, stderr, output: output || [stdout, stderr].filter(Boolean).join("\n"), exitCode };
}
} catch {
// Not a JSON wrapper; parse the Codex CLI tool-result envelope below.
}
const outputMatch = /(?:^|\n)Output:\n([\s\S]*)$/u.exec(text);
const exitMatch = /Process exited with code\s+(-?\d+)/u.exec(text);
if (outputMatch !== null) {
const output = (outputMatch[1] ?? "").trimEnd();
return {
stdout: output,
stderr: "",
output,
exitCode: exitMatch === null ? null : Number(exitMatch[1]),
};
}
return { stdout: text, stderr: "", output: text, exitCode: exitMatch === null ? null : Number(exitMatch[1]) };
}
function formatCommandOutput(output: SessionCommandOutput | null | undefined): string {
if (output === null || output === undefined) return "";
const parts: string[] = [];
const stdout = output.stdout.trimEnd();
const stderr = output.stderr.trimEnd();
if (stdout.length > 0) parts.push(stderr.length > 0 ? `[stdout]\n${stdout}` : stdout);
if (stderr.length > 0) parts.push(`[stderr]\n${stderr}`);
if (parts.length > 0) return parts.join("\n");
return output.output.trimEnd();
}
function addCommandOutputStreams(line: TranscriptLine, output: SessionCommandOutput | null | undefined, fullText: boolean): TranscriptLine {
if (output === null || output === undefined) return line;
const stdout = output.stdout.trimEnd();
const stderr = output.stderr.trimEnd();
if (stdout.length > 0) {
const preview = fullText ? completeTraceText(stdout) : outputPreview(stdout);
if (preview.text.length > 0) {
line.stdoutPreview = preview.text;
line.stdoutOmittedLines = preview.omittedLines || undefined;
}
}
if (stderr.length > 0) {
const preview = fullText ? completeTraceText(stderr) : outputPreview(stderr);
if (preview.text.length > 0) {
line.stderrPreview = preview.text;
line.stderrOmittedLines = preview.omittedLines || undefined;
}
}
return line;
}
function parseCodexSessionCommandOutputs(path: string): Map<string, SessionCommandOutput> {
const signature = codexSessionSignature(path);
const cached = codexSessionCommandOutputCache.get(path);
if (signature !== null && cached?.signature === signature) return cached.outputs;
const outputs = new Map<string, SessionCommandOutput>();
try {
const text = readFileSync(path, "utf8");
for (const line of text.split(/\r?\n/u)) {
if (line.trim().length === 0) continue;
let record: Record<string, unknown>;
try {
record = JSON.parse(line) as Record<string, unknown>;
} catch {
continue;
}
const payload = extractRecord(record.payload);
if (payload === null) continue;
const type = String(payload.type || "");
if (type !== "function_call_output" && type !== "custom_tool_call_output") continue;
const callId = typeof payload.call_id === "string" ? payload.call_id : "";
if (callId.length === 0) continue;
const parsed = parseToolOutputStreams(String(payload.output || ""));
if (parsed.output.trim().length === 0 && parsed.stdout.trim().length === 0 && parsed.stderr.trim().length === 0) continue;
outputs.set(callId, {
callId,
at: typeof record.timestamp === "string" ? record.timestamp : "",
stdout: parsed.stdout,
stderr: parsed.stderr,
output: parsed.output,
exitCode: parsed.exitCode,
});
}
} catch (error) {
ctx().logger("warn", "codex_session_command_output_parse_failed", { path, error: ctx().errorToJson(error) });
}
if (signature !== null) codexSessionCommandOutputCache.set(path, { signature, outputs });
if (codexSessionCommandOutputCache.size > 40) {
const firstKey = codexSessionCommandOutputCache.keys().next().value;
if (typeof firstKey === "string") codexSessionCommandOutputCache.delete(firstKey);
}
return outputs;
}
function codexSessionCommandOutputsByCallId(task: QueueTask): Map<string, SessionCommandOutput> {
const path = findCodexSessionFile(task);
return path === null ? new Map() : parseCodexSessionCommandOutputs(path);
}
function isInlineFileChangeInput(name: string, input: string): boolean {
return name === "apply_patch" && /^\*\*\* Begin Patch/mu.test(input);
}
function trimmedPatchForTrace(input: string): string {
const normalized = input.replace(/\r\n/gu, "\n").replace(/\r/gu, "\n").trimEnd();
const maxChars = 120_000;
if (normalized.length <= maxChars) return normalized;
return `${normalized.slice(0, maxChars)}\n...[patch content truncated: ${normalized.length - maxChars} chars omitted]`;
}
function parseCodexSessionFileChanges(path: string): Map<string, SessionFileChange> {
const signature = codexSessionSignature(path);
const cached = codexSessionFileChangeCache.get(path);
if (signature !== null && cached?.signature === signature) return cached.changes;
const changes = new Map<string, SessionFileChange>();
try {
const text = readFileSync(path, "utf8");
for (const line of text.split(/\r?\n/u)) {
if (line.trim().length === 0) continue;
let record: Record<string, unknown>;
try {
record = JSON.parse(line) as Record<string, unknown>;
} catch {
continue;
}
const payload = extractRecord(record.payload);
if (payload === null) continue;
const type = String(payload.type || "");
const callId = typeof payload.call_id === "string" ? payload.call_id : "";
if (callId.length === 0) continue;
if (type === "custom_tool_call") {
const name = String(payload.name || "");
const input = typeof payload.input === "string" ? payload.input : "";
if (!isInlineFileChangeInput(name, input)) continue;
changes.set(callId, {
callId,
at: typeof record.timestamp === "string" ? record.timestamp : "",
name,
input: trimmedPatchForTrace(input),
output: "",
});
continue;
}
if (type === "custom_tool_call_output") {
const existing = changes.get(callId);
if (existing === undefined) continue;
existing.output = parseCodexToolOutputText(String(payload.output || ""));
}
}
} catch (error) {
ctx().logger("warn", "codex_session_file_change_parse_failed", { path, error: ctx().errorToJson(error) });
}
if (signature !== null) codexSessionFileChangeCache.set(path, { signature, changes });
if (codexSessionFileChangeCache.size > 40) {
const firstKey = codexSessionFileChangeCache.keys().next().value;
if (typeof firstKey === "string") codexSessionFileChangeCache.delete(firstKey);
}
return changes;
}
function codexSessionFileChangesByCallId(task: QueueTask): Map<string, SessionFileChange> {
const path = findCodexSessionFile(task);
return path === null ? new Map() : parseCodexSessionFileChanges(path);
}
function fileChangeTextWithInlinePatch(item: LiveOutput, changes: Map<string, SessionFileChange>): string {
if (item.method !== "item/fileChange/outputDelta" || typeof item.itemId !== "string") return item.text;
const change = changes.get(item.itemId);
if (change === undefined || change.input.length === 0 || item.text.includes(change.input)) return item.text;
return `${item.text.trimEnd()}\n\n${change.input}\n`;
}
function compactNoisyLine(line: string): string {
const compact = line.replace(/\s+/gu, " ").trimEnd();
const hasEncodedBlob = /[A-Za-z0-9+/=]{220,}/u.test(compact);
const hasSshWrapper = compact.includes("UNIDESK_SSH_TOOL_DIR") || compact.includes("apply_patch") || compact.includes("base64 -d");
if (compact.length > 420 && (hasEncodedBlob || hasSshWrapper)) {
return `${compact.slice(0, 220)} ... [omitted noisy wrapper, ${compact.length - 220} chars]`;
}
return compact.length > 1200 ? `${compact.slice(0, 900)} ... [omitted ${compact.length - 900} chars]` : line;
}
function compactTranscriptBody(text: string): string {
return text.split(/\r?\n/u).map(compactNoisyLine).join("\n");
}
function parseCommandLine(text: string): { command: string; status?: string } | null {
const match = text.match(/^item\/(?:started|completed):\s+([\s\S]*?)\s+status=([A-Za-z0-9_-]+)/u);
if (match === null) return null;
return { command: match[1]?.trim() ?? "", status: match[2] };
}
function extractOuterQuotedShellArg(text: string): { body: string; trailing: string } | null {
const quote = text[0];
if (quote !== "\"" && quote !== "'") return null;
let escaped = false;
for (let index = 1; index < text.length; index += 1) {
const char = text[index] ?? "";
if (quote === "\"" && escaped) {
escaped = false;
continue;
}
if (quote === "\"" && char === "\\") {
escaped = true;
continue;
}
if (char === quote) return { body: text.slice(1, index), trailing: text.slice(index + 1).trim() };
}
return null;
}
function decodeShellDoubleQuoted(text: string): string {
return text
.replace(/\\(["\\$`])/gu, "$1")
.replace(/\\\r?\n/gu, "");
}
function displayCommand(command: string): string {
const normalized = command.trim().replace(/\\n/gu, "\n");
const match = normalized.match(/^(?:\/usr\/bin\/env\s+)?(?:\/(?:usr\/)?bin\/)?(?:bash|sh)\s+-lc\s+([\s\S]+)$/u);
if (match === null) return normalized;
const shellText = (match[1] ?? "").trim();
const shellArg = extractOuterQuotedShellArg(shellText);
if (shellArg === null) return (match[1] ?? normalized).trim();
const quote = shellText[0];
const body = quote === "\"" ? decodeShellDoubleQuoted(shellArg.body) : shellArg.body;
return shellArg.trailing.length > 0 ? `${body} ${shellArg.trailing}` : body;
}
function shortCommandTitle(command: string): string {
const firstLine = displayCommand(command).split(/\r?\n/u).find((line) => line.trim().length > 0)?.trim() ?? command.trim();
return safePreview(firstLine, 180);
}
function commandPreview(command: string): { text: string; omittedLines: number } {
return linePreview(compactTranscriptBody(displayCommand(command)), 10, 8000);
}
function outputPreview(text: string): { text: string; omittedLines: number } {
return linePreview(compactTranscriptBody(text), 4, 1600);
}
function fullMessageBody(text: string): { text: string; omittedLines: number } {
return linePreview(text.replace(/\u001b\[[0-9;]*m/gu, "").trimEnd(), 12, 4000);
}
function timestampMs(value: string | null | undefined): number | null {
if (typeof value !== "string" || value.length === 0) return null;
const time = Date.parse(value);
return Number.isFinite(time) ? time : null;
}
function nonNegativeElapsed(startMs: number | null, endMs: number | null): number | null {
if (startMs === null || endMs === null) return null;
return Math.max(0, endMs - startMs);
}
function taskTiming(task: QueueTask): JsonValue {
const nowMs = Date.now();
const createdMs = timestampMs(task.createdAt);
const startedMs = timestampMs(task.startedAt);
const finishedMs = timestampMs(task.finishedAt);
const updatedMs = timestampMs(task.updatedAt);
const terminal = task.status === "succeeded" || task.status === "failed" || task.status === "canceled";
const effectiveEndMs = finishedMs ?? (terminal ? updatedMs : nowMs);
return {
queueWaitMs: nonNegativeElapsed(createdMs, startedMs ?? (terminal ? effectiveEndMs : nowMs)),
durationMs: nonNegativeElapsed(startedMs, effectiveEndMs),
totalElapsedMs: nonNegativeElapsed(createdMs, effectiveEndMs),
effectiveEndAt: finishedMs !== null ? task.finishedAt : terminal ? task.updatedAt : null,
running: !terminal && startedMs !== null,
};
}
const codexStatsTimeZone = "Asia/Shanghai";
const codexStatsDateFormatter = new Intl.DateTimeFormat("en-CA", {
timeZone: codexStatsTimeZone,
year: "numeric",
month: "2-digit",
day: "2-digit",
});
interface DailyTaskStatsBucket {
date: string;
executedTasks: number;
completedTasks: number;
retryAttempts: number;
succeededTasks: number;
failedTasks: number;
canceledTasks: number;
totalDurationMs: number;
durationSamples: number;
}
function codexStatsDateKey(value: string | Date | null | undefined): string | null {
const date = value instanceof Date ? value : typeof value === "string" && value.length > 0 ? new Date(value) : null;
if (date === null || Number.isNaN(date.getTime())) return null;
const parts = codexStatsDateFormatter.formatToParts(date).reduce<Record<string, string>>((memo, part) => {
if (part.type !== "literal") memo[part.type] = part.value;
return memo;
}, {});
const year = parts.year;
const month = parts.month;
const day = parts.day;
return year !== undefined && month !== undefined && day !== undefined ? `${year}-${month}-${day}` : null;
}
function shiftDateKey(dateKey: string, offsetDays: number): string {
const [year = "1970", month = "01", day = "01"] = dateKey.split("-");
const shifted = new Date(Date.UTC(Number(year), Number(month) - 1, Number(day) + offsetDays));
return shifted.toISOString().slice(0, 10);
}
function statsDaysFromUrl(url: URL): number {
const value = Number(url.searchParams.get("days") ?? 14);
return Number.isInteger(value) && value > 0 ? Math.min(90, value) : 14;
}
function emptyDailyTaskStatsBucket(date: string): DailyTaskStatsBucket {
return {
date,
executedTasks: 0,
completedTasks: 0,
retryAttempts: 0,
succeededTasks: 0,
failedTasks: 0,
canceledTasks: 0,
totalDurationMs: 0,
durationSamples: 0,
};
}
function retryAttemptDates(task: QueueTask): Array<string | null> {
const attempts = Array.isArray(task.attempts) ? task.attempts : [];
const retryAttempts = attempts.filter((attempt, index) => attempt.mode === "retry" || Number(attempt.index || 0) > 1 || index > 0);
const fallbackRetryCount = Math.max(0, Math.floor(Number(task.currentAttempt || 0)) - 1);
const fallbackAt = taskTimestamp(task.updatedAt) ?? taskTimestamp(task.startedAt) ?? taskTimestamp(task.createdAt);
return [
...retryAttempts.map((attempt) => taskTimestamp(attempt.startedAt) ?? taskTimestamp(attempt.finishedAt) ?? fallbackAt),
...Array.from({ length: Math.max(0, fallbackRetryCount - retryAttempts.length) }, () => fallbackAt),
];
}
function completedTaskDurationMs(task: QueueTask, finishedAt: string): number | null {
const startedAt = taskTimestamp(task.startedAt) ?? taskTimestamp(task.attempts[0]?.startedAt ?? null) ?? taskTimestamp(task.createdAt);
return durationMsBetween(startedAt, finishedAt);
}
function taskStatisticsSummary(tasks: QueueTask[], days = 14): JsonValue {
const generatedAt = ctx().nowIso();
const endDate = codexStatsDateKey(new Date()) ?? generatedAt.slice(0, 10);
const safeDays = Math.max(1, Math.min(90, Math.floor(days)));
const startDate = shiftDateKey(endDate, 1 - safeDays);
const buckets = new Map<string, DailyTaskStatsBucket>();
for (let offset = 0; offset < safeDays; offset += 1) {
const date = shiftDateKey(startDate, offset);
buckets.set(date, emptyDailyTaskStatsBucket(date));
}
const bucketFor = (value: string | null): DailyTaskStatsBucket | null => {
const date = codexStatsDateKey(value);
return date === null ? null : buckets.get(date) ?? null;
};
for (const task of tasks) {
const executedBucket = bucketFor(taskTimestamp(task.startedAt) ?? taskTimestamp(task.attempts[0]?.startedAt ?? null));
if (executedBucket !== null) executedBucket.executedTasks += 1;
for (const retryAt of retryAttemptDates(task)) {
const retryBucket = bucketFor(retryAt);
if (retryBucket !== null) retryBucket.retryAttempts += 1;
}
if (!terminalTask(task)) continue;
const finishedAt = taskTimestamp(task.finishedAt) ?? taskTimestamp(task.updatedAt);
const completedBucket = bucketFor(finishedAt);
if (finishedAt === null || completedBucket === null) continue;
completedBucket.completedTasks += 1;
if (task.status === "succeeded") completedBucket.succeededTasks += 1;
if (task.status === "failed") completedBucket.failedTasks += 1;
if (task.status === "canceled") completedBucket.canceledTasks += 1;
const durationMs = completedTaskDurationMs(task, finishedAt);
if (durationMs !== null) {
completedBucket.totalDurationMs += durationMs;
completedBucket.durationSamples += 1;
}
}
const daily = Array.from(buckets.values()).map((bucket) => ({
date: bucket.date,
executedTasks: bucket.executedTasks,
completedTasks: bucket.completedTasks,
retryAttempts: bucket.retryAttempts,
succeededTasks: bucket.succeededTasks,
failedTasks: bucket.failedTasks,
canceledTasks: bucket.canceledTasks,
avgDurationMs: bucket.durationSamples > 0 ? Math.round(bucket.totalDurationMs / bucket.durationSamples) : null,
totalDurationMs: bucket.totalDurationMs,
durationSamples: bucket.durationSamples,
}));
const totals = daily.reduce((memo, day) => {
memo.executedTasks += day.executedTasks;
memo.completedTasks += day.completedTasks;
memo.retryAttempts += day.retryAttempts;
memo.succeededTasks += day.succeededTasks;
memo.failedTasks += day.failedTasks;
memo.canceledTasks += day.canceledTasks;
memo.totalDurationMs += day.totalDurationMs;
memo.durationSamples += day.durationSamples;
return memo;
}, {
executedTasks: 0,
completedTasks: 0,
retryAttempts: 0,
succeededTasks: 0,
failedTasks: 0,
canceledTasks: 0,
totalDurationMs: 0,
durationSamples: 0,
});
return {
generatedAt,
timezone: codexStatsTimeZone,
days: safeDays,
range: { startDate, endDate },
totals: {
...totals,
avgDurationMs: totals.durationSamples > 0 ? Math.round(totals.totalDurationMs / totals.durationSamples) : null,
},
daily,
} as unknown as JsonValue;
}
function transcriptLine(kind: TranscriptKind, at: string, seq: number, title: string, rawSeqs: number[], body = "", command = "", status?: string, fullText = false): TranscriptLine {
const bodyInfo = fullText ? completeTraceText(body) : kind === "message" ? fullMessageBody(body) : kind === "edited" ? editedOutputPreview(body) : outputPreview(body);
const commandInfo = command.length > 0 ? fullText ? completeTraceText(displayCommand(command)) : commandPreview(command) : { text: "", omittedLines: 0 };
return {
seq,
at,
kind,
title,
status,
commandPreview: commandInfo.text || undefined,
commandOmittedLines: commandInfo.omittedLines || undefined,
bodyPreview: bodyInfo.text || undefined,
bodyOmittedLines: bodyInfo.omittedLines || undefined,
rawSeqs,
};
}
function pushUniqueRawSeq(rawSeqs: number[], seq: number): void {
if (!rawSeqs.includes(seq)) rawSeqs.push(seq);
}
function messageFragmentMergeKey(line: TranscriptLine): string | null {
if (line.kind !== "message") return null;
const title = String(line.title || "").trim().toLowerCase();
if (title !== "assistant message" && title !== "reasoning") return null;
return title;
}
function appendMessageText(left: string, right: string): string {
if (left.length === 0) return right;
if (right.length === 0) return left;
if (!/\s$/u.test(left) && !/^\s|^[,.;:!?)}\]]/u.test(right) && /[A-Za-z0-9]/u.test(left.at(-1) || "") && /[A-Za-z0-9]/u.test(right[0] || "")) return `${left} ${right}`;
return `${left}${right}`;
}
function mergeTranscriptMessageGroup(group: TranscriptLine[]): TranscriptLine {
const first = group[0];
const last = group[group.length - 1] || first;
const body = group.reduce((text, line) => appendMessageText(text, String(line.bodyPreview || "")), "");
const rawSeqs: number[] = [];
for (const line of group) {
for (const seq of Array.isArray(line.rawSeqs) ? line.rawSeqs : []) pushUniqueRawSeq(rawSeqs, seq);
}
return {
...first,
seq: Number.isFinite(Number(last.seq)) ? Number(last.seq) : Number(first.seq),
at: last.at || first.at,
status: last.status || first.status,
bodyPreview: body.length > 0 ? body : undefined,
bodyOmittedLines: group.reduce((sum, line) => sum + Number(line.bodyOmittedLines || 0), 0) || undefined,
rawSeqs,
};
}
function coalesceTranscriptMessageFragments(entries: TranscriptLine[]): TranscriptLine[] {
const rows = sortTranscript([...entries]);
const merged: TranscriptLine[] = [];
let group: TranscriptLine[] = [];
let groupKey: string | null = null;
const flush = () => {
if (group.length === 0) return;
merged.push(group.length === 1 ? group[0] : mergeTranscriptMessageGroup(group));
group = [];
groupKey = null;
};
for (const line of rows) {
const key = messageFragmentMergeKey(line);
if (key !== null && key === groupKey) {
group.push(line);
continue;
}
flush();
if (key !== null) {
group = [line];
groupKey = key;
} else {
merged.push(line);
}
}
flush();
return merged;
}
function traceMessageOverlayKey(line: TranscriptLine): string | null {
return messageFragmentMergeKey(line);
}
function traceMessageOverlayCandidate(line: TranscriptLine): boolean {
return traceMessageOverlayKey(line) !== null && String(line.bodyPreview || "").trim().length > 0;
}
function overlayTraceMessagesFromRawTranscript(oaLines: TranscriptLine[], rawLines: TranscriptLine[]): TranscriptLine[] {
const rawMessages = rawLines.filter(traceMessageOverlayCandidate);
if (rawMessages.length === 0) return oaLines;
const messageSeqs = new Set<number>();
for (const line of oaLines) {
if (traceMessageOverlayKey(line) === null) continue;
for (const seq of Array.isArray(line.rawSeqs) && line.rawSeqs.length > 0 ? line.rawSeqs : [line.seq]) {
if (Number.isFinite(Number(seq))) messageSeqs.add(Number(seq));
}
}
const result = oaLines.filter((line) => traceMessageOverlayKey(line) === null);
for (const raw of rawMessages) {
const seqs = Array.isArray(raw.rawSeqs) && raw.rawSeqs.length > 0 ? raw.rawSeqs : [raw.seq];
if (!seqs.some((seq) => messageSeqs.has(Number(seq)))) continue;
result.push(raw);
}
return coalesceTranscriptMessageFragments(result);
}
function commandKind(command: string): TranscriptKind {
if (/\bwebSearch\b/u.test(command)) return "explored";
if (/\b(apply_patch|git apply|cat >|tee .*<<|sed -i|python3? .*write_text)\b/u.test(command)) return "edited";
if (/\b(rg|grep|find|ls|cat|sed -n|tail|head|git status|git diff|ps)\b/u.test(command)) return "explored";
return "ran";
}
function commandKindLabel(kind: TranscriptKind): string {
if (kind === "edited") return "Edited";
if (kind === "explored") return "Explored";
return "Ran";
}
function jsonPreview(value: unknown, max = 4000): string {
try {
return safePreview(JSON.stringify(value), max);
} catch {
return safePreview(String(value ?? ""), max);
}
}
function recordDisplayField(record: Record<string, unknown> | null, keys: string[], max = 3000): string {
if (record === null) return "";
for (const key of keys) {
const value = record[key];
if (typeof value === "string") return value;
if (value !== undefined && value !== null) return jsonPreview(value, max);
}
return "";
}
function decodeJsonStringFragment(value: string): string {
try {
return JSON.parse(`"${value}"`) as string;
} catch {
return value
.replace(/\\"/gu, "\"")
.replace(/\\\\/gu, "\\")
.replace(/\\n/gu, "\n")
.replace(/\\r/gu, "\r")
.replace(/\\t/gu, "\t");
}
}
function partialJsonStringField(text: string, key: string): string {
const match = new RegExp(`"${key}"\\s*:\\s*"`, "u").exec(text);
if (match === null) return "";
const start = match.index + match[0].length;
let escaped = false;
for (let index = start; index < text.length; index += 1) {
const char = text[index] ?? "";
if (escaped) {
escaped = false;
continue;
}
if (char === "\\") {
escaped = true;
continue;
}
if (char === "\"") return decodeJsonStringFragment(text.slice(start, index));
}
return decodeJsonStringFragment(text.slice(start).replace(/\s*\.\.\.\s*$/u, ""));
}
function partialJsonNumberField(text: string, key: string): number | null {
const match = new RegExp(`"${key}"\\s*:\\s*(-?\\d+(?:\\.\\d+)?)`, "u").exec(text);
if (match === null) return null;
const value = Number(match[1]);
return Number.isFinite(value) ? value : null;
}
function openCodePartialRecordFromOutput(item: LiveOutput): Record<string, unknown> | null {
if (!String(item.method || "").startsWith("opencode/")) return null;
const text = item.text.trim();
if (!text.includes("\"part\"") && !text.includes("\"tool\"")) return null;
const tool = partialJsonStringField(text, "tool");
const command = partialJsonStringField(text, "command") || partialJsonStringField(text, "cmd") || partialJsonStringField(text, "script");
const output = partialJsonStringField(text, "output") || partialJsonStringField(text, "result");
if (tool.length === 0 && command.length === 0 && output.length === 0) return null;
const input: Record<string, unknown> = {};
for (const key of ["command", "cmd", "script", "filePath", "filepath", "path", "pattern", "query", "title", "description"]) {
const value = partialJsonStringField(text, key);
if (value.length > 0) input[key] = value;
}
for (const key of ["offset", "limit"]) {
const value = partialJsonNumberField(text, key);
if (value !== null) input[key] = value;
}
return {
type: partialJsonStringField(text, "type") || "tool_use",
part: {
type: "tool",
tool: tool || "tool",
state: {
status: partialJsonStringField(text, "status"),
input,
output,
},
},
};
}
function openCodeRecordFromOutput(item: LiveOutput): Record<string, unknown> | null {
if (!String(item.method || "").startsWith("opencode/")) return null;
try {
return extractRecord(JSON.parse(item.text) as unknown);
} catch {
return openCodePartialRecordFromOutput(item);
}
}
function openCodeToolInputCommand(tool: string, input: Record<string, unknown> | null): string {
const command = recordStringField(input, ["command", "cmd", "script"]);
if (command.length > 0) return command;
const path = recordStringField(input, ["filePath", "filepath", "path"]);
const pattern = recordStringField(input, ["pattern", "query"]);
const offset = recordNumberField(input, ["offset"]);
const limit = recordNumberField(input, ["limit"]);
const args: string[] = [tool];
if (pattern.length > 0) args.push(pattern);
if (path.length > 0) args.push(path);
if (offset !== null) args.push(`offset=${offset}`);
if (limit !== null) args.push(`limit=${limit}`);
if (args.length > 1) return args.join(" ");
return input === null ? tool : `${tool} ${jsonPreview(input, 1200)}`;
}
function openCodeToolKind(tool: string, command: string): TranscriptKind {
const normalized = `${tool} ${command}`.toLowerCase();
if (/\b(read|grep|glob|list|ls|find|search|view|cat|sed|rg)\b/u.test(normalized)) return "explored";
if (/\b(edit|write|patch|apply|update|create|delete|apply_patch|git apply|sed -i)\b/u.test(normalized)) return "edited";
return commandKind(command);
}
function openCodeToolTitle(tool: string, command: string, input: Record<string, unknown> | null): string {
const title = recordStringField(input, ["title", "description"]);
if (title.length > 0) return safePreview(title, 180);
const path = recordStringField(input, ["filePath", "filepath", "path"]);
if (path.length > 0) return `${commandKindLabel(openCodeToolKind(tool, command))} ${path}`;
return shortCommandTitle(command || tool);
}
function openCodeToolDurationMs(part: Record<string, unknown> | null, state: Record<string, unknown> | null): number | undefined {
const explicit = recordNumberField(part, ["durationMs", "elapsedMs"]) ?? recordNumberField(state, ["durationMs", "elapsedMs"]);
if (explicit !== null && explicit >= 0) return explicit;
const time = extractRecord(state?.time) ?? extractRecord(part?.time);
const start = recordNumberField(time, ["start", "startedAt"]);
const end = recordNumberField(time, ["end", "finishedAt", "completedAt"]);
return start !== null && end !== null && end >= start ? end - start : undefined;
}
function isOpenCodeStepBoundaryMethod(method: string | undefined): boolean {
return method === "opencode/step-start" || method === "opencode/step-finish";
}
function openCodeVisibleAssistantText(text: string): string {
return String(text || "").replace(/<(?:think|thinking)\b[^>]*>[\s\S]*?<\/(?:think|thinking)>/giu, "").trim();
}
function openCodeToolTranscriptLine(item: LiveOutput, fullText: boolean): TranscriptLine | null {
const record = openCodeRecordFromOutput(item);
const part = extractRecord(record?.part);
if (record === null || part === null) return null;
const recordType = recordStringField(record, ["type", "event", "name"]).toLowerCase();
const partType = recordStringField(part, ["type"]).toLowerCase();
if (recordType === "step_start" || recordType === "step-start" || partType === "step-start") return null;
if (recordType === "step_finish" || recordType === "step-finish" || partType === "step-finish") return null;
const state = extractRecord(part.state);
const input = extractRecord(state?.input) ?? extractRecord(part.input);
const tool = recordStringField(part, ["tool", "title"]) || recordStringField(record, ["type", "event", "name"]) || "tool";
const command = openCodeToolInputCommand(tool, input);
const metadata = extractRecord(state?.metadata);
const filediff = extractRecord(metadata?.filediff);
const rawOutput = recordDisplayField(state, ["output", "result"]) || recordDisplayField(part, ["output", "text", "content"]) || recordDisplayField(record, ["output", "text", "content"]);
const diffOutput = recordDisplayField(metadata, ["diff"]) || recordDisplayField(filediff, ["patch"]);
const status = recordStringField(state, ["status"]) || recordStringField(part, ["status"]) || recordStringField(record, ["status"]);
const kind = openCodeToolKind(tool, command);
const body = kind === "edited" && diffOutput.length > 0 ? diffOutput : rawOutput.length > 0 ? rawOutput : jsonPreview(record, 3000);
const line = transcriptLine(kind, item.at, item.seq, openCodeToolTitle(tool, command, input), [item.seq], body, command, status || item.method, fullText);
const durationMs = openCodeToolDurationMs(part, state);
if (durationMs !== undefined) line.durationMs = durationMs;
return line;
}
function promptLineCount(text: string): number {
return text.length > 0 ? text.split(/\r\n|\r|\n/u).length : 0;
}
function promptSnapshot(text: string, maxPreviewChars = 1200): { text: string; preview: string; chars: number; lines: number; truncated: boolean } {
const normalized = text.trimEnd();
const preview = safePreview(normalized, maxPreviewChars);
return {
text: normalized,
preview,
chars: normalized.length,
lines: promptLineCount(normalized),
truncated: preview.length < normalized.length,
};
}
function setAttemptInputPrompt(attempt: AttemptSummary, prompt: string): void {
const snapshot = promptSnapshot(prompt, 1200);
if (snapshot.chars === 0) return;
attempt.inputPrompt = snapshot.text;
attempt.inputPromptPreview = snapshot.preview;
attempt.inputPromptChars = snapshot.chars;
attempt.inputPromptLines = snapshot.lines;
}
function setAttemptFeedbackPrompt(attempt: AttemptSummary | undefined, prompt: string, source: string, forAttempt: number | null): void {
if (attempt === undefined) return;
const snapshot = promptSnapshot(prompt, 1600);
if (snapshot.chars === 0) return;
attempt.feedbackPrompt = snapshot.text;
attempt.feedbackPromptPreview = snapshot.preview;
attempt.feedbackPromptChars = snapshot.chars;
attempt.feedbackPromptLines = snapshot.lines;
attempt.feedbackPromptSource = source;
attempt.feedbackPromptForAttempt = forAttempt;
}
function taskInitialPromptLine(task: QueueTask, fullText = false): TranscriptLine | null {
const prompt = (task.basePrompt || userPromptForDisplay(task.prompt)).trimEnd();
if (prompt.length === 0) return null;
const line = transcriptLine("message", task.createdAt, 0.5, "Submitted prompt", [], prompt, "", "enqueue", fullText);
const fullPrompt = task.prompt.trimEnd();
if (fullText && fullPrompt.length > 0 && fullPrompt !== prompt) {
line.fullPrompt = fullPrompt;
line.fullPromptLines = promptLineCount(fullPrompt);
line.fullPromptChars = fullPrompt.length;
line.foldedReferencePrompt = true;
}
return line;
}
function promptHistoryTranscriptLines(task: QueueTask, fullText = false): TranscriptLine[] {
return ctx().mergePromptHistory([...(Array.isArray(task.promptHistory) ? task.promptHistory : []), ...ctx().outputPromptHistory(task)])
.map((item) => transcriptLine("message", item.at, item.seq, "Steer prompt", [item.seq], item.text, "", item.method, fullText));
}
function sortTranscript(entries: TranscriptLine[]): TranscriptLine[] {
return entries.sort((left, right) => Number(left.seq) - Number(right.seq));
}
function boundedTranscript(entries: TranscriptLine[], limit: number): TranscriptLine[] {
sortTranscript(entries);
if (entries.length <= limit) return entries;
const first = entries[0];
if (first?.title === "Submitted prompt" && first.rawSeqs.length === 0 && limit > 1) {
return [first, ...entries.slice(-(limit - 1))];
}
return entries.slice(-limit);
}
function buildTaskTranscript(task: QueueTask, limit = 180, rawOutputWindow = 0, fullText = false): TranscriptLine[] {
const entries: TranscriptLine[] = [];
const initialPrompt = taskInitialPromptLine(task, fullText);
if (initialPrompt !== null) entries.push(initialPrompt);
const promptHistoryLines = promptHistoryTranscriptLines(task, fullText);
const promptHistorySeqs = new Set(promptHistoryLines.map((line) => line.seq));
entries.push(...promptHistoryLines);
type ActiveCommand = { seq: number; at: string; command: string; status?: string; body: string; rawSeqs: number[]; itemId?: string };
let activeCommand: ActiveCommand | null = null;
const activeCommandsByItemId = new Map<string, ActiveCommand>();
const pushRawSeq = (command: ActiveCommand, seq: number): void => {
if (!command.rawSeqs.includes(seq)) command.rawSeqs.push(seq);
};
const flushCommand = (command: ActiveCommand | null = activeCommand): void => {
if (command === null) return;
const kind = commandKind(command.command);
const output = command.itemId === undefined ? null : commandOutputs.get(command.itemId) ?? null;
const body = command.body.length > 0 ? command.body : formatCommandOutput(output);
const title = command.command.trim().length > 0 ? shortCommandTitle(command.command) : "Command output";
entries.push(addCommandOutputStreams(transcriptLine(
kind,
command.at,
command.seq,
title,
command.rawSeqs,
body,
command.command,
command.status,
fullText,
), output, fullText));
if (command === activeCommand) activeCommand = null;
if (command.itemId !== undefined && activeCommandsByItemId.get(command.itemId) === command) {
activeCommandsByItemId.delete(command.itemId);
}
};
const itemIdCommand = (item: LiveOutput, parsed: { command: string; status?: string } | null = null): ActiveCommand | null => {
if (typeof item.itemId !== "string" || item.itemId.length === 0) return null;
let command = activeCommandsByItemId.get(item.itemId);
if (command === undefined) {
command = {
seq: item.seq,
at: item.at,
command: parsed?.command ?? "",
status: parsed?.status,
body: "",
rawSeqs: [],
itemId: item.itemId,
};
activeCommandsByItemId.set(item.itemId, command);
}
return command;
};
const outputSource = rawOutputWindow > 0 ? task.output : taskFullOutput(task);
const outputItems = rawOutputWindow > 0 && outputSource.length > rawOutputWindow
? outputSource.slice(-rawOutputWindow)
: outputSource;
const commandOutputs = outputItems.some((item) => item.channel === "command" && typeof item.itemId === "string")
? codexSessionCommandOutputsByCallId(task)
: new Map<string, SessionCommandOutput>();
const fileChangeInputs = outputItems.some((item) => item.channel === "diff" && item.method === "item/fileChange/outputDelta" && typeof item.itemId === "string")
? codexSessionFileChangesByCallId(task)
: new Map<string, SessionFileChange>();
type ActiveMessage = { seq: number; at: string; title: string; status?: string; body: string; rawSeqs: number[] };
type ActiveCodexTool = { seq: number; at: string; text: string; status?: string; rawSeqs: number[]; itemId?: string };
let activeMessage: ActiveMessage | null = null;
const activeCodexToolsByItemId = new Map<string, ActiveCodexTool>();
const flushMessage = (): void => {
if (activeMessage === null) return;
entries.push(transcriptLine("message", activeMessage.at, activeMessage.seq, activeMessage.title, activeMessage.rawSeqs, activeMessage.body, "", activeMessage.status, fullText));
activeMessage = null;
};
const appendMessage = (item: LiveOutput, title: string, body: string, status?: string): void => {
if (body.length === 0) return;
if (activeMessage !== null && activeMessage.title === title && activeMessage.status === status) {
activeMessage.body += body;
activeMessage.at = item.at;
activeMessage.seq = item.seq;
pushUniqueRawSeq(activeMessage.rawSeqs, item.seq);
return;
}
flushMessage();
activeMessage = { seq: item.seq, at: item.at, title, status, body, rawSeqs: [item.seq] };
};
const parseCodexToolLifecycle = (item: LiveOutput): { status: string | undefined; text: string } => {
const status = /\bstatus=([A-Za-z0-9_-]+)/u.exec(item.text)?.[1];
return { status, text: String(item.text || "").trimEnd() };
};
const codexToolLifecycleLine = (tool: ActiveCodexTool): TranscriptLine => {
const kind = commandKind(tool.text);
return transcriptLine(kind, tool.at, tool.seq, shortCommandTitle(tool.text), tool.rawSeqs, "", tool.text, tool.status, fullText);
};
const flushCodexTool = (tool: ActiveCodexTool): void => {
entries.push(codexToolLifecycleLine(tool));
if (tool.itemId !== undefined && activeCodexToolsByItemId.get(tool.itemId) === tool) activeCodexToolsByItemId.delete(tool.itemId);
};
for (const item of outputItems) {
if (initialPrompt !== null && item.channel === "user" && item.method === "enqueue") continue;
if (item.channel === "user" && item.method === "turn/steer" && promptHistorySeqs.has(item.seq)) continue;
if (isOpenCodeStepBoundaryMethod(item.method)) continue;
if (item.channel === "command" && item.method === "item/started") {
flushMessage();
const parsed = parseCommandLine(item.text);
const groupedCommand = itemIdCommand(item, parsed);
if (groupedCommand !== null) {
flushCommand();
groupedCommand.seq = item.seq;
groupedCommand.at = item.at;
groupedCommand.command = parsed?.command || item.text;
groupedCommand.status = parsed?.status;
pushRawSeq(groupedCommand, item.seq);
continue;
}
flushCommand();
activeCommand = {
seq: item.seq,
at: item.at,
command: parsed?.command || item.text,
status: parsed?.status,
body: "",
rawSeqs: [item.seq],
...(typeof item.itemId === "string" ? { itemId: item.itemId } : {}),
};
continue;
}
if (item.channel === "command" && item.method === "item/commandExecution/outputDelta") {
flushMessage();
const groupedCommand = itemIdCommand(item);
if (groupedCommand !== null) {
groupedCommand.body += item.text;
pushRawSeq(groupedCommand, item.seq);
} else if (activeCommand !== null) {
activeCommand.body += item.text;
pushRawSeq(activeCommand, item.seq);
} else {
const output = typeof item.itemId === "string" ? commandOutputs.get(item.itemId) ?? null : null;
entries.push(addCommandOutputStreams(transcriptLine("ran", item.at, item.seq, "Command output", [item.seq], item.text || formatCommandOutput(output), "", undefined, fullText), output, fullText));
}
continue;
}
if (item.channel === "command" && item.method === "item/completed") {
flushMessage();
const parsed = parseCommandLine(item.text);
const groupedCommand = itemIdCommand(item, parsed);
if (groupedCommand !== null) {
if (parsed?.command) groupedCommand.command = parsed.command;
groupedCommand.status = parsed?.status ?? groupedCommand.status;
pushRawSeq(groupedCommand, item.seq);
flushCommand(groupedCommand);
} else if (activeCommand !== null) {
activeCommand.status = parsed?.status ?? activeCommand.status;
pushRawSeq(activeCommand, item.seq);
flushCommand();
} else {
const command = parsed?.command || item.text;
const kind = commandKind(command);
const output = typeof item.itemId === "string" ? commandOutputs.get(item.itemId) ?? null : null;
entries.push(addCommandOutputStreams(transcriptLine(kind, item.at, item.seq, shortCommandTitle(command), [item.seq], formatCommandOutput(output), command, parsed?.status, fullText), output, fullText));
}
continue;
}
if (item.channel !== "assistant" && item.channel !== "reasoning") flushMessage();
flushCommand();
if (isCodexToolLifecycleOutput(item)) {
const parsed = parseCodexToolLifecycle(item);
const itemId = item.itemId || "";
const existing = activeCodexToolsByItemId.get(itemId);
if (item.method === "item/started") {
if (existing !== undefined) flushCodexTool(existing);
activeCodexToolsByItemId.set(itemId, {
seq: item.seq,
at: item.at,
text: parsed.text,
status: parsed.status ?? item.method,
rawSeqs: [item.seq],
itemId,
});
} else if (existing !== undefined) {
existing.at = item.at;
existing.status = parsed.status ?? existing.status;
existing.text = parsed.text.length > 0 ? parsed.text : existing.text;
pushUniqueRawSeq(existing.rawSeqs, item.seq);
flushCodexTool(existing);
} else {
entries.push(codexToolLifecycleLine({
seq: item.seq,
at: item.at,
text: parsed.text,
status: parsed.status ?? item.method,
rawSeqs: [item.seq],
itemId,
}));
}
} else if (item.channel === "diff") {
const text = fileChangeTextWithInlinePatch(item, fileChangeInputs);
entries.push(transcriptLine("edited", item.at, item.seq, "Edited files", [item.seq], text, "", item.method, fullText));
} else if (item.channel === "error") {
entries.push(transcriptLine("error", item.at, item.seq, "Error", [item.seq], item.text, "", item.method, fullText));
} else if (item.channel === "assistant") {
const body = String(item.method || "").startsWith("opencode/") ? openCodeVisibleAssistantText(item.text) : item.text;
appendMessage(item, "Assistant message", body, item.method);
} else if (item.channel === "reasoning") {
appendMessage(item, "Reasoning", item.text, item.method);
} else if (item.channel === "user") {
entries.push(transcriptLine("message", item.at, item.seq, item.method === "enqueue" ? "Submitted prompt" : "User prompt", [item.seq], item.text, "", item.method, fullText));
} else if (item.channel === "tool" && String(item.method || "").startsWith("opencode/")) {
entries.push(openCodeToolTranscriptLine(item, fullText) ?? transcriptLine("system", item.at, item.seq, "OpenCode tool", [item.seq], item.text, "", item.method, fullText));
} else {
const title = item.method === "queue" && item.text.startsWith("attempt ")
? "Attempt started"
: item.method === "startup" || item.method === "shutdown"
? "Recovered thread execution"
: item.method === "judge"
? "Judge result"
: "System";
entries.push(transcriptLine("system", item.at, item.seq, title, [item.seq], item.text, "", item.method, fullText));
}
}
flushMessage();
flushCommand();
for (const command of Array.from(activeCommandsByItemId.values()).sort((left, right) => left.seq - right.seq)) {
flushCommand(command);
}
for (const tool of Array.from(activeCodexToolsByItemId.values()).sort((left, right) => left.seq - right.seq)) {
flushCodexTool(tool);
}
return boundedTranscript(coalesceTranscriptMessageFragments(entries), limit);
}
function buildCompactTaskTranscript(task: QueueTask, limit = 12, rawOutputWindow = 24): TranscriptLine[] {
return buildTaskTranscript(task, limit, rawOutputWindow, false);
}
function transcriptSignature(task: QueueTask): string {
const last = task.output.at(-1);
return `${task.updatedAt}:${task.output.length}:${last?.seq ?? 0}:${last?.text.length ?? 0}:${outputArchiveSignature(task)}:${task.status}:${task.createdAt}:${task.basePrompt.length}:${task.prompt.length}:${task.promptHistory.length}:${task.promptHistory.at(-1)?.seq ?? 0}`;
}
function cachedTranscript(task: QueueTask, fullText: boolean): TranscriptLine[] {
const signature = transcriptSignature(task);
const cached = transcriptCache.get(task.id);
if (cached?.signature === signature) {
const cachedTranscript = fullText ? cached.fullTranscript : cached.previewTranscript;
if (cachedTranscript !== undefined) return cachedTranscript;
}
const transcript = buildTaskTranscript(task, Number.MAX_SAFE_INTEGER, 0, fullText);
const next: { signature: string; previewTranscript?: TranscriptLine[]; fullTranscript?: TranscriptLine[] } = cached?.signature === signature ? cached : { signature };
if (fullText) next.fullTranscript = transcript;
else next.previewTranscript = transcript;
transcriptCache.set(task.id, next);
if (transcriptCache.size > 80) {
const firstKey = transcriptCache.keys().next().value;
if (typeof firstKey === "string") transcriptCache.delete(firstKey);
}
return transcript;
}
function cachedFullTranscript(task: QueueTask): TranscriptLine[] {
return cachedTranscript(task, true);
}
function cachedPreviewTranscript(task: QueueTask): TranscriptLine[] {
return cachedTranscript(task, false);
}
function outputForResponse(task: QueueTask, includeRaw: boolean): LiveOutput[] {
if (includeRaw) return taskFullOutput(task);
return task.output.slice(-80).map((item) => ({ ...item, text: safePreview(item.text, 4000) }));
}
function attemptForResponse(attempt: AttemptSummary, full = false): JsonValue {
const finalResponse = String(attempt.finalResponse ?? "");
const inputPrompt = String(attempt.inputPrompt ?? "");
const feedbackPrompt = String(attempt.feedbackPrompt ?? "");
return {
...attempt,
inputPrompt: full ? inputPrompt : undefined,
inputPromptPreview: safePreview(String(attempt.inputPromptPreview || inputPrompt), full ? 3000 : 1200),
inputPromptChars: Number(attempt.inputPromptChars ?? inputPrompt.length),
inputPromptLines: Number(attempt.inputPromptLines ?? promptLineCount(inputPrompt)),
finalResponse: full ? finalResponse : safePreview(finalResponse, 1200),
finalResponsePreview: safePreview(String(attempt.finalResponsePreview || finalResponse), full ? 3000 : 1200),
finalResponseChars: Number(attempt.finalResponseChars ?? finalResponse.length),
feedbackPrompt: full ? feedbackPrompt : undefined,
feedbackPromptPreview: safePreview(String(attempt.feedbackPromptPreview || feedbackPrompt), full ? 3000 : 1200),
feedbackPromptChars: Number(attempt.feedbackPromptChars ?? feedbackPrompt.length),
feedbackPromptLines: Number(attempt.feedbackPromptLines ?? promptLineCount(feedbackPrompt)),
stderrTail: full ? attempt.stderrTail : safePreview(attempt.stderrTail, 1200),
} as unknown as JsonValue;
}
function taskForResponse(task: QueueTask, full = false, includeRaw = full): JsonValue {
const displayPrompt = task.basePrompt || userPromptForDisplay(task.prompt);
return {
...task,
agentPort: codeAgentPortForModel(task.model),
agentPortInfo: codeAgentPortInfo(codeAgentPortForModel(task.model)),
judgeFailRetryLimit,
stepCount: null,
llmStepCount: null,
traceStats: null,
statsSource: "unavailable",
prompt: full ? task.prompt : safePreview(task.prompt, 2000),
basePrompt: full ? task.basePrompt : safePreview(task.basePrompt, 2000),
displayPrompt: full ? displayPrompt : safePreview(displayPrompt, 2000),
promptEditable: ctx().queuedTaskPromptEditable(task),
...queuedStatusPayload(task),
referenceTaskIds: task.referenceTaskIds,
referenceInjection: task.referenceInjection,
finalResponse: full ? task.finalResponse : safePreview(task.finalResponse, 5000),
terminalUnread: terminalTaskUnread(task),
attempts: task.attempts.map((attempt) => attemptForResponse(attempt, full)),
output: outputForResponse(task, includeRaw),
events: includeRaw ? task.events : task.events.slice(-120),
transcript: full ? fullTranscript(task) : buildTaskTranscript(task, 120, 0),
} as unknown as JsonValue;
}
function fullTranscript(task: QueueTask): TranscriptLine[] {
return cachedFullTranscript(task);
}
function nonNegativeInteger(value: unknown): number | null {
const parsed = Number(value);
return Number.isFinite(parsed) && parsed >= 0 ? Math.floor(parsed) : null;
}
function retainedOutputStartsTraceStep(output: LiveOutput): boolean {
if (output.channel === "diff" || output.channel === "tool") return true;
if (output.channel !== "command") return false;
const method = String(output.method || "");
return method === "item/started" || (method !== "item/commandExecution/outputDelta" && method !== "item/completed");
}
function retainedTaskStepCount(task: QueueTask): number {
return (Array.isArray(task.output) ? task.output : []).reduce((count, output) => count + (retainedOutputStartsTraceStep(output) ? 1 : 0), 0);
}
function taskStoredStepCount(task: QueueTask): number | null {
return nonNegativeInteger(task.stepCount) ?? nonNegativeInteger(task.llmStepCount);
}
// Used only as a producer-side seed for OA stats snapshots; frontend/API
// display authority is the oa_trace_stats projection applied at route edges.
function taskListStepCount(task: QueueTask): number {
return taskStoredStepCount(task) ?? retainedTaskStepCount(task);
}
function taskOutputMaxSeq(task: QueueTask): number {
const output = Array.isArray(task.output) ? task.output : [];
const promptHistory = Array.isArray(task.promptHistory) ? task.promptHistory : [];
const attempts = Array.isArray(task.attempts) ? task.attempts : [];
const values = [
nonNegativeInteger(task.outputMaxSeq),
nonNegativeInteger(output.at(-1)?.seq),
nonNegativeInteger(promptHistory.at(-1)?.seq),
...attempts.map((attempt) => nonNegativeInteger(attempt.outputEndSeq)),
].filter((value): value is number => value !== null);
return values.length > 0 ? Math.max(...values) : 0;
}
function taskForMetaResponse(task: QueueTask): JsonValue {
const lastOutputSeq = taskOutputMaxSeq(task);
const displayPrompt = task.basePrompt || userPromptForDisplay(task.prompt);
return {
id: task.id,
queueId: ctx().queueIdOf(task),
queueEnteredAt: ctx().taskQueueEnteredAt(task),
prompt: task.prompt,
basePrompt: task.basePrompt,
displayPrompt,
promptChars: task.prompt.length,
basePromptChars: task.basePrompt.length,
displayPromptChars: displayPrompt.length,
promptEditable: ctx().queuedTaskPromptEditable(task),
finalResponseChars: task.finalResponse.length,
stepCount: null,
llmStepCount: null,
traceStats: null,
statsSource: "unavailable",
summaryOnly: false,
referenceTaskIds: task.referenceTaskIds,
referenceInjection: task.referenceInjection,
providerId: task.providerId,
executionMode: task.executionMode,
executionModeInfo: codeExecutionModeInfo(task.executionMode),
cwd: task.cwd,
model: task.model,
agentPort: codeAgentPortForModel(task.model),
agentPortInfo: codeAgentPortInfo(codeAgentPortForModel(task.model)),
reasoningEffort: task.reasoningEffort,
maxAttempts: task.maxAttempts,
status: task.status,
...queuedStatusPayload(task),
createdAt: task.createdAt,
updatedAt: task.updatedAt,
startedAt: task.startedAt,
finishedAt: task.finishedAt,
readAt: task.readAt,
currentAttempt: task.currentAttempt,
currentMode: task.currentMode,
judgeFailCount: task.judgeFailCount,
judgeFailRetryLimit,
codexThreadId: task.codexThreadId,
activeTurnId: task.activeTurnId,
finalResponse: task.finalResponse,
lastError: task.lastError,
lastJudge: task.lastJudge,
promptHistory: task.promptHistory,
attempts: task.attempts,
cancelRequested: task.cancelRequested,
terminalUnread: terminalTaskUnread(task),
nextMode: task.nextMode,
outputCount: task.output.length,
retainedOutputCount: task.output.length,
eventCount: task.events.length,
transcriptCount: null,
transcriptMaxSeq: lastOutputSeq,
timing: taskTiming(task),
transcript: [],
output: [],
events: [],
} as unknown as JsonValue;
}
function taskForCompactMetaResponse(task: QueueTask): JsonValue {
const displayPrompt = task.basePrompt || userPromptForDisplay(task.prompt);
const lastOutputSeq = taskOutputMaxSeq(task);
return {
id: task.id,
queueId: ctx().queueIdOf(task),
queueEnteredAt: ctx().taskQueueEnteredAt(task),
prompt: prefixPreview(task.prompt, 900),
basePrompt: prefixPreview(task.basePrompt, 900),
displayPrompt: prefixPreview(displayPrompt, 900),
promptChars: task.prompt.length,
basePromptChars: task.basePrompt.length,
displayPromptChars: displayPrompt.length,
promptEditable: ctx().queuedTaskPromptEditable(task),
finalResponseChars: task.finalResponse.length,
stepCount: null,
llmStepCount: null,
traceStats: null,
statsSource: "unavailable",
summaryOnly: true,
referenceTaskIds: task.referenceTaskIds,
referenceInjection: task.referenceInjection === null ? null : {
injectedAt: task.referenceInjection.injectedAt,
itemCount: task.referenceInjection.itemCount,
directReferenceTaskIds: task.referenceInjection.directReferenceTaskIds,
maxRounds: task.referenceInjection.maxRounds,
truncated: task.referenceInjection.truncated,
},
providerId: task.providerId,
executionMode: task.executionMode,
executionModeInfo: codeExecutionModeInfo(task.executionMode),
cwd: task.cwd,
model: task.model,
agentPort: codeAgentPortForModel(task.model),
agentPortInfo: codeAgentPortInfo(codeAgentPortForModel(task.model)),
reasoningEffort: task.reasoningEffort,
maxAttempts: task.maxAttempts,
status: task.status,
...queuedStatusPayload(task),
createdAt: task.createdAt,
updatedAt: task.updatedAt,
startedAt: task.startedAt,
finishedAt: task.finishedAt,
readAt: task.readAt,
currentAttempt: task.currentAttempt,
currentMode: task.currentMode,
judgeFailCount: task.judgeFailCount,
judgeFailRetryLimit,
codexThreadId: task.codexThreadId,
activeTurnId: task.activeTurnId,
finalResponse: prefixPreview(task.finalResponse, 1200),
lastError: task.lastError,
lastJudge: task.lastJudge,
promptHistory: task.promptHistory.slice(-8),
attempts: task.attempts.slice(-3).map((attempt) => attemptForResponse(attempt, false)),
cancelRequested: task.cancelRequested,
terminalUnread: terminalTaskUnread(task),
nextMode: task.nextMode,
outputCount: task.output.length,
eventCount: task.events.length,
transcriptCount: null,
transcriptMaxSeq: lastOutputSeq,
timing: taskTiming(task),
transcript: [],
output: [],
events: [],
} as unknown as JsonValue;
}
function lastAssistantMessage(task: QueueTask, transcript = fullTranscript(task)): JsonValue {
const assistantLine = transcript.slice().reverse().find((line) => line.kind === "message" && line.title === "Assistant message");
const text = task.finalResponse.trim().length > 0 ? task.finalResponse.trim() : String(assistantLine?.bodyPreview ?? "").trim();
return {
text,
at: assistantLine?.at ?? task.finishedAt ?? task.updatedAt,
seq: assistantLine?.seq ?? null,
source: task.finalResponse.trim().length > 0 ? "finalResponse" : assistantLine !== undefined ? "transcript" : "none",
};
}
function parseToolLimit(url: URL): number {
const value = Number(url.searchParams.get("toolLimit") ?? 160);
return Number.isInteger(value) && value > 0 ? Math.min(500, value) : 160;
}
function taskToolSummaryForLimit(task: QueueTask, limit: number, transcript = fullTranscript(task)): JsonValue {
const allTools = transcript.filter((line) => line.kind === "ran" || line.kind === "explored" || line.kind === "edited");
const boundedLimit = Math.max(1, Math.min(500, Math.floor(limit)));
const start = Math.max(0, allTools.length - boundedLimit);
return {
count: allTools.length,
returned: allTools.length - start,
limit: boundedLimit,
truncated: start > 0,
items: allTools.slice(start).map((line) => ({
seq: line.seq,
at: line.at,
kind: line.kind,
title: line.title,
status: line.status ?? null,
commandPreview: line.commandPreview ?? "",
commandOmittedLines: line.commandOmittedLines ?? 0,
outputPreview: line.bodyPreview ?? "",
outputOmittedLines: line.bodyOmittedLines ?? 0,
rawSeqs: line.rawSeqs,
})),
} as unknown as JsonValue;
}
function taskToolSummary(task: QueueTask, url: URL, transcript = fullTranscript(task)): JsonValue {
return taskToolSummaryForLimit(task, parseToolLimit(url), transcript);
}
function uniqueStrings(values: string[], limit = 20): string[] {
const result: string[] = [];
for (const value of values) {
const normalized = value.trim();
if (normalized.length === 0 || result.includes(normalized)) continue;
result.push(normalized);
if (result.length >= limit) break;
}
return result;
}
function cleanTracePath(value: string): string {
return value
.replace(/^['"`([{<]+/u, "")
.replace(/['"`)\]}>.,;:]+$/u, "")
.replace(/:\d+(?::\d+)?$/u, "")
.replace(/^[ab]\//u, "")
.trim();
}
function extractTracePaths(text: string): string[] {
const source = String(text || "");
const matches = source.match(/(?:~|\.{1,2}|\/)?(?:[A-Za-z0-9_.@+-]+\/)+[A-Za-z0-9_.@+-]+|[A-Za-z0-9_.@+-]+\.(?:c|cc|cpp|h|hpp|js|jsx|ts|tsx|json|md|py|sh|toml|ya?ml|txt|log|lock)/gu) || [];
return uniqueStrings(matches.map(cleanTracePath).filter((path) => path.length >= 2 && !path.includes("...") && !/^(http|https|status|method)$/iu.test(path)), 40);
}
function parseEditedFilesFromText(text: string): string[] {
const files: string[] = [];
const addFile = (path: string): void => {
const cleanPath = cleanTracePath(path);
if (cleanPath.length === 0 || cleanPath === "/dev/null" || files.includes(cleanPath)) return;
files.push(cleanPath);
};
for (const line of text.split(/\r?\n/u)) {
const statusMatch = /^([AMDRCU?]{1,2})\s+(.+)$/u.exec(line);
if (statusMatch) {
addFile(statusMatch[2] || "");
continue;
}
const patchMatch = /^\*\*\*\s+(?:Add|Update|Delete)\s+File:\s+(.+)$/u.exec(line);
if (patchMatch) {
addFile(patchMatch[1] || "");
continue;
}
const moveMatch = /^\*\*\*\s+Move to:\s+(.+)$/u.exec(line);
if (moveMatch) {
addFile(moveMatch[1] || "");
continue;
}
const diffMatch = /^diff --git a\/(.+?) b\/(.+)$/u.exec(line);
if (diffMatch) {
addFile(diffMatch[2] || diffMatch[1] || "");
continue;
}
const plusMatch = /^\+\+\+ b\/(.+)$/u.exec(line);
if (plusMatch) addFile(plusMatch[1] || "");
}
return files.length > 0 ? files : extractTracePaths(text);
}
function transcriptPreviewLines(text: string, maxLines: number, maxChars: number): string[] {
const preview = linePreview(text, maxLines, maxChars).text;
return preview.length === 0 ? [] : preview.split(/\r?\n/u).slice(0, maxLines);
}
function transcriptLineSummaryLines(line: TranscriptLine): string[] {
const lines: string[] = [];
const command = String(line.commandPreview || "").trim();
if (command.length > 0) {
for (const item of transcriptPreviewLines(command, 2, 420)) lines.push(`$ ${item}`);
}
const body = String(line.bodyPreview || "").trim();
const remaining = Math.max(0, 4 - lines.length);
if (body.length > 0 && remaining > 0) {
for (const item of transcriptPreviewLines(body, remaining, 700)) lines.push(item);
}
if (lines.length === 0) lines.push(line.status ? `${line.title} (${line.status})` : line.title);
return lines.slice(0, 4);
}
function isToolActionLine(line: TranscriptLine): boolean {
return line.kind === "ran" || line.kind === "explored" || line.kind === "edited";
}
function toolStepCountsFromTranscript(lines: TranscriptLine[]): { toolCallCount: number; readCount: number; editCount: number; runCount: number } {
const counts = lines.reduce((memo, line) => {
if (line.kind === "explored") memo.readCount += 1;
else if (line.kind === "edited") memo.editCount += 1;
else if (line.kind === "ran") memo.runCount += 1;
return memo;
}, { readCount: 0, editCount: 0, runCount: 0 });
return {
...counts,
toolCallCount: counts.readCount + counts.editCount + counts.runCount,
};
}
function realAttemptWindows(task: QueueTask, transcript: TranscriptLine[]): TraceAttemptWindow[] {
return traceAttemptWindows(task, transcript).filter((window) => window.synthetic !== true && window.index > 0);
}
function taskExecutionTranscript(task: QueueTask, transcript: TranscriptLine[]): TranscriptLine[] {
const windows = realAttemptWindows(task, transcript);
if (windows.length === 0) return transcript;
return sortTranscript(windows.flatMap((window) => executionLinesForAttempt(window.lines)));
}
function executionSummaryFromTranscript(
task: QueueTask,
transcript: TranscriptLine[],
timing: Record<string, JsonValue>,
outputCount = taskFullOutput(task).length,
retainedOutputCount = task.output.length,
): JsonValue {
const toolLines = transcript.filter(isToolActionLine);
const editedFiles = uniqueStrings(toolLines.flatMap((line) => line.kind === "edited" ? parseEditedFilesFromText(`${line.title}\n${line.bodyPreview ?? ""}`) : []), 40);
const commands = uniqueStrings(toolLines
.map((line) => String(line.commandPreview || "").split(/\r?\n/u).find((row) => row.trim().length > 0)?.trim() ?? "")
.filter(Boolean), 30);
const counts = toolStepCountsFromTranscript(transcript);
return {
durationMs: timing.durationMs ?? timing.totalElapsedMs ?? null,
totalElapsedMs: timing.totalElapsedMs ?? null,
queueWaitMs: timing.queueWaitMs ?? null,
toolCallCount: counts.toolCallCount,
readCount: counts.readCount,
editCount: counts.editCount,
runCount: counts.runCount,
editedFiles,
commands,
stepCount: counts.toolCallCount,
llmStepCount: counts.toolCallCount,
traceLineCount: transcript.filter((line) => line.title !== "Submitted prompt").length,
transcriptMaxSeq: transcript.at(-1)?.seq ?? 0,
outputCount,
retainedOutputCount,
} as unknown as JsonValue;
}
function taskExecutionSummary(task: QueueTask, transcript = cachedPreviewTranscript(task)): JsonValue {
return executionSummaryFromTranscript(task, taskExecutionTranscript(task, transcript), taskTiming(task) as Record<string, JsonValue>);
}
function parseAttemptIndex(text: string): number | null {
const match = /\battempt\s+(\d+)\s*\/\s*\d+/iu.exec(text);
if (match === null) return null;
const value = Number(match[1]);
return Number.isInteger(value) && value > 0 ? value : null;
}
function traceAttemptIndexFromLine(line: TranscriptLine): number | null {
return parseAttemptIndex(`${line.bodyPreview ?? ""}\n${line.commandPreview ?? ""}\n${line.title}`);
}
function parseJudgeLine(text: string): JudgeResult | null {
const match = /\bjudge=(complete|retry|fail)\s+confidence=([0-9.]+)\s+source=([A-Za-z0-9_-]+):\s*([\s\S]*)$/u.exec(text.trim());
if (match === null) return null;
const confidence = Number(match[2]);
const source = match[3] === "minimax" ? "minimax" : "fallback";
const rawReason = (match[4] ?? "").trim();
const detailsMatch = /\bMiniMax failure details:\s+([\s\S]*)$/u.exec(rawReason);
const detailsText = detailsMatch?.[1] ?? "";
const detailValue = (key: string): string | null => new RegExp(`\\b${key}=([^\\s]+)`, "u").exec(detailsText)?.[1] ?? null;
const detailNumber = (key: string): number | undefined => {
const value = Number(detailValue(key));
return Number.isFinite(value) ? value : undefined;
};
const errorMessage = /\berror=([\s\S]*)$/u.exec(detailsText)?.[1]?.trim() ?? "";
const failureDetails = detailsText.length === 0 ? null : {
provider: "minimax" as const,
stage: (detailValue("stage") || "unknown") as JudgeResult["failureDetails"] extends infer T ? T extends { stage: infer S } ? S : never : never,
model: "",
apiBase: "",
occurredAt: "",
durationMs: detailNumber("durationMs") ?? 0,
timeoutMs: detailNumber("timeoutMs") ?? 0,
timedOut: detailValue("timedOut") === "true",
errorName: detailValue("errorName") || "",
errorMessage,
promptChars: detailNumber("promptChars"),
promptLines: detailNumber("promptLines"),
payloadBytes: detailNumber("payloadBytes"),
responseStatus: detailNumber("responseStatus") ?? null,
};
return {
decision: match[1] as JudgeDecision,
confidence: Number.isFinite(confidence) ? confidence : 0,
source,
reason: detailsMatch === null ? rawReason : rawReason.slice(0, detailsMatch.index).trim(),
failureDetails,
};
}
function judgeFromAttemptLines(lines: TranscriptLine[]): { judge: JudgeResult; at: string | null; seq: number | null } | null {
for (const line of lines.slice().reverse()) {
if (line.title !== "Judge result" && line.status !== "judge") continue;
const judge = parseJudgeLine(String(line.bodyPreview || ""));
if (judge === null) continue;
return { judge, at: line.at || null, seq: Number.isFinite(Number(line.seq)) ? Number(line.seq) : null };
}
return null;
}
function attemptFeedbackPromptRecord(task: QueueTask, attemptIndex: number, attempt: AttemptSummary | null, judge: JudgeResult | null = attempt?.judge ?? null): FeedbackPromptRecord | null {
const directPrompt = String(attempt?.feedbackPrompt ?? "").trimEnd();
if (directPrompt.length > 0) {
const snapshot = promptSnapshot(directPrompt, 1600);
return {
text: snapshot.text,
preview: String(attempt?.feedbackPromptPreview || snapshot.preview),
chars: Number(attempt?.feedbackPromptChars ?? snapshot.chars),
lines: Number(attempt?.feedbackPromptLines ?? snapshot.lines),
source: String(attempt?.feedbackPromptSource || "judge-feedback"),
forAttempt: Number.isFinite(Number(attempt?.feedbackPromptForAttempt)) ? Number(attempt?.feedbackPromptForAttempt) : attemptIndex + 1,
truncated: snapshot.truncated,
};
}
const hasPendingRetry = task.status === "retry_wait" || task.status === "queued";
if (attemptIndex === task.attempts.length && hasPendingRetry && task.nextMode === "retry" && task.nextPrompt !== null && task.nextPrompt.trim().length > 0) {
const snapshot = promptSnapshot(task.nextPrompt, 1600);
return { ...snapshot, source: "pending-retry", forAttempt: attemptIndex + 1 };
}
const nextAttempt = task.attempts.find((item) => Number(item.index) === attemptIndex + 1) ?? task.attempts[attemptIndex] ?? null;
const nextInput = String(nextAttempt?.inputPrompt ?? "").trimEnd();
if (nextInput.length > 0) {
const snapshot = promptSnapshot(nextInput, 1600);
return { ...snapshot, source: "attempt-input", forAttempt: attemptIndex + 1 };
}
if (judge?.decision === "retry") {
const generated = retryPrompt(task, judge);
const snapshot = promptSnapshot(generated, 1600);
return { ...snapshot, source: judge.continuePrompt?.trim() ? "judge-continue-prompt" : "judge-retry-generated", forAttempt: attemptIndex + 1 };
}
return null;
}
function attemptTimingSummary(attempt: AttemptSummary | null, lines: TranscriptLine[]): Record<string, JsonValue> {
const startedAt = attempt?.startedAt || lines[0]?.at || null;
const finishedAt = attempt?.finishedAt || lines.at(-1)?.at || null;
const startedMs = timestampMs(startedAt);
const finishedMs = timestampMs(finishedAt);
const durationMs = nonNegativeElapsed(startedMs, finishedMs);
return {
durationMs,
totalElapsedMs: durationMs,
queueWaitMs: null,
effectiveEndAt: finishedAt,
};
}
interface TraceAttemptWindow {
index: number;
attempt: AttemptSummary | null;
startSeq: number | null;
endSeq: number | null;
lines: TranscriptLine[];
synthetic?: boolean;
label?: string;
}
type TraceStatsLookup = Map<string, JsonValue> | Record<string, JsonValue> | null | undefined;
function transcriptLineSeq(line: TranscriptLine | undefined): number | null {
const value = Number(line?.seq ?? NaN);
return Number.isFinite(value) ? value : null;
}
function traceWindowFirstSeq(window: TraceAttemptWindow): number {
return transcriptLineSeq(window.lines[0]) ?? window.startSeq ?? Number.POSITIVE_INFINITY;
}
function isRecoveredThreadLine(line: TranscriptLine): boolean {
const status = String(line.status || "");
const text = `${line.title}\n${line.bodyPreview ?? ""}\n${line.commandPreview ?? ""}`;
return line.kind === "system" && (
line.title === "Recovered thread execution"
|| line.title === "Queue recovered"
|| status === "startup"
|| status === "shutdown"
|| /\btask queued for retry\b|Service (?:re)?started while task was active|Service stopping while task was active/iu.test(text)
);
}
function traceSystemLineIsError(line: TranscriptLine): boolean {
const text = [
line.title,
line.status,
line.bodyPreview,
line.commandPreview,
line.stderrPreview,
line.stdoutPreview,
].map((value) => String(value || "")).join("\n");
return /\b(error|failed|failure|interrupt|interrupted|cancell?ed|watchdog|timeout|closed|refused|aborted|exception)\b/iu.test(text);
}
function traceLineVisibleInTraceView(line: TranscriptLine): boolean {
if (line.title === "Submitted prompt") return false;
return line.kind !== "system" || traceSystemLineIsError(line);
}
function oaTraceStepKind(kind: string): TranscriptKind {
const normalized = String(kind || "").trim().toLowerCase();
if (normalized === "read" || normalized === "explored" || normalized === "explore") return "explored";
if (normalized === "edit" || normalized === "edited") return "edited";
if (normalized === "run" || normalized === "ran" || normalized === "command") return "ran";
if (normalized === "error" || normalized === "failed") return "error";
if (normalized === "message" || normalized === "assistant" || normalized === "user") return "message";
return "system";
}
function oaTraceStepToTranscriptLine(step: OaTraceStepSummary): TranscriptLine {
const kind = oaTraceStepKind(step.kind);
const summaryText = step.summaryLines.join("\n").trimEnd();
const commandText = summaryText.startsWith("item/") ? summaryText : "";
return {
seq: step.seq,
at: step.at,
kind,
title: step.title || (kind === "explored" ? "Read" : kind === "edited" ? "Edit" : kind === "ran" ? "Run" : "Trace step"),
status: step.status || undefined,
commandPreview: commandText.length > 0 ? commandText : undefined,
bodyPreview: commandText.length > 0 ? undefined : summaryText || undefined,
rawSeqs: step.rawSeqs,
};
}
function isCodexToolLifecycleTranscriptLine(line: TranscriptLine): boolean {
const text = `${line.commandPreview ?? ""}\n${line.bodyPreview ?? ""}\n${line.title}`;
const status = String(line.status || "");
return (line.kind === "explored" || line.kind === "ran")
&& (status === "item/started" || status === "item/completed" || /^item\/(?:started|completed):/u.test(text))
&& /\b(?:webSearch|mcpToolCall|dynamicToolCall)\b/u.test(text);
}
function mergeCodexToolLifecycleGroup(group: TranscriptLine[]): TranscriptLine {
if (group.length <= 1) return group[0];
const first = group[0];
const last = group.at(-1) || first;
const rawSeqs: number[] = [];
for (const line of group) {
for (const seq of Array.isArray(line.rawSeqs) ? line.rawSeqs : [line.seq]) pushUniqueRawSeq(rawSeqs, Number(seq));
}
const command = String(last.commandPreview || first.commandPreview || last.bodyPreview || first.bodyPreview || last.title || first.title || "");
return {
...first,
seq: Number.isFinite(Number(last.seq)) ? Number(last.seq) : Number(first.seq),
at: last.at || first.at,
kind: commandKind(command),
title: shortCommandTitle(command) || String(last.title || first.title || "WebSearch"),
status: last.status || first.status,
commandPreview: command || undefined,
commandOmittedLines: Number(first.commandOmittedLines || 0) + Number(last.commandOmittedLines || 0) || undefined,
bodyPreview: last.bodyPreview || first.bodyPreview,
bodyOmittedLines: Number(first.bodyOmittedLines || 0) + Number(last.bodyOmittedLines || 0) || undefined,
rawSeqs,
};
}
function coalesceCodexToolLifecycleTranscriptLines(lines: TranscriptLine[]): TranscriptLine[] {
const rows = sortTranscript([...lines]);
const merged: TranscriptLine[] = [];
let group: TranscriptLine[] = [];
const flush = () => {
if (group.length > 0) merged.push(mergeCodexToolLifecycleGroup(group));
group = [];
};
for (const line of rows) {
if (isCodexToolLifecycleTranscriptLine(line)) {
const text = String(line.commandPreview || line.bodyPreview || "");
if ((line.status === "item/started" || /^item\/started:/u.test(text)) && group.length > 0) flush();
group.push(line);
if (line.status === "item/completed" || /^item\/completed:/u.test(text)) flush();
continue;
}
flush();
merged.push(line);
}
flush();
return merged;
}
async function oaTraceTranscriptForTask(task: QueueTask, attemptIndex: number | null): Promise<TranscriptLine[]> {
const taskId = task.id;
const steps = await readOaTraceStepsForTask(taskId, attemptIndex);
const oaLines = coalesceCodexToolLifecycleTranscriptLines(coalesceTranscriptMessageFragments(steps.map(oaTraceStepToTranscriptLine).filter(traceLineVisibleInTraceView)));
const rawLines = coalesceCodexToolLifecycleTranscriptLines(fullTranscript(task).filter(traceLineVisibleInTraceView));
return coalesceCodexToolLifecycleTranscriptLines(overlayTraceMessagesFromRawTranscript(oaLines, rawLines));
}
function mergeTraceWindowLines(left: TranscriptLine[], right: TranscriptLine[]): TranscriptLine[] {
const seen = new Set<string>();
const merged: TranscriptLine[] = [];
for (const line of [...left, ...right]) {
const key = `${line.seq}:${line.title}:${line.status ?? ""}`;
if (seen.has(key)) continue;
seen.add(key);
merged.push(line);
}
return sortTranscript(merged);
}
function minNullableSeq(left: number | null, right: number | null): number | null {
if (left === null) return right;
if (right === null) return left;
return Math.min(left, right);
}
function isSystemOnlyOrphanGroup(lines: TranscriptLine[]): boolean {
const executionLines = executionLinesForAttempt(lines);
return executionLines.length > 0 && executionLines.every((line) => line.kind === "system");
}
function mergeSystemGroupIntoNextAttempt(windows: TraceAttemptWindow[], lines: TranscriptLine[]): boolean {
if (!isSystemOnlyOrphanGroup(lines)) return false;
const groupEndSeq = transcriptLineSeq(lines.at(-1));
if (groupEndSeq === null) return false;
const target = windows
.filter((window) => window.synthetic !== true && traceWindowFirstSeq(window) > groupEndSeq)
.sort((left, right) => traceWindowFirstSeq(left) - traceWindowFirstSeq(right))[0];
if (target === undefined) return false;
target.lines = mergeTraceWindowLines(lines, target.lines);
target.startSeq = minNullableSeq(target.startSeq, transcriptLineSeq(lines[0]));
return true;
}
function traceAttemptWindows(task: QueueTask, transcript: TranscriptLine[]): TraceAttemptWindow[] {
const starts = transcript
.map((line, position) => ({ line, position, index: line.title === "Attempt started" ? traceAttemptIndexFromLine(line) : null }))
.filter((item): item is { line: TranscriptLine; position: number; index: number } => item.index !== null)
.sort((left, right) => Number(left.line.seq) - Number(right.line.seq));
const maxStartIndex = starts.reduce((max, item) => Math.max(max, item.index), 0);
const maxIndex = Math.max(task.attempts.length, task.currentAttempt || 0, maxStartIndex);
const windows: TraceAttemptWindow[] = [];
for (let index = 1; index <= maxIndex; index += 1) {
const attempt = task.attempts.find((item) => Number(item.index) === index) ?? task.attempts[index - 1] ?? null;
const start = starts.find((item) => item.index === index) ?? starts[index - 1] ?? null;
const nextStart = starts.find((item) => item.index > index) ?? null;
const explicitStartSeq = Number((attempt as AttemptSummary | null)?.outputStartSeq ?? NaN);
const explicitEndSeq = Number((attempt as AttemptSummary | null)?.outputEndSeq ?? NaN);
const startSeq = Number.isFinite(explicitStartSeq) ? explicitStartSeq : start?.line.seq ?? null;
const hasExplicitEndSeq = Number.isFinite(explicitEndSeq);
const endSeq = hasExplicitEndSeq ? explicitEndSeq : nextStart !== null ? nextStart.line.seq : null;
let lines = startSeq === null
? []
: transcript.filter((line) => Number(line.seq) >= startSeq && (endSeq === null || (hasExplicitEndSeq ? Number(line.seq) <= endSeq : Number(line.seq) < endSeq)));
if (lines.length === 0 && attempt !== null) {
const startedMs = timestampMs(attempt.startedAt);
const finishedMs = timestampMs(attempt.finishedAt);
lines = transcript.filter((line) => {
const lineMs = timestampMs(line.at);
return lineMs !== null
&& (startedMs === null || lineMs >= startedMs)
&& (finishedMs === null || lineMs <= finishedMs);
});
}
windows.push({ index, attempt, startSeq, endSeq, lines });
}
const coveredSeqs = new Set<number>();
for (const window of windows) {
for (const line of window.lines) coveredSeqs.add(Number(line.seq));
}
const orphanedGroups: TranscriptLine[][] = [];
let group: TranscriptLine[] = [];
const flushGroup = (): void => {
if (group.length > 0 && executionLinesForAttempt(group).length > 0) orphanedGroups.push(group);
group = [];
};
for (const line of transcript) {
const seq = Number(line.seq);
if (line.title === "Submitted prompt" || coveredSeqs.has(seq)) {
flushGroup();
continue;
}
group.push(line);
}
flushGroup();
let syntheticIndex = -1;
for (const lines of orphanedGroups) {
if (mergeSystemGroupIntoNextAttempt(windows, lines)) continue;
const hasSteer = lines.some((line) => line.title === "Steer prompt" || line.status === "turn/steer");
const hasRecovery = lines.some(isRecoveredThreadLine);
const startSeq = Number(lines[0]?.seq ?? NaN);
const endSeq = Number(lines.at(-1)?.seq ?? NaN);
windows.push({
index: syntheticIndex,
attempt: null,
startSeq: Number.isFinite(startSeq) ? startSeq : null,
endSeq: Number.isFinite(endSeq) ? endSeq : null,
lines,
synthetic: true,
label: hasRecovery
? hasSteer ? "Recovered thread execution with steer prompt" : "Recovered thread execution"
: hasSteer ? "Recovered thread execution with steer prompt" : "System events",
});
syntheticIndex -= 1;
}
return windows.sort((left, right) => Number(left.lines[0]?.seq ?? left.startSeq ?? 0) - Number(right.lines[0]?.seq ?? right.startSeq ?? 0));
}
function executionLinesForAttempt(lines: TranscriptLine[]): TranscriptLine[] {
return lines.filter((line) => line.title !== "Submitted prompt" && line.title !== "Attempt started" && line.title !== "Judge result");
}
function taskTraceAttemptSummaries(task: QueueTask, transcript: TranscriptLine[], oaTraceStatsByScope: TraceStatsLookup = null): JsonValue[] {
const windows = traceAttemptWindows(task, transcript);
return windows.map((window) => {
const attempt = window.attempt;
const parsedJudge = judgeFromAttemptLines(window.lines);
const storedJudge = attempt?.judge ?? null;
const synthetic = window.synthetic === true;
const judge = synthetic ? null : storedJudge ?? parsedJudge?.judge ?? (window.index === task.attempts.length && task.lastJudge !== null ? task.lastJudge : null);
const finalResponse = synthetic ? "" : String(attempt?.finalResponse ?? attempt?.finalResponsePreview ?? (window.index === task.attempts.length ? task.finalResponse : ""));
const finalResponseChars = Number(attempt?.finalResponseChars ?? finalResponse.length);
const executionLines = executionLinesForAttempt(window.lines);
const attemptStats = synthetic || window.index <= 0 ? null : traceStatsForScope(oaTraceStatsByScope, taskAttemptScopeId(task.id, window.index));
const stepCount = traceStatsNumber(attemptStats, "stepCount");
const readCount = traceStatsNumber(attemptStats, "readCount");
const editCount = traceStatsNumber(attemptStats, "editCount");
const runCount = traceStatsNumber(attemptStats, "runCount");
const errorCount = traceStatsNumber(attemptStats, "errorCount");
const feedbackPrompt = synthetic ? null : attemptFeedbackPromptRecord(task, window.index, attempt, judge);
const inputPrompt = promptSnapshot(String(attempt?.inputPrompt ?? ""), 1200);
return {
...(attempt ?? {}),
index: attempt?.index ?? window.index,
synthetic,
label: window.label ?? null,
mode: attempt?.mode ?? (window.index <= 1 ? "initial" : "retry"),
startedAt: attempt?.startedAt ?? window.lines[0]?.at ?? task.startedAt,
finishedAt: attempt?.finishedAt ?? null,
terminalStatus: attempt?.terminalStatus ?? null,
transportClosedBeforeTerminal: attempt?.transportClosedBeforeTerminal ?? false,
appServerExitCode: attempt?.appServerExitCode ?? null,
appServerSignal: attempt?.appServerSignal ?? null,
error: attempt?.error ?? null,
stderrTail: attempt?.stderrTail ?? "",
startSeq: window.startSeq,
endSeq: window.endSeq,
inputPrompt: undefined,
inputPromptPreview: inputPrompt.preview,
inputPromptChars: Number(attempt?.inputPromptChars ?? inputPrompt.chars),
inputPromptLines: Number(attempt?.inputPromptLines ?? inputPrompt.lines),
finalResponse,
finalResponsePreview: attempt?.finalResponsePreview ?? safePreview(finalResponse, 3000),
finalResponseChars,
finalResponseTruncated: finalResponse.length < finalResponseChars,
judge,
judgeAt: attempt?.judgeAt ?? parsedJudge?.at ?? null,
judgeSeq: attempt?.judgeSeq ?? parsedJudge?.seq ?? null,
feedbackPrompt: undefined,
feedbackPromptPreview: feedbackPrompt?.preview ?? "",
feedbackPromptChars: feedbackPrompt?.chars ?? 0,
feedbackPromptLines: feedbackPrompt?.lines ?? 0,
feedbackPromptSource: feedbackPrompt?.source ?? null,
feedbackPromptForAttempt: feedbackPrompt?.forAttempt ?? null,
feedbackPromptTruncated: feedbackPrompt?.truncated ?? false,
attemptScopeId: synthetic || window.index <= 0 ? null : taskAttemptScopeId(task.id, window.index),
stepCount,
readCount,
editCount,
runCount,
errorCount,
statsSource: attemptStats === null ? "unavailable" : "oa-event-flow",
traceStats: attemptStats,
execution: executionSummaryWithOaStats(
executionSummaryFromTranscript(
task,
executionLines,
attemptTimingSummary(attempt, executionLines.length > 0 ? executionLines : window.lines),
window.lines.length,
window.lines.length,
),
attemptStats,
),
};
}) as unknown as JsonValue[];
}
function resolvedReferencePromptParts(prompt: string): { reference: string; userPrompt: string } {
const withoutEnvironment = stripCodeQueueEnvironmentHint(prompt);
const trimmed = withoutEnvironment.trimStart();
if (!trimmed.startsWith(resolvedReferenceContextTitle)) {
return { reference: "", userPrompt: userPromptForDisplay(prompt) };
}
const offset = withoutEnvironment.length - trimmed.length;
const index = withoutEnvironment.lastIndexOf(currentTaskPromptMarker);
if (index < offset) return { reference: "", userPrompt: userPromptForDisplay(prompt) };
return {
reference: withoutEnvironment.slice(offset, index).trimEnd(),
userPrompt: withoutEnvironment.slice(index + currentTaskPromptMarker.length).trimStart(),
};
}
function taskTracePromptSummary(task: QueueTask): JsonValue {
const displayPrompt = task.basePrompt || userPromptForDisplay(task.prompt);
const parts = resolvedReferencePromptParts(task.prompt);
return {
basePrompt: displayPrompt,
basePromptChars: displayPrompt.length,
basePromptLines: promptLineCount(displayPrompt),
promptChars: task.prompt.length,
promptLines: promptLineCount(task.prompt),
referencePromptChars: parts.reference.length,
referencePromptLines: promptLineCount(parts.reference),
hasReferenceInjection: parts.reference.length > 0 || task.referenceInjection !== null,
referenceTaskIds: task.referenceTaskIds,
referenceInjectionSummary: task.referenceInjection === null ? null : {
injectedAt: task.referenceInjection.injectedAt,
itemCount: task.referenceInjection.itemCount,
directReferenceTaskIds: task.referenceInjection.directReferenceTaskIds,
maxRounds: task.referenceInjection.maxRounds,
truncated: task.referenceInjection.truncated,
},
} as unknown as JsonValue;
}
function traceStatsRecord(value: JsonValue | null | undefined): Record<string, JsonValue> | null {
return typeof value === "object" && value !== null && !Array.isArray(value) ? value as Record<string, JsonValue> : null;
}
function traceStatsNumber(stats: Record<string, JsonValue> | null, key: string): number | null {
const value = Number(stats?.[key]);
return Number.isFinite(value) && value >= 0 ? Math.floor(value) : null;
}
function taskAttemptScopeId(taskId: string, attemptIndex: number): string {
return `task:${taskId}:attempt:${Math.floor(attemptIndex)}`;
}
function traceStatsForScope(lookup: TraceStatsLookup, scopeId: string): Record<string, JsonValue> | null {
if (lookup === null || lookup === undefined || scopeId.length === 0) return null;
const value = lookup instanceof Map ? lookup.get(scopeId) : lookup[scopeId];
return traceStatsRecord(value);
}
function executionSummaryWithOaStats(execution: JsonValue, stats: Record<string, JsonValue> | null): JsonValue {
if (typeof execution !== "object" || execution === null || Array.isArray(execution)) return execution;
const record = execution as Record<string, JsonValue>;
if (stats === null) {
const {
stepCount: _stepCount,
llmStepCount: _llmStepCount,
toolCallCount: _toolCallCount,
readCount: _readCount,
editCount: _editCount,
runCount: _runCount,
errorCount: _errorCount,
traceLineCount: _traceLineCount,
outputMaxSeq: _outputMaxSeq,
transcriptMaxSeq: _transcriptMaxSeq,
...rest
} = record;
return {
...rest,
statsSource: "unavailable",
traceStats: null,
statsUnavailable: true,
} as unknown as JsonValue;
}
const stepCount = traceStatsNumber(stats, "stepCount");
const llmStepCount = traceStatsNumber(stats, "llmStepCount") ?? stepCount;
const readCount = traceStatsNumber(stats, "readCount");
const editCount = traceStatsNumber(stats, "editCount");
const runCount = traceStatsNumber(stats, "runCount");
const errorCount = traceStatsNumber(stats, "errorCount");
const traceLineCount = traceStatsNumber(stats, "traceLineCount");
const outputMaxSeq = traceStatsNumber(stats, "outputMaxSeq");
const toolCallCount = readCount === null && editCount === null && runCount === null ? null : (readCount ?? 0) + (editCount ?? 0) + (runCount ?? 0);
return {
...record,
...(stepCount === null ? {} : { stepCount }),
...(llmStepCount === null ? {} : { llmStepCount }),
...(toolCallCount === null ? {} : { toolCallCount }),
...(readCount === null ? {} : { readCount }),
...(editCount === null ? {} : { editCount }),
...(runCount === null ? {} : { runCount }),
...(errorCount === null ? {} : { errorCount }),
...(traceLineCount === null ? {} : { traceLineCount }),
...(outputMaxSeq === null ? {} : { outputMaxSeq, transcriptMaxSeq: outputMaxSeq }),
statsSource: "oa-event-flow",
traceStats: stats,
} as unknown as JsonValue;
}
function taskTraceSummaryResponse(task: QueueTask, oaTraceStats: JsonValue | null = null, oaTraceStatsByScope: TraceStatsLookup = null): JsonValue {
const transcript = cachedPreviewTranscript(task);
const attempts = taskTraceAttemptSummaries(task, transcript, oaTraceStatsByScope);
const stats = traceStatsRecord(oaTraceStats);
const stepCount = traceStatsNumber(stats, "stepCount");
const llmStepCount = traceStatsNumber(stats, "llmStepCount") ?? stepCount;
const errorCount = traceStatsNumber(stats, "errorCount");
const execution = executionSummaryWithOaStats(taskExecutionSummary(task, transcript), stats);
return {
id: task.id,
queueId: ctx().queueIdOf(task),
status: task.status,
providerId: task.providerId,
executionMode: task.executionMode,
executionModeInfo: codeExecutionModeInfo(task.executionMode),
model: task.model,
agentPort: codeAgentPortForModel(task.model),
agentPortInfo: codeAgentPortInfo(codeAgentPortForModel(task.model)),
cwd: task.cwd,
reasoningEffort: task.reasoningEffort,
createdAt: task.createdAt,
startedAt: task.startedAt,
finishedAt: task.finishedAt,
updatedAt: task.updatedAt,
currentAttempt: task.currentAttempt,
maxAttempts: task.maxAttempts,
stepCount,
llmStepCount,
promptEditable: ctx().queuedTaskPromptEditable(task),
prompt: taskTracePromptSummary(task),
execution,
traceStats: stats,
statsSource: stats === null ? "unavailable" : "oa-event-flow",
finalResponse: task.finalResponse,
finalResponseChars: task.finalResponse.length,
lastJudge: task.lastJudge,
lastError: task.lastError,
errorCount,
attempts,
timing: taskTiming(task),
} as unknown as JsonValue;
}
function taskFeedbackPromptDetail(task: QueueTask, attemptIndex: number | null): FeedbackPromptRecord | null {
const index = attemptIndex ?? (task.attempts.length > 0 ? task.attempts.length : task.currentAttempt || 1);
if (!Number.isInteger(index) || index <= 0) return null;
const attempt = task.attempts.find((item) => Number(item.index) === index) ?? task.attempts[index - 1] ?? null;
let judge = attempt?.judge ?? null;
if (judge === null) {
const window = traceAttemptWindows(task, cachedPreviewTranscript(task)).find((item) => item.index === index) ?? null;
judge = judgeFromAttemptLines(window?.lines ?? [])?.judge ?? null;
}
return attemptFeedbackPromptRecord(task, index, attempt, judge);
}
function taskPromptDetailResponse(task: QueueTask, url: URL): Response {
const part = String(url.searchParams.get("part") || "full");
const parts = resolvedReferencePromptParts(task.prompt);
const basePrompt = task.basePrompt || userPromptForDisplay(task.prompt);
if (part === "feedback" || part === "judge-feedback") {
const attemptIndex = ctx().parseSeqParam(url, "attempt", null);
const detail = taskFeedbackPromptDetail(task, attemptIndex);
const text = detail?.text ?? "";
return ctx().jsonResponse({
ok: true,
taskId: task.id,
queueId: ctx().queueIdOf(task),
status: task.status,
updatedAt: task.updatedAt,
part: "feedback",
attempt: attemptIndex,
forAttempt: detail?.forAttempt ?? null,
source: detail?.source ?? null,
text,
preview: detail?.preview ?? "",
chars: detail?.chars ?? text.length,
lines: detail?.lines ?? promptLineCount(text),
truncated: detail?.truncated ?? false,
});
}
const text = part === "base"
? basePrompt
: part === "reference"
? parts.reference
: task.prompt;
return ctx().jsonResponse({
ok: true,
taskId: task.id,
queueId: ctx().queueIdOf(task),
status: task.status,
updatedAt: task.updatedAt,
part,
text,
chars: text.length,
lines: promptLineCount(text),
promptChars: task.prompt.length,
basePromptChars: basePrompt.length,
referencePromptChars: parts.reference.length,
});
}
async function taskTraceStepsResponse(task: QueueTask, url: URL): Promise<Response> {
const limit = ctx().parseLimit(url);
const attemptIndex = ctx().parseSeqParam(url, "attempt", null);
const agentPort = codeAgentPortForModel(task.model);
const transcript = await oaTraceTranscriptForTask(task, attemptIndex);
const page = ctx().pageBySeq(transcript, url, limit);
return ctx().jsonResponse({
ok: true,
taskId: task.id,
queueId: ctx().queueIdOf(task),
status: task.status,
updatedAt: task.updatedAt,
agentPort,
agentPortInfo: codeAgentPortInfo(agentPort),
attempt: attemptIndex,
source: "oa-event-flow",
steps: page.chunk.map((line) => ({
seq: line.seq,
at: line.at,
kind: line.kind,
title: line.title,
status: line.status ?? null,
durationMs: line.durationMs ?? null,
rawSeqs: line.rawSeqs,
summaryLines: transcriptLineSummaryLines(line),
hasDetail: true,
})),
mode: page.mode,
afterSeq: page.afterSeq,
nextAfterSeq: page.nextAfterSeq,
beforeSeq: page.beforeSeq,
previousBeforeSeq: page.previousBeforeSeq,
hasMore: page.hasMore,
hasBefore: page.hasBefore,
total: transcript.length,
maxSeq: transcript.at(-1)?.seq ?? 0,
});
}
async function taskTraceStepDetailResponse(task: QueueTask, url: URL): Promise<Response> {
const seq = Number(url.searchParams.get("seq"));
if (!Number.isFinite(seq)) return ctx().jsonResponse({ ok: false, error: "seq is required" }, 400);
const agentPort = codeAgentPortForModel(task.model);
const transcript = await oaTraceTranscriptForTask(task, null);
const line = transcript.find((item) => Number(item.seq) === seq || item.rawSeqs.includes(seq));
if (line === undefined) return ctx().jsonResponse({ ok: false, error: "trace step not found", seq }, 404);
return ctx().jsonResponse({
ok: true,
taskId: task.id,
queueId: ctx().queueIdOf(task),
status: task.status,
updatedAt: task.updatedAt,
agentPort,
agentPortInfo: codeAgentPortInfo(agentPort),
seq,
line,
});
}
function taskSummaryResponse(task: QueueTask, url: URL): JsonValue {
const transcript = fullTranscript(task);
const fullOutput = taskFullOutput(task);
return {
id: task.id,
queueId: ctx().queueIdOf(task),
status: task.status,
providerId: task.providerId,
executionMode: task.executionMode,
executionModeInfo: codeExecutionModeInfo(task.executionMode),
model: task.model,
agentPort: codeAgentPortForModel(task.model),
agentPortInfo: codeAgentPortInfo(codeAgentPortForModel(task.model)),
cwd: task.cwd,
reasoningEffort: task.reasoningEffort,
maxAttempts: task.maxAttempts,
currentAttempt: task.currentAttempt,
currentMode: task.currentMode,
judgeFailCount: task.judgeFailCount,
judgeFailRetryLimit,
codexThreadId: task.codexThreadId,
activeTurnId: task.activeTurnId,
createdAt: task.createdAt,
startedAt: task.startedAt,
finishedAt: task.finishedAt,
updatedAt: task.updatedAt,
timing: taskTiming(task),
initialPrompt: task.prompt,
basePrompt: task.basePrompt,
prompt: task.prompt,
promptEditable: ctx().queuedTaskPromptEditable(task),
referenceTaskIds: task.referenceTaskIds,
referenceInjection: task.referenceInjection,
lastAssistantMessage: lastAssistantMessage(task, transcript),
toolSummary: taskToolSummary(task, url, transcript),
attempts: task.attempts,
lastJudge: task.lastJudge,
lastError: task.lastError,
cancelRequested: task.cancelRequested,
transcriptCount: transcript.length,
transcriptMaxSeq: transcript.at(-1)?.seq ?? 0,
outputCount: fullOutput.length,
retainedOutputCount: task.output.length,
outputMaxSeq: fullOutput.at(-1)?.seq ?? 0,
eventCount: task.events.length,
cliHint: `bun scripts/cli.ts codex task ${task.id}`,
traceHint: `bun scripts/cli.ts codex task ${task.id} --trace --tail --limit 80`,
rawOutputHint: `bun scripts/cli.ts codex output ${task.id} --tail --limit 20`,
} as unknown as JsonValue;
}
export {
buildCompactTaskTranscript,
buildTaskTranscript,
cachedPreviewTranscript,
codexToolLifecycleStartedBeforeIn,
isCodexToolLifecycleOutput,
formatCommandOutput,
fullTranscript,
lastAssistantMessage,
promptLineCount,
promptSnapshot,
recordNumberField,
recordStringField,
safePreview,
prefixPreview,
setAttemptFeedbackPrompt,
setAttemptInputPrompt,
statsDaysFromUrl,
taskExecutionSummary,
taskListStepCount,
taskOutputMaxSeq,
taskForCompactMetaResponse,
taskForMetaResponse,
taskForResponse,
taskStatisticsSummary,
taskSummaryResponse,
taskTiming,
timestampMs,
nonNegativeElapsed,
taskToolSummary,
taskTraceStepDetailResponse,
taskTraceStepsResponse,
taskTraceSummaryResponse,
taskPromptDetailResponse,
transcriptLineSummaryLines,
};