Notification Categorization and Toast Prioritization in openclaw-windows-node
OpenClaw processes every notification through a deterministic five-step categorization pipeline in NotificationCategorizer.cs, then prioritizes Windows toast display via time-based deduplication, per-device idempotency checks, and user-configurable sound settings in ToastService.cs to prevent notification spam.
The openclaw-windows-node repository implements a sophisticated notification management architecture that separates what a message represents from when it appears to the user. Understanding the interaction between the notification categorization system and toast notification prioritization logic allows developers to customize alert routing and ensure critical health or system events surface above routine chatter.
How the Notification Categorization System Works
The categorization engine lives in src/OpenClaw.Shared/NotificationCategorizer.cs. The Classify() method evaluates every OpenClawNotification through a strict hierarchical pipeline, returning a display title and semantic type tuple.
Structured Metadata Processing
The pipeline first inspects high-confidence metadata fields. It checks notification.Intent (e.g., “health”, “urgent”) against an immutable IntentMap, then falls through to notification.Channel (e.g., “calendar”, “email”) against ChannelMap. These maps are implemented as FrozenDictionary instances, providing O(1) case-insensitive lookups with zero allocation per call.
User-Defined Rules and Safety
If structured metadata fails to match, the system evaluates a list of UserNotificationRule objects supplied by the user. Each rule contains a pattern string (plain text or regex), a target category, and an enable flag. To protect against ReDoS attacks, regex patterns are cached in _regexCache with a hard 100 ms timeout per execution.
Keyword Fallback Mechanism
When no user rules match, the scanner invokes ClassifyByKeywords(), a legacy method that searches the concatenated title and message for hardcoded strings such as “blood sugar”, “urgent”, or “stock”. If all steps fail, the method returns the default "🤖 OpenClaw" title with type "info".
Developers can force the pipeline to ignore structured metadata by setting preferStructuredCategories = false, useful for backward-compatible code paths.
Toast Notification Prioritization
Display logic resides in src/OpenClaw.Tray.WinUI/Services/ToastService.cs. Before calling builder.Show(), the service applies two layers of deduplication and respects user preferences to determine final priority.
Deduplication Windows
The service maintains a private Dictionary<string, DateTime> called _recentToastKeys. Each key combines a toastTag with a normalized device ID generated by BuildToastKey(). If an identical key was shown within the ToastDedupeWindow (30 seconds), the new toast is suppressed entirely. This prevents rapid-fire spam from noisy sources while allowing legitimate follow-ups after the window expires.
Device-Level Idempotency
For lifecycle events like pairing, the service uses a HashSet<string> named _shownPairedToasts to track device IDs. The HasShownPairedToast() and MarkPairedToastShown() methods guarantee that "Node paired" toasts appear exactly once per device, surviving Bluetooth reconnects and application restarts without redundant alerts.
Sound Configuration and Thread Safety
The service queries SettingsManager.NotificationSound to determine audio priority. When set to "None", the toast renders silently; "Subtle" plays a system-provided sound; otherwise the default alert plays. All mutable state—including _recentToastKeys and _shownPairedToasts—is guarded by a private _gate lock, ensuring deterministic behavior during multi-threaded bursts of notifications.
Practical Implementation Examples
Classifying a Health Alert
var categorizer = new NotificationCategorizer();
var notif = new OpenClawNotification
{
Message = "Your blood sugar is high",
Intent = null,
Channel = null
};
(string title, string type) = categorizer.Classify(notif);
// title => "🩸 Blood Sugar Alert"
// type => "health"
Adding a Custom User Rule
var rule = new UserNotificationRule
{
Enabled = true,
IsRegex = false,
Pattern = "invoice",
Category = "stock"
};
var userRules = new List<UserNotificationRule> { rule };
var notif = new OpenClawNotification { Message = "New invoice received" };
(string title, string type) = categorizer.Classify(notif, userRules);
// title => "📦 Stock Alert"
// type => "stock"
Showing a Deduplicated Toast
var toastService = new ToastService(() => settingsManager);
var builder = new ToastContentBuilder()
.AddText("Node paired")
.AddText("Your device is now connected");
// The tag “node-paired” combined with deviceId is deduped for 30 seconds.
toastService.ShowToast(builder, toastTag: "node-paired", deviceId: deviceId);
Routing Toast Activation
ToastActivationRouter.Route(
action: args.Action,
getArgument: args.GetArgument,
new ToastActivationActions
{
OpenUrl = url => Process.Start(new ProcessStartInfo(url) { UseShellExecute = true }),
OpenDashboard = () => dashboard.Show(),
OpenSettings = () => settings.Show(),
OpenChat = () => chat.Open(),
OpenActivity = () => activity.Show(),
CopyPairingCommand = cmd => Clipboard.SetText(cmd)
});
Summary
- The notification categorization system uses a five-stage pipeline (
Intent→Channel→User Rules→Keywords→Default) implemented inNotificationCategorizer.csto assign semantic types and display titles. - FrozenDictionary instances and cached regexes with 100 ms timeouts ensure O(1) classification performance without memory pressure or ReDoS vulnerabilities.
- Toast notification prioritization in
ToastService.csrelies on a 30-second sliding window deduplicator and a per-device paired-toast registry to eliminate redundant alerts. - Sound configuration is user-customizable via
SettingsManager.NotificationSound, with all state mutations protected by a thread-safe_gatelock.
Frequently Asked Questions
How does openclaw-windows-node prevent duplicate toast notifications?
The system employs a 30-second deduplication window tracked in _recentToastKeys, where each entry combines a toast tag and device ID. Identical keys within this window are suppressed. Additionally, lifecycle events like "Node paired" use a separate _shownPairedToasts HashSet to ensure exactly one display per device regardless of reconnects.
What data structures optimize notification categorization performance?
The categorizer uses FrozenDictionary implementations for IntentMap, ChannelMap, and CategoryTypeMap, providing immutable, case-insensitive O(1) lookups. User-defined regex patterns are compiled and cached with a 100 ms execution timeout to prevent catastrophic backtracking while maintaining throughput.
Can users override automatic notification categories?
Yes. Developers can pass a List<UserNotificationRule> to the Classify() method, allowing custom regex or plaintext patterns to match against notification content. Each rule specifies a target category, and the system evaluates these before falling back to built-in keyword scanning.
How does the toast service handle concurrent notification requests?
All mutable state in ToastService.cs—including the deduplication dictionary and paired-device registry—is guarded by a private _gate object. This lock ensures thread-safe access when multiple threads simultaneously attempt to show toasts, preventing race conditions in the deduplication logic.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →