fix(workbench): replay SSE outbox through runtime service (#2070)

This commit is contained in:
Lyon
2026-06-24 23:21:02 +08:00
committed by GitHub
parent 51f2f2cd47
commit 2651a6ee55
2 changed files with 11 additions and 7 deletions
+9 -5
View File
@@ -112,11 +112,12 @@ export async function handleWorkbenchRealtimeHttp(request, response, url, option
const requestedSessionId = safeSessionId(url.searchParams.get("sessionId") ?? url.searchParams.get("includeSessionId"));
const requestedTraceId = safeTraceId(url.searchParams.get("traceId"));
const heartbeatMs = parsePositiveInteger(options.env?.HWLAB_WORKBENCH_SSE_HEARTBEAT_MS, DEFAULT_WORKBENCH_SSE_HEARTBEAT_MS);
const realtimeOutboxReader = workbenchRuntimeOutboxReader(request, options);
attachWorkbenchRealtimeOtelContext(request, {
sessionId: requestedSessionId,
traceId: requestedTraceId,
heartbeatMs,
outboxMode: typeof (options.runtimeStore ?? null)?.readWorkbenchProjectionOutbox === "function"
outboxMode: typeof realtimeOutboxReader?.readWorkbenchProjectionOutbox === "function"
});
const perf = options.backendPerformance;
const auth = perf ? await perf.measure("workbench_auth", () => authenticateWorkbenchRead(request, response, options)) : await authenticateWorkbenchRead(request, response, options);
@@ -217,24 +218,23 @@ export async function handleWorkbenchRealtimeHttp(request, response, url, option
traceId: activeTraceId,
threadId: streamThreadId,
heartbeatMs,
outboxMode: typeof (options.runtimeStore ?? null)?.readWorkbenchProjectionOutbox === "function"
outboxMode: typeof realtimeOutboxReader?.readWorkbenchProjectionOutbox === "function"
});
emitWorkbenchRealtimeAcceptedOtelSpan(request, options.env);
if (activeTraceId && requestedAfterSeq <= 0) {
await (perf ? perf.measure("workbench_initial_trace", () => writeTraceRealtimeSnapshotSafe({ writeEvent, options, actor: auth.actor, traceId: activeTraceId, reason: "initial" })) : writeTraceRealtimeSnapshotSafe({ writeEvent, options, actor: auth.actor, traceId: activeTraceId, reason: "initial" }));
}
const runtimeStore = options.runtimeStore ?? null;
const outboxPollMs = parsePositiveInteger(options.env?.HWLAB_WORKBENCH_SSE_OUTBOX_POLL_MS, 500);
let outboxCursor = requestedAfterSeq;
const outboxTraceSnapshots = new Set(activeTraceId ? [activeTraceId] : []);
if (typeof runtimeStore?.readWorkbenchProjectionOutbox === "function" && (streamSessionId || activeTraceId)) {
if (typeof realtimeOutboxReader?.readWorkbenchProjectionOutbox === "function" && (streamSessionId || activeTraceId)) {
const pollOutbox = async () => {
if (closed || response.destroyed) return;
try {
const query = { afterSeq: outboxCursor, limit: 50 };
if (streamSessionId) query.sessionId = streamSessionId;
else query.traceId = activeTraceId;
const rows = await runtimeStore.readWorkbenchProjectionOutbox(query);
const rows = await realtimeOutboxReader.readWorkbenchProjectionOutbox(query);
for (const row of rows) {
if (closed || response.destroyed) break;
outboxCursor = row.outboxSeq;
@@ -737,6 +737,10 @@ function workbenchRuntimeFactsReadModel(request, options, actor) {
};
}
function workbenchRuntimeOutboxReader(request, options) {
return options.workbenchRuntime ?? createWorkbenchRuntimeClient({ env: options.env ?? process.env, fetch: options.fetch, logger: options.logger, traceparent: request?.hwlabHttpRequestContext?.traceparent });
}
async function handleWorkbenchSessionListBunLegacy(response, url, options, actor) {
if (url.searchParams.has("projectId") || url.searchParams.has("workspaceId")) return sendJson(response, 400, workbenchError("workbench_authority_removed", "Workbench session list is keyed by sessionId only."));
const perf = options.backendPerformance;