66 lines
2.4 KiB
Python
66 lines
2.4 KiB
Python
from __future__ import annotations
|
|
|
|
from app.core.contracts import ToolResult, UserTask
|
|
from app.tools.base import BaseTool
|
|
from app.tools.sandbox import ToolSandbox
|
|
|
|
|
|
def _detect_sudo_auth_failure(output: str) -> bool:
|
|
normalized = output.lower()
|
|
return any(
|
|
marker in normalized
|
|
for marker in (
|
|
"incorrect password",
|
|
"incorrect password attempt",
|
|
"sudo: no password was provided",
|
|
"sudo: password incorrect",
|
|
"sorry, try again",
|
|
"authentication failure",
|
|
"wrong password",
|
|
)
|
|
)
|
|
|
|
|
|
class ShellExecTool(BaseTool):
|
|
name = "shell_exec"
|
|
|
|
def __init__(self, sandbox: ToolSandbox) -> None:
|
|
self._sandbox = sandbox
|
|
|
|
def execute(self, task: UserTask, args: dict[str, object]) -> ToolResult:
|
|
command = str(args.get("command", "")).strip()
|
|
if not command:
|
|
return ToolResult(tool=self.name, ok=False, error="Missing command", metadata={"exit_code": -1})
|
|
cwd = args.get("cwd")
|
|
stdin_secret = args.get("stdin_secret")
|
|
password = args.get("password")
|
|
output_callback = args.get("__output_callback")
|
|
|
|
if password:
|
|
command = f'echo "{password}" | sudo -S {command}'
|
|
|
|
completed = self._sandbox.run_shell(
|
|
command=command,
|
|
cwd=str(cwd) if cwd else None,
|
|
stdin_data=str(stdin_secret) if stdin_secret is not None else None,
|
|
output_callback=output_callback if callable(output_callback) else None,
|
|
)
|
|
output = completed.stdout if completed.returncode == 0 else completed.stderr or completed.stdout
|
|
error_output = completed.stderr or completed.stdout
|
|
sudo_auth_failed = completed.returncode != 0 and _detect_sudo_auth_failure(
|
|
f"{completed.stdout}\n{completed.stderr}"
|
|
)
|
|
needs_sudo = completed.returncode != 0 and "permission denied" in error_output.lower() and not sudo_auth_failed
|
|
|
|
return ToolResult(
|
|
tool=self.name,
|
|
ok=completed.returncode == 0,
|
|
output=output,
|
|
error=None if completed.returncode == 0 else f"Command failed with exit code {completed.returncode}",
|
|
metadata={
|
|
"exit_code": completed.returncode,
|
|
"needs_sudo": needs_sudo,
|
|
"sudo_auth_failed": sudo_auth_failed,
|
|
},
|
|
)
|