Skip to content

Add Terminal in-app and themed; a tabbed pane with Session#890

Open
brucestarlove wants to merge 6 commits intohumanlayer:mainfrom
brucestarlove:feat/terminal
Open

Add Terminal in-app and themed; a tabbed pane with Session#890
brucestarlove wants to merge 6 commits intohumanlayer:mainfrom
brucestarlove:feat/terminal

Conversation

@brucestarlove
Copy link
Copy Markdown

@brucestarlove brucestarlove commented Dec 4, 2025

Summary

Adds a built-in, session-scoped terminal pane that uses xterm.js to connect to the active workspace via WebSocket, keeping shell context aligned with the agent session. Includes reconnect/status feedback so developers can run quick commands without leaving the IDE. Same theme as user's preferred app theme. Shortcut cmd/ctrl+\ (backslash).

What problem(s) was I solving?

Alt-tabbing into a terminal app for simple commands, like git.

What user-facing changes did I ship?

  • Tabs above main session window to switch between Session pane and Terminal.
  • Terminal theme matches app theme.
  • Shortcut cmd/ctrl + \ (backslash).
Screenshot 2025-12-04 at 1 47 04 AM

How I implemented it

  • CodeLayer Opus 4.5 research -> create plan -> implement.

How to verify it

Do the thing

  • I have ensured make check test passes
    ^ just a bunch of buildtag: +build line is no longer needed (govet) // +build integration from main repo

Description for the changelog

Adds a built-in, session-scoped terminal pane that uses xterm.js to connect to the active workspace via WebSocket, keeping shell context aligned with the agent session. Includes reconnect/status feedback so developers can run quick commands without leaving the IDE. Same theme as user's preferred app theme. Shortcut cmd/ctrl+\ (backslash).

A picture of a cute animal (not mandatory but encouraged)

image

Important

Adds a terminal pane using xterm.js with WebSocket integration for real-time terminal interaction in the session UI.

  • Behavior:
    • Adds a terminal pane using xterm.js in TerminalPane.tsx, connecting via WebSocket to the active workspace.
    • Terminal supports resize, reconnect, and status feedback.
    • Integrated with session management in ActiveSession.tsx with tabs for switching between conversation and terminal.
  • UI:
    • Adds hotkey cmd/ctrl+\ to toggle terminal in HotkeyPanel.tsx.
    • Updates ActiveSession.tsx to include terminal tab and lazy-load TerminalPane.
  • Dependencies:
    • Adds xterm, @xterm/addon-fit, and @xterm/addon-web-links to package.json.
  • Backend:
    • Implements WebSocket handler in terminal.go for terminal sessions.
    • Registers terminal WebSocket endpoint in http_server.go.

This description was created by Ellipsis for 253ef6b. You can customize this summary. It will automatically update as commits are pushed.

Copy link
Copy Markdown
Contributor

@ellipsis-dev ellipsis-dev bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Changes requested ❌

Reviewed everything up to 253ef6b in 2 minutes and 26 seconds. Click for details.
  • Reviewed 1199 lines of code in 9 files
  • Skipped 0 files when reviewing.
  • Skipped posting 2 draft comments. View those below.
  • Modify your settings and rules to customize what types of comments Ellipsis leaves. And don't forget to react with 👍 or 👎 to teach Ellipsis.
1. humanlayer-wui/src/components/TerminalPane.tsx:165
  • Draft comment:
    The WebSocket onmessage handler writes a Uint8Array directly to the terminal using terminal.write. Since xterm.js expects a string in its write() method, consider decoding the binary data (e.g. via TextDecoder) before writing it.
  • Reason this comment was not posted:
    Decided after close inspection that this draft comment was likely wrong and/or not actionable: usefulness confidence = 10% vs. threshold = 50% The comment suggests that xterm.js write() only accepts strings, but this is likely incorrect. Modern versions of xterm.js (especially v4+) support both string and Uint8Array/Uint8ClampedArray for the write() method. The code is handling binary data from a PTY (pseudo-terminal), which is typically binary data that should be written directly without decoding. The author has set ws.binaryType = 'arraybuffer' on line 154 and 271, indicating intentional binary handling. If the code were incorrect, it would fail at runtime, which would be caught during testing. The comment appears to be based on outdated or incorrect understanding of the xterm.js API. I might be wrong about xterm.js supporting Uint8Array - perhaps older versions only supported strings. However, the code is new (entire file is new), so it's likely using a modern version. Also, if this were a real bug, it would be immediately obvious when testing the terminal. Even if older versions of xterm.js only supported strings, this is a new file being added, so the author would be using a current version. More importantly, if terminal.write() didn't accept Uint8Array, this would cause an immediate runtime error that would be caught during basic testing. The fact that the author explicitly handles binary data suggests this is intentional and working. This comment should be deleted. The xterm.js write() method does accept Uint8Array in modern versions, and the code's intentional binary handling (setting binaryType to 'arraybuffer') indicates this is the correct approach. If this were wrong, it would fail immediately during testing.
