Merge pull request #1453 from trheyi/main
Enhance sandbox environment setup for VNC and Python compatibility
This commit is contained in:
commit
e14722f1fa
3 changed files with 45 additions and 7 deletions
|
|
@ -121,6 +121,10 @@ func BuildCommandWithContinuation(messages []agentContext.Message, opts *Options
|
||||||
// System prompt may contain quotes, newlines, special characters that break shell quoting
|
// System prompt may contain quotes, newlines, special characters that break shell quoting
|
||||||
var bashCmd strings.Builder
|
var bashCmd strings.Builder
|
||||||
|
|
||||||
|
// Ensure $HOME/.Xauthority exists for PyAutoGUI/Xlib (HOME=/workspace).
|
||||||
|
// Xvfb runs without auth, but Xlib requires the file to exist.
|
||||||
|
bashCmd.WriteString("touch /home/sandbox/.Xauthority 2>/dev/null; touch \"$HOME/.Xauthority\" 2>/dev/null\n")
|
||||||
|
|
||||||
// If we have a system prompt (first request only), write it to a temp file via heredoc first
|
// If we have a system prompt (first request only), write it to a temp file via heredoc first
|
||||||
// then use --append-system-prompt-file
|
// then use --append-system-prompt-file
|
||||||
if systemPrompt != "" {
|
if systemPrompt != "" {
|
||||||
|
|
@ -306,10 +310,30 @@ func buildEnvironment(opts *Options, systemPrompt string) map[string]string {
|
||||||
// Session data is stored in $HOME/.claude/ (i.e., /workspace/.claude/)
|
// Session data is stored in $HOME/.claude/ (i.e., /workspace/.claude/)
|
||||||
env["HOME"] = "/workspace"
|
env["HOME"] = "/workspace"
|
||||||
|
|
||||||
|
// Fix Python user-site-packages: changing HOME from /home/sandbox to /workspace
|
||||||
|
// breaks Python's ability to find packages installed via pip --user (e.g., playwright,
|
||||||
|
// pyautogui, playwright-stealth) which live in /home/sandbox/.local/lib/pythonX.Y/site-packages/
|
||||||
|
env["PYTHONPATH"] = "/home/sandbox/.local/lib/python3.12/site-packages"
|
||||||
|
|
||||||
|
// Fix X11 auth: PyAutoGUI/Xlib looks for $HOME/.Xauthority, but HOME=/workspace
|
||||||
|
// so it fails to find /home/sandbox/.Xauthority created during image build.
|
||||||
|
// Explicitly set XAUTHORITY to the correct path.
|
||||||
|
env["XAUTHORITY"] = "/home/sandbox/.Xauthority"
|
||||||
|
|
||||||
// claude-proxy runs on localhost:3456, Claude CLI connects to it
|
// claude-proxy runs on localhost:3456, Claude CLI connects to it
|
||||||
env["ANTHROPIC_BASE_URL"] = "http://127.0.0.1:3456"
|
env["ANTHROPIC_BASE_URL"] = "http://127.0.0.1:3456"
|
||||||
env["ANTHROPIC_API_KEY"] = "dummy" // Proxy doesn't verify this
|
env["ANTHROPIC_API_KEY"] = "dummy" // Proxy doesn't verify this
|
||||||
|
|
||||||
|
// Pass secrets as environment variables for Claude CLI to use
|
||||||
|
// These are configured in package.yao sandbox.secrets (e.g., LLM_API_KEY, GITHUB_TOKEN)
|
||||||
|
// start-claude-proxy also exports them for the proxy process, but Claude CLI
|
||||||
|
// is launched via a separate docker exec, so it needs them passed explicitly here.
|
||||||
|
if len(opts.Secrets) > 0 {
|
||||||
|
for k, v := range opts.Secrets {
|
||||||
|
env[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Note: System prompt and max_turns are passed via CLI flags in BuildCommand
|
// Note: System prompt and max_turns are passed via CLI flags in BuildCommand
|
||||||
// CLAUDE_SYSTEM_PROMPT environment variable is NOT supported by Claude CLI
|
// CLAUDE_SYSTEM_PROMPT environment variable is NOT supported by Claude CLI
|
||||||
// --append-system-prompt or --system-prompt flags must be used instead
|
// --append-system-prompt or --system-prompt flags must be used instead
|
||||||
|
|
|
||||||
|
|
@ -1103,14 +1103,13 @@ func (e *Executor) GetVNCUrl() string {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the image supports VNC (playwright or desktop variants)
|
|
||||||
imageName := e.opts.Image
|
imageName := e.opts.Image
|
||||||
if imageName == "" {
|
if imageName == "" {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// VNC is only available for playwright and desktop images
|
// Check if the image supports VNC using the shared keyword list in sandbox package
|
||||||
if !strings.Contains(imageName, "playwright") && !strings.Contains(imageName, "desktop") {
|
if !infraSandbox.IsVNCImage(imageName) {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,10 @@ import (
|
||||||
"github.com/yaoapp/yao/sandbox/ipc"
|
"github.com/yaoapp/yao/sandbox/ipc"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// vncImageKeywords lists image name keywords that indicate VNC support.
|
||||||
|
// Add new keywords here when adding new VNC-capable sandbox images.
|
||||||
|
var vncImageKeywords = []string{"playwright", "desktop", "chrome"}
|
||||||
|
|
||||||
// execReadCloser wraps a Reader with a Closer
|
// execReadCloser wraps a Reader with a Closer
|
||||||
type execReadCloser struct {
|
type execReadCloser struct {
|
||||||
*bufio.Reader
|
*bufio.Reader
|
||||||
|
|
@ -324,9 +328,15 @@ func (m *Manager) createContainer(ctx context.Context, opts CreateOptions) (*Con
|
||||||
CapDrop: []string{"ALL"},
|
CapDrop: []string{"ALL"},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Chrome/browser images need SYS_ADMIN for namespace-based process isolation.
|
||||||
|
// Without it, Chrome renderer/GPU processes crash with error code 5.
|
||||||
|
if IsVNCImage(image) {
|
||||||
|
hostConfig.CapAdd = []string{"SYS_ADMIN"}
|
||||||
|
}
|
||||||
|
|
||||||
// VNC port mapping for Docker Desktop (macOS/Windows)
|
// VNC port mapping for Docker Desktop (macOS/Windows)
|
||||||
// Only enable for VNC-capable images (playwright/desktop) when config is enabled
|
// Only enable for VNC-capable images (playwright/desktop) when config is enabled
|
||||||
if m.config.VNCPortMapping && isVNCImage(image) {
|
if m.config.VNCPortMapping && IsVNCImage(image) {
|
||||||
// Expose VNC ports in container config
|
// Expose VNC ports in container config
|
||||||
containerConfig.ExposedPorts = nat.PortSet{
|
containerConfig.ExposedPorts = nat.PortSet{
|
||||||
"6080/tcp": struct{}{}, // noVNC websockify
|
"6080/tcp": struct{}{}, // noVNC websockify
|
||||||
|
|
@ -952,9 +962,14 @@ func (m *Manager) fixIPCSocketPermissions(ctx context.Context, containerID strin
|
||||||
time.Sleep(50 * time.Millisecond)
|
time.Sleep(50 * time.Millisecond)
|
||||||
}
|
}
|
||||||
|
|
||||||
// isVNCImage checks if the image is VNC-capable (playwright or desktop variants)
|
// IsVNCImage checks if the image is VNC-capable based on vncImageKeywords.
|
||||||
func isVNCImage(imageName string) bool {
|
func IsVNCImage(imageName string) bool {
|
||||||
return strings.Contains(imageName, "playwright") || strings.Contains(imageName, "desktop")
|
for _, kw := range vncImageKeywords {
|
||||||
|
if strings.Contains(imageName, kw) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
// findAvailablePort finds an available port on the host
|
// findAvailablePort finds an available port on the host
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue