fix(web): always replay terminal buffer on mux open (#1689)#1690
fix(web): always replay terminal buffer on mux open (#1689)#1690fastestdevalive wants to merge 1 commit intoComposioHQ:mainComposioHQ/agent-orchestrator:mainfrom fastestdevalive:bugfix-empty-terminalfastestdevalive/agent-orchestrator:bugfix-empty-terminalCopy head branch name to clipboard
Conversation
Greptile SummaryThis PR fixes a blank terminal regression (#1689) introduced in v0.4.0 by restructuring the guard in the WebSocket
Confidence Score: 5/5Safe to merge — the change is a focused, single-file guard restructure with no new async paths or state mutations. The buffer-send is moved exactly one block outward with no other behavioral changes. The subscribe guard remains intact to prevent double live-delivery. Node.js's single-threaded event loop means there is no window for data loss between the buffer read and the subscribe call. The mock's no-op onData disposable correctly mirrors how TerminalManager actually delivers data (one shared pty.onData handler fans out to terminal.subscribers, so the per-call disposable is never used in production either). Eight new tests pin every lifecycle variant the fix touches. No files require special attention.
|
| Filename | Overview |
|---|---|
| packages/web/server/mux-websocket.ts | Guard restructured so buffer replay always fires on open; subscribe guard retained to prevent double live-delivery; TerminalManager exported for testing. Logic is correct and well-commented. |
| packages/web/server/tests/mux-websocket.test.ts | Eight new TerminalManager unit tests added with a clean node-pty mock; covers all critical lifecycle paths for the regression. Mock dispose being a no-op is consistent with how TerminalManager actually uses onData (single shared handler, not per-subscriber disposables). |
Sequence Diagram
sequenceDiagram
participant C as Browser (MuxProvider)
participant WS as WebSocket Handler
participant TM as TerminalManager
participant PTY as node-pty (tmux)
C->>WS: "open {id, tmuxName}"
WS->>TM: open(id, tmuxName)
TM-->>PTY: spawn / attach (if no PTY)
WS->>C: "{type:"opened"}"
note over WS,TM: Buffer replay — always, unconditional
WS->>TM: getBuffer(id)
TM-->>WS: accumulated tmux output (may be "")
alt buffer non-empty
WS->>C: "{type:"data", data: buffer}"
end
note over WS,TM: Subscribe — guarded by !subscriptions.has(id)
alt not yet subscribed on this connection
WS->>TM: subscribe(id, callback)
TM-->>WS: unsub()
WS->>WS: subscriptions.set(id, unsub)
end
PTY-->>TM: onData(live chunk)
TM-->>WS: callback(live chunk)
WS->>C: "{type:"data", data: chunk}"
C->>WS: close
WS->>TM: unsub() for each subscription
TM-->>PTY: pty.kill() (if last subscriber)
Reviews (1): Last reviewed commit: "fix(web): always replay terminal buffer ..." | Re-trigger Greptile
The buffer-skip guard in the mux WebSocket handler wrapped both the buffer send and the subscribe call, silently skipping screen replay on a same-connection re-open (e.g. React strict-mode double-mount). This left fresh-connect terminals blank in v0.4.0. Move the buffer send out from under the `subscriptions.has(id)` guard so every "open" message replays history. Keep the guard only around the subscribe call so a duplicate "open" doesn't double-deliver live data. Tmux's full-screen redraw sequences are idempotent, so re-sending to an already-subscribed client is safe. Adds TerminalManager unit tests covering empty-buffer-on-open, data accumulation, PTY lifecycle on subscriber drop, fresh PTY on re-open after kill, and multi-subscriber retention. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2e64b21 to
1bd7065
Compare
|
Thanks for the catch — confirmed and fixed. Root cause of the regression in this PR: my branch was based off a stale local `main` (`e94ff281`, May 1 15:45) that predates `b2cdf7ad` (the project-scoping commit, May 1 16:15). `b2cdf7ad` is not an ancestor of `e94ff281`, so the local `mux-websocket.ts` had zero `projectId` references when I made the edit. When the patch landed on real `upstream/main` (which has 35 `projectId` references), it stripped them. Fix: rebased onto `upstream/main` and re-applied the change preserving:
The PR diff is now exactly: move buffer-send out from under the `subscriptionKey` guard, keep the guard around `subscribe()` only — `projectId` plumbing untouched. Also added a test that pins the project-scoping invariant so this can't regress: two sessions with the same id under different projects keep their PTY data isolated, and cross-project `getBuffer` lookups don't leak. `pnpm --filter @aoagents/ao-web exec vitest run server/tests/mux-websocket.test.ts` — 18/18 pass. |
Summary
Fixes #1689 — dashboard terminal renders blank on connect after upgrading to v0.4.0.
The buffer-skip guard added in
78e57125wrapped both the buffer send and the subscribe call inif (!subscriptions.has(id)). This silently skipped tmux screen replay on a same-connection re-open (React strict-mode double-mount edge case), leaving the terminal pane blank even though the WebSocket reported "connected".This change moves the buffer send out from under the guard so every `open` message replays history. The guard is retained only around the `terminalManager.subscribe(...)` call so a duplicate `open` doesn't double-deliver live data. Re-sending the buffer to an already-subscribed client is safe — tmux's full-screen redraw sequences are idempotent and xterm just re-renders the same pane content.
Test plan
🤖 Generated with Claude Code