feat(FN-3466): add mutation coverage for agent view refreshes
Adds deterministic split-pane refresh behavior to the Agents views (AgentsView and AgentDetailView), with comprehensive test coverage for the mutation refreshes in the detail pane and the agents hook. Fusion-Task-Id: FN-3466
This commit is contained in:
@@ -107,6 +107,7 @@ The agents surface provides:
|
|||||||
- A cross-pane **Overview** strip above the split layout with summary metrics and a disclosure to expand active/running live cards
|
- A cross-pane **Overview** strip above the split layout with summary metrics and a disclosure to expand active/running live cards
|
||||||
- A compact **Controls** popup for secondary actions (state filter, Show system agents toggle, Import, and global Heartbeat Speed)
|
- A compact **Controls** popup for secondary actions (state filter, Show system agents toggle, Import, and global Heartbeat Speed)
|
||||||
- Detail/config panels
|
- Detail/config panels
|
||||||
|
- Split-view synchronization: successful saves and lifecycle actions from the right-side Agent Detail pane immediately refresh the left-side list/selection state (no wait for background polling)
|
||||||
- A per-agent **Token Usage** panel that summarizes cumulative token consumption for the currently displayed agents
|
- A per-agent **Token Usage** panel that summarizes cumulative token consumption for the currently displayed agents
|
||||||
- Run history
|
- Run history
|
||||||
- Task assignment context
|
- Task assignment context
|
||||||
|
|||||||
@@ -68,6 +68,7 @@ interface AgentDetailViewProps {
|
|||||||
initialTab?: TabId;
|
initialTab?: TabId;
|
||||||
initialRunId?: string | null;
|
initialRunId?: string | null;
|
||||||
preferActiveRun?: boolean;
|
preferActiveRun?: boolean;
|
||||||
|
onMutationSuccess?: (context: { agentId: string; deleted?: boolean }) => void | Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
type TabId = "dashboard" | "logs" | "config" | "runs" | "tasks" | "employees" | "soul" | "instructions" | "memory" | "reflections";
|
type TabId = "dashboard" | "logs" | "config" | "runs" | "tasks" | "employees" | "soul" | "instructions" | "memory" | "reflections";
|
||||||
@@ -126,7 +127,7 @@ function pickDefaultAgentMemoryPath(files: MemoryFileInfo[], currentPath: string
|
|||||||
?? "";
|
?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
export function AgentDetailView({ agentId, projectId, onClose, addToast, onChildClick, inline = false, showInlineBackButton = false, initialTab, initialRunId, preferActiveRun = false }: AgentDetailViewProps) {
|
export function AgentDetailView({ agentId, projectId, onClose, addToast, onChildClick, inline = false, showInlineBackButton = false, initialTab, initialRunId, preferActiveRun = false, onMutationSuccess }: AgentDetailViewProps) {
|
||||||
const [agent, setAgent] = useState<AgentDetail | null>(null);
|
const [agent, setAgent] = useState<AgentDetail | null>(null);
|
||||||
const { confirm } = useConfirm();
|
const { confirm } = useConfirm();
|
||||||
const [logs, setLogs] = useState<AgentLogEntry[]>([]);
|
const [logs, setLogs] = useState<AgentLogEntry[]>([]);
|
||||||
@@ -220,6 +221,15 @@ export function AgentDetailView({ agentId, projectId, onClose, addToast, onChild
|
|||||||
hasConfigChangesRef.current = hasChanges;
|
hasConfigChangesRef.current = hasChanges;
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const notifyMutationSuccess = useCallback(async (deleted = false) => {
|
||||||
|
await onMutationSuccess?.({ agentId, deleted });
|
||||||
|
}, [agentId, onMutationSuccess]);
|
||||||
|
|
||||||
|
const handleSavedMutation = useCallback(async () => {
|
||||||
|
await loadAgent();
|
||||||
|
await notifyMutationSuccess(false);
|
||||||
|
}, [loadAgent, notifyMutationSuccess]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void loadAgent();
|
void loadAgent();
|
||||||
}, [loadAgent]);
|
}, [loadAgent]);
|
||||||
@@ -396,7 +406,7 @@ export function AgentDetailView({ agentId, projectId, onClose, addToast, onChild
|
|||||||
try {
|
try {
|
||||||
await updateAgentState(agentId, newState, projectId);
|
await updateAgentState(agentId, newState, projectId);
|
||||||
addToast(`Agent state updated to ${newState}`, "success");
|
addToast(`Agent state updated to ${newState}`, "success");
|
||||||
void loadAgent();
|
await handleSavedMutation();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
setAgent((prev) => (prev ? { ...prev, state: previousState } : prev));
|
setAgent((prev) => (prev ? { ...prev, state: previousState } : prev));
|
||||||
addToast(`Failed to update state: ${getErrorMessage(err)}`, "error");
|
addToast(`Failed to update state: ${getErrorMessage(err)}`, "error");
|
||||||
@@ -416,6 +426,7 @@ export function AgentDetailView({ agentId, projectId, onClose, addToast, onChild
|
|||||||
try {
|
try {
|
||||||
await deleteAgent(agentId, projectId);
|
await deleteAgent(agentId, projectId);
|
||||||
addToast(`Agent "${agent.name}" deleted`, "success");
|
addToast(`Agent "${agent.name}" deleted`, "success");
|
||||||
|
await notifyMutationSuccess(true);
|
||||||
onClose();
|
onClose();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addToast(`Failed to delete agent: ${getErrorMessage(err)}`, "error");
|
addToast(`Failed to delete agent: ${getErrorMessage(err)}`, "error");
|
||||||
@@ -681,7 +692,7 @@ export function AgentDetailView({ agentId, projectId, onClose, addToast, onChild
|
|||||||
agent={agent}
|
agent={agent}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
addToast={addToast}
|
addToast={addToast}
|
||||||
onSaved={loadAgent}
|
onSaved={handleSavedMutation}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -690,7 +701,7 @@ export function AgentDetailView({ agentId, projectId, onClose, addToast, onChild
|
|||||||
agent={agent}
|
agent={agent}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
addToast={addToast}
|
addToast={addToast}
|
||||||
onSaved={loadAgent}
|
onSaved={handleSavedMutation}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -699,7 +710,7 @@ export function AgentDetailView({ agentId, projectId, onClose, addToast, onChild
|
|||||||
agent={agent}
|
agent={agent}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
addToast={addToast}
|
addToast={addToast}
|
||||||
onSaved={loadAgent}
|
onSaved={handleSavedMutation}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
@@ -717,7 +728,7 @@ export function AgentDetailView({ agentId, projectId, onClose, addToast, onChild
|
|||||||
agent={agent}
|
agent={agent}
|
||||||
projectId={projectId}
|
projectId={projectId}
|
||||||
addToast={addToast}
|
addToast={addToast}
|
||||||
onSaved={loadAgent}
|
onSaved={handleSavedMutation}
|
||||||
onHasChangesChange={handleConfigChangesState}
|
onHasChangesChange={handleConfigChangesState}
|
||||||
onDelete={handleDelete}
|
onDelete={handleDelete}
|
||||||
/>
|
/>
|
||||||
@@ -2061,6 +2072,7 @@ function MemoryTab({
|
|||||||
setFileSwitchHint("");
|
setFileSwitchHint("");
|
||||||
await loadMemoryFiles(selectedFilePath);
|
await loadMemoryFiles(selectedFilePath);
|
||||||
addToast("Agent memory file saved", "success");
|
addToast("Agent memory file saved", "success");
|
||||||
|
await onSaved();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addToast(`Failed to save agent memory file: ${getErrorMessage(err)}`, "error");
|
addToast(`Failed to save agent memory file: ${getErrorMessage(err)}`, "error");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -2398,6 +2410,7 @@ function InstructionsTab({
|
|||||||
clearTimeout(justSavedFileTimeoutRef.current);
|
clearTimeout(justSavedFileTimeoutRef.current);
|
||||||
}
|
}
|
||||||
justSavedFileTimeoutRef.current = setTimeout(() => setJustSavedFile(false), 3000);
|
justSavedFileTimeoutRef.current = setTimeout(() => setJustSavedFile(false), 3000);
|
||||||
|
await onSaved();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addToast(`Failed to save instructions file: ${getErrorMessage(err)}`, "error");
|
addToast(`Failed to save instructions file: ${getErrorMessage(err)}`, "error");
|
||||||
} finally {
|
} finally {
|
||||||
@@ -2734,6 +2747,7 @@ function HeartbeatProcedureSection({
|
|||||||
clearTimeout(justSavedFileTimeoutRef.current);
|
clearTimeout(justSavedFileTimeoutRef.current);
|
||||||
}
|
}
|
||||||
justSavedFileTimeoutRef.current = setTimeout(() => setJustSavedFile(false), 3000);
|
justSavedFileTimeoutRef.current = setTimeout(() => setJustSavedFile(false), 3000);
|
||||||
|
await onSaved();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addToast(`Failed to save heartbeat procedure file: ${getErrorMessage(err)}`, "error");
|
addToast(`Failed to save heartbeat procedure file: ${getErrorMessage(err)}`, "error");
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@@ -226,7 +226,7 @@ export function AgentsView({ addToast, projectId, onOpenTaskLogs, agentOnboardin
|
|||||||
const viewportMode = useViewportMode();
|
const viewportMode = useViewportMode();
|
||||||
const isMobileViewport = viewportMode === "mobile";
|
const isMobileViewport = viewportMode === "mobile";
|
||||||
const [filterState, setFilterState] = useState<AgentState | "all">("all");
|
const [filterState, setFilterState] = useState<AgentState | "all">("all");
|
||||||
const { agents, stats, isLoading, loadAgents } = useAgents(projectId, {
|
const { agents, stats, isLoading, loadAgents, refreshAgents } = useAgents(projectId, {
|
||||||
filterState,
|
filterState,
|
||||||
showSystemAgents,
|
showSystemAgents,
|
||||||
});
|
});
|
||||||
@@ -486,7 +486,7 @@ export function AgentsView({ addToast, projectId, onOpenTaskLogs, agentOnboardin
|
|||||||
try {
|
try {
|
||||||
await deleteAgent(agentId, projectId);
|
await deleteAgent(agentId, projectId);
|
||||||
addToast(`Agent "${agentName}" deleted`, "success");
|
addToast(`Agent "${agentName}" deleted`, "success");
|
||||||
void loadAgents();
|
await loadAgents();
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
addToast(`Failed to delete agent: ${getErrorMessage(err)}`, "error");
|
addToast(`Failed to delete agent: ${getErrorMessage(err)}`, "error");
|
||||||
}
|
}
|
||||||
@@ -675,6 +675,13 @@ export function AgentsView({ addToast, projectId, onOpenTaskLogs, agentOnboardin
|
|||||||
openAgentDetail(agentId);
|
openAgentDetail(agentId);
|
||||||
}, [openAgentDetail]);
|
}, [openAgentDetail]);
|
||||||
|
|
||||||
|
const handleDetailMutationSuccess = useCallback(async ({ agentId, deleted }: { agentId: string; deleted?: boolean }) => {
|
||||||
|
await refreshAgents();
|
||||||
|
if (deleted && selectedAgentId === agentId) {
|
||||||
|
handleCloseDetail();
|
||||||
|
}
|
||||||
|
}, [refreshAgents, selectedAgentId, handleCloseDetail]);
|
||||||
|
|
||||||
const handleOverviewAgentSelect = useCallback((agentId: string) => {
|
const handleOverviewAgentSelect = useCallback((agentId: string) => {
|
||||||
openAgentDetail(agentId);
|
openAgentDetail(agentId);
|
||||||
if (isMobileViewport) {
|
if (isMobileViewport) {
|
||||||
@@ -1442,6 +1449,7 @@ export function AgentsView({ addToast, projectId, onOpenTaskLogs, agentOnboardin
|
|||||||
initialTab={selectedAgentInitialTab}
|
initialTab={selectedAgentInitialTab}
|
||||||
initialRunId={selectedAgentInitialRunId}
|
initialRunId={selectedAgentInitialRunId}
|
||||||
preferActiveRun={selectedAgentPreferActiveRun}
|
preferActiveRun={selectedAgentPreferActiveRun}
|
||||||
|
onMutationSuccess={handleDetailMutationSuccess}
|
||||||
/>
|
/>
|
||||||
</Suspense>
|
</Suspense>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -776,6 +776,25 @@ describe("AgentDetailView", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("notifies parent mutation callback after successful state change", async () => {
|
||||||
|
const onMutationSuccess = vi.fn();
|
||||||
|
|
||||||
|
render(
|
||||||
|
<AgentDetailView
|
||||||
|
agentId="agent-001"
|
||||||
|
onClose={vi.fn()}
|
||||||
|
addToast={vi.fn()}
|
||||||
|
onMutationSuccess={onMutationSuccess}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
|
||||||
|
await userEvent.click(await screen.findByText("Pause"));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(onMutationSuccess).toHaveBeenCalledWith({ agentId: "agent-001", deleted: false });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("shows Resume button for paused agent", async () => {
|
it("shows Resume button for paused agent", async () => {
|
||||||
mockFetchAgent.mockResolvedValue(createMockAgent({ state: "paused" }));
|
mockFetchAgent.mockResolvedValue(createMockAgent({ state: "paused" }));
|
||||||
|
|
||||||
@@ -1538,6 +1557,7 @@ describe("AgentDetailView", () => {
|
|||||||
mockFetchAgent.mockResolvedValue(createMockAgent({ state: "idle" }));
|
mockFetchAgent.mockResolvedValue(createMockAgent({ state: "idle" }));
|
||||||
const addToast = vi.fn();
|
const addToast = vi.fn();
|
||||||
const onClose = vi.fn();
|
const onClose = vi.fn();
|
||||||
|
const onMutationSuccess = vi.fn();
|
||||||
const user = userEvent.setup();
|
const user = userEvent.setup();
|
||||||
|
|
||||||
render(
|
render(
|
||||||
@@ -1546,6 +1566,7 @@ describe("AgentDetailView", () => {
|
|||||||
projectId="proj_123"
|
projectId="proj_123"
|
||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
addToast={addToast}
|
addToast={addToast}
|
||||||
|
onMutationSuccess={onMutationSuccess}
|
||||||
/>,
|
/>,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -1560,6 +1581,7 @@ describe("AgentDetailView", () => {
|
|||||||
});
|
});
|
||||||
expect(mockDeleteAgent).toHaveBeenCalledWith("agent-001", "proj_123");
|
expect(mockDeleteAgent).toHaveBeenCalledWith("agent-001", "proj_123");
|
||||||
expect(addToast).toHaveBeenCalledWith('Agent "Test Agent" deleted', "success");
|
expect(addToast).toHaveBeenCalledWith('Agent "Test Agent" deleted', "success");
|
||||||
|
expect(onMutationSuccess).toHaveBeenCalledWith({ agentId: "agent-001", deleted: true });
|
||||||
expect(onClose).toHaveBeenCalledTimes(1);
|
expect(onClose).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -3563,6 +3585,7 @@ describe("AgentDetailView", () => {
|
|||||||
|
|
||||||
it("calls saveWorkspaceFileContent when saving file content", async () => {
|
it("calls saveWorkspaceFileContent when saving file content", async () => {
|
||||||
const addToast = vi.fn();
|
const addToast = vi.fn();
|
||||||
|
const onMutationSuccess = vi.fn();
|
||||||
mockFetchAgent.mockResolvedValue(createMockAgent({
|
mockFetchAgent.mockResolvedValue(createMockAgent({
|
||||||
instructionsPath: ".fusion/agents/test.md",
|
instructionsPath: ".fusion/agents/test.md",
|
||||||
}));
|
}));
|
||||||
@@ -3578,6 +3601,7 @@ describe("AgentDetailView", () => {
|
|||||||
agentId="agent-001"
|
agentId="agent-001"
|
||||||
onClose={vi.fn()}
|
onClose={vi.fn()}
|
||||||
addToast={addToast}
|
addToast={addToast}
|
||||||
|
onMutationSuccess={onMutationSuccess}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -3602,6 +3626,7 @@ describe("AgentDetailView", () => {
|
|||||||
".fusion/agents/test.md",
|
".fusion/agents/test.md",
|
||||||
"Updated content",
|
"Updated content",
|
||||||
);
|
);
|
||||||
|
expect(onMutationSuccess).toHaveBeenCalledWith({ agentId: "agent-001", deleted: false });
|
||||||
});
|
});
|
||||||
expect(addToast).toHaveBeenCalledWith("Instructions file saved", "success");
|
expect(addToast).toHaveBeenCalledWith("Instructions file saved", "success");
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -31,11 +31,12 @@ vi.mock("../../api", async (importOriginal) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
vi.mock("../AgentDetailView", () => ({
|
vi.mock("../AgentDetailView", () => ({
|
||||||
AgentDetailView: ({ agentId, inline, onClose, showInlineBackButton, initialTab, initialRunId, preferActiveRun }: { agentId: string; inline?: boolean; onClose?: () => void; showInlineBackButton?: boolean; initialTab?: string; initialRunId?: string | null; preferActiveRun?: boolean }) => (
|
AgentDetailView: ({ agentId, inline, onClose, showInlineBackButton, initialTab, initialRunId, preferActiveRun, onMutationSuccess }: { agentId: string; inline?: boolean; onClose?: () => void; showInlineBackButton?: boolean; initialTab?: string; initialRunId?: string | null; preferActiveRun?: boolean; onMutationSuccess?: (context: { agentId: string; deleted?: boolean }) => void | Promise<void> }) => (
|
||||||
<div data-testid="agent-detail-view" data-inline={inline ? "true" : "false"} data-initial-tab={initialTab ?? "dashboard"} data-initial-run-id={initialRunId ?? ""} data-prefer-active-run={preferActiveRun ? "true" : "false"}>
|
<div data-testid="agent-detail-view" data-inline={inline ? "true" : "false"} data-initial-tab={initialTab ?? "dashboard"} data-initial-run-id={initialRunId ?? ""} data-prefer-active-run={preferActiveRun ? "true" : "false"}>
|
||||||
{showInlineBackButton ? (
|
{showInlineBackButton ? (
|
||||||
<button type="button" aria-label="Back to agents" onClick={onClose}>Agents</button>
|
<button type="button" aria-label="Back to agents" onClick={onClose}>Agents</button>
|
||||||
) : null}
|
) : null}
|
||||||
|
<button type="button" onClick={() => void onMutationSuccess?.({ agentId })}>Trigger detail mutation success</button>
|
||||||
Agent detail: {agentId}
|
Agent detail: {agentId}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
@@ -629,6 +630,29 @@ describe("AgentsView", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("refreshes left-pane list immediately when detail pane reports a successful mutation", async () => {
|
||||||
|
mockFetchAgents
|
||||||
|
.mockResolvedValueOnce(mockAgents)
|
||||||
|
.mockResolvedValueOnce([
|
||||||
|
{ ...mockAgents[0], name: "Renamed Agent" },
|
||||||
|
...mockAgents.slice(1),
|
||||||
|
]);
|
||||||
|
|
||||||
|
render(<AgentsView addToast={mockAddToast} />);
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.getAllByText("Test Agent 1").length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
fireEvent.click(screen.getByRole("button", { name: "View details for Test Agent 1" }));
|
||||||
|
fireEvent.click(await screen.findByRole("button", { name: "Trigger detail mutation success" }));
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockFetchAgents).toHaveBeenCalledTimes(2);
|
||||||
|
expect(screen.getAllByText("Renamed Agent").length).toBeGreaterThan(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it("keeps clickable identity area behavior for opening detail view", async () => {
|
it("keeps clickable identity area behavior for opening detail view", async () => {
|
||||||
render(<AgentsView addToast={mockAddToast} />);
|
render(<AgentsView addToast={mockAddToast} />);
|
||||||
|
|
||||||
|
|||||||
@@ -118,6 +118,25 @@ describe("useAgents", () => {
|
|||||||
expect(mockFetchAgents).toHaveBeenLastCalledWith({ state: "active", role: "executor", includeEphemeral: false }, undefined);
|
expect(mockFetchAgents).toHaveBeenLastCalledWith({ state: "active", role: "executor", includeEphemeral: false }, undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("refreshAgents reloads both list and stats in one call", async () => {
|
||||||
|
const { result } = renderHook(() => useAgents());
|
||||||
|
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(mockFetchAgents).toHaveBeenCalled();
|
||||||
|
expect(mockFetchAgentStats).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
mockFetchAgents.mockClear();
|
||||||
|
mockFetchAgentStats.mockClear();
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
await result.current.refreshAgents();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockFetchAgents).toHaveBeenCalledTimes(1);
|
||||||
|
expect(mockFetchAgentStats).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
it("handles fetchAgents rejection gracefully", async () => {
|
it("handles fetchAgents rejection gracefully", async () => {
|
||||||
mockFetchAgents.mockRejectedValueOnce(new Error("agents failed"));
|
mockFetchAgents.mockRejectedValueOnce(new Error("agents failed"));
|
||||||
|
|
||||||
|
|||||||
@@ -79,6 +79,10 @@ export function useAgents(projectId?: string, options?: UseAgentsOptions) {
|
|||||||
});
|
});
|
||||||
}, [projectId, loadAgents, loadStats]);
|
}, [projectId, loadAgents, loadStats]);
|
||||||
|
|
||||||
|
const refreshAgents = useCallback(async () => {
|
||||||
|
await Promise.all([loadAgents(), loadStats()]);
|
||||||
|
}, [loadAgents, loadStats]);
|
||||||
|
|
||||||
const showSystemAgents = options?.showSystemAgents ?? false;
|
const showSystemAgents = options?.showSystemAgents ?? false;
|
||||||
const activeAgents = agents.filter((agent) => {
|
const activeAgents = agents.filter((agent) => {
|
||||||
if (agent.state !== "active" && agent.state !== "running") {
|
if (agent.state !== "active" && agent.state !== "running") {
|
||||||
@@ -87,5 +91,5 @@ export function useAgents(projectId?: string, options?: UseAgentsOptions) {
|
|||||||
return showSystemAgents || !isEphemeralAgent(agent);
|
return showSystemAgents || !isEphemeralAgent(agent);
|
||||||
});
|
});
|
||||||
|
|
||||||
return { agents, activeAgents, stats, isLoading, loadAgents, loadStats };
|
return { agents, activeAgents, stats, isLoading, loadAgents, loadStats, refreshAgents };
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user