From 982c0aaa77bc4b1723c24be78cff1afdf0bcc9ad Mon Sep 17 00:00:00 2001 From: Shakker Date: Mon, 25 May 2026 16:52:42 +0100 Subject: [PATCH] refactor: route chat send user transcripts through sessions --- .../chat.directive-tags.test.ts | 18 ++++++++ src/gateway/server-methods/chat.ts | 41 ++++++++----------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/gateway/server-methods/chat.directive-tags.test.ts b/src/gateway/server-methods/chat.directive-tags.test.ts index 4121d8066c78..ffebaa259f45 100644 --- a/src/gateway/server-methods/chat.directive-tags.test.ts +++ b/src/gateway/server-methods/chat.directive-tags.test.ts @@ -4331,6 +4331,15 @@ describe("chat directive tag stripping for non-streaming final payloads", () => expect(message?.role).toBe("user"); expect(message?.content).toBe("quick command"); expect(typeof message?.timestamp).toBe("number"); + const persistedUser = readTranscriptJsonLines(mockState.transcriptPath) + .map((entry) => entry.message) + .find( + (candidate): candidate is Record => + typeof candidate === "object" && + candidate !== null && + (candidate as { role?: unknown }).role === "user", + ); + expect(persistedUser?.content).toBe("quick command"); }); it("emits a user transcript update when chat.send fails before an agent run starts", async () => { @@ -4356,6 +4365,15 @@ describe("chat directive tag stripping for non-streaming final payloads", () => expect(message?.role).toBe("user"); expect(message?.content).toBe("hello from failed dispatch"); expect(typeof message?.timestamp).toBe("number"); + const persistedUser = readTranscriptJsonLines(mockState.transcriptPath) + .map((entry) => entry.message) + .find( + (candidate): candidate is Record => + typeof candidate === "object" && + candidate !== null && + (candidate as { role?: unknown }).role === "user", + ); + expect(persistedUser?.content).toBe("hello from failed dispatch"); }); }); }); diff --git a/src/gateway/server-methods/chat.ts b/src/gateway/server-methods/chat.ts index 0337a976a889..03a93b9dd244 100644 --- a/src/gateway/server-methods/chat.ts +++ b/src/gateway/server-methods/chat.ts @@ -51,9 +51,8 @@ import { isPluginOwnedSessionBindingRecord } from "../../plugins/conversation-bi import { normalizeInputProvenance, type InputProvenance } from "../../sessions/input-provenance.js"; import { resolveSendPolicy } from "../../sessions/send-policy.js"; import { parseAgentSessionKey } from "../../sessions/session-key-utils.js"; -import { emitSessionTranscriptUpdate } from "../../sessions/transcript-events.js"; import { - buildPersistedUserTurnMessage, + persistUserTurnTranscript, type UserTurnInput, } from "../../sessions/user-turn-transcript.js"; import { uniqueStrings } from "../../shared/string-normalization.js"; @@ -2677,14 +2676,14 @@ export const chatHandlers: GatewayRequestHandlers = { let appendedWebchatAgentMedia = false; let userTranscriptUpdatePromise: Promise | null = null; let agentRunStarted = false; - const emitGatewayUserTranscriptUpdate = async () => { + const persistGatewayUserTurnTranscript = async () => { if (userTranscriptUpdatePromise) { await userTranscriptUpdatePromise; return; } userTranscriptUpdatePromise = (async () => { await measureDiagnosticsTimelineSpan( - "gateway.chat_send.emit_user_transcript", + "gateway.chat_send.persist_user_transcript", async () => { const { storePath: latestStorePath, entry: latestEntry } = loadSessionEntry(sessionKey); @@ -2692,27 +2691,21 @@ export const chatHandlers: GatewayRequestHandlers = { if (!resolvedSessionId) { return; } - const transcriptPath = resolveTranscriptPath({ - sessionId: resolvedSessionId, - storePath: latestStorePath, - sessionFile: latestEntry?.sessionFile ?? entry?.sessionFile, - agentId, - }); - if (!transcriptPath) { - return; - } const persistedImages = await getPersistedMediaForTranscript(); const userTurnInput = await userTurnInputPromise; - emitSessionTranscriptUpdate({ - sessionFile: transcriptPath, + await persistUserTurnTranscript({ + sessionId: resolvedSessionId, sessionKey, - message: buildPersistedUserTurnMessage( - userTurnInput ?? { - text: parsedMessage, - media: persistedImages, - timestamp: now, - }, - ), + sessionEntry: latestEntry ?? entry, + storePath: latestStorePath, + agentId, + config: cfg, + input: userTurnInput ?? { + text: parsedMessage, + media: persistedImages, + timestamp: now, + }, + updateMode: "inline", }); }, { @@ -2919,7 +2912,7 @@ export const chatHandlers: GatewayRequestHandlers = { // assistant turn, so it appends a gateway-injected assistant entry before // broadcasting the final UI event. if (!agentRunStarted) { - await emitGatewayUserTranscriptUpdate(); + await persistGatewayUserTurnTranscript(); const btwReplies = deliveredReplies .map((entry) => entry.payload) .filter(isBtwReplyPayload); @@ -3456,7 +3449,7 @@ export const chatHandlers: GatewayRequestHandlers = { .catch(async (err) => { const emitAfterError = agentRunStarted ? Promise.resolve() - : emitGatewayUserTranscriptUpdate(); + : persistGatewayUserTurnTranscript(); await emitAfterError.catch((transcriptErr) => { context.logGateway.warn( `webchat user transcript update failed after error: ${formatForLog(transcriptErr)}`,