mirror of
https://github.com/zhayujie/chatgpt-on-wechat.git
synced 2026-07-19 12:47:25 +08:00
fix(bash): bypass cmd.exe length limit for long python -c on Windows
This commit is contained in:
@@ -24,6 +24,8 @@ class Bash(BaseTool):
|
|||||||
_IS_WIN = sys.platform == "win32"
|
_IS_WIN = sys.platform == "win32"
|
||||||
_PROGRESS_MAX_BYTES = 4 * 1024
|
_PROGRESS_MAX_BYTES = 4 * 1024
|
||||||
_PROGRESS_INTERVAL = 0.5
|
_PROGRESS_INTERVAL = 0.5
|
||||||
|
# cmd.exe command line limit is ~8191 chars; rewrite python -c above this.
|
||||||
|
_WIN_CMD_SAFE_LEN = 7000
|
||||||
|
|
||||||
name: str = "bash"
|
name: str = "bash"
|
||||||
description: str = f"""Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last {DEFAULT_MAX_LINES} lines or {DEFAULT_MAX_BYTES // 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file.
|
description: str = f"""Execute a bash command in the current working directory. Returns stdout and stderr. Output is truncated to last {DEFAULT_MAX_LINES} lines or {DEFAULT_MAX_BYTES // 1024}KB (whichever is hit first). If truncated, full output is saved to a temp file.
|
||||||
@@ -111,19 +113,35 @@ SAFETY:
|
|||||||
else:
|
else:
|
||||||
logger.debug(f"[Bash] Process User: {os.environ.get('USERNAME', os.environ.get('USER', 'unknown'))}")
|
logger.debug(f"[Bash] Process User: {os.environ.get('USERNAME', os.environ.get('USER', 'unknown'))}")
|
||||||
|
|
||||||
|
# Temp script written for long `python -c` commands (Windows only),
|
||||||
|
# cleaned up after execution.
|
||||||
|
temp_script_path = None
|
||||||
|
|
||||||
# On Windows, convert $VAR references to %VAR% for cmd.exe
|
# On Windows, convert $VAR references to %VAR% for cmd.exe
|
||||||
if self._IS_WIN:
|
if self._IS_WIN:
|
||||||
env["PYTHONIOENCODING"] = "utf-8"
|
env["PYTHONIOENCODING"] = "utf-8"
|
||||||
command = self._convert_env_vars_for_windows(command, dotenv_vars)
|
command = self._convert_env_vars_for_windows(command, dotenv_vars)
|
||||||
|
# cmd.exe has an ~8191 char command line limit. Long
|
||||||
|
# `python -c "..."` commands silently fail, so spill the inline
|
||||||
|
# code into a temp .py file and run that instead.
|
||||||
|
if len(command) > self._WIN_CMD_SAFE_LEN:
|
||||||
|
command, temp_script_path = self._rewrite_long_python_c(command)
|
||||||
if command and not command.strip().lower().startswith("chcp"):
|
if command and not command.strip().lower().startswith("chcp"):
|
||||||
command = f"chcp 65001 >nul 2>&1 && {command}"
|
command = f"chcp 65001 >nul 2>&1 && {command}"
|
||||||
|
|
||||||
result = self._run_streaming(
|
try:
|
||||||
command,
|
result = self._run_streaming(
|
||||||
timeout,
|
command,
|
||||||
env,
|
timeout,
|
||||||
dotenv_vars,
|
env,
|
||||||
)
|
dotenv_vars,
|
||||||
|
)
|
||||||
|
finally:
|
||||||
|
if temp_script_path:
|
||||||
|
try:
|
||||||
|
os.remove(temp_script_path)
|
||||||
|
except OSError:
|
||||||
|
pass
|
||||||
|
|
||||||
logger.debug(f"[Bash] Exit code: {result.returncode}")
|
logger.debug(f"[Bash] Exit code: {result.returncode}")
|
||||||
logger.debug(f"[Bash] Stdout length: {len(result.stdout)}")
|
logger.debug(f"[Bash] Stdout length: {len(result.stdout)}")
|
||||||
@@ -391,3 +409,43 @@ SAFETY:
|
|||||||
return m.group(0)
|
return m.group(0)
|
||||||
|
|
||||||
return re.sub(r'\$\{(\w+)\}|\$(\w+)', replace_match, command)
|
return re.sub(r'\$\{(\w+)\}|\$(\w+)', replace_match, command)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _rewrite_long_python_c(command: str):
|
||||||
|
"""
|
||||||
|
Rewrite `python -c "<code>"` into `python <tempfile>` to bypass the
|
||||||
|
cmd.exe command line length limit on Windows.
|
||||||
|
|
||||||
|
Returns (new_command, temp_file_path). On any parse failure the original
|
||||||
|
command and None are returned, so behavior is unchanged when unmatched.
|
||||||
|
"""
|
||||||
|
# Match: <python|python3|py> [flags] -c "<code>" (single or double quoted)
|
||||||
|
m = re.search(
|
||||||
|
r'^(?P<prefix>.*?\b(?:python3?|py)\b[^\n]*?\s-c\s+)'
|
||||||
|
r'(?P<quote>["\'])(?P<code>.*)(?P=quote)\s*(?P<suffix>.*)$',
|
||||||
|
command,
|
||||||
|
re.DOTALL,
|
||||||
|
)
|
||||||
|
if not m:
|
||||||
|
return command, None
|
||||||
|
|
||||||
|
quote = m.group("quote")
|
||||||
|
code = m.group("code")
|
||||||
|
# Reverse common shell-level escaping of the quote char inside the code.
|
||||||
|
code = code.replace("\\" + quote, quote)
|
||||||
|
|
||||||
|
try:
|
||||||
|
fd, path = tempfile.mkstemp(suffix=".py", prefix="bash-pyc-")
|
||||||
|
with os.fdopen(fd, "w", encoding="utf-8") as f:
|
||||||
|
f.write(code)
|
||||||
|
except OSError:
|
||||||
|
return command, None
|
||||||
|
|
||||||
|
prefix = m.group("prefix")
|
||||||
|
# Drop the trailing "-c " from the prefix, keep the interpreter + flags.
|
||||||
|
interp = re.sub(r'\s-c\s+$', ' ', prefix).rstrip()
|
||||||
|
suffix = m.group("suffix").strip()
|
||||||
|
new_command = f'{interp} "{path}"'
|
||||||
|
if suffix:
|
||||||
|
new_command += f' {suffix}'
|
||||||
|
return new_command, path
|
||||||
|
|||||||
Reference in New Issue
Block a user