"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.normalizeAgentOutput = exports.slugifyPrompt = void 0;
exports.registerAgentVariantsTools = registerAgentVariantsTools;
exports.buildQaFailurePrompt = buildQaFailurePrompt;
const path_1 = __importDefault(require("path"));
const contracts_1 = require("./contracts");
const VariantHistoryService_1 = require("../../services/VariantHistoryService");
const errors_1 = require("./errors");
const generatedDestination_1 = require("./generatedDestination");
Object.defineProperty(exports, "slugifyPrompt", { enumerable: true, get: function () { return generatedDestination_1.slugifyPrompt; } });
const createZeroToOneTool_1 = require("./createZeroToOneTool");
const ProjectDetectionService_1 = require("../../services/ProjectDetectionService");
const SessionStore_1 = require("./SessionStore");
/**
 * Coerce `output` to a structured value when an agent (incorrectly) sends a
 * stringified JSON. The MCP schema accepts `z.unknown()` so the call doesn't
 * fail validation, but downstream consumers (e.g. `output.html` in
 * WorktreeOrchestrator) expect an object and silently no-op on strings —
 * leaving the variant stuck at "not ready" until session expiry.
 */
const normalizeAgentOutput = (output) => {
    if (typeof output !== 'string')
        return output;
    try {
        return JSON.parse(output);
    }
    catch {
        return output;
    }
};
exports.normalizeAgentOutput = normalizeAgentOutput;
const manifestKind = (manifest) => {
    return manifest.artifact.kind === 'diff' ? 'diff' : 'project-created';
};
/**
 * Reject caller-supplied fresh destinations at the generic variants boundary.
 * Fresh projects must go through `start_variants(mode='zero_to_one')`, where
 * the server derives the destination path before delegating to the
 * orchestrator.
 */
