fix(team-runtime): suppress opencode heartbeat spawn noise
This commit is contained in:
parent
c536557991
commit
50ce94dcaa
2 changed files with 173 additions and 6 deletions
|
|
@ -8913,6 +8913,12 @@ export class TeamProvisioningService {
|
|||
const previousRuntimeRunId =
|
||||
typeof previousMember?.runtimeRunId === 'string' ? previousMember.runtimeRunId.trim() : '';
|
||||
const sameRuntimeRun = previousRuntimeRunId.length > 0 && previousRuntimeRunId === input.runId;
|
||||
const shouldEmitMemberSpawnChange = this.shouldEmitOpenCodeRuntimeLivenessMemberSpawnChange({
|
||||
previousMember,
|
||||
runtimeRunId: input.runId,
|
||||
runtimeSessionId: input.runtimeSessionId,
|
||||
runtimePid: input.metadata?.runtimePid,
|
||||
});
|
||||
const runtimePid =
|
||||
input.metadata?.runtimePid ?? (sameRuntimeRun ? previousMember?.runtimePid : undefined);
|
||||
const pidSource = input.metadata?.runtimePid
|
||||
|
|
@ -8974,12 +8980,48 @@ export class TeamProvisioningService {
|
|||
await this.writeLaunchStateSnapshot(input.teamName, snapshot);
|
||||
this.agentRuntimeSnapshotCache.delete(input.teamName);
|
||||
this.liveTeamAgentRuntimeMetadataCache.delete(input.teamName);
|
||||
this.teamChangeEmitter?.({
|
||||
type: 'member-spawn',
|
||||
teamName: input.teamName,
|
||||
runId: input.runId,
|
||||
detail: input.memberName,
|
||||
});
|
||||
if (shouldEmitMemberSpawnChange) {
|
||||
this.teamChangeEmitter?.({
|
||||
type: 'member-spawn',
|
||||
teamName: input.teamName,
|
||||
runId: input.runId,
|
||||
detail: input.memberName,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private shouldEmitOpenCodeRuntimeLivenessMemberSpawnChange(input: {
|
||||
previousMember?: PersistedTeamLaunchMemberState;
|
||||
runtimeRunId: string;
|
||||
runtimeSessionId: string;
|
||||
runtimePid?: number;
|
||||
}): boolean {
|
||||
const previous = input.previousMember;
|
||||
if (!previous) {
|
||||
return true;
|
||||
}
|
||||
const previousRuntimeRunId =
|
||||
typeof previous.runtimeRunId === 'string' ? previous.runtimeRunId.trim() : '';
|
||||
const previousRuntimeSessionId =
|
||||
typeof previous.runtimeSessionId === 'string' ? previous.runtimeSessionId.trim() : '';
|
||||
if (
|
||||
previousRuntimeRunId !== input.runtimeRunId ||
|
||||
previousRuntimeSessionId !== input.runtimeSessionId
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
if (
|
||||
input.runtimePid !== undefined &&
|
||||
(previous.runtimePid === undefined || previous.runtimePid !== input.runtimePid)
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
return (
|
||||
previous.launchState !== 'confirmed_alive' ||
|
||||
previous.runtimeAlive !== true ||
|
||||
previous.bootstrapConfirmed !== true ||
|
||||
previous.hardFailure === true
|
||||
);
|
||||
}
|
||||
|
||||
private resolvePersistedRuntimeMemberIdentity(params: {
|
||||
|
|
|
|||
|
|
@ -6203,6 +6203,131 @@ describe('TeamProvisioningService', () => {
|
|||
expect(diagnostics.join('\n')).not.toContain('super-secret');
|
||||
});
|
||||
|
||||
it('emits member-spawn when OpenCode runtime liveness first confirms a pending member', async () => {
|
||||
const svc = new TeamProvisioningService();
|
||||
const previousSnapshot = {
|
||||
version: 2 as const,
|
||||
teamName: 'mixed-team',
|
||||
updatedAt: '2026-04-22T12:00:00.000Z',
|
||||
launchPhase: 'active' as const,
|
||||
expectedMembers: ['bob'],
|
||||
members: {
|
||||
bob: {
|
||||
name: 'bob',
|
||||
providerId: 'opencode' as const,
|
||||
laneId: 'secondary:opencode:bob',
|
||||
laneKind: 'secondary' as const,
|
||||
laneOwnerProviderId: 'opencode' as const,
|
||||
launchState: 'runtime_pending_bootstrap' as const,
|
||||
agentToolAccepted: true,
|
||||
runtimeAlive: true,
|
||||
bootstrapConfirmed: false,
|
||||
hardFailure: false,
|
||||
runtimeRunId: 'run-member-spawn-1',
|
||||
runtimeSessionId: 'session-bob',
|
||||
lastEvaluatedAt: '2026-04-22T12:00:00.000Z',
|
||||
},
|
||||
},
|
||||
summary: {
|
||||
confirmedCount: 0,
|
||||
pendingCount: 1,
|
||||
failedCount: 0,
|
||||
runtimeAlivePendingCount: 1,
|
||||
},
|
||||
teamLaunchState: 'partial_pending' as const,
|
||||
};
|
||||
const events: Array<{ type: string; teamName: string; runId?: string; detail?: string }> = [];
|
||||
|
||||
svc.setTeamChangeEmitter((event) => {
|
||||
events.push(event);
|
||||
});
|
||||
(svc as any).launchStateStore = {
|
||||
read: vi.fn(async () => previousSnapshot),
|
||||
write: vi.fn(async () => {}),
|
||||
};
|
||||
|
||||
await (svc as any).updateOpenCodeRuntimeMemberLiveness({
|
||||
teamName: 'mixed-team',
|
||||
runId: 'run-member-spawn-1',
|
||||
memberName: 'bob',
|
||||
runtimeSessionId: 'session-bob',
|
||||
observedAt: '2026-04-22T12:05:00.000Z',
|
||||
diagnostics: ['native heartbeat'],
|
||||
metadata: { runtimePid: 4321 },
|
||||
reason: 'OpenCode runtime heartbeat accepted',
|
||||
});
|
||||
|
||||
expect(events).toEqual([
|
||||
{
|
||||
type: 'member-spawn',
|
||||
teamName: 'mixed-team',
|
||||
runId: 'run-member-spawn-1',
|
||||
detail: 'bob',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
it('does not emit member-spawn for routine OpenCode heartbeat from the same live session', async () => {
|
||||
const svc = new TeamProvisioningService();
|
||||
const previousSnapshot = {
|
||||
version: 2 as const,
|
||||
teamName: 'mixed-team',
|
||||
updatedAt: '2026-04-22T12:00:00.000Z',
|
||||
launchPhase: 'active' as const,
|
||||
expectedMembers: ['bob'],
|
||||
members: {
|
||||
bob: {
|
||||
name: 'bob',
|
||||
providerId: 'opencode' as const,
|
||||
laneId: 'secondary:opencode:bob',
|
||||
laneKind: 'secondary' as const,
|
||||
laneOwnerProviderId: 'opencode' as const,
|
||||
launchState: 'confirmed_alive' as const,
|
||||
agentToolAccepted: true,
|
||||
runtimeAlive: true,
|
||||
bootstrapConfirmed: true,
|
||||
hardFailure: false,
|
||||
runtimePid: 4321,
|
||||
runtimeRunId: 'run-member-spawn-1',
|
||||
runtimeSessionId: 'session-bob',
|
||||
livenessKind: 'confirmed_bootstrap' as const,
|
||||
lastEvaluatedAt: '2026-04-22T12:00:00.000Z',
|
||||
},
|
||||
},
|
||||
summary: {
|
||||
confirmedCount: 1,
|
||||
pendingCount: 0,
|
||||
failedCount: 0,
|
||||
runtimeAlivePendingCount: 0,
|
||||
},
|
||||
teamLaunchState: 'ready' as const,
|
||||
};
|
||||
const events: Array<{ type: string; teamName: string; runId?: string; detail?: string }> = [];
|
||||
const write = vi.fn(async () => {});
|
||||
|
||||
svc.setTeamChangeEmitter((event) => {
|
||||
events.push(event);
|
||||
});
|
||||
(svc as any).launchStateStore = {
|
||||
read: vi.fn(async () => previousSnapshot),
|
||||
write,
|
||||
};
|
||||
|
||||
await (svc as any).updateOpenCodeRuntimeMemberLiveness({
|
||||
teamName: 'mixed-team',
|
||||
runId: 'run-member-spawn-1',
|
||||
memberName: 'bob',
|
||||
runtimeSessionId: 'session-bob',
|
||||
observedAt: '2026-04-22T12:05:00.000Z',
|
||||
diagnostics: ['native heartbeat'],
|
||||
metadata: { runtimePid: 4321 },
|
||||
reason: 'OpenCode runtime heartbeat accepted',
|
||||
});
|
||||
|
||||
expect(write).toHaveBeenCalledTimes(1);
|
||||
expect(events).toEqual([]);
|
||||
});
|
||||
|
||||
it('does not carry a stale OpenCode runtime pid into a fresh runtime run check-in', async () => {
|
||||
const svc = new TeamProvisioningService();
|
||||
const previousSnapshot = {
|
||||
|
|
|
|||
Loading…
Reference in a new issue