@opengeni/react 3.1.0 → 3.1.3-canary.38890816741001
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 +769 -22
- package/dist/accounts.js +89 -11
- package/dist/accounts.js.map +1 -1
- package/dist/approvals.d.ts +2 -1
- package/dist/artifacts-document.js +5 -3
- package/dist/artifacts-presentation.js +5 -3
- package/dist/artifacts-spreadsheet.d.ts +1 -1
- package/dist/artifacts-spreadsheet.js +5 -3
- package/dist/artifacts.d.ts +5 -1
- package/dist/artifacts.js +536 -42
- package/dist/artifacts.js.map +1 -1
- package/dist/browser-viewer-ZWR6MWJ7.js +19 -0
- package/dist/capability-catalog-row.d.ts +20 -0
- package/dist/capability-logo-fallback.d.ts +3 -0
- package/dist/chat-interactive-block-BO2IWXVK.js +14 -0
- package/dist/chunk-2DDJ6YH6.js +41 -0
- package/dist/chunk-2DDJ6YH6.js.map +1 -0
- package/dist/chunk-2HVLQ26H.js +183 -0
- package/dist/chunk-2HVLQ26H.js.map +1 -0
- package/dist/{chunk-RGALCTWO.js → chunk-2I6R3GCZ.js} +4285 -3980
- package/dist/chunk-2I6R3GCZ.js.map +1 -0
- package/dist/{chunk-D3UIAJKY.js → chunk-2SFFYIH3.js} +1179 -166
- package/dist/chunk-2SFFYIH3.js.map +1 -0
- package/dist/chunk-3PVDYTTB.js +24 -0
- package/dist/chunk-3PVDYTTB.js.map +1 -0
- package/dist/chunk-3UZCWK46.js +298 -0
- package/dist/chunk-3UZCWK46.js.map +1 -0
- package/dist/chunk-47CKUXTW.js +293 -0
- package/dist/chunk-47CKUXTW.js.map +1 -0
- package/dist/chunk-4QYP6QUI.js +109 -0
- package/dist/chunk-4QYP6QUI.js.map +1 -0
- package/dist/chunk-5EY26VZ7.js +1053 -0
- package/dist/chunk-5EY26VZ7.js.map +1 -0
- package/dist/chunk-5I4VRWBB.js +56 -0
- package/dist/chunk-5I4VRWBB.js.map +1 -0
- package/dist/chunk-5WOR7UCL.js +191 -0
- package/dist/chunk-5WOR7UCL.js.map +1 -0
- package/dist/chunk-5YJLGOM5.js +51 -0
- package/dist/chunk-5YJLGOM5.js.map +1 -0
- package/dist/chunk-6JFN3OWG.js +386 -0
- package/dist/chunk-6JFN3OWG.js.map +1 -0
- package/dist/chunk-7QJT5O4S.js +35 -0
- package/dist/chunk-7QJT5O4S.js.map +1 -0
- package/dist/chunk-A55KV72M.js +402 -0
- package/dist/chunk-A55KV72M.js.map +1 -0
- package/dist/chunk-ADWCLI4G.js +2028 -0
- package/dist/chunk-ADWCLI4G.js.map +1 -0
- package/dist/chunk-B27E3V37.js +29 -0
- package/dist/chunk-B27E3V37.js.map +1 -0
- package/dist/chunk-B7TC2A33.js +83 -0
- package/dist/chunk-B7TC2A33.js.map +1 -0
- package/dist/chunk-BLL56F7R.js +385 -0
- package/dist/chunk-BLL56F7R.js.map +1 -0
- package/dist/chunk-BOFEQC6K.js +162 -0
- package/dist/chunk-BOFEQC6K.js.map +1 -0
- package/dist/chunk-CM7LVUKC.js +35 -0
- package/dist/chunk-CM7LVUKC.js.map +1 -0
- package/dist/chunk-CORUP6JB.js +122 -0
- package/dist/chunk-CORUP6JB.js.map +1 -0
- package/dist/chunk-CSX5FFKZ.js +35 -0
- package/dist/chunk-CSX5FFKZ.js.map +1 -0
- package/dist/chunk-EGVHJKGJ.js +3125 -0
- package/dist/chunk-EGVHJKGJ.js.map +1 -0
- package/dist/{chunk-NEXLLTP4.js → chunk-G76LSA76.js} +6 -5
- package/dist/chunk-G76LSA76.js.map +1 -0
- package/dist/{chunk-PEI6RANI.js → chunk-GQMRL52F.js} +311 -75
- package/dist/chunk-GQMRL52F.js.map +1 -0
- package/dist/{chunk-ELIYWBZR.js → chunk-GUUXOISA.js} +1113 -457
- package/dist/chunk-GUUXOISA.js.map +1 -0
- package/dist/{chunk-HUIKIDM5.js → chunk-I7X2P4FU.js} +225 -116
- package/dist/chunk-I7X2P4FU.js.map +1 -0
- package/dist/chunk-IGFZFLI2.js +1390 -0
- package/dist/chunk-IGFZFLI2.js.map +1 -0
- package/dist/{chunk-4MDLEHXL.js → chunk-IMSUVYQB.js} +26 -13
- package/dist/chunk-IMSUVYQB.js.map +1 -0
- package/dist/chunk-IOYHDEP3.js +38 -0
- package/dist/chunk-IOYHDEP3.js.map +1 -0
- package/dist/chunk-IZV5FTJC.js +55 -0
- package/dist/chunk-IZV5FTJC.js.map +1 -0
- package/dist/chunk-KF33GTWN.js +343 -0
- package/dist/chunk-KF33GTWN.js.map +1 -0
- package/dist/chunk-KSGQ5ODA.js +457 -0
- package/dist/chunk-KSGQ5ODA.js.map +1 -0
- package/dist/{chunk-OAM4WAAM.js → chunk-KZC6QRNR.js} +1513 -697
- package/dist/chunk-KZC6QRNR.js.map +1 -0
- package/dist/chunk-LJUMYUBO.js +218 -0
- package/dist/chunk-LJUMYUBO.js.map +1 -0
- package/dist/chunk-LSW2OJVV.js +380 -0
- package/dist/chunk-LSW2OJVV.js.map +1 -0
- package/dist/chunk-M357MHH3.js +2 -0
- package/dist/chunk-M357MHH3.js.map +1 -0
- package/dist/chunk-MK2TQO4Y.js +24 -0
- package/dist/chunk-MK2TQO4Y.js.map +1 -0
- package/dist/chunk-N7S72EXK.js +25 -0
- package/dist/chunk-N7S72EXK.js.map +1 -0
- package/dist/{chunk-XXWH2JLC.js → chunk-NKMYZFHA.js} +616 -621
- package/dist/chunk-NKMYZFHA.js.map +1 -0
- package/dist/chunk-NX4ADH73.js +14 -0
- package/dist/chunk-NX4ADH73.js.map +1 -0
- package/dist/chunk-O7F6UJO4.js +71 -0
- package/dist/chunk-O7F6UJO4.js.map +1 -0
- package/dist/chunk-OVB5WIG5.js +14064 -0
- package/dist/chunk-OVB5WIG5.js.map +1 -0
- package/dist/chunk-OYQQJL3J.js +16 -0
- package/dist/chunk-OYQQJL3J.js.map +1 -0
- package/dist/{chunk-H5YGYNXS.js → chunk-P4BNUO57.js} +60 -17
- package/dist/chunk-P4BNUO57.js.map +1 -0
- package/dist/{chunk-JVYQRRC4.js → chunk-PEHTX3V7.js} +21 -1
- package/dist/chunk-PEHTX3V7.js.map +1 -0
- package/dist/chunk-PMV53IMM.js +26 -0
- package/dist/chunk-PMV53IMM.js.map +1 -0
- package/dist/{chunk-KCGXPP5V.js → chunk-PYEOPA4M.js} +13 -7
- package/dist/chunk-PYEOPA4M.js.map +1 -0
- package/dist/{chunk-KZ73RL76.js → chunk-QHUE7E4N.js} +8 -3
- package/dist/chunk-QHUE7E4N.js.map +1 -0
- package/dist/chunk-QV3DJVRZ.js +11 -0
- package/dist/chunk-QV3DJVRZ.js.map +1 -0
- package/dist/chunk-R5SBNG7S.js +810 -0
- package/dist/chunk-R5SBNG7S.js.map +1 -0
- package/dist/chunk-RI42H5IU.js +326 -0
- package/dist/chunk-RI42H5IU.js.map +1 -0
- package/dist/chunk-S2QH6EZ5.js +211 -0
- package/dist/chunk-S2QH6EZ5.js.map +1 -0
- package/dist/chunk-SHLZMAJN.js +304 -0
- package/dist/chunk-SHLZMAJN.js.map +1 -0
- package/dist/chunk-TGXK4BAZ.js +293 -0
- package/dist/chunk-TGXK4BAZ.js.map +1 -0
- package/dist/{chunk-5YK2ZDIG.js → chunk-TNEZNMSN.js} +4 -10
- package/dist/chunk-TNEZNMSN.js.map +1 -0
- package/dist/{chunk-22KP6LR5.js → chunk-TXS65BB2.js} +3 -1
- package/dist/{chunk-22KP6LR5.js.map → chunk-TXS65BB2.js.map} +1 -1
- package/dist/chunk-UNJPZGNK.js +63 -0
- package/dist/chunk-UNJPZGNK.js.map +1 -0
- package/dist/{chunk-MZ2G2XBA.js → chunk-V5ZUJT2T.js} +205 -45
- package/dist/chunk-V5ZUJT2T.js.map +1 -0
- package/dist/chunk-VIUEOHXY.js +394 -0
- package/dist/chunk-VIUEOHXY.js.map +1 -0
- package/dist/chunk-WIUKB3LD.js +1911 -0
- package/dist/chunk-WIUKB3LD.js.map +1 -0
- package/dist/{chunk-ICA6R6TW.js → chunk-WNN7NO2N.js} +1451 -355
- package/dist/chunk-WNN7NO2N.js.map +1 -0
- package/dist/{chunk-YJFGAFBD.js → chunk-X5UVUIMF.js} +1892 -1223
- package/dist/chunk-X5UVUIMF.js.map +1 -0
- package/dist/chunk-XDA5QJE7.js +402 -0
- package/dist/chunk-XDA5QJE7.js.map +1 -0
- package/dist/{chunk-VZTQ73XT.js → chunk-XEA5BCMA.js} +3 -1
- package/dist/{chunk-VZTQ73XT.js.map → chunk-XEA5BCMA.js.map} +1 -1
- package/dist/chunk-XHF7OYAN.js +1150 -0
- package/dist/chunk-XHF7OYAN.js.map +1 -0
- package/dist/chunk-YSRBFJMQ.js +43 -0
- package/dist/chunk-YSRBFJMQ.js.map +1 -0
- package/dist/chunk-ZOZAPAUO.js +174 -0
- package/dist/chunk-ZOZAPAUO.js.map +1 -0
- package/dist/chunk-ZQV5W7YC.js +809 -0
- package/dist/chunk-ZQV5W7YC.js.map +1 -0
- package/dist/chunk-ZUWF4NF7.js +13 -0
- package/dist/chunk-ZUWF4NF7.js.map +1 -0
- package/dist/client.d.ts +16 -7
- package/dist/clipboard.js +3 -1
- package/dist/commands/types.d.ts +2 -0
- package/dist/components/anthropic-mark.d.ts +3 -0
- package/dist/components/approval-surface.d.ts +19 -6
- package/dist/components/artifacts/artifact-chrome.d.ts +120 -0
- package/dist/components/artifacts/artifact-sandbox.d.ts +30 -0
- package/dist/components/artifacts/artifact-surface.d.ts +4 -2
- package/dist/components/artifacts/chat-interactive-block.d.ts +29 -0
- package/dist/components/artifacts/deferred-chat-media.d.ts +15 -0
- package/dist/components/artifacts/document-editor.d.ts +4 -2
- package/dist/components/artifacts/editable-artifact-ui.d.ts +2 -1
- package/dist/components/artifacts/editable-artifact-view.d.ts +48 -0
- package/dist/components/artifacts/editable-document.d.ts +1 -1
- package/dist/components/artifacts/editable-presentation.d.ts +2 -2
- package/dist/components/artifacts/editable-spreadsheet.d.ts +8 -3
- package/dist/components/artifacts/index.d.ts +9 -2
- package/dist/components/artifacts/inline-html-document.d.ts +2 -0
- package/dist/components/artifacts/latest-task-runner.d.ts +15 -0
- package/dist/components/artifacts/presentation-editor.d.ts +6 -2
- package/dist/components/artifacts/published-html-artifact-frame.d.ts +23 -3
- package/dist/components/artifacts/session-artifact-viewer.d.ts +39 -0
- package/dist/components/artifacts/site-view.d.ts +26 -0
- package/dist/components/artifacts/spreadsheet-command-queue.d.ts +20 -0
- package/dist/components/artifacts/spreadsheet-download.d.ts +7 -0
- package/dist/components/artifacts/spreadsheet-general-display.d.ts +24 -0
- package/dist/components/artifacts/spreadsheet-grid.d.ts +10 -3
- package/dist/components/artifacts/spreadsheet-resize.d.ts +50 -0
- package/dist/components/artifacts/spreadsheet-submissions.d.ts +9 -0
- package/dist/components/artifacts/visualization-assets.gen.d.ts +2 -0
- package/dist/components/browser-select-control.d.ts +16 -0
- package/dist/components/browser-viewer.d.ts +11 -2
- package/dist/components/chat-composer.d.ts +21 -3
- package/dist/components/child-session-link.d.ts +9 -0
- package/dist/components/claude-mark.d.ts +3 -0
- package/dist/components/code-editor.d.ts +1 -4
- package/dist/components/composer-transcription-control.d.ts +7 -0
- package/dist/components/composer.d.ts +34 -67
- package/dist/components/computer-viewer.d.ts +1 -1
- package/dist/components/copy-button.d.ts +3 -1
- package/dist/components/desktop-viewer.d.ts +1 -1
- package/dist/components/fleet-tile.d.ts +2 -2
- package/dist/components/grok-mark.d.ts +26 -0
- package/dist/components/human-input-form.d.ts +28 -32
- package/dist/components/human-input-surface.d.ts +7 -2
- package/dist/components/linkified-text.d.ts +18 -0
- package/dist/components/machine-input-display.d.ts +22 -1
- package/dist/components/machines/agent-status-icon.d.ts +6 -0
- package/dist/components/machines/machine-detail.d.ts +7 -1
- package/dist/components/machines/machine-status.d.ts +48 -0
- package/dist/components/markdown-table-layout.d.ts +18 -0
- package/dist/components/markdown.d.ts +37 -4
- package/dist/components/message-timeline.d.ts +86 -7
- package/dist/components/model-mark.d.ts +29 -0
- package/dist/components/model-policy-picker-menu.d.ts +9 -0
- package/dist/components/model-policy-picker.d.ts +41 -25
- package/dist/components/new-conversation.d.ts +32 -0
- package/dist/components/open-geni-chat.d.ts +73 -0
- package/dist/components/open-geni-links.d.ts +68 -0
- package/dist/components/openrouter-mark.d.ts +6 -0
- package/dist/components/opper-mark.d.ts +7 -0
- package/dist/components/preview-loading.d.ts +2 -0
- package/dist/components/provider-recovery-notice.d.ts +16 -0
- package/dist/components/queue-item-content.d.ts +9 -0
- package/dist/components/rehype-secret-refs.d.ts +23 -0
- package/dist/components/remark-soft-line-breaks.d.ts +38 -0
- package/dist/components/sandbox-files.d.ts +3 -1
- package/dist/components/sandbox-workspace.d.ts +18 -0
- package/dist/components/secret-question-field.d.ts +31 -0
- package/dist/components/secret-ref-chip.d.ts +35 -0
- package/dist/components/session-capability-frame.d.ts +37 -0
- package/dist/components/session-chrome.d.ts +57 -8
- package/dist/components/session-commands-panel.d.ts +6 -0
- package/dist/components/session-commands.d.ts +6 -0
- package/dist/components/session-connection-request.d.ts +8 -0
- package/dist/components/session-conversation.d.ts +157 -0
- package/dist/components/session-list.d.ts +39 -0
- package/dist/components/session-mcp-capability-card.d.ts +41 -0
- package/dist/components/session-proxy-scope.d.ts +35 -0
- package/dist/components/session-status.d.ts +4 -1
- package/dist/components/session-usage-limit-notice.d.ts +39 -0
- package/dist/components/timeline-anchor.d.ts +24 -0
- package/dist/components/timeline-annotation-chrome.d.ts +25 -0
- package/dist/components/timeline-annotation-layout.d.ts +42 -0
- package/dist/components/timeline-annotation-markers.d.ts +8 -0
- package/dist/components/timeline-annotation-reveal-context.d.ts +4 -0
- package/dist/components/timeline-annotation-shared.d.ts +31 -0
- package/dist/components/timeline-annotations-dialog.d.ts +3 -2
- package/dist/components/timeline-annotations.d.ts +13 -3
- package/dist/components/timeline-reading-progress.d.ts +11 -0
- package/dist/components/timeline-search.d.ts +34 -0
- package/dist/components/timeline-settlement.d.ts +16 -0
- package/dist/components/tip-follow.d.ts +16 -0
- package/dist/components/tool-action-review.d.ts +47 -0
- package/dist/components/tool-review-history.d.ts +25 -0
- package/dist/components/usage-limit-notice.d.ts +49 -0
- package/dist/components/usage-member-list.d.ts +91 -0
- package/dist/components/usage-meter.d.ts +73 -0
- package/dist/components/use-viewer-menu-dismiss.d.ts +2 -0
- package/dist/components/user-message-body.d.ts +10 -1
- package/dist/composer-messages.d.ts +74 -0
- package/dist/composer-messages.js +9 -0
- package/dist/composer-messages.js.map +1 -0
- package/dist/composer-voice-events.d.ts +8 -0
- package/dist/composer.d.ts +5 -4
- package/dist/composer.js +26 -13
- package/dist/computer-viewer-GMLDXPON.js +20 -0
- package/dist/computer-viewer-GMLDXPON.js.map +1 -0
- package/dist/connect-account-identity.d.ts +8 -0
- package/dist/connect-accounts.d.ts +15 -0
- package/dist/connect-chooser.d.ts +18 -0
- package/dist/connect-panel.d.ts +18 -0
- package/dist/connect-setup.d.ts +18 -0
- package/dist/connect.d.ts +21 -0
- package/dist/connect.js +2149 -0
- package/dist/connect.js.map +1 -0
- package/dist/connection-catalog.d.ts +38 -0
- package/dist/connection-discovery.d.ts +12 -0
- package/dist/connection-installed.d.ts +17 -0
- package/dist/connection-logo.d.ts +9 -0
- package/dist/connection-service-logo.d.ts +8 -0
- package/dist/connection-service-presentation.d.ts +13 -0
- package/dist/connection-skeleton.d.ts +5 -0
- package/dist/connection-type-picker.d.ts +6 -0
- package/dist/conversation-timeline.d.ts +5 -0
- package/dist/desktop.d.ts +7 -0
- package/dist/desktop.js +27 -0
- package/dist/desktop.js.map +1 -0
- package/dist/device-authorization.d.ts +23 -0
- package/dist/diffs.d.ts +8 -0
- package/dist/diffs.js +15 -0
- package/dist/diffs.js.map +1 -0
- package/dist/discovery-cache.d.ts +5 -0
- package/dist/editor.d.ts +7 -0
- package/dist/editor.js +22 -0
- package/dist/editor.js.map +1 -0
- package/dist/embedded-new-voice-OCRNT6U5.js +18 -0
- package/dist/embedded-new-voice-OCRNT6U5.js.map +1 -0
- package/dist/embedded-voice-RPHPYVBT.js +18 -0
- package/dist/embedded-voice-RPHPYVBT.js.map +1 -0
- package/dist/{fleet-decision-row-GGCAT4E4.js → fleet-decision-row-SI63O5V4.js} +4 -1
- package/dist/fleet-decision-row-SI63O5V4.js.map +1 -0
- package/dist/hooks/latest-question.d.ts +25 -0
- package/dist/hooks/shared-machines-list.d.ts +4 -0
- package/dist/hooks/shared-query.d.ts +113 -0
- package/dist/hooks/use-attached-browsers.d.ts +1 -1
- package/dist/hooks/use-auth-runs.d.ts +1 -4
- package/dist/hooks/use-available-models.d.ts +1 -1
- package/dist/hooks/use-browser-frame-stream.d.ts +1 -1
- package/dist/hooks/use-browser-session.d.ts +12 -1
- package/dist/hooks/use-client-config-flags.d.ts +49 -0
- package/dist/hooks/use-codex-accounts.d.ts +59 -10
- package/dist/hooks/use-composer.d.ts +24 -2
- package/dist/hooks/use-computer-frame-stream.d.ts +1 -1
- package/dist/hooks/use-computer-input-posture.d.ts +13 -0
- package/dist/hooks/use-computer-session.d.ts +4 -0
- package/dist/hooks/use-connect.d.ts +23 -0
- package/dist/hooks/use-desktop-stream.d.ts +1 -1
- package/dist/hooks/use-file-attachments.d.ts +12 -0
- package/dist/hooks/use-goal.d.ts +2 -1
- package/dist/hooks/use-last-started-turn-policy.d.ts +5 -1
- package/dist/hooks/use-new-conversation.d.ts +56 -0
- package/dist/hooks/use-new-session-draft.d.ts +74 -0
- package/dist/hooks/use-realtime-voice-models.d.ts +7 -0
- package/dist/hooks/use-sandbox-files.d.ts +4 -1
- package/dist/hooks/use-session-conversation.d.ts +49 -0
- package/dist/hooks/use-session-events.d.ts +28 -7
- package/dist/hooks/use-session-mcp-approval-policy.d.ts +2 -2
- package/dist/hooks/use-session-model-account.d.ts +48 -0
- package/dist/hooks/use-supergrok-accounts.d.ts +7 -1
- package/dist/hooks/use-terminal-stream.d.ts +11 -5
- package/dist/hooks/use-usage.d.ts +40 -0
- package/dist/hooks/use-vaults.d.ts +53 -0
- package/dist/hooks/use-voice-input.d.ts +8 -1
- package/dist/hooks/use-workspace-edit.d.ts +4 -0
- package/dist/hooks/use-workspace-sessions.d.ts +22 -5
- package/dist/human-input-model.d.ts +122 -0
- package/dist/identity-link-accounts.d.ts +11 -0
- package/dist/identity-link-consent.d.ts +18 -0
- package/dist/index.d.ts +82 -20
- package/dist/index.js +2350 -6605
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +1 -1
- package/dist/interaction.js +14 -24
- package/dist/interaction.js.map +1 -1
- package/dist/latest-question-R77KWSUP.js +118 -0
- package/dist/latest-question-R77KWSUP.js.map +1 -0
- package/dist/lib/clipboard.d.ts +7 -0
- package/dist/lib/error-message.d.ts +7 -0
- package/dist/lib/format.d.ts +58 -4
- package/dist/lib/host-theme.d.ts +77 -0
- package/dist/lib/interaction-errors.d.ts +3 -0
- package/dist/lib/menu-styles.d.ts +22 -0
- package/dist/lib/notify-observer.d.ts +2 -0
- package/dist/lib/pierre-diffs-loader.d.ts +20 -0
- package/dist/lib/provider-recovery.d.ts +55 -0
- package/dist/lib/scrollbars.d.ts +11 -0
- package/dist/lib/terminal-input-readiness.d.ts +11 -0
- package/dist/lib/transient-retry.d.ts +25 -0
- package/dist/lib/workbench-peers.d.ts +36 -0
- package/dist/lib/xterm-theme.d.ts +7 -5
- package/dist/machines.js +9 -5
- package/dist/markdown-table-layout-3PV2UAAH.js +113 -0
- package/dist/markdown-table-layout-3PV2UAAH.js.map +1 -0
- package/dist/mcp-connection-status.d.ts +14 -0
- package/dist/model-mark-paths.d.ts +34 -0
- package/dist/model-mark-paths.js +16 -0
- package/dist/model-mark-paths.js.map +1 -0
- package/dist/model-picker-model.d.ts +111 -0
- package/dist/model-picker-model.js +25 -0
- package/dist/model-picker-model.js.map +1 -0
- package/dist/model-policy-picker-menu-7GJ5URPR.js +17 -0
- package/dist/model-policy-picker-menu-7GJ5URPR.js.map +1 -0
- package/dist/model-policy.d.ts +30 -3
- package/dist/model-policy.js +14 -1
- package/dist/native-previews.d.ts +28 -0
- package/dist/native-previews.js +82 -0
- package/dist/native-previews.js.map +1 -0
- package/dist/new-session-draft.d.ts +7 -0
- package/dist/new-session-draft.js +639 -0
- package/dist/new-session-draft.js.map +1 -0
- package/dist/notification-text.d.ts +27 -0
- package/dist/{noto-sans-arabic-loader-IA2T4X2A.js → noto-sans-arabic-loader-XSH2KYWJ.js} +3 -1
- package/dist/{noto-sans-arabic-loader-IA2T4X2A.js.map → noto-sans-arabic-loader-XSH2KYWJ.js.map} +1 -1
- package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js → noto-sans-jp-loader.generated-MR5L6V7K.js} +3 -1
- package/dist/{noto-sans-jp-loader.generated-HFGLYBR3.js.map → noto-sans-jp-loader.generated-MR5L6V7K.js.map} +1 -1
- package/dist/older-history.d.ts +3 -2
- package/dist/organization-model.d.ts +32 -0
- package/dist/organization-model.js +65 -0
- package/dist/organization-model.js.map +1 -0
- package/dist/{platform-activity-row-BJHUHXAT.js → platform-activity-row-5OCKOQWZ.js} +16 -78
- package/dist/platform-activity-row-5OCKOQWZ.js.map +1 -0
- package/dist/plugin-details.d.ts +14 -0
- package/dist/plugin-discovery.d.ts +25 -0
- package/dist/preview-loading-paint.d.ts +20 -0
- package/dist/provider.d.ts +25 -2
- package/dist/queue-presentation-model.d.ts +72 -0
- package/dist/queue-presentation-model.js +38 -0
- package/dist/queue-presentation-model.js.map +1 -0
- package/dist/{queue-surface-implementation-BSKUWHRI.js → queue-surface-implementation-GJBH7NKO.js} +65 -265
- package/dist/queue-surface-implementation-GJBH7NKO.js.map +1 -0
- package/dist/realtime/embedded-new-voice.d.ts +1 -0
- package/dist/realtime/embedded-voice.d.ts +1 -0
- package/dist/realtime/realtime-control.d.ts +4 -65
- package/dist/realtime/session-realtime.d.ts +78 -0
- package/dist/realtime.js +25 -1294
- package/dist/realtime.js.map +1 -1
- package/dist/sandbox-files-O27IPOZW.js +21 -0
- package/dist/sandbox-files-O27IPOZW.js.map +1 -0
- package/dist/sandbox-label-model.d.ts +30 -0
- package/dist/sandbox-label-model.js +20 -0
- package/dist/sandbox-label-model.js.map +1 -0
- package/dist/scrollbars.d.ts +1 -0
- package/dist/scrollbars.js +10 -0
- package/dist/scrollbars.js.map +1 -0
- package/dist/secret-reference-label.d.ts +14 -0
- package/dist/secrets-ui.d.ts +57 -0
- package/dist/session-agents-model.d.ts +10 -0
- package/dist/session-agents-model.js +25 -0
- package/dist/session-agents-model.js.map +1 -0
- package/dist/session-attention-model.d.ts +8 -0
- package/dist/session-attention-model.js +47 -0
- package/dist/session-attention-model.js.map +1 -0
- package/dist/session-auth-recommendation.d.ts +5 -0
- package/dist/session-capability-policy.d.ts +31 -0
- package/dist/session-feedback-model.d.ts +20 -0
- package/dist/session-feedback-model.js +56 -0
- package/dist/session-feedback-model.js.map +1 -0
- package/dist/session-list-model.d.ts +138 -0
- package/dist/session-list-model.js +274 -0
- package/dist/session-list-model.js.map +1 -0
- package/dist/session-realtime-7DJOIHQW.js +21 -0
- package/dist/session-realtime-7DJOIHQW.js.map +1 -0
- package/dist/session-realtime.d.ts +7 -0
- package/dist/session-realtime.js +19 -0
- package/dist/session-realtime.js.map +1 -0
- package/dist/session-status-model.d.ts +36 -0
- package/dist/session-ui.d.ts +39 -1
- package/dist/session-ui.js +126 -12
- package/dist/session-ui.js.map +1 -1
- package/dist/session.d.ts +8 -2
- package/dist/session.js +35 -20
- package/dist/site-snapshot.d.ts +14 -0
- package/dist/sites-ui.d.ts +24 -0
- package/dist/sites.d.ts +3 -0
- package/dist/sites.js +14 -0
- package/dist/sites.js.map +1 -0
- package/dist/skill-discovery.d.ts +47 -0
- package/dist/terminal.d.ts +8 -0
- package/dist/terminal.js +32 -0
- package/dist/terminal.js.map +1 -0
- package/dist/testing/session-scenarios.d.ts +10 -0
- package/dist/testing/session-scenarios.js +367 -0
- package/dist/testing/session-scenarios.js.map +1 -0
- package/dist/timeline/activity-rail.d.ts +11 -2
- package/dist/timeline/agent-identity.d.ts +30 -0
- package/dist/timeline/agent-row.d.ts +31 -0
- package/dist/timeline/allowance-exhausted-row.d.ts +24 -0
- package/dist/timeline/compaction-copy.d.ts +46 -0
- package/dist/timeline/entrance.d.ts +13 -1
- package/dist/timeline/focus-sequence.d.ts +9 -0
- package/dist/timeline/genie-copy.d.ts +3 -0
- package/dist/timeline/genie-loading.d.ts +47 -0
- package/dist/timeline/index.d.ts +10 -5
- package/dist/timeline/knowledge-receipt.d.ts +17 -0
- package/dist/timeline/message-sender.d.ts +19 -0
- package/dist/timeline/notice-presentation.d.ts +13 -0
- package/dist/timeline/parsers.d.ts +11 -0
- package/dist/timeline/patch-apply-command.d.ts +18 -0
- package/dist/timeline/platform-activity-presentation.d.ts +21 -0
- package/dist/timeline/platform-activity-row.d.ts +1 -2
- package/dist/timeline/presented-image.d.ts +11 -0
- package/dist/timeline/projection.d.ts +31 -2
- package/dist/timeline/provider-logo.d.ts +15 -0
- package/dist/timeline/question-nav-model.d.ts +23 -0
- package/dist/timeline/registry.d.ts +12 -1
- package/dist/timeline/retained-image.d.ts +18 -0
- package/dist/timeline/retained-loaders.d.ts +27 -0
- package/dist/timeline/rolling-activity.d.ts +11 -0
- package/dist/timeline/screenshot-lightbox.d.ts +23 -1
- package/dist/timeline/session-usage-limit-row.d.ts +11 -0
- package/dist/timeline/shared.d.ts +4 -1
- package/dist/timeline/startup-dispatch-details.d.ts +5 -0
- package/dist/timeline/startup-preference.d.ts +3 -0
- package/dist/timeline/startup-timings.d.ts +4 -0
- package/dist/timeline/tool-display-name.d.ts +5 -2
- package/dist/timeline/tool-presentation.d.ts +172 -0
- package/dist/timeline/turn-summary-model.d.ts +95 -0
- package/dist/timeline/turn-summary.d.ts +20 -54
- package/dist/timeline/types.d.ts +179 -7
- package/dist/timeline/work-presentation.d.ts +60 -0
- package/dist/timeline-annotation-markers-U3G66KUD.js +166 -0
- package/dist/timeline-annotation-markers-U3G66KUD.js.map +1 -0
- package/dist/timeline-annotation-selection-BI7QTPTZ.js +242 -0
- package/dist/timeline-annotation-selection-BI7QTPTZ.js.map +1 -0
- package/dist/timeline-annotations-dialog-FOMHSDTE.js +274 -0
- package/dist/timeline-annotations-dialog-FOMHSDTE.js.map +1 -0
- package/dist/timeline-model.d.ts +29 -0
- package/dist/timeline-model.js +254 -0
- package/dist/timeline-model.js.map +1 -0
- package/dist/usage/allowance-copy.d.ts +25 -0
- package/dist/usage/session-usage-limit.d.ts +88 -0
- package/dist/usage/summary.d.ts +35 -0
- package/dist/usage.d.ts +17 -0
- package/dist/usage.js +1187 -0
- package/dist/usage.js.map +1 -0
- package/dist/voice-recording-owner.d.ts +7 -0
- package/package.json +107 -7
- package/src/accounts.tsx +116 -14
- package/src/approvals.ts +8 -1
- package/src/artifacts-spreadsheet.ts +1 -0
- package/src/artifacts.ts +44 -0
- package/src/assets/README.md +8 -0
- package/src/assets/gmail.ico +0 -0
- package/src/assets/outlook.ico +0 -0
- package/src/capability-catalog-row.tsx +94 -0
- package/src/capability-logo-fallback.ts +22 -0
- package/src/client.ts +20 -31
- package/src/commands/registry.ts +18 -15
- package/src/commands/types.ts +2 -0
- package/src/components/anthropic-mark.tsx +10 -0
- package/src/components/approval-surface.tsx +401 -138
- package/src/components/artifacts/artifact-chrome.tsx +413 -0
- package/src/components/artifacts/artifact-sandbox.tsx +189 -0
- package/src/components/artifacts/artifact-surface.tsx +40 -20
- package/src/components/artifacts/chat-interactive-block.tsx +260 -0
- package/src/components/artifacts/deferred-chat-media.tsx +67 -0
- package/src/components/artifacts/document-editor.tsx +141 -16
- package/src/components/artifacts/editable-artifact-ui.tsx +4 -2
- package/src/components/artifacts/editable-artifact-view.tsx +197 -0
- package/src/components/artifacts/editable-artifact-workbench.tsx +5 -2
- package/src/components/artifacts/editable-document.tsx +37 -36
- package/src/components/artifacts/editable-presentation.tsx +36 -18
- package/src/components/artifacts/editable-spreadsheet.tsx +543 -103
- package/src/components/artifacts/index.ts +45 -0
- package/src/components/artifacts/inline-html-document.ts +24 -0
- package/src/components/artifacts/latest-task-runner.ts +49 -0
- package/src/components/artifacts/presentation-editor.tsx +323 -67
- package/src/components/artifacts/published-html-artifact-frame.tsx +122 -5
- package/src/components/artifacts/session-artifact-viewer.tsx +378 -0
- package/src/components/artifacts/site-view.tsx +121 -0
- package/src/components/artifacts/spreadsheet-canvas.ts +5 -10
- package/src/components/artifacts/spreadsheet-command-queue.ts +184 -0
- package/src/components/artifacts/spreadsheet-download.tsx +109 -0
- package/src/components/artifacts/spreadsheet-general-display.ts +43 -0
- package/src/components/artifacts/spreadsheet-grid.tsx +344 -144
- package/src/components/artifacts/spreadsheet-resize.tsx +421 -0
- package/src/components/artifacts/spreadsheet-submissions.tsx +46 -0
- package/src/components/artifacts/visualization-assets/base.css +1015 -0
- package/src/components/artifacts/visualization-assets/helpers.html +349 -0
- package/src/components/artifacts/visualization-assets.gen.ts +3 -0
- package/src/components/browser-select-control.tsx +312 -0
- package/src/components/browser-viewer.tsx +778 -318
- package/src/components/chat-composer.tsx +47 -5
- package/src/components/child-session-link.tsx +36 -0
- package/src/components/claude-mark.tsx +11 -0
- package/src/components/code-editor.tsx +13 -16
- package/src/components/composer-transcription-control.tsx +128 -30
- package/src/components/composer.tsx +404 -195
- package/src/components/computer-viewer.tsx +880 -253
- package/src/components/copy-button.tsx +13 -5
- package/src/components/desktop-viewer.tsx +1 -1
- package/src/components/enrollment-consent.tsx +1 -1
- package/src/components/enrollment-device-flow.tsx +1 -1
- package/src/components/file-browser.tsx +10 -14
- package/src/components/fleet-tile.tsx +9 -18
- package/src/components/generated-video-player.tsx +4 -2
- package/src/components/grok-mark.tsx +34 -0
- package/src/components/human-input-form.tsx +590 -283
- package/src/components/human-input-surface.tsx +11 -0
- package/src/components/interaction-intervention-banner.tsx +2 -2
- package/src/components/linkified-text.tsx +79 -0
- package/src/components/machine-card.tsx +35 -78
- package/src/components/machine-dock-bar.tsx +1 -1
- package/src/components/machine-input-display.ts +197 -3
- package/src/components/machines/agent-status-icon.tsx +31 -0
- package/src/components/machines/machine-detail.tsx +200 -31
- package/src/components/machines/machine-operation-policy.tsx +1 -1
- package/src/components/machines/machine-status.ts +241 -0
- package/src/components/machines-dashboard.tsx +2 -17
- package/src/components/markdown-table-layout.ts +143 -0
- package/src/components/markdown.tsx +466 -129
- package/src/components/message-timeline.tsx +2567 -728
- package/src/components/model-mark.tsx +102 -0
- package/src/components/model-picker.tsx +15 -4
- package/src/components/model-policy-picker-menu.tsx +371 -0
- package/src/components/model-policy-picker.tsx +256 -412
- package/src/components/new-conversation.tsx +221 -0
- package/src/components/open-geni-chat.tsx +309 -0
- package/src/components/open-geni-links.tsx +179 -0
- package/src/components/openrouter-mark.tsx +13 -0
- package/src/components/opper-mark.tsx +18 -0
- package/src/components/pierre-diff.tsx +41 -16
- package/src/components/pierre-file.tsx +35 -10
- package/src/components/preview-loading.tsx +109 -0
- package/src/components/provider-recovery-notice.tsx +49 -0
- package/src/components/queue-item-content.ts +14 -0
- package/src/components/queue-surface-implementation.tsx +51 -356
- package/src/components/queue-surface-state.tsx +3 -1
- package/src/components/rehype-secret-refs.ts +98 -0
- package/src/components/remark-soft-line-breaks.ts +166 -0
- package/src/components/sandbox-files.tsx +91 -15
- package/src/components/sandbox-terminal.tsx +84 -13
- package/src/components/sandbox-workspace.tsx +147 -51
- package/src/components/secret-question-field.tsx +324 -0
- package/src/components/secret-ref-chip.tsx +185 -0
- package/src/components/session-capability-frame.tsx +197 -0
- package/src/components/session-chrome.tsx +842 -251
- package/src/components/session-commands-panel.tsx +126 -0
- package/src/components/session-commands.tsx +18 -0
- package/src/components/session-connection-request.tsx +97 -0
- package/src/components/session-conversation.tsx +876 -0
- package/src/components/session-list.tsx +257 -0
- package/src/components/session-mcp-capability-card.tsx +680 -0
- package/src/components/session-proxy-scope.tsx +153 -0
- package/src/components/session-status.tsx +26 -17
- package/src/components/session-usage-limit-notice.tsx +174 -0
- package/src/components/timeline-anchor.tsx +119 -0
- package/src/components/timeline-annotation-chrome.tsx +196 -0
- package/src/components/timeline-annotation-layout.ts +268 -0
- package/src/components/timeline-annotation-markers.tsx +185 -0
- package/src/components/timeline-annotation-reveal-context.ts +9 -0
- package/src/components/timeline-annotation-selection.tsx +106 -54
- package/src/components/timeline-annotation-shared.ts +245 -0
- package/src/components/timeline-annotations-dialog.tsx +185 -128
- package/src/components/timeline-annotations.tsx +189 -45
- package/src/components/timeline-reading-progress.ts +80 -0
- package/src/components/timeline-search.tsx +168 -0
- package/src/components/timeline-settlement.ts +60 -0
- package/src/components/tip-follow.ts +52 -0
- package/src/components/tool-action-review.tsx +650 -0
- package/src/components/tool-review-history.tsx +88 -0
- package/src/components/tooltip.tsx +38 -5
- package/src/components/usage-limit-notice.tsx +179 -0
- package/src/components/usage-member-list.tsx +792 -0
- package/src/components/usage-meter.tsx +382 -0
- package/src/components/use-viewer-menu-dismiss.ts +33 -0
- package/src/components/user-message-body.tsx +27 -2
- package/src/components/workspace-dock.tsx +18 -1
- package/src/composer-messages.ts +150 -0
- package/src/composer-voice-events.ts +12 -0
- package/src/composer.ts +6 -1
- package/src/connect-account-identity.tsx +34 -0
- package/src/connect-accounts.tsx +233 -0
- package/src/connect-chooser.tsx +336 -0
- package/src/connect-panel.tsx +99 -0
- package/src/connect-setup.tsx +355 -0
- package/src/connect.ts +60 -0
- package/src/connection-assets.d.ts +4 -0
- package/src/connection-catalog.tsx +162 -0
- package/src/connection-discovery.tsx +238 -0
- package/src/connection-installed.tsx +51 -0
- package/src/connection-logo.tsx +37 -0
- package/src/connection-service-logo.tsx +55 -0
- package/src/connection-service-presentation.ts +46 -0
- package/src/connection-skeleton.tsx +16 -0
- package/src/connection-type-picker.tsx +36 -0
- package/src/conversation-timeline.ts +92 -0
- package/src/desktop.ts +24 -0
- package/src/device-authorization.tsx +98 -0
- package/src/diffs.ts +16 -0
- package/src/discovery-cache.ts +42 -0
- package/src/editor.ts +13 -0
- package/src/hooks/internal.ts +3 -2
- package/src/hooks/latest-question.ts +164 -0
- package/src/hooks/shared-machines-list.ts +34 -1
- package/src/hooks/shared-query.ts +721 -0
- package/src/hooks/use-attached-browsers.ts +1 -1
- package/src/hooks/use-auth-runs.ts +0 -27
- package/src/hooks/use-available-models.ts +21 -15
- package/src/hooks/use-browser-frame-stream.ts +19 -2
- package/src/hooks/use-browser-session.ts +680 -205
- package/src/hooks/use-browser-sessions.ts +32 -1
- package/src/hooks/use-client-config-flags.ts +118 -0
- package/src/hooks/use-codex-accounts.ts +247 -47
- package/src/hooks/use-composer.ts +276 -47
- package/src/hooks/use-computer-frame-stream.ts +25 -7
- package/src/hooks/use-computer-input-posture.ts +94 -0
- package/src/hooks/use-computer-session.ts +409 -95
- package/src/hooks/use-connect.ts +23 -0
- package/src/hooks/use-desktop-stream.ts +19 -28
- package/src/hooks/use-file-attachments.ts +80 -4
- package/src/hooks/use-goal.ts +107 -13
- package/src/hooks/use-last-started-turn-policy.ts +46 -14
- package/src/hooks/use-machines.ts +4 -0
- package/src/hooks/use-new-conversation.ts +374 -0
- package/src/hooks/use-new-session-draft.ts +813 -0
- package/src/hooks/use-realtime-voice-models.ts +46 -0
- package/src/hooks/use-sandbox-files.ts +68 -20
- package/src/hooks/use-session-background-commands.ts +22 -11
- package/src/hooks/use-session-capabilities.ts +3 -2
- package/src/hooks/use-session-conversation.ts +150 -0
- package/src/hooks/use-session-events.ts +703 -382
- package/src/hooks/use-session-lineage.ts +20 -16
- package/src/hooks/use-session-mcp-approval-policy.ts +13 -5
- package/src/hooks/use-session-model-account.ts +174 -0
- package/src/hooks/use-session.ts +53 -21
- package/src/hooks/use-slash-commands.ts +6 -11
- package/src/hooks/use-supergrok-accounts.ts +22 -8
- package/src/hooks/use-terminal-stream.ts +153 -27
- package/src/hooks/use-transcription.ts +6 -0
- package/src/hooks/use-turn-queue.ts +2 -1
- package/src/hooks/use-usage.ts +100 -0
- package/src/hooks/use-vaults.ts +153 -0
- package/src/hooks/use-video-artifact-playback.ts +1 -1
- package/src/hooks/use-voice-input.ts +127 -19
- package/src/hooks/use-workspace-capture.ts +3 -2
- package/src/hooks/use-workspace-edit.ts +23 -5
- package/src/hooks/use-workspace-sessions.ts +97 -32
- package/src/human-input-model.ts +359 -0
- package/src/identity-link-accounts.tsx +148 -0
- package/src/identity-link-consent.tsx +192 -0
- package/src/index.ts +262 -16
- package/src/interaction.ts +1 -0
- package/src/lib/clipboard.ts +29 -0
- package/src/lib/error-message.ts +37 -0
- package/src/lib/format.ts +213 -16
- package/src/lib/host-theme.ts +529 -0
- package/src/lib/interaction-errors.ts +15 -0
- package/src/lib/menu-styles.ts +44 -0
- package/src/lib/notify-observer.ts +15 -0
- package/src/lib/pierre-diffs-loader.ts +53 -0
- package/src/lib/provider-recovery.ts +243 -0
- package/src/lib/scrollbars.ts +30 -0
- package/src/lib/terminal-input-readiness.ts +51 -0
- package/src/lib/transient-retry.ts +99 -0
- package/src/lib/workbench-peers.ts +62 -0
- package/src/lib/xterm-theme.ts +23 -21
- package/src/mcp-connection-status.ts +35 -0
- package/src/model-mark-paths.ts +53 -0
- package/src/model-picker-model.ts +330 -0
- package/src/model-policy.ts +299 -74
- package/src/native-previews.ts +100 -0
- package/src/new-session-draft.ts +22 -0
- package/src/notification-text.ts +81 -0
- package/src/older-history.ts +22 -4
- package/src/organization-model.ts +118 -0
- package/src/plugin-details.tsx +185 -0
- package/src/plugin-discovery.tsx +196 -0
- package/src/preview-loading-paint.ts +68 -0
- package/src/provider.tsx +74 -15
- package/src/queue-presentation-model.ts +464 -0
- package/src/realtime/dropdown-menu.tsx +19 -12
- package/src/realtime/embedded-new-voice.tsx +1 -0
- package/src/realtime/embedded-voice.tsx +3 -0
- package/src/realtime/realtime-control.tsx +138 -501
- package/src/realtime/session-realtime.ts +566 -0
- package/src/sandbox-label-model.ts +53 -0
- package/src/scrollbars.ts +3 -0
- package/src/secret-reference-label.ts +43 -0
- package/src/secrets-ui.tsx +59 -0
- package/src/session-agents-model.ts +33 -0
- package/src/session-attention-model.ts +62 -0
- package/src/session-auth-recommendation.ts +48 -0
- package/src/session-capability-policy.ts +196 -0
- package/src/session-context.ts +3 -4
- package/src/session-feedback-model.ts +95 -0
- package/src/session-list-model.ts +434 -0
- package/src/session-realtime.ts +18 -0
- package/src/session-status-model.ts +99 -0
- package/src/session-ui.ts +98 -1
- package/src/session.ts +17 -1
- package/src/site-snapshot.ts +74 -0
- package/src/sites-ui.tsx +348 -0
- package/src/sites.ts +3 -0
- package/src/skill-discovery.tsx +208 -0
- package/src/terminal.ts +26 -0
- package/src/testing/session-scenarios.ts +404 -0
- package/src/timeline/activity-rail.tsx +242 -101
- package/src/timeline/agent-identity.tsx +84 -0
- package/src/timeline/agent-row.tsx +231 -0
- package/src/timeline/allowance-exhausted-row.tsx +86 -0
- package/src/timeline/compaction-copy.ts +115 -0
- package/src/timeline/entrance.tsx +28 -3
- package/src/timeline/fleet-decision-projection.ts +2 -1
- package/src/timeline/fleet-decision-row.tsx +1 -0
- package/src/timeline/focus-sequence.ts +44 -0
- package/src/timeline/genie-copy.ts +19 -0
- package/src/timeline/genie-loading.tsx +143 -0
- package/src/timeline/index.ts +30 -2
- package/src/timeline/knowledge-receipt.tsx +100 -0
- package/src/timeline/message-sender.tsx +71 -0
- package/src/timeline/notice-presentation.ts +48 -0
- package/src/timeline/parsers.ts +42 -2
- package/src/timeline/patch-apply-command.tsx +135 -0
- package/src/timeline/platform-activity-presentation.ts +166 -0
- package/src/timeline/platform-activity-row.tsx +15 -90
- package/src/timeline/presented-image.ts +63 -0
- package/src/timeline/projection.ts +1746 -76
- package/src/timeline/provider-logo.tsx +53 -0
- package/src/timeline/question-nav-model.ts +28 -0
- package/src/timeline/registry.ts +26 -4
- package/src/timeline/retained-image.ts +97 -0
- package/src/timeline/retained-loaders.ts +91 -0
- package/src/timeline/rolling-activity.tsx +80 -0
- package/src/timeline/screenshot-lightbox.tsx +228 -5
- package/src/timeline/session-usage-limit-row.tsx +42 -0
- package/src/timeline/shared.tsx +42 -15
- package/src/timeline/startup-dispatch-details.tsx +36 -0
- package/src/timeline/startup-preference.ts +33 -0
- package/src/timeline/startup-timings.tsx +145 -0
- package/src/timeline/tool-display-name.ts +19 -7
- package/src/timeline/tool-presentation.ts +1316 -0
- package/src/timeline/tool-renderers.tsx +835 -1138
- package/src/timeline/turn-summary-model.ts +343 -0
- package/src/timeline/turn-summary.tsx +272 -365
- package/src/timeline/types.ts +189 -6
- package/src/timeline/work-presentation.ts +189 -0
- package/src/timeline-model.ts +143 -0
- package/src/usage/allowance-copy.ts +64 -0
- package/src/usage/session-usage-limit.ts +266 -0
- package/src/usage/summary.ts +93 -0
- package/src/usage.ts +70 -0
- package/src/voice-recording-owner.ts +22 -0
- package/styles/compiled.css +2319 -748
- package/styles/connect.css +290 -0
- package/styles/connect.d.ts +1 -0
- package/styles/effective-tokens.css +1 -0
- package/styles/index.css +309 -9
- package/styles/responsive.css +99 -7
- package/styles/session-capability.css +44 -0
- package/styles/tokens.css +134 -66
- package/dist/browser-viewer-AEFHUYZK.js +0 -14
- package/dist/chunk-4MDLEHXL.js.map +0 -1
- package/dist/chunk-5YK2ZDIG.js.map +0 -1
- package/dist/chunk-D3UIAJKY.js.map +0 -1
- package/dist/chunk-ELIYWBZR.js.map +0 -1
- package/dist/chunk-H5YGYNXS.js.map +0 -1
- package/dist/chunk-HQVCKCLG.js +0 -9458
- package/dist/chunk-HQVCKCLG.js.map +0 -1
- package/dist/chunk-HUIKIDM5.js.map +0 -1
- package/dist/chunk-ICA6R6TW.js.map +0 -1
- package/dist/chunk-JVYQRRC4.js.map +0 -1
- package/dist/chunk-KCGXPP5V.js.map +0 -1
- package/dist/chunk-KZ73RL76.js.map +0 -1
- package/dist/chunk-LAKLF4PA.js +0 -141
- package/dist/chunk-LAKLF4PA.js.map +0 -1
- package/dist/chunk-MZ2G2XBA.js.map +0 -1
- package/dist/chunk-NEXLLTP4.js.map +0 -1
- package/dist/chunk-OAM4WAAM.js.map +0 -1
- package/dist/chunk-PEI6RANI.js.map +0 -1
- package/dist/chunk-RGALCTWO.js.map +0 -1
- package/dist/chunk-W6NFJXSA.js +0 -454
- package/dist/chunk-W6NFJXSA.js.map +0 -1
- package/dist/chunk-WSK7G5LE.js +0 -170
- package/dist/chunk-WSK7G5LE.js.map +0 -1
- package/dist/chunk-X4A66EWZ.js +0 -223
- package/dist/chunk-X4A66EWZ.js.map +0 -1
- package/dist/chunk-XXWH2JLC.js.map +0 -1
- package/dist/chunk-YFKJ7VZH.js +0 -268
- package/dist/chunk-YFKJ7VZH.js.map +0 -1
- package/dist/chunk-YJFGAFBD.js.map +0 -1
- package/dist/computer-viewer-FWEZISW4.js +0 -16
- package/dist/fleet-decision-row-GGCAT4E4.js.map +0 -1
- package/dist/hooks/use-environments.d.ts +0 -41
- package/dist/hooks/use-packs.d.ts +0 -32
- package/dist/platform-activity-row-BJHUHXAT.js.map +0 -1
- package/dist/queue-surface-implementation-BSKUWHRI.js.map +0 -1
- package/dist/timeline-annotation-selection-3L7LHGG2.js +0 -187
- package/dist/timeline-annotation-selection-3L7LHGG2.js.map +0 -1
- package/dist/timeline-annotations-dialog-U7EVLR75.js +0 -196
- package/dist/timeline-annotations-dialog-U7EVLR75.js.map +0 -1
- package/src/hooks/use-environments.ts +0 -182
- package/src/hooks/use-packs.ts +0 -157
- /package/dist/{browser-viewer-AEFHUYZK.js.map → browser-viewer-ZWR6MWJ7.js.map} +0 -0
- /package/dist/{computer-viewer-FWEZISW4.js.map → chat-interactive-block-BO2IWXVK.js.map} +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
2
|
ComputerAction,
|
|
3
|
+
ComputerActionReceipt,
|
|
3
4
|
ComputerClipboard,
|
|
4
5
|
ComputerFrame,
|
|
5
6
|
ComputerObservation,
|
|
@@ -25,6 +26,7 @@ import {
|
|
|
25
26
|
} from "lucide-react";
|
|
26
27
|
import {
|
|
27
28
|
type ClipboardEvent,
|
|
29
|
+
type CompositionEvent,
|
|
28
30
|
type KeyboardEvent,
|
|
29
31
|
type MouseEvent,
|
|
30
32
|
type PointerEvent,
|
|
@@ -32,6 +34,7 @@ import {
|
|
|
32
34
|
type WheelEvent,
|
|
33
35
|
useCallback,
|
|
34
36
|
useEffect,
|
|
37
|
+
useLayoutEffect,
|
|
35
38
|
useMemo,
|
|
36
39
|
useRef,
|
|
37
40
|
useState,
|
|
@@ -40,7 +43,12 @@ import {
|
|
|
40
43
|
type ComputerFrameWebSocketFactory,
|
|
41
44
|
useComputerFrameStream,
|
|
42
45
|
} from "../hooks/use-computer-frame-stream";
|
|
43
|
-
import {
|
|
46
|
+
import {
|
|
47
|
+
computerActionReceiptError,
|
|
48
|
+
isStaleComputerActionResult,
|
|
49
|
+
useComputerSession,
|
|
50
|
+
} from "../hooks/use-computer-session";
|
|
51
|
+
import { useComputerInputPosture } from "../hooks/use-computer-input-posture";
|
|
44
52
|
import { useComputerSessions } from "../hooks/use-computer-sessions";
|
|
45
53
|
import { useInteractionInterventions } from "../hooks/use-interaction-interventions";
|
|
46
54
|
import { cn } from "../lib/cn";
|
|
@@ -49,11 +57,12 @@ import { isSourcePlacementChangedError } from "../lib/interaction-errors";
|
|
|
49
57
|
import type { EmbeddedComputerInteractionClientOverride } from "../session-context";
|
|
50
58
|
import { InteractionInterventionBanner } from "./interaction-intervention-banner";
|
|
51
59
|
import { DesktopViewer } from "./desktop-viewer";
|
|
60
|
+
import { useViewerMenuDismiss } from "./use-viewer-menu-dismiss";
|
|
52
61
|
|
|
53
62
|
export type ComputerViewerNotification = { kind: "error" | "info"; message: string };
|
|
54
63
|
|
|
55
64
|
export type ComputerViewerProps = EmbeddedComputerInteractionClientOverride & {
|
|
56
|
-
/** Selected
|
|
65
|
+
/** Selected Opengeni agent/session. Peer ComputerSessions remain visible. */
|
|
57
66
|
sessionId: string;
|
|
58
67
|
enabled?: boolean | undefined;
|
|
59
68
|
className?: string | undefined;
|
|
@@ -170,6 +179,7 @@ export function ComputerViewer({
|
|
|
170
179
|
|
|
171
180
|
const notifyError = useCallback(
|
|
172
181
|
(cause: unknown, fallback: string) => {
|
|
182
|
+
if (isStaleComputerActionResult(cause)) return;
|
|
173
183
|
onNotify?.({
|
|
174
184
|
kind: "error",
|
|
175
185
|
message: cause instanceof Error ? cause.message : fallback,
|
|
@@ -253,6 +263,9 @@ export function ComputerViewer({
|
|
|
253
263
|
computerSessionId: selection?.sessionId ?? null,
|
|
254
264
|
enabled: enabled && selection !== null && controllerReady,
|
|
255
265
|
});
|
|
266
|
+
useEffect(() => {
|
|
267
|
+
setShowControls(computer.selectedTarget?.kind === "app");
|
|
268
|
+
}, [sessionId, selection?.sessionId, computer.selectedTarget?.id, computer.selectedTarget?.kind]);
|
|
256
269
|
useEffect(() => {
|
|
257
270
|
if (!isSourcePlacementChangedError(computer.error, "computer_session")) return;
|
|
258
271
|
void refreshRegistry();
|
|
@@ -282,18 +295,69 @@ export function ComputerViewer({
|
|
|
282
295
|
const displayedFrame =
|
|
283
296
|
frames.frame &&
|
|
284
297
|
frames.frame.computerSessionId === selection?.sessionId &&
|
|
285
|
-
frames.frame.targetId === computer.selectedTarget?.id
|
|
298
|
+
frames.frame.targetId === computer.selectedTarget?.id &&
|
|
299
|
+
frames.frame.targetGeneration === computer.selectedTarget?.targetGeneration
|
|
286
300
|
? frames.frame
|
|
287
301
|
: null;
|
|
288
302
|
const rfbStream =
|
|
289
303
|
frames.attachment?.stream.kind === "direct_rfb" ? frames.attachment.stream : null;
|
|
304
|
+
const appTarget = computer.selectedTarget?.kind === "app";
|
|
305
|
+
const appInputPosture = useComputerInputPosture({
|
|
306
|
+
...override,
|
|
307
|
+
computerSessionId: selection?.sessionId ?? null,
|
|
308
|
+
controllerGeneration: computer.session?.controller?.controllerGeneration ?? null,
|
|
309
|
+
enabled: enabled && controllerReady && appTarget,
|
|
310
|
+
});
|
|
311
|
+
const inputPostureRef = useRef({
|
|
312
|
+
sessionId: selection?.sessionId ?? null,
|
|
313
|
+
controllerGeneration: computer.session?.controller?.controllerGeneration ?? null,
|
|
314
|
+
denied: false,
|
|
315
|
+
});
|
|
316
|
+
const postureSessionId = selection?.sessionId ?? null;
|
|
317
|
+
const postureController = computer.session?.controller?.controllerGeneration ?? null;
|
|
318
|
+
if (
|
|
319
|
+
inputPostureRef.current.sessionId !== postureSessionId ||
|
|
320
|
+
inputPostureRef.current.controllerGeneration !== postureController
|
|
321
|
+
) {
|
|
322
|
+
inputPostureRef.current = {
|
|
323
|
+
sessionId: postureSessionId,
|
|
324
|
+
controllerGeneration: postureController,
|
|
325
|
+
denied: false,
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
if (
|
|
329
|
+
frames.attachment?.computerSessionId === postureSessionId &&
|
|
330
|
+
frames.attachment.controllerGeneration === postureController
|
|
331
|
+
) {
|
|
332
|
+
inputPostureRef.current.denied = frames.attachment.inputAllowed === false;
|
|
333
|
+
}
|
|
334
|
+
// Keep a known denial while a fresh attachment is pending or a semantic-only
|
|
335
|
+
// target has no stream. Only a fresh posture for this controller clears it.
|
|
336
|
+
const inputDenied = appTarget
|
|
337
|
+
? appInputPosture.inputAllowed !== true
|
|
338
|
+
: inputPostureRef.current.denied;
|
|
339
|
+
const attachmentInputAllowed = frames.attachment !== null && !inputDenied;
|
|
340
|
+
const inputPosturePending = appTarget
|
|
341
|
+
? appInputPosture.pending
|
|
342
|
+
: inputDenied && (frames.state === "attaching" || frames.state === "reconnecting");
|
|
343
|
+
const machineLocked = selectedRegistrySession?.failureCode === "machine_locked";
|
|
344
|
+
const controlUnavailable = computer.controlError !== null;
|
|
345
|
+
const connectionState = controlUnavailable ? "error" : frames.state;
|
|
346
|
+
// RFB has one combined input switch; partial native input stays view-only.
|
|
347
|
+
const rfbInputEnabled =
|
|
348
|
+
rfbStream?.inputAllowed === true &&
|
|
349
|
+
attachmentInputAllowed &&
|
|
350
|
+
!machineLocked &&
|
|
351
|
+
!controlUnavailable &&
|
|
352
|
+
computer.session?.capabilities?.pointerInput === true &&
|
|
353
|
+
computer.session?.capabilities?.keyboardInput === true;
|
|
290
354
|
const rfbCapability = useMemo<DesktopStreamCapability | null>(() => {
|
|
291
355
|
if (!rfbStream || !frames.attachment) return null;
|
|
292
356
|
const bounds = computer.selectedTarget?.bounds;
|
|
293
357
|
return {
|
|
294
358
|
transport: "vnc-ws",
|
|
295
359
|
client: "novnc",
|
|
296
|
-
mode: "interactive",
|
|
360
|
+
mode: rfbInputEnabled ? "interactive" : "read-only",
|
|
297
361
|
url: rfbStream.url,
|
|
298
362
|
token: null,
|
|
299
363
|
expiresAt: frames.attachment.expiresAt,
|
|
@@ -305,8 +369,7 @@ export function ComputerViewer({
|
|
|
305
369
|
sharedSessionIds: [],
|
|
306
370
|
reason: null,
|
|
307
371
|
};
|
|
308
|
-
}, [computer.selectedTarget?.bounds, frames.attachment, rfbStream]);
|
|
309
|
-
const machineLocked = selectedRegistrySession?.failureCode === "machine_locked";
|
|
372
|
+
}, [computer.selectedTarget?.bounds, frames.attachment, rfbInputEnabled, rfbStream]);
|
|
310
373
|
const currentIds = useMemo(() => new Set(relevant.map((session) => session.id)), [relevant]);
|
|
311
374
|
|
|
312
375
|
useEffect(() => {
|
|
@@ -433,37 +496,57 @@ export function ComputerViewer({
|
|
|
433
496
|
]);
|
|
434
497
|
|
|
435
498
|
const perform = useCallback(
|
|
436
|
-
async (
|
|
499
|
+
async (
|
|
500
|
+
action: ComputerAction,
|
|
501
|
+
frame: ComputerFrame | null,
|
|
502
|
+
operationId?: string,
|
|
503
|
+
): Promise<ComputerActionReceipt> => {
|
|
504
|
+
if (computer.controlError) throw computer.controlError;
|
|
505
|
+
if (inputDenied) throw new Error("Desktop is view only. Refresh to check desktop controls.");
|
|
506
|
+
let receipt;
|
|
437
507
|
if (action.type === "pointer") {
|
|
438
508
|
if (!frame) throw new Error("Desktop view is not ready for pointer input.");
|
|
439
|
-
await actFromFrame(action, frame);
|
|
509
|
+
receipt = await actFromFrame(action, frame, operationId);
|
|
440
510
|
} else {
|
|
441
|
-
await act(action);
|
|
511
|
+
receipt = await act(action, operationId);
|
|
442
512
|
}
|
|
513
|
+
if (!isStaleComputerActionResult(receipt) && receipt.state !== "completed") {
|
|
514
|
+
throw computerActionReceiptError(receipt);
|
|
515
|
+
}
|
|
516
|
+
return receipt;
|
|
443
517
|
},
|
|
444
|
-
[act, actFromFrame],
|
|
518
|
+
[act, actFromFrame, computer.controlError, inputDenied],
|
|
445
519
|
);
|
|
446
520
|
|
|
521
|
+
const reconnect = () => {
|
|
522
|
+
void refreshComputer();
|
|
523
|
+
if (appTarget) appInputPosture.refresh();
|
|
524
|
+
else frames.reconnect();
|
|
525
|
+
};
|
|
526
|
+
|
|
447
527
|
const copyFromRfb = useCallback(
|
|
448
528
|
(event: ClipboardEvent<HTMLDivElement>) => {
|
|
449
|
-
if (!rfbStream || computer.session?.capabilities?.clipboard !== true)
|
|
529
|
+
if (!rfbInputEnabled || !rfbStream || computer.session?.capabilities?.clipboard !== true)
|
|
530
|
+
return;
|
|
450
531
|
event.preventDefault();
|
|
451
532
|
event.stopPropagation();
|
|
452
|
-
void
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
533
|
+
void (async () => {
|
|
534
|
+
const receipt = await perform({ type: "clipboard", operation: "copy" }, null);
|
|
535
|
+
if (isStaleComputerActionResult(receipt)) return;
|
|
536
|
+
const clipboard = await computer.readClipboard();
|
|
537
|
+
if (isStaleComputerActionResult(receipt) || isStaleComputerActionResult(clipboard)) return;
|
|
538
|
+
if (clipboard.text && !(await copyTextToClipboard(clipboard.text))) {
|
|
539
|
+
throw new Error("Desktop text could not be copied to the local clipboard");
|
|
540
|
+
}
|
|
541
|
+
})().catch((cause) => notifyError(cause, "Could not copy from the desktop."));
|
|
460
542
|
},
|
|
461
|
-
[computer, notifyError, perform, rfbStream],
|
|
543
|
+
[computer, notifyError, perform, rfbInputEnabled, rfbStream],
|
|
462
544
|
);
|
|
463
545
|
|
|
464
546
|
const pasteIntoRfb = useCallback(
|
|
465
547
|
(text: string): boolean => {
|
|
466
|
-
if (!rfbStream || computer.session?.capabilities?.clipboard !== true)
|
|
548
|
+
if (!rfbInputEnabled || !rfbStream || computer.session?.capabilities?.clipboard !== true)
|
|
549
|
+
return false;
|
|
467
550
|
// Keep paste on the canonical ComputerSession action path. RFB
|
|
468
551
|
// ClientCutText synchronization varies by server and can acknowledge a
|
|
469
552
|
// local paste without ever updating the remote graphical seat. These two
|
|
@@ -472,13 +555,16 @@ export function ComputerViewer({
|
|
|
472
555
|
// ownership is asynchronous, so a successful write response alone does
|
|
473
556
|
// not prove that the graphical seat can already serve the selection.
|
|
474
557
|
void (async () => {
|
|
475
|
-
await perform({ type: "clipboard", operation: "write", text }, null);
|
|
476
|
-
|
|
558
|
+
const receipt = await perform({ type: "clipboard", operation: "write", text }, null);
|
|
559
|
+
if (isStaleComputerActionResult(receipt)) return;
|
|
560
|
+
const clipboard = await computer.readClipboard();
|
|
561
|
+
if (isStaleComputerActionResult(receipt) || isStaleComputerActionResult(clipboard)) return;
|
|
562
|
+
assertExactComputerClipboard(clipboard, text);
|
|
477
563
|
await perform({ type: "clipboard", operation: "paste" }, null);
|
|
478
564
|
})().catch((cause) => notifyError(cause, "Could not paste into the desktop."));
|
|
479
565
|
return true;
|
|
480
566
|
},
|
|
481
|
-
[computer, notifyError, perform, rfbStream],
|
|
567
|
+
[computer, notifyError, perform, rfbInputEnabled, rfbStream],
|
|
482
568
|
);
|
|
483
569
|
|
|
484
570
|
const hideGenericCreate = suppressGenericCreate && liveRelevant.length === 0;
|
|
@@ -501,11 +587,11 @@ export function ComputerViewer({
|
|
|
501
587
|
return (
|
|
502
588
|
<div className={cn("grid h-full place-items-center bg-og-bg p-6", className)}>
|
|
503
589
|
<div className="max-w-sm text-center">
|
|
504
|
-
<CircleAlertIcon className="mx-auto size-5 text-og-
|
|
590
|
+
<CircleAlertIcon className="mx-auto size-5 text-og-danger" />
|
|
505
591
|
<p className="mt-3 text-og-menu font-medium text-og-fg">
|
|
506
592
|
Chrome reconnected—open a fresh browser/desktop.
|
|
507
593
|
</p>
|
|
508
|
-
<p className="mt-1 text-og-control leading-5 text-og-muted">
|
|
594
|
+
<p className="mt-1 text-og-control leading-5 text-og-fg-muted">
|
|
509
595
|
This Chrome profile is live again, but the previous desktop cannot move to the new
|
|
510
596
|
connection. Use Browser → New browser → Connected Chrome.
|
|
511
597
|
</p>
|
|
@@ -518,9 +604,9 @@ export function ComputerViewer({
|
|
|
518
604
|
return (
|
|
519
605
|
<div className={cn("grid h-full place-items-center bg-og-bg p-6", className)}>
|
|
520
606
|
<div className="max-w-sm text-center">
|
|
521
|
-
<span className="mx-auto grid size-10 place-items-center rounded-og-lg border border-og-border bg-og-surface-1 text-og-muted">
|
|
607
|
+
<span className="mx-auto grid size-10 place-items-center rounded-og-lg border border-og-border bg-og-surface-1 text-og-fg-muted">
|
|
522
608
|
{error ? (
|
|
523
|
-
<CircleAlertIcon className="size-5 text-og-
|
|
609
|
+
<CircleAlertIcon className="size-5 text-og-danger" />
|
|
524
610
|
) : (
|
|
525
611
|
<LoaderCircleIcon className="size-5 animate-spin" />
|
|
526
612
|
)}
|
|
@@ -528,7 +614,7 @@ export function ComputerViewer({
|
|
|
528
614
|
<p className="mt-3 text-og-menu font-medium text-og-fg">
|
|
529
615
|
{error ? "Desktop didn’t open" : "Opening desktop…"}
|
|
530
616
|
</p>
|
|
531
|
-
<p className="mt-1 text-og-control leading-5 text-og-muted">
|
|
617
|
+
<p className="mt-1 text-og-control leading-5 text-og-fg-muted">
|
|
532
618
|
{error?.message ?? "Preparing this agent’s desktop. It will appear here when ready."}
|
|
533
619
|
</p>
|
|
534
620
|
{error ? (
|
|
@@ -536,7 +622,7 @@ export function ComputerViewer({
|
|
|
536
622
|
type="button"
|
|
537
623
|
onClick={createError ? createComputer : () => void refreshRegistry()}
|
|
538
624
|
disabled={creating || registry.refreshing}
|
|
539
|
-
className="mt-4 inline-flex h-8 items-center gap-1.5 rounded-og-sm bg-og-
|
|
625
|
+
className="mt-4 inline-flex h-8 items-center gap-1.5 rounded-og-sm border border-og-primary-border bg-og-primary text-og-primary-fg px-3 text-og-control font-medium transition hover:bg-og-primary-hover disabled:opacity-50"
|
|
540
626
|
>
|
|
541
627
|
{creating || registry.refreshing ? (
|
|
542
628
|
<LoaderCircleIcon className="size-3.5 animate-spin" />
|
|
@@ -552,14 +638,27 @@ export function ComputerViewer({
|
|
|
552
638
|
}
|
|
553
639
|
|
|
554
640
|
return (
|
|
555
|
-
<div
|
|
641
|
+
<div
|
|
642
|
+
className={cn(
|
|
643
|
+
"@container/computer-viewer relative flex h-full min-h-0 min-w-0 flex-col overflow-hidden bg-og-bg",
|
|
644
|
+
className,
|
|
645
|
+
)}
|
|
646
|
+
>
|
|
556
647
|
<ComputerToolbar
|
|
557
648
|
sessions={liveSessions}
|
|
558
649
|
relevantSessionIds={currentIds}
|
|
559
650
|
selectedSessionId={selection?.sessionId ?? null}
|
|
560
651
|
creating={creating}
|
|
561
|
-
refreshing={registry.refreshing}
|
|
652
|
+
refreshing={registry.refreshing || inputPosturePending}
|
|
562
653
|
interventionCounts={interventionCounts}
|
|
654
|
+
targets={controllerReady ? computer.targets : []}
|
|
655
|
+
selectedTargetId={computer.selectedTarget?.id ?? null}
|
|
656
|
+
targetsLoading={controllerReady && computer.loading}
|
|
657
|
+
onSelectTarget={(targetId) => {
|
|
658
|
+
void computer
|
|
659
|
+
.selectTarget(targetId)
|
|
660
|
+
.catch((cause) => notifyError(cause, "Could not switch desktop views."));
|
|
661
|
+
}}
|
|
563
662
|
onSelect={(computerSessionId) =>
|
|
564
663
|
selectComputerSession({ sessionId: computerSessionId, pinned: true })
|
|
565
664
|
}
|
|
@@ -567,7 +666,18 @@ export function ComputerViewer({
|
|
|
567
666
|
if (relevant[0]) selectComputerSession({ sessionId: relevant[0].id, pinned: false });
|
|
568
667
|
}}
|
|
569
668
|
onCreate={hideGenericCreate ? undefined : createComputer}
|
|
570
|
-
onRefresh={() =>
|
|
669
|
+
onRefresh={() => {
|
|
670
|
+
if (appTarget) {
|
|
671
|
+
appInputPosture.refresh();
|
|
672
|
+
} else if (
|
|
673
|
+
(connectionState === "error" || inputDenied) &&
|
|
674
|
+
!generationLossSelected &&
|
|
675
|
+
!generationLossFrames
|
|
676
|
+
) {
|
|
677
|
+
frames.reconnect();
|
|
678
|
+
}
|
|
679
|
+
void Promise.all([refreshRegistry(), refreshComputer()]);
|
|
680
|
+
}}
|
|
571
681
|
/>
|
|
572
682
|
<InteractionInterventionBanner
|
|
573
683
|
interventions={selectedInterventions}
|
|
@@ -597,46 +707,81 @@ export function ComputerViewer({
|
|
|
597
707
|
/>
|
|
598
708
|
) : (
|
|
599
709
|
<>
|
|
600
|
-
<
|
|
601
|
-
targets={computer.targets}
|
|
602
|
-
selectedTargetId={computer.selectedTarget?.id ?? null}
|
|
603
|
-
loading={computer.loading}
|
|
604
|
-
onSelect={(targetId) =>
|
|
605
|
-
void computer
|
|
606
|
-
.selectTarget(targetId)
|
|
607
|
-
.catch((cause) => notifyError(cause, "Could not switch desktop views."))
|
|
608
|
-
}
|
|
609
|
-
/>
|
|
610
|
-
<div className="flex min-h-0 flex-1">
|
|
710
|
+
<div className="flex min-h-0 flex-1 flex-col @xl/computer-viewer:flex-row">
|
|
611
711
|
{rfbStream ? (
|
|
612
712
|
<div className="relative min-h-0 flex-1 bg-black" onCopyCapture={copyFromRfb}>
|
|
613
713
|
<DesktopViewer
|
|
614
714
|
capability={rfbCapability}
|
|
615
|
-
interactive
|
|
715
|
+
interactive={rfbInputEnabled}
|
|
616
716
|
showControlToggle={false}
|
|
617
717
|
webSocketProtocols={rfbStream.protocols}
|
|
618
718
|
onPasteText={pasteIntoRfb}
|
|
619
719
|
targetPlatform={computer.session?.platform ?? null}
|
|
620
720
|
className="h-full"
|
|
621
721
|
/>
|
|
722
|
+
{!rfbInputEnabled ? (
|
|
723
|
+
<div className="pointer-events-none absolute bottom-3 left-1/2 -translate-x-1/2 rounded-full border border-white/10 bg-black/70 px-3 py-1 text-[11px] text-white/80 backdrop-blur">
|
|
724
|
+
{controlUnavailable ? (
|
|
725
|
+
<>
|
|
726
|
+
Desktop controls unavailable{" "}
|
|
727
|
+
<button
|
|
728
|
+
type="button"
|
|
729
|
+
onClick={reconnect}
|
|
730
|
+
className="pointer-events-auto ml-2 font-medium underline"
|
|
731
|
+
>
|
|
732
|
+
Reconnect
|
|
733
|
+
</button>
|
|
734
|
+
</>
|
|
735
|
+
) : (
|
|
736
|
+
"View only · direct input unavailable"
|
|
737
|
+
)}
|
|
738
|
+
</div>
|
|
739
|
+
) : null}
|
|
622
740
|
</div>
|
|
623
741
|
) : (
|
|
624
742
|
<ComputerViewport
|
|
743
|
+
key={JSON.stringify([
|
|
744
|
+
selection?.sessionId,
|
|
745
|
+
computer.selectedTarget?.id,
|
|
746
|
+
computer.selectedTarget?.targetGeneration,
|
|
747
|
+
frames.attachment?.controllerGeneration,
|
|
748
|
+
])}
|
|
625
749
|
frame={machineLocked ? null : displayedFrame}
|
|
626
750
|
observation={computer.observation}
|
|
627
751
|
target={computer.selectedTarget}
|
|
628
752
|
machineLocked={machineLocked}
|
|
629
|
-
|
|
630
|
-
|
|
753
|
+
controlUnavailable={controlUnavailable}
|
|
754
|
+
inputAllowed={!inputDenied}
|
|
755
|
+
connectionState={connectionState}
|
|
756
|
+
connectionError={
|
|
757
|
+
computer.controlError ?? appInputPosture.error ?? frames.error ?? computer.error
|
|
758
|
+
}
|
|
631
759
|
mutating={computer.mutating}
|
|
632
760
|
backgroundActions={computer.session?.capabilities?.backgroundActions === true}
|
|
633
|
-
|
|
761
|
+
backgroundInput={computer.session?.capabilities?.backgroundInput === true}
|
|
762
|
+
allowForegroundInputWithinOwnedSeat={
|
|
763
|
+
computer.session?.platform === "linux" &&
|
|
764
|
+
computer.session.adapter === "opengeni.cua.linux.v1" &&
|
|
765
|
+
computer.session.seatId === `linux-virtual:${computer.session.id}` &&
|
|
766
|
+
Boolean(computer.session.displayId)
|
|
767
|
+
}
|
|
768
|
+
clipboardEnabled={
|
|
769
|
+
attachmentInputAllowed && computer.session?.capabilities?.clipboard === true
|
|
770
|
+
}
|
|
771
|
+
pointerInput={
|
|
772
|
+
attachmentInputAllowed && computer.session?.capabilities?.pointerInput === true
|
|
773
|
+
}
|
|
774
|
+
pointerClickContinuation={
|
|
775
|
+
attachmentInputAllowed &&
|
|
776
|
+
computer.session?.capabilities?.pointerClickContinuation === true
|
|
777
|
+
}
|
|
778
|
+
keyboardInput={
|
|
779
|
+
attachmentInputAllowed && computer.session?.capabilities?.keyboardInput === true
|
|
780
|
+
}
|
|
634
781
|
onAction={perform}
|
|
635
782
|
onReadClipboard={computer.readClipboard}
|
|
636
783
|
onReconnect={
|
|
637
|
-
generationLossSelected || generationLossFrames
|
|
638
|
-
? () => undefined
|
|
639
|
-
: frames.reconnect
|
|
784
|
+
generationLossSelected || generationLossFrames ? () => undefined : reconnect
|
|
640
785
|
}
|
|
641
786
|
onError={(cause) => notifyError(cause, "Desktop input failed.")}
|
|
642
787
|
/>
|
|
@@ -644,7 +789,9 @@ export function ComputerViewer({
|
|
|
644
789
|
{showControls ? (
|
|
645
790
|
<ComputerSemanticPanel
|
|
646
791
|
observation={computer.observation}
|
|
647
|
-
mutating={computer.mutating}
|
|
792
|
+
mutating={computer.mutating || controlUnavailable || inputDenied}
|
|
793
|
+
controlUnavailable={controlUnavailable}
|
|
794
|
+
inputAllowed={!inputDenied}
|
|
648
795
|
onAction={(action) =>
|
|
649
796
|
void perform(action, null).catch((cause) =>
|
|
650
797
|
notifyError(cause, "Desktop action failed."),
|
|
@@ -656,10 +803,12 @@ export function ComputerViewer({
|
|
|
656
803
|
<ComputerStatusBar
|
|
657
804
|
session={computer.session}
|
|
658
805
|
target={computer.selectedTarget}
|
|
659
|
-
connectionState={
|
|
806
|
+
connectionState={connectionState}
|
|
807
|
+
controlUnavailable={controlUnavailable}
|
|
808
|
+
inputAllowed={!inputDenied}
|
|
660
809
|
refreshing={registry.refreshing}
|
|
661
810
|
showControls={showControls}
|
|
662
|
-
controlCount={
|
|
811
|
+
controlCount={semanticControls(computer.observation).length}
|
|
663
812
|
onToggleControls={() => setShowControls((current) => !current)}
|
|
664
813
|
/>
|
|
665
814
|
</>
|
|
@@ -678,9 +827,9 @@ function ComputerUnselectedPanel(props: {
|
|
|
678
827
|
return (
|
|
679
828
|
<div className="grid min-h-0 flex-1 place-items-center bg-og-bg p-6 text-center">
|
|
680
829
|
<div className="max-w-sm">
|
|
681
|
-
<span className="mx-auto grid size-10 place-items-center rounded-og-md border border-og-border bg-og-surface-1 text-og-muted">
|
|
830
|
+
<span className="mx-auto grid size-10 place-items-center rounded-og-md border border-og-border bg-og-surface-1 text-og-fg-muted">
|
|
682
831
|
{props.error || props.generationLoss ? (
|
|
683
|
-
<CircleAlertIcon className="size-4.5 text-og-
|
|
832
|
+
<CircleAlertIcon className="size-4.5 text-og-danger" />
|
|
684
833
|
) : (
|
|
685
834
|
<LoaderCircleIcon className="size-4.5 animate-spin" />
|
|
686
835
|
)}
|
|
@@ -692,7 +841,7 @@ function ComputerUnselectedPanel(props: {
|
|
|
692
841
|
? "Desktop didn’t open"
|
|
693
842
|
: "Opening desktop…"}
|
|
694
843
|
</p>
|
|
695
|
-
<p className="mt-1 text-og-control leading-5 text-og-muted">
|
|
844
|
+
<p className="mt-1 text-og-control leading-5 text-og-fg-muted">
|
|
696
845
|
{props.generationLoss
|
|
697
846
|
? "This Chrome profile is live again, but the previous desktop cannot move to the new connection. Use Browser → New browser → Connected Chrome."
|
|
698
847
|
: (props.error?.message ??
|
|
@@ -727,66 +876,97 @@ function ComputerToolbar(props: {
|
|
|
727
876
|
creating: boolean;
|
|
728
877
|
refreshing: boolean;
|
|
729
878
|
interventionCounts: Map<string, number>;
|
|
879
|
+
targets: ComputerTarget[];
|
|
880
|
+
selectedTargetId: string | null;
|
|
881
|
+
targetsLoading: boolean;
|
|
882
|
+
onSelectTarget: (id: string) => void;
|
|
730
883
|
onSelect: (id: string) => void;
|
|
731
884
|
onFollow: () => void;
|
|
732
885
|
onCreate?: (() => void) | undefined;
|
|
733
886
|
onRefresh: () => void;
|
|
734
887
|
}) {
|
|
735
|
-
const detailsRef =
|
|
888
|
+
const detailsRef = useViewerMenuDismiss();
|
|
736
889
|
const selected = props.sessions.find((session) => session.id === props.selectedSessionId);
|
|
737
890
|
const current = props.sessions.filter((session) => props.relevantSessionIds.has(session.id));
|
|
738
891
|
const others = props.sessions.filter((session) => !props.relevantSessionIds.has(session.id));
|
|
739
|
-
const
|
|
740
|
-
props.onSelect(id);
|
|
892
|
+
const closeMenu = (restoreFocus: boolean) => {
|
|
741
893
|
detailsRef.current?.removeAttribute("open");
|
|
894
|
+
if (restoreFocus) detailsRef.current?.querySelector("summary")?.focus();
|
|
895
|
+
};
|
|
896
|
+
const choose = (id: string, restoreFocus: boolean) => {
|
|
897
|
+
props.onSelect(id);
|
|
898
|
+
closeMenu(restoreFocus);
|
|
742
899
|
};
|
|
743
900
|
return (
|
|
744
|
-
<div className="flex h-10 shrink-0 items-center gap-1.5 border-b border-og-border bg-og-surface-
|
|
745
|
-
<details ref={detailsRef} className="
|
|
746
|
-
<summary className="flex h-7 max-w-
|
|
747
|
-
<MonitorIcon className="size-3.5 shrink-0 text-og-muted" />
|
|
901
|
+
<div className="flex h-10 shrink-0 items-center gap-1.5 border-b border-og-border bg-og-surface-1 px-2">
|
|
902
|
+
<details ref={detailsRef} className="min-w-0">
|
|
903
|
+
<summary className="flex h-7 max-w-32 cursor-pointer list-none items-center gap-2 rounded-og-sm px-2 text-og-control text-og-fg transition hover:bg-og-surface-2 @sm/computer-viewer:max-w-52 [&::-webkit-details-marker]:hidden">
|
|
904
|
+
<MonitorIcon className="size-3.5 shrink-0 text-og-fg-muted" />
|
|
748
905
|
<span className="truncate font-medium">
|
|
749
906
|
{selected?.name === "Computer" ? "Desktop" : (selected?.name ?? "Desktop")}
|
|
750
907
|
</span>
|
|
751
908
|
{(props.interventionCounts.get(selected?.id ?? "") ?? 0) > 0 ? (
|
|
752
909
|
<span className="size-1.5 shrink-0 rounded-full bg-og-status-waiting" />
|
|
753
910
|
) : null}
|
|
754
|
-
<ChevronDownIcon className="size-3 shrink-0 text-og-subtle" />
|
|
911
|
+
<ChevronDownIcon className="size-3 shrink-0 text-og-fg-subtle" />
|
|
755
912
|
</summary>
|
|
756
|
-
<div className="absolute left-
|
|
757
|
-
<
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
913
|
+
<div className="absolute left-2 top-10 z-30 flex max-h-[calc(100%-3rem)] w-72 max-w-[calc(100%-1rem)] flex-col overflow-hidden rounded-og-md border border-og-border bg-og-surface-1 p-1 shadow-xl">
|
|
914
|
+
<div className="min-h-0 max-h-96 overflow-y-auto overscroll-contain">
|
|
915
|
+
<ComputerSessionGroup
|
|
916
|
+
label="Current agent"
|
|
917
|
+
sessions={current}
|
|
918
|
+
selectedId={props.selectedSessionId}
|
|
919
|
+
interventionCounts={props.interventionCounts}
|
|
920
|
+
onSelect={choose}
|
|
921
|
+
/>
|
|
922
|
+
<ComputerSessionGroup
|
|
923
|
+
label="Other agents"
|
|
924
|
+
sessions={others}
|
|
925
|
+
selectedId={props.selectedSessionId}
|
|
926
|
+
interventionCounts={props.interventionCounts}
|
|
927
|
+
onSelect={choose}
|
|
928
|
+
/>
|
|
929
|
+
</div>
|
|
930
|
+
<div className="mt-1 flex shrink-0 gap-1 border-t border-og-border pt-1">
|
|
772
931
|
{current.length > 0 ? (
|
|
773
|
-
<MenuButton
|
|
932
|
+
<MenuButton
|
|
933
|
+
onClick={(event) => {
|
|
934
|
+
props.onFollow();
|
|
935
|
+
closeMenu(event.detail === 0);
|
|
936
|
+
}}
|
|
937
|
+
>
|
|
938
|
+
Follow agent
|
|
939
|
+
</MenuButton>
|
|
774
940
|
) : null}
|
|
775
941
|
{props.onCreate ? (
|
|
776
|
-
<MenuButton
|
|
942
|
+
<MenuButton
|
|
943
|
+
onClick={(event) => {
|
|
944
|
+
props.onCreate?.();
|
|
945
|
+
closeMenu(event.detail === 0);
|
|
946
|
+
}}
|
|
947
|
+
disabled={props.creating}
|
|
948
|
+
>
|
|
777
949
|
<PlusIcon className="size-3.5" /> New desktop
|
|
778
950
|
</MenuButton>
|
|
779
951
|
) : null}
|
|
780
952
|
</div>
|
|
781
953
|
</div>
|
|
782
954
|
</details>
|
|
783
|
-
<span className="min-w-0 flex-1 truncate text-og-xs text-og-subtle">
|
|
955
|
+
<span className="min-w-0 flex-1 truncate text-og-xs text-og-fg-subtle">
|
|
784
956
|
{selected ? `${platformLabel(selected)} · ${placementLabel(selected)}` : ""}
|
|
785
957
|
</span>
|
|
958
|
+
<ComputerTargetPicker
|
|
959
|
+
targets={props.targets}
|
|
960
|
+
selectedTargetId={props.selectedTargetId}
|
|
961
|
+
loading={props.targetsLoading}
|
|
962
|
+
onSelect={props.onSelectTarget}
|
|
963
|
+
/>
|
|
786
964
|
<button
|
|
787
965
|
type="button"
|
|
788
966
|
onClick={props.onRefresh}
|
|
789
|
-
|
|
967
|
+
disabled={props.refreshing}
|
|
968
|
+
aria-busy={props.refreshing}
|
|
969
|
+
className="grid size-7 place-items-center rounded-og-sm text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg"
|
|
790
970
|
aria-label="Refresh desktops"
|
|
791
971
|
>
|
|
792
972
|
<RefreshCwIcon className={cn("size-3.5", props.refreshing && "animate-spin")} />
|
|
@@ -796,7 +976,7 @@ function ComputerToolbar(props: {
|
|
|
796
976
|
type="button"
|
|
797
977
|
onClick={props.onCreate}
|
|
798
978
|
disabled={props.creating}
|
|
799
|
-
className="
|
|
979
|
+
className="hidden size-7 place-items-center rounded-og-sm text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg disabled:opacity-40 @sm/computer-viewer:grid"
|
|
800
980
|
aria-label="Open a new desktop"
|
|
801
981
|
>
|
|
802
982
|
{props.creating ? (
|
|
@@ -815,12 +995,12 @@ function ComputerSessionGroup(props: {
|
|
|
815
995
|
sessions: ComputerSession[];
|
|
816
996
|
selectedId: string | null;
|
|
817
997
|
interventionCounts: Map<string, number>;
|
|
818
|
-
onSelect: (id: string) => void;
|
|
998
|
+
onSelect: (id: string, restoreFocus: boolean) => void;
|
|
819
999
|
}) {
|
|
820
1000
|
if (props.sessions.length === 0) return null;
|
|
821
1001
|
return (
|
|
822
1002
|
<div className="py-1">
|
|
823
|
-
<p className="px-2 pb-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-subtle">
|
|
1003
|
+
<p className="px-2 pb-1 text-[10px] font-medium uppercase tracking-[0.12em] text-og-fg-subtle">
|
|
824
1004
|
{props.label}
|
|
825
1005
|
</p>
|
|
826
1006
|
{props.sessions.map((session) => {
|
|
@@ -829,7 +1009,8 @@ function ComputerSessionGroup(props: {
|
|
|
829
1009
|
<button
|
|
830
1010
|
key={session.id}
|
|
831
1011
|
type="button"
|
|
832
|
-
onClick={() => props.onSelect(session.id)}
|
|
1012
|
+
onClick={(event) => props.onSelect(session.id, event.detail === 0)}
|
|
1013
|
+
aria-pressed={session.id === props.selectedId}
|
|
833
1014
|
className={cn(
|
|
834
1015
|
"flex w-full items-center gap-2 rounded-og-sm px-2 py-1.5 text-left transition hover:bg-og-surface-2",
|
|
835
1016
|
session.id === props.selectedId && "bg-og-surface-2",
|
|
@@ -843,7 +1024,7 @@ function ComputerSessionGroup(props: {
|
|
|
843
1024
|
/>
|
|
844
1025
|
<span className="min-w-0 flex-1">
|
|
845
1026
|
<span className="block truncate text-og-control text-og-fg">{session.name}</span>
|
|
846
|
-
<span className="block truncate text-og-xs text-og-subtle">
|
|
1027
|
+
<span className="block truncate text-og-xs text-og-fg-subtle">
|
|
847
1028
|
{platformLabel(session)} · {placementLabel(session)}
|
|
848
1029
|
</span>
|
|
849
1030
|
</span>
|
|
@@ -859,65 +1040,119 @@ function ComputerSessionGroup(props: {
|
|
|
859
1040
|
);
|
|
860
1041
|
}
|
|
861
1042
|
|
|
862
|
-
function MenuButton(props: {
|
|
1043
|
+
function MenuButton(props: {
|
|
1044
|
+
children: ReactNode;
|
|
1045
|
+
onClick: (event: MouseEvent<HTMLButtonElement>) => void;
|
|
1046
|
+
disabled?: boolean;
|
|
1047
|
+
}) {
|
|
863
1048
|
return (
|
|
864
1049
|
<button
|
|
865
1050
|
type="button"
|
|
866
1051
|
onClick={props.onClick}
|
|
867
1052
|
disabled={props.disabled}
|
|
868
|
-
className="flex h-7 flex-1 items-center justify-center gap-1 rounded-og-sm px-2 text-og-control text-og-muted transition hover:bg-og-surface-2 hover:text-og-fg disabled:opacity-50"
|
|
1053
|
+
className="flex h-7 flex-1 items-center justify-center gap-1 rounded-og-sm px-2 text-og-control text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg disabled:opacity-50"
|
|
869
1054
|
>
|
|
870
1055
|
{props.children}
|
|
871
1056
|
</button>
|
|
872
1057
|
);
|
|
873
1058
|
}
|
|
874
1059
|
|
|
875
|
-
function
|
|
1060
|
+
function ComputerTargetPicker(props: {
|
|
876
1061
|
targets: ComputerTarget[];
|
|
877
1062
|
selectedTargetId: string | null;
|
|
878
1063
|
loading: boolean;
|
|
879
1064
|
onSelect: (id: string) => void;
|
|
880
1065
|
}) {
|
|
881
|
-
const
|
|
1066
|
+
const screens = props.targets.filter((target) => target.kind === "screen");
|
|
1067
|
+
const advancedTargets = props.targets.filter(
|
|
1068
|
+
(target) =>
|
|
1069
|
+
target.kind === "app" || (target.kind === "window" && isRenderableComputerView(target)),
|
|
1070
|
+
);
|
|
1071
|
+
const advancedRef = useViewerMenuDismiss(advancedTargets.length > 0);
|
|
1072
|
+
const selected = props.targets.find((target) => target.id === props.selectedTargetId);
|
|
1073
|
+
const choose = (id: string, restoreFocus = false) => {
|
|
1074
|
+
props.onSelect(id);
|
|
1075
|
+
if (advancedRef.current?.open) {
|
|
1076
|
+
advancedRef.current.removeAttribute("open");
|
|
1077
|
+
if (restoreFocus) advancedRef.current.querySelector("summary")?.focus();
|
|
1078
|
+
}
|
|
1079
|
+
};
|
|
882
1080
|
return (
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
1081
|
+
<>
|
|
1082
|
+
{screens.length > 1 ? (
|
|
1083
|
+
<select
|
|
1084
|
+
aria-label="Screen"
|
|
1085
|
+
value={selected?.kind === "screen" ? selected.id : ""}
|
|
1086
|
+
onChange={(event) => choose(event.currentTarget.value)}
|
|
1087
|
+
className="h-7 min-w-0 max-w-28 rounded-og-sm border border-og-border bg-og-surface-1 px-1.5 text-og-control text-og-fg outline-hidden focus:border-og-accent @sm/computer-viewer:max-w-40"
|
|
1088
|
+
>
|
|
1089
|
+
<option value="" disabled hidden>
|
|
1090
|
+
Screen
|
|
1091
|
+
</option>
|
|
1092
|
+
{screens.map((screen, index) => (
|
|
1093
|
+
<option key={screen.id} value={screen.id}>
|
|
1094
|
+
{screen.title || `Screen ${index + 1}`}
|
|
1095
|
+
</option>
|
|
1096
|
+
))}
|
|
1097
|
+
</select>
|
|
1098
|
+
) : screens[0] && selected?.kind !== "screen" ? (
|
|
888
1099
|
<button
|
|
889
|
-
key={target.id}
|
|
890
1100
|
type="button"
|
|
891
|
-
onClick={() =>
|
|
892
|
-
className=
|
|
893
|
-
"flex h-7 max-w-56 shrink-0 items-center gap-1.5 rounded-og-sm border px-2 text-og-control transition",
|
|
894
|
-
target.id === props.selectedTargetId
|
|
895
|
-
? "border-og-border-strong bg-og-surface-2 text-og-fg"
|
|
896
|
-
: "border-transparent text-og-muted hover:bg-og-surface-1 hover:text-og-fg",
|
|
897
|
-
)}
|
|
898
|
-
aria-pressed={target.id === props.selectedTargetId}
|
|
1101
|
+
onClick={() => choose(screens[0]!.id)}
|
|
1102
|
+
className="flex h-7 shrink-0 items-center gap-1.5 rounded-og-sm px-2 text-og-control text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg"
|
|
899
1103
|
>
|
|
900
|
-
|
|
901
|
-
<MonitorIcon className="size-3.5 shrink-0" />
|
|
902
|
-
) : (
|
|
903
|
-
<PanelsTopLeftIcon className="size-3.5 shrink-0" />
|
|
904
|
-
)}
|
|
905
|
-
<span className="truncate">{target.title || target.applicationId || target.kind}</span>
|
|
906
|
-
{target.focused ? (
|
|
907
|
-
<span
|
|
908
|
-
className="size-1.5 shrink-0 rounded-full bg-og-status-running"
|
|
909
|
-
aria-label="Focused"
|
|
910
|
-
/>
|
|
911
|
-
) : null}
|
|
1104
|
+
<MonitorIcon className="size-3.5" /> Full desktop
|
|
912
1105
|
</button>
|
|
913
|
-
))}
|
|
914
|
-
{props.loading ? (
|
|
915
|
-
<LoaderCircleIcon className="ml-1 size-3.5 animate-spin text-og-muted" />
|
|
916
1106
|
) : null}
|
|
917
|
-
{
|
|
918
|
-
<
|
|
1107
|
+
{advancedTargets.length > 0 ? (
|
|
1108
|
+
<details ref={advancedRef} className="shrink-0">
|
|
1109
|
+
<summary
|
|
1110
|
+
aria-label="Advanced desktop views"
|
|
1111
|
+
title="Advanced desktop views"
|
|
1112
|
+
className="flex h-7 cursor-pointer list-none items-center gap-1.5 rounded-og-sm px-2 text-og-control text-og-fg-muted transition hover:bg-og-surface-2 hover:text-og-fg [&::-webkit-details-marker]:hidden"
|
|
1113
|
+
>
|
|
1114
|
+
<PanelsTopLeftIcon className="size-3.5" />
|
|
1115
|
+
<span className="hidden @sm/computer-viewer:inline">Advanced</span>
|
|
1116
|
+
<ChevronDownIcon className="size-3" />
|
|
1117
|
+
</summary>
|
|
1118
|
+
<div className="absolute right-2 top-10 z-30 flex max-h-[calc(100%-3rem)] w-64 max-w-[calc(100%-1rem)] flex-col overflow-hidden rounded-og-md border border-og-border bg-og-surface-1 p-1 shadow-xl">
|
|
1119
|
+
<p className="shrink-0 px-2 py-1 text-og-xs text-og-fg-subtle">Apps and windows</p>
|
|
1120
|
+
<div className="min-h-0 max-h-80 overflow-y-auto overscroll-contain">
|
|
1121
|
+
{advancedTargets.map((target) => (
|
|
1122
|
+
<button
|
|
1123
|
+
key={target.id}
|
|
1124
|
+
type="button"
|
|
1125
|
+
onClick={(event) => choose(target.id, event.detail === 0)}
|
|
1126
|
+
aria-pressed={target.id === props.selectedTargetId}
|
|
1127
|
+
className={cn(
|
|
1128
|
+
"flex w-full min-w-0 items-center gap-2 rounded-og-sm px-2 py-1.5 text-left transition hover:bg-og-surface-2",
|
|
1129
|
+
target.id === props.selectedTargetId && "bg-og-surface-2",
|
|
1130
|
+
)}
|
|
1131
|
+
>
|
|
1132
|
+
<span className="min-w-0 flex-1">
|
|
1133
|
+
<span className="block truncate text-og-control text-og-fg">
|
|
1134
|
+
{target.title || target.applicationId || target.kind}
|
|
1135
|
+
</span>
|
|
1136
|
+
<span className="block text-og-xs text-og-fg-subtle">
|
|
1137
|
+
{target.kind === "app" ? "App controls" : "Window view"}
|
|
1138
|
+
</span>
|
|
1139
|
+
</span>
|
|
1140
|
+
{target.focused ? (
|
|
1141
|
+
<span
|
|
1142
|
+
className="size-1.5 shrink-0 rounded-full bg-og-status-running"
|
|
1143
|
+
aria-label="Focused"
|
|
1144
|
+
/>
|
|
1145
|
+
) : null}
|
|
1146
|
+
</button>
|
|
1147
|
+
))}
|
|
1148
|
+
</div>
|
|
1149
|
+
</div>
|
|
1150
|
+
</details>
|
|
919
1151
|
) : null}
|
|
920
|
-
|
|
1152
|
+
{props.loading ? (
|
|
1153
|
+
<LoaderCircleIcon className="size-3.5 shrink-0 animate-spin text-og-fg-muted" />
|
|
1154
|
+
) : null}
|
|
1155
|
+
</>
|
|
921
1156
|
);
|
|
922
1157
|
}
|
|
923
1158
|
|
|
@@ -947,9 +1182,9 @@ function ComputerLifecyclePanel(props: {
|
|
|
947
1182
|
<div className="grid min-h-0 flex-1 place-items-center bg-og-bg p-6">
|
|
948
1183
|
<div className="max-w-sm text-center">
|
|
949
1184
|
{failed ? (
|
|
950
|
-
<CircleAlertIcon className="mx-auto size-5 text-og-
|
|
1185
|
+
<CircleAlertIcon className="mx-auto size-5 text-og-danger" />
|
|
951
1186
|
) : (
|
|
952
|
-
<LoaderCircleIcon className="mx-auto size-5 animate-spin text-og-muted" />
|
|
1187
|
+
<LoaderCircleIcon className="mx-auto size-5 animate-spin text-og-fg-muted" />
|
|
953
1188
|
)}
|
|
954
1189
|
<p className="mt-3 text-og-menu font-medium text-og-fg">
|
|
955
1190
|
{generationLoss
|
|
@@ -958,7 +1193,7 @@ function ComputerLifecyclePanel(props: {
|
|
|
958
1193
|
? "Desktop needs attention"
|
|
959
1194
|
: lifecycleLabel(props.session.lifecycle)}
|
|
960
1195
|
</p>
|
|
961
|
-
<p className="mt-1 text-og-control leading-5 text-og-muted">
|
|
1196
|
+
<p className="mt-1 text-og-control leading-5 text-og-fg-muted">
|
|
962
1197
|
{generationLoss
|
|
963
1198
|
? "This Chrome profile is live again, but the previous desktop cannot move to the new connection. Use Browser → New browser → Connected Chrome."
|
|
964
1199
|
: (computerFailureMessage(props.session) ??
|
|
@@ -984,23 +1219,41 @@ function ComputerLifecyclePanel(props: {
|
|
|
984
1219
|
);
|
|
985
1220
|
}
|
|
986
1221
|
|
|
1222
|
+
type ComputerQueuedAction = {
|
|
1223
|
+
operationId: string;
|
|
1224
|
+
started: Promise<boolean>;
|
|
1225
|
+
completion: Promise<void>;
|
|
1226
|
+
};
|
|
1227
|
+
|
|
987
1228
|
function ComputerViewport(props: {
|
|
988
1229
|
frame: ComputerFrame | null;
|
|
989
1230
|
observation: ComputerObservation | null;
|
|
990
1231
|
target: ComputerTarget | null;
|
|
991
1232
|
machineLocked: boolean;
|
|
1233
|
+
controlUnavailable: boolean;
|
|
1234
|
+
inputAllowed: boolean;
|
|
992
1235
|
connectionState: string;
|
|
993
1236
|
connectionError: Error | null;
|
|
994
1237
|
mutating: boolean;
|
|
995
1238
|
backgroundActions: boolean;
|
|
1239
|
+
backgroundInput: boolean;
|
|
1240
|
+
allowForegroundInputWithinOwnedSeat: boolean;
|
|
996
1241
|
clipboardEnabled: boolean;
|
|
997
|
-
|
|
1242
|
+
pointerInput: boolean;
|
|
1243
|
+
pointerClickContinuation: boolean;
|
|
1244
|
+
keyboardInput: boolean;
|
|
1245
|
+
onAction: (
|
|
1246
|
+
action: ComputerAction,
|
|
1247
|
+
frame: ComputerFrame | null,
|
|
1248
|
+
operationId?: string,
|
|
1249
|
+
) => Promise<ComputerActionReceipt>;
|
|
998
1250
|
onReadClipboard: () => Promise<ComputerClipboard>;
|
|
999
1251
|
onReconnect: () => void;
|
|
1000
1252
|
onError: (cause: unknown) => void;
|
|
1001
1253
|
}) {
|
|
1002
1254
|
const canvasRef = useRef<HTMLCanvasElement | null>(null);
|
|
1003
1255
|
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
|
1256
|
+
const composingRef = useRef(false);
|
|
1004
1257
|
const pointerStartRef = useRef<PointerStart | null>(null);
|
|
1005
1258
|
const clickTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
|
1006
1259
|
const lastClickRef = useRef<{
|
|
@@ -1008,6 +1261,11 @@ function ComputerViewport(props: {
|
|
|
1008
1261
|
x: number;
|
|
1009
1262
|
y: number;
|
|
1010
1263
|
frame: ComputerFrame;
|
|
1264
|
+
clientX: number;
|
|
1265
|
+
clientY: number;
|
|
1266
|
+
canvasRect: Pick<DOMRect, "left" | "top" | "width" | "height">;
|
|
1267
|
+
pending: boolean;
|
|
1268
|
+
delivery: ComputerQueuedAction | null;
|
|
1011
1269
|
} | null>(null);
|
|
1012
1270
|
const wheelRef = useRef<{
|
|
1013
1271
|
x: number;
|
|
@@ -1022,17 +1280,72 @@ function ComputerViewport(props: {
|
|
|
1022
1280
|
timer: ReturnType<typeof setTimeout>;
|
|
1023
1281
|
} | null>(null);
|
|
1024
1282
|
const actionRef = useRef(props.onAction);
|
|
1283
|
+
const targetRef = useRef(props.target);
|
|
1025
1284
|
const readClipboardRef = useRef(props.onReadClipboard);
|
|
1026
1285
|
const errorRef = useRef(props.onError);
|
|
1027
1286
|
const actionTailRef = useRef<Promise<void>>(Promise.resolve());
|
|
1287
|
+
const actionQueueEpochRef = useRef(0);
|
|
1288
|
+
const queuedTextRef = useRef<{
|
|
1289
|
+
action: Extract<ComputerAction, { type: "keyboard" }>;
|
|
1290
|
+
epoch: number;
|
|
1291
|
+
delivery: ComputerQueuedAction;
|
|
1292
|
+
} | null>(null);
|
|
1028
1293
|
const queuedFrameRef = useRef<ComputerFrame | null>(null);
|
|
1294
|
+
const currentFrameRef = useRef<ComputerFrame | null>(props.frame);
|
|
1295
|
+
const paintedFrameRef = useRef<ComputerFrame | null>(null);
|
|
1296
|
+
const [paintedFrame, setPaintedFrame] = useState<ComputerFrame | null>(null);
|
|
1029
1297
|
const decodingFrameRef = useRef(false);
|
|
1030
1298
|
const mountedRef = useRef(true);
|
|
1031
1299
|
actionRef.current = props.onAction;
|
|
1300
|
+
targetRef.current = props.target;
|
|
1032
1301
|
readClipboardRef.current = props.onReadClipboard;
|
|
1033
1302
|
errorRef.current = props.onError;
|
|
1303
|
+
const streamFailed = props.connectionState === "error";
|
|
1034
1304
|
const rawInputEnabled =
|
|
1035
|
-
|
|
1305
|
+
props.target?.kind !== "app" &&
|
|
1306
|
+
!streamFailed &&
|
|
1307
|
+
!props.controlUnavailable &&
|
|
1308
|
+
!props.machineLocked &&
|
|
1309
|
+
(props.backgroundInput ||
|
|
1310
|
+
// CUA may activate within its allocated Linux desktop. This does not
|
|
1311
|
+
// grant foreground input to an attached physical desktop.
|
|
1312
|
+
props.allowForegroundInputWithinOwnedSeat ||
|
|
1313
|
+
!props.backgroundActions ||
|
|
1314
|
+
props.target?.kind === "screen" ||
|
|
1315
|
+
props.target?.focused === true);
|
|
1316
|
+
|
|
1317
|
+
const clearBufferedInput = useCallback(() => {
|
|
1318
|
+
if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
|
|
1319
|
+
if (wheelRef.current?.timer) clearTimeout(wheelRef.current.timer);
|
|
1320
|
+
if (pendingTextRef.current?.timer) clearTimeout(pendingTextRef.current.timer);
|
|
1321
|
+
clickTimerRef.current = null;
|
|
1322
|
+
wheelRef.current = null;
|
|
1323
|
+
pendingTextRef.current = null;
|
|
1324
|
+
queuedTextRef.current = null;
|
|
1325
|
+
pointerStartRef.current = null;
|
|
1326
|
+
lastClickRef.current = null;
|
|
1327
|
+
composingRef.current = false;
|
|
1328
|
+
if (inputRef.current) inputRef.current.value = "";
|
|
1329
|
+
}, []);
|
|
1330
|
+
|
|
1331
|
+
const pointerInputEnabled = rawInputEnabled && props.pointerInput;
|
|
1332
|
+
const keyboardInputEnabled = rawInputEnabled && props.keyboardInput;
|
|
1333
|
+
|
|
1334
|
+
useLayoutEffect(() => {
|
|
1335
|
+
const canvas = canvasRef.current;
|
|
1336
|
+
const viewport = canvas?.parentElement;
|
|
1337
|
+
if (!canvas || !viewport) return;
|
|
1338
|
+
const fit = () => fitComputerCanvas(canvas);
|
|
1339
|
+
fit();
|
|
1340
|
+
if (typeof ResizeObserver === "undefined") {
|
|
1341
|
+
window.addEventListener("resize", fit);
|
|
1342
|
+
return () => window.removeEventListener("resize", fit);
|
|
1343
|
+
}
|
|
1344
|
+
// The canvas is absolute, so fitting it cannot resize the observed dock.
|
|
1345
|
+
const observer = new ResizeObserver(fit);
|
|
1346
|
+
observer.observe(viewport);
|
|
1347
|
+
return () => observer.disconnect();
|
|
1348
|
+
}, []);
|
|
1036
1349
|
|
|
1037
1350
|
const paintQueuedFrames = useCallback(() => {
|
|
1038
1351
|
if (decodingFrameRef.current) return;
|
|
@@ -1050,8 +1363,15 @@ function ComputerViewport(props: {
|
|
|
1050
1363
|
const bitmap = await createImageBitmap(blob);
|
|
1051
1364
|
try {
|
|
1052
1365
|
const canvas = canvasRef.current;
|
|
1053
|
-
if (
|
|
1366
|
+
if (
|
|
1367
|
+
mountedRef.current &&
|
|
1368
|
+
canvas &&
|
|
1369
|
+
currentFrameRef.current &&
|
|
1370
|
+
sameComputerTarget(frame, currentFrameRef.current)
|
|
1371
|
+
) {
|
|
1054
1372
|
paintCanvas(canvas, bitmap, frame.width, frame.height);
|
|
1373
|
+
paintedFrameRef.current = frame;
|
|
1374
|
+
setPaintedFrame(frame);
|
|
1055
1375
|
}
|
|
1056
1376
|
} finally {
|
|
1057
1377
|
bitmap.close();
|
|
@@ -1061,11 +1381,23 @@ function ComputerViewport(props: {
|
|
|
1061
1381
|
objectUrl = URL.createObjectURL(blob);
|
|
1062
1382
|
const image = await loadImage(objectUrl);
|
|
1063
1383
|
const canvas = canvasRef.current;
|
|
1064
|
-
if (
|
|
1384
|
+
if (
|
|
1385
|
+
mountedRef.current &&
|
|
1386
|
+
canvas &&
|
|
1387
|
+
currentFrameRef.current &&
|
|
1388
|
+
sameComputerTarget(frame, currentFrameRef.current)
|
|
1389
|
+
) {
|
|
1065
1390
|
paintCanvas(canvas, image, frame.width, frame.height);
|
|
1391
|
+
paintedFrameRef.current = frame;
|
|
1392
|
+
setPaintedFrame(frame);
|
|
1066
1393
|
}
|
|
1067
1394
|
} catch (cause) {
|
|
1068
|
-
if (
|
|
1395
|
+
if (
|
|
1396
|
+
mountedRef.current &&
|
|
1397
|
+
currentFrameRef.current &&
|
|
1398
|
+
sameComputerTarget(frame, currentFrameRef.current)
|
|
1399
|
+
)
|
|
1400
|
+
errorRef.current(cause);
|
|
1069
1401
|
} finally {
|
|
1070
1402
|
if (objectUrl) URL.revokeObjectURL(objectUrl);
|
|
1071
1403
|
}
|
|
@@ -1077,40 +1409,127 @@ function ComputerViewport(props: {
|
|
|
1077
1409
|
})();
|
|
1078
1410
|
}, []);
|
|
1079
1411
|
|
|
1080
|
-
|
|
1412
|
+
useLayoutEffect(() => {
|
|
1081
1413
|
mountedRef.current = true;
|
|
1082
1414
|
return () => {
|
|
1083
1415
|
mountedRef.current = false;
|
|
1416
|
+
actionQueueEpochRef.current += 1;
|
|
1084
1417
|
queuedFrameRef.current = null;
|
|
1418
|
+
clearBufferedInput();
|
|
1085
1419
|
};
|
|
1086
|
-
}, []);
|
|
1420
|
+
}, [clearBufferedInput]);
|
|
1087
1421
|
|
|
1088
|
-
|
|
1089
|
-
if (!props.frame)
|
|
1422
|
+
useLayoutEffect(() => {
|
|
1423
|
+
if (currentFrameRef.current && !props.frame) {
|
|
1424
|
+
actionQueueEpochRef.current += 1;
|
|
1425
|
+
clearBufferedInput();
|
|
1426
|
+
}
|
|
1427
|
+
currentFrameRef.current = props.frame;
|
|
1428
|
+
if (!props.frame) {
|
|
1429
|
+
queuedFrameRef.current = null;
|
|
1430
|
+
paintedFrameRef.current = null;
|
|
1431
|
+
setPaintedFrame(null);
|
|
1432
|
+
return;
|
|
1433
|
+
}
|
|
1090
1434
|
queuedFrameRef.current = props.frame;
|
|
1091
1435
|
paintQueuedFrames();
|
|
1092
|
-
}, [paintQueuedFrames, props.frame]);
|
|
1436
|
+
}, [clearBufferedInput, paintQueuedFrames, props.frame]);
|
|
1093
1437
|
|
|
1094
|
-
|
|
1095
|
-
(
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
[],
|
|
1101
|
-
);
|
|
1438
|
+
useLayoutEffect(() => {
|
|
1439
|
+
if (!rawInputEnabled || !props.pointerInput || !props.keyboardInput) {
|
|
1440
|
+
actionQueueEpochRef.current += 1;
|
|
1441
|
+
clearBufferedInput();
|
|
1442
|
+
}
|
|
1443
|
+
}, [clearBufferedInput, props.keyboardInput, props.pointerInput, rawInputEnabled]);
|
|
1102
1444
|
|
|
1103
1445
|
const enqueue = useCallback(
|
|
1104
|
-
(
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1446
|
+
(
|
|
1447
|
+
action: ComputerAction,
|
|
1448
|
+
frame: ComputerFrame | null,
|
|
1449
|
+
after?: (isCurrent: () => boolean) => Promise<void>,
|
|
1450
|
+
continuation?: ComputerQueuedAction,
|
|
1451
|
+
) => {
|
|
1452
|
+
const epoch = actionQueueEpochRef.current;
|
|
1453
|
+
const plainText = action.type === "keyboard" && action.action === "type";
|
|
1454
|
+
const queuedText = queuedTextRef.current;
|
|
1455
|
+
if (
|
|
1456
|
+
plainText &&
|
|
1457
|
+
!frame &&
|
|
1458
|
+
!after &&
|
|
1459
|
+
!continuation &&
|
|
1460
|
+
queuedText?.epoch === epoch &&
|
|
1461
|
+
queuedText.delivery.completion === actionTailRef.current &&
|
|
1462
|
+
queuedText.action.value.length + action.value.length <= 1_000_000
|
|
1463
|
+
) {
|
|
1464
|
+
// Only combine adjacent text that has not crossed the dispatch boundary.
|
|
1465
|
+
queuedText.action.value += action.value;
|
|
1466
|
+
return queuedText.delivery;
|
|
1467
|
+
}
|
|
1468
|
+
queuedTextRef.current = null;
|
|
1469
|
+
const dispatch = actionRef.current;
|
|
1470
|
+
const admittedTarget = targetRef.current;
|
|
1471
|
+
const isCurrent = () => mountedRef.current && epoch === actionQueueEpochRef.current;
|
|
1472
|
+
const operationId = crypto.randomUUID();
|
|
1473
|
+
let markStarted!: (value: boolean) => void;
|
|
1474
|
+
const started = new Promise<boolean>((resolve) => {
|
|
1475
|
+
markStarted = resolve;
|
|
1476
|
+
});
|
|
1477
|
+
const previous = actionTailRef.current;
|
|
1478
|
+
// Only the real second click may start before its first HTTP receipt.
|
|
1479
|
+
// The controller queue and native causal proof still serialize delivery.
|
|
1480
|
+
const ready =
|
|
1481
|
+
continuation && previous === continuation.completion
|
|
1482
|
+
? continuation.started
|
|
1483
|
+
: previous.then(() => true);
|
|
1484
|
+
const delivery = ready
|
|
1485
|
+
.then(async (firstStarted) => {
|
|
1486
|
+
if (queuedTextRef.current?.action === action) queuedTextRef.current = null;
|
|
1487
|
+
if (!firstStarted || !isCurrent()) {
|
|
1488
|
+
markStarted(false);
|
|
1489
|
+
return;
|
|
1490
|
+
}
|
|
1491
|
+
const pending = dispatch(action, frame, operationId);
|
|
1492
|
+
markStarted(true);
|
|
1493
|
+
const receipt = await pending;
|
|
1494
|
+
const isCurrentResult = () => isCurrent() && !isStaleComputerActionResult(receipt);
|
|
1495
|
+
if (!isCurrentResult()) return;
|
|
1496
|
+
const observedTarget = receipt.observation?.target;
|
|
1497
|
+
if (
|
|
1498
|
+
!admittedTarget ||
|
|
1499
|
+
receipt.computerSessionId !== admittedTarget.computerSessionId ||
|
|
1500
|
+
receipt.controllerGeneration !== admittedTarget.controllerGeneration ||
|
|
1501
|
+
receipt.targetId !== admittedTarget.id ||
|
|
1502
|
+
(observedTarget &&
|
|
1503
|
+
(observedTarget.computerSessionId !== admittedTarget.computerSessionId ||
|
|
1504
|
+
observedTarget.controllerGeneration !== admittedTarget.controllerGeneration ||
|
|
1505
|
+
observedTarget.id !== admittedTarget.id ||
|
|
1506
|
+
observedTarget.targetGeneration !== admittedTarget.targetGeneration))
|
|
1507
|
+
) {
|
|
1508
|
+
// Receipt projection can replace the target before React remounts us.
|
|
1509
|
+
actionQueueEpochRef.current += 1;
|
|
1510
|
+
clearBufferedInput();
|
|
1511
|
+
return;
|
|
1512
|
+
}
|
|
1513
|
+
await after?.(isCurrentResult);
|
|
1110
1514
|
})
|
|
1111
|
-
.catch((cause) =>
|
|
1515
|
+
.catch((cause) => {
|
|
1516
|
+
markStarted(false);
|
|
1517
|
+
if (!isCurrent() || isStaleComputerActionResult(cause)) return;
|
|
1518
|
+
actionQueueEpochRef.current += 1;
|
|
1519
|
+
clearBufferedInput();
|
|
1520
|
+
errorRef.current(cause);
|
|
1521
|
+
});
|
|
1522
|
+
const completion = continuation
|
|
1523
|
+
? Promise.all([previous, delivery]).then(() => undefined)
|
|
1524
|
+
: delivery;
|
|
1525
|
+
actionTailRef.current = completion;
|
|
1526
|
+
const queued = { operationId, started, completion };
|
|
1527
|
+
if (plainText && !frame && !after && !continuation) {
|
|
1528
|
+
queuedTextRef.current = { action, epoch, delivery: queued };
|
|
1529
|
+
}
|
|
1530
|
+
return queued;
|
|
1112
1531
|
},
|
|
1113
|
-
[],
|
|
1532
|
+
[clearBufferedInput],
|
|
1114
1533
|
);
|
|
1115
1534
|
|
|
1116
1535
|
const point = useCallback(
|
|
@@ -1133,6 +1552,7 @@ function ComputerViewport(props: {
|
|
|
1133
1552
|
if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
|
|
1134
1553
|
clickTimerRef.current = null;
|
|
1135
1554
|
lastClickRef.current = null;
|
|
1555
|
+
if (!pending.pending) return;
|
|
1136
1556
|
enqueue(
|
|
1137
1557
|
{
|
|
1138
1558
|
type: "pointer",
|
|
@@ -1145,24 +1565,55 @@ function ComputerViewport(props: {
|
|
|
1145
1565
|
);
|
|
1146
1566
|
}, [enqueue]);
|
|
1147
1567
|
|
|
1568
|
+
const flushPendingWheel = useCallback(() => {
|
|
1569
|
+
const batch = wheelRef.current;
|
|
1570
|
+
if (!batch) return;
|
|
1571
|
+
if (batch.timer) clearTimeout(batch.timer);
|
|
1572
|
+
wheelRef.current = null;
|
|
1573
|
+
enqueue(
|
|
1574
|
+
{
|
|
1575
|
+
type: "pointer",
|
|
1576
|
+
frameId: batch.frame.frameId,
|
|
1577
|
+
action: "scroll",
|
|
1578
|
+
x: batch.x,
|
|
1579
|
+
y: batch.y,
|
|
1580
|
+
deltaX: batch.deltaX,
|
|
1581
|
+
deltaY: batch.deltaY,
|
|
1582
|
+
},
|
|
1583
|
+
batch.frame,
|
|
1584
|
+
);
|
|
1585
|
+
}, [enqueue]);
|
|
1586
|
+
|
|
1148
1587
|
const pointerDown = (event: PointerEvent<HTMLCanvasElement>) => {
|
|
1149
|
-
|
|
1588
|
+
const frame = paintedFrameRef.current;
|
|
1589
|
+
if (
|
|
1590
|
+
!frame ||
|
|
1591
|
+
(!props.pointerClickContinuation && props.mutating) ||
|
|
1592
|
+
!rawInputEnabled ||
|
|
1593
|
+
event.button !== 0
|
|
1594
|
+
)
|
|
1595
|
+
return;
|
|
1596
|
+
// Preserve the keyboard sink's focus through the canvas pointer default action.
|
|
1597
|
+
event.preventDefault();
|
|
1598
|
+
if (keyboardInputEnabled) inputRef.current?.focus({ preventScroll: true });
|
|
1599
|
+
if (!pointerInputEnabled) return;
|
|
1600
|
+
flushPendingWheel();
|
|
1150
1601
|
flushPendingText();
|
|
1151
1602
|
pointerStartRef.current = {
|
|
1152
1603
|
x: event.clientX,
|
|
1153
1604
|
y: event.clientY,
|
|
1154
1605
|
pointerId: event.pointerId,
|
|
1155
|
-
frame
|
|
1606
|
+
frame,
|
|
1156
1607
|
};
|
|
1157
1608
|
event.currentTarget.setPointerCapture?.(event.pointerId);
|
|
1158
|
-
inputRef.current?.focus({ preventScroll: true });
|
|
1159
1609
|
};
|
|
1160
1610
|
|
|
1161
1611
|
const pointerUp = (event: PointerEvent<HTMLCanvasElement>) => {
|
|
1162
|
-
if (!
|
|
1612
|
+
if (!pointerInputEnabled) return;
|
|
1163
1613
|
const start = pointerStartRef.current;
|
|
1164
1614
|
pointerStartRef.current = null;
|
|
1165
1615
|
if (!start || start.pointerId !== event.pointerId) return;
|
|
1616
|
+
const canvasRect = event.currentTarget.getBoundingClientRect();
|
|
1166
1617
|
const from = point(start.frame, start.x, start.y);
|
|
1167
1618
|
const to = point(start.frame, event.clientX, event.clientY);
|
|
1168
1619
|
if (!from || !to) return;
|
|
@@ -1187,8 +1638,18 @@ function ComputerViewport(props: {
|
|
|
1187
1638
|
if (
|
|
1188
1639
|
previous &&
|
|
1189
1640
|
now - previous.at < 280 &&
|
|
1190
|
-
|
|
1191
|
-
|
|
1641
|
+
previous.pending === !props.pointerClickContinuation &&
|
|
1642
|
+
(props.pointerClickContinuation
|
|
1643
|
+
? Math.hypot(previous.clientX - event.clientX, previous.clientY - event.clientY) < 6 &&
|
|
1644
|
+
previous.canvasRect.left === canvasRect.left &&
|
|
1645
|
+
previous.canvasRect.top === canvasRect.top &&
|
|
1646
|
+
previous.canvasRect.width === canvasRect.width &&
|
|
1647
|
+
previous.canvasRect.height === canvasRect.height &&
|
|
1648
|
+
sameComputerTarget(previous.frame, start.frame) &&
|
|
1649
|
+
previous.frame.width === start.frame.width &&
|
|
1650
|
+
previous.frame.height === start.frame.height
|
|
1651
|
+
: Math.hypot(previous.x - to.x, previous.y - to.y) < 6 &&
|
|
1652
|
+
sameFrameFence(previous.frame, start.frame))
|
|
1192
1653
|
) {
|
|
1193
1654
|
if (clickTimerRef.current) clearTimeout(clickTimerRef.current);
|
|
1194
1655
|
clickTimerRef.current = null;
|
|
@@ -1197,15 +1658,47 @@ function ComputerViewport(props: {
|
|
|
1197
1658
|
{
|
|
1198
1659
|
type: "pointer",
|
|
1199
1660
|
frameId: start.frame.frameId,
|
|
1200
|
-
action: "double_click",
|
|
1661
|
+
action: props.pointerClickContinuation ? "click" : "double_click",
|
|
1662
|
+
...(props.pointerClickContinuation ? { clickCount: 2 as const } : {}),
|
|
1663
|
+
...(previous.delivery
|
|
1664
|
+
? { continuationOfOperationId: previous.delivery.operationId }
|
|
1665
|
+
: {}),
|
|
1666
|
+
x: to.x,
|
|
1667
|
+
y: to.y,
|
|
1668
|
+
},
|
|
1669
|
+
start.frame,
|
|
1670
|
+
undefined,
|
|
1671
|
+
previous.delivery ?? undefined,
|
|
1672
|
+
);
|
|
1673
|
+
return;
|
|
1674
|
+
}
|
|
1675
|
+
flushPendingClick();
|
|
1676
|
+
lastClickRef.current = {
|
|
1677
|
+
at: now,
|
|
1678
|
+
x: to.x,
|
|
1679
|
+
y: to.y,
|
|
1680
|
+
frame: start.frame,
|
|
1681
|
+
clientX: event.clientX,
|
|
1682
|
+
clientY: event.clientY,
|
|
1683
|
+
canvasRect,
|
|
1684
|
+
pending: !props.pointerClickContinuation,
|
|
1685
|
+
delivery: null,
|
|
1686
|
+
};
|
|
1687
|
+
if (props.pointerClickContinuation) {
|
|
1688
|
+
const delivery = enqueue(
|
|
1689
|
+
{
|
|
1690
|
+
type: "pointer",
|
|
1691
|
+
frameId: start.frame.frameId,
|
|
1692
|
+
action: "click",
|
|
1693
|
+
clickCount: 1,
|
|
1201
1694
|
x: to.x,
|
|
1202
1695
|
y: to.y,
|
|
1203
1696
|
},
|
|
1204
1697
|
start.frame,
|
|
1205
1698
|
);
|
|
1699
|
+
if (lastClickRef.current) lastClickRef.current.delivery = delivery;
|
|
1206
1700
|
return;
|
|
1207
1701
|
}
|
|
1208
|
-
lastClickRef.current = { at: now, x: to.x, y: to.y, frame: start.frame };
|
|
1209
1702
|
clickTimerRef.current = setTimeout(() => {
|
|
1210
1703
|
clickTimerRef.current = null;
|
|
1211
1704
|
lastClickRef.current = null;
|
|
@@ -1224,9 +1717,10 @@ function ComputerViewport(props: {
|
|
|
1224
1717
|
|
|
1225
1718
|
const contextMenu = (event: MouseEvent<HTMLCanvasElement>) => {
|
|
1226
1719
|
event.preventDefault();
|
|
1227
|
-
if (!
|
|
1228
|
-
const frame =
|
|
1720
|
+
if (!pointerInputEnabled) return;
|
|
1721
|
+
const frame = paintedFrameRef.current;
|
|
1229
1722
|
if (!frame) return;
|
|
1723
|
+
flushPendingWheel();
|
|
1230
1724
|
flushPendingText();
|
|
1231
1725
|
flushPendingClick();
|
|
1232
1726
|
const at = point(frame, event.clientX, event.clientY);
|
|
@@ -1246,45 +1740,43 @@ function ComputerViewport(props: {
|
|
|
1246
1740
|
};
|
|
1247
1741
|
|
|
1248
1742
|
const wheel = (event: WheelEvent<HTMLCanvasElement>) => {
|
|
1249
|
-
if (!
|
|
1250
|
-
const frame =
|
|
1743
|
+
if (!pointerInputEnabled) return;
|
|
1744
|
+
const frame = paintedFrameRef.current;
|
|
1251
1745
|
if (!frame) return;
|
|
1252
1746
|
const at = point(frame, event.clientX, event.clientY);
|
|
1253
1747
|
if (!at) return;
|
|
1254
1748
|
flushPendingText();
|
|
1255
1749
|
flushPendingClick();
|
|
1256
1750
|
event.preventDefault();
|
|
1257
|
-
|
|
1258
|
-
if (
|
|
1751
|
+
let pending = wheelRef.current;
|
|
1752
|
+
if (
|
|
1753
|
+
pending &&
|
|
1754
|
+
(!sameComputerTarget(pending.frame, frame) ||
|
|
1755
|
+
Math.hypot(pending.x - at.x, pending.y - at.y) > 6)
|
|
1756
|
+
) {
|
|
1757
|
+
flushPendingWheel();
|
|
1758
|
+
pending = null;
|
|
1759
|
+
}
|
|
1259
1760
|
wheelRef.current = {
|
|
1260
1761
|
x: at.x,
|
|
1261
1762
|
y: at.y,
|
|
1262
|
-
deltaX: (pending
|
|
1263
|
-
deltaY: (pending
|
|
1763
|
+
deltaX: (pending?.deltaX ?? 0) + event.deltaX,
|
|
1764
|
+
deltaY: (pending?.deltaY ?? 0) + event.deltaY,
|
|
1264
1765
|
frame,
|
|
1265
|
-
timer: setTimeout(
|
|
1266
|
-
const batch = wheelRef.current;
|
|
1267
|
-
wheelRef.current = null;
|
|
1268
|
-
if (batch) {
|
|
1269
|
-
enqueue(
|
|
1270
|
-
{
|
|
1271
|
-
type: "pointer",
|
|
1272
|
-
frameId: batch.frame.frameId,
|
|
1273
|
-
action: "scroll",
|
|
1274
|
-
x: batch.x,
|
|
1275
|
-
y: batch.y,
|
|
1276
|
-
deltaX: batch.deltaX,
|
|
1277
|
-
deltaY: batch.deltaY,
|
|
1278
|
-
},
|
|
1279
|
-
batch.frame,
|
|
1280
|
-
);
|
|
1281
|
-
}
|
|
1282
|
-
}, 45),
|
|
1766
|
+
timer: pending?.timer ?? setTimeout(flushPendingWheel, 45),
|
|
1283
1767
|
};
|
|
1284
1768
|
};
|
|
1285
1769
|
|
|
1286
1770
|
const keyDown = (event: KeyboardEvent<HTMLTextAreaElement>) => {
|
|
1287
|
-
if (!
|
|
1771
|
+
if (!keyboardInputEnabled) return;
|
|
1772
|
+
if (
|
|
1773
|
+
composingRef.current ||
|
|
1774
|
+
event.nativeEvent.isComposing ||
|
|
1775
|
+
event.nativeEvent.keyCode === 229
|
|
1776
|
+
) {
|
|
1777
|
+
return;
|
|
1778
|
+
}
|
|
1779
|
+
flushPendingWheel();
|
|
1288
1780
|
const command = event.metaKey || event.ctrlKey;
|
|
1289
1781
|
if (
|
|
1290
1782
|
props.clipboardEnabled &&
|
|
@@ -1304,12 +1796,15 @@ function ComputerViewport(props: {
|
|
|
1304
1796
|
};
|
|
1305
1797
|
|
|
1306
1798
|
const copy = (event: ClipboardEvent<HTMLTextAreaElement>) => {
|
|
1307
|
-
if (!props.clipboardEnabled || !
|
|
1799
|
+
if (!props.clipboardEnabled || !keyboardInputEnabled) return;
|
|
1308
1800
|
event.preventDefault();
|
|
1801
|
+
flushPendingWheel();
|
|
1309
1802
|
flushPendingText();
|
|
1310
1803
|
flushPendingClick();
|
|
1311
|
-
|
|
1312
|
-
|
|
1804
|
+
const readClipboard = readClipboardRef.current;
|
|
1805
|
+
enqueue({ type: "clipboard", operation: "copy" }, null, async (isCurrent) => {
|
|
1806
|
+
const clipboard = await readClipboard();
|
|
1807
|
+
if (!isCurrent()) return;
|
|
1313
1808
|
if (!clipboard.text) return;
|
|
1314
1809
|
if (!(await copyTextToClipboard(clipboard.text))) {
|
|
1315
1810
|
throw new Error("Desktop text could not be copied to the local clipboard");
|
|
@@ -1318,19 +1813,26 @@ function ComputerViewport(props: {
|
|
|
1318
1813
|
};
|
|
1319
1814
|
|
|
1320
1815
|
const paste = (event: ClipboardEvent<HTMLTextAreaElement>) => {
|
|
1321
|
-
if (!props.clipboardEnabled || !
|
|
1816
|
+
if (!props.clipboardEnabled || !keyboardInputEnabled) return;
|
|
1322
1817
|
event.preventDefault();
|
|
1818
|
+
flushPendingWheel();
|
|
1323
1819
|
flushPendingText();
|
|
1324
1820
|
flushPendingClick();
|
|
1325
1821
|
const text = event.clipboardData.getData("text/plain");
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1822
|
+
const readClipboard = readClipboardRef.current;
|
|
1823
|
+
const dispatch = actionRef.current;
|
|
1824
|
+
enqueue({ type: "clipboard", operation: "write", text }, null, async (isCurrent) => {
|
|
1825
|
+
const clipboard = await readClipboard();
|
|
1826
|
+
if (!isCurrent()) return;
|
|
1827
|
+
assertExactComputerClipboard(clipboard, text);
|
|
1828
|
+
await dispatch({ type: "clipboard", operation: "paste" }, null);
|
|
1329
1829
|
});
|
|
1330
1830
|
};
|
|
1331
1831
|
|
|
1332
|
-
const input = (value: string) => {
|
|
1333
|
-
if (
|
|
1832
|
+
const input = (value: string, nativeComposing = false) => {
|
|
1833
|
+
if (composingRef.current || nativeComposing) return;
|
|
1834
|
+
if (!value || !keyboardInputEnabled) return;
|
|
1835
|
+
flushPendingWheel();
|
|
1334
1836
|
flushPendingClick();
|
|
1335
1837
|
const pending = pendingTextRef.current;
|
|
1336
1838
|
if (pending) {
|
|
@@ -1346,7 +1848,17 @@ function ComputerViewport(props: {
|
|
|
1346
1848
|
if (inputRef.current) inputRef.current.value = "";
|
|
1347
1849
|
};
|
|
1348
1850
|
|
|
1349
|
-
const
|
|
1851
|
+
const compositionEnd = (event: CompositionEvent<HTMLTextAreaElement>) => {
|
|
1852
|
+
composingRef.current = false;
|
|
1853
|
+
input(event.currentTarget.value || event.data);
|
|
1854
|
+
};
|
|
1855
|
+
|
|
1856
|
+
const showCanvas =
|
|
1857
|
+
props.frame !== null &&
|
|
1858
|
+
paintedFrame !== null &&
|
|
1859
|
+
sameComputerTarget(props.frame, paintedFrame) &&
|
|
1860
|
+
!props.machineLocked &&
|
|
1861
|
+
!streamFailed;
|
|
1350
1862
|
return (
|
|
1351
1863
|
<div className="relative min-h-0 flex-1 overflow-hidden bg-black">
|
|
1352
1864
|
<canvas
|
|
@@ -1354,7 +1866,7 @@ function ComputerViewport(props: {
|
|
|
1354
1866
|
className={cn(
|
|
1355
1867
|
"absolute inset-0 m-auto max-h-full max-w-full touch-none focus-visible:outline-hidden focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-og-accent",
|
|
1356
1868
|
!showCanvas && "invisible",
|
|
1357
|
-
!
|
|
1869
|
+
!pointerInputEnabled && "cursor-default",
|
|
1358
1870
|
)}
|
|
1359
1871
|
onPointerDown={pointerDown}
|
|
1360
1872
|
onPointerUp={pointerUp}
|
|
@@ -1368,7 +1880,13 @@ function ComputerViewport(props: {
|
|
|
1368
1880
|
<textarea
|
|
1369
1881
|
ref={inputRef}
|
|
1370
1882
|
defaultValue=""
|
|
1371
|
-
onInput={(event) =>
|
|
1883
|
+
onInput={(event) =>
|
|
1884
|
+
input(event.currentTarget.value, (event.nativeEvent as InputEvent).isComposing)
|
|
1885
|
+
}
|
|
1886
|
+
onCompositionStart={() => {
|
|
1887
|
+
composingRef.current = true;
|
|
1888
|
+
}}
|
|
1889
|
+
onCompositionEnd={compositionEnd}
|
|
1372
1890
|
onKeyDown={keyDown}
|
|
1373
1891
|
onCopy={copy}
|
|
1374
1892
|
onPaste={paste}
|
|
@@ -1377,12 +1895,14 @@ function ComputerViewport(props: {
|
|
|
1377
1895
|
autoCapitalize="off"
|
|
1378
1896
|
autoCorrect="off"
|
|
1379
1897
|
spellCheck={false}
|
|
1380
|
-
disabled={!
|
|
1898
|
+
disabled={!keyboardInputEnabled}
|
|
1381
1899
|
/>
|
|
1382
1900
|
{!showCanvas ? (
|
|
1383
1901
|
<ComputerViewportFallback
|
|
1384
1902
|
observation={props.observation}
|
|
1385
1903
|
machineLocked={props.machineLocked}
|
|
1904
|
+
controlUnavailable={props.controlUnavailable}
|
|
1905
|
+
inputAllowed={props.inputAllowed}
|
|
1386
1906
|
connectionState={props.connectionState}
|
|
1387
1907
|
error={props.connectionError}
|
|
1388
1908
|
onAction={(action) => enqueue(action, null)}
|
|
@@ -1394,17 +1914,25 @@ function ComputerViewport(props: {
|
|
|
1394
1914
|
<LoaderCircleIcon className="size-3 animate-spin" /> Acting
|
|
1395
1915
|
</div>
|
|
1396
1916
|
) : null}
|
|
1397
|
-
{showCanvas && !rawInputEnabled ? (
|
|
1398
|
-
<div className="absolute bottom-3 left-1/2 flex -translate-x-1/2 items-center gap-2 rounded-full border border-white/10 bg-black/70 py-1 pl-3 pr-1 text-[11px] text-white/80 backdrop-blur">
|
|
1399
|
-
<span>
|
|
1400
|
-
|
|
1917
|
+
{showCanvas && (!rawInputEnabled || !props.pointerInput || !props.keyboardInput) ? (
|
|
1918
|
+
<div className="absolute bottom-3 left-1/2 flex w-max max-w-[calc(100%-1.5rem)] -translate-x-1/2 items-center gap-2 rounded-full border border-white/10 bg-black/70 py-1 pl-3 pr-1 text-[11px] text-white/80 backdrop-blur">
|
|
1919
|
+
<span>
|
|
1920
|
+
{!rawInputEnabled && (props.pointerInput || props.keyboardInput)
|
|
1921
|
+
? "Background view · use app controls"
|
|
1922
|
+
: !props.pointerInput && !props.keyboardInput
|
|
1923
|
+
? "View only · mouse and keyboard unavailable"
|
|
1924
|
+
: props.keyboardInput
|
|
1925
|
+
? "Keyboard only · mouse unavailable"
|
|
1926
|
+
: "Mouse only · keyboard unavailable"}
|
|
1927
|
+
</span>
|
|
1928
|
+
{!rawInputEnabled && props.target && (props.pointerInput || props.keyboardInput) ? (
|
|
1401
1929
|
<button
|
|
1402
1930
|
type="button"
|
|
1403
1931
|
disabled={props.mutating}
|
|
1404
1932
|
onClick={() => enqueue({ type: "focus", targetId: props.target!.id }, null)}
|
|
1405
|
-
className="rounded-full bg-white/10 px-2 py-0.5 font-medium text-white transition hover:bg-white/20 disabled:opacity-50"
|
|
1933
|
+
className="shrink-0 whitespace-nowrap rounded-full bg-white/10 px-2 py-0.5 font-medium text-white transition hover:bg-white/20 disabled:opacity-50"
|
|
1406
1934
|
>
|
|
1407
|
-
|
|
1935
|
+
Bring to front
|
|
1408
1936
|
</button>
|
|
1409
1937
|
) : null}
|
|
1410
1938
|
</div>
|
|
@@ -1422,6 +1950,8 @@ function assertExactComputerClipboard(clipboard: ComputerClipboard, expected: st
|
|
|
1422
1950
|
function ComputerViewportFallback(props: {
|
|
1423
1951
|
observation: ComputerObservation | null;
|
|
1424
1952
|
machineLocked: boolean;
|
|
1953
|
+
controlUnavailable: boolean;
|
|
1954
|
+
inputAllowed: boolean;
|
|
1425
1955
|
connectionState: string;
|
|
1426
1956
|
error: Error | null;
|
|
1427
1957
|
onAction: (action: ComputerAction) => void;
|
|
@@ -1431,46 +1961,60 @@ function ComputerViewportFallback(props: {
|
|
|
1431
1961
|
return (
|
|
1432
1962
|
<div className="absolute inset-0 grid place-items-center bg-og-bg p-6">
|
|
1433
1963
|
<div className="max-w-sm text-center">
|
|
1434
|
-
<LockKeyholeIcon className="mx-auto size-6 text-og-muted" />
|
|
1964
|
+
<LockKeyholeIcon className="mx-auto size-6 text-og-fg-muted" />
|
|
1435
1965
|
<p className="mt-3 text-og-menu font-medium text-og-fg">Machine locked</p>
|
|
1436
|
-
<p className="mt-1 text-og-control leading-5 text-og-muted">
|
|
1966
|
+
<p className="mt-1 text-og-control leading-5 text-og-fg-muted">
|
|
1437
1967
|
Live frames and input stay private until someone unlocks the machine.
|
|
1438
1968
|
</p>
|
|
1439
1969
|
</div>
|
|
1440
1970
|
</div>
|
|
1441
1971
|
);
|
|
1442
1972
|
}
|
|
1973
|
+
const appControls = props.observation?.target.kind === "app";
|
|
1443
1974
|
const controlFailure = interactionControlFailureFromError(props.error);
|
|
1444
|
-
const interactive =
|
|
1975
|
+
const interactive = semanticControls(props.observation)
|
|
1445
1976
|
.filter((node) => semanticAction(node) !== null)
|
|
1446
1977
|
.slice(0, 10);
|
|
1447
1978
|
return (
|
|
1448
1979
|
<div className="absolute inset-0 grid place-items-center bg-og-bg p-6">
|
|
1449
|
-
<div className="w-full max-w-md rounded-og-lg border border-og-border bg-og-surface-
|
|
1980
|
+
<div className="w-full max-w-md rounded-og-lg border border-og-border bg-og-surface-1 p-4 shadow-lg">
|
|
1450
1981
|
<div className="flex items-center gap-2">
|
|
1451
1982
|
{props.error ? (
|
|
1452
|
-
<CircleAlertIcon className="size-4 text-og-
|
|
1983
|
+
<CircleAlertIcon className="size-4 text-og-danger" />
|
|
1984
|
+
) : appControls ? (
|
|
1985
|
+
<PanelsTopLeftIcon className="size-4 text-og-fg-muted" />
|
|
1453
1986
|
) : (
|
|
1454
|
-
<LoaderCircleIcon className="size-4 animate-spin text-og-muted" />
|
|
1987
|
+
<LoaderCircleIcon className="size-4 animate-spin text-og-fg-muted" />
|
|
1455
1988
|
)}
|
|
1456
1989
|
<p className="text-og-menu font-medium text-og-fg">
|
|
1457
1990
|
{isAttachedChromeGenerationLossError(props.error)
|
|
1458
1991
|
? "Chrome reconnected—open a fresh browser/desktop."
|
|
1459
|
-
: props.
|
|
1460
|
-
? "
|
|
1461
|
-
:
|
|
1992
|
+
: !props.inputAllowed
|
|
1993
|
+
? "View only"
|
|
1994
|
+
: props.controlUnavailable
|
|
1995
|
+
? "Desktop controls unavailable"
|
|
1996
|
+
: props.error
|
|
1997
|
+
? "Live view disconnected"
|
|
1998
|
+
: appControls
|
|
1999
|
+
? "App controls"
|
|
2000
|
+
: computerConnectionLabel(props.connectionState)}
|
|
1462
2001
|
</p>
|
|
1463
2002
|
</div>
|
|
1464
2003
|
{props.error ? (
|
|
1465
|
-
<p className="mt-2 text-og-control leading-5 text-og-muted">
|
|
2004
|
+
<p className="mt-2 text-og-control leading-5 text-og-fg-muted">
|
|
1466
2005
|
{isAttachedChromeGenerationLossError(props.error)
|
|
1467
2006
|
? "Chrome reconnected—open a fresh browser/desktop. Use Browser → New browser → Connected Chrome."
|
|
1468
2007
|
: (controlFailure?.message ?? props.error.message)}
|
|
1469
2008
|
</p>
|
|
1470
2009
|
) : null}
|
|
1471
|
-
{
|
|
2010
|
+
{!props.inputAllowed ? (
|
|
2011
|
+
<p className="mt-2 text-og-control leading-5 text-og-fg-muted">
|
|
2012
|
+
Refresh to check desktop controls.
|
|
2013
|
+
</p>
|
|
2014
|
+
) : null}
|
|
2015
|
+
{interactive.length > 0 && !props.controlUnavailable && props.inputAllowed ? (
|
|
1472
2016
|
<div className="mt-3 border-t border-og-border pt-3">
|
|
1473
|
-
<p className="mb-2 text-og-xs text-og-subtle">
|
|
2017
|
+
<p className="mb-2 text-og-xs text-og-fg-subtle">App controls remain available</p>
|
|
1474
2018
|
<div className="flex flex-wrap gap-1.5">
|
|
1475
2019
|
{interactive.map((node) => (
|
|
1476
2020
|
<button
|
|
@@ -1492,7 +2036,7 @@ function ComputerViewportFallback(props: {
|
|
|
1492
2036
|
<button
|
|
1493
2037
|
type="button"
|
|
1494
2038
|
onClick={props.onReconnect}
|
|
1495
|
-
className="mt-3 text-og-control font-medium
|
|
2039
|
+
className="mt-3 text-og-control font-medium og-link"
|
|
1496
2040
|
>
|
|
1497
2041
|
{controlFailure?.retryable === false ? "Try again" : "Reconnect"}
|
|
1498
2042
|
</button>
|
|
@@ -1505,18 +2049,31 @@ function ComputerViewportFallback(props: {
|
|
|
1505
2049
|
function ComputerSemanticPanel(props: {
|
|
1506
2050
|
observation: ComputerObservation | null;
|
|
1507
2051
|
mutating: boolean;
|
|
2052
|
+
controlUnavailable: boolean;
|
|
2053
|
+
inputAllowed: boolean;
|
|
1508
2054
|
onAction: (action: ComputerAction) => void;
|
|
1509
2055
|
}) {
|
|
1510
|
-
const nodes =
|
|
2056
|
+
const nodes = semanticControls(props.observation).slice(0, 100);
|
|
1511
2057
|
return (
|
|
1512
|
-
<aside className="w-
|
|
1513
|
-
<div className="mb-2 flex items-center gap-1.5 px-1 text-og-xs font-medium uppercase tracking-[0.1em] text-og-subtle">
|
|
1514
|
-
<KeyboardIcon className="size-3" />
|
|
2058
|
+
<aside className="box-border max-h-[40%] w-full shrink-0 overflow-y-auto border-t border-og-border bg-og-surface-1 p-2 @xl/computer-viewer:max-h-none @xl/computer-viewer:w-64 @xl/computer-viewer:border-t-0 @xl/computer-viewer:border-l">
|
|
2059
|
+
<div className="mb-2 flex items-center gap-1.5 px-1 text-og-xs font-medium uppercase tracking-[0.1em] text-og-fg-subtle">
|
|
2060
|
+
<KeyboardIcon className="size-3" /> App controls
|
|
1515
2061
|
</div>
|
|
1516
|
-
{
|
|
1517
|
-
<p className="px-1 py-2 text-og-control leading-5 text-og-muted">
|
|
1518
|
-
|
|
2062
|
+
{!props.inputAllowed ? (
|
|
2063
|
+
<p className="px-1 py-2 text-og-control leading-5 text-og-fg-muted">
|
|
2064
|
+
View only · refresh to check desktop controls.
|
|
1519
2065
|
</p>
|
|
2066
|
+
) : props.controlUnavailable ? (
|
|
2067
|
+
<p className="px-1 py-2 text-og-control leading-5 text-og-fg-muted">
|
|
2068
|
+
Reconnect to use app controls.
|
|
2069
|
+
</p>
|
|
2070
|
+
) : null}
|
|
2071
|
+
{nodes.length === 0 ? (
|
|
2072
|
+
props.controlUnavailable ? null : (
|
|
2073
|
+
<p className="px-1 py-2 text-og-control leading-5 text-og-fg-muted">
|
|
2074
|
+
This view has no accessibility controls. Use the live image instead.
|
|
2075
|
+
</p>
|
|
2076
|
+
)
|
|
1520
2077
|
) : (
|
|
1521
2078
|
<div className="space-y-0.5">
|
|
1522
2079
|
{nodes.map((node) => (
|
|
@@ -1554,6 +2111,7 @@ function ComputerSemanticControl(props: {
|
|
|
1554
2111
|
className="rounded-og-sm px-2 py-1.5 hover:bg-og-surface-2"
|
|
1555
2112
|
onSubmit={(event) => {
|
|
1556
2113
|
event.preventDefault();
|
|
2114
|
+
if (props.mutating) return;
|
|
1557
2115
|
props.onAction({
|
|
1558
2116
|
type: "semantic",
|
|
1559
2117
|
locator: { kind: "ref", ref: node.ref },
|
|
@@ -1562,7 +2120,7 @@ function ComputerSemanticControl(props: {
|
|
|
1562
2120
|
});
|
|
1563
2121
|
}}
|
|
1564
2122
|
>
|
|
1565
|
-
<label className="block truncate text-[10px] uppercase text-og-subtle">
|
|
2123
|
+
<label className="block truncate text-[10px] uppercase text-og-fg-subtle">
|
|
1566
2124
|
{node.name || node.identifier || node.role}
|
|
1567
2125
|
</label>
|
|
1568
2126
|
<div className="mt-1 flex gap-1">
|
|
@@ -1594,7 +2152,7 @@ function ComputerSemanticControl(props: {
|
|
|
1594
2152
|
}}
|
|
1595
2153
|
className="flex w-full items-start gap-2 rounded-og-sm px-2 py-1.5 text-left transition hover:bg-og-surface-2 disabled:cursor-default disabled:hover:bg-transparent"
|
|
1596
2154
|
>
|
|
1597
|
-
<span className="mt-0.5 text-[10px] uppercase text-og-subtle">{node.role}</span>
|
|
2155
|
+
<span className="mt-0.5 text-[10px] uppercase text-og-fg-subtle">{node.role}</span>
|
|
1598
2156
|
<span className="min-w-0 flex-1 truncate text-og-control text-og-fg">
|
|
1599
2157
|
{node.name || semanticValue(node) || node.identifier || "Unnamed control"}
|
|
1600
2158
|
</span>
|
|
@@ -1606,6 +2164,8 @@ function ComputerStatusBar(props: {
|
|
|
1606
2164
|
session: ComputerSession | null;
|
|
1607
2165
|
target: ComputerTarget | null;
|
|
1608
2166
|
connectionState: string;
|
|
2167
|
+
controlUnavailable: boolean;
|
|
2168
|
+
inputAllowed: boolean;
|
|
1609
2169
|
refreshing: boolean;
|
|
1610
2170
|
showControls: boolean;
|
|
1611
2171
|
controlCount: number;
|
|
@@ -1613,7 +2173,7 @@ function ComputerStatusBar(props: {
|
|
|
1613
2173
|
}) {
|
|
1614
2174
|
const screen = props.target?.kind === "screen";
|
|
1615
2175
|
return (
|
|
1616
|
-
<div className="flex h-8 shrink-0 items-center gap-2 border-t border-og-border bg-og-surface-
|
|
2176
|
+
<div className="flex h-8 shrink-0 items-center gap-2 border-t border-og-border bg-og-surface-1 px-2 text-og-xs text-og-fg-subtle">
|
|
1617
2177
|
<span
|
|
1618
2178
|
className={cn(
|
|
1619
2179
|
"size-1.5 rounded-full",
|
|
@@ -1621,28 +2181,48 @@ function ComputerStatusBar(props: {
|
|
|
1621
2181
|
)}
|
|
1622
2182
|
/>
|
|
1623
2183
|
<span>
|
|
1624
|
-
{props.
|
|
2184
|
+
{!props.inputAllowed
|
|
2185
|
+
? "View only"
|
|
2186
|
+
: props.controlUnavailable
|
|
2187
|
+
? "Controls unavailable"
|
|
2188
|
+
: props.target?.kind === "app"
|
|
2189
|
+
? "App controls"
|
|
2190
|
+
: props.connectionState === "live"
|
|
2191
|
+
? "Live"
|
|
2192
|
+
: computerConnectionLabel(props.connectionState)}
|
|
1625
2193
|
</span>
|
|
1626
2194
|
<span className="min-w-0 flex-1 truncate">
|
|
1627
|
-
{
|
|
1628
|
-
? "
|
|
1629
|
-
: props.
|
|
1630
|
-
? "
|
|
1631
|
-
:
|
|
2195
|
+
{!props.inputAllowed
|
|
2196
|
+
? "Refresh to check desktop controls"
|
|
2197
|
+
: props.controlUnavailable
|
|
2198
|
+
? "Reconnect to use desktop input"
|
|
2199
|
+
: screen
|
|
2200
|
+
? "Full screen · input may move pointer and focus"
|
|
2201
|
+
: props.target?.kind === "app"
|
|
2202
|
+
? props.session?.capabilities?.backgroundActions
|
|
2203
|
+
? "App · controls work in the background"
|
|
2204
|
+
: "App controls"
|
|
2205
|
+
: props.session?.capabilities?.backgroundInput
|
|
2206
|
+
? "Window · clicks and typing stay in the background"
|
|
2207
|
+
: props.session?.capabilities?.backgroundActions
|
|
2208
|
+
? "Window · app controls work in the background"
|
|
2209
|
+
: (props.target?.kind ?? "Desktop")}
|
|
1632
2210
|
</span>
|
|
1633
2211
|
{screen ? <MousePointer2Icon className="size-3" aria-hidden /> : null}
|
|
1634
2212
|
{props.refreshing ? <LoaderCircleIcon className="size-3 animate-spin" /> : null}
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
2213
|
+
{props.controlCount > 0 || props.showControls ? (
|
|
2214
|
+
<button
|
|
2215
|
+
type="button"
|
|
2216
|
+
onClick={props.onToggleControls}
|
|
2217
|
+
aria-pressed={props.showControls}
|
|
2218
|
+
className={cn(
|
|
2219
|
+
"rounded px-1.5 py-0.5 transition hover:bg-og-surface-2 hover:text-og-fg",
|
|
2220
|
+
props.showControls && "bg-og-surface-2 text-og-fg",
|
|
2221
|
+
)}
|
|
2222
|
+
>
|
|
2223
|
+
Controls {props.controlCount}
|
|
2224
|
+
</button>
|
|
2225
|
+
) : null}
|
|
1646
2226
|
</div>
|
|
1647
2227
|
);
|
|
1648
2228
|
}
|
|
@@ -1651,7 +2231,7 @@ function ComputerNotice(props: { icon: ReactNode; text: string; className?: stri
|
|
|
1651
2231
|
return (
|
|
1652
2232
|
<div
|
|
1653
2233
|
className={cn(
|
|
1654
|
-
"grid h-full place-items-center bg-og-bg text-og-control text-og-muted",
|
|
2234
|
+
"grid h-full place-items-center bg-og-bg text-og-control text-og-fg-muted",
|
|
1655
2235
|
props.className,
|
|
1656
2236
|
)}
|
|
1657
2237
|
>
|
|
@@ -1663,11 +2243,37 @@ function ComputerNotice(props: { icon: ReactNode; text: string; className?: stri
|
|
|
1663
2243
|
);
|
|
1664
2244
|
}
|
|
1665
2245
|
|
|
1666
|
-
|
|
2246
|
+
const structuralRoles = new Set([
|
|
2247
|
+
"frame",
|
|
2248
|
+
"panel",
|
|
2249
|
+
"section",
|
|
2250
|
+
"document",
|
|
2251
|
+
"document web",
|
|
2252
|
+
"static",
|
|
2253
|
+
"static text",
|
|
2254
|
+
"heading",
|
|
2255
|
+
"paragraph",
|
|
2256
|
+
"notification",
|
|
2257
|
+
"separator",
|
|
2258
|
+
"tool bar",
|
|
2259
|
+
"page tab list",
|
|
2260
|
+
]);
|
|
2261
|
+
|
|
2262
|
+
function semanticControls(observation: ComputerObservation | null): InteractionSemanticNode[] {
|
|
1667
2263
|
if (observation?.semantic?.kind !== "snapshot") return [];
|
|
1668
2264
|
const result: InteractionSemanticNode[] = [];
|
|
1669
2265
|
const visit = (node: InteractionSemanticNode) => {
|
|
1670
|
-
|
|
2266
|
+
const role = node.role.trim().toLowerCase();
|
|
2267
|
+
const label = node.name?.trim() || node.identifier?.trim();
|
|
2268
|
+
if (
|
|
2269
|
+
node.actions.includes("set_value") ||
|
|
2270
|
+
(label &&
|
|
2271
|
+
label.toLowerCase() !== role &&
|
|
2272
|
+
!structuralRoles.has(role) &&
|
|
2273
|
+
semanticAction(node) !== null)
|
|
2274
|
+
) {
|
|
2275
|
+
result.push(node);
|
|
2276
|
+
}
|
|
1671
2277
|
node.children?.forEach(visit);
|
|
1672
2278
|
};
|
|
1673
2279
|
observation.semantic.roots.forEach(visit);
|
|
@@ -1699,11 +2305,26 @@ function paintCanvas(
|
|
|
1699
2305
|
width: number,
|
|
1700
2306
|
height: number,
|
|
1701
2307
|
): void {
|
|
2308
|
+
const dimensionsChanged = canvas.width !== width || canvas.height !== height;
|
|
1702
2309
|
canvas.width = width;
|
|
1703
2310
|
canvas.height = height;
|
|
1704
2311
|
const context = canvas.getContext("2d", { alpha: false });
|
|
1705
2312
|
if (!context) throw new Error("Desktop canvas is unavailable.");
|
|
1706
2313
|
context.drawImage(image, 0, 0, width, height);
|
|
2314
|
+
if (dimensionsChanged) fitComputerCanvas(canvas);
|
|
2315
|
+
}
|
|
2316
|
+
|
|
2317
|
+
function fitComputerCanvas(canvas: HTMLCanvasElement): void {
|
|
2318
|
+
const viewport = canvas.parentElement;
|
|
2319
|
+
if (!viewport || canvas.width <= 0 || canvas.height <= 0) return;
|
|
2320
|
+
const scale = Math.min(
|
|
2321
|
+
viewport.clientWidth / canvas.width,
|
|
2322
|
+
viewport.clientHeight / canvas.height,
|
|
2323
|
+
);
|
|
2324
|
+
// Hidden docks retain their previous fit until their content box is visible.
|
|
2325
|
+
if (!Number.isFinite(scale) || scale <= 0) return;
|
|
2326
|
+
canvas.style.width = `${canvas.width * scale}px`;
|
|
2327
|
+
canvas.style.height = `${canvas.height * scale}px`;
|
|
1707
2328
|
}
|
|
1708
2329
|
|
|
1709
2330
|
function loadImage(url: string): Promise<HTMLImageElement> {
|
|
@@ -1764,9 +2385,13 @@ export function computerKey(
|
|
|
1764
2385
|
}
|
|
1765
2386
|
|
|
1766
2387
|
function sameFrameFence(left: ComputerFrame, right: ComputerFrame): boolean {
|
|
2388
|
+
return left.frameId === right.frameId && sameComputerTarget(left, right);
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
function sameComputerTarget(left: ComputerFrame, right: ComputerFrame): boolean {
|
|
1767
2392
|
return (
|
|
1768
|
-
left.frameId === right.frameId &&
|
|
1769
2393
|
left.computerSessionId === right.computerSessionId &&
|
|
2394
|
+
left.controllerGeneration === right.controllerGeneration &&
|
|
1770
2395
|
left.targetId === right.targetId &&
|
|
1771
2396
|
left.targetGeneration === right.targetGeneration
|
|
1772
2397
|
);
|
|
@@ -1786,6 +2411,8 @@ function computerFailureMessage(session: ComputerSession): string | null {
|
|
|
1786
2411
|
return session.placement.kind === "attached_device"
|
|
1787
2412
|
? "Chrome reconnected—open a fresh browser/desktop. Use Browser → New browser → Connected Chrome."
|
|
1788
2413
|
: "The desktop connection expired. Open a new desktop.";
|
|
2414
|
+
case "idle_released":
|
|
2415
|
+
return "The desktop stopped after it went unused. Open a new desktop.";
|
|
1789
2416
|
case null:
|
|
1790
2417
|
return null;
|
|
1791
2418
|
default:
|