1010from git import GitCommandError
1111
1212from automation .agent .constants import REPO_PATH
13- from core .sandbox .schemas import RunCommandsRequest
1413
1514if TYPE_CHECKING :
1615 from git import Repo
1716
18- from core . sandbox . client import DAIVSandboxClient
17+ from automation . agent . middlewares . file_system import SandboxFileBackend
1918
2019logger = logging .getLogger ("daiv.tools" )
2120
@@ -56,9 +55,9 @@ class RepoStatus:
5655class GitManager :
5756 """Run git operations against a repository in one of two mutually-exclusive modes:
5857
59- - **Sandbox mode** (``client`` + ``session_id`` ): git runs *in the sandbox* via
60- ``run_commands`` in ``repo_path`` (``/workspace/repo``). Used for sandbox-enabled
61- runs, where the agent's changes are sandbox-authoritative (no local copy).
58+ - **Sandbox mode** (``sandbox_backend`` ): git runs *in the sandbox* via the bound
59+ backend's ``run_commands`` in ``repo_path`` (``/workspace/repo``). Used for
60+ sandbox-enabled runs, where the agent's changes are sandbox-authoritative (no local copy).
6261 - **Local mode** (``repo``): git runs as a subprocess against a GitPython clone's
6362 working tree. Used for sandbox-disabled / repoless runs, where changes live on disk.
6463
@@ -67,8 +66,7 @@ class GitManager:
6766
6867 Args:
6968 repo: GitPython repo for local mode.
70- client: Sandbox client for sandbox mode.
71- session_id: Sandbox session id (required with ``client``).
69+ sandbox_backend: The run's bound :class:`SandboxFileBackend` for sandbox mode.
7270 repo_path: Repo path inside the sandbox (defaults to ``REPO_PATH``).
7371 """
7472
@@ -78,19 +76,15 @@ def __init__(
7876 self ,
7977 repo : Repo | None = None ,
8078 * ,
81- client : DAIVSandboxClient | None = None ,
82- session_id : str | None = None ,
79+ sandbox_backend : SandboxFileBackend | None = None ,
8380 repo_path : str | None = None ,
8481 ) -> None :
85- if (repo is None ) == (client is None ):
86- raise ValueError ("GitManager requires exactly one of `repo` (local) or `client` (sandbox)." )
87- if client is not None and not session_id :
88- raise ValueError ("GitManager sandbox mode requires a non-empty session_id." )
82+ if (repo is None ) == (sandbox_backend is None ):
83+ raise ValueError ("GitManager requires exactly one of `repo` (local) or `sandbox_backend` (sandbox)." )
8984 if repo_path is None :
9085 repo_path = REPO_PATH
9186 self .repo = repo
92- self ._client = client
93- self ._session_id = session_id
87+ self ._sandbox_backend = sandbox_backend
9488 self ._repo_path = repo_path
9589
9690 @classmethod
@@ -103,30 +97,31 @@ def for_local(cls, repo: Repo) -> GitManager:
10397 return cls (repo = repo )
10498
10599 @classmethod
106- def for_sandbox (cls , client : DAIVSandboxClient , session_id : str , * , repo_path : str | None = None ) -> GitManager :
100+ def for_sandbox (cls , sandbox_backend : SandboxFileBackend , * , repo_path : str | None = None ) -> GitManager :
107101 """Sandbox-mode manager that runs git in the session's ``repo_path`` (``/workspace/repo``).
108102
109- Preferred over ``GitManager(client=..., session_id=...)``: the required ``session_id`` is
110- positional, so a sandbox manager can't be built without one.
103+ Takes the run's already-bound :class:`SandboxFileBackend` — the single session handle.
104+ The backend's ``_require_bound`` guard surfaces an unbound-session programming error on
105+ the first command, so no session id is threaded here.
111106 """
112- return cls (client = client , session_id = session_id , repo_path = repo_path )
107+ return cls (sandbox_backend = sandbox_backend , repo_path = repo_path )
113108
114109 # -- git invocation ------------------------------------------------------
115110 async def _git (self , * args : str , check : bool = True ) -> _GitResult :
116111 """Run one git command in the repo. Raises ``GitCommandError`` on a non-zero
117112 exit when ``check`` is True; otherwise returns the result for the caller to inspect.
118113 """
119- result = await (self ._git_sandbox (args ) if self ._client is not None else self ._git_local (args ))
114+ result = await (self ._git_sandbox (args ) if self ._sandbox_backend is not None else self ._git_local (args ))
120115 if check and result .exit_code != 0 :
121116 raise GitCommandError (["git" , * args ], result .exit_code , result .output )
122117 return result
123118
124119 async def _git_sandbox (self , args : tuple [str , ...]) -> _GitResult :
125- client , session_id = self ._client , self . _session_id
126- if client is None or session_id is None : # pragma: no cover - guaranteed by __init__
120+ backend = self ._sandbox_backend
121+ if backend is None : # pragma: no cover - guaranteed by __init__
127122 raise RuntimeError ("GitManager is not in sandbox mode" )
128123 command = " " .join (_shell_quote (token ) for token in ("git" , "-C" , self ._repo_path , * args ))
129- response = await client .run_commands (session_id , RunCommandsRequest ( commands = [command ], fail_fast = True ) )
124+ response = await backend .run_commands ([command ], fail_fast = True )
130125 if not response .results :
131126 # The sandbox always returns one result per command; an empty list is a wire-level
132127 # anomaly. Fail with context rather than a bare IndexError on ``results[0]``.
@@ -159,14 +154,11 @@ async def _git_batch(self, commands: list[tuple[str, ...]]) -> list[_GitResult]:
159154 """
160155 if not commands :
161156 return []
162- if self ._client is not None :
163- client , session_id = self ._client , self ._session_id
164- if session_id is None : # pragma: no cover - guaranteed by __init__
165- raise RuntimeError ("GitManager is not in sandbox mode" )
157+ if self ._sandbox_backend is not None :
166158 cmd_strs = [
167159 " " .join (_shell_quote (tok ) for tok in ("git" , "-C" , self ._repo_path , * args )) for args in commands
168160 ]
169- response = await client . run_commands (session_id , RunCommandsRequest ( commands = cmd_strs , fail_fast = False ) )
161+ response = await self . _sandbox_backend . run_commands (cmd_strs , fail_fast = False )
170162 if len (response .results ) != len (commands ):
171163 raise RuntimeError (f"Sandbox returned { len (response .results )} results for { len (commands )} git commands" )
172164 return [_GitResult (exit_code = r .exit_code , output = r .output ) for r in response .results ]
0 commit comments