import { redactTelemetryString, type OutputResolutionIssueKind } from "@hyperframes/core"; import type { SubTimelineWaitOutcome } from "@hyperframes/engine"; import { FEEDBACK_RATING_SCALE } from "../utils/feedbackRating.js"; import { flush, trackEvent } from "./client.js"; import { readConfig } from "./config.js"; // run_id is attached only when the orchestrator set HYPERFRAMES_RUN_ID — an // absent property, never null/"" (PostHog treats those as real values). function runIdField(runId: string | undefined): { run_id?: string } { return runId !== undefined ? { run_id: runId } : {}; } export interface RenderObservabilityTelemetryPayload { /** Worst sub-composition timeline wait outcome across sessions. */ subTimelineWait?: SubTimelineWaitOutcome; observabilityRenderJobId?: string; observabilityCompositionHash?: string; observabilityEventCount?: number; observabilityLastPhase?: string; observabilityLastStatus?: string; observabilityFailedPhase?: string; browserDiagnosticCount?: number; browserDiagnosticErrors?: number; browserDiagnosticPageErrors?: number; browserDiagnosticRequestFailed?: number; browserDiagnosticHttpErrors?: number; browserDiagnosticNavigationStarts?: number; browserDiagnosticNavigationFailures?: number; browserDiagnosticConsoleErrors?: number; browserDiagnosticConsoleWarnings?: number; captureMode?: string; captureForceScreenshot?: boolean; captureWorkerCount?: number; captureUseStreamingEncode?: boolean; captureUseLayeredComposite?: boolean; captureUsePageSideCompositing?: boolean; captureHasHdrContent?: boolean; captureBrowserGpuMode?: string; captureProtocolTimeoutMs?: number; capturePageNavigationTimeoutMs?: number; capturePlayerReadyTimeoutMs?: number; captureTransientRetries?: number; captureMemoryExhaustionDetected?: boolean; // Mirror of the DE inversion/router state on `RenderCaptureObservability` — // sourced from the live-mutated capture object rather than `perfSummary`, // so a hard failure (crash, OOM, timeout) that never reaches perfSummary // construction still reports which DE experiment cohort it was in. Mapped // to the SAME `de_*` event keys `trackRenderComplete` sets explicitly from // `perfSummary.drawElement`; the caller must spread this payload FIRST so // the more authoritative perfSummary value wins when both are present. captureDeWorkerInversion?: string; captureDePreInversionWorkers?: number; captureDeParallelRouter?: string; captureDePreRouterWorkers?: number; captureDeSelfVerifyFallback?: boolean; captureDeFallbackReason?: string; captureDeFallbackFailedDb?: number; captureDeFallbackFrameIndex?: number; captureDeFallbackThresholdDb?: number; /** Non-DE parallel-streaming router outcome ("screenshot" | "beginframe" — * routed; "eligible_off" — would route but the kill switch is off). */ captureParallelStream?: string; observabilityExtractVideoCount?: number; observabilityExtractedVideoCount?: number; observabilityExtractTotalFrames?: number; observabilityExtractMaxFramesPerVideo?: number; observabilityExtractAvgFramesPerVideo?: number; observabilityExtractVfrProbeMs?: number; observabilityExtractVfrPreflightMs?: number; observabilityExtractVfrPreflightCount?: number; observabilityExtractCacheHits?: number; observabilityExtractCacheMisses?: number; observabilityInitDurationMs?: number; observabilityInitTweenCount?: number; } function renderObservabilityEventProperties(props: RenderObservabilityTelemetryPayload) { return { sub_timeline_wait: props.subTimelineWait, observability_render_job_id: props.observabilityRenderJobId, observability_composition_hash: props.observabilityCompositionHash, observability_event_count: props.observabilityEventCount, observability_last_phase: props.observabilityLastPhase, observability_last_status: props.observabilityLastStatus, observability_failed_phase: props.observabilityFailedPhase, browser_diagnostic_count: props.browserDiagnosticCount, browser_diagnostic_errors: props.browserDiagnosticErrors, browser_diagnostic_page_errors: props.browserDiagnosticPageErrors, browser_diagnostic_request_failed: props.browserDiagnosticRequestFailed, browser_diagnostic_http_errors: props.browserDiagnosticHttpErrors, browser_diagnostic_navigation_starts: props.browserDiagnosticNavigationStarts, browser_diagnostic_navigation_failures: props.browserDiagnosticNavigationFailures, browser_diagnostic_console_errors: props.browserDiagnosticConsoleErrors, browser_diagnostic_console_warnings: props.browserDiagnosticConsoleWarnings, capture_mode: props.captureMode, capture_force_screenshot: props.captureForceScreenshot, capture_worker_count: props.captureWorkerCount, capture_use_streaming_encode: props.captureUseStreamingEncode, capture_use_layered_composite: props.captureUseLayeredComposite, capture_use_page_side_compositing: props.captureUsePageSideCompositing, capture_has_hdr_content: props.captureHasHdrContent, capture_browser_gpu_mode: props.captureBrowserGpuMode, capture_protocol_timeout_ms: props.captureProtocolTimeoutMs, capture_page_navigation_timeout_ms: props.capturePageNavigationTimeoutMs, capture_player_ready_timeout_ms: props.capturePlayerReadyTimeoutMs, capture_transient_retries: props.captureTransientRetries, capture_memory_exhaustion_detected: props.captureMemoryExhaustionDetected, de_worker_inversion: props.captureDeWorkerInversion, de_pre_inversion_workers: props.captureDePreInversionWorkers, de_parallel_router: props.captureDeParallelRouter, de_pre_router_workers: props.captureDePreRouterWorkers, de_self_verify_fallback: props.captureDeSelfVerifyFallback, de_fallback_reason: props.captureDeFallbackReason, de_fallback_failed_db: props.captureDeFallbackFailedDb, de_fallback_frame_index: props.captureDeFallbackFrameIndex, de_fallback_threshold_db: props.captureDeFallbackThresholdDb, capture_parallel_stream: props.captureParallelStream, observability_extract_video_count: props.observabilityExtractVideoCount, observability_extracted_video_count: props.observabilityExtractedVideoCount, observability_extract_total_frames: props.observabilityExtractTotalFrames, observability_extract_max_frames_per_video: props.observabilityExtractMaxFramesPerVideo, observability_extract_avg_frames_per_video: props.observabilityExtractAvgFramesPerVideo, observability_extract_vfr_probe_ms: props.observabilityExtractVfrProbeMs, observability_extract_vfr_preflight_ms: props.observabilityExtractVfrPreflightMs, observability_extract_vfr_preflight_count: props.observabilityExtractVfrPreflightCount, observability_extract_cache_hits: props.observabilityExtractCacheHits, observability_extract_cache_misses: props.observabilityExtractCacheMisses, observability_init_duration_ms: props.observabilityInitDurationMs, observability_init_tween_count: props.observabilityInitTweenCount, }; } function redactTelemetryMessage(value: string): string { return redactTelemetryString(value); } export function trackCommand(command: string, runId?: string): void { trackEvent("cli_command", { command, ...runIdField(runId), }); } export function trackRenderComplete( props: { durationMs: number; fps: number; quality: string; /** Authoring workflow skill that drove this render (e.g. "product-launch-video"). */ authoringSkill?: string; workers?: number; docker: boolean; gpu: boolean; // Static-frame dedup outcome (opt-out HF_STATIC_DEDUP=false). Undefined on // render paths with no capture session. staticDedupEnabled?: boolean; staticDedupArmed?: boolean; staticDedupSkipReason?: string; staticDedupPredictedFrames?: number; staticDedupReusedFrames?: number; // BeginFrame no-damage reuse outcome (Linux/Docker lastFrameCache — the BF // counterpart of static dedup). Undefined outside beginframe capture mode. beginFrameNoDamageFrames?: number; beginFrameHasDamageFrames?: number; // drawElement fast-capture outcome (default-on release visibility). // Undefined on render paths with no capture session. deCaptureMode?: string; deCompileGate?: string; deClampReason?: string; deWorkerInversion?: string; dePreInversionWorkers?: number; deParallelRouter?: string; dePreRouterWorkers?: number; deGateReason?: string; deWorkerEncode?: boolean; deVerifyArmed?: number; deVerifyChecked?: number; deVerifyMinDb?: number; deVerifyInitMs?: number; deSelfVerifyFallback?: boolean; deFallbackReason?: string; deFallbackFailedDb?: number; deFallbackFrameIndex?: number; deFallbackThresholdDb?: number; deBlankSuspects?: number; deBlankDeterministicAccepts?: number; deBlankRecaptures?: number; deBoundaryFrames?: number; deNcprFallbacks?: number; // "cli" when triggered by `hyperframes render` (default), "studio" when // triggered by a studio preview-server render (POST /api/projects/:id/render). source?: "cli" | "studio"; // Composition metadata compositionDurationMs?: number; compositionWidth?: number; compositionHeight?: number; totalFrames?: number; // Processing efficiency speedRatio?: number; captureAvgMs?: number; /** Warmup-robust per-frame capture median (basis for speedup estimates). */ captureP50Ms?: number; /**