agent-ecosystem/test/renderer/components/team/teamRuntimeDisplayRows.test.ts

594 lines
18 KiB
TypeScript

import { buildTeamRuntimeDisplayRows } from '@renderer/components/team/teamRuntimeDisplayRows';
import { describe, expect, it } from 'vitest';
import type {
MemberSpawnStatusEntry,
TeamAgentRuntimeEntry,
TeamAgentRuntimeSnapshot,
} from '@shared/types';
const members = [{ name: 'alice' }, { name: 'bob' }];
function createRuntimeEntry(overrides: Partial<TeamAgentRuntimeEntry> = {}): TeamAgentRuntimeEntry {
return {
memberName: 'alice',
alive: true,
restartable: true,
updatedAt: '2026-05-03T10:00:00.000Z',
...overrides,
};
}
function createRuntimeSnapshot(
membersByName: Record<string, TeamAgentRuntimeEntry>
): TeamAgentRuntimeSnapshot {
return {
teamName: 'my-team',
updatedAt: '2026-05-03T10:00:00.000Z',
runId: 'run-1',
members: membersByName,
};
}
function createSpawnStatus(
overrides: Partial<MemberSpawnStatusEntry> = {}
): MemberSpawnStatusEntry {
return {
status: 'spawning',
launchState: 'starting',
updatedAt: '2026-05-03T10:00:00.000Z',
...overrides,
};
}
describe('buildTeamRuntimeDisplayRows', () => {
it('maps alive runtime entries to running display rows', () => {
const rows = buildTeamRuntimeDisplayRows({
members,
runtimeSnapshot: createRuntimeSnapshot({
alice: createRuntimeEntry({ runtimeModel: 'claude-sonnet-4.5', runtimePid: 1234 }),
}),
});
expect(rows[0]).toMatchObject({
memberName: 'alice',
state: 'running',
source: 'runtime',
runtimeModel: 'claude-sonnet-4.5',
pidLabel: 'runtime pid 1234',
actionsAllowed: false,
});
expect(rows[1]).toMatchObject({
memberName: 'bob',
state: 'unknown',
actionsAllowed: false,
});
});
it('does not treat historical bootstrap as running when runtime is not alive', () => {
const rows = buildTeamRuntimeDisplayRows({
members: [{ name: 'alice' }],
runtimeSnapshot: createRuntimeSnapshot({
alice: createRuntimeEntry({
alive: false,
historicalBootstrapConfirmed: true,
runtimeDiagnostic: 'Runtime heartbeat is stale',
}),
}),
spawnStatuses: {
alice: createSpawnStatus({
status: 'online',
launchState: 'confirmed_alive',
bootstrapConfirmed: true,
}),
},
});
expect(rows[0]).toMatchObject({
memberName: 'alice',
state: 'stopped',
source: 'mixed',
stateReason: 'Runtime heartbeat is stale',
actionsAllowed: false,
});
});
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' }],
spawnStatuses: {
alice: createSpawnStatus({
status: 'waiting',
launchState: 'confirmed_alive',
bootstrapConfirmed: true,
runtimeAlive: true,
livenessKind: 'registered_only',
}),
},
});
expect(rows[0]).toMatchObject({
memberName: 'alice',
state: 'running',
source: 'spawn-status',
stateReason: 'Bootstrap confirmed',
actionsAllowed: false,
});
});
it('maps a non-alive runtime with error diagnostics to degraded', () => {
const rows = buildTeamRuntimeDisplayRows({
members: [{ name: 'alice' }],
runtimeSnapshot: createRuntimeSnapshot({
alice: createRuntimeEntry({
alive: false,
runtimeDiagnostic: 'Runtime process crashed',
runtimeDiagnosticSeverity: 'error',
}),
}),
});
expect(rows[0]).toMatchObject({
memberName: 'alice',
state: 'degraded',
stateReason: 'Runtime process crashed',
actionsAllowed: false,
});
});
it('degrades mixed rows when runtime is alive but spawn evidence has failed', () => {
const rows = buildTeamRuntimeDisplayRows({
members: [{ name: 'alice' }],
runtimeSnapshot: createRuntimeSnapshot({
alice: createRuntimeEntry({
alive: true,
runtimeDiagnostic: 'Runtime heartbeat is alive',
}),
}),
spawnStatuses: {
alice: createSpawnStatus({
status: 'error',
launchState: 'failed_to_start',
hardFailure: true,
hardFailureReason: 'Bootstrap command failed',
}),
},
});
expect(rows[0]).toMatchObject({
memberName: 'alice',
state: 'degraded',
source: 'mixed',
stateReason: 'Bootstrap command failed. Process is still alive.',
actionsAllowed: false,
});
});
it('does not degrade bootstrap-confirmed provisioned-but-not-alive rows', () => {
const rows = buildTeamRuntimeDisplayRows({
members: [{ name: 'alice' }],
runtimeSnapshot: createRuntimeSnapshot({
alice: createRuntimeEntry({
alive: false,
livenessKind: 'confirmed_bootstrap',
runtimeDiagnostic:
'runtime pid could not be verified because process table is unavailable',
runtimeDiagnosticSeverity: 'warning',
}),
}),
spawnStatuses: {
alice: createSpawnStatus({
status: 'error',
launchState: 'failed_to_start',
runtimeAlive: false,
bootstrapConfirmed: true,
hardFailure: true,
hardFailureReason: 'CLI process exited (code 1) \u2014 team provisioned but not alive',
livenessKind: 'confirmed_bootstrap',
}),
},
});
expect(rows[0]).toMatchObject({
memberName: 'alice',
state: 'running',
source: 'mixed',
stateReason: 'Bootstrap confirmed',
diagnosticSeverity: 'warning',
actionsAllowed: false,
});
});
it('does not degrade Windows process-table-unavailable registered metadata rows', () => {
const rows = buildTeamRuntimeDisplayRows({
members: [{ name: 'alice' }],
runtimeSnapshot: createRuntimeSnapshot({
alice: createRuntimeEntry({
alive: false,
livenessKind: 'registered_only',
runtimeDiagnostic:
'runtime pid could not be verified because process table is unavailable',
runtimeDiagnosticSeverity: 'warning',
}),
}),
spawnStatuses: {
alice: createSpawnStatus({
status: 'error',
launchState: 'failed_to_start',
runtimeAlive: false,
bootstrapConfirmed: true,
hardFailure: true,
hardFailureReason:
'CLI process exited (code 1) - team provisioned but not alive; process table unavailable',
livenessKind: 'registered_only',
runtimeDiagnostic:
'runtime pid could not be verified because process table is unavailable',
runtimeDiagnosticSeverity: 'warning',
}),
},
});
expect(rows[0]).toMatchObject({
memberName: 'alice',
state: 'running',
source: 'mixed',
stateReason: 'Bootstrap confirmed',
diagnosticSeverity: 'warning',
actionsAllowed: false,
});
});
it('uses spawn process-table proof when runtime registered metadata has no diagnostic text', () => {
const rows = buildTeamRuntimeDisplayRows({
members: [{ name: 'alice' }],
runtimeSnapshot: createRuntimeSnapshot({
alice: createRuntimeEntry({
alive: false,
livenessKind: 'registered_only',
runtimeDiagnosticSeverity: 'warning',
}),
}),
spawnStatuses: {
alice: createSpawnStatus({
status: 'error',
launchState: 'failed_to_start',
runtimeAlive: false,
bootstrapConfirmed: true,
hardFailure: true,
hardFailureReason:
'CLI process exited (code 1) - team provisioned but not alive; process table unavailable',
livenessKind: 'confirmed_bootstrap',
}),
},
});
expect(rows[0]).toMatchObject({
memberName: 'alice',
state: 'running',
source: 'mixed',
stateReason: 'Bootstrap confirmed',
diagnosticSeverity: 'warning',
actionsAllowed: false,
});
});
it('does not let stale provisioned-but-not-alive spawn evidence hide runtime errors', () => {
const rows = buildTeamRuntimeDisplayRows({
members: [{ name: 'alice' }],
runtimeSnapshot: createRuntimeSnapshot({
alice: createRuntimeEntry({
alive: false,
livenessKind: 'confirmed_bootstrap',
runtimeDiagnostic: 'Runtime process crashed',
runtimeDiagnosticSeverity: 'error',
}),
}),
spawnStatuses: {
alice: createSpawnStatus({
status: 'error',
launchState: 'failed_to_start',
runtimeAlive: false,
bootstrapConfirmed: true,
hardFailure: true,
hardFailureReason: 'CLI process exited (code 1) - team provisioned but not alive',
livenessKind: 'confirmed_bootstrap',
}),
},
});
expect(rows[0]).toMatchObject({
memberName: 'alice',
state: 'degraded',
source: 'mixed',
stateReason: 'Runtime process crashed',
diagnosticSeverity: 'error',
actionsAllowed: false,
});
});
it('does not let provisioned-but-not-alive spawn evidence hide stopped runtime evidence', () => {
const rows = buildTeamRuntimeDisplayRows({
members: [{ name: 'alice' }],
runtimeSnapshot: createRuntimeSnapshot({
alice: createRuntimeEntry({
alive: false,
livenessKind: 'not_found',
runtimeDiagnostic: 'Runtime metadata was not found',
runtimeDiagnosticSeverity: 'warning',
}),
}),
spawnStatuses: {
alice: createSpawnStatus({
status: 'error',
launchState: 'failed_to_start',
runtimeAlive: false,
bootstrapConfirmed: true,
hardFailure: true,
hardFailureReason: 'CLI process exited (code 1) - team provisioned but not alive',
livenessKind: 'confirmed_bootstrap',
}),
},
});
expect(rows[0]).toMatchObject({
memberName: 'alice',
state: 'stopped',
source: 'mixed',
stateReason: 'Runtime metadata was not found',
diagnosticSeverity: 'warning',
actionsAllowed: false,
});
});
it('does not let stopped provisioned-but-not-alive spawn evidence hide live runtime context', () => {
const rows = buildTeamRuntimeDisplayRows({
members: [{ name: 'alice' }],
runtimeSnapshot: createRuntimeSnapshot({
alice: createRuntimeEntry({
alive: true,
livenessKind: 'runtime_process',
runtimeDiagnostic: 'Runtime process is alive',
runtimeDiagnosticSeverity: 'info',
}),
}),
spawnStatuses: {
alice: createSpawnStatus({
status: 'error',
launchState: 'failed_to_start',
runtimeAlive: false,
bootstrapConfirmed: true,
hardFailure: true,
hardFailureReason: 'CLI process exited (code 1) - team provisioned but not alive',
livenessKind: 'not_found',
runtimeDiagnostic: 'Runtime is no longer registered',
runtimeDiagnosticSeverity: 'warning',
}),
},
});
expect(rows[0]).toMatchObject({
memberName: 'alice',
state: 'degraded',
source: 'mixed',
stateReason: 'Runtime is no longer registered. Process is still alive.',
diagnosticSeverity: 'warning',
actionsAllowed: false,
});
});
it('keeps spawn-only runtime errors visible for provisioned-but-not-alive entries', () => {
const rows = buildTeamRuntimeDisplayRows({
members: [{ name: 'alice' }],
spawnStatuses: {
alice: createSpawnStatus({
status: 'error',
launchState: 'failed_to_start',
runtimeAlive: false,
bootstrapConfirmed: true,
hardFailure: true,
hardFailureReason: 'CLI process exited (code 1) - team provisioned but not alive',
livenessKind: 'confirmed_bootstrap',
runtimeDiagnostic: 'Runtime process crashed',
runtimeDiagnosticSeverity: 'error',
}),
},
});
expect(rows[0]).toMatchObject({
memberName: 'alice',
state: 'degraded',
source: 'spawn-status',
stateReason: 'Runtime process crashed',
diagnosticSeverity: 'error',
actionsAllowed: false,
});
});
it('keeps spawn-only stopped liveness visible for provisioned-but-not-alive entries', () => {
const rows = buildTeamRuntimeDisplayRows({
members: [{ name: 'alice' }],
spawnStatuses: {
alice: createSpawnStatus({
status: 'error',
launchState: 'failed_to_start',
runtimeAlive: false,
bootstrapConfirmed: true,
hardFailure: true,
hardFailureReason: 'CLI process exited (code 1) - team provisioned but not alive',
livenessKind: 'not_found',
runtimeDiagnostic: 'Runtime is no longer registered',
runtimeDiagnosticSeverity: 'warning',
}),
},
});
expect(rows[0]).toMatchObject({
memberName: 'alice',
state: 'degraded',
source: 'spawn-status',
stateReason: 'Runtime is no longer registered',
diagnosticSeverity: 'warning',
actionsAllowed: false,
});
});
it('degrades spawn-only rows when online process evidence has stalled bootstrap', () => {
const rows = buildTeamRuntimeDisplayRows({
members: [{ name: 'alice' }],
spawnStatuses: {
alice: createSpawnStatus({
status: 'online',
launchState: 'runtime_pending_bootstrap',
runtimeAlive: true,
bootstrapStalled: true,
runtimeDiagnostic: 'Runtime is alive, but bootstrap did not confirm',
}),
},
});
expect(rows[0]).toMatchObject({
memberName: 'alice',
state: 'degraded',
source: 'spawn-status',
stateReason: 'Runtime is alive, but bootstrap did not confirm',
actionsAllowed: false,
});
});
it('uses explicit spawn status handling without promoting unknown statuses to running', () => {
const rows = buildTeamRuntimeDisplayRows({
members: [{ name: 'alice' }, { name: 'bob' }, { name: 'carol' }],
spawnStatuses: {
alice: createSpawnStatus({ status: 'spawning' }),
bob: createSpawnStatus({
status: 'online',
launchState: 'confirmed_alive',
runtimeAlive: true,
}),
carol: createSpawnStatus({ status: 'surprising-new-status' as never }),
},
});
expect(rows.map((row) => [row.memberName, row.state])).toEqual([
['alice', 'starting'],
['bob', 'running'],
['carol', 'unknown'],
]);
});
it('chooses the latest runtime entry when multiple lanes map to one member', () => {
const rows = buildTeamRuntimeDisplayRows({
members: [{ name: 'alice' }],
runtimeSnapshot: createRuntimeSnapshot({
'alice-primary': createRuntimeEntry({
memberName: 'alice',
alive: false,
laneKind: 'primary',
updatedAt: '2026-05-03T10:00:00.000Z',
}),
'alice-secondary': createRuntimeEntry({
memberName: 'alice',
alive: true,
laneKind: 'secondary',
updatedAt: '2026-05-03T10:01:00.000Z',
}),
}),
});
expect(rows[0]).toMatchObject({
memberName: 'alice',
state: 'running',
laneKind: 'secondary',
actionsAllowed: false,
});
});
});