refactor(team): extract provisioning service policies Merge pull request #149 from 777genius/refactor/team-provisioning-policies

refactor(team): extract provisioning service policies

Merge pull request #149 from 777genius/refactor/team-provisioning-policies
This commit is contained in:
Илия 2026-05-24 16:10:05 +03:00 committed by GitHub
commit f2a5429a4b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 4136 additions and 2050 deletions

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,257 @@
type CliLogStream = 'stdout' | 'stderr' | 'unknown';
interface CliLogLine {
stream: CliLogStream;
text: string;
}
export interface CliExitFailurePresentation {
message?: string;
error: string;
}
export interface CliExitPresentationRun {
stdoutBuffer: string;
stderrBuffer: string;
claudeLogLines?: string[];
deterministicBootstrap: boolean;
lastDeterministicBootstrapEvent?: string;
lastDeterministicBootstrapPhase?: string;
deterministicBootstrapMemberSpawnSeen: boolean;
expectedMembers: string[];
memberSpawnStatuses: ReadonlyMap<string, { bootstrapConfirmed?: boolean } | undefined>;
}
const USER_FACING_CLI_NOISE_TEXT_PATTERN =
/additionalContext|skill_flow|EXTREMELY_IMPORTANT|superpowers:using-superpowers|TodoWrite|Skill tool|Invoke Skill tool|Might any skill apply|relevant or requested skills BEFORE|hook_response|hook_started|hook_progress/i;
const USER_FACING_STDOUT_ERROR_PATTERN =
/\b(error|failed|failure|fatal|exception|traceback|uncaught|unauthorized|forbidden|quota|rate limit|not authenticated|invalid api key|token refresh failed|warning)\b|please run \/login/i;
export function buildCombinedLogs(
stdoutBuffer: string | undefined,
stderrBuffer: string | undefined
): string {
const stdoutTrimmed = (stdoutBuffer ?? '').trim();
const stderrTrimmed = (stderrBuffer ?? '').trim();
if (stdoutTrimmed.length === 0 && stderrTrimmed.length === 0) {
return '';
}
if (stdoutTrimmed.length > 0 && stderrTrimmed.length === 0) {
return stdoutTrimmed;
}
if (stdoutTrimmed.length === 0 && stderrTrimmed.length > 0) {
return stderrTrimmed;
}
return [`[stdout]`, stdoutTrimmed, '', `[stderr]`, stderrTrimmed].join('\n');
}
export function parseCliLogLinesFromText(text: string): CliLogLine[] {
const lines: CliLogLine[] = [];
let currentStream: CliLogStream = 'unknown';
for (const rawLine of text.split(/\r?\n/)) {
const trimmed = rawLine.trim();
if (!trimmed) {
continue;
}
if (trimmed === '[stdout]') {
currentStream = 'stdout';
continue;
}
if (trimmed === '[stderr]') {
currentStream = 'stderr';
continue;
}
lines.push({ stream: currentStream, text: trimmed });
}
return lines;
}
function getCliLogLinesForUserFacingError(run: CliExitPresentationRun): CliLogLine[] {
const lineHistory = Array.isArray(run.claudeLogLines) ? run.claudeLogLines : [];
const lines = lineHistory.length > 0 ? parseCliLogLinesFromText(lineHistory.join('\n')) : [];
const combinedBufferLines = parseCliLogLinesFromText(
buildCombinedLogs(run.stdoutBuffer, run.stderrBuffer)
);
if (lines.length === 0) {
return combinedBufferLines;
}
// claudeLogLines stores complete newline-delimited lines. Add raw ring-buffer
// lines as a fallback only when they contain user-facing material that may be
// sitting in a final partial stderr/stdout line at process close.
const seen = new Set(lines.map((line) => `${line.stream}:${line.text}`));
for (const line of combinedBufferLines) {
const key = `${line.stream}:${line.text}`;
if (!seen.has(key) && isPotentiallyUserFacingCliLine(line)) {
lines.push(line);
seen.add(key);
}
}
return lines;
}
function isNoiseCliLine(text: string): boolean {
return USER_FACING_CLI_NOISE_TEXT_PATTERN.test(text);
}
function isPotentiallyUserFacingCliLine(line: CliLogLine): boolean {
if (isNoiseCliLine(line.text)) {
return false;
}
if (line.stream === 'stderr') {
return true;
}
return USER_FACING_STDOUT_ERROR_PATTERN.test(line.text);
}
function extractStringField(value: unknown, key: string): string | undefined {
if (!value || typeof value !== 'object') {
return undefined;
}
const raw = (value as Record<string, unknown>)[key];
return typeof raw === 'string' && raw.trim().length > 0 ? raw.trim() : undefined;
}
function extractStructuredCliError(parsed: Record<string, unknown>): string | undefined {
const type = typeof parsed.type === 'string' ? parsed.type : undefined;
const subtype = typeof parsed.subtype === 'string' ? parsed.subtype : undefined;
if (type === 'system') {
if (subtype === 'team_bootstrap' && parsed.event === 'failed') {
return extractStringField(parsed, 'reason');
}
if (subtype === 'init' || subtype?.startsWith('hook_')) {
return undefined;
}
return undefined;
}
if (type === 'result') {
const result = parsed.result;
const resultSubtype = subtype ?? extractStringField(result, 'subtype');
if (resultSubtype === 'success' || parsed.outcome === 'success') {
return undefined;
}
if (resultSubtype === 'error' || resultSubtype?.startsWith('error_')) {
return (
extractStringField(parsed, 'error') ??
extractStringField(result, 'error') ??
extractStringField(parsed, 'result')
);
}
return undefined;
}
if (type === 'error') {
return extractStringField(parsed, 'error') ?? extractStringField(parsed, 'message');
}
return undefined;
}
export function buildSanitizedCliExitError(run: CliExitPresentationRun): string | undefined {
const errorLines: string[] = [];
for (const line of getCliLogLinesForUserFacingError(run)) {
if (!line.text || isNoiseCliLine(line.text)) {
continue;
}
try {
const parsed = JSON.parse(line.text) as Record<string, unknown>;
const structuredError = extractStructuredCliError(parsed);
if (structuredError && !isNoiseCliLine(structuredError)) {
errorLines.push(structuredError);
}
continue;
} catch {
// Non-JSON stderr/plain CLI errors are handled below.
}
if (isPotentiallyUserFacingCliLine(line)) {
errorLines.push(line.text);
}
}
const deduped = [...new Set(errorLines.map((line) => line.trim()).filter(Boolean))];
if (deduped.length === 0) {
return undefined;
}
return deduped.join('\n').slice(-4000);
}
export function formatPendingBootstrapMemberNames(run: CliExitPresentationRun): string {
const pending = run.expectedMembers.filter((name) => {
const status = run.memberSpawnStatuses.get(name);
return status?.bootstrapConfirmed !== true;
});
const names = pending.length > 0 ? pending : run.expectedMembers;
if (names.length === 0) {
return 'unknown';
}
const visible = names.slice(0, 6);
const suffix = names.length > visible.length ? ` and ${names.length - visible.length} more` : '';
return `${visible.join(', ')}${suffix}`;
}
export function buildDeterministicBootstrapExitFailure(
run: CliExitPresentationRun
): CliExitFailurePresentation {
if (!run.lastDeterministicBootstrapEvent) {
return {
message: 'Launch bootstrap was not confirmed',
error:
'Codex runtime exited before deterministic team bootstrap started. No team_bootstrap event was received.',
};
}
if (!run.deterministicBootstrapMemberSpawnSeen) {
const lastStage = run.lastDeterministicBootstrapPhase
? `${run.lastDeterministicBootstrapEvent}/${run.lastDeterministicBootstrapPhase}`
: run.lastDeterministicBootstrapEvent;
return {
message: 'Launch bootstrap was not confirmed',
error: `Codex runtime exited during deterministic team bootstrap before teammate spawning started. Last bootstrap event: ${lastStage}.`,
};
}
return {
message: 'Launch bootstrap was not confirmed',
error: `Bootstrap was not confirmed before the Codex runtime exited. Pending teammates: ${formatPendingBootstrapMemberNames(run)}.`,
};
}
export function buildCliExitFailurePresentation(
run: CliExitPresentationRun,
code: number | null,
options: { cliCommandLabel: string }
): CliExitFailurePresentation {
const trimmed = buildCombinedLogs(run.stdoutBuffer, run.stderrBuffer).trim();
if (trimmed.length > 0) {
if (trimmed.toLowerCase().includes('please run /login')) {
return {
error:
`${options.cliCommandLabel} reports it is not authenticated ("Please run /login"). ` +
'Run the CLI in a normal terminal and complete login, then retry. ' +
'For automation/headless use, set `ANTHROPIC_API_KEY` for `-p` mode.',
};
}
const sanitized = buildSanitizedCliExitError(run);
if (sanitized) {
return { error: sanitized };
}
}
if (run.deterministicBootstrap) {
return buildDeterministicBootstrapExitFailure(run);
}
if (code === 1) {
return {
error: `${options.cliCommandLabel} exited with code 1 without user-facing stdout/stderr. Typical causes: missing auth/onboarding, interactive TTY requirements, or an early bootstrap/runtime crash. Check \`~/.claude/debug/latest\` for the real stack and retry.`,
};
}
return { error: `${options.cliCommandLabel} exited with code ${code ?? 'unknown'}` };
}

View file

