Problem
When the operator navigates to another page (Home, Settings, different project) and returns, agent terminal panels that were showing live output now appear blank. Only the actively-producing agent (e.g. Dev mid-task) shows content — idle agents (Head, Reviewer1, Reviewer2) show empty black panels until they produce new output.
Root cause
The terminal panel's WebSocket connection is torn down when the component unmounts (page navigation). On remount, a new WebSocket connects to the server's PTY stream, but the server only streams live output going forward — it does not replay the PTY's scrollback buffer. So any output produced before the reconnect is lost from the client's view.
The PTY process itself still has the full scrollback in memory (node-pty maintains it). The gap is that the WebSocket handler only forwards new `data` events, not historical buffer content.
Fix
Server-side: maintain a per-session scrollback ring buffer
In `server/index.js`, when creating each agent's PTY session, keep a ring buffer of the last N bytes (e.g. 32KB or 64KB) of PTY output:
```js
const SCROLLBACK_SIZE = 64 * 1024; // 64KB
session.scrollback = Buffer.alloc(0);
term.onData((data) => {
// Append to ring buffer
session.scrollback = Buffer.concat([session.scrollback, Buffer.from(data)]);
if (session.scrollback.length > SCROLLBACK_SIZE) {
session.scrollback = session.scrollback.slice(-SCROLLBACK_SIZE);
}
// Forward to connected WebSocket clients (existing behavior)
// ...
});
```
WebSocket handler: replay scrollback on connect
When a new WebSocket client connects to `/ws/terminal?project=X&agent=Y`, send the scrollback buffer before streaming live data:
```js
ws.on("open", () => {
// Replay scrollback
if (session.scrollback && session.scrollback.length > 0) {
ws.send(session.scrollback);
}
// Then stream live (existing behavior)
});
```
Client-side: no changes needed
The xterm.js terminal instance processes the replayed data the same way it processes live data — ANSI escape sequences, cursor positioning, colors all render correctly from the buffer replay. The terminal panel just needs to receive the data; xterm handles the rest.
Acceptance
- Navigate to Home page, wait 10 seconds, navigate back to project → all 4 agent terminals show their most recent output (prompt, last command output, status line)
- Active agent (mid-task) shows full context, not just the line being written at the moment of reconnect
- Idle agents show their last prompt / status line, not a blank panel
- Scrollback is bounded (64KB) so memory doesn't grow unbounded for long-running sessions
- No duplicate output — the scrollback replay happens once on connect, then live streaming takes over
Out of scope
- Persisting scrollback across server restarts (PTY scrollback is inherently ephemeral)
- Infinite scrollback / searchable history
- Terminal recording / playback
Files
- `server/index.js` — PTY session creation (`term.onData` handler), WebSocket `/ws/terminal` handler
- No client-side changes needed
Branch
`task/XXX-terminal-scrollback-replay`
Related
Problem
When the operator navigates to another page (Home, Settings, different project) and returns, agent terminal panels that were showing live output now appear blank. Only the actively-producing agent (e.g. Dev mid-task) shows content — idle agents (Head, Reviewer1, Reviewer2) show empty black panels until they produce new output.
Root cause
The terminal panel's WebSocket connection is torn down when the component unmounts (page navigation). On remount, a new WebSocket connects to the server's PTY stream, but the server only streams live output going forward — it does not replay the PTY's scrollback buffer. So any output produced before the reconnect is lost from the client's view.
The PTY process itself still has the full scrollback in memory (node-pty maintains it). The gap is that the WebSocket handler only forwards new `data` events, not historical buffer content.
Fix
Server-side: maintain a per-session scrollback ring buffer
In `server/index.js`, when creating each agent's PTY session, keep a ring buffer of the last N bytes (e.g. 32KB or 64KB) of PTY output:
```js
const SCROLLBACK_SIZE = 64 * 1024; // 64KB
session.scrollback = Buffer.alloc(0);
term.onData((data) => {
// Append to ring buffer
session.scrollback = Buffer.concat([session.scrollback, Buffer.from(data)]);
if (session.scrollback.length > SCROLLBACK_SIZE) {
session.scrollback = session.scrollback.slice(-SCROLLBACK_SIZE);
}
// Forward to connected WebSocket clients (existing behavior)
// ...
});
```
WebSocket handler: replay scrollback on connect
When a new WebSocket client connects to `/ws/terminal?project=X&agent=Y`, send the scrollback buffer before streaming live data:
```js
ws.on("open", () => {
// Replay scrollback
if (session.scrollback && session.scrollback.length > 0) {
ws.send(session.scrollback);
}
// Then stream live (existing behavior)
});
```
Client-side: no changes needed
The xterm.js terminal instance processes the replayed data the same way it processes live data — ANSI escape sequences, cursor positioning, colors all render correctly from the buffer replay. The terminal panel just needs to receive the data; xterm handles the rest.
Acceptance
Out of scope
Files
Branch
`task/XXX-terminal-scrollback-replay`
Related