fix: surface approval prompt in UI instead of getting stuck in Thinking (#187)
* fix: surface approval prompt in UI instead of getting stuck in Thinking
When a dangerous command was detected during streaming, the approval system
would call submit_pending() but no SSE 'approval' event would be emitted to
the frontend. The agent thread either blocked indefinitely (gateway path) or
returned an approval_required status the UI never saw (EXEC_ASK path). Either
way the chat UI stayed stuck in 'Thinking...' with no prompt shown.
Root cause: streaming.py used HERMES_EXEC_ASK=1 but never registered a
register_gateway_notify() callback. Without it, check_all_command_guards()
fell back to the legacy polling path (submit_pending only), which relies on
on_tool() polling -- but on_tool() fires *before* the tool runs, so by the
time the terminal tool detected the dangerous command and called submit_pending,
the approval event had already missed its window.
Fix (streaming.py):
- Register a gateway-style notify_cb via register_gateway_notify() before the
agent runs. The callback calls put('approval', ...) to emit the SSE event
the moment a dangerous command is detected, regardless of on_tool() timing.
- Unregister via unregister_gateway_notify() in the finally block to unblock
any threads still waiting if the stream ends or is cancelled mid-approval.
- Keep the on_tool() fallback poll for older approval module versions.
Fix (routes.py):
- Import and call resolve_gateway_approval() in _handle_approval_respond().
This unblocks the agent thread parked in entry.event.wait() when the user
clicks Allow or Deny in the UI. Without this call the thread would block
until the 5-minute gateway timeout.
Tests (tests/test_approval_unblock.py):
- 16 new tests covering: resolve_gateway_approval() event signalling, deny/
session/once choices, resolve_all, notify_cb registration/firing/cleanup,
unregister signals blocked entries, full end-to-end streaming simulation,
module symbol exports, and HTTP endpoint regressions.
515 tests pass (499 existing + 16 new).
* feat: full approval UI — i18n buttons, keyboard shortcut, loading state, scoping fix
---------
Co-authored-by: Nathan Esquenazi <nesquena@gmail.com>
This commit is contained in:
@@ -219,6 +219,17 @@ $('msg').addEventListener('keydown',e=>{
|
||||
});
|
||||
// B14: Cmd/Ctrl+K creates a new chat from anywhere
|
||||
document.addEventListener('keydown',async e=>{
|
||||
// Enter on approval card = Allow once (when a button inside the card is focused or
|
||||
// card is visible and focus is not on an input/textarea/select)
|
||||
if(e.key==='Enter'&&!e.metaKey&&!e.ctrlKey&&!e.shiftKey){
|
||||
const card=$('approvalCard');
|
||||
const tag=(document.activeElement||{}).tagName||'';
|
||||
if(card&&card.classList.contains('visible')&&tag!=='TEXTAREA'&&tag!=='INPUT'&&tag!=='SELECT'){
|
||||
e.preventDefault();
|
||||
if(typeof respondApproval==='function') respondApproval('once');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if((e.metaKey||e.ctrlKey)&&e.key==='k'){
|
||||
e.preventDefault();
|
||||
if(!S.busy){await newSession();await renderSessionList();$('msg').focus();}
|
||||
|
||||
Reference in New Issue
Block a user