const validateFreshProjectInput = (args) => {
    if (args.projectContext?.kind !== 'fresh')
        return null;
    return {
        stage: 'failed',
        errorCode: 'SCHEMA_VALIDATION_FAILED',
        message: "Fresh-project sessions must be started with start_variants(mode='zero_to_one') so Rivet derives and validates the destination path.",
    };
};
const registerVariantTool = (mcp, name, description, input, handler) => {
    const tool = mcp.tool.bind(mcp);
    tool(name, description, input, handler);
};
function registerAgentVariantsTools(mcp, deps) {
    registerVariantTool(mcp, 'propose_variants', 'Stage 1 of the legacy variants flow. Prefer `start_variants` for new code paths — it collapses propose + report_briefs + request_work into a single call. Reporting briefs starts every direction immediately; there is no in-chat approval gate. Returns { sessionId, briefWorkItem }. Full protocol in the rivet skill.', contracts_1.proposeVariantsInput, async (args) => {
        try {
            const failure = validateFreshProjectInput(args);
            if (failure)
                return jsonResponse(failure);
            const result = deps.orchestrator.propose({
                sessionId: args.sessionId,
                prompt: args.prompt,
                count: args.count,
                target: args.target,
                projectContext: args.projectContext,
                runLabel: args.runLabel,
            });
            if (result.stage === 'awaiting_source_plan') {
                const out = {
                    sessionId: result.sessionId,
                    stage: 'awaiting_source_plan',
                    sourcePlanWorkItem: {
                        id: result.sourcePlanWorkItem.id,
                        kind: 'source_plan',
                        attempt: result.sourcePlanWorkItem.attempt,
                        input: result.sourcePlanWorkItem.input,
                        output_schema: SessionStore_1.SOURCE_PLAN_OUTPUT_SCHEMA,
                    },
                    nextAction: 'continue_variants',
                };
                return jsonResponse(out);
            }
            const out = {
                sessionId: result.sessionId,
                stage: 'awaiting_briefs',
                briefWorkItem: {
                    id: result.briefWorkItem.id,
                    kind: 'brief',
                    attempt: result.briefWorkItem.attempt,
                    input: result.briefWorkItem.input,
                    output_schema: { briefs: 'Array<{briefId, label: "1–5 word plain-English title naming the change", body: "one plain-English sentence on the single most important change vs baseline (motion/color/animation/layout); no abbreviations or arrows"}>' },
                },
                nextAction: 'report_variant_briefs',
            };
            return jsonResponse(out);
        }
        catch (err) {
            return errorResponse(err);
        }
    });
    registerVariantTool(mcp, 'report_variant_briefs', 'Report drafted briefs for the brief work item. Each brief: label = a 1–5 word plain-English title naming what THIS direction changes (describe the change, not the vibe — no marketing or evocative adjectives); it is the variant card title the user reads, so make the intent obvious. body = ONE plain-English sentence describing the single most important change this direction makes vs the baseline (usually a motion, color, animation, or layout change) — don\'t list every change. Write a normal, complete, readable sentence: no arrows, no abbreviations, no value codes (say "from blue to green", not "blue → green"). Functional and plain, but not flowery (avoid "gentle", "elegant", "modern", "smooth", "clean"). e.g. "The pages cross-dissolve into each other instead of cutting."; "The accent color shifts from blue to green."; "The layout becomes two columns on wider screens." No bullets, no line breaks. On success, every brief is started immediately and the session lands in work_items_ready — there is no in-chat approval gate, so directions begin generating right away.', contracts_1.reportVariantBriefsInput, async (args) => {
        try {
            const reported = deps.orchestrator.reportBriefs({
                sessionId: args.sessionId,
                workItemId: args.workItemId,
                attempt: args.attempt,
                leaseId: args.leaseId,
                briefs: args.briefs,
            });
            // Start every reported brief immediately — no in-chat approval gate.
            // Mirrors start_variants' single-call kickoff so directions begin
            // generating without waiting on user confirmation.
            const approved = await deps.orchestrator.approve({
                sessionId: args.sessionId,
                selections: reported.briefs.map((b) => ({ briefId: b.briefId })),
            });
            const out = {
                stage: 'work_items_ready',
                approvedCount: approved.approvedCount,
                codeGenWorkItemIds: approved.codeGenWorkItemIds,
                scaffoldBaseWorkItemId: approved.scaffoldBaseWorkItemId ?? null,
                nextAction: 'continue_variants',
            };
            return jsonResponse(out);
        }
        catch (err) {
            return errorResponse(err);
        }
    });
    registerVariantTool(mcp, 'report_source_plan', 'Report the consolidated source plan for the source_plan work item. Combines URL/role classification, source research findings, browser-extracted DESIGN.md per design_source URL, and the static_preview vs vite_app execution decision. Choose vite_app when the request needs large local assets (e.g. GLB models), model-viewer, Three.js, route structure, package dependencies, or files under public/; choose static_preview for self-contained HTML/CSS/JS prototypes. If the requirement is ambiguous, set executionPlan.userQuestion instead of guessing — Rivet will surface it as a clarification blocker at approve time. Reports unlock the brief work item for source-grounded fresh-project sessions.', contracts_1.reportSourcePlanInput, async (args) => {
        try {
            const result = deps.orchestrator.reportSourcePlan({
                sessionId: args.sessionId,
                workItemId: args.workItemId,
                leaseId: args.leaseId,
                attempt: args.attempt,
                sourcePlan: args.sourcePlan,
            });
            const plan = args.sourcePlan.executionPlan;
            const out = {
                stage: 'awaiting_briefs',
                briefWorkItem: {
                    id: result.briefWorkItem.id,
                    kind: 'brief',
                    attempt: result.briefWorkItem.attempt,
                    input: result.briefWorkItem.input,
                    output_schema: {
                        briefs: 'Array<{briefId, label: "1–5 word plain-English title naming the change", body: "one plain-English sentence on the single most important change vs baseline (motion/color/animation/layout); no abbreviations or arrows"}>',
                    },
                },
                executionPlan: {
                    mode: plan.mode,
                    confidence: plan.confidence,
                    reason: plan.reason,
                    assetCount: plan.assetPlan?.length ?? 0,
                    runtimeRequirementCount: plan.runtimeRequirements?.length ?? 0,
                },
                nextAction: 'report_variant_briefs',
            };
            return jsonResponse(out);
        }
        catch (err) {
            return errorResponse(err);
        }
    });
    registerVariantTool(mcp, 'approve_variant_briefs', 'Legacy/no-op in most flows: report_variant_briefs now starts every brief immediately, so directions kick off without an in-chat approval gate. Kept for backward compatibility — provide briefIds for the briefs to generate (typically all of them). Each brief gets a parallel code-gen work item in its own isolated worktree; advance via continue_variants(action="request_work") to run them in parallel. For tweak-with-feedback, use the selections form with bodyOverride per brief. When the session has already started its briefs, this returns the current work_items_ready state idempotently.', contracts_1.approveVariantBriefsInput, async (args) => {
        try {
            // report_variant_briefs auto-starts the briefs, so a session may
            // already be past the approval point. Treat a repeat call as an
            // idempotent no-op rather than erroring.
            const stage = deps.orchestrator.getStage(args.sessionId);
            if (stage === 'work_items_ready' || stage === 'waiting_for_results') {
                const variants = deps.orchestrator.getVariants(args.sessionId);
                const out = {
                    stage: 'work_items_ready',
                    approvedCount: variants.length,
                    codeGenWorkItemIds: variants.map((v) => v.workItemId),
                    scaffoldBaseWorkItemId: null,
                    nextAction: 'continue_variants',
                };
                return jsonResponse(out);
            }
            const selections = args.selections ??
                (args.briefIds ?? []).map((briefId) => ({ briefId }));
            if (selections.length === 0) {
                throw new Error('approve_variant_briefs requires either briefIds or selections');
            }
            const result = await deps.orchestrator.approve({
                sessionId: args.sessionId,
                selections,
            });
            const out = {
                stage: 'work_items_ready',
                approvedCount: result.approvedCount,
                codeGenWorkItemIds: result.codeGenWorkItemIds,
                scaffoldBaseWorkItemId: result.scaffoldBaseWorkItemId ?? null,
                nextAction: 'continue_variants',
            };
            return jsonResponse(out);
        }
        catch (err) {
            return errorResponse(err);
        }
    });
    registerVariantTool(mcp, 'commit_variant', "Call AFTER all variants have been generated AND you've shown each variant's diff/changes to the user in chat AND the user has picked one. Set confirmedByUser=true when the user confirms the pick. Only use bypassRequestedByUser=true (with bypassReason) if the user explicitly asks to bypass confirmation. Pass the variantId (the workItemId of the chosen variant). Rivet looks up that variant's captured diff and enqueues it onto the existing pending-changes channel — call get_pending_changes next to retrieve and apply. Idempotent on (sessionId, variantId).", contracts_1.commitVariantInput, async (args) => {
        try {
            assertExplicitUserConfirmation('commit_variant', args, 'commit a variant');
            const result = await deps.orchestrator.commitVariant({
                sessionId: args.sessionId,
                variantId: args.variantId,
            });
            const out = {
                stage: deps.orchestrator.getStage(args.sessionId),
                enqueued: result.enqueued,
                duplicate: result.duplicate,
                changedFilesCount: result.changedFilesCount,
                payloadKind: result.payloadKind,
                destinationPath: result.destinationPath,
            };
            return jsonResponse(out);
        }
        catch (err) {
            return errorResponse(err);
        }
    });
    registerVariantTool(mcp, 'continue_variants', 'Long-poll/state-pump the variants flow. action=check returns immediately; action=wait blocks up to ~5min for state changes; action=request_work picks up ready work items (source_research, static_preview, or code_gen depending on stage). static_preview is for fresh zero-to-one sessions and should return {html, css?, js?} so Rivet can show fast visual previews before Vite/code generation finishes.', contracts_1.continueVariantsInput, async (args) => {
        try {
            const stage = deps.orchestrator.getStage(args.sessionId);
            if (args.action === 'refine_variant') {
                if (!args.variantId || !args.instruction) {
                    throw new errors_1.AgentVariantsError('MISSING_REQUIRED_INPUT', 'continue_variants(action="refine_variant") requires variantId and instruction');
                }
                const lease = deps.orchestrator.refineVariant({
                    sessionId: args.sessionId,
                    variantId: args.variantId,
                    instruction: args.instruction,
                    leaseOwner: deps.leaseOwner(),
                });
                return jsonResponse({
                    stage: 'work_items_ready',
                    leaseId: lease.leaseId,
                    leaseTtlMs: lease.leaseTtlMs,
                    leasedWorkItems: lease.leasedWorkItems,
                });
            }
            if (stage === 'awaiting_briefs') {
                // continue_variants while still awaiting briefs means the agent
                // hasn't run the brief work item yet — that's a programming error.
                throw new errors_1.AgentVariantsError('INVALID_STAGE_ACTION', 'Run the brief work item and call report_variant_briefs before continue_variants');
            }
            if (stage === 'awaiting_approval') {
                const briefs = deps.orchestrator.getBriefs(args.sessionId);
                return jsonResponse({
                    stage: 'awaiting_approval',
                    briefs,
                    approvalPrompt: buildApprovalPrompt(briefs),
                });
            }
            if (args.action === 'request_work' &&
                (stage === 'awaiting_source_plan' ||
                    stage === 'work_items_ready' ||
                    stage === 'waiting_for_results')) {
                // SessionStore.requestWork accepts both stages and expires stale
                // leases before issuing new ones, so this also recycles wedged
                // variants whose leases expired while the session was already in
                // waiting_for_results.
                const lease = deps.orchestrator.requestWork({
                    sessionId: args.sessionId,
                    leaseOwner: deps.leaseOwner(),
                    requestedLeaseCount: args.requestedLeaseCount,
                });
                const postLeaseStage = deps.orchestrator.getStage(args.sessionId);
                const leasePayload = {
                    leaseId: lease.leaseId,
                    leaseTtlMs: lease.leaseTtlMs,
                    leasedWorkItems: lease.leasedWorkItems,
                };
                return jsonResponse(postLeaseStage === 'awaiting_source_plan'
                    ? { stage: 'awaiting_source_plan', ...leasePayload }
                    : { stage: 'work_items_ready', ...leasePayload });
            }
            if (stage === 'work_items_ready' || stage === 'waiting_for_results') {
                return jsonResponse({
                    stage: 'waiting_for_results',
                    progress: deps.orchestrator.getProgress(args.sessionId),
                });
            }
            if (stage === 'awaiting_source_plan') {
                return jsonResponse({
                    stage: 'awaiting_source_plan',
                    progress: deps.orchestrator.getProgress(args.sessionId),
                });
            }
            // Terminal stages
            {
                const summary = deps.orchestrator.getSummary(args.sessionId);
                const variants = deps.orchestrator.getVariants(args.sessionId);
                const previewUrls = variants
                    .filter((v) => v.preview?.kind === 'static_artifact')
                    .map((v) => ({
                    label: v.label,
                    url: v.preview.url,
                }));
                const qaSurfacing = buildQaSurfacing(variants);
                return jsonResponse({
                    stage,
                    summary,
                    ...(previewUrls.length > 0 ? { previewUrls } : {}),
                    ...qaSurfacing,
                });
            }
        }
        catch (err) {
            return errorResponse(err);
        }
    });
    registerVariantTool(mcp, 'report_variant_complete', "Agent reports per-work-item status: running (heartbeat), succeeded, failed, or cancelled. Requires a valid leaseId and matching attempt obtained from continue_variants(action='request_work'). " +
        'OUTPUT SHAPE per work-item kind (see work item `output_schema` from start_variants / request_work):' +
        ' • static_preview: succeeded MUST pass output = { html: "<!doctype html>...", css?: "...", js?: "...", assetBase?: "<dir>" } — inline the full HTML; Rivet renders it directly in the iframe. Writing files to disk and reporting a file list does NOT work — the orchestrator never reads disk for the HTML.' +
        ' If the HTML references sibling files (images, .glb, fonts), write those files to disk under a directory inside the work item\'s `input.projectContext.workspacePath` and pass that directory as `assetBase` (absolute or relative to workspacePath). Rivet then serves siblings from there at `/api/variants/.../static/<variantId>/<assetPath>`, so the HTML can use relative URLs like `./avatar.glb`. Omit `assetBase` if the HTML is fully self-contained.' +
        ' • code_gen / scaffold_base: succeeded passes the captured diff in the work item\'s `output` field as the agent\'s tool used (see the `output_schema` for that work item).' +
        ' For benchmark instrumentation, agents can optionally include tokensIn, tokensOut, and model in the call so Rivet can record cost per variant.' +
        ' ABORT: if the response is `{ aborted: true }`, the user removed or cancelled this direction while it was generating — stop this work item immediately, do NOT retry or re-report it, and move on to the others. For long generations, send periodic status="running" heartbeats so you pick up an abort early and avoid burning work on a direction that has already been dismissed.', contracts_1.reportVariantCompleteInput, async (args) => {
        try {
            const result = await deps.orchestrator.reportComplete({
                sessionId: args.sessionId,
                workItemId: args.workItemId,
                leaseId: args.leaseId,
                attempt: args.attempt,
                status: args.status,
                output: (0, exports.normalizeAgentOutput)(args.output),
                error: args.error,
                tokensIn: args.tokensIn,
                tokensOut: args.tokensOut,
                model: args.model,
            });
            let out;
            if (result.aborted) {
                // The work item was removed/cancelled mid-flight. Tell the agent to
                // drop it now rather than retry against a cleared lease.
                out = {
                    aborted: true,
                    stage: result.stage,
                    progress: result.progress,
                };
            }
            else if (result.stage === 'ready' ||
                result.stage === 'degraded' ||
                result.stage === 'failed' ||
                result.stage === 'cancelled') {
                const summary = result.summary ?? deps.orchestrator.getSummary(args.sessionId);
                const variants = deps.orchestrator.getVariants(args.sessionId);
                const previewUrls = variants
                    .filter((v) => v.preview?.kind === 'static_artifact')
                    .map((v) => ({
                    label: v.label,
                    url: v.preview.url,
                }));
                const qaSurfacing = buildQaSurfacing(variants);
                out = {
                    stage: result.stage,
                    summary,
                    ...(previewUrls.length > 0 ? { previewUrls } : {}),
                    ...qaSurfacing,
                };
            }
            else {
                out = {
                    stage: 'waiting_for_results',
                    progress: result.progress,
                };
            }
            return jsonResponse(out);
        }
        catch (err) {
            return errorResponse(err);
        }
    });
    registerVariantTool(mcp, 'cancel_variants', 'Cancel an in-flight variants session. Stops accepting new work, marks running items cancelled, and triggers cleanup.', contracts_1.cancelVariantsInput, async (args) => {
        try {
            const result = await deps.orchestrator.cancel({
                sessionId: args.sessionId,
                reason: args.reason,
            });
            const out = {
                stage: 'cancelled',
                cleanupStatus: result.cleanupStatus,
            };
            return jsonResponse(out);
        }
        catch (err) {
            return errorResponse(err);
        }
    });
    registerVariantTool(mcp, 'start_variants', 'Single-call kickoff for variants exploration, and the single entry point for fresh projects. Spawns N variant worktrees, opens the visual editor, and returns the variant work items + worktreePaths — no follow-up detect_project / open_visual_editor / continue_variants needed. Pass `briefs` (one per variant) so each card has a distinct, plain-English name (a 1–5 word title naming what the direction changes) and description; omitting them stubs every variant with the user prompt verbatim. Also pass `runLabel` — a 1–2 word label for the whole run\'s intent (e.g. prompt "make it brighter" → "Brighter"), shown as a tag on every variant row. `briefs.length` sets the count. mode="existing" for existing projects, "zero_to_one" for fresh projects. For source-grounded fresh projects pass `sourceContext` (inspiration URLs / sourceArtifacts) with mode="zero_to_one": start_variants then runs the source-research flow and returns stage="awaiting_source_plan" instead of work_items_ready. Full protocol in the rivet skill.', contracts_1.startVariantsInput, async (args) => {
        try {
            if (args.mode === 'zero_to_one') {
                const out = await handleStartVariantsZeroToOne(args, deps);
                return jsonResponse(out);
            }
            const out = await handleStartVariantsExisting(args, deps);
            return jsonResponse(out);
        }
        catch (err) {
            return errorResponse(err);
        }
    });
    registerVariantTool(mcp, 'cancel_variant', 'Cancel a single variant within an in-flight session without affecting its siblings. Called by the Rivet UI when the user clicks the cancel button on a variant card. Marks the variant cancelled and frees its slot; sibling variants continue. Idempotent on terminal states (already cancelled / succeeded / failed returns alreadyTerminal=true with the actual finalStatus). Per-variant worktree teardown happens at session commit/cancel time. Distinct from cancel_variants (plural), which kills the entire session.', contracts_1.cancelVariantInput, async (args) => {
        try {
            const result = await deps.orchestrator.cancelVariant({
                sessionId: args.sessionId,
                variantId: args.variantId,
                reason: args.reason,
            });
            const out = {
                sessionId: result.sessionId,
                variantId: result.variantId,
                finalStatus: result.finalStatus,
                sessionStage: result.sessionStage,
                alreadyTerminal: result.alreadyTerminal,
            };
            return jsonResponse(out);
        }
        catch (err) {
            return errorResponse(err);
        }
    });
    const variantHistory = deps.variantHistory ?? new VariantHistoryService_1.VariantHistoryService();
    registerVariantTool(mcp, 'list_past_variants', 'List previously generated variants for the current project from `<projectPath>/.rivet/variants/`. Sorted newest-first. Filter by `status` (completed | committed | rejected | cancelled) or cap with `limit`. Defaults to the active editor session\'s workspaceRoot; falls back to the MCP server cwd when no editor is open.', contracts_1.listPastVariantsInput, async (args) => {
        try {
            // Fresh history lives under `workspaceRoot`, which the bridge tracks
            // (see `bridge.rebindProjectPath(workspaceRoot)` in mcp/server.ts).
            // Defaulting to `process.cwd()` reads the wrong tree on zero-to-one
            // workflows where cwd ≠ workspaceRoot.
            const projectPath = args.projectPath ?? deps.getProjectPath?.() ?? process.cwd();
            if (!path_1.default.isAbsolute(projectPath)) {
                throw new errors_1.AgentVariantsError('SCHEMA_VALIDATION_FAILED', `projectPath must be absolute; got '${projectPath}'.`);
            }
            const entries = await variantHistory.listVariants({
                projectPath,
                limit: args.limit,
                status: args.status,
            });
            // Removed variants are retained on disk but never surfaced in the
            // default history listing — only when explicitly queried with
            // status: 'removed'.
            const visibleEntries = args.status === 'removed'
                ? entries
                : entries.filter((entry) => entry.manifest.status !== 'removed');
            const summaries = visibleEntries.map((entry) => ({
                sessionId: entry.manifest.sessionId,
                variantId: entry.manifest.variantId,
                label: entry.manifest.label,
                brief: entry.manifest.brief,
                kind: manifestKind(entry.manifest),
                status: entry.manifest.status,
                createdAt: entry.manifest.createdAt,
                statusChangedAt: entry.manifest.statusChangedAt,
                changedFilesCount: entry.manifest.changedFilesCount,
                artifact: entry.manifest.artifact,
                preview: entry.manifest.preview,
            }));
            const out = {
                projectPath,
                variants: summaries,
            };
            return jsonResponse(out);
        }
        catch (err) {
            return errorResponse(err);
        }
    });
    registerVariantTool(mcp, 'get_variant', 'Fetch one prior variant by sessionId + variantId from `<projectPath>/.rivet/variants/`. Returns the manifest plus the diff body (for kind=diff) or the file list (for kind=project-created). Pass `includeBody: false` to skip the diff/file list when you just need metadata. Defaults to the active editor session\'s workspaceRoot; falls back to the MCP server cwd when no editor is open.', contracts_1.getVariantInput, async (args) => {
        try {
            const projectPath = args.projectPath ?? deps.getProjectPath?.() ?? process.cwd();
            if (!path_1.default.isAbsolute(projectPath)) {
                throw new errors_1.AgentVariantsError('SCHEMA_VALIDATION_FAILED', `projectPath must be absolute; got '${projectPath}'.`);
            }
            const detail = await variantHistory.getVariant({
                projectPath,
                sessionId: args.sessionId,
                variantId: args.variantId,
            });
            if (!detail) {
                throw new errors_1.AgentVariantsError('WORK_ITEM_NOT_FOUND', `No persisted variant for session ${args.sessionId} variant ${args.variantId} under ${projectPath}/.rivet/variants/.`);
            }
            const includeBody = args.includeBody !== false;
            const out = {
                projectPath,
                variant: {
                    sessionId: detail.manifest.sessionId,
                    variantId: detail.manifest.variantId,
                    label: detail.manifest.label,
                    brief: detail.manifest.brief,
                    kind: manifestKind(detail.manifest),
                    status: detail.manifest.status,
                    createdAt: detail.manifest.createdAt,
                    statusChangedAt: detail.manifest.statusChangedAt,
                    changedFilesCount: detail.manifest.changedFilesCount,
                    sessionPrompt: detail.manifest.sessionPrompt,
                    baseSha: detail.manifest.baseSha,
                    destinationPath: detail.manifest.destinationPath,
                    artifact: detail.manifest.artifact,
                    preview: detail.manifest.preview,
                    ...(detail.manifest.designSource
                        ? { designSource: detail.manifest.designSource }
                        : {}),
                    ...(includeBody && detail.diff !== undefined
                        ? { diff: detail.diff }
                        : {}),
                    ...(includeBody && detail.files !== undefined
                        ? { files: detail.files }
                        : {}),
                    ...(includeBody && detail.designMarkdown !== undefined
                        ? { designMarkdown: detail.designMarkdown }
                        : {}),
                },
            };
            return jsonResponse(out);
        }
        catch (err) {
            return errorResponse(err);
        }
    });
}
// --- Helpers --------------------------------------------------------------
// Return type is left implicit so the MCP SDK's overload selection picks the
// raw-shape variant; tagging an explicit interface here breaks the index
// signature the SDK expects.
function jsonResponse(payload) {
    return {
        content: [
            {
                type: 'text',
                text: JSON.stringify(payload),
            },
        ],
    };
}
function errorResponse(err) {
    const code = err instanceof errors_1.AgentVariantsError ? err.code : 'SCHEMA_VALIDATION_FAILED';
    const message = err instanceof Error ? err.message : 'Unknown error in agent-variants tool';
    return {
        content: [
            {
                type: 'text',
                text: JSON.stringify({
                    stage: 'failed',
                    errorCode: code,
                    message,
                }),
            },
        ],
        isError: true,
    };
}
function assertExplicitUserConfirmation(toolName, confirmation, actionLabel) {
    if (confirmation.confirmedByUser) {
        return;
    }
    if (confirmation.bypassRequestedByUser &&
        confirmation.bypassReason?.trim().length) {
        return;
    }
    throw new errors_1.AgentVariantsError('MISSING_REQUIRED_INPUT', `${toolName} requires explicit user confirmation to ${actionLabel}. Set confirmedByUser=true after user confirmation, or set bypassRequestedByUser=true with bypassReason when the user explicitly asks to bypass.`);
}
function buildApprovalPrompt(briefs) {
    const formattedBriefs = briefs
        .map((brief, index) => `${index + 1}. name: ${brief.label}\n   description: ${brief.body}`)
        .join('\n');
    return (`Review these variant briefs:\n${formattedBriefs}\n\n` +
        'Do these look good, or do you want any adjustments before I proceed?');
}
/**
 * Compose the QA surfacing block (`qaPrompt`, `qa[]`) attached to terminal
 * tool responses. Returns an empty object when no variant has a recorded
 * QA verdict so existing-project flows (which never trigger preview QA)
 * remain a no-op.
 *
 * Exported indirectly via the orchestrator's terminal tool responses so
 * the calling agent can show the user a concrete retry/continue/cancel
 * choice when one or more variants fail QA.
 */
