fix: preserve latest trace during historical repair

This commit is contained in:
Codex Agent
2026-06-06 11:40:21 +08:00
parent 62aeb9411d
commit aa06af6a91
3 changed files with 54 additions and 4 deletions
+34 -3
View File
@@ -1847,12 +1847,43 @@ function publicSession(session) { return { id: session.id, userId: session.userI
function publicAccessTuple(tuple) { return tuple ? { userId: tuple.userId, relation: tuple.relation, object: tuple.object, createdByAdminId: tuple.createdByAdminId, createdAt: tuple.createdAt } : null; }
function toolsFromTuples(tuples = []) { return Object.fromEntries(HWLAB_TOOL_IDS.map((toolId) => [toolId, tuples.some((tuple) => tuple.object === openFgaObject("tool", toolId) && tuple.relation === "can_use")])); }
function defaultDeniedToolCapabilities() { return { contractVersion: "admin-access-v1", actor: null, tools: Object.fromEntries(HWLAB_TOOL_IDS.map((toolId) => [toolId, { allowed: false, authorization: { contractVersion: "openfga-authorization-v1", mode: "enforce", allowed: false, decisionSource: "missing-owner", degraded: false } }])), valuesRedacted: true }; }
function normalizeAgentSessionOwnerRecord(input, existing, now) { return { id: input.sessionId, projectId: input.projectId ?? existing?.projectId ?? "prj_v02_code_agent", agentId: input.agentId ?? existing?.agentId ?? "hwlab-code-agent", status: input.status ?? existing?.status ?? "active", startedAt: existing?.startedAt ?? input.startedAt ?? now, endedAt: input.endedAt ?? existing?.endedAt ?? null, ownerUserId: input.ownerUserId, conversationId: input.conversationId ?? existing?.conversationId ?? null, threadId: input.threadId ?? existing?.threadId ?? null, lastTraceId: input.traceId ?? input.lastTraceId ?? existing?.lastTraceId ?? null, session: mergeAgentSessionOwnerEvidence(input.session, existing?.session), updatedAt: now }; }
function mergeAgentSessionOwnerEvidence(nextValue, existingValue) {
function normalizeAgentSessionOwnerRecord(input, existing, now) {
const nextTraceId = safeTraceIdLocal(input.traceId ?? input.lastTraceId);
const existingLastTraceId = safeTraceIdLocal(existing?.lastTraceId);
const historicalTraceUpdate = Boolean(nextTraceId && existingLastTraceId && nextTraceId !== existingLastTraceId && isHistoricalTraceBackfill(input.session, nextTraceId));
return {
id: input.sessionId,
projectId: input.projectId ?? existing?.projectId ?? "prj_v02_code_agent",
agentId: input.agentId ?? existing?.agentId ?? "hwlab-code-agent",
status: input.status ?? existing?.status ?? "active",
startedAt: existing?.startedAt ?? input.startedAt ?? now,
endedAt: input.endedAt ?? existing?.endedAt ?? null,
ownerUserId: input.ownerUserId,
conversationId: input.conversationId ?? existing?.conversationId ?? null,
threadId: input.threadId ?? existing?.threadId ?? null,
lastTraceId: historicalTraceUpdate ? existingLastTraceId : nextTraceId || existingLastTraceId || null,
session: mergeAgentSessionOwnerEvidence(input.session, existing?.session, { historicalTraceUpdate }),
updatedAt: now
};
}
function isHistoricalTraceBackfill(sessionValue, traceId) {
const snapshot = normalizeObject(sessionValue);
if (snapshot.historicalTraceBackfill === true || snapshot.source === "agent-session-trace-repair") return true;
if (Array.isArray(snapshot.messages) && snapshot.messages.length > 0) return false;
if (agentSessionTraceEvidence({ session: snapshot }, traceId)) return true;
return Boolean(snapshot.finalResponse || snapshot.traceSummary || snapshot.agentRun);
}
function mergeAgentSessionOwnerEvidence(nextValue, existingValue, options = {}) {
const existing = normalizeObject(existingValue);
const next = normalizeObject(nextValue);
const merged = { ...existing, ...next };
merged.traceResults = mergeAgentSessionTraceResults(existing.traceResults, next.traceResults ?? next.traceResult ?? next);
merged.traceResults = mergeAgentSessionTraceResults(mergeAgentSessionTraceResults(existing.traceResults, existing), next.traceResults ?? next.traceResult ?? next);
if (options.historicalTraceUpdate === true) {
for (const field of ["agentRun", "finalResponse", "traceSummary", "traceId", "sessionStatus", "sessionLifecycleStatus"]) {
if (existing[field] !== undefined) merged[field] = existing[field];
else delete merged[field];
}
}
const existingMessages = Array.isArray(existing.messages) ? existing.messages : null;
const nextMessages = Array.isArray(next.messages) ? next.messages : null;
if (nextMessages?.length) {
@@ -613,7 +613,9 @@ export async function loadPersistedAgentRunResult(traceId, options = {}) {
const latestTraceId = safeTraceId(session?.lastTraceId ?? session?.session?.lastTraceId ?? session?.session?.traceId);
const topLevelAgentRun = session?.session?.agentRun && typeof session.session.agentRun === "object" ? session.session.agentRun : null;
const agentRunTraceId = safeTraceId(topLevelAgentRun?.traceId);
const mayUseTopLevelAgentRun = latestTraceId === safeId || agentRunTraceId === safeId || (!latestTraceId && !agentRunTraceId);
const topLevelFinalTraceId = safeTraceId(session?.session?.finalResponse?.traceId ?? session?.session?.traceSummary?.traceId);
const topLevelConflictsTrace = (agentRunTraceId && agentRunTraceId !== safeId) || (topLevelFinalTraceId && topLevelFinalTraceId !== safeId);
const mayUseTopLevelAgentRun = !topLevelConflictsTrace && (latestTraceId === safeId || agentRunTraceId === safeId || topLevelFinalTraceId === safeId || (!latestTraceId && !agentRunTraceId && !topLevelFinalTraceId));
const agentRun = traceEvidence?.agentRun ?? (mayUseTopLevelAgentRun ? topLevelAgentRun : null);
const restored = persistedAgentRunResultFromSession({ traceId: safeId, session, agentRun, traceEvidence, options });
if (restored) return restored;
+17
View File
@@ -1880,6 +1880,23 @@ test("cloud api repairs historical same-session AgentRun trace after lastTraceId
assert.ok(repair);
assert.equal(repair.session.traceResults[firstTraceId].agentRun.commandId, firstCommandId);
assert.equal(ownerSessions.get("ses_issue955_historical").session.traceResults[firstTraceId].finalResponse.text, firstFinalText);
const repairedSession = ownerSessions.get("ses_issue955_historical");
assert.equal(repairedSession.lastTraceId, secondTraceId);
assert.equal(repairedSession.session.agentRun.commandId, secondCommandId);
assert.equal(repairedSession.session.finalResponse.text, secondFinalText);
const secondResultResponse = await fetch(`http://127.0.0.1:${port}/v1/agent/chat/result/${secondTraceId}`, {
headers: { cookie: "hwlab_session=test-stub-session" }
});
assert.equal(secondResultResponse.status, 200);
const secondResult = await secondResultResponse.json();
const secondText = JSON.stringify(secondResult);
assert.equal(secondResult.traceId, secondTraceId);
assert.equal(secondResult.status, "completed");
assert.equal(secondResult.agentRun.commandId, secondCommandId);
assert.match(secondText, //u);
assert.doesNotMatch(secondText, / HWPOD /u);
assert.ok(calls.some((call) => call.path === `/api/v1/runs/${runId}/commands/${secondCommandId}/result`));
} finally {
await new Promise((resolve, reject) => {
server.close((error) => (error ? reject(error) : resolve()));