Skip to content

Commit 70d609b

Browse files
fix: accumulate tool progress into existing activity log bubbles
Non-final replies now append to the source's activity log bubble instead of creating a new bubble per message. Activity logs are keyed by category + agentName so multiple concurrent sources (primary, subagent-X, scheduled-system) each accumulate into their own bubble. BlazorUserFrontend routes non-final DisplayReplyAsync calls through AppendActivityLogEntry. Activity log headers now show category badge, agent name, and entry count. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent cedaa87 commit 70d609b

4 files changed

Lines changed: 51 additions & 18 deletions

File tree

src/RockBot.UserProxy.Blazor/Pages/Chat.razor

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -28,15 +28,24 @@
2828
{
2929
<!-- Activity log bubble: collapsed one-liner with spinner, expandable to full log -->
3030
<div class="message-wrapper mb-3 agent-message">
31-
<div class="message-bubble activity-log-bubble p-2 rounded @(message.IsExpanded ? "expanded" : "collapsed")"
31+
<div class="message-bubble activity-log-bubble p-2 rounded category-@message.Category.ToString().ToLowerInvariant() @(message.IsExpanded ? "expanded" : "collapsed")"
3232
@onclick="() => ChatState.ToggleExpanded(message.MessageId)">
3333
<div class="activity-log-header">
3434
<span class="chevron-indicator @(message.IsExpanded ? "open" : "")">&#9654;</span>
3535
@if (ChatState.IsProcessing)
3636
{
3737
<span class="spinner-border spinner-border-sm me-1" role="status" aria-hidden="true"></span>
3838
}
39-
<span class="activity-log-summary">@TruncateText(message.Content, 100)</span>
39+
@if (message.Category != MessageCategory.PrimaryProgress)
40+
{
41+
<span class="category-badge">@GetCategoryLabel(message.Category)</span>
42+
}
43+
@if (!string.IsNullOrEmpty(message.AgentName))
44+
{
45+
<span class="collapsible-agent-name">@message.AgentName</span>
46+
}
47+
<span class="activity-log-summary">@TruncateText(message.Content, 80)</span>
48+
<span class="activity-log-count">(@(message.ActivityLogEntries.Count))</span>
4049
</div>
4150
@if (message.IsExpanded)
4251
{

src/RockBot.UserProxy.Blazor/Services/BlazorUserFrontend.cs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,9 @@ public Task DisplayReplyAsync(AgentReply reply, CancellationToken cancellationTo
1717
}
1818
else
1919
{
20-
// Intermediate progress — update thinking indicator AND add a bubble so
21-
// all agent traffic is visible for debugging.
22-
chatState.SetThinkingMessage(reply.Content);
23-
chatState.AddAgentReply(reply, category);
20+
// Non-final progress — append to the source's activity log bubble
21+
// instead of creating a separate bubble per message
22+
chatState.AppendActivityLogEntry(reply.Content, category, reply.AgentName);
2423
}
2524
return Task.CompletedTask;
2625
}
@@ -40,7 +39,7 @@ private static MessageCategory CategorizeReply(AgentReply reply)
4039
return MessageCategory.ScheduledUser;
4140

4241
if (reply.AgentName?.StartsWith("subagent-", StringComparison.OrdinalIgnoreCase) == true)
43-
return reply.IsFinal ? MessageCategory.SubagentActivity : MessageCategory.SubagentActivity;
42+
return MessageCategory.SubagentActivity;
4443

4544
if (reply.IsFinal)
4645
return MessageCategory.PrimaryFinal;

src/RockBot.UserProxy.Blazor/Services/ChatStateService.cs

Lines changed: 26 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,12 @@ public sealed class ChatStateService
2323
private readonly object _lock = new();
2424
private string? _currentThinkingMessage;
2525
private bool _isProcessing;
26-
private string? _activeActivityLogId;
26+
27+
/// <summary>
28+
/// Active activity log bubbles keyed by source key (category + agentName).
29+
/// Multiple sources can accumulate concurrently (e.g. primary + subagent).
30+
/// </summary>
31+
private readonly Dictionary<string, string> _activeActivityLogs = new();
2732

2833
public event Action? OnStateChanged;
2934

@@ -79,9 +84,9 @@ public void AddAgentReply(AgentReply reply, MessageCategory category = MessageCa
7984
{
8085
lock (_lock)
8186
{
82-
// When a PrimaryFinal message arrives, close any active activity log
87+
// When a PrimaryFinal message arrives, close the primary activity log
8388
if (category == MessageCategory.PrimaryFinal)
84-
_activeActivityLogId = null;
89+
_activeActivityLogs.Remove(ActivityLogKey(MessageCategory.PrimaryProgress, null));
8590

8691
_messages.Add(new ChatMessage
8792
{
@@ -103,16 +108,22 @@ or MessageCategory.Error
103108
}
104109

105110
/// <summary>
106-
/// Appends an entry to the active WIP activity log bubble. Creates the bubble
107-
/// if one doesn't exist yet for the current processing cycle.
111+
/// Appends an entry to an activity log bubble for the given source.
112+
/// Creates the bubble if one doesn't exist yet. Multiple concurrent sources
113+
/// (primary, subagent-X, scheduled-system) each get their own activity log.
108114
/// </summary>
109-
public void AppendActivityLogEntry(string content)
115+
public void AppendActivityLogEntry(
116+
string content,
117+
MessageCategory category = MessageCategory.PrimaryProgress,
118+
string? agentName = null)
110119
{
111120
lock (_lock)
112121
{
122+
var key = ActivityLogKey(category, agentName);
123+
113124
ChatMessage? logBubble = null;
114-
if (_activeActivityLogId is not null)
115-
logBubble = _messages.FirstOrDefault(m => m.MessageId == _activeActivityLogId);
125+
if (_activeActivityLogs.TryGetValue(key, out var logId))
126+
logBubble = _messages.FirstOrDefault(m => m.MessageId == logId);
116127

117128
if (logBubble is null)
118129
{
@@ -121,12 +132,13 @@ public void AppendActivityLogEntry(string content)
121132
Content = content,
122133
IsFromUser = false,
123134
Timestamp = DateTime.UtcNow,
124-
Category = MessageCategory.PrimaryProgress,
135+
AgentName = agentName,
136+
Category = category,
125137
IsActivityLog = true,
126138
IsExpanded = false
127139
};
128140
_messages.Add(logBubble);
129-
_activeActivityLogId = logBubble.MessageId;
141+
_activeActivityLogs[key] = logBubble.MessageId;
130142
}
131143

132144
logBubble.ActivityLogEntries.Add(new ActivityLogEntry(content, DateTime.UtcNow));
@@ -177,7 +189,7 @@ public void SetProcessing(bool isProcessing)
177189
if (isProcessing)
178190
{
179191
_currentThinkingMessage = null;
180-
_activeActivityLogId = null;
192+
_activeActivityLogs.Clear();
181193
}
182194
NotifyStateChanged();
183195
}
@@ -197,6 +209,9 @@ public void AddError(string message)
197209
NotifyStateChanged();
198210
}
199211

212+
private static string ActivityLogKey(MessageCategory category, string? agentName)
213+
=> $"{category}:{agentName ?? ""}";
214+
200215
private void NotifyStateChanged() => OnStateChanged?.Invoke();
201216
}
202217

src/RockBot.UserProxy.Blazor/wwwroot/css/app.css

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,12 @@ body.dark-mode .btn-feedback:hover:not(:disabled) {
342342
flex: 1;
343343
}
344344

345+
.activity-log-count {
346+
flex-shrink: 0;
347+
font-size: 0.75rem;
348+
color: #9ca3af;
349+
}
350+
345351
.activity-log-entries {
346352
margin-top: 0.5rem;
347353
padding-top: 0.5rem;
@@ -491,6 +497,10 @@ body.dark-mode .entry-time {
491497
color: #6b7280;
492498
}
493499

500+
body.dark-mode .activity-log-count {
501+
color: #6b7280;
502+
}
503+
494504
/* ── Dark mode: collapsible bubbles ──────────────────────────────────── */
495505

496506
body.dark-mode .collapsible-bubble {

0 commit comments

Comments
 (0)