Skip to content

Commit 43d40ed

Browse files
authored
fix: preserve full chat text for Tray TTS (#900)
Keep notification previews compact while retaining the complete assistant response for Tray text-to-speech. Add legacy/null/empty fallback coverage at the Tray ownership boundary. Thanks @ArtLupo for the contribution. Co-authored-by: Master <artlupo3689@gmail.com>
1 parent fcc7477 commit 43d40ed

7 files changed

Lines changed: 87 additions & 1 deletion

File tree

src/OpenClaw.Shared/Models.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ public class OpenClawNotification
9898
{
9999
public string Title { get; set; } = "";
100100
public string Message { get; set; } = "";
101+
public string? FullMessage { get; set; }
101102
public string Type { get; set; } = "";
102103
public bool IsChat { get; set; } = false; // True if from chat response
103104

src/OpenClaw.Shared/OpenClawGatewayClient.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3248,6 +3248,7 @@ private void EmitChatNotification(string text, string? sessionKey = null)
32483248
var notification = new OpenClawNotification
32493249
{
32503250
Message = displayText,
3251+
FullMessage = text,
32513252
IsChat = true,
32523253
SessionKey = sessionKey
32533254
};

src/OpenClaw.Tray.WinUI/App.xaml.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2747,6 +2747,8 @@ private void OnGatewayNotificationReceived(object? sender, OpenClawNotification
27472747
// if the user enabled "Read responses aloud".
27482748
if (notification.IsChat && !string.IsNullOrEmpty(notification.Message))
27492749
{
2750+
var speechText = ChatNotificationSpeechText.Resolve(notification);
2751+
27502752
// Suppress TTS/voice overlay when the user has aborted the response.
27512753
if (ChatProvider?.IsResponseSuppressed == true)
27522754
return;
@@ -2767,7 +2769,7 @@ private void OnGatewayNotificationReceived(object? sender, OpenClawNotification
27672769
// TTS: read response aloud whenever the toggle is on (any chat surface).
27682770
if (_settings?.VoiceTtsEnabled == true)
27692771
{
2770-
_ = (_chatCoordinator?.SpeakResponseAsync(notification.Message) ?? Task.CompletedTask);
2772+
_ = (_chatCoordinator?.SpeakResponseAsync(speechText) ?? Task.CompletedTask);
27712773
}
27722774
}
27732775

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using OpenClaw.Shared;
2+
3+
namespace OpenClawTray.Services;
4+
5+
internal static class ChatNotificationSpeechText
6+
{
7+
public static string Resolve(OpenClawNotification notification)
8+
{
9+
ArgumentNullException.ThrowIfNull(notification);
10+
return string.IsNullOrEmpty(notification.FullMessage)
11+
? notification.Message
12+
: notification.FullMessage;
13+
}
14+
}

tests/OpenClaw.Shared.Tests/OpenClawGatewayClientTests.cs

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -990,6 +990,37 @@ public void ProcessRawMessage_SessionMessageAssistantNotification_DependsOnFinal
990990
}
991991
}
992992

993+
[Fact]
994+
public void ProcessRawMessage_SessionMessageAssistantNotification_PreservesFullMessage()
995+
{
996+
var helper = new GatewayClientTestHelper();
997+
OpenClawNotification? notification = null;
998+
helper.Client.NotificationReceived += (_, value) => notification = value;
999+
1000+
var fullMessage = new string('x', 240);
1001+
1002+
helper.ProcessRawMessage($$"""
1003+
{
1004+
"type": "event",
1005+
"event": "session.message",
1006+
"payload": {
1007+
"sessionKey": "agent:main:whatsapp:direct:+15551234567",
1008+
"message": {
1009+
"role": "assistant",
1010+
"content": "{{fullMessage}}",
1011+
"timestamp": 1781631280633
1012+
},
1013+
"state": "final"
1014+
}
1015+
}
1016+
""");
1017+
1018+
Assert.NotNull(notification);
1019+
Assert.True(notification!.IsChat);
1020+
Assert.Equal(fullMessage[..200] + "…", notification.Message);
1021+
Assert.Equal(fullMessage, notification.FullMessage);
1022+
}
1023+
9931024
[Fact]
9941025
public void ProcessRawMessage_AgentEventLogsRawLengthWithoutPayloadContent()
9951026
{

tests/OpenClaw.Tray.Tests/OpenClaw.Tray.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@
7373
<Compile Include="..\..\src\OpenClaw.Tray.WinUI\Services\ToastActivationRouter.cs" Link="Services\ToastActivationRouter.cs" />
7474
<Compile Include="..\..\src\OpenClaw.Tray.WinUI\Services\AppNotificationService.cs" Link="Services\AppNotificationService.cs" />
7575
<Compile Include="..\..\src\OpenClaw.Tray.WinUI\Services\AppNotificationMapper.cs" Link="Services\AppNotificationMapper.cs" />
76+
<Compile Include="..\..\src\OpenClaw.Tray.WinUI\Services\ChatNotificationSpeechText.cs" Link="Services\ChatNotificationSpeechText.cs" />
7677
<Compile Include="..\..\src\OpenClaw.Tray.WinUI\Services\AppNotificationPublisher.cs" Link="Services\AppNotificationPublisher.cs" />
7778
<Compile Include="..\..\src\OpenClaw.Tray.WinUI\Services\ConnectionStatusPresenter.cs" Link="Services\ConnectionStatusPresenter.cs" />
7879
<Compile Include="..\..\src\OpenClaw.Tray.WinUI\Services\McpRuntimeStatePolicy.cs" Link="Services\McpRuntimeStatePolicy.cs" />
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using OpenClaw.Shared;
2+
using OpenClawTray.Services;
3+
using Xunit;
4+
5+
namespace OpenClaw.Tray.Tests.Services;
6+
7+
public sealed class ChatNotificationSpeechTextTests
8+
{
9+
[Fact]
10+
public void Resolve_UsesFullMessageWhenPreviewWasTruncated()
11+
{
12+
var fullMessage = new string('x', 240);
13+
14+
var resolved = ChatNotificationSpeechText.Resolve(new OpenClawNotification
15+
{
16+
Message = fullMessage[..200] + "…",
17+
FullMessage = fullMessage
18+
});
19+
20+
Assert.Equal(fullMessage, resolved);
21+
}
22+
23+
[Theory]
24+
[InlineData(null)]
25+
[InlineData("")]
26+
public void Resolve_FallsBackToPreviewWhenFullMessageIsUnavailable(string? fullMessage)
27+
{
28+
var resolved = ChatNotificationSpeechText.Resolve(new OpenClawNotification
29+
{
30+
Message = "Preview",
31+
FullMessage = fullMessage
32+
});
33+
34+
Assert.Equal("Preview", resolved);
35+
}
36+
}

0 commit comments

Comments
 (0)