package service import ( "log/slog" "io" "codespace/internal/shell" "codespace/internal/workspace" ) // ShellService manages interactive shells for workspaces. type ShellService struct { workspaces workspace.Manager shells shell.Manager logger *slog.Logger } // NewShellService creates a ShellService. // If lg is nil, slog.Default() is used. func NewShellService(workspaces workspace.Manager, shells shell.Manager, lg *slog.Logger) *ShellService { if lg == nil { lg = slog.Default() } return &ShellService{workspaces: workspaces, shells: shells, logger: lg} } // Start starts an interactive shell for the given workspace. func (s *ShellService) Start(workspaceID string) (string, error) { ws, err := s.workspaces.Get(workspaceID) if err != nil { return "", err } shellID, err := s.shells.Start(workspaceID, ws.Root) if err != nil { s.logger.Error("shell start failed", "workspace_id", workspaceID, "error", err) return "", err } status := s.shells.Status(workspaceID, shellID) s.logger.Info("shell started", "workspace_id", workspaceID, "shell_id", shellID, "pid", status.PID) return shellID, nil } // Stop stops the interactive shell for the given workspace. func (s *ShellService) Stop(workspaceID string, shellID string) error { if _, err := s.workspaces.Get(workspaceID); err != nil { return err } if err := s.shells.Stop(workspaceID, shellID); err != nil { s.logger.Error("shell stop failed", "workspace_id", workspaceID, "error", err) return err } s.logger.Info("shell stopped", "workspace_id", workspaceID, "shell_id", shellID) return nil } // Restart restarts the interactive shell for the given workspace. func (s *ShellService) Restart(workspaceID string, shellID string) (string, error) { ws, err := s.workspaces.Get(workspaceID) if err != nil { return "", err } newShellID, err := s.shells.Restart(workspaceID, shellID, ws.Root) if err != nil { s.logger.Error("shell restart failed", "workspace_id", workspaceID, "error", err) return "", err } status := s.shells.Status(workspaceID, newShellID) s.logger.Info("shell restarted", "workspace_id", workspaceID, "old_shell_id", shellID, "new_shell_id", newShellID, "pid", status.PID) return newShellID, nil } // Resize resizes the PTY for the given workspace. func (s *ShellService) Resize(workspaceID string, shellID string, cols, rows int) error { if _, err := s.workspaces.Get(workspaceID); err != nil { return err } if err := s.shells.Resize(workspaceID, shellID, cols, rows); err != nil { s.logger.Error("shell resize failed", "workspace_id", workspaceID, "error", err) return err } s.logger.Info("shell resized", "workspace_id", workspaceID, "shell_id", shellID, "cols", cols, "rows", rows) return nil } // Status returns the shell status for the given workspace. func (s *ShellService) Status(workspaceID string, shellID string) (shell.Status, error) { if _, err := s.workspaces.Get(workspaceID); err != nil { return shell.Status{}, err } return s.shells.Status(workspaceID, shellID), nil } // Subscribe subscribes to output events for the workspace shell. func (s *ShellService) Subscribe(workspaceID string, shellID string) (shell.Subscription, error) { if _, err := s.workspaces.Get(workspaceID); err != nil { return nil, err } return s.shells.Subscribe(workspaceID, shellID) } // Input returns the stdin writer for the workspace shell. func (s *ShellService) Input(workspaceID string, shellID string) (io.WriteCloser, error) { if _, err := s.workspaces.Get(workspaceID); err != nil { return nil, err } return s.shells.Stdin(workspaceID, shellID) } // ExitStatus returns the exit status for the workspace shell. func (s *ShellService) ExitStatus(workspaceID string, shellID string) (shell.ExitInfo, error) { if _, err := s.workspaces.Get(workspaceID); err != nil { return shell.ExitInfo{}, err } return s.shells.ExitStatus(workspaceID, shellID) } // List returns all shells for the workspace. func (s *ShellService) List(workspaceID string) ([]shell.ShellInfo, error) { if _, err := s.workspaces.Get(workspaceID); err != nil { return nil, err } return s.shells.List(workspaceID), nil }