diff --git a/internal/cloud/workbench-projection-writer.ts b/internal/cloud/workbench-projection-writer.ts index dedc9e57..f5c209d1 100644 --- a/internal/cloud/workbench-projection-writer.ts +++ b/internal/cloud/workbench-projection-writer.ts @@ -108,11 +108,14 @@ export async function writeWorkbenchProjectionEvent({ runtimeStore = null, event const status = terminal ? normalizeWorkbenchStatus(event.status) : "running"; const previousCheckpoint = await latestWorkbenchCheckpoint(runtimeStore, traceId); const previousTiming = normalizeTimingProjection(previousCheckpoint?.timing) ?? normalizeTimingProjection(previousCheckpoint); + const previousTerminal = checkpointIsTerminal(previousCheckpoint); + const suppressedAfterSeal = previousTerminal && !terminal; const explicitStartedAt = optionalTimestampValue(event.startedAt ?? event.traceStartedAt ?? event.runnerStartedAt); const startedAt = explicitStartedAt ?? previousTiming?.startedAt ?? (sourceSeq <= 1 ? occurredAt : null); const lastEventAt = latestTimestamp(previousTiming?.lastEventAt, occurredAt); const finishedAt = terminal ? latestTimestamp(previousTiming?.lastEventAt, optionalTimestampValue(event.finishedAt), occurredAt) : null; const timing = eventTimingProjection({ startedAt, lastEventAt, finishedAt, terminal }); + const eventTiming = suppressedAfterSeal ? previousTiming ?? timing : timing; const sourceEventId = workbenchSourceEventId(event, { traceId, sourceSeq, occurredAt }); const allocation = await runtimeStore.allocateWorkbenchProjectedSeq({ traceId, @@ -135,7 +138,7 @@ export async function writeWorkbenchProjectionEvent({ runtimeStore = null, event if (projectedSeq <= 0) throw new Error("Workbench projection allocator returned an invalid projectedSeq."); const eventId = textValue(event.id) || stableFactId("wte", { traceId, sourceEventId }); const messageFinalText = finalResponseTextValue(event.finalResponse, event.assistantText, event.reply); - const messageFact = sessionId ? normalizeMessageFact({ + const messageFact = sessionId && !suppressedAfterSeal ? normalizeMessageFact({ messageId: eventProjectionAssistantMessageId(traceId, event), role: "agent", status, @@ -157,7 +160,7 @@ export async function writeWorkbenchProjectionEvent({ runtimeStore = null, event source: "workbench-event-projection" }, 0, { traceId, sessionId, turnId, terminal, terminalStatus: status, finalText: messageFinalText, timestamp: projectedAt, timing }) : null; const facts = { - sessions: sessionId ? [{ + sessions: sessionId && !suppressedAfterSeal ? [{ sessionId, status, lastTraceId: traceId, @@ -186,16 +189,17 @@ export async function writeWorkbenchProjectionEvent({ runtimeStore = null, event eventType: textValue(event.eventType ?? event.type ?? event.label) || "event", terminal, sealed: terminal, - timing, - startedAt: timing.startedAt, - lastEventAt: timing.lastEventAt, - finishedAt: timing.finishedAt, - durationMs: timing.durationMs, + timing: eventTiming, + startedAt: eventTiming?.startedAt ?? null, + lastEventAt: eventTiming?.lastEventAt ?? null, + finishedAt: eventTiming?.finishedAt ?? null, + durationMs: eventTiming?.durationMs ?? null, + suppressedAfterSeal, occurredAt, updatedAt: projectedAt }], messages: messageFact ? [messageFact] : [], - checkpoints: [{ + checkpoints: !suppressedAfterSeal ? [{ traceId, sessionId, turnId, @@ -219,7 +223,7 @@ export async function writeWorkbenchProjectionEvent({ runtimeStore = null, event valuesRedacted: true }, updatedAt: projectedAt - }] + }] : [] }; return runtimeStore.writeWorkbenchFacts({ facts }, { traceId, @@ -495,6 +499,14 @@ function projectionTimingForStatus(value, terminal) { return { ...timing, finishedAt: null, durationMs: null, valuesRedacted: timing.valuesRedacted !== false }; } +function checkpointIsTerminal(value) { + const source = value && typeof value === "object" ? value : null; + if (!source) return false; + if (source.terminal === true || source.sealed === true) return true; + const status = normalizeWorkbenchStatus(source.status ?? source.terminalStatus ?? source.traceStatus); + return TERMINAL_STATUSES.has(status); +} + function eventTimingProjection({ startedAt = null, lastEventAt = null, finishedAt = null, terminal = false } = {}) { const normalizedStartedAt = optionalTimestampValue(startedAt); const normalizedLastEventAt = optionalTimestampValue(lastEventAt);