import type { PersistedTeamLaunchPhase } from '@shared/types'; export type ProcessBootstrapTransportEvent = Record; export type ProcessBootstrapTransportTerminalKind = | 'non_retryable_submit_rejection' | 'accepted_without_message_id' | 'process_exited_before_confirmation' | 'runtime_failed_before_confirmation'; export interface ProcessBootstrapTransportSummary { lastStage?: string; lastObservedAt?: string; submitted: boolean; hasProgress: boolean; terminalFailure?: { kind: ProcessBootstrapTransportTerminalKind; reason: string; observedAt?: string; }; } export type ProcessBootstrapTransportProjectionPhase = 'active' | 'final'; // These helpers intentionally summarize process transport only. They explain // where bootstrap got stuck, but never prove teammate readiness by themselves. const MAX_TRANSPORT_DETAIL_CHARS = 500; const WINDOWS_RESERVED_BASENAMES = new Set([ 'con', 'prn', 'aux', 'nul', 'com1', 'com2', 'com3', 'com4', 'com5', 'com6', 'com7', 'com8', 'com9', 'lpt1', 'lpt2', 'lpt3', 'lpt4', 'lpt5', 'lpt6', 'lpt7', 'lpt8', 'lpt9', ]); const TRANSPORT_STAGE_LABELS: Record = { process_spawned: 'process spawned', stdout_attached: 'stdout attached', cli_started: 'CLI started', runtime_ready: 'runtime ready', inbox_poller_ready: 'inbox poller ready', mailbox_bootstrap_written: 'bootstrap mailbox row written', bootstrap_prompt_observed: 'bootstrap prompt observed', bootstrap_submit_attempted: 'bootstrap submit attempted', bootstrap_submit_deferred: 'bootstrap submit deferred', bootstrap_submit_rejected: 'bootstrap submit rejected', bootstrap_submit_accepted_without_uuid: 'bootstrap submit accepted without message id', bootstrap_submitted: 'bootstrap submitted', failed: 'runtime failed', exited: 'runtime exited', }; export function sanitizeProcessRuntimeEventFilePrefix(value: string): string { const normalized = String(value) .replace(/[^a-zA-Z0-9]/g, '-') .toLowerCase(); const normalizedStem = normalized .trim() .replace(/[. ]+$/g, '') .split('.')[0] ?? normalized; return normalizedStem && WINDOWS_RESERVED_BASENAMES.has(normalizedStem) ? `_${normalized}` : normalized; } export function deriveProcessTransportProjectionPhase(input: { launchPhase: PersistedTeamLaunchPhase; finalTimeoutReached?: boolean; }): ProcessBootstrapTransportProjectionPhase { if (input.launchPhase !== 'active') { return 'final'; } return input.finalTimeoutReached === true ? 'final' : 'active'; } export function sanitizeProcessBootstrapTransportDetail(value: unknown): string | undefined { if (typeof value !== 'string') { return undefined; } const sanitized = value .replace(/\b(sk-[A-Za-z0-9_-]{12,}|[A-Za-z0-9_-]{32,})\b/g, '[redacted]') .replace(/\/[^\s"'`]+/g, '[path]') .replace(/\s+/g, ' ') .trim() .slice(0, MAX_TRANSPORT_DETAIL_CHARS); return sanitized.length > 0 ? sanitized : undefined; } function eventType(event: ProcessBootstrapTransportEvent): string { return typeof event.type === 'string' ? event.type : ''; } function eventTimestamp(event: ProcessBootstrapTransportEvent): string | undefined { return typeof event.timestamp === 'string' && Number.isFinite(Date.parse(event.timestamp)) ? event.timestamp : undefined; } function stageLabel(event: ProcessBootstrapTransportEvent): string | undefined { const type = eventType(event); const label = TRANSPORT_STAGE_LABELS[type]; if (!label) { return undefined; } const detail = sanitizeProcessBootstrapTransportDetail(event.detail); if (type === 'process_spawned' || type === 'stdout_attached' || type === 'cli_started') { return label; } return detail ? `${label}: ${detail}` : label; } function terminalFailureForEvent( event: ProcessBootstrapTransportEvent ): ProcessBootstrapTransportSummary['terminalFailure'] | undefined { const type = eventType(event); const label = stageLabel(event); const observedAt = eventTimestamp(event); if (type === 'failed') { return { kind: 'runtime_failed_before_confirmation', reason: label ?? 'runtime failed before bootstrap confirmation', observedAt, }; } if (type === 'exited') { return { kind: 'process_exited_before_confirmation', reason: label ?? 'runtime exited before bootstrap confirmation', observedAt, }; } if (type === 'bootstrap_submit_accepted_without_uuid') { return { kind: 'accepted_without_message_id', reason: label ?? 'bootstrap submit accepted without message id', observedAt, }; } if (type === 'bootstrap_submit_rejected' && event.retryable === false) { return { kind: 'non_retryable_submit_rejection', reason: label ?? 'bootstrap submit rejected', observedAt, }; } return undefined; } export function summarizeProcessBootstrapTransportEvents( events: readonly ProcessBootstrapTransportEvent[] ): ProcessBootstrapTransportSummary | null { if (events.length === 0) { return null; } let lastStage: string | undefined; let lastObservedAt: string | undefined; let submitted = false; let terminalFailure: ProcessBootstrapTransportSummary['terminalFailure']; for (const event of events) { const label = stageLabel(event); if (!label) { continue; } lastStage = label; lastObservedAt = eventTimestamp(event) ?? lastObservedAt; if (eventType(event) === 'bootstrap_submitted') { submitted = true; } terminalFailure = terminalFailureForEvent(event) ?? terminalFailure; } if (!lastStage && !terminalFailure) { return null; } return { ...(lastStage ? { lastStage } : {}), ...(lastObservedAt ? { lastObservedAt } : {}), submitted, hasProgress: Boolean(lastStage), ...(terminalFailure ? { terminalFailure } : {}), }; } export function buildProcessBootstrapPendingDiagnostic( summary: ProcessBootstrapTransportSummary ): string { return summary.lastStage ? `Bootstrap transport reached ${summary.lastStage}; waiting for bootstrap confirmation.` : 'Bootstrap transport is waiting for bootstrap confirmation.'; } export function buildProcessBootstrapTimeoutDiagnostic( summary: ProcessBootstrapTransportSummary ): string { return summary.lastStage ? `Teammate was registered but did not bootstrap-confirm before timeout. Last transport stage: ${summary.lastStage}` : 'Teammate was registered but did not bootstrap-confirm before timeout.'; }