@opengeni/react 1.0.1 → 2.2.0-canary.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -10
- package/dist/artifacts-spreadsheet.js +1 -1
- package/dist/artifacts.js +1 -1
- package/dist/browser-viewer-GXUICIAG.js +14 -0
- package/dist/{chunk-R3AZOAB5.js → chunk-4MDLEHXL.js} +9 -115
- package/dist/chunk-4MDLEHXL.js.map +1 -0
- package/dist/{chunk-Y7OEDUJ5.js → chunk-5RZN3DD5.js} +1014 -420
- package/dist/chunk-5RZN3DD5.js.map +1 -0
- package/dist/chunk-5YK2ZDIG.js +116 -0
- package/dist/chunk-5YK2ZDIG.js.map +1 -0
- package/dist/chunk-H5YGYNXS.js +329 -0
- package/dist/chunk-H5YGYNXS.js.map +1 -0
- package/dist/{chunk-7WCLAEFV.js → chunk-ICA6R6TW.js} +2 -2
- package/dist/{chunk-7WCLAEFV.js.map → chunk-ICA6R6TW.js.map} +1 -1
- package/dist/{chunk-IGC6A2ZI.js → chunk-JOAVA2TW.js} +589 -217
- package/dist/chunk-JOAVA2TW.js.map +1 -0
- package/dist/{chunk-5UQPNLIO.js → chunk-LTCHJ4MS.js} +9 -9
- package/dist/chunk-LTCHJ4MS.js.map +1 -0
- package/dist/{chunk-C5ZPSSS5.js → chunk-NEXLLTP4.js} +2 -1
- package/dist/{chunk-C5ZPSSS5.js.map → chunk-NEXLLTP4.js.map} +1 -1
- package/dist/chunk-QGBJ6GHG.js +2300 -0
- package/dist/chunk-QGBJ6GHG.js.map +1 -0
- package/dist/{chunk-B6WPZSTT.js → chunk-UOLJWT5Y.js} +78 -16
- package/dist/chunk-UOLJWT5Y.js.map +1 -0
- package/dist/{chunk-MKPIAGVL.js → chunk-W6NFJXSA.js} +8 -19
- package/dist/chunk-W6NFJXSA.js.map +1 -0
- package/dist/{chunk-XBZWOM2K.js → chunk-X4A66EWZ.js} +8 -3
- package/dist/chunk-X4A66EWZ.js.map +1 -0
- package/dist/chunk-X4JAFD5R.js +3907 -0
- package/dist/chunk-X4JAFD5R.js.map +1 -0
- package/dist/{chunk-6KLX2RH5.js → chunk-XSA2QAKP.js} +334 -19
- package/dist/chunk-XSA2QAKP.js.map +1 -0
- package/dist/{chunk-HA46CYQK.js → chunk-XXWH2JLC.js} +792 -206
- package/dist/chunk-XXWH2JLC.js.map +1 -0
- package/dist/client.d.ts +2 -2
- package/dist/components/browser-viewer.d.ts +7 -1
- package/dist/components/computer-viewer.d.ts +4 -1
- package/dist/components/enrollment-consent.d.ts +6 -3
- package/dist/components/file-browser.d.ts +7 -1
- package/dist/components/human-input-form.d.ts +1 -1
- package/dist/components/human-input-surface.d.ts +1 -1
- package/dist/components/machine-card.d.ts +4 -1
- package/dist/components/machines/machine-detail.d.ts +4 -1
- package/dist/components/machines/machine-operation-policy.d.ts +11 -0
- package/dist/components/machines-dashboard.d.ts +3 -1
- package/dist/components/markdown.d.ts +20 -1
- package/dist/components/message-timeline.d.ts +1 -1
- package/dist/components/model-policy-picker.d.ts +2 -0
- package/dist/components/pierre-file.d.ts +1 -1
- package/dist/components/sandbox-files.d.ts +8 -2
- package/dist/components/sandbox-workspace.d.ts +33 -3
- package/dist/components/session-chrome.d.ts +15 -3
- package/dist/components/workspace-dock.d.ts +6 -2
- package/dist/composer.js +5 -5
- package/dist/computer-viewer-ZOP4Q7A7.js +16 -0
- package/dist/computer-viewer-ZOP4Q7A7.js.map +1 -0
- package/dist/hooks/channel-cache.d.ts +23 -0
- package/dist/hooks/shared-machines-list.d.ts +14 -0
- package/dist/hooks/use-attached-browsers.d.ts +2 -1
- package/dist/hooks/use-channels.d.ts +1 -0
- package/dist/hooks/use-composer.d.ts +37 -9
- package/dist/hooks/use-machine-chip.d.ts +3 -3
- package/dist/hooks/use-machines.d.ts +19 -1
- package/dist/hooks/use-sandbox-files.d.ts +1 -0
- package/dist/hooks/use-session-background-commands.d.ts +14 -0
- package/dist/hooks/use-turn-queue.d.ts +12 -0
- package/dist/hooks/use-workspace-sessions.d.ts +1 -0
- package/dist/index.d.ts +7 -5
- package/dist/index.js +1096 -623
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +29 -181
- package/dist/interaction.js.map +1 -1
- package/dist/lib/interaction-errors.d.ts +3 -0
- package/dist/machines.d.ts +4 -2
- package/dist/machines.js +10 -2
- package/dist/model-policy.d.ts +1 -1
- package/dist/model-policy.js +1 -1
- package/dist/platform-activity-row-BJHUHXAT.js +169 -0
- package/dist/platform-activity-row-BJHUHXAT.js.map +1 -0
- package/dist/{queue-surface-implementation-2KVJTYXH.js → queue-surface-implementation-BSKUWHRI.js} +76 -16
- package/dist/queue-surface-implementation-BSKUWHRI.js.map +1 -0
- package/dist/realtime/realtime-control.d.ts +6 -3
- package/dist/realtime.js +36 -14
- package/dist/realtime.js.map +1 -1
- package/dist/session-ui.d.ts +1 -1
- package/dist/session-ui.js +6 -2
- package/dist/session.d.ts +2 -2
- package/dist/session.js +4 -4
- package/dist/timeline/index.d.ts +1 -1
- package/dist/timeline/platform-activity-row.d.ts +15 -0
- package/dist/timeline/screenshot-lightbox.d.ts +1 -1
- package/dist/timeline/shared.d.ts +4 -2
- package/dist/timeline/types.d.ts +48 -5
- package/dist/types/machines.d.ts +2 -2
- package/package.json +3 -3
- package/src/client.ts +12 -1
- package/src/components/artifacts/editable-spreadsheet.tsx +1 -1
- package/src/components/browser-viewer.tsx +361 -58
- package/src/components/chat-composer.tsx +1 -1
- package/src/components/composer.tsx +6 -6
- package/src/components/computer-viewer.tsx +368 -115
- package/src/components/enrollment-consent.tsx +64 -6
- package/src/components/file-browser.tsx +117 -2
- package/src/components/human-input-form.tsx +91 -55
- package/src/components/human-input-surface.tsx +1 -1
- package/src/components/machine-card.tsx +111 -0
- package/src/components/machine-input-display.ts +15 -0
- package/src/components/machines/machine-detail.tsx +47 -0
- package/src/components/machines/machine-operation-policy.tsx +179 -0
- package/src/components/machines-dashboard.tsx +6 -0
- package/src/components/markdown.tsx +174 -20
- package/src/components/message-timeline.tsx +162 -17
- package/src/components/model-policy-picker.tsx +9 -17
- package/src/components/pierre-file.tsx +1 -1
- package/src/components/queue-surface-implementation.tsx +82 -16
- package/src/components/sandbox-files.tsx +135 -19
- package/src/components/sandbox-workspace.tsx +204 -92
- package/src/components/session-chrome.tsx +415 -208
- package/src/components/session-status.tsx +1 -1
- package/src/components/user-message-body.tsx +168 -48
- package/src/components/workspace-dock.tsx +141 -175
- package/src/hooks/channel-cache.ts +104 -0
- package/src/hooks/shared-machines-list.ts +239 -0
- package/src/hooks/use-attached-browsers.ts +5 -1
- package/src/hooks/use-browser-frame-stream.ts +6 -1
- package/src/hooks/use-browser-session.ts +6 -4
- package/src/hooks/use-browser-sessions.ts +7 -1
- package/src/hooks/use-channels.ts +32 -10
- package/src/hooks/use-composer.ts +760 -291
- package/src/hooks/use-computer-frame-stream.ts +14 -9
- package/src/hooks/use-computer-session.ts +16 -14
- package/src/hooks/use-computer-sessions.ts +8 -1
- package/src/hooks/use-last-started-turn-policy.ts +1 -1
- package/src/hooks/use-machine-chip.ts +23 -6
- package/src/hooks/use-machines.ts +104 -6
- package/src/hooks/use-sandbox-files.ts +195 -140
- package/src/hooks/use-sandbox-git.ts +58 -29
- package/src/hooks/use-session-background-commands.ts +56 -0
- package/src/hooks/use-session-capabilities.ts +18 -0
- package/src/hooks/use-turn-queue.ts +111 -12
- package/src/hooks/use-workspace-sessions.ts +5 -1
- package/src/index.ts +26 -4
- package/src/lib/interaction-errors.ts +18 -0
- package/src/machines.ts +9 -1
- package/src/model-policy.ts +10 -2
- package/src/realtime/realtime-control.tsx +53 -17
- package/src/session-context.ts +1 -0
- package/src/session-ui.ts +6 -1
- package/src/session.ts +9 -1
- package/src/timeline/activity-rail.tsx +20 -75
- package/src/timeline/index.ts +4 -0
- package/src/timeline/platform-activity-row.tsx +201 -0
- package/src/timeline/projection.ts +476 -23
- package/src/timeline/screenshot-lightbox.tsx +48 -14
- package/src/timeline/shared.tsx +8 -2
- package/src/timeline/tool-renderers.tsx +104 -6
- package/src/timeline/turn-summary.tsx +6 -1
- package/src/timeline/types.ts +75 -2
- package/src/types/machines.ts +10 -0
- package/styles/compiled.css +115 -104
- package/dist/chunk-5UQPNLIO.js.map +0 -1
- package/dist/chunk-6KLX2RH5.js.map +0 -1
- package/dist/chunk-B6WPZSTT.js.map +0 -1
- package/dist/chunk-HA46CYQK.js.map +0 -1
- package/dist/chunk-IGC6A2ZI.js.map +0 -1
- package/dist/chunk-LLOLUHVG.js +0 -5937
- package/dist/chunk-LLOLUHVG.js.map +0 -1
- package/dist/chunk-MKPIAGVL.js.map +0 -1
- package/dist/chunk-R3AZOAB5.js.map +0 -1
- package/dist/chunk-XBZWOM2K.js.map +0 -1
- package/dist/chunk-Y7OEDUJ5.js.map +0 -1
- package/dist/interactive-workbench-viewers-TOHBXJEE.js +0 -15
- package/dist/queue-surface-implementation-2KVJTYXH.js.map +0 -1
- /package/dist/{interactive-workbench-viewers-TOHBXJEE.js.map → browser-viewer-GXUICIAG.js.map} +0 -0
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/model-policy-picker.tsx"],"sourcesContent":["import type { ClientModel, LatencyMode, ReasoningEffort } from \"@opengeni/sdk\";\nimport {\n CheckIcon,\n ChevronDownIcon,\n ChevronLeftIcon,\n ChevronRightIcon,\n KeyRoundIcon,\n ZapIcon,\n} from \"lucide-react\";\nimport { motion, useReducedMotion } from \"motion/react\";\nimport { DropdownMenu } from \"radix-ui\";\nimport {\n useEffect,\n useMemo,\n useState,\n type CSSProperties,\n type ReactNode,\n type SVGProps,\n} from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { usePortalTokenSource, usePortalTokenStyle } from \"../lib/use-portal-token-style\";\nimport {\n coerceReasoningEffortForModel,\n effortOptionsForModel,\n findPickerRow,\n groupPickerRowsByBillingClass,\n labelReasoningEffort,\n projectClientModelRows,\n runnableLatencyModesForModel,\n type PickerBillingClass,\n type PickerModelRow,\n} from \"../model-policy\";\n\ntype ClientPickerModelRow = PickerModelRow<ClientModel>;\n\ntype NavLevel = \"providers\" | \"models\" | \"thinking\";\n\ntype PickerNavState = {\n level: NavLevel;\n rail: PickerBillingClass | null;\n modelId: string | null;\n};\n\ntype NavUpdater = PickerNavState | ((previous: PickerNavState) => PickerNavState);\n\nexport type ModelPolicyPickerMessages = {\n label: string;\n loading: string;\n noModels: string;\n thinking: string;\n fast: string;\n fastRateHint: string;\n codexOnly: string;\n billingHints: Record<PickerBillingClass, string>;\n};\n\nexport const defaultModelPolicyPickerMessages: ModelPolicyPickerMessages = {\n label: \"Model and effort\",\n loading: \"Loading model catalog…\",\n noModels: \"No models available.\",\n thinking: \"Thinking\",\n fast: \"Fast\",\n fastRateHint: \"2× rate\",\n codexOnly: \"Codex-only session\",\n billingHints: {\n opengeni_credits: \"Will use credits\",\n codex_subscription: \"ChatGPT / Codex plan\",\n supergrok_subscription: \"SuperGrok / xAI plan\",\n byok: \"Billed to your AI Gateway\",\n },\n};\n\nexport type ModelPolicyPickerProps = {\n /** Lightweight deployment models. Catalog rows take precedence when supplied. */\n models?: ClientModel[] | undefined;\n /** Catalog-backed rows with availability and billing-class truth. */\n rows?: PickerModelRow[] | undefined;\n model: string;\n effort: ReasoningEffort;\n latencyMode: LatencyMode;\n disabled?: boolean | undefined;\n loading?: boolean | undefined;\n error?: string | null | undefined;\n /** Controlled menu state. Omit for the built-in uncontrolled behavior. */\n open?: boolean | undefined;\n /** Initial menu state when `open` is uncontrolled. */\n defaultOpen?: boolean | undefined;\n onOpenChange?: ((open: boolean) => void) | undefined;\n /** Non-Codex models stay visible but cannot be selected. */\n codexOnly?: boolean | undefined;\n /** Resets drill-down navigation when the session scope changes. */\n sessionKey?: string | undefined;\n /** Prefer bottom on new-chat surfaces and top for bottom-docked composers. */\n menuSide?: \"top\" | \"bottom\" | undefined;\n /** Classes for the portalled menu surface. Prefer --og-* tokens for theming. */\n contentClassName?: string | undefined;\n /** Inline styles for the portalled menu, applied after inherited --og-* tokens. */\n contentStyle?: CSSProperties | undefined;\n className?: string | undefined;\n messages?: Partial<ModelPolicyPickerMessages> | undefined;\n onModelChange: (modelId: string) => void;\n onEffortChange: (effort: ReasoningEffort) => void;\n onLatencyModeChange: (latencyMode: LatencyMode) => void;\n};\n\nconst SLIDE_EASE = [0.22, 1, 0.36, 1] as const;\n\nfunction OpenGeniMark(props: SVGProps<SVGSVGElement>) {\n return (\n <svg\n viewBox=\"0 0 140 133\"\n fill=\"none\"\n stroke=\"currentColor\"\n strokeWidth={1.2}\n strokeLinejoin=\"round\"\n aria-hidden=\"true\"\n {...props}\n >\n <g transform=\"translate(-17.5,-20.999893188476562) scale(1.75)\">\n <g transform=\"translate(0,-952.36218)\">\n <path d=\"m 60.7828,964.36215 27.1809,0.8834 -27.1809,25.9958 z m -1.9745,1.4513 0,26.7845 -25.2681,0 c 8.6166,-8.7334 16.8796,-17.8103 25.2681,-26.7845 z m 27.7053,3.628 3.4864,1.1989 -12.5877,7.4768 z m -68.1835,2.9656 5.5226,0 12.8654,14.0705 -5.9854,6.1204 -12.4026,0 c 9e-4,-6.7347 0,-13.4597 0,-20.1909 z m -1.9746,1.2304 0,5.8364 -6.3555,0 z m 3.363,20.9796 38.627,0 -10.7675,29.43465 z m 39.0898,4.54286 0,41.20229 -12.5878,-6.8775 c 4.1972,-11.443 8.3886,-22.879 12.5878,-34.32479 z\" />\n </g>\n </g>\n </svg>\n );\n}\n\nfunction ChatGptMark(props: SVGProps<SVGSVGElement>) {\n return (\n <svg viewBox=\"0 0 24 24\" fill=\"currentColor\" aria-hidden=\"true\" {...props}>\n <path d=\"M22.2819 9.8211a5.9847 5.9847 0 0 0-.5157-4.9108 6.0462 6.0462 0 0 0-6.5098-2.9A6.0651 6.0651 0 0 0 4.9807 4.1818a5.9847 5.9847 0 0 0-3.9977 2.9 6.0462 6.0462 0 0 0 .7427 7.0966 5.98 5.98 0 0 0 .511 4.9107 6.051 6.051 0 0 0 6.5146 2.9001A5.9847 5.9847 0 0 0 13.2599 24a6.0557 6.0557 0 0 0 5.7718-4.2058 5.9894 5.9894 0 0 0 3.9977-2.9001 6.0557 6.0557 0 0 0-.7475-7.0729zm-9.022 12.6081a4.4755 4.4755 0 0 1-2.8764-1.0408l.1419-.0804 4.7783-2.7582a.7948.7948 0 0 0 .3927-.6813v-6.7369l2.02 1.1686a.071.071 0 0 1 .038.052v5.5826a4.504 4.504 0 0 1-4.4945 4.4944zm-9.6607-4.1254a4.4708 4.4708 0 0 1-.5346-3.0137l.142.0852 4.783 2.7582a.7712.7712 0 0 0 .7806 0l5.8428-3.3685v2.3324a.0804.0804 0 0 1-.0332.0615L9.74 19.9502a4.4992 4.4992 0 0 1-6.1408-1.6464zM2.3408 7.8956a4.485 4.485 0 0 1 2.3653-1.9728V11.6a.7664.7664 0 0 0 .3879.6765l5.8414 3.3698-2.02 1.1685a.0757.0757 0 0 1-.071 0l-4.8303-2.7865A4.504 4.504 0 0 1 2.3408 7.872zm16.5963 3.8558L13.1038 8.364 15.1192 7.2a.0757.0757 0 0 1 .071 0l4.8303 2.7913a4.4944 4.4944 0 0 1-.6765 8.1042v-5.6772a.79.79 0 0 0-.407-.667zm2.0107-3.0231l-.142-.0852-4.783-2.7622a.7759.7759 0 0 0-.7854 0L9.409 9.2297V6.8974a.0662.0662 0 0 1 .0284-.0615l4.8303-2.7866a4.4992 4.4992 0 0 1 6.6802 4.66zM8.3065 12.863l-2.02-1.1638a.0804.0804 0 0 1-.038-.0567V6.0742a4.4992 4.4992 0 0 1 7.3757-3.4537l-.142.0805L8.704 5.459a.7948.7948 0 0 0-.3927.6813zm1.0976-2.3654l2.602-1.4998 2.6069 1.4998v2.9994l-2.5974 1.4997-2.6067-1.4997Z\" />\n </svg>\n );\n}\n\nfunction XaiMark(props: SVGProps<SVGSVGElement>) {\n return (\n <svg viewBox=\"0 0 466.04 516.93\" fill=\"currentColor\" aria-hidden=\"true\" {...props}>\n <polygon points=\"0.12 182.71 234.14 516.92 338.15 516.92 104.13 182.71 0.12 182.71\" />\n <polygon points=\"0 516.92 104.08 516.92 156.08 442.67 104.04 368.34 0 516.92\" />\n <polygon points=\"466.04 0 361.96 0 182.1 256.86 234.15 331.18 466.04 0\" />\n <polygon points=\"380.78 516.92 466.04 516.92 466.04 37.16 380.78 158.92 380.78 516.92\" />\n </svg>\n );\n}\n\nexport function BillingClassMark(props: {\n billingClass: PickerBillingClass;\n className?: string | undefined;\n \"aria-label\"?: string | undefined;\n}) {\n const labels: Record<PickerBillingClass, string> = {\n opengeni_credits: \"OpenGeni\",\n codex_subscription: \"Codex\",\n supergrok_subscription: \"SuperGrok\",\n byok: \"Bring your own key\",\n };\n const label = props[\"aria-label\"] ?? labels[props.billingClass];\n const accessibility =\n label.length === 0\n ? { \"aria-hidden\": true as const }\n : { role: \"img\" as const, \"aria-label\": label };\n const shell = cn(\n \"inline-flex size-3.5 shrink-0 items-center justify-center overflow-hidden text-og-fg-subtle\",\n props.className,\n );\n const mark = \"size-3.5\";\n return (\n <span\n className={shell}\n data-testid={`billing-class-icon-${props.billingClass}`}\n {...accessibility}\n >\n {props.billingClass === \"opengeni_credits\" ? (\n <OpenGeniMark className={mark} />\n ) : props.billingClass === \"codex_subscription\" ? (\n <ChatGptMark className={mark} />\n ) : props.billingClass === \"supergrok_subscription\" ? (\n <XaiMark className={mark} />\n ) : (\n <KeyRoundIcon className={mark} aria-hidden />\n )}\n </span>\n );\n}\n\nfunction isCodexModel(model: ClientModel): boolean {\n return model.id.startsWith(\"codex/\") || model.source === \"codex\";\n}\n\nfunction applyCodexOnly(\n rows: ClientPickerModelRow[],\n codexOnly: boolean,\n unavailableReason: string,\n): ClientPickerModelRow[] {\n if (!codexOnly) return rows;\n return rows.map((row) =>\n isCodexModel(row.catalog)\n ? row\n : {\n ...row,\n selectable: false,\n unavailableReason: row.unavailableReason ?? unavailableReason,\n },\n );\n}\n\nfunction effectiveRows(props: ModelPolicyPickerProps): ClientPickerModelRow[] {\n const rows = props.rows !== undefined ? props.rows : projectClientModelRows(props.models ?? []);\n const messages = { ...defaultModelPolicyPickerMessages, ...props.messages };\n return applyCodexOnly(rows, props.codexOnly === true, messages.codexOnly);\n}\n\nfunction defaultNavState(rows: ClientPickerModelRow[], modelId: string): PickerNavState {\n const selected = findPickerRow(rows, modelId);\n if (!selected) {\n const rails = new Set(rows.map((row) => row.billingClass));\n const onlyRail = rails.size === 1 ? rows[0]?.billingClass : null;\n return onlyRail\n ? { level: \"models\", rail: onlyRail, modelId: null }\n : { level: \"providers\", rail: null, modelId: null };\n }\n return { level: \"thinking\", rail: selected.billingClass, modelId: selected.id };\n}\n\nfunction usePickerNavState(\n sessionKey: string | undefined,\n rows: ClientPickerModelRow[],\n model: string,\n): [PickerNavState, (next: NavUpdater) => void] {\n const [state, setState] = useState<PickerNavState>(() => defaultNavState(rows, model));\n useEffect(() => {\n setState(defaultNavState(rows, model));\n // The menu deliberately preserves its drill-down while the same session is mounted.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [sessionKey]);\n return [state, setState];\n}\n\nexport function PickerNavRow(props: {\n label: string;\n hint?: string | undefined;\n icon?: ReactNode;\n trailing?: ReactNode;\n showChevron?: boolean | undefined;\n disabled?: boolean | undefined;\n title?: string | undefined;\n active?: boolean | undefined;\n testId?: string | undefined;\n onClick: () => void;\n}) {\n return (\n <button\n type=\"button\"\n disabled={props.disabled}\n title={props.title}\n onClick={props.onClick}\n data-testid={props.testId}\n className={cn(\n \"flex w-full cursor-pointer items-center gap-2 rounded-og-sm px-[var(--og-model-picker-row-padding-x)] py-[var(--og-model-picker-row-padding-y)] text-left text-og-fg outline-hidden transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40\",\n props.disabled && \"cursor-not-allowed opacity-50\",\n )}\n >\n {props.icon}\n <span className=\"min-w-0 flex-1\">\n <span className={cn(\"block truncate text-og-menu\", props.active && \"font-medium\")}>\n {props.label}\n </span>\n {props.hint ? (\n <span className=\"mt-0.5 block truncate text-og-control text-og-fg-subtle\">\n {props.hint}\n </span>\n ) : null}\n </span>\n {props.trailing ? <span className=\"ml-auto shrink-0\">{props.trailing}</span> : null}\n {props.showChevron === false ? null : (\n <ChevronRightIcon className=\"size-3.5 shrink-0 text-og-fg-subtle\" />\n )}\n </button>\n );\n}\n\nexport function PickerBackHeader(props: {\n label: string;\n icon?: ReactNode;\n onBack: () => void;\n trailing?: ReactNode;\n}) {\n return (\n <div className=\"mb-1 flex items-center gap-0.5 border-b border-og-border/70 px-0.5 pb-1.5\">\n <button\n type=\"button\"\n onClick={props.onBack}\n data-testid=\"model-picker-back\"\n className=\"flex min-w-0 flex-1 cursor-pointer items-center gap-1.5 rounded-og-sm px-1.5 py-1.5 text-left text-og-fg outline-hidden transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40\"\n >\n <ChevronLeftIcon className=\"size-3.5 shrink-0 text-og-fg-subtle\" />\n {props.icon}\n <span className=\"min-w-0 flex-1 truncate text-og-menu font-medium\">{props.label}</span>\n </button>\n {props.trailing ? <div className=\"relative z-10 shrink-0\">{props.trailing}</div> : null}\n </div>\n );\n}\n\nexport function PickerAnimatedPage(props: {\n pageKey: string;\n direction: 1 | -1;\n children: ReactNode;\n}) {\n const reduceMotion = useReducedMotion();\n return (\n <motion.div\n key={props.pageKey}\n initial={reduceMotion ? false : { x: props.direction * 14, opacity: 0 }}\n animate={{ x: 0, opacity: 1 }}\n transition={{ duration: reduceMotion ? 0 : 0.18, ease: SLIDE_EASE }}\n className=\"w-full\"\n >\n {props.children}\n </motion.div>\n );\n}\n\nexport function ModelPolicyPickerMenu(\n props: ModelPolicyPickerProps & {\n nav?: PickerNavState | undefined;\n onNavChange?: ((next: NavUpdater) => void) | undefined;\n },\n) {\n const messages = useMemo(\n () => ({ ...defaultModelPolicyPickerMessages, ...props.messages }),\n [props.messages],\n );\n const rows = effectiveRows(props);\n const groups = useMemo(() => groupPickerRowsByBillingClass(rows), [rows]);\n const [localNav, setLocalNav] = usePickerNavState(\n props.nav === undefined ? props.sessionKey : undefined,\n rows,\n props.model,\n );\n const nav = props.nav ?? localNav;\n const setNav = props.onNavChange ?? setLocalNav;\n const [direction, setDirection] = useState<1 | -1>(1);\n\n const effectiveNav =\n nav.level === \"providers\" && groups.length === 1\n ? { level: \"models\" as const, rail: groups[0]!.billingClass, modelId: null }\n : nav;\n const activeGroup =\n effectiveNav.rail === null\n ? undefined\n : groups.find((group) => group.billingClass === effectiveNav.rail);\n const focusModel =\n effectiveNav.modelId === null ? undefined : findPickerRow(rows, effectiveNav.modelId);\n const selectedRow = findPickerRow(rows, props.model);\n const { latencyMode, loading, onLatencyModeChange } = props;\n\n useEffect(() => {\n if (loading || !selectedRow?.selectable) return;\n const supportsCurrentMode = runnableLatencyModesForModel(selectedRow.catalog).includes(\n latencyMode,\n );\n if (!supportsCurrentMode && latencyMode !== \"standard\") {\n onLatencyModeChange(\"standard\");\n }\n }, [latencyMode, loading, onLatencyModeChange, selectedRow]);\n\n const go = (next: PickerNavState, nextDirection: 1 | -1) => {\n setDirection(nextDirection);\n setNav(next);\n };\n\n const selectEffort = (row: ClientPickerModelRow, effort: ReasoningEffort) => {\n if (!row.selectable) return;\n if (row.id !== props.model) props.onModelChange(row.id);\n props.onEffortChange(coerceReasoningEffortForModel(row.catalog, effort));\n };\n\n const pageKey =\n effectiveNav.level === \"providers\"\n ? \"providers\"\n : effectiveNav.level === \"models\"\n ? `models:${effectiveNav.rail ?? \"\"}`\n : `thinking:${effectiveNav.modelId ?? \"\"}`;\n\n let body: ReactNode;\n if (props.loading) {\n body = <p className=\"px-2 py-3 text-og-control text-og-fg-subtle\">{messages.loading}</p>;\n } else if (groups.length === 0) {\n body = <p className=\"px-2 py-3 text-og-control text-og-fg-subtle\">{messages.noModels}</p>;\n } else if (effectiveNav.level === \"providers\") {\n body = (\n <div className=\"flex flex-col gap-0.5\" data-testid=\"model-picker-providers\">\n {groups.map((group) => (\n <PickerNavRow\n key={group.billingClass}\n label={group.label}\n hint={messages.billingHints[group.billingClass]}\n icon={<BillingClassMark billingClass={group.billingClass} />}\n active={selectedRow?.billingClass === group.billingClass}\n testId={`model-picker-rail-${group.billingClass}`}\n onClick={() => go({ level: \"models\", rail: group.billingClass, modelId: null }, 1)}\n />\n ))}\n </div>\n );\n } else if (effectiveNav.level === \"models\" && activeGroup) {\n body = (\n <div data-testid=\"model-picker-models\">\n {groups.length > 1 ? (\n <PickerBackHeader\n label={activeGroup.label}\n icon={<BillingClassMark billingClass={activeGroup.billingClass} />}\n onBack={() => go({ level: \"providers\", rail: null, modelId: null }, -1)}\n />\n ) : null}\n <div className=\"flex flex-col gap-0.5\">\n {activeGroup.rows.map((row) => (\n <PickerNavRow\n key={`${row.billingClass}:${row.id}`}\n label={row.label}\n hint={row.unavailableReason ?? undefined}\n disabled={!row.selectable}\n title={row.unavailableReason ?? undefined}\n active={row.id === props.model}\n testId={`model-picker-choice-${row.id}`}\n onClick={() => {\n if (row.selectable) {\n go({ level: \"thinking\", rail: row.billingClass, modelId: row.id }, 1);\n }\n }}\n />\n ))}\n </div>\n </div>\n );\n } else if (effectiveNav.level === \"thinking\" && focusModel) {\n const activeModel = focusModel.id === props.model;\n const supportsFast = runnableLatencyModesForModel(focusModel.catalog).includes(\"fast\");\n body = (\n <div data-testid=\"model-picker-reasoning\">\n <PickerBackHeader\n label={focusModel.label}\n icon={<BillingClassMark billingClass={focusModel.billingClass} />}\n onBack={() => go({ level: \"models\", rail: focusModel.billingClass, modelId: null }, -1)}\n trailing={\n supportsFast ? (\n <button\n type=\"button\"\n disabled={!focusModel.selectable}\n aria-pressed={activeModel && props.latencyMode === \"fast\"}\n aria-label={\n activeModel && props.latencyMode === \"fast\"\n ? `Disable ${messages.fast}`\n : `Enable ${messages.fast}`\n }\n title={`${messages.fast} · ${messages.fastRateHint}`}\n data-testid=\"model-picker-fast\"\n onClick={() => {\n if (!activeModel) {\n props.onModelChange(focusModel.id);\n props.onEffortChange(\n coerceReasoningEffortForModel(focusModel.catalog, props.effort),\n );\n }\n props.onLatencyModeChange(\n activeModel && props.latencyMode === \"fast\" ? \"standard\" : \"fast\",\n );\n }}\n className={cn(\n \"flex size-8 shrink-0 cursor-pointer items-center justify-center rounded-og-sm text-og-fg-subtle outline-hidden transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50\",\n activeModel && props.latencyMode === \"fast\" && \"text-og-fg\",\n )}\n >\n <ZapIcon\n className={cn(\n \"size-3.5\",\n activeModel && props.latencyMode === \"fast\" && \"fill-current\",\n )}\n />\n </button>\n ) : null\n }\n />\n <p className=\"px-2.5 pt-1 pb-1 text-og-control font-medium tracking-wide text-og-fg-subtle uppercase\">\n {messages.thinking}\n </p>\n {focusModel.catalog.capabilities?.inputModalities.includes(\"image\") === false ? (\n <p className=\"px-2.5 pb-1.5 text-og-control leading-relaxed text-og-fg-subtle\">\n Unsupported attachments stay in the session but are hidden from this model.\n </p>\n ) : null}\n <div className=\"flex flex-col gap-0.5\">\n {effortOptionsForModel(focusModel.catalog).map((effort) => {\n const selected = activeModel && effort === props.effort;\n return (\n <button\n key={effort}\n type=\"button\"\n disabled={!focusModel.selectable}\n onClick={() => selectEffort(focusModel, effort)}\n className={cn(\n \"flex h-[var(--og-model-picker-effort-height)] w-full cursor-pointer items-center rounded-og-sm px-[var(--og-model-picker-row-padding-x)] text-left text-og-menu text-og-fg outline-hidden transition-colors hover:bg-og-surface-2 focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50\",\n selected && \"bg-og-surface-2\",\n )}\n >\n <span className=\"min-w-0 flex-1 truncate\">{labelReasoningEffort(effort)}</span>\n {selected ? (\n <CheckIcon\n className=\"ml-auto size-3.5 shrink-0\"\n data-testid=\"model-picker-effort-check\"\n />\n ) : null}\n </button>\n );\n })}\n </div>\n </div>\n );\n } else {\n body = (\n <div className=\"flex flex-col gap-0.5\" data-testid=\"model-picker-providers\">\n {groups.map((group) => (\n <PickerNavRow\n key={group.billingClass}\n label={group.label}\n hint={messages.billingHints[group.billingClass]}\n icon={<BillingClassMark billingClass={group.billingClass} />}\n testId={`model-picker-rail-${group.billingClass}`}\n onClick={() => go({ level: \"models\", rail: group.billingClass, modelId: null }, 1)}\n />\n ))}\n </div>\n );\n }\n\n return (\n <div data-testid=\"model-picker-menu\" className=\"relative overflow-hidden\">\n {props.error ? (\n <p className=\"px-2 py-1 text-og-control text-og-status-failed\" role=\"alert\">\n {props.error}\n </p>\n ) : null}\n <PickerAnimatedPage pageKey={pageKey} direction={direction}>\n {body}\n </PickerAnimatedPage>\n </div>\n );\n}\n\nexport function ModelPolicyPicker(props: ModelPolicyPickerProps) {\n const [uncontrolledOpen, setUncontrolledOpen] = useState(props.defaultOpen ?? false);\n const open = props.open ?? uncontrolledOpen;\n const setOpen = (next: boolean) => {\n if (props.open === undefined) setUncontrolledOpen(next);\n props.onOpenChange?.(next);\n };\n const trigger = usePortalTokenSource<HTMLButtonElement>();\n const portalStyle = usePortalTokenStyle(trigger.source);\n const messages = useMemo(\n () => ({ ...defaultModelPolicyPickerMessages, ...props.messages }),\n [props.messages],\n );\n const rows = effectiveRows(props);\n const [nav, setNav] = usePickerNavState(props.sessionKey, rows, props.model);\n const selected = findPickerRow(rows, props.model);\n\n if (props.loading) {\n return (\n <span\n className={cn(\n \"og-root inline-flex h-8 w-40 shrink-0 animate-pulse rounded-full bg-og-surface-2\",\n props.className,\n )}\n aria-label={messages.loading}\n data-testid=\"model-picker-loading\"\n />\n );\n }\n return (\n <DropdownMenu.Root open={open} onOpenChange={setOpen}>\n <DropdownMenu.Trigger asChild>\n <button\n ref={trigger.ref}\n type=\"button\"\n disabled={props.disabled}\n aria-label={messages.label}\n className={cn(\n \"og-root og-model-policy-trigger inline-flex h-[var(--og-model-picker-trigger-height)] min-w-0 max-w-64 items-center gap-1 rounded-full border border-transparent px-2.5 text-og-control text-og-fg-muted outline-hidden transition-colors hover:border-og-border hover:bg-og-surface-2 hover:text-og-fg focus-visible:ring-2 focus-visible:ring-og-accent/40 disabled:cursor-not-allowed disabled:opacity-50 max-sm:h-11 max-sm:max-w-[7.5rem] max-sm:px-2\",\n props.className,\n )}\n >\n <BillingClassMark\n billingClass={\n selected?.billingClass ??\n (props.model.startsWith(\"codex/\") ? \"codex_subscription\" : \"opengeni_credits\")\n }\n className=\"text-og-fg\"\n />\n <span className=\"og-model-policy-label-full min-w-0 truncate font-medium text-og-fg max-sm:hidden\">\n {selected?.label ?? props.model}\n </span>\n <span className=\"og-model-policy-label-short min-w-0 truncate font-medium text-og-fg sm:hidden\">\n {selected?.shortLabel ?? selected?.label ?? props.model}\n </span>\n <span className=\"og-model-policy-effort max-sm:hidden\">\n {labelReasoningEffort(props.effort)}\n </span>\n {props.latencyMode === \"fast\" ? (\n <ZapIcon\n className=\"size-3.5 shrink-0 fill-current stroke-current text-og-fg\"\n aria-label={messages.fast}\n data-testid=\"model-picker-fast-icon\"\n />\n ) : null}\n <ChevronDownIcon className=\"size-3 shrink-0\" />\n </button>\n </DropdownMenu.Trigger>\n <DropdownMenu.Portal>\n <DropdownMenu.Content\n align=\"start\"\n side={props.menuSide ?? \"bottom\"}\n sideOffset={8}\n collisionPadding={12}\n onCloseAutoFocus={(event) => event.preventDefault()}\n className={cn(\n \"og-root og-model-policy-menu z-50 flex max-h-[min(20rem,var(--radix-dropdown-menu-content-available-height))] w-[var(--og-model-picker-menu-width)] max-w-[calc(100vw-16px)] flex-col overflow-hidden rounded-og-lg border border-og-border bg-og-surface-1 p-[var(--og-model-picker-menu-padding)] text-og-fg shadow-og-lg\",\n props.contentClassName,\n )}\n style={{ ...portalStyle, ...props.contentStyle }}\n data-testid=\"model-picker-content\"\n >\n <div className=\"min-h-0 flex-1 overflow-y-auto overscroll-contain\">\n <ModelPolicyPickerMenu {...props} nav={nav} onNavChange={setNav} />\n </div>\n </DropdownMenu.Content>\n </DropdownMenu.Portal>\n </DropdownMenu.Root>\n );\n}\n"],"mappings":"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","names":[]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/lib/relay-wire.ts","../src/hooks/use-relay-frame-stream.ts","../src/hooks/use-desktop-stream.ts","../src/components/desktop-viewer.tsx","../src/lib/host-platform.ts"],"sourcesContent":["// Minimal HAND-MIRROR of the relay stream wire messages the desktop frame client\n// needs (`StreamOpen`/`StreamClose` encode; `StreamOpenAck` + `StreamFrame` decode). It exists\n// because the publish-closure guard (scripts/publish-closure-guard.ts) forbids\n// `@opengeni/react` from depending on `@opengeni/agent-proto` — the published SDK\n// closure may only reach `@opengeni/sdk` among `@opengeni/*` packages. So, exactly\n// as the guard prescribes (\"hand-mirror it\"), we re-encode just these three\n// messages against the third-party `@bufbuild/protobuf/wire` runtime (the same\n// runtime `@opengeni/agent-proto` generates against), byte-compatible with the\n// generated code. Field numbers copied verbatim from\n// `packages/agent-proto/src/gen/opengeni_agent.ts`:\n// StreamChannel: channelId=1, workspaceId=2, agentId=3, kind=4(int32), port=5(uint32)\n// StreamOpen: channel=1(msg), token=2, role=3(int32), resumeFromSeq=4(uint64, omitted when \"0\")\n// StreamOpenAck: accepted=1(bool), error=2(AgentError msg), resumeFromSeq=3\n// AgentError: code=1, message=2, retryable=3, detail=4 (we read only message)\n// StreamFrame: channelId=1, seq=2, data=3(bytes), producedAtMs=4 (we read only data)\n// StreamClose: channelId=1, reason=2(int32), message=3\nimport { BinaryReader, BinaryWriter } from \"@bufbuild/protobuf/wire\";\n\n/** `StreamKind.STREAM_KIND_DESKTOP` (agent-proto enum value). */\nexport const STREAM_KIND_DESKTOP = 2;\n/** `StreamKind.STREAM_KIND_PTY` (agent-proto enum value). */\nexport const STREAM_KIND_PTY = 1;\n/** `StreamKind.STREAM_KIND_BROWSER` (agent-proto enum value). */\nexport const STREAM_KIND_BROWSER = 3;\n/** `StreamKind.STREAM_KIND_COMPUTER` (agent-proto enum value). */\nexport const STREAM_KIND_COMPUTER = 4;\n/** `StreamRole.STREAM_ROLE_CLIENT` (agent-proto enum value). */\nexport const STREAM_ROLE_CLIENT = 2;\n/** `StreamCloseReason.STREAM_CLOSE_REASON_NORMAL`. */\nexport const STREAM_CLOSE_REASON_NORMAL = 1;\n\nexport interface RelayChannel {\n channelId: string;\n workspaceId: string;\n agentId: string;\n kind: number;\n port: number;\n}\n\nexport interface RelayStreamOpen {\n channel: RelayChannel;\n token: string;\n role: number;\n resumeFromSeq: string;\n}\n\nfunction encodeChannel(message: RelayChannel, writer: BinaryWriter): BinaryWriter {\n if (message.channelId !== \"\") writer.uint32(10).string(message.channelId);\n if (message.workspaceId !== \"\") writer.uint32(18).string(message.workspaceId);\n if (message.agentId !== \"\") writer.uint32(26).string(message.agentId);\n if (message.kind !== 0) writer.uint32(32).int32(message.kind);\n if (message.port !== 0) writer.uint32(40).uint32(message.port);\n return writer;\n}\n\n/** Encode a `StreamOpen`, byte-compatible with `StreamOpen.encode(...).finish()`. */\nexport function encodeStreamOpen(message: RelayStreamOpen): Uint8Array {\n const writer = new BinaryWriter();\n encodeChannel(message.channel, writer.uint32(10).fork()).join();\n if (message.token !== \"\") writer.uint32(18).string(message.token);\n if (message.role !== 0) writer.uint32(24).int32(message.role);\n if (message.resumeFromSeq !== \"0\") writer.uint32(32).uint64(message.resumeFromSeq);\n return writer.finish();\n}\n\n/** Encode an intentional viewer detach. Transport loss omits this message so\n * reconnect/resume remains available. */\nexport function encodeStreamClose(message: {\n channelId: string;\n reason: number;\n message: string;\n}): Uint8Array {\n const writer = new BinaryWriter();\n if (message.channelId !== \"\") writer.uint32(10).string(message.channelId);\n if (message.reason !== 0) writer.uint32(16).int32(message.reason);\n if (message.message !== \"\") writer.uint32(26).string(message.message);\n return writer.finish();\n}\n\n/** Encode a viewer→agent PTY input `StreamFrame`. */\nexport function encodeStreamFrame(message: {\n channelId: string;\n seq: string;\n data: Uint8Array;\n producedAtMs: string;\n}): Uint8Array {\n const writer = new BinaryWriter();\n if (message.channelId !== \"\") writer.uint32(10).string(message.channelId);\n if (message.seq !== \"0\") writer.uint32(16).uint64(message.seq);\n if (message.data.length > 0) writer.uint32(26).bytes(message.data);\n if (message.producedAtMs !== \"0\") writer.uint32(32).uint64(message.producedAtMs);\n return writer.finish();\n}\n\n/** The only `AgentError` field we surface from a rejected ack. */\nfunction decodeAgentErrorMessage(reader: BinaryReader, length: number): { message?: string } {\n const end = reader.pos + length;\n let message: string | undefined;\n while (reader.pos < end) {\n const tag = reader.uint32();\n if (tag >>> 3 === 2 && tag === 18) {\n message = reader.string();\n continue;\n }\n if ((tag & 7) === 4 || tag === 0) break;\n reader.skip(tag & 7);\n }\n return message !== undefined ? { message } : {};\n}\n\n/** Decode a `StreamOpenAck` (we need `accepted` + the error message). */\nexport function decodeStreamOpenAck(bytes: Uint8Array): {\n accepted: boolean;\n error?: { message?: string };\n} {\n const reader = new BinaryReader(bytes);\n const end = reader.len;\n let accepted = false;\n let error: { message?: string } | undefined;\n while (reader.pos < end) {\n const tag = reader.uint32();\n if (tag >>> 3 === 1 && tag === 8) {\n accepted = reader.bool();\n continue;\n }\n if (tag >>> 3 === 2 && tag === 18) {\n error = decodeAgentErrorMessage(reader, reader.uint32());\n continue;\n }\n if ((tag & 7) === 4 || tag === 0) break;\n reader.skip(tag & 7);\n }\n return error !== undefined ? { accepted, error } : { accepted };\n}\n\n/** Decode the sequence plus `data` from a `StreamFrame`. */\nexport function decodeStreamFrame(bytes: Uint8Array): { seq: string; data: Uint8Array } {\n const reader = new BinaryReader(bytes);\n const end = reader.len;\n let seq = \"0\";\n let data = new Uint8Array(0);\n while (reader.pos < end) {\n const tag = reader.uint32();\n if (tag >>> 3 === 2 && tag === 16) {\n seq = reader.uint64().toString();\n continue;\n }\n if (tag >>> 3 === 3 && tag === 26) {\n // Copy into a fresh ArrayBuffer-backed view: BinaryReader.bytes() returns\n // `Uint8Array<ArrayBufferLike>` (a view into the message buffer), which both\n // narrows the type to `Uint8Array<ArrayBuffer>` and isolates just this frame.\n data = new Uint8Array(reader.bytes());\n continue;\n }\n if ((tag & 7) === 4 || tag === 0) break;\n reader.skip(tag & 7);\n }\n return { seq, data };\n}\n","import {\n decodeStreamFrame,\n decodeStreamOpenAck,\n encodeStreamOpen,\n STREAM_KIND_DESKTOP,\n STREAM_ROLE_CLIENT,\n} from \"../lib/relay-wire\";\nimport type { DesktopConnectionState, DesktopStreamCapability } from \"@opengeni/sdk\";\nimport { type RefObject, useEffect, useRef, useState } from \"react\";\n\n/**\n * The minimal WebSocket surface the frame renderer drives. Lets tests (and a\n * future transport swap) inject a fake without a live socket — the exact\n * analogue of `DesktopRfbFactory` for the noVNC path. Structurally a subset of\n * the browser `WebSocket`.\n */\nexport interface DesktopWebSocketLike {\n binaryType: string;\n send(data: ArrayBuffer): void;\n close(): void;\n addEventListener(\n type: \"open\" | \"message\" | \"error\" | \"close\",\n listener: (ev: { data?: unknown } & Record<string, unknown>) => void,\n ): void;\n removeEventListener(\n type: \"open\" | \"message\" | \"error\" | \"close\",\n listener: (ev: { data?: unknown } & Record<string, unknown>) => void,\n ): void;\n}\n\nexport type DesktopWebSocketFactory = (url: string) => DesktopWebSocketLike;\n\nexport type UseRelayFrameStreamOptions = {\n /** The desktop cell of the negotiated capabilities (`capabilities.DesktopStream`). */\n capability: DesktopStreamCapability | null;\n /** The mount target. A `<canvas>` is appended here on connect. */\n containerRef: RefObject<HTMLDivElement | null>;\n /** Custom socket factory (tests / a transport swap). Defaults to `new WebSocket(url)`. */\n webSocketFactory?: DesktopWebSocketFactory | undefined;\n};\n\nexport type UseRelayFrameStreamResult = {\n state: DesktopConnectionState;\n error: Error | null;\n /** Tear down + reopen the socket (e.g. after a drop, once a fresh url arrives). */\n reconnect: () => void;\n};\n\n// Relay datagram tags: byte[0] of every binary message. Mirrors the proven wire\n// protocol in `apps/api/scripts/diagnose-mac-desktop-stream.ts`.\nconst TAG_OPEN = 1;\nconst TAG_OPENACK = 2;\nconst TAG_FRAME = 3;\n\n/** Build a relay datagram: a fresh Uint8Array of `body.length + 1` with the tag\n * at [0] and the protobuf body copied at offset 1 (so `.buffer` is exact). */\nfunction datagram(tag: number, body: Uint8Array): Uint8Array {\n const out = new Uint8Array(body.length + 1);\n out[0] = tag;\n out.set(body, 1);\n return out;\n}\n\nfunction defaultWebSocketFactory(url: string): DesktopWebSocketLike {\n // The browser `WebSocket` is structurally a superset of `DesktopWebSocketLike`\n // (its event maps are stricter); cast so the seam stays loosely typed for fakes.\n return new WebSocket(url) as unknown as DesktopWebSocketLike;\n}\n\n/**\n * VIEW-ONLY PNG-frame renderer for a SELF-HOSTED desktop stream — the relay\n * transport (`transport: \"relay-frames\"`, `client: \"frames\"`). The self-hosted\n * agent produces one PNG per frame as a protobuf datagram over the relay; there\n * is no RFB/noVNC here. This hook opens the relay DESKTOP channel as a CLIENT\n * (mirroring the proven diagnostic wire protocol), decodes each PNG onto a\n * `<canvas>` mounted into `containerRef`, and drives the connection state\n * machine: idle → connecting (ws opening) → connected (first painted frame) →\n * error (ws error/close/ack rejection).\n *\n * It is DORMANT for any other transport (or no url): it stays idle and touches\n * nothing, so the noVNC path owns the surface. SSR-safe: the socket + DOM attach\n * live inside `useEffect`.\n *\n * Backpressure (critical — frames are ~1.8MB at ~10fps): at most one frame is\n * decoded at a time; a frame arriving mid-decode replaces the pending one\n * (latest-wins), so a slow decode can never build an unbounded queue.\n */\nexport function useRelayFrameStream(\n options: UseRelayFrameStreamOptions,\n): UseRelayFrameStreamResult {\n const { capability, containerRef, webSocketFactory } = options;\n const [state, setState] = useState<DesktopConnectionState>(\"idle\");\n const [error, setError] = useState<Error | null>(null);\n const [nonce, setNonce] = useState(0);\n const stateRef = useRef<DesktopConnectionState>(\"idle\");\n const setBoth = (next: DesktopConnectionState) => {\n stateRef.current = next;\n setState(next);\n };\n\n const reconnect = () => setNonce((n) => n + 1);\n\n const transport = capability?.transport ?? null;\n const url = capability?.url ?? null;\n const token = capability?.token ?? null;\n\n // The factory is read via a ref so swapping it never re-opens the socket (it is\n // not a connect-effect dependency), symmetric with the noVNC `rfbFactory`.\n const factoryRef = useRef(webSocketFactory);\n factoryRef.current = webSocketFactory;\n\n useEffect(() => {\n // SSR / no DOM: stay idle and show the placeholder.\n if (typeof window === \"undefined\") return;\n // This hook OWNS the surface ONLY for the frame transport; anything else (or\n // no live url) → dormant idle so the noVNC path can drive.\n if (transport !== \"relay-frames\" || !url) {\n setBoth(\"idle\");\n return;\n }\n const container = containerRef.current;\n if (!container) {\n // Mount target not in the DOM yet (tab hidden) — stay idle so a re-attach\n // nudge can re-run this effect once the container exists.\n setBoth(\"idle\");\n return;\n }\n\n // Parse the 4 relay query params the channel is keyed on.\n let channel: {\n channelId: string;\n workspaceId: string;\n agentId: string;\n kind: number;\n port: number;\n };\n try {\n const u = new URL(url);\n channel = {\n channelId: u.searchParams.get(\"channel\") ?? \"\",\n workspaceId: u.searchParams.get(\"ws\") ?? \"\",\n agentId: u.searchParams.get(\"agent\") ?? \"\",\n kind: STREAM_KIND_DESKTOP,\n port: Number(u.searchParams.get(\"port\") ?? \"0\"),\n };\n } catch (cause) {\n setError(cause instanceof Error ? cause : new Error(String(cause)));\n setBoth(\"error\");\n return;\n }\n\n setError(null);\n setBoth(\"connecting\");\n\n // Canvas mount — mirror the noVNC surface conventions: absolute-fill the\n // panel and CONTAIN the framebuffer (aspect-preserved, centered, letterboxed)\n // — never distorted, never overflowing. `max-*: 100%` + `margin: auto` +\n // intrinsic size gives fit-to-panel without a resize observer.\n const canvas = document.createElement(\"canvas\");\n canvas.setAttribute(\"data-opengeni-desktop-frames\", \"\");\n const style = canvas.style;\n style.position = \"absolute\";\n style.top = \"0\";\n style.left = \"0\";\n style.right = \"0\";\n style.bottom = \"0\";\n style.margin = \"auto\";\n style.maxWidth = \"100%\";\n style.maxHeight = \"100%\";\n style.width = \"auto\";\n style.height = \"auto\";\n style.display = \"block\";\n style.imageRendering = \"auto\";\n container.appendChild(canvas);\n const ctx = canvas.getContext(\"2d\");\n\n let disposed = false;\n let acked = false;\n // Backpressure: decode at most ONE frame at a time; a frame arriving mid-\n // decode replaces `pending` (keep only the LATEST) — never a queue.\n let decoding = false;\n let pending: Uint8Array | null = null;\n\n const drainLatest = async () => {\n if (decoding) return;\n decoding = true;\n try {\n while (pending) {\n if (disposed) break;\n const data = pending;\n pending = null;\n let bmp: ImageBitmap;\n try {\n // Copy the exact frame bytes into a fresh ArrayBuffer-backed view:\n // ts-proto's `bytes()` hands back a subarray VIEW into the larger\n // message buffer, so slicing isolates just this PNG (and satisfies the\n // `BlobPart` typing, which rejects the generic `ArrayBufferLike`).\n bmp = await createImageBitmap(new Blob([new Uint8Array(data)], { type: \"image/png\" }));\n } catch {\n // A corrupt/partial frame is skipped; latest-wins keeps us live.\n continue;\n }\n if (disposed) {\n bmp.close();\n break;\n }\n // Size the backing store to the frame's natural WxH on the first frame\n // (or when the resolution changes); CSS then scales the element to fit.\n if (canvas.width !== bmp.width || canvas.height !== bmp.height) {\n canvas.width = bmp.width;\n canvas.height = bmp.height;\n }\n ctx?.drawImage(bmp, 0, 0, canvas.width, canvas.height);\n bmp.close();\n // First painted frame → connected.\n if (!disposed && stateRef.current !== \"connected\") setBoth(\"connected\");\n }\n } finally {\n decoding = false;\n }\n };\n\n let ws: DesktopWebSocketLike;\n try {\n ws = (factoryRef.current ?? defaultWebSocketFactory)(url);\n } catch (cause) {\n setError(cause instanceof Error ? cause : new Error(String(cause)));\n setBoth(\"error\");\n canvas.remove();\n return;\n }\n ws.binaryType = \"arraybuffer\";\n\n const onOpen = () => {\n if (disposed) return;\n const body = encodeStreamOpen({\n channel,\n token: token ?? \"\",\n role: STREAM_ROLE_CLIENT,\n resumeFromSeq: \"0\",\n });\n try {\n ws.send(datagram(TAG_OPEN, body).buffer as ArrayBuffer);\n } catch {\n // Socket already closing; the close/error handler surfaces it.\n }\n };\n\n const onMessage = (ev: { data?: unknown }) => {\n if (disposed) return;\n const data = ev.data;\n if (!(data instanceof ArrayBuffer)) return;\n const buf = new Uint8Array(data);\n if (buf.length === 0) return;\n const tag = buf[0];\n const rest = buf.subarray(1);\n if (tag === TAG_OPENACK) {\n let ack: ReturnType<typeof decodeStreamOpenAck>;\n try {\n ack = decodeStreamOpenAck(rest);\n } catch {\n return;\n }\n if (!ack.accepted) {\n setError(\n new Error(\n ack.error?.message\n ? `desktop stream rejected: ${ack.error.message}`\n : \"desktop stream rejected by the relay\",\n ),\n );\n setBoth(\"error\");\n try {\n ws.close();\n } catch {\n // already closed\n }\n return;\n }\n // Accepted — stay \"connecting\" until the first frame paints.\n acked = true;\n } else if (tag === TAG_FRAME) {\n let fr: ReturnType<typeof decodeStreamFrame>;\n try {\n fr = decodeStreamFrame(rest);\n } catch {\n return;\n }\n if (fr.data && fr.data.length > 0) {\n pending = fr.data; // latest-wins backpressure\n void drainLatest();\n }\n }\n };\n\n const onError = () => {\n if (disposed) return;\n setError((prev) => prev ?? new Error(\"desktop stream connection error\"));\n setBoth(\"error\");\n };\n\n const onClose = () => {\n if (disposed) return;\n setError(\n (prev) =>\n prev ??\n new Error(acked ? \"desktop stream closed\" : \"desktop stream closed before it opened\"),\n );\n setBoth(\"error\");\n };\n\n ws.addEventListener(\"open\", onOpen);\n ws.addEventListener(\"message\", onMessage);\n ws.addEventListener(\"error\", onError);\n ws.addEventListener(\"close\", onClose);\n\n return () => {\n disposed = true;\n pending = null;\n ws.removeEventListener(\"open\", onOpen);\n ws.removeEventListener(\"message\", onMessage);\n ws.removeEventListener(\"error\", onError);\n ws.removeEventListener(\"close\", onClose);\n try {\n ws.close();\n } catch {\n // ignore teardown errors\n }\n canvas.remove();\n };\n // ONLY a real transport change reopens: a fresh url (rotation), a new token,\n // the transport flipping, or a manual reconnect (`nonce`). The factory is read\n // via a ref and must never re-open the socket.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, token, transport, nonce]);\n\n return { state, error, reconnect };\n}\n","import {\n desktopSocketUrl,\n nextDesktopState,\n type DesktopConnectionState,\n type DesktopRfbFactory,\n type DesktopRfbLike,\n type DesktopStreamCapability,\n} from \"@opengeni/sdk\";\nimport { type RefObject, useCallback, useEffect, useRef, useState } from \"react\";\nimport { type DesktopWebSocketFactory, useRelayFrameStream } from \"./use-relay-frame-stream\";\n\nexport type UseDesktopStreamOptions = {\n /** The desktop cell of the negotiated capabilities (`capabilities.DesktopStream`). */\n capability: DesktopStreamCapability | null;\n /** The mount target. RFB (or the frame canvas) attaches here on connect. */\n containerRef: RefObject<HTMLDivElement | null>;\n /** Read-only by default (v1 ruling H). interactive only when cap.mode allows. */\n interactive?: boolean | undefined;\n scaleViewport?: boolean | undefined;\n /** Custom RFB factory (tests / a WebRTC swap). Defaults to a lazy @novnc/novnc. */\n rfbFactory?: DesktopRfbFactory | undefined;\n /** Additional WebSocket protocols for an authenticated placement proxy. */\n webSocketProtocols?: string[] | undefined;\n /** Custom socket factory for the `relay-frames` transport (tests). Defaults to\n * `new WebSocket(url)`. Mirrors `rfbFactory` for the frame renderer. */\n webSocketFactory?: DesktopWebSocketFactory | undefined;\n};\n\nexport type UseDesktopStreamResult = {\n state: DesktopConnectionState;\n error: Error | null;\n /** Manual reconnect (e.g. after a securityfailure once a fresh URL arrives). */\n reconnect: () => void;\n /** Synchronous input on the already-open RFB authority. */\n sendKey?: ((keysym: number, code: string, down?: boolean) => boolean) | undefined;\n};\n\n/** Lazy-load @novnc/novnc's RFB as the default factory. Imported inside the\n * connect effect so SSR / non-desktop bundles never pull the DOM-only lib.\n * @novnc/novnc ships no types and a single `export default class RFB`. The\n * specifier is STATIC (`import(\"@novnc/novnc\")`) so Vite can pre-bundle and\n * resolve it — a runtime-string indirection with `@vite-ignore` (the previous\n * approach) hands the browser a bare specifier and throws\n * \"Failed to resolve module specifier '@novnc/novnc'\". The dynamic form keeps\n * it out of the SSR / non-desktop critical path while staying resolvable. */\nasync function defaultRfbFactory(): Promise<DesktopRfbFactory> {\n const mod = (await import(\"@novnc/novnc\")) as unknown as {\n default: new (\n t: HTMLElement,\n u: string,\n o: {\n credentials?: { password?: string | undefined } | undefined;\n wsProtocols?: string[] | undefined;\n },\n ) => DesktopRfbLike;\n };\n const RFB = mod.default;\n return (target, url, opts) => new RFB(target, url, opts);\n}\n\n/**\n * Drive the noVNC RFB lifecycle from a `DesktopStreamCapability`, using the\n * SDK's `desktop.ts` reducer + `desktopSocketUrl`. SSR-safe: the RFB import and\n * the DOM attach happen inside `useEffect`, so a server render is a no-op and\n * the component shows its placeholder until hydration.\n *\n * Read-only is enforced at three layers: `capability.mode` (server) →\n * `interactive` prop → `RFB.viewOnly`. v1 always resolves to read-only. On a\n * capability `url` change (a rotation), the old RFB disconnects and a fresh one\n * connects to the new URL — a brief \"desktop blink\", acceptable on rollover.\n *\n * Transport dispatch: a Modal box negotiates `transport: \"vnc-ws\"` and drives the\n * noVNC RFB below. A SELF-HOSTED machine negotiates `transport: \"relay-frames\"`\n * (PNG-per-frame over the relay, view-only) — that path is delegated to\n * `useRelayFrameStream`, which paints a `<canvas>`. Both hooks are ALWAYS called\n * (rules of hooks); each is dormant for the other's transport, and we return the\n * result of whichever owns the surface. The public interface is identical either\n * way, so `DesktopViewer` is transport-agnostic.\n */\nexport function useDesktopStream(options: UseDesktopStreamOptions): UseDesktopStreamResult {\n const {\n capability,\n containerRef,\n interactive,\n scaleViewport,\n rfbFactory,\n webSocketProtocols,\n webSocketFactory,\n } = options;\n\n // The self-hosted PNG-frame path. Always invoked (rules of hooks); dormant\n // unless `transport === \"relay-frames\"`, in which case it owns the surface.\n const frameStream = useRelayFrameStream({\n capability,\n containerRef,\n ...(webSocketFactory ? { webSocketFactory } : {}),\n });\n const [state, setState] = useState<DesktopConnectionState>(\"idle\");\n const [error, setError] = useState<Error | null>(null);\n const [nonce, setNonce] = useState(0);\n const stateRef = useRef<DesktopConnectionState>(\"idle\");\n const setBoth = (next: DesktopConnectionState) => {\n stateRef.current = next;\n setState(next);\n };\n\n const reconnect = () => setNonce((n) => n + 1);\n const sendKey = useCallback((keysym: number, code: string, down?: boolean): boolean => {\n const rfb = rfbRef.current;\n if (!rfb?.sendKey || rfb.viewOnly || stateRef.current !== \"connected\") return false;\n rfb.sendKey(keysym, code, down);\n return true;\n }, []);\n\n const url = capability?.url ?? null;\n // The credential is keyed into the connect effect so a token rotation (new\n // password) reconnects, but NOT folded into the whole-`capability` identity —\n // a benign re-negotiation that re-mints the same cell must not churn the socket.\n const token = capability?.token ?? null;\n const transport = capability?.transport ?? null;\n const mode = capability?.mode ?? \"read-only\";\n // For direct sandbox RFB, authorization is carried in WebSocket\n // subprotocols rather than `capability.token`. The view grant rotates before\n // expiry while the URL stays stable, so the protocol credential is a real\n // connection boundary: keeping the old socket leaves the viewer black once\n // browserd expires it. Key the effect on the primitive value (not the caller's\n // array identity) so credential rotation reconnects exactly once without\n // churning on ordinary renders.\n const webSocketProtocolsKey = webSocketProtocols?.join(\"\\u0000\") ?? \"\";\n\n // The live RFB handle. Holding it in a ref lets us flip view-only (take\n // control / return control) and re-scale on the OPEN connection instead of\n // tearing the socket down — the old behaviour reconnected on every\n // `interactive` change, which read as a constant \"refresh\" and (combined with\n // the canvas churn) bounced control back to watch.\n const rfbRef = useRef<DesktopRfbLike | null>(null);\n // Latest input/scale/factory the connect effect reads via refs so they are NOT\n // connect-effect dependencies: changing them must update the live RFB in place,\n // never reconnect it.\n const interactiveRef = useRef(interactive);\n const scaleViewportRef = useRef(scaleViewport);\n const modeRef = useRef(mode);\n const rfbFactoryRef = useRef(rfbFactory);\n const webSocketProtocolsRef = useRef(webSocketProtocols);\n interactiveRef.current = interactive;\n scaleViewportRef.current = scaleViewport;\n modeRef.current = mode;\n rfbFactoryRef.current = rfbFactory;\n webSocketProtocolsRef.current = webSocketProtocols;\n\n // read-only is forced when the server says so OR the caller didn't opt in.\n const viewOnlyFor = (m: string, want: boolean | undefined) => m === \"read-only\" || !want;\n\n useEffect(() => {\n // SSR / no DOM / no usable transport: stay idle and show the placeholder.\n if (typeof window === \"undefined\") return;\n if (transport !== \"vnc-ws\" || !url) {\n setBoth(\"idle\");\n return;\n }\n const container = containerRef.current;\n if (!container) {\n // The mount target isn't in the DOM yet (e.g. the tab is still hidden).\n // Stay idle (not a stale negotiating/connecting) so a re-attach nudge —\n // fired by the viewer on becoming visible / on mount — can re-run this\n // effect once the container exists. Without this the surface can stick\n // forever on the idle scrim and never open a socket.\n setBoth(\"idle\");\n return;\n }\n\n let rfb: DesktopRfbLike | null = null;\n let disposed = false;\n setError(null);\n setBoth(\"negotiating\");\n\n const onConnect = () => {\n if (!disposed) setBoth(nextDesktopState(stateRef.current, { type: \"connected\" }));\n };\n const onDisconnect = () => {\n if (!disposed) setBoth(nextDesktopState(stateRef.current, { type: \"disconnected\" }));\n };\n const onSecurityFailure = () => {\n if (disposed) return;\n setError(new Error(\"desktop authentication failed (token expired or revoked)\"));\n setBoth(nextDesktopState(stateRef.current, { type: \"fail\" }));\n };\n\n void (async () => {\n try {\n const factory = rfbFactoryRef.current ?? (await defaultRfbFactory());\n if (disposed) return;\n const socketUrl = desktopSocketUrl({ url });\n setBoth(nextDesktopState(stateRef.current, { type: \"negotiated\" }));\n rfb = factory(container, socketUrl, {\n credentials: token ? { password: token } : undefined,\n ...(webSocketProtocolsRef.current ? { wsProtocols: webSocketProtocolsRef.current } : {}),\n });\n rfb.viewOnly = viewOnlyFor(modeRef.current, interactiveRef.current);\n // Fit-to-panel: SCALE the 1280x800 framebuffer down to the container\n // (aspect-preserved) and never 1:1-clip. `clipViewport=false` is pinned\n // explicitly — noVNC forces it off while scaling, but a fresh RFB on a\n // url rotation could otherwise start from a stale clip and read as\n // \"zoomed in\". Order matters: clip off, then scale on.\n rfb.clipViewport = false;\n rfb.scaleViewport = scaleViewportRef.current ?? true;\n rfb.addEventListener(\"connect\", onConnect);\n rfb.addEventListener(\"disconnect\", onDisconnect);\n rfb.addEventListener(\"securityfailure\", onSecurityFailure);\n rfbRef.current = rfb;\n } catch (cause) {\n if (!disposed) {\n setError(cause instanceof Error ? cause : new Error(String(cause)));\n setBoth(\"error\");\n }\n }\n })();\n\n return () => {\n disposed = true;\n if (rfbRef.current === rfb) rfbRef.current = null;\n if (rfb) {\n rfb.removeEventListener?.(\"connect\", onConnect);\n rfb.removeEventListener?.(\"disconnect\", onDisconnect);\n rfb.removeEventListener?.(\"securityfailure\", onSecurityFailure);\n try {\n rfb.disconnect();\n } catch {\n // ignore teardown errors\n }\n }\n };\n // ONLY a real transport change reconnects: a fresh url (rotation), a new\n // credential (RFB password OR bearer subprotocol), the transport flipping,\n // or an explicit manual reconnect\n // (`nonce`). `interactive`/`scaleViewport`/`mode`/`rfbFactory` are read via\n // refs and applied live below — they must never re-open the socket.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [url, token, transport, webSocketProtocolsKey, nonce]);\n\n // Apply take-control / return-control to the OPEN connection in place. noVNC\n // honours `viewOnly` live, so flipping it neither blinks the surface nor drops\n // the framebuffer — the cure for the take-control \"refresh + auto-release\" loop.\n useEffect(() => {\n const rfb = rfbRef.current;\n if (rfb) rfb.viewOnly = viewOnlyFor(mode, interactive);\n // viewOnlyFor is pure; re-run only when the inputs change.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [interactive, mode, state]);\n\n // Re-scale the live RFB in place (no reconnect) when the scale preference flips\n // OR the connection state advances (e.g. the first framebuffer paints after a\n // take-control flip / a re-attach). Re-asserting clip-off + scale-on here is the\n // belt-and-suspenders against a \"zoomed\" surface: it re-fits the canvas to the\n // panel on every meaningful transition, so a transient mis-measure at connect\n // time can never stick.\n useEffect(() => {\n const rfb = rfbRef.current;\n if (rfb) {\n rfb.clipViewport = false;\n rfb.scaleViewport = scaleViewport ?? true;\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [scaleViewport, state]);\n\n // Delegate to whichever transport owns the surface. For `relay-frames` the\n // noVNC effect above is dormant (it bailed to idle) and the frame renderer\n // drives; for `vnc-ws` (and everything else) the noVNC path drives while the\n // frame renderer stays idle. Same public shape either way.\n if (transport === \"relay-frames\") return frameStream;\n return { state, error, reconnect, sendKey };\n}\n","import type {\n CapabilityUnavailableReason,\n DesktopRfbFactory,\n DesktopStreamCapability,\n} from \"@opengeni/sdk\";\nimport { LoaderCircleIcon, MonitorIcon, MousePointerClickIcon, WifiOffIcon } from \"lucide-react\";\nimport {\n type ClipboardEvent,\n type KeyboardEvent as ReactKeyboardEvent,\n type ReactNode,\n useEffect,\n useRef,\n useState,\n} from \"react\";\nimport { cn } from \"../lib/cn\";\nimport { interactionHostPlatform, type InteractionHostPlatform } from \"../lib/host-platform\";\nimport { useDesktopStream } from \"../hooks/use-desktop-stream\";\nimport type { DesktopWebSocketFactory } from \"../hooks/use-relay-frame-stream\";\n\nexport type DesktopViewerProps = {\n /** The desktop cell of the negotiated capabilities (`capabilities.DesktopStream`). */\n capability: DesktopStreamCapability | null;\n /**\n * Initial control mode. Default false (watch). When the user flips\n * \"Take control\" the viewer drives input — but only if `capability.mode`\n * permits it (server-gated; a read-only deployment disables the toggle).\n * Pass a value to control it externally; omit to let the viewer own the state.\n */\n interactive?: boolean | undefined;\n /** Render the built-in Watching ⇄ Take control toggle (default true). */\n showControlToggle?: boolean | undefined;\n scaleViewport?: boolean | undefined;\n /** Custom RFB factory (tests / a WebRTC swap). Defaults to lazy @novnc/novnc. */\n rfbFactory?: DesktopRfbFactory | undefined;\n /** Authenticated controller protocols for proxied RFB transports. */\n webSocketProtocols?: string[] | undefined;\n /** Custom socket factory for the self-hosted `relay-frames` transport (tests).\n * Defaults to `new WebSocket(url)`. Mirrors `rfbFactory`. */\n webSocketFactory?: DesktopWebSocketFactory | undefined;\n /**\n * Optional host-owned paste path. ComputerSession hosts use their native,\n * target-scoped clipboard action instead of relying on an RFB server to sync\n * ClientCutText into the graphical seat. Return true once the host accepted\n * the text. Without a host authority the viewer leaves paste untouched rather\n * than claiming success for an unreliable display-server clipboard heuristic.\n */\n onPasteText?: ((text: string) => boolean) | undefined;\n /** OS behind the RFB seat. Used only to translate the host's primary shortcut\n * (Cmd on macOS, Ctrl elsewhere) to the target's primary modifier. */\n targetPlatform?: \"linux\" | \"macos\" | \"windows\" | null | undefined;\n /**\n * @deprecated Desktop acknowledgment is automatic. Retained as an ignored\n * compatibility prop so existing embedders keep compiling.\n */\n renderConsentGate?: ((onAccept: () => void, shared: boolean) => ReactNode) | undefined;\n /**\n * Called once when the viewer mounts. Hosts use this to engage the desktop\n * holder immediately when the Desktop tab opens.\n */\n onActivate?: (() => void) | undefined;\n /**\n * Called automatically when the server requires acknowledgment of the\n * un-redacted (and possibly shared) pixel plane. The viewer shows its ordinary\n * opening state while this completes; no click-through gate is rendered.\n */\n onAcknowledge?: (() => void | Promise<void>) | undefined;\n /**\n * Whether the host has the viewer attach engaged. Drives the cold-state\n * behaviour: when watching, a cold-but-warmable lease AUTO-WARMS (and re-warms\n * when the box drains) instead of dead-ending. Mounting the viewer counts as\n * watching when this is omitted.\n */\n watching?: boolean | undefined;\n /**\n * Request a (re)warm of the sandbox WITHOUT re-acknowledging (the consent has\n * already been recorded — only the box drained). The host wires this to\n * \"engage the viewer attach + re-negotiate\". Called automatically when a\n * watched desktop is found cold-but-warmable, and behind the manual retry on\n * the warming notice. Distinct from the automatic acknowledgment request.\n */\n onWarm?: (() => void) | undefined;\n /** Shown when transport is null (headless backend / degraded / disabled). */\n renderUnavailable?: ((reason: CapabilityUnavailableReason | null) => ReactNode) | undefined;\n /** Shown while the box is cold/warming (no live address yet). */\n renderWarming?: (() => ReactNode) | undefined;\n /** Shown when the per-session viewer cap (429) was hit. */\n renderViewerCap?: (() => ReactNode) | undefined;\n /** Surface the 429 cap state from `useSessionCapabilities().viewerCapReached`. */\n viewerCapReached?: boolean | undefined;\n /**\n * Connect watchdog (ms): if a live url is present but the RFB hasn't connected\n * within this window, surface a \"Couldn't connect\" + Reconnect instead of an\n * eternal idle scrim. Default 13000. 0 disables the watchdog.\n */\n connectTimeoutMs?: number | undefined;\n className?: string | undefined;\n};\n\n/**\n * The derived desktop surface state. A single source of truth so the overlay,\n * the scrim, the auto-warm effect, and the watchdog all agree. Priority order\n * (highest first): viewer-cap → unavailable → acknowledgment-error → warming →\n * connect-failed → error → opening/connecting → connected.\n */\ntype DesktopUiState =\n | \"viewer_cap\" // 429 — the per-session live-viewer limit is reached.\n | \"unavailable\" // genuinely unsupported (headless/policy/os/backend/not-provisioned).\n | \"acknowledgment_error\" // automatic un-redacted/shared acknowledgment failed.\n | \"opening\" // acknowledgment or viewer attach is in flight.\n | \"warming\" // cold-but-warmable: box not running yet (we auto-warm + spin).\n | \"connecting\" // url is live, RFB negotiating/handshaking (we spin, with a watchdog).\n | \"connect_failed\" // watchdog fired: url present but never connected.\n | \"error\" // RFB error / securityfailure after a connect.\n | \"connected\"; // live framebuffer painting.\n\n// Media-inspection exception: the framebuffer sits on true black so screenshots\n// and desktop pixels are not tinted by the product chrome palette.\nconst DESKTOP_MEDIA_BACKGROUND = \"#000\";\n\n/** Reasons that are genuinely-unavailable (never warmable from the viewer). A\n * `lease_cold` is deliberately EXCLUDED — that's the warmable cold state. */\nfunction isHardUnavailable(reason: CapabilityUnavailableReason | null): boolean {\n switch (reason) {\n case \"backend_unsupported\":\n case \"os_unsupported\":\n case \"not_provisioned\":\n case \"disabled_by_policy\":\n case \"tier_headless\":\n return true;\n default:\n // null or \"lease_cold\" → not a hard reason (cold-but-warmable / live).\n return false;\n }\n}\n\n/**\n * The desktop surface: a noVNC client connecting to the Channel-B scoped tunnel\n * URL from the capability doc. Owns the mount `<div ref>`, drives\n * `useDesktopStream` (SSR-safe lazy RFB), and renders a real\n * cold → warming → connecting → connected → error state machine with live\n * feedback (spinners, transitions) — never a dead black box with stale text.\n *\n * The read-only vs interactive decision is enforced server-first\n * (`capability.mode`): when the deployment advertises mode \"interactive\" the\n * viewer can TAKE CONTROL and drive the mouse & keyboard into the box's :0; a\n * \"read-only\" deployment disables the take-control affordance (graceful, with a\n * reason).\n *\n * Warming: a cold-but-warmable lease (`reason: \"lease_cold\"`) is NOT a dead end.\n * When the viewer is mounted, it asks the host to engage the desktop and records\n * any required acknowledgment automatically. A cold-but-warmable box then\n * (re)warms through `onWarm`; if it later drains to cold it re-warms.\n * Genuinely-unavailable surfaces (headless/policy/os/backend) keep a clear,\n * static unavailable notice.\n */\nexport function DesktopViewer({\n capability,\n interactive,\n showControlToggle = true,\n scaleViewport,\n rfbFactory,\n webSocketProtocols,\n webSocketFactory,\n onPasteText,\n targetPlatform,\n onActivate,\n onAcknowledge,\n watching,\n onWarm,\n renderUnavailable,\n renderWarming,\n renderViewerCap,\n viewerCapReached,\n connectTimeoutMs = 13_000,\n className,\n}: DesktopViewerProps) {\n const containerRef = useRef<HTMLDivElement | null>(null);\n const onActivateRef = useRef(onActivate);\n const onAcknowledgeRef = useRef(onAcknowledge);\n onActivateRef.current = onActivate;\n onAcknowledgeRef.current = onAcknowledge;\n const acknowledgmentAttemptedRef = useRef(false);\n const [acknowledgmentError, setAcknowledgmentError] = useState<Error | null>(null);\n const [acknowledgmentRetryNonce, setAcknowledgmentRetryNonce] = useState(0);\n // Local control state when not externally controlled. The server gate\n // (`capability.mode`) is the hard ceiling — a read-only deployment can never\n // be flipped to interactive regardless of this toggle.\n const [takeControl, setTakeControl] = useState(interactive ?? false);\n const externallyControlled = interactive !== undefined;\n const serverAllowsControl = capability?.mode !== \"read-only\";\n const wantControl = externallyControlled ? interactive : takeControl;\n const inControl = Boolean(wantControl) && serverAllowsControl;\n\n // Mounting this component means the Desktop surface is being viewed. A host\n // may still pass its holder state explicitly while the activation callback\n // flips that state on the first mount.\n const isWatching = watching ?? true;\n\n // ── Decide the rendered state (before touching the stream hook) ─────────────\n const transportNull = !capability || capability.transport === null;\n const reason = capability?.reason ?? null;\n const needsAcknowledgment =\n capability?.requiresAcknowledgment === true && capability.acknowledged !== true;\n // No live address yet on an otherwise-live transport (post-ack, mid-warm).\n const noLiveAddress = Boolean(capability) && !transportNull && !capability!.url;\n\n // Cold-but-warmable: the lease is cold (`lease_cold`) OR the transport is up\n // but no url has been minted yet — either way warming, not unavailable.\n const coldWarmable =\n (transportNull && reason === \"lease_cold\") || (!transportNull && noLiveAddress);\n // Genuinely-unavailable: transport null for a HARD reason (not lease_cold).\n const hardUnavailable = transportNull && isHardUnavailable(reason);\n\n // Selecting Desktop mounts the viewer, which is itself the user's intent to\n // open it. Engage the holder immediately — no separate \"watch\" click.\n useEffect(() => {\n onActivateRef.current?.();\n }, []);\n\n // Preserve the server-side acknowledgment/audit contract without making the\n // user click through a disclosure card. One automatic attempt runs for each\n // unacknowledged episode; a genuine request failure gets an explicit retry.\n useEffect(() => {\n if (!needsAcknowledgment) {\n acknowledgmentAttemptedRef.current = false;\n setAcknowledgmentError(null);\n return;\n }\n if (acknowledgmentAttemptedRef.current) return;\n acknowledgmentAttemptedRef.current = true;\n setAcknowledgmentError(null);\n const acknowledge = onAcknowledgeRef.current;\n if (!acknowledge) {\n setAcknowledgmentError(new Error(\"Desktop acknowledgment is not configured.\"));\n return;\n }\n let active = true;\n void Promise.resolve()\n .then(() => acknowledge())\n .catch((cause) => {\n if (!active) return;\n setAcknowledgmentError(cause instanceof Error ? cause : new Error(String(cause)));\n });\n return () => {\n active = false;\n };\n }, [needsAcknowledgment, acknowledgmentRetryNonce]);\n\n const retryAcknowledgment = () => {\n acknowledgmentAttemptedRef.current = false;\n setAcknowledgmentError(null);\n setAcknowledgmentRetryNonce((nonce) => nonce + 1);\n };\n\n // Release control WITHOUT ever swallowing a key the desktop needs. Esc, and\n // every other key, pass straight through to noVNC/:0 — vital for vim, menus,\n // and dialogs inside the box. Exactly ONE non-trapping keyboard exit:\n // • A single non-conflicting chord — Ctrl+Alt+Shift pressed on its own (no\n // other key) — which no app binds, so it never eats a real keystroke.\n // (Plus the always-visible \"Return control\" button in the in-control bar.)\n //\n // We deliberately DO NOT release on pointer-leave (or window blur): those fire\n // as a SIDE EFFECT of connecting — the noVNC canvas re-laying-out, the surface\n // grabbing focus, the connecting scrim swapping in — which bounced control\n // straight back to \"watch\" the instant the user took it. Control is given up\n // only on an explicit, intentional gesture (the button or the chord).\n useEffect(() => {\n if (!inControl || externallyControlled) return;\n const onKey = (event: KeyboardEvent) => {\n // Only the bare modifier chord releases; if any non-modifier key is also\n // down (event.key is a real key like \"a\"/\"Escape\"), let it pass through.\n if (\n event.ctrlKey &&\n event.altKey &&\n event.shiftKey &&\n (event.key === \"Control\" || event.key === \"Alt\" || event.key === \"Shift\")\n ) {\n event.preventDefault();\n setTakeControl(false);\n }\n };\n window.addEventListener(\"keydown\", onKey);\n return () => {\n window.removeEventListener(\"keydown\", onKey);\n };\n }, [inControl, externallyControlled]);\n\n // The hook is always called (rules of hooks); it stays idle until `url` is set.\n // Do NOT open a socket while the viewer-cap (429) notice is showing — the slot\n // is already exhausted, so connecting would only burn a doomed attempt (and in\n // tests leak an unhandled ws error from the never-resolving tunnel URL).\n const connectCapability =\n !transportNull && !needsAcknowledgment && !viewerCapReached ? capability : null;\n const stream = useDesktopStream({\n capability: connectCapability,\n containerRef,\n interactive: inControl,\n ...(scaleViewport !== undefined ? { scaleViewport } : {}),\n ...(rfbFactory ? { rfbFactory } : {}),\n ...(webSocketProtocols ? { webSocketProtocols } : {}),\n ...(webSocketFactory ? { webSocketFactory } : {}),\n });\n\n const connected = stream.state === \"connected\";\n const hasLiveUrl = Boolean(connectCapability?.url);\n // The latest stream state, read without making it an effect dependency — so the\n // re-attach below can consult it on a visibility change WITHOUT re-subscribing\n // (and re-firing) every time the state walks idle→negotiating→connected.\n const streamStateRef = useRef(stream.state);\n streamStateRef.current = stream.state;\n\n // ── AUTO-WARM ───────────────────────────────────────────────────────────────\n // When the user is watching and the desktop is cold-but-warmable, ask the host\n // to (re)warm the box. This covers BOTH (a) the desktop was just opened and\n // (b) a previously-warm box that drained back to cold under a live viewer. We\n // fire once per distinct cold episode (keyed on the lease epoch + url) so we\n // don't spam the attach while a single warm is in flight. `onWarm` is the\n // no-re-ack path; acknowledgment remains a separate automatic request.\n const warmKeyRef = useRef<string | null>(null);\n useEffect(() => {\n if (!isWatching || !coldWarmable || needsAcknowledgment || viewerCapReached) {\n // Reset the de-dupe once we leave the cold episode so a future drain warms.\n if (!coldWarmable) warmKeyRef.current = null;\n return;\n }\n if (!onWarm) return;\n // Key the de-dupe on the cell fields available to the viewer: while cold,\n // `reason`/`url`/`expiresAt` are stable, so we warm exactly once; once the box\n // warms the cell changes (url+expiresAt minted) and `coldWarmable` flips false,\n // resetting the ref so a later drain re-warms.\n const key = `${capability?.reason ?? \"\"}:${capability?.url ?? \"\"}:${capability?.expiresAt ?? \"\"}`;\n if (warmKeyRef.current === key) return; // already kicked this episode.\n warmKeyRef.current = key;\n onWarm();\n // capability identity is the trigger; leaseEpoch/expiresAt key the episode.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [isWatching, coldWarmable, needsAcknowledgment, viewerCapReached, capability, onWarm]);\n\n // ── TAB RE-ATTACH ───────────────────────────────────────────────────────────\n // The \"stuck Watching, never connects\" bug: the RFB socket never (re)fires when\n // the Desktop tab is shown without a refresh. When a live url exists but we are\n // not connected/connecting, nudge the stream hook to (re)attach — on mount, on\n // the document becoming visible, and on a fresh capability. Re-attach is cheap\n // (disconnect-old/connect-new) and idempotent: the hook ignores it once live.\n useEffect(() => {\n if (!hasLiveUrl || typeof document === \"undefined\") return;\n const maybeReattach = () => {\n if (document.visibilityState !== \"visible\") return;\n // Only kick a socket that never OPENED (idle — e.g. the tab was hidden when\n // the url arrived, so the connect effect bailed on a missing container).\n // Deliberately NOT on \"error\": that surfaces an overlay with an explicit\n // Reconnect, and auto-retrying here would hammer (reconnect → error →\n // reconnect…). We read the live state via a ref so this effect does NOT\n // depend on `stream.state` — depending on it re-ran the effect on every\n // transition and re-fired the kick, which is the reconnect loop.\n if (streamStateRef.current === \"idle\") stream.reconnect();\n };\n // The connect effect already opens the socket on mount / a fresh url. The\n // ONLY gap it can't self-heal is a tab that was hidden when the url arrived\n // (container not in the DOM → connect effect bailed to idle): the socket then\n // never (re)fires on its own. So we revive it on the tab becoming visible —\n // NOT on mount (that double-opens the socket the connect effect just opened).\n document.addEventListener(\"visibilitychange\", maybeReattach);\n return () => document.removeEventListener(\"visibilitychange\", maybeReattach);\n // Re-run ONLY on a fresh live url, never on a state transition.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hasLiveUrl, connectCapability?.url]);\n\n // ── CONNECT WATCHDOG ─────────────────────────────────────────────────────────\n // If a live url is present but the RFB hasn't reached \"connected\" within the\n // window, stop spinning forever and surface \"Couldn't connect\" + Reconnect.\n // Cleared whenever we connect, the url rotates, or the user reconnects.\n const [connectTimedOut, setConnectTimedOut] = useState(false);\n // Bumped on every manual reconnect so the watchdog re-arms its window (the\n // stream hook's url/error don't change on a reconnect, so we need our own key).\n const [reconnectNonce, setReconnectNonce] = useState(0);\n useEffect(() => {\n // Re-arm on a fresh url / a reconnect and clear the moment we connect or hit a\n // real RFB error. We deliberately do NOT key on `stream.state`, so the benign\n // idle→connecting walk doesn't keep resetting the window — the full timeout\n // runs from the url becoming live.\n setConnectTimedOut(false);\n if (!hasLiveUrl || connected || stream.error || connectTimeoutMs <= 0) return;\n const timer = setTimeout(() => setConnectTimedOut(true), connectTimeoutMs);\n return () => clearTimeout(timer);\n }, [\n hasLiveUrl,\n connected,\n connectTimeoutMs,\n connectCapability?.url,\n stream.error,\n reconnectNonce,\n ]);\n\n const reconnect = () => {\n setConnectTimedOut(false);\n setReconnectNonce((n) => n + 1);\n stream.reconnect();\n };\n\n const paste = (event: ClipboardEvent<HTMLDivElement>) => {\n if (!inControl) return;\n const text = event.clipboardData.getData(\"text/plain\");\n if (!text && !event.clipboardData.types.includes(\"text/plain\")) return;\n if (!onPasteText?.(text)) return;\n event.preventDefault();\n event.stopPropagation();\n };\n\n const primaryShortcut = (event: ReactKeyboardEvent<HTMLDivElement>) => {\n if (!inControl || !targetPlatform) return;\n const hostPlatform = interactionHostPlatform();\n // noVNC maps a Mac Command key to remote Alt before the actual chord key\n // arrives. Suppress that standalone physical modifier; the complete target\n // chord below owns both its down and up events.\n if (isHostPrimaryModifier(event, hostPlatform)) {\n event.stopPropagation();\n return;\n }\n const chord = desktopPrimaryShortcut(event, hostPlatform, targetPlatform);\n if (!chord) return;\n // Copy/paste must keep the browser's default clipboard dispatch alive, but\n // noVNC must not also send the host modifier into the remote seat.\n if (chord === \"clipboard\") {\n event.stopPropagation();\n return;\n }\n const sent = chord.every(({ keysym, code, down }) => stream.sendKey?.(keysym, code, down));\n if (!sent) return;\n event.preventDefault();\n event.stopPropagation();\n };\n\n const primaryModifierUp = (event: ReactKeyboardEvent<HTMLDivElement>) => {\n if (inControl && isHostPrimaryModifier(event, interactionHostPlatform())) {\n event.stopPropagation();\n }\n };\n\n // ── Resolve the single UI state (priority order) ────────────────────────────\n let uiState: DesktopUiState;\n if (viewerCapReached) uiState = \"viewer_cap\";\n else if (hardUnavailable) uiState = \"unavailable\";\n else if (acknowledgmentError) uiState = \"acknowledgment_error\";\n else if (needsAcknowledgment) uiState = \"opening\";\n else if (coldWarmable) uiState = \"warming\";\n else if (connected) uiState = \"connected\";\n else if (stream.error) uiState = \"error\";\n else if (connectTimedOut) uiState = \"connect_failed\";\n else uiState = \"connecting\"; // hasLiveUrl && not yet connected (idle/negotiating/connecting).\n\n // An overlay blocks the surface for every state except connected; the toggle\n // and the live scrim are suppressed under an overlay.\n const overlayShown = uiState !== \"connected\" && uiState !== \"connecting\" && uiState !== \"opening\";\n const showToggle = showControlToggle && !overlayShown;\n\n let overlay: ReactNode = null;\n switch (uiState) {\n case \"viewer_cap\":\n overlay =\n renderViewerCap?.() ??\n defaultNotice(\n \"Too many viewers\",\n \"This session has reached its live-viewer limit. Try again shortly.\",\n );\n break;\n case \"unavailable\":\n overlay =\n renderUnavailable?.(reason) ??\n defaultNotice(\"Desktop unavailable\", unavailableCopy(reason));\n break;\n case \"acknowledgment_error\":\n overlay = defaultNotice(\n \"Couldn’t open desktop\",\n acknowledgmentError?.message ?? \"The desktop acknowledgment failed.\",\n retryAcknowledgment,\n );\n break;\n case \"warming\":\n overlay = renderWarming?.() ?? <WarmingNotice />;\n break;\n case \"connect_failed\":\n overlay = defaultNotice(\n \"Couldn’t connect\",\n \"The desktop is warm but the live stream didn’t come up. This usually clears on a retry.\",\n reconnect,\n );\n break;\n case \"error\":\n overlay = defaultNotice(\n \"Desktop disconnected\",\n stream.error?.message ?? \"The stream dropped.\",\n reconnect,\n );\n break;\n case \"opening\":\n case \"connecting\":\n case \"connected\":\n overlay = null;\n break;\n }\n\n return (\n <div\n className={cn(\n \"relative h-full w-full overflow-hidden\",\n inControl && \"ring-2 ring-inset ring-og-accent\",\n className,\n )}\n style={{ backgroundColor: DESKTOP_MEDIA_BACKGROUND }}\n data-opengeni-desktop\n data-state={stream.state}\n data-ui-state={uiState}\n data-in-control={inControl ? \"true\" : undefined}\n data-target-platform={targetPlatform ?? undefined}\n onKeyDownCapture={primaryShortcut}\n onKeyUpCapture={primaryModifierUp}\n onPasteCapture={paste}\n >\n {/* noVNC mount target. It appends a `width:100%;height:100%` `_screen` div\n and AUTOSCALES the 1280x800 framebuffer to fit THIS box. We pin it to\n the bounded `relative` wrapper with `absolute inset-0` (not just\n `h-full w-full`) so its measured size is ALWAYS the panel — never the\n canvas content. A content-sized mount is exactly what makes noVNC\n measure a huge screen and paint the desktop \"zoomed in\". `overflow-hidden`\n keeps the centered (margin:auto) canvas from ever spilling the panel. */}\n <div\n ref={containerRef}\n className=\"absolute inset-0 overflow-hidden\"\n data-opengeni-desktop-canvas\n data-state={stream.state}\n />\n\n {/* Idle / connecting scrim: a quiet, ALIVE \"connecting to the desktop\"\n state behind the canvas so the surface never reads as a dead black\n rectangle before the first framebuffer paints. Only shown in the\n `connecting` UI-state (every other non-connected state has an explicit\n overlay). A spinner + transitional copy makes it feel live. */}\n {(uiState === \"opening\" || uiState === \"connecting\") && (\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center gap-3 text-og-fg-subtle\">\n <span className=\"relative flex items-center justify-center\">\n <MonitorIcon className=\"size-8 opacity-30\" strokeWidth={1.5} />\n <LoaderCircleIcon\n className=\"absolute size-12 animate-og-spin text-og-accent opacity-70\"\n strokeWidth={1.25}\n />\n </span>\n <span className=\"text-og-sm\">\n {uiState === \"opening\" ? \"Opening the desktop…\" : \"Connecting to the desktop…\"}\n </span>\n </div>\n )}\n\n {/* Take-control affordance. Two distinct states:\n - WATCHING → a prominent, centered call-to-action button overlaid on\n the desktop (the primary CTA; tasteful so the screen stays visible).\n - IN CONTROL → a small top bar (\"You're in control · click Return\n control (or Ctrl+Alt+Shift)\") with a clearly-visible Return-control\n button; the desktop stays fully usable and EVERY key — Esc included —\n passes through to the box. Server-gated: when the deployment is\n read-only the CTA renders disabled with a reason so it degrades\n gracefully. */}\n {showToggle && !externallyControlled && inControl && (\n <InControlBar\n shared={capability?.shared ?? false}\n onRelease={() => setTakeControl(false)}\n />\n )}\n {/* The big CTA only appears once the framebuffer is live + the viewer is\n watching: before connect the scrim already communicates state, so we\n don't double up. A read-only deployment (serverAllowsControl=false) still\n surfaces the CTA disabled-with-reason once connected, so it degrades\n gracefully and stays discoverable. */}\n {showToggle && !externallyControlled && !inControl && connected && (\n <TakeControlCallToAction\n disabled={!serverAllowsControl}\n disabledReason={\n !serverAllowsControl\n ? capability?.client === \"frames\"\n ? \"View-only — live control isn't available for this machine yet.\"\n : \"This deployment streams the desktop read-only.\"\n : undefined\n }\n onTakeControl={() => setTakeControl(true)}\n />\n )}\n\n {overlay && (\n <div className=\"absolute inset-0 flex items-center justify-center p-4\">{overlay}</div>\n )}\n </div>\n );\n}\n\ntype RfbKeyEvent = { keysym: number; code: string; down: boolean };\n\nfunction isHostPrimaryModifier(\n event: Pick<ReactKeyboardEvent<HTMLDivElement>, \"key\">,\n hostPlatform: InteractionHostPlatform,\n): boolean {\n return event.key === (hostPlatform === \"mac\" ? \"Meta\" : \"Control\");\n}\n\n/** Translate one host-primary chord into deterministic RFB events for the\n * target OS. noVNC otherwise treats a Mac Command key as remote Alt, which\n * makes ordinary Cmd+A/Z shortcuts silently wrong on Linux sandboxes. */\nexport function desktopPrimaryShortcut(\n event: Pick<\n ReactKeyboardEvent<HTMLDivElement>,\n \"altKey\" | \"ctrlKey\" | \"key\" | \"metaKey\" | \"shiftKey\" | \"code\"\n >,\n hostPlatform: InteractionHostPlatform,\n targetPlatform: \"linux\" | \"macos\" | \"windows\",\n): RfbKeyEvent[] | \"clipboard\" | null {\n const primaryHeld = hostPlatform === \"mac\" ? event.metaKey : event.ctrlKey;\n if (!primaryHeld || [...event.key].length !== 1) return null;\n const key = event.key.toLowerCase();\n if (!event.altKey && (key === \"c\" || key === \"v\")) return \"clipboard\";\n const primary =\n targetPlatform === \"macos\"\n ? { keysym: 0xffeb, code: \"MetaLeft\" }\n : { keysym: 0xffe3, code: \"ControlLeft\" };\n const modifiers = [\n primary,\n ...(event.altKey ? [{ keysym: 0xffe9, code: \"AltLeft\" }] : []),\n ...(event.shiftKey ? [{ keysym: 0xffe1, code: \"ShiftLeft\" }] : []),\n ];\n const keyEvent = {\n keysym: key.codePointAt(0)!,\n code: event.code || (/^[a-z]$/u.test(key) ? `Key${key.toUpperCase()}` : key),\n };\n return [\n ...modifiers.map((modifier) => ({ ...modifier, down: true })),\n { ...keyEvent, down: true },\n { ...keyEvent, down: false },\n ...modifiers.reverse().map((modifier) => ({ ...modifier, down: false })),\n ];\n}\n\n/**\n * The primary WATCHING-state call-to-action: a large, centered pill button\n * overlaid on the desktop inviting the viewer to drive the mouse & keyboard.\n * Tasteful by default — it sits in the LOWER-center with a soft scrim only behind\n * the button (not the whole screen) and lifts on hover, so a watcher can still see\n * the agent work. Server-gated: when the deployment is read-only (or the desktop\n * hasn't connected yet) it renders disabled with a reason. Accessible: a real\n * <button> with a title, focus ring, and Enter/Space activation.\n */\nfunction TakeControlCallToAction({\n disabled,\n disabledReason,\n onTakeControl,\n}: {\n disabled: boolean;\n disabledReason?: string | undefined;\n onTakeControl: () => void;\n}) {\n return (\n // The wrapper spans the surface but is click-through (pointer-events-none); only\n // the button itself is interactive, so watchers can still see the desktop.\n <div className=\"pointer-events-none absolute inset-0 flex items-end justify-center pb-[8%]\">\n <button\n type=\"button\"\n disabled={disabled}\n aria-label=\"Take control of the desktop\"\n title={disabled ? disabledReason : \"Take control of the desktop\"}\n onClick={onTakeControl}\n className={cn(\n \"group pointer-events-auto flex items-center gap-3 rounded-og-lg border px-5 py-3\",\n \"border-og-border\",\n \"bg-og-bg/85 backdrop-blur-md\",\n \"shadow-og-lg\",\n \"outline-hidden transition-all duration-150 ease-out\",\n \"focus-visible:ring-2 focus-visible:ring-og-accent focus-visible:ring-offset-2 focus-visible:ring-offset-og-bg\",\n disabled\n ? \"cursor-not-allowed opacity-60\"\n : cn(\n \"cursor-pointer opacity-90 hover:-translate-y-0.5 hover:opacity-100\",\n \"hover:border-og-accent\",\n \"hover:bg-og-accent/10\",\n ),\n )}\n >\n <span\n className={cn(\n \"flex size-9 shrink-0 items-center justify-center rounded-full transition-colors\",\n \"bg-og-accent\",\n \"text-og-accent-fg\",\n disabled ? \"\" : \"group-hover:scale-105\",\n )}\n >\n <MousePointerClickIcon className=\"size-5\" strokeWidth={2} />\n </span>\n <span className=\"flex flex-col items-start leading-tight\">\n <span className=\"text-og-base font-semibold text-og-fg\">Take control</span>\n <span className=\"text-og-xs text-og-fg-subtle\">\n {disabled && disabledReason ? disabledReason : \"Drive the mouse & keyboard\"}\n </span>\n </span>\n </button>\n </div>\n );\n}\n\n/**\n * The IN-CONTROL state: a small top bar so the desktop stays fully usable while\n * driving (the accent ring around the viewport carries the primary \"you're\n * driving\" signal). Every keystroke — including Esc — passes through to the box,\n * so release lives only on explicit, non-trapping affordances: a clearly-visible\n * \"Return control\" button and the Ctrl+Alt+Shift chord. The button is solid\n * (not a faint ghost) so it's always discoverable as the way out.\n */\nfunction InControlBar({ shared, onRelease }: { shared: boolean; onRelease: () => void }) {\n return (\n <div className=\"pointer-events-none absolute inset-x-0 top-0 flex items-center justify-between gap-2 p-2\">\n <span className=\"pointer-events-auto inline-flex items-center gap-2 rounded-og-sm bg-og-accent px-2.5 py-1 text-og-xs font-medium text-og-accent-fg shadow-og-md\">\n <span className=\"size-1.5 animate-pulse rounded-full bg-current\" aria-hidden />\n You're in control\n <span className=\"opacity-75\">· click Return control (or Ctrl+Alt+Shift)</span>\n </span>\n <div className=\"pointer-events-auto flex items-center gap-1.5\">\n {shared && (\n <span className=\"rounded-og-sm bg-og-status-failed/85 px-2 py-0.5 text-og-xs text-og-accent-fg\">\n Shared sandbox — others are watching\n </span>\n )}\n <button\n type=\"button\"\n onClick={onRelease}\n title=\"Return control (or press Ctrl+Alt+Shift)\"\n className={cn(\n \"inline-flex items-center gap-1.5 rounded-og-sm border px-2.5 py-1 text-og-xs font-semibold shadow-og-md transition-colors\",\n \"border-og-accent bg-og-bg/90 text-og-fg backdrop-blur-sm\",\n \"outline-hidden hover:bg-og-accent hover:text-og-accent-fg focus-visible:ring-2 focus-visible:ring-og-accent\",\n )}\n >\n <MonitorIcon className=\"size-3.5\" strokeWidth={2} aria-hidden />\n Return control\n </button>\n </div>\n </div>\n );\n}\n\nfunction unavailableCopy(reason: CapabilityUnavailableReason | null): string {\n switch (reason) {\n case \"backend_unsupported\":\n return \"This sandbox backend cannot stream a desktop.\";\n case \"tier_headless\":\n return \"This deployment is headless — terminal, files, and diff only.\";\n case \"os_unsupported\":\n return \"The sandbox OS does not support a desktop stream.\";\n case \"not_provisioned\":\n return \"This sandbox doesn't have a desktop yet.\";\n case \"disabled_by_policy\":\n return \"Desktop streaming is disabled on this deployment.\";\n case \"lease_cold\":\n // Should not reach the unavailable notice (lease_cold warms), but keep\n // friendly copy as a fallback rather than the old dead-end wording.\n return \"Waiting for the sandbox to start…\";\n default:\n return \"The desktop isn’t available for this sandbox.\";\n }\n}\n\n/**\n * The WARMING state: a cold-but-warmable box that is being spun up. A spinner +\n * clear, accurate copy — and (when the host wired a manual warm) an explicit\n * retry so a slow warm never feels stuck. This is the cure for the old\n * \"Desktop unavailable — Start a turn or attach to warm it\" dead-end: the box IS\n * being warmed automatically; we just tell the user it's happening.\n */\nfunction WarmingNotice({ onRetry }: { onRetry?: (() => void) | undefined }) {\n return (\n <div className=\"flex max-w-sm flex-col items-center gap-3 rounded-og-lg border border-og-border bg-og-bg/90 p-5 text-center text-og-base text-og-fg backdrop-blur-sm\">\n <LoaderCircleIcon className=\"size-7 animate-og-spin text-og-accent\" strokeWidth={1.5} />\n <div className=\"space-y-1\">\n <div className=\"font-medium\">Warming the sandbox…</div>\n <p className=\"text-og-sm text-og-fg-subtle\">\n Spinning up the desktop — this takes a few seconds.\n </p>\n </div>\n {onRetry && (\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"rounded-og-sm border border-og-border px-3 py-1.5 text-og-sm text-og-fg-muted transition-colors hover:text-og-fg pointer-coarse:min-h-11\"\n >\n Taking too long? Retry\n </button>\n )}\n </div>\n );\n}\n\nfunction defaultNotice(title: string, body: string, onRetry?: () => void): ReactNode {\n return (\n <div className=\"max-w-sm rounded-og-lg border border-og-border bg-og-bg p-4 text-center text-og-base text-og-fg\">\n <div className=\"mb-1 flex items-center justify-center gap-1.5 font-medium\">\n {onRetry && <WifiOffIcon className=\"size-4 opacity-70\" strokeWidth={1.75} />}\n {title}\n </div>\n <p className=\"text-og-sm text-og-fg-subtle\">{body}</p>\n {onRetry && (\n <button\n type=\"button\"\n onClick={onRetry}\n className=\"mt-3 rounded-og-sm border border-og-border px-3 py-1.5 text-og-sm transition-colors hover:border-og-accent pointer-coarse:min-h-11\"\n >\n Reconnect\n </button>\n )}\n </div>\n );\n}\n","export type InteractionHostPlatform = \"mac\" | \"other\";\n\n/** Physical client platform. Remote input adapters use this only to identify\n * the user's primary shortcut modifier; the target platform is resolved\n * separately. */\nexport function interactionHostPlatform(): InteractionHostPlatform {\n if (typeof navigator === \"undefined\") return \"other\";\n const platform = navigator.platform || navigator.userAgent;\n return /Mac|iPhone|iPad|iPod/u.test(platform) ? \"mac\" : \"other\";\n}\n"],"mappings":"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","names":["useEffect","useRef","useState","useState","useRef","useEffect","useEffect","useRef","useState","useRef","useState","useEffect"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/model-policy.ts"],"sourcesContent":["import type { ClientModel, ReasoningEffort, WorkspaceModelCatalogModel } from \"@opengeni/sdk\";\n\nexport type PickerBillingClass =\n | \"opengeni_credits\"\n | \"codex_subscription\"\n | \"supergrok_subscription\"\n | \"byok\";\n\nexport type PickerModelRow<TCatalog extends ClientModel = WorkspaceModelCatalogModel> = {\n id: string;\n label: string;\n /** Catalog-curated compact label for dense UI; fall back to `label` when absent. */\n shortLabel?: string | undefined;\n billingClass: PickerBillingClass;\n billingClassLabel: string;\n selectable: boolean;\n unavailableReason: string | null;\n provider: string;\n providerLabel: string;\n catalog: TCatalog;\n};\n\nexport type LatencyModeId = \"standard\" | \"priority\" | \"fast\";\n\nconst BILLING_CLASS_ORDER: PickerBillingClass[] = [\n \"opengeni_credits\",\n \"codex_subscription\",\n \"supergrok_subscription\",\n \"byok\",\n];\n\nconst BILLING_CLASS_LABELS: Record<PickerBillingClass, string> = {\n opengeni_credits: \"OpenGeni\",\n codex_subscription: \"Codex\",\n supergrok_subscription: \"SuperGrok\",\n byok: \"Your Gateway\",\n};\n\nconst AVAILABILITY_REASON_LABELS: Record<string, string> = {\n missing_credential: \"Credentials required\",\n needs_reauth: \"Reconnect required\",\n credential_not_ready: \"Credential not ready\",\n not_entitled: \"Not entitled\",\n provider_unhealthy: \"Provider unavailable\",\n policy_blocked: \"Blocked by workspace policy\",\n unsupported: \"Unsupported\",\n};\n\nexport function billingClassForModel(model: ClientModel): PickerBillingClass {\n if (model.source === \"supergrok\") {\n return \"supergrok_subscription\";\n }\n if (model.source === \"codex\") {\n return \"codex_subscription\";\n }\n if (model.source === \"workspace_gateway\") {\n return \"byok\";\n }\n if (model.credentialSource?.kind === \"connected_subscription\") {\n return model.credentialSource.provider === \"xai\"\n ? \"supergrok_subscription\"\n : \"codex_subscription\";\n }\n if (model.credentialSource?.kind === \"workspace_connection\") {\n return \"byok\";\n }\n if (model.billing?.upstreamPayer === \"connected_subscription\") {\n return \"codex_subscription\";\n }\n if (model.billing?.upstreamPayer === \"workspace\") {\n return \"byok\";\n }\n return \"opengeni_credits\";\n}\n\nexport function billingClassLabel(billingClass: PickerBillingClass): string {\n return BILLING_CLASS_LABELS[billingClass];\n}\n\nexport function availabilityReasonLabel(\n reason: WorkspaceModelCatalogModel[\"availability\"][\"reason\"],\n): string | null {\n if (!reason) {\n return null;\n }\n return AVAILABILITY_REASON_LABELS[reason] ?? \"Unavailable\";\n}\n\nexport function effortOptionsForModel(model: ClientModel): ReasoningEffort[] {\n const efforts = model.capabilities?.reasoning.efforts;\n if (!efforts || efforts.length === 0) {\n return [\"low\"];\n }\n const order: ReasoningEffort[] = [\"none\", \"minimal\", \"low\", \"medium\", \"high\", \"xhigh\", \"max\"];\n return order.filter((effort) => efforts.includes(effort));\n}\n\nexport function defaultEffortForModel(model: ClientModel): ReasoningEffort {\n const options = effortOptionsForModel(model);\n const configured = model.capabilities?.reasoning.defaultEffort;\n if (configured && options.includes(configured)) {\n return configured;\n }\n return options[0] ?? \"low\";\n}\n\nexport function coerceReasoningEffortForModel(\n model: ClientModel,\n effort: ReasoningEffort,\n): ReasoningEffort {\n const options = effortOptionsForModel(model);\n if (options.includes(effort)) {\n return effort;\n }\n return defaultEffortForModel(model);\n}\n\nexport function runnableLatencyModesForModel(model: ClientModel): LatencyModeId[] {\n const modes = model.capabilities?.latencyModes ?? [];\n return modes.filter((mode) => mode.runnable).map((mode) => mode.id);\n}\n\nexport function labelLatencyMode(mode: LatencyModeId): string {\n if (mode === \"fast\") {\n return \"Fast\";\n }\n if (mode === \"priority\") {\n return \"Priority\";\n }\n return \"Standard\";\n}\n\nexport function labelReasoningEffort(effort: ReasoningEffort): string {\n if (effort === \"xhigh\") {\n return \"Extra high\";\n }\n return effort.slice(0, 1).toUpperCase() + effort.slice(1);\n}\n\nexport function payerSummaryForModel(model: ClientModel): string {\n const billing = model.billing;\n if (!billing) {\n return \"Route unknown\";\n }\n if (billing.metering === \"opengeni_credits\") {\n return \"OpenGeni credits · automatic managed route\";\n }\n if (billing.upstreamPayer === \"connected_subscription\") {\n return model.source === \"supergrok\"\n ? \"SuperGrok subscription · external billing\"\n : \"Codex subscription · external billing\";\n }\n if (billing.upstreamPayer === \"workspace\") {\n return \"Billed to your AI Gateway\";\n }\n return \"Deployment route · external billing\";\n}\n\nexport function advancedSourceSummary(model: ClientModel): string | null {\n const source = model.credentialSource;\n if (!source) {\n return null;\n }\n if (source.kind === \"connected_subscription\") {\n return source.provider === \"xai\"\n ? \"Connected SuperGrok subscription\"\n : \"Connected Codex subscription\";\n }\n if (source.kind === \"workspace_connection\") {\n return \"Workspace AI Gateway\";\n }\n if (source.kind === \"deployment\") {\n return source.mechanism === \"azure_ad_bearer\"\n ? \"Deployment Azure identity\"\n : \"Deployment API key\";\n }\n return null;\n}\n\nexport function projectPickerRows(models: WorkspaceModelCatalogModel[]): PickerModelRow[] {\n return models\n .filter((catalog) => {\n const billingClass = billingClassForModel(catalog);\n return billingClass === \"opengeni_credits\" || catalog.credentialReadiness.status === \"ready\";\n })\n .map((catalog) => {\n const billingClass = billingClassForModel(catalog);\n return {\n id: catalog.id,\n label: catalog.label,\n ...(catalog.shortLabel ? { shortLabel: catalog.shortLabel } : {}),\n billingClass,\n billingClassLabel: billingClassLabel(billingClass),\n selectable: catalog.availability.selectable,\n unavailableReason: catalog.availability.selectable\n ? null\n : availabilityReasonLabel(catalog.availability.reason),\n provider: catalog.provider,\n providerLabel: catalog.providerLabel,\n catalog,\n };\n });\n}\n\n/** Project the lightweight client-config model list into the same picker contract. */\nexport function projectClientModelRows(models: ClientModel[]): PickerModelRow<ClientModel>[] {\n return models.map((catalog) => {\n const billingClass = billingClassForModel(catalog);\n return {\n id: catalog.id,\n label: catalog.label,\n ...(catalog.shortLabel ? { shortLabel: catalog.shortLabel } : {}),\n billingClass,\n billingClassLabel: billingClassLabel(billingClass),\n selectable: true,\n unavailableReason: null,\n provider: catalog.provider,\n providerLabel: catalog.providerLabel,\n catalog,\n };\n });\n}\n\nexport function sortPickerRows<TCatalog extends ClientModel>(\n rows: PickerModelRow<TCatalog>[],\n): PickerModelRow<TCatalog>[] {\n return [...rows].sort((left, right) => {\n const classDelta =\n BILLING_CLASS_ORDER.indexOf(left.billingClass) -\n BILLING_CLASS_ORDER.indexOf(right.billingClass);\n if (classDelta !== 0) {\n return classDelta;\n }\n if (left.selectable !== right.selectable) {\n return left.selectable ? -1 : 1;\n }\n return left.label.localeCompare(right.label);\n });\n}\n\nexport function findPickerRow<TCatalog extends ClientModel>(\n rows: PickerModelRow<TCatalog>[],\n modelId: string,\n): PickerModelRow<TCatalog> | null {\n return rows.find((row) => row.id === modelId) ?? null;\n}\n\nexport function groupPickerRowsByBillingClass(\n rows: PickerModelRow[],\n): Array<{ billingClass: PickerBillingClass; label: string; rows: PickerModelRow[] }>;\nexport function groupPickerRowsByBillingClass<TCatalog extends ClientModel>(\n rows: PickerModelRow<TCatalog>[],\n): Array<{\n billingClass: PickerBillingClass;\n label: string;\n rows: PickerModelRow<TCatalog>[];\n}>;\nexport function groupPickerRowsByBillingClass<TCatalog extends ClientModel>(\n rows: PickerModelRow<TCatalog>[],\n): Array<{\n billingClass: PickerBillingClass;\n label: string;\n rows: PickerModelRow<TCatalog>[];\n}> {\n const sorted = sortPickerRows(rows);\n const groups: Array<{\n billingClass: PickerBillingClass;\n label: string;\n rows: PickerModelRow<TCatalog>[];\n }> = [];\n for (const row of sorted) {\n const existing = groups.find((group) => group.billingClass === row.billingClass);\n if (existing) {\n existing.rows.push(row);\n continue;\n }\n groups.push({\n billingClass: row.billingClass,\n label: row.billingClassLabel,\n rows: [row],\n });\n }\n return groups;\n}\n"],"mappings":"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","names":[]}
|