perf(renderer): ignore provisioning heartbeat rerenders
This commit is contained in:
parent
dc3001f713
commit
0ec8a3f962
2 changed files with 109 additions and 5 deletions
|
|
@ -171,6 +171,7 @@ import type {
|
|||
TeamAgentRuntimeSnapshot,
|
||||
TeamCreateRequest,
|
||||
TeamGetDataOptions,
|
||||
TeamLaunchDiagnosticItem,
|
||||
TeamLaunchRequest,
|
||||
TeamMemberActivityMeta,
|
||||
TeamProvisioningProgress,
|
||||
|
|
@ -1194,6 +1195,65 @@ export function getCurrentProvisioningProgressForTeam(
|
|||
return currentRunId ? (state.provisioningRuns[currentRunId] ?? null) : null;
|
||||
}
|
||||
|
||||
function stringArraysEqual(
|
||||
a: readonly string[] | undefined,
|
||||
b: readonly string[] | undefined
|
||||
): boolean {
|
||||
if (a === b) return true;
|
||||
if (!a || !b || a.length !== b.length) return false;
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (a[i] !== b[i]) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function launchDiagnosticsEqual(
|
||||
a: readonly TeamLaunchDiagnosticItem[] | undefined,
|
||||
b: readonly TeamLaunchDiagnosticItem[] | undefined
|
||||
): boolean {
|
||||
if (a === b) return true;
|
||||
if (!a || !b || a.length !== b.length) return false;
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
const left = a[i];
|
||||
const right = b[i];
|
||||
if (
|
||||
!left ||
|
||||
!right ||
|
||||
left.id !== right.id ||
|
||||
left.memberName !== right.memberName ||
|
||||
left.severity !== right.severity ||
|
||||
left.code !== right.code ||
|
||||
left.label !== right.label ||
|
||||
left.detail !== right.detail ||
|
||||
left.observedAt !== right.observedAt
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function provisioningProgressPayloadEqual(
|
||||
a: TeamProvisioningProgress,
|
||||
b: TeamProvisioningProgress
|
||||
): boolean {
|
||||
return (
|
||||
a.runId === b.runId &&
|
||||
a.teamName === b.teamName &&
|
||||
a.state === b.state &&
|
||||
a.message === b.message &&
|
||||
a.messageSeverity === b.messageSeverity &&
|
||||
a.startedAt === b.startedAt &&
|
||||
a.pid === b.pid &&
|
||||
a.error === b.error &&
|
||||
a.cliLogsTail === b.cliLogsTail &&
|
||||
a.assistantOutput === b.assistantOutput &&
|
||||
a.configReady === b.configReady &&
|
||||
stringArraysEqual(a.warnings, b.warnings) &&
|
||||
launchDiagnosticsEqual(a.launchDiagnostics, b.launchDiagnostics)
|
||||
);
|
||||
}
|
||||
|
||||
export function isTeamProvisioningActive(
|
||||
state: Pick<TeamSlice, 'currentProvisioningRunIdByTeam' | 'provisioningRuns'>,
|
||||
teamName: string
|
||||
|
|
@ -4004,11 +4064,8 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
|||
const becameConfigReady =
|
||||
progress.configReady === true && existingProgress?.configReady !== true;
|
||||
const isDuplicateProgress =
|
||||
existingProgress?.updatedAt === progress.updatedAt &&
|
||||
existingProgress?.state === progress.state &&
|
||||
existingProgress?.message === progress.message &&
|
||||
existingProgress?.error === progress.error &&
|
||||
existingProgress?.pid === progress.pid;
|
||||
existingProgress !== undefined &&
|
||||
provisioningProgressPayloadEqual(existingProgress, progress);
|
||||
if (isDuplicateProgress && currentRunId === progress.runId) {
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5602,6 +5602,53 @@ describe('teamSlice actions', () => {
|
|||
expect(store.getState().provisioningRuns['run-stale']).toBeUndefined();
|
||||
});
|
||||
|
||||
it('ignores provisioning heartbeat updates when only updatedAt changes', () => {
|
||||
const store = createSliceStore();
|
||||
const baseProgress = {
|
||||
runId: 'run-current',
|
||||
teamName: 'my-team',
|
||||
state: 'spawning' as const,
|
||||
message: 'Waiting for model response',
|
||||
startedAt: '2026-03-12T10:00:00.000Z',
|
||||
updatedAt: '2026-03-12T10:00:00.000Z',
|
||||
pid: 1234,
|
||||
assistantOutput: 'partial output',
|
||||
warnings: ['slow response'],
|
||||
launchDiagnostics: [
|
||||
{
|
||||
id: 'diag-1',
|
||||
severity: 'warning' as const,
|
||||
code: 'runtime_process_candidate' as const,
|
||||
label: 'Runtime candidate',
|
||||
observedAt: '2026-03-12T10:00:00.000Z',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
store.getState().onProvisioningProgress(baseProgress);
|
||||
const firstRuns = store.getState().provisioningRuns;
|
||||
const firstProgress = firstRuns['run-current'];
|
||||
|
||||
store.getState().onProvisioningProgress({
|
||||
...baseProgress,
|
||||
updatedAt: '2026-03-12T10:00:01.000Z',
|
||||
});
|
||||
|
||||
expect(store.getState().provisioningRuns).toBe(firstRuns);
|
||||
expect(store.getState().provisioningRuns['run-current']).toBe(firstProgress);
|
||||
|
||||
store.getState().onProvisioningProgress({
|
||||
...baseProgress,
|
||||
updatedAt: '2026-03-12T10:00:02.000Z',
|
||||
assistantOutput: 'partial output\\nnext chunk',
|
||||
});
|
||||
|
||||
expect(store.getState().provisioningRuns).not.toBe(firstRuns);
|
||||
expect(store.getState().provisioningRuns['run-current']?.assistantOutput).toContain(
|
||||
'next chunk'
|
||||
);
|
||||
});
|
||||
|
||||
it('promotes a pending run to a real run without throwing', () => {
|
||||
const store = createSliceStore();
|
||||
store.setState({
|
||||
|
|
|
|||
Loading…
Reference in a new issue