@@ -21,12 +21,14 @@ import (
2121
2222// CheckAvailable returns a user-friendly error when Docker is not
2323// installed or the sandbox feature is not supported.
24- func CheckAvailable (ctx context.Context ) error {
25- if _ , err := exec .LookPath ("docker" ); err != nil {
24+ func ( b * Backend ) CheckAvailable (ctx context.Context ) error {
25+ if _ , err := exec .LookPath (b . program ); err != nil {
2626 return fmt .Errorf ("--sandbox requires Docker Desktop: %w\n \n Install Docker Desktop from https://docs.docker.com/get-docker/" , err )
2727 }
2828
29- if err := exec .CommandContext (ctx , "docker" , "sandbox" , "version" ).Run (); err != nil {
29+ cmd := exec .CommandContext (ctx , b .program , b .args ("version" )... )
30+ b .applyEnv (cmd )
31+ if err := cmd .Run (); err != nil {
3032 return errors .New ("--sandbox requires Docker Desktop with sandbox support\n \n " +
3133 "Make sure Docker Desktop is running and up to date.\n " +
3234 "For more information, see https://docs.docker.com/ai/sandboxes/" )
@@ -51,22 +53,34 @@ func (s *Existing) HasWorkspace(dir string) bool {
5153
5254// ForWorkspace returns the existing sandbox whose primary workspace
5355// matches wd, or nil if none exists.
54- func ForWorkspace (ctx context.Context , wd string ) * Existing {
55- out , err := exec .CommandContext (ctx , "docker" , "sandbox" , "ls" , "--json" ).Output ()
56+ func (b * Backend ) ForWorkspace (ctx context.Context , wd string ) * Existing {
57+ cmd := exec .CommandContext (ctx , b .program , b .args ("ls" , "--json" )... )
58+ b .applyEnv (cmd )
59+ out , err := cmd .Output ()
5660 if err != nil {
5761 return nil
5862 }
5963
60- var result struct {
61- VMs []Existing `json:"vms"`
64+ // The JSON key differs between backends: "vms" for docker sandbox,
65+ // "sandboxes" for sbx.
66+ var raw map [string ]json.RawMessage
67+ if err := json .Unmarshal (out , & raw ); err != nil {
68+ return nil
69+ }
70+
71+ listJSON , ok := raw [b .vmListKey ]
72+ if ! ok {
73+ return nil
6274 }
63- if err := json .Unmarshal (out , & result ); err != nil {
75+
76+ var entries []Existing
77+ if err := json .Unmarshal (listJSON , & entries ); err != nil {
6478 return nil
6579 }
6680
67- for _ , vm := range result . VMs {
68- if len (vm .Workspaces ) > 0 && vm .Workspaces [0 ] == wd {
69- return & vm
81+ for _ , entry := range entries {
82+ if len (entry .Workspaces ) > 0 && entry .Workspaces [0 ] == wd {
83+ return & entry
7084 }
7185 }
7286 return nil
@@ -75,7 +89,7 @@ func ForWorkspace(ctx context.Context, wd string) *Existing {
7589// Ensure makes sure a sandbox exists for the given workspace,
7690// creating or recreating it as needed. When template is non-empty it is
7791// passed to `docker sandbox create -t`. Returns the sandbox name.
78- func Ensure (ctx context.Context , wd , extra , template , configDir string ) (string , error ) {
92+ func ( b * Backend ) Ensure (ctx context.Context , wd , extra , template , configDir string ) (string , error ) {
7993 // Resolve wd to an absolute path so that it matches the absolute
8094 // workspace paths returned by `docker sandbox ls --json`.
8195 absWd , err := filepath .Abs (wd )
@@ -84,7 +98,7 @@ func Ensure(ctx context.Context, wd, extra, template, configDir string) (string,
8498 }
8599 wd = absWd
86100
87- existing := ForWorkspace (ctx , wd )
101+ existing := b . ForWorkspace (ctx , wd )
88102
89103 // If the sandbox exists with the right mounts, reuse it.
90104 if existing != nil &&
@@ -97,55 +111,69 @@ func Ensure(ctx context.Context, wd, extra, template, configDir string) (string,
97111 // Remove a stale sandbox whose mounts don't match.
98112 if existing != nil {
99113 slog .Debug ("Removing existing sandbox to change workspace mounts" , "name" , existing .Name )
100- _ = exec .CommandContext (ctx , "docker" , "sandbox" , "rm" , existing .Name ).Run ()
114+ rmCmd := exec .CommandContext (ctx , b .program , b .args ("rm" , existing .Name )... )
115+ b .applyEnv (rmCmd )
116+ _ = rmCmd .Run ()
101117 }
102118
103- // docker sandbox create [-t template] cagent <wd> [<extra>:ro] <dataDir> <configDir>
104- createArgs := []string {"sandbox" , "create" }
119+ createExtra := []string {}
105120 if template != "" {
106- createArgs = append (createArgs , "-t" , template )
121+ createExtra = append (createExtra , "-t" , template )
107122 }
108- createArgs = append (createArgs , "cagent" , wd )
123+ createExtra = append (createExtra , "cagent" , wd )
109124 if extra != "" && extra != wd {
110- createArgs = append (createArgs , extra + ":ro" )
125+ createExtra = append (createExtra , extra + ":ro" )
111126 }
112127 // Mount config directory read-only so the sandbox can
113128 // read the token file and access user config.
114- createArgs = append (createArgs , configDir + ":ro" )
129+ createExtra = append (createExtra , configDir + ":ro" )
130+
131+ createArgs := b .args ("create" , createExtra ... )
115132 slog .Debug ("Creating sandbox" , "args" , createArgs )
116133
117- createCmd := exec .CommandContext (ctx , "docker" , createArgs ... )
134+ createCmd := exec .CommandContext (ctx , b .program , createArgs ... )
135+ b .applyEnv (createCmd )
118136 createCmd .Stdin = os .Stdin
119137 createCmd .Stdout = os .Stdout
120138 createCmd .Stderr = os .Stderr
121139
122140 if err := createCmd .Run (); err != nil {
123- return "" , fmt .Errorf ("docker sandbox create failed: %w" , err )
141+ return "" , fmt .Errorf ("sandbox create failed: %w" , err )
124142 }
125143
126144 // Read back the sandbox name that was just created.
127- created := ForWorkspace (ctx , wd )
145+ created := b . ForWorkspace (ctx , wd )
128146 if created == nil {
129147 return "" , errors .New ("sandbox was created but could not be found" )
130148 }
131149
132150 return created .Name , nil
133151}
134152
135- // BuildExecCmd assembles the `docker sandbox exec` command.
136- func BuildExecCmd (ctx context.Context , name , wd string , cagentArgs , envFlags , envVars []string ) * exec.Cmd {
137- execArgs := []string {"sandbox" , "exec" , "-it" , "-w" , wd }
138- execArgs = append (execArgs , envFlags ... )
139- execArgs = append (execArgs , name , "cagent" , "run" )
140- execArgs = append (execArgs , cagentArgs ... )
153+ // BuildExecCmd assembles the sandbox exec command.
154+ func (b * Backend ) BuildExecCmd (ctx context.Context , name , wd string , cagentArgs , envFlags , envVars []string ) * exec.Cmd {
155+ execExtra := []string {"-it" , "-w" , wd }
156+ execExtra = append (execExtra , envFlags ... )
157+
158+ // Improve the rendering of the TUI
159+ execExtra = append (execExtra ,
160+ "-e" , "TERM=xterm-256color" ,
161+ "-e" , "COLORTERM=truecolor" ,
162+ "-e" , "LANG=en_US.UTF-8" ,
163+ name , "docker-agent" , "run" ,
164+ )
165+ execExtra = append (execExtra , cagentArgs ... )
166+
167+ args := b .args ("exec" , execExtra ... )
141168
142- dockerCmd := exec .CommandContext (ctx , "docker" , execArgs ... )
143- dockerCmd .Stdin = os .Stdin
144- dockerCmd .Stdout = os .Stdout
145- dockerCmd .Stderr = os .Stderr
146- dockerCmd .Env = append (os .Environ (), envVars ... )
169+ cmd := exec .CommandContext (ctx , b .program , args ... )
170+ cmd .Stdin = os .Stdin
171+ cmd .Stdout = os .Stdout
172+ cmd .Stderr = os .Stderr
173+ cmd .Env = append (os .Environ (), envVars ... )
174+ b .applyEnv (cmd )
147175
148- return dockerCmd
176+ return cmd
149177}
150178
151179// StartTokenWriterIfNeeded starts a background goroutine that refreshes
0 commit comments