feat: show project and workspace activity indicators

This commit is contained in:
Federico Jaramillo Martinez
2026-05-19 13:24:02 +02:00
parent fb9e524e5b
commit ebe5639399
29 changed files with 498 additions and 36 deletions
+36
View File
@@ -0,0 +1,36 @@
import { describe, expect, it } from "vitest";
import { isSessionActive, sessionActivityLabel, isWorkspaceActivityActive } from "./activity";
import type { SessionStatus, WorkspaceActivity } from "./apiTypes";
const idleStatus: SessionStatus = {
sessionId: "s1",
isStreaming: false,
isCompacting: false,
isBashRunning: false,
pendingMessageCount: 0,
queuedMessages: [],
tokens: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, total: 0 },
cost: 0,
};
describe("activity helpers", () => {
it("detects and labels active session states consistently", () => {
expect(isSessionActive(idleStatus)).toBe(false);
expect(sessionActivityLabel(idleStatus)).toBeUndefined();
expect(isSessionActive({ ...idleStatus, isStreaming: true })).toBe(true);
expect(sessionActivityLabel({ ...idleStatus, isStreaming: true })).toBe("streaming");
expect(isSessionActive({ ...idleStatus, pendingMessageCount: 2 })).toBe(true);
expect(sessionActivityLabel({ ...idleStatus, pendingMessageCount: 2 })).toBe("2 pending");
expect(sessionActivityLabel(idleStatus, { sessionId: "s1", phase: "active", label: "running tool", detail: "read", at: "now" })).toBe("running tool: read");
});
it("detects workspace activity presence without exposing details", () => {
const idle: WorkspaceActivity = { cwd: "/repo", hasSessionActivity: false, hasTerminalActivity: false, updatedAt: "now" };
expect(isWorkspaceActivityActive(idle)).toBe(false);
expect(isWorkspaceActivityActive({ ...idle, hasSessionActivity: true })).toBe(true);
expect(isWorkspaceActivityActive({ ...idle, hasTerminalActivity: true })).toBe(true);
});
});
+23
View File
@@ -0,0 +1,23 @@
import type { SessionActivity, SessionStatus, WorkspaceActivity } from "./apiTypes.js";
export function isSessionActive(status?: SessionStatus, activity?: SessionActivity): boolean {
return activity?.phase === "active"
|| status?.isStreaming === true
|| status?.isBashRunning === true
|| status?.isCompacting === true
|| (status?.pendingMessageCount ?? 0) > 0;
}
export function sessionActivityLabel(status?: SessionStatus, activity?: SessionActivity): string | undefined {
if (activity?.phase === "active") return activity.detail !== undefined && activity.detail !== "" ? `${activity.label}: ${activity.detail}` : activity.label;
if (status === undefined) return undefined;
if (status.isCompacting) return "compacting";
if (status.isBashRunning) return "bash";
if (status.isStreaming) return "streaming";
if (status.pendingMessageCount > 0) return `${String(status.pendingMessageCount)} pending`;
return undefined;
}
export function isWorkspaceActivityActive(activity: WorkspaceActivity | undefined): boolean {
return activity !== undefined && (activity.hasSessionActivity || activity.hasTerminalActivity);
}
+18 -1
View File
@@ -107,6 +107,18 @@ export interface SessionStatus {
contextUsage?: { tokens: number | null; contextWindow: number; percent: number | null };
}
export interface WorkspaceActivity {
cwd: string;
hasSessionActivity: boolean;
hasTerminalActivity: boolean;
updatedAt: string;
}
export interface WorkspaceActivityResponse {
workspaces: WorkspaceActivity[];
generatedAt: string;
}
export interface SlashCommand {
name: string;
description?: string;
@@ -242,6 +254,11 @@ export type TerminalUiEvent =
| { type: "terminal.exited"; terminal: TerminalInfo }
| { type: "terminal.closed"; terminalId: string; cwd: string };
export interface WorkspaceActivityUiEvent {
type: "workspace.activity";
activity: WorkspaceActivity;
}
export interface CommandOption {
value: string;
label: string;
@@ -280,4 +297,4 @@ export type SessionUiEvent =
| { type: "pi.event"; eventType: string };
export type GlobalSessionEvent = Extract<SessionUiEvent, { type: "status.update" | "activity.update" | "session.name" }>;
export type RealtimeEvent = GlobalSessionEvent | TerminalUiEvent;
export type RealtimeEvent = GlobalSessionEvent | TerminalUiEvent | WorkspaceActivityUiEvent;