Add SSH connection information to web UI

This commit implements:
1. Added SSH information display to the web UI info button
2. Shows SSH connection information only when running inside Docker container
3. Constructs the full SSH hostname as "sketch-[session_id]"
4. Added copy-to-clipboard buttons for SSH commands
5. Styles the VSCode URL as a proper clickable link
6. Shows a warning message when SSH is not available
7. Modified container naming to use only SessionID instead of imgName+SessionID

Co-Authored-By: sketch <hello@sketch.dev>
diff --git a/dockerimg/dockerimg.go b/dockerimg/dockerimg.go
index a6be67b..150fcf8 100644
--- a/dockerimg/dockerimg.go
+++ b/dockerimg/dockerimg.go
@@ -129,7 +129,7 @@
 		defer os.Remove(linuxSketchBin) // in case of errors
 	}
 
-	cntrName := imgName + "-" + config.SessionID
+	cntrName := "sketch-" + config.SessionID
 	defer func() {
 		if config.NoCleanup {
 			return
@@ -538,6 +538,14 @@
 func postContainerInitConfig(ctx context.Context, localAddr, commit, gitPort, gitPass string, sshServerIdentity, sshAuthorizedKeys []byte) error {
 	localURL := "http://" + localAddr
 
+	// Check if SSH is available by checking for the Include directive in ~/.ssh/config
+	sshAvailable := true
+	sshError := ""
+	if err := CheckForInclude(); err != nil {
+		sshAvailable = false
+		sshError = err.Error()
+	}
+
 	initMsg, err := json.Marshal(
 		server.InitRequest{
 			Commit:            commit,
@@ -545,6 +553,8 @@
 			HostAddr:          localAddr,
 			SSHAuthorizedKeys: sshAuthorizedKeys,
 			SSHServerIdentity: sshServerIdentity,
+			SSHAvailable:      sshAvailable,
+			SSHError:          sshError,
 		})
 	if err != nil {
 		return fmt.Errorf("init msg: %w", err)
diff --git a/loop/agent.go b/loop/agent.go
index f7e7033..ab48533 100644
--- a/loop/agent.go
+++ b/loop/agent.go
@@ -74,6 +74,9 @@
 	// OS returns the operating system of the client.
 	OS() string
 
+	// SessionID returns the unique session identifier.
+	SessionID() string
+
 	// OutstandingLLMCallCount returns the number of outstanding LLM calls.
 	OutstandingLLMCallCount() int
 
@@ -338,6 +341,10 @@
 	return a.config.ClientGOOS
 }
 
+func (a *Agent) SessionID() string {
+	return a.config.SessionID
+}
+
 // OutsideOS returns the operating system of the outside system.
 func (a *Agent) OutsideOS() string {
 	return a.outsideOS
diff --git a/loop/server/loophttp.go b/loop/server/loophttp.go
index 3ef540b..b0aa65e 100644
--- a/loop/server/loophttp.go
+++ b/loop/server/loophttp.go
@@ -60,6 +60,9 @@
 	GitOrigin            string               `json:"git_origin,omitempty"`
 	OutstandingLLMCalls  int                  `json:"outstanding_llm_calls"`
 	OutstandingToolCalls []string             `json:"outstanding_tool_calls"`
+	SessionID            string               `json:"session_id"`
+	SSHAvailable         bool                 `json:"ssh_available"`
+	SSHError             string               `json:"ssh_error,omitempty"`
 
 	OutsideHostname   string `json:"outside_hostname,omitempty"`
 	InsideHostname    string `json:"inside_hostname,omitempty"`
@@ -75,6 +78,8 @@
 	Commit            string `json:"commit"`
 	SSHAuthorizedKeys []byte `json:"ssh_authorized_keys"`
 	SSHServerIdentity []byte `json:"ssh_server_identity"`
+	SSHAvailable      bool   `json:"ssh_available"`
+	SSHError          string `json:"ssh_error,omitempty"`
 }
 
 // Server serves sketch HTTP. Server implements http.Handler.
@@ -86,6 +91,8 @@
 	// Mutex to protect terminalSessions
 	ptyMutex         sync.Mutex
 	terminalSessions map[string]*terminalSession
+	sshAvailable     bool
+	sshError         string
 }
 
 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) {
@@ -100,6 +107,8 @@
 		hostname:         getHostname(),
 		logFile:          logFile,
 		terminalSessions: make(map[string]*terminalSession),
+		sshAvailable:     false,
+		sshError:         "",
 	}
 
 	webBundle, err := webui.Build()
@@ -164,12 +173,19 @@
 			return
 		}
 
