story-kit: queue 235_story_show_agent_logs_for_a_story_in_expanded_work_item for merge
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
name: "Show agent logs for a story in expanded work item"
|
name: "Show agent logs for a story in expanded work item"
|
||||||
merge_failure: "Merge pipeline infrastructure failure: squash merge succeeded on merge-queue branch, conflicts auto-resolved (WorkItemDetailPanel.test.tsx), quality gates passed, but final cherry-pick onto master failed with \"fatal: bad revision 'merge-queue/235_story_show_agent_logs_for_a_story_in_expanded_work_item'\" — the merge-queue branch reference was not found at cherry-pick time. Master is untouched."
|
merge_failure: "Merge workspace is occupied by story 236 (merge-queue/236_story_show_test_results_for_a_story_in_expanded_work_item). The merge pipeline uses a single merge_workspace directory and it is currently locked by another merge. Story 235 cannot proceed until the 236 merge completes or its stale worktree is cleaned up. To unblock: either wait for 236's merge to finish, or manually clean up with `git worktree remove .story_kit/merge_workspace` and `git branch -D merge-queue/236_story_show_test_results_for_a_story_in_expanded_work_item`."
|
||||||
---
|
---
|
||||||
|
|
||||||
# Story 235: Show agent logs for a story in expanded work item
|
# Story 235: Show agent logs for a story in expanded work item
|
||||||
|
|||||||
@@ -1,18 +1,43 @@
|
|||||||
import { render, screen, waitFor } from "@testing-library/react";
|
import { act, render, screen, waitFor } from "@testing-library/react";
|
||||||
import { describe, expect, it, vi } from "vitest";
|
import { beforeEach, describe, expect, it, vi } from "vitest";
|
||||||
import { WorkItemDetailPanel } from "./WorkItemDetailPanel";
|
import type { AgentEvent, AgentInfo } from "../api/agents";
|
||||||
|
import { agentsApi, subscribeAgentStream } from "../api/agents";
|
||||||
|
import { api } from "../api/client";
|
||||||
|
|
||||||
vi.mock("../api/client", () => ({
|
vi.mock("../api/client", () => ({
|
||||||
api: {
|
api: {
|
||||||
getWorkItemContent: vi.fn().mockResolvedValue({
|
getWorkItemContent: vi.fn(),
|
||||||
content: "# Big Title\n\nSome content here.",
|
|
||||||
stage: "current",
|
|
||||||
name: "Big Title Story",
|
|
||||||
}),
|
|
||||||
},
|
},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
vi.mock("../api/agents", () => ({
|
||||||
|
agentsApi: {
|
||||||
|
listAgents: vi.fn(),
|
||||||
|
},
|
||||||
|
subscribeAgentStream: vi.fn(() => () => {}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Dynamic import so mocks are in place before the module loads
|
||||||
|
const { WorkItemDetailPanel } = await import("./WorkItemDetailPanel");
|
||||||
|
|
||||||
|
const mockedGetWorkItemContent = vi.mocked(api.getWorkItemContent);
|
||||||
|
const mockedListAgents = vi.mocked(agentsApi.listAgents);
|
||||||
|
const mockedSubscribeAgentStream = vi.mocked(subscribeAgentStream);
|
||||||
|
|
||||||
|
const DEFAULT_CONTENT = {
|
||||||
|
content: "# Big Title\n\nSome content here.",
|
||||||
|
stage: "current",
|
||||||
|
name: "Big Title Story",
|
||||||
|
};
|
||||||
|
|
||||||
describe("WorkItemDetailPanel", () => {
|
describe("WorkItemDetailPanel", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
mockedGetWorkItemContent.mockResolvedValue(DEFAULT_CONTENT);
|
||||||
|
mockedListAgents.mockResolvedValue([]);
|
||||||
|
mockedSubscribeAgentStream.mockReturnValue(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
it("renders the story name in the header", async () => {
|
it("renders the story name in the header", async () => {
|
||||||
render(<WorkItemDetailPanel storyId="237_bug_test" onClose={() => {}} />);
|
render(<WorkItemDetailPanel storyId="237_bug_test" onClose={() => {}} />);
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
@@ -41,9 +66,279 @@ describe("WorkItemDetailPanel", () => {
|
|||||||
const content = screen.getByTestId("detail-panel-content");
|
const content = screen.getByTestId("detail-panel-content");
|
||||||
const h1 = content.querySelector("h1");
|
const h1 = content.querySelector("h1");
|
||||||
expect(h1).not.toBeNull();
|
expect(h1).not.toBeNull();
|
||||||
// Headings must have a constrained fontSize inline style
|
|
||||||
// Browser-default 2em is too large for the work item detail panel
|
|
||||||
expect(h1?.style.fontSize).toBeTruthy();
|
expect(h1?.style.fontSize).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("WorkItemDetailPanel - Agent Logs", () => {
|
||||||
|
beforeEach(() => {
|
||||||
|
vi.clearAllMocks();
|
||||||
|
mockedGetWorkItemContent.mockResolvedValue(DEFAULT_CONTENT);
|
||||||
|
mockedListAgents.mockResolvedValue([]);
|
||||||
|
mockedSubscribeAgentStream.mockReturnValue(() => {});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows placeholder when no agent is assigned to the story", async () => {
|
||||||
|
render(<WorkItemDetailPanel storyId="42_story_test" onClose={() => {}} />);
|
||||||
|
await screen.findByTestId("detail-panel-content");
|
||||||
|
const placeholder = screen.getByTestId("placeholder-agent-logs");
|
||||||
|
expect(placeholder).toBeInTheDocument();
|
||||||
|
expect(placeholder).toHaveTextContent("Coming soon");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows agent name and running status when agent is running", async () => {
|
||||||
|
const agentList: AgentInfo[] = [
|
||||||
|
{
|
||||||
|
story_id: "42_story_test",
|
||||||
|
agent_name: "coder-1",
|
||||||
|
status: "running",
|
||||||
|
session_id: null,
|
||||||
|
worktree_path: "/tmp/wt",
|
||||||
|
base_branch: "master",
|
||||||
|
log_session_id: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
mockedListAgents.mockResolvedValue(agentList);
|
||||||
|
|
||||||
|
render(<WorkItemDetailPanel storyId="42_story_test" onClose={() => {}} />);
|
||||||
|
|
||||||
|
const statusBadge = await screen.findByTestId("agent-status-badge");
|
||||||
|
expect(statusBadge).toHaveTextContent("coder-1");
|
||||||
|
expect(statusBadge).toHaveTextContent("running");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows log output when agent emits output events", async () => {
|
||||||
|
let emitEvent: ((e: AgentEvent) => void) | null = null;
|
||||||
|
mockedSubscribeAgentStream.mockImplementation(
|
||||||
|
(_storyId, _agentName, onEvent) => {
|
||||||
|
emitEvent = onEvent;
|
||||||
|
return () => {};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const agentList: AgentInfo[] = [
|
||||||
|
{
|
||||||
|
story_id: "42_story_test",
|
||||||
|
agent_name: "coder-1",
|
||||||
|
status: "running",
|
||||||
|
session_id: null,
|
||||||
|
worktree_path: "/tmp/wt",
|
||||||
|
base_branch: "master",
|
||||||
|
log_session_id: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
mockedListAgents.mockResolvedValue(agentList);
|
||||||
|
|
||||||
|
render(<WorkItemDetailPanel storyId="42_story_test" onClose={() => {}} />);
|
||||||
|
|
||||||
|
await screen.findByTestId("agent-status-badge");
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
emitEvent?.({
|
||||||
|
type: "output",
|
||||||
|
story_id: "42_story_test",
|
||||||
|
agent_name: "coder-1",
|
||||||
|
text: "Writing tests...",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const logOutput = screen.getByTestId("agent-log-output");
|
||||||
|
expect(logOutput).toHaveTextContent("Writing tests...");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("appends multiple output events to the log", async () => {
|
||||||
|
let emitEvent: ((e: AgentEvent) => void) | null = null;
|
||||||
|
mockedSubscribeAgentStream.mockImplementation(
|
||||||
|
(_storyId, _agentName, onEvent) => {
|
||||||
|
emitEvent = onEvent;
|
||||||
|
return () => {};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const agentList: AgentInfo[] = [
|
||||||
|
{
|
||||||
|
story_id: "42_story_test",
|
||||||
|
agent_name: "coder-1",
|
||||||
|
status: "running",
|
||||||
|
session_id: null,
|
||||||
|
worktree_path: "/tmp/wt",
|
||||||
|
base_branch: "master",
|
||||||
|
log_session_id: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
mockedListAgents.mockResolvedValue(agentList);
|
||||||
|
|
||||||
|
render(<WorkItemDetailPanel storyId="42_story_test" onClose={() => {}} />);
|
||||||
|
|
||||||
|
await screen.findByTestId("agent-status-badge");
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
emitEvent?.({
|
||||||
|
type: "output",
|
||||||
|
story_id: "42_story_test",
|
||||||
|
agent_name: "coder-1",
|
||||||
|
text: "Line one\n",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
emitEvent?.({
|
||||||
|
type: "output",
|
||||||
|
story_id: "42_story_test",
|
||||||
|
agent_name: "coder-1",
|
||||||
|
text: "Line two\n",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const logOutput = screen.getByTestId("agent-log-output");
|
||||||
|
expect(logOutput.textContent).toContain("Line one");
|
||||||
|
expect(logOutput.textContent).toContain("Line two");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("updates status to completed after done event", async () => {
|
||||||
|
let emitEvent: ((e: AgentEvent) => void) | null = null;
|
||||||
|
mockedSubscribeAgentStream.mockImplementation(
|
||||||
|
(_storyId, _agentName, onEvent) => {
|
||||||
|
emitEvent = onEvent;
|
||||||
|
return () => {};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const agentList: AgentInfo[] = [
|
||||||
|
{
|
||||||
|
story_id: "42_story_test",
|
||||||
|
agent_name: "coder-1",
|
||||||
|
status: "running",
|
||||||
|
session_id: null,
|
||||||
|
worktree_path: "/tmp/wt",
|
||||||
|
base_branch: "master",
|
||||||
|
log_session_id: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
mockedListAgents.mockResolvedValue(agentList);
|
||||||
|
|
||||||
|
render(<WorkItemDetailPanel storyId="42_story_test" onClose={() => {}} />);
|
||||||
|
|
||||||
|
await screen.findByTestId("agent-status-badge");
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
emitEvent?.({
|
||||||
|
type: "done",
|
||||||
|
story_id: "42_story_test",
|
||||||
|
agent_name: "coder-1",
|
||||||
|
session_id: "session-123",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const statusBadge = screen.getByTestId("agent-status-badge");
|
||||||
|
expect(statusBadge).toHaveTextContent("completed");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows failed status after error event", async () => {
|
||||||
|
let emitEvent: ((e: AgentEvent) => void) | null = null;
|
||||||
|
mockedSubscribeAgentStream.mockImplementation(
|
||||||
|
(_storyId, _agentName, onEvent) => {
|
||||||
|
emitEvent = onEvent;
|
||||||
|
return () => {};
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const agentList: AgentInfo[] = [
|
||||||
|
{
|
||||||
|
story_id: "42_story_test",
|
||||||
|
agent_name: "coder-1",
|
||||||
|
status: "running",
|
||||||
|
session_id: null,
|
||||||
|
worktree_path: "/tmp/wt",
|
||||||
|
base_branch: "master",
|
||||||
|
log_session_id: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
mockedListAgents.mockResolvedValue(agentList);
|
||||||
|
|
||||||
|
render(<WorkItemDetailPanel storyId="42_story_test" onClose={() => {}} />);
|
||||||
|
|
||||||
|
await screen.findByTestId("agent-status-badge");
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
emitEvent?.({
|
||||||
|
type: "error",
|
||||||
|
story_id: "42_story_test",
|
||||||
|
agent_name: "coder-1",
|
||||||
|
message: "Process failed",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const statusBadge = screen.getByTestId("agent-status-badge");
|
||||||
|
expect(statusBadge).toHaveTextContent("failed");
|
||||||
|
|
||||||
|
const logOutput = screen.getByTestId("agent-log-output");
|
||||||
|
expect(logOutput.textContent).toContain("[ERROR] Process failed");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows completed agent status without subscribing to stream", async () => {
|
||||||
|
const agentList: AgentInfo[] = [
|
||||||
|
{
|
||||||
|
story_id: "42_story_test",
|
||||||
|
agent_name: "coder-1",
|
||||||
|
status: "completed",
|
||||||
|
session_id: "session-123",
|
||||||
|
worktree_path: "/tmp/wt",
|
||||||
|
base_branch: "master",
|
||||||
|
log_session_id: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
mockedListAgents.mockResolvedValue(agentList);
|
||||||
|
|
||||||
|
render(<WorkItemDetailPanel storyId="42_story_test" onClose={() => {}} />);
|
||||||
|
|
||||||
|
const statusBadge = await screen.findByTestId("agent-status-badge");
|
||||||
|
expect(statusBadge).toHaveTextContent("completed");
|
||||||
|
expect(mockedSubscribeAgentStream).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows failed agent status for a failed agent without subscribing to stream", async () => {
|
||||||
|
const agentList: AgentInfo[] = [
|
||||||
|
{
|
||||||
|
story_id: "42_story_test",
|
||||||
|
agent_name: "coder-1",
|
||||||
|
status: "failed",
|
||||||
|
session_id: null,
|
||||||
|
worktree_path: null,
|
||||||
|
base_branch: "master",
|
||||||
|
log_session_id: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
mockedListAgents.mockResolvedValue(agentList);
|
||||||
|
|
||||||
|
render(<WorkItemDetailPanel storyId="42_story_test" onClose={() => {}} />);
|
||||||
|
|
||||||
|
const statusBadge = await screen.findByTestId("agent-status-badge");
|
||||||
|
expect(statusBadge).toHaveTextContent("failed");
|
||||||
|
expect(mockedSubscribeAgentStream).not.toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows agent logs section (not placeholder) when agent is assigned", async () => {
|
||||||
|
const agentList: AgentInfo[] = [
|
||||||
|
{
|
||||||
|
story_id: "42_story_test",
|
||||||
|
agent_name: "coder-1",
|
||||||
|
status: "running",
|
||||||
|
session_id: null,
|
||||||
|
worktree_path: "/tmp/wt",
|
||||||
|
base_branch: "master",
|
||||||
|
log_session_id: null,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
mockedListAgents.mockResolvedValue(agentList);
|
||||||
|
|
||||||
|
render(<WorkItemDetailPanel storyId="42_story_test" onClose={() => {}} />);
|
||||||
|
|
||||||
|
await screen.findByTestId("agent-logs-section");
|
||||||
|
|
||||||
|
expect(
|
||||||
|
screen.queryByTestId("placeholder-agent-logs"),
|
||||||
|
).not.toBeInTheDocument();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import Markdown from "react-markdown";
|
import Markdown from "react-markdown";
|
||||||
|
import type { AgentEvent, AgentInfo, AgentStatusValue } from "../api/agents";
|
||||||
|
import { agentsApi, subscribeAgentStream } from "../api/agents";
|
||||||
import { api } from "../api/client";
|
import { api } from "../api/client";
|
||||||
|
|
||||||
const { useEffect, useRef, useState } = React;
|
const { useEffect, useRef, useState } = React;
|
||||||
@@ -13,6 +15,13 @@ const STAGE_LABELS: Record<string, string> = {
|
|||||||
archived: "Archived",
|
archived: "Archived",
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const STATUS_COLORS: Record<AgentStatusValue, string> = {
|
||||||
|
running: "#3fb950",
|
||||||
|
pending: "#e3b341",
|
||||||
|
completed: "#aaa",
|
||||||
|
failed: "#f85149",
|
||||||
|
};
|
||||||
|
|
||||||
interface WorkItemDetailPanelProps {
|
interface WorkItemDetailPanelProps {
|
||||||
storyId: string;
|
storyId: string;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
@@ -27,7 +36,11 @@ export function WorkItemDetailPanel({
|
|||||||
const [name, setName] = useState<string | null>(null);
|
const [name, setName] = useState<string | null>(null);
|
||||||
const [loading, setLoading] = useState(true);
|
const [loading, setLoading] = useState(true);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [agentInfo, setAgentInfo] = useState<AgentInfo | null>(null);
|
||||||
|
const [agentLog, setAgentLog] = useState<string[]>([]);
|
||||||
|
const [agentStatus, setAgentStatus] = useState<AgentStatusValue | null>(null);
|
||||||
const panelRef = useRef<HTMLDivElement>(null);
|
const panelRef = useRef<HTMLDivElement>(null);
|
||||||
|
const cleanupRef = useRef<(() => void) | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
@@ -47,6 +60,61 @@ export function WorkItemDetailPanel({
|
|||||||
});
|
});
|
||||||
}, [storyId]);
|
}, [storyId]);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
cleanupRef.current?.();
|
||||||
|
cleanupRef.current = null;
|
||||||
|
setAgentInfo(null);
|
||||||
|
setAgentLog([]);
|
||||||
|
setAgentStatus(null);
|
||||||
|
|
||||||
|
agentsApi
|
||||||
|
.listAgents()
|
||||||
|
.then((agents) => {
|
||||||
|
const agent = agents.find((a) => a.story_id === storyId);
|
||||||
|
if (!agent) return;
|
||||||
|
setAgentInfo(agent);
|
||||||
|
setAgentStatus(agent.status);
|
||||||
|
|
||||||
|
if (agent.status === "running" || agent.status === "pending") {
|
||||||
|
const cleanup = subscribeAgentStream(
|
||||||
|
storyId,
|
||||||
|
agent.agent_name,
|
||||||
|
(event: AgentEvent) => {
|
||||||
|
switch (event.type) {
|
||||||
|
case "status":
|
||||||
|
setAgentStatus((event.status as AgentStatusValue) ?? null);
|
||||||
|
break;
|
||||||
|
case "output":
|
||||||
|
setAgentLog((prev) => [...prev, event.text ?? ""]);
|
||||||
|
break;
|
||||||
|
case "done":
|
||||||
|
setAgentStatus("completed");
|
||||||
|
break;
|
||||||
|
case "error":
|
||||||
|
setAgentStatus("failed");
|
||||||
|
setAgentLog((prev) => [
|
||||||
|
...prev,
|
||||||
|
`[ERROR] ${event.message ?? "Unknown error"}`,
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
cleanupRef.current = cleanup;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err: unknown) => {
|
||||||
|
console.error("Failed to load agents:", err);
|
||||||
|
});
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
cleanupRef.current?.();
|
||||||
|
cleanupRef.current = null;
|
||||||
|
};
|
||||||
|
}, [storyId]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handleKeyDown = (e: KeyboardEvent) => {
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
if (e.key === "Escape") {
|
if (e.key === "Escape") {
|
||||||
@@ -187,7 +255,6 @@ export function WorkItemDetailPanel({
|
|||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Placeholder sections for future content */}
|
|
||||||
<div
|
<div
|
||||||
style={{
|
style={{
|
||||||
display: "flex",
|
display: "flex",
|
||||||
@@ -195,9 +262,80 @@ export function WorkItemDetailPanel({
|
|||||||
gap: "8px",
|
gap: "8px",
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{/* Agent Logs section */}
|
||||||
|
<div
|
||||||
|
data-testid={
|
||||||
|
agentInfo ? "agent-logs-section" : "placeholder-agent-logs"
|
||||||
|
}
|
||||||
|
style={{
|
||||||
|
border: "1px solid #2a2a2a",
|
||||||
|
borderRadius: "8px",
|
||||||
|
padding: "10px 12px",
|
||||||
|
background: "#161616",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: "flex",
|
||||||
|
alignItems: "center",
|
||||||
|
justifyContent: "space-between",
|
||||||
|
marginBottom: agentInfo ? "6px" : "4px",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
fontWeight: 600,
|
||||||
|
fontSize: "0.8em",
|
||||||
|
color: agentInfo ? "#888" : "#555",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Agent Logs
|
||||||
|
</div>
|
||||||
|
{agentInfo && agentStatus && (
|
||||||
|
<div
|
||||||
|
data-testid="agent-status-badge"
|
||||||
|
style={{
|
||||||
|
fontSize: "0.7em",
|
||||||
|
color: STATUS_COLORS[agentStatus],
|
||||||
|
fontWeight: 600,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{agentInfo.agent_name} — {agentStatus}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{agentInfo && agentLog.length > 0 ? (
|
||||||
|
<div
|
||||||
|
data-testid="agent-log-output"
|
||||||
|
style={{
|
||||||
|
fontSize: "0.75em",
|
||||||
|
fontFamily: "monospace",
|
||||||
|
color: "#ccc",
|
||||||
|
whiteSpace: "pre-wrap",
|
||||||
|
wordBreak: "break-word",
|
||||||
|
lineHeight: "1.5",
|
||||||
|
maxHeight: "200px",
|
||||||
|
overflowY: "auto",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{agentLog.join("")}
|
||||||
|
</div>
|
||||||
|
) : agentInfo ? (
|
||||||
|
<div style={{ fontSize: "0.75em", color: "#444" }}>
|
||||||
|
{agentStatus === "running" || agentStatus === "pending"
|
||||||
|
? "Waiting for output..."
|
||||||
|
: "No output."}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div style={{ fontSize: "0.75em", color: "#444" }}>
|
||||||
|
Coming soon
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Placeholder sections for future content */}
|
||||||
{(
|
{(
|
||||||
[
|
[
|
||||||
{ id: "agent-logs", label: "Agent Logs" },
|
|
||||||
{ id: "test-output", label: "Test Output" },
|
{ id: "test-output", label: "Test Output" },
|
||||||
{ id: "coverage", label: "Coverage" },
|
{ id: "coverage", label: "Coverage" },
|
||||||
] as { id: string; label: string }[]
|
] as { id: string; label: string }[]
|
||||||
|
|||||||
Reference in New Issue
Block a user