fix(copilot): stop chat panel from wiping on tool invocation (ENT-517)#2
Open
dustywusty wants to merge 1 commit intomainfrom
Open
fix(copilot): stop chat panel from wiping on tool invocation (ENT-517)#2dustywusty wants to merge 1 commit intomainfrom
dustywusty wants to merge 1 commit intomainfrom
Conversation
Root cause: CopilotKitProvider declares headers/properties/agents as default
`= {}` params, which create fresh object references on every render. Those
refs invalidate the internal useMemos for mergedHeaders/mergedAgents, which
re-fires the giant setRuntimeUrl/setRuntimeTransport effect mid-tool-run
and replaces the agent's threadId. The visible symptom was "chat resets to
welcome screen with no console errors" any time a tool was invoked.
Pass module-level constant {} for those four props so they stay stable
across renders and the runtime reconnect effect only runs once at mount.
Additional fixes surfaced while investigating:
- Lift threadId ownership from CopilotChat into CopilotOverlayPanel and
Dashboard; pass it explicitly to useAgent({threadId}) so quick-action
buttons (Search Contacts, Lead Triage) operate on the same threaded
agent clone CopilotChat renders from. Without this, addMessage went to
the BASE agent while the chat displayed a different thread's clone, so
the buttons appeared to do nothing.
- Wrap frontend-tool handlers with try/catch that returns {error, ok:false}
instead of throwing. A thrown handler caused CopilotKit's RunHandler to
abort the run and spawn a fresh thread on the next user message — same
user-visible wipe symptom from a different cause.
- Add res.ok guards in useGetTopLeads, useGetContactsByCompany,
useSearchContacts so HTTP errors become typed errors instead of
res.json() parse explosions.
- useCreateTask now validates contactId against the data provider before
creating, and sets sales_id from the logged-in identity (the dashboard's
Upcoming Tasks widget filters by sales_id, so omitting it hid chat-
created tasks).
- useSearchContacts exposes firstName/lastName params (server already
supported them) so the agent can look up contacts by name rather than
guessing at company-only filters.
- useGetTopLeads pre-shapes its response to match LeadPriorityList's prop
names (contactId, name, score, lifecycleStage, lastActivity), so the
agent doesn't have to translate snake_case → camelCase per row.
- Wrap CopilotOverlayPanelGate in its own ErrorBoundary so an uncaught
render error in the panel surfaces a visible fallback instead of
silently unmounting the subtree.
- Broaden the demo vite proxy from /api/copilotkit to /api/* and add a
COPILOTKIT_PROXY_TARGET env-var override, so dev can run the frontend
alone against the deployed CopilotKit backend without touching CORS.
README updated with the workflow.
e6f8661 to
0cd5f6b
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The deployed Copilot chat would silently wipe back to the welcome screen any time the model invoked a tool. Three independent bugs all produced the same symptom; this PR fixes the root cause + the worst secondary issues that surfaced once the chat stopped wiping.
Root cause — CopilotKitProvider thrashing
@copilotkit/react-core/v2'sCopilotKitProviderdeclaresheaders = {}, properties = {}, agents__unsafe_dev_only = {}, selfManagedAgents = {}as default param values. JS evaluates those defaults on every render, producing fresh{}literals each time. Those refs invalidate the internaluseMemos formergedHeaders/mergedAgents. The deps of the giant setupuseEffect(setRuntimeUrl+setRuntimeTransport+setHeaders+setProperties+setAgents__unsafe_dev_only) include those memos, so the effect re-fires on every render. Mid-tool-run, this reconnects the runtime and replaces the agent's threadId — the visible "chat reset to welcome" symptom.Fix: pass module-level constant
{}for those four props inCopilotProvider.tsx. Refs stay stable, the effect runs once at mount, no thread thrashing.Secondary bugs surfaced after the chat stopped wiping
getOrCreateThreadClone);useAgent()outside<CopilotChat>returned the BASE agent while the chat rendered a CLONED agent for its own thread. LiftedthreadIdownership up toCopilotOverlayPanelandDashboard, pass it touseAgent({ threadId }), and forward it to<CopilotChat>so both ends share the same threaded clone.useAuditedFrontendToolto catch and return{ error, ok: false }instead of re-throwing.res.json()exploded on HTTP errors inuseGetTopLeads,useGetContactsByCompany,useSearchContacts(nores.okcheck). Added guards.useCreateTasknow validatescontactIdviadataProvider.getOnebefore creating, and setssales_idfrom the logged-in identity (the dashboard's Upcoming Tasks widget filters bysales_id, so chat-created tasks were invisible).useSearchContactsnow acceptsfirstName/lastNameparameters (the server already supported them); previously the agent had no way to look up a contact by name.useGetTopLeadsnow pre-shapes its response to matchLeadPriorityList's prop names so the agent doesn't have to translate snake_case → camelCase per row.CopilotOverlayPanelGatein its own<ErrorBoundary>so a render error in the panel shows a visible fallback instead of silently unmounting.Dev-quality changes
/api/*(was/api/copilotkit) and accepts aCOPILOTKIT_PROXY_TARGETenv var, so frontend devs can hit the deployed CopilotKit backend without running the local server.Test plan
npm run typecheckcleannpm run lintclean (no new warnings on changed files)getTopLeadsruns,LeadPriorityListrenders with names linking to contact pages, scores, and "Lifecycle · Last activity: …" subtitlessearchContactsfinds her,createTasksucceeds, task appears under Shelley's contact page AND on the dashboard's Upcoming Tasks widget