@openhands/agent-canvas 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -12
- package/README.windows.md +4 -2
- package/build/assets/{Trans-Ch-OVWEA.js → Trans-DqF_OEZB.js} +1 -1
- package/build/assets/{acp-providers-Sy2LKyMa.js → acp-providers-BQwoUBpe.js} +1 -1
- package/build/assets/active-backend-context-BCXRv5pm.js +1 -0
- package/build/assets/{add-backend-modal-Bgy8anoR.js → add-backend-modal-BKHzq5no.js} +1 -1
- package/build/assets/{agent-profiles-settings-3mmXljZX.js → agent-profiles-settings-CEp5CIz8.js} +1 -1
- package/build/assets/agent-server-compatibility-D6DSanlx.js +1 -0
- package/build/assets/agent-server-conversation-service.api-cylugfDz.js +41 -0
- package/build/assets/{agent-settings-B-W10B4E.js → agent-settings-C-tann3x.js} +2 -2
- package/build/assets/agent-settings-DGUKNSnU.js +1 -0
- package/build/assets/{alert-banner-Bpf2NQJq.js → alert-banner-Dd4woGbm.js} +1 -1
- package/build/assets/api-key-entry-screen-Bll_CkuQ.js +1 -0
- package/build/assets/app-settings-BpViWwpC.js +1 -0
- package/build/assets/automation-detail-Z9dN2f5l.js +2 -0
- package/build/assets/automation-export-D5uL0Tf8.js +2 -0
- package/build/assets/automations-list-BMOWcqzZ.js +1 -0
- package/build/assets/{back-nav-button-CUS2mi-U.js → back-nav-button-ZUDTMOTB.js} +1 -1
- package/build/assets/backend-form-modal-DhBT8P4g.js +1 -0
- package/build/assets/{backend-synced-settings-badge-D9hZx05w.js → backend-synced-settings-badge-Db_REKVw.js} +1 -1
- package/build/assets/{base-modal-BuF2f0YL.js → base-modal-Cb0C6m0Q.js} +1 -1
- package/build/assets/{bot-D7eUAuSB.js → bot-C-EA6685.js} +1 -1
- package/build/assets/{brand-button-vMVuyDXu.js → brand-button-2Tp3aXk1.js} +1 -1
- package/build/assets/{browser-BifzWIBd.js → browser-QYLVr1kS.js} +2 -2
- package/build/assets/browser-store-BGXOGo-R.js +1 -0
- package/build/assets/{browser-tab-CkkqJnqw.js → browser-tab-BTTP1OE2.js} +1 -1
- package/build/assets/canvas-ui-BhxjoA1f.js +1 -0
- package/build/assets/{chat-send-button-B_jc5z5C.js → chat-send-button-BZHG2hAW.js} +1 -1
- package/build/assets/check-TbOEtHKj.js +1 -0
- package/build/assets/{chevron-down-CI8A6tet.js → chevron-down-D7IvFMBM.js} +1 -1
- package/build/assets/{circle-plus-check-toggle-C5YxjxvI.js → circle-plus-check-toggle-QQJKUnF6.js} +1 -1
- package/build/assets/client-source-Biqarhgr.js +1 -0
- package/build/assets/cloud-funnel-analytics-D-27T5Ao.js +1 -0
- package/build/assets/{combobox-caret-DoObzqf4.js → combobox-caret-A5jMVHgE.js} +1 -1
- package/build/assets/{command-menu-D-JV3Wqm.js → command-menu-BMVEme4H.js} +1 -1
- package/build/assets/{command-store-D8J5LMW1.js → command-store-jrWX_CcW.js} +1 -1
- package/build/assets/condenser-settings-CIa6eyB4.js +1 -0
- package/build/assets/{confirmation-modal-BGLu6XJN.js → confirmation-modal-4n_944zq.js} +1 -1
- package/build/assets/{context-menu-list-item-BD5_EaKN.js → context-menu-list-item-DvfFic5I.js} +1 -1
- package/build/assets/conversation-CHnEXGRM.js +1 -0
- package/build/assets/conversation-D4o4dG2t.js +89 -0
- package/build/assets/conversation-panel-11FjLCId.js +1 -0
- package/build/assets/conversation-service.api-0JG_fF9d.js +1 -0
- package/build/assets/conversation-state-store-JiUQp02U.js +1 -0
- package/build/assets/{conversation-store-LptbOvKa.js → conversation-store-BCSpLuEf.js} +1 -1
- package/build/assets/{conversation-tab-empty-state-B2dDYEwf.js → conversation-tab-empty-state-BUhKVwoi.js} +1 -1
- package/build/assets/{copy-3JCw_XFB.js → copy-D1YGjP-C.js} +1 -1
- package/build/assets/{copy-to-clipboard-button-CaR52OnA.js → copy-to-clipboard-button-Dp4Q_hXi.js} +1 -1
- package/build/assets/declaration-BGsHjPEW.js +1 -0
- package/build/assets/{device-verify-Dj6I4Iqe.js → device-verify-CM086oKs.js} +1 -1
- package/build/assets/{dropdown-classes-DJSXeLqc.js → dropdown-classes-DZq0BQjb.js} +1 -1
- package/build/assets/{ellipsis-button-C5Z-tPFp.js → ellipsis-button-TPJdyxnK.js} +1 -1
- package/build/assets/{entry.client-2_3ePAm0.js → entry.client-DypYHsYm.js} +2 -2
- package/build/assets/{enum-filter-dropdown-DXbVwiNZ.js → enum-filter-dropdown-CW_mBg0e.js} +1 -1
- package/build/assets/{environment-switch-overlay-DZQfRGTx.js → environment-switch-overlay-6laJch-9.js} +1 -1
- package/build/assets/{extensions-hub-CLjmOGqS.js → extensions-hub-BJFbFfKS.js} +1 -1
- package/build/assets/{extensions-navigation-B5MeUiMq.js → extensions-navigation-DqSl7CJw.js} +1 -1
- package/build/assets/{external-link-D2zJjE9G.js → external-link-lhdITPqq.js} +1 -1
- package/build/assets/files-tab-c316dKAq.js +1 -0
- package/build/assets/{files-tab-store-DJsPxYdg.js → files-tab-store-tfCav6bB.js} +1 -1
- package/build/assets/{form-control-classes-ChpNBe9n.js → form-control-classes-CvUaw8_L.js} +1 -1
- package/build/assets/format-time-delta-DhDbkD7V.js +1 -0
- package/build/assets/git-provider-icon-DUb2pEos.js +1 -0
- package/build/assets/home-BQ-DC06B.js +1 -0
- package/build/assets/home-D7ds4Jyw.js +1 -0
- package/build/assets/index-home-BRFDWs_4.js +1 -0
- package/build/assets/install-server-modal-CvHvzZj3.js +1 -0
- package/build/assets/{launch-V_3Rp7WS.js → launch-A47TK2Me.js} +1 -1
- package/build/assets/lesson-plan-DSf95kQ2.js +4 -0
- package/build/assets/{list-todo-BfH6yYUK.js → list-todo-CAtvkzcy.js} +1 -1
- package/build/assets/llm-not-configured-banner-B7DR8SPs.js +27 -0
- package/build/assets/llm-settings-Ch4ZbW3c.js +1 -0
- package/build/assets/llm-settings-Cvi7q4ox.js +1 -0
- package/build/assets/llm-subscription-service-Cz1yBBvE.js +1 -0
- package/build/assets/loader-circle-CsAsfLIs.js +1 -0
- package/build/assets/{loading-spinner-BLdswi6q.js → loading-spinner-Txxx1I1p.js} +1 -1
- package/build/assets/manage-backends-modal-DAVhNUCe.js +1 -0
- package/build/assets/manifest-958c741a.js +1 -0
- package/build/assets/{markdown-renderer-DMT7Zkpd.js → markdown-renderer-B6cJJ_MN.js} +1 -1
- package/build/assets/{mcp-BQufNPZ6.js → mcp-BwssgMpg.js} +2 -2
- package/build/assets/{messages-BnJCqnFf.js → messages-D8MeG2ZQ.js} +26 -26
- package/build/assets/{modal-body-DouOE5KH.js → modal-body-CDZdZ-eT.js} +1 -1
- package/build/assets/{modal-classes-Cf-4tbHz.js → modal-classes-B4oit_4c.js} +1 -1
- package/build/assets/{modal-close-button-B6CfxxJj.js → modal-close-button-DQElFMPo.js} +1 -1
- package/build/assets/model-selector-BxTbbKub.js +1 -0
- package/build/assets/{navigation-link-D8hfwvxI.js → navigation-link-imH6S7h2.js} +1 -1
- package/build/assets/onboarding-CyicadGt.js +1 -0
- package/build/assets/onboarding-modal-DQwihcTz.js +1 -0
- package/build/assets/option-service.api-DAkg_F7p.js +1 -0
- package/build/assets/organization-service.api-QknzKDh3.js +1 -0
- package/build/assets/package-Dk_VtWJ4.js +1 -0
- package/build/assets/path-utils-BECGet7G.js +1 -0
- package/build/assets/{pencil-Dj2XjKmI.js → pencil-CWhApFIT.js} +1 -1
- package/build/assets/{plan-components-CcCBmCzW.js → plan-components-CgPHWz3E.js} +1 -1
- package/build/assets/{planner-tab-Dhyqcqmb.js → planner-tab-DPKHmMAX.js} +1 -1
- package/build/assets/plugins-management-service-DuTTxquo.js +1 -0
- package/build/assets/profiles-service.api-D6jvZOWu.js +1 -0
- package/build/assets/providers-B8pxQK8x.js +1 -0
- package/build/assets/proxy-CuhhyjdX.js +1 -0
- package/build/assets/{recommended-automations-launcher-CTcGTl4d.js → recommended-automations-launcher-DA6DWS7V.js} +1 -1
- package/build/assets/{refresh-cw-DM82EIyx.js → refresh-cw-B9B7-vsy.js} +1 -1
- package/build/assets/root-BhP6bTBB.js +2 -0
- package/build/assets/root-CYWeTrWE.css +1 -0
- package/build/assets/root-layout-oBaHHW4s.js +2 -0
- package/build/assets/{runtime-waiting-state-BsXLElvN.js → runtime-waiting-state-DeD9G7yJ.js} +1 -1
- package/build/assets/schema-field-BOLdL_U7.js +1 -0
- package/build/assets/sdk-section-page-ZIltERxf.js +1 -0
- package/build/assets/{sdk-settings-schema-Cv7M9eSe.js → sdk-settings-schema-Ba_Pa9uf.js} +1 -1
- package/build/assets/search-dIQ09UY2.js +1 -0
- package/build/assets/secrets-service-50DElFny.js +1 -0
- package/build/assets/{secrets-settings-Cx6yJlyb.js → secrets-settings-Digl7JLe.js} +1 -1
- package/build/assets/{segmented-toggle-DyQqPhoD.js → segmented-toggle-C1-ZVyr5.js} +1 -1
- package/build/assets/settings-BfgLHsgd.js +1 -0
- package/build/assets/{settings-dropdown-input-BjunGXJb.js → settings-dropdown-input-BD5C9fBn.js} +1 -1
- package/build/assets/{settings-index-D6sxWSRk.js → settings-index-CODBijya.js} +1 -1
- package/build/assets/{settings-input-bD6npsDm.js → settings-input-CzNOq-Pj.js} +1 -1
- package/build/assets/{settings-list-classes-Dp_OgzN0.js → settings-list-classes-CuxH9ZoR.js} +1 -1
- package/build/assets/settings-modal-BcSWpKI0.js +1 -0
- package/build/assets/settings-service.api-Bsu1PvYO.js +1 -0
- package/build/assets/{settings-switch-5cuxl8Dc.js → settings-switch-DqmHAfWJ.js} +1 -1
- package/build/assets/{settings-utils-BMdG65By.js → settings-utils-B1lBErvV.js} +1 -1
- package/build/assets/shared-conversation-CDskdmrb.js +1 -0
- package/build/assets/{sidebar-layout-DuzJMMw-.js → sidebar-layout-Cn6eQaEI.js} +1 -1
- package/build/assets/{sidebar-mobile-menu-toggle-VvvX32iF.js → sidebar-mobile-menu-toggle-DmCmZgq1.js} +1 -1
- package/build/assets/{sidebar-nav-link-5AlTT5-j.js → sidebar-nav-link-DMc8AIe7.js} +1 -1
- package/build/assets/{sidebar-store-CZuE8AH5.js → sidebar-store-DXWSCFpa.js} +1 -1
- package/build/assets/{skill-card-pill-row-eTIkE4nz.js → skill-card-pill-row-DOIWjiMF.js} +1 -1
- package/build/assets/skills-plugins-DLgINYuY.js +1 -0
- package/build/assets/{skills-settings-BUv04tSc.js → skills-settings-DPiCWalR.js} +2 -2
- package/build/assets/{square-chevron-right-Czw3FFWc.js → square-chevron-right-DE-JN-2Z.js} +1 -1
- package/build/assets/{styled-tooltip-CgX2oRaK.js → styled-tooltip-CvsjHuIf.js} +1 -1
- package/build/assets/{suspense-BO1Tl_Vf.js → suspense-DmZ3MSSh.js} +1 -1
- package/build/assets/{task-list-tab-0k9PhuqO.js → task-list-tab-CuhcDycu.js} +1 -1
- package/build/assets/telemetry-DHiTRl5_.js +2 -0
- package/build/assets/{terminal-B61WrJdS.js → terminal-CVqF-tCD.js} +1 -1
- package/build/assets/{toggle-switch-2j5wwR85.js → toggle-switch-CS0ClOy2.js} +1 -1
- package/build/assets/{trash-2-Be_7y3Mn.js → trash-2-BU6BlW4i.js} +1 -1
- package/build/assets/{typography-D8mHDeNG.js → typography-CQn9wyEX.js} +1 -1
- package/build/assets/use-acp-credential-form-LlfZO_DD.js +1 -0
- package/build/assets/{use-activate-agent-profile-20v1xU0H.js → use-activate-agent-profile-DpQjsXM0.js} +1 -1
- package/build/assets/{use-active-agent-profile-BkJN7Xsy.js → use-active-agent-profile-Gw_uJujV.js} +1 -1
- package/build/assets/{use-active-conversation-DOZVSVtU.js → use-active-conversation-B0sttISF.js} +1 -1
- package/build/assets/use-agent-profiles-LHZEK0Gg.js +1 -0
- package/build/assets/{use-agent-state-DHZO1obC.js → use-agent-state-C5uA0bRj.js} +1 -1
- package/build/assets/use-backends-health-DKt1SmiU.js +1 -0
- package/build/assets/{use-can-manage-org-profiles-C-OhWVBS.js → use-can-manage-org-profiles-BAtqsbCi.js} +1 -1
- package/build/assets/use-cloud-current-user-id-C8m1oBy4.js +1 -0
- package/build/assets/use-config-CXy4vD0H.js +1 -0
- package/build/assets/use-create-conversation-CWQGNQ9J.js +1 -0
- package/build/assets/{use-create-secret-D0HCOZFu.js → use-create-secret-BIrsX3OB.js} +1 -1
- package/build/assets/{use-event-store-DjgU4wfO.js → use-event-store-CsD17BKu.js} +1 -1
- package/build/assets/{use-handle-plan-click-DyRASKMC.js → use-handle-plan-click-DEuO7Byo.js} +1 -1
- package/build/assets/use-is-authed-Dvbpiu3B.js +1 -0
- package/build/assets/{use-launch-skill-in-chat-BnoDgvAy.js → use-launch-skill-in-chat-CdHOoQGM.js} +1 -1
- package/build/assets/use-llm-profiles-DQpQxVOD.js +1 -0
- package/build/assets/use-plugins-marketplace-Bkk07W_M.js +1 -0
- package/build/assets/use-runtime-is-ready-D4pkGT6E.js +1 -0
- package/build/assets/use-save-agent-profile-BxEsjnW0.js +1 -0
- package/build/assets/{use-save-settings-BGlSfuYn.js → use-save-settings-ioSUMqbE.js} +1 -1
- package/build/assets/use-settings-DNJC4gqu.js +1 -0
- package/build/assets/use-settings-nav-items-BUsrDbVR.js +16 -0
- package/build/assets/use-skills-BsmKm23M.js +1 -0
- package/build/assets/{use-task-list-AVBLbbcR.js → use-task-list-rs7Xbzl8.js} +1 -1
- package/build/assets/{use-tracking-BIXzagSI.js → use-tracking-poxQHMwR.js} +1 -1
- package/build/assets/{use-user-conversation-zpx7I18x.js → use-user-conversation-B337kUKB.js} +1 -1
- package/build/assets/{useMutation-DrCAVg5w.js → useMutation-u8UgRgDK.js} +1 -1
- package/build/assets/{useMutationState-D8QN0ZCy.js → useMutationState-OnAsCLA_.js} +1 -1
- package/build/assets/{useQueries-DClgUTc7.js → useQueries-BpIlT8Mt.js} +1 -1
- package/build/assets/{useQuery-CRBP8vIS.js → useQuery-D5pEZbY0.js} +1 -1
- package/build/assets/{useTranslation-CDRCKC9-.js → useTranslation-Cbf5AGg_.js} +1 -1
- package/build/assets/{utils-D3U3rlqr.js → utils-DB9LsAzA.js} +1 -1
- package/build/assets/{vendor~browser-Cvg7jXCP.js → vendor~browser-5Njjw1Cl.js} +1 -1
- package/build/assets/{vendor~browser-tab-Di0kh5Zw.js → vendor~browser-tab-Xn94EtUo.js} +1 -1
- package/build/assets/{vendor~entry.client-9tWQ_Ugw.js → vendor~entry.client-DDuH4--y.js} +1 -1
- package/build/assets/{vendor~entry.client-D6BtSOtn.js → vendor~entry.client-DQGrNspu.js} +1 -1
- package/build/assets/vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~dhilvkxv-BIieS_OD.js +2 -0
- package/build/assets/vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~iztiaf2o-rqo6vRFz.js +185 -0
- package/build/assets/{vendor~entry.client~root~root-layout~home~conversation-panel~conversation~launch~skills-set~jewnulst-C57fm7C-.js → vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~ljq5gbus-B2XD4bi2.js} +1 -1
- package/build/assets/{vendor~files-tab-B02k3xvx.js → vendor~files-tab-CiC3-lGh.js} +1 -1
- package/build/assets/{vendor~home~conversation-panel~conversation-CYMF9JYB.js → vendor~index-home~home~conversation-panel~conversation-ChmYLVgz.js} +1 -1
- package/build/assets/{vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-9UMeuPV6.js → vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-B8XNNEQZ.js} +1 -1
- package/build/assets/{vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-d8aETPbZ.js → vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-CnJRLn5s.js} +1 -1
- package/build/assets/vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-DWteoTyr.js +2 -0
- package/build/assets/{vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-D7EUmQ8Y.js → vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-Dc5nKwLO.js} +1 -1
- package/build/assets/{vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-BD0GaRGa.js → vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-tlf6AnDe.js} +1 -1
- package/build/assets/{vendor~launch-CHXA9cJu.js → vendor~launch-28DK5Rsl.js} +1 -1
- package/build/assets/{vendor~root-layout~conversation-panel~conversation-CpiTHpHl.js → vendor~root-layout~conversation-panel~conversation-8tIsyrPj.js} +1 -1
- package/build/assets/{vendor~root-layout~conversation-panel~conversation~shared-conversation-_TdZcNMD.js → vendor~root-layout~conversation-panel~conversation~shared-conversation-lYNG9C1U.js} +1 -1
- package/build/assets/{vendor~root-layout~home~conversation-panel~conversation-Cn2jwSga.js → vendor~root-layout~index-home~home~conversation-panel~conversation-Zhq6xAxR.js} +1 -1
- package/build/assets/{vendor~root-layout~home~conversation-panel~conversation~shared-conversation~alert-banner~pl~rqjteh0a-CZM4hwUT.js → vendor~root-layout~index-home~home~conversation-panel~conversation~shared-conversation~aler~gfu7qdtz-B81b08bw.js} +1 -1
- package/build/assets/{vendor~root-layout~home~conversation-panel~conversation~launch~settings~settings-index~agen~enjl5xdm-B0EIDu4M.js → vendor~root~root-layout~index-home~home~conversation-panel~conversation~launch~extensions-h~ftka0fnu-jpyLM1Nn.js} +2 -2
- package/build/assets/{verification-settings-D576N2xa.js → verification-settings-HRgEM-_3.js} +1 -1
- package/build/assets/x-ZE4iplpF.js +1 -0
- package/build/index.html +4 -4
- package/build/locales/ar/openhands.json +9 -0
- package/build/locales/ca/openhands.json +9 -0
- package/build/locales/de/openhands.json +9 -0
- package/build/locales/en/openhands.json +9 -0
- package/build/locales/es/openhands.json +9 -0
- package/build/locales/fr/openhands.json +9 -0
- package/build/locales/it/openhands.json +9 -0
- package/build/locales/ja/openhands.json +9 -0
- package/build/locales/ko-KR/openhands.json +9 -0
- package/build/locales/no/openhands.json +9 -0
- package/build/locales/pt/openhands.json +9 -0
- package/build/locales/tr/openhands.json +9 -0
- package/build/locales/uk/openhands.json +9 -0
- package/build/locales/zh-CN/openhands.json +9 -0
- package/build/locales/zh-TW/openhands.json +9 -0
- package/config/defaults.json +5 -4
- package/dist/api/agent-server-adapter.cjs +3 -3
- package/dist/api/agent-server-adapter.cjs.map +1 -1
- package/dist/api/agent-server-adapter.d.ts +9 -1
- package/dist/api/agent-server-adapter.js +105 -102
- package/dist/api/agent-server-adapter.js.map +1 -1
- package/dist/api/agent-server-config.cjs +1 -1
- package/dist/api/agent-server-config.cjs.map +1 -1
- package/dist/api/agent-server-config.d.ts +3 -0
- package/dist/api/agent-server-config.js +45 -23
- package/dist/api/agent-server-config.js.map +1 -1
- package/dist/api/backend-registry/auth.cjs +1 -1
- package/dist/api/backend-registry/auth.cjs.map +1 -1
- package/dist/api/backend-registry/auth.js +1 -1
- package/dist/api/backend-registry/auth.js.map +1 -1
- package/dist/api/backend-registry/default-backend.cjs +1 -1
- package/dist/api/backend-registry/default-backend.cjs.map +1 -1
- package/dist/api/backend-registry/default-backend.d.ts +3 -0
- package/dist/api/backend-registry/default-backend.js +22 -10
- package/dist/api/backend-registry/default-backend.js.map +1 -1
- package/dist/api/backend-registry/storage.cjs +1 -1
- package/dist/api/backend-registry/storage.cjs.map +1 -1
- package/dist/api/backend-registry/storage.js +38 -33
- package/dist/api/backend-registry/storage.js.map +1 -1
- package/dist/api/backend-registry/types.d.ts +2 -0
- package/dist/api/canvas-ui-client-tool.cjs +38 -0
- package/dist/api/canvas-ui-client-tool.cjs.map +1 -0
- package/dist/api/canvas-ui-client-tool.d.ts +14 -0
- package/dist/api/canvas-ui-client-tool.js +47 -0
- package/dist/api/canvas-ui-client-tool.js.map +1 -0
- package/dist/api/client-source.cjs +2 -0
- package/dist/api/client-source.cjs.map +1 -0
- package/dist/api/client-source.d.ts +9 -0
- package/dist/api/client-source.js +10 -0
- package/dist/api/client-source.js.map +1 -0
- package/dist/api/cloud/client.cjs +2 -0
- package/dist/api/cloud/client.cjs.map +1 -0
- package/dist/api/cloud/client.d.ts +4 -0
- package/dist/api/cloud/client.js +38 -0
- package/dist/api/cloud/client.js.map +1 -0
- package/dist/api/cloud/conversation-service.api.cjs +1 -1
- package/dist/api/cloud/conversation-service.api.cjs.map +1 -1
- package/dist/api/cloud/conversation-service.api.js +33 -31
- package/dist/api/cloud/conversation-service.api.js.map +1 -1
- package/dist/api/cloud/organization-service.api.cjs +1 -1
- package/dist/api/cloud/organization-service.api.cjs.map +1 -1
- package/dist/api/cloud/organization-service.api.js +11 -10
- package/dist/api/cloud/organization-service.api.js.map +1 -1
- package/dist/api/cloud/proxy.cjs +1 -1
- package/dist/api/cloud/proxy.cjs.map +1 -1
- package/dist/api/cloud/proxy.d.ts +2 -40
- package/dist/api/cloud/proxy.js +14 -39
- package/dist/api/cloud/proxy.js.map +1 -1
- package/dist/api/conversation-metadata-store.cjs +1 -1
- package/dist/api/conversation-metadata-store.cjs.map +1 -1
- package/dist/api/conversation-metadata-store.d.ts +2 -9
- package/dist/api/conversation-metadata-store.js +13 -9
- package/dist/api/conversation-metadata-store.js.map +1 -1
- package/dist/api/conversation-service/agent-server-conversation-service.api.cjs +1 -1
- package/dist/api/conversation-service/agent-server-conversation-service.api.cjs.map +1 -1
- package/dist/api/conversation-service/agent-server-conversation-service.api.d.ts +2 -1
- package/dist/api/conversation-service/agent-server-conversation-service.api.js +98 -88
- package/dist/api/conversation-service/agent-server-conversation-service.api.js.map +1 -1
- package/dist/api/conversation-service/agent-server-conversation-service.types.d.ts +5 -0
- package/dist/api/device-flow-client.cjs +1 -1
- package/dist/api/device-flow-client.cjs.map +1 -1
- package/dist/api/device-flow-client.d.ts +4 -58
- package/dist/api/device-flow-client.js +8 -98
- package/dist/api/device-flow-client.js.map +1 -1
- package/dist/api/git-service/agent-server-git-service.api.cjs +1 -1
- package/dist/api/git-service/agent-server-git-service.api.cjs.map +1 -1
- package/dist/api/git-service/agent-server-git-service.api.d.ts +10 -2
- package/dist/api/git-service/agent-server-git-service.api.js +107 -40
- package/dist/api/git-service/agent-server-git-service.api.js.map +1 -1
- package/dist/api/open-hands.types.d.ts +11 -0
- package/dist/api/secrets-service.cjs +1 -1
- package/dist/api/secrets-service.cjs.map +1 -1
- package/dist/api/secrets-service.js +15 -14
- package/dist/api/secrets-service.js.map +1 -1
- package/dist/components/conversation-events/chat/event-content-helpers/get-event-content.cjs +1 -1
- package/dist/components/conversation-events/chat/event-content-helpers/get-event-content.cjs.map +1 -1
- package/dist/components/conversation-events/chat/event-content-helpers/get-event-content.js +73 -64
- package/dist/components/conversation-events/chat/event-content-helpers/get-event-content.js.map +1 -1
- package/dist/components/conversation-events/chat/event-content-helpers/get-observation-content.cjs +2 -2
- package/dist/components/conversation-events/chat/event-content-helpers/get-observation-content.cjs.map +1 -1
- package/dist/components/conversation-events/chat/event-content-helpers/get-observation-content.js +2 -0
- package/dist/components/conversation-events/chat/event-content-helpers/get-observation-content.js.map +1 -1
- package/dist/components/features/backends/backend-form-modal.cjs +1 -1
- package/dist/components/features/backends/backend-form-modal.cjs.map +1 -1
- package/dist/components/features/backends/backend-form-modal.d.ts +4 -2
- package/dist/components/features/backends/backend-form-modal.js +202 -188
- package/dist/components/features/backends/backend-form-modal.js.map +1 -1
- package/dist/components/features/backends/backend-row.cjs +1 -1
- package/dist/components/features/backends/backend-row.cjs.map +1 -1
- package/dist/components/features/backends/backend-row.js +2 -1
- package/dist/components/features/backends/backend-row.js.map +1 -1
- package/dist/components/features/backends/backend-selector.cjs +1 -1
- package/dist/components/features/backends/backend-selector.cjs.map +1 -1
- package/dist/components/features/backends/backend-selector.js +51 -51
- package/dist/components/features/backends/backend-selector.js.map +1 -1
- package/dist/components/features/backends/device-flow-auth.cjs +1 -1
- package/dist/components/features/backends/device-flow-auth.cjs.map +1 -1
- package/dist/components/features/backends/device-flow-auth.d.ts +4 -1
- package/dist/components/features/backends/device-flow-auth.js +38 -38
- package/dist/components/features/backends/device-flow-auth.js.map +1 -1
- package/dist/components/features/chat/chat-interface.cjs +2 -2
- package/dist/components/features/chat/chat-interface.cjs.map +1 -1
- package/dist/components/features/chat/chat-interface.js +173 -165
- package/dist/components/features/chat/chat-interface.js.map +1 -1
- package/dist/components/features/chat/components/chat-input-actions.cjs +1 -1
- package/dist/components/features/chat/components/chat-input-actions.cjs.map +1 -1
- package/dist/components/features/chat/components/chat-input-actions.d.ts +2 -1
- package/dist/components/features/chat/components/chat-input-actions.js +44 -43
- package/dist/components/features/chat/components/chat-input-actions.js.map +1 -1
- package/dist/components/features/chat/components/chat-input-container.cjs +1 -1
- package/dist/components/features/chat/components/chat-input-container.cjs.map +1 -1
- package/dist/components/features/chat/components/chat-input-container.d.ts +2 -1
- package/dist/components/features/chat/components/chat-input-container.js +20 -19
- package/dist/components/features/chat/components/chat-input-container.js.map +1 -1
- package/dist/components/features/chat/components/chat-input-profile-picker.cjs +1 -1
- package/dist/components/features/chat/components/chat-input-profile-picker.cjs.map +1 -1
- package/dist/components/features/chat/components/chat-input-profile-picker.js +44 -32
- package/dist/components/features/chat/components/chat-input-profile-picker.js.map +1 -1
- package/dist/components/features/chat/components/resolve-picker-kind.cjs +1 -1
- package/dist/components/features/chat/components/resolve-picker-kind.cjs.map +1 -1
- package/dist/components/features/chat/components/resolve-picker-kind.d.ts +10 -22
- package/dist/components/features/chat/components/resolve-picker-kind.js +6 -3
- package/dist/components/features/chat/components/resolve-picker-kind.js.map +1 -1
- package/dist/components/features/chat/custom-chat-input.cjs +1 -1
- package/dist/components/features/chat/custom-chat-input.cjs.map +1 -1
- package/dist/components/features/chat/custom-chat-input.d.ts +2 -1
- package/dist/components/features/chat/custom-chat-input.js +52 -51
- package/dist/components/features/chat/custom-chat-input.js.map +1 -1
- package/dist/components/features/chat/interactive-chat-box.cjs +1 -1
- package/dist/components/features/chat/interactive-chat-box.cjs.map +1 -1
- package/dist/components/features/chat/interactive-chat-box.d.ts +2 -1
- package/dist/components/features/chat/interactive-chat-box.js +9 -8
- package/dist/components/features/chat/interactive-chat-box.js.map +1 -1
- package/dist/components/features/diff-viewer/commit-list.cjs +2 -0
- package/dist/components/features/diff-viewer/commit-list.cjs.map +1 -0
- package/dist/components/features/diff-viewer/commit-list.d.ts +13 -0
- package/dist/components/features/diff-viewer/commit-list.js +27 -0
- package/dist/components/features/diff-viewer/commit-list.js.map +1 -0
- package/dist/components/features/diff-viewer/commit-row.cjs +2 -0
- package/dist/components/features/diff-viewer/commit-row.cjs.map +1 -0
- package/dist/components/features/diff-viewer/commit-row.d.ts +14 -0
- package/dist/components/features/diff-viewer/commit-row.js +59 -0
- package/dist/components/features/diff-viewer/commit-row.js.map +1 -0
- package/dist/components/features/diff-viewer/file-diff-viewer.cjs +1 -1
- package/dist/components/features/diff-viewer/file-diff-viewer.cjs.map +1 -1
- package/dist/components/features/diff-viewer/file-diff-viewer.d.ts +7 -1
- package/dist/components/features/diff-viewer/file-diff-viewer.js +40 -39
- package/dist/components/features/diff-viewer/file-diff-viewer.js.map +1 -1
- package/dist/components/features/plugins/plugin-detail-modal.d.ts +2 -1
- package/dist/components/shared/modals/settings/settings-modal.cjs +1 -1
- package/dist/components/shared/modals/settings/settings-modal.cjs.map +1 -1
- package/dist/components/shared/modals/settings/settings-modal.js +23 -22
- package/dist/components/shared/modals/settings/settings-modal.js.map +1 -1
- package/dist/config/defaults.cjs +1 -1
- package/dist/config/defaults.cjs.map +1 -1
- package/dist/config/defaults.js +5 -4
- package/dist/config/defaults.js.map +1 -1
- package/dist/constants/acp-providers.cjs.map +1 -1
- package/dist/constants/acp-providers.d.ts +1 -1
- package/dist/constants/acp-providers.js.map +1 -1
- package/dist/constants/canvas-ui.cjs +2 -0
- package/dist/constants/canvas-ui.cjs.map +1 -0
- package/dist/constants/canvas-ui.d.ts +8 -0
- package/dist/constants/canvas-ui.js +6 -0
- package/dist/constants/canvas-ui.js.map +1 -0
- package/dist/contexts/conversation-websocket-context.cjs.map +1 -1
- package/dist/contexts/conversation-websocket-context.js.map +1 -1
- package/dist/hooks/mutation/use-create-conversation.cjs +1 -1
- package/dist/hooks/mutation/use-create-conversation.cjs.map +1 -1
- package/dist/hooks/mutation/use-create-conversation.d.ts +2 -1
- package/dist/hooks/mutation/use-create-conversation.js +52 -52
- package/dist/hooks/mutation/use-create-conversation.js.map +1 -1
- package/dist/hooks/query/query-keys.cjs +1 -1
- package/dist/hooks/query/query-keys.cjs.map +1 -1
- package/dist/hooks/query/query-keys.d.ts +2 -0
- package/dist/hooks/query/query-keys.js +4 -1
- package/dist/hooks/query/query-keys.js.map +1 -1
- package/dist/hooks/query/use-backends-health.cjs +1 -1
- package/dist/hooks/query/use-backends-health.cjs.map +1 -1
- package/dist/hooks/query/use-backends-health.js +50 -48
- package/dist/hooks/query/use-backends-health.js.map +1 -1
- package/dist/hooks/query/use-cloud-organizations.cjs +1 -1
- package/dist/hooks/query/use-cloud-organizations.cjs.map +1 -1
- package/dist/hooks/query/use-cloud-organizations.d.ts +1 -0
- package/dist/hooks/query/use-cloud-organizations.js +5 -2
- package/dist/hooks/query/use-cloud-organizations.js.map +1 -1
- package/dist/hooks/query/use-commit-changes.cjs +2 -0
- package/dist/hooks/query/use-commit-changes.cjs.map +1 -0
- package/dist/hooks/query/use-commit-changes.d.ts +5 -0
- package/dist/hooks/query/use-commit-changes.js +34 -0
- package/dist/hooks/query/use-commit-changes.js.map +1 -0
- package/dist/hooks/query/use-task-polling.cjs +1 -1
- package/dist/hooks/query/use-task-polling.cjs.map +1 -1
- package/dist/hooks/query/use-task-polling.d.ts +3 -2
- package/dist/hooks/query/use-task-polling.js +53 -40
- package/dist/hooks/query/use-task-polling.js.map +1 -1
- package/dist/hooks/query/use-unified-git-commits.cjs +2 -0
- package/dist/hooks/query/use-unified-git-commits.cjs.map +1 -0
- package/dist/hooks/query/use-unified-git-commits.d.ts +10 -0
- package/dist/hooks/query/use-unified-git-commits.js +41 -0
- package/dist/hooks/query/use-unified-git-commits.js.map +1 -0
- package/dist/hooks/query/use-unified-git-diff.cjs +1 -1
- package/dist/hooks/query/use-unified-git-diff.cjs.map +1 -1
- package/dist/hooks/query/use-unified-git-diff.d.ts +5 -0
- package/dist/hooks/query/use-unified-git-diff.js +11 -4
- package/dist/hooks/query/use-unified-git-diff.js.map +1 -1
- package/dist/hooks/use-acp-model-context.cjs +1 -1
- package/dist/hooks/use-acp-model-context.cjs.map +1 -1
- package/dist/hooks/use-acp-model-context.d.ts +4 -1
- package/dist/hooks/use-acp-model-context.js +9 -8
- package/dist/hooks/use-acp-model-context.js.map +1 -1
- package/dist/hooks/use-auto-refresh-files-on-edit.cjs +1 -1
- package/dist/hooks/use-auto-refresh-files-on-edit.cjs.map +1 -1
- package/dist/hooks/use-auto-refresh-files-on-edit.js +1 -1
- package/dist/hooks/use-auto-refresh-files-on-edit.js.map +1 -1
- package/dist/hooks/use-chat-input-model-state.cjs.map +1 -1
- package/dist/hooks/use-chat-input-model-state.d.ts +2 -1
- package/dist/hooks/use-chat-input-model-state.js.map +1 -1
- package/dist/hooks/use-chat-input-profile-state.cjs +1 -1
- package/dist/hooks/use-chat-input-profile-state.cjs.map +1 -1
- package/dist/hooks/use-chat-input-profile-state.d.ts +1 -10
- package/dist/hooks/use-chat-input-profile-state.js +42 -15
- package/dist/hooks/use-chat-input-profile-state.js.map +1 -1
- package/dist/hooks/use-device-flow.cjs +1 -1
- package/dist/hooks/use-device-flow.cjs.map +1 -1
- package/dist/hooks/use-device-flow.d.ts +4 -1
- package/dist/hooks/use-device-flow.js +42 -40
- package/dist/hooks/use-device-flow.js.map +1 -1
- package/dist/hooks/use-tracking.cjs.map +1 -1
- package/dist/hooks/use-tracking.d.ts +2 -1
- package/dist/hooks/use-tracking.js.map +1 -1
- package/dist/i18n/declaration.cjs +1 -1
- package/dist/i18n/declaration.cjs.map +1 -1
- package/dist/i18n/declaration.d.ts +9 -0
- package/dist/i18n/declaration.js +1 -1
- package/dist/i18n/declaration.js.map +1 -1
- package/dist/i18n/index.cjs +1 -1
- package/dist/i18n/index.cjs.map +1 -1
- package/dist/i18n/index.js +19 -18
- package/dist/i18n/index.js.map +1 -1
- package/dist/i18n/translation.cjs +2 -2
- package/dist/i18n/translation.cjs.map +1 -1
- package/dist/i18n/translation.js +153 -0
- package/dist/i18n/translation.js.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.js +32 -32
- package/dist/lib/index.cjs +1 -1
- package/dist/lib/index.js +1 -1
- package/dist/locales/ar/openhands.json +9 -0
- package/dist/locales/ca/openhands.json +9 -0
- package/dist/locales/de/openhands.json +9 -0
- package/dist/locales/en/openhands.json +9 -0
- package/dist/locales/es/openhands.json +9 -0
- package/dist/locales/fr/openhands.json +9 -0
- package/dist/locales/it/openhands.json +9 -0
- package/dist/locales/ja/openhands.json +9 -0
- package/dist/locales/ko-KR/openhands.json +9 -0
- package/dist/locales/no/openhands.json +9 -0
- package/dist/locales/pt/openhands.json +9 -0
- package/dist/locales/tr/openhands.json +9 -0
- package/dist/locales/uk/openhands.json +9 -0
- package/dist/locales/zh-CN/openhands.json +9 -0
- package/dist/locales/zh-TW/openhands.json +9 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/cloud-client.cjs +2 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/cloud-client.cjs.map +1 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/cloud-client.js +351 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/cloud-client.js.map +1 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/device-flow-client.cjs +2 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/device-flow-client.cjs.map +1 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/device-flow-client.js +106 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/device-flow-client.js.map +1 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/openhands-client.cjs +2 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/openhands-client.cjs.map +1 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/openhands-client.js +69 -0
- package/dist/node_modules/@openhands/typescript-client/dist/client/openhands-client.js.map +1 -0
- package/dist/node_modules/@openhands/typescript-client/dist/clients.cjs +1 -1
- package/dist/node_modules/@openhands/typescript-client/dist/clients.js +3 -0
- package/dist/node_modules/@openhands/typescript-client/dist/models/acp-providers.cjs +1 -1
- package/dist/node_modules/@openhands/typescript-client/dist/models/acp-providers.cjs.map +1 -1
- package/dist/node_modules/@openhands/typescript-client/dist/models/acp-providers.js +18 -2
- package/dist/node_modules/@openhands/typescript-client/dist/models/acp-providers.js.map +1 -1
- package/dist/node_modules/@openhands/typescript-client/dist/models/acp.cjs.map +1 -1
- package/dist/node_modules/@openhands/typescript-client/dist/models/acp.js.map +1 -1
- package/dist/node_modules/axios/index.cjs +1 -1
- package/dist/node_modules/axios/index.js +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.cjs.map +1 -1
- package/dist/package.js +2 -2
- package/dist/package.js.map +1 -1
- package/dist/routes/app-settings.cjs +1 -1
- package/dist/routes/app-settings.cjs.map +1 -1
- package/dist/routes/app-settings.js +67 -66
- package/dist/routes/app-settings.js.map +1 -1
- package/dist/routes/commits-tab.cjs +2 -0
- package/dist/routes/commits-tab.cjs.map +1 -0
- package/dist/routes/commits-tab.d.ts +8 -0
- package/dist/routes/commits-tab.js +45 -0
- package/dist/routes/commits-tab.js.map +1 -0
- package/dist/routes/files-tab.cjs +1 -1
- package/dist/routes/files-tab.cjs.map +1 -1
- package/dist/routes/files-tab.js +148 -127
- package/dist/routes/files-tab.js.map +1 -1
- package/dist/services/cloud-funnel-analytics.cjs +2 -0
- package/dist/services/cloud-funnel-analytics.cjs.map +1 -0
- package/dist/services/cloud-funnel-analytics.d.ts +12 -0
- package/dist/services/cloud-funnel-analytics.js +53 -0
- package/dist/services/cloud-funnel-analytics.js.map +1 -0
- package/dist/services/telemetry.cjs +1 -1
- package/dist/services/telemetry.cjs.map +1 -1
- package/dist/services/telemetry.js +49 -47
- package/dist/services/telemetry.js.map +1 -1
- package/dist/types/agent-server/core/base/action.d.ts +5 -4
- package/dist/types/agent-server/core/base/base.d.ts +3 -2
- package/dist/types/agent-server/core/base/observation.d.ts +1 -1
- package/dist/types/agent-server/type-guards.cjs +1 -1
- package/dist/types/agent-server/type-guards.cjs.map +1 -1
- package/dist/types/agent-server/type-guards.d.ts +3 -5
- package/dist/types/agent-server/type-guards.js +2 -1
- package/dist/types/agent-server/type-guards.js.map +1 -1
- package/dist/types/automation.d.ts +5 -0
- package/dist/utils/api-error-message.d.ts +11 -0
- package/dist/utils/automation-timeout.d.ts +19 -0
- package/dist/utils/base-path.cjs +2 -0
- package/dist/utils/base-path.cjs.map +1 -0
- package/dist/utils/base-path.d.ts +2 -0
- package/dist/utils/base-path.js +23 -0
- package/dist/utils/base-path.js.map +1 -0
- package/package.json +2 -2
- package/scripts/check-sdk-version-sync.mjs +6 -6
- package/scripts/dev-safe.mjs +7 -8
- package/scripts/dev-static.mjs +3 -0
- package/scripts/dev-with-automation.mjs +3 -0
- package/scripts/docker-build.mjs +4 -0
- package/scripts/static-server.mjs +93 -7
- package/tools/canvas_ui_tool.py +14 -8
- package/build/assets/active-backend-context-hxq31W25.js +0 -1
- package/build/assets/agent-server-client-options-DVLRd9sw.js +0 -1
- package/build/assets/agent-server-conversation-service.api-CuSdr4Qb.js +0 -5
- package/build/assets/agent-settings-D_yOy8XB.js +0 -1
- package/build/assets/api-key-entry-screen-Dl0qVYi4.js +0 -1
- package/build/assets/app-settings-yVizGXox.js +0 -1
- package/build/assets/automation-detail-dvJfLs5u.js +0 -2
- package/build/assets/automation-export-BPcgLqPD.js +0 -2
- package/build/assets/automations-list-CUXogTe0.js +0 -1
- package/build/assets/backend-form-modal-CQJGmItq.js +0 -1
- package/build/assets/browser-store-BI6uklCG.js +0 -1
- package/build/assets/check-Bvi71siz.js +0 -1
- package/build/assets/condenser-settings-Ca8fYZ_Z.js +0 -1
- package/build/assets/conversation-9NBSRoR9.js +0 -1
- package/build/assets/conversation-BXg6Oti2.js +0 -89
- package/build/assets/conversation-panel-CZri9Rup.js +0 -1
- package/build/assets/conversation-service.api-DPBQvTKD.js +0 -1
- package/build/assets/conversation-state-store-B2Bp32XI.js +0 -1
- package/build/assets/declaration-gq8V5eTU.js +0 -1
- package/build/assets/files-tab-UVoray88.js +0 -1
- package/build/assets/git-provider-icon-o6hdnJUB.js +0 -1
- package/build/assets/home-D1IHvGTN.js +0 -1
- package/build/assets/index-redirect-CUUJiNki.js +0 -1
- package/build/assets/install-server-modal-BbuQaA2w.js +0 -1
- package/build/assets/lesson-plan-jC0HBoe6.js +0 -4
- package/build/assets/llm-not-configured-banner-DoARbcdL.js +0 -27
- package/build/assets/llm-settings-4JMLV8mq.js +0 -1
- package/build/assets/llm-settings-D91gMw3U.js +0 -1
- package/build/assets/llm-subscription-service-CKyaV24y.js +0 -1
- package/build/assets/loader-circle-BEykUKYG.js +0 -1
- package/build/assets/manage-backends-modal-DkWAMSle.js +0 -1
- package/build/assets/manifest-aa5c29f6.js +0 -1
- package/build/assets/model-selector-BfqpMkSJ.js +0 -1
- package/build/assets/mouse-pointer-click-PdEQPL6v.js +0 -1
- package/build/assets/onboarding-Bu-HN1ze.js +0 -1
- package/build/assets/onboarding-modal-CZgjs5Bx.js +0 -1
- package/build/assets/option-service.api-BrZ5q87N.js +0 -1
- package/build/assets/organization-service.api-Bk4pCGU1.js +0 -1
- package/build/assets/path-utils-CSmuZoix.js +0 -1
- package/build/assets/plugins-management-service-BRBLxvUl.js +0 -1
- package/build/assets/profiles-service.api-BMXQ9pZC.js +0 -1
- package/build/assets/providers-BpO967xS.js +0 -1
- package/build/assets/proxy-BgJLtxu7.js +0 -1
- package/build/assets/root-BM5WgQ4_.js +0 -2
- package/build/assets/root-V8fkplMy.css +0 -1
- package/build/assets/root-layout-BdoyoobY.js +0 -2
- package/build/assets/schema-field-BSHrUMme.js +0 -1
- package/build/assets/sdk-section-page-t82gsOqa.js +0 -1
- package/build/assets/search-4DHskuhE.js +0 -1
- package/build/assets/secrets-service-B2GqPV0N.js +0 -1
- package/build/assets/settings-DmPLkgq0.js +0 -1
- package/build/assets/settings-modal-CwrZKrTq.js +0 -1
- package/build/assets/settings-service.api-Ce-ub11I.js +0 -1
- package/build/assets/shared-conversation-BDREQPnE.js +0 -1
- package/build/assets/skills-plugins-2Y44YHbO.js +0 -1
- package/build/assets/use-acp-credential-form-MzZM6D7D.js +0 -1
- package/build/assets/use-agent-profiles-DwZxnwO5.js +0 -1
- package/build/assets/use-backends-health-CRHzuvxO.js +0 -1
- package/build/assets/use-cloud-current-user-id-utFreJ0i.js +0 -1
- package/build/assets/use-config-mJrtZHP1.js +0 -1
- package/build/assets/use-create-conversation-DdqsP78F.js +0 -1
- package/build/assets/use-is-authed-B6M0mcGL.js +0 -1
- package/build/assets/use-llm-profiles-RC-PkpBl.js +0 -1
- package/build/assets/use-plugins-marketplace-FHlbeQ4c.js +0 -1
- package/build/assets/use-runtime-is-ready-RKj2WmEh.js +0 -1
- package/build/assets/use-save-agent-profile-tvDP15mh.js +0 -1
- package/build/assets/use-settings-CH5LzXD8.js +0 -1
- package/build/assets/use-settings-nav-items-Dwktv8wo.js +0 -16
- package/build/assets/use-skills-CSzSL6HG.js +0 -1
- package/build/assets/vendor~entry.client~root~root-layout~home~conversation-panel~conversation~launch~extensions~lf310vj7-CSVPGId3.js +0 -10
- package/build/assets/vendor~entry.client~root~root-layout~home~conversation-panel~conversation~launch~extensions~nfpll1wp-BCpa95De.js +0 -177
- package/build/assets/vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-CqXiHwjU.js +0 -2
- package/build/assets/x-Cb0WAtak.js +0 -1
- /package/build/assets/{__vite-browser-external-4xJsU2-u.js → __vite-browser-external-DU-Cb_sT.js} +0 -0
- /package/build/assets/{automation-BU8zu-FP.js → automation-BVEmGghX.js} +0 -0
- /package/build/assets/{checkmark-h7udgXMM.js → checkmark-DhcMGwtO.js} +0 -0
- /package/build/assets/{close-p3Op5lxI.js → close-DO5Rq_CS.js} +0 -0
- /package/build/assets/{code-tag-DYlAhEQY.js → code-tag-BvjtzUsr.js} +0 -0
- /package/build/assets/{color-themes-DPBzNtVg.js → color-themes-bVMmJJG6.js} +0 -0
- /package/build/assets/{common-Dh7OqABE.js → common-CxbUL3nR.js} +0 -0
- /package/build/assets/{conversation-local-storage-F6zVMM--.js → conversation-local-storage-B31voBg7.js} +0 -0
- /package/build/assets/{copy-BLmGeJcd.js → copy-DLUw2us-.js} +0 -0
- /package/build/assets/{createLucideIcon-CVAsW2Yo.js → createLucideIcon-D4Tak8A1.js} +0 -0
- /package/build/assets/{dist-CodhvuAP.js → dist-Dj01FXbZ.js} +0 -0
- /package/build/assets/{environment-switch-store-DkjlKWzN.js → environment-switch-store-D9hB9PvY.js} +0 -0
- /package/build/assets/{extension-module-card-classes-9V3J9J_b.js → extension-module-card-classes-BlO4w702.js} +0 -0
- /package/build/assets/{folder-mMWFvaw0.js → folder-ynxP2EIL.js} +0 -0
- /package/build/assets/{get-language-from-path-CPNnuVhI.js → get-language-from-path-rWEQrOGU.js} +0 -0
- /package/build/assets/{git-status-mapper-BI8FyUVp.js → git-status-mapper-C3rfzUex.js} +0 -0
- /package/build/assets/{handle-capture-consent-CPMXU29K.js → handle-capture-consent-DDnXMC9Y.js} +0 -0
- /package/build/assets/{iconBase-DaTRRUjy.js → iconBase-CuvxAQZ5.js} +0 -0
- /package/build/assets/{link-external-DfKQroKX.js → link-external-BVDiJfZt.js} +0 -0
- /package/build/assets/{llm-subscription-DtsX5Lhg.js → llm-subscription-BkdHwtQe.js} +0 -0
- /package/build/assets/{map-provider-BM2SJarp.js → map-provider-XSFHmdDs.js} +0 -0
- /package/build/assets/{middleware-DYHYNAnP.js → middleware-CWYDMVuG.js} +0 -0
- /package/build/assets/{modal-backdrop-zVwfOJZr.js → modal-backdrop-Dsmzgr5s.js} +0 -0
- /package/build/assets/{navigation-context-DPs2G0X8.js → navigation-context-SjzfpygH.js} +0 -0
- /package/build/assets/{objectWithoutPropertiesLoose-BgYIAs2y.js → objectWithoutPropertiesLoose-CQwg6tC0.js} +0 -0
- /package/build/assets/{openhands-logo-bcNw5bkY.js → openhands-logo-C-gDp1wr.js} +0 -0
- /package/build/assets/{react-DPDgL6du.js → react-ao3tWj6K.js} +0 -0
- /package/build/assets/{search-BsjCEFNf.js → search-pb4UKTfK.js} +0 -0
- /package/build/assets/{settings-mQUckT0g.js → settings-DksoDv5z.js} +0 -0
- /package/build/assets/{settings-gear-MXCyZW0x.js → settings-gear-D_HVvE5L.js} +0 -0
- /package/build/assets/{settings-like-page-layout-classes-DENKlZpD.js → settings-like-page-layout-classes-DLrsPCaI.js} +0 -0
- /package/build/assets/{settings-section-header-context-COMOIaxV.js → settings-section-header-context-C2MUIG88.js} +0 -0
- /package/build/assets/{skills-9cDZONKh.js → skills-CH4xgIh-.js} +0 -0
- /package/build/assets/{switch-skeleton-DqQwalyu.js → switch-skeleton-BpSOvZ-n.js} +0 -0
- /package/build/assets/{terminal-BuLtIrb9.js → terminal-Dokfkxn0.js} +0 -0
- /package/build/assets/{u-check-circle-BqoIxNz_.js → u-check-circle-DPWVO-9N.js} +0 -0
- /package/build/assets/{u-check-circle-half-LiwZkuFe.js → u-check-circle-half-BGNAAjOW.js} +0 -0
- /package/build/assets/{u-edit-DJ90xc8Z.js → u-edit-BIskOoEG.js} +0 -0
- /package/build/assets/{use-breakpoint-B-O0lQ84.js → use-breakpoint-OjLm94cF.js} +0 -0
- /package/build/assets/{use-click-outside-element-Bf4Omzot.js → use-click-outside-element-Jpt2WWd7.js} +0 -0
- /package/build/assets/{use-onboarding-completion-C_Swglzp.js → use-onboarding-completion-DkRpDTsD.js} +0 -0
- /package/build/assets/{v4-DbCkTSoU.js → v4-DPgam_jW.js} +0 -0
- /package/build/assets/{vendor~browser-Dqd33JyP.js → vendor~browser-BvEKaLBz.js} +0 -0
- /package/build/assets/{vendor~conversation-panel~conversation~alert-banner-DHDkknt5.js → vendor~conversation-panel~conversation~alert-banner-CznX01nZ.js} +0 -0
- /package/build/assets/{vendor~entry.client~root~root-layout~home~conversation-panel~conversation~launch~extensions~epa72zsm-B4rXs7Zd.js → vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~e0djk9ee-Cw6LUe8p.js} +0 -0
- /package/build/assets/{vendor~entry.client~root~root-layout~home~conversation-panel~conversation~launch~skills-set~ivvrgjsa-BlIZPCXX.js → vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~g9hift2g-DmmpG9SI.js} +0 -0
- /package/build/assets/{vendor~entry.client~root~root-layout~index-redirect~home~conversation-panel~conversation~la~byh8a1g4-XNPdpuO8.js → vendor~entry.client~root~root-layout~index-home~home~conversation-panel~conversation~launch~jkpzbynd-CkpBIhVe.js} +0 -0
- /package/build/assets/{vendor~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-DTzw2x50.js → vendor~index-home~home~conversation-panel~conversation~shared-conversation~planner-tab~files-tab-BuL5kjfS.js} +0 -0
- /package/build/assets/{vendor~root-layout~home~conversation-panel~conversation~launch~skills-settings~automations-~ki5qnp0d-rV6d1Iv_.js → vendor~root-layout~index-home~home~conversation-panel~conversation~launch~skills-settings~a~j2jlu22h-DUYl8gbZ.js} +0 -0
- /package/build/assets/{vendor~root-layout~home~mcp~automations-list~onboarding-modal-CXyYYvYh.js → vendor~root-layout~index-home~home~mcp~automations-list~onboarding-modal-pIgQDxTc.js} +0 -0
- /package/build/assets/{vendor~terminal-aeP3PnKf.js → vendor~terminal-jAaNQWS9.js} +0 -0
- /package/build/assets/{x-mark-Batp6gXa.js → x-mark-C5przfXl.js} +0 -0
|
@@ -6,14 +6,14 @@ import { cn as i } from "../../../utils/utils.js";
|
|
|
6
6
|
import { useConversationStore as a } from "../../../stores/conversation-store.js";
|
|
7
7
|
import { isNoBackend as o } from "../../../api/backend-registry/active-store.js";
|
|
8
8
|
import { useActiveBackendContext as s } from "../../../contexts/active-backend-context.js";
|
|
9
|
-
import { formControlTransitionClassName as
|
|
10
|
-
import { StyledTooltip as
|
|
11
|
-
import { dropdownFooterActionClassName as
|
|
12
|
-
import { NavigationLink as
|
|
13
|
-
import { useAllCloudOrganizations as
|
|
14
|
-
import { useCloudCurrentUserId as
|
|
15
|
-
import { Dropdown as
|
|
16
|
-
import { useBackendsHealth as
|
|
9
|
+
import { formControlTransitionClassName as c } from "../../../utils/form-control-classes.js";
|
|
10
|
+
import { StyledTooltip as ee } from "../../shared/buttons/styled-tooltip.js";
|
|
11
|
+
import { dropdownFooterActionClassName as l, dropdownMenuListClassName as te, dropdownMenuRowIconWrapperClassName as u } from "../../../utils/dropdown-classes.js";
|
|
12
|
+
import { NavigationLink as ne } from "../../shared/navigation-link.js";
|
|
13
|
+
import { useAllCloudOrganizations as re } from "../../../hooks/query/use-cloud-organizations.js";
|
|
14
|
+
import { useCloudCurrentUserId as d } from "../../../hooks/query/use-cloud-current-user-id.js";
|
|
15
|
+
import { Dropdown as ie } from "../../../ui/dropdown/dropdown.js";
|
|
16
|
+
import { useBackendsHealth as ae } from "../../../hooks/query/use-backends-health.js";
|
|
17
17
|
import { triggerEnvironmentSwitch as f } from "./environment-switch-store.js";
|
|
18
18
|
import { BackendStatusDot as p } from "./backend-status-dot.js";
|
|
19
19
|
import { AddBackendModal as oe } from "./add-backend-modal.js";
|
|
@@ -39,7 +39,7 @@ function b(e) {
|
|
|
39
39
|
function de() {
|
|
40
40
|
return /* @__PURE__ */ h(p, { isConnected: "unavailable" });
|
|
41
41
|
}
|
|
42
|
-
function
|
|
42
|
+
function x(e, t, n, r, i) {
|
|
43
43
|
let a = [], o = e.filter((e) => e.kind === "local"), s = e.filter((e) => e.kind === "cloud");
|
|
44
44
|
for (let e of o) a.push({
|
|
45
45
|
value: y(e.id, null),
|
|
@@ -67,20 +67,20 @@ function fe(e, t, n, r, i) {
|
|
|
67
67
|
}
|
|
68
68
|
return a;
|
|
69
69
|
}
|
|
70
|
-
function
|
|
71
|
-
let { t: E } = e("openhands"), { backends: D, active: O, setActive: k } = s(), A =
|
|
70
|
+
function S({ openUpward: p = !1, hideTrigger: v = !1, defaultOpen: S = !1, onSelectOption: C, onOpenAddBackend: w, onOpenManageBackends: T, sidebarCollapsed: fe = !1 } = {}) {
|
|
71
|
+
let { t: E } = e("openhands"), { backends: D, active: O, setActive: k } = s(), A = re(), j = d(), M = ae(D), N = le(), P = _("/settings"), F = _("/settings/*"), I = _("/conversations/:conversationId"), L = _("/automations/:automationId"), [R, z] = m.useState(!1), [B, V] = m.useState(!1), H = E(t.BACKEND$PERSONAL_WORKSPACE), U = m.useMemo(() => x(D, H, A, j, M), [
|
|
72
72
|
D,
|
|
73
73
|
H,
|
|
74
74
|
A,
|
|
75
75
|
j,
|
|
76
76
|
M
|
|
77
|
-
]), W = o(O.backend), G = E(t.BACKEND$NO_BACKEND_AVAILABLE), K = y(O.backend.id, O.orgId), q = W ? void 0 : U.find((e) => e.value === K), J = !!(P || F), Y = E(t.SIDEBAR$SETTINGS),
|
|
77
|
+
]), W = o(O.backend), G = E(t.BACKEND$NO_BACKEND_AVAILABLE), K = y(O.backend.id, O.orgId), q = W ? void 0 : U.find((e) => e.value === K), J = !!(P || F), Y = E(t.SIDEBAR$SETTINGS), X = a((e) => e.isRightPanelShown), pe = !fe || I && X ? "top" : "left", me = Object.values(A).some((e) => e.isLoading);
|
|
78
78
|
m.useEffect(() => {
|
|
79
79
|
if (W || O.backend.kind !== "cloud" || O.orgId) return;
|
|
80
80
|
let { backend: e } = O, t = A[e.id];
|
|
81
81
|
if (!t || t.orgs.length === 0) return;
|
|
82
|
-
let n = j[e.id]?.userId ?? null,
|
|
83
|
-
|
|
82
|
+
let n = t.currentOrgId ? t.orgs.find((e) => e.id === t.currentOrgId) : void 0, r = j[e.id]?.userId ?? null, i = r ? t.orgs.find((e) => e.id === r) : void 0, a = n ?? i ?? t.orgs[0];
|
|
83
|
+
a && k(e.id, a.id);
|
|
84
84
|
}, [
|
|
85
85
|
O,
|
|
86
86
|
A,
|
|
@@ -88,40 +88,40 @@ function x({ openUpward: p = !1, hideTrigger: v = !1, defaultOpen: x = !1, onSel
|
|
|
88
88
|
k,
|
|
89
89
|
W
|
|
90
90
|
]);
|
|
91
|
-
let
|
|
92
|
-
if (
|
|
93
|
-
|
|
91
|
+
let Z = m.useCallback(() => {
|
|
92
|
+
if (w) {
|
|
93
|
+
w(), C?.();
|
|
94
94
|
return;
|
|
95
95
|
}
|
|
96
96
|
z(!0);
|
|
97
|
-
}, [
|
|
98
|
-
if (
|
|
99
|
-
|
|
97
|
+
}, [w, C]), Q = m.useCallback(() => {
|
|
98
|
+
if (T) {
|
|
99
|
+
T(), C?.();
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
102
|
V(!0);
|
|
103
|
-
}, [
|
|
103
|
+
}, [T, C]), $ = m.useCallback((e) => {
|
|
104
104
|
e.preventDefault(), e.stopPropagation();
|
|
105
|
-
}, []),
|
|
106
|
-
|
|
107
|
-
}, [
|
|
108
|
-
|
|
109
|
-
}, [
|
|
110
|
-
|
|
111
|
-
}, [
|
|
112
|
-
|
|
113
|
-
}, [
|
|
114
|
-
className:
|
|
105
|
+
}, []), he = m.useCallback((e) => {
|
|
106
|
+
$(e), Z();
|
|
107
|
+
}, [Z, $]), ge = m.useCallback((e) => {
|
|
108
|
+
$(e), Z();
|
|
109
|
+
}, [Z, $]), _e = m.useCallback((e) => {
|
|
110
|
+
$(e), Q();
|
|
111
|
+
}, [Q, $]), ve = m.useCallback((e) => {
|
|
112
|
+
$(e), Q();
|
|
113
|
+
}, [Q, $]), ye = /* @__PURE__ */ g("div", {
|
|
114
|
+
className: te,
|
|
115
115
|
children: [/* @__PURE__ */ g("button", {
|
|
116
116
|
type: "button",
|
|
117
117
|
"data-testid": "add-backend-menu-item",
|
|
118
|
-
onMouseDown:
|
|
119
|
-
onTouchStart:
|
|
120
|
-
onTouchEnd:
|
|
121
|
-
onClick:
|
|
122
|
-
className: i(
|
|
118
|
+
onMouseDown: $,
|
|
119
|
+
onTouchStart: $,
|
|
120
|
+
onTouchEnd: ge,
|
|
121
|
+
onClick: he,
|
|
122
|
+
className: i(l, "cursor-pointer rounded-md"),
|
|
123
123
|
children: [/* @__PURE__ */ h("span", {
|
|
124
|
-
className:
|
|
124
|
+
className: u,
|
|
125
125
|
"aria-hidden": !0,
|
|
126
126
|
children: /* @__PURE__ */ h(n, {
|
|
127
127
|
width: 16,
|
|
@@ -131,13 +131,13 @@ function x({ openUpward: p = !1, hideTrigger: v = !1, defaultOpen: x = !1, onSel
|
|
|
131
131
|
}), /* @__PURE__ */ g("button", {
|
|
132
132
|
type: "button",
|
|
133
133
|
"data-testid": "manage-backends-menu-item",
|
|
134
|
-
onMouseDown:
|
|
135
|
-
onTouchStart:
|
|
134
|
+
onMouseDown: $,
|
|
135
|
+
onTouchStart: $,
|
|
136
136
|
onTouchEnd: ve,
|
|
137
137
|
onClick: _e,
|
|
138
|
-
className: i(
|
|
138
|
+
className: i(l, "cursor-pointer rounded-md"),
|
|
139
139
|
children: [/* @__PURE__ */ h("span", {
|
|
140
|
-
className:
|
|
140
|
+
className: u,
|
|
141
141
|
"aria-hidden": !0,
|
|
142
142
|
children: /* @__PURE__ */ h(r, {
|
|
143
143
|
width: 16,
|
|
@@ -150,7 +150,7 @@ function x({ openUpward: p = !1, hideTrigger: v = !1, defaultOpen: x = !1, onSel
|
|
|
150
150
|
let { backendId: t, orgId: n } = ue(e), r = D.find((e) => e.id === t);
|
|
151
151
|
r && (f(U.find((t) => t.value === e)?.label ?? r.name), await new Promise((e) => {
|
|
152
152
|
setTimeout(e, 400);
|
|
153
|
-
}), I ? N("/conversations") : L && N("/automations"), k(r.id, n),
|
|
153
|
+
}), I ? N("/conversations") : L && N("/automations"), k(r.id, n), C?.());
|
|
154
154
|
}, [
|
|
155
155
|
K,
|
|
156
156
|
D,
|
|
@@ -160,14 +160,14 @@ function x({ openUpward: p = !1, hideTrigger: v = !1, defaultOpen: x = !1, onSel
|
|
|
160
160
|
U,
|
|
161
161
|
k,
|
|
162
162
|
E,
|
|
163
|
-
|
|
163
|
+
C
|
|
164
164
|
]);
|
|
165
165
|
return /* @__PURE__ */ g(ce, { children: [
|
|
166
166
|
/* @__PURE__ */ g("div", {
|
|
167
167
|
className: "flex items-center gap-2 w-full",
|
|
168
168
|
children: [/* @__PURE__ */ h("div", {
|
|
169
169
|
className: "flex-1 min-w-0",
|
|
170
|
-
children: /* @__PURE__ */ h(
|
|
170
|
+
children: /* @__PURE__ */ h(ie, {
|
|
171
171
|
testId: "backend-selector",
|
|
172
172
|
defaultValue: q ?? {
|
|
173
173
|
value: K,
|
|
@@ -177,26 +177,26 @@ function x({ openUpward: p = !1, hideTrigger: v = !1, defaultOpen: x = !1, onSel
|
|
|
177
177
|
footer: ye,
|
|
178
178
|
openUpward: p,
|
|
179
179
|
hideTrigger: v,
|
|
180
|
-
defaultOpen:
|
|
180
|
+
defaultOpen: S,
|
|
181
181
|
openOnHover: !v,
|
|
182
182
|
onChange: (e) => {
|
|
183
183
|
e && be(e.value);
|
|
184
184
|
},
|
|
185
185
|
placeholder: W ? G : O.backend.name,
|
|
186
|
-
loading:
|
|
186
|
+
loading: me,
|
|
187
187
|
options: U,
|
|
188
188
|
className: "h-10 px-2 py-0 bg-transparent border-transparent hover:bg-[var(--oh-surface-raised)] focus-within:bg-[var(--oh-surface-raised)] focus-within:border-transparent focus-within:ring-0"
|
|
189
189
|
}, `${K}-${q?.label ?? ""}`)
|
|
190
|
-
}), v ? null : /* @__PURE__ */ h(
|
|
190
|
+
}), v ? null : /* @__PURE__ */ h(ee, {
|
|
191
191
|
content: Y,
|
|
192
|
-
placement:
|
|
192
|
+
placement: pe,
|
|
193
193
|
offset: 10,
|
|
194
|
-
children: /* @__PURE__ */ h(
|
|
194
|
+
children: /* @__PURE__ */ h(ne, {
|
|
195
195
|
to: "/settings",
|
|
196
196
|
"data-testid": "backend-selector-settings-link",
|
|
197
197
|
"data-active": J,
|
|
198
198
|
"aria-label": Y,
|
|
199
|
-
className: i(J ? "inline-flex items-center justify-center shrink-0 w-9 h-9 rounded-md bg-tertiary text-white font-normal cursor-pointer" : "inline-flex items-center justify-center shrink-0 w-9 h-9 rounded-md text-[var(--oh-muted)] hover:text-white hover:bg-[var(--oh-surface-raised)] cursor-pointer",
|
|
199
|
+
className: i(J ? "inline-flex items-center justify-center shrink-0 w-9 h-9 rounded-md bg-tertiary text-white font-normal cursor-pointer" : "inline-flex items-center justify-center shrink-0 w-9 h-9 rounded-md text-[var(--oh-muted)] hover:text-white hover:bg-[var(--oh-surface-raised)] cursor-pointer", c),
|
|
200
200
|
children: /* @__PURE__ */ h(r, {
|
|
201
201
|
width: 16,
|
|
202
202
|
height: 16
|
|
@@ -209,6 +209,6 @@ function x({ openUpward: p = !1, hideTrigger: v = !1, defaultOpen: x = !1, onSel
|
|
|
209
209
|
] });
|
|
210
210
|
}
|
|
211
211
|
//#endregion
|
|
212
|
-
export {
|
|
212
|
+
export { S as BackendSelector };
|
|
213
213
|
|
|
214
214
|
//# sourceMappingURL=backend-selector.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"backend-selector.js","names":[],"sources":["../../../../src/components/features/backends/backend-selector.tsx"],"sourcesContent":["import React from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { useMatch, useNavigate } from \"react-router\";\nimport { Plus, Settings } from \"lucide-react\";\nimport { Dropdown } from \"#/ui/dropdown/dropdown\";\nimport { DropdownOption } from \"#/ui/dropdown/types\";\nimport { isNoBackend } from \"#/api/backend-registry/active-store\";\nimport { useActiveBackendContext } from \"#/contexts/active-backend-context\";\nimport { useAllCloudOrganizations } from \"#/hooks/query/use-cloud-organizations\";\nimport { useCloudCurrentUserId } from \"#/hooks/query/use-cloud-current-user-id\";\nimport {\n useBackendsHealth,\n type BackendHealth,\n} from \"#/hooks/query/use-backends-health\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport type { Backend } from \"#/api/backend-registry/types\";\n// Import the trigger helpers from the lightweight store, not the overlay\n// component, so the eagerly-mounted sidebar/backend-selector graph does not\n// pull in the overlay's render code (the overlay is lazy-loaded from\n// `routes/root-layout.tsx`).\nimport {\n ENVIRONMENT_SWITCH_SETACTIVE_DELAY_MS,\n triggerEnvironmentSwitch,\n} from \"#/components/features/backends/environment-switch-store\";\nimport { NavigationLink } from \"#/components/shared/navigation-link\";\nimport { StyledTooltip } from \"#/components/shared/buttons/styled-tooltip\";\nimport { useConversationStore } from \"#/stores/conversation-store\";\nimport { AddBackendModal } from \"./add-backend-modal\";\nimport { BackendStatusDot } from \"./backend-status-dot\";\nimport { ManageBackendsModal } from \"./manage-backends-modal\";\nimport { cn } from \"#/utils/utils\";\nimport { formControlTransitionClassName } from \"#/utils/form-control-classes\";\nimport {\n dropdownFooterActionClassName,\n dropdownMenuListClassName,\n dropdownMenuRowIconWrapperClassName,\n} from \"#/utils/dropdown-classes\";\n\nconst VALUE_SEPARATOR = \"::\";\n\nfunction makeOptionValue(backendId: string, orgId: string | null): string {\n return orgId ? `${backendId}${VALUE_SEPARATOR}${orgId}` : backendId;\n}\n\nfunction parseOptionValue(value: string): {\n backendId: string;\n orgId: string | null;\n} {\n const [backendId, orgId] = value.split(VALUE_SEPARATOR);\n return { backendId, orgId: orgId ?? null };\n}\n\nfunction buildStatusPrefix(health: BackendHealth | undefined) {\n return <BackendStatusDot isConnected={health?.isConnected ?? null} />;\n}\n\nfunction buildNoBackendPrefix() {\n return <BackendStatusDot isConnected=\"unavailable\" />;\n}\n\nfunction buildOptions(\n registered: Backend[],\n personalWorkspaceLabel: string,\n cloudOrgs: ReturnType<typeof useAllCloudOrganizations>,\n currentUserIds: ReturnType<typeof useCloudCurrentUserId>,\n healthByBackendId: Record<string, BackendHealth>,\n): DropdownOption[] {\n const options: DropdownOption[] = [];\n\n const locals = registered.filter((b) => b.kind === \"local\");\n const clouds = registered.filter((b) => b.kind === \"cloud\");\n\n for (const b of locals) {\n options.push({\n value: makeOptionValue(b.id, null),\n label: b.name,\n prefix: buildStatusPrefix(healthByBackendId[b.id]),\n });\n }\n\n for (const b of clouds) {\n const entry = cloudOrgs[b.id];\n const prefix = buildStatusPrefix(healthByBackendId[b.id]);\n if (!entry || entry.orgs.length === 0) {\n options.push({\n value: makeOptionValue(b.id, null),\n label: b.name,\n prefix,\n });\n } else {\n // Personal-workspace rule (per the cloud contract): the org whose\n // id matches the calling user's id is the user's personal\n // workspace. We resolve `user_id` once per backend (via /me on any\n // one org) and apply it across all orgs of that backend.\n const userIdForBackend = currentUserIds[b.id]?.userId ?? null;\n\n for (const org of entry.orgs) {\n const isPersonal = !!userIdForBackend && userIdForBackend === org.id;\n const orgLabel = isPersonal ? personalWorkspaceLabel : org.name;\n options.push({\n value: makeOptionValue(b.id, org.id),\n label: `${b.name} – ${orgLabel}`,\n // All org rows for the same cloud backend share that backend's\n // single connectivity verdict — there is no per-org probe.\n prefix,\n });\n }\n }\n }\n\n return options;\n}\n\ninterface BackendSelectorProps {\n /** Render the menu above the trigger (e.g. when pinned to bottom of sidebar). */\n openUpward?: boolean;\n /** Hide the selector input trigger and only render the dropdown menu. */\n hideTrigger?: boolean;\n /** Whether the dropdown menu should start open on mount. */\n defaultOpen?: boolean;\n /** Callback fired after selecting a backend/org option. */\n onSelectOption?: () => void;\n /**\n * Override the internal Add Backend modal handling. When provided,\n * clicking \"Add Backend\" calls this instead of opening BackendSelector's\n * own modal. Useful when the selector is mounted inside an ephemeral\n * container (e.g. the collapsed-sidebar popover) and the modal must\n * survive the parent unmounting.\n */\n onOpenAddBackend?: () => void;\n /** Same as onOpenAddBackend but for the Manage Backends modal. */\n onOpenManageBackends?: () => void;\n /**\n * Whether the surrounding sidebar rail is in its collapsed variant. Passed\n * down from `SidebarRailBody` so the mobile drawer (which always renders\n * the expanded rail) can override the persisted desktop value.\n */\n sidebarCollapsed?: boolean;\n}\n\nexport function BackendSelector({\n openUpward = false,\n hideTrigger = false,\n defaultOpen = false,\n onSelectOption,\n onOpenAddBackend,\n onOpenManageBackends,\n sidebarCollapsed = false,\n}: BackendSelectorProps = {}) {\n const { t } = useTranslation(\"openhands\");\n const { backends, active, setActive } = useActiveBackendContext();\n const cloudOrgs = useAllCloudOrganizations();\n const currentUserIds = useCloudCurrentUserId();\n // Probe each registered backend every 10s.\n const healthByBackendId = useBackendsHealth(backends);\n const navigate = useNavigate();\n const settingsMatch = useMatch(\"/settings\");\n const settingsSubrouteMatch = useMatch(\"/settings/*\");\n const conversationMatch = useMatch(\"/conversations/:conversationId\");\n const automationDetailMatch = useMatch(\"/automations/:automationId\");\n const [addBackendModalOpen, setAddBackendModalOpen] = React.useState(false);\n const [manageBackendsModalOpen, setManageBackendsModalOpen] =\n React.useState(false);\n\n const personalWorkspaceLabel = t(I18nKey.BACKEND$PERSONAL_WORKSPACE);\n\n const options = React.useMemo(\n () =>\n buildOptions(\n backends,\n personalWorkspaceLabel,\n cloudOrgs,\n currentUserIds,\n healthByBackendId,\n ),\n [\n backends,\n personalWorkspaceLabel,\n cloudOrgs,\n currentUserIds,\n healthByBackendId,\n ],\n );\n\n const noBackendSelected = isNoBackend(active.backend);\n const noBackendLabel = t(I18nKey.BACKEND$NO_BACKEND_AVAILABLE);\n const activeValue = makeOptionValue(active.backend.id, active.orgId);\n const activeOption = noBackendSelected\n ? undefined\n : options.find((o) => o.value === activeValue);\n const isSettingsActive = Boolean(settingsMatch || settingsSubrouteMatch);\n const settingsLabel = t(I18nKey.SIDEBAR$SETTINGS);\n const isRightPanelShown = useConversationStore(\n (state) => state.isRightPanelShown,\n );\n // When the sidebar rail is expanded, `placement=\"left\"` hugs the main\n // canvas and reads awkwardly; prefer above the control. When the rail is\n // collapsed, keep left except on active conversation + open right drawer.\n const settingsTooltipPlacement =\n !sidebarCollapsed || (conversationMatch && isRightPanelShown)\n ? \"top\"\n : \"left\";\n\n const someCloudLoading = Object.values(cloudOrgs).some((c) => c.isLoading);\n\n // Self-heal a malformed `(cloudBackendId, null)` selection.\n //\n // Once a cloud backend's orgs resolve, the dropdown only renders\n // per-org rows for it — the `(backendId, null)` row disappears, so\n // selecting that shape would drift from what the dropdown can render\n // (UI says \"Local\", APIs hit cloud). When we detect the drift, snap\n // the selection onto the personal-workspace org (or, lacking a /me\n // result, the first org). The selection is recorded locally only;\n // the cloud request scope follows from the API key's bound org and the\n // X-Org-Id header sent by `callCloudProxy`, so the cloud UI's\n // org choice is never mutated as a side effect.\n React.useEffect(() => {\n if (noBackendSelected || active.backend.kind !== \"cloud\" || active.orgId)\n return;\n const { backend } = active;\n const entry = cloudOrgs[backend.id];\n if (!entry || entry.orgs.length === 0) return;\n\n const userId = currentUserIds[backend.id]?.userId ?? null;\n const personal = userId\n ? entry.orgs.find((o) => o.id === userId)\n : undefined;\n const target = personal ?? entry.orgs[0];\n if (target) {\n setActive(backend.id, target.id);\n }\n }, [active, cloudOrgs, currentUserIds, setActive, noBackendSelected]);\n\n const openAddBackendModal = React.useCallback(() => {\n if (onOpenAddBackend) {\n onOpenAddBackend();\n onSelectOption?.();\n return;\n }\n setAddBackendModalOpen(true);\n }, [onOpenAddBackend, onSelectOption]);\n\n const openManageBackendsModal = React.useCallback(() => {\n if (onOpenManageBackends) {\n onOpenManageBackends();\n onSelectOption?.();\n return;\n }\n setManageBackendsModalOpen(true);\n }, [onOpenManageBackends, onSelectOption]);\n\n const preventDropdownMenuClose = React.useCallback(\n (event: React.SyntheticEvent<HTMLButtonElement>) => {\n event.preventDefault();\n event.stopPropagation();\n },\n [],\n );\n\n const handleAddBackendClick = React.useCallback(\n (event: React.MouseEvent<HTMLButtonElement>) => {\n preventDropdownMenuClose(event);\n openAddBackendModal();\n },\n [openAddBackendModal, preventDropdownMenuClose],\n );\n\n const handleAddBackendTouchEnd = React.useCallback(\n (event: React.TouchEvent<HTMLButtonElement>) => {\n preventDropdownMenuClose(event);\n openAddBackendModal();\n },\n [openAddBackendModal, preventDropdownMenuClose],\n );\n\n const handleManageBackendsClick = React.useCallback(\n (event: React.MouseEvent<HTMLButtonElement>) => {\n preventDropdownMenuClose(event);\n openManageBackendsModal();\n },\n [openManageBackendsModal, preventDropdownMenuClose],\n );\n\n const handleManageBackendsTouchEnd = React.useCallback(\n (event: React.TouchEvent<HTMLButtonElement>) => {\n preventDropdownMenuClose(event);\n openManageBackendsModal();\n },\n [openManageBackendsModal, preventDropdownMenuClose],\n );\n\n const addBackendFooter = (\n <div className={dropdownMenuListClassName}>\n <button\n type=\"button\"\n data-testid=\"add-backend-menu-item\"\n onMouseDown={preventDropdownMenuClose}\n onTouchStart={preventDropdownMenuClose}\n onTouchEnd={handleAddBackendTouchEnd}\n onClick={handleAddBackendClick}\n className={cn(\n dropdownFooterActionClassName,\n \"cursor-pointer rounded-md\",\n )}\n >\n <span className={dropdownMenuRowIconWrapperClassName} aria-hidden>\n <Plus width={16} height={16} />\n </span>\n {t(I18nKey.BACKEND$ADD)}\n </button>\n <button\n type=\"button\"\n data-testid=\"manage-backends-menu-item\"\n onMouseDown={preventDropdownMenuClose}\n onTouchStart={preventDropdownMenuClose}\n onTouchEnd={handleManageBackendsTouchEnd}\n onClick={handleManageBackendsClick}\n className={cn(\n dropdownFooterActionClassName,\n \"cursor-pointer rounded-md\",\n )}\n >\n <span className={dropdownMenuRowIconWrapperClassName} aria-hidden>\n <Settings width={16} height={16} />\n </span>\n {t(I18nKey.BACKEND$MANAGE)}\n </button>\n </div>\n );\n\n const handleSelectBackend = React.useCallback(\n async (value: string) => {\n if (value === activeValue) return;\n\n const { backendId, orgId } = parseOptionValue(value);\n const target = backends.find((b) => b.id === backendId);\n if (!target) return;\n\n triggerEnvironmentSwitch(\n options.find((option) => option.value === value)?.label ?? target.name,\n );\n await new Promise<void>((resolve) => {\n setTimeout(resolve, ENVIRONMENT_SWITCH_SETACTIVE_DELAY_MS);\n });\n\n // @spec BM-002 — Switching backends keeps the user on the same page\n if (conversationMatch) navigate(\"/conversations\");\n else if (automationDetailMatch) navigate(\"/automations\");\n\n setActive(target.id, orgId);\n onSelectOption?.();\n },\n [\n activeValue,\n backends,\n conversationMatch,\n automationDetailMatch,\n navigate,\n options,\n setActive,\n t,\n onSelectOption,\n ],\n );\n\n return (\n <>\n <div className=\"flex items-center gap-2 w-full\">\n <div className=\"flex-1 min-w-0\">\n <Dropdown\n testId=\"backend-selector\"\n key={`${activeValue}-${activeOption?.label ?? \"\"}`}\n defaultValue={\n activeOption ?? {\n value: activeValue,\n label: noBackendSelected ? noBackendLabel : active.backend.name,\n prefix: noBackendSelected\n ? buildNoBackendPrefix()\n : buildStatusPrefix(healthByBackendId[active.backend.id]),\n }\n }\n footer={addBackendFooter}\n openUpward={openUpward}\n hideTrigger={hideTrigger}\n defaultOpen={defaultOpen}\n openOnHover={!hideTrigger}\n onChange={(item) => {\n if (!item) return;\n void handleSelectBackend(item.value);\n }}\n placeholder={\n noBackendSelected ? noBackendLabel : active.backend.name\n }\n loading={someCloudLoading}\n options={options}\n className=\"h-10 px-2 py-0 bg-transparent border-transparent hover:bg-[var(--oh-surface-raised)] focus-within:bg-[var(--oh-surface-raised)] focus-within:border-transparent focus-within:ring-0\"\n />\n </div>\n {!hideTrigger ? (\n <StyledTooltip\n content={settingsLabel}\n placement={settingsTooltipPlacement}\n offset={10}\n >\n <NavigationLink\n to=\"/settings\"\n data-testid=\"backend-selector-settings-link\"\n data-active={isSettingsActive}\n aria-label={settingsLabel}\n className={\n isSettingsActive\n ? cn(\n \"inline-flex items-center justify-center shrink-0 w-9 h-9 rounded-md bg-tertiary text-white font-normal cursor-pointer\",\n formControlTransitionClassName,\n )\n : cn(\n \"inline-flex items-center justify-center shrink-0 w-9 h-9 rounded-md text-[var(--oh-muted)] hover:text-white hover:bg-[var(--oh-surface-raised)] cursor-pointer\",\n formControlTransitionClassName,\n )\n }\n >\n <Settings width={16} height={16} />\n </NavigationLink>\n </StyledTooltip>\n ) : null}\n </div>\n {addBackendModalOpen ? (\n <AddBackendModal onClose={() => setAddBackendModalOpen(false)} />\n ) : null}\n {manageBackendsModalOpen ? (\n <ManageBackendsModal\n onClose={() => setManageBackendsModalOpen(false)}\n />\n ) : null}\n </>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"backend-selector.js","names":[],"sources":["../../../../src/components/features/backends/backend-selector.tsx"],"sourcesContent":["import React from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { useMatch, useNavigate } from \"react-router\";\nimport { Plus, Settings } from \"lucide-react\";\nimport { Dropdown } from \"#/ui/dropdown/dropdown\";\nimport { DropdownOption } from \"#/ui/dropdown/types\";\nimport { isNoBackend } from \"#/api/backend-registry/active-store\";\nimport { useActiveBackendContext } from \"#/contexts/active-backend-context\";\nimport { useAllCloudOrganizations } from \"#/hooks/query/use-cloud-organizations\";\nimport { useCloudCurrentUserId } from \"#/hooks/query/use-cloud-current-user-id\";\nimport {\n useBackendsHealth,\n type BackendHealth,\n} from \"#/hooks/query/use-backends-health\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport type { Backend } from \"#/api/backend-registry/types\";\n// Import the trigger helpers from the lightweight store, not the overlay\n// component, so the eagerly-mounted sidebar/backend-selector graph does not\n// pull in the overlay's render code (the overlay is lazy-loaded from\n// `routes/root-layout.tsx`).\nimport {\n ENVIRONMENT_SWITCH_SETACTIVE_DELAY_MS,\n triggerEnvironmentSwitch,\n} from \"#/components/features/backends/environment-switch-store\";\nimport { NavigationLink } from \"#/components/shared/navigation-link\";\nimport { StyledTooltip } from \"#/components/shared/buttons/styled-tooltip\";\nimport { useConversationStore } from \"#/stores/conversation-store\";\nimport { AddBackendModal } from \"./add-backend-modal\";\nimport { BackendStatusDot } from \"./backend-status-dot\";\nimport { ManageBackendsModal } from \"./manage-backends-modal\";\nimport { cn } from \"#/utils/utils\";\nimport { formControlTransitionClassName } from \"#/utils/form-control-classes\";\nimport {\n dropdownFooterActionClassName,\n dropdownMenuListClassName,\n dropdownMenuRowIconWrapperClassName,\n} from \"#/utils/dropdown-classes\";\n\nconst VALUE_SEPARATOR = \"::\";\n\nfunction makeOptionValue(backendId: string, orgId: string | null): string {\n return orgId ? `${backendId}${VALUE_SEPARATOR}${orgId}` : backendId;\n}\n\nfunction parseOptionValue(value: string): {\n backendId: string;\n orgId: string | null;\n} {\n const [backendId, orgId] = value.split(VALUE_SEPARATOR);\n return { backendId, orgId: orgId ?? null };\n}\n\nfunction buildStatusPrefix(health: BackendHealth | undefined) {\n return <BackendStatusDot isConnected={health?.isConnected ?? null} />;\n}\n\nfunction buildNoBackendPrefix() {\n return <BackendStatusDot isConnected=\"unavailable\" />;\n}\n\nfunction buildOptions(\n registered: Backend[],\n personalWorkspaceLabel: string,\n cloudOrgs: ReturnType<typeof useAllCloudOrganizations>,\n currentUserIds: ReturnType<typeof useCloudCurrentUserId>,\n healthByBackendId: Record<string, BackendHealth>,\n): DropdownOption[] {\n const options: DropdownOption[] = [];\n\n const locals = registered.filter((b) => b.kind === \"local\");\n const clouds = registered.filter((b) => b.kind === \"cloud\");\n\n for (const b of locals) {\n options.push({\n value: makeOptionValue(b.id, null),\n label: b.name,\n prefix: buildStatusPrefix(healthByBackendId[b.id]),\n });\n }\n\n for (const b of clouds) {\n const entry = cloudOrgs[b.id];\n const prefix = buildStatusPrefix(healthByBackendId[b.id]);\n if (!entry || entry.orgs.length === 0) {\n options.push({\n value: makeOptionValue(b.id, null),\n label: b.name,\n prefix,\n });\n } else {\n // Personal-workspace rule (per the cloud contract): the org whose\n // id matches the calling user's id is the user's personal\n // workspace. We resolve `user_id` once per backend (via /me on any\n // one org) and apply it across all orgs of that backend.\n const userIdForBackend = currentUserIds[b.id]?.userId ?? null;\n\n for (const org of entry.orgs) {\n const isPersonal = !!userIdForBackend && userIdForBackend === org.id;\n const orgLabel = isPersonal ? personalWorkspaceLabel : org.name;\n options.push({\n value: makeOptionValue(b.id, org.id),\n label: `${b.name} – ${orgLabel}`,\n // All org rows for the same cloud backend share that backend's\n // single connectivity verdict — there is no per-org probe.\n prefix,\n });\n }\n }\n }\n\n return options;\n}\n\ninterface BackendSelectorProps {\n /** Render the menu above the trigger (e.g. when pinned to bottom of sidebar). */\n openUpward?: boolean;\n /** Hide the selector input trigger and only render the dropdown menu. */\n hideTrigger?: boolean;\n /** Whether the dropdown menu should start open on mount. */\n defaultOpen?: boolean;\n /** Callback fired after selecting a backend/org option. */\n onSelectOption?: () => void;\n /**\n * Override the internal Add Backend modal handling. When provided,\n * clicking \"Add Backend\" calls this instead of opening BackendSelector's\n * own modal. Useful when the selector is mounted inside an ephemeral\n * container (e.g. the collapsed-sidebar popover) and the modal must\n * survive the parent unmounting.\n */\n onOpenAddBackend?: () => void;\n /** Same as onOpenAddBackend but for the Manage Backends modal. */\n onOpenManageBackends?: () => void;\n /**\n * Whether the surrounding sidebar rail is in its collapsed variant. Passed\n * down from `SidebarRailBody` so the mobile drawer (which always renders\n * the expanded rail) can override the persisted desktop value.\n */\n sidebarCollapsed?: boolean;\n}\n\nexport function BackendSelector({\n openUpward = false,\n hideTrigger = false,\n defaultOpen = false,\n onSelectOption,\n onOpenAddBackend,\n onOpenManageBackends,\n sidebarCollapsed = false,\n}: BackendSelectorProps = {}) {\n const { t } = useTranslation(\"openhands\");\n const { backends, active, setActive } = useActiveBackendContext();\n const cloudOrgs = useAllCloudOrganizations();\n const currentUserIds = useCloudCurrentUserId();\n // Probe each registered backend every 10s.\n const healthByBackendId = useBackendsHealth(backends);\n const navigate = useNavigate();\n const settingsMatch = useMatch(\"/settings\");\n const settingsSubrouteMatch = useMatch(\"/settings/*\");\n const conversationMatch = useMatch(\"/conversations/:conversationId\");\n const automationDetailMatch = useMatch(\"/automations/:automationId\");\n const [addBackendModalOpen, setAddBackendModalOpen] = React.useState(false);\n const [manageBackendsModalOpen, setManageBackendsModalOpen] =\n React.useState(false);\n\n const personalWorkspaceLabel = t(I18nKey.BACKEND$PERSONAL_WORKSPACE);\n\n const options = React.useMemo(\n () =>\n buildOptions(\n backends,\n personalWorkspaceLabel,\n cloudOrgs,\n currentUserIds,\n healthByBackendId,\n ),\n [\n backends,\n personalWorkspaceLabel,\n cloudOrgs,\n currentUserIds,\n healthByBackendId,\n ],\n );\n\n const noBackendSelected = isNoBackend(active.backend);\n const noBackendLabel = t(I18nKey.BACKEND$NO_BACKEND_AVAILABLE);\n const activeValue = makeOptionValue(active.backend.id, active.orgId);\n const activeOption = noBackendSelected\n ? undefined\n : options.find((o) => o.value === activeValue);\n const isSettingsActive = Boolean(settingsMatch || settingsSubrouteMatch);\n const settingsLabel = t(I18nKey.SIDEBAR$SETTINGS);\n const isRightPanelShown = useConversationStore(\n (state) => state.isRightPanelShown,\n );\n // When the sidebar rail is expanded, `placement=\"left\"` hugs the main\n // canvas and reads awkwardly; prefer above the control. When the rail is\n // collapsed, keep left except on active conversation + open right drawer.\n const settingsTooltipPlacement =\n !sidebarCollapsed || (conversationMatch && isRightPanelShown)\n ? \"top\"\n : \"left\";\n\n const someCloudLoading = Object.values(cloudOrgs).some((c) => c.isLoading);\n\n // Self-heal a malformed `(cloudBackendId, null)` selection.\n //\n // Once a cloud backend's orgs resolve, the dropdown only renders\n // per-org rows for it — the `(backendId, null)` row disappears, so\n // selecting that shape would drift from what the dropdown can render\n // (UI says \"Local\", APIs hit cloud). When we detect the drift, snap\n // the selection onto Cloud's current org first, then fall back to the\n // personal workspace (or, lacking a /me result, the first org). The\n // selection is recorded locally only; the cloud request scope follows\n // from the X-Org-Id header sent by `callCloudProxy`, so the cloud UI's\n // org choice is never mutated as a side effect.\n React.useEffect(() => {\n if (noBackendSelected || active.backend.kind !== \"cloud\" || active.orgId)\n return;\n const { backend } = active;\n const entry = cloudOrgs[backend.id];\n if (!entry || entry.orgs.length === 0) return;\n\n const currentOrg = entry.currentOrgId\n ? entry.orgs.find((o) => o.id === entry.currentOrgId)\n : undefined;\n const userId = currentUserIds[backend.id]?.userId ?? null;\n const personal = userId\n ? entry.orgs.find((o) => o.id === userId)\n : undefined;\n const target = currentOrg ?? personal ?? entry.orgs[0];\n if (target) {\n setActive(backend.id, target.id);\n }\n }, [active, cloudOrgs, currentUserIds, setActive, noBackendSelected]);\n\n const openAddBackendModal = React.useCallback(() => {\n if (onOpenAddBackend) {\n onOpenAddBackend();\n onSelectOption?.();\n return;\n }\n setAddBackendModalOpen(true);\n }, [onOpenAddBackend, onSelectOption]);\n\n const openManageBackendsModal = React.useCallback(() => {\n if (onOpenManageBackends) {\n onOpenManageBackends();\n onSelectOption?.();\n return;\n }\n setManageBackendsModalOpen(true);\n }, [onOpenManageBackends, onSelectOption]);\n\n const preventDropdownMenuClose = React.useCallback(\n (event: React.SyntheticEvent<HTMLButtonElement>) => {\n event.preventDefault();\n event.stopPropagation();\n },\n [],\n );\n\n const handleAddBackendClick = React.useCallback(\n (event: React.MouseEvent<HTMLButtonElement>) => {\n preventDropdownMenuClose(event);\n openAddBackendModal();\n },\n [openAddBackendModal, preventDropdownMenuClose],\n );\n\n const handleAddBackendTouchEnd = React.useCallback(\n (event: React.TouchEvent<HTMLButtonElement>) => {\n preventDropdownMenuClose(event);\n openAddBackendModal();\n },\n [openAddBackendModal, preventDropdownMenuClose],\n );\n\n const handleManageBackendsClick = React.useCallback(\n (event: React.MouseEvent<HTMLButtonElement>) => {\n preventDropdownMenuClose(event);\n openManageBackendsModal();\n },\n [openManageBackendsModal, preventDropdownMenuClose],\n );\n\n const handleManageBackendsTouchEnd = React.useCallback(\n (event: React.TouchEvent<HTMLButtonElement>) => {\n preventDropdownMenuClose(event);\n openManageBackendsModal();\n },\n [openManageBackendsModal, preventDropdownMenuClose],\n );\n\n const addBackendFooter = (\n <div className={dropdownMenuListClassName}>\n <button\n type=\"button\"\n data-testid=\"add-backend-menu-item\"\n onMouseDown={preventDropdownMenuClose}\n onTouchStart={preventDropdownMenuClose}\n onTouchEnd={handleAddBackendTouchEnd}\n onClick={handleAddBackendClick}\n className={cn(\n dropdownFooterActionClassName,\n \"cursor-pointer rounded-md\",\n )}\n >\n <span className={dropdownMenuRowIconWrapperClassName} aria-hidden>\n <Plus width={16} height={16} />\n </span>\n {t(I18nKey.BACKEND$ADD)}\n </button>\n <button\n type=\"button\"\n data-testid=\"manage-backends-menu-item\"\n onMouseDown={preventDropdownMenuClose}\n onTouchStart={preventDropdownMenuClose}\n onTouchEnd={handleManageBackendsTouchEnd}\n onClick={handleManageBackendsClick}\n className={cn(\n dropdownFooterActionClassName,\n \"cursor-pointer rounded-md\",\n )}\n >\n <span className={dropdownMenuRowIconWrapperClassName} aria-hidden>\n <Settings width={16} height={16} />\n </span>\n {t(I18nKey.BACKEND$MANAGE)}\n </button>\n </div>\n );\n\n const handleSelectBackend = React.useCallback(\n async (value: string) => {\n if (value === activeValue) return;\n\n const { backendId, orgId } = parseOptionValue(value);\n const target = backends.find((b) => b.id === backendId);\n if (!target) return;\n\n triggerEnvironmentSwitch(\n options.find((option) => option.value === value)?.label ?? target.name,\n );\n await new Promise<void>((resolve) => {\n setTimeout(resolve, ENVIRONMENT_SWITCH_SETACTIVE_DELAY_MS);\n });\n\n // @spec BM-002 — Switching backends keeps the user on the same page\n if (conversationMatch) navigate(\"/conversations\");\n else if (automationDetailMatch) navigate(\"/automations\");\n\n setActive(target.id, orgId);\n onSelectOption?.();\n },\n [\n activeValue,\n backends,\n conversationMatch,\n automationDetailMatch,\n navigate,\n options,\n setActive,\n t,\n onSelectOption,\n ],\n );\n\n return (\n <>\n <div className=\"flex items-center gap-2 w-full\">\n <div className=\"flex-1 min-w-0\">\n <Dropdown\n testId=\"backend-selector\"\n key={`${activeValue}-${activeOption?.label ?? \"\"}`}\n defaultValue={\n activeOption ?? {\n value: activeValue,\n label: noBackendSelected ? noBackendLabel : active.backend.name,\n prefix: noBackendSelected\n ? buildNoBackendPrefix()\n : buildStatusPrefix(healthByBackendId[active.backend.id]),\n }\n }\n footer={addBackendFooter}\n openUpward={openUpward}\n hideTrigger={hideTrigger}\n defaultOpen={defaultOpen}\n openOnHover={!hideTrigger}\n onChange={(item) => {\n if (!item) return;\n void handleSelectBackend(item.value);\n }}\n placeholder={\n noBackendSelected ? noBackendLabel : active.backend.name\n }\n loading={someCloudLoading}\n options={options}\n className=\"h-10 px-2 py-0 bg-transparent border-transparent hover:bg-[var(--oh-surface-raised)] focus-within:bg-[var(--oh-surface-raised)] focus-within:border-transparent focus-within:ring-0\"\n />\n </div>\n {!hideTrigger ? (\n <StyledTooltip\n content={settingsLabel}\n placement={settingsTooltipPlacement}\n offset={10}\n >\n <NavigationLink\n to=\"/settings\"\n data-testid=\"backend-selector-settings-link\"\n data-active={isSettingsActive}\n aria-label={settingsLabel}\n className={\n isSettingsActive\n ? cn(\n \"inline-flex items-center justify-center shrink-0 w-9 h-9 rounded-md bg-tertiary text-white font-normal cursor-pointer\",\n formControlTransitionClassName,\n )\n : cn(\n \"inline-flex items-center justify-center shrink-0 w-9 h-9 rounded-md text-[var(--oh-muted)] hover:text-white hover:bg-[var(--oh-surface-raised)] cursor-pointer\",\n formControlTransitionClassName,\n )\n }\n >\n <Settings width={16} height={16} />\n </NavigationLink>\n </StyledTooltip>\n ) : null}\n </div>\n {addBackendModalOpen ? (\n <AddBackendModal onClose={() => setAddBackendModalOpen(false)} />\n ) : null}\n {manageBackendsModalOpen ? (\n <ManageBackendsModal\n onClose={() => setManageBackendsModalOpen(false)}\n />\n ) : null}\n </>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require(`../../../_virtual/_rolldown/runtime.cjs`),t=require(`../../../node_modules/react-i18next/dist/es/useTranslation.cjs`),n=require(`../../../i18n/declaration.cjs`),r=require(`../../../utils/utils.cjs`),i=require(`../../shared/modals/modal-backdrop.cjs`),a=require(`../../shared/modals/modal-body.cjs`),o=require(`../settings/brand-button.cjs`),s=require(`../../../hooks/use-device-flow.cjs`);let c=require(`react`);c=e.__toESM(c,1);let l=require(`react/jsx-runtime`);function u(e){try{return new URL(e).protocol===`https:`}catch{return!1}}function d({host:e,onSuccess:d,testIdRoot:p,isDisabled:m=!1,idleButtonLabel:h,idleButtonContent:g,className:_,buttonClassName:v,buttonVariant:y=`primary`,statusDisplay:b=`inline
|
|
1
|
+
const e=require(`../../../_virtual/_rolldown/runtime.cjs`),t=require(`../../../node_modules/react-i18next/dist/es/useTranslation.cjs`),n=require(`../../../i18n/declaration.cjs`),r=require(`../../../utils/utils.cjs`),i=require(`../../shared/modals/modal-backdrop.cjs`),a=require(`../../shared/modals/modal-body.cjs`),o=require(`../settings/brand-button.cjs`),s=require(`../../../hooks/use-device-flow.cjs`);let c=require(`react`);c=e.__toESM(c,1);let l=require(`react/jsx-runtime`);function u(e){try{return new URL(e).protocol===`https:`}catch{return!1}}function d({host:e,onSuccess:d,testIdRoot:p,isDisabled:m=!1,idleButtonLabel:h,idleButtonContent:g,className:_,buttonClassName:v,buttonVariant:y=`primary`,statusDisplay:b=`inline`,analyticsSource:x}){let{t:S}=t.useTranslation(`openhands`),C=s.useDeviceFlow(),w=c.default.useRef(null);c.default.useEffect(()=>()=>{w.current?.close()},[]),c.default.useEffect(()=>{if(C.status===`awaiting_authorization`&&C.verificationUrl&&w.current&&!w.current.closed){if(!u(C.verificationUrl)){console.error(`Invalid verification URL protocol`);return}try{w.current.location.href=C.verificationUrl}catch{w.current=window.open(C.verificationUrl,`_blank`,`noopener,noreferrer`)}}},[C.status,C.verificationUrl]),c.default.useEffect(()=>{if(C.status===`success`&&C.apiKey)try{d(C.apiKey)}finally{C.reset(),w.current?.close()}},[C.status,C.apiKey,C.reset,d]);let T=()=>{let t=e.trim().replace(/\/+$/,``),n=/^https?:\/\//i.test(t)?t:`https://${t}`;try{let e=new URL(n);if(e.username||e.password)throw Error(`Invalid URL format`)}catch{return}w.current=window.open(`about:blank`,`_blank`),w.current||console.warn(`Popup blocked - user will need to use manual link`),C.start(n,x)},E=()=>{C.cancel(),w.current?.close()},D=(0,l.jsx)(f,{status:C.status,error:C.error,verificationUrl:C.verificationUrl,testIdRoot:p,onCancel:E,onRetry:T}),O=b===`modal`&&C.status!==`idle`,k=h??S(n.I18nKey.BACKEND$LOGIN_WITH_OPENHANDS);return(0,l.jsxs)(`div`,{"data-testid":`${p}-device-flow`,className:r.cn(`flex flex-col gap-3`,_),children:[C.status===`idle`&&y===`unstyled`&&(0,l.jsx)(`button`,{type:`button`,onClick:T,"data-testid":`${p}-login-button`,className:v,disabled:m,"aria-label":g?k:void 0,children:g??k}),C.status===`idle`&&y!==`unstyled`&&(0,l.jsx)(o.BrandButton,{type:`button`,variant:y,onClick:T,testId:`${p}-login-button`,className:r.cn(`w-full`,v),isDisabled:m,ariaLabel:g?k:void 0,children:g??k}),b===`inline`?D:null,O?(0,l.jsx)(i.ModalBackdrop,{onClose:E,"aria-label":S(n.I18nKey.BACKEND$LOGIN_WITH_OPENHANDS),closeOnBackdropClick:!1,children:(0,l.jsx)(a.ModalBody,{testID:`${p}-auth-modal`,width:`sm`,className:`items-stretch border border-[var(--oh-border)]`,children:D})}):null]})}function f({status:e,error:r,verificationUrl:i,testIdRoot:a,onCancel:s,onRetry:c}){let{t:d}=t.useTranslation(`openhands`);if(e===`idle`||e===`success`)return null;if(e===`starting`)return(0,l.jsxs)(`div`,{className:`flex items-center gap-2 rounded-lg bg-base-tertiary p-3`,"data-testid":`${a}-auth-starting`,role:`status`,"aria-live":`polite`,children:[(0,l.jsx)(p,{}),(0,l.jsx)(`span`,{className:`text-sm text-[var(--oh-text-tertiary)]`,children:d(n.I18nKey.BACKEND$AUTH_STARTING)})]});if(e===`awaiting_authorization`){let e=i&&u(i)?i:null;return(0,l.jsxs)(`div`,{className:`flex flex-col gap-4 rounded-lg bg-base-tertiary p-4`,"data-testid":`${a}-auth-awaiting`,role:`status`,"aria-live":`polite`,children:[(0,l.jsxs)(`div`,{className:`flex items-center gap-2`,children:[(0,l.jsx)(p,{}),(0,l.jsx)(`span`,{className:`text-sm font-medium text-white`,children:d(n.I18nKey.BACKEND$AUTH_AWAITING)})]}),(0,l.jsx)(`p`,{className:`text-sm leading-5 text-[var(--oh-text-tertiary)]`,children:d(n.I18nKey.BACKEND$AUTH_BROWSER_OPENED)}),e?(0,l.jsxs)(`div`,{className:`flex flex-col gap-1 rounded-md border border-[var(--oh-border)] bg-[var(--oh-surface)] p-3 text-xs text-[var(--oh-muted)]`,children:[(0,l.jsx)(`p`,{children:d(n.I18nKey.BACKEND$AUTH_OPEN_MANUALLY)}),(0,l.jsx)(`a`,{href:e,target:`_blank`,rel:`noopener noreferrer`,className:`max-h-16 overflow-auto break-all text-blue-400 hover:underline`,children:e})]}):null,(0,l.jsx)(o.BrandButton,{type:`button`,variant:`secondary`,onClick:s,testId:`${a}-auth-cancel`,className:`w-full`,children:d(n.I18nKey.BACKEND$AUTH_CANCEL)})]})}return(0,l.jsxs)(`div`,{className:`flex flex-col gap-3 rounded-lg border border-red-700 bg-red-900/20 p-4`,"data-testid":`${a}-auth-error`,role:`alert`,children:[(0,l.jsx)(`p`,{className:`text-sm text-red-400`,children:r}),(0,l.jsx)(o.BrandButton,{type:`button`,variant:`secondary`,onClick:c,testId:`${a}-auth-retry`,className:`w-full`,children:d(n.I18nKey.BACKEND$AUTH_RETRY)})]})}function p(){return(0,l.jsx)(`svg`,{className:`animate-spin h-4 w-4 text-white`,xmlns:`http://www.w3.org/2000/svg`,fill:`none`,viewBox:`0 0 24 24`,"aria-hidden":`true`,children:(0,l.jsx)(`path`,{className:`opacity-75`,fill:`currentColor`,d:`M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z`})})}exports.DeviceFlowAuth=d;
|
|
2
2
|
//# sourceMappingURL=device-flow-auth.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"device-flow-auth.cjs","names":[],"sources":["../../../../src/components/features/backends/device-flow-auth.tsx"],"sourcesContent":["import React from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { BrandButton } from \"#/components/features/settings/brand-button\";\nimport { ModalBackdrop } from \"#/components/shared/modals/modal-backdrop\";\nimport { ModalBody } from \"#/components/shared/modals/modal-body\";\nimport { useDeviceFlow, type DeviceFlowStatus } from \"#/hooks/use-device-flow\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { cn } from \"#/utils/utils\";\n\ntype DeviceFlowButtonVariant =\n | \"primary\"\n | \"secondary\"\n | \"tertiary\"\n | \"unstyled\";\ntype DeviceFlowStatusDisplay = \"inline\" | \"modal\";\n\ninterface DeviceFlowAuthProps {\n /** The host URL for the cloud backend */\n host: string;\n /** Callback when authentication succeeds with the API key */\n onSuccess: (apiKey: string) => void;\n /** Test ID prefix for the component */\n testIdRoot: string;\n /** Whether the login button should be disabled (e.g., when no host is entered) */\n isDisabled?: boolean;\n /** Override for the idle button label and icon-only accessible name. */\n idleButtonLabel?: string;\n /** Optional visible content for the idle button. Defaults to the idle label. */\n idleButtonContent?: React.ReactNode;\n /** Optional classes for the root wrapper. */\n className?: string;\n /** Optional classes for the idle button. */\n buttonClassName?: string;\n /** Visual variant for the idle button. */\n buttonVariant?: DeviceFlowButtonVariant;\n /** Whether in-progress auth content should render inline or in a modal. */\n statusDisplay?: DeviceFlowStatusDisplay;\n}\n\n/**\n * Device Flow authentication UI component.\n *\n * Shows a \"Login with OpenHands Cloud\" button that initiates OAuth 2.0 Device Flow\n * authentication. Displays status during the auth process and auto-opens\n * the browser for user authorization.\n */\n/**\n * Validate that a URL is safe to open in a popup.\n * Prevents XSS via javascript: URLs or other malicious schemes.\n */\nfunction isValidVerificationUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n return parsed.protocol === \"https:\";\n } catch {\n return false;\n }\n}\n\nexport function DeviceFlowAuth({\n host,\n onSuccess,\n testIdRoot,\n isDisabled = false,\n idleButtonLabel,\n idleButtonContent,\n className,\n buttonClassName,\n buttonVariant = \"primary\",\n statusDisplay = \"inline\",\n}: DeviceFlowAuthProps) {\n const { t } = useTranslation(\"openhands\");\n const deviceFlow = useDeviceFlow();\n const popupRef = React.useRef<Window | null>(null);\n\n // Close popup on unmount or when auth completes/errors\n React.useEffect(() => {\n return () => {\n popupRef.current?.close();\n };\n }, []);\n\n // Update popup URL when verification URL becomes available\n React.useEffect(() => {\n if (\n deviceFlow.status === \"awaiting_authorization\" &&\n deviceFlow.verificationUrl &&\n popupRef.current &&\n !popupRef.current.closed\n ) {\n // Validate URL before assigning to prevent XSS\n if (!isValidVerificationUrl(deviceFlow.verificationUrl)) {\n console.error(\"Invalid verification URL protocol\");\n return;\n }\n try {\n popupRef.current.location.href = deviceFlow.verificationUrl;\n } catch {\n // Cross-origin error - popup was navigated away\n // Open a new one as fallback\n popupRef.current = window.open(\n deviceFlow.verificationUrl,\n \"_blank\",\n \"noopener,noreferrer\",\n );\n }\n }\n }, [deviceFlow.status, deviceFlow.verificationUrl]);\n\n // Call onSuccess when authentication completes\n React.useEffect(() => {\n if (deviceFlow.status === \"success\" && deviceFlow.apiKey) {\n try {\n onSuccess(deviceFlow.apiKey);\n } finally {\n deviceFlow.reset();\n popupRef.current?.close();\n }\n }\n }, [deviceFlow.status, deviceFlow.apiKey, deviceFlow.reset, onSuccess]);\n\n const handleStartAuth = () => {\n // Normalize and validate the host URL\n const normalizedHost = host.trim().replace(/\\/+$/, \"\");\n const fullHost = /^https?:\\/\\//i.test(normalizedHost)\n ? normalizedHost\n : `https://${normalizedHost}`;\n\n // Validate URL before proceeding\n try {\n const url = new URL(fullHost);\n // Check for URL manipulation attacks\n if (url.username || url.password) {\n throw new Error(\"Invalid URL format\");\n }\n } catch {\n return; // Invalid URL, don't proceed\n }\n\n // Open popup immediately on user click to avoid popup blocker\n // Start with about:blank and update URL once we have verification URL\n // Note: We intentionally don't use \"noopener\" here because we need to\n // maintain a reference to update the popup's location when the\n // verification URL becomes available\n popupRef.current = window.open(\"about:blank\", \"_blank\");\n\n if (!popupRef.current) {\n // Popup was blocked - flow will still work, user can click manual link\n console.warn(\"Popup blocked - user will need to use manual link\");\n }\n\n deviceFlow.start(fullHost);\n };\n\n const handleCancel = () => {\n deviceFlow.cancel();\n popupRef.current?.close();\n };\n\n const statusContent = (\n <DeviceFlowStatusContent\n status={deviceFlow.status}\n error={deviceFlow.error}\n verificationUrl={deviceFlow.verificationUrl}\n testIdRoot={testIdRoot}\n onCancel={handleCancel}\n onRetry={handleStartAuth}\n />\n );\n const showStatusModal =\n statusDisplay === \"modal\" && deviceFlow.status !== \"idle\";\n const idleLabel = idleButtonLabel ?? t(I18nKey.BACKEND$LOGIN_WITH_OPENHANDS);\n\n return (\n <div\n data-testid={`${testIdRoot}-device-flow`}\n className={cn(\"flex flex-col gap-3\", className)}\n >\n {deviceFlow.status === \"idle\" && buttonVariant === \"unstyled\" && (\n <button\n type=\"button\"\n onClick={handleStartAuth}\n data-testid={`${testIdRoot}-login-button`}\n className={buttonClassName}\n disabled={isDisabled}\n aria-label={idleButtonContent ? idleLabel : undefined}\n >\n {idleButtonContent ?? idleLabel}\n </button>\n )}\n\n {deviceFlow.status === \"idle\" && buttonVariant !== \"unstyled\" && (\n <BrandButton\n type=\"button\"\n variant={buttonVariant}\n onClick={handleStartAuth}\n testId={`${testIdRoot}-login-button`}\n className={cn(\"w-full\", buttonClassName)}\n isDisabled={isDisabled}\n ariaLabel={idleButtonContent ? idleLabel : undefined}\n >\n {idleButtonContent ?? idleLabel}\n </BrandButton>\n )}\n\n {statusDisplay === \"inline\" ? statusContent : null}\n\n {showStatusModal ? (\n <ModalBackdrop\n onClose={handleCancel}\n aria-label={t(I18nKey.BACKEND$LOGIN_WITH_OPENHANDS)}\n closeOnBackdropClick={false}\n >\n <ModalBody\n testID={`${testIdRoot}-auth-modal`}\n width=\"sm\"\n className=\"items-stretch border border-[var(--oh-border)]\"\n >\n {statusContent}\n </ModalBody>\n </ModalBackdrop>\n ) : null}\n </div>\n );\n}\n\ninterface DeviceFlowStatusContentProps {\n status: DeviceFlowStatus;\n error: string | null;\n verificationUrl: string | null;\n testIdRoot: string;\n onCancel: () => void;\n onRetry: () => void;\n}\n\nfunction DeviceFlowStatusContent({\n status,\n error,\n verificationUrl,\n testIdRoot,\n onCancel,\n onRetry,\n}: DeviceFlowStatusContentProps) {\n const { t } = useTranslation(\"openhands\");\n\n if (status === \"idle\" || status === \"success\") return null;\n\n if (status === \"starting\") {\n return (\n <div\n className=\"flex items-center gap-2 rounded-lg bg-base-tertiary p-3\"\n data-testid={`${testIdRoot}-auth-starting`}\n role=\"status\"\n aria-live=\"polite\"\n >\n <LoadingSpinner />\n <span className=\"text-sm text-[var(--oh-text-tertiary)]\">\n {t(I18nKey.BACKEND$AUTH_STARTING)}\n </span>\n </div>\n );\n }\n\n if (status === \"awaiting_authorization\") {\n const validVerificationUrl =\n verificationUrl && isValidVerificationUrl(verificationUrl)\n ? verificationUrl\n : null;\n\n return (\n <div\n className=\"flex flex-col gap-4 rounded-lg bg-base-tertiary p-4\"\n data-testid={`${testIdRoot}-auth-awaiting`}\n role=\"status\"\n aria-live=\"polite\"\n >\n <div className=\"flex items-center gap-2\">\n <LoadingSpinner />\n <span className=\"text-sm font-medium text-white\">\n {t(I18nKey.BACKEND$AUTH_AWAITING)}\n </span>\n </div>\n <p className=\"text-sm leading-5 text-[var(--oh-text-tertiary)]\">\n {t(I18nKey.BACKEND$AUTH_BROWSER_OPENED)}\n </p>\n {validVerificationUrl ? (\n <div className=\"flex flex-col gap-1 rounded-md border border-[var(--oh-border)] bg-[var(--oh-surface)] p-3 text-xs text-[var(--oh-muted)]\">\n <p>{t(I18nKey.BACKEND$AUTH_OPEN_MANUALLY)}</p>\n <a\n href={validVerificationUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"max-h-16 overflow-auto break-all text-blue-400 hover:underline\"\n >\n {validVerificationUrl}\n </a>\n </div>\n ) : null}\n <BrandButton\n type=\"button\"\n variant=\"secondary\"\n onClick={onCancel}\n testId={`${testIdRoot}-auth-cancel`}\n className=\"w-full\"\n >\n {t(I18nKey.BACKEND$AUTH_CANCEL)}\n </BrandButton>\n </div>\n );\n }\n\n return (\n <div\n className=\"flex flex-col gap-3 rounded-lg border border-red-700 bg-red-900/20 p-4\"\n data-testid={`${testIdRoot}-auth-error`}\n role=\"alert\"\n >\n <p className=\"text-sm text-red-400\">{error}</p>\n <BrandButton\n type=\"button\"\n variant=\"secondary\"\n onClick={onRetry}\n testId={`${testIdRoot}-auth-retry`}\n className=\"w-full\"\n >\n {t(I18nKey.BACKEND$AUTH_RETRY)}\n </BrandButton>\n </div>\n );\n}\n\nfunction LoadingSpinner() {\n return (\n <svg\n className=\"animate-spin h-4 w-4 text-white\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"device-flow-auth.cjs","names":[],"sources":["../../../../src/components/features/backends/device-flow-auth.tsx"],"sourcesContent":["import React from \"react\";\nimport { useTranslation } from \"react-i18next\";\nimport { BrandButton } from \"#/components/features/settings/brand-button\";\nimport { ModalBackdrop } from \"#/components/shared/modals/modal-backdrop\";\nimport { ModalBody } from \"#/components/shared/modals/modal-body\";\nimport { useDeviceFlow, type DeviceFlowStatus } from \"#/hooks/use-device-flow\";\nimport type { CloudConnectionSource } from \"#/services/cloud-funnel-analytics\";\nimport { I18nKey } from \"#/i18n/declaration\";\nimport { cn } from \"#/utils/utils\";\n\ntype DeviceFlowButtonVariant =\n | \"primary\"\n | \"secondary\"\n | \"tertiary\"\n | \"unstyled\";\ntype DeviceFlowStatusDisplay = \"inline\" | \"modal\";\n\ninterface DeviceFlowAuthProps {\n /** The host URL for the cloud backend */\n host: string;\n /** Callback when authentication succeeds with the API key */\n onSuccess: (apiKey: string) => void;\n /** Test ID prefix for the component */\n testIdRoot: string;\n /** Whether the login button should be disabled (e.g., when no host is entered) */\n isDisabled?: boolean;\n /** Override for the idle button label and icon-only accessible name. */\n idleButtonLabel?: string;\n /** Optional visible content for the idle button. Defaults to the idle label. */\n idleButtonContent?: React.ReactNode;\n /** Optional classes for the root wrapper. */\n className?: string;\n /** Optional classes for the idle button. */\n buttonClassName?: string;\n /** Visual variant for the idle button. */\n buttonVariant?: DeviceFlowButtonVariant;\n /** Whether in-progress auth content should render inline or in a modal. */\n statusDisplay?: DeviceFlowStatusDisplay;\n /** Product surface that initiated this authorization attempt. */\n analyticsSource?: CloudConnectionSource;\n}\n\n/**\n * Device Flow authentication UI component.\n *\n * Shows a \"Login with OpenHands Cloud\" button that initiates OAuth 2.0 Device Flow\n * authentication. Displays status during the auth process and auto-opens\n * the browser for user authorization.\n */\n/**\n * Validate that a URL is safe to open in a popup.\n * Prevents XSS via javascript: URLs or other malicious schemes.\n */\nfunction isValidVerificationUrl(url: string): boolean {\n try {\n const parsed = new URL(url);\n return parsed.protocol === \"https:\";\n } catch {\n return false;\n }\n}\n\nexport function DeviceFlowAuth({\n host,\n onSuccess,\n testIdRoot,\n isDisabled = false,\n idleButtonLabel,\n idleButtonContent,\n className,\n buttonClassName,\n buttonVariant = \"primary\",\n statusDisplay = \"inline\",\n analyticsSource,\n}: DeviceFlowAuthProps) {\n const { t } = useTranslation(\"openhands\");\n const deviceFlow = useDeviceFlow();\n const popupRef = React.useRef<Window | null>(null);\n\n // Close popup on unmount or when auth completes/errors\n React.useEffect(() => {\n return () => {\n popupRef.current?.close();\n };\n }, []);\n\n // Update popup URL when verification URL becomes available\n React.useEffect(() => {\n if (\n deviceFlow.status === \"awaiting_authorization\" &&\n deviceFlow.verificationUrl &&\n popupRef.current &&\n !popupRef.current.closed\n ) {\n // Validate URL before assigning to prevent XSS\n if (!isValidVerificationUrl(deviceFlow.verificationUrl)) {\n console.error(\"Invalid verification URL protocol\");\n return;\n }\n try {\n popupRef.current.location.href = deviceFlow.verificationUrl;\n } catch {\n // Cross-origin error - popup was navigated away\n // Open a new one as fallback\n popupRef.current = window.open(\n deviceFlow.verificationUrl,\n \"_blank\",\n \"noopener,noreferrer\",\n );\n }\n }\n }, [deviceFlow.status, deviceFlow.verificationUrl]);\n\n // Call onSuccess when authentication completes\n React.useEffect(() => {\n if (deviceFlow.status === \"success\" && deviceFlow.apiKey) {\n try {\n onSuccess(deviceFlow.apiKey);\n } finally {\n deviceFlow.reset();\n popupRef.current?.close();\n }\n }\n }, [deviceFlow.status, deviceFlow.apiKey, deviceFlow.reset, onSuccess]);\n\n const handleStartAuth = () => {\n // Normalize and validate the host URL\n const normalizedHost = host.trim().replace(/\\/+$/, \"\");\n const fullHost = /^https?:\\/\\//i.test(normalizedHost)\n ? normalizedHost\n : `https://${normalizedHost}`;\n\n // Validate URL before proceeding\n try {\n const url = new URL(fullHost);\n // Check for URL manipulation attacks\n if (url.username || url.password) {\n throw new Error(\"Invalid URL format\");\n }\n } catch {\n return; // Invalid URL, don't proceed\n }\n\n // Open popup immediately on user click to avoid popup blocker\n // Start with about:blank and update URL once we have verification URL\n // Note: We intentionally don't use \"noopener\" here because we need to\n // maintain a reference to update the popup's location when the\n // verification URL becomes available\n popupRef.current = window.open(\"about:blank\", \"_blank\");\n\n if (!popupRef.current) {\n // Popup was blocked - flow will still work, user can click manual link\n console.warn(\"Popup blocked - user will need to use manual link\");\n }\n\n deviceFlow.start(fullHost, analyticsSource);\n };\n\n const handleCancel = () => {\n deviceFlow.cancel();\n popupRef.current?.close();\n };\n\n const statusContent = (\n <DeviceFlowStatusContent\n status={deviceFlow.status}\n error={deviceFlow.error}\n verificationUrl={deviceFlow.verificationUrl}\n testIdRoot={testIdRoot}\n onCancel={handleCancel}\n onRetry={handleStartAuth}\n />\n );\n const showStatusModal =\n statusDisplay === \"modal\" && deviceFlow.status !== \"idle\";\n const idleLabel = idleButtonLabel ?? t(I18nKey.BACKEND$LOGIN_WITH_OPENHANDS);\n\n return (\n <div\n data-testid={`${testIdRoot}-device-flow`}\n className={cn(\"flex flex-col gap-3\", className)}\n >\n {deviceFlow.status === \"idle\" && buttonVariant === \"unstyled\" && (\n <button\n type=\"button\"\n onClick={handleStartAuth}\n data-testid={`${testIdRoot}-login-button`}\n className={buttonClassName}\n disabled={isDisabled}\n aria-label={idleButtonContent ? idleLabel : undefined}\n >\n {idleButtonContent ?? idleLabel}\n </button>\n )}\n\n {deviceFlow.status === \"idle\" && buttonVariant !== \"unstyled\" && (\n <BrandButton\n type=\"button\"\n variant={buttonVariant}\n onClick={handleStartAuth}\n testId={`${testIdRoot}-login-button`}\n className={cn(\"w-full\", buttonClassName)}\n isDisabled={isDisabled}\n ariaLabel={idleButtonContent ? idleLabel : undefined}\n >\n {idleButtonContent ?? idleLabel}\n </BrandButton>\n )}\n\n {statusDisplay === \"inline\" ? statusContent : null}\n\n {showStatusModal ? (\n <ModalBackdrop\n onClose={handleCancel}\n aria-label={t(I18nKey.BACKEND$LOGIN_WITH_OPENHANDS)}\n closeOnBackdropClick={false}\n >\n <ModalBody\n testID={`${testIdRoot}-auth-modal`}\n width=\"sm\"\n className=\"items-stretch border border-[var(--oh-border)]\"\n >\n {statusContent}\n </ModalBody>\n </ModalBackdrop>\n ) : null}\n </div>\n );\n}\n\ninterface DeviceFlowStatusContentProps {\n status: DeviceFlowStatus;\n error: string | null;\n verificationUrl: string | null;\n testIdRoot: string;\n onCancel: () => void;\n onRetry: () => void;\n}\n\nfunction DeviceFlowStatusContent({\n status,\n error,\n verificationUrl,\n testIdRoot,\n onCancel,\n onRetry,\n}: DeviceFlowStatusContentProps) {\n const { t } = useTranslation(\"openhands\");\n\n if (status === \"idle\" || status === \"success\") return null;\n\n if (status === \"starting\") {\n return (\n <div\n className=\"flex items-center gap-2 rounded-lg bg-base-tertiary p-3\"\n data-testid={`${testIdRoot}-auth-starting`}\n role=\"status\"\n aria-live=\"polite\"\n >\n <LoadingSpinner />\n <span className=\"text-sm text-[var(--oh-text-tertiary)]\">\n {t(I18nKey.BACKEND$AUTH_STARTING)}\n </span>\n </div>\n );\n }\n\n if (status === \"awaiting_authorization\") {\n const validVerificationUrl =\n verificationUrl && isValidVerificationUrl(verificationUrl)\n ? verificationUrl\n : null;\n\n return (\n <div\n className=\"flex flex-col gap-4 rounded-lg bg-base-tertiary p-4\"\n data-testid={`${testIdRoot}-auth-awaiting`}\n role=\"status\"\n aria-live=\"polite\"\n >\n <div className=\"flex items-center gap-2\">\n <LoadingSpinner />\n <span className=\"text-sm font-medium text-white\">\n {t(I18nKey.BACKEND$AUTH_AWAITING)}\n </span>\n </div>\n <p className=\"text-sm leading-5 text-[var(--oh-text-tertiary)]\">\n {t(I18nKey.BACKEND$AUTH_BROWSER_OPENED)}\n </p>\n {validVerificationUrl ? (\n <div className=\"flex flex-col gap-1 rounded-md border border-[var(--oh-border)] bg-[var(--oh-surface)] p-3 text-xs text-[var(--oh-muted)]\">\n <p>{t(I18nKey.BACKEND$AUTH_OPEN_MANUALLY)}</p>\n <a\n href={validVerificationUrl}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"max-h-16 overflow-auto break-all text-blue-400 hover:underline\"\n >\n {validVerificationUrl}\n </a>\n </div>\n ) : null}\n <BrandButton\n type=\"button\"\n variant=\"secondary\"\n onClick={onCancel}\n testId={`${testIdRoot}-auth-cancel`}\n className=\"w-full\"\n >\n {t(I18nKey.BACKEND$AUTH_CANCEL)}\n </BrandButton>\n </div>\n );\n }\n\n return (\n <div\n className=\"flex flex-col gap-3 rounded-lg border border-red-700 bg-red-900/20 p-4\"\n data-testid={`${testIdRoot}-auth-error`}\n role=\"alert\"\n >\n <p className=\"text-sm text-red-400\">{error}</p>\n <BrandButton\n type=\"button\"\n variant=\"secondary\"\n onClick={onRetry}\n testId={`${testIdRoot}-auth-retry`}\n className=\"w-full\"\n >\n {t(I18nKey.BACKEND$AUTH_RETRY)}\n </BrandButton>\n </div>\n );\n}\n\nfunction LoadingSpinner() {\n return (\n <svg\n className=\"animate-spin h-4 w-4 text-white\"\n xmlns=\"http://www.w3.org/2000/svg\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n aria-hidden=\"true\"\n >\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z\"\n />\n </svg>\n );\n}\n"],"mappings":"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"}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import React from "react";
|
|
2
|
+
import type { CloudConnectionSource } from "#/services/cloud-funnel-analytics";
|
|
2
3
|
type DeviceFlowButtonVariant = "primary" | "secondary" | "tertiary" | "unstyled";
|
|
3
4
|
type DeviceFlowStatusDisplay = "inline" | "modal";
|
|
4
5
|
interface DeviceFlowAuthProps {
|
|
@@ -22,6 +23,8 @@ interface DeviceFlowAuthProps {
|
|
|
22
23
|
buttonVariant?: DeviceFlowButtonVariant;
|
|
23
24
|
/** Whether in-progress auth content should render inline or in a modal. */
|
|
24
25
|
statusDisplay?: DeviceFlowStatusDisplay;
|
|
26
|
+
/** Product surface that initiated this authorization attempt. */
|
|
27
|
+
analyticsSource?: CloudConnectionSource;
|
|
25
28
|
}
|
|
26
|
-
export declare function DeviceFlowAuth({ host, onSuccess, testIdRoot, isDisabled, idleButtonLabel, idleButtonContent, className, buttonClassName, buttonVariant, statusDisplay, }: DeviceFlowAuthProps): import("react/jsx-runtime").JSX.Element;
|
|
29
|
+
export declare function DeviceFlowAuth({ host, onSuccess, testIdRoot, isDisabled, idleButtonLabel, idleButtonContent, className, buttonClassName, buttonVariant, statusDisplay, analyticsSource, }: DeviceFlowAuthProps): import("react/jsx-runtime").JSX.Element;
|
|
27
30
|
export {};
|
|
@@ -15,35 +15,35 @@ function u(e) {
|
|
|
15
15
|
return !1;
|
|
16
16
|
}
|
|
17
17
|
}
|
|
18
|
-
function d({ host: d, onSuccess: p, testIdRoot: m, isDisabled: h = !1, idleButtonLabel: g, idleButtonContent: _, className: v, buttonClassName: y, buttonVariant: b = "primary", statusDisplay: x = "inline" }) {
|
|
19
|
-
let { t:
|
|
18
|
+
function d({ host: d, onSuccess: p, testIdRoot: m, isDisabled: h = !1, idleButtonLabel: g, idleButtonContent: _, className: v, buttonClassName: y, buttonVariant: b = "primary", statusDisplay: x = "inline", analyticsSource: S }) {
|
|
19
|
+
let { t: C } = e("openhands"), w = o(), T = s.useRef(null);
|
|
20
20
|
s.useEffect(() => () => {
|
|
21
|
-
|
|
21
|
+
T.current?.close();
|
|
22
22
|
}, []), s.useEffect(() => {
|
|
23
|
-
if (
|
|
24
|
-
if (!u(
|
|
23
|
+
if (w.status === "awaiting_authorization" && w.verificationUrl && T.current && !T.current.closed) {
|
|
24
|
+
if (!u(w.verificationUrl)) {
|
|
25
25
|
console.error("Invalid verification URL protocol");
|
|
26
26
|
return;
|
|
27
27
|
}
|
|
28
28
|
try {
|
|
29
|
-
|
|
29
|
+
T.current.location.href = w.verificationUrl;
|
|
30
30
|
} catch {
|
|
31
|
-
|
|
31
|
+
T.current = window.open(w.verificationUrl, "_blank", "noopener,noreferrer");
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
}, [
|
|
35
|
-
if (
|
|
36
|
-
p(
|
|
34
|
+
}, [w.status, w.verificationUrl]), s.useEffect(() => {
|
|
35
|
+
if (w.status === "success" && w.apiKey) try {
|
|
36
|
+
p(w.apiKey);
|
|
37
37
|
} finally {
|
|
38
|
-
|
|
38
|
+
w.reset(), T.current?.close();
|
|
39
39
|
}
|
|
40
40
|
}, [
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
w.status,
|
|
42
|
+
w.apiKey,
|
|
43
|
+
w.reset,
|
|
44
44
|
p
|
|
45
45
|
]);
|
|
46
|
-
let
|
|
46
|
+
let E = () => {
|
|
47
47
|
let e = d.trim().replace(/\/+$/, ""), t = /^https?:\/\//i.test(e) ? e : `https://${e}`;
|
|
48
48
|
try {
|
|
49
49
|
let e = new URL(t);
|
|
@@ -51,50 +51,50 @@ function d({ host: d, onSuccess: p, testIdRoot: m, isDisabled: h = !1, idleButto
|
|
|
51
51
|
} catch {
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
|
-
|
|
55
|
-
},
|
|
56
|
-
|
|
57
|
-
},
|
|
58
|
-
status:
|
|
59
|
-
error:
|
|
60
|
-
verificationUrl:
|
|
54
|
+
T.current = window.open("about:blank", "_blank"), T.current || console.warn("Popup blocked - user will need to use manual link"), w.start(t, S);
|
|
55
|
+
}, D = () => {
|
|
56
|
+
w.cancel(), T.current?.close();
|
|
57
|
+
}, O = /* @__PURE__ */ c(f, {
|
|
58
|
+
status: w.status,
|
|
59
|
+
error: w.error,
|
|
60
|
+
verificationUrl: w.verificationUrl,
|
|
61
61
|
testIdRoot: m,
|
|
62
|
-
onCancel:
|
|
63
|
-
onRetry:
|
|
64
|
-
}),
|
|
62
|
+
onCancel: D,
|
|
63
|
+
onRetry: E
|
|
64
|
+
}), k = x === "modal" && w.status !== "idle", A = g ?? C(t.BACKEND$LOGIN_WITH_OPENHANDS);
|
|
65
65
|
return /* @__PURE__ */ l("div", {
|
|
66
66
|
"data-testid": `${m}-device-flow`,
|
|
67
67
|
className: n("flex flex-col gap-3", v),
|
|
68
68
|
children: [
|
|
69
|
-
|
|
69
|
+
w.status === "idle" && b === "unstyled" && /* @__PURE__ */ c("button", {
|
|
70
70
|
type: "button",
|
|
71
|
-
onClick:
|
|
71
|
+
onClick: E,
|
|
72
72
|
"data-testid": `${m}-login-button`,
|
|
73
73
|
className: y,
|
|
74
74
|
disabled: h,
|
|
75
|
-
"aria-label": _ ?
|
|
76
|
-
children: _ ??
|
|
75
|
+
"aria-label": _ ? A : void 0,
|
|
76
|
+
children: _ ?? A
|
|
77
77
|
}),
|
|
78
|
-
|
|
78
|
+
w.status === "idle" && b !== "unstyled" && /* @__PURE__ */ c(a, {
|
|
79
79
|
type: "button",
|
|
80
80
|
variant: b,
|
|
81
|
-
onClick:
|
|
81
|
+
onClick: E,
|
|
82
82
|
testId: `${m}-login-button`,
|
|
83
83
|
className: n("w-full", y),
|
|
84
84
|
isDisabled: h,
|
|
85
|
-
ariaLabel: _ ?
|
|
86
|
-
children: _ ??
|
|
85
|
+
ariaLabel: _ ? A : void 0,
|
|
86
|
+
children: _ ?? A
|
|
87
87
|
}),
|
|
88
|
-
x === "inline" ?
|
|
89
|
-
|
|
90
|
-
onClose:
|
|
91
|
-
"aria-label":
|
|
88
|
+
x === "inline" ? O : null,
|
|
89
|
+
k ? /* @__PURE__ */ c(r, {
|
|
90
|
+
onClose: D,
|
|
91
|
+
"aria-label": C(t.BACKEND$LOGIN_WITH_OPENHANDS),
|
|
92
92
|
closeOnBackdropClick: !1,
|
|
93
93
|
children: /* @__PURE__ */ c(i, {
|
|
94
94
|
testID: `${m}-auth-modal`,
|
|
95
95
|
width: "sm",
|
|
96
96
|
className: "items-stretch border border-[var(--oh-border)]",
|
|
97
|
-
children:
|
|
97
|
+
children: O
|
|
98
98
|
})
|
|
99
99
|
}) : null
|
|
100
100
|
]
|