"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.forkMainDialogTreeAtGeneration = forkMainDialogTreeAtGeneration;
const promises_1 = __importDefault(require("node:fs/promises"));
const node_path_1 = __importDefault(require("node:path"));
const storage_1 = require("@longrun-ai/kernel/types/storage");
const time_1 = require("@longrun-ai/kernel/utils/time");
const dialog_1 = require("./dialog");
const dialog_latest_state_1 = require("./dialog-latest-state");
const persistence_1 = require("./persistence");
const tool_1 = require("./tool");
const id_1 = require("./utils/id");
const FORK_BASELINE_ANCHOR = (0, storage_1.toRootGenerationAnchor)({
    rootCourse: 1,
    rootGenseq: 0,
});
function compareRootAnchor(left, right) {
    if (left.rootCourse < right.rootCourse)
        return -1;
    if (left.rootCourse > right.rootCourse)
        return 1;
    if (left.rootGenseq < right.rootGenseq)
        return -1;
    if (left.rootGenseq > right.rootGenseq)
        return 1;
    return 0;
}
function anchorAtOrBefore(candidate, cutoff) {
    return compareRootAnchor(candidate, cutoff) <= 0;
}
function cloneReminderSnapshot(snapshot) {
    return (0, tool_1.materializeReminder)({
        id: snapshot.id,
        content: snapshot.content,
        owner: undefined,
        meta: snapshot.meta,
        echoback: snapshot.echoback,
        scope: snapshot.scope ?? 'dialog',
        renderMode: snapshot.renderMode ?? 'markdown',
        createdAt: snapshot.createdAt,
        priority: snapshot.priority,
    });
}
function cloneQuestions(questions) {
    return questions.map((question) => ({
        ...question,
        callSiteRef: { ...question.callSiteRef },
    }));
}
function cloneActiveCalleeDispatches(activeCalleeDispatches) {
    return activeCalleeDispatches.map((entry) => ({
        ...entry,
        mentionList: entry.mentionList ? [...entry.mentionList] : undefined,
    }));
}
function cloneRegistryEntries(entries) {
    return entries.map((entry) => ({ ...entry }));
}
function cloneSideDialogResponses(responses) {
    return responses.map((response) => ({
        ...response,
        mentionList: response.mentionList ? [...response.mentionList] : undefined,
    }));
}
function rewriteForkTreeDialogSelfId(sourceDialogSelfId, sourceRootId, targetRootId) {
    return sourceDialogSelfId === sourceRootId ? targetRootId : sourceDialogSelfId;
}
function rewriteAssignmentFromAskerForFork(assignmentFromAsker, sourceRootId, targetRootId) {
    return {
        ...assignmentFromAsker,
        askerDialogId: rewriteForkTreeDialogSelfId(assignmentFromAsker.askerDialogId, sourceRootId, targetRootId),
        mentionList: assignmentFromAsker.mentionList ? [...assignmentFromAsker.mentionList] : undefined,
        collectiveTargets: assignmentFromAsker.collectiveTargets
            ? [...assignmentFromAsker.collectiveTargets]
            : undefined,
    };
}
function rewriteSideDialogMetadataForFork(metadata) {
    return {
        ...metadata,
    };
}
function rewriteTellaskReplyDirectiveForFork(directive, sourceRootId, targetRootId) {
    return {
        ...directive,
        targetDialogId: rewriteForkTreeDialogSelfId(directive.targetDialogId, sourceRootId, targetRootId),
    };
}
function rewriteSideDialogAskerStackStateForFork(state, sourceRootId, targetRootId) {
    return {
        askerStack: state.askerStack.map((frame) => ({
            kind: 'asker_dialog_stack_frame',
            askerDialogId: rewriteForkTreeDialogSelfId(frame.askerDialogId, sourceRootId, targetRootId),
            ...(frame.assignmentFromAsker === undefined
                ? {}
                : {
                    assignmentFromAsker: rewriteAssignmentFromAskerForFork(frame.assignmentFromAsker, sourceRootId, targetRootId),
                }),
            ...(frame.tellaskReplyObligation === undefined
                ? {}
                : {
                    tellaskReplyObligation: rewriteTellaskReplyDirectiveForFork(frame.tellaskReplyObligation, sourceRootId, targetRootId),
                }),
        })),
    };
}
function isForkStateRecord(record) {
    switch (record.type) {
        case 'sideDialog_created_record':
        case 'reminders_reconciled_record':
        case 'questions4human_reconciled_record':
        case 'active_callees_reconciled_record':
        case 'sideDialog_registry_reconciled_record':
        case 'sideDialog_responses_reconciled_record':
            return record;
        default:
            return null;
    }
}
function isPersistedMessageRecord(record) {
    switch (record.type) {
        case 'agent_thought_record':
        case 'agent_words_record':
        case 'ui_only_markdown_record':
        case 'runtime_guide_record':
        case 'human_text_record':
        case 'func_call_record':
        case 'tellask_call_record':
        case 'func_result_record':
        case 'tellask_result_record':
        case 'tellask_carryover_record':
            return true;
        // UI-only timeline records belong to the retained transcript for replay/forking, but they do
        // not contribute to message-count / context reconstruction semantics.
        case 'web_search_call_record':
        case 'native_tool_call_record':
        case 'tool_result_image_ingest_record':
        case 'user_image_ingest_record':
        case 'sideDialog_request_record':
        case 'tellask_reply_resolution_record':
        case 'tellask_anchor_record':
        case 'tellask_callee_record':
        case 'gen_start_record':
        case 'gen_finish_record':
        case 'sideDialog_created_record':
        case 'reminders_reconciled_record':
        case 'questions4human_reconciled_record':
        case 'active_callees_reconciled_record':
        case 'sideDialog_registry_reconciled_record':
        case 'sideDialog_responses_reconciled_record':
            return false;
        default: {
            const _exhaustive = record;
            throw new Error(`Unhandled persisted record while counting messages: ${String(_exhaustive)}`);
        }
    }
}
function getRecordRootAnchor(record) {
    const rootCourse = 'rootCourse' in record && typeof record.rootCourse === 'number' ? record.rootCourse : null;
    const rootGenseq = 'rootGenseq' in record && typeof record.rootGenseq === 'number' ? record.rootGenseq : null;
    if (rootCourse === null || rootGenseq === null) {
        return null;
    }
    return (0, storage_1.toRootGenerationAnchor)({
        rootCourse,
        rootGenseq,
    });
}
function normalizeDraftUserText(events, targetGenseq) {
    const texts = events
        .filter((event) => event.type === 'human_text_record' && event.genseq === targetGenseq)
        .map((event) => event.content.trim())
        .filter((value) => value !== '');
    if (texts.length === 0)
        return null;
    return texts.join('\n\n');
}
function rewriteContentItemsForFork(items, newRootId) {
    if (!items)
        return undefined;
    return items.map((item) => {
        switch (item.type) {
            case 'input_text':
                return item;
            case 'input_image':
                return {
                    ...item,
                    artifact: {
                        ...item.artifact,
                        rootId: newRootId,
                    },
                };
            default: {
                const _exhaustive = item;
                return _exhaustive;
            }
        }
    });
}
function rewriteRecordForFork(record, newRootId) {
    switch (record.type) {
        case 'agent_thought_record':
        case 'agent_words_record':
        case 'ui_only_markdown_record':
        case 'runtime_guide_record':
        case 'func_call_record':
        case 'tellask_call_record':
        // UI-only transcript records are safe to copy verbatim into the forked transcript. They are
        // not part of baseline state reconciliation and must not make forking fail.
        case 'web_search_call_record':
        case 'native_tool_call_record':
        case 'sideDialog_request_record':
        case 'tellask_reply_resolution_record':
        case 'tellask_anchor_record':
        case 'tellask_callee_record':
        case 'gen_start_record':
        case 'gen_finish_record':
            return record;
        case 'human_text_record':
        case 'tellask_result_record':
        case 'tellask_carryover_record':
            return {
                ...record,
                contentItems: rewriteContentItemsForFork(record.contentItems, newRootId),
            };
        case 'func_result_record':
            return {
                ...record,
                contentItems: rewriteContentItemsForFork(record.contentItems, newRootId),
            };
        case 'tool_result_image_ingest_record':
        case 'user_image_ingest_record':
            return {
                ...record,
                artifact: {
                    ...record.artifact,
                    rootId: newRootId,
                },
            };
        case 'sideDialog_created_record':
        case 'reminders_reconciled_record':
        case 'questions4human_reconciled_record':
        case 'active_callees_reconciled_record':
        case 'sideDialog_registry_reconciled_record':
        case 'sideDialog_responses_reconciled_record':
            throw new Error(`Fork transcript copy must not include state record ${record.type}`);
        default: {
            const _exhaustive = record;
            return _exhaustive;
        }
    }
}
function countMessages(events) {
    let count = 0;
    for (const event of events) {
        if (isPersistedMessageRecord(event)) {
            count += 1;
        }
    }
    return count;
}
function countFunctionCalls(events) {
    let count = 0;
    for (const event of events) {
        if (event.type === 'func_call_record' || event.type === 'tellask_call_record') {
            count += 1;
        }
    }
    return count;
}
function computeRootForkDisplayState(args) {
    const topFrame = args.askerStack?.askerStack[args.askerStack.askerStack.length - 1];
    if (topFrame?.tellaskReplyObligation !== undefined) {
        return { kind: 'proceeding' };
    }
    if (args.action.kind === 'draft_user_text') {
        return { kind: 'idle_waiting_user' };
    }
    const hasQ4H = args.questions.length > 0;
    if (hasQ4H) {
        return { kind: 'blocked', reason: { kind: 'needs_human_input' } };
    }
    return { kind: 'stopped', reason: { kind: 'fork_continue_ready' }, continueEnabled: true };
}
function computeSideDialogForkDisplayState(askerStack) {
    const topFrame = askerStack?.askerStack[askerStack.askerStack.length - 1];
    if (topFrame?.tellaskReplyObligation !== undefined) {
        return { kind: 'proceeding' };
    }
    return { kind: 'idle_waiting_user' };
}
async function copyArtifactsIfPresent(sourceId, targetId, sourceStatus) {
    const sourceDir = node_path_1.default.join(persistence_1.DialogPersistence.getDialogEventsPath(sourceId, sourceStatus), 'artifacts');
    const targetDir = node_path_1.default.join(persistence_1.DialogPersistence.getDialogEventsPath(targetId, 'running'), 'artifacts');
    try {
        await promises_1.default.access(sourceDir);
    }
    catch (error) {
        const code = typeof error === 'object' && error !== null ? error.code : undefined;
        if (code === 'ENOENT')
            return;
        throw error;
    }
    await promises_1.default.mkdir(node_path_1.default.dirname(targetDir), { recursive: true });
    await promises_1.default.cp(sourceDir, targetDir, { recursive: true });
}
async function listDialogCourseNumbers(dialogId, status) {
    const dialogDir = persistence_1.DialogPersistence.getDialogEventsPath(dialogId, status);
    let entries = [];
    try {
        entries = (await promises_1.default.readdir(dialogDir, { withFileTypes: true }));
    }
    catch (error) {
        const code = typeof error === 'object' && error !== null ? error.code : undefined;
        if (code === 'ENOENT')
            return [];
        throw error;
    }
    const courses = [];
    for (const entry of entries) {
        if (!entry.isFile())
            continue;
        if (!/^course-\d+\.jsonl$/.test(entry.name))
            continue;
        courses.push(persistence_1.DialogPersistence.getCourseFromFilename(entry.name));
    }
    courses.sort((a, b) => a - b);
    return courses;
}
async function collectForkSnapshot(dialogId, status, cutoffAnchor) {
    const courseNumbers = await listDialogCourseNumbers(dialogId, status);
    let latestReminders = null;
    let latestQuestions = null;
    let latestActiveCalleeDispatches = null;
    let latestRegistry = null;
    let latestResponses = null;
    for (const course of courseNumbers) {
        const events = await persistence_1.DialogPersistence.readCourseEvents(dialogId, course, status);
        for (const event of events) {
            const stateRecord = isForkStateRecord(event);
            if (!stateRecord)
                continue;
            if (!anchorAtOrBefore(stateRecord, cutoffAnchor))
                continue;
            switch (stateRecord.type) {
                case 'reminders_reconciled_record':
                    latestReminders = stateRecord.reminders.map((item) => ({ ...item }));
                    break;
                case 'questions4human_reconciled_record':
                    latestQuestions = cloneQuestions(stateRecord.questions);
                    break;
                case 'active_callees_reconciled_record':
                    latestActiveCalleeDispatches = cloneActiveCalleeDispatches(stateRecord.activeCalleeDispatches);
                    break;
                case 'sideDialog_registry_reconciled_record':
                    latestRegistry = cloneRegistryEntries(stateRecord.entries);
                    break;
                case 'sideDialog_responses_reconciled_record':
                    latestResponses = cloneSideDialogResponses(stateRecord.responses);
                    break;
                case 'sideDialog_created_record':
                    break;
                default: {
                    const _exhaustive = stateRecord;
                    throw new Error(`Unhandled fork snapshot record ${String(_exhaustive)}`);
                }
            }
        }
    }
    return {
        reminders: latestReminders !== null ? latestReminders.map((item) => cloneReminderSnapshot(item)) : [],
        questions: latestQuestions ?? [],
        activeCalleeDispatches: latestActiveCalleeDispatches ?? [],
        registryEntries: latestRegistry ?? [],
        sideDialogResponses: latestResponses ?? [],
    };
}
async function collectIncludedSideDialogs(args) {
    const queue = [new dialog_1.DialogID(args.sourceRootId)];
    const scannedDialogSelfIds = new Set();
    const included = new Map();
    while (queue.length > 0) {
        const sourceDialogId = queue.shift();
        if (!sourceDialogId)
            break;
        if (scannedDialogSelfIds.has(sourceDialogId.selfId))
            continue;
        scannedDialogSelfIds.add(sourceDialogId.selfId);
        const courseNumbers = await listDialogCourseNumbers(sourceDialogId, args.sourceStatus);
        for (const course of courseNumbers) {
            const events = await persistence_1.DialogPersistence.readCourseEvents(sourceDialogId, course, args.sourceStatus);
            for (const event of events) {
                if (event.type !== 'sideDialog_created_record')
                    continue;
                if (!anchorAtOrBefore(event, args.cutoffAnchor))
                    continue;
                if (included.has(event.sideDialogId))
                    continue;
                const sourceId = new dialog_1.DialogID(event.sideDialogId, args.sourceRootId);
                const metadata = await persistence_1.DialogPersistence.loadDialogMetadata(sourceId, args.sourceStatus);
                if (!metadata) {
                    throw new Error(`Missing included sideDialog metadata for ${sourceId.valueOf()}`);
                }
                const assignmentFromAsker = await persistence_1.DialogPersistence.loadSideDialogAssignmentFromAsker(sourceId, args.sourceStatus);
                included.set(event.sideDialogId, {
                    sourceId,
                    targetId: new dialog_1.DialogID(event.sideDialogId, args.targetRootId),
                    metadata,
                    assignmentFromAsker,
                });
                queue.push(sourceId);
            }
        }
    }
    const orderedSelfIds = Array.from(included.keys()).sort();
    const orderedIncluded = [];
    for (const selfId of orderedSelfIds) {
        const item = included.get(selfId);
        if (!item) {
            throw new Error(`Missing ordered included sideDialog for ${selfId}`);
        }
        orderedIncluded.push(item);
    }
    return orderedIncluded;
}
async function buildDialogForkPlan(args) {
    const courseNumbers = await listDialogCourseNumbers(args.sourceId, args.sourceStatus);
    const retainedCourses = [];
    for (const course of courseNumbers) {
        const events = await persistence_1.DialogPersistence.readCourseEvents(args.sourceId, course, args.sourceStatus);
        const isMainDialog = args.sourceId.selfId === args.sourceId.rootId;
        const retained = events.filter((event, index) => {
            if (isForkStateRecord(event)) {
                return false;
            }
            if (args.truncateRootCourse && course === args.truncateRootCourse.course && isMainDialog) {
                return index < args.truncateRootCourse.keepCount;
            }
            if (!isMainDialog) {
                const recordAnchor = getRecordRootAnchor(event);
                if (recordAnchor === null) {
                    throw new Error(`fork dialog requires root anchor on sideDialog transcript record: dialog=${args.sourceId.valueOf()} course=${String(course)} type=${event.type}`);
                }
                return anchorAtOrBefore(recordAnchor, args.cutoffAnchor);
            }
            return true;
        });
        if (retained.length > 0) {
            retainedCourses.push({ course, events: retained });
        }
    }
    const snapshot = await collectForkSnapshot(args.sourceId, args.sourceStatus, args.cutoffAnchor);
    const retainedCurrentCourse = retainedCourses.length > 0 ? retainedCourses[retainedCourses.length - 1].course : 1;
    return {
        sourceId: args.sourceId,
        targetId: args.targetId,
        metadata: args.sourceMetadata,
        retainedCourses,
        currentCourse: retainedCurrentCourse,
        reminders: snapshot.reminders,
        questions: snapshot.questions,
        activeCalleeDispatches: snapshot.activeCalleeDispatches,
        registryEntries: snapshot.registryEntries,
        sideDialogResponses: snapshot.sideDialogResponses,
        childCount: args.childCount,
    };
}
async function appendForkBaselineState(plan, baselineSideDialogCreatedRecords) {
    const baselineTs = (0, time_1.formatUnifiedTimestamp)(new Date());
    for (const record of baselineSideDialogCreatedRecords) {
        await persistence_1.DialogPersistence.appendEvent(plan.targetId, 1, record, 'running');
    }
    const remindersRecord = {
        ts: baselineTs,
        type: 'reminders_reconciled_record',
        ...FORK_BASELINE_ANCHOR,
        reminders: plan.reminders.map((reminder) => ({
            id: reminder.id,
            content: reminder.content,
            ownerName: reminder.owner?.name,
            meta: reminder.meta,
            echoback: reminder.echoback,
            scope: reminder.scope ?? 'dialog',
            createdAt: reminder.createdAt ?? baselineTs,
            priority: reminder.priority ?? 'medium',
        })),
    };
    const q4hRecord = {
        ts: baselineTs,
        type: 'questions4human_reconciled_record',
        ...FORK_BASELINE_ANCHOR,
        questions: cloneQuestions(plan.questions),
    };
    const activeCalleesRecord = {
        ts: baselineTs,
        type: 'active_callees_reconciled_record',
        ...FORK_BASELINE_ANCHOR,
        activeCalleeDispatches: cloneActiveCalleeDispatches(plan.activeCalleeDispatches),
    };
    const registryRecord = {
        ts: baselineTs,
        type: 'sideDialog_registry_reconciled_record',
        ...FORK_BASELINE_ANCHOR,
        entries: cloneRegistryEntries(plan.registryEntries),
    };
    const responsesRecord = {
        ts: baselineTs,
        type: 'sideDialog_responses_reconciled_record',
        ...FORK_BASELINE_ANCHOR,
        responses: cloneSideDialogResponses(plan.sideDialogResponses),
    };
    await persistence_1.DialogPersistence.appendEvent(plan.targetId, 1, remindersRecord, 'running');
    await persistence_1.DialogPersistence.appendEvent(plan.targetId, 1, q4hRecord, 'running');
    await persistence_1.DialogPersistence.appendEvent(plan.targetId, 1, activeCalleesRecord, 'running');
    await persistence_1.DialogPersistence.appendEvent(plan.targetId, 1, registryRecord, 'running');
    await persistence_1.DialogPersistence.appendEvent(plan.targetId, 1, responsesRecord, 'running');
}
async function persistForkPlan(args) {
    const { plan } = args;
    if (plan.targetId.selfId === plan.targetId.rootId) {
        const priming = 'priming' in plan.metadata ? plan.metadata.priming : undefined;
        const rewrittenMetadata = {
            id: plan.targetId.selfId,
            agentId: plan.metadata.agentId,
            taskDocPath: plan.metadata.taskDocPath,
            createdAt: args.now,
            ...(priming ? { priming } : {}),
        };
        await persistence_1.DialogPersistence.saveMainDialogMetadata(plan.targetId, rewrittenMetadata, 'running');
    }
    else {
        const rewrittenMetadata = rewriteSideDialogMetadataForFork(plan.metadata);
        const sourceAskerStackState = await persistence_1.DialogPersistence.loadSideDialogAskerStackState(plan.sourceId, args.sourceStatus);
        if (!sourceAskerStackState) {
            throw new Error(`fork sideDialog plan missing asker stack: ${plan.sourceId.valueOf()}`);
        }
        const rewrittenAskerStackState = rewriteSideDialogAskerStackStateForFork(sourceAskerStackState, plan.sourceId.rootId, plan.targetId.rootId);
        await persistence_1.DialogPersistence.ensureSideDialogDirectory(plan.targetId, 'running');
        await persistence_1.DialogPersistence.saveSideDialogAskerStackState(plan.targetId, rewrittenAskerStackState, 'running');
        await persistence_1.DialogPersistence.saveSideDialogMetadata(plan.targetId, rewrittenMetadata, 'running');
    }
    const sourceAskerStack = await persistence_1.DialogPersistence.loadDialogAskerStack(plan.sourceId, args.sourceStatus);
    if (sourceAskerStack.askerStack.length > 0) {
        await persistence_1.DialogPersistence.saveDialogAskerStack(plan.targetId, rewriteSideDialogAskerStackStateForFork(sourceAskerStack, plan.sourceId.rootId, plan.targetId.rootId), 'running');
    }
    const currentCourseEvents = plan.retainedCourses.find((item) => item.course === plan.currentCourse)?.events ?? [];
    const displayState = plan.targetId.selfId === plan.targetId.rootId
        ? computeRootForkDisplayState({
            action: args.action,
            questions: plan.questions,
            askerStack: sourceAskerStack,
        })
        : computeSideDialogForkDisplayState(sourceAskerStack);
    await persistence_1.DialogPersistence.mutateDialogLatest(plan.targetId, () => ({
        kind: 'replace',
        next: {
            currentCourse: plan.currentCourse,
            lastModified: args.now,
            status: 'active',
            messageCount: countMessages(currentCourseEvents),
            functionCallCount: countFunctionCalls(currentCourseEvents),
            sideDialogCount: plan.childCount,
            generating: false,
            nextStep: (0, dialog_latest_state_1.createEmptyDialogNextStepState)(),
            tellaskCalls: (0, dialog_latest_state_1.createEmptyDialogTellaskCallState)(),
            tellaskResults: (0, dialog_latest_state_1.createEmptyDialogTellaskResultState)(),
            displayState,
            disableDiligencePush: plan.targetId.selfId === plan.targetId.rootId ? args.latestDisableDiligencePush : false,
            diligencePushRemainingBudget: plan.targetId.selfId === plan.targetId.rootId
                ? args.latestDiligencePushRemainingBudget
                : undefined,
        },
    }));
    for (const course of plan.retainedCourses) {
        for (const event of course.events) {
            await persistence_1.DialogPersistence.appendEvent(plan.targetId, course.course, rewriteRecordForFork(event, plan.targetId.rootId), 'running');
        }
    }
    await appendForkBaselineState(plan, args.baselineRecordsByParentSelfId.get(plan.targetId.selfId) ?? []);
    await persistence_1.DialogPersistence._saveReminderState(plan.targetId, [...plan.reminders], 'running');
    await persistence_1.DialogPersistence._saveQuestions4HumanState(plan.targetId, [...plan.questions], 'running');
    for (const record of plan.activeCalleeDispatches) {
        await persistence_1.DialogPersistence.appendActiveCalleeDispatch(plan.targetId, record, undefined, 'running');
    }
    await persistence_1.DialogPersistence.saveSideDialogRegistry(plan.targetId, plan.registryEntries.map((entry) => ({
        key: entry.key,
        sideDialogId: new dialog_1.DialogID(entry.sideDialogId, plan.targetId.rootId),
        agentId: entry.agentId,
        sessionSlug: entry.sessionSlug,
    })), 'running');
    await persistence_1.DialogPersistence.saveSideDialogResponses(plan.targetId, [...plan.sideDialogResponses], undefined, 'running');
    await copyArtifactsIfPresent(plan.sourceId, plan.targetId, args.sourceStatus);
    await persistence_1.DialogPersistence.mutateDialogLatest(plan.targetId, (previous) => ({
        kind: 'patch',
        patch: {
            currentCourse: plan.currentCourse,
            messageCount: countMessages(currentCourseEvents),
            functionCallCount: countFunctionCalls(currentCourseEvents),
            sideDialogCount: plan.childCount,
            displayState,
            executionMarker: displayState.kind === 'stopped'
                ? { kind: 'interrupted', reason: displayState.reason }
                : previous.executionMarker,
        },
    }));
}
async function forkMainDialogTreeAtGeneration(args) {
    const sourceRootId = args.sourceRootId.trim();
    if (sourceRootId === '') {
        throw new Error('sourceRootId is required');
    }
    if (!Number.isFinite(args.course) || args.course <= 0) {
        throw new Error('course must be a positive integer');
    }
    if (!Number.isFinite(args.genseq) || args.genseq <= 0) {
        throw new Error('genseq must be a positive integer');
    }
    const sourceMainDialogId = new dialog_1.DialogID(sourceRootId);
    const sourceMetadata = await persistence_1.DialogPersistence.loadDialogMetadata(sourceMainDialogId, args.sourceStatus);
    if (!sourceMetadata) {
        throw new Error(`Main dialog not found: ${sourceRootId} (${args.sourceStatus})`);
    }
    const targetCourse = Math.floor(args.course);
    const targetGenseq = Math.floor(args.genseq);
    const rootEvents = await persistence_1.DialogPersistence.readCourseEvents(sourceMainDialogId, targetCourse, args.sourceStatus);
    const targetStartIndex = rootEvents.findIndex((event) => 'genseq' in event && typeof event.genseq === 'number' && event.genseq === targetGenseq);
    if (targetStartIndex < 0) {
        throw new Error(`Target genseq ${String(targetGenseq)} not found in dialog ${sourceRootId} course ${String(targetCourse)}`);
    }
    const targetStartEvent = rootEvents[targetStartIndex];
    if (!targetStartEvent) {
        throw new Error(`Target genseq ${String(targetGenseq)} missing start event in dialog ${sourceRootId} course ${String(targetCourse)}`);
    }
    const cutoffAnchor = (0, storage_1.toRootGenerationAnchor)({
        rootCourse: targetCourse,
        rootGenseq: targetGenseq - 1,
    });
    const draftUserText = normalizeDraftUserText(rootEvents, targetGenseq);
    const latest = await persistence_1.DialogPersistence.loadDialogLatest(sourceMainDialogId, args.sourceStatus);
    const targetRootId = (0, id_1.generateDialogID)();
    const now = (0, time_1.formatUnifiedTimestamp)(new Date());
    const includedSideDialogs = await collectIncludedSideDialogs({
        sourceRootId,
        sourceStatus: args.sourceStatus,
        cutoffAnchor,
        targetRootId,
    });
    const childCountByParentSelfId = new Map();
    for (const sideDialog of includedSideDialogs) {
        const askerDialogId = sideDialog.assignmentFromAsker.askerDialogId;
        childCountByParentSelfId.set(askerDialogId, (childCountByParentSelfId.get(askerDialogId) ?? 0) + 1);
    }
    const rootPlan = await buildDialogForkPlan({
        sourceId: sourceMainDialogId,
        targetId: new dialog_1.DialogID(targetRootId),
        sourceStatus: args.sourceStatus,
        sourceMetadata,
        cutoffAnchor,
        truncateRootCourse: { course: targetCourse, keepCount: targetStartIndex },
        childCount: childCountByParentSelfId.get(sourceRootId) ?? 0,
    });
    const action = draftUserText !== null
        ? { kind: 'draft_user_text', userText: draftUserText }
        : rootPlan.questions.length > 0
            ? {
                kind: 'restore_pending',
                pendingQ4H: rootPlan.questions.length > 0,
            }
            : { kind: 'auto_continue' };
    const sideDialogPlans = [];
    for (const sideDialog of includedSideDialogs) {
        sideDialogPlans.push(await buildDialogForkPlan({
            sourceId: sideDialog.sourceId,
            targetId: sideDialog.targetId,
            sourceStatus: args.sourceStatus,
            sourceMetadata: sideDialog.metadata,
            cutoffAnchor,
            childCount: childCountByParentSelfId.get(sideDialog.sourceId.selfId) ?? 0,
        }));
    }
    const baselineRecordsByParentSelfId = new Map();
    for (const sideDialog of includedSideDialogs) {
        const rewrittenAskerDialogId = rewriteForkTreeDialogSelfId(sideDialog.assignmentFromAsker.askerDialogId, sourceRootId, targetRootId);
        const rewrittenRecord = {
            ts: now,
            type: 'sideDialog_created_record',
            ...FORK_BASELINE_ANCHOR,
            sideDialogId: sideDialog.targetId.selfId,
            askerDialogId: rewrittenAskerDialogId,
            agentId: sideDialog.metadata.agentId,
            taskDocPath: sideDialog.metadata.taskDocPath,
            createdAt: sideDialog.metadata.createdAt,
            sessionSlug: sideDialog.metadata.sessionSlug,
            assignmentFromAsker: rewriteAssignmentFromAskerForFork(sideDialog.assignmentFromAsker, sourceRootId, targetRootId),
        };
        const existing = baselineRecordsByParentSelfId.get(rewrittenAskerDialogId);
        if (existing) {
            existing.push(rewrittenRecord);
        }
        else {
            baselineRecordsByParentSelfId.set(rewrittenAskerDialogId, [rewrittenRecord]);
        }
    }
    const allPlans = [rootPlan, ...sideDialogPlans];
    for (const plan of allPlans) {
        await persistForkPlan({
            plan,
            sourceStatus: args.sourceStatus,
            now,
            action,
            baselineRecordsByParentSelfId,
            latestDisableDiligencePush: latest?.disableDiligencePush,
            latestDiligencePushRemainingBudget: latest?.diligencePushRemainingBudget,
        });
    }
    return {
        rootId: targetRootId,
        selfId: targetRootId,
        agentId: sourceMetadata.agentId,
        taskDocPath: sourceMetadata.taskDocPath,
        action,
    };
}
