feat: add ssh-connection-string option for container SSH access

Add internal -ssh-connection-string flag to pass SSH hostname from dockerimg
to sketch container, allowing the UI to display the correct SSH connection
string based on SSH Theater configuration rather than generating it locally.

Co-Authored-By: sketch <hello@sketch.dev>
Change-ID: s1872f10f74da5f9bk
diff --git a/cmd/sketch/main.go b/cmd/sketch/main.go
index 02ae66c..9d8023c 100644
--- a/cmd/sketch/main.go
+++ b/cmd/sketch/main.go
@@ -197,25 +197,26 @@
 	forceRebuild bool
 	linkToGitHub bool
 
-	gitUsername       string
-	gitEmail          string
-	experimentFlag    experiment.Flag
-	sessionID         string
-	record            bool
-	noCleanup         bool
-	containerLogDest  string
-	outsideHostname   string
-	outsideOS         string
-	outsideWorkingDir string
-	sketchBinaryLinux string
-	dockerArgs        string
-	mounts            StringSliceFlag
-	termUI            bool
-	gitRemoteURL      string
-	upstream          string
-	commit            string
-	outsideHTTP       string
-	branchPrefix      string
+	gitUsername         string
+	gitEmail            string
+	experimentFlag      experiment.Flag
+	sessionID           string
+	record              bool
+	noCleanup           bool
+	containerLogDest    string
+	outsideHostname     string
+	outsideOS           string
+	outsideWorkingDir   string
+	sketchBinaryLinux   string
+	dockerArgs          string
+	mounts              StringSliceFlag
+	termUI              bool
+	gitRemoteURL        string
+	upstream            string
+	commit              string
+	outsideHTTP         string
+	branchPrefix        string
+	sshConnectionString string
 }
 
 // parseCLIFlags parses all command-line flags and returns a CLIFlags struct
@@ -265,6 +266,7 @@
 	internalFlags.StringVar(&flags.commit, "commit", "", "(internal) the git commit reference to check out from git remote url")
 	internalFlags.StringVar(&flags.outsideHTTP, "outside-http", "", "(internal) host for outside sketch")
 	internalFlags.BoolVar(&flags.linkToGitHub, "link-to-github", false, "(internal) enable GitHub branch linking in UI")
+	internalFlags.StringVar(&flags.sshConnectionString, "ssh-connection-string", "", "(internal) SSH connection string for connecting to the container")
 
 	// Developer flags
 	internalFlags.StringVar(&flags.httprrFile, "httprr", "", "if set, record HTTP interactions to file")
@@ -523,14 +525,15 @@
 		WorkingDir:        wd,
 		// Ultimately this is a subtle flag because it's trying to distinguish
 		// between unsafe-on-host and inside sketch, and should probably be renamed/simplified.
-		InDocker:      flags.outsideHostname != "",
-		OneShot:       flags.oneShot,
-		GitRemoteAddr: flags.gitRemoteURL,
-		Upstream:      flags.upstream,
-		OutsideHTTP:   flags.outsideHTTP,
-		Commit:        flags.commit,
-		BranchPrefix:  flags.branchPrefix,
-		LinkToGitHub:  flags.linkToGitHub,
+		InDocker:            flags.outsideHostname != "",
+		OneShot:             flags.oneShot,
+		GitRemoteAddr:       flags.gitRemoteURL,
+		Upstream:            flags.upstream,
+		OutsideHTTP:         flags.outsideHTTP,
+		Commit:              flags.commit,
+		BranchPrefix:        flags.branchPrefix,
+		LinkToGitHub:        flags.linkToGitHub,
+		SSHConnectionString: flags.sshConnectionString,
 	}
 
 	// Create SkabandClient if skaband address is provided
diff --git a/dockerimg/dockerimg.go b/dockerimg/dockerimg.go
index 12c65f3..ac07fa8 100644
--- a/dockerimg/dockerimg.go
+++ b/dockerimg/dockerimg.go
@@ -561,6 +561,8 @@
 		"-branch-prefix="+config.BranchPrefix,
 		"-link-to-github="+fmt.Sprintf("%t", config.LinkToGitHub),
 	)
+	// Set SSH connection string based on session ID for SSH Theater
+	cmdArgs = append(cmdArgs, "-ssh-connection-string=sketch-"+config.SessionID)
 	if config.Model != "" {
 		cmdArgs = append(cmdArgs, "-model="+config.Model)
 	}
diff --git a/loop/agent.go b/loop/agent.go
index 6282705..25c3807 100644
--- a/loop/agent.go
+++ b/loop/agent.go
@@ -115,6 +115,9 @@
 	// SessionID returns the unique session identifier.
 	SessionID() string
 
