fix: honour PreferStructuredCategories in notification pipeline
When PreferStructuredCategories is false, classification skips structured metadata (Intent, Channel) and goes straight to user rules + keyword fallback. 5 new tests. 521 shared tests pass. Reimplements #104 on current master. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
parent
1d836390e9
commit
344461d30d
@ -51,16 +51,22 @@ public class NotificationCategorizer
|
||||
|
||||
/// <summary>
|
||||
/// Classify a notification using the layered pipeline.
|
||||
/// When <paramref name="preferStructuredCategories"/> is true (default),
|
||||
/// structured metadata (Intent, Channel) is checked first.
|
||||
/// When false, classification starts from user-defined rules then keyword fallback.
|
||||
/// </summary>
|
||||
public (string title, string type) Classify(OpenClawNotification notification, IReadOnlyList<UserNotificationRule>? userRules = null)
|
||||
public (string title, string type) Classify(OpenClawNotification notification, IReadOnlyList<UserNotificationRule>? userRules = null, bool preferStructuredCategories = true)
|
||||
{
|
||||
// 1. Structured metadata: Intent
|
||||
if (!string.IsNullOrEmpty(notification.Intent) && IntentMap.TryGetValue(notification.Intent, out var intentResult))
|
||||
return intentResult;
|
||||
if (preferStructuredCategories)
|
||||
{
|
||||
// 1. Structured metadata: Intent
|
||||
if (!string.IsNullOrEmpty(notification.Intent) && IntentMap.TryGetValue(notification.Intent, out var intentResult))
|
||||
return intentResult;
|
||||
|
||||
// 2. Structured metadata: Channel
|
||||
if (!string.IsNullOrEmpty(notification.Channel) && ChannelMap.TryGetValue(notification.Channel, out var channelResult))
|
||||
return channelResult;
|
||||
// 2. Structured metadata: Channel
|
||||
if (!string.IsNullOrEmpty(notification.Channel) && ChannelMap.TryGetValue(notification.Channel, out var channelResult))
|
||||
return channelResult;
|
||||
}
|
||||
|
||||
// 3. User-defined rules (pattern match on title + message)
|
||||
if (userRules is { Count: > 0 })
|
||||
|
||||
@ -60,6 +60,13 @@ public class OpenClawGatewayClient : WebSocketClientBase
|
||||
private bool _operatorReadScopeUnavailable;
|
||||
private bool _pairingRequiredAwaitingApproval;
|
||||
private IReadOnlyList<UserNotificationRule>? _userRules;
|
||||
private bool _preferStructuredCategories = true;
|
||||
|
||||
/// <summary>
|
||||
/// Controls whether structured notification metadata (Intent, Channel) takes priority
|
||||
/// over keyword-based classification. Call after construction and whenever settings change.
|
||||
/// </summary>
|
||||
public void SetPreferStructuredCategories(bool value) => _preferStructuredCategories = value;
|
||||
|
||||
private void ResetUnsupportedMethodFlags()
|
||||
{
|
||||
@ -2005,7 +2012,7 @@ public class OpenClawGatewayClient : WebSocketClientBase
|
||||
{
|
||||
Message = text.Length > 200 ? text[..200] + "…" : text
|
||||
};
|
||||
var (title, type) = _categorizer.Classify(notification, _userRules);
|
||||
var (title, type) = _categorizer.Classify(notification, _userRules, _preferStructuredCategories);
|
||||
notification.Title = title;
|
||||
notification.Type = type;
|
||||
NotificationReceived?.Invoke(this, notification);
|
||||
|
||||
@ -1103,6 +1103,7 @@ public partial class App : Application
|
||||
|
||||
_gatewayClient = new OpenClawGatewayClient(_settings.GetEffectiveGatewayUrl(), _settings.Token, new AppLogger());
|
||||
_gatewayClient.SetUserRules(_settings.UserRules.Count > 0 ? _settings.UserRules : null);
|
||||
_gatewayClient.SetPreferStructuredCategories(_settings.PreferStructuredCategories);
|
||||
_gatewayClient.StatusChanged += OnConnectionStatusChanged;
|
||||
_gatewayClient.ActivityChanged += OnActivityChanged;
|
||||
_gatewayClient.NotificationReceived += OnNotificationReceived;
|
||||
|
||||
@ -272,6 +272,52 @@ public class NotificationCategorizerTests
|
||||
Assert.Equal("health", _categorizer.Classify(notification).type);
|
||||
}
|
||||
|
||||
// --- PreferStructuredCategories = false ---
|
||||
|
||||
[Fact]
|
||||
public void PreferStructuredCategories_False_SkipsIntent()
|
||||
{
|
||||
var notification = new OpenClawNotification { Message = "New email notification", Intent = "build" };
|
||||
var (_, type) = _categorizer.Classify(notification, preferStructuredCategories: false);
|
||||
Assert.Equal("email", type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreferStructuredCategories_False_SkipsChannel()
|
||||
{
|
||||
var notification = new OpenClawNotification { Message = "Check your email", Channel = "calendar" };
|
||||
var (_, type) = _categorizer.Classify(notification, preferStructuredCategories: false);
|
||||
Assert.Equal("email", type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreferStructuredCategories_False_UserRulesStillApply()
|
||||
{
|
||||
var rules = new List<UserNotificationRule>
|
||||
{
|
||||
new() { Pattern = "invoice", Category = "email", Enabled = true }
|
||||
};
|
||||
var notification = new OpenClawNotification { Message = "New invoice received", Intent = "urgent" };
|
||||
var (_, type) = _categorizer.Classify(notification, rules, preferStructuredCategories: false);
|
||||
Assert.Equal("email", type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreferStructuredCategories_False_FallsBackToKeywords()
|
||||
{
|
||||
var notification = new OpenClawNotification { Message = "Hello world", Intent = "build", Channel = "email" };
|
||||
var (_, type) = _categorizer.Classify(notification, preferStructuredCategories: false);
|
||||
Assert.Equal("info", type);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void PreferStructuredCategories_True_Default_BehaviourUnchanged()
|
||||
{
|
||||
var notification = new OpenClawNotification { Message = "New email notification", Intent = "build" };
|
||||
Assert.Equal("build", _categorizer.Classify(notification).type);
|
||||
Assert.Equal("build", _categorizer.Classify(notification, preferStructuredCategories: true).type);
|
||||
}
|
||||
|
||||
// --- ClassifyByKeywords static method ---
|
||||
|
||||
[Fact]
|
||||
|
||||
Loading…
Reference in New Issue
Block a user