Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

fix(web): always replay terminal buffer on mux open (#1689)#1690

Open
fastestdevalive wants to merge 1 commit intoComposioHQ:mainComposioHQ/agent-orchestrator:mainfrom
fastestdevalive:bugfix-empty-terminalfastestdevalive/agent-orchestrator:bugfix-empty-terminalCopy head branch name to clipboard
Open

fix(web): always replay terminal buffer on mux open (#1689)#1690
fastestdevalive 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

@fastestdevalive
Copy link
Copy Markdown
Collaborator

Summary

Fixes #1689 — dashboard terminal renders blank on connect after upgrading to v0.4.0.

The buffer-skip guard added in 78e57125 wrapped both the buffer send and the subscribe call in if (!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.

  • File: `packages/web/server/mux-websocket.ts` — guard restructure + comment refresh
  • File: `packages/web/server/tests/mux-websocket.test.ts` — `TerminalManager` unit tests (empty buffer on fresh open, data accumulation, PTY lifecycle on subscriber drop, fresh PTY after re-open, multi-subscriber retention)

Test plan

  • `pnpm --filter @aoagents/ao-web exec vitest run server/tests/mux-websocket.test.ts` — 17/17 pass (8 new)
  • `pnpm --filter @aoagents/ao-web test` — same baseline (3 pre-existing test files unrelated to this PR remain failing on `main`); +8 new passing
  • `pnpm --filter @aoagents/ao-core test` — 1010/1010 pass
  • `pnpm lint` on changed files — clean
  • Manual smoke: `ao start` → open session in dashboard → terminal renders pane content immediately
  • Manual smoke: drop network briefly → MuxProvider reconnects → terminal still shows live output

🤖 Generated with Claude Code

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 7, 2026

Greptile Summary

This PR fixes a blank terminal regression (#1689) introduced in v0.4.0 by restructuring the guard in the WebSocket open message handler: the buffered history replay is now unconditional, while the terminalManager.subscribe() call retains the !subscriptions.has(id) guard to prevent double-delivery of live PTY events.

  • mux-websocket.ts — moves the getBuffer send outside the !subscriptions.has(id) check and exports TerminalManager for unit testing; adds a detailed explanatory comment.
  • mux-websocket.test.ts — adds eight new TerminalManager unit tests covering empty-buffer-on-fresh-open, data accumulation, PTY lifecycle (kill on last-subscriber-leave), fresh-PTY-on-re-open, and multi-subscriber retention.

Confidence Score: 5/5

Safe 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.

Important Files Changed

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)
Loading

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>
@fastestdevalive fastestdevalive force-pushed the bugfix-empty-terminal branch from 2e64b21 to 1bd7065 Compare May 7, 2026 01:09
@fastestdevalive
Copy link
Copy Markdown
Collaborator Author

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:

  • `terminalManager.getBuffer(id, projectId)`
  • `bufferMsg` with `...(projectId && { projectId })`
  • `subscriptions.has(subscriptionKey)` (not `subscriptions.has(id)`)

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.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Dashboard terminal renders blank on connect after upgrading to v0.4.0

1 participant

Morty Proxy This is a proxified and sanitized view of the page, visit original site.