"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.runConfiguredWorkerRuntimeCycle = runConfiguredWorkerRuntimeCycle;
exports.runConfiguredWorkerContinuationIssueSelectionCycle = runConfiguredWorkerContinuationIssueSelectionCycle;
exports.runConfiguredWorkerIssueDraftCycle = runConfiguredWorkerIssueDraftCycle;
exports.runConfiguredWorkerTemplateDraftCycle = runConfiguredWorkerTemplateDraftCycle;
exports.runConfiguredWorkerTemplateUpdateCycle = runConfiguredWorkerTemplateUpdateCycle;
exports.runConfiguredWorkerAiWorkflowOptionAutofillCycle = runConfiguredWorkerAiWorkflowOptionAutofillCycle;
exports.runConfiguredWorkerFollowupProposalReviewCycle = runConfiguredWorkerFollowupProposalReviewCycle;
exports.runConfiguredWorkerWorkflowTemplateFitReviewCycle = runConfiguredWorkerWorkflowTemplateFitReviewCycle;
exports.runConfiguredWorkerWorkflowPlanReviewCycle = runConfiguredWorkerWorkflowPlanReviewCycle;
exports.runConfiguredWorkerPullRequestReviewNecessityCycle = runConfiguredWorkerPullRequestReviewNecessityCycle;
exports.runConfiguredWorkerCatalogSearchSelectionCycle = runConfiguredWorkerCatalogSearchSelectionCycle;
exports.runConfiguredWorkerEventHookCycle = runConfiguredWorkerEventHookCycle;
exports.runConfiguredWorkerEventHookDraftCycle = runConfiguredWorkerEventHookDraftCycle;
exports.resolveWorkerRuntimePurposeForWorkflowMode = resolveWorkerRuntimePurposeForWorkflowMode;
// biome-ignore lint/nursery/noExcessiveLinesPerFile: worker runtime failover keeps candidate selection and cooldown handling in one module.
const availability_1 = require("../auth-profiles/availability");
const environment_1 = require("../auth-profiles/environment");
const kind_1 = require("../auth-profiles/kind");
const store_1 = require("../auth-profiles/store");
const control_plane_config_1 = require("../config/control-plane-config");
const command_1 = require("../core/command");
const required_mcp_1 = require("../workflows/required-mcp");
const required_skills_1 = require("../workflows/required-skills");
const errors_1 = require("./errors");
const types_1 = require("./types");
async function runConfiguredWorkerRuntimeCycle(params) {
    return runConfiguredWorkerOperation(params, (launcher) => launcher.run(params.input));
}
async function runConfiguredWorkerContinuationIssueSelectionCycle(params) {
    return runConfiguredWorkerOperation(params, (launcher) => launcher.runContinuationIssueSelection(params.input));
}
async function runConfiguredWorkerIssueDraftCycle(params) {
    return runConfiguredWorkerOperation(params, (launcher) => launcher.runIssueDraft(params.input));
}
async function runConfiguredWorkerTemplateDraftCycle(params) {
    return runConfiguredWorkerOperation(params, (launcher) => launcher.runTemplateDraft(params.input));
}
async function runConfiguredWorkerTemplateUpdateCycle(params) {
    return runConfiguredWorkerOperation(params, (launcher) => launcher.runTemplateUpdate(params.input));
}
async function runConfiguredWorkerAiWorkflowOptionAutofillCycle(params) {
    return runConfiguredWorkerOperation(params, (launcher) => launcher.runAiWorkflowOptionAutofill(params.input));
}
async function runConfiguredWorkerFollowupProposalReviewCycle(params) {
    return runConfiguredWorkerOperation(params, (launcher) => {
        const reviewLauncher = launcher;
        if (reviewLauncher.runFollowupProposalReview === undefined) {
            throw new errors_1.WorkerUnavailableError("Worker launcher does not support follow-up proposal review.");
        }
        return reviewLauncher.runFollowupProposalReview(params.input);
    });
}
async function runConfiguredWorkerWorkflowTemplateFitReviewCycle(params) {
    return runConfiguredWorkerOperation(params, (launcher) => launcher.runWorkflowTemplateFitReview(params.input));
}
async function runConfiguredWorkerWorkflowPlanReviewCycle(params) {
    return runConfiguredWorkerOperation(params, (launcher) => launcher.runWorkflowPlanReview(params.input));
}
async function runConfiguredWorkerPullRequestReviewNecessityCycle(params) {
    return runConfiguredWorkerOperation(params, (launcher) => launcher.runPullRequestReviewNecessity(params.input));
}
async function runConfiguredWorkerCatalogSearchSelectionCycle(params) {
    return runConfiguredWorkerOperation(params, (launcher) => {
        if (launcher.runCatalogSearchSelection === undefined) {
            throw new Error("Worker launcher does not support catalog search.");
        }
        return launcher.runCatalogSearchSelection(params.input);
    });
}
async function runConfiguredWorkerEventHookCycle(params) {
    return runConfiguredWorkerOperation({
        ...params,
        retryAfterAttemptedExecution: false,
    }, (launcher) => {
        if (launcher.runEventHook === undefined) {
            throw new errors_1.WorkerExecutionError("Worker event hook runtime is unavailable.");
        }
        return launcher.runEventHook(params.input);
    });
}
async function runConfiguredWorkerEventHookDraftCycle(params) {
    return runConfiguredWorkerOperation(params, (launcher) => {
        if (launcher.runEventHookDraft === undefined) {
            throw new errors_1.WorkerExecutionError("Worker event hook draft runtime is unavailable.");
        }
        return launcher.runEventHookDraft(params.input);
    });
}
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: failover policy is intentionally centralized so all worker launch modes share ordering and cooldown behavior.
async function runConfiguredWorkerOperation(params, runOperation) {
    const currentState = await (0, store_1.loadAuthProfileStateFile)(params.homeDirectory);
    const stateByProfileId = new Map(currentState.states.map((state) => [state.profileId, state]));
    const profilesByProvider = groupAuthProfilesByProvider(params.profiles);
    const runtimeConfig = resolveRuntimeConfigForInput(params.config, params.input);
    const requiredSkills = getRequiredSkillsForRuntimeInput(params.input);
    const requiredMcp = getRequiredMcpForRuntimeInput(params.input);
    const runnableWorkers = new Set(await resolveRunnableWorkersForRuntimeInput({
        ...params,
        config: runtimeConfig,
    }));
    const { candidates, activeCooldowns, authInvalidProfileIds } = await resolveCandidates(runtimeConfig, params.workerPurpose, profilesByProvider, stateByProfileId, runnableWorkers, params.now(), {
        repositoryRoot: getRepositoryRootForRuntimeInput(params.input),
        requiredSkills,
        requiredMcp,
        homeDirectory: params.homeDirectory,
    });
    const triedProfiles = [];
    let attemptedWorkerExecution = false;
    let lastAuthInvalidError = null;
    let workerDurationMs = 0;
    for (const candidate of candidates) {
        triedProfiles.push({
            workerId: candidate.workerId,
            workerName: candidate.workerName,
            ...(candidate.kind === "profile"
                ? { authProfileId: candidate.authProfile.id }
                : {}),
        });
        try {
            const launcher = params.createLauncher({
                workerName: candidate.workerName,
                authProfile: candidate.kind === "profile" ? candidate.authProfile : null,
                customWorker: candidate.customWorker,
            });
            const launchResult = await runOperation(launcher);
            workerDurationMs += readLaunchResultDurationMs(launchResult);
            return {
                kind: "success",
                workerId: candidate.workerId,
                workerName: candidate.workerName,
                ...(candidate.kind === "profile"
                    ? {
                        authProfileId: candidate.authProfile.id,
                        authProfileKind: candidate.authProfile.kind,
                    }
                    : {}),
                workerDurationMs,
                launchResult,
            };
        }
        catch (error) {
            if ((0, errors_1.hasAttemptedWorkerExecution)(error)) {
                workerDurationMs += (0, errors_1.readWorkerFailureDurationMs)(error);
                attemptedWorkerExecution = true;
            }
            if (params.retryAfterAttemptedExecution === false &&
                attemptedWorkerExecution) {
                if ((0, errors_1.isWorkerFailureError)(error)) {
                    error.attempted = true;
                    error.workerDurationMs = workerDurationMs;
                    throw error;
                }
                throw new errors_1.WorkerExecutionError(error instanceof Error ? error.message : String(error), {
                    cause: error instanceof Error ? error : undefined,
                    attempted: true,
                    workerDurationMs,
                });
            }
            const retriableFailure = await handleRetriableWorkerFailure({
                error,
                candidate,
                params,
                activeCooldowns,
            });
            if (retriableFailure) {
                if (retriableFailure.kind === "auth_invalid") {
                    lastAuthInvalidError = retriableFailure.error;
                }
                continue;
            }
            if ((0, errors_1.isWorkerFailureError)(error) && attemptedWorkerExecution) {
                error.attempted = true;
                error.workerDurationMs = workerDurationMs;
                throw error;
            }
            if (attemptedWorkerExecution) {
                throw new errors_1.WorkerExecutionError(error instanceof Error ? error.message : String(error), {
                    cause: error instanceof Error ? error : undefined,
                    attempted: true,
                    workerDurationMs,
                });
            }
            throw error;
        }
    }
    return finalizeWorkerOperationWithoutSuccess({
        params: { ...params, config: runtimeConfig },
        profilesByProvider,
        activeCooldowns,
        authInvalidProfileIds,
        candidates,
        triedProfiles,
        lastAuthInvalidError,
        attemptedWorkerExecution,
        workerDurationMs,
    });
}
function resolveRuntimeConfigForInput(config, input) {
    if (!("workflow" in input)) {
        return config;
    }
    return (0, control_plane_config_1.resolveIssueEffectiveConfigForWorkflowState)(config, input.workflow?.state ?? null);
}
function resolveWorkerRuntimePurposeForWorkflowMode(mode) {
    switch (mode) {
        case types_1.WORKER_RUN_MODES.workflowPlan:
        case types_1.WORKER_RUN_MODES.workflowSelect:
        case types_1.WORKER_RUN_MODES.workflowReconcileStepBatch:
        case types_1.WORKER_RUN_MODES.workflowClosing:
            return "thinking";
        default:
            return "default";
    }
}
async function resolveRunnableWorkersForRuntimeInput(params) {
    const { config, input } = params;
    const requiredSkills = getRequiredSkillsForRuntimeInput(input);
    const requiredMcp = getRequiredMcpForRuntimeInput(input);
    const repositoryRoot = getRepositoryRootForRuntimeInput(input);
    if (requiredSkills.length === 0 && requiredMcp.length === 0) {
        return (0, control_plane_config_1.resolveWorkersForPurpose)(config, params.workerPurpose);
    }
    if (!repositoryRoot) {
        return [];
    }
    const skillRunnableWorkers = await (0, required_skills_1.resolveRunnableWorkersForRequiredSkills)({
        repoRoot: repositoryRoot,
        workers: (0, control_plane_config_1.resolveWorkersForPurpose)(config, params.workerPurpose),
        authProfiles: params.profiles,
        ...(params.homeDirectory === undefined
            ? {}
            : { homeDirectory: params.homeDirectory }),
        skillRefs: requiredSkills,
    });
    return (0, required_mcp_1.resolveRunnableWorkersForRequiredMcp)({
        repoRoot: repositoryRoot,
        workers: skillRunnableWorkers,
        authProfiles: params.profiles,
        ...(params.homeDirectory === undefined
            ? {}
            : { homeDirectory: params.homeDirectory }),
        mcpRefs: requiredMcp,
    });
}
function getRepositoryRootForRuntimeInput(input) {
    return "repositoryRoot" in input ? input.repositoryRoot : null;
}
function getRequiredSkillsForRuntimeInput(input) {
    return "workflow" in input ? (input.workflow?.requiredSkills ?? []) : [];
}
function getRequiredMcpForRuntimeInput(input) {
    return "workflow" in input ? (input.workflow?.requiredMcp ?? []) : [];
}
function readLaunchResultDurationMs(result) {
    if (typeof result === "object" &&
        result !== null &&
        "workerDurationMs" in result &&
        typeof result.workerDurationMs === "number") {
        return result.workerDurationMs;
    }
    return 0;
}
async function handleRetriableWorkerFailure(input) {
    if (!(0, errors_1.isWorkerFailureError)(input.error)) {
        return null;
    }
    if (input.candidate.kind === "custom") {
        return input.error.failureKind !== errors_1.WORKER_FAILURE_KINDS.authInvalid &&
            input.error.failureKind !== errors_1.WORKER_FAILURE_KINDS.decisionInvalid
            ? { kind: "retry" }
            : null;
    }
    if (input.error.failureKind === errors_1.WORKER_FAILURE_KINDS.authInvalid) {
        const now = input.params.now();
        await (0, store_1.upsertAuthProfileState)(input.candidate.authProfile.id, {
            status: "auth_invalid",
            cooldownUntil: null,
            lastReason: resolveAuthInvalidReason(input.error),
            updatedAt: now.toISOString(),
        }, input.params.homeDirectory);
        return {
            kind: "auth_invalid",
            error: input.error,
        };
    }
    const isQuotaExhausted = input.error.failureKind === errors_1.WORKER_FAILURE_KINDS.quotaExhausted;
    const isWorkerCapacity = input.error.failureKind === errors_1.WORKER_FAILURE_KINDS.workerUnavailable &&
        input.error.attempted;
    if (!isQuotaExhausted && !isWorkerCapacity) {
        return null;
    }
    const now = input.params.now();
    const fallbackCooldownSec = isQuotaExhausted
        ? input.params.config.cooldownDefaultSecs.quotaExhausted
        : input.params.config.cooldownDefaultSecs.capacity;
    const lastReason = isQuotaExhausted
        ? errors_1.WORKER_FAILURE_KINDS.quotaExhausted
        : errors_1.WORKER_FAILURE_KINDS.workerUnavailable;
    const cooldownUntil = input.error.cooldownUntil ??
        new Date(now.getTime() + fallbackCooldownSec * 1_000).toISOString();
    const nextState = await (0, store_1.upsertAuthProfileState)(input.candidate.authProfile.id, {
        status: "cooldown",
        cooldownUntil,
        lastReason,
        updatedAt: input.params.now().toISOString(),
    }, input.params.homeDirectory);
    input.activeCooldowns.set(nextState.profileId, nextState);
    return {
        kind: "retry",
    };
}
async function resolveCandidates(config, workerPurpose, profilesByProvider, stateByProfileId, runnableWorkers, now, requiredCapabilityContext) {
    const candidates = [];
    const activeCooldowns = new Map();
    const authInvalidProfileIds = [];
    const seenProfileIds = new Set();
    for (const worker of (0, control_plane_config_1.resolveWorkersForPurpose)(config, workerPurpose)) {
        if (!runnableWorkers.has(worker)) {
            continue;
        }
        if (!(0, control_plane_config_1.isBuiltInWorkerName)(worker)) {
            const candidate = resolveCustomWorkerCandidate(config, worker);
            if (candidate !== null) {
                candidates.push(candidate);
            }
            continue;
        }
        const providerProfiles = profilesByProvider.get(worker) ?? [];
        for (const authProfile of providerProfiles) {
            const resolved = await resolveAuthProfileCandidate({
                worker,
                authProfile,
                seenProfileIds,
                state: stateByProfileId.get(authProfile.id) ?? null,
                now,
                requiredCapabilityContext,
            });
            switch (resolved.kind) {
                case "candidate":
                    candidates.push(resolved.candidate);
                    break;
                case "auth_invalid":
                    authInvalidProfileIds.push(authProfile.id);
                    break;
                case "cooldown":
                    activeCooldowns.set(authProfile.id, resolved.state);
                    break;
                case "skip":
                    break;
            }
        }
    }
    return { candidates, activeCooldowns, authInvalidProfileIds };
}
function resolveCustomWorkerCandidate(config, worker) {
    const customWorker = (0, control_plane_config_1.resolveCustomWorkerConfig)(config, worker);
    return customWorker === null
        ? null
        : {
            kind: "custom",
            workerId: worker,
            workerName: worker,
            authProfile: null,
            customWorker,
        };
}
async function resolveAuthProfileCandidate(input) {
    if (input.seenProfileIds.has(input.authProfile.id)) {
        return { kind: "skip" };
    }
    if (!(0, store_1.supportsWorkerAuthProfile)(input.worker, input.authProfile)) {
        return { kind: "skip" };
    }
    input.seenProfileIds.add(input.authProfile.id);
    if (input.state && (0, kind_1.isAuthProfileInvalid)(input.state)) {
        return { kind: "auth_invalid" };
    }
    if (input.state && (0, store_1.isAuthProfileOnCooldown)(input.state, input.now)) {
        return { kind: "cooldown", state: input.state };
    }
    const availability = await (0, availability_1.getSavedAuthAvailability)(input.authProfile);
    if (!availability.available) {
        return { kind: "skip" };
    }
    if (!(await authProfileCanRunRequiredCapabilities({
        worker: input.worker,
        authProfile: input.authProfile,
        repositoryRoot: input.requiredCapabilityContext.repositoryRoot,
        requiredSkills: input.requiredCapabilityContext.requiredSkills,
        requiredMcp: input.requiredCapabilityContext.requiredMcp,
        homeDirectory: input.requiredCapabilityContext.homeDirectory,
    }))) {
        return { kind: "skip" };
    }
    return {
        kind: "candidate",
        candidate: {
            kind: "profile",
            workerId: input.worker,
            workerName: input.worker,
            authProfile: input.authProfile,
            customWorker: null,
        },
    };
}
async function authProfileCanRunRequiredCapabilities(input) {
    if (input.requiredSkills.length === 0 && input.requiredMcp.length === 0) {
        return true;
    }
    if (!input.repositoryRoot) {
        return false;
    }
    const env = (0, environment_1.buildAuthProfileEnvironment)(input.authProfile, process.env);
    if (!env) {
        return false;
    }
    const runnableWorkers = await (0, required_skills_1.resolveRunnableWorkersForRequiredSkills)({
        repoRoot: input.repositoryRoot,
        workers: [input.worker],
        authProfiles: [input.authProfile],
        env,
        ...(input.homeDirectory === undefined
            ? {}
            : { homeDirectory: input.homeDirectory }),
        skillRefs: input.requiredSkills,
    });
    if (!runnableWorkers.includes(input.worker)) {
        return false;
    }
    const mcpRunnableWorkers = await (0, required_mcp_1.resolveRunnableWorkersForRequiredMcp)({
        repoRoot: input.repositoryRoot,
        workers: [input.worker],
        authProfiles: [input.authProfile],
        env,
        ...(input.homeDirectory === undefined
            ? {}
            : { homeDirectory: input.homeDirectory }),
        mcpRefs: input.requiredMcp,
    });
    return mcpRunnableWorkers.includes(input.worker);
}
function collectConfiguredCooldowns(config, workerPurpose, profilesByProvider, cooldownsByProfileId, now) {
    const collected = [];
    const seen = new Set();
    for (const { worker, providerProfiles } of getConfiguredProviderGroups(config, workerPurpose, profilesByProvider)) {
        for (const profile of providerProfiles) {
            if (seen.has(profile.id)) {
                continue;
            }
            if (!(0, store_1.supportsWorkerAuthProfile)(worker, profile)) {
                continue;
            }
            seen.add(profile.id);
            const state = cooldownsByProfileId.get(profile.id);
            if (!state || !(0, store_1.isAuthProfileOnCooldown)(state, now)) {
                continue;
            }
            collected.push({
                profileId: profile.id,
                cooldownUntil: state.cooldownUntil,
                lastReason: state.lastReason ?? null,
            });
        }
    }
    return collected;
}
function getConfiguredProviderGroups(config, workerPurpose, profilesByProvider) {
    return (0, control_plane_config_1.resolveWorkersForPurpose)(config, workerPurpose)
        .filter(control_plane_config_1.isBuiltInWorkerName)
        .map((worker) => ({
        worker,
        providerProfiles: profilesByProvider.get(worker) ?? [],
    }));
}
function findEarliestCooldownUntil(cooldowns) {
    const timestamps = cooldowns
        .map((cooldown) => cooldown.cooldownUntil)
        .filter((value) => value !== null)
        .map((value) => new Date(value).getTime())
        .filter((value) => Number.isFinite(value));
    if (timestamps.length === 0) {
        return null;
    }
    return new Date(Math.min(...timestamps)).toISOString();
}
function finalizeWorkerOperationWithoutSuccess(input) {
    if (input.lastAuthInvalidError) {
        if ((0, errors_1.isWorkerFailureError)(input.lastAuthInvalidError)) {
            input.lastAuthInvalidError.workerDurationMs = input.workerDurationMs;
        }
        throw input.lastAuthInvalidError;
    }
    const cooldowns = collectConfiguredCooldowns(input.params.config, input.params.workerPurpose, input.profilesByProvider, input.activeCooldowns, input.params.now());
    if (input.candidates.length === 0 &&
        cooldowns.length === 0 &&
        input.authInvalidProfileIds.length > 0) {
        throw new errors_1.WorkerAuthInvalidError("All configured auth profiles are invalid.", {
            attempted: false,
        });
    }
    return {
        kind: "deferred",
        reason: workerPurposeIncludesCustomWorker(input.params.config, input.params.workerPurpose)
            ? "all_workers_unavailable"
            : "all_profiles_exhausted",
        triedProfiles: input.triedProfiles,
        cooldowns,
        cooldownUntil: findEarliestCooldownUntil(cooldowns),
        attemptedWorkerExecution: input.attemptedWorkerExecution,
        workerDurationMs: input.attemptedWorkerExecution
            ? input.workerDurationMs
            : 0,
    };
}
function workerPurposeIncludesCustomWorker(config, workerPurpose) {
    return (0, control_plane_config_1.resolveWorkersForPurpose)(config, workerPurpose).some((worker) => !(0, control_plane_config_1.isBuiltInWorkerName)(worker));
}
function resolveAuthInvalidReason(error) {
    const commandError = error.cause instanceof command_1.CommandError ? error.cause : null;
    return summarizeAuthCommandFailure(commandError) ?? error.diagnosticMessage;
}
function summarizeAuthCommandFailure(error) {
    if (!error) {
        return null;
    }
    const text = [error.stderr, error.stdout].filter(Boolean).join("\n");
    const summarizedReasons = summarizeAuthReasonMarkers(text);
    if (summarizedReasons) {
        return summarizedReasons;
    }
    const lines = [error.stderr, error.stdout]
        .filter(Boolean)
        .flatMap((text) => text.split(/\r?\n/))
        .map((line) => line.trim())
        .filter((line) => line.length > 0);
    if (lines.length === 0) {
        return null;
    }
    return lines[0] ?? null;
}
function summarizeAuthReasonMarkers(text) {
    const specificAuthReasons = [
        { pattern: /\brefresh_token_reused\b/i, value: "refresh_token_reused" },
        { pattern: /\btoken_invalidated\b/i, value: "token_invalidated" },
        { pattern: /\binvalid api key\b/i, value: "invalid_api_key" },
        { pattern: /\binvalid token\b/i, value: "invalid_token" },
        { pattern: /\bauth(?:entication)? invalid\b/i, value: "auth_invalid" },
        {
            pattern: /\bauthentication failed\b/i,
            value: "authentication_failed",
        },
    ];
    const specificMatches = specificAuthReasons
        .filter((reason) => reason.pattern.test(text))
        .map((reason) => reason.value);
    if (specificMatches.length > 0) {
        return [...new Set(specificMatches)].join(", ");
    }
    return /\bunauthorized\b/i.test(text) ? "unauthorized" : null;
}
function groupAuthProfilesByProvider(profiles) {
    const grouped = new Map();
    for (const profile of profiles) {
        const provider = (0, store_1.getAuthProfileProvider)(profile);
        const existing = grouped.get(provider);
        if (existing) {
            existing.push(profile);
            continue;
        }
        grouped.set(provider, [profile]);
    }
    return new Map([...grouped.entries()].map(([provider, providerProfiles]) => [
        provider,
        orderProfilesWithinProvider(providerProfiles),
    ]));
}
function orderProfilesWithinProvider(profiles) {
    return profiles
        .slice()
        .sort((left, right) => {
        return (left.order - right.order ||
            left.createdAt.localeCompare(right.createdAt) ||
            left.id.localeCompare(right.id));
    })
        .map((profile) => profile);
}
