Files
Dusty aa08743c7e fix(core): only reset agent state on actual thread switch (supersedes #4720)
When the chat's connect effect re-fires due to React effect-dep churn, it
calls copilotkit.connectAgent({ agent }) on the same thread again. The
RunHandler previously called agent.setMessages([]), agent.setState({}),
and (transitively) clearReconnectCursor on every such call. That forced
the realtime gateway to replay the topic's full event history on every
churn re-connect — sending the same persisted cpki_event_ids 2-3 times
per thread switch — and produced both halves of Tyler's bug: duplicate
rows in the inspector AG-UI Events tab plus "Message not found" toasts
when the next runAgent fired with an empty agent.messages.

This change makes the orchestrator detect actual thread switches:

- RunHandler tracks _lastConnectedThreadId across connectAgent calls.
  On a fresh restore (different threadId from last call) it does the
  reset and clears the replay cursor. On churn (same threadId) it
  skips the reset entirely so local messages/state are preserved and
  the gateway resumes from lastSeenEventId instead of replaying.

- IntelligenceAgent.connect() no longer auto-clears the cursor;
  cursor management is the caller's decision now. clearReconnectCursor
  is made public so RunHandler (and tests) can call it explicitly.

- ProxiedCopilotRuntimeAgent exposes a clearReplayCursor(threadId)
  method that delegates to the IntelligenceAgent. Non-Intelligence
  runtime modes are a safe no-op.

This supersedes #4720, which attempted to solve only the inspector
duplicate-row symptom by adding a dispatcher dedup. That approach
introduced a blocking regression in the A → B → A restore path: the
dedup persisted across thread switches and suppressed the gateway's
restore replay, leaving agent.messages at [] and triggering the very
"Message not found" toast we were trying to prevent. Mike's review
caught it. The dispatcher dedup is removed entirely; the
orchestrator-level gate is sufficient and a smaller surface change.

Tests:
- intelligence-agent.test.ts: updated 4 tests that codified the old
  "always clear cursor" semantics. Added an explicit test that
  clearReconnectCursor() empties the cursor for the next connect, and
  Mike's regression — replay the same cpki_event_id after A → B → A
  and verify the rehydrate still produces the user message.
- core-connect-thread-switch.test.ts (new): four tests for the
  RunHandler gate covering churn, single switch, A → B → A, and the
  no-clearReplayCursor non-Intelligence path.

All 430 core tests pass.
2026-05-08 14:31:49 -06:00
..
2026-04-13 14:57:48 +02:00
2026-04-13 14:55:54 +02:00