fix(team): refine bootstrap and provider diagnostics
This commit is contained in:
parent
fb3d1ceb27
commit
452948b260
8 changed files with 198 additions and 19 deletions
|
|
@ -85,6 +85,7 @@ import { buildActionModeProtocol } from './actionModeInstructions';
|
||||||
import { atomicWriteAsync } from './atomicWrite';
|
import { atomicWriteAsync } from './atomicWrite';
|
||||||
import { peekAutoResumeService } from './AutoResumeService';
|
import { peekAutoResumeService } from './AutoResumeService';
|
||||||
import { ClaudeBinaryResolver } from './ClaudeBinaryResolver';
|
import { ClaudeBinaryResolver } from './ClaudeBinaryResolver';
|
||||||
|
import { getConfiguredCliCommandLabel } from './cliFlavor';
|
||||||
import { withFileLock } from './fileLock';
|
import { withFileLock } from './fileLock';
|
||||||
import {
|
import {
|
||||||
type ClassifiedMainProcessIdle,
|
type ClassifiedMainProcessIdle,
|
||||||
|
|
@ -1345,6 +1346,8 @@ ${buildCanonicalSendMessageExample({ to: leadName, summary: 'short update', mess
|
||||||
After member_briefing succeeds:
|
After member_briefing succeeds:
|
||||||
- Do NOT send a "ready", "online", "status accepted", or other acknowledgement-only message just to confirm you started successfully.
|
- Do NOT send a "ready", "online", "status accepted", or other acknowledgement-only message just to confirm you started successfully.
|
||||||
- If bootstrap succeeded and you have no task yet, stay silent and wait for task assignments.
|
- If bootstrap succeeded and you have no task yet, stay silent and wait for task assignments.
|
||||||
|
- If bootstrap succeeded and you have no task, produce ZERO assistant text for that turn and end it immediately after the successful tool result.
|
||||||
|
- Do NOT ask the user or the lead to send you a task ID, task description, or "next task" right after bootstrap.
|
||||||
- Only SendMessage the lead after bootstrap when there is a real blocker, a failed bootstrap, an explicit question, an urgent coordination need, or a completed task result to report.
|
- Only SendMessage the lead after bootstrap when there is a real blocker, a failed bootstrap, an explicit question, an urgent coordination need, or a completed task result to report.
|
||||||
- Never send raw tool output, JSON, dict/object dumps, Python-style structs, or internal state payloads to the lead or the user. If you need to report bootstrap/task/tool status, rewrite it as one short natural-language sentence.
|
- Never send raw tool output, JSON, dict/object dumps, Python-style structs, or internal state payloads to the lead or the user. If you need to report bootstrap/task/tool status, rewrite it as one short natural-language sentence.
|
||||||
- When you later receive work or reconnect after a restart, use task_briefing as your compact queue view. Use task_get when you need the full task context before starting a pending/needsFix task or when the in_progress briefing details are not enough.
|
- When you later receive work or reconnect after a restart, use task_briefing as your compact queue view. Use task_get when you need the full task context before starting a pending/needsFix task or when the in_progress briefing details are not enough.
|
||||||
|
|
@ -1415,6 +1418,8 @@ ${actionModeProtocol}
|
||||||
After member_briefing succeeds:
|
After member_briefing succeeds:
|
||||||
- Do NOT send a "ready", "online", "status accepted", or other acknowledgement-only message just to confirm you reconnected successfully.
|
- Do NOT send a "ready", "online", "status accepted", or other acknowledgement-only message just to confirm you reconnected successfully.
|
||||||
- If reconnect bootstrap succeeded and you have no immediate blocker or question, stay silent and continue with your queue.
|
- If reconnect bootstrap succeeded and you have no immediate blocker or question, stay silent and continue with your queue.
|
||||||
|
- If reconnect bootstrap succeeded and you have no immediate blocker, question, or task, produce ZERO assistant text for that turn and end it immediately.
|
||||||
|
- Do NOT ask the user or the lead to send you a task ID, task description, or "next task" right after reconnect bootstrap.
|
||||||
- Never send raw tool output, JSON, dict/object dumps, Python-style structs, or internal state payloads to the lead or the user. If you need to report bootstrap/task/tool status, rewrite it as one short natural-language sentence.
|
- Never send raw tool output, JSON, dict/object dumps, Python-style structs, or internal state payloads to the lead or the user. If you need to report bootstrap/task/tool status, rewrite it as one short natural-language sentence.
|
||||||
- Use task_briefing as your compact queue view.
|
- Use task_briefing as your compact queue view.
|
||||||
- If task_briefing shows any in_progress task, resume/finish those first. Call task_get only if you need more context than task_briefing already gave you.
|
- If task_briefing shows any in_progress task, resume/finish those first. Call task_get only if you need more context than task_briefing already gave you.
|
||||||
|
|
@ -12441,6 +12446,7 @@ export class TeamProvisioningService {
|
||||||
providerId: TeamProviderId | undefined = 'anthropic'
|
providerId: TeamProviderId | undefined = 'anthropic'
|
||||||
): Promise<{ warning?: string }> {
|
): Promise<{ warning?: string }> {
|
||||||
const resolvedProviderId = resolveTeamProviderId(providerId);
|
const resolvedProviderId = resolveTeamProviderId(providerId);
|
||||||
|
const cliCommandLabel = getConfiguredCliCommandLabel();
|
||||||
try {
|
try {
|
||||||
const versionProbe = await this.spawnProbe(
|
const versionProbe = await this.spawnProbe(
|
||||||
claudePath,
|
claudePath,
|
||||||
|
|
@ -12452,9 +12458,9 @@ export class TeamProvisioningService {
|
||||||
if (versionProbe.exitCode !== 0) {
|
if (versionProbe.exitCode !== 0) {
|
||||||
const errorText =
|
const errorText =
|
||||||
buildCombinedLogs(versionProbe.stdout, versionProbe.stderr) ||
|
buildCombinedLogs(versionProbe.stdout, versionProbe.stderr) ||
|
||||||
`Claude CLI exited with code ${versionProbe.exitCode ?? 'unknown'} during warm-up`;
|
`${cliCommandLabel} exited with code ${versionProbe.exitCode ?? 'unknown'} during warm-up`;
|
||||||
return {
|
return {
|
||||||
warning: `Claude CLI binary failed to start correctly. Details: ${errorText}`,
|
warning: `${cliCommandLabel} binary failed to start correctly. Details: ${errorText}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|
@ -12465,7 +12471,7 @@ export class TeamProvisioningService {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
warning: `Claude CLI binary failed to start. Details: ${message}`,
|
warning: `${cliCommandLabel} binary failed to start. Details: ${message}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -12527,7 +12533,7 @@ export class TeamProvisioningService {
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
warning:
|
warning:
|
||||||
'Preflight check for `claude -p` did not complete. ' +
|
`Preflight check for \`${cliCommandLabel} -p\` did not complete. ` +
|
||||||
`Proceeding anyway. Details: ${message}`,
|
`Proceeding anyway. Details: ${message}`,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
@ -12548,13 +12554,15 @@ export class TeamProvisioningService {
|
||||||
const hint = isAuthFailure
|
const hint = isAuthFailure
|
||||||
? resolvedProviderId === 'codex'
|
? resolvedProviderId === 'codex'
|
||||||
? 'Codex provider is not authenticated for `-p` mode. ' +
|
? 'Codex provider is not authenticated for `-p` mode. ' +
|
||||||
'Run `claude-multimodel auth login --provider codex` and retry.' +
|
`Authenticate Codex in ${cliCommandLabel} and retry.` +
|
||||||
(attempt > 1 ? ` (failed after ${attempt} attempts)` : '')
|
(attempt > 1 ? ` (failed after ${attempt} attempts)` : '')
|
||||||
: 'Claude CLI `-p` mode is not authenticated. ' +
|
: `${cliCommandLabel} \`-p\` mode is not authenticated. ` +
|
||||||
'Run `claude auth login` (or start `claude` and run `/login`) to authenticate. ' +
|
(cliCommandLabel === 'claude'
|
||||||
|
? 'Run `claude auth login` (or start `claude` and run `/login`) to authenticate. '
|
||||||
|
: `Authenticate Anthropic in ${cliCommandLabel} and retry. `) +
|
||||||
'For automation/headless use, set ANTHROPIC_API_KEY.' +
|
'For automation/headless use, set ANTHROPIC_API_KEY.' +
|
||||||
(attempt > 1 ? ` (failed after ${attempt} attempts)` : '')
|
(attempt > 1 ? ` (failed after ${attempt} attempts)` : '')
|
||||||
: `Claude CLI preflight check failed (exit code ${pingProbe.exitCode ?? 'unknown'}).`;
|
: `${cliCommandLabel} preflight check failed (exit code ${pingProbe.exitCode ?? 'unknown'}).`;
|
||||||
return { warning: hint };
|
return { warning: hint };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -12595,7 +12603,7 @@ export class TeamProvisioningService {
|
||||||
const targetCwd = cwd ?? process.cwd();
|
const targetCwd = cwd ?? process.cwd();
|
||||||
const probeResult = await this.getCachedOrProbeResult(targetCwd, 'anthropic');
|
const probeResult = await this.getCachedOrProbeResult(targetCwd, 'anthropic');
|
||||||
if (!probeResult?.claudePath) {
|
if (!probeResult?.claudePath) {
|
||||||
throw new Error('Claude CLI not found');
|
throw new Error(`${getConfiguredCliCommandLabel()} not found`);
|
||||||
}
|
}
|
||||||
const { env } = await this.buildProvisioningEnv();
|
const { env } = await this.buildProvisioningEnv();
|
||||||
const result = await this.spawnProbe(
|
const result = await this.spawnProbe(
|
||||||
|
|
@ -12608,7 +12616,7 @@ export class TeamProvisioningService {
|
||||||
const output = (result.stdout + '\n' + result.stderr).trim();
|
const output = (result.stdout + '\n' + result.stderr).trim();
|
||||||
if (!output) {
|
if (!output) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`claude --help returned empty output (exit code: ${String(result.exitCode)})`
|
`${getConfiguredCliCommandLabel()} --help returned empty output (exit code: ${String(result.exitCode)})`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
this.helpOutputCache = output;
|
this.helpOutputCache = output;
|
||||||
|
|
@ -12966,7 +12974,7 @@ export class TeamProvisioningService {
|
||||||
const timeoutHandle = setTimeout(() => {
|
const timeoutHandle = setTimeout(() => {
|
||||||
settled = true;
|
settled = true;
|
||||||
killProcessTree(child);
|
killProcessTree(child);
|
||||||
reject(new Error(`Timeout running: claude ${args.join(' ')}`));
|
reject(new Error(`Timeout running: ${getConfiguredCliCommandLabel()} ${args.join(' ')}`));
|
||||||
}, timeoutMs);
|
}, timeoutMs);
|
||||||
|
|
||||||
const maybeResolveEarly = (): void => {
|
const maybeResolveEarly = (): void => {
|
||||||
|
|
|
||||||
|
|
@ -41,3 +41,17 @@ export function getCliFlavorUiOptions(flavor: CliFlavor): CliFlavorUiOptions {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getCliFlavorCommandLabel(flavor: CliFlavor): string {
|
||||||
|
switch (flavor) {
|
||||||
|
case 'agent_teams_orchestrator':
|
||||||
|
return 'orchestrator-cli';
|
||||||
|
case 'claude':
|
||||||
|
default:
|
||||||
|
return 'claude';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getConfiguredCliCommandLabel(): string {
|
||||||
|
return getCliFlavorCommandLabel(getConfiguredCliFlavor());
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -135,7 +135,7 @@ function summarizeDetail(
|
||||||
) {
|
) {
|
||||||
return 'CLI binary could not be started';
|
return 'CLI binary could not be started';
|
||||||
}
|
}
|
||||||
if (lower.includes('preflight check for `claude -p` did not complete')) {
|
if (lower.includes('preflight check for `') && lower.includes('-p` did not complete')) {
|
||||||
return 'CLI preflight did not complete';
|
return 'CLI preflight did not complete';
|
||||||
}
|
}
|
||||||
if (lower.includes('not authenticated') || lower.includes('not logged in')) {
|
if (lower.includes('not authenticated') || lower.includes('not logged in')) {
|
||||||
|
|
|
||||||
|
|
@ -232,6 +232,50 @@ function createRuntimeDetailLines(result: TeamProvisioningPrepareResult): string
|
||||||
return [...(result.details ?? []), ...(result.warnings ?? [])];
|
return [...(result.details ?? []), ...(result.warnings ?? [])];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function extractTimedOutPreflightProbeModelId(detail: string): string | null {
|
||||||
|
const trimmed = detail.trim();
|
||||||
|
if (!trimmed) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
!trimmed.toLowerCase().includes('preflight check for `') ||
|
||||||
|
!trimmed.toLowerCase().includes('-p` did not complete')
|
||||||
|
) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const match = /--model\s+([^\s]+)/i.exec(trimmed);
|
||||||
|
return match?.[1]?.trim() || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function suppressSupersededRuntimeWarnings(params: {
|
||||||
|
runtimeDetailLines: string[];
|
||||||
|
runtimeWarnings: string[];
|
||||||
|
modelResultsById: Map<string, ProviderPrepareDiagnosticsModelResult>;
|
||||||
|
}): {
|
||||||
|
runtimeDetailLines: string[];
|
||||||
|
runtimeWarnings: string[];
|
||||||
|
} {
|
||||||
|
const suppressedEntries = new Set<string>();
|
||||||
|
|
||||||
|
for (const warning of params.runtimeWarnings) {
|
||||||
|
const probedModelId = extractTimedOutPreflightProbeModelId(warning);
|
||||||
|
if (!probedModelId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (params.modelResultsById.get(probedModelId)?.status !== 'ready') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
suppressedEntries.add(warning);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
runtimeDetailLines: params.runtimeDetailLines.filter(
|
||||||
|
(detail) => !suppressedEntries.has(detail)
|
||||||
|
),
|
||||||
|
runtimeWarnings: params.runtimeWarnings.filter((warning) => !suppressedEntries.has(warning)),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function resolveModelResultFromBatch(
|
function resolveModelResultFromBatch(
|
||||||
providerId: TeamProviderId,
|
providerId: TeamProviderId,
|
||||||
modelId: string,
|
modelId: string,
|
||||||
|
|
@ -351,7 +395,7 @@ export async function runProviderPrepareDiagnostics({
|
||||||
const modelLines = new Map<string, string>();
|
const modelLines = new Map<string, string>();
|
||||||
let completedCount = 0;
|
let completedCount = 0;
|
||||||
let hasFailure = false;
|
let hasFailure = false;
|
||||||
let hasNotes = runtimeWarnings.length > 0;
|
let hasNotes = false;
|
||||||
const modelWarnings: string[] = [];
|
const modelWarnings: string[] = [];
|
||||||
|
|
||||||
for (const modelId of orderedModelIds) {
|
for (const modelId of orderedModelIds) {
|
||||||
|
|
@ -436,7 +480,14 @@ export async function runProviderPrepareDiagnostics({
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const dedupedWarnings = Array.from(new Set([...runtimeWarnings, ...modelWarnings]));
|
const filteredRuntime = suppressSupersededRuntimeWarnings({
|
||||||
|
runtimeDetailLines,
|
||||||
|
runtimeWarnings,
|
||||||
|
modelResultsById,
|
||||||
|
});
|
||||||
|
const dedupedWarnings = Array.from(
|
||||||
|
new Set([...filteredRuntime.runtimeWarnings, ...modelWarnings])
|
||||||
|
);
|
||||||
const selectedModelResultsById = Object.fromEntries(
|
const selectedModelResultsById = Object.fromEntries(
|
||||||
orderedModelIds
|
orderedModelIds
|
||||||
.map((modelId) => [modelId, modelResultsById.get(modelId)] as const)
|
.map((modelId) => [modelId, modelResultsById.get(modelId)] as const)
|
||||||
|
|
@ -446,9 +497,9 @@ export async function runProviderPrepareDiagnostics({
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: hasFailure ? 'failed' : hasNotes ? 'notes' : 'ready',
|
status: hasFailure ? 'failed' : hasNotes || dedupedWarnings.length > 0 ? 'notes' : 'ready',
|
||||||
details: [
|
details: [
|
||||||
...runtimeDetailLines,
|
...filteredRuntime.runtimeDetailLines,
|
||||||
...orderedModelIds.map((modelId) => modelLines.get(modelId) ?? ''),
|
...orderedModelIds.map((modelId) => modelLines.get(modelId) ?? ''),
|
||||||
],
|
],
|
||||||
warnings: dedupedWarnings,
|
warnings: dedupedWarnings,
|
||||||
|
|
|
||||||
|
|
@ -401,7 +401,7 @@ describe('TeamProvisioningService prepare/auth behavior', () => {
|
||||||
});
|
});
|
||||||
vi.spyOn(svc as any, 'spawnProbe').mockRejectedValue(
|
vi.spyOn(svc as any, 'spawnProbe').mockRejectedValue(
|
||||||
new Error(
|
new Error(
|
||||||
'Timeout running: claude -p Output only the single word PONG. --output-format text --model gpt-5.3-codex --max-turns 1 --no-session-persistence'
|
'Timeout running: orchestrator-cli -p Output only the single word PONG. --output-format text --model gpt-5.3-codex --max-turns 1 --no-session-persistence'
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -417,6 +417,26 @@ describe('TeamProvisioningService prepare/auth behavior', () => {
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('surfaces preflight timeouts with the orchestrator-cli label', async () => {
|
||||||
|
const svc = new TeamProvisioningService();
|
||||||
|
vi.spyOn(svc as any, 'getCachedOrProbeResult').mockResolvedValue({
|
||||||
|
claudePath: '/fake/claude',
|
||||||
|
authSource: 'codex_runtime',
|
||||||
|
warning:
|
||||||
|
'Preflight check for `orchestrator-cli -p` did not complete. Proceeding anyway. Details: Timeout running: orchestrator-cli -p Output only the single word PONG. --output-format text --model gpt-5.4-mini --max-turns 1 --no-session-persistence',
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await svc.prepareForProvisioning(tempRoot, {
|
||||||
|
forceFresh: true,
|
||||||
|
providerId: 'codex',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.ready).toBe(true);
|
||||||
|
expect(result.warnings).toContain(
|
||||||
|
'Preflight check for `orchestrator-cli -p` did not complete. Proceeding anyway. Details: Timeout running: orchestrator-cli -p Output only the single word PONG. --output-format text --model gpt-5.4-mini --max-turns 1 --no-session-persistence'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('maps ANTHROPIC_AUTH_TOKEN into ANTHROPIC_API_KEY for headless preflight', async () => {
|
it('maps ANTHROPIC_AUTH_TOKEN into ANTHROPIC_API_KEY for headless preflight', async () => {
|
||||||
const svc = new TeamProvisioningService();
|
const svc = new TeamProvisioningService();
|
||||||
vi.mocked(resolveInteractiveShellEnv).mockResolvedValue({
|
vi.mocked(resolveInteractiveShellEnv).mockResolvedValue({
|
||||||
|
|
|
||||||
|
|
@ -365,6 +365,20 @@ describe('TeamProvisioningService prompt content (solo mode discipline)', () =>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('add-member spawn prompt explicitly forbids no-task bootstrap chatter', () => {
|
||||||
|
const prompt = buildAddMemberSpawnMessage('my-team', 'My Team', 'team-lead', {
|
||||||
|
name: 'alice',
|
||||||
|
role: 'developer',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(prompt).toContain(
|
||||||
|
'If bootstrap succeeded and you have no task, produce ZERO assistant text for that turn and end it immediately after the successful tool result.'
|
||||||
|
);
|
||||||
|
expect(prompt).toContain(
|
||||||
|
'Do NOT ask the user or the lead to send you a task ID, task description, or "next task" right after bootstrap.'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('launchTeam hydration prompt includes task-comment handling guidance by default', async () => {
|
it('launchTeam hydration prompt includes task-comment handling guidance by default', async () => {
|
||||||
const teamName = 'forward-live-team';
|
const teamName = 'forward-live-team';
|
||||||
const teamDir = path.join(tempTeamsBase, teamName);
|
const teamDir = path.join(tempTeamsBase, teamName);
|
||||||
|
|
@ -502,7 +516,6 @@ describe('TeamProvisioningService prompt content (solo mode discipline)', () =>
|
||||||
expect(prompt).toContain(
|
expect(prompt).toContain(
|
||||||
'Correct flow: finish implementation on #X -> task_complete #X -> review_request #X -> reviewer runs review_start #X -> reviewer runs review_approve or review_request_changes on #X.'
|
'Correct flow: finish implementation on #X -> task_complete #X -> review_request #X -> reviewer runs review_start #X -> reviewer runs review_approve or review_request_changes on #X.'
|
||||||
);
|
);
|
||||||
|
|
||||||
await svc.cancelProvisioning(runId);
|
await svc.cancelProvisioning(runId);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -128,4 +128,36 @@ describe('ProvisioningProviderStatusList', () => {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('normalizes generic preflight timeout notes without depending on a hardcoded CLI name', async () => {
|
||||||
|
vi.stubGlobal('IS_REACT_ACT_ENVIRONMENT', true);
|
||||||
|
const host = document.createElement('div');
|
||||||
|
document.body.appendChild(host);
|
||||||
|
const root = createRoot(host);
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
root.render(
|
||||||
|
React.createElement(ProvisioningProviderStatusList, {
|
||||||
|
checks: [
|
||||||
|
{
|
||||||
|
providerId: 'codex',
|
||||||
|
status: 'notes',
|
||||||
|
backendSummary: 'Default adapter',
|
||||||
|
details: [
|
||||||
|
'Preflight check for `orchestrator-cli -p` did not complete. Proceeding anyway. Details: Timeout running: orchestrator-cli -p Output only the single word PONG. --output-format text --model gpt-5.4-mini --max-turns 1 --no-session-persistence',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
);
|
||||||
|
await Promise.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(host.textContent).toContain('Codex (Default adapter): CLI preflight did not complete');
|
||||||
|
|
||||||
|
await act(async () => {
|
||||||
|
root.unmount();
|
||||||
|
await Promise.resolve();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -196,7 +196,7 @@ describe('runProviderPrepareDiagnostics', () => {
|
||||||
ready: true,
|
ready: true,
|
||||||
message: 'CLI is warmed up and ready to launch',
|
message: 'CLI is warmed up and ready to launch',
|
||||||
warnings: [
|
warnings: [
|
||||||
'Selected model gpt-5.3-codex could not be verified. Timeout running: claude -p Output only the single word PONG. --output-format text --model gpt-5.3-codex --max-turns 1 --no-session-persistence',
|
'Selected model gpt-5.3-codex could not be verified. Timeout running: orchestrator-cli -p Output only the single word PONG. --output-format text --model gpt-5.3-codex --max-turns 1 --no-session-persistence',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
@ -372,4 +372,45 @@ describe('runProviderPrepareDiagnostics', () => {
|
||||||
'gpt-5.2-codex',
|
'gpt-5.2-codex',
|
||||||
], undefined);
|
], undefined);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('suppresses a timed out runtime preflight note when that same model later verifies', async () => {
|
||||||
|
const prepareProvisioning = vi.fn<
|
||||||
|
(
|
||||||
|
cwd?: string,
|
||||||
|
providerId?: 'anthropic' | 'codex' | 'gemini',
|
||||||
|
providerIds?: ('anthropic' | 'codex' | 'gemini')[],
|
||||||
|
selectedModels?: string[]
|
||||||
|
) => Promise<TeamProvisioningPrepareResult>
|
||||||
|
>((_, __, ___, selectedModels) => {
|
||||||
|
if (!selectedModels || selectedModels.length === 0) {
|
||||||
|
return Promise.resolve({
|
||||||
|
ready: true,
|
||||||
|
message: 'CLI is ready to launch (see notes)',
|
||||||
|
warnings: [
|
||||||
|
'Preflight check for `orchestrator-cli -p` did not complete. Proceeding anyway. Details: Timeout running: orchestrator-cli -p Output only the single word PONG. --output-format text --model gpt-5.4-mini --max-turns 1 --no-session-persistence',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.resolve({
|
||||||
|
ready: true,
|
||||||
|
message: 'CLI is warmed up and ready to launch',
|
||||||
|
details: [
|
||||||
|
'Selected model gpt-5.4-mini verified for launch.',
|
||||||
|
'Selected model gpt-5.4 verified for launch.',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await runProviderPrepareDiagnostics({
|
||||||
|
cwd: '/tmp/project',
|
||||||
|
providerId: 'codex',
|
||||||
|
selectedModelIds: ['gpt-5.4-mini', 'gpt-5.4'],
|
||||||
|
prepareProvisioning,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result.status).toBe('ready');
|
||||||
|
expect(result.warnings).toEqual([]);
|
||||||
|
expect(result.details).toEqual(['5.4 Mini - verified', '5.4 - verified']);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue