Merge pull request #1598 from pikasTech/fix/1585-terminal-trace-cache

[P0] Workbench terminal trace 纯读缓存,降低长 trace 重复 GET 延迟
This commit is contained in:
Lyon
2026-06-19 12:44:12 +08:00
committed by GitHub
2 changed files with 32 additions and 5 deletions
@@ -183,8 +183,10 @@ test("workbench read model recovers trace events from durable projection without
async ensureBootstrap() {},
async authenticate() { return { ok: true, actor: ACTOR, session: { id: "uss_workbench_reader" } }; }
};
let traceQueryCount = 0;
const runtimeStore = {
async queryAgentTraceEvents(params = {}) {
traceQueryCount += 1;
assert.equal(params.traceId, traceId);
return { events: durableEvents, count: durableEvents.length };
}
@@ -210,6 +212,7 @@ test("workbench read model recovers trace events from durable projection without
assert.equal(trace.body.events.length, 2);
assert.equal(trace.body.traceStatus, "completed");
assert.equal(trace.body.hasMore, false);
assert.equal(traceQueryCount, 1);
} finally {
await new Promise((resolve, reject) => server.close((error) => error ? reject(error) : resolve()));
}
+29 -5
View File
@@ -6,6 +6,9 @@ import { defaultCodeAgentTraceStore } from "./code-agent-trace-store.ts";
import { safeConversationId, safeSessionId, safeTraceId } from "./server-http-utils.ts";
import { durableTraceStatus, normalizeWorkbenchStatus, TERMINAL_STATUSES, traceTerminalEvidence } from "./workbench-turn-projection.ts";
const TERMINAL_DURABLE_TRACE_CACHE_MAX = 256;
const terminalDurableTraceCache = new Map();
export function createWorkbenchFactsStore(options = {}, actor = null) {
const accessStore = options.accessController?.store ?? options.accessController ?? null;
const traceStore = options.traceStore ?? defaultCodeAgentTraceStore;
@@ -101,21 +104,29 @@ export function createWorkbenchFactsStore(options = {}, actor = null) {
async function durableTraceSnapshot(runtimeStore, traceId) {
if (!runtimeStore || typeof runtimeStore.queryAgentTraceEvents !== "function") return null;
const safeId = safeTraceId(traceId);
if (!safeId) return null;
const cached = terminalDurableTraceCache.get(safeId);
if (cached) {
terminalDurableTraceCache.delete(safeId);
terminalDurableTraceCache.set(safeId, cached);
return cached;
}
let result;
try {
result = await runtimeStore.queryAgentTraceEvents({ traceId });
result = await runtimeStore.queryAgentTraceEvents({ traceId: safeId });
} catch (error) {
return projectionStoreUnavailableTrace(traceId, error);
return projectionStoreUnavailableTrace(safeId, error);
}
const events = Array.isArray(result?.events) ? result.events : [];
if (events.length === 0) return null;
const normalizedEvents = events.map((event, index) => ({ ...event, traceId, seq: eventSeq(event, index) }));
const normalizedEvents = events.map((event, index) => ({ ...event, traceId: safeId, seq: eventSeq(event, index) }));
const firstEvent = normalizedEvents[0] ?? null;
const lastEvent = normalizedEvents.at(-1) ?? null;
const status = durableTraceStatus(normalizedEvents);
const terminalEvidence = traceTerminalEvidence({ events: normalizedEvents, status });
return {
traceId,
const snapshot = {
traceId: safeId,
status,
createdAt: firstEvent?.createdAt ?? null,
updatedAt: lastEvent?.createdAt ?? firstEvent?.createdAt ?? null,
@@ -130,6 +141,19 @@ async function durableTraceSnapshot(runtimeStore, traceId) {
outputTruncated: normalizedEvents.some((event) => event.outputTruncated === true),
valuesPrinted: false
};
rememberTerminalDurableTrace(snapshot);
return snapshot;
}
function rememberTerminalDurableTrace(snapshot) {
const traceId = safeTraceId(snapshot?.traceId);
if (!traceId || !TERMINAL_STATUSES.has(normalizeStatus(snapshot?.status))) return;
terminalDurableTraceCache.delete(traceId);
terminalDurableTraceCache.set(traceId, snapshot);
while (terminalDurableTraceCache.size > TERMINAL_DURABLE_TRACE_CACHE_MAX) {
const oldest = terminalDurableTraceCache.keys().next().value;
terminalDurableTraceCache.delete(oldest);
}
}
function projectionStoreUnavailableTrace(traceId, error) {