diff --git a/agent.py b/agent.py index 267a90807..e875c1269 100644 --- a/agent.py +++ b/agent.py @@ -300,6 +300,11 @@ class AgentContext: return response except Exception as e: await self.handle_exception("process_chain", e) + finally: + # Push updated running state to ALL connections when agent finishes. + # Without this, non-active tabs don't see running:false until they click. + from helpers.state_monitor_integration import mark_dirty_all + mark_dirty_all(reason="agent.AgentContext._process_chain completed") @extension.extensible async def handle_exception(self, location: str, exception: Exception): diff --git a/webui/components/chat/message-queue/message-queue-store.js b/webui/components/chat/message-queue/message-queue-store.js index 9c83a50ba..ac556278b 100644 --- a/webui/components/chat/message-queue/message-queue-store.js +++ b/webui/components/chat/message-queue/message-queue-store.js @@ -47,8 +47,15 @@ const model = { }, // Combined items for display: confirmed first, then pending at the end + // Deduplicate by id to prevent Alpine "Duplicate key" errors during + // the race between server poll updates and pending item cleanup. get allItems() { - return [...this.items, ...this.pendingItems]; + const seen = new Set(); + return [...this.items, ...this.pendingItems].filter((item) => { + if (seen.has(item.id)) return false; + seen.add(item.id); + return true; + }); }, async addToQueue(text, attachments = []) { diff --git a/webui/components/sidebar/chats/chats-list.html b/webui/components/sidebar/chats/chats-list.html index 2c13755fc..e11317c84 100644 --- a/webui/components/sidebar/chats/chats-list.html +++ b/webui/components/sidebar/chats/chats-list.html @@ -24,7 +24,7 @@