import { randomUUID } from 'node:crypto';
import { getProviderById } from '../executors/index.js';
import { getEventBus, getSessionManager } from '../core/container.js';
import { NotFoundError, ServiceError } from '../errors.js';
import { prisma } from '../utils/index.js';
import { defaultTeamLockService } from './team-lock.service.js';
import { WorkspaceService } from './workspace.service.js';
import { appendAttachmentMarkdownContext } from './attachment-context.js';
import { emitTeamRunInvalidated } from './team-run-events.js';
import { TeamReconcilerService } from './team-reconciler.service.js';
import { ensureTaskNotDeleted, isTaskDeleted } from './deleted-task-guard.js';
const ACTIVE_INVOCATION_STATUSES = [
    'QUEUED',
    'RUNNING',
    'SESSION_ENDED',
    'WAITING_ROOM_REPLY',
];
const STOPPABLE_INVOCATION_STATUSES = [
    'QUEUED',
    'RUNNING',
    'SESSION_ENDED',
    'WAITING_ROOM_REPLY',
];
const CANCELLABLE_QUEUED_WORK_REQUEST_STATUSES = [
    'PENDING_APPROVAL',
    'QUEUED',
];
const DEFAULT_CAPABILITIES = {
    readRoom: false,
    postRoomMessage: false,
    mentionMembers: false,
    stopMemberWork: false,
    markReadyForReview: false,
    readFiles: false,
    writeFiles: false,
    runCommands: false,
    readDiff: false,
    mergeWorkspace: false,
};
const DEFAULT_QUEUE_MANAGEMENT_POLICY = 'own_only';
function parseJsonField(value, fallback) {
    if (value == null || value === '') {
        return fallback;
    }
    try {
        return JSON.parse(value);
    }
    catch {
        return fallback;
    }
}
function toIso(date) {
    return date.toISOString();
}
function invalidTransition(action, status) {
    return new ServiceError(`Cannot ${action} WorkRequest in ${status} status`, 'INVALID_STATE_TRANSITION', 400);
}
export class TeamSchedulerService {
    lockService;
    static memberSchedulingLocks = new Set();
    static sharedWorkspaceClaims = new Map();
    workspaceService;
    sessionManager;
    providerLookup;
    teamRunReviewAdvancer;
    constructor(lockService = defaultTeamLockService, dependencies = {}) {
        this.lockService = lockService;
        this.workspaceService = dependencies.workspaceService ?? new WorkspaceService();
        this.sessionManager = dependencies.sessionManager ?? getSessionManager();
        this.providerLookup = dependencies.getProviderById ?? getProviderById;
        this.teamRunReviewAdvancer = dependencies.teamRunReviewAdvancer ?? new TeamReconcilerService({
            eventBus: getEventBus(),
            scheduler: {
                releaseInvocationLocks: (invocationId) => this.releaseInvocationLocks(invocationId),
                startNextSessions: (nextTeamRunId) => this.startNextSessions(nextTeamRunId),
            },
        });
    }
    async planNext(teamRunId) {
        const context = await this.getSchedulingContext(teamRunId);
        const activeMemberIds = await this.findActiveMemberIds(teamRunId);
        const plannedMemberIds = new Set();
        const plannedLockKeys = new Set();
        const plans = [];
        for (const workRequest of context.workRequests) {
            const member = context.memberById.get(workRequest.targetMemberId);
            if (!member) {
                plans.push({
                    workRequestId: workRequest.id,
                    memberId: workRequest.targetMemberId,
                    canStart: false,
                    blockedReason: 'member_not_found',
                    requiresStopCurrent: false,
                    lockKeys: [],
                    workspaceId: null,
                    projectId: context.teamRun.task.projectId,
                });
                continue;
            }
            const workspaceId = this.resolveInvocationWorkspaceId(context.teamRun, member);
            const projectId = context.teamRun.task.projectId;
            const lockKeys = this.getRequiredLocks(context.teamRun, member);
            const requiresStopCurrent = workRequest.ifBusy === 'cancel_current_and_start'
                && activeMemberIds.has(member.id);
            if (activeMemberIds.has(member.id)) {
                plans.push({
                    workRequestId: workRequest.id,
                    memberId: member.id,
                    canStart: false,
                    blockedReason: 'member_busy',
                    requiresStopCurrent,
                    lockKeys,
                    workspaceId,
                    projectId,
                });
                continue;
            }
            if (plannedMemberIds.has(member.id)) {
                plans.push({
                    workRequestId: workRequest.id,
                    memberId: member.id,
                    canStart: false,
                    blockedReason: 'member_already_planned',
                    requiresStopCurrent: false,
                    lockKeys,
                    workspaceId,
                    projectId,
                });
                continue;
            }
            const hasPlannedLockConflict = lockKeys.some((key) => plannedLockKeys.has(key));
            if (hasPlannedLockConflict || !this.lockService.canAcquire(lockKeys)) {
                plans.push({
                    workRequestId: workRequest.id,
                    memberId: member.id,
                    canStart: false,
                    blockedReason: 'resource_locked',
                    requiresStopCurrent: false,
                    lockKeys,
                    workspaceId,
                    projectId,
                });
                continue;
            }
            plannedMemberIds.add(member.id);
            for (const key of lockKeys) {
                plannedLockKeys.add(key);
            }
            plans.push({
                workRequestId: workRequest.id,
                memberId: member.id,
                canStart: true,
                requiresStopCurrent: false,
                lockKeys,
                workspaceId,
                projectId,
            });
        }
        return plans;
    }
    async startNext(teamRunId) {
        const context = await this.getSchedulingContext(teamRunId);
        const startedInvocations = [];
        for (const workRequest of context.workRequests) {
            const member = context.memberById.get(workRequest.targetMemberId);
            if (!member) {
                continue;
            }
            const memberLockKey = this.memberSchedulingLockKey(teamRunId, member.id);
            if (!this.acquireMemberSchedulingLock(memberLockKey)) {
                continue;
            }
            let invocationId = null;
            try {
                if (await this.hasActiveInvocation(teamRunId, member.id)) {
                    continue;
                }
                const freshWorkRequest = await prisma.workRequest.findUnique({
                    where: { id: workRequest.id },
                });
                if (!freshWorkRequest || freshWorkRequest.status !== 'QUEUED') {
                    continue;
                }
                const lockKeys = this.getRequiredLocks(context.teamRun, member);
                invocationId = randomUUID();
                if (!this.lockService.acquire(invocationId, lockKeys)) {
                    invocationId = null;
                    continue;
                }
                const workspaceId = await this.resolveQueuedInvocationWorkspaceId(context.teamRun, member);
                const createdInvocation = await this.createInvocationForClaimedWorkRequest(context.teamRun, member, freshWorkRequest, invocationId, workspaceId);
                if (!createdInvocation) {
                    this.lockService.releaseByOwner(invocationId);
                    invocationId = null;
                    continue;
                }
                startedInvocations.push(this.serializeAgentInvocation(createdInvocation));
                await this.emitTeamRunInvalidated(context.teamRun, ['work-requests', 'agent-invocations'], 'agent-invocation-updated');
                invocationId = null;
            }
            catch (error) {
                if (invocationId) {
                    this.lockService.releaseByOwner(invocationId);
                }
                throw error;
            }
            finally {
                this.releaseMemberSchedulingLock(memberLockKey);
            }
        }
        return startedInvocations;
    }
    async startNextSessions(teamRunId) {
        const context = await this.getSchedulingContext(teamRunId);
        const startedInvocations = [];
        let createdTerminalInvocation = false;
        for (const workRequest of context.workRequests) {
            const member = context.memberById.get(workRequest.targetMemberId);
            if (!member) {
                continue;
            }
            const memberLockKey = this.memberSchedulingLockKey(teamRunId, member.id);
            if (!this.acquireMemberSchedulingLock(memberLockKey)) {
                continue;
            }
            let invocationId = null;
            try {
                if (await this.hasActiveInvocation(teamRunId, member.id)) {
                    continue;
                }
                const freshWorkRequest = await prisma.workRequest.findUnique({
                    where: { id: workRequest.id },
                });
                if (!freshWorkRequest || freshWorkRequest.status !== 'QUEUED') {
                    continue;
                }
                const lockKeys = this.getRequiredLocks(context.teamRun, member);
                invocationId = randomUUID();
                if (!this.lockService.acquire(invocationId, lockKeys)) {
                    invocationId = null;
                    continue;
                }
                const provider = this.providerLookup(member.providerId);
                if (!provider) {
                    console.warn('[TeamSchedulerService] Skipping TeamRun WorkRequest because provider was not found:', {
                        teamRunId,
                        workRequestId: freshWorkRequest.id,
                        memberId: member.id,
                        providerId: member.providerId,
                    });
                    const failedInvocation = await this.createFailedInvocationForClaimedWorkRequest(context.teamRun, member, freshWorkRequest, invocationId, null);
                    this.lockService.releaseByOwner(invocationId);
                    invocationId = null;
                    if (failedInvocation) {
                        createdTerminalInvocation = true;
                        await this.emitTeamRunInvalidated(context.teamRun, ['work-requests', 'agent-invocations'], 'agent-invocation-updated');
                    }
                    continue;
                }
                const workspace = await this.getOrCreateWorkspaceForMember(context.teamRun, member);
                const session = await this.sessionManager.create(workspace.id, provider.agentType, await this.buildSessionPrompt(context.teamRun, member, freshWorkRequest), 'DEFAULT', member.providerId);
                const createdInvocation = await this.createRunningInvocationForClaimedWorkRequest(context.teamRun, member, freshWorkRequest, invocationId, workspace.id, session.id);
                if (!createdInvocation) {
                    await this.markSessionFailed(session.id);
                    this.lockService.releaseByOwner(invocationId);
                    invocationId = null;
                    continue;
                }
                const resumeFromSessionId = await this.findResumeSourceSessionId(member, session.id, workspace.id);
                try {
                    if (resumeFromSessionId && this.sessionManager.startFollowUp) {
                        await this.sessionManager.startFollowUp(session.id, resumeFromSessionId);
                    }
                    else {
                        await this.sessionManager.start(session.id);
                    }
                }
                catch (error) {
                    await this.markInvocationStartFailed(createdInvocation.id, session.id);
                    this.lockService.releaseByOwner(createdInvocation.id);
                    invocationId = null;
                    throw error;
                }
                startedInvocations.push(this.serializeAgentInvocation(createdInvocation));
                await this.emitTeamRunInvalidated(context.teamRun, ['work-requests', 'agent-invocations', 'workspaces'], 'agent-invocation-updated');
                invocationId = null;
            }
            catch (error) {
                if (invocationId) {
                    this.lockService.releaseByOwner(invocationId);
                }
                throw error;
            }
            finally {
                this.releaseMemberSchedulingLock(memberLockKey);
            }
        }
        if (createdTerminalInvocation) {
            await this.teamRunReviewAdvancer.maybeAdvanceTeamRunToReview(teamRunId);
        }
        return startedInvocations;
    }
    async approveWorkRequest(workRequestId, options) {
        const current = await this.getWorkRequestOrThrow(workRequestId);
        await this.assertMemberCanControlWorkRequest(current, options, 'approve');
        const workRequest = await this.transitionWorkRequestStatus(workRequestId, 'approve', ['PENDING_APPROVAL'], 'QUEUED');
        await this.emitTeamRunInvalidatedById(workRequest.teamRunId, ['work-requests', 'team-run'], 'work-request-updated');
        return workRequest;
    }
    async approveWorkRequestAndStartNext(workRequestId, options) {
        const workRequest = await this.approveWorkRequest(workRequestId, options);
        const startedInvocations = await this.startNextSessions(workRequest.teamRunId);
        return { workRequest, startedInvocations };
    }
    async rejectWorkRequest(workRequestId, options) {
        const current = await this.getWorkRequestOrThrow(workRequestId);
        await this.assertMemberCanControlWorkRequest(current, options, 'reject');
        const workRequest = await this.transitionWorkRequestStatus(workRequestId, 'reject', ['PENDING_APPROVAL'], 'REJECTED');
        await this.emitTeamRunInvalidatedById(workRequest.teamRunId, ['work-requests', 'team-run'], 'work-request-updated');
        return workRequest;
    }
    async cancelWorkRequest(workRequestId, options) {
        if (!options?.teamRunId || !options.requesterMemberId) {
            throw new ServiceError('teamRunId and requesterMemberId are required when cancelling a TeamRun WorkRequest', 'VALIDATION_ERROR', 400);
        }
        const current = await this.getWorkRequestOrThrow(workRequestId);
        if (current.teamRunId !== options.teamRunId) {
            throw new NotFoundError('WorkRequest', workRequestId);
        }
        await this.assertMemberCanControlWorkRequest(current, options, 'cancel');
        const workRequest = await this.transitionWorkRequestStatus(workRequestId, 'cancel', ['PENDING_APPROVAL', 'QUEUED'], 'CANCELLED');
        await this.emitTeamRunInvalidatedById(workRequest.teamRunId, ['work-requests', 'team-run'], 'work-request-updated');
        return workRequest;
    }
    async stopMemberWork(teamRunId, memberId, options = {}) {
        await this.getTeamMemberOrThrow(teamRunId, memberId);
        const activeInvocations = await prisma.agentInvocation.findMany({
            where: {
                teamRunId,
                memberId,
                status: { in: STOPPABLE_INVOCATION_STATUSES },
            },
            orderBy: [{ createdAt: 'asc' }, { id: 'asc' }],
        });
        const stoppedSessionIds = [];
        const queuedInvocationIds = activeInvocations
            .filter((invocation) => invocation.sessionId == null)
            .map((invocation) => invocation.id);
        const queuedCancellation = queuedInvocationIds.length > 0
            ? await this.cancelInvocationsWithoutSession(queuedInvocationIds)
            : { cancelledInvocationIds: [], cancelledWorkRequestIds: [] };
        if (options.cancelQueued) {
            const cancelledQueuedRequestIds = await this.cancelQueuedWorkRequestsForMember(teamRunId, memberId);
            queuedCancellation.cancelledWorkRequestIds.push(...cancelledQueuedRequestIds);
        }
        const sessionIds = activeInvocations
            .map((invocation) => invocation.sessionId)
            .filter((sessionId) => sessionId != null);
        if (sessionIds.length > 0) {
            if (!this.sessionManager.stop) {
                throw new ServiceError('Session stop is not available', 'SESSION_STOP_UNAVAILABLE', 500);
            }
            for (const sessionId of sessionIds) {
                const stopped = await this.sessionManager.stop(sessionId);
                if (stopped) {
                    stoppedSessionIds.push(sessionId);
                }
            }
        }
        const cancelledWorkRequestIds = Array.from(new Set(queuedCancellation.cancelledWorkRequestIds));
        const shouldStartNext = stoppedSessionIds.length > 0
            || queuedCancellation.cancelledInvocationIds.length > 0
            || cancelledWorkRequestIds.length > 0;
        const startedInvocations = shouldStartNext
            ? await this.startNextSessions(teamRunId)
            : [];
        if (shouldStartNext) {
            await this.emitTeamRunInvalidatedById(teamRunId, ['work-requests', 'agent-invocations', 'team-run'], 'member-work-stopped');
        }
        return {
            stoppedSessionIds,
            cancelledInvocationIds: queuedCancellation.cancelledInvocationIds,
            cancelledWorkRequestIds,
            startedInvocations,
        };
    }
    releaseInvocationLocks(invocationId) {
        this.lockService.releaseByOwner(invocationId);
    }
    async createInvocationForClaimedWorkRequest(teamRun, member, workRequest, invocationId, workspaceId) {
        return prisma.$transaction(async (tx) => {
            const claimed = await tx.workRequest.updateMany({
                where: {
                    id: workRequest.id,
                    teamRunId: teamRun.id,
                    status: 'QUEUED',
                },
                data: { status: 'STARTED' },
            });
            if (claimed.count !== 1) {
                return null;
            }
            if (workRequest.cancelQueued) {
                await tx.workRequest.updateMany({
                    where: {
                        teamRunId: teamRun.id,
                        targetMemberId: member.id,
                        status: 'QUEUED',
                        id: { not: workRequest.id },
                    },
                    data: { status: 'CANCELLED' },
                });
            }
            return tx.agentInvocation.create({
                data: {
                    id: invocationId,
                    teamRunId: teamRun.id,
                    workRequestId: workRequest.id,
                    memberId: member.id,
                    workspaceId,
                    sessionId: null,
                    status: 'QUEUED',
                },
            });
        });
    }
    async createRunningInvocationForClaimedWorkRequest(teamRun, member, workRequest, invocationId, workspaceId, sessionId) {
        return prisma.$transaction(async (tx) => {
            const claimed = await tx.workRequest.updateMany({
                where: {
                    id: workRequest.id,
                    teamRunId: teamRun.id,
                    status: 'QUEUED',
                },
                data: { status: 'STARTED' },
            });
            if (claimed.count !== 1) {
                return null;
            }
            if (workRequest.cancelQueued) {
                await tx.workRequest.updateMany({
                    where: {
                        teamRunId: teamRun.id,
                        targetMemberId: member.id,
                        status: 'QUEUED',
                        id: { not: workRequest.id },
                    },
                    data: { status: 'CANCELLED' },
                });
            }
            return tx.agentInvocation.create({
                data: {
                    id: invocationId,
                    teamRunId: teamRun.id,
                    workRequestId: workRequest.id,
                    memberId: member.id,
                    workspaceId,
                    sessionId,
                    status: 'RUNNING',
                },
            });
        });
    }
    async createFailedInvocationForClaimedWorkRequest(teamRun, member, workRequest, invocationId, workspaceId) {
        return prisma.$transaction(async (tx) => {
            const claimed = await tx.workRequest.updateMany({
                where: {
                    id: workRequest.id,
                    teamRunId: teamRun.id,
                    status: 'QUEUED',
                },
                data: { status: 'STARTED' },
            });
            if (claimed.count !== 1) {
                return null;
            }
            if (workRequest.cancelQueued) {
                await tx.workRequest.updateMany({
                    where: {
                        teamRunId: teamRun.id,
                        targetMemberId: member.id,
                        status: 'QUEUED',
                        id: { not: workRequest.id },
                    },
                    data: { status: 'CANCELLED' },
                });
            }
            return tx.agentInvocation.create({
                data: {
                    id: invocationId,
                    teamRunId: teamRun.id,
                    workRequestId: workRequest.id,
                    memberId: member.id,
                    workspaceId,
                    sessionId: null,
                    status: 'FAILED',
                },
            });
        });
    }
    async getSchedulingContext(teamRunId) {
        const teamRun = await prisma.teamRun.findUnique({
            where: { id: teamRunId },
            include: {
                task: {
                    include: {
                        workspaces: {
                            where: {
                                status: 'ACTIVE',
                                parentWorkspaceId: null,
                            },
                            orderBy: [{ createdAt: 'asc' }, { id: 'asc' }],
                            take: 1,
                        },
                    },
                },
            },
        });
        if (!teamRun) {
            throw new NotFoundError('TeamRun', teamRunId);
        }
        if (isTaskDeleted(teamRun.task)) {
            return {
                teamRun,
                memberById: new Map(),
                workRequests: [],
            };
        }
        const [members, workRequests] = await Promise.all([
            prisma.teamMember.findMany({
                where: { teamRunId, membershipStatus: 'ACTIVE' },
                orderBy: [{ createdAt: 'asc' }, { id: 'asc' }],
            }),
            prisma.workRequest.findMany({
                where: { teamRunId, status: 'QUEUED' },
                orderBy: [{ createdAt: 'asc' }, { id: 'asc' }],
            }),
        ]);
        return {
            teamRun,
            memberById: new Map(members.map((member) => [member.id, member])),
            workRequests,
        };
    }
    async getWorkRequestOrThrow(workRequestId) {
        const workRequest = await prisma.workRequest.findUnique({ where: { id: workRequestId } });
        if (!workRequest) {
            throw new NotFoundError('WorkRequest', workRequestId);
        }
        return workRequest;
    }
    async getTeamMemberOrThrow(teamRunId, memberId) {
        const member = await prisma.teamMember.findFirst({ where: { id: memberId, teamRunId } });
        if (member) {
            return member;
        }
        const teamRun = await prisma.teamRun.findUnique({
            where: { id: teamRunId },
            select: { id: true },
        });
        if (!teamRun) {
            throw new NotFoundError('TeamRun', teamRunId);
        }
        throw new NotFoundError('TeamMember', memberId);
    }
    async assertMemberCanControlWorkRequest(workRequest, options, action) {
        if (!options) {
            return;
        }
        if (!options.teamRunId || !options.requesterMemberId) {
            throw new ServiceError(`teamRunId and requesterMemberId are required when ${action}ing a TeamRun WorkRequest`, 'VALIDATION_ERROR', 400);
        }
        if (workRequest.teamRunId !== options.teamRunId) {
            throw new NotFoundError('WorkRequest', workRequest.id);
        }
        const requester = await this.getTeamMemberOrThrow(workRequest.teamRunId, options.requesterMemberId);
        if (requester.membershipStatus === 'REMOVED') {
            throw new ServiceError('Removed TeamRun members cannot control WorkRequests', 'FORBIDDEN', 403);
        }
        const requesterMemberId = options.requesterMemberId;
        if (workRequest.targetMemberId === requesterMemberId) {
            return;
        }
        if (this.resolveQueueManagementPolicy(requester.queueManagementPolicy) === 'team_pending') {
            return;
        }
        throw new ServiceError(`Current TeamRun member cannot ${action} WorkRequest for another member`, 'FORBIDDEN', 403);
    }
    resolveQueueManagementPolicy(value) {
        return value === 'team_pending' ? 'team_pending' : DEFAULT_QUEUE_MANAGEMENT_POLICY;
    }
    async transitionWorkRequestStatus(workRequestId, action, allowedStatuses, nextStatus) {
        const updated = await prisma.$transaction(async (tx) => {
            const current = await tx.workRequest.findUnique({
                where: { id: workRequestId },
                include: { teamRun: { include: { task: true } } },
            });
            if (!current) {
                return null;
            }
            ensureTaskNotDeleted(current.teamRun.task);
            const claimed = allowedStatuses.length === 1
                ? await tx.workRequest.updateMany({
                    where: { id: workRequestId, status: allowedStatuses[0] },
                    data: { status: nextStatus },
                })
                : await tx.workRequest.updateMany({
                    where: { id: workRequestId, status: { in: allowedStatuses } },
                    data: { status: nextStatus },
                });
            if (claimed.count !== 1) {
                return null;
            }
            return tx.workRequest.findUnique({ where: { id: workRequestId } });
        });
        if (!updated) {
            const current = await this.getWorkRequestOrThrow(workRequestId);
            throw invalidTransition(action, current.status);
        }
        return this.serializeWorkRequest(updated);
    }
    async cancelInvocationsWithoutSession(invocationIds) {
        if (invocationIds.length === 0) {
            return { cancelledInvocationIds: [], cancelledWorkRequestIds: [] };
        }
        const cancelled = await prisma.$transaction(async (tx) => {
            const invocations = await tx.agentInvocation.findMany({
                where: {
                    id: { in: invocationIds },
                    sessionId: null,
                    status: { in: STOPPABLE_INVOCATION_STATUSES },
                },
                select: { id: true, workRequestId: true },
            });
            if (invocations.length === 0) {
                return { invocationIds: [], workRequestIds: [] };
            }
            const cancellableWorkRequestIds = invocations.map((invocation) => invocation.workRequestId);
            await tx.agentInvocation.updateMany({
                where: { id: { in: invocations.map((invocation) => invocation.id) } },
                data: {
                    status: 'CANCELLED',
                    nextRoomReplyReminderAt: null,
                },
            });
            await tx.workRequest.updateMany({
                where: {
                    id: { in: cancellableWorkRequestIds },
                    status: { in: ['PENDING_APPROVAL', 'QUEUED', 'STARTED'] },
                },
                data: { status: 'CANCELLED' },
            });
            return {
                invocationIds: invocations.map((invocation) => invocation.id),
                workRequestIds: cancellableWorkRequestIds,
            };
        });
        for (const invocationId of cancelled.invocationIds) {
            this.releaseInvocationLocks(invocationId);
        }
        return {
            cancelledInvocationIds: cancelled.invocationIds,
            cancelledWorkRequestIds: cancelled.workRequestIds,
        };
    }
    async cancelQueuedWorkRequestsForMember(teamRunId, memberId) {
        const workRequests = await prisma.workRequest.findMany({
            where: {
                teamRunId,
                targetMemberId: memberId,
                status: { in: CANCELLABLE_QUEUED_WORK_REQUEST_STATUSES },
            },
            select: { id: true },
        });
        if (workRequests.length === 0) {
            return [];
        }
        const workRequestIds = workRequests.map((workRequest) => workRequest.id);
        await prisma.workRequest.updateMany({
            where: {
                id: { in: workRequestIds },
                status: { in: CANCELLABLE_QUEUED_WORK_REQUEST_STATUSES },
            },
            data: { status: 'CANCELLED' },
        });
        return workRequestIds;
    }
    async findActiveMemberIds(teamRunId) {
        const activeInvocations = await prisma.agentInvocation.findMany({
            where: {
                teamRunId,
                status: { in: ACTIVE_INVOCATION_STATUSES },
            },
            select: { memberId: true },
        });
        return new Set(activeInvocations.map((invocation) => invocation.memberId));
    }
    async hasActiveInvocation(teamRunId, memberId) {
        const count = await prisma.agentInvocation.count({
            where: {
                teamRunId,
                memberId,
                status: { in: ACTIVE_INVOCATION_STATUSES },
            },
        });
        return count > 0;
    }
    async getOrCreateWorkspaceForMember(teamRun, member) {
        if (member.workspacePolicy === 'dedicated') {
            return this.getOrCreateDedicatedWorkspace(teamRun, member);
        }
        return this.getOrCreateSharedWorkspace(teamRun);
    }
    async resolveQueuedInvocationWorkspaceId(teamRun, member) {
        if (member.workspacePolicy !== 'dedicated') {
            return this.resolveInvocationWorkspaceId(teamRun, member);
        }
        const workspace = await this.getOrCreateDedicatedWorkspace(teamRun, member);
        await this.emitTeamRunInvalidated(teamRun, ['workspaces'], 'agent-invocation-updated');
        return workspace.id;
    }
    async getOrCreateSharedWorkspace(teamRun) {
        const existingClaim = TeamSchedulerService.sharedWorkspaceClaims.get(teamRun.taskId);
        if (existingClaim) {
            return existingClaim;
        }
        const claim = this.findOrCreateSharedWorkspace(teamRun);
        TeamSchedulerService.sharedWorkspaceClaims.set(teamRun.taskId, claim);
        try {
            return await claim;
        }
        finally {
            if (TeamSchedulerService.sharedWorkspaceClaims.get(teamRun.taskId) === claim) {
                TeamSchedulerService.sharedWorkspaceClaims.delete(teamRun.taskId);
            }
        }
    }
    async findOrCreateSharedWorkspace(teamRun) {
        if (this.workspaceService.getOrCreateMainWorkspace) {
            const workspace = await this.workspaceService.getOrCreateMainWorkspace(teamRun.id);
            await this.emitTeamRunInvalidated(teamRun, ['workspaces', 'team-run'], 'agent-invocation-updated');
            return workspace;
        }
        const activeWorkspace = await prisma.workspace.findFirst({
            where: {
                taskId: teamRun.taskId,
                parentWorkspaceId: null,
                status: 'ACTIVE',
            },
            orderBy: [{ createdAt: 'asc' }, { id: 'asc' }],
            select: { id: true },
        });
        if (activeWorkspace) {
            return activeWorkspace;
        }
        const workspace = await this.workspaceService.create(teamRun.taskId);
        await this.emitTeamRunInvalidated(teamRun, ['workspaces'], 'agent-invocation-updated');
        return workspace;
    }
    async getOrCreateDedicatedWorkspace(teamRun, member) {
        if (!this.workspaceService.getOrCreateDedicatedWorkspace) {
            throw new ServiceError('Dedicated workspace startup is not available', 'DEDICATED_WORKSPACE_UNAVAILABLE', 500);
        }
        const workspace = await this.workspaceService.getOrCreateDedicatedWorkspace(teamRun.id, member.id);
        await this.emitTeamRunInvalidated(teamRun, ['workspaces', 'team-run'], 'agent-invocation-updated');
        return workspace;
    }
    async buildSessionPrompt(teamRun, member, workRequest) {
        const triggerMessage = await prisma.roomMessage.findUnique({
            where: { id: workRequest.triggerMessageId },
            select: { content: true, attachmentIds: true },
        });
        const attachmentIds = parseJsonField(triggerMessage?.attachmentIds, []);
        const taskContext = [
            teamRun.task.title.trim(),
            teamRun.task.description?.trim() ?? '',
        ].filter(Boolean).join('\n\n');
        const triggerContent = triggerMessage?.content?.trim() ?? '';
        const instructionParts = [
            taskContext,
            triggerContent && triggerContent !== taskContext ? `Triggering room message:\n${triggerContent}` : '',
            workRequest.instruction.trim() && workRequest.instruction.trim() !== triggerContent
                ? `Work request summary:\n${workRequest.instruction.trim()}`
                : '',
        ].filter(Boolean);
        const instruction = await appendAttachmentMarkdownContext(instructionParts.join('\n\n'), attachmentIds);
        return `${member.rolePrompt}\n\nTask:\n${instruction}`;
    }
    async findResumeSourceSessionId(member, currentSessionId, workspaceId) {
        if (member.sessionPolicy !== 'resume_last') {
            return null;
        }
        const previousInvocation = await prisma.agentInvocation.findFirst({
            where: {
                memberId: member.id,
                workspaceId,
                sessionId: {
                    not: null,
                    notIn: [currentSessionId],
                },
            },
            orderBy: [{ updatedAt: 'desc' }, { createdAt: 'desc' }, { id: 'desc' }],
            select: {
                sessionId: true,
            },
        });
        if (!previousInvocation?.sessionId) {
            return null;
        }
        const previousSession = await prisma.session.findUnique({
            where: { id: previousInvocation.sessionId },
            select: { logSnapshot: true },
        });
        if (!previousSession?.logSnapshot) {
            return null;
        }
        return previousInvocation.sessionId;
    }
    async markInvocationStartFailed(invocationId, sessionId) {
        await prisma.$transaction([
            prisma.agentInvocation.update({
                where: { id: invocationId },
                data: { status: 'FAILED' },
            }),
            prisma.session.update({
                where: { id: sessionId },
                data: { status: 'FAILED' },
            }),
        ]);
    }
    async emitTeamRunInvalidated(teamRun, scopes, reason) {
        await emitTeamRunInvalidated({
            teamRunId: teamRun.id,
            taskId: teamRun.taskId,
            projectId: teamRun.task.projectId,
            scopes,
            reason,
        });
    }
    async emitTeamRunInvalidatedById(teamRunId, scopes, reason) {
        await emitTeamRunInvalidated({ teamRunId, scopes, reason });
    }
    async markSessionFailed(sessionId) {
        await prisma.session.update({
            where: { id: sessionId },
            data: { status: 'FAILED' },
        });
    }
    getRequiredLocks(teamRun, member) {
        const request = {
            teamRunId: teamRun.id,
            memberId: member.id,
            workspaceId: this.resolveLockWorkspaceId(teamRun, member),
            projectId: teamRun.task.projectId,
            capabilities: parseJsonField(member.capabilities, DEFAULT_CAPABILITIES),
            workspacePolicy: member.workspacePolicy,
        };
        return this.lockService.getRequiredLocks(request);
    }
    resolveInvocationWorkspaceId(teamRun, member) {
        if (member.workspacePolicy === 'dedicated') {
            return null;
        }
        if (member.workspacePolicy !== 'shared' && member.workspacePolicy !== 'none') {
            return null;
        }
        return teamRun.mainWorkspaceId ?? teamRun.task.workspaces[0]?.id ?? null;
    }
    resolveLockWorkspaceId(teamRun, member) {
        if (member.workspacePolicy !== 'shared') {
            return null;
        }
        return `task:${teamRun.taskId}`;
    }
    memberSchedulingLockKey(teamRunId, memberId) {
        return `scheduling:${teamRunId}:member:${memberId}`;
    }
    acquireMemberSchedulingLock(lockKey) {
        if (TeamSchedulerService.memberSchedulingLocks.has(lockKey)) {
            return false;
        }
        TeamSchedulerService.memberSchedulingLocks.add(lockKey);
        return true;
    }
    releaseMemberSchedulingLock(lockKey) {
        TeamSchedulerService.memberSchedulingLocks.delete(lockKey);
    }
    serializeWorkRequest(workRequest) {
        return {
            ...workRequest,
            requesterType: workRequest.requesterType,
            ifBusy: workRequest.ifBusy,
            status: workRequest.status,
            createdAt: toIso(workRequest.createdAt),
            updatedAt: toIso(workRequest.updatedAt),
        };
    }
    serializeAgentInvocation(invocation) {
        return {
            ...invocation,
            status: invocation.status,
            createdAt: toIso(invocation.createdAt),
            updatedAt: toIso(invocation.updatedAt),
            nextRoomReplyReminderAt: invocation.nextRoomReplyReminderAt
                ? toIso(invocation.nextRoomReplyReminderAt)
                : null,
        };
    }
}
//# sourceMappingURL=team-scheduler.service.js.map