11import asyncio
22import os
33import re
4+ import sys
45from asyncio import Queue
56from asyncio .subprocess import PIPE , STDOUT
67from typing import Optional
@@ -22,8 +23,20 @@ class Terminal:
2223 """
2324
2425 def __init__ (self ):
25- self .shell_command = ["bash" ] # FIXME: should consider windows support later
26- self .command_terminator = "\n "
26+ #self.shell_command = ["bash"] # FIXME: should consider windows support later
27+ #self.command_terminator = "\n"
28+ # Add Windows Terminal support to terminal.py #1897
29+ if sys .platform .startswith ("win" ):
30+ self .shell_command = ["cmd.exe" ] # Windows
31+ self .executable = None
32+ self .command_terminator = "\r \n "
33+ self .pwd_command = "cd"
34+ else :
35+ self .shell_command = ["bash" ] # Linux / macOS
36+ self .executable = "bash"
37+ self .command_terminator = "\n "
38+ self .pwd_command = "pwd"
39+
2740 self .stdout_queue = Queue (maxsize = 1000 )
2841 self .observer = TerminalReporter ()
2942 self .process : Optional [asyncio .subprocess .Process ] = None
@@ -33,6 +46,9 @@ def __init__(self):
3346 # serve cmd have a space behind it,
3447 "serve " : "Use Deployer.deploy_to_public instead." ,
3548 }
49+ # yswang add
50+ self .chat_id = ""
51+ self .role = None ;
3652
3753 async def _start_process (self ):
3854 # Start a persistent shell process
@@ -41,17 +57,19 @@ async def _start_process(self):
4157 stdin = PIPE ,
4258 stdout = PIPE ,
4359 stderr = STDOUT ,
44- executable = "bash" ,
60+ executable = self . executable ,
4561 env = os .environ .copy (),
46- cwd = DEFAULT_WORKSPACE_ROOT .absolute (),
62+ #cwd=DEFAULT_WORKSPACE_ROOT.absolute(),
63+ cwd = str (DEFAULT_WORKSPACE_ROOT ) if sys .platform .startswith ("win" ) else DEFAULT_WORKSPACE_ROOT , # Windows
4764 )
4865 await self ._check_state ()
4966
5067 async def _check_state (self ):
5168 """
5269 Check the state of the terminal, e.g. the current directory of the terminal process. Useful for agent to understand.
5370 """
54- output = await self .run_command ("pwd" )
71+ #output = await self.run_command("pwd")
72+ output = await self .run_command (self .pwd_command )
5573 logger .info ("The terminal is at:" , output )
5674
5775 async def run_command (self , cmd : str , daemon = False ) -> str :
@@ -74,6 +92,8 @@ async def run_command(self, cmd: str, daemon=False) -> str:
7492 output = ""
7593 # Remove forbidden commands
7694 commands = re .split (r"\s*&&\s*" , cmd )
95+
96+ """
7797 for cmd_name, reason in self.forbidden_commands.items():
7898 # "true" is a pass command in linux terminal.
7999 for index, command in enumerate(commands):
@@ -87,7 +107,23 @@ async def run_command(self, cmd: str, daemon=False) -> str:
87107 self.process.stdin.write(
88108 f'echo "{END_MARKER_VALUE}"{self.command_terminator}'.encode() # write EOF
89109 ) # Unique marker to signal command end
110+ """
111+ skip_cmd = "echo Skipped" if sys .platform .startswith ("win" ) else "true"
112+ for cmd_name , reason in self .forbidden_commands .items ():
113+ # "true" is a pass command in linux terminal.
114+ for index , command in enumerate (commands ):
115+ if cmd_name in command :
116+ output += f"Failed to execute { command } . { reason } \n "
117+ commands [index ] = skip_cmd
118+ cmd = " && " .join (commands )
119+ # Send the command
120+ self .process .stdin .write ((cmd + self .command_terminator ).encode ())
121+
122+ marker_cmd = f"echo { END_MARKER_VALUE } "
123+ self .process .stdin .write ((marker_cmd + self .command_terminator ).encode ()) # Unique marker to signal command end
124+
90125 await self .process .stdin .drain ()
126+
91127 if daemon :
92128 asyncio .create_task (self ._read_and_process_output (cmd ))
93129 else :
@@ -116,7 +152,10 @@ async def execute_in_conda_env(self, cmd: str, env, daemon=False) -> str:
116152 This function wraps `run_command`, prepending the necessary Conda activation commands
117153 to ensure the specified environment is active for the command's execution.
118154 """
119- cmd = f"conda run -n { env } { cmd } "
155+ #cmd = f"conda run -n {env} {cmd}"
156+ # windows & linux conda run
157+ cmd = f"conda activate { env } && { cmd } " if sys .platform .startswith ("win" ) else f"conda run -n { env } { cmd } "
158+
120159 return await self .run_command (cmd , daemon = daemon )
121160
122161 async def get_stdout_output (self ) -> str :
@@ -147,10 +186,10 @@ async def _read_and_process_output(self, cmd, daemon=False) -> str:
147186 continue
148187 * lines , tmp = output .splitlines (True )
149188 for line in lines :
150- line = line .decode ()
189+ line = line .decode (errors = "ignore" )
151190 ix = line .rfind (END_MARKER_VALUE )
152191 if ix >= 0 :
153- line = line [0 :ix ]
192+ line = line [:ix ]
154193 if line :
155194 await observer .async_report (line , "output" )
156195 # report stdout in real-time
@@ -164,8 +203,21 @@ async def _read_and_process_output(self, cmd, daemon=False) -> str:
164203
165204 async def close (self ):
166205 """Close the persistent shell process."""
167- self .process .stdin .close ()
168- await self .process .wait ()
206+ if self .process :
207+ self .process .stdin .close ()
208+ await self .process .wait ()
209+
210+ # yswang add
211+ def set_chat_id (self , chat_id : str ):
212+ self .chat_id = chat_id
213+ self .observer .set_chat_id (chat_id )
214+
215+ def get_chat_id (self ) -> str :
216+ return self .chat_id
217+
218+ def set_role (self , role ):
219+ self .observer .set_role (role )
220+
169221
170222
171223@register_tool (include_functions = ["run" ])
0 commit comments