agent-ecosystem/src/renderer/store/team/teamAgentRuntimeSnapshotStabilizer.ts

116 lines
3.1 KiB
TypeScript

import type {
TeamAgentRuntimeEntry,
TeamAgentRuntimeLivenessKind,
TeamAgentRuntimeSnapshot,
} from '@shared/types';
const TRANSIENT_RUNTIME_OFFLINE_GRACE_MS = 15_000;
const TRANSIENT_RUNTIME_OFFLINE_KINDS = new Set<TeamAgentRuntimeLivenessKind>([
'registered_only',
'stale_metadata',
'not_found',
]);
const STRONG_LIVE_RUNTIME_KINDS = new Set<TeamAgentRuntimeLivenessKind>([
'runtime_process',
'confirmed_bootstrap',
]);
function parseTimestampMs(value: string | undefined): number | null {
if (!value) return null;
const parsed = Date.parse(value);
return Number.isFinite(parsed) ? parsed : null;
}
function getEntryObservedAtMs(
snapshot: TeamAgentRuntimeSnapshot,
entry: TeamAgentRuntimeEntry
): number | null {
return (
parseTimestampMs(entry.updatedAt) ??
parseTimestampMs(entry.runtimeLastSeenAt) ??
parseTimestampMs(snapshot.updatedAt)
);
}
function hasStrongLiveRuntimeEvidence(entry: TeamAgentRuntimeEntry | undefined): boolean {
return Boolean(
entry?.alive === true &&
(entry.livenessKind == null || STRONG_LIVE_RUNTIME_KINDS.has(entry.livenessKind))
);
}
function isTransientRuntimeOfflineEntry(entry: TeamAgentRuntimeEntry | undefined): boolean {
return Boolean(
entry?.alive === false &&
entry.runtimeDiagnosticSeverity !== 'error' &&
entry.livenessKind != null &&
TRANSIENT_RUNTIME_OFFLINE_KINDS.has(entry.livenessKind)
);
}
function shouldKeepPreviousLiveRuntimeEntry({
previousSnapshot,
previousEntry,
nextEntry,
nowMs,
}: {
previousSnapshot: TeamAgentRuntimeSnapshot;
previousEntry: TeamAgentRuntimeEntry | undefined;
nextEntry: TeamAgentRuntimeEntry | undefined;
nowMs: number;
}): boolean {
if (!hasStrongLiveRuntimeEvidence(previousEntry)) return false;
if (!isTransientRuntimeOfflineEntry(nextEntry)) return false;
const previousObservedAtMs = previousEntry
? getEntryObservedAtMs(previousSnapshot, previousEntry)
: null;
if (previousObservedAtMs == null) return false;
return nowMs - previousObservedAtMs <= TRANSIENT_RUNTIME_OFFLINE_GRACE_MS;
}
export function stabilizeTeamAgentRuntimeSnapshot(
previousSnapshot: TeamAgentRuntimeSnapshot | undefined,
nextSnapshot: TeamAgentRuntimeSnapshot,
nowMs = Date.now()
): TeamAgentRuntimeSnapshot {
if (
!previousSnapshot ||
previousSnapshot.teamName !== nextSnapshot.teamName ||
previousSnapshot.runId !== nextSnapshot.runId
) {
return nextSnapshot;
}
let stabilizedMembers: Record<string, TeamAgentRuntimeEntry> | null = null;
for (const [memberName, nextEntry] of Object.entries(nextSnapshot.members)) {
const previousEntry = previousSnapshot.members[memberName];
if (!previousEntry) continue;
if (
!shouldKeepPreviousLiveRuntimeEntry({
previousSnapshot,
previousEntry,
nextEntry,
nowMs,
})
) {
continue;
}
stabilizedMembers ??= { ...nextSnapshot.members };
stabilizedMembers[memberName] = previousEntry;
}
if (!stabilizedMembers) {
return nextSnapshot;
}
return {
...nextSnapshot,
members: stabilizedMembers,
};
}