Extract session subpackage with pure guest-session helpers

Moves the stateless parts of the guest-session subsystem into
internal/daemon/session:

- consts (BackendSSH, attach/transport kinds, StateRoot, LogTailLineDefault)
- StateSnapshot plus ParseState / InspectStateFromDir / ApplyStateSnapshot / StateChanged
- 10 on-guest path helpers (StateDir, StdoutLogPath, StdinPipePath, …)
- 3 bash script generators (Script, InspectScript, SignalScript)
- small utilities (ShellQuote, ExitCode, CloneStringMap, TailFileContent,
  ProcessAlive + syscallKill test seam, FormatStepError)
- launch helpers (DefaultName, DefaultCWD, FailLaunch,
  NormalizeRequiredCommands, CWDPreflightScript, CommandPreflightScript,
  AttachInputCommand, AttachTailCommand, EnvLines)

Callers inside the daemon package import the new package under the
alias "sess" to avoid colliding with the local `session model.GuestSession`
variables threaded through the orchestrator code. guest_sessions.go
shrinks from 616 → 156 LOC; session_stream.go, session_attach.go,
session_lifecycle.go, workspace.go, and guest_sessions_test.go rewire to
the exported names.

The orchestrator methods (StartGuestSession, BeginGuestSessionAttach,
SendToGuestSession, GuestSessionLogs, refresh/inspect, sessionRegistry,
guestSessionController) stay on *Daemon. Full Manager-style extraction
would need prerequisite phases (operation protocol, workdisk helpers),
mirroring Phase 4a's trade-off.

All tests green.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Thales Maciel 2026-04-15 16:33:12 -03:00
parent c13c8b11af
commit 37e02b1576
No known key found for this signature in database
GPG key ID: 33112E6833C34679
8 changed files with 612 additions and 566 deletions

View file

@ -13,6 +13,7 @@ import (
"time"
"banger/internal/api"
sess "banger/internal/daemon/session"
"banger/internal/model"
"banger/internal/store"
)
@ -135,7 +136,7 @@ func TestSendToGuestSession_HappyPath(t *testing.T) {
t.Fatalf("RunScript call count = %d, want 1", len(fake.ranScripts))
}
script := fake.ranScripts[0]
pipePath := guestSessionStdinPipePath(session.ID)
pipePath := sess.StdinPipePath(session.ID)
if !strings.Contains(script, "cat ") {
t.Fatalf("send script missing cat command: %q", script)
}
@ -321,15 +322,15 @@ func testGuestSession(vmID string, stdinMode model.GuestSessionStdinMode, status
ID: id,
VMID: vmID,
Name: vmID + "-sess",
Backend: guestSessionBackendSSH,
Backend: sess.BackendSSH,
Command: "pi",
Args: []string{"--mode", "rpc"},
CWD: "/root/repo",
StdinMode: stdinMode,
Status: status,
GuestStateDir: guestSessionStateDir(id),
StdoutLogPath: guestSessionStdoutLogPath(id),
StderrLogPath: guestSessionStderrLogPath(id),
GuestStateDir: sess.StateDir(id),
StdoutLogPath: sess.StdoutLogPath(id),
StderrLogPath: sess.StderrLogPath(id),
Attachable: stdinMode == model.GuestSessionStdinPipe && status == model.GuestSessionStatusRunning,
Reattachable: stdinMode == model.GuestSessionStdinPipe && status == model.GuestSessionStatusRunning,
CreatedAt: now,
@ -355,7 +356,7 @@ func startFakeFirecracker(t *testing.T, apiSock string) *exec.Cmd {
func TestGuestSessionPreflightScriptsUseRealNewlines(t *testing.T) {
t.Parallel()
cwdScript := guestSessionCWDPreflightScript("/root/repo")
cwdScript := sess.CWDPreflightScript("/root/repo")
if strings.Contains(cwdScript, `\n`) {
t.Fatalf("cwd preflight script still contains escaped newline literals: %q", cwdScript)
}
@ -363,7 +364,7 @@ func TestGuestSessionPreflightScriptsUseRealNewlines(t *testing.T) {
t.Fatalf("cwd preflight script should contain real newlines: %q", cwdScript)
}
commandScript := guestSessionCommandPreflightScript([]string{"git", "pi"})
commandScript := sess.CommandPreflightScript([]string{"git", "pi"})
if strings.Contains(commandScript, `\n`) {
t.Fatalf("command preflight script still contains escaped newline literals: %q", commandScript)
}
@ -371,12 +372,12 @@ func TestGuestSessionPreflightScriptsUseRealNewlines(t *testing.T) {
t.Fatalf("command preflight script should contain real newlines: %q", commandScript)
}
attachInput := guestSessionAttachInputCommand("session-id")
attachInput := sess.AttachInputCommand("session-id")
if strings.Contains(attachInput, `\n`) {
t.Fatalf("attach input command still contains escaped newline literals: %q", attachInput)
}
attachTail := guestSessionAttachTailCommand("/tmp/stdout.log")
attachTail := sess.AttachTailCommand("/tmp/stdout.log")
if strings.Contains(attachTail, `\n`) {
t.Fatalf("attach tail command still contains escaped newline literals: %q", attachTail)
}