diff --git a/frontend/src/utils/__tests__/status.test.ts b/frontend/src/utils/__tests__/status.test.ts new file mode 100644 index 0000000000..cca6c0efaf --- /dev/null +++ b/frontend/src/utils/__tests__/status.test.ts @@ -0,0 +1,209 @@ +import { describe, it, expect } from "vitest"; +import { getStatusCode, getIndicatorColor, IndicatorColor } from "../status"; +import { AgentState } from "#/types/agent-state"; +import { I18nKey } from "#/i18n/declaration"; + +describe("getStatusCode", () => { + it("should prioritize agent readiness over stale runtime status", () => { + // Test case: Agent is ready (AWAITING_USER_INPUT) but runtime status is still starting + const result = getStatusCode( + { id: "", message: "", type: "info", status_update: true }, // statusMessage + "CONNECTED", // webSocketStatus + "RUNNING", // conversationStatus + "STATUS$STARTING_RUNTIME", // runtimeStatus (stale) + AgentState.AWAITING_USER_INPUT, // agentState (ready) + ); + + // Should return agent state message, not runtime status + expect(result).toBe(I18nKey.AGENT_STATUS$WAITING_FOR_TASK); + }); + + it("should show runtime status when agent is not ready", () => { + // Test case: Agent is loading and runtime is starting + const result = getStatusCode( + { id: "", message: "", type: "info", status_update: true }, // statusMessage + "CONNECTED", // webSocketStatus + "STARTING", // conversationStatus + "STATUS$STARTING_RUNTIME", // runtimeStatus + AgentState.LOADING, // agentState (not ready) + ); + + // Should return runtime status since agent is not ready + expect(result).toBe("STATUS$STARTING_RUNTIME"); + }); + + it("should handle agent running state with stale runtime status", () => { + // Test case: Agent is running but runtime status is stale + const result = getStatusCode( + { id: "", message: "", type: "info", status_update: true }, // statusMessage + "CONNECTED", // webSocketStatus + "RUNNING", // conversationStatus + "STATUS$BUILDING_RUNTIME", // runtimeStatus (stale) + AgentState.RUNNING, // agentState (ready) + ); + + // Should return agent state message, not runtime status + expect(result).toBe(I18nKey.AGENT_STATUS$RUNNING_TASK); + }); + + it("should handle agent finished state with stale runtime status", () => { + // Test case: Agent is finished but runtime status is stale + const result = getStatusCode( + { id: "", message: "", type: "info", status_update: true }, // statusMessage + "CONNECTED", // webSocketStatus + "RUNNING", // conversationStatus + "STATUS$SETTING_UP_WORKSPACE", // runtimeStatus (stale) + AgentState.FINISHED, // agentState (ready) + ); + + // Should return agent state message, not runtime status + expect(result).toBe(I18nKey.AGENT_STATUS$WAITING_FOR_TASK); + }); + + it("should still respect stopped states", () => { + // Test case: Runtime is stopped - should always show stopped + const result = getStatusCode( + { id: "", message: "", type: "info", status_update: true }, // statusMessage + "CONNECTED", // webSocketStatus + "STOPPED", // conversationStatus + "STATUS$STOPPED", // runtimeStatus + AgentState.RUNNING, // agentState + ); + + // Should return stopped status regardless of agent state + expect(result).toBe(I18nKey.CHAT_INTERFACE$STOPPED); + }); + + it("should handle null agent state with runtime status", () => { + // Test case: No agent state, runtime is starting + const result = getStatusCode( + { id: "", message: "", type: "info", status_update: true }, // statusMessage + "CONNECTED", // webSocketStatus + "STARTING", // conversationStatus + "STATUS$STARTING_RUNTIME", // runtimeStatus + null, // agentState + ); + + // Should return runtime status since no agent state + expect(result).toBe("STATUS$STARTING_RUNTIME"); + }); +}); + +describe("getIndicatorColor", () => { + it("should prioritize agent readiness over stale runtime status for AWAITING_USER_INPUT", () => { + // Test case: Agent is ready (AWAITING_USER_INPUT) but runtime status is still starting + const result = getIndicatorColor( + "CONNECTED", // webSocketStatus + "RUNNING", // conversationStatus + "STATUS$STARTING_RUNTIME", // runtimeStatus (stale) + AgentState.AWAITING_USER_INPUT, // agentState (ready) + ); + + // Should return blue for AWAITING_USER_INPUT, not yellow for stale runtime + expect(result).toBe(IndicatorColor.BLUE); + }); + + it("should prioritize agent readiness over stale runtime status for RUNNING", () => { + // Test case: Agent is running but runtime status is stale + const result = getIndicatorColor( + "CONNECTED", // webSocketStatus + "RUNNING", // conversationStatus + "STATUS$BUILDING_RUNTIME", // runtimeStatus (stale) + AgentState.RUNNING, // agentState (ready) + ); + + // Should return green for RUNNING, not yellow for stale runtime + expect(result).toBe(IndicatorColor.GREEN); + }); + + it("should prioritize agent readiness over stale runtime status for FINISHED", () => { + // Test case: Agent is finished but runtime status is stale + const result = getIndicatorColor( + "CONNECTED", // webSocketStatus + "RUNNING", // conversationStatus + "STATUS$SETTING_UP_WORKSPACE", // runtimeStatus (stale) + AgentState.FINISHED, // agentState (ready) + ); + + // Should return green for FINISHED, not yellow for stale runtime + expect(result).toBe(IndicatorColor.GREEN); + }); + + it("should show yellow when agent is not ready and runtime is starting", () => { + // Test case: Agent is loading and runtime is starting + const result = getIndicatorColor( + "CONNECTED", // webSocketStatus + "STARTING", // conversationStatus + "STATUS$STARTING_RUNTIME", // runtimeStatus + AgentState.LOADING, // agentState (not ready) + ); + + // Should return yellow since agent is not ready + expect(result).toBe(IndicatorColor.YELLOW); + }); + + it("should show orange for AWAITING_USER_CONFIRMATION even with stale runtime", () => { + // Test case: Agent is awaiting confirmation but runtime status is stale + const result = getIndicatorColor( + "CONNECTED", // webSocketStatus + "RUNNING", // conversationStatus + "STATUS$STARTING_RUNTIME", // runtimeStatus (stale) + AgentState.AWAITING_USER_CONFIRMATION, // agentState (ready) + ); + + // Should return orange for AWAITING_USER_CONFIRMATION, not yellow for stale runtime + expect(result).toBe(IndicatorColor.ORANGE); + }); + + it("should still respect stopped states", () => { + // Test case: Runtime is stopped - should always show red + const result = getIndicatorColor( + "CONNECTED", // webSocketStatus + "STOPPED", // conversationStatus + "STATUS$STOPPED", // runtimeStatus + AgentState.RUNNING, // agentState + ); + + // Should return red for stopped status regardless of agent state + expect(result).toBe(IndicatorColor.RED); + }); + + it("should handle null agent state with runtime status", () => { + // Test case: No agent state, runtime is starting + const result = getIndicatorColor( + "CONNECTED", // webSocketStatus + "STARTING", // conversationStatus + "STATUS$STARTING_RUNTIME", // runtimeStatus + null, // agentState + ); + + // Should return yellow since no agent state and runtime is starting + expect(result).toBe(IndicatorColor.YELLOW); + }); + + it("should handle USER_CONFIRMED state with stale runtime status", () => { + // Test case: Agent is in USER_CONFIRMED state but runtime status is stale + const result = getIndicatorColor( + "CONNECTED", // webSocketStatus + "RUNNING", // conversationStatus + "STATUS$BUILDING_RUNTIME", // runtimeStatus (stale) + AgentState.USER_CONFIRMED, // agentState (ready) + ); + + // Should return green for USER_CONFIRMED, not yellow for stale runtime + expect(result).toBe(IndicatorColor.GREEN); + }); + + it("should handle USER_REJECTED state with stale runtime status", () => { + // Test case: Agent is in USER_REJECTED state but runtime status is stale + const result = getIndicatorColor( + "CONNECTED", // webSocketStatus + "RUNNING", // conversationStatus + "STATUS$BUILDING_RUNTIME", // runtimeStatus (stale) + AgentState.USER_REJECTED, // agentState (ready) + ); + + // Should return green for USER_REJECTED, not yellow for stale runtime + expect(result).toBe(IndicatorColor.GREEN); + }); +}); diff --git a/frontend/src/utils/status.ts b/frontend/src/utils/status.ts index 637d9336c8..3df5ee57cd 100644 --- a/frontend/src/utils/status.ts +++ b/frontend/src/utils/status.ts @@ -57,10 +57,23 @@ export function getIndicatorColor( ) { return IndicatorColor.RED; } + + // Prioritize agent state when it indicates readiness, even if runtime status is stale + const agentIsReady = + agentState && + [ + AgentState.AWAITING_USER_INPUT, + AgentState.RUNNING, + AgentState.FINISHED, + AgentState.AWAITING_USER_CONFIRMATION, + AgentState.USER_CONFIRMED, + AgentState.USER_REJECTED, + ].includes(agentState); + // Display a yellow working icon while the runtime is starting if ( conversationStatus === "STARTING" || - !["STATUS$READY", null].includes(runtimeStatus) || + (!["STATUS$READY", null].includes(runtimeStatus) && !agentIsReady) || (agentState != null && [ AgentState.LOADING, @@ -96,10 +109,23 @@ export function getStatusCode( return I18nKey.CHAT_INTERFACE$STOPPED; } - // Handle runtime status messages + // Prioritize agent state when it indicates readiness, even if runtime status is stale + const agentIsReady = + agentState && + [ + AgentState.AWAITING_USER_INPUT, + AgentState.RUNNING, + AgentState.FINISHED, + AgentState.PAUSED, + AgentState.AWAITING_USER_CONFIRMATION, + AgentState.USER_CONFIRMED, + AgentState.USER_REJECTED, + ].includes(agentState); + if ( runtimeStatus && - !["STATUS$READY", "STATUS$RUNTIME_STARTED"].includes(runtimeStatus) + !["STATUS$READY", "STATUS$RUNTIME_STARTED"].includes(runtimeStatus) && + !agentIsReady // Skip runtime status check if agent is ready ) { const result = (I18nKey as { [key: string]: string })[runtimeStatus]; if (result) {