fix(team): match suffixed live agents during reconcile

This commit is contained in:
777genius 2026-04-23 04:11:59 +03:00
parent 1223fa236a
commit b29adf1008
2 changed files with 36 additions and 10 deletions

View file

@ -12666,14 +12666,12 @@ export class TeamProvisioningService {
current.bootstrapConfirmed = true; current.bootstrapConfirmed = true;
current.lastHeartbeatAt = current.lastHeartbeatAt ?? bootstrapMember.lastHeartbeatAt; current.lastHeartbeatAt = current.lastHeartbeatAt ?? bootstrapMember.lastHeartbeatAt;
} }
const matchedRuntimeNames = [...configMembers].filter((name) => { const matchedConfigNames = [...configMembers].filter((name) =>
if (name === expected) return true; matchesTeamMemberIdentity(name, expected)
const parsed = parseNumericSuffixName(name); );
return parsed !== null && parsed.suffix >= 2 && parsed.base === expected; const runtimeAlive = [...liveAgentNames].some((name) =>
}); matchesTeamMemberIdentity(name, expected)
const runtimeAlive = );
liveAgentNames.has(expected) ||
matchedRuntimeNames.some((runtimeName) => liveAgentNames.has(runtimeName));
const heartbeatMessage = leadInboxMessages.find((message) => { const heartbeatMessage = leadInboxMessages.find((message) => {
if (typeof message.from !== 'string' || message.from.trim() !== expected) return false; if (typeof message.from !== 'string' || message.from.trim() !== expected) return false;
if ( if (
@ -12705,9 +12703,9 @@ export class TeamProvisioningService {
current.sources = { current.sources = {
...(current.sources ?? {}), ...(current.sources ?? {}),
processAlive: runtimeAlive || undefined, processAlive: runtimeAlive || undefined,
configRegistered: matchedRuntimeNames.length > 0 || undefined, configRegistered: matchedConfigNames.length > 0 || undefined,
configDrift: configDrift:
heartbeatMessage != null && matchedRuntimeNames.length === 0 heartbeatMessage != null && matchedConfigNames.length === 0
? true ? true
: current.sources?.configDrift, : current.sources?.configDrift,
inboxHeartbeat: heartbeatMessage != null ? true : current.sources?.inboxHeartbeat, inboxHeartbeat: heartbeatMessage != null ? true : current.sources?.inboxHeartbeat,

View file

@ -5802,6 +5802,34 @@ describe('TeamProvisioningService', () => {
}); });
}); });
it('treats suffixed live runtime names as alive during persisted launch reconcile', async () => {
const teamName = 'suffixed-live-runtime-team';
const leadSessionId = 'lead-session';
writeLaunchConfig(teamName, '/Users/test/proj', leadSessionId, ['alice']);
writeLaunchState(teamName, leadSessionId, {
alice: {
launchState: 'runtime_pending_bootstrap',
agentToolAccepted: true,
runtimeAlive: false,
bootstrapConfirmed: false,
hardFailure: false,
hardFailureReason: undefined,
firstSpawnAcceptedAt: new Date(Date.now() - 5_000).toISOString(),
},
});
const svc = new TeamProvisioningService();
(svc as any).getLiveTeamAgentNames = vi.fn(async () => new Set(['alice-2']));
const result = await svc.getMemberSpawnStatuses(teamName);
expect(result.statuses.alice).toMatchObject({
status: 'online',
launchState: 'runtime_pending_bootstrap',
runtimeAlive: true,
});
});
it('returns persisted expectedMembers as the union of expected and materialized launch members', async () => { it('returns persisted expectedMembers as the union of expected and materialized launch members', async () => {
const teamName = 'persisted-union-member-spawn-team'; const teamName = 'persisted-union-member-spawn-team';
const teamDir = path.join(tempTeamsBase, teamName); const teamDir = path.join(tempTeamsBase, teamName);