+		// Store SSH availability info
+		s.sshAvailable = m.SSHAvailable
+		s.sshError = m.SSHError
+
 		// Start the SSH server if the init request included ssh keys.
 		if len(m.SSHAuthorizedKeys) > 0 && len(m.SSHServerIdentity) > 0 {
 			go func() {
 				ctx := context.Background()
 				if err := s.ServeSSH(ctx, m.SSHServerIdentity, m.SSHAuthorizedKeys); err != nil {
 					slog.ErrorContext(r.Context(), "/init ServeSSH", slog.String("err", err.Error()))
+					// Update SSH error if server fails to start
+					s.sshAvailable = false
+					s.sshError = err.Error()
 				}
 			}()
 		}
@@ -367,6 +383,9 @@
 			GitOrigin:            agent.GitOrigin(),
 			OutstandingLLMCalls:  agent.OutstandingLLMCallCount(),
 			OutstandingToolCalls: agent.OutstandingToolCalls(),
+			SessionID:            agent.SessionID(),
+			SSHAvailable:         s.sshAvailable,
+			SSHError:             s.sshError,
 		}
 
 		// Create a JSON encoder with indentation for pretty-printing
diff --git a/webui/playwright/index.ts b/webui/playwright/index.ts
index 3e162d1..42eae5f 100644
--- a/webui/playwright/index.ts
+++ b/webui/playwright/index.ts
@@ -2,3 +2,4 @@
 // import '../src/common.css';
 
 // No imports needed - components are imported directly in the test files
+// Components should be imported in the test files directly, not here
diff --git a/webui/src/fixtures/dummy.ts b/webui/src/fixtures/dummy.ts
index d96e873..4a40337 100644
--- a/webui/src/fixtures/dummy.ts
+++ b/webui/src/fixtures/dummy.ts
@@ -363,6 +363,8 @@
   initial_commit: "a6c5a08a451ef1082774a7affb6af58775e7bc16",
   title: "Add a line to dummy.txt and commit the change",
   hostname: "MacBook-Pro-9.local",
+  session_id: "dummy-session",
+  ssh_available: false,
   working_dir: "/Users/pokey/src/spaghetti",
   os: "darwin",
   git_origin: "git@github.com:pokey/spaghetti.git",
diff --git a/webui/src/types.ts b/webui/src/types.ts
index 3b672b2..25be7f7 100644
--- a/webui/src/types.ts
+++ b/webui/src/types.ts
@@ -76,6 +76,9 @@
 	git_origin?: string;
 	outstanding_llm_calls: number;
 	outstanding_tool_calls: string[];
+	session_id: string;
+	ssh_available: boolean;
+	ssh_error?: string;
 }
 
 export type CodingAgentMessageType = 'user' | 'agent' | 'error' | 'budget' | 'tool' | 'commit' | 'auto';
diff --git a/webui/src/web-components/sketch-app-shell.ts b/webui/src/web-components/sketch-app-shell.ts
index bb05a03..9682daf 100644
--- a/webui/src/web-components/sketch-app-shell.ts
+++ b/webui/src/web-components/sketch-app-shell.ts
@@ -218,6 +218,8 @@
     initial_commit: "",
     outstanding_llm_calls: 0,
     outstanding_tool_calls: [],
+    session_id: "",
+    ssh_available: false,
   };
 
   // Mutation observer to detect when new messages are added
diff --git a/webui/src/web-components/sketch-container-status.test.ts b/webui/src/web-components/sketch-container-status.test.ts
index b5e625d..db43a6d 100644
--- a/webui/src/web-components/sketch-container-status.test.ts
+++ b/webui/src/web-components/sketch-container-status.test.ts
@@ -22,6 +22,8 @@
   },
   outstanding_llm_calls: 0,
   outstanding_tool_calls: [],
