fix(team): ignore replayed bootstrap progress events

This commit is contained in:
iliya 2026-04-07 01:49:37 +03:00
parent ac3475d3be
commit ad56f0e337
3 changed files with 106 additions and 0 deletions

View file

@ -660,6 +660,8 @@ interface ProvisioningRun {
>;
/** Agent tool_use_id -> teammate name for persistent teammate spawns. */
memberSpawnToolUseIds: Map<string, string>;
/** Highest accepted deterministic bootstrap event sequence for this run. */
lastDeterministicBootstrapSeq: number;
/** Throttles config/inbox audit work triggered by frequent status polling. */
lastMemberSpawnAuditAt: number;
/** Throttles repeated audit warnings when config.json is temporarily unreadable. */
@ -707,6 +709,33 @@ function createInitialMemberSpawnStatusEntry(): MemberSpawnStatusEntry {
};
}
export function shouldAcceptDeterministicBootstrapEvent(params: {
runId: string;
teamName: string;
lastSeq: number;
msg: Record<string, unknown>;
}): { accept: boolean; nextSeq: number } {
const msgRunId = typeof params.msg.run_id === 'string' ? params.msg.run_id.trim() : '';
if (msgRunId && msgRunId !== params.runId) {
return { accept: false, nextSeq: params.lastSeq };
}
const msgTeamName = typeof params.msg.team_name === 'string' ? params.msg.team_name.trim() : '';
if (msgTeamName && msgTeamName !== params.teamName) {
return { accept: false, nextSeq: params.lastSeq };
}
const seq = typeof params.msg.seq === 'number' ? params.msg.seq : NaN;
if (Number.isFinite(seq)) {
if (!Number.isInteger(seq) || seq <= params.lastSeq) {
return { accept: false, nextSeq: params.lastSeq };
}
return { accept: true, nextSeq: seq };
}
return { accept: true, nextSeq: params.lastSeq };
}
function deriveMemberLaunchState(entry: {
agentToolAccepted?: boolean;
runtimeAlive?: boolean;
@ -4386,6 +4415,7 @@ export class TeamProvisioningService {
request.members.map((m) => [m.name, createInitialMemberSpawnStatusEntry()])
),
memberSpawnToolUseIds: new Map(),
lastDeterministicBootstrapSeq: 0,
lastMemberSpawnAuditAt: 0,
lastMemberSpawnAuditConfigReadWarningAt: 0,
lastMemberSpawnAuditMissingWarningAt: new Map(),
@ -4899,6 +4929,7 @@ export class TeamProvisioningService {
expectedMembers.map((name) => [name, createInitialMemberSpawnStatusEntry()])
),
memberSpawnToolUseIds: new Map(),
lastDeterministicBootstrapSeq: 0,
lastMemberSpawnAuditAt: 0,
lastMemberSpawnAuditConfigReadWarningAt: 0,
lastMemberSpawnAuditMissingWarningAt: new Map(),
@ -7069,6 +7100,17 @@ export class TeamProvisioningService {
return false;
}
const acceptance = shouldAcceptDeterministicBootstrapEvent({
runId: run.runId,
teamName: run.teamName,
lastSeq: run.lastDeterministicBootstrapSeq,
msg,
});
if (!acceptance.accept) {
return true;
}
run.lastDeterministicBootstrapSeq = acceptance.nextSeq;
const event = typeof msg.event === 'string' ? msg.event : undefined;
if (!event) {
return true;

View file

@ -100,6 +100,7 @@ describe('TeamProvisioningService', () => {
fs.mkdirSync(tempProjectsBase, { recursive: true });
});
afterEach(() => {
vi.useRealTimers();
try {

View file

@ -0,0 +1,63 @@
import { describe, expect, it } from 'vitest';
import { shouldAcceptDeterministicBootstrapEvent } from '@main/services/team/TeamProvisioningService';
describe('TeamProvisioningService deterministic bootstrap event ordering', () => {
it('accepts newer in-order bootstrap events', () => {
expect(
shouldAcceptDeterministicBootstrapEvent({
runId: 'run-1',
teamName: 'atlas-hq',
lastSeq: 2,
msg: {
run_id: 'run-1',
team_name: 'atlas-hq',
seq: 3,
},
})
).toEqual({ accept: true, nextSeq: 3 });
});
it('rejects replayed or out-of-order bootstrap events', () => {
expect(
shouldAcceptDeterministicBootstrapEvent({
runId: 'run-1',
teamName: 'atlas-hq',
lastSeq: 3,
msg: {
run_id: 'run-1',
team_name: 'atlas-hq',
seq: 2,
},
})
).toEqual({ accept: false, nextSeq: 3 });
});
it('rejects bootstrap events for another run or team', () => {
expect(
shouldAcceptDeterministicBootstrapEvent({
runId: 'run-1',
teamName: 'atlas-hq',
lastSeq: 1,
msg: {
run_id: 'run-2',
team_name: 'atlas-hq',
seq: 2,
},
})
).toEqual({ accept: false, nextSeq: 1 });
expect(
shouldAcceptDeterministicBootstrapEvent({
runId: 'run-1',
teamName: 'atlas-hq',
lastSeq: 1,
msg: {
run_id: 'run-1',
team_name: 'forge-labs',
seq: 2,
},
})
).toEqual({ accept: false, nextSeq: 1 });
});
});