Skip to content

ChatPanel profile selector can mismatch the active external-agent thread #141

Description

@mydmdm

Bug

In some cases, especially after a ChatPanel has been left inactive for a long time or after refreshing a long-open ChatPanel, the profile selector can show the wrong profile even though the panel's thread history and thread ID are correct.

A common example is an open panel whose thread belongs to an external agent node: the displayed history and thread ID correspond to that external-agent thread, but the profile selector below incorrectly displays chat.

Expected behavior

The profile selector should be derived from the currently active thread/panel context and should display the profile associated with that thread. Refreshing or resuming an idle panel must not replace the selected profile with a stale or default value.

Actual behavior

The ChatPanel can show the correct external-agent thread history and thread ID while the profile selector displays chat, producing an inconsistent UI state and making it unclear which profile will be used for the next action.

Conditions observed

  • ChatPanel has been open and inactive for an extended period.
  • A long-open ChatPanel is refreshed.
  • The panel contains a long thread history.
  • The active thread belongs to an external agent node.

Investigation notes

Please trace profile selection and synchronization across ChatPanel refresh/resume, thread hydration, thread switching, and external-agent thread loading. Check for stale closure/state, default-profile fallback, or an update ordering/race where thread metadata arrives after the selector state is initialized.

Acceptance criteria

  • The profile selector consistently matches the active thread's profile after refresh, resume, and thread switching.
  • External-agent threads never fall back visually to chat when their thread metadata identifies another profile.
  • Existing thread history, thread ID, draft persistence, and profile switching behavior remain intact.
  • Add regression coverage for a refreshed/rehydrated long-lived panel and an external-agent thread.

Activity

  1. mydmdm commented on Aug 27, 2026

    @mydmdm
    ContributorAuthor

    Additional sanitized evidence from a screenshot

    The affected panel can show a prominent error banner stating that the thread cannot change driver kind, while the profile selector below still displays Chat. The composer remains visible and shows GPT-5.6 Sol with Auto mode selected.

    This supports the report's core symptom: thread/panel state and the profile/driver selection can become inconsistent after rehydration or refresh. Thread identifiers and other potentially identifying details from the screenshot are intentionally omitted.

  2. mydmdm commented on Sep 11, 2026

    @mydmdm
    ContributorAuthor

    Code-level finding from a current reproduction (huabu dev displayed as Chat)

    The server-side Agent Node creation path appears correct: apps/server/src/modules/agent/agent-node.service.ts resolves the selected Profile and creates the Question node with an external agentBinding (profileId + alias) and agentBindingPolicy: 'fixed'.

    The mismatch is introduced by Web state ownership/hydration:

    • ChatPanel reads agentBinding from chatStore via selectThreadBinding(state, threadId).
    • chatStore.threadOf() returns DEFAULT_BINDING = { kind: 'internal' } whenever that thread has no cached/persisted binding, which renders as Chat.
    • Question-owned thread metadata is deliberately made ephemeral, so after reload its binding is expected to come from the Question/Agent node rather than bindingByThread.
    • Opening a node interactively calls enterQuestionConversation(..., data.agentBinding, ...), which seeds the store before mounting the panel. A Preview Workspace tab restored directly after refresh does not pass through that entry path.
    • The restored ChatPanel therefore initially uses the internal default and only later attempts to repair it in a useEffect from conversationOwnerSource.agentBinding when agentBindingPolicy === 'fixed'.
    • useChatHistory hydrates messages but does not hydrate the thread binding. If the conversation owner is temporarily unavailable/stale during Canvas or World-reference hydration, the repair effect has no authoritative binding and the selector can remain Chat despite the correct thread/history.

    This also explains why the symptom is associated with refresh/resume and long-lived panels rather than new interactive opens.

    Suggested fix direction

    For a fixed Agent Node, derive the effective binding synchronously from the authoritative conversation owner (fixedAgentBinding ?? cachedThreadBinding) and use that value consistently for the selector, title, ACP hooks, and send path. The store synchronization effect can remain a cache update, but should not be the correctness boundary. Restored tabs should also be covered where the thread cache is empty and owner hydration arrives after the first render.

    Regression coverage

    Add a test that mounts/restores a Question/Agent Preview tab with:

    • an empty chatStore binding cache,
    • a fixed external node binding for huabu dev, and
    • owner data arriving after the initial render.

    The selector may show a neutral loading state before owner resolution, but must never identify the fixed external thread as Chat, and all downstream ACP behavior must use the fixed binding.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions