99import sys
1010from dataclasses import dataclass
1111
12- from trpc_agent_sdk .code_executors import (WorkspacePutFileInfo , WorkspaceRunProgramSpec ,
12+ from trpc_agent_sdk .code_executors import (BaseWorkspaceRuntime , WorkspacePutFileInfo ,
13+ WorkspaceRunProgramSpec ,
1314 WorkspaceStageOptions ,
1415 create_container_workspace_runtime ,
1516 create_local_workspace_runtime )
1617
1718SKILL_WS_DIR = "skills/code-review"
1819DIFF_WS_PATH = "work/inputs/changes.diff"
19- _PYTHON3_DIR = os .path .dirname (shutil .which ("python3" ) or sys .executable )
20- SANDBOX_ENV = {"PATH" : f"{ _PYTHON3_DIR } :/usr/local/bin:/usr/bin:/bin" ,
20+ SANDBOX_ENV = {"PATH" : "/usr/local/bin:/usr/bin:/bin" ,
2121 "HOME" : "/tmp" , "LANG" : "C.UTF-8" }
2222
2323RUNTIME_LOCAL = "local"
@@ -69,7 +69,7 @@ class SandboxRunOutcome:
6969class SandboxSession :
7070 """One workspace: staged code-review skill + staged diff + script runs."""
7171
72- def __init__ (self , runtime , skill_root : str , timeout_sec : float = 60.0 ,
72+ def __init__ (self , runtime : BaseWorkspaceRuntime , skill_root : str , timeout_sec : float = 60.0 ,
7373 max_output_bytes : int = 262144 ):
7474 self ._runtime = runtime
7575 self ._skill_root = skill_root
@@ -79,6 +79,8 @@ def __init__(self, runtime, skill_root: str, timeout_sec: float = 60.0,
7979 self ._exec_id = ""
8080
8181 async def open (self , exec_id : str ) -> None :
82+ if self ._ws is not None :
83+ raise RuntimeError ("SandboxSession is already open; call close() first" )
8284 self ._exec_id = exec_id
8385 manager = self ._runtime .manager (None )
8486 self ._ws = await manager .create_workspace (exec_id , None )
@@ -97,10 +99,21 @@ def _truncate(self, text: str):
9799 return text .encode ("utf-8" , errors = "replace" )[:self ._max_output ].decode (
98100 "utf-8" , errors = "replace" ), True
99101
102+ def _effective_env (self ) -> dict :
103+ """Whitelist stays PATH/HOME/LANG only; host python3 dir is appended as a
104+ dev fallback for hosts without /usr/bin/python3 (e.g. Nix); harmless
105+ nonexistent entry inside containers."""
106+ env = dict (SANDBOX_ENV )
107+ python3_dir = os .path .dirname (shutil .which ("python3" ) or sys .executable )
108+ if python3_dir and python3_dir not in env ["PATH" ].split (":" ):
109+ env ["PATH" ] = f"{ env ['PATH' ]} :{ python3_dir } "
110+ return env
111+
100112 async def run_script (self , script_name : str ,
101113 args : tuple = (DIFF_WS_PATH ,)) -> SandboxRunOutcome :
102114 """Run one skill script under `env -i` (environment whitelist)."""
103- env_args = [f"{ k } ={ v } " for k , v in SANDBOX_ENV .items ()]
115+ env = self ._effective_env ()
116+ env_args = [f"{ k } ={ v } " for k , v in env .items ()]
104117 argv = ["-i" , * env_args , "python3" ,
105118 f"{ SKILL_WS_DIR } /scripts/{ script_name } " , * args ]
106119 spec = WorkspaceRunProgramSpec (cmd = "env" , args = argv , env = {}, cwd = "." ,
@@ -134,3 +147,4 @@ async def close(self) -> None:
134147 await self ._runtime .manager (None ).cleanup (self ._exec_id , None )
135148 except Exception : # noqa: BLE001 - best-effort cleanup
136149 pass
150+ self ._ws = None
0 commit comments