197 lines
5.9 KiB
TypeScript
197 lines
5.9 KiB
TypeScript
import type { WorkspaceTrustExecutionResult } from '@features/workspace-trust/main';
|
|
import type { MemberSpawnStatusEntry, TeamLaunchDiagnosticItem } from '@shared/types';
|
|
|
|
export interface TeamProvisioningLaunchDiagnosticsRun {
|
|
isLaunch: boolean;
|
|
memberSpawnStatuses?: ReadonlyMap<string, MemberSpawnStatusEntry> | null;
|
|
}
|
|
|
|
interface LaunchDiagnosticsClockOptions {
|
|
nowIso?: () => string;
|
|
}
|
|
|
|
const defaultNowIso = (): string => new Date().toISOString();
|
|
|
|
export function mentionsProcessTableUnavailable(value: string | undefined): boolean {
|
|
return /\bprocess table\b.*\bunavailable\b/i.test(value ?? '');
|
|
}
|
|
|
|
export function buildLaunchDiagnosticsFromRun(
|
|
run: TeamProvisioningLaunchDiagnosticsRun,
|
|
options: LaunchDiagnosticsClockOptions = {}
|
|
): TeamLaunchDiagnosticItem[] | undefined {
|
|
const memberSpawnStatuses = run.memberSpawnStatuses;
|
|
if (!run.isLaunch || !memberSpawnStatuses || memberSpawnStatuses.size === 0) {
|
|
return undefined;
|
|
}
|
|
|
|
const observedAt = (options.nowIso ?? defaultNowIso)();
|
|
const items: TeamLaunchDiagnosticItem[] = [];
|
|
for (const [memberName, entry] of memberSpawnStatuses.entries()) {
|
|
if (entry.launchState === 'confirmed_alive') {
|
|
items.push({
|
|
id: `${memberName}:bootstrap_confirmed`,
|
|
memberName,
|
|
severity: 'info',
|
|
code: 'bootstrap_confirmed',
|
|
label: `${memberName} - bootstrap confirmed`,
|
|
observedAt,
|
|
});
|
|
continue;
|
|
}
|
|
if (entry.launchState === 'failed_to_start') {
|
|
items.push({
|
|
id: `${memberName}:bootstrap_stalled`,
|
|
memberName,
|
|
severity: 'error',
|
|
code: 'bootstrap_stalled',
|
|
label: `${memberName} - failed to start`,
|
|
detail: entry.hardFailureReason ?? entry.error,
|
|
observedAt,
|
|
});
|
|
continue;
|
|
}
|
|
if (entry.launchState === 'runtime_pending_permission') {
|
|
items.push({
|
|
id: `${memberName}:permission_pending`,
|
|
memberName,
|
|
severity: 'warning',
|
|
code: 'permission_pending',
|
|
label: `${memberName} - awaiting permission`,
|
|
detail: entry.runtimeDiagnostic,
|
|
observedAt,
|
|
});
|
|
continue;
|
|
}
|
|
if (entry.bootstrapStalled === true) {
|
|
items.push({
|
|
id: `${memberName}:bootstrap_stalled`,
|
|
memberName,
|
|
severity: 'warning',
|
|
code: 'bootstrap_stalled',
|
|
label: `${memberName} - bootstrap stalled`,
|
|
detail: entry.runtimeDiagnostic,
|
|
observedAt,
|
|
});
|
|
continue;
|
|
}
|
|
if (mentionsProcessTableUnavailable(entry.runtimeDiagnostic)) {
|
|
items.push({
|
|
id: `${memberName}:process_table_unavailable`,
|
|
memberName,
|
|
severity: 'warning',
|
|
code: 'process_table_unavailable',
|
|
label: `${memberName} - process table unavailable`,
|
|
detail: entry.runtimeDiagnostic,
|
|
observedAt,
|
|
});
|
|
continue;
|
|
}
|
|
if (entry.livenessKind === 'shell_only') {
|
|
items.push({
|
|
id: `${memberName}:tmux_shell_only`,
|
|
memberName,
|
|
severity: 'warning',
|
|
code: 'tmux_shell_only',
|
|
label: `${memberName} - shell only`,
|
|
detail: entry.runtimeDiagnostic,
|
|
observedAt,
|
|
});
|
|
continue;
|
|
}
|
|
if (entry.livenessKind === 'runtime_process_candidate') {
|
|
items.push({
|
|
id: `${memberName}:runtime_process_candidate`,
|
|
memberName,
|
|
severity: 'warning',
|
|
code: 'runtime_process_candidate',
|
|
label: `${memberName} - bootstrap unconfirmed`,
|
|
detail: entry.runtimeDiagnostic,
|
|
observedAt,
|
|
});
|
|
continue;
|
|
}
|
|
if (entry.livenessKind === 'runtime_process') {
|
|
items.push({
|
|
id: `${memberName}:runtime_process_detected`,
|
|
memberName,
|
|
severity: 'info',
|
|
code: 'runtime_process_detected',
|
|
label: `${memberName} - waiting for bootstrap`,
|
|
detail: entry.runtimeDiagnostic,
|
|
observedAt,
|
|
});
|
|
continue;
|
|
}
|
|
if (
|
|
entry.livenessKind === 'registered_only' ||
|
|
entry.livenessKind === 'stale_metadata' ||
|
|
entry.livenessKind === 'not_found'
|
|
) {
|
|
items.push({
|
|
id: `${memberName}:runtime_not_found`,
|
|
memberName,
|
|
severity: 'warning',
|
|
code: 'runtime_not_found',
|
|
label: `${memberName} - waiting for runtime`,
|
|
detail: entry.runtimeDiagnostic,
|
|
observedAt,
|
|
});
|
|
continue;
|
|
}
|
|
if (entry.agentToolAccepted) {
|
|
items.push({
|
|
id: `${memberName}:spawn_accepted`,
|
|
memberName,
|
|
severity: 'info',
|
|
code: 'spawn_accepted',
|
|
label: `${memberName} - spawn accepted`,
|
|
detail: entry.runtimeDiagnostic,
|
|
observedAt,
|
|
});
|
|
}
|
|
}
|
|
return items.length > 0 ? items : undefined;
|
|
}
|
|
|
|
export function buildWorkspaceTrustPreflightLaunchDiagnostic(
|
|
execution: WorkspaceTrustExecutionResult,
|
|
options: LaunchDiagnosticsClockOptions = {}
|
|
): TeamLaunchDiagnosticItem | null {
|
|
if (execution.status === 'cancelled') {
|
|
return null;
|
|
}
|
|
|
|
const severity =
|
|
execution.status === 'blocked'
|
|
? 'error'
|
|
: execution.status === 'soft_failed'
|
|
? 'warning'
|
|
: 'info';
|
|
const label =
|
|
execution.status === 'blocked'
|
|
? 'Workspace trust preflight blocked launch'
|
|
: execution.status === 'soft_failed'
|
|
? 'Workspace trust preflight could not verify trust'
|
|
: 'Workspace trust preflight completed';
|
|
const detail =
|
|
execution.errorMessage?.trim() ||
|
|
execution.errorCode?.trim() ||
|
|
execution.evidence?.find((item) => item.trim().length > 0)?.trim();
|
|
|
|
return {
|
|
id: 'workspace-trust:preflight',
|
|
severity,
|
|
code: 'workspace_trust_preflight',
|
|
label,
|
|
...(detail ? { detail } : {}),
|
|
observedAt: (options.nowIso ?? defaultNowIso)(),
|
|
};
|
|
}
|
|
|
|
export function mergeLaunchDiagnosticItem(
|
|
items: readonly TeamLaunchDiagnosticItem[] | undefined,
|
|
item: TeamLaunchDiagnosticItem
|
|
): TeamLaunchDiagnosticItem[] {
|
|
return [...(items ?? []).filter((candidate) => candidate.id !== item.id), item];
|
|
}
|