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 */}
{(