| |
| import logging |
|
|
| from pydantic import BaseModel, Field |
|
|
| |
| from .file_operation_tools import ToolDependencies |
|
|
| logger = logging.getLogger(__name__) |
|
|
| |
| |
| SAFE_COMMAND_WHITELIST = [ |
| "make test-be", |
| "make test", |
| "make lint-be", |
| "make lint", |
| |
| |
| ] |
|
|
|
|
| class ProposeShellCommandTool(BaseModel): |
| """ |
| Proposes running a shell command from a pre-approved whitelist. |
| This is a high-risk operation that requires human approval. Only commands |
| from a known safe list (like 'make test-be') should ever be approved. |
| """ |
|
|
| command: str = Field( |
| ..., description=f"The shell command to propose. Must be one of: {', '.join(SAFE_COMMAND_WHITELIST)}" |
| ) |
|
|
| async def execute(self) -> str: |
| """Submits the shell command execution proposal.""" |
| logger.info(f"Proposing to execute a shell command: {self.command}") |
|
|
| |
| |
| if self.command not in SAFE_COMMAND_WHITELIST: |
| logger.warning(f"Blocked attempt to propose a non-whitelisted command: {self.command}") |
| return ( |
| f"Error: The command '{self.command}' is not in the list of approved safe commands. Proposal rejected." |
| ) |
|
|
| try: |
| service = ToolDependencies.get_propose_change_service() |
| payload = {"command": self.command, "description": f"Propose to run the shell command: `{self.command}`."} |
| proposal = await service.create_proposal(change_type="shell", payload=payload) |
|
|
| return ( |
| f"Successfully proposed to run the command: `{self.command}`. " |
| f"Proposal ID: {proposal['id']}. Please await human approval." |
| ) |
| except Exception as e: |
| logger.error(f"Failed to propose shell command '{self.command}': {e}", exc_info=True) |
| return f"Error: Could not propose shell command execution. Reason: {e}" |
|
|
|
|
| class ExecuteShellCommandTool(BaseModel): |
| """ |
| Physically executes a shell command on the system. |
| WARNING: High-risk operation. Use for verification, linting, and system tasks. |
| Used for autonomous self-healing (Phase 4.6.43). |
| """ |
|
|
| tool_name: str = "execute_shell_command" |
| command: str = Field(..., description="The exact shell command to execute.") |
|
|
| async def execute(self) -> str: |
| """Executes the shell command directly.""" |
| import asyncio |
|
|
| logger.info(f"π MCP Tool: Physically executing command: {self.command}") |
| try: |
| process = await asyncio.create_subprocess_shell( |
| self.command, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE |
| ) |
| stdout, stderr = await process.communicate() |
| output = stdout.decode().strip() |
| error = stderr.decode().strip() |
|
|
| if process.returncode == 0: |
| return f"β
Command succeeded:\n{output}" |
| else: |
| return f"β Command failed (Code {process.returncode}):\n{error}\nOutput:\n{output}" |
| except Exception as e: |
| logger.error(f"Command execution failed: {e}") |
| return f"β Execution error: {str(e)}" |
|
|
|
|
| |
| developer_execution_tools = [ |
| ProposeShellCommandTool, |
| ExecuteShellCommandTool, |
| ] |
|
|