Polish the update notification action

Present Open updater as the existing confirm-style button on its own toast row. Opening the Self Update modal now dismisses the persistent notification so users do not need to close it separately.\n\nKeep external update-instruction links unchanged and extend the persistent-toast regression coverage.

Alessandro committed Jul 12, 2026 at 17:10 UTC 831bed7a0cc39ab8a7c19a388d06357374fe3c46
4 files changed +23 -2
extensions/python/user_message_ui/AGENTS.md
+1 -1
@@ -11,7 +11,7 @@
11 ## Local Contracts
12
13 - Keep proactive UI messages relevant, non-spammy, and safe for display.
14 -- Keep update-available notifications visible until the user dismisses them.
14 +- Keep update-available notifications visible until the user dismisses them or opens the updater.
15 - Do not expose local diagnostics or update data that should stay internal.
16
17 ## Work Guidance
extensions/python/user_message_ui/_10_update_check.py
+13 -1
@@ -98,10 +98,22 @@ class UpdateCheck(Extension):
98 if not self.agent:
99 return
100
101 + message = notif.get(
102 + "message",
103 + "A newer version of Agent Zero is available. Please update to the latest version.",
104 + )
105 + message = message.replace(
106 + '<a href="#" @click.prevent="$store.selfUpdateStore.openModal()">Open updater</a>.',
107 + '<div class="toast-action-row">'
108 + '<button type="button" class="button confirm" '
109 + '@click="$store.selfUpdateStore.openModal(); '
110 + '$store.notificationStore.dismissToast(toast.toastId)">'
111 + "Open updater</button></div>",
112 + )
113 notifs = self.agent.context.get_notification_manager()
114 notifs.send_notification(
115 title=notif.get("title", "Newer version available"),
104 - message=notif.get("message", "A newer version of Agent Zero is available. Please update to the latest version."),
116 + message=message,
117 type=notif.get("type", "info"),
118 detail=notif.get("detail", ""),
119 display_time=0,
tests/test_download_toast_regressions.py
+5
@@ -88,6 +88,7 @@ def previous_non_space(source: str, index: int) -> str:
88
89 def test_notification_store_supports_persistent_grouped_toasts():
90 store = read("webui", "components", "notifications", "notification-store.js")
91 + toast_stack = read("webui", "components", "notifications", "notification-toast-stack.html")
92 api = read("api", "notification_create.py")
93 plugins = read("helpers", "plugins.py")
94 update_check = read("extensions", "python", "user_message_ui", "_10_update_check.py")
@@ -103,6 +104,10 @@ def test_notification_store_supports_persistent_grouped_toasts():
104 assert 'id=notif.get("id", "update_check_available"),' in update_check
105 assert "display_time=0," in plugins
106 assert "display_time=0," in update_check
107 + assert 'class="toast-action-row"' in update_check
108 + assert 'class="button confirm"' in update_check
109 + assert "$store.notificationStore.dismissToast(toast.toastId)" in update_check
110 + assert ".toast-action-row" in toast_stack
111
112
113 def test_backup_zip_downloads_emit_grouped_preparing_and_downloading_toasts():
webui/components/notifications/notification-toast-stack.html
+4
@@ -150,6 +150,10 @@
150 margin-bottom: 4px;
151 }
152
153 + .toast-action-row {
154 + margin-top: 10px;
155 + }
156 +
157 .toast-timestamp {
158 font-size: 0.75rem;
159 color: var(--color-text);