feat: enhance clarify tool with configurable timeout and countdown display
- Added a new configuration option for the clarify tool to set a custom timeout for user responses. - Updated the clarify callback to implement a countdown display during user interaction, improving user experience. - Refactored timeout handling to ensure the UI remains responsive and provides feedback on remaining time. - Enhanced hint text to include countdown information when clarify questions are active.
This commit is contained in:
parent
9350e26e68
commit
748f0b2b5f
1 changed files with 48 additions and 23 deletions
71
cli.py
71
cli.py
|
|
@ -132,6 +132,9 @@ def load_cli_config() -> Dict[str, Any]:
|
||||||
"display": {
|
"display": {
|
||||||
"compact": False,
|
"compact": False,
|
||||||
},
|
},
|
||||||
|
"clarify": {
|
||||||
|
"timeout": 120, # Seconds to wait for a clarify answer before auto-proceeding
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
# Track whether the config file explicitly set terminal config.
|
# Track whether the config file explicitly set terminal config.
|
||||||
|
|
@ -1446,19 +1449,18 @@ class HermesCLI:
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# How long to wait for the user to answer a clarify question before
|
|
||||||
# the agent auto-proceeds with its own judgment (seconds).
|
|
||||||
CLARIFY_TIMEOUT = 120
|
|
||||||
|
|
||||||
def _clarify_callback(self, question, choices):
|
def _clarify_callback(self, question, choices):
|
||||||
"""
|
"""
|
||||||
Platform callback for the clarify tool. Called from the agent thread.
|
Platform callback for the clarify tool. Called from the agent thread.
|
||||||
|
|
||||||
Sets up the interactive selection UI (or freetext prompt for open-ended
|
Sets up the interactive selection UI (or freetext prompt for open-ended
|
||||||
questions), then blocks until the user responds via the prompt_toolkit
|
questions), then blocks until the user responds via the prompt_toolkit
|
||||||
key bindings. If no response arrives within CLARIFY_TIMEOUT seconds the
|
key bindings. If no response arrives within the configured timeout the
|
||||||
question is dismissed and the agent is told to decide on its own.
|
question is dismissed and the agent is told to decide on its own.
|
||||||
"""
|
"""
|
||||||
|
import time as _time
|
||||||
|
|
||||||
|
timeout = CLI_CONFIG.get("clarify", {}).get("timeout", 120)
|
||||||
response_queue = queue.Queue()
|
response_queue = queue.Queue()
|
||||||
is_open_ended = not choices or len(choices) == 0
|
is_open_ended = not choices or len(choices) == 0
|
||||||
|
|
||||||
|
|
@ -1468,6 +1470,7 @@ class HermesCLI:
|
||||||
"selected": 0,
|
"selected": 0,
|
||||||
"response_queue": response_queue,
|
"response_queue": response_queue,
|
||||||
}
|
}
|
||||||
|
self._clarify_deadline = _time.monotonic() + timeout
|
||||||
# Open-ended questions skip straight to freetext input
|
# Open-ended questions skip straight to freetext input
|
||||||
self._clarify_freetext = is_open_ended
|
self._clarify_freetext = is_open_ended
|
||||||
|
|
||||||
|
|
@ -1475,21 +1478,32 @@ class HermesCLI:
|
||||||
if hasattr(self, '_app') and self._app:
|
if hasattr(self, '_app') and self._app:
|
||||||
self._app.invalidate()
|
self._app.invalidate()
|
||||||
|
|
||||||
# Block until the user answers, or time out so automated /
|
# Poll in 1-second ticks so the countdown refreshes in the UI.
|
||||||
# unattended sessions aren't stuck forever.
|
# Each tick triggers an invalidate() to repaint the hint line.
|
||||||
try:
|
while True:
|
||||||
return response_queue.get(timeout=self.CLARIFY_TIMEOUT)
|
try:
|
||||||
except queue.Empty:
|
result = response_queue.get(timeout=1)
|
||||||
# Timed out — tear down the UI and let the agent decide
|
self._clarify_deadline = 0
|
||||||
self._clarify_state = None
|
return result
|
||||||
self._clarify_freetext = False
|
except queue.Empty:
|
||||||
if hasattr(self, '_app') and self._app:
|
remaining = self._clarify_deadline - _time.monotonic()
|
||||||
self._app.invalidate()
|
if remaining <= 0:
|
||||||
_cprint(f"\n{_DIM}(clarify timed out after {self.CLARIFY_TIMEOUT}s — agent will decide){_RST}")
|
break
|
||||||
return (
|
# Repaint so the countdown updates
|
||||||
"The user did not provide a response within the time limit. "
|
if hasattr(self, '_app') and self._app:
|
||||||
"Use your best judgement to make the choice and proceed."
|
self._app.invalidate()
|
||||||
)
|
|
||||||
|
# Timed out — tear down the UI and let the agent decide
|
||||||
|
self._clarify_state = None
|
||||||
|
self._clarify_freetext = False
|
||||||
|
self._clarify_deadline = 0
|
||||||
|
if hasattr(self, '_app') and self._app:
|
||||||
|
self._app.invalidate()
|
||||||
|
_cprint(f"\n{_DIM}(clarify timed out after {timeout}s — agent will decide){_RST}")
|
||||||
|
return (
|
||||||
|
"The user did not provide a response within the time limit. "
|
||||||
|
"Use your best judgement to make the choice and proceed."
|
||||||
|
)
|
||||||
|
|
||||||
def chat(self, message: str) -> Optional[str]:
|
def chat(self, message: str) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
|
|
@ -1628,6 +1642,7 @@ class HermesCLI:
|
||||||
# the prompt_toolkit UI switches to a selection mode.
|
# the prompt_toolkit UI switches to a selection mode.
|
||||||
self._clarify_state = None # dict with question, choices, selected, response_queue
|
self._clarify_state = None # dict with question, choices, selected, response_queue
|
||||||
self._clarify_freetext = False # True when user chose "Other" and is typing
|
self._clarify_freetext = False # True when user chose "Other" and is typing
|
||||||
|
self._clarify_deadline = 0 # monotonic timestamp when the clarify times out
|
||||||
|
|
||||||
# Key bindings for the input area
|
# Key bindings for the input area
|
||||||
kb = KeyBindings()
|
kb = KeyBindings()
|
||||||
|
|
@ -1805,11 +1820,20 @@ class HermesCLI:
|
||||||
def get_hint_text():
|
def get_hint_text():
|
||||||
if not cli_ref._agent_running:
|
if not cli_ref._agent_running:
|
||||||
return []
|
return []
|
||||||
# When clarify is active, show a different hint
|
# When clarify is active, show a different hint with countdown
|
||||||
if cli_ref._clarify_state:
|
if cli_ref._clarify_state:
|
||||||
|
import time as _time
|
||||||
|
remaining = max(0, int(cli_ref._clarify_deadline - _time.monotonic()))
|
||||||
|
countdown = f' ({remaining}s)' if cli_ref._clarify_deadline else ''
|
||||||
if cli_ref._clarify_freetext:
|
if cli_ref._clarify_freetext:
|
||||||
return [('class:hint', ' type your answer and press Enter')]
|
return [
|
||||||
return [('class:hint', ' ↑/↓ to select, Enter to confirm')]
|
('class:hint', ' type your answer and press Enter'),
|
||||||
|
('class:clarify-countdown', countdown),
|
||||||
|
]
|
||||||
|
return [
|
||||||
|
('class:hint', ' ↑/↓ to select, Enter to confirm'),
|
||||||
|
('class:clarify-countdown', countdown),
|
||||||
|
]
|
||||||
buf = input_area.buffer
|
buf = input_area.buffer
|
||||||
if buf.text:
|
if buf.text:
|
||||||
return []
|
return []
|
||||||
|
|
@ -1933,6 +1957,7 @@ class HermesCLI:
|
||||||
'clarify-choice': '#AAAAAA',
|
'clarify-choice': '#AAAAAA',
|
||||||
'clarify-selected': '#FFD700 bold',
|
'clarify-selected': '#FFD700 bold',
|
||||||
'clarify-active-other': '#FFD700 italic',
|
'clarify-active-other': '#FFD700 italic',
|
||||||
|
'clarify-countdown': '#CD7F32',
|
||||||
})
|
})
|
||||||
|
|
||||||
# Create the application
|
# Create the application
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue