import type { ProviderPath, ProviderRouteId, VideoOperationKind, VideoProvider, } from './provider-platform/types.js'; export type VideoProductionMode = 'storyboard' | 'director'; export type VideoProviderAvailability = 'available' | 'degraded' | 'unavailable'; export interface VideoProviderRuntimeDependencyStatus { name: 'python3' | 'bun' | 'ffmpeg'; available: boolean; path?: string; } export interface VideoProviderRouteStatusReport { routeId: ProviderRouteId; provider: VideoProvider; displayName: string; path: ProviderPath; availability: VideoProviderAvailability; maturity: 'production' | 'scaffold'; summary: string; supportedOperations: VideoOperationKind[]; requiredEnvVars: string[]; availableEnvVars: string[]; missingEnvVars: string[]; requiredDependencies: Array<'python3' | 'bun' | 'ffmpeg'>; availableDependencies: Array<'python3' | 'bun' | 'ffmpeg'>; missingDependencies: Array<'python3' | 'bun' | 'ffmpeg'>; issues: string[]; notes: string[]; } export interface VideoProviderStatusReport { generatedAt: string; workspace: { root: string; ok: boolean; issues: string[]; }; envSources: string[]; runtimeDependencies: VideoProviderRuntimeDependencyStatus[]; routes: VideoProviderRouteStatusReport[]; } export type VideoStageArtifactName = | 'brief' | 'clone-plan' | 'storyboard' | 'asset-manifest' | 'execution-plan' | 'execution-report' | 'review-report' | 'publish-report' | 'analyze-output' | 'assemble-report' | 'story-bible' | 'multi-shot-prompt' | 'filmmaking-prompts' | 'project-blueprint' | 'brand-definition'; export interface VideoPipelineStageManifest { name: 'brief' | 'storyboard' | 'assets' | 'review' | 'publish'; requiredArtifactsIn?: VideoStageArtifactName[]; produces: VideoStageArtifactName[]; checkpointRequired: boolean; humanApprovalDefault: boolean; successCriteria: string[]; } export interface VideoPipelineManifest { name: string; version: string; productionMode: VideoProductionMode; stages: VideoPipelineStageManifest[]; } export interface VideoAnalyzeOutput { reference: { source: string; title?: string; durationSeconds?: number; }; pacing: { label: 'slow' | 'medium' | 'fast' | 'mixed'; notes: string[]; }; structure: { hook?: string; beats: string[]; ending?: string; }; motionClassification: { primaryMode: 'motion-clips' | 'animated-stills' | 'mixed' | 'unknown'; notes: string[]; }; keep: string[]; change: string[]; reusableVariables: string[]; styleLayers?: string[]; beatCompression?: { targetDurationSeconds: number; maxBeats: number; dialogueWordBudget: number; notes: string[]; }; technicalNotes?: string[]; dialogueNotes?: string[]; generatedAt: string; } export interface LegacyImportSummary { sourcePath: string; importedAt: string; imageCount: number; videoCount: number; finalCount: number; telemetryCount: number; manifestPresent: boolean; queueFilePresent: boolean; queuePendingStatusDetected: boolean; queueStatusMismatch: boolean; nestedVideoCount: number; nestedFinalCount: number; nestedOutputRootDetected: boolean; inferredCurrentStage: string | null; inferredLastCompletedStage: string | null; inferredCheckpointStatus: string | null; } export interface VideoExecutionPlan { projectSlug: string; productionMode: VideoProductionMode; operationKind: VideoOperationKind; recommendedRouteId: ProviderRouteId | null; executionProfile: { aspectRatio: '16:9' | '9:16' | '1:1'; quality: 'fast' | 'quality'; resolution: '720p' | '1080p'; generateAudio: boolean; outputCount: number; /** * Optional Veo model override for the `veo-useapi` route. When set it * selects the underlying Flow v1 model passed to `flow.ts -m`; `omni-flash` * unlocks native audio / V2V. Absent → the transport falls back to * `quality`, so existing payloads are byte-identical. */ veoModel?: 'fast' | 'quality' | 'lite' | 'free' | 'omni-flash'; }; ready: boolean; blockers: string[]; rationale: string[]; promptGuidance: Array<{ name: string; reason: string; category: 'provider' | 'framework'; }>; generatedAt: string; } export interface VideoExecutionTask { sceneIndex: number; prompt: string; inputKind: 'text' | 'image' | 'video'; referencePaths: string[]; referenceSlots?: Array<{ slot: string; role: string; label: string; path?: string; }>; sourceAssetIds: string[]; backendHints: string[]; characters: string[]; durationSeconds?: number; /** * OUTPUT-DEPENDENT render resolution (e.g. `720p`, `1080p`) threaded from the * filmmaking-prompts packet, so a per-render resolution can flow to the * provider instead of a fixed one. Absent when the packet did not carry it. */ resolution?: string; promptPacketVariant?: string; /** * When a scene was resolved via chain-from-prev, this carries the source * candidate id so downstream adapters can record provenance. Absent when the * scene is not chained. */ chainedFromCandidateId?: string; /** * How `referencePaths` should be delivered to the provider: * - `'character'` — identity references (character sheets / Asset:// avatars). * Each goes into the gateway's reference slot (Runway `imageAssetId1..N`, * Dreamina `omni_N_imageRef`) EVEN when there is only one, so a lone * character sheet is never used as the literal first frame of the video * (which made solo clips open on the character grid). * - `'keyframe'` — a real first-frame seed (user keyframe / chained-shot * continuity). A single ref drives first-frame i2v (`startFrameAssetId` / * `firstFrameRef`), preserving today's behavior. * Absent → treated as `'keyframe'`, so existing payloads are byte-identical. */ referenceRole?: 'character' | 'keyframe'; /** * Optional END keyframe (local path or http(s) URL). When set alongside a * single first-frame keyframe (`referencePaths[0]`), the clip animates from * the first frame to this end frame (Seedance-2 / Gen-4.5 keyframe * interpolation) — letting two stills become one continuous shot. Carried * separately from `referencePaths` so it never counts as a second reference * (which would switch the route into multi-reference mode). Absent → today's * single-keyframe behavior is byte-identical. */ endKeyframePath?: string; /** * Optional voice-narration preset for this scene. Routed to the `veo-useapi` * transport's `flow.ts --voice` flag (Flow v1 `referenceAudio_1`); one of the * 30 named Flow voice presets. Provider-side this is `omni-flash`-only. * Absent → no `--voice` flag is emitted (byte-identical legacy behavior). */ voicePreset?: string; /** * Optional V2V edit source media id. Routed to the `veo-useapi` transport's * `flow.ts --ref-video` flag (Flow v1 `referenceVideo_1`); `omni-flash`-only * at the provider. This is the dedicated video-to-video edit reference — NOT * the scene-chaining seed (`inputKind: 'video'`), which any model supports. * Absent → no `--ref-video` flag is emitted (byte-identical legacy behavior). */ referenceVideoMediaId?: string; /** * omni-flash First-Frame (startImage / I2V) mode — GATED by the * `VCLAW_OMNI_FIRST_FRAME` env flag. When true (and the gate is enabled), the * `veo-useapi` transport delivers `referencePaths[0]` as the literal first * frame (`flow.ts image:

` → startImage) instead of the loose R2V * `ingredients:` path — locking the opening frame while keeping omni-flash's * native voice (`--voice`). LIVE-VERIFIED 2026-06-06 (omni-flash startImage * routes to abra_i2v_8s on useapi.net), so the gate now defaults ON; * `VCLAW_OMNI_FIRST_FRAME=0/off` is the kill-switch. Absent → loose R2V. */ firstFrame?: boolean; /** * Saved Google Flow character refs (from `flow-characters.json`) for this * scene. Routed to the `veo-useapi` transport as repeated `flow.ts --character` * flags (Flow v1 `character_1..7`) — reusable locked identity + bundled voice * via R2V entity mode. Resolved by `buildExecutionPayload` from the scene's * characters on the veo-useapi route only. Absent → no `--character` flags * (byte-identical legacy behavior). NOTE: routes through R2V, so realistic * human faces are moderation-filtered; use for stylized/mascot identities. */ characterRefs?: string[]; } export interface VideoExecutionPayload { workspaceRoot: string; projectSlug: string; productionMode: VideoProductionMode; routeId: ProviderRouteId; operationKind: VideoOperationKind; executionProfile: { aspectRatio: '16:9' | '9:16' | '1:1'; quality: 'fast' | 'quality'; resolution: '720p' | '1080p'; generateAudio: boolean; outputCount: number; /** * Optional Veo model override for the `veo-useapi` route. Copied verbatim * from the plan profile; `omni-flash` unlocks native audio / V2V. Absent → * the transport falls back to `quality` (byte-identical legacy behavior). */ veoModel?: 'fast' | 'quality' | 'lite' | 'free' | 'omni-flash'; }; generatedAt: string; outputDir: string; tasks: VideoExecutionTask[]; promptGuidance: Array<{ name: string; reason: string; category: 'provider' | 'framework'; }>; } export interface VideoExecutionPollResult { status: 'pending' | 'completed' | 'failed'; externalJobId: string | null; outputs: Array<{ id: string; kind: 'image' | 'video' | 'audio' | 'subtitle' | 'other'; path: string; sceneIndex?: number; backend?: string; }>; issues: string[]; rawResult: unknown; } export interface VideoExecutionCancelResult { status: 'cancelled' | 'unsupported'; externalJobId: string | null; issues: string[]; rawResult: unknown; } export interface VideoExecutionReport { projectSlug: string; productionMode: VideoProductionMode; operationKind: VideoOperationKind; routeId: ProviderRouteId | null; status: 'dry-run-complete' | 'live-submitted' | 'blocked'; dryRun: boolean; generatedAt: string; blockers: string[]; executedSteps: string[]; taskCount?: number; /** * Non-fatal advisory warnings surfaced by the run. Currently carries the * findings from the automated character-consistency vision audit * (`consistency-audit`) when a real run completed and a Gemini vision key is * configured. Advisory only — these never change `status`; absent when the * audit did not run (dry-run, no key, or no rendered outputs to inspect). */ warnings?: string[]; submission?: { adapterCommand?: string; externalJobId?: string | null; rawResult?: unknown; }; /** * When the execute runtime wrote per-scene candidates (candidate mode), this * records which candidate id was created for each scene in this run. Poll * refreshes use this map to update candidate status without re-reading the * storyboard. Absent on legacy asset-manifest runs. */ candidatesByScene?: Array<{ sceneIndex: number; candidateId: string; }>; poll?: { lastCheckedAt: string; status: 'pending' | 'completed' | 'failed'; issues: string[]; outputsIngested?: number; rawResult?: unknown; }; } export interface CharacterConsistencyReport { slug: string; ok: boolean; referencedCharacters: string[]; missingProfiles: string[]; missingReferenceAssets: string[]; issues: string[]; } export type ReferenceSheetType = | 'identity' | 'outfit-material' | 'environment' | 'motion-camera' | 'palette-mood'; export type ReferenceRole = // identity | 'identity' | 'wardrobe' | 'silhouette' | 'age-reference' // outfit-material | 'outfit' | 'material' | 'accessory' | 'texture' | 'product-hero' | 'product-variant' | 'product-in-use' | 'packaging' // environment | 'location' | 'set-dressing' | 'weather' | 'time-of-day' // motion-camera | 'motion-rhythm' | 'camera-behavior' | 'blocking' | 'shot-framing' // palette-mood | 'palette' | 'composition' | 'mood' | 'lighting-reference'; export type GbRefKind = 'character' | 'product' | 'scene' | 'style-preset' | 'reference-group'; export interface GbRef { kind: GbRefKind; id: number; } export type ReferenceEntry = | { path: string; role: ReferenceRole; note?: string } | { gbRef: GbRef; role: ReferenceRole; note?: string }; export interface ReferenceSheetBindings { sceneIndices: number[]; } export interface ReferenceSheet { id: string; type: ReferenceSheetType; name: string; description?: string; characterName?: string; references: ReferenceEntry[]; bindings: ReferenceSheetBindings; createdAt: string; updatedAt: string; } export interface ReferenceSheetsArtifact { schemaVersion: 1; sheets: ReferenceSheet[]; } export type SceneCandidateStatus = 'pending' | 'completed' | 'failed' | 'cancelled'; export interface SceneCandidateOutput { kind: 'video' | 'audio' | 'image'; path: string; durationSec?: number; } export interface SceneCandidateSource { executionRound: number; adapter: 'builtin' | 'shim' | 'custom' | 'native'; externalJobId?: string; chainedFromCandidateId: string | null; } export interface SceneCandidate { id: string; generationRound: number; prompt: string; route: string; submittedAt: string; completedAt?: string; status: SceneCandidateStatus; outputs: SceneCandidateOutput[]; source: SceneCandidateSource; } export interface SceneCandidatesEntry { sceneIndex: number; /** * The canonical scene description this scene's candidates are all rendering. * Captured once, from the first candidate appended for the scene, so that a * later regenerated candidate's (possibly stylised/mutated) prompt never * becomes the reference intent fed to the auto-select judge. Optional for * backward compatibility with artifacts written before this field existed. */ intendedPrompt?: string; candidates: SceneCandidate[]; } export interface SceneCandidatesArtifact { schemaVersion: 1; scenes: SceneCandidatesEntry[]; } export interface SceneSelectionEntry { sceneIndex: number; selectedCandidateId: string | null; rejectedCandidateIds: string[]; pendingCandidateIds: string[]; rerollRequested: boolean; chainFromPrev: boolean; /** * Optional explicit chain source. When `chainFromPrev` is true, the chain seed * comes from this scene's selected candidate; absent → the immediately previous * scene (`sceneIndex - 1`, legacy behavior). Set by the auto-chain fallback * ladder to chain from an earlier anchor when the prior clip is rejected. */ chainFromSceneIndex?: number; notes?: string; } export interface SceneSelectionArtifact { schemaVersion: 1; scenes: SceneSelectionEntry[]; }