import path from "node:path";
import { readFile } from "node:fs/promises";
import { listBackgroundTasks } from "../background-state/index.js";
import { appendEvent, readTraceSummary } from "../observability/index.js";
import { addProfessorMemory } from "../professor-memory/index.js";
import { getRun, listTasks, runDir, updateRun } from "../run-state/index.js";
import { evaluateRunBudget, refreshRunBudgetEvaluation } from "../run-budget/index.js";
import { listSubplans } from "../subplan/index.js";
import { loadTeamRuntime, listTeamTasks } from "../team-mode/index.js";
import { listWorkspaceConflicts, listWorkspaceLeases } from "../workspace-state/index.js";
import { createId, ensureDir, nowIso, readJsonFile, sanitizeText, writeJsonFile, writeTextFile } from "../../shared/index.js";
import { LAB_REPRESENTATIVE_AGENT } from "../../agents/identity.js";
export async function initializeLabMeetingState(store, runId, timestamp = nowIso(), input = {}) {
    const existing = await readLabMeetingState(store, runId);
    if (existing)
        return existing;
    if (!store.config.lab_meeting.enabled && !input.forceEnabled)
        return undefined;
    const run = await getRun(store, runId);
    const base = run?.created_at ?? timestamp;
    const state = {
        version: 1,
        run_id: runId,
        enabled: input.forceEnabled ?? store.config.lab_meeting.enabled,
        interval_minutes: normalizedPositiveMinutes(store.config.lab_meeting.interval_minutes, 60),
        grace_minutes: normalizedNonNegativeMinutes(store.config.lab_meeting.grace_minutes, 0),
        require_feedback_before_resume: store.config.lab_meeting.require_feedback_before_resume,
        status: "scheduled",
        sequence: 0,
        next_deadline_at: addMinutes(base, normalizedPositiveMinutes(store.config.lab_meeting.interval_minutes, 60)),
    };
    await writeLabMeetingState(store, runId, state);
    return state;
}
export async function readLabMeetingState(store, runId) {
    const raw = await readJsonFile(labMeetingStatePath(store, runId));
    if (!raw)
        return undefined;
    return normalizeState(store, runId, raw);
}
export async function peekLabMeetingDeadline(store, runId, input = {}) {
    const existingState = await readLabMeetingState(store, runId);
    if (existingState?.enabled === false)
        return { enabled: false, triggered: false, state: existingState };
    if (!store.config.lab_meeting.enabled && existingState?.enabled !== true)
        return { enabled: false, triggered: false };
    const now = input.now ?? nowIso();
    if (!existingState)
        return { enabled: true, triggered: false };
    if (existingState.status === "awaiting_feedback") {
        return {
            enabled: true,
            triggered: false,
            state: existingState,
            message: "SEED is awaiting professor feedback for the current Lab Meeting.",
        };
    }
    if (existingState.status === "wrap_up") {
        const activeBackgrounds = await listActiveBackgrounds(store, runId);
        return {
            enabled: true,
            triggered: false,
            state: existingState,
            message: activeBackgrounds.length
                ? `Lab Meeting wrap-up is active; waiting for ${formatBackgroundWaitList(activeBackgrounds)} to finish.`
                : "Lab Meeting wrap-up is active; no running background work remains.",
        };
    }
    if (store.config.lab_meeting.enabled && safeTime(now) >= safeTime(existingState.next_deadline_at)) {
        return {
            enabled: true,
            triggered: false,
            state: existingState,
            message: "Lab Meeting is available; /seed-lab-meeting can pause the run for professor feedback.",
        };
    }
    return { enabled: true, triggered: false, state: existingState };
}
export async function checkLabMeetingDeadline(store, runId, input = {}) {
    const existingState = await readLabMeetingState(store, runId);
    if (existingState?.enabled === false)
        return { enabled: false, triggered: false, state: existingState };
    if (!store.config.lab_meeting.enabled && !existingState)
        return { enabled: false, triggered: false };
    const now = input.now ?? nowIso();
    let state = existingState ?? await initializeLabMeetingState(store, runId, now, { forceEnabled: true });
    if (!state)
        return { enabled: false, triggered: false };
    if (state.status === "awaiting_feedback") {
        return { enabled: true, triggered: false, state, message: "SEED is awaiting professor feedback for the current Lab Meeting." };
    }
    if (!store.config.lab_meeting.enabled && state.status === "scheduled") {
        return { enabled: true, triggered: false, state };
    }
    if (state.status === "scheduled" && safeTime(now) < safeTime(state.next_deadline_at)) {
        return { enabled: true, triggered: false, state };
    }
    if (state.status === "scheduled") {
        state = await requestLabMeetingWrapUp(store, runId, state, now, input.reason ?? "deadline");
    }
    if (state.status === "wrap_up") {
        const activeBackgrounds = await listActiveBackgrounds(store, runId);
        if (activeBackgrounds.length > 0) {
            const waiting = activeBackgrounds.map((task) => task.background_id);
            const next = await updateWrapUpWaitingBackgrounds(store, runId, state, waiting);
            return {
                enabled: true,
                triggered: false,
                state: next,
                message: `Lab Meeting wrap-up requested; waiting for ${formatBackgroundWaitList(activeBackgrounds)} to finish before professor report.`,
            };
        }
        const artifact = await finalizeLabMeeting(store, runId, state, now);
        const nextState = (await readLabMeetingState(store, runId)) ?? state;
        return {
            enabled: true,
            triggered: true,
            state: nextState,
            artifact,
            message: "SEED paused for a Lab Meeting and is awaiting professor feedback.",
        };
    }
    return { enabled: true, triggered: false, state };
}
export async function triggerLabMeetingNow(store, runId, reason = "manual") {
    let existingState = await readLabMeetingState(store, runId);
    const now = nowIso();
    if (existingState?.enabled === false) {
        existingState = {
            ...existingState,
            enabled: true,
            next_deadline_at: addMinutes(now, existingState.interval_minutes),
        };
        await writeLabMeetingState(store, runId, existingState);
    }
    const existing = existingState ?? await initializeLabMeetingState(store, runId, now, { forceEnabled: true });
    if (!existing)
        return { enabled: false, triggered: false };
    const state = existing.status === "scheduled"
        ? await requestLabMeetingWrapUp(store, runId, existing, now, reason)
        : existing;
    if (state.status === "wrap_up") {
        const activeBackgrounds = await listActiveBackgrounds(store, runId);
        if (activeBackgrounds.length > 0) {
            const waiting = activeBackgrounds.map((task) => task.background_id);
            const next = await updateWrapUpWaitingBackgrounds(store, runId, state, waiting);
            return {
                enabled: true,
                triggered: false,
                state: next,
                message: `Lab Meeting wrap-up requested; waiting for ${formatBackgroundWaitList(activeBackgrounds)} to finish before professor report.`,
            };
        }
    }
    const artifact = state.status === "awaiting_feedback"
        ? await readCurrentMeetingArtifact(store, runId, state)
        : await finalizeLabMeeting(store, runId, state, now);
    return {
        enabled: true,
        triggered: true,
        state: (await readLabMeetingState(store, runId)) ?? state,
        artifact,
        message: "SEED paused for a Lab Meeting and is awaiting professor feedback.",
    };
}
export async function markLabMeetingRepresentativeReportRequested(store, runId, timestamp = nowIso()) {
    const state = await readLabMeetingState(store, runId);
    if (!state)
        return { changed: false };
    if (state.representative_report_requested_at)
        return { state, changed: false };
    const next = {
        ...state,
        representative_report_requested_at: timestamp,
    };
    await writeLabMeetingState(store, runId, next);
    return { state: next, changed: true };
}
export async function resumeLabMeeting(store, runId, input = {}) {
    const state = await initializeLabMeetingState(store, runId, input.now ?? nowIso());
    if (!state)
        throw new Error("Lab Meeting Mode is disabled.");
    if (!state.enabled)
        throw new Error("Lab Meeting Mode is disabled for this run.");
    const feedback = sanitizeText(input.feedback ?? "").trim();
    if (state.require_feedback_before_resume && !feedback) {
        throw new Error("Lab Meeting resume requires professor feedback.");
    }
    const now = input.now ?? nowIso();
    const next = {
        ...state,
        status: "scheduled",
        next_deadline_at: addMinutes(now, state.interval_minutes),
        last_resumed_at: now,
        latest_professor_feedback: feedback || state.latest_professor_feedback,
        latest_decision: sanitizeText(input.decision ?? "continue"),
        current_meeting_id: undefined,
        current_meeting_ref: state.current_meeting_ref,
        current_meeting_json_ref: state.current_meeting_json_ref,
        wrap_up_requested_at: undefined,
        grace_expires_at: undefined,
        waiting_background_ids: undefined,
        awaiting_feedback_at: undefined,
        representative_report_requested_at: undefined,
    };
    await updateLatestMeetingArtifact(store, runId, state, {
        status: "resumed",
        professor_feedback: feedback || undefined,
        professor_decision: next.latest_decision,
        resumed_at: now,
    });
    await writeLabMeetingState(store, runId, next);
    await updateRun(store, runId, { status: "running" });
    await appendEvent(store, runId, {
        type: "lab_meeting_resumed",
        actor: "professor",
        summary: `Lab Meeting resumed: ${next.latest_decision}`,
        metadata: {
            meeting_id: state.current_meeting_id,
            feedback,
            decision: next.latest_decision,
            next_deadline_at: next.next_deadline_at,
        },
    });
    if (feedback) {
        await addProfessorMemory(store, {
            kind: "feedback",
            source: "lab_meeting",
            source_ref: `lab_meeting:${runId}:${state.sequence}`,
            run_id: runId,
            title: "Lab Meeting feedback",
            detail: feedback,
            why: "The professor sent normal chat feedback after a manual Lab Meeting to confirm or redirect how SEED should continue.",
            how_to_apply: `When planning the next sub-plan or dispatch, apply this professor feedback: ${feedback}`,
            reusable: true,
            tags: ["lab_meeting", "feedback"],
        });
    }
    return next;
}
export async function setLabMeetingEnabled(store, runId, enabled, now = nowIso()) {
    const existing = await readLabMeetingState(store, runId);
    const state = existing ?? await initializeLabMeetingState(store, runId, now, { forceEnabled: true });
    if (!state)
        throw new Error("Lab Meeting Mode is disabled by configuration and cannot be initialized.");
    const run = await getRun(store, runId);
    const next = {
        ...state,
        enabled,
        status: enabled ? state.status : "scheduled",
        next_deadline_at: enabled && state.enabled ? state.next_deadline_at : addMinutes(now, state.interval_minutes),
        current_meeting_id: enabled ? state.current_meeting_id : undefined,
        current_meeting_ref: enabled ? state.current_meeting_ref : undefined,
        current_meeting_json_ref: enabled ? state.current_meeting_json_ref : undefined,
        wrap_up_requested_at: enabled ? state.wrap_up_requested_at : undefined,
        grace_expires_at: enabled ? state.grace_expires_at : undefined,
        waiting_background_ids: enabled ? state.waiting_background_ids : undefined,
        awaiting_feedback_at: enabled ? state.awaiting_feedback_at : undefined,
        representative_report_requested_at: enabled ? state.representative_report_requested_at : undefined,
    };
    await writeLabMeetingState(store, runId, next);
    const unblockedRun = !enabled && run?.status === "awaiting_feedback";
    if (unblockedRun)
        await updateRun(store, runId, { status: "running" });
    await appendEvent(store, runId, {
        type: enabled ? "lab_meeting_enabled" : "lab_meeting_disabled",
        actor: "professor",
        summary: enabled ? "Lab Meeting Mode enabled for run." : "Lab Meeting Mode disabled for run.",
        metadata: { next_deadline_at: next.next_deadline_at, run_unblocked: unblockedRun },
    });
    return next;
}
export async function assertLabMeetingAllowsInternalWork(store, runId, action) {
    const state = await readLabMeetingState(store, runId);
    if (!state || !state.enabled || state.status === "scheduled")
        return { allowed: true, state };
    return {
        allowed: false,
        state,
        reason: state.status === "awaiting_feedback"
            ? `SEED is paused for Lab Meeting feedback; ${action} is blocked until the professor sends normal feedback in the Lab-Representative chat.`
            : `SEED is in a manual Lab Meeting background-completion wait; ${action} is blocked while already-running background workers finish.`,
        meeting_path: state.current_meeting_ref,
    };
}
export function formatLabMeetingNotice(result) {
    if (!result.enabled)
        return result.message ?? "Lab Meeting Mode is disabled.";
    const state = result.state;
    if (!state)
        return result.message ?? "Lab Meeting Mode is disabled.";
    const meeting = result.artifact;
    const meetingPath = state.current_meeting_ref ?? "(meeting artifact pending)";
    const blockers = meeting?.summary.blockers.length ? meeting.summary.blockers.map((blocker) => `- ${blocker}`).join("\n") : "- no blocker recorded";
    const waiting = state.waiting_background_ids?.length ? state.waiting_background_ids.join(", ") : "none";
    return `SEED Lab Meeting
Status: ${state.status === "awaiting_feedback" ? "awaiting professor feedback" : state.status}
Meeting: ${state.current_meeting_id ?? "(scheduled)"}
Deadline: ${state.next_deadline_at}
Summary: ${meetingPath}
Waiting backgrounds: ${waiting}
Framework note: ${result.message ?? "Lab Meeting state loaded."}

Professor feedback options:
- continue
- redirect priorities
- stop
- finalize
- change priorities

Current blockers:
${blockers}

Send a normal professor feedback message in the Lab-Representative chat to resume and let SEED reschedule from that feedback.`;
}
async function requestLabMeetingWrapUp(store, runId, state, timestamp, reason) {
    const sequence = state.sequence + 1;
    const meetingId = `meeting_${sequence}_${createId("lab").replace(/^lab_/, "")}`;
    const activeBackgrounds = await listActiveBackgrounds(store, runId);
    const next = {
        ...state,
        status: "wrap_up",
        sequence,
        current_meeting_id: meetingId,
        wrap_up_requested_at: timestamp,
        grace_expires_at: undefined,
        waiting_background_ids: activeBackgrounds.map((task) => task.background_id),
        representative_report_requested_at: undefined,
    };
    await writeLabMeetingState(store, runId, next);
    await appendEvent(store, runId, {
        type: "lab_meeting_wrap_up_requested",
        actor: store.config.default_run_agent ?? LAB_REPRESENTATIVE_AGENT,
        summary: `Lab Meeting requested (${reason}); wait for already-running background work to finish before professor report.`,
        metadata: {
            meeting_id: meetingId,
            deadline_at: state.next_deadline_at,
            waiting_background_ids: next.waiting_background_ids ?? [],
            active_background_count: activeBackgrounds.length,
        },
    });
    return next;
}
async function finalizeLabMeeting(store, runId, state, timestamp) {
    const meetingId = state.current_meeting_id ?? `meeting_${state.sequence + 1}_${createId("lab").replace(/^lab_/, "")}`;
    const artifact = await buildMeetingArtifact(store, runId, state, meetingId, timestamp);
    const refs = await writeMeetingArtifacts(store, runId, artifact);
    const next = {
        ...state,
        status: "awaiting_feedback",
        current_meeting_id: meetingId,
        current_meeting_ref: refs.markdown,
        current_meeting_json_ref: refs.json,
        awaiting_feedback_at: timestamp,
        representative_report_requested_at: undefined,
    };
    await writeLabMeetingState(store, runId, next);
    await updateRun(store, runId, { status: "awaiting_feedback" });
    await appendEvent(store, runId, {
        type: "lab_meeting_awaiting_feedback",
        actor: store.config.default_run_agent ?? LAB_REPRESENTATIVE_AGENT,
        summary: "Lab Meeting summary written; awaiting professor feedback.",
        metadata: {
            meeting_id: meetingId,
            markdown: refs.markdown,
            json: refs.json,
        },
    });
    return artifact;
}
async function buildMeetingArtifact(store, runId, state, meetingId, timestamp) {
    const [run, tasks, teamRuntime, teamTasks, backgrounds, workspaceLeases, workspaceConflicts, trace, evidenceText, subplans, budgetState] = await Promise.all([
        getRun(store, runId),
        listTasks(store, runId),
        loadTeamRuntime(store, runId),
        listTeamTasks(store, runId),
        listBackgroundTasks(store, runId),
        listWorkspaceLeases(store, runId),
        listWorkspaceConflicts(store, runId),
        readTraceSummary(store, runId),
        readEvidence(store, runId),
        listSubplans(store, runId),
        refreshRunBudgetEvaluation(store, runId),
    ]);
    const budgetEvaluation = await evaluateRunBudget(store, budgetState);
    const staleBackgrounds = backgrounds.filter((task) => task.status === "running" && task.stale_at);
    const blockers = [
        ...tasks.filter((task) => task.status === "blocked").map((task) => `${task.title}: ${task.blocker ?? "blocked"}`),
        ...teamTasks.filter((task) => task.status === "blocked").map((task) => `${task.title}: ${task.blocker ?? "blocked"}`),
        ...backgrounds.filter((task) => task.status === "failed").map((task) => `${task.target_agent ?? task.kind} failed: ${task.last_message ?? task.error ?? "review output"}`),
        ...staleBackgrounds.map((task) => `${task.target_agent ?? task.kind} stale: ${task.stale_reason ?? task.last_message ?? "review stale worker"}`),
        ...workspaceConflicts.filter((conflict) => conflict.status === "open").map((conflict) => `workspace ${conflict.path}: ${conflict.summary}`),
    ].map((line) => sanitizeText(line).slice(0, 240)).slice(0, 12);
    return {
        version: 1,
        meeting_id: meetingId,
        run_id: runId,
        sequence: state.sequence,
        status: "awaiting_feedback",
        created_at: timestamp,
        deadline_at: state.next_deadline_at,
        wrap_up_requested_at: state.wrap_up_requested_at ?? timestamp,
        background_wait_started_at: state.wrap_up_requested_at ?? timestamp,
        waiting_background_ids: state.waiting_background_ids ?? [],
        grace_expires_at: state.grace_expires_at,
        professor_goal: run?.professor_goal ?? "Unknown professor goal",
        summary: {
            run_status: "awaiting_feedback",
            task_counts: countBy(tasks.map((task) => task.status)),
            team_task_counts: countBy(teamTasks.map((task) => task.status)),
            team_member_counts: countBy((teamRuntime?.members ?? []).map((member) => member.status)),
            background_counts: countBy(backgrounds.map((task) => task.status)),
            subplan_counts: countBy(subplans.map((subplan) => subplan.status)),
            model_call_counts: countBy(trace.modelCalls.map((call) => call.status)),
            tool_call_counts: countBy(trace.toolCalls.map((call) => call.status)),
            budget_status: budgetEvaluation.status,
            budget_warnings: budgetEvaluation.warnings,
            stale_backgrounds: staleBackgrounds.map((task) => `${task.background_id} ${task.target_agent ?? task.kind}: ${task.stale_reason ?? task.stale_at}`).slice(0, 8),
            active_workspace_leases: workspaceLeases.filter((lease) => lease.status === "active").length,
            open_workspace_conflicts: workspaceConflicts.filter((conflict) => conflict.status === "open").length,
            evidence_refs: evidenceRefs(evidenceText),
            blockers,
            recent_events: trace.events.slice(-8).map((event) => `${event.type}: ${event.summary}`),
        },
        recommended_actions: recommendedActions(blockers, backgrounds.some((task) => task.status === "running")),
        feedback_options: ["continue", "redirect priorities", "stop", "finalize", "change priorities"],
    };
}
async function updateLatestMeetingArtifact(store, runId, state, patch) {
    if (!state.current_meeting_id)
        return;
    const existing = await readCurrentMeetingArtifact(store, runId, state);
    if (!existing)
        return;
    const updated = { ...existing, ...patch };
    await writeMeetingArtifacts(store, runId, updated);
}
async function readCurrentMeetingArtifact(store, runId, state) {
    if (!state.current_meeting_id)
        return undefined;
    return readJsonFile(meetingJsonPath(store, runId, state.current_meeting_id));
}
async function writeMeetingArtifacts(store, runId, artifact) {
    await ensureDir(path.join(runDir(store, runId), "meetings"));
    const json = meetingJsonPath(store, runId, artifact.meeting_id);
    const markdown = meetingMarkdownPath(store, runId, artifact.meeting_id);
    await writeJsonFile(json, artifact);
    await writeTextFile(markdown, renderMeetingMarkdown(artifact));
    return { json, markdown };
}
function renderMeetingMarkdown(artifact) {
    return `${[
        "# SEED Lab Meeting",
        "",
        `Meeting: ${artifact.meeting_id}`,
        `Run: ${artifact.run_id}`,
        `Status: ${artifact.status}`,
        `Created: ${artifact.created_at}`,
        `Deadline: ${artifact.deadline_at}`,
        `Background wait started: ${artifact.background_wait_started_at}`,
        `Waited backgrounds: ${artifact.waiting_background_ids.length ? artifact.waiting_background_ids.join(", ") : "none"}`,
        `Professor goal: ${sanitizeText(artifact.professor_goal)}`,
        "",
        "## Summary",
        "",
        `- run status: ${artifact.summary.run_status}`,
        `- tasks: ${formatCounts(artifact.summary.task_counts)}`,
        `- team tasks: ${formatCounts(artifact.summary.team_task_counts)}`,
        `- team members: ${formatCounts(artifact.summary.team_member_counts)}`,
        `- background work: ${formatCounts(artifact.summary.background_counts)}`,
        `- sub-plans: ${formatCounts(artifact.summary.subplan_counts ?? {})}`,
        `- model calls: ${formatCounts(artifact.summary.model_call_counts)}`,
        `- tool calls: ${formatCounts(artifact.summary.tool_call_counts)}`,
        `- budget: ${artifact.summary.budget_status ?? "unknown"}`,
        ...((artifact.summary.budget_warnings ?? []).length ? (artifact.summary.budget_warnings ?? []).map((warning) => `- budget warning: ${warning}`) : []),
        ...((artifact.summary.stale_backgrounds ?? []).length ? (artifact.summary.stale_backgrounds ?? []).map((line) => `- stale worker: ${line}`) : []),
        `- active workspace leases: ${artifact.summary.active_workspace_leases}`,
        `- open workspace conflicts: ${artifact.summary.open_workspace_conflicts}`,
        "",
        "## Blockers",
        "",
        renderList(artifact.summary.blockers.length ? artifact.summary.blockers : ["no blocker recorded"]),
        "",
        "## Recent Events",
        "",
        renderList(artifact.summary.recent_events),
        "",
        "## Evidence",
        "",
        renderList(artifact.summary.evidence_refs),
        "",
        "## Recommended Next Actions",
        "",
        renderList(artifact.recommended_actions),
        "",
        "## Professor Feedback Options",
        "",
        renderList(artifact.feedback_options),
        "",
        ...(artifact.professor_feedback ? ["## Recorded Feedback", "", sanitizeText(artifact.professor_feedback), ""] : []),
    ].join("\n")}\n`;
}
function recommendedActions(blockers, hasRunningBackground) {
    if (blockers.length)
        return ["Resolve the listed blocker before continuing.", "Redirect priorities if the blocker changes the professor goal."];
    if (hasRunningBackground)
        return ["Review active background checkpoints.", "Continue if the current direction is still useful."];
    return ["Continue with the current plan.", "Redirect priorities if the current plan is no longer the right research direction.", "Finalize if the report already satisfies the professor goal."];
}
async function listActiveBackgrounds(store, runId) {
    return (await listBackgroundTasks(store, runId)).filter((task) => task.status === "running");
}
async function updateWrapUpWaitingBackgrounds(store, runId, state, waitingBackgroundIds) {
    const current = state.waiting_background_ids ?? [];
    if (sameStringList(current, waitingBackgroundIds))
        return state;
    const next = {
        ...state,
        waiting_background_ids: waitingBackgroundIds,
    };
    await writeLabMeetingState(store, runId, next);
    return next;
}
function formatBackgroundWaitList(backgrounds) {
    return backgrounds
        .map((task) => `${task.background_id}${task.target_agent ? `/${task.target_agent}` : ""}`)
        .join(", ");
}
function sameStringList(left, right) {
    if (left.length !== right.length)
        return false;
    return left.every((value, index) => value === right[index]);
}
function meetingStatePath(store, runId) {
    return labMeetingStatePath(store, runId);
}
function labMeetingStatePath(store, runId) {
    return path.join(runDir(store, runId), "meetings", "state.json");
}
function meetingJsonPath(store, runId, meetingId) {
    return path.join(runDir(store, runId), "meetings", `${meetingId}.json`);
}
function meetingMarkdownPath(store, runId, meetingId) {
    return path.join(runDir(store, runId), "meetings", `${meetingId}.md`);
}
async function writeLabMeetingState(store, runId, state) {
    await writeJsonFile(meetingStatePath(store, runId), state);
}
function normalizeState(store, runId, raw) {
    const interval = normalizedPositiveMinutes(raw.interval_minutes ?? store.config.lab_meeting.interval_minutes, 60);
    const grace = normalizedNonNegativeMinutes(raw.grace_minutes ?? store.config.lab_meeting.grace_minutes, 0);
    return {
        version: 1,
        run_id: runId,
        enabled: raw.enabled ?? store.config.lab_meeting.enabled,
        interval_minutes: interval,
        grace_minutes: grace,
        require_feedback_before_resume: raw.require_feedback_before_resume ?? store.config.lab_meeting.require_feedback_before_resume,
        status: isStateStatus(raw.status) ? raw.status : "scheduled",
        sequence: typeof raw.sequence === "number" && Number.isFinite(raw.sequence) ? raw.sequence : 0,
        next_deadline_at: typeof raw.next_deadline_at === "string" ? raw.next_deadline_at : addMinutes(nowIso(), interval),
        current_meeting_id: stringValue(raw.current_meeting_id),
        current_meeting_ref: stringValue(raw.current_meeting_ref),
        current_meeting_json_ref: stringValue(raw.current_meeting_json_ref),
        wrap_up_requested_at: stringValue(raw.wrap_up_requested_at),
        grace_expires_at: stringValue(raw.grace_expires_at),
        representative_report_requested_at: stringValue(raw.representative_report_requested_at),
        waiting_background_ids: Array.isArray(raw.waiting_background_ids) ? raw.waiting_background_ids.filter((value) => typeof value === "string") : undefined,
        awaiting_feedback_at: stringValue(raw.awaiting_feedback_at),
        last_resumed_at: stringValue(raw.last_resumed_at),
        latest_professor_feedback: stringValue(raw.latest_professor_feedback),
        latest_decision: stringValue(raw.latest_decision),
    };
}
async function readEvidence(store, runId) {
    try {
        return await readFile(path.join(runDir(store, runId), "evidence.md"), "utf8");
    }
    catch {
        return "";
    }
}
function evidenceRefs(text) {
    return [...text.matchAll(/id="([^"]+)"/g)].map((match) => `evidence.md#${match[1]}`).slice(-12);
}
function countBy(values) {
    const output = {};
    for (const value of values)
        output[value] = (output[value] ?? 0) + 1;
    return output;
}
function formatCounts(counts) {
    return Object.entries(counts).map(([status, count]) => `${status}:${count}`).join(", ") || "none";
}
function renderList(items) {
    return items.length ? items.map((item) => `- ${sanitizeText(item)}`).join("\n") : "- none";
}
function addMinutes(timestamp, minutes) {
    return new Date(safeTime(timestamp) + minutes * 60 * 1000).toISOString();
}
function safeTime(timestamp) {
    const date = new Date(timestamp);
    return Number.isFinite(date.getTime()) ? date.getTime() : Date.now();
}
function normalizedPositiveMinutes(value, fallback) {
    return Number.isFinite(value) && value >= 1 ? Math.floor(value) : fallback;
}
function normalizedNonNegativeMinutes(value, fallback) {
    return Number.isFinite(value) && value >= 0 ? Math.floor(value) : fallback;
}
function isStateStatus(value) {
    return value === "scheduled" || value === "wrap_up" || value === "awaiting_feedback";
}
function stringValue(value) {
    return typeof value === "string" && value.length > 0 ? value : undefined;
}
//# sourceMappingURL=index.js.map