function buildQaSurfacing(variants) {
    const withQa = variants.filter((v) => Boolean(v.qa) && v.qa.status !== 'not_run');
    if (withQa.length === 0)
        return {};
    const qaEntries = withQa.map((v) => ({
        workItemId: v.workItemId,
        label: v.label,
        qa: v.qa,
    }));
    const failures = withQa.filter((v) => v.qa.status === 'failed');
    if (failures.length === 0) {
        return { qa: qaEntries };
    }
    return {
        qa: qaEntries,
        qaPrompt: buildQaFailurePrompt(failures.map((v) => ({ label: v.label, qa: v.qa })), withQa.length - failures.length),
    };
}
/**
 * User-facing prompt template for QA failures. Lists each failed variant
 * with its summary + first asset/console issue, then asks the user
 * whether to retry, continue with healthy variants, or cancel. Kept as
 * data (not a log line) so the calling agent can present the choice
 * directly to the human.
 */
function buildQaFailurePrompt(failures, healthyCount) {
    if (failures.length === 0) {
        return '';
    }
    const lines = failures.map((failure) => {
        const firstIssue = failure.qa.issues[0];
        const issueDetail = firstIssue
            ? ` (${describeIssue(firstIssue.kind)}: ${firstIssue.detail}${firstIssue.message ? ` — ${firstIssue.message}` : ''})`
            : '';
        return `• ${failure.label}: ${failure.qa.summary}${issueDetail}`;
    });
    const healthyLine = healthyCount > 0
        ? `\n\n${healthyCount} other variant${healthyCount === 1 ? '' : 's'} passed QA and can still be committed.`
        : '';
    const intro = failures.length === 1
        ? '1 variant failed QA:'
        : `${failures.length} variants failed QA:`;
    return `${intro}\n${lines.join('\n')}${healthyLine}\n\nWould you like to retry the failed variant${failures.length === 1 ? '' : 's'}, continue with healthy variants only, or cancel?`;
}
const describeIssue = (kind) => {
    switch (kind) {
        case 'asset_load_failed':
            return 'asset load failed';
        case 'console_error':
            return 'console/runtime error';
        case 'preview_unavailable':
            return 'preview unavailable';
    }
};
/**
 * Handle the zero-to-one branch of `start_variants` — the single entry point
 * for fresh projects. Prompt-only sessions take the single-call path
 * (`startUnified` → `work_items_ready`). Source-grounded sessions (inspiration
 * URLs / source artifacts) delegate to `kickoffSourceGroundedZeroToOne`, which
 * runs the source-research flow and returns a multi-step `awaiting_*` stage.
 */
