416 lines
15 KiB
TypeScript
416 lines
15 KiB
TypeScript
import { isLeadMember } from '@shared/utils/leadDetection';
|
|
import { migrateProviderBackendId } from '@shared/utils/providerBackend';
|
|
import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider';
|
|
|
|
import type {
|
|
MemberLaunchState,
|
|
MemberSpawnLivenessSource,
|
|
MemberSpawnStatusEntry,
|
|
PersistedTeamLaunchMemberSources,
|
|
PersistedTeamLaunchMemberState,
|
|
PersistedTeamLaunchPhase,
|
|
PersistedTeamLaunchSnapshot,
|
|
ProviderModelLaunchIdentity,
|
|
TeamAgentRuntimeDiagnosticSeverity,
|
|
TeamAgentRuntimeLivenessKind,
|
|
TeamAgentRuntimePidSource,
|
|
TeamFastMode,
|
|
TeamProviderBackendId,
|
|
TeamProviderId,
|
|
TeamProvisioningMemberInput,
|
|
} from '@shared/types';
|
|
|
|
export interface MixedLaneLeadRuntimeDefaults {
|
|
providerId: TeamProviderId;
|
|
providerBackendId?: TeamProviderBackendId | null;
|
|
selectedFastMode?: TeamFastMode;
|
|
resolvedFastMode?: boolean | null;
|
|
launchIdentity?: ProviderModelLaunchIdentity | null;
|
|
}
|
|
|
|
export interface MixedSecondaryLaneMemberStateInput {
|
|
laneId: string;
|
|
member: TeamProvisioningMemberInput;
|
|
leadDefaults: MixedLaneLeadRuntimeDefaults;
|
|
evidence?: {
|
|
launchState?: MemberLaunchState;
|
|
agentToolAccepted?: boolean;
|
|
runtimeAlive?: boolean;
|
|
bootstrapConfirmed?: boolean;
|
|
hardFailure?: boolean;
|
|
hardFailureReason?: string;
|
|
pendingPermissionRequestIds?: string[];
|
|
runtimePid?: number;
|
|
runtimeSessionId?: string;
|
|
sessionId?: string;
|
|
livenessKind?: TeamAgentRuntimeLivenessKind;
|
|
pidSource?: TeamAgentRuntimePidSource;
|
|
runtimeDiagnostic?: string;
|
|
runtimeDiagnosticSeverity?: TeamAgentRuntimeDiagnosticSeverity;
|
|
diagnostics?: string[];
|
|
} | null;
|
|
pendingReason?: string;
|
|
}
|
|
|
|
function deriveMemberLaunchState(params: {
|
|
hardFailure?: boolean;
|
|
bootstrapConfirmed?: boolean;
|
|
runtimeAlive?: boolean;
|
|
agentToolAccepted?: boolean;
|
|
pendingPermissionRequestIds?: string[];
|
|
}): MemberLaunchState {
|
|
if (params.hardFailure) {
|
|
return 'failed_to_start';
|
|
}
|
|
if (params.bootstrapConfirmed) {
|
|
return 'confirmed_alive';
|
|
}
|
|
if ((params.pendingPermissionRequestIds?.length ?? 0) > 0) {
|
|
return 'runtime_pending_permission';
|
|
}
|
|
if (params.runtimeAlive || params.agentToolAccepted) {
|
|
return 'runtime_pending_bootstrap';
|
|
}
|
|
return 'starting';
|
|
}
|
|
|
|
function preservesStrongRuntimeAlive(value: {
|
|
runtimeAlive?: boolean;
|
|
bootstrapConfirmed?: boolean;
|
|
livenessKind?: TeamAgentRuntimeLivenessKind;
|
|
}): boolean {
|
|
return (
|
|
value.runtimeAlive === true &&
|
|
(value.bootstrapConfirmed === true ||
|
|
value.livenessKind === 'confirmed_bootstrap' ||
|
|
value.livenessKind === 'runtime_process')
|
|
);
|
|
}
|
|
|
|
function buildDiagnostics(
|
|
member: Pick<
|
|
PersistedTeamLaunchMemberState,
|
|
| 'agentToolAccepted'
|
|
| 'runtimeAlive'
|
|
| 'bootstrapConfirmed'
|
|
| 'hardFailureReason'
|
|
| 'sources'
|
|
| 'pendingPermissionRequestIds'
|
|
>
|
|
): string[] {
|
|
const diagnostics: string[] = [];
|
|
if (member.agentToolAccepted) diagnostics.push('spawn accepted');
|
|
if (member.runtimeAlive) diagnostics.push('runtime alive');
|
|
if (member.bootstrapConfirmed) diagnostics.push('late heartbeat received');
|
|
if ((member.pendingPermissionRequestIds?.length ?? 0) > 0) {
|
|
diagnostics.push('waiting for permission approval');
|
|
} else if (member.runtimeAlive && !member.bootstrapConfirmed) {
|
|
diagnostics.push('waiting for teammate check-in');
|
|
}
|
|
if (member.hardFailureReason)
|
|
diagnostics.push(`hard failure reason: ${member.hardFailureReason}`);
|
|
if (member.sources?.duplicateRespawnBlocked) diagnostics.push('respawn blocked as duplicate');
|
|
if (member.sources?.configDrift) diagnostics.push('config drift detected');
|
|
return diagnostics;
|
|
}
|
|
|
|
function createSourcesFromStatus(
|
|
status: Pick<
|
|
MemberSpawnStatusEntry,
|
|
'livenessSource' | 'runtimeAlive' | 'bootstrapConfirmed' | 'livenessKind'
|
|
>
|
|
): PersistedTeamLaunchMemberSources | undefined {
|
|
const sources: PersistedTeamLaunchMemberSources = {};
|
|
if (status.livenessSource === 'heartbeat') {
|
|
sources.nativeHeartbeat = true;
|
|
sources.inboxHeartbeat = true;
|
|
}
|
|
if (status.livenessSource === 'process' && preservesStrongRuntimeAlive(status)) {
|
|
sources.processAlive = true;
|
|
}
|
|
return Object.values(sources).some(Boolean) ? sources : undefined;
|
|
}
|
|
|
|
function normalizeFastMode(value: TeamFastMode | undefined): TeamFastMode | undefined {
|
|
return value === 'inherit' || value === 'on' || value === 'off' ? value : undefined;
|
|
}
|
|
|
|
function createPrimaryLaneMemberState(params: {
|
|
member: TeamProvisioningMemberInput;
|
|
status?: MemberSpawnStatusEntry;
|
|
updatedAt: string;
|
|
leadDefaults: MixedLaneLeadRuntimeDefaults;
|
|
}): PersistedTeamLaunchMemberState {
|
|
const providerId =
|
|
normalizeOptionalTeamProviderId(params.member.providerId) ?? params.leadDefaults.providerId;
|
|
const runtime = params.status;
|
|
const strongRuntimeAlive = preservesStrongRuntimeAlive(runtime ?? {});
|
|
const sources = runtime ? createSourcesFromStatus(runtime) : undefined;
|
|
const base: PersistedTeamLaunchMemberState = {
|
|
name: params.member.name.trim(),
|
|
providerId,
|
|
providerBackendId:
|
|
migrateProviderBackendId(providerId, params.member.providerBackendId) ??
|
|
(providerId === params.leadDefaults.providerId
|
|
? (params.leadDefaults.providerBackendId ?? undefined)
|
|
: undefined),
|
|
model: params.member.model?.trim() || undefined,
|
|
effort: params.member.effort,
|
|
selectedFastMode:
|
|
normalizeFastMode(params.member.fastMode) ??
|
|
(providerId === params.leadDefaults.providerId
|
|
? normalizeFastMode(params.leadDefaults.selectedFastMode)
|
|
: undefined),
|
|
resolvedFastMode:
|
|
providerId === params.leadDefaults.providerId
|
|
? (params.leadDefaults.resolvedFastMode ?? undefined)
|
|
: undefined,
|
|
laneId: 'primary',
|
|
laneKind: 'primary',
|
|
laneOwnerProviderId: params.leadDefaults.providerId,
|
|
launchIdentity:
|
|
providerId === params.leadDefaults.providerId
|
|
? (params.leadDefaults.launchIdentity ?? undefined)
|
|
: undefined,
|
|
launchState:
|
|
runtime?.launchState ??
|
|
deriveMemberLaunchState({
|
|
hardFailure: runtime?.hardFailure,
|
|
bootstrapConfirmed: runtime?.bootstrapConfirmed,
|
|
runtimeAlive: strongRuntimeAlive,
|
|
agentToolAccepted: runtime?.agentToolAccepted,
|
|
pendingPermissionRequestIds: runtime?.pendingPermissionRequestIds,
|
|
}),
|
|
agentToolAccepted: runtime?.agentToolAccepted === true,
|
|
runtimeAlive: strongRuntimeAlive,
|
|
bootstrapConfirmed: runtime?.bootstrapConfirmed === true,
|
|
hardFailure: runtime?.hardFailure === true || runtime?.launchState === 'failed_to_start',
|
|
hardFailureReason: runtime?.hardFailureReason ?? runtime?.error,
|
|
pendingPermissionRequestIds: runtime?.pendingPermissionRequestIds?.length
|
|
? [...new Set(runtime.pendingPermissionRequestIds)]
|
|
: undefined,
|
|
livenessKind: runtime?.livenessKind,
|
|
runtimeDiagnostic: runtime?.runtimeDiagnostic,
|
|
runtimeDiagnosticSeverity: runtime?.runtimeDiagnosticSeverity,
|
|
firstSpawnAcceptedAt: runtime?.firstSpawnAcceptedAt,
|
|
lastHeartbeatAt: runtime?.lastHeartbeatAt,
|
|
runtimeLastSeenAt: runtime?.livenessLastCheckedAt,
|
|
lastRuntimeAliveAt: preservesStrongRuntimeAlive(runtime ?? {}) ? params.updatedAt : undefined,
|
|
lastEvaluatedAt: runtime?.updatedAt ?? params.updatedAt,
|
|
sources,
|
|
diagnostics: undefined,
|
|
};
|
|
base.diagnostics = buildDiagnostics(base);
|
|
return base;
|
|
}
|
|
|
|
function createSecondaryLaneMemberState(
|
|
params: MixedSecondaryLaneMemberStateInput & { updatedAt: string }
|
|
): PersistedTeamLaunchMemberState {
|
|
const providerId =
|
|
normalizeOptionalTeamProviderId(params.member.providerId) ?? params.leadDefaults.providerId;
|
|
const evidence = params.evidence;
|
|
const strongRuntimeAlive = preservesStrongRuntimeAlive(evidence ?? {});
|
|
const hardFailureReason = evidence?.hardFailureReason;
|
|
const launchState =
|
|
evidence?.launchState ??
|
|
deriveMemberLaunchState({
|
|
hardFailure: evidence?.hardFailure,
|
|
bootstrapConfirmed: evidence?.bootstrapConfirmed,
|
|
runtimeAlive: strongRuntimeAlive,
|
|
agentToolAccepted: evidence?.agentToolAccepted,
|
|
pendingPermissionRequestIds: evidence?.pendingPermissionRequestIds,
|
|
});
|
|
const base: PersistedTeamLaunchMemberState = {
|
|
name: params.member.name.trim(),
|
|
providerId,
|
|
providerBackendId:
|
|
migrateProviderBackendId(providerId, params.member.providerBackendId) ??
|
|
(providerId === params.leadDefaults.providerId
|
|
? (params.leadDefaults.providerBackendId ?? undefined)
|
|
: undefined),
|
|
model: params.member.model?.trim() || undefined,
|
|
effort: params.member.effort,
|
|
selectedFastMode:
|
|
normalizeFastMode(params.member.fastMode) ??
|
|
(providerId === params.leadDefaults.providerId
|
|
? normalizeFastMode(params.leadDefaults.selectedFastMode)
|
|
: undefined),
|
|
resolvedFastMode:
|
|
providerId === params.leadDefaults.providerId
|
|
? (params.leadDefaults.resolvedFastMode ?? undefined)
|
|
: undefined,
|
|
laneId: params.laneId,
|
|
laneKind: 'secondary',
|
|
laneOwnerProviderId: providerId,
|
|
launchState,
|
|
agentToolAccepted: evidence?.agentToolAccepted === true,
|
|
runtimeAlive: strongRuntimeAlive,
|
|
bootstrapConfirmed: evidence?.bootstrapConfirmed === true,
|
|
hardFailure: evidence?.hardFailure === true || launchState === 'failed_to_start',
|
|
hardFailureReason,
|
|
pendingPermissionRequestIds: evidence?.pendingPermissionRequestIds?.length
|
|
? [...new Set(evidence.pendingPermissionRequestIds)]
|
|
: undefined,
|
|
runtimePid:
|
|
typeof evidence?.runtimePid === 'number' &&
|
|
Number.isFinite(evidence.runtimePid) &&
|
|
evidence.runtimePid > 0
|
|
? Math.trunc(evidence.runtimePid)
|
|
: undefined,
|
|
runtimeSessionId: evidence?.runtimeSessionId ?? evidence?.sessionId,
|
|
livenessKind: evidence?.livenessKind,
|
|
pidSource: evidence?.pidSource,
|
|
runtimeDiagnostic: evidence?.runtimeDiagnostic,
|
|
runtimeDiagnosticSeverity: evidence?.runtimeDiagnosticSeverity,
|
|
firstSpawnAcceptedAt: evidence?.agentToolAccepted ? params.updatedAt : undefined,
|
|
lastHeartbeatAt: evidence?.bootstrapConfirmed ? params.updatedAt : undefined,
|
|
runtimeLastSeenAt: strongRuntimeAlive ? params.updatedAt : undefined,
|
|
lastRuntimeAliveAt: strongRuntimeAlive ? params.updatedAt : undefined,
|
|
lastEvaluatedAt: params.updatedAt,
|
|
sources: strongRuntimeAlive
|
|
? {
|
|
processAlive: true,
|
|
nativeHeartbeat: evidence?.bootstrapConfirmed === true || undefined,
|
|
inboxHeartbeat: evidence?.bootstrapConfirmed === true || undefined,
|
|
}
|
|
: undefined,
|
|
diagnostics: evidence?.diagnostics?.length
|
|
? [...evidence.diagnostics]
|
|
: !evidence && params.pendingReason
|
|
? [params.pendingReason]
|
|
: undefined,
|
|
};
|
|
base.diagnostics = base.diagnostics?.length ? base.diagnostics : buildDiagnostics(base);
|
|
return base;
|
|
}
|
|
|
|
function summarizeMembers(
|
|
expectedMembers: readonly string[],
|
|
members: Record<string, PersistedTeamLaunchMemberState>
|
|
): PersistedTeamLaunchSnapshot['summary'] {
|
|
let confirmedCount = 0;
|
|
let pendingCount = 0;
|
|
let failedCount = 0;
|
|
let runtimeAlivePendingCount = 0;
|
|
let shellOnlyPendingCount = 0;
|
|
let runtimeProcessPendingCount = 0;
|
|
let runtimeCandidatePendingCount = 0;
|
|
let noRuntimePendingCount = 0;
|
|
let permissionPendingCount = 0;
|
|
|
|
for (const memberName of expectedMembers) {
|
|
const entry = members[memberName];
|
|
if (!entry) {
|
|
pendingCount += 1;
|
|
continue;
|
|
}
|
|
if (entry.launchState === 'confirmed_alive') {
|
|
confirmedCount += 1;
|
|
continue;
|
|
}
|
|
if (entry.launchState === 'failed_to_start') {
|
|
failedCount += 1;
|
|
continue;
|
|
}
|
|
pendingCount += 1;
|
|
if (entry.runtimeAlive) {
|
|
runtimeAlivePendingCount += 1;
|
|
}
|
|
if (entry.launchState === 'runtime_pending_permission') {
|
|
permissionPendingCount += 1;
|
|
}
|
|
if (entry.livenessKind === 'shell_only') {
|
|
shellOnlyPendingCount += 1;
|
|
} else if (entry.livenessKind === 'runtime_process') {
|
|
runtimeProcessPendingCount += 1;
|
|
} else if (entry.livenessKind === 'runtime_process_candidate') {
|
|
runtimeCandidatePendingCount += 1;
|
|
} else if (
|
|
entry.livenessKind === 'not_found' ||
|
|
entry.livenessKind === 'stale_metadata' ||
|
|
entry.livenessKind === 'registered_only'
|
|
) {
|
|
noRuntimePendingCount += 1;
|
|
}
|
|
}
|
|
|
|
return {
|
|
confirmedCount,
|
|
pendingCount,
|
|
failedCount,
|
|
runtimeAlivePendingCount,
|
|
shellOnlyPendingCount,
|
|
runtimeProcessPendingCount,
|
|
runtimeCandidatePendingCount,
|
|
noRuntimePendingCount,
|
|
permissionPendingCount,
|
|
};
|
|
}
|
|
|
|
function deriveTeamLaunchState(
|
|
summary: PersistedTeamLaunchSnapshot['summary']
|
|
): PersistedTeamLaunchSnapshot['teamLaunchState'] {
|
|
if (summary.failedCount > 0) {
|
|
return 'partial_failure';
|
|
}
|
|
if (summary.pendingCount > 0) {
|
|
return 'partial_pending';
|
|
}
|
|
return 'clean_success';
|
|
}
|
|
|
|
export function buildMixedPersistedLaunchSnapshot(params: {
|
|
teamName: string;
|
|
leadSessionId?: string;
|
|
launchPhase: PersistedTeamLaunchPhase;
|
|
leadDefaults: MixedLaneLeadRuntimeDefaults;
|
|
primaryMembers: readonly TeamProvisioningMemberInput[];
|
|
primaryStatuses: Record<string, MemberSpawnStatusEntry>;
|
|
secondaryMembers?: readonly MixedSecondaryLaneMemberStateInput[];
|
|
updatedAt?: string;
|
|
}): PersistedTeamLaunchSnapshot {
|
|
const updatedAt = params.updatedAt ?? new Date().toISOString();
|
|
const primaryExpectedMembers = params.primaryMembers
|
|
.map((member) => member.name.trim())
|
|
.filter((name) => name.length > 0 && name !== 'user' && !isLeadMember({ name }));
|
|
const members: Record<string, PersistedTeamLaunchMemberState> = {};
|
|
|
|
for (const member of params.primaryMembers) {
|
|
const trimmedName = member.name.trim();
|
|
if (!trimmedName || trimmedName === 'user' || isLeadMember({ name: trimmedName })) continue;
|
|
members[trimmedName] = createPrimaryLaneMemberState({
|
|
member,
|
|
status: params.primaryStatuses[trimmedName],
|
|
updatedAt,
|
|
leadDefaults: params.leadDefaults,
|
|
});
|
|
}
|
|
|
|
for (const laneMember of params.secondaryMembers ?? []) {
|
|
const trimmedName = laneMember.member.name.trim();
|
|
if (!trimmedName || trimmedName === 'user' || isLeadMember({ name: trimmedName })) continue;
|
|
members[trimmedName] = createSecondaryLaneMemberState({
|
|
...laneMember,
|
|
updatedAt,
|
|
});
|
|
}
|
|
|
|
const expectedMembers = Array.from(new Set([...primaryExpectedMembers, ...Object.keys(members)]));
|
|
const summary = summarizeMembers(expectedMembers, members);
|
|
|
|
return {
|
|
version: 2,
|
|
teamName: params.teamName,
|
|
updatedAt,
|
|
...(params.leadSessionId ? { leadSessionId: params.leadSessionId } : {}),
|
|
launchPhase: params.launchPhase,
|
|
expectedMembers,
|
|
...(primaryExpectedMembers.join('\u0000') !== expectedMembers.join('\u0000')
|
|
? { bootstrapExpectedMembers: primaryExpectedMembers }
|
|
: {}),
|
|
members,
|
|
summary,
|
|
teamLaunchState: deriveTeamLaunchState(summary),
|
|
};
|
|
}
|