Skip to content

fix(cas): seal cross-chat response leaks with eager sessions + end-on-switch - #123

Draft
scottcmg wants to merge 1 commit into
developfrom
fix/cas-shared-event-chat-output
Draft

fix(cas): seal cross-chat response leaks with eager sessions + end-on-switch#123
scottcmg wants to merge 1 commit into
developfrom
fix/cas-shared-event-chat-output

Conversation

@scottcmg

Copy link
Copy Markdown
Contributor

Problem

Agent responses could render into the wrong chat (or appear to vanish) when the user switched conversations or started a new chat while a turn was in flight. Separately, a fresh chat showed "New chat" in the sidebar until a second message was sent.

Changes

Session model — end-on-switch

  • Switching / New Chat / unmount ends the conversation's session, detaching its event handlers. The turn keeps running server-side but stops streaming into the shared chat surface, so it can't render on whichever chat is now open. A reopened conversation loads its latest state from history.
  • On open we clear the streaming/loading UI directly instead of calling Apollo's stopResponse() — that publishes StopResponse and would end the exchange server-side.
  • A too-early switch (before sessionStarted) no longer drops the buffered send: it flushes when the session starts, then the orphaned session ends.

Eager conversation + session creation

  • The conversation and session are created eagerly (launch, New Chat, after InputsPage submit), so the first send has no create-await. The wiring completes synchronously, so a subsequent switch (a separate macrotask) can't interleave and misdirect the turn.
  • In-flight creation is deduped so a send racing the warm-up reuses it rather than starting a second conversation.

Sidebar labels

  • The first user message (collapsed, truncated to 150 chars) is shown immediately as an optimistic title (UI-only), replaced by the generated one when it arrives.
  • The service-generated title is applied globally via conversations.events.onAny, so it lands even for a backgrounded conversation whose session has ended (a per-session listener would miss it). Fetch fallbacks on open and turn-end cover non-delivery.

Tests

228 passing. Added coverage for switch/new-chat send abandonment, flush-then-end durability, optimistic + generated labels, background label delivery, and eager warm-on-New-Chat reuse.

Notes / tradeoffs

  • Eager creation makes an empty conversation server-side per New Chat / launch — the accepted cost of an instant first turn and the sealed switch race.
  • One narrow residual remains: sending within the sub-second eager-warm window and immediately starting a new chat. Fully closing it would need a state+effect session-lifecycle refactor (noted as future work).

🤖 Generated with Claude Code

Copilot AI review requested due to automatic review settings July 26, 2026 21:48
@github-actions

github-actions Bot commented Jul 26, 2026

Copy link
Copy Markdown
Contributor

📊 Coverage Report

Package Line Coverage
datatable 88.29%
multi-file-upload 87.27%
conversational-agent-chat 89.79%
validation-station 99.05%
external-auth 97.26%

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR hardens the conversational agent chat widget against cross-chat streaming leaks by ending/detaching sessions on conversation switches and by eagerly creating conversations/sessions to remove timing windows that could misroute responses. It also improves sidebar usability by showing an immediate optimistic title from the first user message and ensuring generated titles land even when a conversation is backgrounded.

Changes:

  • End active sessions on switch/new-chat/unmount and avoid calling stopResponse() on open, preventing server-side turn abortion while still clearing local streaming UI state.
  • Add eager (deduped) conversation+session warm-up and abandon sends that race a switch, preventing cross-chat response rendering.
  • Add optimistic sidebar labels + global labelUpdated handling with server refresh fallbacks, so titles update promptly and reliably.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.

File Description
packages/conversational-agent-chat/src/ConversationalAgentChat.tsx Implements end-on-switch session lifecycle, eager/deduped session creation, switch-safe send behavior, and improved sidebar labeling (optimistic + global updates + refresh fallbacks).
packages/conversational-agent-chat/src/tests/ConversationalAgentChat.test.tsx Adds coverage for switch/new-chat races, orphan session flush+end behavior, stream UI clearing on open, eager warm reuse, and optimistic/generated label flows (including background delivery).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines 726 to +733
const endActiveSession = useCallback(() => {
const sessionHelper = session.current;
activeExchange.current = null;
session.current = null;
// Detach any in-flight creation from "current" so a fresh session is built
// for the next conversation instead of reusing this abandoned one.
sessionPromiseRef.current = null;
if (!sessionHelper) return;
applyConversationLabel(event.conversationId, event.labelUpdated.label);
}
});
}, [chatService, applyConversationLabel]);
…-switch

- End the conversation's session on switch/new-chat/unmount so an in-flight
  turn (still running server-side) stops streaming into the shared chat
  surface; reopened conversations load latest state from history.
- Clear streaming UI directly on open instead of Apollo stopResponse(), which
  would end the turn.
- Preserve a pending send across a too-early switch: flush on sessionStarted,
  then end the orphaned session.
- Create the conversation + session eagerly (launch, New Chat, inputs submit)
  so the first send has no create-await and can't race a switch; dedup the
  in-flight creation so a racing send reuses it.
- Sidebar labels: show the first user message (150-char) optimistically; apply
  the generated title globally via events.onAny so it lands for backgrounded
  chats; fetch fallback on open and turn-end.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 26, 2026 22:05
@scottcmg
scottcmg force-pushed the fix/cas-shared-event-chat-output branch from ee24457 to d164381 Compare July 26, 2026 22:05

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants