diff --git a/__tests__/components/features/chat/change-agent-button.test.tsx b/__tests__/components/features/chat/change-agent-button.test.tsx
index b7d44d15..766561a5 100644
--- a/__tests__/components/features/chat/change-agent-button.test.tsx
+++ b/__tests__/components/features/chat/change-agent-button.test.tsx
@@ -5,9 +5,10 @@ import { ChangeAgentButton } from "#/components/features/chat/change-agent-butto
import { renderWithProviders } from "../../../../test-utils";
import { useConversationStore } from "#/stores/conversation-store";
-// Mock WebSocket status
+const mockWebSocketStatus = vi.hoisted(() => vi.fn(() => "OPEN"));
+
vi.mock("#/hooks/use-unified-websocket-status", () => ({
- useUnifiedWebSocketStatus: () => "CONNECTED",
+ useUnifiedWebSocketStatus: () => mockWebSocketStatus(),
}));
// Mock agent state
@@ -68,6 +69,7 @@ vi.mock("#/hooks/use-handle-plan-click", () => ({
describe("ChangeAgentButton - Cache Invalidation", () => {
beforeEach(() => {
vi.clearAllMocks();
+ mockWebSocketStatus.mockReturnValue("OPEN");
// Reset store state
useConversationStore.setState({
conversationMode: "code",
@@ -151,4 +153,12 @@ describe("ChangeAgentButton - Cache Invalidation", () => {
const button = screen.getByRole("button");
expect(button).toBeInTheDocument();
});
+
+ it("enables mode switch on pre-conversation surfaces without a websocket", () => {
+ mockWebSocketStatus.mockReturnValue("CLOSED");
+ renderWithProviders(, {
+ navigation: { conversationId: null },
+ });
+ expect(screen.getByRole("button")).not.toBeDisabled();
+ });
});
diff --git a/__tests__/components/features/chat/components/chat-input-model.test.tsx b/__tests__/components/features/chat/components/chat-input-model.test.tsx
index 870e326e..8a990ad1 100644
--- a/__tests__/components/features/chat/components/chat-input-model.test.tsx
+++ b/__tests__/components/features/chat/components/chat-input-model.test.tsx
@@ -47,7 +47,7 @@ describe("ChatInputModel", () => {
const llmSettingsLink = screen.getByRole("link", {
name: /LLM Profiles|SETTINGS\$LLM_PROFILES|LLM Settings|SETTINGS\$LLM_SETTINGS/,
});
- expect(llmSettingsLink).toHaveAttribute("href", "/settings");
+ expect(llmSettingsLink).toHaveAttribute("href", "/settings/llm");
});
it("renders nothing when llm_model is missing", () => {
diff --git a/src/components/features/chat/change-agent-button.tsx b/src/components/features/chat/change-agent-button.tsx
index c9827b92..fb47b900 100644
--- a/src/components/features/chat/change-agent-button.tsx
+++ b/src/components/features/chat/change-agent-button.tsx
@@ -15,6 +15,7 @@ import { useActiveConversation } from "#/hooks/query/use-active-conversation";
import { useUnifiedWebSocketStatus } from "#/hooks/use-unified-websocket-status";
import { useSubConversationTaskPolling } from "#/hooks/query/use-sub-conversation-task-polling";
import { useHandlePlanClick } from "#/hooks/use-handle-plan-click";
+import { useOptionalConversationId } from "#/hooks/use-conversation-id";
export function ChangeAgentButton() {
const [contextMenuOpen, setContextMenuOpen] = useState(false);
@@ -22,6 +23,9 @@ export function ChangeAgentButton() {
const { conversationMode, setConversationMode, subConversationTaskId } =
useConversationStore();
+ const { conversationId } = useOptionalConversationId();
+ const isPreConversation = conversationId == null;
+
const webSocketStatus = useUnifiedWebSocketStatus();
const isWebSocketConnected = webSocketStatus === "OPEN";
@@ -74,13 +78,21 @@ export function ChangeAgentButton() {
// Close context menu when agent starts running
useEffect(() => {
- if ((isAgentRunning || !isWebSocketConnected) && contextMenuOpen) {
+ const blockedBySocket = !isPreConversation && !isWebSocketConnected;
+ if ((isAgentRunning || blockedBySocket) && contextMenuOpen) {
setContextMenuOpen(false);
}
- }, [isAgentRunning, contextMenuOpen, isWebSocketConnected]);
+ }, [
+ isAgentRunning,
+ contextMenuOpen,
+ isWebSocketConnected,
+ isPreConversation,
+ ]);
const isButtonDisabled =
- isAgentRunning || isCreatingConversation || !isWebSocketConnected;
+ isCreatingConversation ||
+ isAgentRunning ||
+ (!isPreConversation && !isWebSocketConnected);
// Handle Shift + Tab keyboard shortcut to cycle through modes
useEffect(() => {
diff --git a/src/components/features/chat/components/chat-input-actions.tsx b/src/components/features/chat/components/chat-input-actions.tsx
index 544cb81b..820b761a 100644
--- a/src/components/features/chat/components/chat-input-actions.tsx
+++ b/src/components/features/chat/components/chat-input-actions.tsx
@@ -260,7 +260,7 @@ export function ChatInputActions({
const isAgentSwitcherDisabled =
curAgentState === AgentState.RUNNING ||
isCreatingConversation ||
- webSocketStatus !== "OPEN";
+ (conversationId != null && webSocketStatus !== "OPEN");
const closeOverflowMenus = () => {
setActiveSubmenu(null);
@@ -449,7 +449,7 @@ export function ChatInputActions({
diff --git a/src/components/features/chat/components/chat-input-model.tsx b/src/components/features/chat/components/chat-input-model.tsx
index 04b5af5d..ae40c4c8 100644
--- a/src/components/features/chat/components/chat-input-model.tsx
+++ b/src/components/features/chat/components/chat-input-model.tsx
@@ -105,7 +105,7 @@ export function ChatInputModel() {
setIsPopoverOpen(false)}
className="flex h-[30px] items-center gap-2 rounded p-2 leading-5 text-white hover:bg-[var(--oh-interactive-hover)] transition-colors"
>