refactor(team): extract runtime snapshot equality
This commit is contained in:
parent
0a1e4c6e8b
commit
3f7b793816
3 changed files with 313 additions and 105 deletions
|
|
@ -29,6 +29,7 @@ import {
|
||||||
isTeamTaskNeedsFixActionable,
|
isTeamTaskNeedsFixActionable,
|
||||||
} from '@shared/utils/teamTaskState';
|
} from '@shared/utils/teamTaskState';
|
||||||
|
|
||||||
|
import { areTeamAgentRuntimeSnapshotsEqual } from '../team/teamAgentRuntimeSnapshotEquality';
|
||||||
import {
|
import {
|
||||||
clearAllLastResolvedTeamDataRefreshes,
|
clearAllLastResolvedTeamDataRefreshes,
|
||||||
clearLastResolvedTeamDataRefreshAt,
|
clearLastResolvedTeamDataRefreshAt,
|
||||||
|
|
@ -127,8 +128,6 @@ import type {
|
||||||
SendMessageResult,
|
SendMessageResult,
|
||||||
TaskChangePresenceState,
|
TaskChangePresenceState,
|
||||||
TaskComment,
|
TaskComment,
|
||||||
TeamAgentRuntimeEntry,
|
|
||||||
TeamAgentRuntimeResourceSample,
|
|
||||||
TeamAgentRuntimeSnapshot,
|
TeamAgentRuntimeSnapshot,
|
||||||
TeamCreateRequest,
|
TeamCreateRequest,
|
||||||
TeamGetDataOptions,
|
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 {
|
function clearPendingReplyRefreshTimer(teamName: string): void {
|
||||||
const existingTimer = pendingTeamPendingReplyRefreshTimers.get(teamName);
|
const existingTimer = pendingTeamPendingReplyRefreshTimers.get(teamName);
|
||||||
if (existingTimer == null) {
|
if (existingTimer == null) {
|
||||||
|
|
|
||||||
108
src/renderer/store/team/teamAgentRuntimeSnapshotEquality.ts
Normal file
108
src/renderer/store/team/teamAgentRuntimeSnapshotEquality.ts
Normal 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;
|
||||||
|
}
|
||||||
204
test/renderer/store/teamAgentRuntimeSnapshotEquality.test.ts
Normal file
204
test/renderer/store/teamAgentRuntimeSnapshotEquality.test.ts
Normal 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);
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in a new issue