fix(dashboard): unstick Active Agents panel cards from "Connecting..."
Agents in `active` state without a current taskId had no SSE to attach to, but the card still rendered "Connecting..." — implying a network state that never resolves. Branch on taskId first: idle agents show "Idle — no task assigned"; running-without-task shows "Starting...". Also fix a related SSE multiplexer race: subscribers joining a channel that had already opened never got an onOpen callback (EventSource only fires `open` once), so they sat at isConnected=false forever whenever another component was already streaming the same URL. Fire onOpen on a microtask for late-joiners. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -98,7 +98,13 @@ function LiveAgentCard({ agent, projectId, onSelect, onOpenTaskLogs }: LiveAgent
|
||||
<div className="live-agent-card-transcript">
|
||||
{entries.length === 0 ? (
|
||||
<div className="live-agent-card-empty">
|
||||
{currentStep ? (
|
||||
{!agent.taskId ? (
|
||||
// "active" agents that aren't currently working a task have no
|
||||
// SSE stream to attach to; useLiveTranscript bails out with
|
||||
// isConnected=false. Showing "Connecting..." here is misleading
|
||||
// — the agent is just idle.
|
||||
<span>{agent.state === "running" ? "Starting..." : "Idle — no task assigned"}</span>
|
||||
) : currentStep ? (
|
||||
<>
|
||||
<div className="live-agent-card-status">
|
||||
Step {stepNumber}
|
||||
|
||||
@@ -119,6 +119,48 @@ describe("ActiveAgentsPanel", () => {
|
||||
expect(screen.getByText("Waiting for output...")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("shows idle copy (not 'Connecting...') when an active agent has no taskId", async () => {
|
||||
mockUseLiveTranscript.mockReturnValue({
|
||||
entries: [],
|
||||
isConnected: false,
|
||||
});
|
||||
|
||||
const mockAgent: Agent = {
|
||||
id: "agent-001",
|
||||
name: "Test Agent",
|
||||
role: "executor",
|
||||
state: "active",
|
||||
// taskId intentionally omitted — agent is available but not running
|
||||
lastHeartbeatAt: new Date().toISOString(),
|
||||
} as Agent;
|
||||
|
||||
render(<ActiveAgentsPanel agents={[mockAgent]} />);
|
||||
|
||||
expect(screen.getByText("Idle — no task assigned")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Connecting...")).toBeNull();
|
||||
});
|
||||
|
||||
it("shows 'Starting...' for running agents that haven't picked up a task yet", async () => {
|
||||
mockUseLiveTranscript.mockReturnValue({
|
||||
entries: [],
|
||||
isConnected: false,
|
||||
});
|
||||
|
||||
const mockAgent: Agent = {
|
||||
id: "agent-001",
|
||||
name: "Test Agent",
|
||||
role: "executor",
|
||||
state: "running",
|
||||
// taskId intentionally omitted — race between state flip and task bind
|
||||
lastHeartbeatAt: new Date().toISOString(),
|
||||
} as Agent;
|
||||
|
||||
render(<ActiveAgentsPanel agents={[mockAgent]} />);
|
||||
|
||||
expect(screen.getByText("Starting...")).toBeInTheDocument();
|
||||
expect(screen.queryByText("Connecting...")).toBeNull();
|
||||
});
|
||||
|
||||
it("renders multiple agent cards with separate transcript streams", async () => {
|
||||
mockUseLiveTranscript
|
||||
.mockReturnValueOnce({
|
||||
|
||||
@@ -383,9 +383,22 @@ export function subscribeSse(url: string, sub: SseSubscription = {}): () => void
|
||||
}
|
||||
|
||||
channel.subscribers.add(subscriber);
|
||||
const wasAlreadyOpen = !!channel.es && channel.hasOpenedOnce;
|
||||
openChannel(channel);
|
||||
reattachNativeListeners(channel);
|
||||
|
||||
// Subscribers joining a channel that already opened never see another
|
||||
// `open` event from EventSource, so fire onOpen for them on a microtask
|
||||
// (microtask, not sync, so the caller finishes wiring before state
|
||||
// updates land).
|
||||
if (wasAlreadyOpen) {
|
||||
queueMicrotask(() => {
|
||||
if (channel.subscribers.has(subscriber)) {
|
||||
subscriber.onOpen?.();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let active = true;
|
||||
return () => {
|
||||
if (!active) return;
|
||||
|
||||
Reference in New Issue
Block a user