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,
|
TeamAgentRuntimeSnapshot,
|
||||||
TeamCreateRequest,
|
TeamCreateRequest,
|
||||||
TeamGetDataOptions,
|
TeamGetDataOptions,
|
||||||
|
TeamLaunchDiagnosticItem,
|
||||||
TeamLaunchRequest,
|
TeamLaunchRequest,
|
||||||
TeamMemberActivityMeta,
|
TeamMemberActivityMeta,
|
||||||
TeamProvisioningProgress,
|
TeamProvisioningProgress,
|
||||||
|
|
@ -1194,6 +1195,65 @@ export function getCurrentProvisioningProgressForTeam(
|
||||||
return currentRunId ? (state.provisioningRuns[currentRunId] ?? null) : null;
|
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(
|
export function isTeamProvisioningActive(
|
||||||
state: Pick<TeamSlice, 'currentProvisioningRunIdByTeam' | 'provisioningRuns'>,
|
state: Pick<TeamSlice, 'currentProvisioningRunIdByTeam' | 'provisioningRuns'>,
|
||||||
teamName: string
|
teamName: string
|
||||||
|
|
@ -4004,11 +4064,8 @@ export const createTeamSlice: StateCreator<AppState, [], [], TeamSlice> = (set,
|
||||||
const becameConfigReady =
|
const becameConfigReady =
|
||||||
progress.configReady === true && existingProgress?.configReady !== true;
|
progress.configReady === true && existingProgress?.configReady !== true;
|
||||||
const isDuplicateProgress =
|
const isDuplicateProgress =
|
||||||
existingProgress?.updatedAt === progress.updatedAt &&
|
existingProgress !== undefined &&
|
||||||
existingProgress?.state === progress.state &&
|
provisioningProgressPayloadEqual(existingProgress, progress);
|
||||||
existingProgress?.message === progress.message &&
|
|
||||||
existingProgress?.error === progress.error &&
|
|
||||||
existingProgress?.pid === progress.pid;
|
|
||||||
if (isDuplicateProgress && currentRunId === progress.runId) {
|
if (isDuplicateProgress && currentRunId === progress.runId) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5602,6 +5602,53 @@ describe('teamSlice actions', () => {
|
||||||
expect(store.getState().provisioningRuns['run-stale']).toBeUndefined();
|
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', () => {
|
it('promotes a pending run to a real run without throwing', () => {
|
||||||
const store = createSliceStore();
|
const store = createSliceStore();
|
||||||
store.setState({
|
store.setState({
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue