feat: implement interactive prompts for sudo password and command approval in CLI
- Added methods for handling sudo password and dangerous command approval prompts using a callback mechanism in cli.py. - Integrated these prompts with the prompt_toolkit UI for improved user experience. - Updated terminal_tool.py to support callback registration for interactive prompts, enhancing the CLI's interactivity. - Introduced a background thread for API calls in run_agent.py to allow for interrupt handling during long-running operations. - Enhanced error handling for interrupted API calls, ensuring graceful degradation of user experience.
This commit is contained in:
parent
ecb430effe
commit
c98ee98525
3 changed files with 409 additions and 39 deletions
45
run_agent.py
45
run_agent.py
|
|
@ -2285,6 +2285,35 @@ class AIAgent:
|
|||
if self._memory_store:
|
||||
self._memory_store.load_from_disk()
|
||||
|
||||
def _interruptible_api_call(self, api_kwargs: dict):
|
||||
"""
|
||||
Run the API call in a background thread so the main conversation loop
|
||||
can detect interrupts without waiting for the full HTTP round-trip.
|
||||
|
||||
Returns the API response, or raises InterruptedError if the agent was
|
||||
interrupted while waiting.
|
||||
"""
|
||||
result = {"response": None, "error": None}
|
||||
|
||||
def _call():
|
||||
try:
|
||||
result["response"] = self.client.chat.completions.create(**api_kwargs)
|
||||
except Exception as e:
|
||||
result["error"] = e
|
||||
|
||||
t = threading.Thread(target=_call, daemon=True)
|
||||
t.start()
|
||||
# Poll every 0.3s so interrupts are noticed quickly
|
||||
while t.is_alive():
|
||||
t.join(timeout=0.3)
|
||||
if self._interrupt_requested:
|
||||
# Can't cancel the HTTP request cleanly, but we can stop
|
||||
# waiting and let the thread finish in the background.
|
||||
raise InterruptedError("Agent interrupted during API call")
|
||||
if result["error"] is not None:
|
||||
raise result["error"]
|
||||
return result["response"]
|
||||
|
||||
def _build_api_kwargs(self, api_messages: list) -> dict:
|
||||
"""Build the keyword arguments dict for the chat completions API call."""
|
||||
provider_preferences = {}
|
||||
|
|
@ -2778,7 +2807,7 @@ class AIAgent:
|
|||
if os.getenv("HERMES_DUMP_REQUESTS", "").strip().lower() in {"1", "true", "yes", "on"}:
|
||||
self._dump_api_request_debug(api_kwargs, reason="preflight")
|
||||
|
||||
response = self.client.chat.completions.create(**api_kwargs)
|
||||
response = self._interruptible_api_call(api_kwargs)
|
||||
|
||||
api_duration = time.time() - api_start_time
|
||||
|
||||
|
|
@ -2935,6 +2964,16 @@ class AIAgent:
|
|||
|
||||
break # Success, exit retry loop
|
||||
|
||||
except InterruptedError:
|
||||
if thinking_spinner:
|
||||
thinking_spinner.stop("")
|
||||
thinking_spinner = None
|
||||
print(f"{self.log_prefix}⚡ Interrupted during API call.")
|
||||
self._flush_messages_to_session_db(messages, conversation_history)
|
||||
interrupted = True
|
||||
final_response = "Operation interrupted."
|
||||
break
|
||||
|
||||
except Exception as api_error:
|
||||
# Stop spinner before printing error messages
|
||||
if thinking_spinner:
|
||||
|
|
@ -3053,6 +3092,10 @@ class AIAgent:
|
|||
}
|
||||
time.sleep(0.2) # Check interrupt every 200ms
|
||||
|
||||
# If the API call was interrupted, skip response processing
|
||||
if interrupted:
|
||||
break
|
||||
|
||||
try:
|
||||
assistant_message = response.choices[0].message
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue