fix: surface terminal-server connection errors in agent chat#7
Conversation
When the chat tab auto-creates a session via POST /api/sessions/for-agent,
a silent `.catch(() => {})` swallowed the fetch failure if terminal-server
was unreachable. The parent left `activeChatSessionId` as null, AgentChat
received `sessionId=undefined`, its connect effect early-returned, and the
UI sat silently with a non-functional input — no feedback, no error.
Surface the failure through to the chat view:
- AgentDetail tracks two new states, `chatConnecting` and `chatConnectError`,
set around the find-or-create fetch. On failure the error message matches
AgentTerminal's wording ("Could not reach terminal-server at {url}. Is it
running?"). Both are passed down as `externalLoading` / `externalError`.
- AgentChat accepts the two new optional props and folds them into a single
`effectiveError` / `inputDisabled`. It renders a small amber "Connecting…"
pill in the top-right corner while the session is being created, swaps it
for a red error pill if the fetch fails, and disables the textarea (plus
the send button via `canSend`) while either state is active. The rest of
the chat layout stays mounted.
- AgentChat also does its own HTTP preflight (`GET /api/health`) before
opening the WebSocket, matching AgentTerminal's pattern, so a
terminal-server that dies mid-session also flips to the same error pill
instead of hanging forever on a WebSocket that never resolves.
- Removed the center-bottom status text that the corner pill replaces.
Reviewer's GuideSurfaced terminal-server connectivity errors in the agent chat flow by propagating session creation failures from AgentDetail into AgentChat, adding HTTP health preflight before the WebSocket connection, unifying internal/external loading & error handling into a corner status pill, and disabling input while connection issues are present. Sequence diagram for agent chat session creation and connectivity handlingsequenceDiagram
actor User
participant AgentDetail
participant AgentChat
participant TerminalServer_HTTP as TerminalServer_HTTP
participant TerminalServer_WS as TerminalServer_WS
User->>AgentDetail: Switch viewMode to chat
AgentDetail->>AgentDetail: useEffect_createChatSession
AgentDetail->>AgentDetail: set chatConnectError = null
AgentDetail->>AgentDetail: set chatConnecting = true
AgentDetail->>TerminalServer_HTTP: POST /api/sessions/for-agent
alt Session creation succeeds
TerminalServer_HTTP-->>AgentDetail: 200 OK + sessionId
AgentDetail->>AgentDetail: add ChatSession to chatSessions
AgentDetail->>AgentDetail: set activeChatSessionId
AgentDetail->>AgentDetail: set chatConnecting = false
AgentDetail->>AgentChat: Render with externalLoading=false externalError=null sessionId
AgentChat->>AgentChat: useEffect_connectWebSocket
AgentChat->>AgentChat: setStatus(connecting)
AgentChat->>AgentChat: setErrorMsg(null)
AgentChat->>TerminalServer_HTTP: GET /api/health
alt Health check ok
TerminalServer_HTTP-->>AgentChat: 200 OK
AgentChat->>TerminalServer_WS: WebSocket connect /ws
TerminalServer_WS-->>AgentChat: onopen
AgentChat->>TerminalServer_WS: send join_session(sessionId)
TerminalServer_WS-->>AgentChat: session_joined + optional chatHistory
AgentChat->>AgentChat: setStatus(idle)
AgentChat->>AgentChat: render status pill hidden
else Health check fails
TerminalServer_HTTP-->>AgentChat: Error or non 2xx
AgentChat->>AgentChat: setStatus(error)
AgentChat->>AgentChat: setErrorMsg("Could not reach terminal-server at TS_HTTP. Is it running?")
AgentChat->>AgentChat: show error pill, disable input
end
else Session creation fails
TerminalServer_HTTP-->>AgentDetail: Network error or non 2xx
AgentDetail->>AgentDetail: set chatConnectError("Could not reach terminal-server at TS_HTTP. Is it running?")
AgentDetail->>AgentDetail: set chatConnecting = false
AgentDetail->>AgentChat: Render with externalLoading=false externalError=chatConnectError sessionId undefined
AgentChat->>AgentChat: derive inputDisabled from externalError
AgentChat->>AgentChat: show error pill, disable input
end
Class diagram for AgentDetail and AgentChat connectivity state changesclassDiagram
class AgentDetail {
+ChatSession[] chatSessions
+string activeChatSessionId
+string chatConnectError
+boolean chatConnecting
+useEffect_createChatSession()
}
class ChatSession {
+string sessionId
+string title
+number createdAt
}
class AgentChatProps {
+string agent
+string sessionId
+string accentColor
+boolean externalLoading
+string externalError
}
class AgentChat {
+ChatMessage[] messages
+string input
+Status status
+boolean isThinking
+string errorMsg
+boolean isDragging
+boolean isConnecting
+string effectiveError
+boolean inputDisabled
+boolean canSend
+useEffect_connectWebSocket()
+handleChatEvent(event)
}
class ChatMessage {
+string role
+string text
+number ts
+boolean streaming
}
class Status {
<<enumeration>>
idle
connecting
running
error
}
AgentDetail "1" o-- "*" ChatSession : manages
AgentDetail "1" --> "1" AgentChat : renders_with_props
AgentChat "1" -- "1" AgentChatProps : uses
AgentChat "1" o-- "*" ChatMessage : displays
AgentChat --> Status : has_status
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've found 1 issue, and left some high level feedback:
- The terminal-server HTTP base URL and health-preflight logic are now duplicated between AgentChat and AgentTerminal/AgentDetail; consider extracting a small shared helper/module so the URL construction and error messaging stay consistent in one place.
- In the AgentChat WebSocket effect, you’re manually tracking
cancelled; using anAbortControllerwithfetch(and checkingsignal.aborted) would make cancellation more explicit and avoid potential future races if additional async steps are added.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The terminal-server HTTP base URL and health-preflight logic are now duplicated between AgentChat and AgentTerminal/AgentDetail; consider extracting a small shared helper/module so the URL construction and error messaging stay consistent in one place.
- In the AgentChat WebSocket effect, you’re manually tracking `cancelled`; using an `AbortController` with `fetch` (and checking `signal.aborted`) would make cancellation more explicit and avoid potential future races if additional async steps are added.
## Individual Comments
### Comment 1
<location path="dashboard/frontend/src/pages/AgentDetail.tsx" line_range="76" />
<code_context>
useEffect(() => {
if (!sessionId) return
</code_context>
<issue_to_address>
**issue (bug_risk):** Auto-create chat session effect can issue multiple POSTs if the user toggles view modes quickly
Because the effect depends on `viewMode`, `chatSessions.length`, and `name`, quickly toggling away from and back to `chat` while there is no session can fire multiple `POST /api/sessions/for-agent` requests in parallel, creating duplicate server sessions even though only the last response is used locally.
To prevent this, gate the call on a "request in flight" flag (e.g. `chatConnecting`) and add it to the dependency array:
```ts
if (viewMode === 'chat' && chatSessions.length === 0 && name && !chatConnecting) {
...
}
```
so a new request isn’t started while one is already pending.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| const [chatConnecting, setChatConnecting] = useState(false) | ||
|
|
||
| // Track agent visit for "Recent" section | ||
| useEffect(() => { |
There was a problem hiding this comment.
issue (bug_risk): Auto-create chat session effect can issue multiple POSTs if the user toggles view modes quickly
Because the effect depends on viewMode, chatSessions.length, and name, quickly toggling away from and back to chat while there is no session can fire multiple POST /api/sessions/for-agent requests in parallel, creating duplicate server sessions even though only the last response is used locally.
To prevent this, gate the call on a "request in flight" flag (e.g. chatConnecting) and add it to the dependency array:
if (viewMode === 'chat' && chatSessions.length === 0 && name && !chatConnecting) {
...
}so a new request isn’t started while one is already pending.
When the chat tab auto-creates a session via POST /api/sessions/for-agent, a silent
.catch(() => {})swallowed the fetch failure if terminal-server was unreachable. The parent leftactiveChatSessionIdas null, AgentChat receivedsessionId=undefined, its connect effect early-returned, and the UI sat silently with a non-functional input — no feedback, no error.Surface the failure through to the chat view:
AgentDetail tracks two new states,
chatConnectingandchatConnectError, set around the find-or-create fetch. On failure the error message matches AgentTerminal's wording ("Could not reach terminal-server at {url}. Is it running?"). Both are passed down asexternalLoading/externalError.AgentChat accepts the two new optional props and folds them into a single
effectiveError/inputDisabled. It renders a small amber "Connecting…" pill in the top-right corner while the session is being created, swaps it for a red error pill if the fetch fails, and disables the textarea (plus the send button viacanSend) while either state is active. The rest of the chat layout stays mounted.AgentChat also does its own HTTP preflight (
GET /api/health) before opening the WebSocket, matching AgentTerminal's pattern, so a terminal-server that dies mid-session also flips to the same error pill instead of hanging forever on a WebSocket that never resolves.Removed the center-bottom status text that the corner pill replaces.
Summary by Sourcery
Surface terminal-server connectivity failures in agent chat by propagating session creation errors to the chat UI and improving connection handling.
Bug Fixes:
Enhancements: