Work on new session logic

This commit is contained in:
Peter Steinberger 2025-06-20 19:22:02 +02:00
parent 1519fbca16
commit 22eb5a2e3f
7 changed files with 128 additions and 40 deletions

View file

@ -504,8 +504,22 @@ func main() {
os.Exit(1) os.Exit(1)
} }
// Attach to the session // For spawned sessions, we need to execute the command and connect I/O
if err := sess.Attach(); err != nil { // 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) fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1) os.Exit(1)
} }

View file

@ -380,11 +380,11 @@ func (s *Server) handleCreateSession(w http.ResponseWriter, r *http.Request) {
// Generate a session ID // Generate a session ID
sessionID := session.GenerateID() sessionID := session.GenerateID()
// Get vt binary path (not vibetunnel) // Get vibetunnel binary path
vtPath := findVTBinary() vtPath := findVTBinary()
if vtPath == "" { if vtPath == "" {
log.Printf("[ERROR] vt binary not found") log.Printf("[ERROR] vibetunnel binary not found")
http.Error(w, "vt binary not found", http.StatusInternalServerError) http.Error(w, "vibetunnel binary not found", http.StatusInternalServerError)
return return
} }
@ -466,18 +466,38 @@ func (s *Server) handleCreateSession(w http.ResponseWriter, r *http.Request) {
}) })
if err != nil { if err != nil {
log.Printf("[ERROR] Failed to create session: %v", err) 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 return
} }
// Get vt binary path (not vibetunnel) // Get vibetunnel binary path
vtPath := findVTBinary() vtPath := findVTBinary()
if vtPath == "" { 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 { if err := s.manager.RemoveSession(sess.ID); err != nil {
log.Printf("Failed to remove session: %v", err) 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 return
} }
@ -518,7 +538,28 @@ func (s *Server) handleCreateSession(w http.ResponseWriter, r *http.Request) {
IsSpawned: false, // This is not a spawned session (detached) IsSpawned: false, // This is not a spawned session (detached)
}) })
if err != nil { 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 return
} }
@ -1028,29 +1069,27 @@ func (s *Server) GetNgrokStatus() ngrok.StatusResponse {
return s.ngrokService.GetStatus() return s.ngrokService.GetStatus()
} }
// findVTBinary locates the vt binary in common locations // findVTBinary locates the vibetunnel Go binary in common locations
func findVTBinary() string { func findVTBinary() string {
// Get the directory of the current executable (vibetunnel) // Get the directory of the current executable (vibetunnel)
execPath, err := os.Executable() execPath, err := os.Executable()
if err == nil { if err == nil {
execDir := filepath.Dir(execPath) // Return the current executable path since we want to use vibetunnel itself
// Check if vt is in the same directory as vibetunnel return execPath
vtPath := filepath.Join(execDir, "vt")
if _, err := os.Stat(vtPath); err == nil {
return vtPath
}
} }
// Check common locations // Check common locations
paths := []string{ paths := []string{
// App bundle location // App bundle location
"/Applications/VibeTunnel.app/Contents/Resources/vt", "/Applications/VibeTunnel.app/Contents/Resources/vibetunnel",
// Development locations // Development locations
"./vt", "./linux/cmd/vibetunnel/vibetunnel",
"../vt", "../linux/cmd/vibetunnel/vibetunnel",
"../../linux/vt", "../../linux/cmd/vibetunnel/vibetunnel",
"./vibetunnel",
"../vibetunnel",
// Installed location // Installed location
"/usr/local/bin/vt", "/usr/local/bin/vibetunnel",
} }
for _, path := range paths { for _, path := range paths {
@ -1061,10 +1100,11 @@ func findVTBinary() string {
} }
// Try to find in PATH // Try to find in PATH
if path, err := exec.LookPath("vt"); err == nil { if path, err := exec.LookPath("vibetunnel"); err == nil {
return path return path
} }
// No binary found
return "" return ""
} }

View file

@ -53,12 +53,20 @@ func NewPTY(session *Session) (*PTY, error) {
// If just launching the shell itself, don't use -c // If just launching the shell itself, don't use -c
cmd = exec.Command(shell) cmd = exec.Command(shell)
} else { } 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 // This ensures aliases and functions from .zshrc/.bashrc are loaded
shellCmd := strings.Join(cmdline, " ") shellCmd := strings.Join(cmdline, " ")
// Use interactive login shell to load user's configuration
cmd = exec.Command(shell, "-i", "-l", "-c", shellCmd) // Try different approaches based on the shell
debugLog("[DEBUG] NewPTY: Executing through interactive login shell: %s -i -l -c %q", shell, shellCmd) 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 // Add some debugging to understand what's happening
debugLog("[DEBUG] NewPTY: Shell: %s", shell) 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 // Pass all environment variables like Node.js implementation does
// This ensures terminal features, locale settings, and shell prompts work correctly // This ensures terminal features, locale settings, and shell prompts work correctly
env := os.Environ() 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 // Override TERM if specified in session info
termSet := false termSet := false

View file

@ -265,6 +265,25 @@ func (s *Session) Attach() error {
return s.pty.Attach() 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 { func (s *Session) SendKey(key string) error {
return s.sendInput([]byte(key)) return s.sendInput([]byte(key))
} }

View file

@ -11,8 +11,8 @@ import (
// This is used as a fallback when the Mac app's terminal service is not available // 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 { func SpawnInTerminal(sessionID, vtBinaryPath string, cmdline []string, workingDir string) error {
// Format the command to run in the terminal // Format the command to run in the terminal
// This matches the format used by the Rust implementation // Using the Go vibetunnel binary with TTY_SESSION_ID environment variable
vtCommand := fmt.Sprintf("TTY_SESSION_ID=\"%s\" \"%s\" -- %s", vtCommand := fmt.Sprintf("TTY_SESSION_ID=\"%s\" \"%s\" %s",
sessionID, vtBinaryPath, shellQuoteArgs(cmdline)) sessionID, vtBinaryPath, shellQuoteArgs(cmdline))
switch runtime.GOOS { switch runtime.GOOS {

View file

@ -100,15 +100,14 @@ remove_item "build/SourcePackages" "Source packages"
remove_item "build/dmg-temp" "DMG temporary files" remove_item "build/dmg-temp" "DMG temporary files"
remove_item "DerivedData" "DerivedData" remove_item "DerivedData" "DerivedData"
# Clean tty-fwd Rust target (but keep the built binaries) # Clean Go build artifacts
if [[ "$CLEAN_ALL" == "true" ]]; then if [[ "$CLEAN_ALL" == "true" ]]; then
remove_item "tty-fwd/target" "Rust build artifacts" remove_item "linux/build" "Go build artifacts"
else else
# Keep the release binaries # Keep the release binaries but clean intermediate files
find tty-fwd/target -type f -name "*.d" -delete 2>/dev/null || true find linux/build -type f -name "*.a" -delete 2>/dev/null || true
find tty-fwd/target -type f -name "*.rmeta" -delete 2>/dev/null || true find linux/build -type f -name "*.o" -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 Go intermediate files"
[[ "$DRY_RUN" == "false" ]] && print_success "Cleaned Rust intermediate files"
fi fi
# Clean SPM build artifacts # Clean SPM build artifacts

View file

@ -93,10 +93,10 @@ if [ -d "$APP_BUNDLE/Contents/Frameworks" ]; then
done done
fi fi
# Sign embedded binaries (like tty-fwd) # Sign embedded binaries (like vibetunnel)
if [ -f "$APP_BUNDLE/Contents/Resources/tty-fwd" ]; then if [ -f "$APP_BUNDLE/Contents/Resources/vibetunnel" ]; then
log "Signing tty-fwd binary..." log "Signing vibetunnel binary..."
codesign --force --options runtime --timestamp --sign "$SIGN_IDENTITY" $KEYCHAIN_OPTS "$APP_BUNDLE/Contents/Resources/tty-fwd" || log "Warning: Failed to sign tty-fwd" codesign --force --options runtime --timestamp --sign "$SIGN_IDENTITY" $KEYCHAIN_OPTS "$APP_BUNDLE/Contents/Resources/vibetunnel" || log "Warning: Failed to sign vibetunnel"
fi fi
# Sign the main executable # Sign the main executable