mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 20:17:20 +00:00
feat(ui): team leader processing indicator and announce run streaming
- Add team.leader.processing backend event (emitted from onDrain before announce run starts) so all WS clients show processing status - Capture announce run.started events to stream leader's summarization in real-time (thinking, tool calls, text) - Add leader_processing phase to ActivityIndicator and ChatTopBar - isBusy stays true during leader processing → stop button visible
This commit is contained in:
1 parent
c6bab9a059
commit
9ad3043fe9
8 files changed
+36
-7
No files matched your search
@@ -143,6 +143,16 @@ func runGateway() {
|
||||
for _, item := range items {
|
||||
batchMedia = append(batchMedia, item.Media...)
|
||||
}
|
||||
// Notify clients that leader is processing team results
|
||||
// (bridges UI gap between last task.completed and announce run.started).
|
||||
msgBus.Broadcast(bus.Event{
|
||||
Name: protocol.EventTeamLeaderProcessing,
|
||||
Payload: map[string]any{
|
||||
"agentId": meta.ParentAgent,
|
||||
"tasks": len(items),
|
||||
},
|
||||
})
|
||||
|
||||
msgBus.PublishInbound(bus.InboundMessage{
|
||||
Channel: "system",
|
||||
SenderID: senderID,
|
||||
|
||||
@@ -52,6 +52,9 @@ const (
|
||||
EventTeamTaskDeleted = "team.task.deleted"
|
||||
EventTeamTaskStale = "team.task.stale"
|
||||
|
||||
// Emitted when leader starts processing completed team task results (before announce run).
|
||||
EventTeamLeaderProcessing = "team.leader.processing"
|
||||
|
||||
// Team CRUD events (admin operations).
|
||||
EventTeamCreated = "team.created"
|
||||
EventTeamUpdated = "team.updated"
|
||||
|
||||
@@ -206,6 +206,9 @@ export const Events = {
|
||||
TEAM_TASK_DISPATCHED: "team.task.dispatched",
|
||||
TEAM_TASK_DELETED: "team.task.deleted",
|
||||
|
||||
// Team leader processing (bridges gap between last task.completed and announce run.started)
|
||||
TEAM_LEADER_PROCESSING: "team.leader.processing",
|
||||
|
||||
// Team messages
|
||||
TEAM_MESSAGE_SENT: "team.message.sent",
|
||||
|
||||
|
||||
@@ -63,7 +63,7 @@ export function ActiveRunZone({
|
||||
</div>
|
||||
)}
|
||||
|
||||
{isRunning && (
|
||||
{(isRunning || activity?.phase === "leader_processing") && (
|
||||
<ActivityIndicator activity={activity} isRunning={isRunning} />
|
||||
)}
|
||||
</div>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Brain, Wrench, Pencil, Archive, RefreshCw } from "lucide-react";
|
||||
import { Brain, Wrench, Pencil, Archive, RefreshCw, Users } from "lucide-react";
|
||||
import type { RunActivity } from "@/types/chat";
|
||||
|
||||
interface ActivityIndicatorProps {
|
||||
@@ -7,7 +7,7 @@ interface ActivityIndicatorProps {
|
||||
}
|
||||
|
||||
export function ActivityIndicator({ activity, isRunning }: ActivityIndicatorProps) {
|
||||
if (!isRunning) return null;
|
||||
if (!isRunning && activity?.phase !== "leader_processing") return null;
|
||||
|
||||
if (!activity) {
|
||||
return (
|
||||
@@ -51,6 +51,8 @@ function getPhaseConfig(activity: RunActivity) {
|
||||
color: "text-orange-500",
|
||||
label: `Retrying (${activity.retryAttempt ?? 0}/${activity.retryMax ?? 0})...`,
|
||||
};
|
||||
case "leader_processing":
|
||||
return { icon: Users, color: "text-emerald-500", label: "Processing team results..." };
|
||||
default:
|
||||
return { icon: Brain, color: "text-muted-foreground", label: "Working..." };
|
||||
}
|
||||
|
||||
@@ -19,6 +19,7 @@ const phaseLabels: Record<RunActivity["phase"], string> = {
|
||||
streaming: "Responding…",
|
||||
compacting: "Compacting…",
|
||||
retrying: "Retrying…",
|
||||
leader_processing: "Processing team results…",
|
||||
};
|
||||
|
||||
export function ChatTopBar({ agentId, isRunning, isBusy, activity, teamTasks }: ChatTopBarProps) {
|
||||
|
||||
@@ -433,14 +433,24 @@ export function useChatMessages(sessionKey: string, agentId: string) {
|
||||
useWsEvent(Events.TEAM_TASK_PROGRESS, onTaskProgress);
|
||||
useWsEvent(Events.TEAM_TASK_ASSIGNED, onTaskAssigned);
|
||||
|
||||
// Leader processing: backend emits when announce queue drains (before announce run starts).
|
||||
const handleLeaderProcessing = useCallback((payload: unknown) => {
|
||||
const event = payload as { agentId?: string; tasks?: number };
|
||||
if (event?.agentId === agentIdRef.current) {
|
||||
activityRef.current = { phase: "leader_processing" };
|
||||
setActivity(activityRef.current);
|
||||
}
|
||||
}, []);
|
||||
useWsEvent(Events.TEAM_LEADER_PROCESSING, handleLeaderProcessing);
|
||||
|
||||
// Add a local message optimistically (shown immediately, replaced on next loadHistory)
|
||||
const addLocalMessage = useCallback((msg: ChatMessage) => {
|
||||
setMessages((prev) => [...prev, msg]);
|
||||
}, []);
|
||||
|
||||
// isBusy: true when main agent is running OR team tasks are active.
|
||||
// Used for stop button visibility, top bar status, empty state check.
|
||||
const isBusy = isRunning || teamTasks.length > 0;
|
||||
// isBusy: true when main agent is running, team tasks are active,
|
||||
// or leader is processing team results before announce run starts.
|
||||
const isBusy = isRunning || teamTasks.length > 0 || activity?.phase === "leader_processing";
|
||||
|
||||
return {
|
||||
messages,
|
||||
|
||||
@@ -4,7 +4,7 @@ import type { Message } from "./session";
|
||||
|
||||
/** Activity phase tracking during agent run */
|
||||
export interface RunActivity {
|
||||
phase: "thinking" | "tool_exec" | "streaming" | "compacting" | "retrying";
|
||||
phase: "thinking" | "tool_exec" | "streaming" | "compacting" | "retrying" | "leader_processing";
|
||||
tool?: string;
|
||||
tools?: string[];
|
||||
iteration?: number;
|
||||
|
||||
Reference in new issue
Block a user