diff --git a/dify-agent-runtime/internal/server/tmux.go b/dify-agent-runtime/internal/server/tmux.go index 42a05c7de5f..dc5cfac9565 100644 --- a/dify-agent-runtime/internal/server/tmux.go +++ b/dify-agent-runtime/internal/server/tmux.go @@ -21,7 +21,11 @@ func NewTmuxController(config *Config) *TmuxController { // StartServer ensures the tmux server is running. func (t *TmuxController) StartServer() error { - _, err := t.runTmux("start-server") + _, err := t.runTmux( + "start-server", + ";", + "set-option", "-g", "exit-empty", "off", + ) return err } diff --git a/dify-agent-runtime/internal/server/tmux_test.go b/dify-agent-runtime/internal/server/tmux_test.go index 186b57dbfa7..68a8003a2c6 100644 --- a/dify-agent-runtime/internal/server/tmux_test.go +++ b/dify-agent-runtime/internal/server/tmux_test.go @@ -1,9 +1,91 @@ package server import ( + "os/exec" + "strings" "testing" ) +func newTestTmuxController(t *testing.T) *TmuxController { + t.Helper() + + if _, err := exec.LookPath("tmux"); err != nil { + t.Skip("tmux is not installed") + } + + config := DefaultConfig() + config.RuntimeDir = t.TempDir() + controller := NewTmuxController(config) + t.Cleanup(func() { + _, _ = controller.runTmuxNoCheck("kill-server") + }) + + return controller +} + +func assertTmuxServerRunning(t *testing.T, controller *TmuxController) { + t.Helper() + + exitEmpty, err := controller.runTmux("show-options", "-gv", "exit-empty") + if err != nil { + t.Fatalf("tmux server is not running: %v", err) + } + if got := strings.TrimSpace(exitEmpty); got != "off" { + t.Fatalf("exit-empty = %q, want %q", got, "off") + } +} + +func TestStartServerKeepsTmuxServerRunningWithoutSessions(t *testing.T) { + controller := newTestTmuxController(t) + + if err := controller.StartServer(); err != nil { + t.Fatalf("StartServer() error = %v", err) + } + + assertTmuxServerRunning(t, controller) + + sessions, err := controller.ListSessions() + if err != nil { + t.Fatalf("ListSessions() error = %v", err) + } + if len(sessions) != 0 { + t.Fatalf("ListSessions() = %v, want no sessions", sessions) + } +} + +func TestTmuxServerRemainsRunningAfterLastSessionIsDeleted(t *testing.T) { + controller := newTestTmuxController(t) + + if err := controller.StartServer(); err != nil { + t.Fatalf("StartServer() error = %v", err) + } + + const jobID = "last-session" + sessionName := JobSessionName(jobID) + if _, err := controller.runTmux("new-session", "-d", "-s", sessionName); err != nil { + t.Fatalf("create tmux session: %v", err) + } + + sessions, err := controller.ListSessions() + if err != nil { + t.Fatalf("ListSessions() before cleanup error = %v", err) + } + if !sessions[sessionName] { + t.Fatalf("ListSessions() = %v, want session %q", sessions, sessionName) + } + + controller.CleanupSession(jobID) + assertTmuxServerRunning(t, controller) + + sessions, err = controller.ListSessions() + if err != nil { + t.Fatalf("ListSessions() after cleanup error = %v", err) + } + if len(sessions) != 0 { + t.Fatalf("ListSessions() after cleanup = %v, want no sessions", sessions) + } +} + func TestShellQuote(t *testing.T) { tests := []struct { input string