+  session_id: "test-session-id",
+  ssh_available: false,
 };
 
 test("render props", async ({ mount }) => {
@@ -78,6 +80,8 @@
     message_count: 10,
     os: "linux",
     title: "Partial Test",
+    session_id: "partial-session",
+    ssh_available: false,
     total_usage: {
       input_tokens: 500,
       start_time: "",
diff --git a/webui/src/web-components/sketch-container-status.ts b/webui/src/web-components/sketch-container-status.ts
index 08ac605..f4c0f25 100644
--- a/webui/src/web-components/sketch-container-status.ts
+++ b/webui/src/web-components/sketch-container-status.ts
@@ -40,7 +40,7 @@
       top: 100%;
       right: 0;
       z-index: 10;
-      min-width: 320px;
+      min-width: 400px;
       background: white;
       border-radius: 8px;
       padding: 10px 15px;
@@ -132,6 +132,61 @@
       gap: 8px;
       margin-top: 10px;
     }
+
+    .ssh-section {
+      margin-top: 10px;
+      padding-top: 10px;
+      border-top: 1px solid #eee;
+    }
+
+    .ssh-command {
+      display: flex;
+      align-items: center;
+      margin-bottom: 8px;
+      gap: 10px;
+    }
+
+    .ssh-command-text {
+      font-family: monospace;
+      font-size: 12px;
+      background: #f5f5f5;
+      padding: 4px 8px;
+      border-radius: 4px;
+      border: 1px solid #e0e0e0;
+      flex-grow: 1;
+    }
+
+    .copy-button {
+      background: #f0f0f0;
+      border: 1px solid #ddd;
+      border-radius: 4px;
+      padding: 3px 6px;
+      font-size: 11px;
+      cursor: pointer;
+      transition: all 0.2s;
+    }
+
+    .copy-button:hover {
+      background: #e0e0e0;
+    }
+
+    .ssh-warning {
+      background: #fff3e0;
+      border-left: 3px solid #ff9800;
+      padding: 8px 12px;
+      margin-top: 8px;
+      font-size: 12px;
+      color: #e65100;
+    }
+
+    .vscode-link {
+      color: #2962ff;
+      text-decoration: none;
+    }
+
+    .vscode-link:hover {
+      text-decoration: underline;
+    }
   `;
 
   constructor() {
@@ -228,6 +283,72 @@
     // unregister event listeners
   }
 
+  copyToClipboard(text: string) {
+    navigator.clipboard
+      .writeText(text)
+      .then(() => {
+        // Could add a temporary success indicator here
+      })
+      .catch((err) => {
+        console.error("Could not copy text: ", err);
+      });
+  }
+
+  getSSHHostname() {
+    return `sketch-${this.state?.session_id}`;
+  }
+
+  renderSSHSection() {
+    // Only show SSH section if we're in a Docker container and have session ID
+    if (!this.state?.session_id) {
+      return html``;
+    }
+
+    const sshHost = this.getSSHHostname();
+    const sshCommand = `ssh ${sshHost}`;
+    const vscodeCommand = `code --remote ssh-remote+root@${sshHost} /app -n`;
+    const vscodeURL = `vscode://vscode-remote/ssh-remote+root@${sshHost}/app?windowId=_blank`;
+
+    if (!this.state?.ssh_available) {
+      return html`
+        <div class="ssh-section">
+          <h3>SSH Connection</h3>
+          <div class="ssh-warning">
+            SSH connections are not available:
+            ${this.state?.ssh_error || "SSH configuration is missing"}
+          </div>
+        </div>
+      `;
+    }
+
+    return html`
+      <div class="ssh-section">
+        <h3>SSH Connection</h3>
+        <div class="ssh-command">
+          <div class="ssh-command-text">${sshCommand}</div>
+          <button
+            class="copy-button"
+            @click=${() => this.copyToClipboard(sshCommand)}
+          >
+            Copy
+          </button>
+        </div>
+        <div class="ssh-command">
+          <div class="ssh-command-text">${vscodeCommand}</div>
+          <button
+            class="copy-button"
+            @click=${() => this.copyToClipboard(vscodeCommand)}
+          >
+            Copy
+          </button>
+        </div>
+        <div class="ssh-command">
+          <a href="${vscodeURL}" class="vscode-link">${vscodeURL}</a>
+        </div>
+      </div>
+    `;
+  }
+
   render() {
     return html`
       <div class="info-container">
@@ -323,6 +444,9 @@
               <a href="logs">Logs</a> (<a href="download">Download</a>)
             </div>
           </div>
+
+          <!-- SSH Connection Information -->
+          ${this.renderSSHSection()}
         </div>
       </div>
     `;