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 =
|
const previousRuntimeRunId =
|
||||||
typeof previousMember?.runtimeRunId === 'string' ? previousMember.runtimeRunId.trim() : '';
|
typeof previousMember?.runtimeRunId === 'string' ? previousMember.runtimeRunId.trim() : '';
|
||||||
const sameRuntimeRun = previousRuntimeRunId.length > 0 && previousRuntimeRunId === input.runId;
|
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 =
|
const runtimePid =
|
||||||
input.metadata?.runtimePid ?? (sameRuntimeRun ? previousMember?.runtimePid : undefined);
|
input.metadata?.runtimePid ?? (sameRuntimeRun ? previousMember?.runtimePid : undefined);
|
||||||
const pidSource = input.metadata?.runtimePid
|
const pidSource = input.metadata?.runtimePid
|
||||||
|
|
@ -8974,12 +8980,48 @@ export class TeamProvisioningService {
|
||||||
await this.writeLaunchStateSnapshot(input.teamName, snapshot);
|
await this.writeLaunchStateSnapshot(input.teamName, snapshot);
|
||||||
this.agentRuntimeSnapshotCache.delete(input.teamName);
|
this.agentRuntimeSnapshotCache.delete(input.teamName);
|
||||||
this.liveTeamAgentRuntimeMetadataCache.delete(input.teamName);
|
this.liveTeamAgentRuntimeMetadataCache.delete(input.teamName);
|
||||||
this.teamChangeEmitter?.({
|
if (shouldEmitMemberSpawnChange) {
|
||||||
type: 'member-spawn',
|
this.teamChangeEmitter?.({
|
||||||
teamName: input.teamName,
|
type: 'member-spawn',
|
||||||
runId: input.runId,
|
teamName: input.teamName,
|
||||||
detail: input.memberName,
|
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: {
|
private resolvePersistedRuntimeMemberIdentity(params: {
|
||||||
|
|
|
||||||
|
|
@ -6203,6 +6203,131 @@ describe('TeamProvisioningService', () => {
|
||||||
expect(diagnostics.join('\n')).not.toContain('super-secret');
|
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 () => {
|
it('does not carry a stale OpenCode runtime pid into a fresh runtime run check-in', async () => {
|
||||||
const svc = new TeamProvisioningService();
|
const svc = new TeamProvisioningService();
|
||||||
const previousSnapshot = {
|
const previousSnapshot = {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue