diff --git a/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderChatPanel/__tests__/BuilderChatPanel.test.tsx b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderChatPanel/__tests__/BuilderChatPanel.test.tsx
index 8ff705ef56..aee47e7122 100644
--- a/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderChatPanel/__tests__/BuilderChatPanel.test.tsx
+++ b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderChatPanel/__tests__/BuilderChatPanel.test.tsx
@@ -214,6 +214,14 @@ describe("BuilderChatPanel", () => {
expect(screen.getByText(/Connection error/i)).toBeDefined();
});
+ it("shows session error message when sessionError is true", () => {
+ mockUseBuilderChatPanel.mockReturnValue(
+ makeMockHook({ isOpen: true, sessionError: true }),
+ );
+ render();
+ expect(screen.getByText(/Failed to start chat session/i)).toBeDefined();
+ });
+
it("renders the panel with role=dialog and message list with role=log", () => {
mockUseBuilderChatPanel.mockReturnValue(makeMockHook({ isOpen: true }));
render();
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
new file mode 100644
index 0000000000..594d2c886a
--- /dev/null
+++ b/autogpt_platform/frontend/src/app/(platform)/build/components/BuilderChatPanel/__tests__/useBuilderChatPanel.test.ts
@@ -0,0 +1,210 @@
+import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
+import { renderHook, act, cleanup } from "@testing-library/react";
+
+// --- Module mocks (must be hoisted before imports) ---
+
+// Bypass useShallow's ref-based shallow comparison so selectors work in tests.
+vi.mock("zustand/react/shallow", () => ({
+ useShallow: (fn: (s: unknown) => unknown) => fn,
+}));
+
+const mockNodes: unknown[] = [];
+const mockEdges: unknown[] = [];
+const mockUpdateNodeData = vi.fn();
+const mockAddEdge = vi.fn();
+
+vi.mock("../../../stores/nodeStore", () => ({
+ useNodeStore: (selector: (s: unknown) => unknown) =>
+ selector({
+ nodes: mockNodes,
+ updateNodeData: mockUpdateNodeData,
+ }),
+}));
+
+vi.mock("../../../stores/edgeStore", () => ({
+ useEdgeStore: (selector: (s: unknown) => unknown) =>
+ selector({
+ edges: mockEdges,
+ addEdge: mockAddEdge,
+ }),
+}));
+
+const mockPostV2CreateSession = vi.fn();
+vi.mock("@/app/api/__generated__/endpoints/chat/chat", () => ({
+ postV2CreateSession: (...args: unknown[]) => mockPostV2CreateSession(...args),
+}));
+
+vi.mock("@/app/api/__generated__/endpoints/graphs/graphs", () => ({
+ getGetV1GetSpecificGraphQueryKey: (id: string) => ["graphs", id],
+}));
+
+vi.mock("@/lib/supabase/actions", () => ({
+ getWebSocketToken: vi.fn().mockResolvedValue({ token: "tok", error: null }),
+}));
+
+vi.mock("@/services/environment", () => ({
+ environment: { getAGPTServerBaseUrl: () => "http://localhost:8000" },
+}));
+
+const mockInvalidateQueries = vi.fn();
+vi.mock("@tanstack/react-query", () => ({
+ useQueryClient: () => ({ invalidateQueries: mockInvalidateQueries }),
+}));
+
+const mockSendMessage = vi.fn();
+const mockStop = vi.fn();
+vi.mock("@ai-sdk/react", () => ({
+ useChat: () => ({
+ messages: [],
+ sendMessage: mockSendMessage,
+ stop: mockStop,
+ status: "ready",
+ error: undefined,
+ }),
+}));
+
+vi.mock("ai", () => ({
+ DefaultChatTransport: vi.fn().mockImplementation(() => ({})),
+}));
+
+vi.mock("nuqs", () => ({
+ parseAsString: { withDefault: (d: string) => d },
+ useQueryStates: () => [{ flowID: null }, vi.fn()],
+}));
+
+// Import after mocks
+import { useBuilderChatPanel } from "../useBuilderChatPanel";
+
+beforeEach(() => {
+ mockNodes.length = 0;
+ mockEdges.length = 0;
+ mockUpdateNodeData.mockClear();
+ mockAddEdge.mockClear();
+ mockPostV2CreateSession.mockClear();
+ mockInvalidateQueries.mockClear();
+ mockSendMessage.mockClear();
+});
+
+afterEach(() => {
+ cleanup();
+});
+
+describe("useBuilderChatPanel – handleApplyAction", () => {
+ it("update_node_input: calls updateNodeData with merged hardcodedValues", () => {
+ mockNodes.push({
+ id: "node-1",
+ data: { hardcodedValues: { existing: "value" } },
+ });
+ const { result } = renderHook(() => useBuilderChatPanel());
+
+ act(() => {
+ result.current.handleApplyAction({
+ type: "update_node_input",
+ nodeId: "node-1",
+ key: "query",
+ value: "AI news",
+ });
+ });
+
+ expect(mockUpdateNodeData).toHaveBeenCalledWith("node-1", {
+ hardcodedValues: { existing: "value", query: "AI news" },
+ });
+ });
+
+ it("update_node_input: does nothing when node not found", () => {
+ const { result } = renderHook(() => useBuilderChatPanel());
+
+ act(() => {
+ result.current.handleApplyAction({
+ type: "update_node_input",
+ nodeId: "nonexistent",
+ key: "query",
+ value: "test",
+ });
+ });
+
+ expect(mockUpdateNodeData).not.toHaveBeenCalled();
+ });
+
+ it("connect_nodes: calls addEdge when both nodes exist", () => {
+ mockNodes.push({ id: "src" }, { id: "tgt" });
+ const { result } = renderHook(() => useBuilderChatPanel());
+
+ act(() => {
+ result.current.handleApplyAction({
+ type: "connect_nodes",
+ source: "src",
+ target: "tgt",
+ sourceHandle: "output",
+ targetHandle: "input",
+ });
+ });
+
+ expect(mockAddEdge).toHaveBeenCalledWith({
+ id: "src:output->tgt:input",
+ source: "src",
+ target: "tgt",
+ sourceHandle: "output",
+ targetHandle: "input",
+ type: "custom",
+ });
+ });
+
+ it("connect_nodes: does NOT call addEdge when source node is missing", () => {
+ mockNodes.push({ id: "tgt" });
+ const { result } = renderHook(() => useBuilderChatPanel());
+
+ act(() => {
+ result.current.handleApplyAction({
+ type: "connect_nodes",
+ source: "missing-src",
+ target: "tgt",
+ sourceHandle: "output",
+ targetHandle: "input",
+ });
+ });
+
+ expect(mockAddEdge).not.toHaveBeenCalled();
+ });
+
+ it("connect_nodes: does NOT call addEdge when target node is missing", () => {
+ mockNodes.push({ id: "src" });
+ const { result } = renderHook(() => useBuilderChatPanel());
+
+ act(() => {
+ result.current.handleApplyAction({
+ type: "connect_nodes",
+ source: "src",
+ target: "missing-tgt",
+ sourceHandle: "output",
+ targetHandle: "input",
+ });
+ });
+
+ expect(mockAddEdge).not.toHaveBeenCalled();
+ });
+});
+
+describe("useBuilderChatPanel – initial state", () => {
+ it("starts with panel closed and no session", () => {
+ const { result } = renderHook(() => useBuilderChatPanel());
+ expect(result.current.isOpen).toBe(false);
+ expect(result.current.sessionId).toBeNull();
+ expect(result.current.sessionError).toBe(false);
+ expect(result.current.isCreatingSession).toBe(false);
+ });
+
+ it("handleToggle opens and closes the panel", () => {
+ const { result } = renderHook(() => useBuilderChatPanel());
+
+ act(() => {
+ result.current.handleToggle();
+ });
+ expect(result.current.isOpen).toBe(true);
+
+ act(() => {
+ result.current.handleToggle();
+ });
+ expect(result.current.isOpen).toBe(false);
+ });
+});