async function handleStartVariantsZeroToOne(args, deps) {
    const framework = args.framework ?? 'vite';
    if (framework !== 'vite') {
        return {
            stage: 'failed',
            errorCode: 'UNSUPPORTED_FRAMEWORK',
            message: `start_variants(mode='zero_to_one') only supports framework='vite'; got '${framework}'.`,
        };
    }
    // Source-grounded sessions (inspiration URLs / source artifacts) need the
    // source-research flow. Route them through the shared kickoff, which returns
    // a multi-step `awaiting_*` stage rather than `work_items_ready` — the agent
    // researches sources, reports a source plan, then continues.
    const hasSourceContext = args.sourceContext &&
        ((args.sourceContext.sourceUrls?.length ?? 0) > 0 ||
            (args.sourceContext.sourceArtifacts?.length ?? 0) > 0 ||
            Boolean(args.sourceContext.sourceIntent) ||
            Boolean(args.sourceContext.artifact));
    if (hasSourceContext) {
        const out = await (0, createZeroToOneTool_1.kickoffSourceGroundedZeroToOne)({
            prompt: args.prompt,
            count: args.count,
            destinationParent: args.destinationParent,
            framework: args.framework,
            sourceContext: args.sourceContext,
        }, deps);
        if (out.stage === 'failed')
            return out;
        return { ...out, mode: 'zero_to_one' };
    }
    // Prompt-only: single-call path. Shared destination derivation with the
    // source-grounded kickoff — resolve the destination parent (arg → open
    // editor project → cwd), slugify, collapse any nested `.rivet/`, and create
    // `<workspaceRoot>/.rivet/`.
    const cwd = deps.cwd ?? (() => process.cwd());
    const dest = (0, generatedDestination_1.deriveGeneratedDestination)({
        prompt: args.prompt,
        destinationParent: args.destinationParent,
        editorProjectPath: deps.getProjectPath?.() ?? undefined,
        cwd,
    });
    if (!dest.ok) {
        return {
            stage: 'failed',
            errorCode: dest.errorCode,
            message: dest.message,
        };
    }
    const { workspaceRoot, destinationPath } = dest;
    const projectContext = {
        kind: 'fresh',
        workspacePath: destinationPath,
        workspaceRoot,
        framework: 'vite',
    };
    const visualEditor = deps.ensureVisualEditorOpen
        ? await deps.ensureVisualEditorOpen({
            projectPath: destinationPath,
            workspaceRoot,
            framework: 'vite',
        })
        : undefined;
    const result = await deps.orchestrator.startUnified({
        prompt: args.prompt,
        count: args.count,
        target: args.target,
        projectContext,
        runLabel: args.runLabel,
        leaseOwner: deps.leaseOwner(),
        briefs: args.briefs,
    });
    return assembleStartVariantsResponse({
        mode: 'zero_to_one',
        result,
        destinationPath,
        visualEditor,
    });
}
/**
 * Handle the existing-project branch of `start_variants`. Detects the
 * project's framework server-side (cwd or the provided projectPath) so
 * the agent doesn't need a separate detect_project call, auto-opens the
 * visual editor on the resolved projectPath, and leases all variant
 * work items so they're ready to code in the same response.
 */
