Make agentic chat initialization route-driven and unconditional (stream adoption additive)

Problem

In the chat state manager component (duo_agentic_chat_state_manager.vue), the connectToStream() method gates view initialization on a module-level websocket "connected" flag. If a reply is already streaming, it skips initializing from the route and instead attaches to that reply. Since the history route now has its own component that remounts on entry, initialization correctness depends on that flag being accurate at that moment. The flag is a live, moment-to-moment signal that can override the source of truth (the route).

Proposed change

Make view initialization unconditional, deriving it from the route. Stream adoption becomes a separate, additive step that runs after the view loads.

Refactor connectToStream() to:

  1. Await initializeView() (always initialize from the route first)
  2. Call adoptRunningStream() if a reply is actively streaming
  3. Subscribe to the stream

The new adoptRunningStream() method checks for a running reply, anchors lastProcessedMessageId to the last loaded message, and shows the waiting state. initializeView() becomes awaitable so adoption runs after the transcript loads.

Behaviour change and risk

Today, when a reply is streaming, the code intentionally skips reloading the conversation. Tier 2 reloads it and then attaches. This is the one real behaviour change.

Safety depends on two factors:

  1. The dedup marker (lastProcessedMessageId) is set from the loaded transcript before the stream buffer replays, preventing duplication.
  2. On the new-chat route, onNewChat already disconnects the socket (via cleanupState and cleanupSocket), so a stale reply is not adopted onto a blank chat.

Test scenarios

  • Open a thread while a reply is mid-stream: transcript loads and the reply keeps flowing, with the dedup marker anchored to the last loaded message.
  • Open a thread with no live reply: normal load, no phantom waiting spinner.
  • Land on the new-chat route while a previous reply is still streaming: starts blank, does not hydrate, does not adopt the old reply.

Deferred

hydrateActiveWorkflow contains its own reconnect logic for the "running server-side but socket not connected" case. Moving that into adoptRunningStream (so hydration only loads messages) is a further cleanup, deferred because hydrateActiveWorkflow has several other callers that also rely on that reconnect.

References

Edited by 🤖 GitLab Bot 🤖