fix(node): route capture/record toasts through App.ShowToast - #378
Conversation
NodeService.ShowToast called ToastContentBuilder.Show() directly, bypassing the user's sound preference (None/Subtle/Default) and the 30-second deduplication window implemented in App.ShowToast. Replace the private helper with a ToastRequested event; App subscribes and delegates to its own ShowToast, so sound and dedup are honoured for all screen-capture, screen-record, and camera toasts. Fixes openclaw#342. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Good direction routing these through
Suggested fix: keep notification failures non-fatal at the app boundary: private void OnNodeToastRequested(object? sender, ToastContentBuilder builder)
{
try
{
ShowToast(builder);
}
catch (Exception ex)
{
Logger.Warn($"Failed to show node toast: {ex.Message}");
}
}A small unit seam/test would be ideal, but the main requirement is that toast delivery failures must not change node command results. |
A toast-platform exception propagating through ToastRequested?.Invoke could fail screen.snapshot, replace the intended camera error, or mask the original capture/record exception. Wrap ShowToast in a try/catch so notification failures are logged but never change node command results. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extracts a NonFatalAction.Run helper to OpenClaw.Shared so the try/catch behaviour in OnNodeToastRequested is independently testable without instantiating App or NodeService. Three unit tests cover: exception not propagated, error message forwarded to onError callback, and success path leaves onError uncalled. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
ToastRequested fires from async node workers (screen capture/record, camera blocked) on the thread pool. App.ShowToast reads and modifies _recentToastKeys (a plain Dictionary) without synchronisation, which races with UI-thread toasts and can corrupt dedup state or throw. Re-queue through _dispatcherQueue so ShowToast always runs on the UI thread, consistent with how OnSystemNotify already handles the same pattern via NodeService._dispatcherQueue.TryEnqueue. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Added the try/catch so toast failures are non-fatal. Also fixed a thread-safety gap: the capture/record handlers fire |
Summary
NodeServicehad its ownShowToasthelper that calledToastContentBuilder.Show()directly. This bypassed the sound preference (None/Subtle/Default) and the 30-second deduplication window both implemented inApp.ShowToast.All screen-capture, screen-record, and camera toasts were affected — including toasts that fire on attacker-controllable triggers (
screen.snapshotthrottled once per 10 s).Changes
NodeService.ShowToastNodeService.ToastRequestedevent (EventHandler<ToastContentBuilder>)ToastRequested?.InvokeAppand delegate toApp.ShowToast, so sound preferences and dedup are honouredFixes #342.
Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com