async function handleStartVariantsExisting(args, deps) {
    const projectPath = args.projectPath ?? process.cwd();
    if (!path_1.default.isAbsolute(projectPath)) {
        return {
            stage: 'failed',
            errorCode: 'SCHEMA_VALIDATION_FAILED',
            message: `projectPath must be absolute; got '${projectPath}'.`,
        };
    }
    let framework;
    try {
        framework = new ProjectDetectionService_1.ProjectDetectionService().detectFramework(projectPath);
    }
    catch (err) {
        const msg = err instanceof Error ? err.message : 'Unknown detection error';
        return {
            stage: 'failed',
            errorCode: 'SCHEMA_VALIDATION_FAILED',
            message: `Could not detect project at '${projectPath}': ${msg}. Ensure it has a package.json, or pass projectPath explicitly.`,
        };
    }
    // Auto-open the editor for the modes ensureVisualEditorOpen supports —
    // 'vite' (scaffolded apps) and 'static' (empty / HTML-only workspaces, which
    // open in static mode with no dev server). For other frameworks (next, etc.)
    // the agent opens the editor itself via the standalone open_visual_editor
    // tool. workspaceRoot === projectPath for existing-project sessions — there's
    // no subproject nesting, the project is the workspace.
    const editorFramework = framework === 'vite' || framework === 'static' ? framework : undefined;
    const visualEditor = deps.ensureVisualEditorOpen && editorFramework
        ? await deps.ensureVisualEditorOpen({
            projectPath,
            workspaceRoot: projectPath,
            framework: editorFramework,
        })
        : undefined;
    const result = await deps.orchestrator.startUnified({
        prompt: args.prompt,
        count: args.count,
        target: args.target,
        runLabel: args.runLabel,
        leaseOwner: deps.leaseOwner(),
        briefs: args.briefs,
    });
    return assembleStartVariantsResponse({
        mode: 'existing',
        result,
        project: { projectPath, framework },
        visualEditor,
    });
}
/**
 * Build the StartVariantsOutput from a startUnified result + the lease
 * info it surfaced. Pairs each variant in the original `variants` list
 * with its `leasedWorkItems[]` entry from requestWork so the agent sees
 * one consistent per-variant object with everything it needs.
 */
