22
33from __future__ import annotations
44
5+ import asyncio
56import json
67import os
78import shlex
8- import subprocess
99import sys
1010from pathlib import Path
1111from time import perf_counter
1212from typing import Any
13+ from uuid import uuid4
1314
1415from trpc_agent_sdk .code_executors import BaseWorkspaceRuntime
1516from trpc_agent_sdk .code_executors import DEFAULT_SKILLS_CONTAINER
17+ from trpc_agent_sdk .code_executors import WorkspacePutFileInfo
18+ from trpc_agent_sdk .code_executors import WorkspaceRunProgramSpec
19+ from trpc_agent_sdk .code_executors import WorkspaceStageOptions
1620from trpc_agent_sdk .code_executors import create_container_workspace_runtime
1721from trpc_agent_sdk .code_executors import create_local_workspace_runtime
1822from trpc_agent_sdk .skills import BaseSkillRepository
2630SKILL_NAME = "code-review"
2731SCRIPT_TIMEOUT_SECONDS = 20
2832OUTPUT_LIMIT_CHARS = 4000
29- HOST_EXECUTION_RUNTIME = "local"
33+ WORKSPACE_SKILL_DIR = f"skills/{ SKILL_NAME } "
34+ WORKSPACE_DIFF_PATH = "work/inputs/review.diff"
3035
3136
3237def create_skill_tool_set (
@@ -58,37 +63,38 @@ def build_skill_script_plan(
5863 """Build the list of planned skill-script executions for the review."""
5964
6065 scripts_dir = resolve_code_review_skill_dir (project_root = project_root ) / "scripts"
66+ _ = diff_file
6167 return [
6268 SkillScriptInvocation (
6369 name = "parse_diff" ,
6470 script_path = scripts_dir / "parse_diff.py" ,
6571 command = [
66- sys . executable ,
67- str ( scripts_dir / " parse_diff.py") ,
72+ "python" ,
73+ f" { WORKSPACE_SKILL_DIR } /scripts/ parse_diff.py" ,
6874 "--diff-file" ,
69- str ( diff_file ) ,
75+ WORKSPACE_DIFF_PATH ,
7076 ],
7177 target = "skill:code-review/scripts/parse_diff.py" ,
7278 ),
7379 SkillScriptInvocation (
7480 name = "run_linters" ,
7581 script_path = scripts_dir / "run_linters.py" ,
7682 command = [
77- sys . executable ,
78- str ( scripts_dir / " run_linters.py") ,
83+ "python" ,
84+ f" { WORKSPACE_SKILL_DIR } /scripts/ run_linters.py" ,
7985 "--diff-file" ,
80- str ( diff_file ) ,
86+ WORKSPACE_DIFF_PATH ,
8187 ],
8288 target = "skill:code-review/scripts/run_linters.py" ,
8389 ),
8490 SkillScriptInvocation (
8591 name = "run_tests" ,
8692 script_path = scripts_dir / "run_tests.py" ,
8793 command = [
88- sys . executable ,
89- str ( scripts_dir / " run_tests.py") ,
94+ "python" ,
95+ f" { WORKSPACE_SKILL_DIR } /scripts/ run_tests.py" ,
9096 "--diff-file" ,
91- str ( diff_file ) ,
97+ WORKSPACE_DIFF_PATH ,
9298 ],
9399 target = "skill:code-review/scripts/run_tests.py" ,
94100 ),
@@ -119,25 +125,23 @@ def execute_skill_script(
119125 invocation : SkillScriptInvocation ,
120126 * ,
121127 runtime : str ,
128+ diff_text : str ,
129+ project_root : Path | None = None ,
122130 timeout_seconds : int = SCRIPT_TIMEOUT_SECONDS ,
123131 output_limit_chars : int = OUTPUT_LIMIT_CHARS ,
124132) -> SandboxRunRecord :
125- """Execute a skill script in a controlled subprocess."""
126-
127- if runtime != HOST_EXECUTION_RUNTIME :
128- raise RuntimeError (
129- f"Runtime `{ runtime } ` is not backed by a real isolated executor in this example."
130- )
133+ """Execute a skill script through the configured workspace runtime."""
131134
132135 started = perf_counter ()
133136 try :
134- completed = subprocess .run (
135- invocation .command ,
136- capture_output = True ,
137- check = False ,
138- text = True ,
139- encoding = "utf-8" ,
140- timeout = timeout_seconds ,
137+ completed = asyncio .run (
138+ _run_skill_script_in_workspace (
139+ invocation ,
140+ runtime = runtime ,
141+ diff_text = diff_text ,
142+ project_root = project_root ,
143+ timeout_seconds = timeout_seconds ,
144+ )
141145 )
142146 stdout , stdout_truncated = _truncate_output (
143147 _normalize_process_output (completed .stdout ),
@@ -147,9 +151,9 @@ def execute_skill_script(
147151 _normalize_process_output (completed .stderr ),
148152 output_limit_chars ,
149153 )
150- status = (
154+ status = SandboxRunStatus . TIMED_OUT if completed . timed_out else (
151155 SandboxRunStatus .SUCCEEDED
152- if completed .returncode == 0
156+ if completed .exit_code == 0
153157 else SandboxRunStatus .FAILED
154158 )
155159 return SandboxRunRecord (
@@ -158,33 +162,26 @@ def execute_skill_script(
158162 status = status ,
159163 runtime = runtime ,
160164 duration_ms = int ((perf_counter () - started ) * 1000 ),
161- exit_code = completed .returncode ,
165+ exit_code = completed .exit_code ,
162166 stdout = _sanitize_output_text (stdout ),
163167 stderr = _sanitize_output_text (stderr ),
164- timed_out = False ,
168+ timed_out = completed . timed_out ,
165169 output_truncated = stdout_truncated or stderr_truncated ,
166170 blocked_by_filter = False ,
167171 )
168- except subprocess .TimeoutExpired as exc :
169- stdout , stdout_truncated = _truncate_output (
170- _normalize_process_output (exc .stdout ),
171- output_limit_chars ,
172- )
173- stderr , stderr_truncated = _truncate_output (
174- _normalize_process_output (exc .stderr ),
175- output_limit_chars ,
176- )
172+ except Exception as exc : # pylint: disable=broad-except
173+ stderr , stderr_truncated = _truncate_output (str (exc ), output_limit_chars )
177174 return SandboxRunRecord (
178175 name = invocation .name ,
179176 command = [_sanitize_display_value (part ) for part in invocation .command ],
180- status = SandboxRunStatus .TIMED_OUT ,
177+ status = SandboxRunStatus .FAILED ,
181178 runtime = runtime ,
182179 duration_ms = int ((perf_counter () - started ) * 1000 ),
183180 exit_code = None ,
184- stdout = _sanitize_output_text ( stdout ) ,
181+ stdout = "" ,
185182 stderr = _sanitize_output_text (stderr ),
186- timed_out = True ,
187- output_truncated = stdout_truncated or stderr_truncated ,
183+ timed_out = False ,
184+ output_truncated = stderr_truncated ,
188185 blocked_by_filter = False ,
189186 )
190187
@@ -232,6 +229,53 @@ def _normalize_process_output(text: object) -> str:
232229 return str (text )
233230
234231
232+ async def _run_skill_script_in_workspace (
233+ invocation : SkillScriptInvocation ,
234+ * ,
235+ runtime : str ,
236+ diff_text : str ,
237+ project_root : Path | None ,
238+ timeout_seconds : int ,
239+ ) -> Any :
240+ """Stage skill inputs into a workspace and execute the script via runtime APIs."""
241+
242+ workspace_runtime = _create_workspace_runtime (workspace_runtime_type = runtime )
243+ manager = workspace_runtime .manager ()
244+ fs = workspace_runtime .fs ()
245+ runner = workspace_runtime .runner ()
246+ exec_id = f"code_review_{ invocation .name } _{ uuid4 ().hex } "
247+ workspace = await manager .create_workspace (exec_id )
248+
249+ try :
250+ skill_dir = resolve_code_review_skill_dir (project_root = project_root )
251+ await fs .stage_directory (
252+ workspace ,
253+ str (skill_dir ),
254+ WORKSPACE_SKILL_DIR ,
255+ WorkspaceStageOptions (read_only = True , allow_mount = True , mode = "copy" ),
256+ )
257+ await fs .put_files (
258+ workspace ,
259+ [
260+ WorkspacePutFileInfo (
261+ path = WORKSPACE_DIFF_PATH ,
262+ content = diff_text .encode ("utf-8" ),
263+ mode = 0o644 ,
264+ )
265+ ],
266+ )
267+ return await runner .run_program (
268+ workspace ,
269+ WorkspaceRunProgramSpec (
270+ cmd = invocation .command [0 ],
271+ args = invocation .command [1 :],
272+ timeout = timeout_seconds ,
273+ ),
274+ )
275+ finally :
276+ await manager .cleanup (exec_id )
277+
278+
235279def _sanitize_output_text (text : str ) -> str :
236280 """Normalize sandbox output so reports stay portable across machines."""
237281
0 commit comments