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)
}
// 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)
}

View file

@ -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 ""
}

View file

@ -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

View file

@ -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))
}

View file

@ -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 {

View file

@ -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

View file

@ -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