fix(team): show stopped runtime from spawn status
This commit is contained in:
parent
4a4c67fcb9
commit
a324f6cc66
2 changed files with 158 additions and 6 deletions
|
|
@ -42,6 +42,12 @@ interface SpawnDegradation {
|
|||
diagnosticSeverity: TeamAgentRuntimeDiagnosticSeverity;
|
||||
}
|
||||
|
||||
interface SpawnStoppedEvidence {
|
||||
reason: string;
|
||||
diagnostic?: string;
|
||||
diagnosticSeverity?: TeamAgentRuntimeDiagnosticSeverity;
|
||||
}
|
||||
|
||||
const ACTIVE_SPAWN_STATUSES = new Set(['waiting', 'spawning']);
|
||||
|
||||
export function buildTeamRuntimeDisplayRows({
|
||||
|
|
@ -134,12 +140,16 @@ function buildRuntimeBackedDisplayRow(
|
|||
): TeamRuntimeDisplayRow {
|
||||
const hasErrorDiagnostic = runtime.runtimeDiagnosticSeverity === 'error';
|
||||
const spawnDegradation = getSpawnDegradation(spawn);
|
||||
const state = getRuntimeBackedState(runtime, hasErrorDiagnostic, spawnDegradation != null);
|
||||
const spawnStoppedEvidence = spawnDegradation ? null : getSpawnStoppedEvidence(runtime, spawn);
|
||||
const state = spawnStoppedEvidence
|
||||
? 'stopped'
|
||||
: getRuntimeBackedState(runtime, hasErrorDiagnostic, spawnDegradation != null);
|
||||
const degradedReason = spawnDegradation
|
||||
? withLiveProcessContext(spawnDegradation.reason, runtime)
|
||||
: undefined;
|
||||
const stateReason =
|
||||
degradedReason ??
|
||||
spawnStoppedEvidence?.reason ??
|
||||
runtime.runtimeDiagnostic ??
|
||||
(runtime.alive === true ? 'Runtime heartbeat is alive' : 'Runtime heartbeat is not alive');
|
||||
|
||||
|
|
@ -157,8 +167,13 @@ function buildRuntimeBackedDisplayRow(
|
|||
diagnostic:
|
||||
spawnDegradation && degradedReason
|
||||
? withLiveProcessContext(spawnDegradation.diagnostic ?? degradedReason, runtime)
|
||||
: runtime.runtimeDiagnostic,
|
||||
diagnosticSeverity: spawnDegradation?.diagnosticSeverity ?? runtime.runtimeDiagnosticSeverity,
|
||||
: spawnStoppedEvidence
|
||||
? spawnStoppedEvidence.diagnostic
|
||||
: runtime.runtimeDiagnostic,
|
||||
diagnosticSeverity:
|
||||
spawnDegradation?.diagnosticSeverity ??
|
||||
spawnStoppedEvidence?.diagnosticSeverity ??
|
||||
runtime.runtimeDiagnosticSeverity,
|
||||
pidLabel: formatRuntimePidLabel(runtime),
|
||||
actionsAllowed: false,
|
||||
};
|
||||
|
|
@ -207,6 +222,24 @@ function getSpawnDegradation(spawn?: MemberSpawnStatusEntry): SpawnDegradation |
|
|||
return null;
|
||||
}
|
||||
|
||||
function getSpawnStoppedEvidence(
|
||||
runtime: TeamAgentRuntimeEntry,
|
||||
spawn?: MemberSpawnStatusEntry
|
||||
): SpawnStoppedEvidence | null {
|
||||
if (!spawn || spawn.runtimeAlive !== false || runtime.livenessKind !== 'confirmed_bootstrap') {
|
||||
return null;
|
||||
}
|
||||
if (spawn.status !== 'online' && spawn.launchState !== 'confirmed_alive') {
|
||||
return null;
|
||||
}
|
||||
const reason = spawn.runtimeDiagnostic ?? 'Spawn status reports runtime is not alive';
|
||||
return {
|
||||
reason,
|
||||
diagnostic: spawn.runtimeDiagnostic ?? reason,
|
||||
diagnosticSeverity: spawn.runtimeDiagnosticSeverity ?? 'warning',
|
||||
};
|
||||
}
|
||||
|
||||
function getRuntimeBackedState(
|
||||
runtime: TeamAgentRuntimeEntry,
|
||||
hasErrorDiagnostic: boolean,
|
||||
|
|
@ -220,7 +253,11 @@ function getRuntimeBackedState(
|
|||
}
|
||||
|
||||
function withLiveProcessContext(reason: string, runtime: TeamAgentRuntimeEntry): string {
|
||||
if (runtime.alive !== true || /process is still alive/i.test(reason)) {
|
||||
if (
|
||||
runtime.alive !== true ||
|
||||
runtime.livenessKind === 'confirmed_bootstrap' ||
|
||||
/process is still alive/i.test(reason)
|
||||
) {
|
||||
return reason;
|
||||
}
|
||||
return `${reason}. Process is still alive.`;
|
||||
|
|
@ -245,6 +282,21 @@ function buildSpawnBackedDisplayRow(
|
|||
};
|
||||
}
|
||||
|
||||
const spawnStoppedEvidence = getSpawnOnlyStoppedEvidence(spawn);
|
||||
if (spawnStoppedEvidence) {
|
||||
return {
|
||||
memberName,
|
||||
state: 'stopped',
|
||||
stateReason: spawnStoppedEvidence.reason,
|
||||
source: 'spawn-status',
|
||||
updatedAt: spawn.livenessLastCheckedAt ?? spawn.updatedAt,
|
||||
runtimeModel: spawn.runtimeModel,
|
||||
diagnostic: spawnStoppedEvidence.diagnostic,
|
||||
diagnosticSeverity: spawnStoppedEvidence.diagnosticSeverity,
|
||||
actionsAllowed: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
(spawn.status === 'online' && hasConfirmedSpawnLiveness(spawn)) ||
|
||||
isConfirmedSpawnLaunch(spawn)
|
||||
|
|
@ -306,6 +358,18 @@ function buildSpawnBackedDisplayRow(
|
|||
};
|
||||
}
|
||||
|
||||
function getSpawnOnlyStoppedEvidence(spawn: MemberSpawnStatusEntry): SpawnStoppedEvidence | null {
|
||||
if (spawn.runtimeAlive !== false) return null;
|
||||
if (spawn.status !== 'online' && spawn.launchState !== 'confirmed_alive') return null;
|
||||
|
||||
const reason = spawn.runtimeDiagnostic ?? 'Spawn status reports runtime is not alive';
|
||||
return {
|
||||
reason,
|
||||
diagnostic: spawn.runtimeDiagnostic ?? reason,
|
||||
diagnosticSeverity: spawn.runtimeDiagnosticSeverity ?? 'warning',
|
||||
};
|
||||
}
|
||||
|
||||
function hasConfirmedSpawnLiveness(spawn: MemberSpawnStatusEntry): boolean {
|
||||
return (
|
||||
spawn.runtimeAlive === true ||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import { buildTeamRuntimeDisplayRows } from '@renderer/components/team/teamRuntimeDisplayRows';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
import type {
|
||||
MemberSpawnStatusEntry,
|
||||
|
|
@ -92,6 +91,95 @@ describe('buildTeamRuntimeDisplayRows', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('does not show bootstrap-only runtime evidence as running when spawn says runtime is stopped', () => {
|
||||
const rows = buildTeamRuntimeDisplayRows({
|
||||
members: [{ name: 'alice' }],
|
||||
runtimeSnapshot: createRuntimeSnapshot({
|
||||
alice: createRuntimeEntry({
|
||||
alive: true,
|
||||
livenessKind: 'confirmed_bootstrap',
|
||||
runtimeDiagnostic: 'bootstrap confirmed',
|
||||
runtimeDiagnosticSeverity: 'info',
|
||||
}),
|
||||
}),
|
||||
spawnStatuses: {
|
||||
alice: createSpawnStatus({
|
||||
status: 'online',
|
||||
launchState: 'confirmed_alive',
|
||||
bootstrapConfirmed: true,
|
||||
runtimeAlive: false,
|
||||
runtimeDiagnostic: 'persisted runtime pid is not alive',
|
||||
runtimeDiagnosticSeverity: 'warning',
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
expect(rows[0]).toMatchObject({
|
||||
memberName: 'alice',
|
||||
state: 'stopped',
|
||||
source: 'mixed',
|
||||
stateReason: 'persisted runtime pid is not alive',
|
||||
diagnosticSeverity: 'warning',
|
||||
actionsAllowed: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('keeps spawn degradation stronger than stopped evidence for mixed rows', () => {
|
||||
const rows = buildTeamRuntimeDisplayRows({
|
||||
members: [{ name: 'alice' }],
|
||||
runtimeSnapshot: createRuntimeSnapshot({
|
||||
alice: createRuntimeEntry({
|
||||
alive: true,
|
||||
livenessKind: 'confirmed_bootstrap',
|
||||
runtimeDiagnostic: 'bootstrap confirmed',
|
||||
}),
|
||||
}),
|
||||
spawnStatuses: {
|
||||
alice: createSpawnStatus({
|
||||
status: 'online',
|
||||
launchState: 'runtime_pending_permission',
|
||||
bootstrapConfirmed: true,
|
||||
runtimeAlive: false,
|
||||
pendingPermissionRequestIds: ['perm-1'],
|
||||
runtimeDiagnostic: 'Runtime is waiting for permission approval',
|
||||
runtimeDiagnosticSeverity: 'warning',
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
expect(rows[0]).toMatchObject({
|
||||
memberName: 'alice',
|
||||
state: 'degraded',
|
||||
source: 'mixed',
|
||||
stateReason: 'Runtime is waiting for permission approval',
|
||||
diagnosticSeverity: 'warning',
|
||||
actionsAllowed: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('does not show spawn-only confirmed bootstrap as running when spawn says runtime is stopped', () => {
|
||||
const rows = buildTeamRuntimeDisplayRows({
|
||||
members: [{ name: 'alice' }],
|
||||
spawnStatuses: {
|
||||
alice: createSpawnStatus({
|
||||
status: 'online',
|
||||
launchState: 'confirmed_alive',
|
||||
bootstrapConfirmed: true,
|
||||
runtimeAlive: false,
|
||||
}),
|
||||
},
|
||||
});
|
||||
|
||||
expect(rows[0]).toMatchObject({
|
||||
memberName: 'alice',
|
||||
state: 'stopped',
|
||||
source: 'spawn-status',
|
||||
stateReason: 'Spawn status reports runtime is not alive',
|
||||
diagnosticSeverity: 'warning',
|
||||
actionsAllowed: false,
|
||||
});
|
||||
});
|
||||
|
||||
it('treats confirmed spawn bootstrap as running even if stale status is still waiting', () => {
|
||||
const rows = buildTeamRuntimeDisplayRows({
|
||||
members: [{ name: 'alice' }],
|
||||
|
|
|
|||
Loading…
Reference in a new issue