@@ -34,10 +34,12 @@ def __init__(self, shell_type: str = "bash", encoding: str = "utf-8"):
3434 Initialize the shell communicator.
3535
3636 Args:
37- shell_type: Type of shell ("powershell", "cmd", " bash", "python" )
37+ shell_type: Type of shell (only " bash" is supported )
3838 encoding: Text encoding for communication
3939 """
4040 self .shell_type = shell_type .lower ()
41+ if self .shell_type != "bash" :
42+ raise ValueError (f"Unsupported shell type: { shell_type } . Only 'bash' is supported." )
4143 self .encoding = encoding
4244 self .process : Optional [subprocess .Popen ] = None
4345 self .output_queue = queue .Queue ()
@@ -47,13 +49,9 @@ def __init__(self, shell_type: str = "bash", encoding: str = "utf-8"):
4749 self .error_thread : Optional [threading .Thread ] = None
4850 self .output_callback : Optional [Callable ] = None
4951
50- # Define shell commands
52+ # Define shell commands - only supporting bash
5153 self .shell_commands = {
52- "powershell" : ["powershell.exe" , "-NoLogo" , "-NoExit" ],
53- "cmd" : ["cmd.exe" , "/k" ],
5454 "bash" : ["bash" ],
55- "python" : [sys .executable , "-i" ],
56- "wsl" : ["wsl.exe" ],
5755 }
5856
5957 def start_session (self ) -> bool :
@@ -134,7 +132,6 @@ def _re_escape(self, command: str) -> str:
134132 # command = command.replace("<", "<").replace(">", ">")
135133 return command
136134
137- # TODO: Exit code is not properly captured. Need to fix it.
138135 def send_command (
139136 self , command : str , wait_for_output : bool = True , timeout : float = 300
140137 ) -> CmdReturn :
@@ -155,28 +152,24 @@ def send_command(
155152
156153 try :
157154 command = self ._re_escape (command )
158- # Send the command
159- self .process .stdin .write (command + "\n " )
160- self .process .stdin .flush ()
161- logger .debug ("➡️ Sent command: %s" , command )
162-
155+
163156 if not wait_for_output :
157+ # Send the command without marker for async execution
158+ self .process .stdin .write (command + "\n " )
159+ self .process .stdin .flush ()
160+ logger .debug ("➡️ Sent async command: %s" , command )
164161 return CmdReturn (stdout = "ASYNC: Not waiting for completion" , stderr = "" , return_code = 0 )
165162
166163 # Generate a unique command completion marker
167164 marker = f"__COMMAND_COMPLETE_{ int (time .time () * 1000000 )} __"
168165
169- # Send the marker command based on shell type
170- if self .shell_type in ["bash" , "wsl" ]:
171- self .process .stdin .write (f"echo '{ marker } '; echo $? > /tmp/last_exit_code\n " )
172- elif self .shell_type == "powershell" :
173- self .process .stdin .write (f"echo '{ marker } '; echo $LASTEXITCODE\n " )
174- elif self .shell_type == "cmd" :
175- self .process .stdin .write (f"echo { marker } & echo %ERRORLEVEL%\n " )
176- elif self .shell_type == "python" :
177- self .process .stdin .write (f"print('{ marker } ')\n " )
178-
166+ # For bash only: Send command + marker in a single line to capture correct exit code
167+ combined_command = f"{ command } ; echo '{ marker } ' $?"
168+
169+ # Send the combined command
170+ self .process .stdin .write (combined_command + "\n " )
179171 self .process .stdin .flush ()
172+ logger .debug ("➡️ Sent command: %s" , command )
180173
181174 # Collect output until marker is found or timeout
182175 output_lines = []
@@ -193,23 +186,24 @@ def send_command(
193186 # Check if this is our completion marker
194187 if marker in line :
195188 marker_found = True
196- # For bash/wsl, try to get the exit code from the next line
197- if self .shell_type in ["bash" , "wsl" ]:
198- try :
199- # Try to get exit code from next output
200- stream_type , exit_code_line = self .output_queue .get (timeout = 0.5 )
201- if exit_code_line .strip ().isdigit ():
202- last_exit_code = int (exit_code_line .strip ())
203- except (queue .Empty , ValueError ):
204- pass
205- elif self .shell_type in ["powershell" , "cmd" ]:
206- try :
207- # Try to get exit code from next output
208- stream_type , exit_code_line = self .output_queue .get (timeout = 0.5 )
209- if exit_code_line .strip ().isdigit ():
210- last_exit_code = int (exit_code_line .strip ())
211- except (queue .Empty , ValueError ):
212- pass
189+ # For bash, the exit code is on the same line after the marker
190+ try :
191+ # Extract exit code from the same line as the marker
192+ # Format: "__COMMAND_COMPLETE_xxxxx__ exit_code"
193+ parts = line .split ()
194+ if len (parts ) >= 2 :
195+ exit_code_str = parts [- 1 ].strip ()
196+ # Handle bash exit codes (0-255 only)
197+ if exit_code_str .isdigit ():
198+ last_exit_code = int (exit_code_str )
199+ else :
200+ last_exit_code = 1
201+ else :
202+ last_exit_code = 1
203+ except (ValueError , AttributeError , IndexError ):
204+ # Default to 1 if parsing fails
205+ last_exit_code = 1
206+ logger .debug ("🔍 Found marker with exit code: %s" , last_exit_code )
213207 continue
214208
215209 # Add output to appropriate list
@@ -236,7 +230,6 @@ def send_command(
236230 except queue .Empty :
237231 break
238232
239- # TODO: Final return code is not correct. Need a fix
240233 final_return_code = last_exit_code if marker_found else (1 if error_lines else 0 )
241234
242235 # Handle timeout case
@@ -295,13 +288,8 @@ def close_session(self):
295288
296289 if self .process :
297290 try :
298- # Try to terminate gracefully
299- if self .shell_type == "powershell" :
300- self .send_command ("exit" , wait_for_output = False )
301- elif self .shell_type == "cmd" :
302- self .send_command ("exit" , wait_for_output = False )
303- else :
304- self .send_command ("exit" , wait_for_output = False )
291+ # Try to terminate gracefully with bash exit command
292+ self .send_command ("exit" , wait_for_output = False )
305293
306294 # Wait a bit for graceful shutdown
307295 time .sleep (1 )
0 commit comments