diff --git a/frontend/src/components/AgentPanel.test.tsx b/frontend/src/components/AgentPanel.test.tsx index 8d47c18..29b94ac 100644 --- a/frontend/src/components/AgentPanel.test.tsx +++ b/frontend/src/components/AgentPanel.test.tsx @@ -213,7 +213,7 @@ describe("RosterBadge availability state", () => { }); }); -describe("Thinking traces hidden from agent stream UI", () => { +describe("Agent output not shown in sidebar (story 290)", () => { beforeAll(() => { Element.prototype.scrollIntoView = vi.fn(); }); @@ -224,7 +224,51 @@ describe("Thinking traces hidden from agent stream UI", () => { mockedSubscribeAgentStream.mockReturnValue(() => {}); }); - // AC1: thinking block is never rendered even when thinking events arrive + // AC1: output events do not appear in the agents sidebar + it("does not render agent output when output event arrives", async () => { + let emitEvent: ((e: AgentEvent) => void) | null = null; + mockedSubscribeAgentStream.mockImplementation( + (_storyId, _agentName, onEvent) => { + emitEvent = onEvent; + return () => {}; + }, + ); + + const agentList: AgentInfo[] = [ + { + story_id: "290_output", + agent_name: "coder-1", + status: "running", + session_id: null, + worktree_path: "/tmp/wt", + base_branch: "master", + log_session_id: null, + }, + ]; + mockedAgents.listAgents.mockResolvedValue(agentList); + + const { container } = render(); + await screen.findByTestId("roster-badge-coder-1"); + + await act(async () => { + emitEvent?.({ + type: "output", + story_id: "290_output", + agent_name: "coder-1", + text: "doing some work...", + }); + }); + + // No output elements in the sidebar + expect( + container.querySelector('[data-testid^="agent-output-"]'), + ).not.toBeInTheDocument(); + expect( + container.querySelector('[data-testid^="agent-stream-"]'), + ).not.toBeInTheDocument(); + }); + + // AC1: thinking events do not appear in the agents sidebar it("does not render thinking block when thinking event arrives", async () => { let emitEvent: ((e: AgentEvent) => void) | null = null; mockedSubscribeAgentStream.mockImplementation( @@ -236,7 +280,7 @@ describe("Thinking traces hidden from agent stream UI", () => { const agentList: AgentInfo[] = [ { - story_id: "218_thinking", + story_id: "290_thinking", agent_name: "coder-1", status: "running", session_id: null, @@ -253,109 +297,16 @@ describe("Thinking traces hidden from agent stream UI", () => { await act(async () => { emitEvent?.({ type: "thinking", - story_id: "218_thinking", + story_id: "290_thinking", agent_name: "coder-1", text: "Let me consider the problem carefully...", }); }); - // AC1: thinking block must not be present + // No thinking block or output in sidebar expect(screen.queryByTestId("thinking-block")).not.toBeInTheDocument(); - }); - - // AC2: after thinking events, only regular output is rendered - it("renders regular output but not thinking block when both arrive", async () => { - let emitEvent: ((e: AgentEvent) => void) | null = null; - mockedSubscribeAgentStream.mockImplementation( - (_storyId, _agentName, onEvent) => { - emitEvent = onEvent; - return () => {}; - }, - ); - - const agentList: AgentInfo[] = [ - { - story_id: "218_output", - agent_name: "coder-1", - status: "running", - session_id: null, - worktree_path: "/tmp/wt", - base_branch: "master", - log_session_id: null, - }, - ]; - mockedAgents.listAgents.mockResolvedValue(agentList); - - render(); - await screen.findByTestId("roster-badge-coder-1"); - - // Thinking event — must be ignored visually - await act(async () => { - emitEvent?.({ - type: "thinking", - story_id: "218_output", - agent_name: "coder-1", - text: "thinking deeply", - }); - }); - - // AC3: output event still renders correctly (no regression) - await act(async () => { - emitEvent?.({ - type: "output", - story_id: "218_output", - agent_name: "coder-1", - text: "Here is the result.", - }); - }); - - // AC1: no thinking block - expect(screen.queryByTestId("thinking-block")).not.toBeInTheDocument(); - - // AC2+AC3: output area renders the text but NOT thinking text - const outputArea = screen.getByTestId("agent-output-coder-1"); - expect(outputArea).toBeInTheDocument(); - expect(outputArea.textContent).toContain("Here is the result."); - expect(outputArea.textContent).not.toContain("thinking deeply"); - }); - - // AC3: output-only event stream (no thinking) still works - it("renders output event text without a thinking block", async () => { - let emitEvent: ((e: AgentEvent) => void) | null = null; - mockedSubscribeAgentStream.mockImplementation( - (_storyId, _agentName, onEvent) => { - emitEvent = onEvent; - return () => {}; - }, - ); - - const agentList: AgentInfo[] = [ - { - story_id: "218_noThink", - agent_name: "coder-1", - status: "running", - session_id: null, - worktree_path: "/tmp/wt", - base_branch: "master", - log_session_id: null, - }, - ]; - mockedAgents.listAgents.mockResolvedValue(agentList); - - render(); - await screen.findByTestId("roster-badge-coder-1"); - - await act(async () => { - emitEvent?.({ - type: "output", - story_id: "218_noThink", - agent_name: "coder-1", - text: "plain output line", - }); - }); - - expect(screen.queryByTestId("thinking-block")).not.toBeInTheDocument(); - const outputArea = screen.getByTestId("agent-output-coder-1"); - expect(outputArea.textContent).toContain("plain output line"); + expect( + screen.queryByText("Let me consider the problem carefully..."), + ).not.toBeInTheDocument(); }); }); diff --git a/frontend/src/components/AgentPanel.tsx b/frontend/src/components/AgentPanel.tsx index 1262ff0..404f61f 100644 --- a/frontend/src/components/AgentPanel.tsx +++ b/frontend/src/components/AgentPanel.tsx @@ -13,7 +13,6 @@ const { useCallback, useEffect, useRef, useState } = React; interface AgentState { agentName: string; status: AgentStatusValue; - log: string[]; sessionId: string | null; worktreePath: string | null; baseBranch: string | null; @@ -120,7 +119,6 @@ export function AgentPanel({ const current = prev[key] ?? { agentName, status: "pending" as AgentStatusValue, - log: [], sessionId: null, worktreePath: null, baseBranch: null, @@ -144,14 +142,6 @@ export function AgentPanel({ }, }; } - case "output": - return { - ...prev, - [key]: { - ...current, - log: [...current.log, event.text ?? ""], - }, - }; case "done": return { ...prev, @@ -168,17 +158,12 @@ export function AgentPanel({ [key]: { ...current, status: "failed", - log: [ - ...current.log, - `[ERROR] ${event.message ?? "Unknown error"}`, - ], terminalAt: current.terminalAt ?? Date.now(), }, }; - case "thinking": - // Thinking traces are internal model state — never display them. - return prev; default: + // output, thinking, and other events are not displayed in the sidebar. + // Agent output streams appear in the work item detail panel instead. return prev; } }); @@ -204,7 +189,6 @@ export function AgentPanel({ agentMap[key] = { agentName: a.agent_name, status: a.status, - log: [], sessionId: a.session_id, worktreePath: a.worktree_path, baseBranch: a.base_branch, @@ -261,9 +245,6 @@ export function AgentPanel({ } }; - // Agents that have streaming content to show - const activeAgents = Object.values(agents).filter((a) => a.log.length > 0); - return (
)} - {/* Per-agent streaming output */} - {activeAgents.map((agent) => ( -
- {agent.log.length > 0 && ( -
- {agent.log.join("")} -
- )} -
- ))} - {actionError && (
{/* Agent Logs section */} -
+ {!agentInfo && (
Agent Logs
- {agentInfo && agentStatus && ( -
- {agentInfo.agent_name} — {agentStatus} -
- )} -
- {agentInfo && agentLog.length > 0 ? ( -
- {agentLog.join("")} -
- ) : agentInfo ? ( -
- {agentStatus === "running" || agentStatus === "pending" - ? "Waiting for output..." - : "No output."} -
- ) : (
Coming soon
- )} -
+
+ )} + {agentInfo && ( +
+
+
+ Agent Logs +
+ {agentStatus && ( +
+ {agentInfo.agent_name} — {agentStatus} +
+ )} +
+ {agentLog.length > 0 ? ( +
+ {agentLog.join("")} +
+ ) : ( +
+ {agentStatus === "running" || agentStatus === "pending" + ? "Waiting for output..." + : "No output."} +
+ )} +
+ )} {/* Placeholder sections for future content */} {(