diff --git a/src/main/process-manager.ts b/src/main/process-manager.ts index c57f2194..ff3c191d 100644 --- a/src/main/process-manager.ts +++ b/src/main/process-manager.ts @@ -156,12 +156,33 @@ export class ProcessManager extends EventEmitter { ptyArgs = finalArgs; } + // Build environment for PTY process + // For terminal sessions, we want the shell to build its own PATH from startup files + // (.zprofile, .zshrc, etc.) rather than inheriting Electron's limited PATH. + // Electron launched from Finder gets a minimal PATH from launchd, not the user's shell PATH. + let ptyEnv: NodeJS.ProcessEnv; + if (isTerminal) { + // For terminals: pass minimal env, let shell startup files set PATH + // This ensures the terminal has the same environment as a regular terminal app + ptyEnv = { + HOME: process.env.HOME, + USER: process.env.USER, + SHELL: process.env.SHELL, + TERM: 'xterm-256color', + LANG: process.env.LANG || 'en_US.UTF-8', + // Don't pass PATH - let the shell build it from scratch via .zprofile/.zshrc + }; + } else { + // For AI agents in PTY mode: pass full env (they need NODE_PATH, etc.) + ptyEnv = process.env; + } + const ptyProcess = pty.spawn(ptyCommand, ptyArgs, { name: 'xterm-256color', cols: 100, rows: 30, cwd: cwd, - env: process.env as any, + env: ptyEnv as any, }); const managedProcess: ManagedProcess = { diff --git a/src/renderer/components/TerminalOutput.tsx b/src/renderer/components/TerminalOutput.tsx index eb711006..4e9b2a3f 100644 --- a/src/renderer/components/TerminalOutput.tsx +++ b/src/renderer/components/TerminalOutput.tsx @@ -543,6 +543,7 @@ const LogItemComponent = memo(({ .prose h5 { color: ${theme.colors.textMain}; font-size: 1.2em; font-weight: bold; margin: 0; line-height: 1.4; } .prose h6 { color: ${theme.colors.textDim}; font-size: 1.1em; font-weight: bold; margin: 0; line-height: 1.4; } .prose p { color: ${theme.colors.textMain}; margin: 0; line-height: 1.4; } + .prose p:empty { display: none; } .prose > ul, .prose > ol { color: ${theme.colors.textMain}; margin: 0.5em 0; padding-left: 2em; } .prose li ul, .prose li ol { margin: 0 !important; padding-left: 1.5em; } .prose li { margin: 0 !important; padding: 0; line-height: 1.4; display: list-item; } @@ -694,6 +695,7 @@ const LogItemComponent = memo(({ .prose h5 { color: ${theme.colors.textMain}; font-size: 1.2em; font-weight: bold; margin: 0; line-height: 1.4; } .prose h6 { color: ${theme.colors.textDim}; font-size: 1.1em; font-weight: bold; margin: 0; line-height: 1.4; } .prose p { color: ${theme.colors.textMain}; margin: 0; line-height: 1.4; } + .prose p:empty { display: none; } .prose > ul, .prose > ol { color: ${theme.colors.textMain}; margin: 0.5em 0; padding-left: 2em; } .prose li ul, .prose li ol { margin: 0 !important; padding-left: 1.5em; } .prose li { margin: 0 !important; padding: 0; line-height: 1.4; display: list-item; } @@ -827,6 +829,7 @@ const LogItemComponent = memo(({ .prose h5 { color: ${theme.colors.textMain}; font-size: 1.2em; font-weight: bold; margin: 0; line-height: 1.4; } .prose h6 { color: ${theme.colors.textDim}; font-size: 1.1em; font-weight: bold; margin: 0; line-height: 1.4; } .prose p { color: ${theme.colors.textMain}; margin: 0; line-height: 1.4; } + .prose p:empty { display: none; } .prose > ul, .prose > ol { color: ${theme.colors.textMain}; margin: 0.5em 0; padding-left: 2em; } .prose li ul, .prose li ol { margin: 0 !important; padding-left: 1.5em; } .prose li { margin: 0 !important; padding: 0; line-height: 1.4; display: list-item; } diff --git a/src/renderer/hooks/useTabCompletion.ts b/src/renderer/hooks/useTabCompletion.ts index c6c756db..79cad756 100644 --- a/src/renderer/hooks/useTabCompletion.ts +++ b/src/renderer/hooks/useTabCompletion.ts @@ -17,7 +17,7 @@ export interface UseTabCompletionReturn { /** * Hook for providing tab completion suggestions from: * 1. Shell command history - * 2. Current directory file tree + * 2. Current directory file tree (relative to shell CWD) * 3. Git branches and tags (for git commands in git repos) * * Performance optimizations: @@ -26,10 +26,32 @@ export interface UseTabCompletionReturn { * - getSuggestions is wrapped in useCallback to maintain referential equality */ export function useTabCompletion(session: Session | null): UseTabCompletionReturn { + // Compute relative path from project root (cwd) to shell working directory (shellCwd) + const shellRelativePath = useMemo(() => { + if (!session?.cwd || !session?.shellCwd) return ''; + + // Normalize paths + const projectRoot = session.cwd.replace(/\/$/, ''); + const shellDir = session.shellCwd.replace(/\/$/, ''); + + // If shell is at project root, no relative path needed + if (shellDir === projectRoot) return ''; + + // If shell is within project, compute relative path + if (shellDir.startsWith(projectRoot + '/')) { + return shellDir.slice(projectRoot.length + 1); + } + + // Shell is outside project root - can't use file tree + return null; + }, [session?.cwd, session?.shellCwd]); + // Build a flat list of file/folder names from the file tree - // Only re-computed when fileTree actually changes + // Filtered to show only files relative to the shell's current working directory const fileNames = useMemo(() => { if (!session?.fileTree) return []; + // If shell is outside project, return empty + if (shellRelativePath === null) return []; const names: { name: string; type: 'file' | 'folder'; path: string }[] = []; @@ -47,9 +69,31 @@ export function useTabCompletion(session: Session | null): UseTabCompletionRetur } }; - traverse(session.fileTree); + // If we have a relative path, find that subtree first + if (shellRelativePath) { + const pathParts = shellRelativePath.split('/'); + let currentNodes: FileNode[] = session.fileTree; + + // Navigate to the shell's current directory in the tree + for (const part of pathParts) { + const found = currentNodes.find(n => n.name === part && n.type === 'folder'); + if (found && found.children) { + currentNodes = found.children; + } else { + // Directory not found in tree - return empty + return []; + } + } + + // Traverse from the shell's current directory + traverse(currentNodes); + } else { + // Shell is at project root - traverse entire tree + traverse(session.fileTree); + } + return names; - }, [session?.fileTree]); + }, [session?.fileTree, shellRelativePath]); // Memoize shell history reference to avoid unnecessary getSuggestions re-creation const shellHistory = useMemo(() => {