refactor(team): extract runtime snapshot equality

This commit is contained in:
777genius 2026-05-22 11:01:56 +03:00
parent 0a1e4c6e8b
commit 3f7b793816
3 changed files with 313 additions and 105 deletions

View file

@ -29,6 +29,7 @@ import {
isTeamTaskNeedsFixActionable,
} from '@shared/utils/teamTaskState';
import { areTeamAgentRuntimeSnapshotsEqual } from '../team/teamAgentRuntimeSnapshotEquality';
import {
clearAllLastResolvedTeamDataRefreshes,
clearLastResolvedTeamDataRefreshAt,
@ -127,8 +128,6 @@ import type {
SendMessageResult,
TaskChangePresenceState,
TaskComment,
TeamAgentRuntimeEntry,
TeamAgentRuntimeResourceSample,
TeamAgentRuntimeSnapshot,
TeamCreateRequest,
TeamGetDataOptions,
@ -643,109 +642,6 @@ function maybeLogMemberSpawnUiEqualSuppressed(
);
}
function isTeamAgentRuntimeResourceSampleLike(
value: unknown
): value is TeamAgentRuntimeResourceSample {
return Boolean(value) && typeof value === 'object';
}
function areTeamAgentRuntimeResourceSamplesEqual(left: unknown, right: unknown): boolean {
if (left === right) return true;
if (!isTeamAgentRuntimeResourceSampleLike(left) || !isTeamAgentRuntimeResourceSampleLike(right)) {
return false;
}
return (
left.timestamp === right.timestamp &&
left.cpuPercent === right.cpuPercent &&
left.rssBytes === right.rssBytes &&
left.primaryCpuPercent === right.primaryCpuPercent &&
left.primaryRssBytes === right.primaryRssBytes &&
left.childCpuPercent === right.childCpuPercent &&
left.childRssBytes === right.childRssBytes &&
left.processCount === right.processCount &&
left.runtimeLoadScope === right.runtimeLoadScope &&
left.runtimeLoadTruncated === right.runtimeLoadTruncated &&
left.pidSource === right.pidSource &&
left.pid === right.pid &&
left.runtimePid === right.runtimePid
);
}
function areTeamAgentRuntimeEntriesEqual(
left: TeamAgentRuntimeEntry | undefined,
right: TeamAgentRuntimeEntry | undefined
): boolean {
if (left === right) return true;
if (!left || !right) return left === right;
const leftDiagnostics = Array.isArray(left.diagnostics) ? left.diagnostics : [];
const rightDiagnostics = Array.isArray(right.diagnostics) ? right.diagnostics : [];
const leftResourceHistory = Array.isArray(left.resourceHistory) ? left.resourceHistory : [];
const rightResourceHistory = Array.isArray(right.resourceHistory) ? right.resourceHistory : [];
return (
left.memberName === right.memberName &&
left.alive === right.alive &&
left.restartable === right.restartable &&
left.backendType === right.backendType &&
left.providerId === right.providerId &&
left.providerBackendId === right.providerBackendId &&
left.laneId === right.laneId &&
left.laneKind === right.laneKind &&
left.pid === right.pid &&
left.runtimeModel === right.runtimeModel &&
left.rssBytes === right.rssBytes &&
left.cpuPercent === right.cpuPercent &&
left.primaryCpuPercent === right.primaryCpuPercent &&
left.primaryRssBytes === right.primaryRssBytes &&
left.childCpuPercent === right.childCpuPercent &&
left.childRssBytes === right.childRssBytes &&
left.processCount === right.processCount &&
left.runtimeLoadScope === right.runtimeLoadScope &&
left.runtimeLoadTruncated === right.runtimeLoadTruncated &&
left.livenessKind === right.livenessKind &&
left.pidSource === right.pidSource &&
left.processCommand === right.processCommand &&
left.paneId === right.paneId &&
left.panePid === right.panePid &&
left.paneCurrentCommand === right.paneCurrentCommand &&
left.runtimePid === right.runtimePid &&
left.runtimeSessionId === right.runtimeSessionId &&
left.runtimeDiagnostic === right.runtimeDiagnostic &&
left.runtimeDiagnosticSeverity === right.runtimeDiagnosticSeverity &&
left.runtimeLastSeenAt === right.runtimeLastSeenAt &&
left.historicalBootstrapConfirmed === right.historicalBootstrapConfirmed &&
leftDiagnostics.length === rightDiagnostics.length &&
leftDiagnostics.every((value, index) => value === rightDiagnostics[index]) &&
leftResourceHistory.length === rightResourceHistory.length &&
leftResourceHistory.every((value, index) =>
areTeamAgentRuntimeResourceSamplesEqual(value, rightResourceHistory[index])
)
);
}
function areTeamAgentRuntimeSnapshotsEqual(
left: TeamAgentRuntimeSnapshot | undefined,
right: TeamAgentRuntimeSnapshot
): boolean {
if (!left) return false;
if (left.teamName !== right.teamName || left.runId !== right.runId) {
return false;
}
const leftKeys = Object.keys(left.members);
const rightKeys = Object.keys(right.members);
if (leftKeys.length !== rightKeys.length) {
return false;
}
for (const key of leftKeys) {
if (!(key in right.members)) {
return false;
}
if (!areTeamAgentRuntimeEntriesEqual(left.members[key], right.members[key])) {
return false;
}
}
return true;
}
function clearPendingReplyRefreshTimer(teamName: string): void {
const existingTimer = pendingTeamPendingReplyRefreshTimers.get(teamName);
if (existingTimer == null) {

View file

@ -0,0 +1,108 @@
import type {
TeamAgentRuntimeEntry,
TeamAgentRuntimeResourceSample,
TeamAgentRuntimeSnapshot,
} from '@shared/types';
function isTeamAgentRuntimeResourceSampleLike(
value: unknown
): value is TeamAgentRuntimeResourceSample {
return Boolean(value) && typeof value === 'object';
}
export function areTeamAgentRuntimeResourceSamplesEqual(left: unknown, right: unknown): boolean {
if (left === right) return true;
if (!isTeamAgentRuntimeResourceSampleLike(left) || !isTeamAgentRuntimeResourceSampleLike(right)) {
return false;
}
return (
left.timestamp === right.timestamp &&
left.cpuPercent === right.cpuPercent &&
left.rssBytes === right.rssBytes &&
left.primaryCpuPercent === right.primaryCpuPercent &&
left.primaryRssBytes === right.primaryRssBytes &&
left.childCpuPercent === right.childCpuPercent &&
left.childRssBytes === right.childRssBytes &&
left.processCount === right.processCount &&
left.runtimeLoadScope === right.runtimeLoadScope &&
left.runtimeLoadTruncated === right.runtimeLoadTruncated &&
left.pidSource === right.pidSource &&
left.pid === right.pid &&
left.runtimePid === right.runtimePid
);
}
export function areTeamAgentRuntimeEntriesEqual(
left: TeamAgentRuntimeEntry | undefined,
right: TeamAgentRuntimeEntry | undefined
): boolean {
if (left === right) return true;
if (!left || !right) return left === right;
const leftDiagnostics = Array.isArray(left.diagnostics) ? left.diagnostics : [];
const rightDiagnostics = Array.isArray(right.diagnostics) ? right.diagnostics : [];
const leftResourceHistory = Array.isArray(left.resourceHistory) ? left.resourceHistory : [];
const rightResourceHistory = Array.isArray(right.resourceHistory) ? right.resourceHistory : [];
return (
left.memberName === right.memberName &&
left.alive === right.alive &&
left.restartable === right.restartable &&
left.backendType === right.backendType &&
left.providerId === right.providerId &&
left.providerBackendId === right.providerBackendId &&
left.laneId === right.laneId &&
left.laneKind === right.laneKind &&
left.pid === right.pid &&
left.runtimeModel === right.runtimeModel &&
left.rssBytes === right.rssBytes &&
left.cpuPercent === right.cpuPercent &&
left.primaryCpuPercent === right.primaryCpuPercent &&
left.primaryRssBytes === right.primaryRssBytes &&
left.childCpuPercent === right.childCpuPercent &&
left.childRssBytes === right.childRssBytes &&
left.processCount === right.processCount &&
left.runtimeLoadScope === right.runtimeLoadScope &&
left.runtimeLoadTruncated === right.runtimeLoadTruncated &&
left.livenessKind === right.livenessKind &&
left.pidSource === right.pidSource &&
left.processCommand === right.processCommand &&
left.paneId === right.paneId &&
left.panePid === right.panePid &&
left.paneCurrentCommand === right.paneCurrentCommand &&
left.runtimePid === right.runtimePid &&
left.runtimeSessionId === right.runtimeSessionId &&
left.runtimeDiagnostic === right.runtimeDiagnostic &&
left.runtimeDiagnosticSeverity === right.runtimeDiagnosticSeverity &&
left.runtimeLastSeenAt === right.runtimeLastSeenAt &&
left.historicalBootstrapConfirmed === right.historicalBootstrapConfirmed &&
leftDiagnostics.length === rightDiagnostics.length &&
leftDiagnostics.every((value, index) => value === rightDiagnostics[index]) &&
leftResourceHistory.length === rightResourceHistory.length &&
leftResourceHistory.every((value, index) =>
areTeamAgentRuntimeResourceSamplesEqual(value, rightResourceHistory[index])
)
);
}
export function areTeamAgentRuntimeSnapshotsEqual(
left: TeamAgentRuntimeSnapshot | undefined,
right: TeamAgentRuntimeSnapshot
): boolean {
if (!left) return false;
if (left.teamName !== right.teamName || left.runId !== right.runId) {
return false;
}
const leftKeys = Object.keys(left.members);
const rightKeys = Object.keys(right.members);
if (leftKeys.length !== rightKeys.length) {
return false;
}
for (const key of leftKeys) {
if (!(key in right.members)) {
return false;
}
if (!areTeamAgentRuntimeEntriesEqual(left.members[key], right.members[key])) {
return false;
}
}
return true;
}

View file

@ -0,0 +1,204 @@
import { describe, expect, it } from 'vitest';
import {
areTeamAgentRuntimeEntriesEqual,
areTeamAgentRuntimeResourceSamplesEqual,
areTeamAgentRuntimeSnapshotsEqual,
} from '../../../src/renderer/store/team/teamAgentRuntimeSnapshotEquality';
import type {
TeamAgentRuntimeEntry,
TeamAgentRuntimeResourceSample,
TeamAgentRuntimeSnapshot,
} from '../../../src/shared/types';
function createResourceSample(
overrides: Partial<TeamAgentRuntimeResourceSample> = {}
): TeamAgentRuntimeResourceSample {
return {
timestamp: '2026-05-22T10:00:00.000Z',
cpuPercent: 4,
rssBytes: 1024,
primaryCpuPercent: 3,
primaryRssBytes: 768,
childCpuPercent: 1,
childRssBytes: 256,
processCount: 2,
runtimeLoadScope: 'process-tree',
runtimeLoadTruncated: false,
pidSource: 'agent_process_table',
pid: 111,
runtimePid: 222,
...overrides,
};
}
function createRuntimeEntry(overrides: Partial<TeamAgentRuntimeEntry> = {}): TeamAgentRuntimeEntry {
return {
memberName: 'alice',
alive: true,
restartable: true,
backendType: 'process',
providerId: 'codex',
providerBackendId: 'codex-native',
laneId: 'lane-1',
laneKind: 'primary',
pid: 111,
runtimeModel: 'gpt-5.3-codex',
cwd: '/tmp/old',
rssBytes: 1024,
cpuPercent: 4,
primaryCpuPercent: 3,
primaryRssBytes: 768,
childCpuPercent: 1,
childRssBytes: 256,
processCount: 2,
runtimeLoadScope: 'process-tree',
runtimeLoadTruncated: false,
resourceHistory: [createResourceSample()],
livenessKind: 'confirmed_bootstrap',
pidSource: 'agent_process_table',
processCommand: 'codex',
paneId: '%1',
panePid: 333,
paneCurrentCommand: 'node',
runtimePid: 222,
runtimeSessionId: 'runtime-session-1',
runtimeLeaseExpiresAt: '2026-05-22T10:10:00.000Z',
runtimeLastSeenAt: '2026-05-22T10:00:00.000Z',
historicalBootstrapConfirmed: true,
runtimeDiagnostic: 'Ready',
runtimeDiagnosticSeverity: 'info',
diagnostics: ['healthy'],
updatedAt: '2026-05-22T10:00:00.000Z',
...overrides,
};
}
function createRuntimeSnapshot(
overrides: Partial<TeamAgentRuntimeSnapshot> = {}
): TeamAgentRuntimeSnapshot {
return {
teamName: 'my-team',
updatedAt: '2026-05-22T10:00:00.000Z',
runId: 'run-1',
providerBackendId: 'codex-native',
fastMode: 'inherit',
members: {
alice: createRuntimeEntry(),
},
...overrides,
};
}
describe('teamAgentRuntimeSnapshotEquality', () => {
it('compares runtime resource samples by visible process metrics', () => {
expect(
areTeamAgentRuntimeResourceSamplesEqual(createResourceSample(), createResourceSample())
).toBe(true);
expect(
areTeamAgentRuntimeResourceSamplesEqual(
createResourceSample(),
createResourceSample({ cpuPercent: 5 })
)
).toBe(false);
expect(areTeamAgentRuntimeResourceSamplesEqual(null, createResourceSample())).toBe(false);
});
it('ignores runtime entry fields that do not currently affect equality', () => {
const left = createRuntimeEntry({
cwd: '/tmp/old',
runtimeLeaseExpiresAt: '2026-05-22T10:10:00.000Z',
updatedAt: '2026-05-22T10:00:00.000Z',
});
const right = createRuntimeEntry({
cwd: '/tmp/new',
runtimeLeaseExpiresAt: '2026-05-22T10:20:00.000Z',
updatedAt: '2026-05-22T10:05:00.000Z',
});
expect(areTeamAgentRuntimeEntriesEqual(left, right)).toBe(true);
});
it('detects visible runtime entry field changes', () => {
expect(
areTeamAgentRuntimeEntriesEqual(
createRuntimeEntry(),
createRuntimeEntry({ runtimeDiagnosticSeverity: 'warning' })
)
).toBe(false);
expect(
areTeamAgentRuntimeEntriesEqual(
createRuntimeEntry(),
createRuntimeEntry({ resourceHistory: [createResourceSample({ rssBytes: 2048 })] })
)
).toBe(false);
});
it('compares diagnostics and resource history arrays in stable order', () => {
expect(
areTeamAgentRuntimeEntriesEqual(
createRuntimeEntry({ diagnostics: ['a', 'b'] }),
createRuntimeEntry({ diagnostics: ['b', 'a'] })
)
).toBe(false);
expect(
areTeamAgentRuntimeEntriesEqual(
createRuntimeEntry({
resourceHistory: [
createResourceSample({ timestamp: '2026-05-22T10:00:00.000Z' }),
createResourceSample({ timestamp: '2026-05-22T10:01:00.000Z' }),
],
}),
createRuntimeEntry({
resourceHistory: [
createResourceSample({ timestamp: '2026-05-22T10:01:00.000Z' }),
createResourceSample({ timestamp: '2026-05-22T10:00:00.000Z' }),
],
})
)
).toBe(false);
});
it('compares runtime snapshots by team, run id, and semantic member entries', () => {
expect(areTeamAgentRuntimeSnapshotsEqual(createRuntimeSnapshot(), createRuntimeSnapshot())).toBe(
true
);
expect(
areTeamAgentRuntimeSnapshotsEqual(
createRuntimeSnapshot(),
createRuntimeSnapshot({ runId: 'run-2' })
)
).toBe(false);
expect(
areTeamAgentRuntimeSnapshotsEqual(
createRuntimeSnapshot(),
createRuntimeSnapshot({
members: {
alice: createRuntimeEntry(),
bob: createRuntimeEntry({ memberName: 'bob' }),
},
})
)
).toBe(false);
});
it('ignores snapshot metadata fields that do not currently affect equality', () => {
const left = createRuntimeSnapshot({
updatedAt: '2026-05-22T10:00:00.000Z',
providerBackendId: 'codex-native',
fastMode: 'inherit',
});
const right = createRuntimeSnapshot({
updatedAt: '2026-05-22T10:05:00.000Z',
providerBackendId: 'api',
fastMode: 'on',
});
expect(areTeamAgentRuntimeSnapshotsEqual(left, right)).toBe(true);
});
it('returns false when there is no previous runtime snapshot', () => {
expect(areTeamAgentRuntimeSnapshotsEqual(undefined, createRuntimeSnapshot())).toBe(false);
});
});