diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderChatPanel/__tests__/useBuilderChatPanel.test.ts b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderChatPanel/__tests__/useBuilderChatPanel.test.ts index 783f42930d..9c121bc241 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderChatPanel/__tests__/useBuilderChatPanel.test.ts +++ b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderChatPanel/__tests__/useBuilderChatPanel.test.ts @@ -252,6 +252,58 @@ describe("useBuilderChatPanel – no auto-send on open", () => { }); }); +describe("useBuilderChatPanel – seed message", () => { + it("sends seed message via sendMessage when session is available and isGraphLoaded=true", async () => { + mockPostV2CreateSession.mockResolvedValue({ + status: 200, + data: { id: "sess-seed" }, + }); + mockNodes.push({ id: "n1", data: { title: "Search", description: "" } }); + + const { result } = renderHook(() => + useBuilderChatPanel({ isGraphLoaded: true }), + ); + + await openAndFlush(() => result.current.handleToggle()); + + expect(mockSendMessage).toHaveBeenCalledOnce(); + const callArg = mockSendMessage.mock.calls[0][0] as { text: string }; + expect(typeof callArg.text).toBe("string"); + expect(callArg.text).toContain("I'm building an agent"); + }); + + it("does NOT send seed message when isGraphLoaded is false (default)", async () => { + mockPostV2CreateSession.mockResolvedValue({ + status: 200, + data: { id: "sess-no-seed" }, + }); + + const { result } = renderHook(() => useBuilderChatPanel()); + + await openAndFlush(() => result.current.handleToggle()); + + expect(mockSendMessage).not.toHaveBeenCalled(); + }); + + it("sends seed message only once even when sessionId and isGraphLoaded deps re-run (hasSentSeedMessageRef guard)", async () => { + mockPostV2CreateSession.mockResolvedValue({ + status: 200, + data: { id: "sess-once" }, + }); + + const { result, rerender } = renderHook(() => + useBuilderChatPanel({ isGraphLoaded: true }), + ); + + await openAndFlush(() => result.current.handleToggle()); + expect(mockSendMessage).toHaveBeenCalledOnce(); + + rerender(); + + expect(mockSendMessage).toHaveBeenCalledOnce(); + }); +}); + describe("useBuilderChatPanel – flowID reset", () => { it("resets appliedActionKeys when flowID changes", () => { mockNodes.push({ id: "n1", data: { hardcodedValues: {} } }); @@ -1208,6 +1260,31 @@ describe("useBuilderChatPanel – transport prepareSendMessagesRequest", () => { ctorArg.prepareSendMessagesRequest({ messages }), ).rejects.toThrow("Authentication failed"); }); + + it("throws when messages array is empty (empty messages guard)", async () => { + const { DefaultChatTransport } = await import("ai"); + const MockTransport = DefaultChatTransport as ReturnType; + + mockPostV2CreateSession.mockResolvedValue({ + status: 200, + data: { id: "sess-empty-msg" }, + }); + + const { result } = renderHook(() => useBuilderChatPanel()); + + await openAndFlush(() => result.current.handleToggle()); + + const ctorArg = MockTransport.mock.calls[ + MockTransport.mock.calls.length - 1 + ][0] as { + prepareSendMessagesRequest: (args: { + messages: unknown[]; + }) => Promise; + }; + await expect( + ctorArg.prepareSendMessagesRequest({ messages: [] }), + ).rejects.toThrow("No message to send"); + }); }); describe("useBuilderChatPanel – handleKeyDown empty input guard", () => { diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderChatPanel/useBuilderChatPanel.ts b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderChatPanel/useBuilderChatPanel.ts index a378cd6ecf..4c50386744 100644 --- a/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderChatPanel/useBuilderChatPanel.ts +++ b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderChatPanel/useBuilderChatPanel.ts @@ -207,7 +207,11 @@ export function useBuilderChatPanel({ ? new DefaultChatTransport({ api: `${environment.getAGPTServerBaseUrl()}/api/chat/sessions/${sessionId}/stream`, prepareSendMessagesRequest: async ({ messages }) => { - const last = messages[messages.length - 1]; + const last = messages.at(-1); + if (!last) + throw new Error( + "No message to send — messages array is empty.", + ); const { token, error } = await getWebSocketToken(); if (error || !token) throw new Error(