Files
paseo/packages/app/src/hooks/use-aggregated-agents.ts
T
Mohamed Boudra 060f3d457c refactor: remove terminal agent concept entirely
Remove the "agent can be a terminal" branching from the entire codebase.
An agent is now always a session-backed chat agent. Standalone terminal
infrastructure (terminal component, ANSI handling, terminal-stream-protocol)
is preserved.

Server: delete ManagedTerminalAgent, AgentKind, TerminalExitDetails,
launchTerminalAgent, registerTerminalAgent, handleTerminalAgentExited,
supportsTerminalMode capability, buildTerminalCreate/ResumeCommand from
all providers, terminal agent persistence/projections.

App: delete terminal-agent-panel.tsx, terminal-agent-reopen-store.ts,
terminal/terminalExit fields on agent state, "Terminal Agents" launcher
section, terminal-agent workspace setup flow, terminal badge in agent list.

CLI: remove terminal column from ls, terminal-agent error from send.

60 files changed, -3592 lines
2026-04-02 10:55:54 +07:00

124 lines
3.9 KiB
TypeScript

import { useMemo, useCallback, useSyncExternalStore } from "react";
import { useShallow } from "zustand/shallow";
import { useSessionStore } from "@/stores/session-store";
import type { AgentDirectoryEntry } from "@/types/agent-directory";
import type { Agent } from "@/stores/session-store";
import { getHostRuntimeStore, useHosts } from "@/runtime/host-runtime";
export interface AggregatedAgent extends AgentDirectoryEntry {
serverId: string;
serverLabel: string;
}
export interface AggregatedAgentsResult {
agents: AggregatedAgent[];
isLoading: boolean;
isInitialLoad: boolean;
isRevalidating: boolean;
refreshAll: () => void;
}
export function useAggregatedAgents(options?: {
includeArchived?: boolean;
}): AggregatedAgentsResult {
const daemons = useHosts();
const runtime = getHostRuntimeStore();
const includeArchived = options?.includeArchived ?? false;
const runtimeVersion = useSyncExternalStore(
(onStoreChange) => runtime.subscribeAll(onStoreChange),
() => runtime.getVersion(),
() => runtime.getVersion(),
);
const sessionAgents = useSessionStore(
useShallow((state) => {
const result: Record<string, Map<string, Agent> | undefined> = {};
for (const [serverId, session] of Object.entries(state.sessions)) {
result[serverId] = session.agents;
}
return result;
}),
);
const refreshAll = useCallback(() => {
runtime.refreshAllAgentDirectories();
}, [runtime]);
const result = useMemo(() => {
const allAgents: AggregatedAgent[] = [];
const serverLabelById = new Map(
daemons.map((daemon) => [daemon.serverId, daemon.label] as const),
);
// Derive agent directory from all sessions
for (const [serverId, agents] of Object.entries(sessionAgents)) {
if (!agents || agents.size === 0) {
continue;
}
const serverLabel = serverLabelById.get(serverId) ?? serverId;
for (const agent of agents.values()) {
if (!includeArchived && agent.archivedAt) {
continue;
}
const nextAgent: AggregatedAgent = {
id: agent.id,
serverId,
serverLabel,
title: agent.title ?? null,
status: agent.status,
lastActivityAt: agent.lastActivityAt,
cwd: agent.cwd,
provider: agent.provider,
pendingPermissionCount: agent.pendingPermissions.length,
requiresAttention: agent.requiresAttention,
attentionReason: agent.attentionReason,
attentionTimestamp: agent.attentionTimestamp,
archivedAt: agent.archivedAt,
createdAt: agent.createdAt,
labels: agent.labels,
};
allAgents.push(nextAgent);
}
}
// Sort by: running agents first, then by most recent activity
allAgents.sort((left, right) => {
const leftRunning = left.status === "running";
const rightRunning = right.status === "running";
if (leftRunning && !rightRunning) {
return -1;
}
if (!leftRunning && rightRunning) {
return 1;
}
const leftTime = left.lastActivityAt.getTime();
const rightTime = right.lastActivityAt.getTime();
return rightTime - leftTime;
});
// Check if we have any cached data
const hasAnyData = allAgents.length > 0;
// Align list loading with the runtime directory-sync machine.
const isLoading = daemons.some((daemon) => {
const status =
runtime.getSnapshot(daemon.serverId)?.agentDirectoryStatus ?? "initial_loading";
return status === "initial_loading" || status === "revalidating";
});
const isInitialLoad = isLoading && !hasAnyData;
const isRevalidating = isLoading && hasAnyData;
return {
agents: allAgents,
isLoading,
isInitialLoad,
isRevalidating,
};
}, [daemons, includeArchived, runtime, runtimeVersion, sessionAgents]);
return {
...result,
refreshAll,
};
}