202 lines
7.6 KiB
TypeScript
202 lines
7.6 KiB
TypeScript
import { formatTeamModelSummary } from '@renderer/components/team/dialogs/TeamModelSelector';
|
|
import { formatBytes } from '@renderer/utils/formatters';
|
|
import { formatTeamProviderBackendLabel } from '@renderer/utils/providerBackendIdentity';
|
|
import { extractProviderScopedBaseModel } from '@renderer/utils/teamModelContext';
|
|
import { isLeadMember } from '@shared/utils/leadDetection';
|
|
import { inferTeamProviderIdFromModel } from '@shared/utils/teamProvider';
|
|
|
|
import type { TeamLaunchParams } from '@renderer/store/slices/teamSlice';
|
|
import type {
|
|
MemberSpawnStatusEntry,
|
|
ResolvedTeamMember,
|
|
TeamAgentRuntimeEntry,
|
|
TeamProviderId,
|
|
} from '@shared/types';
|
|
|
|
function shouldShowRuntimeMemory(
|
|
spawnEntry: MemberSpawnStatusEntry | undefined,
|
|
runtimeEntry: TeamAgentRuntimeEntry | undefined
|
|
): boolean {
|
|
if (typeof runtimeEntry?.rssBytes !== 'number' || runtimeEntry.rssBytes <= 0) {
|
|
return false;
|
|
}
|
|
|
|
if (
|
|
spawnEntry?.status === 'offline' ||
|
|
spawnEntry?.status === 'skipped' ||
|
|
spawnEntry?.launchState === 'skipped_for_launch'
|
|
) {
|
|
return false;
|
|
}
|
|
|
|
if (!spawnEntry) {
|
|
return runtimeEntry.alive === true;
|
|
}
|
|
|
|
return (
|
|
runtimeEntry.alive === true ||
|
|
spawnEntry.runtimeAlive === true ||
|
|
spawnEntry.bootstrapConfirmed === true ||
|
|
spawnEntry.livenessSource === 'process' ||
|
|
spawnEntry.livenessSource === 'heartbeat'
|
|
);
|
|
}
|
|
|
|
function normalizeMemberBackendLabel(
|
|
providerId: TeamProviderId,
|
|
backendLabel: string | undefined
|
|
): string | undefined {
|
|
if (!backendLabel) {
|
|
return undefined;
|
|
}
|
|
|
|
if (providerId === 'codex' && backendLabel === 'Codex native') {
|
|
return 'Codex';
|
|
}
|
|
|
|
return backendLabel;
|
|
}
|
|
|
|
function isMemberLaunchPending(spawnEntry: MemberSpawnStatusEntry | undefined): boolean {
|
|
if (!spawnEntry) {
|
|
return false;
|
|
}
|
|
|
|
return (
|
|
spawnEntry.launchState === 'starting' ||
|
|
spawnEntry.launchState === 'runtime_pending_bootstrap' ||
|
|
spawnEntry.launchState === 'runtime_pending_permission' ||
|
|
spawnEntry.status === 'waiting' ||
|
|
spawnEntry.status === 'spawning'
|
|
);
|
|
}
|
|
|
|
function appendRuntimeSummarySuffixes(
|
|
summary: string,
|
|
backendLabel: string | undefined,
|
|
memorySuffix: string
|
|
): string {
|
|
const summaryParts = new Set(summary.split(' · '));
|
|
const backendSuffix = backendLabel && !summaryParts.has(backendLabel) ? ` · ${backendLabel}` : '';
|
|
return `${summary}${backendSuffix}${memorySuffix}`;
|
|
}
|
|
|
|
export function getRuntimeMemorySourceLabel(
|
|
runtimeEntry: TeamAgentRuntimeEntry | undefined
|
|
): string | undefined {
|
|
if (!runtimeEntry?.pidSource) {
|
|
return undefined;
|
|
}
|
|
if (runtimeEntry.pidSource === 'tmux_pane') {
|
|
return 'RSS source: tmux pane shell';
|
|
}
|
|
if (
|
|
runtimeEntry.providerId === 'opencode' &&
|
|
runtimeEntry.restartable === false &&
|
|
runtimeEntry.pidSource === 'opencode_bridge'
|
|
) {
|
|
return 'RSS source: shared OpenCode host';
|
|
}
|
|
if (runtimeEntry.pidSource === 'tmux_child' || runtimeEntry.pidSource === 'agent_process_table') {
|
|
return 'RSS source: runtime process';
|
|
}
|
|
if (runtimeEntry.pidSource === 'lead_process') {
|
|
return 'RSS source: lead process';
|
|
}
|
|
if (runtimeEntry.pidSource === 'runtime_bootstrap') {
|
|
return 'RSS source: runtime bootstrap process';
|
|
}
|
|
if (runtimeEntry.pidSource === 'persisted_metadata') {
|
|
return 'RSS source: persisted runtime metadata';
|
|
}
|
|
return `PID source: ${runtimeEntry.pidSource}`;
|
|
}
|
|
|
|
export function resolveMemberRuntimeSummary(
|
|
member: ResolvedTeamMember,
|
|
launchParams: TeamLaunchParams | undefined,
|
|
spawnEntry: MemberSpawnStatusEntry | undefined,
|
|
runtimeEntry?: TeamAgentRuntimeEntry
|
|
): string | undefined {
|
|
const leadLaunchParams = isLeadMember(member) ? launchParams : undefined;
|
|
const memberProviderBackendId = (member as ResolvedTeamMember & { providerBackendId?: string })
|
|
.providerBackendId;
|
|
const memberModel = member.model?.trim() || '';
|
|
const runtimeModel = spawnEntry?.runtimeModel?.trim() || runtimeEntry?.runtimeModel?.trim();
|
|
const runtimeModelProvider = inferTeamProviderIdFromModel(runtimeModel);
|
|
const inferredMemberProvider = inferTeamProviderIdFromModel(memberModel) ?? runtimeModelProvider;
|
|
const launchPending = isMemberLaunchPending(spawnEntry);
|
|
const stalePrimaryLaneConflictsWithLaunch =
|
|
!leadLaunchParams &&
|
|
launchPending &&
|
|
launchParams?.providerId != null &&
|
|
member.laneKind === 'primary' &&
|
|
member.laneOwnerProviderId != null &&
|
|
member.laneOwnerProviderId !== launchParams.providerId;
|
|
const authoritativeLaunchParams =
|
|
leadLaunchParams ?? (stalePrimaryLaneConflictsWithLaunch ? launchParams : undefined);
|
|
const configuredProvider: TeamProviderId =
|
|
authoritativeLaunchParams?.providerId ??
|
|
member.providerId ??
|
|
inferredMemberProvider ??
|
|
launchParams?.providerId ??
|
|
'anthropic';
|
|
const memberProviderForInheritance =
|
|
authoritativeLaunchParams?.providerId ?? member.providerId ?? inferredMemberProvider;
|
|
const inheritsLeadRuntimeDefaults =
|
|
memberProviderForInheritance == null ||
|
|
launchParams?.providerId == null ||
|
|
memberProviderForInheritance === launchParams.providerId;
|
|
const configuredModel = authoritativeLaunchParams
|
|
? authoritativeLaunchParams.model?.trim() || ''
|
|
: memberModel || (inheritsLeadRuntimeDefaults ? launchParams?.model?.trim() || '' : '');
|
|
const configuredEffort = authoritativeLaunchParams
|
|
? authoritativeLaunchParams.effort
|
|
: (member.effort ?? (inheritsLeadRuntimeDefaults ? launchParams?.effort : undefined));
|
|
const configuredProviderBackendId = authoritativeLaunchParams
|
|
? authoritativeLaunchParams.providerBackendId
|
|
: (memberProviderBackendId ??
|
|
(inheritsLeadRuntimeDefaults ? launchParams?.providerBackendId : undefined));
|
|
const runtimeProviderId = runtimeModelProvider ?? runtimeEntry?.providerId;
|
|
const runtimeModelConflictsWithAuthoritativeLaunch =
|
|
launchPending &&
|
|
authoritativeLaunchParams != null &&
|
|
runtimeModel != null &&
|
|
(authoritativeLaunchParams.model == null ||
|
|
extractProviderScopedBaseModel(runtimeModel, runtimeProviderId ?? configuredProvider) !==
|
|
extractProviderScopedBaseModel(
|
|
authoritativeLaunchParams.model,
|
|
authoritativeLaunchParams.providerId
|
|
));
|
|
const runtimeConflictsWithAuthoritativeLaunch =
|
|
authoritativeLaunchParams?.providerId != null &&
|
|
(stalePrimaryLaneConflictsWithLaunch ||
|
|
(runtimeProviderId != null && runtimeProviderId !== authoritativeLaunchParams.providerId) ||
|
|
runtimeModelConflictsWithAuthoritativeLaunch);
|
|
const displayRuntimeModel = runtimeConflictsWithAuthoritativeLaunch ? undefined : runtimeModel;
|
|
const backendLabel = normalizeMemberBackendLabel(
|
|
configuredProvider,
|
|
formatTeamProviderBackendLabel(configuredProvider, configuredProviderBackendId)
|
|
);
|
|
const memorySuffix =
|
|
!runtimeConflictsWithAuthoritativeLaunch && shouldShowRuntimeMemory(spawnEntry, runtimeEntry)
|
|
? ` · ${formatBytes(runtimeEntry!.rssBytes!)}`
|
|
: '';
|
|
|
|
if (displayRuntimeModel && (launchPending || configuredModel.length === 0)) {
|
|
const runtimeProvider = runtimeModelProvider ?? configuredProvider;
|
|
const summary = formatTeamModelSummary(runtimeProvider, displayRuntimeModel, configuredEffort);
|
|
return appendRuntimeSummarySuffixes(summary, backendLabel, memorySuffix);
|
|
}
|
|
|
|
if (launchPending) {
|
|
if (!authoritativeLaunchParams && !configuredModel.length && !memorySuffix) {
|
|
return undefined;
|
|
}
|
|
const summary = formatTeamModelSummary(configuredProvider, configuredModel, configuredEffort);
|
|
return appendRuntimeSummarySuffixes(summary, backendLabel, memorySuffix);
|
|
}
|
|
|
|
const summary = formatTeamModelSummary(configuredProvider, configuredModel, configuredEffort);
|
|
return appendRuntimeSummarySuffixes(summary, backendLabel, memorySuffix);
|
|
}
|