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) ? (
+
+ ) : (
+
+ ))}