From cbad9ccd5952332b2b0301e17e8c12e8d4d2558b Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Sun, 15 Sep 2024 12:15:14 +1000 Subject: [PATCH 01/17] dealing with interactive commands --- src/goose/toolkit/developer.py | 134 +++++++++++++++++++++++++++++++-- 1 file changed, 126 insertions(+), 8 deletions(-) diff --git a/src/goose/toolkit/developer.py b/src/goose/toolkit/developer.py index 450ce244..2709b416 100644 --- a/src/goose/toolkit/developer.py +++ b/src/goose/toolkit/developer.py @@ -11,6 +11,11 @@ from rich.prompt import Confirm from rich.table import Table from rich.text import Text +import subprocess +import threading +import queue +import re + from goose.toolkit.base import Toolkit, tool from goose.toolkit.utils import get_language, render_template @@ -146,11 +151,9 @@ def shell(self, command: str) -> str: command (str): The shell command to run. It can support multiline statements if you need to run more than one at a time """ + self.notifier.status("planning to run shell command") # Log the command being executed in a visually structured format (Markdown). - # The `.log` method is used here to log the command execution in the application's UX - # this method is dynamically attached to functions in the Goose framework to handle user-visible - # logging and integrates with the overall UI logging system self.notifier.log(Panel.fit(Markdown(f"```bash\n{command}\n```"), title="shell")) if is_dangerous_command(command): @@ -163,12 +166,127 @@ def shell(self, command: str) -> str: ) self.notifier.start() self.notifier.status("running shell command") - result: CompletedProcess = run(command, shell=True, text=True, capture_output=True, check=False) - if result.returncode == 0: - output = "Command succeeded" + + # Define patterns that might indicate the process is waiting for input + PROMPT_PATTERNS = [ + r'Do you want to', # Common prompt phrase + r'Enter password', # Password prompt + r'Are you sure', # Confirmation prompt + r'\(y/N\)', # Yes/No prompt + r'Press any key to continue', # Awaiting keypress + r'Waiting for input', # General waiting message + r'Config already exist.*overwrite\?', # Specific to 'jira init' example + r'\?\s' # Prompts starting with '? ' + ] + + # Compile the patterns for faster matching + compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in PROMPT_PATTERNS] + + # Start the process + proc = subprocess.Popen( + command, + shell=True, + stdin=subprocess.DEVNULL, # Close stdin to prevent the process from waiting for input + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + text=True + ) + + # Queues to store the output + stdout_queue = queue.Queue() + stderr_queue = queue.Queue() + + # Function to read stdout and stderr without blocking + def reader_thread(pipe, output_queue): + try: + for line in iter(pipe.readline, ''): + output_queue.put(line) + # Check for prompt patterns + for pattern in compiled_patterns: + if pattern.search(line): + output_queue.put('PROMPT_DETECTED') + finally: + pipe.close() + + # Start threads to read stdout and stderr + stdout_thread = threading.Thread(target=reader_thread, args=(proc.stdout, stdout_queue)) + stderr_thread = threading.Thread(target=reader_thread, args=(proc.stderr, stderr_queue)) + stdout_thread.start() + stderr_thread.start() + + # Collect output + output = '' + error = '' + is_waiting_for_input = False + + # Continuously read output + while True: + # Check if process has terminated + if proc.poll() is not None: + break + + # Process output from stdout + try: + line = stdout_queue.get_nowait() + if line == 'PROMPT_DETECTED': + is_waiting_for_input = True + break + else: + output += line + except queue.Empty: + pass + + # Process output from stderr + try: + line = stderr_queue.get_nowait() + if line == 'PROMPT_DETECTED': + is_waiting_for_input = True + break + else: + error += line + except queue.Empty: + pass + + # Brief sleep to prevent high CPU usage + threading.Event().wait(0.1) + + if is_waiting_for_input: + # Allow threads to finish reading + stdout_thread.join() + stderr_thread.join() + return ( + "Command requires interactive input.\n" + f"Output:\n{output}\nError:\n{error}" + ) + + # Wait for process to complete + proc.wait() + + # Ensure all output is read + stdout_thread.join() + stderr_thread.join() + + # Retrieve any remaining output from queues + try: + while True: + output += stdout_queue.get_nowait() + except queue.Empty: + pass + + try: + while True: + error += stderr_queue.get_nowait() + except queue.Empty: + pass + + # Determine the result based on the return code + if proc.returncode == 0: + result = "Command succeeded" else: - output = f"Command failed with returncode {result.returncode}" - return "\n".join([output, result.stdout, result.stderr]) + result = f"Command failed with returncode {proc.returncode}" + + # Return the combined result and outputs + return "\n".join([result, output, error]) @tool def write_file(self, path: str, content: str) -> str: From a348c3c8ae29fa9f2a9160af9dd4ed9f948d2190 Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Sun, 15 Sep 2024 12:23:01 +1000 Subject: [PATCH 02/17] prompt clarification --- src/goose/toolkit/developer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/goose/toolkit/developer.py b/src/goose/toolkit/developer.py index 2709b416..418d0984 100644 --- a/src/goose/toolkit/developer.py +++ b/src/goose/toolkit/developer.py @@ -255,7 +255,7 @@ def reader_thread(pipe, output_queue): stdout_thread.join() stderr_thread.join() return ( - "Command requires interactive input.\n" + "Command requires interactive input. If unclear, prompt user for required input or ask to run outside of goose.\n" f"Output:\n{output}\nError:\n{error}" ) From 47f126ea307c34338e7d855c6629e61ec6d692b2 Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Sun, 15 Sep 2024 12:35:11 +1000 Subject: [PATCH 03/17] debugging --- src/goose/toolkit/developer.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/goose/toolkit/developer.py b/src/goose/toolkit/developer.py index 418d0984..e5f56f4e 100644 --- a/src/goose/toolkit/developer.py +++ b/src/goose/toolkit/developer.py @@ -228,6 +228,7 @@ def reader_thread(pipe, output_queue): # Process output from stdout try: line = stdout_queue.get_nowait() + #print('out line', line) if line == 'PROMPT_DETECTED': is_waiting_for_input = True break @@ -239,6 +240,7 @@ def reader_thread(pipe, output_queue): # Process output from stderr try: line = stderr_queue.get_nowait() + #print('err line', line) if line == 'PROMPT_DETECTED': is_waiting_for_input = True break From 5d911b943b863333b3ad8e92d8eaf008dadd307a Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Mon, 16 Sep 2024 12:08:40 +1000 Subject: [PATCH 04/17] check when thing is running in background with timeout --- src/goose/toolkit/developer.py | 87 ++++++++++++++++++++++++++++------ 1 file changed, 73 insertions(+), 14 deletions(-) diff --git a/src/goose/toolkit/developer.py b/src/goose/toolkit/developer.py index e5f56f4e..13b67ca4 100644 --- a/src/goose/toolkit/developer.py +++ b/src/goose/toolkit/developer.py @@ -138,6 +138,7 @@ def read_file(self, path: str) -> str: self.timestamps[path] = os.path.getmtime(path) return f"```{language}\n{content}\n```" + @tool def shell(self, command: str) -> str: """ @@ -151,6 +152,11 @@ def shell(self, command: str) -> str: command (str): The shell command to run. It can support multiline statements if you need to run more than one at a time """ + import subprocess + import threading + import queue + import re + import time # Import time module for timing functionality self.notifier.status("planning to run shell command") # Log the command being executed in a visually structured format (Markdown). @@ -208,6 +214,18 @@ def reader_thread(pipe, output_queue): finally: pipe.close() + # Nested function: maybe_prompt + def maybe_prompt(lines): + nonlocal is_waiting_for_input # Access the nonlocal variable to modify it + # Default behavior: log the lines + self.notifier.log(f"No output for 10 seconds. Recent lines:\n{''.join(lines)}") + + # Placeholder for future logic + # If certain conditions are met, set is_waiting_for_input = True + # Example condition: + # if some_condition_based_on(lines): + # is_waiting_for_input = True + # Start threads to read stdout and stderr stdout_thread = threading.Thread(target=reader_thread, args=(proc.stdout, stdout_queue)) stderr_thread = threading.Thread(target=reader_thread, args=(proc.stderr, stderr_queue)) @@ -219,36 +237,70 @@ def reader_thread(pipe, output_queue): error = '' is_waiting_for_input = False + # Initialize timer and recent lines list + last_line_time = time.time() + recent_lines = [] + # Continuously read output while True: # Check if process has terminated if proc.poll() is not None: break + # Flag to check if a new line was received + new_line_received = False + # Process output from stdout try: - line = stdout_queue.get_nowait() - #print('out line', line) - if line == 'PROMPT_DETECTED': - is_waiting_for_input = True + while True: + line = stdout_queue.get_nowait() + print("line", line) + if line == 'PROMPT_DETECTED': + is_waiting_for_input = True + break + else: + output += line + recent_lines.append(line) + recent_lines = recent_lines[-10:] # Keep only the last 10 lines + last_line_time = time.time() # Reset timer + new_line_received = True + if is_waiting_for_input: break - else: - output += line except queue.Empty: pass + if is_waiting_for_input: + break + # Process output from stderr try: - line = stderr_queue.get_nowait() - #print('err line', line) - if line == 'PROMPT_DETECTED': - is_waiting_for_input = True + while True: + line = stderr_queue.get_nowait() + if line == 'PROMPT_DETECTED': + is_waiting_for_input = True + break + else: + error += line + recent_lines.append(line) + recent_lines = recent_lines[-10:] # Keep only the last 10 lines + last_line_time = time.time() # Reset timer + new_line_received = True + if is_waiting_for_input: break - else: - error += line except queue.Empty: pass + if is_waiting_for_input: + break + + # Check if no new lines have been received for 10 seconds + if time.time() - last_line_time > 10: + # Call maybe_prompt with the last 2 to 10 recent lines + lines_to_check = recent_lines[-10:] + maybe_prompt(lines_to_check) + # Reset last_line_time to avoid repeated calls + last_line_time = time.time() + # Brief sleep to prevent high CPU usage threading.Event().wait(0.1) @@ -271,13 +323,19 @@ def reader_thread(pipe, output_queue): # Retrieve any remaining output from queues try: while True: - output += stdout_queue.get_nowait() + line = stdout_queue.get_nowait() + output += line + recent_lines.append(line) + recent_lines = recent_lines[-10:] except queue.Empty: pass try: while True: - error += stderr_queue.get_nowait() + line = stderr_queue.get_nowait() + error += line + recent_lines.append(line) + recent_lines = recent_lines[-10:] except queue.Empty: pass @@ -290,6 +348,7 @@ def reader_thread(pipe, output_queue): # Return the combined result and outputs return "\n".join([result, output, error]) + @tool def write_file(self, path: str, content: str) -> str: """ From 53ce559a055068113865dcf7b30fc9a5b2e6ec49 Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Mon, 16 Sep 2024 12:35:44 +1000 Subject: [PATCH 05/17] exit after 10s - placeholder --- src/goose/toolkit/developer.py | 24 +++++++++--------------- 1 file changed, 9 insertions(+), 15 deletions(-) diff --git a/src/goose/toolkit/developer.py b/src/goose/toolkit/developer.py index 13b67ca4..77ba3ebc 100644 --- a/src/goose/toolkit/developer.py +++ b/src/goose/toolkit/developer.py @@ -216,15 +216,9 @@ def reader_thread(pipe, output_queue): # Nested function: maybe_prompt def maybe_prompt(lines): - nonlocal is_waiting_for_input # Access the nonlocal variable to modify it # Default behavior: log the lines self.notifier.log(f"No output for 10 seconds. Recent lines:\n{''.join(lines)}") - - # Placeholder for future logic - # If certain conditions are met, set is_waiting_for_input = True - # Example condition: - # if some_condition_based_on(lines): - # is_waiting_for_input = True + return True # Start threads to read stdout and stderr stdout_thread = threading.Thread(target=reader_thread, args=(proc.stdout, stdout_queue)) @@ -247,8 +241,6 @@ def maybe_prompt(lines): if proc.poll() is not None: break - # Flag to check if a new line was received - new_line_received = False # Process output from stdout try: @@ -263,7 +255,6 @@ def maybe_prompt(lines): recent_lines.append(line) recent_lines = recent_lines[-10:] # Keep only the last 10 lines last_line_time = time.time() # Reset timer - new_line_received = True if is_waiting_for_input: break except queue.Empty: @@ -284,7 +275,6 @@ def maybe_prompt(lines): recent_lines.append(line) recent_lines = recent_lines[-10:] # Keep only the last 10 lines last_line_time = time.time() # Reset timer - new_line_received = True if is_waiting_for_input: break except queue.Empty: @@ -297,17 +287,21 @@ def maybe_prompt(lines): if time.time() - last_line_time > 10: # Call maybe_prompt with the last 2 to 10 recent lines lines_to_check = recent_lines[-10:] - maybe_prompt(lines_to_check) + if maybe_prompt(lines_to_check): + print("should break") + is_waiting_for_input = True + break # Reset last_line_time to avoid repeated calls last_line_time = time.time() + if is_waiting_for_input: + break + + # Brief sleep to prevent high CPU usage threading.Event().wait(0.1) if is_waiting_for_input: - # Allow threads to finish reading - stdout_thread.join() - stderr_thread.join() return ( "Command requires interactive input. If unclear, prompt user for required input or ask to run outside of goose.\n" f"Output:\n{output}\nError:\n{error}" From 88b8df51f3b240a281053bf486800bd110865814 Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Mon, 16 Sep 2024 13:47:13 +1000 Subject: [PATCH 06/17] use accelerator to check if command is intentionally long running --- src/goose/toolkit/developer.py | 46 +++++++++++----------------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/src/goose/toolkit/developer.py b/src/goose/toolkit/developer.py index 77ba3ebc..d0370026 100644 --- a/src/goose/toolkit/developer.py +++ b/src/goose/toolkit/developer.py @@ -2,6 +2,7 @@ from subprocess import CompletedProcess, run from typing import List, Dict import os +from goose.utils.ask import ask_an_ai from goose.utils.check_shell_command import is_dangerous_command from exchange import Message @@ -181,7 +182,6 @@ def shell(self, command: str) -> str: r'\(y/N\)', # Yes/No prompt r'Press any key to continue', # Awaiting keypress r'Waiting for input', # General waiting message - r'Config already exist.*overwrite\?', # Specific to 'jira init' example r'\?\s' # Prompts starting with '? ' ] @@ -229,7 +229,6 @@ def maybe_prompt(lines): # Collect output output = '' error = '' - is_waiting_for_input = False # Initialize timer and recent lines list last_line_time = time.time() @@ -241,71 +240,56 @@ def maybe_prompt(lines): if proc.poll() is not None: break - # Process output from stdout try: while True: line = stdout_queue.get_nowait() - print("line", line) if line == 'PROMPT_DETECTED': - is_waiting_for_input = True - break + return f"Command requires interactive input. If unclear, prompt user for required input or ask to run outside of goose.\nOutput:\n{output}\nError:\n{error}" + else: output += line recent_lines.append(line) recent_lines = recent_lines[-10:] # Keep only the last 10 lines last_line_time = time.time() # Reset timer - if is_waiting_for_input: - break except queue.Empty: pass - if is_waiting_for_input: - break - # Process output from stderr try: while True: line = stderr_queue.get_nowait() if line == 'PROMPT_DETECTED': - is_waiting_for_input = True - break + return f"Command requires interactive input. If unclear, prompt user for required input or ask to run outside of goose.\nOutput:\n{output}\nError:\n{error}" else: error += line recent_lines.append(line) recent_lines = recent_lines[-10:] # Keep only the last 10 lines last_line_time = time.time() # Reset timer - if is_waiting_for_input: - break except queue.Empty: pass - if is_waiting_for_input: - break # Check if no new lines have been received for 10 seconds if time.time() - last_line_time > 10: # Call maybe_prompt with the last 2 to 10 recent lines - lines_to_check = recent_lines[-10:] - if maybe_prompt(lines_to_check): - print("should break") - is_waiting_for_input = True - break + lines_to_check = recent_lines[-10:] + self.notifier.log(f"Still working:\n{''.join(lines_to_check)}") + response = ask_an_ai(input=('\n').join(recent_lines), + prompt='This looks to see if the lines provided from running a command are potentially waiting for something, running a server or something that will not termiinate in a shell. Return [Yes], if so [No] otherwise.', + exchange=self.exchange_view.accelerator) + if response.content[0].text == '[Yes]': + answer = f"The command {command} looks to be a long running task. Do not run it in goose but tell user to run it outside, unlese the user explicitly tells you to run it (and then, remind them they will need to cancel it as long running)." + return answer + else: + self.notifier.log(f"Will continue to run {command}") + # Reset last_line_time to avoid repeated calls last_line_time = time.time() - if is_waiting_for_input: - break - - # Brief sleep to prevent high CPU usage threading.Event().wait(0.1) - if is_waiting_for_input: - return ( - "Command requires interactive input. If unclear, prompt user for required input or ask to run outside of goose.\n" - f"Output:\n{output}\nError:\n{error}" - ) # Wait for process to complete proc.wait() From 422694fc99e0fc6f3e1922db0e0d97c6648799ee Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Mon, 16 Sep 2024 14:03:03 +1000 Subject: [PATCH 07/17] tidy up --- src/goose/toolkit/developer.py | 18 +++++------------- 1 file changed, 5 insertions(+), 13 deletions(-) diff --git a/src/goose/toolkit/developer.py b/src/goose/toolkit/developer.py index d0370026..a4f4f32e 100644 --- a/src/goose/toolkit/developer.py +++ b/src/goose/toolkit/developer.py @@ -175,7 +175,7 @@ def shell(self, command: str) -> str: self.notifier.status("running shell command") # Define patterns that might indicate the process is waiting for input - PROMPT_PATTERNS = [ + INTERACTION_PATTERNS = [ r'Do you want to', # Common prompt phrase r'Enter password', # Password prompt r'Are you sure', # Confirmation prompt @@ -184,9 +184,7 @@ def shell(self, command: str) -> str: r'Waiting for input', # General waiting message r'\?\s' # Prompts starting with '? ' ] - - # Compile the patterns for faster matching - compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in PROMPT_PATTERNS] + compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in INTERACTION_PATTERNS] # Start the process proc = subprocess.Popen( @@ -210,16 +208,10 @@ def reader_thread(pipe, output_queue): # Check for prompt patterns for pattern in compiled_patterns: if pattern.search(line): - output_queue.put('PROMPT_DETECTED') + output_queue.put('INTERACTION_DETECTED') finally: pipe.close() - # Nested function: maybe_prompt - def maybe_prompt(lines): - # Default behavior: log the lines - self.notifier.log(f"No output for 10 seconds. Recent lines:\n{''.join(lines)}") - return True - # Start threads to read stdout and stderr stdout_thread = threading.Thread(target=reader_thread, args=(proc.stdout, stdout_queue)) stderr_thread = threading.Thread(target=reader_thread, args=(proc.stderr, stderr_queue)) @@ -244,7 +236,7 @@ def maybe_prompt(lines): try: while True: line = stdout_queue.get_nowait() - if line == 'PROMPT_DETECTED': + if line == 'INTERACTION_DETECTED': return f"Command requires interactive input. If unclear, prompt user for required input or ask to run outside of goose.\nOutput:\n{output}\nError:\n{error}" else: @@ -259,7 +251,7 @@ def maybe_prompt(lines): try: while True: line = stderr_queue.get_nowait() - if line == 'PROMPT_DETECTED': + if line == 'INTERACTION_DETECTED': return f"Command requires interactive input. If unclear, prompt user for required input or ask to run outside of goose.\nOutput:\n{output}\nError:\n{error}" else: error += line From b71a694789f9cab9755811ad2fd8f1c19671a26b Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Mon, 16 Sep 2024 14:48:26 +1000 Subject: [PATCH 08/17] ruff fixes --- src/goose/toolkit/developer.py | 47 ++++++++++++++++++++-------------- 1 file changed, 28 insertions(+), 19 deletions(-) diff --git a/src/goose/toolkit/developer.py b/src/goose/toolkit/developer.py index a4f4f32e..47dd5635 100644 --- a/src/goose/toolkit/developer.py +++ b/src/goose/toolkit/developer.py @@ -1,5 +1,4 @@ from pathlib import Path -from subprocess import CompletedProcess, run from typing import List, Dict import os from goose.utils.ask import ask_an_ai @@ -16,6 +15,8 @@ import threading import queue import re +import time + from goose.toolkit.base import Toolkit, tool @@ -139,7 +140,7 @@ def read_file(self, path: str) -> str: self.timestamps[path] = os.path.getmtime(path) return f"```{language}\n{content}\n```" - + @tool def shell(self, command: str) -> str: """ @@ -153,11 +154,6 @@ def shell(self, command: str) -> str: command (str): The shell command to run. It can support multiline statements if you need to run more than one at a time """ - import subprocess - import threading - import queue - import re - import time # Import time module for timing functionality self.notifier.status("planning to run shell command") # Log the command being executed in a visually structured format (Markdown). @@ -169,13 +165,13 @@ def shell(self, command: str) -> str: if not keep_unsafe_command_prompt(command): raise RuntimeError( f"The command {command} was rejected as dangerous by the user." - + " Do not proceed further, instead ask for instructions." + " Do not proceed further, instead ask for instructions." ) self.notifier.start() self.notifier.status("running shell command") # Define patterns that might indicate the process is waiting for input - INTERACTION_PATTERNS = [ + interaction_patterns = [ r'Do you want to', # Common prompt phrase r'Enter password', # Password prompt r'Are you sure', # Confirmation prompt @@ -184,7 +180,7 @@ def shell(self, command: str) -> str: r'Waiting for input', # General waiting message r'\?\s' # Prompts starting with '? ' ] - compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in INTERACTION_PATTERNS] + compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in interaction_patterns] # Start the process proc = subprocess.Popen( @@ -201,7 +197,7 @@ def shell(self, command: str) -> str: stderr_queue = queue.Queue() # Function to read stdout and stderr without blocking - def reader_thread(pipe, output_queue): + def reader_thread(pipe: any, output_queue: any) -> None: try: for line in iter(pipe.readline, ''): output_queue.put(line) @@ -237,7 +233,10 @@ def reader_thread(pipe, output_queue): while True: line = stdout_queue.get_nowait() if line == 'INTERACTION_DETECTED': - return f"Command requires interactive input. If unclear, prompt user for required input or ask to run outside of goose.\nOutput:\n{output}\nError:\n{error}" + return ( + "Command requires interactive input. If unclear, prompt user for required input " + f"or ask to run outside of goose.\nOutput:\n{output}\nError:\n{error}" + ) else: output += line @@ -250,9 +249,12 @@ def reader_thread(pipe, output_queue): # Process output from stderr try: while True: - line = stderr_queue.get_nowait() + line = stderr_queue.get_nowait() if line == 'INTERACTION_DETECTED': - return f"Command requires interactive input. If unclear, prompt user for required input or ask to run outside of goose.\nOutput:\n{output}\nError:\n{error}" + return ( + "Command requires interactive input. If unclear, prompt user for required input " + f"or ask to run outside of goose.\nOutput:\n{output}\nError:\n{error}" + ) else: error += line recent_lines.append(line) @@ -265,17 +267,24 @@ def reader_thread(pipe, output_queue): # Check if no new lines have been received for 10 seconds if time.time() - last_line_time > 10: # Call maybe_prompt with the last 2 to 10 recent lines - lines_to_check = recent_lines[-10:] + lines_to_check = recent_lines[-10:] self.notifier.log(f"Still working:\n{''.join(lines_to_check)}") response = ask_an_ai(input=('\n').join(recent_lines), - prompt='This looks to see if the lines provided from running a command are potentially waiting for something, running a server or something that will not termiinate in a shell. Return [Yes], if so [No] otherwise.', + prompt='This looks to see if the lines provided from running a command are potentially waiting' + +' for something, running a server or something that will not termiinate in a shell.' + +' Return [Yes], if so [No] otherwise.', exchange=self.exchange_view.accelerator) - if response.content[0].text == '[Yes]': - answer = f"The command {command} looks to be a long running task. Do not run it in goose but tell user to run it outside, unlese the user explicitly tells you to run it (and then, remind them they will need to cancel it as long running)." + if response.content[0].text == '[Yes]': + answer = ( + f"The command {command} looks to be a long running task. " + f"Do not run it in goose but tell user to run it outside, " + f"unless the user explicitly tells you to run it (and then, " + f"remind them they will need to cancel it as long running)." + ) return answer else: self.notifier.log(f"Will continue to run {command}") - + # Reset last_line_time to avoid repeated calls last_line_time = time.time() From 065a3ff137af9745f376839e79c47b493bca8d55 Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Mon, 16 Sep 2024 14:49:04 +1000 Subject: [PATCH 09/17] small change --- src/goose/toolkit/developer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/goose/toolkit/developer.py b/src/goose/toolkit/developer.py index 47dd5635..277cc544 100644 --- a/src/goose/toolkit/developer.py +++ b/src/goose/toolkit/developer.py @@ -268,7 +268,7 @@ def reader_thread(pipe: any, output_queue: any) -> None: if time.time() - last_line_time > 10: # Call maybe_prompt with the last 2 to 10 recent lines lines_to_check = recent_lines[-10:] - self.notifier.log(f"Still working:\n{''.join(lines_to_check)}") + self.notifier.log(f"Still working\n{''.join(lines_to_check)}") response = ask_an_ai(input=('\n').join(recent_lines), prompt='This looks to see if the lines provided from running a command are potentially waiting' +' for something, running a server or something that will not termiinate in a shell.' From 702abd53894811566fc76f6a89a1a364cf0b2696 Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Mon, 16 Sep 2024 14:51:06 +1000 Subject: [PATCH 10/17] formatting --- src/goose/toolkit/developer.py | 61 ++++++++++++++++------------------ 1 file changed, 29 insertions(+), 32 deletions(-) diff --git a/src/goose/toolkit/developer.py b/src/goose/toolkit/developer.py index 277cc544..99cb2f9f 100644 --- a/src/goose/toolkit/developer.py +++ b/src/goose/toolkit/developer.py @@ -18,7 +18,6 @@ import time - from goose.toolkit.base import Toolkit, tool from goose.toolkit.utils import get_language, render_template @@ -140,7 +139,6 @@ def read_file(self, path: str) -> str: self.timestamps[path] = os.path.getmtime(path) return f"```{language}\n{content}\n```" - @tool def shell(self, command: str) -> str: """ @@ -172,13 +170,13 @@ def shell(self, command: str) -> str: # Define patterns that might indicate the process is waiting for input interaction_patterns = [ - r'Do you want to', # Common prompt phrase - r'Enter password', # Password prompt - r'Are you sure', # Confirmation prompt - r'\(y/N\)', # Yes/No prompt - r'Press any key to continue', # Awaiting keypress - r'Waiting for input', # General waiting message - r'\?\s' # Prompts starting with '? ' + r"Do you want to", # Common prompt phrase + r"Enter password", # Password prompt + r"Are you sure", # Confirmation prompt + r"\(y/N\)", # Yes/No prompt + r"Press any key to continue", # Awaiting keypress + r"Waiting for input", # General waiting message + r"\?\s", # Prompts starting with '? ' ] compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in interaction_patterns] @@ -189,7 +187,7 @@ def shell(self, command: str) -> str: stdin=subprocess.DEVNULL, # Close stdin to prevent the process from waiting for input stdout=subprocess.PIPE, stderr=subprocess.PIPE, - text=True + text=True, ) # Queues to store the output @@ -199,12 +197,12 @@ def shell(self, command: str) -> str: # Function to read stdout and stderr without blocking def reader_thread(pipe: any, output_queue: any) -> None: try: - for line in iter(pipe.readline, ''): + for line in iter(pipe.readline, ""): output_queue.put(line) # Check for prompt patterns for pattern in compiled_patterns: if pattern.search(line): - output_queue.put('INTERACTION_DETECTED') + output_queue.put("INTERACTION_DETECTED") finally: pipe.close() @@ -215,8 +213,8 @@ def reader_thread(pipe: any, output_queue: any) -> None: stderr_thread.start() # Collect output - output = '' - error = '' + output = "" + error = "" # Initialize timer and recent lines list last_line_time = time.time() @@ -232,7 +230,7 @@ def reader_thread(pipe: any, output_queue: any) -> None: try: while True: line = stdout_queue.get_nowait() - if line == 'INTERACTION_DETECTED': + if line == "INTERACTION_DETECTED": return ( "Command requires interactive input. If unclear, prompt user for required input " f"or ask to run outside of goose.\nOutput:\n{output}\nError:\n{error}" @@ -250,7 +248,7 @@ def reader_thread(pipe: any, output_queue: any) -> None: try: while True: line = stderr_queue.get_nowait() - if line == 'INTERACTION_DETECTED': + if line == "INTERACTION_DETECTED": return ( "Command requires interactive input. If unclear, prompt user for required input " f"or ask to run outside of goose.\nOutput:\n{output}\nError:\n{error}" @@ -263,25 +261,26 @@ def reader_thread(pipe: any, output_queue: any) -> None: except queue.Empty: pass - # Check if no new lines have been received for 10 seconds if time.time() - last_line_time > 10: # Call maybe_prompt with the last 2 to 10 recent lines lines_to_check = recent_lines[-10:] self.notifier.log(f"Still working\n{''.join(lines_to_check)}") - response = ask_an_ai(input=('\n').join(recent_lines), - prompt='This looks to see if the lines provided from running a command are potentially waiting' - +' for something, running a server or something that will not termiinate in a shell.' - +' Return [Yes], if so [No] otherwise.', - exchange=self.exchange_view.accelerator) - if response.content[0].text == '[Yes]': - answer = ( - f"The command {command} looks to be a long running task. " - f"Do not run it in goose but tell user to run it outside, " - f"unless the user explicitly tells you to run it (and then, " - f"remind them they will need to cancel it as long running)." - ) - return answer + response = ask_an_ai( + input=("\n").join(recent_lines), + prompt="This looks to see if the lines provided from running a command are potentially waiting" + + " for something, running a server or something that will not termiinate in a shell." + + " Return [Yes], if so [No] otherwise.", + exchange=self.exchange_view.accelerator, + ) + if response.content[0].text == "[Yes]": + answer = ( + f"The command {command} looks to be a long running task. " + f"Do not run it in goose but tell user to run it outside, " + f"unless the user explicitly tells you to run it (and then, " + f"remind them they will need to cancel it as long running)." + ) + return answer else: self.notifier.log(f"Will continue to run {command}") @@ -291,7 +290,6 @@ def reader_thread(pipe: any, output_queue: any) -> None: # Brief sleep to prevent high CPU usage threading.Event().wait(0.1) - # Wait for process to complete proc.wait() @@ -327,7 +325,6 @@ def reader_thread(pipe: any, output_queue: any) -> None: # Return the combined result and outputs return "\n".join([result, output, error]) - @tool def write_file(self, path: str, content: str) -> str: """ From 131dc4b27324b6725ce2319600c9e60ec9605b11 Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Mon, 16 Sep 2024 14:58:41 +1000 Subject: [PATCH 11/17] removing dead code --- src/goose/toolkit/developer.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/goose/toolkit/developer.py b/src/goose/toolkit/developer.py index 99cb2f9f..fc6091e3 100644 --- a/src/goose/toolkit/developer.py +++ b/src/goose/toolkit/developer.py @@ -302,8 +302,6 @@ def reader_thread(pipe: any, output_queue: any) -> None: while True: line = stdout_queue.get_nowait() output += line - recent_lines.append(line) - recent_lines = recent_lines[-10:] except queue.Empty: pass @@ -311,8 +309,6 @@ def reader_thread(pipe: any, output_queue: any) -> None: while True: line = stderr_queue.get_nowait() error += line - recent_lines.append(line) - recent_lines = recent_lines[-10:] except queue.Empty: pass From ea918c7b8973c2ba3f4ed7b4971a9b93759ffd6c Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Mon, 16 Sep 2024 18:10:50 +1000 Subject: [PATCH 12/17] tweaking --- src/goose/toolkit/developer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/goose/toolkit/developer.py b/src/goose/toolkit/developer.py index fc6091e3..ad16fe46 100644 --- a/src/goose/toolkit/developer.py +++ b/src/goose/toolkit/developer.py @@ -318,7 +318,7 @@ def reader_thread(pipe: any, output_queue: any) -> None: else: result = f"Command failed with returncode {proc.returncode}" - # Return the combined result and outputs + # Return the combined result and outputs if we made it this far return "\n".join([result, output, error]) @tool From 99b27f800854f139abd828fd5172c5a1ce943e20 Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Wed, 18 Sep 2024 19:55:29 +1000 Subject: [PATCH 13/17] small fixes --- src/goose/toolkit/developer.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/goose/toolkit/developer.py b/src/goose/toolkit/developer.py index ad16fe46..10707d47 100644 --- a/src/goose/toolkit/developer.py +++ b/src/goose/toolkit/developer.py @@ -266,8 +266,10 @@ def reader_thread(pipe: any, output_queue: any) -> None: # Call maybe_prompt with the last 2 to 10 recent lines lines_to_check = recent_lines[-10:] self.notifier.log(f"Still working\n{''.join(lines_to_check)}") + if not lines_to_check or len(recent_lines) == 0: + lines_to_check = list(['busy...']) response = ask_an_ai( - input=("\n").join(recent_lines), + input=("\n").join(lines_to_check), prompt="This looks to see if the lines provided from running a command are potentially waiting" + " for something, running a server or something that will not termiinate in a shell." + " Return [Yes], if so [No] otherwise.", From 68def21c28282257222d4227bdbbfc1aee6735c2 Mon Sep 17 00:00:00 2001 From: Mic Neale Date: Thu, 19 Sep 2024 11:17:26 +1000 Subject: [PATCH 14/17] style quotes --- src/goose/toolkit/developer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/goose/toolkit/developer.py b/src/goose/toolkit/developer.py index 10707d47..4fc4d828 100644 --- a/src/goose/toolkit/developer.py +++ b/src/goose/toolkit/developer.py @@ -267,7 +267,7 @@ def reader_thread(pipe: any, output_queue: any) -> None: lines_to_check = recent_lines[-10:] self.notifier.log(f"Still working\n{''.join(lines_to_check)}") if not lines_to_check or len(recent_lines) == 0: - lines_to_check = list(['busy...']) + lines_to_check = list(["busy..."]) response = ask_an_ai( input=("\n").join(lines_to_check), prompt="This looks to see if the lines provided from running a command are potentially waiting" From ab4de2464e48528b9a705aa02363e3fe320d1c8b Mon Sep 17 00:00:00 2001 From: Bradley Axen Date: Mon, 23 Sep 2024 20:33:49 -0700 Subject: [PATCH 15/17] Refactor interactive checks --- src/goose/toolkit/developer.py | 217 +++++++++++---------------------- src/goose/utils/ask.py | 15 ++- 2 files changed, 84 insertions(+), 148 deletions(-) diff --git a/src/goose/toolkit/developer.py b/src/goose/toolkit/developer.py index 4fc4d828..2a2b8e4a 100644 --- a/src/goose/toolkit/developer.py +++ b/src/goose/toolkit/developer.py @@ -1,31 +1,29 @@ -from pathlib import Path -from typing import List, Dict import os -from goose.utils.ask import ask_an_ai -from goose.utils.check_shell_command import is_dangerous_command +import re +import subprocess +import time +from pathlib import Path +from typing import Dict, List from exchange import Message +from goose.toolkit.base import Toolkit, tool +from goose.toolkit.utils import get_language, render_template +from goose.utils.ask import ask_an_ai +from goose.utils.check_shell_command import is_dangerous_command from rich import box from rich.markdown import Markdown from rich.panel import Panel from rich.prompt import Confirm from rich.table import Table from rich.text import Text -import subprocess -import threading -import queue -import re -import time - - -from goose.toolkit.base import Toolkit, tool -from goose.toolkit.utils import get_language, render_template def keep_unsafe_command_prompt(command: str) -> bool: command_text = Text(command, style="bold red") message = ( - Text("\nWe flagged the command: ") + command_text + Text(" as potentially unsafe, do you want to proceed?") + Text("\nWe flagged the command: ") + + command_text + + Text(" as potentially unsafe, do you want to proceed?") ) return Confirm.ask(message, default=True) @@ -106,9 +104,13 @@ def patch_file(self, path: str, before: str, after: str) -> str: content = _path.read_text() if content.count(before) > 1: - raise ValueError("The before content is present multiple times in the file, be more specific.") + raise ValueError( + "The before content is present multiple times in the file, be more specific." + ) if content.count(before) < 1: - raise ValueError("The before content was not found in file, be careful that you recreate it exactly.") + raise ValueError( + "The before content was not found in file, be careful that you recreate it exactly." + ) content = content.replace(before, after) _path.write_text(content) @@ -142,7 +144,7 @@ def read_file(self, path: str) -> str: @tool def shell(self, command: str) -> str: """ - Execute a command on the shell (in OSX) + Execute a command on the shell This will return the output and error concatenated into a single string, as you would see from running on the command line. There will also be an indication @@ -152,10 +154,10 @@ def shell(self, command: str) -> str: command (str): The shell command to run. It can support multiline statements if you need to run more than one at a time """ - - self.notifier.status("planning to run shell command") # Log the command being executed in a visually structured format (Markdown). - self.notifier.log(Panel.fit(Markdown(f"```bash\n{command}\n```"), title="shell")) + self.notifier.log( + Panel.fit(Markdown(f"```bash\n{command}\n```"), title="shell") + ) if is_dangerous_command(command): # Stop the notifications so we can prompt @@ -178,141 +180,64 @@ def shell(self, command: str) -> str: r"Waiting for input", # General waiting message r"\?\s", # Prompts starting with '? ' ] - compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in interaction_patterns] + compiled_patterns = [ + re.compile(pattern, re.IGNORECASE) for pattern in interaction_patterns + ] - # Start the process proc = subprocess.Popen( command, shell=True, - stdin=subprocess.DEVNULL, # Close stdin to prevent the process from waiting for input + stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, - stderr=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True, ) - - # Queues to store the output - stdout_queue = queue.Queue() - stderr_queue = queue.Queue() - - # Function to read stdout and stderr without blocking - def reader_thread(pipe: any, output_queue: any) -> None: - try: - for line in iter(pipe.readline, ""): - output_queue.put(line) - # Check for prompt patterns - for pattern in compiled_patterns: - if pattern.search(line): - output_queue.put("INTERACTION_DETECTED") - finally: - pipe.close() - - # Start threads to read stdout and stderr - stdout_thread = threading.Thread(target=reader_thread, args=(proc.stdout, stdout_queue)) - stderr_thread = threading.Thread(target=reader_thread, args=(proc.stderr, stderr_queue)) - stdout_thread.start() - stderr_thread.start() - - # Collect output - output = "" - error = "" - - # Initialize timer and recent lines list - last_line_time = time.time() - recent_lines = [] - - # Continuously read output - while True: - # Check if process has terminated - if proc.poll() is not None: - break - - # Process output from stdout - try: - while True: - line = stdout_queue.get_nowait() - if line == "INTERACTION_DETECTED": - return ( - "Command requires interactive input. If unclear, prompt user for required input " - f"or ask to run outside of goose.\nOutput:\n{output}\nError:\n{error}" - ) - - else: - output += line - recent_lines.append(line) - recent_lines = recent_lines[-10:] # Keep only the last 10 lines - last_line_time = time.time() # Reset timer - except queue.Empty: - pass - - # Process output from stderr - try: - while True: - line = stderr_queue.get_nowait() - if line == "INTERACTION_DETECTED": - return ( - "Command requires interactive input. If unclear, prompt user for required input " - f"or ask to run outside of goose.\nOutput:\n{output}\nError:\n{error}" - ) - else: - error += line - recent_lines.append(line) - recent_lines = recent_lines[-10:] # Keep only the last 10 lines - last_line_time = time.time() # Reset timer - except queue.Empty: - pass - - # Check if no new lines have been received for 10 seconds - if time.time() - last_line_time > 10: - # Call maybe_prompt with the last 2 to 10 recent lines - lines_to_check = recent_lines[-10:] - self.notifier.log(f"Still working\n{''.join(lines_to_check)}") - if not lines_to_check or len(recent_lines) == 0: - lines_to_check = list(["busy..."]) + # this enables us to read lines without blocking + os.set_blocking(proc.stdout.fileno(), False) + + # Accumulate the output logs while checking if it might be blocked + output_lines = [] + last_output_time = time.time() + cutoff = 10 + while proc.poll() is None: + self.notifier.status("running shell command") + line = proc.stdout.readline() + if line: + output_lines.append(line) + last_output_time = time.time() + + # If we see a clear pattern match, we plan to abort + exit_criteria = any(pattern.search(line) for pattern in compiled_patterns) + + # and if we haven't seen a new line in 10+s, check with AI to see if it may be stuck + if not exit_criteria and time.time() - last_output_time > cutoff: + self.notifier.status("checking on shell status") response = ask_an_ai( - input=("\n").join(lines_to_check), - prompt="This looks to see if the lines provided from running a command are potentially waiting" - + " for something, running a server or something that will not termiinate in a shell." - + " Return [Yes], if so [No] otherwise.", + input="\n".join([command] + output_lines), + prompt=( + "You will evaluate the output of shell commands to see if they may be stuck." + " If the command appears to be awaiting user input, or otherwise running indefinitely (such as a web service)" + " return [Yes] if so [No] otherwise." + ), exchange=self.exchange_view.accelerator, + with_tools=False, + ) + exit_criteria = "[yes]" in response.content[0].text.lower() + # We add exponential backoff for how often we check for the command being stuck + cutoff *= 10 + + if exit_criteria: + proc.terminate() + raise ValueError( + f"The command `{command}` looks like it will run indefinitely or is otherwise stuck." + f"You may be able to specify inputs if it applies to this command." + f"Otherwise to enable continued iteration, you'll need to ask the user to run this command in another terminal." ) - if response.content[0].text == "[Yes]": - answer = ( - f"The command {command} looks to be a long running task. " - f"Do not run it in goose but tell user to run it outside, " - f"unless the user explicitly tells you to run it (and then, " - f"remind them they will need to cancel it as long running)." - ) - return answer - else: - self.notifier.log(f"Will continue to run {command}") - - # Reset last_line_time to avoid repeated calls - last_line_time = time.time() - - # Brief sleep to prevent high CPU usage - threading.Event().wait(0.1) - - # Wait for process to complete - proc.wait() - - # Ensure all output is read - stdout_thread.join() - stderr_thread.join() - - # Retrieve any remaining output from queues - try: - while True: - line = stdout_queue.get_nowait() - output += line - except queue.Empty: - pass - - try: - while True: - line = stderr_queue.get_nowait() - error += line - except queue.Empty: - pass + + # read any remaining lines + while line := proc.stdout.readline(): + output_lines.append(line) + output = "".join(output_lines) # Determine the result based on the return code if proc.returncode == 0: @@ -321,7 +246,7 @@ def reader_thread(pipe: any, output_queue: any) -> None: result = f"Command failed with returncode {proc.returncode}" # Return the combined result and outputs if we made it this far - return "\n".join([result, output, error]) + return "\n".join([result, output]) @tool def write_file(self, path: str, content: str) -> str: diff --git a/src/goose/utils/ask.py b/src/goose/utils/ask.py index 0e34b444..50b82ab9 100644 --- a/src/goose/utils/ask.py +++ b/src/goose/utils/ask.py @@ -1,7 +1,13 @@ from exchange import Exchange, Message, CheckpointData -def ask_an_ai(input: str, exchange: Exchange, prompt: str = "", no_history: bool = True) -> Message: +def ask_an_ai( + input: str, + exchange: Exchange, + prompt: str = "", + no_history: bool = True, + with_tools: bool = True, +) -> Message: """Sends a separate message to an LLM using a separate Exchange than the one underlying the Goose session. Can be used to summarize a file, or submit any other request that you'd like to an AI. The Exchange can have a @@ -36,6 +42,9 @@ def ask_an_ai(input: str, exchange: Exchange, prompt: str = "", no_history: bool if no_history: exchange = clear_exchange(exchange) + if not with_tools: + exchange = exchange.replace(tools=()) + if prompt: exchange = replace_prompt(exchange, prompt) @@ -61,7 +70,9 @@ def clear_exchange(exchange: Exchange, clear_tools: bool = False) -> Exchange: """ if clear_tools: - new_exchange = exchange.replace(messages=[], checkpoint_data=CheckpointData(), tools=()) + new_exchange = exchange.replace( + messages=[], checkpoint_data=CheckpointData(), tools=() + ) else: new_exchange = exchange.replace(messages=[], checkpoint_data=CheckpointData()) return new_exchange From 6ea0eee9360fa003a16cfd7c883916a3b5d0440b Mon Sep 17 00:00:00 2001 From: Bradley Axen Date: Mon, 23 Sep 2024 20:44:03 -0700 Subject: [PATCH 16/17] format --- src/goose/toolkit/developer.py | 25 ++++++++----------------- src/goose/utils/ask.py | 4 +--- 2 files changed, 9 insertions(+), 20 deletions(-) diff --git a/src/goose/toolkit/developer.py b/src/goose/toolkit/developer.py index 2a2b8e4a..536533ad 100644 --- a/src/goose/toolkit/developer.py +++ b/src/goose/toolkit/developer.py @@ -21,9 +21,7 @@ def keep_unsafe_command_prompt(command: str) -> bool: command_text = Text(command, style="bold red") message = ( - Text("\nWe flagged the command: ") - + command_text - + Text(" as potentially unsafe, do you want to proceed?") + Text("\nWe flagged the command: ") + command_text + Text(" as potentially unsafe, do you want to proceed?") ) return Confirm.ask(message, default=True) @@ -104,13 +102,9 @@ def patch_file(self, path: str, before: str, after: str) -> str: content = _path.read_text() if content.count(before) > 1: - raise ValueError( - "The before content is present multiple times in the file, be more specific." - ) + raise ValueError("The before content is present multiple times in the file, be more specific.") if content.count(before) < 1: - raise ValueError( - "The before content was not found in file, be careful that you recreate it exactly." - ) + raise ValueError("The before content was not found in file, be careful that you recreate it exactly.") content = content.replace(before, after) _path.write_text(content) @@ -155,9 +149,7 @@ def shell(self, command: str) -> str: if you need to run more than one at a time """ # Log the command being executed in a visually structured format (Markdown). - self.notifier.log( - Panel.fit(Markdown(f"```bash\n{command}\n```"), title="shell") - ) + self.notifier.log(Panel.fit(Markdown(f"```bash\n{command}\n```"), title="shell")) if is_dangerous_command(command): # Stop the notifications so we can prompt @@ -180,9 +172,7 @@ def shell(self, command: str) -> str: r"Waiting for input", # General waiting message r"\?\s", # Prompts starting with '? ' ] - compiled_patterns = [ - re.compile(pattern, re.IGNORECASE) for pattern in interaction_patterns - ] + compiled_patterns = [re.compile(pattern, re.IGNORECASE) for pattern in interaction_patterns] proc = subprocess.Popen( command, @@ -216,7 +206,8 @@ def shell(self, command: str) -> str: input="\n".join([command] + output_lines), prompt=( "You will evaluate the output of shell commands to see if they may be stuck." - " If the command appears to be awaiting user input, or otherwise running indefinitely (such as a web service)" + " If the command appears to be awaiting user input, or otherwise running indefinitely (such as a web service)." # noqa + " Long running tasks are okay, only identify tasks that are awaiting input or will otherwise never terminate." # noqa " return [Yes] if so [No] otherwise." ), exchange=self.exchange_view.accelerator, @@ -231,7 +222,7 @@ def shell(self, command: str) -> str: raise ValueError( f"The command `{command}` looks like it will run indefinitely or is otherwise stuck." f"You may be able to specify inputs if it applies to this command." - f"Otherwise to enable continued iteration, you'll need to ask the user to run this command in another terminal." + f"Otherwise to enable continued iteration, you'll need to ask the user to run this command in another terminal." # noqa ) # read any remaining lines diff --git a/src/goose/utils/ask.py b/src/goose/utils/ask.py index 50b82ab9..c0fee1bc 100644 --- a/src/goose/utils/ask.py +++ b/src/goose/utils/ask.py @@ -70,9 +70,7 @@ def clear_exchange(exchange: Exchange, clear_tools: bool = False) -> Exchange: """ if clear_tools: - new_exchange = exchange.replace( - messages=[], checkpoint_data=CheckpointData(), tools=() - ) + new_exchange = exchange.replace(messages=[], checkpoint_data=CheckpointData(), tools=()) else: new_exchange = exchange.replace(messages=[], checkpoint_data=CheckpointData()) return new_exchange From 0a9fd4a176d2e7acc90e44474459196773d982be Mon Sep 17 00:00:00 2001 From: Bradley Axen Date: Mon, 23 Sep 2024 20:55:28 -0700 Subject: [PATCH 17/17] use processor for performance --- src/goose/toolkit/developer.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/goose/toolkit/developer.py b/src/goose/toolkit/developer.py index 536533ad..e5cc864c 100644 --- a/src/goose/toolkit/developer.py +++ b/src/goose/toolkit/developer.py @@ -206,11 +206,11 @@ def shell(self, command: str) -> str: input="\n".join([command] + output_lines), prompt=( "You will evaluate the output of shell commands to see if they may be stuck." - " If the command appears to be awaiting user input, or otherwise running indefinitely (such as a web service)." # noqa - " Long running tasks are okay, only identify tasks that are awaiting input or will otherwise never terminate." # noqa - " return [Yes] if so [No] otherwise." + " Look for commands that appear to be awaiting user input, or otherwise running indefinitely (such as a web service)." # noqa + " A command that will take a while, such as downloading resources is okay." # noqa + " return [Yes] if stuck, [No] otherwise." ), - exchange=self.exchange_view.accelerator, + exchange=self.exchange_view.processor, with_tools=False, ) exit_criteria = "[yes]" in response.content[0].text.lower()