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.lastHeartbeatAt = current.lastHeartbeatAt ?? bootstrapMember.lastHeartbeatAt;
}
const matchedRuntimeNames = [...configMembers].filter((name) => {
if (name === expected) return true;
const parsed = parseNumericSuffixName(name);
return parsed !== null && parsed.suffix >= 2 && parsed.base === expected;
});
const runtimeAlive =
liveAgentNames.has(expected) ||
matchedRuntimeNames.some((runtimeName) => liveAgentNames.has(runtimeName));
const matchedConfigNames = [...configMembers].filter((name) =>
matchesTeamMemberIdentity(name, expected)
);
const runtimeAlive = [...liveAgentNames].some((name) =>
matchesTeamMemberIdentity(name, expected)
);
const heartbeatMessage = leadInboxMessages.find((message) => {
if (typeof message.from !== 'string' || message.from.trim() !== expected) return false;
if (
@ -12705,9 +12703,9 @@ export class TeamProvisioningService {
current.sources = {
...(current.sources ?? {}),
processAlive: runtimeAlive || undefined,
configRegistered: matchedRuntimeNames.length > 0 || undefined,
configRegistered: matchedConfigNames.length > 0 || undefined,
configDrift:
heartbeatMessage != null && matchedRuntimeNames.length === 0
heartbeatMessage != null && matchedConfigNames.length === 0
? true
: current.sources?.configDrift,
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 () => {
const teamName = 'persisted-union-member-spawn-team';
const teamDir = path.join(tempTeamsBase, teamName);