2. humanlayer-wui/src/components/internal/SessionDetail/components/ActiveSession.tsx:777
  • Draft comment:
    The terminal tab is lazily loaded and toggled via a hotkey and Tabs trigger which is a good optimization. Just ensure state consistency when switching between tabs, especially that terminalOpened is properly managed to avoid reinitializing the terminal unnecessarily.
  • Reason this comment was not posted:
    Confidence changes required: 20% <= threshold 50% None

Workflow ID: wflow_9Pa1IFqgCp6T5Im2

You can customize Ellipsis by changing your verbosity settings, reacting with 👍 or 👎, replying to comments, or adding code review rules.

Comment thread humanlayer-wui/src/components/TerminalPane.tsx Outdated
@brucestarlove
Copy link
Copy Markdown
Author

Terminal Integration Plan

Summary

Implement an interactive terminal within the session detail view using Go PTY on the backend and xterm.js on the frontend. The terminal will be scoped to the session's working directory.

Backend Implementation (Go)

1. WebSocket Terminal Handler

Create a new handler in hld/api/handlers/terminal.go:

  • WebSocket upgrade: Use Gin's websocket capability (via github.com/gorilla/websocket) to upgrade HTTP connections
  • PTY management: Use github.com/creack/pty to spawn $SHELL with the session's WorkingDir
  • Message protocol:
    • Binary messages: stdin/stdout data
    • JSON messages: {type: "resize", cols: number, rows: number} for terminal resize
  • Lifecycle: One WebSocket = one PTY; clean up on disconnect

2. Route Registration

Wire the handler in hld/daemon/http_server.go at line ~145:

// Register terminal WebSocket endpoint
terminalHandler := handlers.NewTerminalHandler(sessionManager, conversationStore)
v1.GET("/terminal", terminalHandler.HandleWebSocket)

3. Security Considerations

  • Validate sessionId parameter and verify user owns the session
  • Validate workspace directory exists and is within allowed paths
  • Pass safe environment variables only (filter sensitive vars)
  • Add idle timeout (configurable, default 30 minutes)

Frontend Implementation (React/TypeScript)

1. Install Dependencies

Add to humanlayer-wui/package.json:

  • xterm
  • @xterm/addon-fit
  • @xterm/addon-web-links

2. TerminalPane Component

Create humanlayer-wui/src/components/TerminalPane.tsx:

  • Initialize xterm.js with fit and web-links addons
  • Connect via WebSocket to ws://{daemonUrl}/api/v1/terminal?sessionId={id}
  • Forward term.onData to WebSocket as binary
  • Render WebSocket binary messages to terminal
  • Send resize events on mount, window resize, and panel resize
  • Clean up on unmount

3. UI Integration

Modify humanlayer-wui/src/components/internal/SessionDetail/components/ActiveSession.tsx:

  • Add ShadCN Tabs component wrapping the conversation Card
  • Tab 1: "Conversation" (existing ConversationStream)
  • Tab 2: "Terminal" (new TerminalPane, lazy-loaded)
  • Add hotkey `Cmd+`` to toggle between tabs

4. Styling

Use existing theme CSS variables for terminal colors:

  • --background for terminal background
  • --foreground for terminal text
  • --terminal-accent for cursor/selection

Key Files to Create/Modify

| File | Change |

|------|--------|

| hld/api/handlers/terminal.go | New: WebSocket handler with PTY |

| hld/daemon/http_server.go | Add terminal handler registration |

| hld/go.mod | Add github.com/creack/pty dependency |

| humanlayer-wui/package.json | Add xterm dependencies |

| humanlayer-wui/src/components/TerminalPane.tsx | New: xterm.js component |

| humanlayer-wui/src/components/internal/SessionDetail/components/ActiveSession.tsx | Add tabs UI |

Testing Steps

  1. Open a running session in the UI
  2. Switch to Terminal tab
  3. Verify pwd matches session's working directory
  4. Test commands: ls, echo $SHELL, resize window
  5. Disconnect and reconnect to verify cleanup

@brucestarlove brucestarlove changed the title Feat/terminal Add Terminal in-app and themed; a tabbed pane with Session Dec 4, 2025
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.

1 participant