function assembleStartVariantsResponse(args) {
    const { result } = args;
    if (result.leaseId === undefined ||
        result.leaseTtlMs === undefined ||
        result.leasedWorkItems === undefined) {
        return {
            stage: 'failed',
            errorCode: 'INVALID_STAGE_ACTION',
            message: 'start_variants expected startUnified to start the variants but none were returned.',
        };
    }
    const itemById = new Map(result.leasedWorkItems.map((item) => [item.id, item]));
    const variants = [];
    for (const v of result.variants) {
        const workItem = itemById.get(v.variantId);
        if (!workItem) {
            return {
                stage: 'failed',
                errorCode: 'INVALID_STAGE_ACTION',
                message: `start_variants leased fewer items than variants — missing workItem for ${v.variantId}.`,
            };
        }
        variants.push({ ...v, workItem });
    }
    return {
        sessionId: result.sessionId,
        stage: 'work_items_ready',
        mode: args.mode,
        leaseId: result.leaseId,
        leaseTtlMs: result.leaseTtlMs,
        variants,
        scaffoldBaseWorkItemId: result.scaffoldBaseWorkItemId,
        ...(args.destinationPath ? { destinationPath: args.destinationPath } : {}),
        ...(args.visualEditor ? { visualEditor: args.visualEditor } : {}),
        ...(args.project ? { project: args.project } : {}),
        nextAction: 'report_variant_complete',
    };
}
//# sourceMappingURL=tools.js.map