Spaces:
Paused
Paused
frdel commited on
Commit ·
48bdeac
1
Parent(s): 93d5e89
windows tty
Browse files- python/helpers/runtime.py +52 -19
- python/helpers/shell_local.py +3 -2
- python/helpers/tty_session.py +37 -26
- python/tools/code_execution_tool.py +4 -4
- requirements.txt +1 -0
python/helpers/runtime.py
CHANGED
|
@@ -7,9 +7,10 @@ from python.helpers import dotenv, rfc, settings, files
|
|
| 7 |
import asyncio
|
| 8 |
import threading
|
| 9 |
import queue
|
|
|
|
| 10 |
|
| 11 |
-
T = TypeVar(
|
| 12 |
-
R = TypeVar(
|
| 13 |
|
| 14 |
parser = argparse.ArgumentParser()
|
| 15 |
args = {}
|
|
@@ -41,31 +42,38 @@ def initialize():
|
|
| 41 |
key = key.lstrip("-")
|
| 42 |
args[key] = value
|
| 43 |
|
|
|
|
| 44 |
def get_arg(name: str):
|
| 45 |
global args
|
| 46 |
return args.get(name, None)
|
| 47 |
|
|
|
|
| 48 |
def has_arg(name: str):
|
| 49 |
global args
|
| 50 |
return name in args
|
| 51 |
|
|
|
|
| 52 |
def is_dockerized() -> bool:
|
| 53 |
return bool(get_arg("dockerized"))
|
| 54 |
|
|
|
|
| 55 |
def is_development() -> bool:
|
| 56 |
return not is_dockerized()
|
| 57 |
|
|
|
|
| 58 |
def get_local_url():
|
| 59 |
if is_dockerized():
|
| 60 |
return "host.docker.internal"
|
| 61 |
return "127.0.0.1"
|
| 62 |
|
|
|
|
| 63 |
def get_runtime_id() -> str:
|
| 64 |
global runtime_id
|
| 65 |
if not runtime_id:
|
| 66 |
-
runtime_id = secrets.token_hex(8)
|
| 67 |
return runtime_id
|
| 68 |
|
|
|
|
| 69 |
def get_persistent_id() -> str:
|
| 70 |
id = dotenv.get_dotenv_value("A0_PERSISTENT_RUNTIME_ID")
|
| 71 |
if not id:
|
|
@@ -73,19 +81,28 @@ def get_persistent_id() -> str:
|
|
| 73 |
dotenv.save_dotenv_value("A0_PERSISTENT_RUNTIME_ID", id)
|
| 74 |
return id
|
| 75 |
|
|
|
|
| 76 |
@overload
|
| 77 |
-
async def call_development_function(
|
|
|
|
|
|
|
|
|
|
| 78 |
|
| 79 |
@overload
|
| 80 |
async def call_development_function(func: Callable[..., T], *args, **kwargs) -> T: ...
|
| 81 |
|
| 82 |
-
|
|
|
|
|
|
|
|
|
|
| 83 |
if is_development():
|
| 84 |
url = _get_rfc_url()
|
| 85 |
password = _get_rfc_password()
|
| 86 |
# Normalize path components to build a valid Python module path across OSes
|
| 87 |
-
module_path = Path(
|
| 88 |
-
|
|
|
|
|
|
|
| 89 |
result = await rfc.call_rfc(
|
| 90 |
url=url,
|
| 91 |
password=password,
|
|
@@ -99,7 +116,7 @@ async def call_development_function(func: Union[Callable[..., T], Callable[...,
|
|
| 99 |
if inspect.iscoroutinefunction(func):
|
| 100 |
return await func(*args, **kwargs)
|
| 101 |
else:
|
| 102 |
-
return func(*args, **kwargs)
|
| 103 |
|
| 104 |
|
| 105 |
async def handle_rfc(rfc_call: rfc.RFCCall):
|
|
@@ -117,45 +134,61 @@ def _get_rfc_url() -> str:
|
|
| 117 |
set = settings.get_settings()
|
| 118 |
url = set["rfc_url"]
|
| 119 |
if not "://" in url:
|
| 120 |
-
url = "http://"+url
|
| 121 |
if url.endswith("/"):
|
| 122 |
url = url[:-1]
|
| 123 |
-
url = url+":"+str(set["rfc_port_http"])
|
| 124 |
url += "/rfc"
|
| 125 |
return url
|
| 126 |
|
| 127 |
|
| 128 |
-
def call_development_function_sync(
|
|
|
|
|
|
|
| 129 |
# run async function in sync manner
|
| 130 |
result_queue = queue.Queue()
|
| 131 |
-
|
| 132 |
def run_in_thread():
|
| 133 |
result = asyncio.run(call_development_function(func, *args, **kwargs))
|
| 134 |
result_queue.put(result)
|
| 135 |
-
|
| 136 |
thread = threading.Thread(target=run_in_thread)
|
| 137 |
thread.start()
|
| 138 |
thread.join(timeout=30) # wait for thread with timeout
|
| 139 |
-
|
| 140 |
if thread.is_alive():
|
| 141 |
raise TimeoutError("Function call timed out after 30 seconds")
|
| 142 |
-
|
| 143 |
result = result_queue.get_nowait()
|
| 144 |
return cast(T, result)
|
| 145 |
|
| 146 |
|
| 147 |
def get_web_ui_port():
|
| 148 |
web_ui_port = (
|
| 149 |
-
get_arg("port")
|
| 150 |
-
or int(dotenv.get_dotenv_value("WEB_UI_PORT", 0))
|
| 151 |
-
or 5000
|
| 152 |
)
|
| 153 |
return web_ui_port
|
| 154 |
|
|
|
|
| 155 |
def get_tunnel_api_port():
|
| 156 |
tunnel_api_port = (
|
| 157 |
get_arg("tunnel_api_port")
|
| 158 |
or int(dotenv.get_dotenv_value("TUNNEL_API_PORT", 0))
|
| 159 |
or 55520
|
| 160 |
)
|
| 161 |
-
return tunnel_api_port
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 7 |
import asyncio
|
| 8 |
import threading
|
| 9 |
import queue
|
| 10 |
+
import sys
|
| 11 |
|
| 12 |
+
T = TypeVar("T")
|
| 13 |
+
R = TypeVar("R")
|
| 14 |
|
| 15 |
parser = argparse.ArgumentParser()
|
| 16 |
args = {}
|
|
|
|
| 42 |
key = key.lstrip("-")
|
| 43 |
args[key] = value
|
| 44 |
|
| 45 |
+
|
| 46 |
def get_arg(name: str):
|
| 47 |
global args
|
| 48 |
return args.get(name, None)
|
| 49 |
|
| 50 |
+
|
| 51 |
def has_arg(name: str):
|
| 52 |
global args
|
| 53 |
return name in args
|
| 54 |
|
| 55 |
+
|
| 56 |
def is_dockerized() -> bool:
|
| 57 |
return bool(get_arg("dockerized"))
|
| 58 |
|
| 59 |
+
|
| 60 |
def is_development() -> bool:
|
| 61 |
return not is_dockerized()
|
| 62 |
|
| 63 |
+
|
| 64 |
def get_local_url():
|
| 65 |
if is_dockerized():
|
| 66 |
return "host.docker.internal"
|
| 67 |
return "127.0.0.1"
|
| 68 |
|
| 69 |
+
|
| 70 |
def get_runtime_id() -> str:
|
| 71 |
global runtime_id
|
| 72 |
if not runtime_id:
|
| 73 |
+
runtime_id = secrets.token_hex(8)
|
| 74 |
return runtime_id
|
| 75 |
|
| 76 |
+
|
| 77 |
def get_persistent_id() -> str:
|
| 78 |
id = dotenv.get_dotenv_value("A0_PERSISTENT_RUNTIME_ID")
|
| 79 |
if not id:
|
|
|
|
| 81 |
dotenv.save_dotenv_value("A0_PERSISTENT_RUNTIME_ID", id)
|
| 82 |
return id
|
| 83 |
|
| 84 |
+
|
| 85 |
@overload
|
| 86 |
+
async def call_development_function(
|
| 87 |
+
func: Callable[..., Awaitable[T]], *args, **kwargs
|
| 88 |
+
) -> T: ...
|
| 89 |
+
|
| 90 |
|
| 91 |
@overload
|
| 92 |
async def call_development_function(func: Callable[..., T], *args, **kwargs) -> T: ...
|
| 93 |
|
| 94 |
+
|
| 95 |
+
async def call_development_function(
|
| 96 |
+
func: Union[Callable[..., T], Callable[..., Awaitable[T]]], *args, **kwargs
|
| 97 |
+
) -> T:
|
| 98 |
if is_development():
|
| 99 |
url = _get_rfc_url()
|
| 100 |
password = _get_rfc_password()
|
| 101 |
# Normalize path components to build a valid Python module path across OSes
|
| 102 |
+
module_path = Path(
|
| 103 |
+
files.deabsolute_path(func.__code__.co_filename)
|
| 104 |
+
).with_suffix("")
|
| 105 |
+
module = ".".join(module_path.parts) # __module__ is not reliable
|
| 106 |
result = await rfc.call_rfc(
|
| 107 |
url=url,
|
| 108 |
password=password,
|
|
|
|
| 116 |
if inspect.iscoroutinefunction(func):
|
| 117 |
return await func(*args, **kwargs)
|
| 118 |
else:
|
| 119 |
+
return func(*args, **kwargs) # type: ignore
|
| 120 |
|
| 121 |
|
| 122 |
async def handle_rfc(rfc_call: rfc.RFCCall):
|
|
|
|
| 134 |
set = settings.get_settings()
|
| 135 |
url = set["rfc_url"]
|
| 136 |
if not "://" in url:
|
| 137 |
+
url = "http://" + url
|
| 138 |
if url.endswith("/"):
|
| 139 |
url = url[:-1]
|
| 140 |
+
url = url + ":" + str(set["rfc_port_http"])
|
| 141 |
url += "/rfc"
|
| 142 |
return url
|
| 143 |
|
| 144 |
|
| 145 |
+
def call_development_function_sync(
|
| 146 |
+
func: Union[Callable[..., T], Callable[..., Awaitable[T]]], *args, **kwargs
|
| 147 |
+
) -> T:
|
| 148 |
# run async function in sync manner
|
| 149 |
result_queue = queue.Queue()
|
| 150 |
+
|
| 151 |
def run_in_thread():
|
| 152 |
result = asyncio.run(call_development_function(func, *args, **kwargs))
|
| 153 |
result_queue.put(result)
|
| 154 |
+
|
| 155 |
thread = threading.Thread(target=run_in_thread)
|
| 156 |
thread.start()
|
| 157 |
thread.join(timeout=30) # wait for thread with timeout
|
| 158 |
+
|
| 159 |
if thread.is_alive():
|
| 160 |
raise TimeoutError("Function call timed out after 30 seconds")
|
| 161 |
+
|
| 162 |
result = result_queue.get_nowait()
|
| 163 |
return cast(T, result)
|
| 164 |
|
| 165 |
|
| 166 |
def get_web_ui_port():
|
| 167 |
web_ui_port = (
|
| 168 |
+
get_arg("port") or int(dotenv.get_dotenv_value("WEB_UI_PORT", 0)) or 5000
|
|
|
|
|
|
|
| 169 |
)
|
| 170 |
return web_ui_port
|
| 171 |
|
| 172 |
+
|
| 173 |
def get_tunnel_api_port():
|
| 174 |
tunnel_api_port = (
|
| 175 |
get_arg("tunnel_api_port")
|
| 176 |
or int(dotenv.get_dotenv_value("TUNNEL_API_PORT", 0))
|
| 177 |
or 55520
|
| 178 |
)
|
| 179 |
+
return tunnel_api_port
|
| 180 |
+
|
| 181 |
+
|
| 182 |
+
def get_platform():
|
| 183 |
+
return sys.platform
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def is_windows():
|
| 187 |
+
return get_platform() == "win32"
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
def get_terminal_executable():
|
| 191 |
+
if is_windows():
|
| 192 |
+
return "powershell.exe"
|
| 193 |
+
else:
|
| 194 |
+
return "/bin/bash"
|
python/helpers/shell_local.py
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
|
|
| 1 |
import select
|
| 2 |
import subprocess
|
| 3 |
import time
|
| 4 |
import sys
|
| 5 |
from typing import Optional, Tuple
|
| 6 |
-
from python.helpers import tty_session
|
| 7 |
from python.helpers.shell_ssh import clean_string
|
| 8 |
|
| 9 |
class LocalInteractiveSession:
|
|
@@ -13,7 +14,7 @@ class LocalInteractiveSession:
|
|
| 13 |
self.cwd = cwd
|
| 14 |
|
| 15 |
async def connect(self):
|
| 16 |
-
self.session = tty_session.TTYSession(
|
| 17 |
await self.session.start()
|
| 18 |
await self.session.read_full_until_idle(idle_timeout=1, total_timeout=1)
|
| 19 |
|
|
|
|
| 1 |
+
import platform
|
| 2 |
import select
|
| 3 |
import subprocess
|
| 4 |
import time
|
| 5 |
import sys
|
| 6 |
from typing import Optional, Tuple
|
| 7 |
+
from python.helpers import tty_session, runtime
|
| 8 |
from python.helpers.shell_ssh import clean_string
|
| 9 |
|
| 10 |
class LocalInteractiveSession:
|
|
|
|
| 14 |
self.cwd = cwd
|
| 15 |
|
| 16 |
async def connect(self):
|
| 17 |
+
self.session = tty_session.TTYSession(runtime.get_terminal_executable(), cwd=self.cwd)
|
| 18 |
await self.session.start()
|
| 19 |
await self.session.read_full_until_idle(idle_timeout=1, total_timeout=1)
|
| 20 |
|
python/helpers/tty_session.py
CHANGED
|
@@ -204,55 +204,66 @@ async def _spawn_posix_pty(cmd, cwd, env, echo):
|
|
| 204 |
|
| 205 |
|
| 206 |
async def _spawn_winpty(cmd, cwd, env, echo):
|
| 207 |
-
#
|
| 208 |
-
if
|
| 209 |
-
|
|
|
|
| 210 |
|
| 211 |
cols, rows = 80, 25
|
| 212 |
-
|
| 213 |
-
# winpty expects env as None or list of "KEY=VALUE" strings, not a dict
|
| 214 |
-
winpty_env = None if env is None else [f"{k}={v}" for k, v in env.items()]
|
| 215 |
-
child = pty.spawn(cmd, cwd=cwd or os.getcwd(), env=winpty_env)
|
| 216 |
-
|
| 217 |
-
master_r_fd = msvcrt.open_osfhandle(child.conout_pipe, os.O_RDONLY) # type: ignore
|
| 218 |
-
master_w_fd = msvcrt.open_osfhandle(child.conin_pipe, 0) # type: ignore
|
| 219 |
|
| 220 |
loop = asyncio.get_running_loop()
|
| 221 |
reader = asyncio.StreamReader()
|
| 222 |
|
| 223 |
-
def _on_data():
|
| 224 |
-
|
| 225 |
-
|
| 226 |
-
|
| 227 |
-
|
| 228 |
-
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
| 232 |
-
|
|
|
|
|
|
|
| 233 |
|
| 234 |
-
|
|
|
|
| 235 |
|
| 236 |
class _Stdin:
|
| 237 |
def write(self, d):
|
| 238 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
|
| 240 |
async def drain(self):
|
| 241 |
-
await asyncio.sleep(0)
|
| 242 |
|
| 243 |
-
class _Proc
|
| 244 |
def __init__(self):
|
| 245 |
self.stdin = _Stdin() # type: ignore
|
| 246 |
self.stdout = reader
|
| 247 |
self.pid = child.pid
|
|
|
|
| 248 |
|
| 249 |
async def wait(self):
|
| 250 |
while child.isalive():
|
| 251 |
await asyncio.sleep(0.2)
|
|
|
|
| 252 |
return 0
|
| 253 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 254 |
def kill(self):
|
| 255 |
-
child.
|
|
|
|
| 256 |
|
| 257 |
return _Proc()
|
| 258 |
|
|
@@ -261,7 +272,7 @@ async def _spawn_winpty(cmd, cwd, env, echo):
|
|
| 261 |
if __name__ == "__main__":
|
| 262 |
|
| 263 |
async def interactive_shell():
|
| 264 |
-
shell_cmd, prompt_hint = ("
|
| 265 |
|
| 266 |
# echo=False → suppress the shell’s own echo of commands
|
| 267 |
term = TTYSession(shell_cmd)
|
|
|
|
| 204 |
|
| 205 |
|
| 206 |
async def _spawn_winpty(cmd, cwd, env, echo):
|
| 207 |
+
# Clean PowerShell startup: no logo, no profile, bypass execution policy for deterministic behavior
|
| 208 |
+
if cmd.strip().lower().startswith("powershell"):
|
| 209 |
+
if "-nolog" not in cmd.lower():
|
| 210 |
+
cmd = cmd.replace("powershell.exe", "powershell.exe -NoLogo -NoProfile -ExecutionPolicy Bypass", 1)
|
| 211 |
|
| 212 |
cols, rows = 80, 25
|
| 213 |
+
child = winpty.PtyProcess.spawn(cmd, dimensions=(rows, cols), cwd=cwd or os.getcwd(), env=env) # type: ignore
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 214 |
|
| 215 |
loop = asyncio.get_running_loop()
|
| 216 |
reader = asyncio.StreamReader()
|
| 217 |
|
| 218 |
+
async def _on_data():
|
| 219 |
+
while child.isalive():
|
| 220 |
+
try:
|
| 221 |
+
# Run blocking read in executor to not block event loop
|
| 222 |
+
data = await loop.run_in_executor(None, child.read, 1 << 16)
|
| 223 |
+
if data:
|
| 224 |
+
reader.feed_data(data.encode('utf-8') if isinstance(data, str) else data)
|
| 225 |
+
except EOFError:
|
| 226 |
+
break
|
| 227 |
+
except Exception:
|
| 228 |
+
await asyncio.sleep(0.01)
|
| 229 |
+
reader.feed_eof()
|
| 230 |
|
| 231 |
+
# Start pumping output in background
|
| 232 |
+
asyncio.create_task(_on_data())
|
| 233 |
|
| 234 |
class _Stdin:
|
| 235 |
def write(self, d):
|
| 236 |
+
# Use winpty's write method, not os.write
|
| 237 |
+
if isinstance(d, bytes):
|
| 238 |
+
d = d.decode('utf-8', errors='replace')
|
| 239 |
+
# Windows needs \r\n for proper line endings
|
| 240 |
+
if _IS_WIN:
|
| 241 |
+
d = d.replace('\n', '\r\n')
|
| 242 |
+
child.write(d)
|
| 243 |
|
| 244 |
async def drain(self):
|
| 245 |
+
await asyncio.sleep(0.01) # Give write time to complete
|
| 246 |
|
| 247 |
+
class _Proc:
|
| 248 |
def __init__(self):
|
| 249 |
self.stdin = _Stdin() # type: ignore
|
| 250 |
self.stdout = reader
|
| 251 |
self.pid = child.pid
|
| 252 |
+
self.returncode = None
|
| 253 |
|
| 254 |
async def wait(self):
|
| 255 |
while child.isalive():
|
| 256 |
await asyncio.sleep(0.2)
|
| 257 |
+
self.returncode = 0
|
| 258 |
return 0
|
| 259 |
|
| 260 |
+
def terminate(self):
|
| 261 |
+
if child.isalive():
|
| 262 |
+
child.terminate()
|
| 263 |
+
|
| 264 |
def kill(self):
|
| 265 |
+
if child.isalive():
|
| 266 |
+
child.kill()
|
| 267 |
|
| 268 |
return _Proc()
|
| 269 |
|
|
|
|
| 272 |
if __name__ == "__main__":
|
| 273 |
|
| 274 |
async def interactive_shell():
|
| 275 |
+
shell_cmd, prompt_hint = ("powershell.exe", ">") if _IS_WIN else ("/bin/bash", "$")
|
| 276 |
|
| 277 |
# echo=False → suppress the shell’s own echo of commands
|
| 278 |
term = TTYSession(shell_cmd)
|
python/tools/code_execution_tool.py
CHANGED
|
@@ -3,7 +3,7 @@ from dataclasses import dataclass
|
|
| 3 |
import shlex
|
| 4 |
import time
|
| 5 |
from python.helpers.tool import Tool, Response
|
| 6 |
-
from python.helpers import files, rfc_exchange, projects
|
| 7 |
from python.helpers.print_style import PrintStyle
|
| 8 |
from python.helpers.shell_local import LocalInteractiveSession
|
| 9 |
from python.helpers.shell_ssh import SSHInteractiveSession
|
|
@@ -12,7 +12,6 @@ from python.helpers.strings import truncate_text as truncate_text_string
|
|
| 12 |
from python.helpers.messages import truncate_text as truncate_text_agent
|
| 13 |
import re
|
| 14 |
|
| 15 |
-
|
| 16 |
# Timeouts for python, nodejs, and terminal runtimes.
|
| 17 |
CODE_EXEC_TIMEOUTS: dict[str, int] = {
|
| 18 |
"first_output_timeout": 30,
|
|
@@ -48,6 +47,7 @@ class CodeExecution(Tool):
|
|
| 48 |
re.compile(r"\\(venv\\).+[$#] ?$"), # (venv) ...$ or (venv) ...#
|
| 49 |
re.compile(r"root@[^:]+:[^#]+# ?$"), # root@container:~#
|
| 50 |
re.compile(r"[a-zA-Z0-9_.-]+@[^:]+:[^$#]+[$#] ?$"), # user@host:~$
|
|
|
|
| 51 |
]
|
| 52 |
# potential dialog detection
|
| 53 |
dialog_patterns = [
|
|
@@ -173,7 +173,7 @@ class CodeExecution(Tool):
|
|
| 173 |
async def execute_terminal_command(
|
| 174 |
self, session: int, command: str, reset: bool = False
|
| 175 |
):
|
| 176 |
-
prefix = "bash> " + self.format_command_for_output(command) + "\n\n"
|
| 177 |
return await self.terminal_session(session, command, reset, prefix)
|
| 178 |
|
| 179 |
async def terminal_session(
|
|
@@ -467,7 +467,7 @@ class CodeExecution(Tool):
|
|
| 467 |
# remove any single byte \xXX escapes
|
| 468 |
output = re.sub(r"(?<!\\)\\x[0-9A-Fa-f]{2}", "", output)
|
| 469 |
# Strip every line of output before truncation
|
| 470 |
-
output = "\n".join(line.strip() for line in output.splitlines())
|
| 471 |
output = truncate_text_agent(agent=self.agent, output=output, threshold=1000000) # ~1MB, larger outputs should be dumped to file, not read from terminal
|
| 472 |
return output
|
| 473 |
|
|
|
|
| 3 |
import shlex
|
| 4 |
import time
|
| 5 |
from python.helpers.tool import Tool, Response
|
| 6 |
+
from python.helpers import files, rfc_exchange, projects, runtime
|
| 7 |
from python.helpers.print_style import PrintStyle
|
| 8 |
from python.helpers.shell_local import LocalInteractiveSession
|
| 9 |
from python.helpers.shell_ssh import SSHInteractiveSession
|
|
|
|
| 12 |
from python.helpers.messages import truncate_text as truncate_text_agent
|
| 13 |
import re
|
| 14 |
|
|
|
|
| 15 |
# Timeouts for python, nodejs, and terminal runtimes.
|
| 16 |
CODE_EXEC_TIMEOUTS: dict[str, int] = {
|
| 17 |
"first_output_timeout": 30,
|
|
|
|
| 47 |
re.compile(r"\\(venv\\).+[$#] ?$"), # (venv) ...$ or (venv) ...#
|
| 48 |
re.compile(r"root@[^:]+:[^#]+# ?$"), # root@container:~#
|
| 49 |
re.compile(r"[a-zA-Z0-9_.-]+@[^:]+:[^$#]+[$#] ?$"), # user@host:~$
|
| 50 |
+
re.compile(r"\(?.*\)?\s*PS\s+[^>]+> ?$"), # PowerShell prompt like (base) PS C:\...>
|
| 51 |
]
|
| 52 |
# potential dialog detection
|
| 53 |
dialog_patterns = [
|
|
|
|
| 173 |
async def execute_terminal_command(
|
| 174 |
self, session: int, command: str, reset: bool = False
|
| 175 |
):
|
| 176 |
+
prefix = ("bash>" if not runtime.is_windows() or self.agent.config.code_exec_ssh_enabled else "PS>") + self.format_command_for_output(command) + "\n\n"
|
| 177 |
return await self.terminal_session(session, command, reset, prefix)
|
| 178 |
|
| 179 |
async def terminal_session(
|
|
|
|
| 467 |
# remove any single byte \xXX escapes
|
| 468 |
output = re.sub(r"(?<!\\)\\x[0-9A-Fa-f]{2}", "", output)
|
| 469 |
# Strip every line of output before truncation
|
| 470 |
+
# output = "\n".join(line.strip() for line in output.splitlines())
|
| 471 |
output = truncate_text_agent(agent=self.agent, output=output, threshold=1000000) # ~1MB, larger outputs should be dumped to file, not read from terminal
|
| 472 |
return output
|
| 473 |
|
requirements.txt
CHANGED
|
@@ -48,3 +48,4 @@ exchangelib>=5.4.3
|
|
| 48 |
pytest>=8.4.2
|
| 49 |
pytest-asyncio>=1.2.0
|
| 50 |
pytest-mock>=3.15.1
|
|
|
|
|
|
| 48 |
pytest>=8.4.2
|
| 49 |
pytest-asyncio>=1.2.0
|
| 50 |
pytest-mock>=3.15.1
|
| 51 |
+
pywinpty==3.0.2; sys_platform == "win32"
|