agent-ecosystem/src/features/agent-graph/renderer/hooks/useTeamGraphAdapter.ts

140 lines
4.3 KiB
TypeScript

/**
* React hook bridge for TeamGraphAdapter class.
* Thin wrapper — instantiates the class adapter and calls adapt() with store data.
*/
import { useEffect, useMemo, useRef, useSyncExternalStore } from 'react';
import { getSnapshot, subscribe } from '@renderer/services/commentReadStorage';
import { useStore } from '@renderer/store';
import {
getDefaultTeamGraphSlotAssignmentsForMembers,
getCurrentProvisioningProgressForTeam,
selectResolvedMembersForTeamName,
hasAppliedDefaultTeamGraphSlotAssignments,
isTeamGraphSlotPersistenceDisabled,
selectTeamDataForName,
selectTeamMessages,
} from '@renderer/store/slices/teamSlice';
import { useShallow } from 'zustand/react/shallow';
import { TeamGraphAdapter } from '../adapters/TeamGraphAdapter';
import type { GraphDataPort } from '@claude-teams/agent-graph';
import type { TeamGraphData } from '../adapters/TeamGraphAdapter';
export function useTeamGraphAdapter(teamName: string): GraphDataPort {
const adapterRef = useRef<TeamGraphAdapter>(TeamGraphAdapter.create());
const {
teamSnapshot,
members,
messages,
spawnStatuses,
leadActivity,
leadContext,
pendingApprovals,
activeTools,
finishedVisible,
toolHistory,
provisioningProgress,
memberSpawnSnapshot,
slotAssignments,
ensureTeamGraphSlotAssignments,
} = useStore(
useShallow((s) => ({
teamSnapshot: selectTeamDataForName(s, teamName),
members: selectResolvedMembersForTeamName(s, teamName),
messages: selectTeamMessages(s, teamName),
spawnStatuses: teamName ? s.memberSpawnStatusesByTeam[teamName] : undefined,
leadActivity: teamName ? s.leadActivityByTeam[teamName] : undefined,
leadContext: teamName ? s.leadContextByTeam[teamName] : undefined,
pendingApprovals: s.pendingApprovals,
activeTools: teamName ? s.activeToolsByTeam[teamName] : undefined,
finishedVisible: teamName ? s.finishedVisibleByTeam[teamName] : undefined,
toolHistory: teamName ? s.toolHistoryByTeam[teamName] : undefined,
provisioningProgress: teamName ? getCurrentProvisioningProgressForTeam(s, teamName) : null,
memberSpawnSnapshot: teamName ? s.memberSpawnSnapshotsByTeam[teamName] : undefined,
slotAssignments: teamName ? s.slotAssignmentsByTeam[teamName] : undefined,
ensureTeamGraphSlotAssignments: s.ensureTeamGraphSlotAssignments,
}))
);
const pendingApprovalAgents = useMemo(() => {
const agents = new Set<string>();
for (const a of pendingApprovals) {
if (a.teamName === teamName) {
agents.add(a.source);
}
}
return agents;
}, [pendingApprovals, teamName]);
const teamData = useMemo<TeamGraphData | null>(() => {
if (!teamSnapshot) {
return null;
}
return {
...teamSnapshot,
members,
messageFeed: messages,
};
}, [members, messages, teamSnapshot]);
const commentReadState = useSyncExternalStore(subscribe, getSnapshot);
const effectiveSlotAssignments = useMemo(() => {
if (!teamData) {
return slotAssignments;
}
if (!isTeamGraphSlotPersistenceDisabled()) {
return slotAssignments;
}
if (hasAppliedDefaultTeamGraphSlotAssignments(teamName)) {
return slotAssignments;
}
const defaults = getDefaultTeamGraphSlotAssignmentsForMembers(teamData.members);
return Object.keys(defaults).length === 0 ? undefined : defaults;
}, [slotAssignments, teamData, teamName]);
useEffect(() => {
if (!teamName || !teamData) {
return;
}
ensureTeamGraphSlotAssignments(teamName, teamData.members);
}, [ensureTeamGraphSlotAssignments, teamData, teamName]);
return useMemo(
() =>
adapterRef.current.adapt(
teamData,
teamName,
spawnStatuses,
leadActivity,
leadContext,
pendingApprovalAgents,
activeTools,
finishedVisible,
toolHistory,
commentReadState,
provisioningProgress,
memberSpawnSnapshot,
effectiveSlotAssignments
),
[
teamData,
teamName,
spawnStatuses,
leadActivity,
leadContext,
pendingApprovalAgents,
activeTools,
finishedVisible,
toolHistory,
commentReadState,
provisioningProgress,
memberSpawnSnapshot,
effectiveSlotAssignments,
]
);
}