agent-ecosystem/test/renderer/utils/memberRuntimeSummary.test.ts

496 lines
14 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
getRuntimeMemorySourceLabel,
resolveMemberRuntimeSummary,
} from '@renderer/utils/memberRuntimeSummary';
import type { MemberSpawnStatusEntry, ResolvedTeamMember } from '@shared/types';
type TestResolvedTeamMember = ResolvedTeamMember & { providerBackendId?: string };
function createMember(overrides: Partial<TestResolvedTeamMember> = {}): TestResolvedTeamMember {
return {
name: 'alice',
agentId: 'alice@test-team',
agentType: 'general-purpose',
role: 'developer',
providerId: 'codex',
effort: 'medium',
status: 'idle',
currentTaskId: null,
taskCount: 0,
lastActiveAt: null,
messageCount: 0,
color: 'blue',
...overrides,
};
}
function createSpawnEntry(overrides: Partial<MemberSpawnStatusEntry> = {}): MemberSpawnStatusEntry {
return {
status: 'waiting',
launchState: 'starting',
runtimeAlive: false,
bootstrapConfirmed: false,
hardFailure: false,
agentToolAccepted: true,
updatedAt: '2026-04-16T17:10:48.646Z',
...overrides,
};
}
describe('resolveMemberRuntimeSummary', () => {
it('shows the live runtime model for loading members when available', () => {
const member = createMember();
const spawnEntry = createSpawnEntry({ runtimeModel: 'claude-opus-4-7', runtimeAlive: true });
expect(resolveMemberRuntimeSummary(member, undefined, spawnEntry)).toBe(
'Anthropic · Opus 4.7 · Medium · Codex'
);
});
it('keeps the configured summary visible while a pending member waits for the live runtime model', () => {
const member = createMember({ model: 'gpt-5.4-mini' });
const spawnEntry = createSpawnEntry();
expect(resolveMemberRuntimeSummary(member, undefined, spawnEntry)).toBe(
'5.4 Mini · Medium · Codex'
);
});
it('still keeps the loading skeleton when a pending member has neither live nor configured model truth', () => {
const member = createMember({ model: undefined });
const spawnEntry = createSpawnEntry();
expect(resolveMemberRuntimeSummary(member, undefined, spawnEntry)).toBeUndefined();
});
it('uses the live runtime model as a fallback when config has no explicit model', () => {
const member = createMember({ providerId: 'codex', model: undefined });
const spawnEntry = createSpawnEntry({
status: 'online',
launchState: 'confirmed_alive',
runtimeAlive: true,
runtimeModel: 'gpt-5.4-mini',
});
expect(resolveMemberRuntimeSummary(member, undefined, spawnEntry)).toBe(
'5.4 Mini · Medium · Codex'
);
});
it('appends runtime memory when a live process snapshot is available', () => {
const member = createMember({ model: 'gpt-5.4-mini' });
const runtimeEntry = {
memberName: 'alice',
alive: true,
restartable: true,
pid: 4242,
runtimeModel: 'gpt-5.4-mini',
rssBytes: 256 * 1024 * 1024,
updatedAt: '2026-04-18T18:00:00.000Z',
};
expect(resolveMemberRuntimeSummary(member, undefined, undefined, runtimeEntry)).toBe(
'5.4 Mini · Medium · Codex · 256.0 MB'
);
});
it('appends runtime memory while a configured member is still pending', () => {
const member = createMember({ model: 'gpt-5.4-mini' });
const spawnEntry = createSpawnEntry();
const runtimeEntry = {
memberName: 'alice',
alive: true,
restartable: true,
pid: 4242,
rssBytes: 256 * 1024 * 1024,
updatedAt: '2026-04-18T18:00:00.000Z',
};
expect(resolveMemberRuntimeSummary(member, undefined, spawnEntry, runtimeEntry as never)).toBe(
'5.4 Mini · Medium · Codex · 256.0 MB'
);
});
it('hides stale runtime memory when the spawn state is explicitly offline', () => {
const member = createMember({ model: 'gpt-5.4-mini' });
const spawnEntry = createSpawnEntry({
status: 'offline',
launchState: 'failed_to_start',
runtimeAlive: false,
bootstrapConfirmed: false,
});
const runtimeEntry = {
memberName: 'alice',
alive: true,
restartable: false,
providerId: 'opencode',
pid: 333,
pidSource: 'opencode_bridge',
rssBytes: 97.3 * 1024 * 1024,
updatedAt: '2026-04-24T12:00:00.000Z',
};
expect(resolveMemberRuntimeSummary(member, undefined, spawnEntry, runtimeEntry as never)).toBe(
'5.4 Mini · Medium · Codex'
);
});
it('keeps the persisted backend lane visible in the runtime summary', () => {
const member = createMember({ model: 'gpt-5.4-mini' });
expect(
resolveMemberRuntimeSummary(
member,
{
providerId: 'codex',
providerBackendId: 'codex-native',
model: 'gpt-5.4-mini',
effort: 'medium',
limitContext: false,
},
undefined
)
).toBe('5.4 Mini · Medium · Codex');
});
it('uses lead launch params instead of stale persisted lead runtime fields', () => {
const member = createMember({
name: 'team-lead',
agentType: 'team-lead',
providerId: 'codex',
providerBackendId: 'codex-native',
model: 'gpt-5.5',
effort: 'medium',
});
expect(
resolveMemberRuntimeSummary(
member,
{
providerId: 'anthropic',
providerBackendId: undefined,
model: 'haiku',
effort: undefined,
limitContext: false,
},
undefined
)
).toBe('Anthropic · Haiku 4.5');
});
it('uses lead launch params instead of stale pending lead runtime evidence', () => {
const member = createMember({
name: 'team-lead',
agentType: 'team-lead',
providerId: 'codex',
providerBackendId: 'codex-native',
model: 'gpt-5.5',
effort: 'medium',
});
expect(
resolveMemberRuntimeSummary(
member,
{
providerId: 'anthropic',
providerBackendId: undefined,
model: 'haiku',
effort: undefined,
limitContext: false,
},
createSpawnEntry({
runtimeModel: 'gpt-5.5',
runtimeAlive: true,
}),
{
memberName: 'team-lead',
alive: true,
restartable: false,
providerId: 'codex',
runtimeModel: 'gpt-5.5',
rssBytes: 300 * 1024 * 1024,
updatedAt: '2026-04-18T18:00:00.000Z',
}
)
).toBe('Anthropic · Haiku 4.5');
});
it('uses pending lead launch params instead of stale same-provider runtime model evidence', () => {
const member = createMember({
name: 'team-lead',
agentType: 'team-lead',
providerId: 'codex',
providerBackendId: 'codex-native',
model: 'gpt-5.5',
effort: 'medium',
});
expect(
resolveMemberRuntimeSummary(
member,
{
providerId: 'codex',
providerBackendId: 'codex-native',
model: 'gpt-5.4',
effort: 'high',
limitContext: false,
},
createSpawnEntry({
runtimeModel: 'gpt-5.5',
runtimeAlive: true,
}),
{
memberName: 'team-lead',
alive: true,
restartable: false,
providerId: 'codex',
runtimeModel: 'gpt-5.5',
rssBytes: 300 * 1024 * 1024,
updatedAt: '2026-04-18T18:00:00.000Z',
}
)
).toBe('5.4 · High · Codex');
});
it('uses pending lead default launch params instead of stale same-provider runtime model evidence', () => {
const member = createMember({
name: 'team-lead',
agentType: 'team-lead',
providerId: 'codex',
providerBackendId: 'codex-native',
model: 'gpt-5.5',
effort: 'medium',
});
expect(
resolveMemberRuntimeSummary(
member,
{
providerId: 'codex',
providerBackendId: 'codex-native',
model: undefined,
effort: 'low',
limitContext: false,
},
createSpawnEntry({
runtimeModel: 'gpt-5.5',
runtimeAlive: true,
}),
{
memberName: 'team-lead',
alive: true,
restartable: false,
providerId: 'codex',
runtimeModel: 'gpt-5.5',
rssBytes: 300 * 1024 * 1024,
updatedAt: '2026-04-18T18:00:00.000Z',
}
)
).toBe('Codex · Default · Low');
});
it('uses staged default launch params without duplicating the Codex backend label', () => {
const member = createMember({
name: 'team-lead',
agentType: 'team-lead',
providerId: 'codex',
providerBackendId: 'codex-native',
model: 'gpt-5.5',
effort: 'medium',
});
expect(
resolveMemberRuntimeSummary(
member,
{
providerId: 'codex',
providerBackendId: 'codex-native',
model: 'default',
effort: 'low',
limitContext: false,
},
createSpawnEntry()
)
).toBe('Codex · Default · Low');
});
it('uses pending launch params for stale primary teammate cards during provider switch', () => {
const member = createMember({
providerId: 'codex',
providerBackendId: 'codex-native',
model: 'gpt-5.5',
effort: 'medium',
laneKind: 'primary',
laneOwnerProviderId: 'codex',
});
expect(
resolveMemberRuntimeSummary(
member,
{
providerId: 'anthropic',
providerBackendId: undefined,
model: 'haiku',
effort: 'low',
limitContext: false,
},
createSpawnEntry({
runtimeModel: 'gpt-5.5',
runtimeAlive: true,
}),
{
memberName: 'alice',
alive: true,
restartable: false,
providerId: 'codex',
runtimeModel: 'gpt-5.5',
rssBytes: 221 * 1024 * 1024,
updatedAt: '2026-04-18T18:00:00.000Z',
}
)
).toBe('Anthropic · Haiku 4.5 · Low');
});
it('normalizes persisted legacy Codex lanes to the native runtime summary', () => {
const member = createMember({ model: 'gpt-5.4-mini' });
expect(
resolveMemberRuntimeSummary(
member,
{
providerId: 'codex',
providerBackendId: 'api',
model: 'gpt-5.4-mini',
effort: 'medium',
limitContext: false,
},
undefined
)
).toBe('5.4 Mini · Medium · Codex');
});
it('does not leak the lead backend label into OpenCode side-lane members', () => {
const member = createMember({
providerId: 'opencode',
providerBackendId: undefined,
model: 'opencode/nemotron-3-super-free',
effort: undefined,
});
expect(
resolveMemberRuntimeSummary(
member,
{
providerId: 'codex',
providerBackendId: 'codex-native',
model: 'gpt-5.4',
effort: 'medium',
limitContext: false,
},
undefined
)
).toBe('nemotron-3-super-free · via OpenCode');
});
it('infers OpenCode from an OpenCode model when member provider metadata is missing', () => {
const member = createMember({
providerId: undefined,
providerBackendId: undefined,
model: 'opencode/minimax-m2.5-free',
effort: undefined,
});
expect(
resolveMemberRuntimeSummary(
member,
{
providerId: 'codex',
providerBackendId: 'codex-native',
model: 'gpt-5.4',
effort: 'medium',
limitContext: false,
},
undefined
)
).toBe('minimax-m2.5-free · via OpenCode');
});
it('appends memory for OpenCode side-lane runtime snapshots without adding Codex backend text', () => {
const member = createMember({
providerId: 'opencode',
providerBackendId: undefined,
model: 'opencode/minimax-m2.5-free',
effort: undefined,
});
expect(
resolveMemberRuntimeSummary(
member,
{
providerId: 'codex',
providerBackendId: 'codex-native',
model: 'gpt-5.4',
effort: 'medium',
limitContext: false,
},
undefined,
{
memberName: 'alice',
alive: true,
restartable: false,
runtimeModel: 'opencode/minimax-m2.5-free',
rssBytes: 183.9 * 1024 * 1024,
updatedAt: '2026-04-18T18:00:00.000Z',
}
)
).toBe('minimax-m2.5-free · via OpenCode · 183.9 MB');
});
});
describe('getRuntimeMemorySourceLabel', () => {
it('explains when RSS comes from a tmux pane shell', () => {
expect(
getRuntimeMemorySourceLabel({
memberName: 'alice',
alive: false,
restartable: true,
pid: 26676,
pidSource: 'tmux_pane',
rssBytes: 2 * 1024 * 1024,
updatedAt: '2026-04-24T12:00:00.000Z',
})
).toBe('RSS source: tmux pane shell');
});
it('explains shared OpenCode host memory separately from member-owned runtime memory', () => {
expect(
getRuntimeMemorySourceLabel({
memberName: 'alice',
alive: true,
restartable: false,
providerId: 'opencode',
pid: 333,
pidSource: 'opencode_bridge',
rssBytes: 183.9 * 1024 * 1024,
updatedAt: '2026-04-24T12:00:00.000Z',
})
).toBe('RSS source: shared OpenCode host');
});
it('labels verified runtime child memory as runtime process memory', () => {
expect(
getRuntimeMemorySourceLabel({
memberName: 'alice',
alive: true,
restartable: true,
pid: 4242,
pidSource: 'tmux_child',
rssBytes: 256 * 1024 * 1024,
updatedAt: '2026-04-24T12:00:00.000Z',
})
).toBe('RSS source: runtime process');
});
});