fix(workbench): keep sealed turn timing immutable (#2033)
This commit is contained in:
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user