From 22eb5a2e3fd025d76031d4ccf95f462e59523eb2 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Fri, 20 Jun 2025 19:22:02 +0200 Subject: [PATCH] Work on new session logic --- linux/cmd/vibetunnel/main.go | 18 +++++++- linux/pkg/api/server.go | 82 +++++++++++++++++++++++++++--------- linux/pkg/session/pty.go | 24 +++++++++-- linux/pkg/session/session.go | 19 +++++++++ linux/pkg/terminal/spawn.go | 4 +- mac/scripts/clean.sh | 13 +++--- mac/scripts/codesign-app.sh | 8 ++-- 7 files changed, 128 insertions(+), 40 deletions(-) diff --git a/linux/cmd/vibetunnel/main.go b/linux/cmd/vibetunnel/main.go index 7eb9602a..c1694438 100644 --- a/linux/cmd/vibetunnel/main.go +++ b/linux/cmd/vibetunnel/main.go @@ -504,8 +504,22 @@ func main() { os.Exit(1) } - // Attach to the session - if err := sess.Attach(); err != nil { + // For spawned sessions, we need to execute the command and connect I/O + // The session was already created by the server, we just need to run the command + info := sess.GetInfo() + if info == nil { + fmt.Fprintf(os.Stderr, "Error: Failed to get session info\n") + os.Exit(1) + } + + // Execute the command that was stored in the session + if len(info.Args) == 0 { + fmt.Fprintf(os.Stderr, "Error: No command specified in session\n") + os.Exit(1) + } + + // Create a new PTY and attach it to the existing session + if err := sess.AttachSpawnedSession(); err != nil { fmt.Fprintf(os.Stderr, "Error: %v\n", err) os.Exit(1) } diff --git a/linux/pkg/api/server.go b/linux/pkg/api/server.go index a3daf803..d01bab9f 100644 --- a/linux/pkg/api/server.go +++ b/linux/pkg/api/server.go @@ -380,11 +380,11 @@ func (s *Server) handleCreateSession(w http.ResponseWriter, r *http.Request) { // Generate a session ID sessionID := session.GenerateID() - // Get vt binary path (not vibetunnel) + // Get vibetunnel binary path vtPath := findVTBinary() if vtPath == "" { - log.Printf("[ERROR] vt binary not found") - http.Error(w, "vt binary not found", http.StatusInternalServerError) + log.Printf("[ERROR] vibetunnel binary not found") + http.Error(w, "vibetunnel binary not found", http.StatusInternalServerError) return } @@ -466,18 +466,38 @@ func (s *Server) handleCreateSession(w http.ResponseWriter, r *http.Request) { }) if err != nil { log.Printf("[ERROR] Failed to create session: %v", err) - http.Error(w, err.Error(), http.StatusInternalServerError) + + // Return structured error response for frontends to parse + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + errorResponse := map[string]interface{}{ + "success": false, + "error": err.Error(), + "details": fmt.Sprintf("Failed to create session with command '%s'", strings.Join(cmdline, " ")), + } + + // Extract more specific error information if available + if sessionErr, ok := err.(*session.SessionError); ok { + errorResponse["code"] = string(sessionErr.Code) + if sessionErr.Code == session.ErrPTYCreationFailed { + errorResponse["details"] = sessionErr.Message + } + } + + if err := json.NewEncoder(w).Encode(errorResponse); err != nil { + log.Printf("Failed to encode error response: %v", err) + } return } - // Get vt binary path (not vibetunnel) + // Get vibetunnel binary path vtPath := findVTBinary() if vtPath == "" { - log.Printf("[ERROR] vt binary not found for native terminal spawn") + log.Printf("[ERROR] vibetunnel binary not found for native terminal spawn") if err := s.manager.RemoveSession(sess.ID); err != nil { log.Printf("Failed to remove session: %v", err) } - http.Error(w, "vt binary not found", http.StatusInternalServerError) + http.Error(w, "vibetunnel binary not found", http.StatusInternalServerError) return } @@ -518,7 +538,28 @@ func (s *Server) handleCreateSession(w http.ResponseWriter, r *http.Request) { IsSpawned: false, // This is not a spawned session (detached) }) if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) + log.Printf("[ERROR] Failed to create session: %v", err) + + // Return structured error response for frontends to parse + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusInternalServerError) + errorResponse := map[string]interface{}{ + "success": false, + "error": err.Error(), + "details": fmt.Sprintf("Failed to create session with command '%s'", strings.Join(cmdline, " ")), + } + + // Extract more specific error information if available + if sessionErr, ok := err.(*session.SessionError); ok { + errorResponse["code"] = string(sessionErr.Code) + if sessionErr.Code == session.ErrPTYCreationFailed { + errorResponse["details"] = sessionErr.Message + } + } + + if err := json.NewEncoder(w).Encode(errorResponse); err != nil { + log.Printf("Failed to encode error response: %v", err) + } return } @@ -1028,29 +1069,27 @@ func (s *Server) GetNgrokStatus() ngrok.StatusResponse { return s.ngrokService.GetStatus() } -// findVTBinary locates the vt binary in common locations +// findVTBinary locates the vibetunnel Go binary in common locations func findVTBinary() string { // Get the directory of the current executable (vibetunnel) execPath, err := os.Executable() if err == nil { - execDir := filepath.Dir(execPath) - // Check if vt is in the same directory as vibetunnel - vtPath := filepath.Join(execDir, "vt") - if _, err := os.Stat(vtPath); err == nil { - return vtPath - } + // Return the current executable path since we want to use vibetunnel itself + return execPath } // Check common locations paths := []string{ // App bundle location - "/Applications/VibeTunnel.app/Contents/Resources/vt", + "/Applications/VibeTunnel.app/Contents/Resources/vibetunnel", // Development locations - "./vt", - "../vt", - "../../linux/vt", + "./linux/cmd/vibetunnel/vibetunnel", + "../linux/cmd/vibetunnel/vibetunnel", + "../../linux/cmd/vibetunnel/vibetunnel", + "./vibetunnel", + "../vibetunnel", // Installed location - "/usr/local/bin/vt", + "/usr/local/bin/vibetunnel", } for _, path := range paths { @@ -1061,10 +1100,11 @@ func findVTBinary() string { } // Try to find in PATH - if path, err := exec.LookPath("vt"); err == nil { + if path, err := exec.LookPath("vibetunnel"); err == nil { return path } + // No binary found return "" } diff --git a/linux/pkg/session/pty.go b/linux/pkg/session/pty.go index 7d8e8904..34e30bf0 100644 --- a/linux/pkg/session/pty.go +++ b/linux/pkg/session/pty.go @@ -53,12 +53,20 @@ func NewPTY(session *Session) (*PTY, error) { // If just launching the shell itself, don't use -c cmd = exec.Command(shell) } else { - // Execute command through interactive login shell for proper environment handling + // Execute command through login shell for proper environment handling // This ensures aliases and functions from .zshrc/.bashrc are loaded shellCmd := strings.Join(cmdline, " ") - // Use interactive login shell to load user's configuration - cmd = exec.Command(shell, "-i", "-l", "-c", shellCmd) - debugLog("[DEBUG] NewPTY: Executing through interactive login shell: %s -i -l -c %q", shell, shellCmd) + + // Try different approaches based on the shell + if strings.Contains(shell, "zsh") { + // For zsh, use login shell without -i flag as it can cause issues with PTY allocation + cmd = exec.Command(shell, "-l", "-c", shellCmd) + debugLog("[DEBUG] NewPTY: Executing through zsh login shell: %s -l -c %q", shell, shellCmd) + } else { + // For other shells (bash, sh), use interactive login + cmd = exec.Command(shell, "-i", "-l", "-c", shellCmd) + debugLog("[DEBUG] NewPTY: Executing through interactive login shell: %s -i -l -c %q", shell, shellCmd) + } // Add some debugging to understand what's happening debugLog("[DEBUG] NewPTY: Shell: %s", shell) @@ -85,6 +93,14 @@ func NewPTY(session *Session) (*PTY, error) { // Pass all environment variables like Node.js implementation does // This ensures terminal features, locale settings, and shell prompts work correctly env := os.Environ() + + // Log PATH for debugging + for _, e := range env { + if strings.HasPrefix(e, "PATH=") { + debugLog("[DEBUG] NewPTY: PATH=%s", e[5:]) + break + } + } // Override TERM if specified in session info termSet := false diff --git a/linux/pkg/session/session.go b/linux/pkg/session/session.go index e2402527..67881561 100644 --- a/linux/pkg/session/session.go +++ b/linux/pkg/session/session.go @@ -265,6 +265,25 @@ func (s *Session) Attach() error { return s.pty.Attach() } +// AttachSpawnedSession is used when a terminal is spawned with TTY_SESSION_ID +// It creates a new PTY for the spawned terminal and runs the command +func (s *Session) AttachSpawnedSession() error { + // Create a new PTY for this spawned session + pty, err := NewPTY(s) + if err != nil { + return fmt.Errorf("failed to create PTY: %w", err) + } + s.pty = pty + + // Start the PTY with the command from session info + if err := s.pty.Start(); err != nil { + return fmt.Errorf("failed to start PTY: %w", err) + } + + // Attach to the PTY to connect stdin/stdout + return s.pty.Attach() +} + func (s *Session) SendKey(key string) error { return s.sendInput([]byte(key)) } diff --git a/linux/pkg/terminal/spawn.go b/linux/pkg/terminal/spawn.go index 578fab95..fbfc00bf 100644 --- a/linux/pkg/terminal/spawn.go +++ b/linux/pkg/terminal/spawn.go @@ -11,8 +11,8 @@ import ( // This is used as a fallback when the Mac app's terminal service is not available func SpawnInTerminal(sessionID, vtBinaryPath string, cmdline []string, workingDir string) error { // Format the command to run in the terminal - // This matches the format used by the Rust implementation - vtCommand := fmt.Sprintf("TTY_SESSION_ID=\"%s\" \"%s\" -- %s", + // Using the Go vibetunnel binary with TTY_SESSION_ID environment variable + vtCommand := fmt.Sprintf("TTY_SESSION_ID=\"%s\" \"%s\" %s", sessionID, vtBinaryPath, shellQuoteArgs(cmdline)) switch runtime.GOOS { diff --git a/mac/scripts/clean.sh b/mac/scripts/clean.sh index acd10bd1..1b0c63ad 100755 --- a/mac/scripts/clean.sh +++ b/mac/scripts/clean.sh @@ -100,15 +100,14 @@ remove_item "build/SourcePackages" "Source packages" remove_item "build/dmg-temp" "DMG temporary files" remove_item "DerivedData" "DerivedData" -# Clean tty-fwd Rust target (but keep the built binaries) +# Clean Go build artifacts if [[ "$CLEAN_ALL" == "true" ]]; then - remove_item "tty-fwd/target" "Rust build artifacts" + remove_item "linux/build" "Go build artifacts" else - # Keep the release binaries - find tty-fwd/target -type f -name "*.d" -delete 2>/dev/null || true - find tty-fwd/target -type f -name "*.rmeta" -delete 2>/dev/null || true - find tty-fwd/target -type d -name "incremental" -exec rm -rf {} + 2>/dev/null || true - [[ "$DRY_RUN" == "false" ]] && print_success "Cleaned Rust intermediate files" + # Keep the release binaries but clean intermediate files + find linux/build -type f -name "*.a" -delete 2>/dev/null || true + find linux/build -type f -name "*.o" -delete 2>/dev/null || true + [[ "$DRY_RUN" == "false" ]] && print_success "Cleaned Go intermediate files" fi # Clean SPM build artifacts diff --git a/mac/scripts/codesign-app.sh b/mac/scripts/codesign-app.sh index a89e08a1..4a99536d 100755 --- a/mac/scripts/codesign-app.sh +++ b/mac/scripts/codesign-app.sh @@ -93,10 +93,10 @@ if [ -d "$APP_BUNDLE/Contents/Frameworks" ]; then done fi -# Sign embedded binaries (like tty-fwd) -if [ -f "$APP_BUNDLE/Contents/Resources/tty-fwd" ]; then - log "Signing tty-fwd binary..." - codesign --force --options runtime --timestamp --sign "$SIGN_IDENTITY" $KEYCHAIN_OPTS "$APP_BUNDLE/Contents/Resources/tty-fwd" || log "Warning: Failed to sign tty-fwd" +# Sign embedded binaries (like vibetunnel) +if [ -f "$APP_BUNDLE/Contents/Resources/vibetunnel" ]; then + log "Signing vibetunnel binary..." + codesign --force --options runtime --timestamp --sign "$SIGN_IDENTITY" $KEYCHAIN_OPTS "$APP_BUNDLE/Contents/Resources/vibetunnel" || log "Warning: Failed to sign vibetunnel" fi # Sign the main executable