180 lines
6.5 KiB
TypeScript
180 lines
6.5 KiB
TypeScript
import {
|
|
deriveMemberLaunchState,
|
|
isAutoClearableLaunchFailureReason,
|
|
isBootstrapCheckInTimeoutFailureReason,
|
|
isBootstrapInstructionPromptFailureReason,
|
|
isCliProvisionedButNotAliveFailureReason,
|
|
isBootstrapMcpResourceReadFailureReason,
|
|
isConfigRegistrationFailureReason,
|
|
isLaunchCleanupBootstrapIncompleteFailureReason,
|
|
isLaunchGraceWindowFailureReason,
|
|
isNeverSpawnedDuringLaunchReason,
|
|
isOpenCodeBridgeLaunchFailureReason,
|
|
isProcessTableUnavailableFailureReason,
|
|
isProvisionedButNotAliveFailureReason,
|
|
isRegisteredRuntimeMetadataFailureReason,
|
|
stripProcessTableUnavailableDiagnosticSuffix,
|
|
} from '@main/services/team/provisioning/TeamProvisioningLaunchFailurePolicy';
|
|
import { isBootstrapConfirmedProvisionedButNotAliveFailure } from '@shared/utils/teamLaunchFailureReason';
|
|
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);
|
|
expect(
|
|
isProvisionedButNotAliveFailureReason(
|
|
'CLI process exited (code 1) \u2014 team provisioned but not alive'
|
|
)
|
|
).toBe(true);
|
|
expect(
|
|
isProvisionedButNotAliveFailureReason(
|
|
'CLI process exited (code unknown) - team provisioned but not alive; process table unavailable'
|
|
)
|
|
).toBe(true);
|
|
expect(
|
|
isCliProvisionedButNotAliveFailureReason(
|
|
'CLI process exited (code ?) - team provisioned but not alive'
|
|
)
|
|
).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(
|
|
'CLI process exited (code 1) \u2014 team provisioned but not alive'
|
|
)
|
|
).toBe(false);
|
|
expect(isAutoClearableLaunchFailureReason('model not found')).toBe(false);
|
|
expect(isAutoClearableLaunchFailureReason()).toBe(false);
|
|
});
|
|
|
|
it('requires bootstrap proof before treating provisioned-but-not-alive as healed', () => {
|
|
const reason = 'CLI process exited (code 1) \u2014 team provisioned but not alive';
|
|
|
|
expect(
|
|
isBootstrapConfirmedProvisionedButNotAliveFailure({
|
|
status: 'error',
|
|
launchState: 'failed_to_start',
|
|
hardFailure: true,
|
|
error: reason,
|
|
bootstrapConfirmed: true,
|
|
})
|
|
).toBe(true);
|
|
|
|
expect(
|
|
isBootstrapConfirmedProvisionedButNotAliveFailure({
|
|
status: 'error',
|
|
launchState: 'failed_to_start',
|
|
hardFailure: true,
|
|
hardFailureReason: 'model not found',
|
|
error: reason,
|
|
bootstrapConfirmed: true,
|
|
})
|
|
).toBe(false);
|
|
|
|
expect(
|
|
isBootstrapConfirmedProvisionedButNotAliveFailure({
|
|
status: 'error',
|
|
launchState: 'failed_to_start',
|
|
hardFailure: true,
|
|
hardFailureReason: reason,
|
|
bootstrapConfirmed: false,
|
|
livenessKind: 'registered_only',
|
|
})
|
|
).toBe(false);
|
|
|
|
expect(
|
|
isBootstrapConfirmedProvisionedButNotAliveFailure({
|
|
status: 'error',
|
|
launchState: 'failed_to_start',
|
|
hardFailure: true,
|
|
runtimeDiagnostic: reason,
|
|
bootstrapConfirmed: true,
|
|
})
|
|
).toBe(true);
|
|
});
|
|
|
|
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');
|
|
});
|
|
});
|