@ -0,0 +1,188 @@
import * as path from 'path';
import {
ANTHROPIC_HELPER_MODE_COMPETING_AUTH_ENV_KEYS,
CLAUDE_TEAM_ANTHROPIC_API_KEY_HELPER_SETTINGS_PATH_ENV,
CLAUDE_TEAM_ANTHROPIC_AUTH_MODE_API_KEY_HELPER,
CLAUDE_TEAM_ANTHROPIC_AUTH_MODE_ENV,
} from '../../runtime/anthropicTeamApiKeyHelper';
import type { TeamProviderId } from '@shared/types';
const DIRECT_TMUX_RESTART_ENV_KEYS = [
'CLAUDE_CONFIG_DIR',
'CLAUDE_TEAM_CONTROL_URL',
'CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST',
'CLAUDE_CODE_USE_OPENAI',
'CLAUDE_CODE_USE_BEDROCK',
'CLAUDE_CODE_USE_VERTEX',
'CLAUDE_CODE_USE_FOUNDRY',
'CLAUDE_CODE_USE_GEMINI',
'CLAUDE_CODE_ENTRY_PROVIDER',
'CLAUDE_CODE_GEMINI_BACKEND',
'CLAUDE_CODE_CODEX_BACKEND',
'CODEX_HOME',
CLAUDE_TEAM_ANTHROPIC_AUTH_MODE_ENV,
CLAUDE_TEAM_ANTHROPIC_API_KEY_HELPER_SETTINGS_PATH_ENV,
'ANTHROPIC_BASE_URL',
'ANTHROPIC_AWS_WORKSPACE_ID',
'ANTHROPIC_AWS_API_KEY',
'ANTHROPIC_API_KEY',
'ANTHROPIC_AUTH_TOKEN',
'GEMINI_BASE_URL',
'GEMINI_API_VERSION',
'GEMINI_API_KEY',
'CODEX_API_KEY',
'OPENAI_API_KEY',
'GOOGLE_APPLICATION_CREDENTIALS',
'GOOGLE_CLOUD_PROJECT',
'GOOGLE_CLOUD_PROJECT_ID',
'GCLOUD_PROJECT',
'HTTPS_PROXY',
'https_proxy',
'HTTP_PROXY',
'http_proxy',
'NO_PROXY',
'no_proxy',
'SSL_CERT_FILE',
'NODE_EXTRA_CA_CERTS',
'REQUESTS_CA_BUNDLE',
'CURL_CA_BUNDLE',
] as const;
const DIRECT_TMUX_PROVIDER_SELECTION_ENV_KEYS = [
'CLAUDE_CODE_USE_OPENAI',
'CLAUDE_CODE_USE_BEDROCK',
'CLAUDE_CODE_USE_VERTEX',
'CLAUDE_CODE_USE_FOUNDRY',
'CLAUDE_CODE_USE_GEMINI',
'CLAUDE_CODE_ENTRY_PROVIDER',
] as const;
const INTERACTIVE_SHELL_COMMANDS = new Set([
'bash',
'zsh',
'sh',
'fish',
'nu',
'pwsh',
'powershell',
'cmd',
'cmd.exe',
]);
export function shellQuote(value: string): string {
if (value.length === 0) {
return "''";
}
return `'${value.replace(/'/g, `'\\''`)}'`;
}
export function isInteractiveShellCommand(command: string | undefined): boolean {
const normalized = command?.trim().toLowerCase();
if (!normalized) {
return false;
}
return INTERACTIVE_SHELL_COMMANDS.has(path.basename(normalized));
}
function getDirectRestartEntryProvider(providerId: TeamProviderId): string {
return providerId === 'codex' || providerId === 'gemini' ? providerId : 'anthropic';
}
export function isAnthropicCompatibleBaseUrl(baseUrl?: string | null): boolean {
const trimmed = baseUrl?.trim();
if (!trimmed) {
return false;
}
try {
const url = new URL(trimmed);
return (
(url.protocol === 'http:' || url.protocol === 'https:') &&
!url.username &&
!url.password &&
url.hostname !== 'api.anthropic.com' &&
url.hostname !== 'api-staging.anthropic.com'
);
} catch {
return false;
}
}
export function hasAnthropicCompatibleAuthTokenEnv(env: NodeJS.ProcessEnv): boolean {
return Boolean(
isAnthropicCompatibleBaseUrl(env.ANTHROPIC_BASE_URL) && env.ANTHROPIC_AUTH_TOKEN?.trim()
);
}
export function buildDirectTmuxRestartEnvAssignments(
env: NodeJS.ProcessEnv,
providerId: TeamProviderId
): string {
const assignments = new Map<string, string>();
assignments.set('CLAUDECODE', '1');
assignments.set('CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS', '1');
for (const key of DIRECT_TMUX_RESTART_ENV_KEYS) {
const value = env[key];
if (typeof value === 'string' && value.length > 0) {
assignments.set(key, value);
}
}
for (const key of DIRECT_TMUX_PROVIDER_SELECTION_ENV_KEYS) {
assignments.set(key, '');
}
assignments.set('CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST', '1');
assignments.set('CLAUDE_CODE_ENTRY_PROVIDER', getDirectRestartEntryProvider(providerId));
if (providerId === 'anthropic') {
if (hasAnthropicCompatibleAuthTokenEnv(env)) {
assignments.set('ANTHROPIC_BASE_URL', env.ANTHROPIC_BASE_URL?.trim() ?? '');
assignments.set('ANTHROPIC_AUTH_TOKEN', env.ANTHROPIC_AUTH_TOKEN?.trim() ?? '');
if (!env.ANTHROPIC_API_KEY?.trim()) {
assignments.set('ANTHROPIC_API_KEY', '');
}
} else if (!isAnthropicCompatibleBaseUrl(env.ANTHROPIC_BASE_URL)) {
assignments.set('ANTHROPIC_AUTH_TOKEN', '');
}
}
if (
providerId === 'anthropic' &&
env[CLAUDE_TEAM_ANTHROPIC_AUTH_MODE_ENV] === CLAUDE_TEAM_ANTHROPIC_AUTH_MODE_API_KEY_HELPER
) {
assignments.set(
CLAUDE_TEAM_ANTHROPIC_AUTH_MODE_ENV,
CLAUDE_TEAM_ANTHROPIC_AUTH_MODE_API_KEY_HELPER
);
const settingsPath = env[CLAUDE_TEAM_ANTHROPIC_API_KEY_HELPER_SETTINGS_PATH_ENV];
if (typeof settingsPath === 'string') {
assignments.set(CLAUDE_TEAM_ANTHROPIC_API_KEY_HELPER_SETTINGS_PATH_ENV, settingsPath);
}
for (const key of ANTHROPIC_HELPER_MODE_COMPETING_AUTH_ENV_KEYS) {
assignments.set(key, '');
}
}
return [...assignments.entries()].map(([key, value]) => `${key}=${shellQuote(value)}`).join(' ');
}
export function buildDirectTmuxRestartCommand(input: {
cwd: string;
env: NodeJS.ProcessEnv;
providerId: TeamProviderId;
binaryPath: string;
args: string[];
}): string {
const envAssignments = buildDirectTmuxRestartEnvAssignments(input.env, input.providerId);
const command = [
'cd',
shellQuote(input.cwd),
'&&',
'env',
envAssignments,
shellQuote(input.binaryPath),
...input.args.map(shellQuote),
].join(' ');
return `(${command}); __claude_teammate_exit=$?; printf '\\n__CLAUDE_TEAMMATE_EXIT__:%s\\n' "$__claude_teammate_exit"`;
}

View file

@ -0,0 +1,123 @@
import { fromProvisioningMembers } from '@features/team-runtime-lanes';
import { normalizeOptionalTeamProviderId } from '@shared/utils/teamProvider';
import type { TeamCreateRequest } from '@shared/types';
export type TeamLaunchCompatibilityLevel = 'ready' | 'repairable' | 'unsafe';
export type TeamLaunchCompatibilityRosterSource = 'members-meta' | 'config' | 'inboxes' | 'missing';
export type TeamLaunchCompatibilityRepairAction = 'materialize-members-meta';
export interface TeamLaunchCompatibilityReport {
level: TeamLaunchCompatibilityLevel;
rosterSource: TeamLaunchCompatibilityRosterSource;
members: TeamCreateRequest['members'];
warnings: string[];
blockers: string[];
repairAction?: TeamLaunchCompatibilityRepairAction;
}
export function isOpenCodeLegacyProvisioningRequest(request: {
providerId?: unknown;
members?: readonly { providerId?: unknown; provider?: unknown }[];
}): boolean {
return (
normalizeOptionalTeamProviderId(request.providerId) === 'opencode' ||
(request.members ?? []).some(
(member) =>
normalizeOptionalTeamProviderId(member.providerId) === 'opencode' ||
normalizeOptionalTeamProviderId(member.provider) === 'opencode'
)
);
}
export function isPureOpenCodeProvisioningRequest(request: {
providerId?: unknown;
members?: readonly { providerId?: unknown; provider?: unknown }[];
}): boolean {
if (!isOpenCodeLegacyProvisioningRequest(request)) {
return false;
}
const rootProviderId = normalizeOptionalTeamProviderId(request.providerId);
if (rootProviderId && rootProviderId !== 'opencode') {
return false;
}
return (request.members ?? []).every((member) => {
const memberProviderId =
normalizeOptionalTeamProviderId(member.providerId) ??
normalizeOptionalTeamProviderId(member.provider);
return !memberProviderId || memberProviderId === 'opencode';
});
}
export function getOpenCodeMixedProviderProvisioningError(): string {
return (
'This OpenCode mixed-team request is outside the current support scope. ' +
'Supported mixed teams keep the lead on Anthropic or Codex. OpenCode-led mixed teams still remain blocked in this phase.'
);
}
export function getMixedLaunchFallbackRecoveryError(): string {
return 'This old mixed team is missing stable member metadata. Open Edit Team and save the roster once before launching.';
}
export function assertOpenCodeNotLaunchedThroughLegacyProvisioning(request: {
providerId?: unknown;
members?: readonly { providerId?: unknown; provider?: unknown }[];
}): void {
if (!isOpenCodeLegacyProvisioningRequest(request)) {
return;
}
const lanePlan = fromProvisioningMembers(
normalizeOptionalTeamProviderId(request.providerId),
(request.members ?? []).map((member, index) => ({
name: `member-${index + 1}`,
providerId:
normalizeOptionalTeamProviderId(member.providerId) ??
normalizeOptionalTeamProviderId(member.provider),
}))
);
if (!lanePlan.ok) {
throw new Error(lanePlan.message || getOpenCodeMixedProviderProvisioningError());
}
if (!isPureOpenCodeProvisioningRequest(request)) {
return;
}
throw new Error(
'OpenCode team launch is not enabled in the legacy Claude stream-json provisioning path. ' +
'Use the gated OpenCode runtime adapter once production launch is enabled.'
);
}
export function mergeProvisioningWarnings(
existing: string[] | undefined,
nextWarning: string | null
): string[] | undefined {
if (!nextWarning) return existing;
const merged = (existing ?? []).filter((warning) => warning !== nextWarning);
merged.push(nextWarning);
return merged.length > 0 ? merged : undefined;
}
const DETERMINISTIC_BOOTSTRAP_LARGE_TEAM_WARNING_THRESHOLD = 8;
const DETERMINISTIC_BOOTSTRAP_MAX_PRIMARY_MEMBERS = 20;
export function buildLargeDeterministicBootstrapWarning(memberCount: number): string | null {
if (memberCount <= DETERMINISTIC_BOOTSTRAP_LARGE_TEAM_WARNING_THRESHOLD) {
return null;
}
return (
`Large Codex team launch: ${memberCount} primary teammates will bootstrap in one runtime. ` +
`Launches above ${DETERMINISTIC_BOOTSTRAP_LARGE_TEAM_WARNING_THRESHOLD} teammates can be slower and more likely to hit provider rate limits or bootstrap timeouts.`
);
}
export function assertDeterministicBootstrapPrimaryMemberLimit(memberCount: number): void {
if (memberCount <= DETERMINISTIC_BOOTSTRAP_MAX_PRIMARY_MEMBERS) {
return;
}
throw new Error(
`Codex deterministic bootstrap currently supports up to ${DETERMINISTIC_BOOTSTRAP_MAX_PRIMARY_MEMBERS} primary teammates; this team has ${memberCount}. Reduce primary teammates or move extra OpenCode members to secondary lanes.`
);
}

View file

@ -0,0 +1,197 @@
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];
}

View file

@ -0,0 +1,131 @@
import { mentionsProcessTableUnavailable } from './TeamProvisioningLaunchDiagnostics';
import { isBootstrapInstructionPrompt } from './TeamProvisioningPromptBuilders';
import type { MemberLaunchState } from '@shared/types';
export function isNeverSpawnedDuringLaunchReason(reason?: string): boolean {
return reason?.trim() === 'Teammate was never spawned during launch.';
}
export function isLaunchGraceWindowFailureReason(reason?: string): boolean {
return reason?.trim() === 'Teammate did not join within the launch grace window.';
}
export function isConfigRegistrationFailureReason(reason?: string): boolean {
return (
reason?.trim() ===
'Teammate was not registered in config.json during launch. Persistent spawn failed.'
);
}
export function isOpenCodeBridgeLaunchFailureReason(reason?: string): boolean {
return reason?.trim() === 'OpenCode bridge reported member launch failure';
}
export function isRegisteredRuntimeMetadataFailureReason(reason?: string): boolean {
return reason?.trim() === 'registered runtime metadata without live process';
}
export function isProcessTableUnavailableFailureReason(reason?: string): boolean {
const text = reason?.trim();
if (!text || !mentionsProcessTableUnavailable(text)) {
return false;
}
return (
/^process table (?:is )?unavailable$/i.test(text) ||
/^runtime pid could not be verified because process table (?:is )?unavailable$/i.test(text)
);
}
export function stripProcessTableUnavailableDiagnosticSuffix(reason: string): string | null {
const match = /^(.*?);\s*process table (?:is )?unavailable$/i.exec(reason.trim());
const baseReason = match?.[1]?.trim();
return baseReason && baseReason.length > 0 ? baseReason : null;
}
function isBaseAutoClearableLaunchFailureReason(reason?: string): boolean {
return (
isNeverSpawnedDuringLaunchReason(reason) ||
isLaunchGraceWindowFailureReason(reason) ||
isConfigRegistrationFailureReason(reason) ||
isRegisteredRuntimeMetadataFailureReason(reason) ||
isOpenCodeBridgeLaunchFailureReason(reason) ||
isBootstrapMcpResourceReadFailureReason(reason) ||
isBootstrapCheckInTimeoutFailureReason(reason) ||
isBootstrapInstructionPromptFailureReason(reason) ||
isLaunchCleanupBootstrapIncompleteFailureReason(reason)
);
}
export function isBootstrapMcpResourceReadFailureReason(reason?: string): boolean {
const text = reason?.trim().toLowerCase() ?? '';
return (
text.includes('resources/read failed') &&
text.includes('member_briefing') &&
(text.includes('method not found') || text.includes('mcp error'))
);
}
export function isBootstrapCheckInTimeoutFailureReason(reason?: string): boolean {
return reason?.trim() === 'Teammate was registered but did not bootstrap-confirm before timeout.';
}
export function isBootstrapInstructionPromptFailureReason(reason?: string): boolean {
return typeof reason === 'string' && isBootstrapInstructionPrompt(reason);
}
export function isLaunchCleanupBootstrapIncompleteFailureReason(reason?: string): boolean {
const text = reason?.trim();
if (!text) {
return false;
}
if (
text === 'Launch ended before teammate bootstrap completed.' ||
text === 'Deterministic bootstrap failed before teammate check-in.'
) {
return true;
}
return (
text.startsWith('Launch ended before teammate bootstrap completed. ') &&
text.includes('Runtime process was alive after bootstrap failure')
);
}
export function isAutoClearableLaunchFailureReason(reason?: string): boolean {
const text = reason?.trim();
if (!text) {
return false;
}
const baseReason = stripProcessTableUnavailableDiagnosticSuffix(text);
return (
isBaseAutoClearableLaunchFailureReason(text) ||
isProcessTableUnavailableFailureReason(text) ||
(baseReason != null && isBaseAutoClearableLaunchFailureReason(baseReason))
);
}
export function deriveMemberLaunchState(entry: {
agentToolAccepted?: boolean;
runtimeAlive?: boolean;
bootstrapConfirmed?: boolean;
hardFailure?: boolean;
skippedForLaunch?: boolean;
pendingPermissionRequestIds?: string[];
}): MemberLaunchState {
if (entry.skippedForLaunch) {
return 'skipped_for_launch';
}
if (entry.hardFailure) {
return 'failed_to_start';
}
if (entry.bootstrapConfirmed) {
return 'confirmed_alive';
}
if ((entry.pendingPermissionRequestIds?.length ?? 0) > 0) {
return 'runtime_pending_permission';
}
if (entry.runtimeAlive || entry.agentToolAccepted) {
return 'runtime_pending_bootstrap';
}
return 'starting';
}

View file

@ -0,0 +1,44 @@
import { parseNumericSuffixName } from '@shared/utils/teamMemberName';
export function matchesMemberNameOrBase(candidateName: string, memberName: string): boolean {
if (candidateName === memberName) {
return true;
}
const parsed = parseNumericSuffixName(candidateName);
return parsed !== null && parsed.suffix >= 2 && parsed.base === memberName;
}
export function matchesTeamMemberIdentity(leftName: string, rightName: string): boolean {
return (
matchesMemberNameOrBase(leftName, rightName) || matchesMemberNameOrBase(rightName, leftName)
);
}
export function matchesObservedMemberNameForExpected(
observedName: string,
expectedName: string
): boolean {
return matchesMemberNameOrBase(observedName, expectedName);
}
export function matchesExactTeamMemberName(candidateName: string, memberName: string): boolean {
const left = candidateName.trim().toLowerCase();
const right = memberName.trim().toLowerCase();
return left.length > 0 && left === right;
}
export function namesMatchCaseInsensitive(left: string, right: string): boolean {
return left.trim().toLowerCase() === right.trim().toLowerCase();
}
export function isOpenCodeOverlayMemberRemoved(
metaMembers: readonly { name?: string; removedAt?: unknown }[],
memberName: string
): boolean {
return metaMembers.some(
(member) =>
typeof member.name === 'string' &&
namesMatchCaseInsensitive(member.name, memberName) &&
member.removedAt != null
);
}

View file

@ -0,0 +1,192 @@
import type { MemberSpawnStatusEntry, PersistedTeamLaunchSummary } from '@shared/types';
export const TASK_ACTIVITY_RUNTIME_PAUSE_GRACE_MS = 5_000;
export const MEMBER_SPAWN_AUDIT_WARNING_THROTTLE_MS = 10_000;
export const MEMBER_LAUNCH_GRACE_MS = 120_000;
export function shouldWarnOnUnreadableMemberAuditConfig(params: {
nowMs: number;
lastWarnAt: number;
expectedMembers: readonly string[];
memberSpawnStatuses: ReadonlyMap<
string,
Pick<MemberSpawnStatusEntry, 'agentToolAccepted' | 'firstSpawnAcceptedAt'> | undefined
>;
}): boolean {
const { nowMs, lastWarnAt, expectedMembers, memberSpawnStatuses } = params;
if (nowMs - lastWarnAt < MEMBER_SPAWN_AUDIT_WARNING_THROTTLE_MS) {
return false;
}
return expectedMembers.some((memberName) => {
const current = memberSpawnStatuses.get(memberName);
if (!current?.agentToolAccepted || typeof current.firstSpawnAcceptedAt !== 'string') {
return false;
}
const acceptedAtMs = Date.parse(current.firstSpawnAcceptedAt);
return Number.isFinite(acceptedAtMs) && nowMs - acceptedAtMs >= MEMBER_LAUNCH_GRACE_MS;
});
}
export function shouldWarnOnMissingRegisteredMember(params: {
nowMs: number;
lastWarnAt: number;
graceExpired: boolean;
}): boolean {
const { nowMs, lastWarnAt, graceExpired } = params;
return graceExpired && nowMs - lastWarnAt >= MEMBER_SPAWN_AUDIT_WARNING_THROTTLE_MS;
}
function nowIso(): string {
return new Date().toISOString();
}
export function parseOptionalIsoMs(value: string | undefined): number {
if (!value) return 0;
const parsed = Date.parse(value);
return Number.isFinite(parsed) ? parsed : 0;
}
export function deriveTaskActivityPauseAt(
previous: MemberSpawnStatusEntry,
fallbackAt: string
): string {
const fallbackMs = parseOptionalIsoMs(fallbackAt);
const explicitEvidenceMs = Math.max(
parseOptionalIsoMs(previous.lastHeartbeatAt),
parseOptionalIsoMs(previous.livenessLastCheckedAt)
);
const evidenceMs =
explicitEvidenceMs > 0 ? explicitEvidenceMs : parseOptionalIsoMs(previous.updatedAt);
if (evidenceMs <= 0 || fallbackMs <= 0) {
return fallbackAt;
}
const boundedEvidenceMs = Math.min(evidenceMs, fallbackMs);
const closeMs = Math.max(
boundedEvidenceMs,
Math.min(fallbackMs, boundedEvidenceMs + TASK_ACTIVITY_RUNTIME_PAUSE_GRACE_MS)
);
return new Date(closeMs).toISOString();
}
export function deriveTaskActivityResumeAt(
previous: MemberSpawnStatusEntry,
evidenceAt: string,
fallbackAt: string
): string {
const fallbackMs = parseOptionalIsoMs(fallbackAt);
const evidenceMs = parseOptionalIsoMs(evidenceAt);
const previousUpdatedMs = parseOptionalIsoMs(previous.updatedAt);
if (evidenceMs <= 0 || fallbackMs <= 0) {
return fallbackAt;
}
if (previousUpdatedMs > 0 && evidenceMs < previousUpdatedMs) {
return fallbackAt;
}
return new Date(Math.min(evidenceMs, fallbackMs)).toISOString();
}
export function createInitialMemberSpawnStatusEntry(): MemberSpawnStatusEntry {
const updatedAt = nowIso();
return {
status: 'offline',
launchState: 'starting',
agentToolAccepted: false,
runtimeAlive: false,
bootstrapConfirmed: false,
hardFailure: false,
updatedAt,
};
}
export function summarizeMemberSpawnStatusRecord(
expectedMembers: readonly string[],
statuses: Record<string, MemberSpawnStatusEntry>
): PersistedTeamLaunchSummary {
let confirmedCount = 0;
let pendingCount = 0;
let failedCount = 0;
let skippedCount = 0;
let runtimeAlivePendingCount = 0;
let shellOnlyPendingCount = 0;
let runtimeProcessPendingCount = 0;
let runtimeCandidatePendingCount = 0;
let noRuntimePendingCount = 0;
let permissionPendingCount = 0;
const memberNames = Array.from(new Set([...expectedMembers, ...Object.keys(statuses)]));
for (const memberName of memberNames) {
const entry = statuses[memberName];
if (!entry) {
pendingCount += 1;
continue;
}
if (entry.launchState === 'confirmed_alive') {
confirmedCount += 1;
continue;
}
if (entry.launchState === 'skipped_for_launch' || entry.skippedForLaunch === true) {
skippedCount += 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,
skippedCount,
runtimeAlivePendingCount,
shellOnlyPendingCount,
runtimeProcessPendingCount,
runtimeCandidatePendingCount,
noRuntimePendingCount,
permissionPendingCount,
};
}
export function buildRestartStillRunningReason(memberName: string): string {
return (
`Restart for teammate "${memberName}" was skipped because the previous runtime still appears ` +
`to be active. The requested settings may not have been applied.`
);
}
export function buildRestartDuplicateUnconfirmedReason(
memberName: string,
rawReason?: string
): string {
const suffix = rawReason?.trim()
? ` Agent returned duplicate_skipped with unrecognized reason "${rawReason.trim()}".`
: ' Agent returned duplicate_skipped without a reason.';
return (
`Restart for teammate "${memberName}" could not be confirmed and may not have applied.` + suffix
);
}
export function buildRestartGraceTimeoutReason(memberName: string): string {
return `Teammate "${memberName}" did not rejoin within the restart grace window.`;
}

View file

@ -0,0 +1,216 @@
import { createPersistedLaunchSnapshot } from '../TeamLaunchStateEvaluator';
import type { PersistedTeamLaunchMemberState, PersistedTeamLaunchSnapshot } from '@shared/types';
export const OPENCODE_UNCOMMITTED_BOOTSTRAP_DIAGNOSTIC =
'OpenCode bridge reported bootstrap confirmation, but no lane runtime evidence was committed.';
const OPEN_CODE_GENERIC_MEMBER_LAUNCH_FAILURE_REASON =
'OpenCode bridge reported member launch failure';
const OPEN_CODE_SECRET_FLAG_PATTERN =
/(--(?:api-key|token|password|secret|authorization|auth-token)(?:=|\s+))("[^"]*"|'[^']*'|\S+)/gi;
const OPEN_CODE_BEARER_TOKEN_PATTERN = /\bBearer\s+[A-Z0-9._~+/=-]+/gi;
const OPEN_CODE_SECRET_KEY_PATTERN = /\bsk-[A-Za-z0-9_-]{16,}\b/g;
const OPEN_CODE_APP_MANAGED_BRIEFING_MAX_CHARS = 12_000;
function nowIso(): string {
return new Date().toISOString();
}
export function isPersistedOpenCodeSecondaryLaneMember(
member: PersistedTeamLaunchMemberState | undefined | null
): boolean {
return (
member?.providerId === 'opencode' &&
member.laneKind === 'secondary' &&
member.laneOwnerProviderId === 'opencode' &&
typeof member.laneId === 'string' &&
member.laneId.trim().length > 0
);
}
export function hasStaleOpenCodeSecondaryLaunchDiagnostic(
member: PersistedTeamLaunchMemberState
): boolean {
return hasStaleOpenCodeDiagnostics(getOpenCodeLaunchDiagnosticValues(member));
}
export function hasRealOpenCodeLaunchDiagnostic(member: PersistedTeamLaunchMemberState): boolean {
const text = getOpenCodeLaunchDiagnosticValues(member)
.filter((value): value is string => typeof value === 'string')
.join('\n')
.toLowerCase();
return text.length > 0 && hasRealOpenCodeFailureDiagnostic(text);
}
export function getOpenCodeLaunchDiagnosticValues(
member: PersistedTeamLaunchMemberState
): readonly unknown[] {
return [member.hardFailureReason, member.runtimeDiagnostic, ...(member.diagnostics ?? [])];
}
export function hasStaleOpenCodeDiagnostics(values: readonly unknown[] | undefined): boolean {
const text = (values ?? [])
.filter((value): value is string => typeof value === 'string')
.join('\n')
.toLowerCase();
if (!text) {
return false;
}
if (hasRealOpenCodeFailureDiagnostic(text)) {
return false;
}
return (
text.includes('no lane runtime evidence') ||
text.includes('no runtime evidence') ||
text.includes('runtime evidence was not committed') ||
text.includes('no lane runtime evidence was committed') ||
text.includes('registered runtime metadata without live process') ||
text.includes('member has persisted runtime metadata only') ||
text.includes('opencode bridge reported member launch failure') ||
text.includes('file lock timeout') ||
text.includes(OPENCODE_UNCOMMITTED_BOOTSTRAP_DIAGNOSTIC.toLowerCase())
);
}
export function isFileLockTimeoutError(error: unknown): boolean {
const message = error instanceof Error ? error.message : String(error);
return message.toLowerCase().includes('file lock timeout');
}
export function hasRealOpenCodeFailureDiagnostic(text: string): boolean {
return (
/\bauth(?:entication|orization)?\b/.test(text) ||
text.includes('api key') ||
text.includes('unauthorized') ||
text.includes('forbidden') ||
text.includes('invalid_request') ||
text.includes('model not found') ||
text.includes('not found in live opencode catalog') ||
text.includes('provider unavailable') ||
text.includes('quota') ||
text.includes('credits') ||
text.includes('max_tokens') ||
text.includes('rate limit') ||
text.includes('member removed') ||
text.includes('session conflict') ||
text.includes('run tombstoned') ||
text.includes('stop requested') ||
text.includes('relaunch started')
);
}
export function normalizeOpenCodePersistedFailureReason(
value: string | undefined
): string | undefined {
const trimmed = value?.replace(/\s+/g, ' ').trim();
if (!trimmed) {
return undefined;
}
return trimmed
.replace(OPEN_CODE_SECRET_FLAG_PATTERN, '$1[redacted]')
.replace(OPEN_CODE_BEARER_TOKEN_PATTERN, 'Bearer [redacted]')
.replace(OPEN_CODE_SECRET_KEY_PATTERN, '[redacted-api-key]');
}
export function redactOpenCodeAppManagedContextText(value: string): string {
return value
.replace(OPEN_CODE_SECRET_FLAG_PATTERN, '$1[redacted]')
.replace(OPEN_CODE_BEARER_TOKEN_PATTERN, 'Bearer [redacted]')
.replace(OPEN_CODE_SECRET_KEY_PATTERN, '[redacted-api-key]');
}
export function boundOpenCodeAppManagedBriefingText(value: string): string {
const normalized = redactOpenCodeAppManagedContextText(value.replace(/\r\n/g, '\n')).trim();
if (normalized.length <= OPEN_CODE_APP_MANAGED_BRIEFING_MAX_CHARS) {
return normalized;
}
return `${normalized.slice(0, OPEN_CODE_APP_MANAGED_BRIEFING_MAX_CHARS)}\n[truncated app-managed briefing]`;
}
export function isGenericOpenCodePersistedFailureReason(value: string | undefined): boolean {
const normalized = normalizeOpenCodePersistedFailureReason(value);
return (
normalized === OPEN_CODE_GENERIC_MEMBER_LAUNCH_FAILURE_REASON ||
normalized?.startsWith(`${OPEN_CODE_GENERIC_MEMBER_LAUNCH_FAILURE_REASON}:`) === true ||
normalized?.startsWith('OpenCode secondary lane timing:') === true ||
normalized?.startsWith(
'OpenCode bridge reported ready without all required durable checkpoints:'
) === true ||
normalized?.startsWith(
'OpenCode bridge reported ready before all expected members were confirmed:'
) === true ||
normalized?.startsWith(
'OpenCode bootstrap MCP did not complete required tools before assistant response:'
) === true ||
normalized?.startsWith('info:opencode_launch_member_timing:') === true ||
normalized?.startsWith('info:opencode_launch_total_timing:') === true
);
}
export function selectOpenCodePersistedFailureReasonFromDiagnostics(
member: PersistedTeamLaunchMemberState
): string | undefined {
if (!isPersistedOpenCodeSecondaryLaneMember(member)) {
return undefined;
}
if (member.launchState !== 'failed_to_start' || member.hardFailure !== true) {
return undefined;
}
if (!isGenericOpenCodePersistedFailureReason(member.hardFailureReason)) {
return undefined;
}
for (const value of member.diagnostics ?? []) {
const normalized = normalizeOpenCodePersistedFailureReason(value);
if (!normalized || isGenericOpenCodePersistedFailureReason(normalized)) {
continue;
}
return normalized;
}
return undefined;
}
export function promoteOpenCodePersistedFailureReasonsFromDiagnostics(
snapshot: PersistedTeamLaunchSnapshot | null
): PersistedTeamLaunchSnapshot | null {
if (!snapshot) {
return null;
}
let changed = false;
const members: Record<string, PersistedTeamLaunchMemberState> = { ...snapshot.members };
for (const [memberName, member] of Object.entries(snapshot.members)) {
const promotedReason = selectOpenCodePersistedFailureReasonFromDiagnostics(member);
if (!promotedReason || promotedReason === member.hardFailureReason) {
continue;
}
members[memberName] = {
...member,
hardFailureReason: promotedReason,
runtimeDiagnostic:
member.runtimeDiagnostic &&
!isGenericOpenCodePersistedFailureReason(member.runtimeDiagnostic)
? member.runtimeDiagnostic
: promotedReason,
runtimeDiagnosticSeverity: member.runtimeDiagnosticSeverity ?? 'error',
};
changed = true;
}
if (!changed) {
return snapshot;
}
return createPersistedLaunchSnapshot({
teamName: snapshot.teamName,
expectedMembers: snapshot.expectedMembers,
bootstrapExpectedMembers: snapshot.bootstrapExpectedMembers,
leadSessionId: snapshot.leadSessionId,
launchPhase: snapshot.launchPhase,
members,
updatedAt: nowIso(),
});
}
export function filterStaleOpenCodeOverlayDiagnostics(
values: readonly string[] | undefined
): string[] {
return (values ?? []).filter((value) => !hasStaleOpenCodeDiagnostics([value]));
}

View file

@ -0,0 +1,619 @@
import {
hasRealOpenCodeFailureDiagnostic,
isPersistedOpenCodeSecondaryLaneMember,
normalizeOpenCodePersistedFailureReason,
OPENCODE_UNCOMMITTED_BOOTSTRAP_DIAGNOSTIC,
} from './TeamProvisioningOpenCodeDiagnosticsPolicy';
import type {
TeamRuntimeLaunchResult,
TeamRuntimeMemberLaunchEvidence,
} from '../runtime/TeamRuntimeAdapter';
import type {
PersistedTeamLaunchMemberState,
TeamAgentRuntimeEntry,
TeamLaunchAggregateState,
} from '@shared/types';
export const MEMBER_BOOTSTRAP_STALL_MS = 5 * 60_000;
export const OPENCODE_APP_MANAGED_BOOTSTRAP_STALLED_DIAGNOSTIC =
'OpenCode app-managed bootstrap evidence did not commit within 5 min.';
export const OPENCODE_BOOTSTRAP_PENDING_DIAGNOSTIC =
'opencode_bootstrap_pending_after_materialized_session';
export const OPENCODE_APP_MANAGED_BOOTSTRAP_PENDING_DIAGNOSTIC =
'OpenCode app-managed bootstrap evidence is pending after materialized session.';
const OPENCODE_MEMBER_SESSION_RECORDED_AT_PATTERN =
/\bmember_session_recorded\s+at\s+([0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9:.+-]+Z?)\b/i;
export function formatOpenCodeLaneTimingMs(value: number | null | undefined): string {
return typeof value === 'number' && Number.isFinite(value)
? `${Math.max(0, Math.round(value))}ms`
: 'n/a';
}
export function appendDiagnosticOnce(
diagnostics: readonly string[],
diagnostic: string | null
): string[] {
if (!diagnostic || diagnostics.includes(diagnostic)) {
return [...diagnostics];
}
return [...diagnostics, diagnostic];
}
export function buildOpenCodeSecondaryLaneTimingDiagnostic(lane: {
member: { name: string };
queuedAtMs?: number;
launchStartedAtMs?: number;
launchFinishedAtMs?: number;
}): string | null {
if (
typeof lane.queuedAtMs !== 'number' ||
typeof lane.launchStartedAtMs !== 'number' ||
typeof lane.launchFinishedAtMs !== 'number'
) {
return null;
}
return [
'OpenCode secondary lane timing:',
`member=${lane.member.name}`,
`queueWaitMs=${formatOpenCodeLaneTimingMs(lane.launchStartedAtMs - lane.queuedAtMs)}`,
`launchMs=${formatOpenCodeLaneTimingMs(lane.launchFinishedAtMs - lane.launchStartedAtMs)}`,
`totalMs=${formatOpenCodeLaneTimingMs(lane.launchFinishedAtMs - lane.queuedAtMs)}`,
].join(' ');
}
export function createUnexpectedMixedSecondaryLaneFailureResult(input: {
runId: string;
teamName: string;
memberName: string;
message: string;
}): TeamRuntimeLaunchResult {
return {
runId: input.runId,
teamName: input.teamName,
launchPhase: 'finished',
teamLaunchState: 'partial_failure',
members: {
[input.memberName]: {
memberName: input.memberName,
providerId: 'opencode',
launchState: 'failed_to_start',
agentToolAccepted: false,
runtimeAlive: false,
bootstrapConfirmed: false,
hardFailure: true,
hardFailureReason: input.message,
diagnostics: [input.message],
},
},
warnings: [],
diagnostics: [input.message],
};
}
export function isExplicitLegacyOpenCodeBootstrap(
value:
| {
bootstrapMode?: 'model_tool_checkin' | 'app_managed_context';
}
| undefined
| null
): boolean {
return value?.bootstrapMode === 'model_tool_checkin';
}
export function hasRecoverableOpenCodeBootstrapDiagnostic(diagnostics: readonly string[]): boolean {
const text = diagnostics.join('\n').toLowerCase();
if (!text) {
return false;
}
if (hasRealOpenCodeFailureDiagnostic(text)) {
return false;
}
return (
text.includes('runtime_bootstrap_checkin') ||
text.includes('member_briefing') ||
text.includes('bootstrap mcp') ||
text.includes('member_session_recorded') ||
text.includes('not connected') ||
text.includes('mcp not connected') ||
text.includes('member_launch_reconcile_pending') ||
text.includes('member_launch_preview_timeout')
);
}
export function collectRuntimeLaunchFailureDiagnostics(
result: TeamRuntimeLaunchResult,
memberName: string
): string[] {
const member = result.members[memberName];
return [...(member?.diagnostics ?? []), member?.hardFailureReason, ...result.diagnostics].filter(
(value): value is string => typeof value === 'string' && value.trim().length > 0
);
}
export function collectOpenCodeSecondaryLaneFailureDiagnostics(
result: TeamRuntimeLaunchResult,
memberName: string,
prefixDiagnostics: readonly string[]
): string[] {
const diagnostics = [
...prefixDiagnostics,
...collectRuntimeLaunchFailureDiagnostics(result, memberName),
].filter((value): value is string => typeof value === 'string' && value.trim().length > 0);
return diagnostics.length > 0 ? diagnostics : ['OpenCode bridge reported member launch failure'];
}
export function isReconciliableOpenCodeUnknownOutcome(diagnostics: readonly string[]): boolean {
return diagnostics.some((diagnostic) =>
/outcome must be reconciled before retry/i.test(diagnostic)
);
}
export function isDefinitiveOpenCodePreLaunchFailure(
result: TeamRuntimeLaunchResult,
memberName: string
): boolean {
const member = result.members[memberName];
if (!member) {
return false;
}
const hardFailed = member.launchState === 'failed_to_start' || member.hardFailure === true;
if (!hardFailed) {
return false;
}
const runtimeMaterialized =
member.agentToolAccepted ||
member.runtimeAlive ||
member.bootstrapConfirmed ||
(typeof member.sessionId === 'string' && member.sessionId.trim().length > 0) ||
(typeof member.runtimePid === 'number' &&
Number.isFinite(member.runtimePid) &&
member.runtimePid > 0);
if (runtimeMaterialized) {
return false;
}
return !isReconciliableOpenCodeUnknownOutcome(
collectRuntimeLaunchFailureDiagnostics(result, memberName)
);
}
export function isMaterializedOpenCodeSessionId(sessionId: unknown): boolean {
if (typeof sessionId !== 'string') {
return false;
}
const trimmed = sessionId.trim();
return trimmed.length > 0 && !trimmed.toLowerCase().startsWith('failed:');
}
export function hasMaterializedOpenCodeRuntimeForBootstrap(
member: TeamRuntimeMemberLaunchEvidence | undefined
): member is TeamRuntimeMemberLaunchEvidence {
if (!member) {
return false;
}
if (isMaterializedOpenCodeSessionId(member.sessionId)) {
return true;
}
return (
hasOpenCodeRuntimeLivenessMarker(member) &&
typeof member.runtimePid === 'number' &&
Number.isFinite(member.runtimePid) &&
member.runtimePid > 0
);
}
export function isRecoverableOpenCodeBootstrapPendingLaunchResult(
result: TeamRuntimeLaunchResult,
memberName: string
): boolean {
const member = result.members[memberName];
if (!hasMaterializedOpenCodeRuntimeForBootstrap(member)) {
return false;
}
if (member.bootstrapConfirmed || member.launchState === 'confirmed_alive') {
return false;
}
if ((member.pendingPermissionRequestIds?.length ?? 0) > 0) {
return false;
}
return hasRecoverableOpenCodeBootstrapDiagnostic(
collectRuntimeLaunchFailureDiagnostics(result, memberName)
);
}
export function summarizeRuntimeLaunchResultMembers(
members: Record<string, TeamRuntimeMemberLaunchEvidence>
): TeamLaunchAggregateState {
const values = Object.values(members);
if (
values.some((member) => member.launchState === 'failed_to_start' || member.hardFailure === true)
) {
return 'partial_failure';
}
if (values.length > 0 && values.every((member) => member.launchState === 'confirmed_alive')) {
return 'clean_success';
}
return 'partial_pending';
}
export function normalizeRecoverableOpenCodeBootstrapPendingLaunchResult(
result: TeamRuntimeLaunchResult,
memberName: string,
diagnostics: readonly string[]
): TeamRuntimeLaunchResult {
const member = result.members[memberName];
if (!member) {
return result;
}
const memberDiagnostics = Array.from(
new Set([
...(member.diagnostics ?? []),
OPENCODE_BOOTSTRAP_PENDING_DIAGNOSTIC,
isExplicitLegacyOpenCodeBootstrap(member)
? 'OpenCode runtime session materialized; waiting for runtime_bootstrap_checkin.'
: OPENCODE_APP_MANAGED_BOOTSTRAP_PENDING_DIAGNOSTIC,
...diagnostics,
])
);
const normalizedMember: TeamRuntimeMemberLaunchEvidence = {
...member,
launchState: 'runtime_pending_bootstrap',
agentToolAccepted: true,
runtimeAlive: true,
bootstrapConfirmed: false,
hardFailure: false,
hardFailureReason: undefined,
pendingPermissionRequestIds: undefined,
livenessKind:
member.livenessKind === 'confirmed_bootstrap'
? 'runtime_process'
: (member.livenessKind ?? 'runtime_process'),
runtimeDiagnostic:
member.runtimeDiagnostic ??
'OpenCode runtime process detected; waiting for bootstrap check-in.',
runtimeDiagnosticSeverity: member.runtimeDiagnosticSeverity ?? 'info',
diagnostics: memberDiagnostics,
};
const members = {
...result.members,
[memberName]: normalizedMember,
};
const teamLaunchState = summarizeRuntimeLaunchResultMembers(members);
return {
...result,
launchPhase: teamLaunchState === 'clean_success' ? result.launchPhase : 'active',
teamLaunchState,
members,
diagnostics: Array.from(new Set([...result.diagnostics, ...memberDiagnostics])),
};
}
export function buildOpenCodeUncommittedBootstrapDiagnostic(storage: {
manifestEntryCount: number | null;
manifestUpdatedAt: string | null;
fileNames: string[];
}): string[] {
return [
OPENCODE_UNCOMMITTED_BOOTSTRAP_DIAGNOSTIC,
`OpenCode lane manifest entries: ${storage.manifestEntryCount ?? 0}`,
...(storage.manifestUpdatedAt
? [`OpenCode lane manifest updated at: ${storage.manifestUpdatedAt}`]
: []),
storage.fileNames.length > 0
? `OpenCode lane files: ${storage.fileNames.slice(0, 8).join(', ')}`
: 'OpenCode lane files: none',
];
}
export function downgradeUncommittedOpenCodeBootstrapEvidence(
evidence: TeamRuntimeMemberLaunchEvidence,
diagnostics: readonly string[]
): TeamRuntimeMemberLaunchEvidence {
const hasRuntimeHandle = hasOpenCodeRuntimeHandle(evidence);
return {
...evidence,
launchState: hasRuntimeHandle ? 'runtime_pending_bootstrap' : 'starting',
agentToolAccepted: hasRuntimeHandle,
runtimeAlive: false,
bootstrapConfirmed: false,
hardFailure: false,
hardFailureReason: undefined,
livenessKind: hasRuntimeHandle
? evidence.livenessKind === 'confirmed_bootstrap'
? 'runtime_process_candidate'
: (evidence.livenessKind ?? 'runtime_process_candidate')
: 'registered_only',
runtimeDiagnostic: hasRuntimeHandle
? 'OpenCode runtime handle is present, but bootstrap evidence was not committed.'
: 'OpenCode bootstrap confirmation was not committed to lane runtime evidence.',
runtimeDiagnosticSeverity: 'warning',
diagnostics: Array.from(new Set([...evidence.diagnostics, ...diagnostics])),
};
}
export function promoteCommittedOpenCodeAppManagedBootstrapEvidence(
evidence: TeamRuntimeMemberLaunchEvidence
): TeamRuntimeMemberLaunchEvidence {
return {
...evidence,
launchState: 'confirmed_alive',
agentToolAccepted: true,
runtimeAlive: true,
bootstrapConfirmed: true,
hardFailure: false,
hardFailureReason: undefined,
livenessKind: 'confirmed_bootstrap',
runtimeDiagnostic:
'OpenCode app-managed bootstrap evidence was committed and read back by the desktop app.',
runtimeDiagnosticSeverity: 'info',
diagnostics: appendDiagnosticOnce(
evidence.diagnostics,
'OpenCode app-managed bootstrap evidence committed and read back.'
),
};
}
export function hasOpenCodeRuntimeHandle(
value:
| Pick<PersistedTeamLaunchMemberState, 'runtimePid' | 'runtimeSessionId' | 'livenessKind'>
| Pick<TeamRuntimeMemberLaunchEvidence, 'runtimePid' | 'sessionId' | 'livenessKind'>
| undefined
): boolean {
if (!value) {
return false;
}
const runtimePid =
typeof value.runtimePid === 'number' &&
Number.isFinite(value.runtimePid) &&
value.runtimePid > 0;
const runtimeSessionId = (value as { runtimeSessionId?: unknown }).runtimeSessionId;
const runtimeEvidenceSessionId = (value as { sessionId?: unknown }).sessionId;
const sessionId =
isMaterializedOpenCodeSessionId(runtimeSessionId) ||
isMaterializedOpenCodeSessionId(runtimeEvidenceSessionId);
return runtimePid || sessionId;
}
export function hasOpenCodeRuntimeLivenessMarker(
value: Pick<TeamRuntimeMemberLaunchEvidence, 'livenessKind'> | undefined
): boolean {
return (
value?.livenessKind === 'runtime_process' ||
value?.livenessKind === 'runtime_process_candidate' ||
value?.livenessKind === 'permission_blocked'
);
}
export function hasOpenCodeRuntimeEntryHandle(
value:
| Pick<TeamAgentRuntimeEntry, 'pid' | 'runtimePid' | 'runtimeSessionId' | 'livenessKind'>
| undefined
| null
): boolean {
if (!value) {
return false;
}
const pid = typeof value.pid === 'number' && Number.isFinite(value.pid) && value.pid > 0;
const runtimePid =
typeof value.runtimePid === 'number' &&
Number.isFinite(value.runtimePid) &&
value.runtimePid > 0;
const runtimeSessionId = isMaterializedOpenCodeSessionId(value.runtimeSessionId);
return pid || runtimePid || runtimeSessionId || hasOpenCodeRuntimeLivenessMarker(value);
}
export function isRecoverablePersistedOpenCodeRuntimeCandidate(
member: PersistedTeamLaunchMemberState | undefined | null
): boolean {
if (!member || member.skippedForLaunch) {
return false;
}
if (!isPersistedOpenCodeSecondaryLaneMember(member)) {
return false;
}
const hasPendingPermission = (member.pendingPermissionRequestIds?.length ?? 0) > 0;
return (
member.agentToolAccepted === true && (hasOpenCodeRuntimeHandle(member) || hasPendingPermission)
);
}
export function normalizeIsoTimestamp(value: unknown): string | null {
if (typeof value !== 'string') {
return null;
}
const trimmed = value.trim();
if (!trimmed) {
return null;
}
const parsed = Date.parse(trimmed);
return Number.isFinite(parsed) ? new Date(parsed).toISOString() : null;
}
function selectEarliestIsoTimestamp(values: readonly unknown[]): string | undefined {
let selected: { value: string; timeMs: number } | null = null;
for (const value of values) {
const normalized = normalizeIsoTimestamp(value);
if (!normalized) {
continue;
}
const timeMs = Date.parse(normalized);
if (!selected || timeMs < selected.timeMs) {
selected = { value: normalized, timeMs };
}
}
return selected?.value;
}
function extractOpenCodeMemberSessionRecordedAt(
diagnostics: readonly string[] | undefined
): string[] {
return (diagnostics ?? []).flatMap((diagnostic) => {
const match = OPENCODE_MEMBER_SESSION_RECORDED_AT_PATTERN.exec(diagnostic);
return match?.[1] ? [match[1]] : [];
});
}
export function resolveOpenCodeBootstrapAcceptedAt(
member: Pick<PersistedTeamLaunchMemberState, 'firstSpawnAcceptedAt' | 'diagnostics'>
): string | undefined {
return selectEarliestIsoTimestamp([
member.firstSpawnAcceptedAt,
...extractOpenCodeMemberSessionRecordedAt(member.diagnostics),
]);
}
function hasOpenCodeSecondaryFatalBootstrapDiagnostic(
member: Pick<
PersistedTeamLaunchMemberState,
'diagnostics' | 'runtimeDiagnostic' | 'hardFailureReason'
>
): boolean {
const text = [member.runtimeDiagnostic, member.hardFailureReason, ...(member.diagnostics ?? [])]
.filter((value): value is string => typeof value === 'string' && value.trim().length > 0)
.join('\n')
.toLowerCase();
return text.length > 0 && hasRealOpenCodeFailureDiagnostic(text);
}
export function selectOpenCodeSecondaryBootstrapStallDiagnostic(
values: readonly unknown[]
): string | null {
const normalizedValues = values
.filter((value): value is string => typeof value === 'string')
.map((value) => normalizeOpenCodePersistedFailureReason(value))
.filter((value): value is string => typeof value === 'string' && value.length > 0);
const runtimeCheckinDiagnostic = normalizedValues.find((value) =>
value.toLowerCase().includes('runtime_bootstrap_checkin')
);
if (runtimeCheckinDiagnostic) {
return runtimeCheckinDiagnostic;
}
const memberBriefingDiagnostic = normalizedValues.find((value) =>
value.toLowerCase().includes('member_briefing')
);
if (memberBriefingDiagnostic) {
return `${memberBriefingDiagnostic}; runtime_bootstrap_checkin did not complete after 5 min.`;
}
return null;
}
export function getOpenCodeSecondaryBootstrapStallDiagnosticFromPersisted(
member: PersistedTeamLaunchMemberState
): string {
if (!isExplicitLegacyOpenCodeBootstrap(member)) {
return OPENCODE_APP_MANAGED_BOOTSTRAP_STALLED_DIAGNOSTIC;
}
const selected = selectOpenCodeSecondaryBootstrapStallDiagnostic([
member.runtimeDiagnostic,
...(member.diagnostics ?? []),
member.hardFailureReason,
]);
if (selected) {
return selected;
}
return 'OpenCode bootstrap did not complete runtime_bootstrap_checkin after 5 min.';
}
export function shouldMarkPersistedOpenCodeBootstrapStalled(
member: PersistedTeamLaunchMemberState,
nowMs: number
): boolean {
if (!isPersistedOpenCodeSecondaryLaneMember(member)) {
return false;
}
if (
member.launchState !== 'runtime_pending_bootstrap' ||
member.bootstrapConfirmed === true ||
member.hardFailure === true ||
member.skippedForLaunch === true ||
(member.pendingPermissionRequestIds?.length ?? 0) > 0
) {
return false;
}
if (hasOpenCodeSecondaryFatalBootstrapDiagnostic(member)) {
return false;
}
const acceptedAt = resolveOpenCodeBootstrapAcceptedAt(member);
const acceptedAtMs = acceptedAt ? Date.parse(acceptedAt) : NaN;
if (!Number.isFinite(acceptedAtMs) || nowMs - acceptedAtMs < MEMBER_BOOTSTRAP_STALL_MS) {
return false;
}
return (
hasOpenCodeRuntimeHandle(member) ||
hasOpenCodeRuntimeLivenessMarker(member) ||
hasRecoverableOpenCodeBootstrapDiagnostic(
[member.runtimeDiagnostic, ...(member.diagnostics ?? [])].filter(
(value): value is string => typeof value === 'string'
)
)
);
}
export function isRecoverablePersistedOpenCodeTerminalRuntimeCandidate(
member: PersistedTeamLaunchMemberState | undefined | null
): boolean {
return (
isRecoverablePersistedOpenCodeRuntimeCandidate(member) &&
member?.launchState === 'failed_to_start' &&
member.hardFailure === true &&
hasOpenCodeRuntimeHandle(member)
);
}
export function isRecoverableOpenCodeRuntimeEvidence(
evidence: TeamRuntimeMemberLaunchEvidence | undefined | null
): evidence is TeamRuntimeMemberLaunchEvidence {
if (!evidence) {
return false;
}
return (
evidence.runtimeAlive === true ||
evidence.bootstrapConfirmed === true ||
(evidence.pendingPermissionRequestIds?.length ?? 0) > 0 ||
hasOpenCodeRuntimeHandle(evidence) ||
(evidence.agentToolAccepted === true && hasOpenCodeRuntimeLivenessMarker(evidence))
);
}
export function isBootstrapMemberEvidenceCurrentForMember(
current: { firstSpawnAcceptedAt?: string; lastEvaluatedAt?: string },
bootstrapMember: Pick<
PersistedTeamLaunchMemberState,
'firstSpawnAcceptedAt' | 'lastHeartbeatAt' | 'lastRuntimeAliveAt' | 'lastEvaluatedAt'
>,
evidenceKind: 'acceptance' | 'confirmation'
): boolean {
const bootstrapFirstSpawnAcceptedMs = Date.parse(bootstrapMember.firstSpawnAcceptedAt ?? '');
const bootstrapLastEvaluatedMs = Date.parse(bootstrapMember.lastEvaluatedAt ?? '');
const hasDurableBootstrapSpawnAcceptedAt =
Number.isFinite(bootstrapFirstSpawnAcceptedMs) &&
(!Number.isFinite(bootstrapLastEvaluatedMs) ||
bootstrapFirstSpawnAcceptedMs <= bootstrapLastEvaluatedMs);
const evidenceAt =
evidenceKind === 'confirmation'
? (bootstrapMember.lastHeartbeatAt ??
bootstrapMember.lastRuntimeAliveAt ??
bootstrapMember.lastEvaluatedAt)
: hasDurableBootstrapSpawnAcceptedAt
? bootstrapMember.firstSpawnAcceptedAt
: bootstrapMember.lastEvaluatedAt;
const evidenceMs = Date.parse(evidenceAt ?? '');
if (!Number.isFinite(evidenceMs)) {
return false;
}
const firstSpawnAcceptedMs = Date.parse(current.firstSpawnAcceptedAt ?? '');
const lastEvaluatedMs = Date.parse(current.lastEvaluatedAt ?? '');
const hasDurableSpawnBoundary =
Number.isFinite(firstSpawnAcceptedMs) &&
(!Number.isFinite(lastEvaluatedMs) || firstSpawnAcceptedMs <= lastEvaluatedMs);
const boundaryMs = hasDurableSpawnBoundary ? firstSpawnAcceptedMs : NaN;
return !Number.isFinite(boundaryMs) || evidenceMs >= boundaryMs;
}

View file

@ -0,0 +1,172 @@
import { ConfigManager } from '@main/services/infrastructure/ConfigManager';
import { migrateProviderBackendId } from '@shared/utils/providerBackend';
import { resolveTeamProviderId } from '../../runtime/providerRuntimeEnv';
import type { GeminiRuntimeAuthState } from '../../runtime/geminiRuntimeAuth';
import type { ProviderModelLaunchIdentity, TeamCreateRequest, TeamProviderId } from '@shared/types';
export interface PromptSizeSummary {
chars: number;
lines: number;
}
export interface RuntimeLaunchLogger {
info(message: string): void;
}
export function getAnthropicFastModeDefault(): boolean {
return (
ConfigManager.getInstance().getConfig().providerConnections.anthropic.fastModeDefault === true
);
}
export function getTeamProviderLabel(providerId: TeamProviderId): string {
switch (providerId) {
case 'opencode':
return 'OpenCode';
case 'codex':
return 'Codex';
case 'gemini':
return 'Gemini';
case 'anthropic':
default:
return 'Anthropic';
}
}
export function getConfiguredRuntimeBackend(providerId: TeamProviderId): string | null {
const runtimeConfig = ConfigManager.getInstance().getConfig().runtime.providerBackends;
switch (providerId) {
case 'opencode':
return null;
case 'gemini':
return runtimeConfig.gemini;
case 'codex':
return migrateProviderBackendId('codex', runtimeConfig.codex) ?? 'codex-native';
case 'anthropic':
default:
return null;
}
}
export function buildRuntimeLaunchWarning(
request: Pick<
TeamCreateRequest,
'providerId' | 'providerBackendId' | 'model' | 'effort' | 'fastMode'
>,
env: NodeJS.ProcessEnv,
options?: {
geminiRuntimeAuth?: GeminiRuntimeAuthState | null;
promptSize?: PromptSizeSummary | null;
expectedMembersCount?: number;
}
): string {
const providerId = resolveTeamProviderId(request.providerId);
const providerLabel = getTeamProviderLabel(providerId);
const modelLabel = request.model?.trim() || 'default';
const effortLabel = request.effort ?? 'default';
const fastLabel =
providerId === 'anthropic'
? `, fast ${request.fastMode ?? (getAnthropicFastModeDefault() ? 'inherit:on' : 'inherit:off')}`
: providerId === 'codex'
? `, fast ${request.fastMode ?? 'inherit:off'}`
: '';
const backend =
migrateProviderBackendId(providerId, request.providerBackendId?.trim()) ||
getConfiguredRuntimeBackend(providerId);
const flags: string[] = [];
if (env.CLAUDE_CODE_USE_GEMINI === '1') flags.push('USE_GEMINI');
if (env.CLAUDE_CODE_USE_OPENAI === '1') flags.push('USE_OPENAI');
if (env.CLAUDE_CODE_ENTRY_PROVIDER) {
flags.push(`ENTRY_PROVIDER=${env.CLAUDE_CODE_ENTRY_PROVIDER}`);
}
if (env.CLAUDE_CODE_GEMINI_BACKEND) {
flags.push(`GEMINI_BACKEND=${env.CLAUDE_CODE_GEMINI_BACKEND}`);
}
if (env.CLAUDE_CODE_CODEX_BACKEND) {
flags.push(`CODEX_BACKEND=${env.CLAUDE_CODE_CODEX_BACKEND}`);
}
if (env.CLAUDE_TEAM_FORCE_PROCESS_TEAMMATES === '1') {
flags.push('FORCE_PROCESS_TEAMMATES');
}
const backendPart = backend ? `, backend ${backend}` : '';
const flagsPart = flags.length > 0 ? `, env ${flags.join(', ')}` : '';
const geminiAuth = options?.geminiRuntimeAuth;
const authPart =
providerId === 'gemini' && geminiAuth
? `, auth ${geminiAuth.authMethod ?? 'none'}/${geminiAuth.resolvedBackend}`
: '';
const promptSize = options?.promptSize;
const promptPart = promptSize
? `, prompt ${promptSize.chars.toLocaleString('en-US')} chars/${promptSize.lines} lines`
: '';
const membersPart =
typeof options?.expectedMembersCount === 'number'
? `, members ${options.expectedMembersCount}`
: '';
return `Launch runtime: ${providerLabel} · ${modelLabel} · ${effortLabel}${fastLabel}${backendPart}${authPart}${promptPart}${membersPart}${flagsPart}`;
}
export function logRuntimeLaunchSnapshot(
logger: RuntimeLaunchLogger,
teamName: string,
claudePath: string,
args: string[],
request: Pick<
TeamCreateRequest,
'providerId' | 'providerBackendId' | 'model' | 'effort' | 'fastMode'
>,
env: NodeJS.ProcessEnv,
options?: {
geminiRuntimeAuth?: GeminiRuntimeAuthState | null;
promptSize?: PromptSizeSummary | null;
expectedMembersCount?: number;
launchIdentity?: ProviderModelLaunchIdentity | null;
}
): void {
const providerId = resolveTeamProviderId(request.providerId);
const snapshot = {
providerId,
providerBackendId: migrateProviderBackendId(providerId, request.providerBackendId) ?? null,
model: request.model ?? null,
effort: request.effort ?? null,
fastMode: request.fastMode ?? null,
configuredBackend:
migrateProviderBackendId(providerId, request.providerBackendId?.trim()) ||
getConfiguredRuntimeBackend(providerId),
promptSize: options?.promptSize ?? null,
expectedMembersCount: options?.expectedMembersCount ?? null,
launchIdentity: options?.launchIdentity ?? null,
geminiRuntimeAuth:
providerId === 'gemini'
? {
authenticated: options?.geminiRuntimeAuth?.authenticated ?? null,
authMethod: options?.geminiRuntimeAuth?.authMethod ?? null,
resolvedBackend: options?.geminiRuntimeAuth?.resolvedBackend ?? null,
projectId: options?.geminiRuntimeAuth?.projectId ?? null,
statusMessage: options?.geminiRuntimeAuth?.statusMessage ?? null,
}
: null,
env: {
CLAUDE_CODE_USE_GEMINI: env.CLAUDE_CODE_USE_GEMINI ?? null,
CLAUDE_CODE_USE_OPENAI: env.CLAUDE_CODE_USE_OPENAI ?? null,
CLAUDE_CODE_ENTRY_PROVIDER: env.CLAUDE_CODE_ENTRY_PROVIDER ?? null,
CLAUDE_CODE_GEMINI_BACKEND: env.CLAUDE_CODE_GEMINI_BACKEND ?? null,
CLAUDE_CODE_CODEX_BACKEND: env.CLAUDE_CODE_CODEX_BACKEND ?? null,
CLAUDE_TEAM_FORCE_PROCESS_TEAMMATES: env.CLAUDE_TEAM_FORCE_PROCESS_TEAMMATES ?? null,
CLAUDE_CONFIG_DIR: env.CLAUDE_CONFIG_DIR ?? null,
CLAUDE_TEAM_CONTROL_URL: env.CLAUDE_TEAM_CONTROL_URL ?? null,
},
args,
claudePath,
};
logger.info(`[${teamName}] Launch runtime snapshot ${JSON.stringify(snapshot)}`);
}
export function getPromptSizeSummary(prompt: string): PromptSizeSummary {
return {
chars: prompt.length,
lines: prompt.length === 0 ? 0 : prompt.split(/\r?\n/g).length,
};
}

View file

@ -0,0 +1,135 @@
import {
buildCliExitFailurePresentation,
buildCombinedLogs,
buildDeterministicBootstrapExitFailure,
buildSanitizedCliExitError,
type CliExitPresentationRun,
formatPendingBootstrapMemberNames,
parseCliLogLinesFromText,
} from '@main/services/team/provisioning/TeamProvisioningCliExitPresentation';
import { describe, expect, it } from 'vitest';
function run(overrides: Partial<CliExitPresentationRun> = {}): CliExitPresentationRun {
return {
stdoutBuffer: '',
stderrBuffer: '',
claudeLogLines: [],
deterministicBootstrap: false,
deterministicBootstrapMemberSpawnSeen: false,
expectedMembers: [],
memberSpawnStatuses: new Map(),
...overrides,
};
}
describe('TeamProvisioningCliExitPresentation', () => {
it('combines stdout and stderr with stable stream markers', () => {
expect(buildCombinedLogs('', '')).toBe('');
expect(buildCombinedLogs(' stdout only ', '')).toBe('stdout only');
expect(buildCombinedLogs('', ' stderr only ')).toBe('stderr only');
expect(buildCombinedLogs('out', 'err')).toBe('[stdout]\nout\n\n[stderr]\nerr');
});
it('parses stream markers and ignores blank log lines', () => {
expect(parseCliLogLinesFromText('[stdout]\nhello\n\n[stderr]\nboom')).toEqual([
{ stream: 'stdout', text: 'hello' },
{ stream: 'stderr', text: 'boom' },
]);
});
it('extracts structured CLI errors while filtering setup noise', () => {
const sanitized = buildSanitizedCliExitError(
run({
claudeLogLines: [
'[stdout]',
'{"type":"system","subtype":"init","message":"ignore"}',
'[stderr]',
'{"type":"error","message":"Invalid API key"}',
'{"type":"result","subtype":"error","error":"Quota exceeded"}',
'TodoWrite hook_progress should stay hidden',
'plain stderr failure',
],
})
);
expect(sanitized).toBe('Invalid API key\nQuota exceeded\nplain stderr failure');
});
it('adds final partial buffer errors when line history already exists', () => {
expect(
buildSanitizedCliExitError(
run({
claudeLogLines: ['[stderr]', 'first failure'],
stderrBuffer: 'first failure\nsecond failure without newline',
})
)
).toBe('first failure\nsecond failure without newline');
});
it('reports login guidance before generic sanitized errors', () => {
expect(
buildCliExitFailurePresentation(
run({ stderrBuffer: 'Please run /login to authenticate' }),
1,
{ cliCommandLabel: 'Claude CLI' }
).error
).toContain('Claude CLI reports it is not authenticated');
});
it('formats deterministic bootstrap failures by observed stage', () => {
expect(
buildDeterministicBootstrapExitFailure(run({ deterministicBootstrap: true })).error
).toContain('before deterministic team bootstrap started');
expect(
buildDeterministicBootstrapExitFailure(
run({
deterministicBootstrap: true,
lastDeterministicBootstrapEvent: 'team_bootstrap',
lastDeterministicBootstrapPhase: 'planning',
})
).error
).toContain('Last bootstrap event: team_bootstrap/planning');
});
it('summarizes pending bootstrap members with a stable cap', () => {
expect(
formatPendingBootstrapMemberNames(
run({
expectedMembers: ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'],
memberSpawnStatuses: new Map([
['a', { bootstrapConfirmed: true }],
['b', { bootstrapConfirmed: false }],
]),
})
)
).toBe('b, c, d, e, f, g and 1 more');
});
it('falls back to deterministic or generic exit presentations when logs are not useful', () => {
expect(
buildCliExitFailurePresentation(
run({
deterministicBootstrap: true,
lastDeterministicBootstrapEvent: 'team_bootstrap',
deterministicBootstrapMemberSpawnSeen: true,
expectedMembers: ['lead', 'worker'],
memberSpawnStatuses: new Map([['lead', { bootstrapConfirmed: true }]]),
}),
1,
{ cliCommandLabel: 'Codex runtime' }
)
).toEqual({
message: 'Launch bootstrap was not confirmed',
error:
'Bootstrap was not confirmed before the Codex runtime exited. Pending teammates: worker.',
});
expect(
buildCliExitFailurePresentation(run(), 1, { cliCommandLabel: 'Claude CLI' }).error
).toContain('Claude CLI exited with code 1 without user-facing stdout/stderr');
expect(
buildCliExitFailurePresentation(run(), null, { cliCommandLabel: 'Claude CLI' }).error
).toBe('Claude CLI exited with code unknown');
});
});

View file

@ -0,0 +1,146 @@
import {
buildDirectTmuxRestartCommand,
buildDirectTmuxRestartEnvAssignments,
hasAnthropicCompatibleAuthTokenEnv,
isAnthropicCompatibleBaseUrl,
isInteractiveShellCommand,
shellQuote,
} from '@main/services/team/provisioning/TeamProvisioningDirectRestart';
import { describe, expect, it } from 'vitest';
describe('TeamProvisioningDirectRestart', () => {
it('quotes shell values without losing apostrophes or empty strings', () => {
expect(shellQuote('')).toBe("''");
expect(shellQuote('/tmp/demo path')).toBe("'/tmp/demo path'");
expect(shellQuote("worker's path")).toBe("'worker'\\''s path'");
});
it('detects interactive shell pane commands by basename', () => {
expect(isInteractiveShellCommand('/bin/zsh')).toBe(true);
expect(isInteractiveShellCommand(' FISH ')).toBe(true);
expect(isInteractiveShellCommand('node')).toBe(false);
expect(isInteractiveShellCommand(undefined)).toBe(false);
});
it('classifies Anthropic-compatible base URLs without accepting first-party or credential URLs', () => {
expect(isAnthropicCompatibleBaseUrl('http://localhost:1234')).toBe(true);
expect(isAnthropicCompatibleBaseUrl('https://proxy.example.test')).toBe(true);
expect(isAnthropicCompatibleBaseUrl('https://api.anthropic.com')).toBe(false);
expect(isAnthropicCompatibleBaseUrl('https://api-staging.anthropic.com')).toBe(false);
expect(isAnthropicCompatibleBaseUrl('http://token@localhost:1234')).toBe(false);
expect(isAnthropicCompatibleBaseUrl('not a url')).toBe(false);
expect(isAnthropicCompatibleBaseUrl('')).toBe(false);
});
it('requires both compatible base URL and auth token for compatible auth token env', () => {
expect(
hasAnthropicCompatibleAuthTokenEnv({
ANTHROPIC_BASE_URL: 'http://localhost:1234',
ANTHROPIC_AUTH_TOKEN: 'local-token',
})
).toBe(true);
expect(
hasAnthropicCompatibleAuthTokenEnv({
ANTHROPIC_BASE_URL: 'http://localhost:1234',
ANTHROPIC_AUTH_TOKEN: ' ',
})
).toBe(false);
expect(
hasAnthropicCompatibleAuthTokenEnv({
ANTHROPIC_BASE_URL: 'https://api.anthropic.com',
ANTHROPIC_AUTH_TOKEN: 'stale-token',
})
).toBe(false);
});
it('preserves provider-specific direct restart env while resetting provider selection flags', () => {
const assignments = buildDirectTmuxRestartEnvAssignments(
{
CODEX_HOME: '/tmp/codex home',
CLAUDE_CODE_USE_GEMINI: '1',
CLAUDE_CODE_ENTRY_PROVIDER: 'gemini',
CLAUDE_CODE_CODEX_BACKEND: 'codex-native',
},
'codex'
);
expect(assignments).toContain("CLAUDECODE='1'");
expect(assignments).toContain("CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS='1'");
expect(assignments).toContain("CODEX_HOME='/tmp/codex home'");
expect(assignments).toContain("CLAUDE_CODE_USE_GEMINI=''");
expect(assignments).toContain("CLAUDE_CODE_ENTRY_PROVIDER='codex'");
expect(assignments).toContain("CLAUDE_CODE_CODEX_BACKEND='codex-native'");
expect(assignments).toContain("CLAUDE_CODE_PROVIDER_MANAGED_BY_HOST='1'");
});
it('preserves Anthropic-compatible tokens but blanks stale first-party auth tokens', () => {
const compatibleAssignments = buildDirectTmuxRestartEnvAssignments(
{
ANTHROPIC_BASE_URL: ' http://localhost:1234 ',
ANTHROPIC_AUTH_TOKEN: ' local-token ',
ANTHROPIC_API_KEY: '',
},
'anthropic'
);
expect(compatibleAssignments).toContain("ANTHROPIC_BASE_URL='http://localhost:1234'");
expect(compatibleAssignments).toContain("ANTHROPIC_AUTH_TOKEN='local-token'");
expect(compatibleAssignments).toContain("ANTHROPIC_API_KEY=''");
const firstPartyAssignments = buildDirectTmuxRestartEnvAssignments(
{
ANTHROPIC_BASE_URL: 'https://api.anthropic.com',
ANTHROPIC_AUTH_TOKEN: 'stale-token',
},
'anthropic'
);
expect(firstPartyAssignments).toContain("ANTHROPIC_BASE_URL='https://api.anthropic.com'");
expect(firstPartyAssignments).toContain("ANTHROPIC_AUTH_TOKEN=''");
expect(firstPartyAssignments).not.toContain('stale-token');
});
it('blanks competing Anthropic helper auth carriers for direct restart helper mode', () => {
const assignments = buildDirectTmuxRestartEnvAssignments(
{
CLAUDE_TEAM_ANTHROPIC_AUTH_MODE: 'api_key_helper',
CLAUDE_TEAM_ANTHROPIC_API_KEY_HELPER_SETTINGS_PATH:
'/tmp/team-runtime-auth/demo/runtime-settings-anthropic.json',
ANTHROPIC_API_KEY: 'sk-ant-direct-restart-should-not-leak',
ANTHROPIC_AUTH_TOKEN: 'direct-restart-token-should-not-leak',
CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR: '3',
CLAUDE_CODE_OAUTH_TOKEN: 'direct-restart-oauth-token-should-not-leak',
CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR: '4',
},
'anthropic'
);
expect(assignments).toContain("CLAUDE_TEAM_ANTHROPIC_AUTH_MODE='api_key_helper'");
expect(assignments).toContain(
"CLAUDE_TEAM_ANTHROPIC_API_KEY_HELPER_SETTINGS_PATH='/tmp/team-runtime-auth/demo/runtime-settings-anthropic.json'"
);
expect(assignments).toContain("ANTHROPIC_API_KEY=''");
expect(assignments).toContain("ANTHROPIC_AUTH_TOKEN=''");
expect(assignments).toContain("CLAUDE_CODE_API_KEY_FILE_DESCRIPTOR=''");
expect(assignments).toContain("CLAUDE_CODE_OAUTH_TOKEN=''");
expect(assignments).toContain("CLAUDE_CODE_OAUTH_TOKEN_FILE_DESCRIPTOR=''");
expect(assignments).not.toContain('sk-ant-direct-restart-should-not-leak');
expect(assignments).not.toContain('direct-restart-token-should-not-leak');
expect(assignments).not.toContain('direct-restart-oauth-token-should-not-leak');
});
it('builds a restart command that preserves cwd, binary and args quoting', () => {
const command = buildDirectTmuxRestartCommand({
cwd: '/tmp/team work',
env: { CODEX_HOME: '/tmp/codex' },
providerId: 'codex',
binaryPath: '/usr/local/bin/claude',
args: ['--model', "gpt worker's model"],
});
expect(command).toContain("cd '/tmp/team work' && env");
expect(command).toContain("CODEX_HOME='/tmp/codex'");
expect(command).toContain("'/usr/local/bin/claude' '--model' 'gpt worker'\\''s model'");
expect(command).toContain('__CLAUDE_TEAMMATE_EXIT__:%s');
});
});

View file

@ -0,0 +1,75 @@
import {
assertDeterministicBootstrapPrimaryMemberLimit,
assertOpenCodeNotLaunchedThroughLegacyProvisioning,
buildLargeDeterministicBootstrapWarning,
getMixedLaunchFallbackRecoveryError,
getOpenCodeMixedProviderProvisioningError,
isPureOpenCodeProvisioningRequest,
mergeProvisioningWarnings,
} from '@main/services/team/provisioning/TeamProvisioningLaunchCompatibility';
import { describe, expect, it } from 'vitest';
describe('TeamProvisioningLaunchCompatibility', () => {
it('classifies pure OpenCode legacy provisioning requests', () => {
expect(
isPureOpenCodeProvisioningRequest({
providerId: 'opencode',
members: [{ providerId: 'opencode' }, {}],
})
).toBe(true);
expect(
isPureOpenCodeProvisioningRequest({
providerId: 'codex',
members: [{ providerId: 'opencode' }],
})
).toBe(false);
});
it('blocks pure OpenCode legacy stream-json launch but allows supported side-lane mixed teams', () => {
expect(() =>
assertOpenCodeNotLaunchedThroughLegacyProvisioning({
providerId: 'opencode',
members: [{ providerId: 'opencode' }],
})
).toThrow('OpenCode team launch is not enabled in the legacy Claude stream-json provisioning path');
expect(() =>
assertOpenCodeNotLaunchedThroughLegacyProvisioning({
providerId: 'codex',
members: [{ providerId: 'opencode' }, { providerId: 'codex' }],
})
).not.toThrow();
});
it('keeps unsupported OpenCode-led mixed teams blocked with planner diagnostics', () => {
expect(() =>
assertOpenCodeNotLaunchedThroughLegacyProvisioning({
providerId: 'opencode',
members: [{ providerId: 'anthropic' }, { providerId: 'opencode' }],
})
).toThrow('Mixed teams with an OpenCode lead are not supported');
});
it('deduplicates provisioning warnings while preserving latest ordering', () => {
expect(mergeProvisioningWarnings(undefined, null)).toBeUndefined();
expect(mergeProvisioningWarnings(['alpha', 'beta'], 'alpha')).toEqual(['beta', 'alpha']);
expect(mergeProvisioningWarnings(['alpha'], 'beta')).toEqual(['alpha', 'beta']);
});
it('bounds deterministic bootstrap team size warnings and hard limits', () => {
expect(buildLargeDeterministicBootstrapWarning(8)).toBeNull();
expect(buildLargeDeterministicBootstrapWarning(9)).toContain('Large Codex team launch: 9');
expect(() => assertDeterministicBootstrapPrimaryMemberLimit(20)).not.toThrow();
expect(() => assertDeterministicBootstrapPrimaryMemberLimit(21)).toThrow(
'supports up to 20 primary teammates'
);
});
it('keeps user-facing compatibility error copy stable', () => {
expect(getOpenCodeMixedProviderProvisioningError()).toContain(
'outside the current support scope'
);
expect(getMixedLaunchFallbackRecoveryError()).toContain('missing stable member metadata');
});
});

View file

@ -0,0 +1,324 @@
import {
buildLaunchDiagnosticsFromRun,
buildWorkspaceTrustPreflightLaunchDiagnostic,
mentionsProcessTableUnavailable,
mergeLaunchDiagnosticItem,
} from '@main/services/team/provisioning/TeamProvisioningLaunchDiagnostics';
import { describe, expect, it } from 'vitest';
import type { WorkspaceTrustExecutionResult } from '@features/workspace-trust/main';
import type { MemberSpawnStatusEntry, TeamLaunchDiagnosticItem } from '@shared/types';
const NOW = '2026-05-24T00:00:00.000Z';
const nowIso = () => NOW;
function spawnEntry(overrides: Partial<MemberSpawnStatusEntry>): MemberSpawnStatusEntry {
return {
status: 'waiting',
launchState: 'starting',
updatedAt: NOW,
...overrides,
};
}
function buildRun(entries: Array<[string, Partial<MemberSpawnStatusEntry>]>, isLaunch = true) {
return {
isLaunch,
memberSpawnStatuses: new Map(
entries.map(([memberName, entry]) => [memberName, spawnEntry(entry)])
),
};
}
function workspaceTrustExecution(
overrides: Partial<WorkspaceTrustExecutionResult>
): WorkspaceTrustExecutionResult {
return {
id: 'claude-pty-workspace-trust',
provider: 'claude',
status: 'ok',
workspaceIds: ['workspace-1'],
...overrides,
};
}
describe('TeamProvisioningLaunchDiagnostics', () => {
it('skips non-launch and empty launch runs', () => {
expect(buildLaunchDiagnosticsFromRun(buildRun([], false), { nowIso })).toBeUndefined();
expect(buildLaunchDiagnosticsFromRun(buildRun([]), { nowIso })).toBeUndefined();
expect(
buildLaunchDiagnosticsFromRun({ isLaunch: true, memberSpawnStatuses: undefined }, { nowIso })
).toBeUndefined();
});
it('projects member spawn status entries into launch diagnostics without changing priority order', () => {
const diagnostics = buildLaunchDiagnosticsFromRun(
buildRun([
['Lead', { launchState: 'confirmed_alive' }],
[
'WorkerA',
{
launchState: 'failed_to_start',
error: 'fallback error',
hardFailureReason: 'hard failure reason',
agentToolAccepted: true,
},
],
[
'WorkerB',
{
launchState: 'runtime_pending_permission',
runtimeDiagnostic: 'waiting for user approval',
},
],
[
'WorkerC',
{
bootstrapStalled: true,
runtimeDiagnostic: 'bootstrap deadline exceeded',
livenessKind: 'runtime_process',
},
],
[
'WorkerD',
{
runtimeDiagnostic: 'process table temporarily unavailable',
livenessKind: 'shell_only',
},
],
['WorkerE', { livenessKind: 'shell_only', runtimeDiagnostic: 'tmux pane only' }],
[
'WorkerF',
{
livenessKind: 'runtime_process_candidate',
runtimeDiagnostic: 'process found but bootstrap missing',
},
],
['WorkerG', { livenessKind: 'runtime_process', runtimeDiagnostic: 'process found' }],
['WorkerH', { livenessKind: 'registered_only', runtimeDiagnostic: 'registered only' }],
['WorkerI', { livenessKind: 'stale_metadata', runtimeDiagnostic: 'stale metadata' }],
['WorkerJ', { livenessKind: 'not_found', runtimeDiagnostic: 'no runtime' }],
['WorkerK', { agentToolAccepted: true, runtimeDiagnostic: 'spawn accepted' }],
['WorkerL', {}],
]),
{ nowIso }
);
expect(diagnostics).toEqual([
{
id: 'Lead:bootstrap_confirmed',
memberName: 'Lead',
severity: 'info',
code: 'bootstrap_confirmed',
label: 'Lead - bootstrap confirmed',
observedAt: NOW,
},
{
id: 'WorkerA:bootstrap_stalled',
memberName: 'WorkerA',
severity: 'error',
code: 'bootstrap_stalled',
label: 'WorkerA - failed to start',
detail: 'hard failure reason',
observedAt: NOW,
},
{
id: 'WorkerB:permission_pending',
memberName: 'WorkerB',
severity: 'warning',
code: 'permission_pending',
label: 'WorkerB - awaiting permission',
detail: 'waiting for user approval',
observedAt: NOW,
},
{
id: 'WorkerC:bootstrap_stalled',
memberName: 'WorkerC',
severity: 'warning',
code: 'bootstrap_stalled',
label: 'WorkerC - bootstrap stalled',
detail: 'bootstrap deadline exceeded',
observedAt: NOW,
},
{
id: 'WorkerD:process_table_unavailable',
memberName: 'WorkerD',
severity: 'warning',
code: 'process_table_unavailable',
label: 'WorkerD - process table unavailable',
detail: 'process table temporarily unavailable',
observedAt: NOW,
},
{
id: 'WorkerE:tmux_shell_only',
memberName: 'WorkerE',
severity: 'warning',
code: 'tmux_shell_only',
label: 'WorkerE - shell only',
detail: 'tmux pane only',
observedAt: NOW,
},
{
id: 'WorkerF:runtime_process_candidate',
memberName: 'WorkerF',
severity: 'warning',
code: 'runtime_process_candidate',
label: 'WorkerF - bootstrap unconfirmed',
detail: 'process found but bootstrap missing',
observedAt: NOW,
},
{
id: 'WorkerG:runtime_process_detected',
memberName: 'WorkerG',
severity: 'info',
code: 'runtime_process_detected',
label: 'WorkerG - waiting for bootstrap',
detail: 'process found',
observedAt: NOW,
},
{
id: 'WorkerH:runtime_not_found',
memberName: 'WorkerH',
severity: 'warning',
code: 'runtime_not_found',
label: 'WorkerH - waiting for runtime',
detail: 'registered only',
observedAt: NOW,
},
{
id: 'WorkerI:runtime_not_found',
memberName: 'WorkerI',
severity: 'warning',
code: 'runtime_not_found',
label: 'WorkerI - waiting for runtime',
detail: 'stale metadata',
observedAt: NOW,
},
{
id: 'WorkerJ:runtime_not_found',
memberName: 'WorkerJ',
severity: 'warning',
code: 'runtime_not_found',
label: 'WorkerJ - waiting for runtime',
detail: 'no runtime',
observedAt: NOW,
},
{
id: 'WorkerK:spawn_accepted',
memberName: 'WorkerK',
severity: 'info',
code: 'spawn_accepted',
label: 'WorkerK - spawn accepted',
detail: 'spawn accepted',
observedAt: NOW,
},
]);
});
it('uses failed launch error when hard failure reason is absent', () => {
expect(
buildLaunchDiagnosticsFromRun(
buildRun([['Worker', { launchState: 'failed_to_start', error: 'spawn failed' }]]),
{ nowIso }
)?.[0]
).toMatchObject({
id: 'Worker:bootstrap_stalled',
detail: 'spawn failed',
});
});
it('recognizes process table unavailable diagnostics case-insensitively', () => {
expect(mentionsProcessTableUnavailable('Process table is currently unavailable')).toBe(true);
expect(mentionsProcessTableUnavailable('process runtime unavailable')).toBe(false);
expect(mentionsProcessTableUnavailable(undefined)).toBe(false);
});
it('builds workspace trust launch diagnostics for blocked, soft-failed, and completed preflight results', () => {
expect(
buildWorkspaceTrustPreflightLaunchDiagnostic(
workspaceTrustExecution({
status: 'blocked',
errorCode: 'workspace_trust_required',
errorMessage: ' trust must be accepted ',
evidence: ['fallback evidence'],
}),
{ nowIso }
)
).toEqual({
id: 'workspace-trust:preflight',
severity: 'error',
code: 'workspace_trust_preflight',
label: 'Workspace trust preflight blocked launch',
detail: 'trust must be accepted',
observedAt: NOW,
});
expect(
buildWorkspaceTrustPreflightLaunchDiagnostic(
workspaceTrustExecution({
status: 'soft_failed',
errorCode: 'workspace_trust_probe_failed',
evidence: ['fallback evidence'],
}),
{ nowIso }
)
).toMatchObject({
severity: 'warning',
label: 'Workspace trust preflight could not verify trust',
detail: 'workspace_trust_probe_failed',
});
expect(
buildWorkspaceTrustPreflightLaunchDiagnostic(
workspaceTrustExecution({
status: 'ok',
evidence: [' ', ' trusted from state probe '],
}),
{ nowIso }
)
).toMatchObject({
severity: 'info',
label: 'Workspace trust preflight completed',
detail: 'trusted from state probe',
});
});
it('omits cancelled workspace trust launch diagnostics', () => {
expect(
buildWorkspaceTrustPreflightLaunchDiagnostic(
workspaceTrustExecution({ status: 'cancelled' }),
{ nowIso }
)
).toBeNull();
});
it('merges launch diagnostic items by id without mutating existing diagnostics', () => {
const existing: TeamLaunchDiagnosticItem[] = [
{
id: 'old',
severity: 'info',
code: 'spawn_accepted',
label: 'Old',
observedAt: NOW,
},
{
id: 'same',
severity: 'warning',
code: 'runtime_not_found',
label: 'Previous',
observedAt: NOW,
},
];
const replacement: TeamLaunchDiagnosticItem = {
id: 'same',
severity: 'error',
code: 'bootstrap_stalled',
label: 'Replacement',
observedAt: NOW,
};
expect(mergeLaunchDiagnosticItem(existing, replacement)).toEqual([existing[0], replacement]);
expect(existing[1].label).toBe('Previous');
expect(mergeLaunchDiagnosticItem(undefined, replacement)).toEqual([replacement]);
});
});

View file

@ -0,0 +1,113 @@
import {
deriveMemberLaunchState,
isAutoClearableLaunchFailureReason,
isBootstrapCheckInTimeoutFailureReason,
isBootstrapInstructionPromptFailureReason,
isBootstrapMcpResourceReadFailureReason,
isConfigRegistrationFailureReason,
isLaunchCleanupBootstrapIncompleteFailureReason,
isLaunchGraceWindowFailureReason,
isNeverSpawnedDuringLaunchReason,
isOpenCodeBridgeLaunchFailureReason,
isProcessTableUnavailableFailureReason,
isRegisteredRuntimeMetadataFailureReason,
stripProcessTableUnavailableDiagnosticSuffix,
} from '@main/services/team/provisioning/TeamProvisioningLaunchFailurePolicy';
import { describe, expect, it } from 'vitest';
describe('TeamProvisioningLaunchFailurePolicy', () => {
it('recognizes exact launch failure reasons that are safe to auto-clear', () => {
expect(isNeverSpawnedDuringLaunchReason(' Teammate was never spawned during launch. ')).toBe(
true
);
expect(
isLaunchGraceWindowFailureReason('Teammate did not join within the launch grace window.')
).toBe(true);
expect(
isConfigRegistrationFailureReason(
'Teammate was not registered in config.json during launch. Persistent spawn failed.'
)
).toBe(true);
expect(isOpenCodeBridgeLaunchFailureReason('OpenCode bridge reported member launch failure')).toBe(
true
);
expect(
isRegisteredRuntimeMetadataFailureReason('registered runtime metadata without live process')
).toBe(true);
});
it('recognizes bootstrap-specific failure reasons without accepting unrelated text', () => {
expect(
isBootstrapMcpResourceReadFailureReason(
'resources/read failed for member_briefing: MCP error method not found'
)
).toBe(true);
expect(isBootstrapMcpResourceReadFailureReason('resources/read failed for other resource')).toBe(
false
);
expect(
isBootstrapCheckInTimeoutFailureReason(
'Teammate was registered but did not bootstrap-confirm before timeout.'
)
).toBe(true);
expect(
isBootstrapInstructionPromptFailureReason(
'You are bootstrapping into team atlas. Your first action is to call the MCP tool member_briefing.'
)
).toBe(true);
expect(
isLaunchCleanupBootstrapIncompleteFailureReason(
'Launch ended before teammate bootstrap completed. Runtime process was alive after bootstrap failure'
)
).toBe(true);
});
it('handles process-table unavailable reasons and suffixes conservatively', () => {
expect(isProcessTableUnavailableFailureReason('process table unavailable')).toBe(true);
expect(
isProcessTableUnavailableFailureReason(
'runtime pid could not be verified because process table is unavailable'
)
).toBe(true);
expect(isProcessTableUnavailableFailureReason('runtime failed; process table unavailable')).toBe(
false
);
expect(
stripProcessTableUnavailableDiagnosticSuffix(
'Teammate did not join within the launch grace window.; process table unavailable'
)
).toBe('Teammate did not join within the launch grace window.');
});
it('keeps auto-clear policy narrow but accepts known recoverable suffixes', () => {
expect(
isAutoClearableLaunchFailureReason('Teammate was never spawned during launch.')
).toBe(true);
expect(isAutoClearableLaunchFailureReason('process table is unavailable')).toBe(true);
expect(
isAutoClearableLaunchFailureReason(
'Teammate did not join within the launch grace window.; process table unavailable'
)
).toBe(true);
expect(isAutoClearableLaunchFailureReason('model not found')).toBe(false);
expect(isAutoClearableLaunchFailureReason(undefined)).toBe(false);
});
it('derives member launch state by the existing precedence order', () => {
expect(deriveMemberLaunchState({ skippedForLaunch: true, hardFailure: true })).toBe(
'skipped_for_launch'
);
expect(deriveMemberLaunchState({ hardFailure: true, bootstrapConfirmed: true })).toBe(
'failed_to_start'
);
expect(deriveMemberLaunchState({ bootstrapConfirmed: true })).toBe('confirmed_alive');
expect(deriveMemberLaunchState({ pendingPermissionRequestIds: ['req-1'] })).toBe(
'runtime_pending_permission'
);
expect(deriveMemberLaunchState({ runtimeAlive: true })).toBe('runtime_pending_bootstrap');
expect(deriveMemberLaunchState({ agentToolAccepted: true })).toBe(
'runtime_pending_bootstrap'
);
expect(deriveMemberLaunchState({})).toBe('starting');
});
});

View file

@ -0,0 +1,54 @@
import {
isOpenCodeOverlayMemberRemoved,
matchesExactTeamMemberName,
matchesMemberNameOrBase,
matchesObservedMemberNameForExpected,
matchesTeamMemberIdentity,
namesMatchCaseInsensitive,
} from '@main/services/team/provisioning/TeamProvisioningMemberIdentity';
import { describe, expect, it } from 'vitest';
describe('TeamProvisioningMemberIdentity', () => {
it('matches a member name against its auto-suffixed variants', () => {
expect(matchesMemberNameOrBase('Builder', 'Builder')).toBe(true);
expect(matchesMemberNameOrBase('Builder-2', 'Builder')).toBe(true);
expect(matchesMemberNameOrBase('Builder-10', 'Builder')).toBe(true);
expect(matchesMemberNameOrBase('Builder-1', 'Builder')).toBe(false);
expect(matchesMemberNameOrBase('Builder 2', 'Builder')).toBe(false);
expect(matchesMemberNameOrBase('Builder-2', 'builder')).toBe(false);
expect(matchesMemberNameOrBase('Reviewer-2', 'Builder')).toBe(false);
});
it('matches team member identity in either direction', () => {
expect(matchesTeamMemberIdentity('Builder-2', 'Builder')).toBe(true);
expect(matchesTeamMemberIdentity('Builder', 'Builder-2')).toBe(true);
expect(matchesTeamMemberIdentity('Builder-2', 'Builder-3')).toBe(false);
});
it('keeps observed-name matching one-directional', () => {
expect(matchesObservedMemberNameForExpected('Builder-2', 'Builder')).toBe(true);
expect(matchesObservedMemberNameForExpected('Builder', 'Builder-2')).toBe(false);
});
it('matches exact team member names case-insensitively after trimming', () => {
expect(matchesExactTeamMemberName(' Builder ', 'builder')).toBe(true);
expect(matchesExactTeamMemberName('', 'builder')).toBe(false);
expect(matchesExactTeamMemberName('Builder-2', 'Builder')).toBe(false);
});
it('detects removed OpenCode overlay members case-insensitively', () => {
expect(namesMatchCaseInsensitive(' Builder ', 'builder')).toBe(true);
expect(namesMatchCaseInsensitive('Builder-2', 'Builder')).toBe(false);
expect(
isOpenCodeOverlayMemberRemoved(
[
{ name: 'Reviewer', removedAt: undefined },
{ name: ' Builder ', removedAt: 123 },
],
'builder'
)
).toBe(true);
expect(isOpenCodeOverlayMemberRemoved([{ name: 'Builder' }], 'builder')).toBe(false);
expect(isOpenCodeOverlayMemberRemoved([{ removedAt: 123 }], 'builder')).toBe(false);
});
});

View file

@ -0,0 +1,190 @@
import {
buildRestartDuplicateUnconfirmedReason,
buildRestartGraceTimeoutReason,
buildRestartStillRunningReason,
createInitialMemberSpawnStatusEntry,
deriveTaskActivityPauseAt,
deriveTaskActivityResumeAt,
MEMBER_LAUNCH_GRACE_MS,
parseOptionalIsoMs,
shouldWarnOnMissingRegisteredMember,
shouldWarnOnUnreadableMemberAuditConfig,
summarizeMemberSpawnStatusRecord,
} from '@main/services/team/provisioning/TeamProvisioningMemberSpawnStatusPolicy';
import { describe, expect, it, vi } from 'vitest';
import type { MemberSpawnStatusEntry } from '@shared/types';
function makeStatus(overrides: Partial<MemberSpawnStatusEntry> = {}): MemberSpawnStatusEntry {
return {
status: 'offline',
launchState: 'starting',
agentToolAccepted: false,
runtimeAlive: false,
bootstrapConfirmed: false,
hardFailure: false,
updatedAt: '2026-01-01T00:00:00.000Z',
...overrides,
};
}
describe('TeamProvisioningMemberSpawnStatusPolicy', () => {
it('warns about unreadable audit config only after throttle and launch grace windows', () => {
const acceptedAt = '2026-01-01T00:00:00.000Z';
const nowMs = Date.parse(acceptedAt) + MEMBER_LAUNCH_GRACE_MS;
const memberSpawnStatuses = new Map([
['Builder', { agentToolAccepted: true, firstSpawnAcceptedAt: acceptedAt }],
]);
expect(
shouldWarnOnUnreadableMemberAuditConfig({
nowMs,
lastWarnAt: nowMs - 9_999,
expectedMembers: ['Builder'],
memberSpawnStatuses,
})
).toBe(false);
expect(
shouldWarnOnUnreadableMemberAuditConfig({
nowMs,
lastWarnAt: 0,
expectedMembers: ['Builder'],
memberSpawnStatuses,
})
).toBe(true);
expect(
shouldWarnOnUnreadableMemberAuditConfig({
nowMs,
lastWarnAt: 0,
expectedMembers: ['Reviewer'],
memberSpawnStatuses,
})
).toBe(false);
});
it('warns about missing registered members only after grace expiry and throttle', () => {
expect(
shouldWarnOnMissingRegisteredMember({
nowMs: 20_000,
lastWarnAt: 0,
graceExpired: false,
})
).toBe(false);
expect(
shouldWarnOnMissingRegisteredMember({
nowMs: 20_000,
lastWarnAt: 15_000,
graceExpired: true,
})
).toBe(false);
expect(
shouldWarnOnMissingRegisteredMember({
nowMs: 20_000,
lastWarnAt: 0,
graceExpired: true,
})
).toBe(true);
});
it('derives bounded task activity pause and resume timestamps', () => {
expect(parseOptionalIsoMs(undefined)).toBe(0);
expect(parseOptionalIsoMs('not-a-date')).toBe(0);
expect(parseOptionalIsoMs('2026-01-01T00:00:00.000Z')).toBe(
Date.parse('2026-01-01T00:00:00.000Z')
);
expect(
deriveTaskActivityPauseAt(
makeStatus({ lastHeartbeatAt: '2026-01-01T00:00:00.000Z' }),
'2026-01-01T00:00:10.000Z'
)
).toBe('2026-01-01T00:00:05.000Z');
expect(
deriveTaskActivityPauseAt(
makeStatus({ lastHeartbeatAt: 'not-a-date' }),
'2026-01-01T00:00:10.000Z'
)
).toBe('2026-01-01T00:00:05.000Z');
expect(
deriveTaskActivityPauseAt(
makeStatus({ updatedAt: 'not-a-date' }),
'2026-01-01T00:00:10.000Z'
)
).toBe('2026-01-01T00:00:10.000Z');
expect(
deriveTaskActivityResumeAt(
makeStatus({ updatedAt: '2026-01-01T00:00:05.000Z' }),
'2026-01-01T00:00:06.000Z',
'2026-01-01T00:00:10.000Z'
)
).toBe('2026-01-01T00:00:06.000Z');
expect(
deriveTaskActivityResumeAt(
makeStatus({ updatedAt: '2026-01-01T00:00:05.000Z' }),
'2026-01-01T00:00:04.000Z',
'2026-01-01T00:00:10.000Z'
)
).toBe('2026-01-01T00:00:10.000Z');
});
it('creates initial member spawn statuses with the current timestamp', () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-01-02T03:04:05.000Z'));
try {
expect(createInitialMemberSpawnStatusEntry()).toEqual({
status: 'offline',
launchState: 'starting',
agentToolAccepted: false,
runtimeAlive: false,
bootstrapConfirmed: false,
hardFailure: false,
updatedAt: '2026-01-02T03:04:05.000Z',
});
} finally {
vi.useRealTimers();
}
});
it('summarizes member spawn statuses across launch states and liveness kinds', () => {
expect(
summarizeMemberSpawnStatusRecord(['Confirmed', 'Missing'], {
Confirmed: makeStatus({ launchState: 'confirmed_alive' }),
Skipped: makeStatus({ launchState: 'skipped_for_launch' }),
Failed: makeStatus({ launchState: 'failed_to_start' }),
Permission: makeStatus({
launchState: 'runtime_pending_permission',
runtimeAlive: true,
}),
Shell: makeStatus({ livenessKind: 'shell_only' }),
Runtime: makeStatus({ livenessKind: 'runtime_process' }),
Candidate: makeStatus({ livenessKind: 'runtime_process_candidate' }),
MissingRuntime: makeStatus({ livenessKind: 'registered_only' }),
})
).toEqual({
confirmedCount: 1,
pendingCount: 6,
failedCount: 1,
skippedCount: 1,
runtimeAlivePendingCount: 1,
shellOnlyPendingCount: 1,
runtimeProcessPendingCount: 1,
runtimeCandidatePendingCount: 1,
noRuntimePendingCount: 1,
permissionPendingCount: 1,
});
});
it('builds restart status reasons without changing message text', () => {
expect(buildRestartStillRunningReason('Builder')).toContain(
'previous runtime still appears to be active'
);
expect(buildRestartDuplicateUnconfirmedReason('Builder')).toContain(
'duplicate_skipped without a reason'
);
expect(buildRestartDuplicateUnconfirmedReason('Builder', 'still running')).toContain(
'unrecognized reason "still running"'
);
expect(buildRestartGraceTimeoutReason('Builder')).toBe(
'Teammate "Builder" did not rejoin within the restart grace window.'
);
});
});

View file

@ -0,0 +1,129 @@
import {
boundOpenCodeAppManagedBriefingText,
filterStaleOpenCodeOverlayDiagnostics,
hasRealOpenCodeFailureDiagnostic,
hasRealOpenCodeLaunchDiagnostic,
hasStaleOpenCodeDiagnostics,
isFileLockTimeoutError,
isGenericOpenCodePersistedFailureReason,
isPersistedOpenCodeSecondaryLaneMember,
normalizeOpenCodePersistedFailureReason,
promoteOpenCodePersistedFailureReasonsFromDiagnostics,
selectOpenCodePersistedFailureReasonFromDiagnostics,
} from '@main/services/team/provisioning/TeamProvisioningOpenCodeDiagnosticsPolicy';
import { createPersistedLaunchSnapshot } from '@main/services/team/TeamLaunchStateEvaluator';
import { describe, expect, it, vi } from 'vitest';
import type { PersistedTeamLaunchMemberState } from '@shared/types';
function makeMember(
overrides: Partial<PersistedTeamLaunchMemberState> = {}
): PersistedTeamLaunchMemberState {
return {
name: 'Builder',
providerId: 'opencode',
laneKind: 'secondary',
laneOwnerProviderId: 'opencode',
laneId: 'opencode-secondary',
launchState: 'failed_to_start',
agentToolAccepted: true,
runtimeAlive: false,
bootstrapConfirmed: false,
hardFailure: true,
hardFailureReason: 'OpenCode bridge reported member launch failure',
lastEvaluatedAt: '2026-01-01T00:00:00.000Z',
...overrides,
};
}
describe('TeamProvisioningOpenCodeDiagnosticsPolicy', () => {
it('recognizes only persisted OpenCode secondary lane members', () => {
expect(isPersistedOpenCodeSecondaryLaneMember(makeMember())).toBe(true);
expect(isPersistedOpenCodeSecondaryLaneMember(makeMember({ laneId: ' ' }))).toBe(false);
expect(isPersistedOpenCodeSecondaryLaneMember(makeMember({ laneKind: 'primary' }))).toBe(false);
expect(isPersistedOpenCodeSecondaryLaneMember(makeMember({ providerId: undefined }))).toBe(
false
);
});
it('keeps stale OpenCode diagnostics separate from real launch failures', () => {
expect(hasStaleOpenCodeDiagnostics(['No lane runtime evidence was committed'])).toBe(true);
expect(hasStaleOpenCodeDiagnostics(['OpenCode bridge reported member launch failure'])).toBe(
true
);
expect(hasStaleOpenCodeDiagnostics(['model not found in live OpenCode catalog'])).toBe(false);
expect(hasRealOpenCodeFailureDiagnostic('provider unavailable: quota exceeded')).toBe(true);
expect(
hasRealOpenCodeLaunchDiagnostic(
makeMember({ runtimeDiagnostic: 'OpenCode bridge reported member launch failure' })
)
).toBe(false);
expect(hasRealOpenCodeLaunchDiagnostic(makeMember({ hardFailureReason: 'model not found' }))).toBe(
true
);
});
it('redacts secrets and bounds app-managed briefing text', () => {
const normalized = normalizeOpenCodePersistedFailureReason(
' failed --api-key sk-abcdefghijklmnopqrstuvwxyz Bearer ABC123._+/=- '
);
expect(normalized).toBe('failed --api-key [redacted] Bearer [redacted]');
expect(isGenericOpenCodePersistedFailureReason('OpenCode bridge reported member launch failure')).toBe(
true
);
const longBriefing = `${'x'.repeat(12_005)} --token secret-token`;
const bounded = boundOpenCodeAppManagedBriefingText(longBriefing);
expect(bounded.length).toBeGreaterThan(12_000);
expect(bounded.length).toBeLessThanOrEqual(12_040);
expect(bounded.endsWith('\n[truncated app-managed briefing]')).toBe(true);
expect(bounded).not.toContain('secret-token');
});
it('promotes generic persisted failure reasons from specific diagnostics', () => {
vi.useFakeTimers();
vi.setSystemTime(new Date('2026-02-03T04:05:06.000Z'));
try {
const generic = makeMember({
diagnostics: [
'OpenCode secondary lane timing: 100ms',
'model not found in live OpenCode catalog',
],
});
expect(selectOpenCodePersistedFailureReasonFromDiagnostics(generic)).toBe(
'model not found in live OpenCode catalog'
);
const snapshot = createPersistedLaunchSnapshot({
teamName: 'demo',
expectedMembers: ['Builder'],
launchPhase: 'finished',
members: { Builder: generic },
updatedAt: '2026-01-01T00:00:00.000Z',
});
const promoted = promoteOpenCodePersistedFailureReasonsFromDiagnostics(snapshot);
expect(promoted?.members.Builder.hardFailureReason).toBe(
'model not found in live OpenCode catalog'
);
expect(promoted?.members.Builder.runtimeDiagnostic).toBe(
'model not found in live OpenCode catalog'
);
expect(promoted?.updatedAt).toBe('2026-02-03T04:05:06.000Z');
} finally {
vi.useRealTimers();
}
});
it('filters stale overlay diagnostics and recognizes file lock timeouts', () => {
expect(
filterStaleOpenCodeOverlayDiagnostics([
'No runtime evidence was committed',
'model not found',
])
).toEqual(['model not found']);
expect(isFileLockTimeoutError(new Error('File lock timeout while reading manifest'))).toBe(
true
);
expect(isFileLockTimeoutError('other failure')).toBe(false);
});
});

View file

@ -0,0 +1,492 @@
import {
appendDiagnosticOnce,
buildOpenCodeSecondaryLaneTimingDiagnostic,
buildOpenCodeUncommittedBootstrapDiagnostic,
collectOpenCodeSecondaryLaneFailureDiagnostics,
collectRuntimeLaunchFailureDiagnostics,
createUnexpectedMixedSecondaryLaneFailureResult,
downgradeUncommittedOpenCodeBootstrapEvidence,
formatOpenCodeLaneTimingMs,
getOpenCodeSecondaryBootstrapStallDiagnosticFromPersisted,
hasMaterializedOpenCodeRuntimeForBootstrap,
hasOpenCodeRuntimeEntryHandle,
hasOpenCodeRuntimeHandle,
hasOpenCodeRuntimeLivenessMarker,
hasRecoverableOpenCodeBootstrapDiagnostic,
isBootstrapMemberEvidenceCurrentForMember,
isDefinitiveOpenCodePreLaunchFailure,
isExplicitLegacyOpenCodeBootstrap,
isMaterializedOpenCodeSessionId,
isRecoverableOpenCodeBootstrapPendingLaunchResult,
isRecoverableOpenCodeRuntimeEvidence,
isRecoverablePersistedOpenCodeRuntimeCandidate,
isRecoverablePersistedOpenCodeTerminalRuntimeCandidate,
MEMBER_BOOTSTRAP_STALL_MS,
normalizeIsoTimestamp,
normalizeRecoverableOpenCodeBootstrapPendingLaunchResult,
OPENCODE_APP_MANAGED_BOOTSTRAP_PENDING_DIAGNOSTIC,
OPENCODE_APP_MANAGED_BOOTSTRAP_STALLED_DIAGNOSTIC,
OPENCODE_BOOTSTRAP_PENDING_DIAGNOSTIC,
promoteCommittedOpenCodeAppManagedBootstrapEvidence,
resolveOpenCodeBootstrapAcceptedAt,
selectOpenCodeSecondaryBootstrapStallDiagnostic,
shouldMarkPersistedOpenCodeBootstrapStalled,
summarizeRuntimeLaunchResultMembers,
} from '@main/services/team/provisioning/TeamProvisioningOpenCodeRuntimeEvidencePolicy';
import { describe, expect, it } from 'vitest';
import type {
TeamRuntimeLaunchResult,
TeamRuntimeMemberLaunchEvidence,
} from '@main/services/team/runtime/TeamRuntimeAdapter';
import type { PersistedTeamLaunchMemberState } from '@shared/types';
const acceptedAt = '2026-01-01T00:00:00.000Z';
const stalledAtMs = Date.parse(acceptedAt) + MEMBER_BOOTSTRAP_STALL_MS + 1;
function makePersisted(
overrides: Partial<PersistedTeamLaunchMemberState> = {}
): PersistedTeamLaunchMemberState {
return {
name: 'Builder',
providerId: 'opencode',
laneKind: 'secondary',
laneOwnerProviderId: 'opencode',
laneId: 'opencode-secondary',
launchState: 'runtime_pending_bootstrap',
agentToolAccepted: true,
runtimeAlive: false,
bootstrapConfirmed: false,
hardFailure: false,
firstSpawnAcceptedAt: acceptedAt,
lastEvaluatedAt: acceptedAt,
...overrides,
};
}
function makeEvidence(
overrides: Partial<TeamRuntimeMemberLaunchEvidence> = {}
): TeamRuntimeMemberLaunchEvidence {
return {
memberName: 'Builder',
providerId: 'opencode',
launchState: 'starting',
agentToolAccepted: false,
runtimeAlive: false,
bootstrapConfirmed: false,
hardFailure: false,
diagnostics: [],
...overrides,
};
}
function makeLaunchResult(
member: TeamRuntimeMemberLaunchEvidence = makeEvidence(),
overrides: Partial<TeamRuntimeLaunchResult> = {}
): TeamRuntimeLaunchResult {
return {
runId: 'run-1',
teamName: 'demo',
launchPhase: 'active',
teamLaunchState: 'partial_pending',
members: { [member.memberName]: member },
warnings: [],
diagnostics: [],
...overrides,
};
}
describe('TeamProvisioningOpenCodeRuntimeEvidencePolicy', () => {
it('formats timing diagnostics and appends diagnostics without duplicates', () => {
expect(formatOpenCodeLaneTimingMs(12.6)).toBe('13ms');
expect(formatOpenCodeLaneTimingMs(-4.4)).toBe('0ms');
expect(formatOpenCodeLaneTimingMs(Number.NaN)).toBe('n/a');
expect(appendDiagnosticOnce(['existing'], 'new')).toEqual(['existing', 'new']);
expect(appendDiagnosticOnce(['existing'], 'existing')).toEqual(['existing']);
expect(appendDiagnosticOnce(['existing'], null)).toEqual(['existing']);
expect(
buildOpenCodeSecondaryLaneTimingDiagnostic({
member: { name: 'Builder' },
queuedAtMs: 10,
launchStartedAtMs: 40,
launchFinishedAtMs: 145,
})
).toBe(
'OpenCode secondary lane timing: member=Builder queueWaitMs=30ms launchMs=105ms totalMs=135ms'
);
expect(
buildOpenCodeSecondaryLaneTimingDiagnostic({
member: { name: 'Builder' },
queuedAtMs: 10,
launchStartedAtMs: 40,
})
).toBeNull();
});
it('recognizes OpenCode runtime handles without accepting empty or failed sessions', () => {
expect(hasOpenCodeRuntimeHandle({ runtimePid: 12 })).toBe(true);
expect(hasOpenCodeRuntimeHandle({ runtimeSessionId: ' session-1 ' })).toBe(true);
expect(hasOpenCodeRuntimeHandle({ sessionId: 'session-2' })).toBe(true);
expect(hasOpenCodeRuntimeHandle({ runtimePid: 0, sessionId: ' ' })).toBe(false);
expect(hasOpenCodeRuntimeHandle({ sessionId: 'failed:session-2' })).toBe(false);
expect(hasOpenCodeRuntimeHandle({ runtimeSessionId: 'FAILED:session-3' })).toBe(false);
expect(hasOpenCodeRuntimeEntryHandle({ runtimeSessionId: 'failed:entry-session' })).toBe(false);
expect(hasOpenCodeRuntimeLivenessMarker({ livenessKind: 'runtime_process_candidate' })).toBe(
true
);
expect(hasOpenCodeRuntimeLivenessMarker({ livenessKind: 'registered_only' })).toBe(false);
});
it('collects launch diagnostics and detects definitive pre-launch failures', () => {
const result = makeLaunchResult(
makeEvidence({
launchState: 'failed_to_start',
hardFailure: true,
hardFailureReason: 'binary missing',
diagnostics: ['member diagnostic'],
}),
{ diagnostics: ['result diagnostic'] }
);
expect(collectRuntimeLaunchFailureDiagnostics(result, 'Builder')).toEqual([
'member diagnostic',
'binary missing',
'result diagnostic',
]);
expect(collectOpenCodeSecondaryLaneFailureDiagnostics(result, 'Builder', ['prefix'])).toEqual([
'prefix',
'member diagnostic',
'binary missing',
'result diagnostic',
]);
expect(
collectOpenCodeSecondaryLaneFailureDiagnostics(makeLaunchResult(), 'Missing', [])
).toEqual(['OpenCode bridge reported member launch failure']);
expect(isDefinitiveOpenCodePreLaunchFailure(result, 'Builder')).toBe(true);
expect(
isDefinitiveOpenCodePreLaunchFailure(
makeLaunchResult(
makeEvidence({
launchState: 'failed_to_start',
hardFailure: true,
diagnostics: ['outcome must be reconciled before retry'],
})
),
'Builder'
)
).toBe(false);
expect(
isDefinitiveOpenCodePreLaunchFailure(
makeLaunchResult(
makeEvidence({
launchState: 'failed_to_start',
hardFailure: true,
sessionId: 'runtime-session',
})
),
'Builder'
)
).toBe(false);
});
it('normalizes recoverable bootstrap-pending launch results', () => {
const result = makeLaunchResult(
makeEvidence({
sessionId: 'runtime-session',
diagnostics: ['member_briefing not connected'],
}),
{ diagnostics: ['result diagnostic'] }
);
expect(isMaterializedOpenCodeSessionId('runtime-session')).toBe(true);
expect(isMaterializedOpenCodeSessionId('failed:runtime-session')).toBe(false);
expect(isMaterializedOpenCodeSessionId('FAILED:runtime-session')).toBe(false);
expect(hasMaterializedOpenCodeRuntimeForBootstrap(result.members.Builder)).toBe(true);
expect(isRecoverableOpenCodeBootstrapPendingLaunchResult(result, 'Builder')).toBe(true);
const normalized = normalizeRecoverableOpenCodeBootstrapPendingLaunchResult(result, 'Builder', [
'extra diagnostic',
]);
expect(normalized.members.Builder.launchState).toBe('runtime_pending_bootstrap');
expect(normalized.members.Builder.runtimeAlive).toBe(true);
expect(normalized.members.Builder.hardFailure).toBe(false);
expect(normalized.members.Builder.diagnostics).toEqual([
'member_briefing not connected',
OPENCODE_BOOTSTRAP_PENDING_DIAGNOSTIC,
OPENCODE_APP_MANAGED_BOOTSTRAP_PENDING_DIAGNOSTIC,
'extra diagnostic',
]);
expect(normalized.diagnostics).toContain('result diagnostic');
expect(normalized.diagnostics).toContain(OPENCODE_BOOTSTRAP_PENDING_DIAGNOSTIC);
expect(normalized.teamLaunchState).toBe('partial_pending');
expect(normalizeRecoverableOpenCodeBootstrapPendingLaunchResult(result, 'Missing', [])).toBe(
result
);
});
it('summarizes launch result members and transforms bootstrap evidence', () => {
expect(
summarizeRuntimeLaunchResultMembers({
Builder: makeEvidence({ launchState: 'confirmed_alive' }),
Reviewer: makeEvidence({ memberName: 'Reviewer', launchState: 'confirmed_alive' }),
})
).toBe('clean_success');
expect(
summarizeRuntimeLaunchResultMembers({
Builder: makeEvidence({ launchState: 'confirmed_alive' }),
Reviewer: makeEvidence({
memberName: 'Reviewer',
launchState: 'failed_to_start',
hardFailure: true,
}),
})
).toBe('partial_failure');
expect(summarizeRuntimeLaunchResultMembers({})).toBe('partial_pending');
expect(
buildOpenCodeUncommittedBootstrapDiagnostic({
manifestEntryCount: 2,
manifestUpdatedAt: '2026-01-01T00:00:00.000Z',
fileNames: ['lane-a.json', 'lane-b.json'],
})
).toEqual([
'OpenCode bridge reported bootstrap confirmation, but no lane runtime evidence was committed.',
'OpenCode lane manifest entries: 2',
'OpenCode lane manifest updated at: 2026-01-01T00:00:00.000Z',
'OpenCode lane files: lane-a.json, lane-b.json',
]);
const downgraded = downgradeUncommittedOpenCodeBootstrapEvidence(
makeEvidence({
sessionId: 'runtime-session',
livenessKind: 'confirmed_bootstrap',
diagnostics: ['existing'],
}),
['new diagnostic']
);
expect(downgraded.launchState).toBe('runtime_pending_bootstrap');
expect(downgraded.livenessKind).toBe('runtime_process_candidate');
expect(downgraded.diagnostics).toEqual(['existing', 'new diagnostic']);
const promoted = promoteCommittedOpenCodeAppManagedBootstrapEvidence(
makeEvidence({ diagnostics: ['existing'] })
);
expect(promoted.launchState).toBe('confirmed_alive');
expect(promoted.bootstrapConfirmed).toBe(true);
expect(promoted.livenessKind).toBe('confirmed_bootstrap');
expect(promoted.diagnostics).toEqual([
'existing',
'OpenCode app-managed bootstrap evidence committed and read back.',
]);
});
it('builds unexpected mixed secondary lane failure results', () => {
const result = createUnexpectedMixedSecondaryLaneFailureResult({
runId: 'run-1',
teamName: 'demo',
memberName: 'Builder',
message: 'launch failed',
});
expect(result.launchPhase).toBe('finished');
expect(result.teamLaunchState).toBe('partial_failure');
expect(result.members.Builder).toMatchObject({
providerId: 'opencode',
launchState: 'failed_to_start',
hardFailure: true,
hardFailureReason: 'launch failed',
diagnostics: ['launch failed'],
});
expect(result.diagnostics).toEqual(['launch failed']);
});
it('recognizes runtime entry handles from pid, runtime session, or liveness', () => {
expect(hasOpenCodeRuntimeEntryHandle({ pid: 7 })).toBe(true);
expect(hasOpenCodeRuntimeEntryHandle({ runtimePid: 8 })).toBe(true);
expect(hasOpenCodeRuntimeEntryHandle({ runtimeSessionId: 'runtime-session' })).toBe(true);
expect(hasOpenCodeRuntimeEntryHandle({ livenessKind: 'permission_blocked' })).toBe(true);
expect(hasOpenCodeRuntimeEntryHandle({ pid: 0, runtimeSessionId: ' ' })).toBe(false);
});
it('keeps recoverable bootstrap diagnostics separate from real failures', () => {
expect(hasRecoverableOpenCodeBootstrapDiagnostic(['member_briefing not connected'])).toBe(true);
expect(hasRecoverableOpenCodeBootstrapDiagnostic(['runtime_bootstrap_checkin pending'])).toBe(
true
);
expect(hasRecoverableOpenCodeBootstrapDiagnostic(['provider unavailable: quota exceeded'])).toBe(
false
);
expect(hasRecoverableOpenCodeBootstrapDiagnostic([])).toBe(false);
});
it('classifies recoverable persisted OpenCode runtime candidates', () => {
expect(
isRecoverablePersistedOpenCodeRuntimeCandidate(makePersisted({ runtimeSessionId: 'rt-1' }))
).toBe(true);
expect(
isRecoverablePersistedOpenCodeRuntimeCandidate(
makePersisted({ pendingPermissionRequestIds: ['perm-1'] })
)
).toBe(true);
expect(
isRecoverablePersistedOpenCodeRuntimeCandidate(makePersisted({ agentToolAccepted: false }))
).toBe(false);
expect(
isRecoverablePersistedOpenCodeRuntimeCandidate(makePersisted({ skippedForLaunch: true }))
).toBe(false);
expect(
isRecoverablePersistedOpenCodeRuntimeCandidate(makePersisted({ laneKind: 'primary' }))
).toBe(false);
});
it('selects the earliest accepted-at timestamp from first spawn and diagnostics', () => {
expect(normalizeIsoTimestamp('2026-01-01T00:00:00Z')).toBe('2026-01-01T00:00:00.000Z');
expect(normalizeIsoTimestamp('not a date')).toBeNull();
expect(
resolveOpenCodeBootstrapAcceptedAt(
makePersisted({
firstSpawnAcceptedAt: '2026-01-01T00:10:00.000Z',
diagnostics: ['member_session_recorded at 2026-01-01T00:05:00.000Z'],
})
)
).toBe('2026-01-01T00:05:00.000Z');
expect(
resolveOpenCodeBootstrapAcceptedAt(
makePersisted({ firstSpawnAcceptedAt: 'not a date', diagnostics: ['noise'] })
)
).toBeUndefined();
});
it('builds legacy and app-managed bootstrap stall diagnostics', () => {
expect(isExplicitLegacyOpenCodeBootstrap({ bootstrapMode: 'model_tool_checkin' })).toBe(true);
expect(isExplicitLegacyOpenCodeBootstrap({ bootstrapMode: 'app_managed_context' })).toBe(false);
expect(
selectOpenCodeSecondaryBootstrapStallDiagnostic([
'OpenCode secondary lane timing: 100ms',
'member_briefing delivery pending',
])
).toBe('member_briefing delivery pending; runtime_bootstrap_checkin did not complete after 5 min.');
expect(getOpenCodeSecondaryBootstrapStallDiagnosticFromPersisted(makePersisted())).toBe(
OPENCODE_APP_MANAGED_BOOTSTRAP_STALLED_DIAGNOSTIC
);
expect(
getOpenCodeSecondaryBootstrapStallDiagnosticFromPersisted(
makePersisted({
bootstrapMode: 'model_tool_checkin',
diagnostics: ['member_briefing delivery pending'],
})
)
).toBe('member_briefing delivery pending; runtime_bootstrap_checkin did not complete after 5 min.');
expect(
getOpenCodeSecondaryBootstrapStallDiagnosticFromPersisted(
makePersisted({
bootstrapMode: 'model_tool_checkin',
runtimeDiagnostic: 'runtime_bootstrap_checkin timed out',
})
)
).toBe('runtime_bootstrap_checkin timed out');
});
it('marks persisted bootstrap as stalled only after threshold with recoverable evidence', () => {
expect(
shouldMarkPersistedOpenCodeBootstrapStalled(
makePersisted({ runtimeSessionId: 'runtime-session' }),
stalledAtMs
)
).toBe(true);
expect(
shouldMarkPersistedOpenCodeBootstrapStalled(
makePersisted({ diagnostics: ['member_briefing not connected'] }),
stalledAtMs
)
).toBe(true);
expect(
shouldMarkPersistedOpenCodeBootstrapStalled(
makePersisted({ runtimeSessionId: 'runtime-session' }),
Date.parse(acceptedAt) + MEMBER_BOOTSTRAP_STALL_MS - 1
)
).toBe(false);
expect(
shouldMarkPersistedOpenCodeBootstrapStalled(
makePersisted({ runtimeSessionId: 'runtime-session', hardFailureReason: 'model not found' }),
stalledAtMs
)
).toBe(false);
expect(
shouldMarkPersistedOpenCodeBootstrapStalled(
makePersisted({
runtimeSessionId: 'runtime-session',
pendingPermissionRequestIds: ['perm-1'],
}),
stalledAtMs
)
).toBe(false);
});
it('recognizes recoverable terminal persisted candidates and runtime evidence', () => {
expect(
isRecoverablePersistedOpenCodeTerminalRuntimeCandidate(
makePersisted({
launchState: 'failed_to_start',
hardFailure: true,
runtimeSessionId: 'runtime-session',
})
)
).toBe(true);
expect(
isRecoverablePersistedOpenCodeTerminalRuntimeCandidate(
makePersisted({ launchState: 'failed_to_start', hardFailure: true })
)
).toBe(false);
const evidence: Partial<TeamRuntimeMemberLaunchEvidence> = {
agentToolAccepted: true,
livenessKind: 'runtime_process',
};
expect(isRecoverableOpenCodeRuntimeEvidence(evidence as TeamRuntimeMemberLaunchEvidence)).toBe(
true
);
expect(isRecoverableOpenCodeRuntimeEvidence({ runtimeAlive: true } as TeamRuntimeMemberLaunchEvidence)).toBe(
true
);
expect(isRecoverableOpenCodeRuntimeEvidence(undefined)).toBe(false);
});
it('checks bootstrap evidence recency against the current member spawn boundary', () => {
const current = {
firstSpawnAcceptedAt: '2026-01-01T00:01:00.000Z',
lastEvaluatedAt: '2026-01-01T00:01:10.000Z',
};
expect(
isBootstrapMemberEvidenceCurrentForMember(
current,
{
firstSpawnAcceptedAt: '2026-01-01T00:01:01.000Z',
lastHeartbeatAt: '2026-01-01T00:01:02.000Z',
lastEvaluatedAt: '2026-01-01T00:01:03.000Z',
},
'acceptance'
)
).toBe(true);
expect(
isBootstrapMemberEvidenceCurrentForMember(
current,
{
firstSpawnAcceptedAt: '2026-01-01T00:00:30.000Z',
lastHeartbeatAt: '2026-01-01T00:00:40.000Z',
lastEvaluatedAt: '2026-01-01T00:00:50.000Z',
},
'confirmation'
)
).toBe(false);
expect(
isBootstrapMemberEvidenceCurrentForMember(
{ firstSpawnAcceptedAt: undefined, lastEvaluatedAt: undefined },
{
firstSpawnAcceptedAt: 'not-a-date',
lastHeartbeatAt: undefined,
lastRuntimeAliveAt: '2026-01-01T00:00:40.000Z',
lastEvaluatedAt: '2026-01-01T00:00:30.000Z',
},
'confirmation'
)
).toBe(true);
});
});

View file

@ -0,0 +1,213 @@
import {
buildRuntimeLaunchWarning,
getAnthropicFastModeDefault,
getConfiguredRuntimeBackend,
getPromptSizeSummary,
getTeamProviderLabel,
logRuntimeLaunchSnapshot,
} from '@main/services/team/provisioning/TeamProvisioningRuntimeDiagnostics';
import { describe, expect, it, vi } from 'vitest';
import type { GeminiRuntimeAuthState } from '@main/services/runtime/geminiRuntimeAuth';
import type { ProviderModelLaunchIdentity, TeamProviderId } from '@shared/types';
vi.mock('@main/services/infrastructure/ConfigManager', () => ({
ConfigManager: {
getInstance: vi.fn().mockReturnValue({
getConfig: vi.fn().mockReturnValue({
providerConnections: {
anthropic: {
fastModeDefault: true,
},
},
runtime: {
providerBackends: {
codex: 'codex-native',
gemini: 'cli-sdk',
},
},
}),
}),
},
}));
describe('TeamProvisioningRuntimeDiagnostics', () => {
it('keeps prompt size accounting stable for empty and multiline prompts', () => {
expect(getPromptSizeSummary('')).toEqual({ chars: 0, lines: 0 });
expect(getPromptSizeSummary('alpha\r\nbeta\ngamma')).toEqual({
chars: 'alpha\r\nbeta\ngamma'.length,
lines: 3,
});
});
it('labels supported team providers without leaking raw ids into diagnostics', () => {
const labels = new Map<TeamProviderId, string>([
['anthropic', 'Anthropic'],
['codex', 'Codex'],
['gemini', 'Gemini'],
['opencode', 'OpenCode'],
]);
for (const [providerId, label] of labels) {
expect(getTeamProviderLabel(providerId)).toBe(label);
}
});
it('reads configured runtime defaults through a narrow diagnostics adapter', () => {
expect(getAnthropicFastModeDefault()).toBe(true);
expect(getConfiguredRuntimeBackend('anthropic')).toBeNull();
expect(getConfiguredRuntimeBackend('opencode')).toBeNull();
expect(getConfiguredRuntimeBackend('codex')).toBe('codex-native');
expect(getConfiguredRuntimeBackend('gemini')).toBe('cli-sdk');
});
it('builds Codex launch warnings with explicit backend, prompt and env evidence', () => {
const warning = buildRuntimeLaunchWarning(
{
providerId: 'codex',
providerBackendId: 'codex-native',
model: 'gpt-5.4',
effort: 'high',
fastMode: 'on',
},
{
CLAUDE_CODE_USE_OPENAI: '1',
CLAUDE_CODE_ENTRY_PROVIDER: 'codex',
CLAUDE_CODE_CODEX_BACKEND: 'codex-native',
CLAUDE_TEAM_FORCE_PROCESS_TEAMMATES: '1',
},
{
promptSize: { chars: 12345, lines: 7 },
expectedMembersCount: 3,
}
);
expect(warning).toContain('Launch runtime: Codex');
expect(warning).toContain('gpt-5.4');
expect(warning).toContain('high');
expect(warning).toContain('fast on');
expect(warning).toContain('backend codex-native');
expect(warning).toContain('prompt 12,345 chars/7 lines');
expect(warning).toContain('members 3');
expect(warning).toContain(
'env USE_OPENAI, ENTRY_PROVIDER=codex, CODEX_BACKEND=codex-native, FORCE_PROCESS_TEAMMATES'
);
});
it('includes Gemini auth diagnostics only for Gemini launch warnings', () => {
const geminiRuntimeAuth: GeminiRuntimeAuthState = {
authenticated: true,
authMethod: 'adc_authorized_user',
resolvedBackend: 'cli-sdk',
projectId: 'agent-teams-dev',
statusMessage: null,
};
expect(
buildRuntimeLaunchWarning(
{
providerId: 'gemini',
providerBackendId: 'cli-sdk',
model: 'gemini-2.5-pro',
effort: undefined,
fastMode: undefined,
},
{
CLAUDE_CODE_USE_GEMINI: '1',
CLAUDE_CODE_GEMINI_BACKEND: 'cli-sdk',
},
{ geminiRuntimeAuth }
)
).toContain('auth adc_authorized_user/cli-sdk');
expect(
buildRuntimeLaunchWarning(
{
providerId: 'anthropic',
providerBackendId: undefined,
model: undefined,
effort: undefined,
fastMode: undefined,
},
{},
{ geminiRuntimeAuth }
)
).not.toContain('auth adc_authorized_user/cli-sdk');
});
it('logs a structured launch snapshot with nullable env fields and launch identity', () => {
const messages: string[] = [];
const launchIdentity: ProviderModelLaunchIdentity = {
providerId: 'codex',
providerBackendId: 'codex-native',
selectedModel: 'gpt-5.4',
selectedModelKind: 'explicit',
resolvedLaunchModel: 'gpt-5.4',
catalogId: null,
catalogSource: 'runtime',
catalogFetchedAt: null,
selectedEffort: 'medium',
resolvedEffort: 'medium',
selectedFastMode: 'on',
resolvedFastMode: true,
fastResolutionReason: 'explicit',
};
logRuntimeLaunchSnapshot(
{ info: (message) => messages.push(message) },
'atlas',
'/usr/local/bin/claude',
['--model', 'gpt-5.4'],
{
providerId: 'codex',
providerBackendId: 'codex-native',
model: 'gpt-5.4',
effort: 'medium',
fastMode: 'on',
},
{ CLAUDE_CODE_USE_OPENAI: '1' },
{
promptSize: { chars: 10, lines: 2 },
expectedMembersCount: 2,
launchIdentity,
}
);
expect(messages).toHaveLength(1);
const prefix = '[atlas] Launch runtime snapshot ';
expect(messages[0]?.startsWith(prefix)).toBe(true);
const snapshot = JSON.parse(messages[0]!.slice(prefix.length)) as {
providerId: string;
providerBackendId: string | null;
model: string | null;
effort: string | null;
fastMode: string | null;
configuredBackend: string | null;
promptSize: { chars: number; lines: number } | null;
expectedMembersCount: number | null;
launchIdentity: ProviderModelLaunchIdentity | null;
geminiRuntimeAuth: unknown;
env: Record<string, string | null>;
args: string[];
claudePath: string;
};
expect(snapshot).toMatchObject({
providerId: 'codex',
providerBackendId: 'codex-native',
model: 'gpt-5.4',
effort: 'medium',
fastMode: 'on',
configuredBackend: 'codex-native',
promptSize: { chars: 10, lines: 2 },
expectedMembersCount: 2,
launchIdentity,
geminiRuntimeAuth: null,
args: ['--model', 'gpt-5.4'],
claudePath: '/usr/local/bin/claude',
});
expect(snapshot.env.CLAUDE_CODE_USE_OPENAI).toBe('1');
expect(snapshot.env.CLAUDE_CODE_USE_GEMINI).toBeNull();
expect(snapshot.env.CLAUDE_CONFIG_DIR).toBeNull();
});
});

View file

@ -1,10 +1,9 @@
import { describe, expect, it } from 'vitest';
import {
getOpenCodeMixedProviderProvisioningError,
shouldWarnOnMissingRegisteredMember,
shouldWarnOnUnreadableMemberAuditConfig,
} from '@main/services/team/TeamProvisioningService';
import { describe, expect, it } from 'vitest';
describe('TeamProvisioningService audit warning policy', () => {
it('suppresses unreadable config warnings during the short post-accept grace window', () => {