"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.resolveRunnableReadyIssues = resolveRunnableReadyIssues;
exports.resolveRunnableControlPlaneActionCandidates = resolveRunnableControlPlaneActionCandidates;
exports.resolveRunnableRunningIssues = resolveRunnableRunningIssues;
exports.resolveRunnableRecurringParentIssues = resolveRunnableRecurringParentIssues;
exports.resolveRunnableScheduledIssues = resolveRunnableScheduledIssues;
exports.listServiceRepoIssuesForSync = listServiceRepoIssuesForSync;
exports.resolveRunnableNeedsHumanIssues = resolveRunnableNeedsHumanIssues;
exports.resolveRunnableAutoMergeIssues = resolveRunnableAutoMergeIssues;
exports.resolvePullRequestMonitorTasks = resolvePullRequestMonitorTasks;
exports.resolveTerminalSourceFinalizationTasks = resolveTerminalSourceFinalizationTasks;
exports.resolveWorkerIssueTasks = resolveWorkerIssueTasks;
exports.resolveRunnableWorkingInPrCleanupIssues = resolveRunnableWorkingInPrCleanupIssues;
exports.launchScheduledIssueTask = launchScheduledIssueTask;
exports.recoverDueNetworkIssues = recoverDueNetworkIssues;
exports.countActiveWorkerSlots = countActiveWorkerSlots;
exports.countActiveWorkerIssueTasks = countActiveWorkerIssueTasks;
exports.countActiveMergeTasks = countActiveMergeTasks;
exports.pruneCompletedActiveTasks = pruneCompletedActiveTasks;
const branding_1 = require("../../core/branding");
const network_recovery_state_1 = require("../../core/network-recovery-state");
const rate_limit_backoff_1 = require("../../core/rate-limit-backoff");
const service_repo_state_1 = require("../../core/service-repo-state");
const service_repo_state_storage_1 = require("../../core/service-repo-state-storage");
const catalog_1 = require("../../i18n/catalog");
const constants_1 = require("../../platform/constants");
const provider_errors_1 = require("../../platform/provider-errors");
const types_1 = require("../../platform/ticket-tracker/types");
const types_2 = require("../../platform/workflow-state/types");
const constants_2 = require("../../run/constants");
const execution_ownership_1 = require("../../run/execution-ownership");
const run_once_cycle_1 = require("../../run/run-once-cycle");
const dependencies_1 = require("../../run/run-once-cycle/dependencies");
const pending_publication_recovery_1 = require("../../run/run-once-cycle/pending-publication-recovery");
const pull_request_completion_1 = require("../../run/run-once-cycle/pull-request-completion");
const selection_1 = require("../../run/run-once-cycle/selection");
const shared_1 = require("../../run/run-once-cycle/shared");
const terminal_source_finalization_1 = require("../../run/run-once-cycle/terminal-source-finalization");
const thread_effective_config_1 = require("../../run/thread-effective-config");
const workflow_issue_graph_1 = require("../../run/workflow-issue-graph");
const auto_merge_state_1 = require("../../workflows/auto-merge-state");
const default_issue_workspace_manager_1 = require("../../workspaces/default-issue-workspace-manager");
const auth_message_sanitizer_1 = require("../auth-message-sanitizer");
const run_cycle_context_1 = require("../run-cycle-context");
const shared_2 = require("../service/shared");
const config_publish_execution_1 = require("./config-publish-execution");
const config_scan_1 = require("./config-scan");
const events_1 = require("./events");
const repo_helpers_1 = require("./repo-helpers");
const tracked_project_1 = require("./tracked-project");
const SERVICE_LOG_NO_EXECUTABLE_ISSUES = "No executable issues.";
const SERVICE_LOG_CYCLE_FAILED = `${branding_1.SERVICE_DISPLAY_NAME} cycle failed.`;
function serviceLogCleanupRemoved(count) {
    return `Removed ${count} closed issue workspace(s).`;
}
function serviceLogCleanupFailedWithReason(reason) {
    return `Workspace cleanup failed: ${reason}`;
}
function serviceLogDeferredReason(input) {
    return `${input.issue} deferred (${input.reason}); tried sign-ins: ${input.triedProfiles}; cooldownUntil: ${input.cooldownUntil}`;
}
async function excludeIssuesWithPendingWorkflowCommands(input) {
    const pendingCommandStateByIssueId = await (0, repo_helpers_1.loadPendingWorkflowCommandStateForScheduling)(input.ticketTracker, input.issues, input.issueCommentsById);
    return input.issues.filter((issue) => pendingCommandStateByIssueId.get(issue.id) !== true);
}
async function resolveRunnableReadyIssues(input) {
    const workflowStateByIssueId = await (0, repo_helpers_1.loadPersistedWorkflowStatesForIssues)(input.ticketTracker, input.allOpenIssues);
    const runnable = input.issues.filter((issue) => (0, repo_helpers_1.isReadyRunnableCandidate)({
        issue,
        workflowState: workflowStateByIssueId.get(issue.id) ?? null,
        currentUserLogin: input.currentUserLogin,
    }));
    const commandReadyIssues = await excludeIssuesWithPendingWorkflowCommands({
        ticketTracker: input.ticketTracker,
        issues: runnable,
    });
    // Prefer issues that can actually run before truncating to maxIssues.
    const unblockedCandidates = await (0, repo_helpers_1.loadUnblockedSchedulingCandidates)({
        config: input.config,
        ticketTracker: input.ticketTracker,
        issues: commandReadyIssues,
        workflowStateByIssueId,
    });
    const graph = (0, workflow_issue_graph_1.buildWorkflowIssueGraph)(input.allOpenIssues.map((issue) => ({
        issue,
        workflowState: workflowStateByIssueId.get(issue.id) ?? null,
    })));
    return (0, selection_1.sortWorkflowFrontierCandidates)(unblockedCandidates
        .filter(({ workflowState }) => !(0, execution_ownership_1.isExecutionOwnershipActive)(workflowState?.executionOwnership))
        .map(({ issue, workflowState }) => ({
        issue,
        issueId: issue.id,
        workflowState,
    })), graph).map(({ issue }) => issue);
}
async function resolveRunnableControlPlaneActionCandidates(input) {
    return (0, repo_helpers_1.loadUnblockedSchedulingCandidates)({
        config: input.config,
        ticketTracker: input.ticketTracker,
        issues: input.issues,
        workflowStateByIssueId: input.workflowStateByIssueId,
    });
}
async function resolveRunnableRunningIssues(input) {
    const workflowStateByIssueId = await (0, repo_helpers_1.loadPersistedWorkflowStatesForIssues)(input.ticketTracker, input.issues);
    const runnable = input.issues.filter((issue) => (0, repo_helpers_1.isServiceWorkerRunnableCandidate)({
        issue,
        workflowState: workflowStateByIssueId.get(issue.id) ?? null,
        currentUserLogin: input.currentUserLogin,
    }));
    const commandReadyIssues = await excludeIssuesWithPendingWorkflowCommands({
        ticketTracker: input.ticketTracker,
        issues: runnable,
    });
    return (await (0, repo_helpers_1.loadUnblockedSchedulingCandidates)({
        config: input.config,
        ticketTracker: input.ticketTracker,
        issues: commandReadyIssues,
        workflowStateByIssueId,
    }))
        .sort((left, right) => (0, shared_1.compareExecutionPriority)({
        issueId: left.issue.id,
        workflowState: left.workflowState,
    }, {
        issueId: right.issue.id,
        workflowState: right.workflowState,
    }))
        .map(({ issue }) => issue);
}
async function resolveRunnableRecurringParentIssues(input) {
    return resolveRunnableScheduledQueueIssues(input, {
        waitingStatus: types_2.WORKFLOW_STATUSES.aiRecurring,
        isRunnableCandidate: repo_helpers_1.isRecurringParentRunnableCandidate,
        promoteDueIssue: config_scan_1.promoteDueRecurringIssue,
    });
}
async function resolveRunnableScheduledIssues(input) {
    return resolveRunnableScheduledQueueIssues({
        ...input,
        ticketTrackerKind: input.ticketTrackerKind ?? "portable",
    }, {
        waitingStatus: types_2.WORKFLOW_STATUSES.aiScheduled,
        isRunnableCandidate: repo_helpers_1.isScheduledRunRunnableCandidate,
        promoteDueIssue: config_scan_1.promoteDueScheduledIssue,
    });
}
async function resolveRunnableScheduledQueueIssues(input, policy) {
    const candidateIssues = input.issues.filter((issue) => issue.kind === types_1.TRACKER_ITEM_KINDS.issue &&
        (0, repo_helpers_1.isServiceRunnableAssignment)({
            assignees: issue.assignees,
            ...(input.currentUserLogin !== undefined
                ? { currentUserLogin: input.currentUserLogin }
                : {}),
        }));
    const workflowStateByIssueId = await (0, repo_helpers_1.loadPersistedWorkflowStatesForIssues)(input.ticketTracker, candidateIssues);
    const runnable = [];
    for (const issue of candidateIssues) {
        const workflowState = workflowStateByIssueId.get(issue.id) ?? null;
        if (!policy.isRunnableCandidate({
            issue,
            workflowState,
            ...(input.currentUserLogin !== undefined
                ? { currentUserLogin: input.currentUserLogin }
                : {}),
        })) {
            continue;
        }
        if ((0, repo_helpers_1.shouldSkipForServiceRuleLabels)(issue.workflowState.rules)) {
            continue;
        }
        if (issue.workflowState.status === policy.waitingStatus) {
            if (!(0, config_scan_1.supportsServiceConfigScan)(input.ticketTracker)) {
                continue;
            }
            const promotion = await policy.promoteDueIssue({
                ticketTracker: input.ticketTracker,
                config: input.config,
                issue,
            });
            if (!promotion.promoted) {
                continue;
            }
            await (0, config_publish_execution_1.publishServiceRuntimeConfigScanPlan)({
                repoRoot: input.repoRoot,
                ticketTrackerKind: input.ticketTrackerKind,
                displayTimeZone: input.displayTimeZone ?? "UTC",
                workflowLabels: input.workflowLabels,
                ticketTracker: input.ticketTracker,
                plan: promotion.plan,
                openIssues: input.issues,
                eventHooks: {
                    config: input.config,
                },
            });
        }
        if (issue.workflowState.status !== types_2.WORKFLOW_STATUSES.aiReady) {
            continue;
        }
        runnable.push(issue);
    }
    const commandReadyIssues = await excludeIssuesWithPendingWorkflowCommands({
        ticketTracker: input.ticketTracker,
        issues: runnable,
    });
    return (await (0, repo_helpers_1.loadUnblockedSchedulingCandidates)({
        config: input.config,
        ticketTracker: input.ticketTracker,
        issues: commandReadyIssues,
        workflowStateByIssueId,
    })).map(({ issue }) => issue);
}
async function listServiceRepoIssuesForSync(ticketTracker, syncPlan) {
    if (syncPlan.mode === "delta" && syncPlan.query.since) {
        return ticketTracker.listServiceManagedDeltaIssues({
            since: syncPlan.query.since,
            ...(syncPlan.query.state ? { state: syncPlan.query.state } : {}),
            ...(syncPlan.query.includeSourceBranchName !== undefined
                ? {
                    includeSourceBranchName: syncPlan.query.includeSourceBranchName,
                }
                : {}),
        });
    }
    return ticketTracker.listIssues(syncPlan.query);
}
async function resolveRunnableNeedsHumanIssues(input) {
    const reviewableCandidates = input.issues.filter((issue) => (0, repo_helpers_1.isNeedsHumanRunnableCandidate)({
        issue,
        currentUserLogin: input.currentUserLogin,
    }));
    const runnableCandidates = [];
    const issueCommentsById = new Map();
    for (const issue of reviewableCandidates) {
        const hydratedIssue = await (0, shared_1.hydratePullRequestIssueSourceBranchName)(input.ticketTracker, issue);
        const comments = await (0, shared_1.loadSortedIssueComments)(input.ticketTracker, hydratedIssue.id, {
            timeline: "issue-only",
        });
        issueCommentsById.set(hydratedIssue.id, comments);
        if (await (0, thread_effective_config_1.isTrackerIssueAutoMergeEnabled)({
            config: input.config,
            ticketTracker: input.ticketTracker,
            issue: hydratedIssue,
            comments,
        })) {
            if (input.skipOpenManagedDependencyWait === true) {
                const shouldSkip = await shouldSkipOpenManagedDependencyWait({
                    ticketTracker: input.ticketTracker,
                    issue: hydratedIssue,
                });
                if (shouldSkip) {
                    continue;
                }
            }
            runnableCandidates.push(hydratedIssue);
            continue;
        }
        if (await isCompletedNeedsHumanPullRequestCleanupCandidate({
            config: input.config,
            repositoryHost: input.repositoryHost,
            issue: hydratedIssue,
        })) {
            runnableCandidates.push(hydratedIssue);
        }
    }
    return excludeIssuesWithPendingWorkflowCommands({
        ticketTracker: input.ticketTracker,
        issues: runnableCandidates,
        issueCommentsById,
    });
}
async function resolveRunnableAutoMergeIssues(input) {
    const pullRequestCandidates = input.issues.filter((issue) => issue.kind === types_1.TRACKER_ITEM_KINDS.pullRequest &&
        issue.workflowState.status === types_2.WORKFLOW_STATUSES.aiReady &&
        (0, repo_helpers_1.hasServiceRunnableBasePreconditions)({
            assignees: issue.assignees,
            rules: issue.workflowState.rules,
            ...(input.currentUserLogin !== undefined
                ? { currentUserLogin: input.currentUserLogin }
                : {}),
        }));
    const workflowStateByIssueId = await (0, repo_helpers_1.loadPersistedWorkflowStatesForIssues)(input.ticketTracker, pullRequestCandidates);
    const runnableCandidates = pullRequestCandidates.filter((issue) => {
        const workflowState = workflowStateByIssueId.get(issue.id) ?? null;
        return ((0, auto_merge_state_1.hasActiveAutoMergeState)(workflowState) &&
            !(0, execution_ownership_1.isExecutionOwnershipActive)(workflowState?.executionOwnership));
    });
    const commandReadyIssues = await excludeIssuesWithPendingWorkflowCommands({
        ticketTracker: input.ticketTracker,
        issues: runnableCandidates,
    });
    return filterPullRequestsWaitingOnOpenManagedDependencies({
        ticketTracker: input.ticketTracker,
        issues: commandReadyIssues,
        ...(input.skipOpenManagedDependencyWait !== undefined
            ? { skipOpenManagedDependencyWait: input.skipOpenManagedDependencyWait }
            : {}),
    });
}
async function isCompletedNeedsHumanPullRequestCleanupCandidate(input) {
    const branchName = input.issue.sourceBranchName ??
        (0, default_issue_workspace_manager_1.resolveIssueBranchName)(input.config.branchPrefix, input.issue);
    const pullRequest = await input.repositoryHost.findPullRequestByBranch(branchName);
    const completion = await (0, pull_request_completion_1.resolvePullRequestCompletion)({
        repositoryHost: input.repositoryHost,
        pullRequest,
        branchName,
    });
    return completion.status === "completed";
}
async function resolvePullRequestMonitorTasks(input) {
    const tasks = [];
    for (const issue of input.issues) {
        const linkedIssueId = await (0, shared_1.resolveLinkedIssueIdFromPullRequestTimeline)(input.ticketTracker, issue);
        if (input.skipOpenManagedDependencyWait === true) {
            const shouldSkip = await shouldSkipOpenManagedDependencyWait({
                ticketTracker: input.ticketTracker,
                issue,
                linkedIssueId,
            });
            if (shouldSkip) {
                continue;
            }
        }
        const linkedSourceWorkflowState = linkedIssueId !== null && linkedIssueId !== issue.id
            ? ((await (0, repo_helpers_1.loadPersistedWorkflowStatesForIssues)(input.ticketTracker, [
                { id: linkedIssueId },
            ])).get(linkedIssueId) ?? null)
            : null;
        const requiresWorkerRuntime = linkedSourceWorkflowState !== null;
        tasks.push({
            kind: "run_cycle",
            issue,
            affectedIssueIds: resolveLinkedIssueAffectedIssueIds(issue, linkedIssueId),
            requiresWorkerRuntime,
            workerSlotCount: requiresWorkerRuntime ? 1 : 0,
            consumesMergeSlot: true,
        });
    }
    return tasks;
}
async function resolveTerminalSourceFinalizationTasks(input) {
    const commandReadyIssues = await excludeIssuesWithPendingWorkflowCommands({
        ticketTracker: input.ticketTracker,
        issues: input.issues,
    });
    const unblockedCandidates = await (0, repo_helpers_1.loadUnblockedSchedulingCandidates)({
        config: input.config,
        ticketTracker: input.ticketTracker,
        issues: commandReadyIssues,
        workflowStateByIssueId: input.workflowStateByIssueId,
    });
    const tasks = [];
    for (const { issue, workflowState } of unblockedCandidates) {
        const candidate = await (0, terminal_source_finalization_1.resolveTerminalSourceFinalizationCandidate)({
            config: input.config,
            repositoryHost: input.repositoryHost,
            issue,
            workflowState,
        });
        if (candidate === null) {
            continue;
        }
        tasks.push({
            kind: "run_cycle",
            issue,
            affectedIssueIds: [issue.id],
            workflowGraphIssues: input.workflowGraphIssues,
            requiresWorkerRuntime: true,
            workerSlotCount: 1,
            workerSlotDemand: 1,
            consumesMergeSlot: false,
        });
    }
    return tasks;
}
async function resolveWorkerIssueTasks(input) {
    const tasks = [];
    for (const issue of input.issues) {
        const linkedIssueId = await (0, shared_1.resolveLinkedIssueIdFromPullRequestTimeline)(input.ticketTracker, issue);
        tasks.push({
            kind: "run_cycle",
            issue,
            affectedIssueIds: resolveLinkedIssueAffectedIssueIds(issue, linkedIssueId),
            workflowGraphIssues: input.workflowGraphIssues,
            requiresWorkerRuntime: true,
            workerSlotCount: input.workerSlotCount,
            ...(input.workerSlotDemand === undefined
                ? {}
                : { workerSlotDemand: input.workerSlotDemand }),
            consumesMergeSlot: false,
        });
    }
    return tasks;
}
function resolveLinkedIssueAffectedIssueIds(issue, linkedIssueId) {
    return linkedIssueId !== null && linkedIssueId !== issue.id
        ? [issue.id, linkedIssueId]
        : [issue.id];
}
async function filterPullRequestsWaitingOnOpenManagedDependencies(input) {
    if (input.skipOpenManagedDependencyWait !== true) {
        return input.issues;
    }
    const filteredIssues = [];
    for (const issue of input.issues) {
        if (!(await shouldSkipOpenManagedDependencyWait({
            ticketTracker: input.ticketTracker,
            issue,
        }))) {
            filteredIssues.push(issue);
        }
    }
    return filteredIssues;
}
async function shouldSkipOpenManagedDependencyWait(input) {
    if (!hasPullRequestDependencyReader(input.ticketTracker)) {
        return false;
    }
    const dependencyState = await (0, dependencies_1.resolvePullRequestManagedDependencyState)({
        ticketTracker: input.ticketTracker,
        issue: input.issue,
        ...(input.linkedIssueId !== undefined
            ? { linkedIssueId: input.linkedIssueId }
            : {}),
    });
    return (dependencyState.openDependencies.length > 0 &&
        dependencyState.closedDependencies.length === 0);
}
function hasPullRequestDependencyReader(ticketTracker) {
    return (typeof ticketTracker.getIssueCleanupState === "function" &&
        typeof ticketTracker.getIssueRecord === "function");
}
async function resolveRunnableWorkingInPrCleanupIssues(input) {
    const cleanupIssues = [];
    for (const issue of input.issues) {
        const branchName = (0, default_issue_workspace_manager_1.resolveIssueBranchName)(input.config.branchPrefix, issue);
        const pullRequest = await input.repositoryHost.findPullRequestByBranch(branchName);
        if (pullRequest?.state === constants_1.PLATFORM_PULL_REQUEST_STATES.open) {
            continue;
        }
        cleanupIssues.push(issue);
    }
    return cleanupIssues;
}
async function launchScheduledIssueTask(entry, config, sharedRunContext, issueId, locale, workflowGraphIssues, workerSlotCount = 1, requiresWorkerRuntime = workerSlotCount > 0, homeDirectory, repoStateRegistry = (0, service_repo_state_1.createRepoServiceStateRegistry)(), taskKind = "run_cycle", recoveryEntry) {
    let issueWorkspaceManager = null;
    let ticketTracker = null;
    try {
        locale = config.i18n.defaultLanguage;
        const runContext = await (0, run_cycle_context_1.createRunCycleContextBundle)(entry.cwd, config, {
            ...(homeDirectory ? { homeDirectory } : {}),
            ...(requiresWorkerRuntime ? {} : { skipWorkerRuntime: true }),
            issueIds: [issueId],
            runSource: "service",
            workerSlotBudget: workerSlotCount,
            ...(sharedRunContext.workflowTemplateRepoRoot
                ? {
                    workflowTemplateRepoRoot: sharedRunContext.workflowTemplateRepoRoot,
                }
                : {}),
            sharedClients: {
                repositoryHost: sharedRunContext.repositoryHost,
                linkedBranchService: sharedRunContext.linkedBranchService,
                ticketTracker: sharedRunContext.ticketTracker,
                freshTicketTracker: sharedRunContext.freshTicketTracker,
                gitRepository: sharedRunContext.gitRepository,
                issueWorkspaceManager: sharedRunContext.issueWorkspaceManager,
                ...(sharedRunContext.currentUserLogin !== undefined
                    ? { currentUserLogin: sharedRunContext.currentUserLogin }
                    : {}),
            },
        });
        issueWorkspaceManager = runContext.issueWorkspaceManager;
        ticketTracker = runContext.ticketTracker;
        if (homeDirectory) {
            runContext.logHomeDirectory = homeDirectory;
        }
        const freshIssue = await loadFreshOpenIssueForServiceLaunch(runContext.freshTicketTracker, issueId);
        if (freshIssue === null) {
            console.log(`[${(0, tracked_project_1.formatTrackedProjectLabel)(entry)}] ${SERVICE_LOG_NO_EXECUTABLE_ISSUES}`);
            await (0, events_1.logServiceEvent)({
                type: shared_2.SERVICE_EVENT_TYPES.repoResult,
                ...(0, events_1.buildRepoEventBase)(entry),
                cwd: entry.cwd,
                result: shared_2.SERVICE_REPO_RESULTS.noOp,
                timestamp: new Date().toISOString(),
            });
            return [];
        }
        runContext.prefetchedIssues = [freshIssue];
        if (workflowGraphIssues) {
            runContext.workflowGraphIssues = [...workflowGraphIssues];
        }
        const result = taskKind === "pending_publication_recovery"
            ? await runPendingPublicationRecoveryTask({
                runContext,
                recoveryEntry,
                issue: freshIssue,
            })
            : await (0, run_once_cycle_1.runOnceCycle)(runContext);
        const deferredReason = buildDeferredServiceReason(result.summaries, locale);
        if (result.summaries.length === 0) {
            console.log(`[${(0, tracked_project_1.formatTrackedProjectLabel)(entry)}] ${SERVICE_LOG_NO_EXECUTABLE_ISSUES}`);
        }
        else {
            for (const summary of result.summaries) {
                console.log(`[${(0, tracked_project_1.formatTrackedProjectLabel)(entry)}] ${summary.headline}`);
            }
        }
        await (0, events_1.logServiceEvent)({
            type: shared_2.SERVICE_EVENT_TYPES.repoResult,
            ...(0, events_1.buildRepoEventBase)(entry),
            cwd: entry.cwd,
            result: mapRunCycleStatus(result.status),
            ...(deferredReason ? { reason: deferredReason } : {}),
            timestamp: new Date().toISOString(),
        });
        return extractAdditionalAffectedIssueIdsFromSummaries(result.summaries);
    }
    catch (error) {
        if ((0, provider_errors_1.isUnsupportedProviderError)(error)) {
            throw error;
        }
        await recordServiceLaunchRateLimitBackoff({
            error,
            entry,
            ...(homeDirectory !== undefined ? { homeDirectory } : {}),
            repoStateRegistry,
        });
        const reason = (0, auth_message_sanitizer_1.sanitizeAuthMessage)(error instanceof Error ? error.message : SERVICE_LOG_CYCLE_FAILED, catalog_1.DEFAULT_APP_LOCALE);
        console.error(`[${(0, tracked_project_1.formatTrackedProjectLabel)(entry)}] ${reason}`);
        await (0, events_1.logServiceEvent)({
            type: shared_2.SERVICE_EVENT_TYPES.repoResult,
            ...(0, events_1.buildRepoEventBase)(entry),
            cwd: entry.cwd,
            result: shared_2.SERVICE_REPO_RESULTS.failed,
            reason,
            timestamp: new Date().toISOString(),
        });
        return [];
    }
    finally {
        if (issueWorkspaceManager && ticketTracker) {
            await cleanupRepoIssueWorkspaces(entry, config, issueWorkspaceManager, ticketTracker);
        }
    }
}
async function recordServiceLaunchRateLimitBackoff(input) {
    const rateLimitBackoff = (0, rate_limit_backoff_1.extractRateLimitBackoff)(input.error);
    if (rateLimitBackoff === null) {
        return;
    }
    const retryAtMs = Date.parse(rateLimitBackoff.nextRetryAt);
    if (Number.isNaN(retryAtMs)) {
        return;
    }
    (0, service_repo_state_1.recordRepoCooldown)(input.repoStateRegistry, (0, tracked_project_1.getEntryTrackedProjectKey)(input.entry), {
        nextRetryAtMs: retryAtMs,
        reason: rateLimitBackoff.reason,
        lastError: input.error instanceof Error
            ? input.error.message
            : SERVICE_LOG_CYCLE_FAILED,
        updatedAt: new Date().toISOString(),
    });
    await (0, service_repo_state_storage_1.persistRepoServiceStateRegistrySafely)(input.repoStateRegistry, input.homeDirectory);
}
async function loadFreshOpenIssueForServiceLaunch(ticketTracker, issueId) {
    const cleanupState = await ticketTracker.getIssueCleanupState(issueId);
    if (!cleanupState.isOpen) {
        return null;
    }
    const issue = await ticketTracker.getIssue(issueId);
    return issue.isOpen ? issue : null;
}
async function runPendingPublicationRecoveryTask(input) {
    if (input.recoveryEntry === undefined) {
        throw new Error("Pending publication recovery task is missing recovery state.");
    }
    const summary = await (0, pending_publication_recovery_1.recoverPendingWaitHumanPublication)({
        context: input.runContext,
        entry: input.recoveryEntry,
        ...(input.issue ? { issue: input.issue } : {}),
    });
    return {
        status: summary.outcome === constants_2.RUN_ISSUE_OUTCOMES.failed
            ? constants_2.RUN_CYCLE_STATUSES.failed
            : constants_2.RUN_CYCLE_STATUSES.processed,
        // biome-ignore lint/style/useNamingConvention: CLI summary contract uses snake_case.
        processed_issue_count: 1,
        summaries: [summary],
    };
}
function extractAdditionalAffectedIssueIdsFromSummaries(summaries) {
    const issueIds = new Set();
    for (const summary of summaries) {
        const pullRequestThreadId = summary.pull_request_thread_id;
        if (pullRequestThreadId !== null &&
            pullRequestThreadId !== summary.issue_id) {
            issueIds.add(pullRequestThreadId);
        }
    }
    return [...issueIds];
}
async function recoverDueNetworkIssues(input) {
    const dueEntries = input.recoveryEntries.filter((entry) => (0, pending_publication_recovery_1.hasPendingWaitHumanPublication)(entry)
        ? (0, pending_publication_recovery_1.isPendingWaitHumanPublicationRecoveryDue)(entry)
        : Date.parse(entry.nextRetryAt) <= Date.now());
    if (dueEntries.length === 0) {
        return;
    }
    for (const recoveryEntry of dueEntries) {
        if (input.activeTasks.byIssueId.has(recoveryEntry.issueId)) {
            continue;
        }
        const issue = await input.ticketTracker.getIssue(recoveryEntry.issueId);
        const pendingPublication = (0, pending_publication_recovery_1.hasPendingWaitHumanPublication)(recoveryEntry);
        const serviceRunnableBasePreconditions = (0, repo_helpers_1.hasServiceRunnableBasePreconditions)({
            assignees: issue.assignees,
            rules: issue.workflowState.rules,
            ...(input.currentUserLogin !== undefined
                ? { currentUserLogin: input.currentUserLogin }
                : {}),
        });
        if (pendingPublication) {
            const safety = (0, pending_publication_recovery_1.resolvePendingWaitHumanPublicationRecoverySafety)({
                entry: recoveryEntry,
                issue,
                serviceRunnableBasePreconditions,
            });
            if (!safety.safe) {
                await (0, pending_publication_recovery_1.blockPendingWaitHumanPublicationRecovery)({
                    trackedProjectKey: (0, tracked_project_1.getEntryTrackedProjectKey)(input.entry),
                    entry: recoveryEntry,
                    reason: safety.reason,
                    homeDirectory: input.homeDirectory,
                });
            }
            continue;
        }
        if (!issue.isOpen ||
            !isServiceRecoverableWorkflowStatus(issue.workflowState) ||
            !serviceRunnableBasePreconditions) {
            await (0, network_recovery_state_1.removeNetworkRecoveryEntry)((0, tracked_project_1.getEntryTrackedProjectKey)(input.entry), recoveryEntry.issueId, recoveryEntry.reason, input.homeDirectory);
            continue;
        }
        if (issue.workflowState.status !== types_2.WORKFLOW_STATUSES.aiRunning) {
            await (0, network_recovery_state_1.removeNetworkRecoveryEntry)((0, tracked_project_1.getEntryTrackedProjectKey)(input.entry), recoveryEntry.issueId, recoveryEntry.reason, input.homeDirectory);
        }
    }
}
function isServiceRecoverableWorkflowStatus(workflowState) {
    return (workflowState.status === types_2.WORKFLOW_STATUSES.aiRunning ||
        workflowState.status === types_2.WORKFLOW_STATUSES.aiReady);
}
function countActiveWorkerSlots(registry) {
    let count = 0;
    for (const entry of registry.byIssueId.values()) {
        count += entry.workerSlotCount;
    }
    return count;
}
function countActiveWorkerIssueTasks(registry) {
    let count = 0;
    for (const entry of registry.byIssueId.values()) {
        if (entry.workerSlotCount > 0) {
            count += 1;
        }
    }
    return count;
}
function countActiveMergeTasks(registry) {
    let count = 0;
    for (const entry of registry.byIssueId.values()) {
        if (entry.consumesMergeSlot) {
            count += 1;
        }
    }
    return count;
}
function pruneCompletedActiveTasks(registry) {
    for (const [issueId, entry] of registry.byIssueId.entries()) {
        void entry.promise.finally(() => {
            registry.byIssueId.delete(issueId);
        });
    }
}
async function cleanupRepoIssueWorkspaces(entry, config, issueWorkspaceManager, ticketTracker) {
    try {
        const result = await issueWorkspaceManager.cleanupClosedWorkspaces({
            repositoryRoot: entry.cwd,
            repositoryHost: config.repositoryHost,
            ticketTrackerRef: config.ticketTracker,
            ticketTracker,
        });
        if (result.removedPaths.length > 0) {
            console.log(`[${(0, tracked_project_1.formatTrackedProjectLabel)(entry)}] ${serviceLogCleanupRemoved(result.removedPaths.length)}`);
        }
        for (const warning of result.warnings) {
            console.warn(`[${(0, tracked_project_1.formatTrackedProjectLabel)(entry)}] ${warning}`);
        }
    }
    catch (error) {
        const reason = (0, auth_message_sanitizer_1.sanitizeAuthMessage)(error instanceof Error ? error.message : "Workspace cleanup failed.", catalog_1.DEFAULT_APP_LOCALE);
        console.error(`[${(0, tracked_project_1.formatTrackedProjectLabel)(entry)}] ${serviceLogCleanupFailedWithReason(reason)}`);
    }
}
function mapRunCycleStatus(status) {
    if (status === constants_2.RUN_CYCLE_STATUSES.noOp) {
        return shared_2.SERVICE_REPO_RESULTS.noOp;
    }
    if (status === constants_2.RUN_CYCLE_STATUSES.failed) {
        return shared_2.SERVICE_REPO_RESULTS.failed;
    }
    return shared_2.SERVICE_REPO_RESULTS.success;
}
function buildDeferredServiceReason(summaries, locale = catalog_1.DEFAULT_APP_LOCALE) {
    void locale;
    const deferred = summaries.filter((summary) => summary.deferred_reason !== null);
    if (deferred.length === 0) {
        return null;
    }
    return deferred
        .map((summary) => serviceLogDeferredReason({
        issue: summary.issue_display_id,
        reason: summary.deferred_reason ?? "-",
        triedProfiles: summary.tried_profiles.join(", ") || "(none)",
        cooldownUntil: summary.cooldown_until ?? "-",
    }))
        .join(" | ");
}
