Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 42 additions & 2 deletions src/aish/tools/code_exec.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import json
import logging
import os
import shlex
import sys
import threading
from pathlib import Path
Expand Down Expand Up @@ -28,9 +29,48 @@
logger = logging.getLogger("aish.tools.code_exec")


_INTERACTIVE_COMMANDS = {"sudo", "su"}
_COMMAND_SEPARATORS = {";", "&", "&&", "||", "|", "(", ")"}


def _is_shell_assignment(token: str) -> bool:
if "=" not in token:
return False

name, _value = token.split("=", 1)
return bool(name) and (name[0].isalpha() or name[0] == "_") and all(
char.isalnum() or char == "_" for char in name
)


def _needs_interactive_bash(command: str) -> bool:
lower = command.lower()
return "sudo" in lower or " su " in lower or lower.startswith("su ")
lexer = shlex.shlex(
command.replace("\n", ";"),
posix=True,
punctuation_chars=";&|()",
)
lexer.whitespace_split = True
lexer.commenters = "#"

try:
tokens = list(lexer)
except ValueError:
return False

command_position = True
for token in tokens:
if token in _COMMAND_SEPARATORS:
command_position = True
continue
if command_position and token == "!":
continue
if command_position and _is_shell_assignment(token):
continue
if command_position and token in _INTERACTIVE_COMMANDS:
return True
command_position = False

return False


def _collapse_output_lines(text: str, max_lines: int = DISPLAY_MAX_LINES) -> str:
Expand Down
7 changes: 6 additions & 1 deletion tests/tools/test_bash_output_offload.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,17 @@ def _extract_tag(xml_text: str, tag_name: str) -> str:
return match.group(1).strip("\n")


def test_needs_interactive_bash_matches_rust_heuristic():
def test_needs_interactive_bash_matches_shell_commands_only():
assert _needs_interactive_bash("sudo apt update") is True
assert _needs_interactive_bash("echo 3 | sudo tee /tmp/x") is True
assert _needs_interactive_bash("su -") is True
assert _needs_interactive_bash("cmd && su -") is True
assert _needs_interactive_bash("FOO=bar sudo apt update") is True
assert _needs_interactive_bash("ls -la") is False
assert _needs_interactive_bash("printf 'sudo prompt'") is False
assert _needs_interactive_bash("echo ok # sudo") is False
assert _needs_interactive_bash("printf '[sudo] password: '; read pw # sudo") is False
assert _needs_interactive_bash("echo nosudo") is False


@pytest.mark.asyncio
Expand Down
Loading