fix(webui): delete unpersisted pane sessions (#5624)

Co-authored-by: chengyongru <chengyongru.ai@gmail.com>
This commit is contained in:
Kail Tian
2026-09-02 13:05:36 +08:00
committed by GitHub
co-authored by chengyongru
parent 9ecdc4533f
commit 042f96f6ba
5 changed files with 108 additions and 3 deletions
+5 -2
View File
@@ -257,11 +257,14 @@ export function useSessions(): {
const deleteChat = useCallback(
async (key: string, options?: { deleteAutomations?: boolean }) => {
const optimistic = optimisticKeysRef.current.has(key);
const result = await apiDeleteSession(client, key, options);
if (!result.deleted) return result;
if (result.blocked_by_automations || (!result.deleted && !optimistic)) return result;
optimisticKeysRef.current.delete(key);
setSessions((prev) => prev.filter((s) => s.key !== key));
return result;
// The gateway may have restarted and forgotten an unpersisted chat's
// draft scope, but removing that optimistic session is still a deletion.
return result.deleted ? result : { ...result, deleted: true };
},
[client],
);
+53
View File
@@ -124,6 +124,59 @@ describe("useSessions", () => {
expect(result.current.sessions.map((s) => s.key)).toEqual(["websocket:chat-b"]);
});
it("removes an optimistic chat when the gateway no longer has its draft", async () => {
vi.mocked(api.listSessions).mockResolvedValue([]);
vi.mocked(api.deleteSession).mockResolvedValue({ deleted: false });
const client = fakeClient();
client.newChat.mockResolvedValue("chat-empty");
const { result } = renderHook(() => useSessions(), {
wrapper: wrap(client),
});
await waitFor(() => expect(result.current.loading).toBe(false));
await act(async () => {
await result.current.createChat();
});
expect(result.current.sessions.map((s) => s.key)).toEqual(["websocket:chat-empty"]);
let deleteResult: Awaited<ReturnType<typeof result.current.deleteChat>> | undefined;
await act(async () => {
deleteResult = await result.current.deleteChat("websocket:chat-empty");
});
expect(deleteResult?.deleted).toBe(true);
expect(result.current.sessions).toEqual([]);
});
it("keeps an optimistic chat when delete is blocked by bound automations", async () => {
vi.mocked(api.listSessions).mockResolvedValue([]);
vi.mocked(api.deleteSession).mockResolvedValue({
deleted: false,
blocked_by_automations: true,
automations: [],
});
const client = fakeClient();
client.newChat.mockResolvedValue("chat-empty");
const { result } = renderHook(() => useSessions(), {
wrapper: wrap(client),
});
await waitFor(() => expect(result.current.loading).toBe(false));
await act(async () => {
await result.current.createChat();
});
let deleteResult: Awaited<ReturnType<typeof result.current.deleteChat>> | undefined;
await act(async () => {
deleteResult = await result.current.deleteChat("websocket:chat-empty");
});
expect(deleteResult?.blocked_by_automations).toBe(true);
expect(result.current.sessions.map((s) => s.key)).toEqual(["websocket:chat-empty"]);
});
it("keeps a session when delete is blocked by bound automations", async () => {
vi.mocked(api.listSessions).mockResolvedValue([
{