+	// SSHConnectionString returns the SSH connection string for the container.
+	SSHConnectionString() string
+
 	// DetectGitChanges checks for new git commits and pushes them if found
 	DetectGitChanges(ctx context.Context) error
 
@@ -678,6 +681,11 @@
 	return a.config.SessionID
 }
 
+// SSHConnectionString returns the SSH connection string for the container.
+func (a *Agent) SSHConnectionString() string {
+	return a.config.SSHConnectionString
+}
+
 // OutsideOS returns the operating system of the outside system.
 func (a *Agent) OutsideOS() string {
 	return a.outsideOS
@@ -997,6 +1005,8 @@
 	BranchPrefix string
 	// LinkToGitHub enables GitHub branch linking in UI
 	LinkToGitHub bool
+	// SSH connection string for connecting to the container
+	SSHConnectionString string
 	// Skaband client for session history (optional)
 	SkabandClient *skabandclient.SkabandClient
 }
diff --git a/loop/server/loophttp.go b/loop/server/loophttp.go
index 6c2debc..609b546 100644
--- a/loop/server/loophttp.go
+++ b/loop/server/loophttp.go
@@ -95,9 +95,10 @@
 	InsideOS             string                        `json:"inside_os,omitempty"`
 	OutsideWorkingDir    string                        `json:"outside_working_dir,omitempty"`
 	InsideWorkingDir     string                        `json:"inside_working_dir,omitempty"`
-	TodoContent          string                        `json:"todo_content,omitempty"`   // Contains todo list JSON data
-	SkabandAddr          string                        `json:"skaband_addr,omitempty"`   // URL of the skaband server
-	LinkToGitHub         bool                          `json:"link_to_github,omitempty"` // Enable GitHub branch linking in UI
+	TodoContent          string                        `json:"todo_content,omitempty"`          // Contains todo list JSON data
+	SkabandAddr          string                        `json:"skaband_addr,omitempty"`          // URL of the skaband server
+	LinkToGitHub         bool                          `json:"link_to_github,omitempty"`        // Enable GitHub branch linking in UI
+	SSHConnectionString  string                        `json:"ssh_connection_string,omitempty"` // SSH connection string for container
 }
 
 type InitRequest struct {
@@ -1308,6 +1309,7 @@
 		TodoContent:          s.agent.CurrentTodoContent(),
 		SkabandAddr:          s.agent.SkabandAddr(),
 		LinkToGitHub:         s.agent.LinkToGitHub(),
+		SSHConnectionString:  s.agent.SSHConnectionString(),
 	}
 }
 
diff --git a/loop/server/loophttp_test.go b/loop/server/loophttp_test.go
index 5d054fc..531f961 100644
--- a/loop/server/loophttp_test.go
+++ b/loop/server/loophttp_test.go
@@ -229,6 +229,7 @@
 func (m *mockAgent) Diff(commit *string) (string, error)         { return "", nil }
 func (m *mockAgent) OS() string                                  { return "linux" }
 func (m *mockAgent) SessionID() string                           { return m.sessionID }
+func (m *mockAgent) SSHConnectionString() string                 { return "sketch-" + m.sessionID }
 func (m *mockAgent) BranchPrefix() string                        { return m.branchPrefix }
 func (m *mockAgent) CurrentTodoContent() string                  { return "" } // Mock returns empty for simplicity
 func (m *mockAgent) OutstandingLLMCallCount() int                { return 0 }
diff --git a/webui/src/types.ts b/webui/src/types.ts
index f79e35d..6b020e6 100644
--- a/webui/src/types.ts
+++ b/webui/src/types.ts
@@ -90,6 +90,7 @@
 	todo_content?: string;
 	skaband_addr?: string;
 	link_to_github?: boolean;
+	ssh_connection_string?: string;
 }
 
 export interface TodoItem {
diff --git a/webui/src/web-components/sketch-container-status.ts b/webui/src/web-components/sketch-container-status.ts
index 560044a..e0c19cd 100644
--- a/webui/src/web-components/sketch-container-status.ts
+++ b/webui/src/web-components/sketch-container-status.ts
@@ -547,7 +547,10 @@
   }
 
   getSSHHostname() {
-    return `sketch-${this.state?.session_id}`;
+    // Use the ssh_connection_string from the state if available, otherwise fall back to generating it
+    return (
+      this.state?.ssh_connection_string || `sketch-${this.state?.session_id}`
+    );
   }
 
   // Format GitHub repository URL to org/repo format