Keep action-required notifications visible

Make plugin frontend-reload and Agent Zero update notifications persistent until the user dismisses or acts on them. Reuse the existing zero-duration toast contract without changing ordinary notification timeouts.\n\nDocument the behavior and extend the persistent-toast regression coverage.

Alessandro committed Jul 12, 2026 at 16:55 UTC a55a76f62b9dbc22573b8697d53cb47e1bf731be
5 files changed +10 -3
extensions/python/user_message_ui/AGENTS.md
+1
@@ -11,6 +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.
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
+1 -1
@@ -104,7 +104,7 @@ class UpdateCheck(Extension):
104 message=notif.get("message", "A newer version of Agent Zero is available. Please update to the latest version."),
105 type=notif.get("type", "info"),
106 detail=notif.get("detail", ""),
107 - display_time=notif.get("display_time", 10),
107 + display_time=0,
108 group=notif.get("group", "update_check"),
109 priority=notif.get("priority", notification.NotificationPriority.NORMAL),
110 id=notif.get("id", "update_check_available"),
helpers/plugins.py
+1 -1
@@ -842,7 +842,7 @@ def send_frontend_reload_notification(plugin_names: list[str] | None = None):
842 title="Plugins with frontend extensions updated, page reload recommended",
843 message="""<button type="button" class="button confirm" onclick="window.location.reload()"><span class="icon material-symbols-outlined">refresh</span>Reload page</button>""",
844 detail="",
845 - display_time=display_time,
845 + display_time=0,
846 group="plugins_changed",
847 id="plugins_frontend_reload",
848 )
helpers/plugins.py.dox.md
+1 -1
@@ -40,7 +40,7 @@
40 - `find_plugin_asset(plugin_name: str, *subpaths, project_name=..., agent_profile=...)`
41 - `find_plugin_assets(*subpaths, plugin_name: str=..., project_name: str=..., agent_profile: str=..., only_first: bool=...) -> list[PluginAssetFile]`
42 - `determine_plugin_asset_path(plugin_name: str, project_name: str, agent_profile: str, *subpaths)`
43 -- `send_frontend_reload_notification(plugin_names: list[str] | None=...)`: If the plugin changed has webui extensions, notify frontend to reload the page
43 +- `send_frontend_reload_notification(plugin_names: list[str] | None=...)`: If the plugin changed has webui extensions, show a persistent reload notification
44 - `call_plugin_hook(plugin_name: str, hook_name: str, default: Any=..., *args, **kwargs)`
45 - `_apply_defaults_from_env(plugin_name: str, config: dict[str, Any])`
46 - Notable constants/configuration names: `_META_TARGET_RE`, `META_FILE_NAME`, `CONFIG_FILE_NAME`, `CONFIG_DEFAULT_FILE_NAME`, `DISABLED_FILE_NAME`, `ENABLED_FILE_NAME`, `TOGGLE_FILE_PATTERN`, `HOOKS_SCRIPT`, `HOOKS_CACHE_AREA`, `PLUGINS_LIST_CACHE_AREA`, `ENABLED_PLUGINS_LIST_CACHE_AREA`, `ENABLED_PLUGINS_PATHS_CACHE_AREA`.
tests/test_download_toast_regressions.py
+6
@@ -89,6 +89,8 @@ def previous_non_space(source: str, index: int) -> str:
89 def test_notification_store_supports_persistent_grouped_toasts():
90 store = read("webui", "components", "notifications", "notification-store.js")
91 api = read("api", "notification_create.py")
92 + plugins = read("helpers", "plugins.py")
93 + update_check = read("extensions", "python", "user_message_ui", "_10_update_check.py")
94
95 assert "isPersistentToast(toast)" in store
96 assert "return this.getToastDisplayTime(toast) <= 0;" in store
@@ -97,6 +99,10 @@ def test_notification_store_supports_persistent_grouped_toasts():
99 assert "this.removeFromToastStack(existingToast.toastId);" in store
100 assert "if display_time < 0:" in api
101 assert "if display_time <= 0:" not in api
102 + assert 'id="plugins_frontend_reload",' in plugins
103 + assert 'id=notif.get("id", "update_check_available"),' in update_check
104 + assert "display_time=0," in plugins
105 + assert "display_time=0," in update_check
106
107
108 def test_backup_zip_downloads_emit_grouped_preparing_and_downloading_toasts():