| @@ -1215,26 +1215,37 @@ export abstract class McpAgentSessionDOBase< · 1214 unmodified lines |
| 1215 | 1215 | } |
| 1216 | 1216 | if (request.method === "GET") { |
| 1217 | 1217 | const lastEventId = request.headers.get("last-event-id"); |
| 1218 | | − if (lastEventId) { |
| 1218 | + const resumeHasPendingEvents = |
| 1219 | + lastEventId !== null && (await this.eventStore.hasEventsAfter(lastEventId)); |
| 1220 | + if (lastEventId && resumeHasPendingEvents) { |
| 1219 | 1221 | await this.supersedeReplayStream(transport, lastEventId); |
| 1220 | 1222 | } else { |
| 1223 | + // Standalone listener path — taken for a bare GET AND for a GET |
| 1224 | + // whose Last-Event-ID stream is fully drained. EventSource clients |
| 1225 | + // echo their last id on every reconnect, so a drained id must not |
| 1226 | + // enter the transport's resume (it closes a completed stream's |
| 1227 | + // resume immediately, turning the echo into a reconnect loop). |
| 1228 | + let serveRequest = request; |
| 1229 | + if (lastEventId) { |
| 1230 | + const headers = new Headers(request.headers); |
| 1231 | + headers.delete("last-event-id"); |
| 1232 | + serveRequest = new Request(request, { headers }); |
| 1233 | + } |
| 1221 | 1234 | // Latest-listener-wins. Client cancellation is not reliably relayed |
| 1222 | 1235 | // through every workerd/Vite streaming hop, so explicitly retire a |
| 1223 | 1236 | // stale standalone mapping before opening its replacement. |
| 1224 | 1237 | transport.closeStandaloneSSEStream(); |
| 1225 | 1238 | const replay = await this.collectUndeliveredReplay(); |
| 1226 | | − if (replay) { |
| 1227 | | − // Prepend the replay onto the transport's own long-lived |
| 1228 | | − // standalone stream — the stream MUST stay open afterwards (see |
| 1229 | | − // collectUndeliveredReplay). Acknowledgement moves to stream end: |
| 1230 | | − // "complete"/"rotate" imply the client stayed attached long |
| 1231 | | − // enough to have drained the prepended frames. |
| 1232 | | − const response = await transport.handleRequest(request); |
| 1233 | | − return this.trackedLegacyResponse(response, { |
| 1234 | | − initialFrame: replay.frame, |
| 1235 | | − acknowledge: replay.streamIds, |
| 1236 | | − }); |
| 1237 | | − } |
| 1239 | + // Replayed responses ride as the opening frames of the transport's |
| 1240 | + // own long-lived standalone stream — the stream MUST stay open |
| 1241 | + // afterwards (see collectUndeliveredReplay). Acknowledgement moves |
| 1242 | + // to stream end: "complete"/"rotate" imply the client stayed |
| 1243 | + // attached long enough to have drained the prepended frames. |
| 1244 | + const response = await transport.handleRequest(serveRequest); |
| 1245 | + return this.trackedLegacyResponse( |
| 1246 | + response, |
| 1247 | + replay ? { initialFrame: replay.frame, acknowledge: replay.streamIds } : {}, |
| 1248 | + ); |
| 1238 | 1249 | } |
| 1239 | 1250 | } |
| 1240 | 1251 | const toolCallIds = legacyToolCallRequestIds(parsedBody); |