fix(notifications): event-driven TUI adapter, interrupt wait, queue drain - #36089
fix(notifications): event-driven TUI adapter, interrupt wait, queue drain#36089someaka wants to merge 1 commit into
Conversation
mxnstrexgl
left a comment
There was a problem hiding this comment.
LGTM — automated review passed. No security, quality, or test coverage issues detected.
e7e676e to
23ec095
Compare
…rain Three fixes for notification delivery and TUI input handling: 1. **Gateway: await interrupt** — session.interrupt now waits up to 5s for session['running'] to become False before returning. Previously it returned immediately while the agent was still running, causing prompt.submit to hit 'session busy'. 2. **TUI: await interrupt before send** — handleBusyInput now awaits the session.interrupt promise before calling send(). Previously it fired interrupt and send simultaneously, creating a race where the submit arrived before the gateway processed the cancel. 3. **TUI: drain queue on enqueue** — the drain useEffect now watches queuedDisplay.length so it re-fires when a message is enqueued after the drain already ran with an empty queue. Also includes: - Event-driven TUI platform adapter (HTTP POST, no polling) - Kanban→TUI notification bridge in tui_gateway - Deduplication of file watcher events and adapter subscriptions - Test fix for always-on TUI adapter in startup failure tests - session.interrupt added to _LONG_HANDLERS for thread pool dispatch
23ec095 to
e70d093
Compare
teknium1
left a comment
There was a problem hiding this comment.
Thanks for tracing the TUI queue and notification paths. A narrow current-main race is still worth salvaging: session.steer fallback enqueues asynchronously in ui-tui/src/app/useSubmission.ts:199-208, while the drain effect at ui-tui/src/app/useMainApp.ts:700-716 does not depend on queuedDisplay; the PR's dependency addition addresses that.
Problems
gateway/platforms/tui_adapter.py:78reports success even when_post_event()swallowed a missing-port or HTTP failure.gateway/kanban_watchers.py:415-470retries only exceptions, so this can silently lose a claimed notification.- Current main deliberately queues and uses
keepBusyto avoid racing a still-unwinding turn (ui-tui/src/app/turnController.ts:291-338), andprompt.submitnow queues a running session rather than rejecting it (tui_gateway/server.py:8447-8452). Replacing that path with send-after-interrupt would discard newer race handling.
Suggested changes
- Salvage the queue-effect dependency with a regression test covering async steer fallback after
busybecomes false. - Retain main's queue/settle protocol; make any adapter failure observable to the notifier.
Automated hermes-sweeper review.
| chat_id: str, | ||
| content: str, | ||
| reply_to: Optional[str] = None, | ||
| metadata: Optional[Dict[str, Any]] = None, |
There was a problem hiding this comment.
_post_event() catches missing-port and HTTP failures, so this always returns success even when nothing was delivered. The kanban watcher retries only thrown adapter.send() failures; propagate a failure/exception here so its cursor can be rewound instead of silently dropping the notification.
Three fixes for notification delivery and TUI input handling:
Changes
1. Gateway: await interrupt (tui_gateway/server.py)
session.interruptnow waits up to 5s forsession['running']to becomeFalsebefore returning. Previously it returned immediately while the agent was still running, causingprompt.submitto hit 'session busy'. Also added to_LONG_HANDLERSso the wait runs on the thread pool.2. TUI: await interrupt before send (ui-tui/src/app/useSubmission.ts)
handleBusyInputnow awaits thesession.interruptpromise before callingsend(). Previously it fired interrupt and send simultaneously, creating a race where the submit arrived before the gateway processed the cancel.3. TUI: drain queue on enqueue (ui-tui/src/app/useMainApp.ts)
The drain
useEffectnow watchesqueuedDisplay.lengthso it re-fires when a message is enqueued after the drain already ran with an empty queue.4. TUI: interruptTurn returns promise (ui-tui/src/app/turnController.ts)
interruptTurn()now returns the interrupt promise instead of fire-and-forget.Also includes
Testing