-
Notifications
You must be signed in to change notification settings - Fork 14
feat: add terminal support #558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
159 commits
Select commit
Hold shift + click to select a range
77c5d7c
🤖 fix: downgrade Electron 38→31 for node-pty C++17 compatibility
sreya 5ce9bd5
feat: add backend PTY service and terminal server
sreya 666cc8d
feat: add IPC types for terminal operations
sreya b9746fe
feat: add frontend terminal component
sreya 13b4e62
fix: SSH terminal timeout and add multi-instance support
sreya 1205cfc
🤖 fix: prevent terminal reconnection loop and add debug logging
sreya c54cd45
🤖 fix: make node-pty import lazy to prevent startup crash
sreya b013189
🤖 fix: use console instead of log service in frontend hook
sreya 90061e0
🤖 debug: add console logging to TerminalView component
sreya 1d8095d
🤖 debug: add WebSocket message flow logging
sreya 4dc33ea
🤖 fix: WebSocket message handler wasn't being set up
sreya a282c2f
🤖 fix: ensure terminal is ready before setting up WebSocket handler
sreya 651cc1a
🤖 debug: send newline to trigger shell prompt
sreya 8c0cb8d
🤖 debug: change PTY logs from debug to info level
sreya dd8cc20
🤖 debug: add try-catch around SSH exec to catch errors
sreya 994a967
🤖 fix: send attach message to register WebSocket with session
sreya 67865e4
🤖 fix: use 'bash -l' instead of 'exec $SHELL' for SSH terminals
sreya 79c6e0e
🤖 feat: add embedded terminal for SSH workspaces
sreya 86e043f
🤖 fix: remove vite-plugin-top-level-await for Bun compatibility
sreya e5c9b72
Revert "🤖 fix: remove vite-plugin-top-level-await for Bun compatibility"
sreya 26214ef
🤖 fix: add postinstall patch for vite-plugin-top-level-await
sreya 3eb30c3
🤖 fix: downgrade chalk 5→4 for CommonJS compatibility
sreya dc20c07
🤖 fix: use login shell for local PTY sessions
sreya 1d75904
🤖 fix: set TERM_PROGRAM and disable problematic zsh features
sreya 6fee74c
Revert "🤖 fix: set TERM_PROGRAM and disable problematic zsh features"
sreya c982c7b
fix: synchronize terminal size between PTY and display
sreya 1c3fe26
no patching
sreya dca8263
test: upgrade to Electron 38 + node-pty beta39
sreya 6f6e602
🤖 fix: update ghostty-wasm to ghostty-web and improve PTY error handling
sreya 05f052c
fix: remove duplicate ghostty-web dependency and fix async calls
sreya 074c587
🤖 fix: add ExtensionMetadataService and fix async/type errors after r…
sreya c20222a
🤖 test: add unit tests for terminal/PTY functionality
sreya a3c08b1
revert: restore chalk to v5 to match main branch
sreya a7ce049
refactor: remove debug console.log statements from terminal components
sreya d5faac7
fix: restore --add-project CLI option and project initialization
sreya ec31e28
🤖 refactor: import ghostty-vt.wasm directly from package
sreya 0efd49e
🤖 refactor: enable and simplify terminal service unit tests
sreya 3479b2e
🤖 refactor: remove trivial terminal service unit tests
sreya 0db0ba7
🤖 refactor: remove wasmPath now that ghostty-web handles it
sreya 3bc778c
🤖 fix: use HTTPS instead of SSH for ghostty-web dependency
sreya b5e69a3
bun
sreya aac958c
🤖 chore: explicitly use HEAD for ghostty-web git dependency
sreya 3594132
🤖 chore: update lockfile for latest ghostty-web commit
sreya eb6c123
🤖 feat: refactor terminals to pop-out windows with multiple windows p…
sreya 63e4ddc
🤖 fix: resolve build issues with terminal window
sreya 05f7369
🤖 fix: use static import for TerminalView in dev mode
sreya e99cb95
🤖 feat: publish ghostty-web to GitHub Packages for CI/CD
sreya 704fca6
🤖 feat: update to ghostty-web 0.1.1 from npm
sreya b3e48b3
🤖 debug: add logging to diagnose window.api issue in terminal window
sreya a791e7a
🤖 fix: ensure preload script is built before starting dev server
sreya 1818bad
🤖 fix: prevent browser API shim from overriding Electron preload
sreya eb81054
🤖 debug: add logging to preload and browser API to diagnose loading i…
sreya c0ef9e2
🤖 debug: add logging to diagnose window.api loading issue
sreya 4c6cce1
🤖 fix: correct preload script path in terminal window manager
sreya bb7b76c
🤖 fix: send clear screen on terminal connection to fix prompt position
sreya a031841
🤖 debug: add logging to trace double input issue
sreya 8078806
🤖 fix: remove StrictMode from terminal window to prevent double-mounting
sreya 82f2ae6
🤖 debug: add logging to trace window resize handling
sreya bcfd055
🤖 fix: add terminalReady to ResizeObserver dependencies
sreya 131c56e
🤖 debug: add more logging to diagnose resize issues
sreya 8f6e343
🤖 fix: add window resize listener as backup for terminal resizing
sreya 5526594
🤖 fix: prevent terminal session from reconnecting on every resize
sreya c64a4fd
🤖 fix: terminal not spawning after removing terminalSize from deps
sreya ba5dd32
fix: prevent terminal session recreation on resize
sreya 1e80bdb
🤖 fix: debounce PTY resize to prevent vim cursor position issues
sreya 2cd3cea
🤖 fix: send resize to PTY immediately instead of debouncing
sreya a5a8dcf
🤖 fix: properly debounce PTY resize to prevent vim display corruption
sreya a0a3fa2
🤖 fix: detect maximize/minimize and send immediate resize
sreya 8678040
🤖 fix: improve maximize/minimize detection with size delta
sreya 28ccc0b
🤖 fix: simplify to always-debounce strategy for vim stability
sreya a74e17e
🤖 debug: add logging to detect color code corruption source
sreya 23bb489
🤖 debug: add hex dump for malformed escape sequences
sreya 4812eb4
🤖 debug: add backend logging to detect PTY data corruption
sreya 5796edb
🤖 debug: log every PTY chunk with hex dump
sreya 36eab74
🤖 fix: buffer PTY data to prevent escape sequences from splitting
sreya 8766cf9
🤖 chore: remove debug logging after fixing escape sequence splitting
sreya 209e7ca
🤖 feat: improve terminal window titles to show project and branch
sreya 8dc77e0
🤖 fix: remove hardcoded Terminal title from HTML
sreya e8d182f
🤖 fix: look up workspace metadata for terminal window titles
sreya 18b8631
🤖 fix: correct Config import path and type annotations
sreya 97a30fc
Merge branch 'main' into local-pty
sreya ee5bfd4
🤖 refactor: simplify browser API shim initialization
sreya 7f2ad26
🤖 chore: remove vim swap file
sreya efa06e8
🤖 chore: cleanup debug logs and unnecessary files
sreya 147d808
🤖 chore: remove remaining console.log from preload.ts
sreya 443fff6
🤖 fix: disable electron-builder native rebuild and update terminal test
sreya 17a9a66
🤖 fix: add eslint-disable for terminal service files
sreya ab20774
🤖 chore: remove accidental .bak files
sreya f2769eb
🤖 fix: remaining lint errors for terminal feature
sreya 7c44343
🤖 fix: resolve final lint errors
sreya c9c3581
🤖 fix: resolve final 6 lint errors
sreya 181ebfe
🤖 fix: remove async from remaining terminal IPC handlers
sreya a6ecb55
🤖 fix: complete async removal and IIFE closure
sreya 091bd06
Revert unnecessary ipcMain.ts changes
sreya 2024d14
Make terminal server initialization lazy
sreya 34a99f4
Restore updateRecency call in sendMessage handler
sreya 724938a
Fix all async/await issues in IPC handlers
sreya 10518fa
Restore WORKSPACE_OPEN_TERMINAL handler and openTerminal method
sreya 635f813
Restore deleteWorkspace call to extensionMetadata
sreya fd73b88
Fix formatting: add blank line before isCommandAvailable
sreya b5d0a7b
Move helper methods before openTerminal and fix escaping
sreya 5c2e7ba
revert more changes
sreya 98bebc0
Revert main-server.ts changes to match original structure
sreya a044df0
fmt
sreya 325d5f5
Revert createWindow to synchronous
sreya e5ef5c5
Fix terminal window opening - await async openTerminalWindow call
sreya 24270a0
Add logging to terminal window open handler for debugging
sreya 296eb5d
Add frontend logging for terminal open debugging
sreya eec8003
Fix terminalServer.start() to return the promise
sreya 247ec96
Add preload logging to trace terminal window API calls
sreya 4263554
Add log to WORKSPACE_OPEN_TERMINAL to trace which handler is called
sreya 4b6e414
Add console.log to both terminal handlers for debugging
sreya cfcaafd
Add logging and stack trace to workspace.openTerminal (old API)
sreya c863b8f
Use console.error for workspace.openTerminal to make it more visible
sreya 9247988
Fix useCreationWorkspace to skip branch loading when projectPath is e…
sreya 60a6d47
Revert unnecessary vite.config.ts changes
sreya e59cc63
fmt
sreya 679d933
revert accidental diff
sreya c74940b
🤖 refactor: replace TerminalServer WebSocket with IPC for desktop + s…
sreya fe65373
🤖 fix: resolve linting errors from terminal refactor
sreya 7124dcb
🤖 fix: resolve import and dynamic import lint errors
sreya b82c178
🤖 fix: implement terminal support in browser/server mode
sreya 9cb55b3
🤖 fix: correct dev-server command to use main-server.js directly
sreya 83eb538
🤖 fix: increase nodemon delay to 1000ms for dev-server
sreya 65415fc
rm comments
sreya becd147
🤖 refactor: address review comments on terminal IPC refactor
sreya 74d3274
🤖 fix: revert terminal auto-open (breaks IPC routing)
sreya ca43b55
🤖 fix: send terminal IPC events to correct window
sreya eb1694e
🤖 refactor: use event.stopPropagation() in terminal instead of focus …
sreya d274456
🤖 refactor: replace fs.existsSync with async fs.access in ptyService
sreya 7df02e8
🤖 refactor: use native event listener to stop terminal keyboard propa…
sreya aed32ee
🤖 fix: terminal input not working due to capture phase event listener
sreya b35c2e5
🤖 refactor: remove vestigial terminal focus checks
sreya 36050b0
🤖 fix: add stub update handlers for server mode
sreya 633ee3d
🤖 debug: add logging to handleAddWorkspace
sreya 95d6530
Merge branch 'main' into local-pty
sreya 9f9dc55
🤖 feat: add terminal window support for browser/server mode
sreya b3e9668
🤖 fix: import browser API in terminal-window for browser mode
sreya 2b3f5a0
🤖 fix: handle BrowserWindow.fromWebContents in server mode
sreya 2cb3fdf
🤖 fix: handle null event in server mode
sreya 5da9b1a
🤖 fix: change TERMINAL_INPUT from on() to handle() for server mode
sreya 491d780
🤖 fix: create unique terminal windows in browser mode
sreya 6e3e6e3
🤖 chore: remove debug logging from terminal handlers
sreya 9324068
🤖 refactor: skip update checks in browser mode instead of stub handlers
sreya 7b06d01
fmt
sreya ea25cc7
🤖 fix: prevent nodemon from watching main.js in dev-server
sreya 5897608
🤖 feat: set terminal window title to show project/workspace name
sreya c673be7
🤖 fix: remove error when opening terminal in server mode
sreya a508097
🤖 fix: use workspace.list() to get metadata for terminal title
sreya e4ec87c
Revert "🤖 fix: remove error when opening terminal in server mode"
sreya b356201
fmt
sreya f6fdddb
🤖 fix: add postinstall script to rebuild native dependencies
sreya d697d32
ci
sreya 6c48b73
build
sreya 10ac0d5
Merge branch 'main' into local-pty
sreya b3da99a
readd dependency
sreya 291d12d
omit 'server' arg to main-server
sreya 2e011cf
make lint
sreya 84432e2
Merge branch 'main' into local-pty
sreya File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,3 +1,7 @@ | ||
| # Vim swap files | ||
| *.swp | ||
| *.swo | ||
| *~ | ||
| # Font files (copied from node_modules during build) | ||
| public/fonts/ | ||
|
|
||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,248 @@ | ||
| import { useRef, useEffect, useState } from "react"; | ||
| import { Terminal, FitAddon } from "ghostty-web"; | ||
| import { useTerminalSession } from "@/hooks/useTerminalSession"; | ||
|
|
||
| interface TerminalViewProps { | ||
| workspaceId: string; | ||
| sessionId?: string; | ||
| visible: boolean; | ||
| } | ||
|
|
||
| export function TerminalView({ workspaceId, sessionId, visible }: TerminalViewProps) { | ||
| const containerRef = useRef<HTMLDivElement>(null); | ||
| const termRef = useRef<Terminal | null>(null); | ||
| const fitAddonRef = useRef<FitAddon | null>(null); | ||
| const [terminalError, setTerminalError] = useState<string | null>(null); | ||
| const [terminalReady, setTerminalReady] = useState(false); | ||
| const [terminalSize, setTerminalSize] = useState<{ cols: number; rows: number } | null>(null); | ||
|
|
||
| // Handler for terminal output | ||
| const handleOutput = (data: string) => { | ||
| const term = termRef.current; | ||
| if (term) { | ||
| term.write(data); | ||
| } | ||
| }; | ||
|
|
||
| // Handler for terminal exit | ||
| const handleExit = (exitCode: number) => { | ||
| const term = termRef.current; | ||
| if (term) { | ||
| term.write(`\r\n[Process exited with code ${exitCode}]\r\n`); | ||
| } | ||
| }; | ||
|
|
||
| const { | ||
| sendInput, | ||
| resize, | ||
| error: sessionError, | ||
| } = useTerminalSession(workspaceId, sessionId, visible, terminalSize, handleOutput, handleExit); | ||
|
|
||
| // Keep refs to latest functions so callbacks always use current version | ||
| const sendInputRef = useRef(sendInput); | ||
| const resizeRef = useRef(resize); | ||
|
|
||
| useEffect(() => { | ||
| sendInputRef.current = sendInput; | ||
| resizeRef.current = resize; | ||
| }, [sendInput, resize]); | ||
|
|
||
| // Initialize terminal when visible | ||
| useEffect(() => { | ||
| if (!containerRef.current || !visible) { | ||
| return; | ||
| } | ||
|
|
||
| let terminal: Terminal | null = null; | ||
|
|
||
| const initTerminal = async () => { | ||
| try { | ||
| terminal = new Terminal({ | ||
| fontSize: 13, | ||
| fontFamily: "Monaco, Menlo, 'Courier New', monospace", | ||
| cursorBlink: true, | ||
| theme: { | ||
| background: "#1e1e1e", | ||
| foreground: "#d4d4d4", | ||
| cursor: "#d4d4d4", | ||
| cursorAccent: "#1e1e1e", | ||
| selectionBackground: "#264f78", | ||
| black: "#000000", | ||
| red: "#cd3131", | ||
| green: "#0dbc79", | ||
| yellow: "#e5e510", | ||
| blue: "#2472c8", | ||
| magenta: "#bc3fbc", | ||
| cyan: "#11a8cd", | ||
| white: "#e5e5e5", | ||
| brightBlack: "#666666", | ||
| brightRed: "#f14c4c", | ||
| brightGreen: "#23d18b", | ||
| brightYellow: "#f5f543", | ||
| brightBlue: "#3b8eea", | ||
| brightMagenta: "#d670d6", | ||
| brightCyan: "#29b8db", | ||
| brightWhite: "#ffffff", | ||
| }, | ||
| }); | ||
|
|
||
| const fitAddon = new FitAddon(); | ||
| terminal.loadAddon(fitAddon); | ||
|
|
||
| await terminal.open(containerRef.current!); | ||
| fitAddon.fit(); | ||
|
|
||
| const { cols, rows } = terminal; | ||
|
|
||
| // Set terminal size so PTY session can be created with matching dimensions | ||
| // Use stable object reference to prevent unnecessary effect re-runs | ||
| setTerminalSize((prev) => { | ||
| if (prev?.cols === cols && prev?.rows === rows) { | ||
| return prev; | ||
| } | ||
| return { cols, rows }; | ||
| }); | ||
|
|
||
| // User input → IPC (use ref to always get latest sendInput) | ||
| terminal.onData((data: string) => { | ||
| sendInputRef.current(data); | ||
| }); | ||
|
|
||
| termRef.current = terminal; | ||
| fitAddonRef.current = fitAddon; | ||
| setTerminalReady(true); | ||
| } catch (err) { | ||
| console.error("Failed to initialize terminal:", err); | ||
| setTerminalError(err instanceof Error ? err.message : "Failed to initialize terminal"); | ||
| } | ||
| }; | ||
|
|
||
| void initTerminal(); | ||
|
|
||
| return () => { | ||
| if (terminal) { | ||
| terminal.dispose(); | ||
| } | ||
| termRef.current = null; | ||
| fitAddonRef.current = null; | ||
| setTerminalReady(false); | ||
| setTerminalSize(null); | ||
| }; | ||
| // Note: sendInput and resize are intentionally not in deps | ||
| // They're used in callbacks, not during effect execution | ||
| }, [visible, workspaceId]); | ||
|
|
||
| // Resize on container size change | ||
| useEffect(() => { | ||
| if (!visible || !fitAddonRef.current || !containerRef.current || !termRef.current) { | ||
| return; | ||
| } | ||
|
|
||
| let lastCols = 0; | ||
| let lastRows = 0; | ||
| let resizeTimeoutId: ReturnType<typeof setTimeout> | null = null; | ||
| let pendingResize: { cols: number; rows: number } | null = null; | ||
|
|
||
| // Use both ResizeObserver (for container changes) and window resize (as backup) | ||
| const handleResize = () => { | ||
| if (fitAddonRef.current && termRef.current) { | ||
| try { | ||
| // Resize terminal UI to fit container immediately for responsive UX | ||
| fitAddonRef.current.fit(); | ||
|
|
||
| // Get new dimensions | ||
| const { cols, rows } = termRef.current; | ||
|
|
||
| // Only process if dimensions actually changed | ||
| if (cols === lastCols && rows === lastRows) { | ||
| return; | ||
| } | ||
|
|
||
| lastCols = cols; | ||
| lastRows = rows; | ||
|
|
||
| // Update state (with stable reference to prevent unnecessary re-renders) | ||
| setTerminalSize((prev) => { | ||
| if (prev?.cols === cols && prev?.rows === rows) { | ||
| return prev; | ||
| } | ||
| return { cols, rows }; | ||
| }); | ||
|
|
||
| // Store pending resize | ||
| pendingResize = { cols, rows }; | ||
|
|
||
| // Always debounce PTY resize to prevent vim corruption | ||
| // Clear any pending timeout and set a new one | ||
| if (resizeTimeoutId !== null) { | ||
| clearTimeout(resizeTimeoutId); | ||
| } | ||
|
|
||
| resizeTimeoutId = setTimeout(() => { | ||
| if (pendingResize) { | ||
| console.log( | ||
| `[TerminalView] Sending resize to PTY: ${pendingResize.cols}x${pendingResize.rows}` | ||
| ); | ||
| // Double requestAnimationFrame to ensure vim is ready | ||
| requestAnimationFrame(() => { | ||
| requestAnimationFrame(() => { | ||
| if (pendingResize) { | ||
| resizeRef.current(pendingResize.cols, pendingResize.rows); | ||
| pendingResize = null; | ||
| } | ||
| }); | ||
| }); | ||
| } | ||
| resizeTimeoutId = null; | ||
| }, 300); // 300ms debounce - enough time for vim to stabilize | ||
| } catch (err) { | ||
| console.error("[TerminalView] Error fitting terminal:", err); | ||
| } | ||
| } | ||
| }; | ||
|
|
||
| const resizeObserver = new ResizeObserver(handleResize); | ||
| resizeObserver.observe(containerRef.current); | ||
|
|
||
| // Also listen to window resize as backup | ||
| window.addEventListener("resize", handleResize); | ||
|
|
||
| return () => { | ||
| if (resizeTimeoutId !== null) { | ||
| clearTimeout(resizeTimeoutId); | ||
| } | ||
| resizeObserver.disconnect(); | ||
| window.removeEventListener("resize", handleResize); | ||
| }; | ||
| }, [visible, terminalReady]); // terminalReady ensures ResizeObserver is set up after terminal is initialized | ||
|
|
||
| if (!visible) return null; | ||
|
|
||
| const errorMessage = terminalError ?? sessionError; | ||
|
|
||
| return ( | ||
| <div | ||
| className="terminal-view" | ||
| style={{ | ||
| width: "100%", | ||
| height: "100%", | ||
| backgroundColor: "#1e1e1e", | ||
| }} | ||
| > | ||
| {errorMessage && ( | ||
| <div className="border-b border-red-900/30 bg-red-900/20 p-2 text-sm text-red-400"> | ||
| Terminal Error: {errorMessage} | ||
| </div> | ||
| )} | ||
| <div | ||
| ref={containerRef} | ||
| className="terminal-container" | ||
| style={{ | ||
| width: "100%", | ||
| height: "100%", | ||
| overflow: "hidden", | ||
| }} | ||
| /> | ||
| </div> | ||
| ); | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might be slow over the network. Fine for now, but we should probably allow the
wsManagerto send data for the IPC at some point.