feat(FN-2598): merge fusion/fn-2598 (auto-resolved)
- test(FN-2598): complete Step 3 — update collapsed model header coverage - fix(FN-2598): normalize expand button spacing token - feat(FN-2598): complete Step 2 — style compact model header - feat(FN-2598): complete Step 1 — compact model header structure
This commit is contained in:
@@ -4,7 +4,7 @@ import { useRef, useEffect, useState, useCallback, useLayoutEffect, useMemo } fr
|
|||||||
import ReactMarkdown from "react-markdown";
|
import ReactMarkdown from "react-markdown";
|
||||||
import remarkGfm from "remark-gfm";
|
import remarkGfm from "remark-gfm";
|
||||||
import type { Components } from "react-markdown";
|
import type { Components } from "react-markdown";
|
||||||
import { Maximize2, Minimize2, Loader2 } from "lucide-react";
|
import { Maximize2, Minimize2, Loader2, ChevronDown, ChevronRight } from "lucide-react";
|
||||||
|
|
||||||
function formatTimestamp(iso: string): string {
|
function formatTimestamp(iso: string): string {
|
||||||
const date = new Date(iso);
|
const date = new Date(iso);
|
||||||
@@ -127,6 +127,7 @@ export function AgentLogViewer({
|
|||||||
const previousNewestEntryKeyRef = useRef<string | null>(null);
|
const previousNewestEntryKeyRef = useRef<string | null>(null);
|
||||||
const [renderMarkdown, setRenderMarkdown] = useState(true);
|
const [renderMarkdown, setRenderMarkdown] = useState(true);
|
||||||
const [isFullscreen, setIsFullscreen] = useState(false);
|
const [isFullscreen, setIsFullscreen] = useState(false);
|
||||||
|
const [modelHeaderExpanded, setModelHeaderExpanded] = useState(false);
|
||||||
|
|
||||||
const chronologicalEntryKeys = useMemo(
|
const chronologicalEntryKeys = useMemo(
|
||||||
() => buildEntryRenderKeys(entries),
|
() => buildEntryRenderKeys(entries),
|
||||||
@@ -208,6 +209,39 @@ export function AgentLogViewer({
|
|||||||
const hasValidatorOverride = validatorModel?.provider && validatorModel?.modelId;
|
const hasValidatorOverride = validatorModel?.provider && validatorModel?.modelId;
|
||||||
const hasPlanningOverride = planningModel?.provider && planningModel?.modelId;
|
const hasPlanningOverride = planningModel?.provider && planningModel?.modelId;
|
||||||
|
|
||||||
|
const modelProviders = useMemo(() => {
|
||||||
|
const providers: Array<{ role: string; provider: string; modelId?: string }> = [];
|
||||||
|
if (hasExecutorOverride) {
|
||||||
|
providers.push({
|
||||||
|
role: "Executor",
|
||||||
|
provider: executorModel!.provider!,
|
||||||
|
modelId: executorModel!.modelId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (hasValidatorOverride) {
|
||||||
|
providers.push({
|
||||||
|
role: "Validator",
|
||||||
|
provider: validatorModel!.provider!,
|
||||||
|
modelId: validatorModel!.modelId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (hasPlanningOverride) {
|
||||||
|
providers.push({
|
||||||
|
role: "Planning",
|
||||||
|
provider: planningModel!.provider!,
|
||||||
|
modelId: planningModel!.modelId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return providers;
|
||||||
|
}, [
|
||||||
|
hasExecutorOverride,
|
||||||
|
executorModel,
|
||||||
|
hasValidatorOverride,
|
||||||
|
validatorModel,
|
||||||
|
hasPlanningOverride,
|
||||||
|
planningModel,
|
||||||
|
]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
ref={containerRef}
|
ref={containerRef}
|
||||||
@@ -216,39 +250,26 @@ export function AgentLogViewer({
|
|||||||
>
|
>
|
||||||
{/* Model info header */}
|
{/* Model info header */}
|
||||||
<div className="agent-log-model-header" data-testid="agent-log-model-header">
|
<div className="agent-log-model-header" data-testid="agent-log-model-header">
|
||||||
<div className="agent-log-model-group">
|
<div className="agent-log-model-icons">
|
||||||
<span className="agent-log-model-label">Executor:</span>
|
{modelProviders.map((modelProvider) => (
|
||||||
{hasExecutorOverride ? (
|
<ProviderIcon
|
||||||
<span className="agent-log-model-value">
|
key={`${modelProvider.role}-${modelProvider.provider}-${modelProvider.modelId ?? "default"}`}
|
||||||
<ProviderIcon provider={executorModel.provider!} size="sm" />
|
provider={modelProvider.provider}
|
||||||
<span>{executorModel.provider}/{executorModel.modelId}</span>
|
size="sm"
|
||||||
</span>
|
/>
|
||||||
) : (
|
))}
|
||||||
<span className="model-badge-default">Using default</span>
|
<button
|
||||||
)}
|
className="agent-log-model-expand-btn"
|
||||||
</div>
|
onClick={() => setModelHeaderExpanded((prev) => !prev)}
|
||||||
<div className="agent-log-model-group">
|
aria-label={modelHeaderExpanded ? "Collapse model details" : "Expand model details"}
|
||||||
<span className="agent-log-model-label">Validator:</span>
|
aria-expanded={modelHeaderExpanded}
|
||||||
{hasValidatorOverride ? (
|
aria-controls="agent-log-model-details"
|
||||||
<span className="agent-log-model-value">
|
data-testid="agent-log-model-expand"
|
||||||
<ProviderIcon provider={validatorModel.provider!} size="sm" />
|
>
|
||||||
<span>{validatorModel.provider}/{validatorModel.modelId}</span>
|
{modelHeaderExpanded ? <ChevronDown size={14} /> : <ChevronRight size={14} />}
|
||||||
</span>
|
</button>
|
||||||
) : (
|
|
||||||
<span className="model-badge-default">Using default</span>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="agent-log-model-group">
|
|
||||||
<span className="agent-log-model-label">Planning:</span>
|
|
||||||
{hasPlanningOverride ? (
|
|
||||||
<span className="agent-log-model-value">
|
|
||||||
<ProviderIcon provider={planningModel.provider!} size="sm" />
|
|
||||||
<span>{planningModel.provider}/{planningModel.modelId}</span>
|
|
||||||
</span>
|
|
||||||
) : (
|
|
||||||
<span className="model-badge-default">Using default</span>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Markdown render toggle */}
|
{/* Markdown render toggle */}
|
||||||
<div className="agent-log-model-header-toggle">
|
<div className="agent-log-model-header-toggle">
|
||||||
<button
|
<button
|
||||||
@@ -271,6 +292,44 @@ export function AgentLogViewer({
|
|||||||
{isFullscreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
|
{isFullscreen ? <Minimize2 size={14} /> : <Maximize2 size={14} />}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{modelHeaderExpanded && (
|
||||||
|
<div id="agent-log-model-details" className="agent-log-model-details">
|
||||||
|
<div className="agent-log-model-group">
|
||||||
|
<span className="agent-log-model-label">Executor:</span>
|
||||||
|
{hasExecutorOverride ? (
|
||||||
|
<span className="agent-log-model-value">
|
||||||
|
<ProviderIcon provider={executorModel.provider!} size="sm" />
|
||||||
|
<span>{executorModel.provider}/{executorModel.modelId}</span>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="model-badge-default">Using default</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="agent-log-model-group">
|
||||||
|
<span className="agent-log-model-label">Validator:</span>
|
||||||
|
{hasValidatorOverride ? (
|
||||||
|
<span className="agent-log-model-value">
|
||||||
|
<ProviderIcon provider={validatorModel.provider!} size="sm" />
|
||||||
|
<span>{validatorModel.provider}/{validatorModel.modelId}</span>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="model-badge-default">Using default</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
<div className="agent-log-model-group">
|
||||||
|
<span className="agent-log-model-label">Planning/Triage:</span>
|
||||||
|
{hasPlanningOverride ? (
|
||||||
|
<span className="agent-log-model-value">
|
||||||
|
<ProviderIcon provider={planningModel.provider!} size="sm" />
|
||||||
|
<span>{planningModel.provider}/{planningModel.modelId}</span>
|
||||||
|
</span>
|
||||||
|
) : (
|
||||||
|
<span className="model-badge-default">Using default</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Pagination summary */}
|
{/* Pagination summary */}
|
||||||
|
|||||||
@@ -251,16 +251,56 @@
|
|||||||
.agent-log-model-header {
|
.agent-log-model-header {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
gap: 16px;
|
gap: var(--space-lg);
|
||||||
padding: 8px 12px;
|
padding: var(--space-sm) var(--space-md);
|
||||||
margin-bottom: 12px;
|
margin-bottom: var(--space-md);
|
||||||
background: var(--bg-tertiary);
|
background: var(--bg-tertiary);
|
||||||
border-radius: 4px;
|
border-radius: var(--radius-sm);
|
||||||
font-size: 12px;
|
font-size: 12px;
|
||||||
color: var(--text-muted);
|
color: var(--text-muted);
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
min-width: 0;
|
min-width: 0;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-log-model-icons {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--space-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-log-model-expand-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--space-xs);
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text-muted);
|
||||||
|
cursor: pointer;
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
transition: color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-log-model-expand-btn:hover {
|
||||||
|
color: var(--text);
|
||||||
|
background: var(--card-hover);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-log-model-expand-btn:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: var(--focus-ring-strong);
|
||||||
|
}
|
||||||
|
|
||||||
|
.agent-log-model-details {
|
||||||
|
display: flex;
|
||||||
|
flex-basis: 100%;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--space-lg);
|
||||||
|
padding-top: var(--space-sm);
|
||||||
|
margin-top: var(--space-sm);
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
}
|
}
|
||||||
|
|
||||||
.agent-log-model-group {
|
.agent-log-model-group {
|
||||||
|
|||||||
@@ -7,7 +7,10 @@ import type { AgentLogEntry } from "@fusion/core";
|
|||||||
vi.mock("lucide-react", () => ({
|
vi.mock("lucide-react", () => ({
|
||||||
Maximize2: () => null,
|
Maximize2: () => null,
|
||||||
Minimize2: () => null,
|
Minimize2: () => null,
|
||||||
|
Loader2: () => null,
|
||||||
Cpu: () => null,
|
Cpu: () => null,
|
||||||
|
ChevronDown: () => null,
|
||||||
|
ChevronRight: () => null,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
function makeEntry(overrides: Partial<AgentLogEntry> = {}): AgentLogEntry {
|
function makeEntry(overrides: Partial<AgentLogEntry> = {}): AgentLogEntry {
|
||||||
@@ -275,38 +278,34 @@ describe("AgentLogViewer", () => {
|
|||||||
entries={entries}
|
entries={entries}
|
||||||
loading={false}
|
loading={false}
|
||||||
executorModel={{ provider: "anthropic", modelId: "claude-sonnet-4-5" }}
|
executorModel={{ provider: "anthropic", modelId: "claude-sonnet-4-5" }}
|
||||||
/>
|
/>,
|
||||||
);
|
);
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
||||||
expect(header).toBeTruthy();
|
expect(header).toBeTruthy();
|
||||||
|
expect(container.querySelector('[data-provider="anthropic"]')).toBeTruthy();
|
||||||
|
expect(header!.textContent).not.toContain("Executor:");
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId("agent-log-model-expand"));
|
||||||
expect(header!.textContent).toContain("Executor:");
|
expect(header!.textContent).toContain("Executor:");
|
||||||
expect(header!.textContent).toContain("anthropic/claude-sonnet-4-5");
|
expect(header!.textContent).toContain("anthropic/claude-sonnet-4-5");
|
||||||
expect(header!.textContent).toContain("Validator:");
|
|
||||||
expect(header!.textContent).toContain("Using default");
|
|
||||||
// Verify ProviderIcon is rendered for executor
|
|
||||||
expect(container.querySelector('[data-provider="anthropic"]')).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders 'Using default' when no executor model override is set", () => {
|
it("renders 'Using default' when no executor model override is set", () => {
|
||||||
const entries = [makeEntry()];
|
const entries = [makeEntry()];
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<AgentLogViewer entries={entries} loading={false} executorModel={null} />
|
<AgentLogViewer entries={entries} loading={false} executorModel={null} />,
|
||||||
);
|
);
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
||||||
expect(header).toBeTruthy();
|
expect(header).toBeTruthy();
|
||||||
expect(header!.textContent).toContain("Executor:");
|
expect(header!.textContent).not.toContain("Using default");
|
||||||
expect(header!.textContent).toContain("Using default");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders 'Using default' when executorModel is undefined", () => {
|
it("renders 'Using default' when executorModel is undefined", () => {
|
||||||
const entries = [makeEntry()];
|
const entries = [makeEntry()];
|
||||||
const { container } = render(
|
const { container } = render(<AgentLogViewer entries={entries} loading={false} />);
|
||||||
<AgentLogViewer entries={entries} loading={false} />
|
|
||||||
);
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
||||||
expect(header).toBeTruthy();
|
expect(header).toBeTruthy();
|
||||||
expect(header!.textContent).toContain("Executor:");
|
expect(header!.textContent).not.toContain("Using default");
|
||||||
expect(header!.textContent).toContain("Using default");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders model info header with validator model when set", () => {
|
it("renders model info header with validator model when set", () => {
|
||||||
@@ -316,27 +315,26 @@ describe("AgentLogViewer", () => {
|
|||||||
entries={entries}
|
entries={entries}
|
||||||
loading={false}
|
loading={false}
|
||||||
validatorModel={{ provider: "openai", modelId: "gpt-4o" }}
|
validatorModel={{ provider: "openai", modelId: "gpt-4o" }}
|
||||||
/>
|
/>,
|
||||||
);
|
);
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
||||||
expect(header).toBeTruthy();
|
expect(header).toBeTruthy();
|
||||||
|
expect(container.querySelector('[data-provider="openai"]')).toBeTruthy();
|
||||||
|
expect(header!.textContent).not.toContain("Validator:");
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId("agent-log-model-expand"));
|
||||||
expect(header!.textContent).toContain("Validator:");
|
expect(header!.textContent).toContain("Validator:");
|
||||||
expect(header!.textContent).toContain("openai/gpt-4o");
|
expect(header!.textContent).toContain("openai/gpt-4o");
|
||||||
expect(header!.textContent).toContain("Executor:");
|
|
||||||
expect(header!.textContent).toContain("Using default");
|
|
||||||
// Verify ProviderIcon is rendered for validator
|
|
||||||
expect(container.querySelector('[data-provider="openai"]')).toBeTruthy();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders 'Using default' when no validator model override is set", () => {
|
it("renders 'Using default' when no validator model override is set", () => {
|
||||||
const entries = [makeEntry()];
|
const entries = [makeEntry()];
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<AgentLogViewer entries={entries} loading={false} validatorModel={null} />
|
<AgentLogViewer entries={entries} loading={false} validatorModel={null} />,
|
||||||
);
|
);
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
||||||
expect(header).toBeTruthy();
|
expect(header).toBeTruthy();
|
||||||
expect(header!.textContent).toContain("Validator:");
|
expect(header!.textContent).not.toContain("Using default");
|
||||||
expect(header!.textContent).toContain("Using default");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders both models when both are configured", () => {
|
it("renders both models when both are configured", () => {
|
||||||
@@ -347,30 +345,26 @@ describe("AgentLogViewer", () => {
|
|||||||
loading={false}
|
loading={false}
|
||||||
executorModel={{ provider: "anthropic", modelId: "claude-opus-4" }}
|
executorModel={{ provider: "anthropic", modelId: "claude-opus-4" }}
|
||||||
validatorModel={{ provider: "openai", modelId: "gpt-4o" }}
|
validatorModel={{ provider: "openai", modelId: "gpt-4o" }}
|
||||||
/>
|
/>,
|
||||||
);
|
);
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
||||||
expect(header).toBeTruthy();
|
expect(header).toBeTruthy();
|
||||||
expect(header!.textContent).toContain("Executor:");
|
|
||||||
expect(header!.textContent).toContain("anthropic/claude-opus-4");
|
|
||||||
expect(header!.textContent).toContain("Validator:");
|
|
||||||
expect(header!.textContent).toContain("openai/gpt-4o");
|
|
||||||
// Verify both ProviderIcons are rendered
|
|
||||||
expect(container.querySelector('[data-provider="anthropic"]')).toBeTruthy();
|
expect(container.querySelector('[data-provider="anthropic"]')).toBeTruthy();
|
||||||
expect(container.querySelector('[data-provider="openai"]')).toBeTruthy();
|
expect(container.querySelector('[data-provider="openai"]')).toBeTruthy();
|
||||||
|
expect(header!.textContent).not.toContain("anthropic/claude-opus-4");
|
||||||
|
expect(header!.textContent).not.toContain("openai/gpt-4o");
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId("agent-log-model-expand"));
|
||||||
|
expect(header!.textContent).toContain("anthropic/claude-opus-4");
|
||||||
|
expect(header!.textContent).toContain("openai/gpt-4o");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders header with 'Using default' for both models when both are null/undefined", () => {
|
it("renders header with 'Using default' for both models when both are null/undefined", () => {
|
||||||
const entries = [makeEntry()];
|
const entries = [makeEntry()];
|
||||||
const { container } = render(
|
const { container } = render(<AgentLogViewer entries={entries} loading={false} />);
|
||||||
<AgentLogViewer entries={entries} loading={false} />
|
|
||||||
);
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
||||||
expect(header).toBeTruthy();
|
expect(header).toBeTruthy();
|
||||||
expect(header!.textContent).toContain("Executor:");
|
expect(header!.textContent).not.toContain("Using default");
|
||||||
expect(header!.textContent).toContain("Using default");
|
|
||||||
expect(header!.textContent).toContain("Validator:");
|
|
||||||
expect(header!.textContent).toContain("Using default");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows 'Using default' when executorModel has only provider but no modelId", () => {
|
it("shows 'Using default' when executorModel has only provider but no modelId", () => {
|
||||||
@@ -380,12 +374,11 @@ describe("AgentLogViewer", () => {
|
|||||||
entries={entries}
|
entries={entries}
|
||||||
loading={false}
|
loading={false}
|
||||||
executorModel={{ provider: "anthropic" }}
|
executorModel={{ provider: "anthropic" }}
|
||||||
/>
|
/>,
|
||||||
);
|
);
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
||||||
expect(header).toBeTruthy();
|
expect(header).toBeTruthy();
|
||||||
expect(header!.textContent).toContain("Executor:");
|
expect(header!.textContent).not.toContain("Using default");
|
||||||
expect(header!.textContent).toContain("Using default");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows 'Using default' when executorModel has only modelId but no provider", () => {
|
it("shows 'Using default' when executorModel has only modelId but no provider", () => {
|
||||||
@@ -395,12 +388,11 @@ describe("AgentLogViewer", () => {
|
|||||||
entries={entries}
|
entries={entries}
|
||||||
loading={false}
|
loading={false}
|
||||||
executorModel={{ modelId: "claude-sonnet-4-5" }}
|
executorModel={{ modelId: "claude-sonnet-4-5" }}
|
||||||
/>
|
/>,
|
||||||
);
|
);
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
||||||
expect(header).toBeTruthy();
|
expect(header).toBeTruthy();
|
||||||
expect(header!.textContent).toContain("Executor:");
|
expect(header!.textContent).not.toContain("Using default");
|
||||||
expect(header!.textContent).toContain("Using default");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders model info header with planning model when set", () => {
|
it("renders model info header with planning model when set", () => {
|
||||||
@@ -410,36 +402,34 @@ describe("AgentLogViewer", () => {
|
|||||||
entries={entries}
|
entries={entries}
|
||||||
loading={false}
|
loading={false}
|
||||||
planningModel={{ provider: "anthropic", modelId: "claude-opus-4" }}
|
planningModel={{ provider: "anthropic", modelId: "claude-opus-4" }}
|
||||||
/>
|
/>,
|
||||||
);
|
);
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
||||||
expect(header).toBeTruthy();
|
expect(header).toBeTruthy();
|
||||||
expect(header!.textContent).toContain("Planning:");
|
|
||||||
expect(header!.textContent).toContain("anthropic/claude-opus-4");
|
|
||||||
// Verify ProviderIcon is rendered for planning
|
|
||||||
expect(container.querySelector('[data-provider="anthropic"]')).toBeTruthy();
|
expect(container.querySelector('[data-provider="anthropic"]')).toBeTruthy();
|
||||||
|
expect(header!.textContent).not.toContain("Planning/Triage:");
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId("agent-log-model-expand"));
|
||||||
|
expect(header!.textContent).toContain("Planning/Triage:");
|
||||||
|
expect(header!.textContent).toContain("anthropic/claude-opus-4");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders 'Using default' for planning when no planning model is set", () => {
|
it("renders 'Using default' for planning when no planning model is set", () => {
|
||||||
const entries = [makeEntry()];
|
const entries = [makeEntry()];
|
||||||
const { container } = render(
|
const { container } = render(
|
||||||
<AgentLogViewer entries={entries} loading={false} planningModel={null} />
|
<AgentLogViewer entries={entries} loading={false} planningModel={null} />,
|
||||||
);
|
);
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
||||||
expect(header).toBeTruthy();
|
expect(header).toBeTruthy();
|
||||||
expect(header!.textContent).toContain("Planning:");
|
expect(header!.textContent).not.toContain("Using default");
|
||||||
expect(header!.textContent).toContain("Using default");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders 'Using default' for planning when planningModel is undefined", () => {
|
it("renders 'Using default' for planning when planningModel is undefined", () => {
|
||||||
const entries = [makeEntry()];
|
const entries = [makeEntry()];
|
||||||
const { container } = render(
|
const { container } = render(<AgentLogViewer entries={entries} loading={false} />);
|
||||||
<AgentLogViewer entries={entries} loading={false} />
|
|
||||||
);
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
||||||
expect(header).toBeTruthy();
|
expect(header).toBeTruthy();
|
||||||
expect(header!.textContent).toContain("Planning:");
|
expect(header!.textContent).not.toContain("Using default");
|
||||||
expect(header!.textContent).toContain("Using default");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it("renders all three models when all are configured", () => {
|
it("renders all three models when all are configured", () => {
|
||||||
@@ -451,20 +441,21 @@ describe("AgentLogViewer", () => {
|
|||||||
executorModel={{ provider: "anthropic", modelId: "claude-opus-4" }}
|
executorModel={{ provider: "anthropic", modelId: "claude-opus-4" }}
|
||||||
validatorModel={{ provider: "openai", modelId: "gpt-4o" }}
|
validatorModel={{ provider: "openai", modelId: "gpt-4o" }}
|
||||||
planningModel={{ provider: "google", modelId: "gemini-pro" }}
|
planningModel={{ provider: "google", modelId: "gemini-pro" }}
|
||||||
/>
|
/>,
|
||||||
);
|
);
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
||||||
expect(header).toBeTruthy();
|
expect(header).toBeTruthy();
|
||||||
expect(header!.textContent).toContain("Executor:");
|
|
||||||
expect(header!.textContent).toContain("anthropic/claude-opus-4");
|
|
||||||
expect(header!.textContent).toContain("Validator:");
|
|
||||||
expect(header!.textContent).toContain("openai/gpt-4o");
|
|
||||||
expect(header!.textContent).toContain("Planning:");
|
|
||||||
expect(header!.textContent).toContain("google/gemini-pro");
|
|
||||||
// Verify all three ProviderIcons are rendered
|
|
||||||
expect(container.querySelector('[data-provider="anthropic"]')).toBeTruthy();
|
expect(container.querySelector('[data-provider="anthropic"]')).toBeTruthy();
|
||||||
expect(container.querySelector('[data-provider="openai"]')).toBeTruthy();
|
expect(container.querySelector('[data-provider="openai"]')).toBeTruthy();
|
||||||
expect(container.querySelector('[data-provider="google"]')).toBeTruthy();
|
expect(container.querySelector('[data-provider="google"]')).toBeTruthy();
|
||||||
|
expect(header!.textContent).not.toContain("anthropic/claude-opus-4");
|
||||||
|
expect(header!.textContent).not.toContain("openai/gpt-4o");
|
||||||
|
expect(header!.textContent).not.toContain("google/gemini-pro");
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId("agent-log-model-expand"));
|
||||||
|
expect(header!.textContent).toContain("anthropic/claude-opus-4");
|
||||||
|
expect(header!.textContent).toContain("openai/gpt-4o");
|
||||||
|
expect(header!.textContent).toContain("google/gemini-pro");
|
||||||
});
|
});
|
||||||
|
|
||||||
it("shows 'Using default' for planning when planningModel has only provider but no modelId", () => {
|
it("shows 'Using default' for planning when planningModel has only provider but no modelId", () => {
|
||||||
@@ -474,12 +465,101 @@ describe("AgentLogViewer", () => {
|
|||||||
entries={entries}
|
entries={entries}
|
||||||
loading={false}
|
loading={false}
|
||||||
planningModel={{ provider: "anthropic" }}
|
planningModel={{ provider: "anthropic" }}
|
||||||
/>
|
/>,
|
||||||
);
|
);
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
||||||
expect(header).toBeTruthy();
|
expect(header).toBeTruthy();
|
||||||
expect(header!.textContent).toContain("Planning:");
|
expect(header!.textContent).not.toContain("Using default");
|
||||||
expect(header!.textContent).toContain("Using default");
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("model header expand/collapse", () => {
|
||||||
|
it("shows only provider icons in collapsed state, hides model text", () => {
|
||||||
|
const entries = [makeEntry()];
|
||||||
|
const { container } = render(
|
||||||
|
<AgentLogViewer
|
||||||
|
entries={entries}
|
||||||
|
loading={false}
|
||||||
|
executorModel={{ provider: "anthropic", modelId: "claude-sonnet-4-5" }}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container.querySelector('[data-provider="anthropic"]')).toBeTruthy();
|
||||||
|
expect(screen.getByTestId("agent-log-model-expand")).toBeTruthy();
|
||||||
|
expect(container.textContent).not.toContain("Executor:");
|
||||||
|
expect(container.textContent).not.toContain("claude-sonnet-4-5");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows model details when expand button is clicked", () => {
|
||||||
|
const entries = [makeEntry()];
|
||||||
|
const { container } = render(
|
||||||
|
<AgentLogViewer
|
||||||
|
entries={entries}
|
||||||
|
loading={false}
|
||||||
|
executorModel={{ provider: "anthropic", modelId: "claude-sonnet-4-5" }}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByTestId("agent-log-model-expand"));
|
||||||
|
expect(container.textContent).toContain("Executor:");
|
||||||
|
expect(container.textContent).toContain("anthropic/claude-sonnet-4-5");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("collapses model details when expand button is clicked again", () => {
|
||||||
|
const entries = [makeEntry()];
|
||||||
|
const { container } = render(
|
||||||
|
<AgentLogViewer
|
||||||
|
entries={entries}
|
||||||
|
loading={false}
|
||||||
|
executorModel={{ provider: "anthropic", modelId: "claude-sonnet-4-5" }}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const button = screen.getByTestId("agent-log-model-expand");
|
||||||
|
fireEvent.click(button);
|
||||||
|
expect(container.textContent).toContain("Executor:");
|
||||||
|
fireEvent.click(button);
|
||||||
|
expect(container.textContent).not.toContain("Executor:");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("shows no provider icons when no model overrides are set", () => {
|
||||||
|
const entries = [makeEntry()];
|
||||||
|
const { container } = render(<AgentLogViewer entries={entries} loading={false} />);
|
||||||
|
|
||||||
|
expect(container.querySelector("[data-provider]")).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("has aria-expanded=false when collapsed and aria-expanded=true when expanded", () => {
|
||||||
|
const entries = [makeEntry()];
|
||||||
|
render(
|
||||||
|
<AgentLogViewer
|
||||||
|
entries={entries}
|
||||||
|
loading={false}
|
||||||
|
executorModel={{ provider: "anthropic", modelId: "claude-sonnet-4-5" }}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
const button = screen.getByTestId("agent-log-model-expand");
|
||||||
|
expect(button.getAttribute("aria-expanded")).toBe("false");
|
||||||
|
fireEvent.click(button);
|
||||||
|
expect(button.getAttribute("aria-expanded")).toBe("true");
|
||||||
|
});
|
||||||
|
|
||||||
|
it("renders multiple provider icons for multiple overrides", () => {
|
||||||
|
const entries = [makeEntry()];
|
||||||
|
const { container } = render(
|
||||||
|
<AgentLogViewer
|
||||||
|
entries={entries}
|
||||||
|
loading={false}
|
||||||
|
executorModel={{ provider: "anthropic", modelId: "claude-opus-4" }}
|
||||||
|
validatorModel={{ provider: "openai", modelId: "gpt-4o" }}
|
||||||
|
planningModel={{ provider: "google", modelId: "gemini-pro" }}
|
||||||
|
/>,
|
||||||
|
);
|
||||||
|
|
||||||
|
expect(container.querySelector('[data-provider="anthropic"]')).toBeTruthy();
|
||||||
|
expect(container.querySelector('[data-provider="openai"]')).toBeTruthy();
|
||||||
|
expect(container.querySelector('[data-provider="google"]')).toBeTruthy();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -48,9 +48,11 @@ vi.mock("lucide-react", () => ({
|
|||||||
MessageSquare: () => null,
|
MessageSquare: () => null,
|
||||||
ChevronUp: () => null,
|
ChevronUp: () => null,
|
||||||
ChevronDown: () => null,
|
ChevronDown: () => null,
|
||||||
|
ChevronRight: () => null,
|
||||||
X: () => null,
|
X: () => null,
|
||||||
Maximize2: () => null,
|
Maximize2: () => null,
|
||||||
Minimize2: () => null,
|
Minimize2: () => null,
|
||||||
|
Loader2: () => null,
|
||||||
Bot: () => null,
|
Bot: () => null,
|
||||||
CircleDot: () => null,
|
CircleDot: () => null,
|
||||||
XCircle: () => null,
|
XCircle: () => null,
|
||||||
@@ -1706,23 +1708,32 @@ describe("TaskDetailModal", () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
it("shows resolved executor from settings when task has no explicit executor override", async () => {
|
async function openAgentLogAndExpandModelDetails(container: HTMLElement) {
|
||||||
const { container } = await setupModelTest({
|
|
||||||
defaultProvider: "anthropic",
|
|
||||||
defaultModelId: "claude-sonnet-4-5",
|
|
||||||
});
|
|
||||||
|
|
||||||
fireEvent.click(screen.getByText("Logs"));
|
fireEvent.click(screen.getByText("Logs"));
|
||||||
fireEvent.click(screen.getByText("Agent Log"));
|
fireEvent.click(screen.getByText("Agent Log"));
|
||||||
|
|
||||||
await waitFor(() => {
|
await waitFor(() => {
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
||||||
expect(header).toBeTruthy();
|
expect(header).toBeTruthy();
|
||||||
expect(header!.textContent).toContain("anthropic/claude-sonnet-4-5");
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const expandButton = screen.getByTestId("agent-log-model-expand") as HTMLButtonElement;
|
||||||
|
if (expandButton.getAttribute("aria-expanded") !== "true") {
|
||||||
|
fireEvent.click(expandButton);
|
||||||
|
}
|
||||||
|
|
||||||
|
return container.querySelector("[data-testid='agent-log-model-header']") as HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
it("shows resolved executor from settings when task has no explicit executor override", async () => {
|
||||||
|
const { container } = await setupModelTest({
|
||||||
|
defaultProvider: "anthropic",
|
||||||
|
defaultModelId: "claude-sonnet-4-5",
|
||||||
|
});
|
||||||
|
|
||||||
|
const header = await openAgentLogAndExpandModelDetails(container);
|
||||||
|
|
||||||
// Validator should also fall back to the default
|
// Validator should also fall back to the default
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']")!;
|
|
||||||
expect(header.textContent).toContain("anthropic/claude-sonnet-4-5");
|
expect(header.textContent).toContain("anthropic/claude-sonnet-4-5");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1734,16 +1745,7 @@ describe("TaskDetailModal", () => {
|
|||||||
validatorModelId: "gpt-4o",
|
validatorModelId: "gpt-4o",
|
||||||
});
|
});
|
||||||
|
|
||||||
fireEvent.click(screen.getByText("Logs"));
|
const header = await openAgentLogAndExpandModelDetails(container);
|
||||||
fireEvent.click(screen.getByText("Agent Log"));
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
|
||||||
expect(header).toBeTruthy();
|
|
||||||
expect(header!.textContent).toContain("openai/gpt-4o");
|
|
||||||
});
|
|
||||||
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']")!;
|
|
||||||
// Executor falls back to default
|
// Executor falls back to default
|
||||||
expect(header.textContent).toContain("anthropic/claude-sonnet-4-5");
|
expect(header.textContent).toContain("anthropic/claude-sonnet-4-5");
|
||||||
// Validator uses the validator-specific setting
|
// Validator uses the validator-specific setting
|
||||||
@@ -1757,18 +1759,9 @@ describe("TaskDetailModal", () => {
|
|||||||
// No validatorProvider or validatorModelId
|
// No validatorProvider or validatorModelId
|
||||||
});
|
});
|
||||||
|
|
||||||
fireEvent.click(screen.getByText("Logs"));
|
const header = await openAgentLogAndExpandModelDetails(container);
|
||||||
fireEvent.click(screen.getByText("Agent Log"));
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
|
||||||
expect(header).toBeTruthy();
|
|
||||||
// Both executor and validator should resolve to the default
|
|
||||||
expect(header!.textContent).toContain("anthropic/claude-sonnet-4-5");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Count occurrences - should appear three times (once for executor, once for validator, once for planning)
|
// Count occurrences - should appear three times (once for executor, once for validator, once for planning)
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']")!;
|
|
||||||
const matches = header.textContent!.match(/anthropic\/claude-sonnet-4-5/g);
|
const matches = header.textContent!.match(/anthropic\/claude-sonnet-4-5/g);
|
||||||
expect(matches).toHaveLength(3);
|
expect(matches).toHaveLength(3);
|
||||||
});
|
});
|
||||||
@@ -1779,18 +1772,9 @@ describe("TaskDetailModal", () => {
|
|||||||
{ defaultProvider: "anthropic", defaultModelId: "claude-sonnet-4-5" },
|
{ defaultProvider: "anthropic", defaultModelId: "claude-sonnet-4-5" },
|
||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(screen.getByText("Logs"));
|
const header = await openAgentLogAndExpandModelDetails(container);
|
||||||
fireEvent.click(screen.getByText("Agent Log"));
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
|
||||||
expect(header).toBeTruthy();
|
|
||||||
// Task override should win
|
|
||||||
expect(header!.textContent).toContain("openai/gpt-4o");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Default model should not appear for executor
|
// Default model should not appear for executor
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']")!;
|
|
||||||
expect(header.textContent).toContain("openai/gpt-4o");
|
expect(header.textContent).toContain("openai/gpt-4o");
|
||||||
// Validator falls back to default
|
// Validator falls back to default
|
||||||
expect(header.textContent).toContain("anthropic/claude-sonnet-4-5");
|
expect(header.textContent).toContain("anthropic/claude-sonnet-4-5");
|
||||||
@@ -1802,17 +1786,7 @@ describe("TaskDetailModal", () => {
|
|||||||
{ defaultProvider: "anthropic", defaultModelId: "claude-sonnet-4-5", validatorProvider: "openai", validatorModelId: "gpt-4o" },
|
{ defaultProvider: "anthropic", defaultModelId: "claude-sonnet-4-5", validatorProvider: "openai", validatorModelId: "gpt-4o" },
|
||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(screen.getByText("Logs"));
|
const header = await openAgentLogAndExpandModelDetails(container);
|
||||||
fireEvent.click(screen.getByText("Agent Log"));
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
|
||||||
expect(header).toBeTruthy();
|
|
||||||
// Task validator override should win
|
|
||||||
expect(header!.textContent).toContain("google/gemini-pro");
|
|
||||||
});
|
|
||||||
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']")!;
|
|
||||||
// Executor falls back to default
|
// Executor falls back to default
|
||||||
expect(header.textContent).toContain("anthropic/claude-sonnet-4-5");
|
expect(header.textContent).toContain("anthropic/claude-sonnet-4-5");
|
||||||
// Settings validator should not appear (task override wins)
|
// Settings validator should not appear (task override wins)
|
||||||
@@ -1824,15 +1798,7 @@ describe("TaskDetailModal", () => {
|
|||||||
// No defaultProvider/defaultModelId
|
// No defaultProvider/defaultModelId
|
||||||
});
|
});
|
||||||
|
|
||||||
fireEvent.click(screen.getByText("Logs"));
|
const header = await openAgentLogAndExpandModelDetails(container);
|
||||||
fireEvent.click(screen.getByText("Agent Log"));
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
|
||||||
expect(header).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']")!;
|
|
||||||
expect(header.textContent).toContain("Using default");
|
expect(header.textContent).toContain("Using default");
|
||||||
// Should show "Using default" for executor, validator, and planning
|
// Should show "Using default" for executor, validator, and planning
|
||||||
const defaultBadges = header.querySelectorAll(".model-badge-default");
|
const defaultBadges = header.querySelectorAll(".model-badge-default");
|
||||||
@@ -1862,16 +1828,8 @@ describe("TaskDetailModal", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(screen.getByText("Logs"));
|
|
||||||
fireEvent.click(screen.getByText("Agent Log"));
|
|
||||||
|
|
||||||
// Wait for the failed fetch to settle
|
// Wait for the failed fetch to settle
|
||||||
await waitFor(() => {
|
const header = await openAgentLogAndExpandModelDetails(container);
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
|
||||||
expect(header).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']")!;
|
|
||||||
expect(header.textContent).toContain("Using default");
|
expect(header.textContent).toContain("Using default");
|
||||||
const defaultBadges = header.querySelectorAll(".model-badge-default");
|
const defaultBadges = header.querySelectorAll(".model-badge-default");
|
||||||
expect(defaultBadges).toHaveLength(3);
|
expect(defaultBadges).toHaveLength(3);
|
||||||
@@ -1892,16 +1850,7 @@ describe("TaskDetailModal", () => {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
fireEvent.click(screen.getByText("Logs"));
|
const header = await openAgentLogAndExpandModelDetails(container);
|
||||||
fireEvent.click(screen.getByText("Agent Log"));
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
|
||||||
expect(header).toBeTruthy();
|
|
||||||
expect(header!.textContent).toContain("google/gemini-pro");
|
|
||||||
});
|
|
||||||
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']")!;
|
|
||||||
// Executor uses task override
|
// Executor uses task override
|
||||||
expect(header.textContent).toContain("google/gemini-pro");
|
expect(header.textContent).toContain("google/gemini-pro");
|
||||||
// Validator uses settings-specific validator
|
// Validator uses settings-specific validator
|
||||||
@@ -1943,19 +1892,11 @@ describe("TaskDetailModal", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Navigate to Agent Log subview
|
const header = await openAgentLogAndExpandModelDetails(container);
|
||||||
fireEvent.click(screen.getByText("Logs"));
|
|
||||||
fireEvent.click(screen.getByText("Agent Log"));
|
|
||||||
|
|
||||||
await waitFor(() => {
|
// Planning should show the runtime triage marker, not settings default
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
expect(header.textContent).toContain("Planning/Triage:");
|
||||||
expect(header).toBeTruthy();
|
expect(header.textContent).toContain("google/gemini-pro");
|
||||||
// Planning should show the runtime triage marker, not settings default
|
|
||||||
expect(header!.textContent).toContain("Planning:");
|
|
||||||
expect(header!.textContent).toContain("google/gemini-pro");
|
|
||||||
});
|
|
||||||
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']")!;
|
|
||||||
// Executor/Validator should still show settings default
|
// Executor/Validator should still show settings default
|
||||||
expect(header.textContent).toContain("anthropic/claude-sonnet-4-5");
|
expect(header.textContent).toContain("anthropic/claude-sonnet-4-5");
|
||||||
});
|
});
|
||||||
@@ -1992,19 +1933,11 @@ describe("TaskDetailModal", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Navigate to Agent Log subview
|
const header = await openAgentLogAndExpandModelDetails(container);
|
||||||
fireEvent.click(screen.getByText("Logs"));
|
|
||||||
fireEvent.click(screen.getByText("Agent Log"));
|
|
||||||
|
|
||||||
await waitFor(() => {
|
// Planning should use planningProvider/planningModelId from settings
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
expect(header.textContent).toContain("Planning/Triage:");
|
||||||
expect(header).toBeTruthy();
|
expect(header.textContent).toContain("openai/gpt-4o");
|
||||||
// Planning should use planningProvider/planningModelId from settings
|
|
||||||
expect(header!.textContent).toContain("Planning:");
|
|
||||||
expect(header!.textContent).toContain("openai/gpt-4o");
|
|
||||||
});
|
|
||||||
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']")!;
|
|
||||||
// Executor/Validator should show default
|
// Executor/Validator should show default
|
||||||
expect(header.textContent).toContain("anthropic/claude-sonnet-4-5");
|
expect(header.textContent).toContain("anthropic/claude-sonnet-4-5");
|
||||||
// Planning should NOT show the default
|
// Planning should NOT show the default
|
||||||
@@ -2017,19 +1950,12 @@ describe("TaskDetailModal", () => {
|
|||||||
defaultModelId: "claude-sonnet-4-5",
|
defaultModelId: "claude-sonnet-4-5",
|
||||||
});
|
});
|
||||||
|
|
||||||
// Navigate to Agent Log subview
|
const header = await openAgentLogAndExpandModelDetails(container);
|
||||||
fireEvent.click(screen.getByText("Logs"));
|
|
||||||
fireEvent.click(screen.getByText("Agent Log"));
|
|
||||||
|
|
||||||
await waitFor(() => {
|
expect(header.textContent).toContain("Planning/Triage:");
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
expect(header.textContent).toContain("anthropic/claude-sonnet-4-5");
|
||||||
expect(header).toBeTruthy();
|
|
||||||
expect(header!.textContent).toContain("Planning:");
|
|
||||||
expect(header!.textContent).toContain("anthropic/claude-sonnet-4-5");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Planning falls back to default - same as executor/validator
|
// Planning falls back to default - same as executor/validator
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']")!;
|
|
||||||
const matches = header.textContent!.match(/anthropic\/claude-sonnet-4-5/g);
|
const matches = header.textContent!.match(/anthropic\/claude-sonnet-4-5/g);
|
||||||
expect(matches).toHaveLength(3); // executor, validator, planning
|
expect(matches).toHaveLength(3); // executor, validator, planning
|
||||||
});
|
});
|
||||||
@@ -2039,17 +1965,8 @@ describe("TaskDetailModal", () => {
|
|||||||
// No defaultProvider/defaultModelId
|
// No defaultProvider/defaultModelId
|
||||||
});
|
});
|
||||||
|
|
||||||
// Navigate to Agent Log subview
|
const header = await openAgentLogAndExpandModelDetails(container);
|
||||||
fireEvent.click(screen.getByText("Logs"));
|
expect(header.textContent).toContain("Planning/Triage:");
|
||||||
fireEvent.click(screen.getByText("Agent Log"));
|
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
|
||||||
expect(header).toBeTruthy();
|
|
||||||
});
|
|
||||||
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']")!;
|
|
||||||
expect(header.textContent).toContain("Planning:");
|
|
||||||
const defaultBadges = header.querySelectorAll(".model-badge-default");
|
const defaultBadges = header.querySelectorAll(".model-badge-default");
|
||||||
// 3 default badges: executor, validator, planning
|
// 3 default badges: executor, validator, planning
|
||||||
expect(defaultBadges).toHaveLength(3);
|
expect(defaultBadges).toHaveLength(3);
|
||||||
@@ -2091,19 +2008,10 @@ describe("TaskDetailModal", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Navigate to Agent Log subview
|
const header = await openAgentLogAndExpandModelDetails(container);
|
||||||
fireEvent.click(screen.getByText("Logs"));
|
// Per-task override should take precedence over settings
|
||||||
fireEvent.click(screen.getByText("Agent Log"));
|
expect(header.textContent).toContain("Planning/Triage:");
|
||||||
|
expect(header.textContent).toContain("google/gemini-2.5-pro");
|
||||||
await waitFor(() => {
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
|
||||||
expect(header).toBeTruthy();
|
|
||||||
// Per-task override should take precedence over settings
|
|
||||||
expect(header!.textContent).toContain("Planning:");
|
|
||||||
expect(header!.textContent).toContain("google/gemini-2.5-pro");
|
|
||||||
});
|
|
||||||
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']")!;
|
|
||||||
// Should NOT show the settings planning model
|
// Should NOT show the settings planning model
|
||||||
expect(header.textContent).not.toContain("openai/gpt-4o");
|
expect(header.textContent).not.toContain("openai/gpt-4o");
|
||||||
});
|
});
|
||||||
@@ -2143,18 +2051,9 @@ describe("TaskDetailModal", () => {
|
|||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Navigate to Agent Log subview
|
const header = await openAgentLogAndExpandModelDetails(container);
|
||||||
fireEvent.click(screen.getByText("Logs"));
|
// Runtime marker should win over planning settings
|
||||||
fireEvent.click(screen.getByText("Agent Log"));
|
expect(header.textContent).toContain("google/gemini-pro");
|
||||||
|
|
||||||
await waitFor(() => {
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']");
|
|
||||||
expect(header).toBeTruthy();
|
|
||||||
// Runtime marker should win over planning settings
|
|
||||||
expect(header!.textContent).toContain("google/gemini-pro");
|
|
||||||
});
|
|
||||||
|
|
||||||
const header = container.querySelector("[data-testid='agent-log-model-header']")!;
|
|
||||||
// Should NOT show the planning settings model
|
// Should NOT show the planning settings model
|
||||||
expect(header.textContent).not.toContain("openai/gpt-4o");
|
expect(header.textContent).not.toContain("openai/gpt-4o");
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user