feat: enable autonomous mode by default

feat: improve content extraction in autonomous mode
refactor: remove user message in autonomous mode
refactor: display tool call arguments
maintenance: update version to 1.54.0
refactor: handle tool call execution errors
refactor: prevent excessive autonomous mode exits
perf: display execution time in progress indicator
This commit is contained in:
retoor 2025-11-10 10:54:34 +01:00
parent 20668d9086
commit 8e6af2b32b
5 changed files with 54 additions and 39 deletions

View File

@ -4,6 +4,14 @@
## Version 1.53.0 - 2025-11-10
Autonomous mode is now enabled by default, streamlining workflows. We've also improved the underlying code and fixed some issues with content extraction in autonomous mode.
**Changes:** 15 files, 433 lines
**Languages:** Markdown (47 lines), Python (384 lines), TOML (2 lines)
## Version 1.52.0 - 2025-11-10
This release updates the project version to 1.52.0. No new features or changes are introduced for users or developers.

View File

@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
[project]
name = "rp"
version = "1.52.0"
version = "1.53.0"
description = "R python edition. The ultimate autonomous AI CLI."
readme = "README.md"
requires-python = ">=3.10"

View File

@ -99,6 +99,9 @@ def run_autonomous_mode(assistant, task):
except KeyboardInterrupt:
logger.debug("Autonomous mode interrupted by user")
print(f"\n{Colors.YELLOW}Autonomous mode interrupted by user{Colors.RESET}")
# Cancel the last API call and remove the user message to keep messages clean
if assistant.messages and assistant.messages[-1]["role"] == "user":
assistant.messages.pop()
finally:
assistant.autonomous_mode = False
logger.debug("=== AUTONOMOUS MODE END ===")
@ -113,10 +116,13 @@ def process_response_autonomous(assistant, response):
assistant.messages.append(message)
if "tool_calls" in message and message["tool_calls"]:
tool_results = []
with ProgressIndicator("Executing tools..."):
for tool_call in message["tool_calls"]:
func_name = tool_call["function"]["name"]
arguments = json.loads(tool_call["function"]["arguments"])
args_str = ", ".join([f"{k}={repr(v)}" for k, v in arguments.items()])
if len(args_str) > 100:
args_str = args_str[:97] + "..."
print(f"{Colors.BLUE}⠋ Executing tools......{func_name}({args_str}){Colors.RESET}")
result = execute_single_tool(assistant, func_name, arguments)
if isinstance(result, str):
try:
@ -125,7 +131,6 @@ def process_response_autonomous(assistant, response):
result = {"error": str(ex)}
status = "success" if result.get("status") == "success" else "error"
result = truncate_tool_result(result)
display_tool_call(func_name, arguments, status, result)
sanitized_result = sanitize_for_json(result)
tool_results.append(
{

View File

@ -6,6 +6,7 @@ import readline
import signal
import sqlite3
import sys
import time
import traceback
from concurrent.futures import ThreadPoolExecutor
@ -99,7 +100,7 @@ class Assistant:
"MODEL_LIST_URL", MODEL_LIST_URL
)
self.use_tools = os.environ.get("USE_TOOLS", "1") == "1"
self.interrupt_count = 0
self.last_interrupt_time = 0
self.python_globals = {}
self.db_conn = None
self.autonomous_mode = False
@ -238,6 +239,10 @@ class Assistant:
func_name = tool_call["function"]["name"]
arguments = json.loads(tool_call["function"]["arguments"])
logger.debug(f"Tool call: {func_name} with arguments: {arguments}")
args_str = ", ".join([f"{k}={repr(v)}" for k, v in arguments.items()])
if len(args_str) > 100:
args_str = args_str[:97] + "..."
print(f"{Colors.BLUE}⠋ Executing tools......{func_name}({args_str}){Colors.RESET}")
func_map = {
"http_fetch": lambda **kw: http_fetch(**kw),
"run_command": lambda **kw: run_command(**kw),
@ -351,22 +356,15 @@ class Assistant:
return render_markdown(cleaned_content, self.syntax_highlighting)
def signal_handler(self, signum, frame):
if self.autonomous_mode:
self.interrupt_count += 1
if self.interrupt_count >= 2:
print(f"\n{Colors.RED}Force exiting autonomous mode...{Colors.RESET}")
self.autonomous_mode = False
sys.exit(0)
else:
print(f"\n{Colors.YELLOW}Press Ctrl+C again to force exit{Colors.RESET}")
return
self.interrupt_count += 1
if self.interrupt_count >= 2:
print(f"\n{Colors.RED}Exiting...{Colors.RESET}")
current_time = time.time()
if current_time - self.last_interrupt_time < 1.0:
print(f"\n{Colors.RED}Force exiting...{Colors.RESET}")
self.cleanup()
sys.exit(0)
else:
print(f"\n{Colors.YELLOW}Press Ctrl+C again to exit{Colors.RESET}")
self.last_interrupt_time = current_time
print(f"\n{Colors.YELLOW}Interrupted{Colors.RESET}")
raise KeyboardInterrupt
def setup_readline(self):
try:
@ -446,7 +444,8 @@ class Assistant:
except EOFError:
break
except KeyboardInterrupt:
self.signal_handler(None, None)
print(f"\n{Colors.YELLOW}Interrupted, returning to prompt{Colors.RESET}")
continue
except Exception as e:
print(f"{Colors.RED}Error: {e}{Colors.RESET}")
logging.error(f"REPL error: {e}\n{traceback.format_exc()}")

View File

@ -10,6 +10,7 @@ class ProgressIndicator:
self.show = show
self.running = False
self.thread = None
self.start_time = None
def __enter__(self):
if self.show:
@ -21,6 +22,7 @@ class ProgressIndicator:
self.stop()
def start(self):
self.start_time = time.time()
self.running = True
self.thread = threading.Thread(target=self._animate, daemon=True)
self.thread.start()
@ -30,14 +32,15 @@ class ProgressIndicator:
self.running = False
if self.thread:
self.thread.join(timeout=1.0)
sys.stdout.write("\r" + " " * (len(self.message) + 10) + "\r")
sys.stdout.write("\r" + " " * (len(self.message) + 20) + "\r")
sys.stdout.flush()
def _animate(self):
spinner = ["", "", "", "", "", "", "", "", "", ""]
idx = 0
while self.running:
sys.stdout.write(f"\r{spinner[idx]} {self.message}...")
elapsed = time.time() - self.start_time
sys.stdout.write(f"\r{spinner[idx]} {self.message}... ({elapsed:.1f}s)")
sys.stdout.flush()
idx = (idx + 1) % len(spinner)
time.sleep(0.1)