mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 19:35:28 -06:00
fix(ui): clear terminal session activity indicators (#120327)
Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -21,6 +21,7 @@ import {
|
||||
presenceViewerLabel,
|
||||
projectOnlinePresenceViewers,
|
||||
} from "../lib/presence-users.ts";
|
||||
import { isSessionRunActive } from "../lib/session-run-state.ts";
|
||||
import {
|
||||
resolveSessionPreferredFace,
|
||||
sessionNavigationTarget,
|
||||
@@ -178,7 +179,7 @@ export function renderAppSidebarHomeRow(host: AppSidebarRenderHost) {
|
||||
isSessionRouteId(host.activeRouteId) &&
|
||||
areUiSessionKeysEquivalent(host.getRouteSessionKey(), mainKey);
|
||||
const hasComposerDraft = host.hasSessionDraft(mainKey);
|
||||
const running = mainRow?.hasActiveRun === true;
|
||||
const running = mainRow ? isSessionRunActive(mainRow) : false;
|
||||
const unread = mainRow?.unread === true && !active;
|
||||
// Home keeps its page/attention glyph leading and shares trailing activity with session rows.
|
||||
const homeGlyph = renderSessionGlyph({
|
||||
|
||||
@@ -11,6 +11,7 @@ import { t } from "../i18n/index.ts";
|
||||
import { formatUiError } from "../lib/format-error.ts";
|
||||
import { handleContextMenuEvent } from "../lib/keyboard-shortcuts.ts";
|
||||
import { shouldHandleNavigationClick } from "../lib/navigation-click.ts";
|
||||
import { isSessionRunActive } from "../lib/session-run-state.ts";
|
||||
import type { CatalogSessionKey } from "../lib/sessions/catalog-key.ts";
|
||||
import { buildCatalogSessionKey } from "../lib/sessions/catalog-key.ts";
|
||||
import {
|
||||
@@ -140,7 +141,7 @@ export function renderSessionCatalogGroups(params: SessionCatalogGroupsParams) {
|
||||
const row = session.sessionKey ? liveRowsByKey.get(session.sessionKey) : undefined;
|
||||
return row ? [row] : [];
|
||||
});
|
||||
const hasActiveRun = liveRows.some((row) => row.hasActiveRun === true);
|
||||
const hasActiveRun = liveRows.some(isSessionRunActive);
|
||||
const hasUnread = liveRows.some((row) => row.unread === true);
|
||||
const hasBrandIcon = hasProviderBrandIcon(catalog.id);
|
||||
const loadingMore = params.loadingMoreCatalogIds.has(catalog.id);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { describe, expect, it, vi } from "vitest";
|
||||
import type { GatewaySessionRow, SessionsListResult } from "../api/types.ts";
|
||||
import { t } from "../i18n/index.ts";
|
||||
import { createTestGatewayClient } from "../test-helpers/gateway-client.ts";
|
||||
import { collectKnownSessionRows, fetchSessionLineage } from "./app-sidebar-child-session-data.ts";
|
||||
import {
|
||||
buildSidebarSessionNavigationState,
|
||||
compareSidebarSessionRowsByMode,
|
||||
resolveSidebarAgentChipSubtitle,
|
||||
} from "./app-sidebar-session-navigation-logic.ts";
|
||||
import { projectSessionTree } from "./app-sidebar-session-tree.ts";
|
||||
import type { SidebarRecentSession } from "./app-sidebar-session-types.ts";
|
||||
@@ -177,6 +179,22 @@ describe("sidebar session live-run projection", () => {
|
||||
},
|
||||
);
|
||||
|
||||
it("stops calling a completed registry-active agent Working", () => {
|
||||
const session = {
|
||||
key: "agent:main:main",
|
||||
kind: "direct",
|
||||
updatedAt: 1,
|
||||
hasActiveRun: true,
|
||||
} satisfies GatewaySessionRow;
|
||||
|
||||
expect(resolveSidebarAgentChipSubtitle({ ...session, status: "done" })).not.toBe(
|
||||
t("agentChip.working"),
|
||||
);
|
||||
expect(resolveSidebarAgentChipSubtitle({ ...session, status: "running" })).toBe(
|
||||
t("agentChip.working"),
|
||||
);
|
||||
});
|
||||
|
||||
it("carries active cloud disk pressure into the existing sidebar badge model", () => {
|
||||
const projected = projectSidebarSession({
|
||||
placement: {
|
||||
|
||||
@@ -557,7 +557,7 @@ export function resolveSidebarAgentResumeKey(
|
||||
}
|
||||
|
||||
export function resolveSidebarAgentChipSubtitle(latest: SessionRow | null): string {
|
||||
if (latest?.hasActiveRun) {
|
||||
if (latest && isSessionRunActive(latest)) {
|
||||
return t("agentChip.working");
|
||||
}
|
||||
return latest ? resolveSessionDisplayName(latest.key, latest) : t("agentChip.ready");
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { SessionRunStatus } from "../../../packages/gateway-protocol/src/schema/sessions-row.js";
|
||||
import { fnv1aUtf16 } from "../lib/fnv1a.ts";
|
||||
import { isSessionRunActive } from "../lib/session-run-state.ts";
|
||||
|
||||
export type LobsterPetMode = "idle" | "busy" | "offline";
|
||||
|
||||
@@ -159,10 +160,10 @@ export function resolveLobsterRunOutcome(
|
||||
|
||||
export function resolveLobsterPetMode(
|
||||
connected: boolean,
|
||||
sessions: ReadonlyArray<{ hasActiveRun?: boolean | null }> | null | undefined,
|
||||
sessions: ReadonlyArray<{ hasActiveRun?: boolean; status?: SessionRunStatus }> | null | undefined,
|
||||
): LobsterPetMode {
|
||||
if (!connected) {
|
||||
return "offline";
|
||||
}
|
||||
return sessions?.some((row) => row.hasActiveRun === true) ? "busy" : "idle";
|
||||
return sessions?.some(isSessionRunActive) ? "busy" : "idle";
|
||||
}
|
||||
|
||||
@@ -169,6 +169,11 @@ describe("resolveLobsterPetMode", () => {
|
||||
"busy",
|
||||
);
|
||||
});
|
||||
|
||||
it("stops looking busy after a registry-active run reaches a terminal status", () => {
|
||||
expect(resolveLobsterPetMode(true, [{ status: "done", hasActiveRun: true }])).toBe("idle");
|
||||
expect(resolveLobsterPetMode(true, [{ status: "running", hasActiveRun: true }])).toBe("busy");
|
||||
});
|
||||
});
|
||||
|
||||
describe("resolveLobsterRunOutcome", () => {
|
||||
|
||||
@@ -105,6 +105,21 @@ describe("chat Swarm progress", () => {
|
||||
expect(container.querySelector("[data-test-id=chat-swarm]")).toBeNull();
|
||||
});
|
||||
|
||||
it("keeps registry-active terminal workers completed and hides finished groups", () => {
|
||||
const running = session({ key: "running", status: "running" });
|
||||
const completed = session({ key: "completed", status: "done", hasActiveRun: true });
|
||||
const failed = session({ key: "failed", status: "failed", hasActiveRun: true });
|
||||
const container = renderProgress([running, completed, failed]);
|
||||
|
||||
expect(container.textContent?.replace(/\s+/g, " ")).toContain("1 Running · 1 Done · 1 Failed");
|
||||
|
||||
render(
|
||||
renderChatSwarmProgress({ sessionKey: parentSessionKey, sessions: [completed, failed] }),
|
||||
container,
|
||||
);
|
||||
expect(container.querySelector("[data-test-id=chat-swarm]")).toBeNull();
|
||||
});
|
||||
|
||||
it("buckets children by phase, labels the default bucket, and shows the latest log", () => {
|
||||
const container = renderProgress([
|
||||
session({ key: "unphased", label: "Older child", status: "running" }),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { html, nothing, type TemplateResult } from "lit";
|
||||
import type { GatewaySessionRow } from "../../../api/types.ts";
|
||||
import { t } from "../../../i18n/index.ts";
|
||||
import { isSessionRunActive } from "../../../lib/session-run-state.ts";
|
||||
import { areUiSessionKeysEquivalent } from "../../../lib/sessions/session-key.ts";
|
||||
|
||||
type SwarmDotStatus = "queued" | "running" | "done" | "failed";
|
||||
@@ -53,7 +54,7 @@ function swarmDotStatus(row: GatewaySessionRow): SwarmDotStatus | null {
|
||||
if (row.status === "queued") {
|
||||
return "queued";
|
||||
}
|
||||
if (row.status === "running" || row.hasActiveRun === true) {
|
||||
if (isSessionRunActive(row)) {
|
||||
return "running";
|
||||
}
|
||||
if (row.status === "done") {
|
||||
|
||||
Reference in New Issue
Block a user