mirror of
https://github.com/paperclipai/paperclip.git
synced 2026-11-11 05:11:49 +01:00
Fix sidebar agent action edge cases
Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
1 parent
f5fafa369e
commit
8730e30ed8
2 files changed
+102
-4
No files matched your search
@@ -238,4 +238,76 @@ describe("SidebarAgents", () => {
|
||||
expect(mockAgentsApi.resume).toHaveBeenCalledWith("agent-1", "company-1");
|
||||
expect(mockPushToast).toHaveBeenCalledWith(expect.objectContaining({ title: "Agent resumed" }));
|
||||
});
|
||||
|
||||
it("only shows updating state for the agent currently being changed", async () => {
|
||||
mockAgentsApi.list.mockResolvedValue([
|
||||
makeAgent({ id: "agent-1", name: "Alpha", urlKey: "alpha" }),
|
||||
makeAgent({ id: "agent-2", name: "Beta", urlKey: "beta" }),
|
||||
]);
|
||||
mockAgentsApi.pause.mockImplementation(() => new Promise(() => {}));
|
||||
const currentRoot = createRoot(container);
|
||||
root = currentRoot;
|
||||
|
||||
await act(async () => {
|
||||
currentRoot.render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SidebarAgents />
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
});
|
||||
await flushReact();
|
||||
await openAgentMenu();
|
||||
|
||||
const pauseItem = Array.from(document.body.querySelectorAll('[data-slot="dropdown-menu-item"]'))
|
||||
.find((element) => element.textContent?.includes("Pause agent"));
|
||||
expect(pauseItem).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
pauseItem?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
});
|
||||
await flushReact();
|
||||
await openAgentMenu("Open actions for Beta");
|
||||
|
||||
const betaPauseItem = Array.from(
|
||||
document.body.querySelectorAll('[data-slot="dropdown-menu-item"]'),
|
||||
)
|
||||
.find((element) => element.textContent?.includes("Pause agent"));
|
||||
expect(betaPauseItem).toBeTruthy();
|
||||
expect(document.body.textContent).not.toContain("Updating...");
|
||||
});
|
||||
|
||||
it("does not offer sidebar resume for budget-paused agents", async () => {
|
||||
mockAgentsApi.list.mockResolvedValue([
|
||||
makeAgent({
|
||||
status: "paused",
|
||||
pauseReason: "budget",
|
||||
pausedAt: new Date("2026-01-02T00:00:00Z"),
|
||||
}),
|
||||
]);
|
||||
const currentRoot = createRoot(container);
|
||||
root = currentRoot;
|
||||
|
||||
await act(async () => {
|
||||
currentRoot.render(
|
||||
<QueryClientProvider client={queryClient}>
|
||||
<SidebarAgents />
|
||||
</QueryClientProvider>,
|
||||
);
|
||||
});
|
||||
await flushReact();
|
||||
await openAgentMenu();
|
||||
|
||||
const budgetPausedItem = Array.from(
|
||||
document.body.querySelectorAll('[data-slot="dropdown-menu-item"]'),
|
||||
)
|
||||
.find((element) => element.textContent?.includes("Budget paused"));
|
||||
expect(budgetPausedItem).toBeTruthy();
|
||||
|
||||
await act(async () => {
|
||||
budgetPausedItem?.dispatchEvent(new MouseEvent("click", { bubbles: true }));
|
||||
});
|
||||
await flushReact();
|
||||
|
||||
expect(mockAgentsApi.resume).not.toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
@@ -61,7 +61,14 @@ function SidebarAgentItem({
|
||||
const editHref = `${agentUrl(agent)}/configuration`;
|
||||
const isActive = activeAgentId === routeRef;
|
||||
const isPaused = agent.status === "paused";
|
||||
const isBudgetPaused = isPaused && agent.pauseReason === "budget";
|
||||
const pauseResumeLabel = isPaused ? "Resume agent" : "Pause agent";
|
||||
const pauseResumeDisabled = disabled || agent.status === "pending_approval" || isBudgetPaused;
|
||||
const pauseResumeDisabledLabel = disabled
|
||||
? "Updating..."
|
||||
: isBudgetPaused
|
||||
? "Budget paused"
|
||||
: pauseResumeLabel;
|
||||
|
||||
return (
|
||||
<div className="group/agent relative flex items-center">
|
||||
@@ -130,11 +137,15 @@ function SidebarAgentItem({
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem
|
||||
onClick={() => onPauseResume(agent, isPaused ? "resume" : "pause")}
|
||||
disabled={disabled || agent.status === "pending_approval"}
|
||||
onClick={() => {
|
||||
if (pauseResumeDisabled) return;
|
||||
onPauseResume(agent, isPaused ? "resume" : "pause");
|
||||
}}
|
||||
disabled={pauseResumeDisabled}
|
||||
title={isBudgetPaused ? "Agent was paused by budget limits" : undefined}
|
||||
>
|
||||
{isPaused ? <PlayCircle className="size-4" /> : <PauseCircle className="size-4" />}
|
||||
<span>{disabled ? "Updating..." : pauseResumeLabel}</span>
|
||||
<span>{pauseResumeDisabledLabel}</span>
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
@@ -144,6 +155,7 @@ function SidebarAgentItem({
|
||||
|
||||
export function SidebarAgents() {
|
||||
const [open, setOpen] = useState(true);
|
||||
const [pendingAgentIds, setPendingAgentIds] = useState<Set<string>>(() => new Set());
|
||||
const queryClient = useQueryClient();
|
||||
const { selectedCompanyId } = useCompany();
|
||||
const { openNewAgent } = useDialog();
|
||||
@@ -198,6 +210,13 @@ export function SidebarAgents() {
|
||||
action === "pause"
|
||||
? agentsApi.pause(agent.id, selectedCompanyId ?? undefined)
|
||||
: agentsApi.resume(agent.id, selectedCompanyId ?? undefined),
|
||||
onMutate: ({ agent }) => {
|
||||
setPendingAgentIds((current) => {
|
||||
const next = new Set(current);
|
||||
next.add(agent.id);
|
||||
return next;
|
||||
});
|
||||
},
|
||||
onSuccess: async (_agent, { agent, action }) => {
|
||||
if (selectedCompanyId) {
|
||||
await Promise.all([
|
||||
@@ -223,6 +242,13 @@ export function SidebarAgents() {
|
||||
tone: "error",
|
||||
});
|
||||
},
|
||||
onSettled: (_data, _error, { agent }) => {
|
||||
setPendingAgentIds((current) => {
|
||||
const next = new Set(current);
|
||||
next.delete(agent.id);
|
||||
return next;
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
@@ -263,7 +289,7 @@ export function SidebarAgents() {
|
||||
activeAgentId={activeAgentId}
|
||||
activeTab={activeTab}
|
||||
agent={agent}
|
||||
disabled={pauseResumeAgent.isPending}
|
||||
disabled={pendingAgentIds.has(agent.id)}
|
||||
isMobile={isMobile}
|
||||
onPauseResume={(targetAgent, action) => pauseResumeAgent.mutate({ agent: targetAgent, action })}
|
||||
runCount={runCount}
|
||||
|
||||
Reference in new issue
Block a user