PR: #406: feat(agents): Dock live dock_chat WS rail — real Atlas turn, drop turnResolved stub [Story-1665] by @ankitmaheshwari-com
View PR →

Story-1665 — Dock WS streaming: real Atlas dock_chat turn

EPIC-1646 · Lane C · pramaan-app · Closes pramaan-io/pramaan-spec#1665

0. The defect this closes

The Agent Dock on every workspace page LOOKED live but FAKED its answer. On send, agent-dock-mount.tsx POSTed the /session-context/resolve authority handshake and then appended a canned i18n string — turnResolved: "Got your message — I'm oriented on this page and ready." — as if it were Atlas. The brain (AgentCore) was never invoked: zero service.name=atlas Dock spans in Logfire. The component doc admitted it: "the brain's streamed reply … is a distinct runtime boundary not wired here."

1. What this Story wires

The Dock's send now launches a real kind:"dock_chat" turn over the ADR-110 broker WebSocket and streams Atlas's answer into the thread. The producers it depends on are already on main: the contract kind/DockChatContext (#1663), the broker routing (#1660), the Atlas dock-chat brain (#1664), and the synced src/gen (#1669).

The rail flow

composer send
  → useDockChatRail.send(text)
  → AgentRailConnection (shared transport; dock launch builder)
  → WS { agentKey:"atlas_dock_chat", kind:"dock_chat",
         dockChat:{ surface_key, available_surfaces, granted_permissions,
                    current_context, counsel_message, thinking_level } }
  → broker routes on kind → AgentCore Atlas dock_chat (#1664)
  → frames: run.accepted → turn.delta* → turn.completed → run.done
  → reduceFrame → streamingMessageMd (live bubble) → committed reply in thread
  → navigate_to (if any) → confirm-first nav chip (dispatcher, never auto-route)

2. Shared, not forked

The wizard intake rail and the Dock chat rail are now the same module-scoped connection machinery (atlas-rail-store) — the hard-won reconnect / watchdog / StrictMode-survival logic (Story-1373) is reused, not duplicated. The only agent-specific bit is the launch/query message, injected as a builder. The wizard path is byte-unchanged (its tests pass untouched).

FileChange
lib/agents/atlas-rail.tsbuildDockChatLaunch/buildDockChatQuery; RailNavigateTo; reduceFrame + RailState capture a completed turn's navigate_to.
lib/agents/atlas-rail-store.tsConnection generalized — injected launch/query builders + generic prime. getAtlasRailConnection (wizard) kept; getDockChatRailConnection added.
lib/agents/use-dock-chat-rail.tsNew hook: builds DockChatContext from live CPC + surface + permissions + manifest + thinking_level; exposes status/stream/turn/navigateTo/send.
components/agents/agent-dock-mount.tsxSwapped HTTP resolve+turnResolved → WS rail; live streaming bubble; commit-once-per-run effect; navigate_to → confirm-first chip; status mapping.
messages/en.json, hi.jsonDeleted the orphan turnResolved key (both locales).

3. Trust boundary (RFC-APB-002)

The DockChatContext blocks (available_surfaces, granted_permissions, current_context) are orientation only. The broker re-resolves real authority from the Clerk session on the socket — a forged client context grants nothing. Browser context never becomes authority.

4. Verification

Files changed

6 file(s), +448 / -113 lines

Path Role + -
messages/en.jsonConfig+0-1
messages/hi.jsonConfig+0-1
src/components/agents/agent-dock-mount.tsxComponent+87-48
src/lib/agents/atlas-rail-store.tsLibrary+131-61
src/lib/agents/atlas-rail.tsLibrary+94-2
src/lib/agents/use-dock-chat-rail.tsLibrary+136-0