diff --git a/apps/web/components/task/preview-session-tabs.tsx b/apps/web/components/task/preview-session-tabs.tsx index d3c2642547..25845d19a7 100644 --- a/apps/web/components/task/preview-session-tabs.tsx +++ b/apps/web/components/task/preview-session-tabs.tsx @@ -4,6 +4,7 @@ import { useCallback, useMemo } from "react"; import { IconLoader2 } from "@tabler/icons-react"; import { Button } from "@kandev/ui/button"; import { AgentLogo } from "@/components/agent-logo"; +import { GridSpinner } from "@/components/grid-spinner"; import { SessionTabs, type SessionTab } from "@/components/session-tabs"; import { useAppStore } from "@/components/state-provider"; import { useToast } from "@/components/toast-provider"; @@ -11,12 +12,13 @@ import { useSessionResumption } from "@/hooks/domains/session/use-session-resump import { useTaskSessions } from "@/hooks/use-task-sessions"; import type { UseEnsureTaskSessionResult } from "@/hooks/domains/session/use-ensure-task-session"; import type { AgentProfileOption } from "@/lib/state/slices"; -import type { TaskSession, TaskSessionState } from "@/lib/types/http"; +import type { TaskSession } from "@/lib/types/http"; import { getWebSocketClient } from "@/lib/ws/connection"; import { PassthroughTerminal } from "./passthrough-terminal"; import { TaskChatPanel } from "./task-chat-panel"; import { buildAgentLabelsById, + isSessionActive, pickActiveSessionId, resolveAgentLabelFor, sortSessions, @@ -174,12 +176,8 @@ function PreviewSessionBody({ session, taskId }: { session: TaskSession; taskId: ); } -function isSessionActive(state: TaskSessionState): boolean { - return state === "RUNNING" || state === "STARTING"; -} - function RunningSpinner() { - return ; + return ; } function PreviewLoadingState({ label }: { label: string }) { diff --git a/apps/web/components/task/session-sort.test.ts b/apps/web/components/task/session-sort.test.ts index c6e9399770..f48cff5bfa 100644 --- a/apps/web/components/task/session-sort.test.ts +++ b/apps/web/components/task/session-sort.test.ts @@ -1,6 +1,7 @@ import { describe, expect, it } from "vitest"; import { buildAgentLabelsById, + isSessionActive, pickActiveSessionId, resolveAgentLabelFor, sortSessions, @@ -83,3 +84,23 @@ describe("pickActiveSessionId", () => { expect(pickActiveSessionId(sessions, null)).toBe("a"); }); }); + +describe("isSessionActive", () => { + it("is true while the agent is starting or running", () => { + expect(isSessionActive("RUNNING")).toBe(true); + expect(isSessionActive("STARTING")).toBe(true); + }); + + it("is false for terminal and idle states", () => { + expect(isSessionActive("WAITING_FOR_INPUT")).toBe(false); + expect(isSessionActive("COMPLETED")).toBe(false); + expect(isSessionActive("FAILED")).toBe(false); + expect(isSessionActive("CANCELLED")).toBe(false); + expect(isSessionActive("CREATED")).toBe(false); + }); + + it("is false for null/undefined", () => { + expect(isSessionActive(null)).toBe(false); + expect(isSessionActive(undefined)).toBe(false); + }); +}); diff --git a/apps/web/components/task/session-sort.ts b/apps/web/components/task/session-sort.ts index a5b3e8fb14..4d91df0921 100644 --- a/apps/web/components/task/session-sort.ts +++ b/apps/web/components/task/session-sort.ts @@ -57,3 +57,7 @@ export function pickActiveSessionId( const primary = sessions.find((s) => s.is_primary); return (primary ?? sessions[0]).id; } + +export function isSessionActive(state: TaskSessionState | null | undefined): boolean { + return state === "RUNNING" || state === "STARTING"; +} diff --git a/apps/web/components/task/session-tab.tsx b/apps/web/components/task/session-tab.tsx index 0412d78606..4ca533c583 100644 --- a/apps/web/components/task/session-tab.tsx +++ b/apps/web/components/task/session-tab.tsx @@ -4,6 +4,7 @@ import { useCallback, useEffect, useState } from "react"; import { DockviewDefaultTab, type IDockviewPanelHeaderProps } from "dockview-react"; import { IconStar } from "@tabler/icons-react"; import { AgentLogo } from "@/components/agent-logo"; +import { GridSpinner } from "@/components/grid-spinner"; import { ContextMenu, ContextMenuContent, @@ -29,6 +30,7 @@ import { isSessionResumable as isResumable, } from "@/hooks/domains/session/use-session-actions"; import type { TaskSessionState } from "@/lib/types/http"; +import { isSessionActive } from "./session-sort"; import { useTabMaximizeOnDoubleClick } from "./use-tab-maximize"; function useSessionTabState(sessionId: string | undefined) { @@ -244,13 +246,18 @@ export function SessionTab(props: IDockviewPanelHeaderProps) { {sessionNumber} )} - {agentName && ( - - )} + {agentName && + (isSessionActive(sessionState) ? ( + + ) : ( + + ))}