fix(frontend): keep agent chat mounted across revision switches#4997
Conversation
The agent playground conversation lives inside a generation subtree keyed by
the revision id (`ExecutionItems key={variantId}` in MainLayout), so every
revision switch — the agent self-committing a new revision, or the user picking
one in the config header — remounted the whole conversation. The remount aborted
the in-flight stream (surfaced as "connection lost") and re-seeded messages from
localStorage, which omits the still-streaming turn (persistence is skipped
mid-stream), so the transcript for that turn vanished.
Decouple the conversation's mount identity from the revision id at the two layers
that tear it down:
- MainLayout: give the single-agent generation panel a stable key instead of the
revision id, so a switch flows through as an `entityId` prop update, not a
remount. Agents are single-entity (excluded from comparison).
- ExecutionItems: latch the agent surface so it keeps rendering across the
fresh-revision load gap (where flags load a beat late, isAgent flips false and
the query goes pending) instead of dropping to the skeleton and unmounting the
chat.
Both use a flip-proof latch keyed on query pending state, released only once the
entity has loaded as a definitively non-agent workflow. Non-agent chat/completion/
comparison and the create/edit drawer are untouched. FE-only; the conversation is
already app/session-scoped in localStorage, so no backend change is needed.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (3)
Disabled knowledge base sources:
📝 WalkthroughSummary by CodeRabbit
WalkthroughThe changes keep agent chat requests tied to the latest entityId without recreating transport on entity switches, and they latch agent-generation rendering in Playground so the agent UI persists across entity/revision transitions and pending states. ChangesAgent UI/transport stability across entity switches
Estimated code review effort: 3 (Moderate) | ~25 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
…switch Follow-up to the mount-decoupling fix. `useChat` pins its `Chat` (and the transport it holds) for the life of the session `id`; it is not recreated when `entityId` changes. The transport's `prepareSendMessagesRequest` captured `entityId` by value, and `buildAgentRequest` reads the config/references from whatever id it's given — so once the conversation no longer remounts on a revision switch, every subsequent turn would have gone out with the config of the revision that was displayed when the session first mounted. Read `entityId` through a ref inside the request builder so each send resolves the currently displayed revision. This restores the pre-fix behavior (a switch or a self-commit makes the next turn run against the new revision) without the remount that used to reset the stream.
|
@coderabbitai review |
✅ Action performedReview finished.
|
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro Plus
Run ID: 10d6c19c-8d2f-40a4-9382-8d85f1ef58a8
📒 Files selected for processing (3)
web/oss/src/components/AgentChatSlice/AgentChatPanel.tsxweb/oss/src/components/Playground/Components/MainLayout/index.tsxweb/packages/agenta-playground-ui/src/components/ExecutionItems/index.tsx
Addresses a review note. The latched agent branch rendered ExecutionHeader, which self-nulls for agents (`if (isAgent) return null`). But during the revision-switch load gap the new revision's flags aren't loaded yet, so isAgent is momentarily false — ExecutionHeader then flashed the non-agent header (Clear / Run all) and registered the run-all shortcut over the agent chat until the revision resolved. Agents own their composer and never show this header, so the latched branch skips it. Mount stability is unchanged (the panel keeps its position across the gap).
Problem
In the agent playground, switching the displayed revision — the agent self-committing a new revision, or the user picking one in the config header — caused two symptoms at once:
Both are one root cause. The conversation lives inside a generation subtree keyed by the revision id (
<ExecutionItems key={variantId}>inMainLayout→AgentChatPanel→useChat). A revision switch changes that key and remounts the whole conversation:useChatre-seeds messages from localStorage, which omits the still-streaming turn (persistence is deliberately skipped mid-stream) → the transcript vanishes.A one-line "defer the switch" isn't enough: the manual picker remounts the same way, and even keeping
ExecutionItemsmounted isn't sufficient becauseExecutionItemsitself drops to its loading skeleton / swaps render modes during the fresh-revision load gap.Fix
Decouple the conversation's mount identity from the revision id at the two layers that tear it down:
MainLayout— give the single-agent generation panel a stable key instead ofvariantId, so a switch flows through as anentityIdprop update, not a remount. (Agents are single-entity; they're excluded from comparison.)ExecutionItems— latch the agent surface so it keeps rendering across the fresh-revision load gap (where flags load a beat late:isAgentflips false and the query goes pending) instead of unmounting the chat to show the skeleton.Both use a flip-proof latch keyed on the workflow query's pending state, released only once the entity has loaded as a definitively non-agent workflow (
isPending === false ⟺ flags loaded ⟺ agent-ness is accurate).Follow-up: keep new turns on the live revision
Removing the remount exposed a second issue in the request chain.
useChatpins itsChat(and the transport it holds) for the life of the sessionidand does not recreate it whenentityIdchanges. The transport'sprepareSendMessagesRequestcapturedentityIdby value, andbuildAgentRequestreads the config/referencesfrom whatever id it's given — so without a remount, every subsequent turn would have gone out with the config of the revision displayed when the session first mounted. Fixed by readingentityIdthrough a ref in the request builder, so each send resolves the currently displayed revision (restoring the pre-fix "next turn uses the new revision" behavior, without the stream-resetting remount).Net effect:
sessionIdnever changes across a revision switch, souseChatkeeps its live stream and messages — no abort, no re-seed — and new turns still run against the live revision's config.Scope / blast radius
Testing
Verified live against a running
big-agentsEE-dev stack:Also:
tscclean on all touched files, ESLint + prettier clean.Recommended extra QA for the follow-up: after switching revisions mid-conversation, send a new message and confirm the outgoing request body carries the newly selected revision's config/
references(inspect the network request) — the visual checks above don't cover which revision the next turn runs against.