From 5f938b31c9850a8f898e8e254dee63ff709b1daf Mon Sep 17 00:00:00 2001 From: Lily Shen <115414357+lilyshen0722@users.noreply.github.com> Date: Wed, 4 Nov 2026 00:47:03 -0800 Subject: [PATCH] fix(v2): after Discord setup, an ensemble pod returns to its kept room From CodeRabbit on #3369. An ensemble pod's owner can start Discord setup from the kept V1 room, so the return reads the pod's type again. An agent-ensemble pod goes back to /v2/pods/agent-ensemble/:id; every other pod, or a failed read, goes to the v2 pod view. A test covers the ensemble return and fails without the branch. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01MDs9DT6tbhKm6UhsmFhLk9 --- .../v2/__tests__/V2DiscordCallback.test.tsx | 18 +++++++++++++++++- .../src/v2/components/V2DiscordCallback.tsx | 14 ++++++++++++-- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/frontend/src/v2/__tests__/V2DiscordCallback.test.tsx b/frontend/src/v2/__tests__/V2DiscordCallback.test.tsx index 540d20da3..6f150e7fc 100644 --- a/frontend/src/v2/__tests__/V2DiscordCallback.test.tsx +++ b/frontend/src/v2/__tests__/V2DiscordCallback.test.tsx @@ -115,7 +115,23 @@ describe('V2DiscordCallback (d215 s-handoff)', () => { expect(screen.queryByRole('combobox')).toBeNull(); act(() => { jest.advanceTimersByTime(2000); }); expect(screen.getByText('at /v2/pods/p1')).toBeInTheDocument(); - expect(axios.get).not.toHaveBeenCalledWith('/api/pods/p1', expect.anything()); + }); + + test('an agent-ensemble pod returns to its kept room, where its controls live', async () => { + jest.useFakeTimers(); + localStorage.setItem('token', 'jwt'); + axios.get.mockImplementation((url) => (url === '/api/discord/channels/112233445566778899' + ? Promise.resolve({ data: [{ id: '112233445566778801', name: 'general' }] }) + : Promise.resolve({ data: { type: 'agent-ensemble' } }))); + axios.post.mockResolvedValue({ data: {} }); + renderAt('/discord/success?pod_id=p1&guild_id=112233445566778899&server_name=S'); + const select = await screen.findByRole('combobox', { name: 'Channel' }); + await screen.findByRole('option', { name: '#general' }); + fireEvent.change(select, { target: { value: '112233445566778801' } }); + await act(async () => { fireEvent.click(screen.getByRole('button', { name: 'Connect' })); }); + await waitFor(() => expect(screen.getByRole('status')).toBeInTheDocument()); + act(() => { jest.advanceTimersByTime(2000); }); + expect(screen.getByText('at /v2/pods/agent-ensemble/p1')).toBeInTheDocument(); }); test('a failed channel fetch and a failed create each land in the alert row', async () => { diff --git a/frontend/src/v2/components/V2DiscordCallback.tsx b/frontend/src/v2/components/V2DiscordCallback.tsx index 91eaf9573..fd0cbeca1 100644 --- a/frontend/src/v2/components/V2DiscordCallback.tsx +++ b/frontend/src/v2/components/V2DiscordCallback.tsx @@ -107,8 +107,18 @@ const V2DiscordCallback: React.FC = ({ type = 'callback' setSuccess(t('auth.discordHandoff.created')); - // The v2 pod view takes any pod type, so the return no longer reads the pod first. - setTimeout(() => navigate(`/v2/pods/${encodeURIComponent(podId)}`), 2000); + // Every pod returns to the v2 pod view, except agent-ensemble: its controls + // live only in the room kept at /v2/pods/agent-ensemble/:id. + let back = `/v2/pods/${encodeURIComponent(podId)}`; + try { + const podResponse = await axios.get<{ type?: string }>(`/api/pods/${podId}`, { + headers: { Authorization: `Bearer ${token}` }, + }); + if (podResponse.data.type === 'agent-ensemble') back = `/v2/pods/agent-ensemble/${encodeURIComponent(podId)}`; + } catch (podError) { + console.error('Error fetching pod info, returning to the pod view:', podError); + } + setTimeout(() => navigate(back), 2000); } catch (err: unknown) { console.error('Error creating integration:', err); const e = err as { response?: { data?: { message?: string } } };