Files
paseo/packages/app/src/utils/workspace-agent-activity.ts
T
Mohamed BoudraandGitHub 28a27b02d3 Prevent Android chats from freezing or going blank (#1989)
* fix(app): stabilize streamed chat rendering

Use deterministic Markdown AST keys and preserve unique row identities when an assistant message resumes after a tool. Keep native gesture and plan-card children correctly keyed to avoid unsupported Fabric reconciliation paths.

* fix(app): stabilize retained native panels

Keep retained workspace roots in a stable native order and make active
panels present in the selection commit. Hidden panels now stop expensive
subscriptions and animations without losing mounted state.

* fix(app): harden retained panel activity
2026-07-11 15:39:14 +08:00

72 lines
2.2 KiB
TypeScript

import type { Agent, WorkspaceDescriptor } from "@/stores/session-store";
import { deriveSidebarStateBucket } from "./sidebar-agent-state";
export interface WorkspaceAgentActivity {
agentId: string;
status: WorkspaceDescriptor["status"];
enteredAt: Date | null;
}
export function buildWorkspaceAgentActivityIndex(
agents: ReadonlyMap<string, Agent>,
previous?: ReadonlyMap<string, WorkspaceAgentActivity>,
): Map<string, WorkspaceAgentActivity> {
const activityByWorkspaceId = new Map<string, WorkspaceAgentActivity>();
const latestActivityAtByWorkspaceId = new Map<string, Date>();
for (const agent of agents.values()) {
if (agent.archivedAt || agent.parentAgentId || !agent.workspaceId) {
continue;
}
const enteredAt = agent.attentionTimestamp ?? agent.updatedAt;
const latestActivityAt = latestActivityAtByWorkspaceId.get(agent.workspaceId);
if (latestActivityAt && enteredAt <= latestActivityAt) {
continue;
}
latestActivityAtByWorkspaceId.set(agent.workspaceId, enteredAt);
const status = deriveSidebarStateBucket({
status: agent.status,
pendingPermissionCount: agent.pendingPermissions.length,
requiresAttention: agent.requiresAttention,
attentionReason: agent.attentionReason,
});
activityByWorkspaceId.set(agent.workspaceId, {
agentId: agent.id,
status,
enteredAt,
});
}
for (const [workspaceId, activity] of activityByWorkspaceId) {
const previousActivity = previous?.get(workspaceId);
if (
previousActivity?.agentId === activity.agentId &&
previousActivity.status === activity.status
) {
activityByWorkspaceId.set(workspaceId, previousActivity);
}
}
if (previous && areWorkspaceAgentActivityIndexesIdentical(previous, activityByWorkspaceId)) {
return previous instanceof Map ? previous : new Map(previous);
}
return activityByWorkspaceId;
}
function areWorkspaceAgentActivityIndexesIdentical(
previous: ReadonlyMap<string, WorkspaceAgentActivity>,
next: ReadonlyMap<string, WorkspaceAgentActivity>,
): boolean {
if (previous.size !== next.size) {
return false;
}
for (const [workspaceId, activity] of next) {
if (previous.get(workspaceId) !== activity) {
return false;
}
}
return true;
}