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 ## 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. 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] [project]
name = "rp" name = "rp"
version = "1.52.0" version = "1.53.0"
description = "R python edition. The ultimate autonomous AI CLI." description = "R python edition. The ultimate autonomous AI CLI."
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"

View File

@ -99,6 +99,9 @@ def run_autonomous_mode(assistant, task):
except KeyboardInterrupt: except KeyboardInterrupt:
logger.debug("Autonomous mode interrupted by user") logger.debug("Autonomous mode interrupted by user")
print(f"\n{Colors.YELLOW}Autonomous mode interrupted by user{Colors.RESET}") 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: finally:
assistant.autonomous_mode = False assistant.autonomous_mode = False
logger.debug("=== AUTONOMOUS MODE END ===") logger.debug("=== AUTONOMOUS MODE END ===")
@ -113,27 +116,29 @@ def process_response_autonomous(assistant, response):
assistant.messages.append(message) assistant.messages.append(message)
if "tool_calls" in message and message["tool_calls"]: if "tool_calls" in message and message["tool_calls"]:
tool_results = [] tool_results = []
with ProgressIndicator("Executing tools..."): for tool_call in message["tool_calls"]:
for tool_call in message["tool_calls"]: func_name = tool_call["function"]["name"]
func_name = tool_call["function"]["name"] arguments = json.loads(tool_call["function"]["arguments"])
arguments = json.loads(tool_call["function"]["arguments"]) args_str = ", ".join([f"{k}={repr(v)}" for k, v in arguments.items()])
result = execute_single_tool(assistant, func_name, arguments) if len(args_str) > 100:
if isinstance(result, str): args_str = args_str[:97] + "..."
try: print(f"{Colors.BLUE}⠋ Executing tools......{func_name}({args_str}){Colors.RESET}")
result = json.loads(result) result = execute_single_tool(assistant, func_name, arguments)
except json.JSONDecodeError as ex: if isinstance(result, str):
result = {"error": str(ex)} try:
status = "success" if result.get("status") == "success" else "error" result = json.loads(result)
result = truncate_tool_result(result) except json.JSONDecodeError as ex:
display_tool_call(func_name, arguments, status, result) result = {"error": str(ex)}
sanitized_result = sanitize_for_json(result) status = "success" if result.get("status") == "success" else "error"
tool_results.append( result = truncate_tool_result(result)
{ sanitized_result = sanitize_for_json(result)
"tool_call_id": tool_call["id"], tool_results.append(
"role": "tool", {
"content": json.dumps(sanitized_result), "tool_call_id": tool_call["id"],
} "role": "tool",
) "content": json.dumps(sanitized_result),
}
)
for result in tool_results: for result in tool_results:
assistant.messages.append(result) assistant.messages.append(result)
with ProgressIndicator("Processing tool results..."): with ProgressIndicator("Processing tool results..."):

View File

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

View File

@ -10,6 +10,7 @@ class ProgressIndicator:
self.show = show self.show = show
self.running = False self.running = False
self.thread = None self.thread = None
self.start_time = None
def __enter__(self): def __enter__(self):
if self.show: if self.show:
@ -21,6 +22,7 @@ class ProgressIndicator:
self.stop() self.stop()
def start(self): def start(self):
self.start_time = time.time()
self.running = True self.running = True
self.thread = threading.Thread(target=self._animate, daemon=True) self.thread = threading.Thread(target=self._animate, daemon=True)
self.thread.start() self.thread.start()
@ -30,14 +32,15 @@ class ProgressIndicator:
self.running = False self.running = False
if self.thread: if self.thread:
self.thread.join(timeout=1.0) 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() sys.stdout.flush()
def _animate(self): def _animate(self):
spinner = ["", "", "", "", "", "", "", "", "", ""] spinner = ["", "", "", "", "", "", "", "", "", ""]
idx = 0 idx = 0
while self.running: 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() sys.stdout.flush()
idx = (idx + 1) % len(spinner) idx = (idx + 1) % len(spinner)
time.sleep(0.1) time.sleep(0.1)