@opengeni/react 4.6.1 → 4.6.2-canary.39854174564001
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/dist/app/app-state.js +5 -5
- package/dist/app/components/Composer.js +14 -14
- package/dist/app/components/artifacts/artifact-library.js +2 -2
- package/dist/app/components/artifacts/chat-interactive-block.js +10 -10
- package/dist/app/components/artifacts/inline-chat-image.js +14 -14
- package/dist/app/components/artifacts/retained-file-preview.js +15 -15
- package/dist/app/components/artifacts/retained-text-preview.js +2 -2
- package/dist/app/components/artifacts/site-conversations.js +4 -3
- package/dist/app/components/artifacts/site-data.js +2 -2
- package/dist/app/components/artifacts/site-publishing.js +11 -11
- package/dist/app/components/artifacts/site-settings.js +12 -12
- package/dist/app/components/billing-class-mark.js +3 -3
- package/dist/app/components/capabilities/capability-detail-sheet.js +2 -2
- package/dist/app/components/capabilities/capability-logo-source.js +7 -7
- package/dist/app/components/capabilities/native-connect-readiness.js +1 -0
- package/dist/app/components/capabilities/native-connect-readiness.js.map +1 -1
- package/dist/app/components/capabilities/native-connect-setup.js +6 -6
- package/dist/app/components/capabilities/perform-capability-action.js +2 -1
- package/dist/app/components/capabilities/prepared-mcp-setup-card.js +8 -8
- package/dist/app/components/capabilities/session-capability-card.js +28 -27
- package/dist/app/components/capabilities/session-capability-card.js.map +1 -1
- package/dist/app/components/capabilities/session-chrome-card.js +14 -14
- package/dist/app/components/capabilities/session-custom-mcp-card.js +9 -9
- package/dist/app/components/capabilities/session-github-card.js +9 -9
- package/dist/app/components/capabilities/session-machine-card.js +13 -13
- package/dist/app/components/command-menu/chat-status.js +6 -5
- package/dist/app/components/command-menu/chat-switcher.d.ts +2 -0
- package/dist/app/components/command-menu/chat-switcher.js +26 -9
- package/dist/app/components/command-menu/chat-switcher.js.map +1 -1
- package/dist/app/components/command-menu/command-menu.js +9 -8
- package/dist/app/components/command-menu/command-menu.js.map +1 -1
- package/dist/app/components/command-menu/shortcut-sheet.js +4 -4
- package/dist/app/components/composer-mobile-plus-panel.js +5 -5
- package/dist/app/components/composer-mobile-plus.js +1 -1
- package/dist/app/components/credit-required-gate.js +1 -1
- package/dist/app/components/credit-required-prompt.js +3 -3
- package/dist/app/components/desktop/app-shots.js +5 -5
- package/dist/app/components/desktop/this-computer.js +4 -4
- package/dist/app/components/feedback.js +3 -3
- package/dist/app/components/github-app-connect-launcher.js +1 -1
- package/dist/app/components/machines/connect-machine-dialog.js +3 -3
- package/dist/app/components/markdown.js +4 -4
- package/dist/app/components/models/models-ui.js +2 -2
- package/dist/app/components/pickers.js +5 -5
- package/dist/app/components/rail/rail-context.js +1 -1
- package/dist/app/components/rail/rail-sections.js +15 -14
- package/dist/app/components/rail/session-header.d.ts +6 -5
- package/dist/app/components/rail/session-header.js +11 -10
- package/dist/app/components/rail/session-list.js +14 -13
- package/dist/app/components/rail/session-row-hover-details.js +7 -6
- package/dist/app/components/rail/session-shell.js +30 -32
- package/dist/app/components/rail/site-session-group-heading.js +2 -1
- package/dist/app/components/session/agent-configuration-panel.js +3 -3
- package/dist/app/components/session/banners.js +6 -6
- package/dist/app/components/session/editable-artifacts-workspace.js +12 -12
- package/dist/app/components/session/failed-session-banner.js +4 -4
- package/dist/app/components/session/human-input.js +24 -24
- package/dist/app/components/session/inspector.js +30 -30
- package/dist/app/components/session/message-actions.js +3 -3
- package/dist/app/components/session/message-resource-attachments.js +5 -5
- package/dist/app/components/session/model-recovery-notice.js +3 -3
- package/dist/app/components/session/new-session-settings-menu.js +4 -4
- package/dist/app/components/session/sandbox-switcher.js +7 -7
- package/dist/app/components/session/sandbox-workspace.js +33 -33
- package/dist/app/components/session/search-markdown-highlight.js +5 -5
- package/dist/app/components/session/search-results-view.js +6 -6
- package/dist/app/components/session/session-admission-notice.js +4 -4
- package/dist/app/components/session/session-machine-access-notice.js +4 -4
- package/dist/app/components/session/session-search-dialog.js +6 -6
- package/dist/app/components/session/session-usage-limit-section.js +3 -3
- package/dist/app/components/session/session-wait-status.js +2 -1
- package/dist/app/components/session/session-wait-status.js.map +1 -1
- package/dist/app/components/session/subagents.js +5 -4
- package/dist/app/components/session/subscription-account-indicator.d.ts +35 -0
- package/dist/app/components/session/{codex-account-indicator.js → subscription-account-indicator.js} +23 -10
- package/dist/app/components/session/subscription-account-menu.d.ts +11 -12
- package/dist/app/components/session/subscription-account-menu.js +1 -1
- package/dist/app/components/session/subscription-account.d.ts +105 -0
- package/dist/app/components/usage/usage-entry.js +3 -3
- package/dist/app/components/workspace-composer-plus.js +10 -10
- package/dist/app/embed/session-app.js +92 -94
- package/dist/app/lib/actions/actions-provider.js +4 -4
- package/dist/app/lib/actions/chat-switcher.d.ts +19 -7
- package/dist/app/lib/codex-usage-query.js +2 -2
- package/dist/app/lib/composer-connectors.js +8 -8
- package/dist/app/lib/console-text.js +2 -2
- package/dist/app/lib/drive-connect-return.d.ts +26 -0
- package/dist/app/lib/drive-connect-return.js +16 -0
- package/dist/app/lib/events.js +7 -7
- package/dist/app/lib/failed-session-copy.js +3 -3
- package/dist/app/lib/model-access-onboarding.js +3 -3
- package/dist/app/lib/move-chat-to-machine.js +3 -3
- package/dist/app/lib/session-branch-cache.js +6 -5
- package/dist/app/lib/session-group-pagination.js +6 -5
- package/dist/app/lib/session-hover-facts.js +6 -5
- package/dist/app/lib/session-rail.js +2 -1
- package/dist/app/lib/sessions-group.js +5 -4
- package/dist/app/lib/turn-failure-analytics.js +4 -4
- package/dist/app/lib/use-workspace-machines.js +4 -4
- package/dist/app/lib/use-workspace-model-catalog.js +3 -3
- package/dist/app/lib/use-workspace-rigs.js +2 -2
- package/dist/app/lib/workspace-navigation-preference.js +4 -4
- package/dist/app/lib/workspace-scope-context.js +3 -3
- package/dist/app/routes/artifacts.js +26 -25
- package/dist/app/routes/artifacts.js.map +1 -1
- package/dist/app/routes/retained-artifact.js +15 -15
- package/dist/app/routes/session.js +64 -63
- package/dist/app/routes/sessions-index.js +41 -41
- package/dist/app/routes/site-view.js +30 -29
- package/dist/app/routes/site-view.js.map +1 -1
- package/dist/artifacts-document.js +4 -4
- package/dist/artifacts-presentation.js +4 -4
- package/dist/artifacts-spreadsheet.js +4 -4
- package/dist/artifacts.js +10 -10
- package/dist/{browser-viewer-YQC6BFMS.js → browser-viewer-VKIAULG7.js} +4 -4
- package/dist/{chat-interactive-block-Z22YHDRU.js → chat-interactive-block-JGYSW4KB.js} +5 -5
- package/dist/{chunk-WN2MIB4Q.js → chunk-22V2QCEM.js} +2 -2
- package/dist/{chunk-SJPAKWWP.js → chunk-2BISI664.js} +2 -2
- package/dist/{chunk-O5UOCMH4.js → chunk-2LPJVEWY.js} +3 -3
- package/dist/{chunk-JI6WPLZB.js → chunk-2SBCOGQN.js} +4 -4
- package/dist/{chunk-6JF7C3U3.js → chunk-2WERWWSS.js} +2 -2
- package/dist/{chunk-4XKT4ENZ.js → chunk-2Y3QL3DA.js} +4 -4
- package/dist/{chunk-KVMFT6FN.js → chunk-3FL3F4WF.js} +2 -2
- package/dist/{chunk-4VJOZJDV.js → chunk-3JPQAIYJ.js} +2 -2
- package/dist/{chunk-27EORKJ4.js → chunk-3N4LNBJF.js} +2 -2
- package/dist/{chunk-7JZSFJHB.js → chunk-3OJU3RUR.js} +2 -2
- package/dist/{chunk-NV5OYV3G.js → chunk-3U6K36QA.js} +5 -5
- package/dist/{chunk-MDRLP5DO.js → chunk-3W7ROHN4.js} +3 -3
- package/dist/{chunk-3MQM4ZOC.js → chunk-3Y6ZR2VN.js} +3 -3
- package/dist/{chunk-HXZIX7MR.js → chunk-47AB65IO.js} +4 -4
- package/dist/{chunk-DNYD7AUG.js → chunk-4BOLIZ7Y.js} +3 -3
- package/dist/{chunk-4XO346BW.js → chunk-4FBJFQFU.js} +2 -2
- package/dist/{chunk-DAQG3C4W.js → chunk-4HM6YDBX.js} +3 -3
- package/dist/{chunk-BWKZEW6K.js → chunk-4T3E56NT.js} +3 -3
- package/dist/{chunk-4PC7LIQK.js → chunk-4ZMUWRFF.js} +3 -3
- package/dist/{chunk-O4AWXTZQ.js → chunk-52XIYULR.js} +3 -3
- package/dist/{chunk-GXK4CUAB.js → chunk-5WQKTT5Z.js} +2 -2
- package/dist/{chunk-MHIYYHCL.js → chunk-5ZAHAUWD.js} +10 -10
- package/dist/{chunk-6N7W3TOV.js → chunk-63BPMTBM.js} +2 -2
- package/dist/{chunk-R5YZ3ROM.js → chunk-63PQKTTT.js} +2 -2
- package/dist/{chunk-WLZVXCPZ.js → chunk-6GO6WN22.js} +3 -3
- package/dist/{chunk-6PQBKMUL.js → chunk-6IIA63VD.js} +11 -7
- package/dist/chunk-6IIA63VD.js.map +1 -0
- package/dist/{chunk-PQCFW2KT.js → chunk-6IRDW7K3.js} +18 -13
- package/dist/chunk-6IRDW7K3.js.map +1 -0
- package/dist/{chunk-XDCZBOEY.js → chunk-6LGBGPBU.js} +1 -1
- package/dist/{chunk-D4QOZXPM.js → chunk-7ITDATTP.js} +2 -2
- package/dist/{chunk-M5E3CKSE.js → chunk-7QWWE5XL.js} +2 -2
- package/dist/{chunk-RABKQFTA.js → chunk-A2HCAMRH.js} +2 -2
- package/dist/{chunk-6K3WNTRI.js → chunk-AODISQO6.js} +2 -2
- package/dist/{chunk-C6JL6PBL.js → chunk-AQDRH6VF.js} +3 -3
- package/dist/{chunk-72XEFLMD.js → chunk-ASWINLSW.js} +2 -2
- package/dist/{chunk-DNLXQUBX.js → chunk-B746OUJC.js} +24 -36
- package/dist/chunk-B746OUJC.js.map +1 -0
- package/dist/{chunk-FVCLVASD.js → chunk-B7ZKSROO.js} +24 -24
- package/dist/{chunk-EURDMOXZ.js → chunk-BYJZUCOQ.js} +2 -2
- package/dist/{chunk-ZWACUOU5.js → chunk-CDBGAAJY.js} +3 -3
- package/dist/chunk-CDBGAAJY.js.map +1 -0
- package/dist/{chunk-BPD4IJBG.js → chunk-CG6CJMIS.js} +2 -2
- package/dist/{chunk-B254JG72.js → chunk-CQX24PT7.js} +2 -2
- package/dist/{chunk-HJTTDLF6.js → chunk-CY3NO4JN.js} +4 -4
- package/dist/{chunk-OLUG4IFF.js → chunk-D2RLPRSG.js} +2 -2
- package/dist/{chunk-74NIHMAT.js → chunk-D653JH6D.js} +3 -3
- package/dist/{chunk-FMATVQFW.js → chunk-DA6EQDFR.js} +6 -6
- package/dist/{chunk-2LUO22FR.js → chunk-DAI5CTQF.js} +4 -4
- package/dist/{chunk-HTKVYEVB.js → chunk-DBM4ZXDN.js} +2 -2
- package/dist/{chunk-MB2DTUEG.js → chunk-DNYKLU3K.js} +4 -4
- package/dist/{chunk-NPDL4W4Y.js → chunk-DT53LBQN.js} +44 -45
- package/dist/chunk-DT53LBQN.js.map +1 -0
- package/dist/{chunk-26P7B46A.js → chunk-DXYARXK3.js} +3 -3
- package/dist/{chunk-JQKURO3S.js → chunk-E4MQRQ6Z.js} +2 -2
- package/dist/{chunk-KW53DBY7.js → chunk-EBW2E32G.js} +2 -2
- package/dist/{chunk-O73DRA77.js → chunk-ENKBO7FC.js} +4 -4
- package/dist/{chunk-32HA7DXJ.js → chunk-EQNGX77H.js} +11 -11
- package/dist/{chunk-CA64OZLZ.js → chunk-FAPYRTG6.js} +4 -4
- package/dist/{chunk-WWM7WVD2.js → chunk-GABFKHQE.js} +2 -2
- package/dist/chunk-GLCOKWLH.js +42 -0
- package/dist/chunk-GLCOKWLH.js.map +1 -0
- package/dist/{chunk-OHG2UQ2O.js → chunk-GNM3OY36.js} +51 -13
- package/dist/chunk-GNM3OY36.js.map +1 -0
- package/dist/{chunk-JJ7VMIEQ.js → chunk-H2GTWX4D.js} +14 -14
- package/dist/{chunk-LR7U3HCD.js → chunk-HPSATFLQ.js} +4 -4
- package/dist/{chunk-TM2ZLY3Q.js → chunk-I56TUH3Z.js} +13 -13
- package/dist/{chunk-ZZUKJ5HQ.js → chunk-IG2ERMSB.js} +2 -2
- package/dist/{chunk-2SZ3KC5S.js → chunk-IMKIIS4Y.js} +2 -2
- package/dist/{chunk-I57HZMMA.js → chunk-IXIE3H43.js} +2 -2
- package/dist/{chunk-OWJD3V4V.js → chunk-J3YQ3554.js} +2 -2
- package/dist/{chunk-BCFHMBMK.js → chunk-J5HZBHU4.js} +7 -7
- package/dist/{chunk-UHMMRQGU.js → chunk-KD7GNHOP.js} +3 -3
- package/dist/{chunk-RXMIXKB3.js → chunk-KIKAVZOW.js} +2 -2
- package/dist/{chunk-X7Z4B5GK.js → chunk-KPL7EXS6.js} +2 -2
- package/dist/{chunk-GTR2WUBA.js → chunk-KX6M5CJI.js} +1 -1
- package/dist/{chunk-CYO5HL4C.js → chunk-KYL2BPS2.js} +2 -2
- package/dist/{chunk-ZPZYYSHW.js → chunk-L7EAQGWN.js} +5 -5
- package/dist/{chunk-QLDMF7MH.js → chunk-LC5HE7PZ.js} +2 -2
- package/dist/{chunk-3SMJ2BIQ.js → chunk-LGD5RESZ.js} +7 -33
- package/dist/chunk-LGD5RESZ.js.map +1 -0
- package/dist/{chunk-4ICPJZAB.js → chunk-LMYSYG6U.js} +2 -2
- package/dist/{chunk-3ZZOE4SN.js → chunk-LOCDYMD6.js} +4 -4
- package/dist/{chunk-DNWBLT4U.js → chunk-MASBMJ2M.js} +2 -2
- package/dist/{chunk-6VITVQ23.js → chunk-MTYE2PPR.js} +1 -1
- package/dist/{chunk-NZMQDUOD.js → chunk-MU6HYDNF.js} +5 -5
- package/dist/{chunk-GMVKCEBG.js → chunk-MY6QEDZ2.js} +2 -2
- package/dist/{chunk-SK57RWSA.js → chunk-MZGKQ5WV.js} +2 -2
- package/dist/{chunk-HSQRYJ4P.js → chunk-N52SFH2K.js} +2 -2
- package/dist/{chunk-CEQUNJJY.js → chunk-NDCP5TVN.js} +2 -2
- package/dist/{chunk-MY6VZVCP.js → chunk-OAJX5CEL.js} +4 -4
- package/dist/{chunk-BQWUXC55.js → chunk-OBBLVC2W.js} +9 -9
- package/dist/{chunk-IWIMIRV3.js → chunk-OSJTLQXA.js} +9 -4
- package/dist/chunk-OSJTLQXA.js.map +1 -0
- package/dist/{chunk-FH75NNLB.js → chunk-OZ5NAXCP.js} +2 -2
- package/dist/{chunk-PEGNFZ3F.js → chunk-P5S777YI.js} +2 -2
- package/dist/{chunk-LY6727IF.js → chunk-PPSVFNEA.js} +1 -1
- package/dist/{chunk-K4FDZVMQ.js → chunk-PR42VIVY.js} +2 -2
- package/dist/{chunk-I74J2UZ2.js → chunk-PWMZCW4H.js} +8 -8
- package/dist/{chunk-6YDXUE5H.js → chunk-PYA2O3WS.js} +2 -2
- package/dist/{chunk-GUIZDI5T.js → chunk-Q2PA4KZS.js} +2 -2
- package/dist/{chunk-DZH2HTTB.js → chunk-R27GW5WV.js} +1 -1
- package/dist/chunk-R4U4EWZG.js +414 -0
- package/dist/chunk-R4U4EWZG.js.map +1 -0
- package/dist/{chunk-7OIBLMDP.js → chunk-RAQISW42.js} +2 -2
- package/dist/{chunk-CPKD2YGK.js → chunk-RNU5CUPI.js} +2 -2
- package/dist/{chunk-467VRP4E.js → chunk-RO5YZQHZ.js} +3 -3
- package/dist/{chunk-SSZROOOI.js → chunk-ROLXYGT5.js} +2 -2
- package/dist/{chunk-F4B2YYCN.js → chunk-TAWFXRXB.js} +3 -3
- package/dist/{chunk-G3FU2IF7.js → chunk-TB7UW5BB.js} +3 -3
- package/dist/{chunk-NF6Q4XTN.js → chunk-TQF2I5SY.js} +3 -3
- package/dist/{chunk-MM7OKOTE.js → chunk-UBXHP224.js} +43 -18
- package/dist/chunk-UBXHP224.js.map +1 -0
- package/dist/{chunk-M7A226EQ.js → chunk-UL44R3W2.js} +2 -2
- package/dist/{chunk-65K5LV4L.js → chunk-UQKBV22P.js} +21 -20
- package/dist/{chunk-65K5LV4L.js.map → chunk-UQKBV22P.js.map} +1 -1
- package/dist/{chunk-GTR5NLVP.js → chunk-VCRQPLKS.js} +2 -2
- package/dist/{chunk-NWSAF6P5.js → chunk-WJDH7CH5.js} +22 -1
- package/dist/chunk-WJDH7CH5.js.map +1 -0
- package/dist/{chunk-LG67ZNRU.js → chunk-WK2ZCTUF.js} +2 -2
- package/dist/{chunk-XGE75VPK.js → chunk-WNARM3LY.js} +3 -3
- package/dist/{chunk-RNBH5TSM.js → chunk-WQ2RTQYO.js} +2 -2
- package/dist/chunk-X5XEYQ4B.js +84 -0
- package/dist/chunk-X5XEYQ4B.js.map +1 -0
- package/dist/{chunk-W57QFLQD.js → chunk-X6JIGNLI.js} +4 -4
- package/dist/{chunk-CKA4SJRK.js → chunk-XIQDCQX7.js} +2 -2
- package/dist/{chunk-AJ7PY4FJ.js → chunk-Y3GCG6CA.js} +2 -2
- package/dist/{chunk-2K27S6LK.js → chunk-Y565N5ZJ.js} +24 -7
- package/dist/chunk-Y565N5ZJ.js.map +1 -0
- package/dist/{chunk-ZRB4M3GG.js → chunk-YC5KH7ZB.js} +3 -3
- package/dist/{chunk-4RY64KQY.js → chunk-YJUKLLNP.js} +2 -2
- package/dist/{chunk-24POWVUY.js → chunk-ZDJNRRVS.js} +4 -4
- package/dist/{chunk-S6WJ6TUT.js → chunk-ZEVJGMNB.js} +2 -2
- package/dist/chunk-ZEVJGMNB.js.map +1 -0
- package/dist/{chunk-M7YEZR6X.js → chunk-ZITMDA4C.js} +2 -2
- package/dist/components/model-policy-picker.d.ts +2 -0
- package/dist/composer.js +6 -6
- package/dist/{computer-viewer-BWVNDFTO.js → computer-viewer-6CRLWQHP.js} +5 -5
- package/dist/connect.js +6 -6
- package/dist/connection-service-presentation.d.ts +1 -4
- package/dist/desktop.js +2 -2
- package/dist/editor.js +2 -2
- package/dist/{embedded-new-voice-GBXSZ3TO.js → embedded-new-voice-KILV3RYM.js} +5 -5
- package/dist/{embedded-voice-MGI7EK7C.js → embedded-voice-GHDRLCFX.js} +5 -5
- package/dist/{fleet-decision-row-35S3GAUN.js → fleet-decision-row-DQXRRBT7.js} +2 -2
- package/dist/index.js +36 -36
- package/dist/interaction.js +5 -5
- package/dist/machines.js +3 -3
- package/dist/messages/en.d.ts +1 -1
- package/dist/messages.js +1 -1
- package/dist/{model-policy-picker-menu-KP53N6UJ.js → model-policy-picker-menu-5ALKUJMR.js} +4 -4
- package/dist/{platform-activity-row-ISMXR7NJ.js → platform-activity-row-227C764Z.js} +2 -2
- package/dist/{queue-surface-implementation-ULW7DDJA.js → queue-surface-implementation-H3NOBXU5.js} +5 -5
- package/dist/realtime.js +4 -4
- package/dist/{sandbox-files-QYSD4XCZ.js → sandbox-files-5FEOHUMD.js} +6 -6
- package/dist/session-app.js +92 -94
- package/dist/{session-realtime-JXRLPNUH.js → session-realtime-OHBMODNX.js} +3 -3
- package/dist/session-realtime.js +2 -2
- package/dist/session-ui.js +24 -24
- package/dist/session.js +5 -5
- package/dist/sites.js +2 -2
- package/dist/terminal.js +2 -2
- package/dist/this-computer.js +3 -3
- package/dist/timeline/connector-logo-index.d.ts +19 -0
- package/dist/{timeline-annotation-markers-OJYKHBEO.js → timeline-annotation-markers-NDHS7MCK.js} +2 -2
- package/dist/{timeline-annotation-selection-6OAZLXFU.js → timeline-annotation-selection-DRQSTJOV.js} +2 -2
- package/dist/{timeline-annotations-dialog-5M6DP5FX.js → timeline-annotations-dialog-G6J5ZJKN.js} +3 -3
- package/dist/timeline-model.d.ts +2 -0
- package/dist/timeline-model.js +9 -1
- package/dist/ui/list-row.js +1 -1
- package/dist/usage.js +2 -2
- package/dist/workbench.js +4 -4
- package/package.json +4 -4
- package/src/app/components/capabilities/native-connect-readiness.tsx +1 -0
- package/src/app/components/capabilities/perform-capability-action.test.ts +67 -0
- package/src/app/components/capabilities/perform-capability-action.tsx +28 -0
- package/src/app/components/command-menu/chat-switcher.tsx +25 -4
- package/src/app/components/rail/session-header.test.tsx +73 -2
- package/src/app/components/rail/session-header.tsx +20 -12
- package/src/app/components/rail/session-shell.tsx +10 -18
- package/src/app/components/session/subscription-account-indicator.test.ts +239 -0
- package/src/app/components/session/subscription-account-indicator.tsx +361 -0
- package/src/app/components/session/subscription-account-menu.tsx +21 -43
- package/src/app/components/session/subscription-account.test.ts +314 -0
- package/src/app/components/session/subscription-account.ts +354 -0
- package/src/app/lib/actions/actions-provider.tsx +11 -0
- package/src/app/lib/actions/actions.test.ts +59 -4
- package/src/app/lib/actions/chat-switcher.ts +73 -20
- package/src/app/lib/drive-connect-return.test.ts +183 -0
- package/src/app/lib/drive-connect-return.tsx +116 -0
- package/src/app/routes/session.tsx +12 -14
- package/src/app/routes/sessions-index.tsx +1 -0
- package/src/components/model-policy-picker-menu.tsx +25 -4
- package/src/components/model-policy-picker.tsx +2 -0
- package/src/components/session-mcp-capability-card.tsx +13 -4
- package/src/connection-service-presentation.ts +6 -1
- package/src/messages/en.ts +1 -1
- package/src/timeline/connector-logo-index.test.ts +46 -0
- package/src/timeline/connector-logo-index.ts +68 -0
- package/src/timeline-model.ts +6 -0
- package/src/ui/list-row.tsx +38 -10
- package/styles/compiled.css +13 -0
- package/dist/app/components/session/codex-account-indicator.d.ts +0 -9
- package/dist/app/components/session/model-account-indicator.d.ts +0 -10
- package/dist/app/components/session/model-account-indicator.js +0 -34
- package/dist/app/components/session/model-account-usage.d.ts +0 -8
- package/dist/app/components/session/model-account-usage.js +0 -8
- package/dist/chunk-2K27S6LK.js.map +0 -1
- package/dist/chunk-3SMJ2BIQ.js.map +0 -1
- package/dist/chunk-6PQBKMUL.js.map +0 -1
- package/dist/chunk-7H2D4VNU.js +0 -150
- package/dist/chunk-7H2D4VNU.js.map +0 -1
- package/dist/chunk-DNLXQUBX.js.map +0 -1
- package/dist/chunk-HCCSAECI.js +0 -132
- package/dist/chunk-HCCSAECI.js.map +0 -1
- package/dist/chunk-IWIMIRV3.js.map +0 -1
- package/dist/chunk-M77ZQE5T.js +0 -58
- package/dist/chunk-M77ZQE5T.js.map +0 -1
- package/dist/chunk-MM7OKOTE.js.map +0 -1
- package/dist/chunk-NPDL4W4Y.js.map +0 -1
- package/dist/chunk-NWSAF6P5.js.map +0 -1
- package/dist/chunk-OHG2UQ2O.js.map +0 -1
- package/dist/chunk-PQCFW2KT.js.map +0 -1
- package/dist/chunk-S6WJ6TUT.js.map +0 -1
- package/dist/chunk-ZWACUOU5.js.map +0 -1
- package/dist/session-realtime-JXRLPNUH.js.map +0 -1
- package/src/app/components/session/codex-account-indicator.tsx +0 -207
- package/src/app/components/session/model-account-indicator.tsx +0 -189
- package/src/app/components/session/model-account-usage.test.ts +0 -59
- package/src/app/components/session/model-account-usage.tsx +0 -89
- /package/dist/app/components/session/{codex-account-indicator.js.map → subscription-account-indicator.js.map} +0 -0
- /package/dist/app/{components/session/model-account-indicator.js.map → lib/drive-connect-return.js.map} +0 -0
- /package/dist/{app/components/session/model-account-usage.js.map → browser-viewer-VKIAULG7.js.map} +0 -0
- /package/dist/{browser-viewer-YQC6BFMS.js.map → chat-interactive-block-JGYSW4KB.js.map} +0 -0
- /package/dist/{chunk-WN2MIB4Q.js.map → chunk-22V2QCEM.js.map} +0 -0
- /package/dist/{chunk-SJPAKWWP.js.map → chunk-2BISI664.js.map} +0 -0
- /package/dist/{chunk-O5UOCMH4.js.map → chunk-2LPJVEWY.js.map} +0 -0
- /package/dist/{chunk-JI6WPLZB.js.map → chunk-2SBCOGQN.js.map} +0 -0
- /package/dist/{chunk-6JF7C3U3.js.map → chunk-2WERWWSS.js.map} +0 -0
- /package/dist/{chunk-4XKT4ENZ.js.map → chunk-2Y3QL3DA.js.map} +0 -0
- /package/dist/{chunk-KVMFT6FN.js.map → chunk-3FL3F4WF.js.map} +0 -0
- /package/dist/{chunk-4VJOZJDV.js.map → chunk-3JPQAIYJ.js.map} +0 -0
- /package/dist/{chunk-27EORKJ4.js.map → chunk-3N4LNBJF.js.map} +0 -0
- /package/dist/{chunk-7JZSFJHB.js.map → chunk-3OJU3RUR.js.map} +0 -0
- /package/dist/{chunk-NV5OYV3G.js.map → chunk-3U6K36QA.js.map} +0 -0
- /package/dist/{chunk-MDRLP5DO.js.map → chunk-3W7ROHN4.js.map} +0 -0
- /package/dist/{chunk-3MQM4ZOC.js.map → chunk-3Y6ZR2VN.js.map} +0 -0
- /package/dist/{chunk-HXZIX7MR.js.map → chunk-47AB65IO.js.map} +0 -0
- /package/dist/{chunk-DNYD7AUG.js.map → chunk-4BOLIZ7Y.js.map} +0 -0
- /package/dist/{chunk-4XO346BW.js.map → chunk-4FBJFQFU.js.map} +0 -0
- /package/dist/{chunk-DAQG3C4W.js.map → chunk-4HM6YDBX.js.map} +0 -0
- /package/dist/{chunk-BWKZEW6K.js.map → chunk-4T3E56NT.js.map} +0 -0
- /package/dist/{chunk-4PC7LIQK.js.map → chunk-4ZMUWRFF.js.map} +0 -0
- /package/dist/{chunk-O4AWXTZQ.js.map → chunk-52XIYULR.js.map} +0 -0
- /package/dist/{chunk-GXK4CUAB.js.map → chunk-5WQKTT5Z.js.map} +0 -0
- /package/dist/{chunk-MHIYYHCL.js.map → chunk-5ZAHAUWD.js.map} +0 -0
- /package/dist/{chunk-6N7W3TOV.js.map → chunk-63BPMTBM.js.map} +0 -0
- /package/dist/{chunk-R5YZ3ROM.js.map → chunk-63PQKTTT.js.map} +0 -0
- /package/dist/{chunk-WLZVXCPZ.js.map → chunk-6GO6WN22.js.map} +0 -0
- /package/dist/{chunk-XDCZBOEY.js.map → chunk-6LGBGPBU.js.map} +0 -0
- /package/dist/{chunk-D4QOZXPM.js.map → chunk-7ITDATTP.js.map} +0 -0
- /package/dist/{chunk-M5E3CKSE.js.map → chunk-7QWWE5XL.js.map} +0 -0
- /package/dist/{chunk-RABKQFTA.js.map → chunk-A2HCAMRH.js.map} +0 -0
- /package/dist/{chunk-6K3WNTRI.js.map → chunk-AODISQO6.js.map} +0 -0
- /package/dist/{chunk-C6JL6PBL.js.map → chunk-AQDRH6VF.js.map} +0 -0
- /package/dist/{chunk-72XEFLMD.js.map → chunk-ASWINLSW.js.map} +0 -0
- /package/dist/{chunk-FVCLVASD.js.map → chunk-B7ZKSROO.js.map} +0 -0
- /package/dist/{chunk-EURDMOXZ.js.map → chunk-BYJZUCOQ.js.map} +0 -0
- /package/dist/{chunk-BPD4IJBG.js.map → chunk-CG6CJMIS.js.map} +0 -0
- /package/dist/{chunk-B254JG72.js.map → chunk-CQX24PT7.js.map} +0 -0
- /package/dist/{chunk-HJTTDLF6.js.map → chunk-CY3NO4JN.js.map} +0 -0
- /package/dist/{chunk-OLUG4IFF.js.map → chunk-D2RLPRSG.js.map} +0 -0
- /package/dist/{chunk-74NIHMAT.js.map → chunk-D653JH6D.js.map} +0 -0
- /package/dist/{chunk-FMATVQFW.js.map → chunk-DA6EQDFR.js.map} +0 -0
- /package/dist/{chunk-2LUO22FR.js.map → chunk-DAI5CTQF.js.map} +0 -0
- /package/dist/{chunk-HTKVYEVB.js.map → chunk-DBM4ZXDN.js.map} +0 -0
- /package/dist/{chunk-MB2DTUEG.js.map → chunk-DNYKLU3K.js.map} +0 -0
- /package/dist/{chunk-26P7B46A.js.map → chunk-DXYARXK3.js.map} +0 -0
- /package/dist/{chunk-JQKURO3S.js.map → chunk-E4MQRQ6Z.js.map} +0 -0
- /package/dist/{chunk-KW53DBY7.js.map → chunk-EBW2E32G.js.map} +0 -0
- /package/dist/{chunk-O73DRA77.js.map → chunk-ENKBO7FC.js.map} +0 -0
- /package/dist/{chunk-32HA7DXJ.js.map → chunk-EQNGX77H.js.map} +0 -0
- /package/dist/{chunk-CA64OZLZ.js.map → chunk-FAPYRTG6.js.map} +0 -0
- /package/dist/{chunk-WWM7WVD2.js.map → chunk-GABFKHQE.js.map} +0 -0
- /package/dist/{chunk-JJ7VMIEQ.js.map → chunk-H2GTWX4D.js.map} +0 -0
- /package/dist/{chunk-LR7U3HCD.js.map → chunk-HPSATFLQ.js.map} +0 -0
- /package/dist/{chunk-TM2ZLY3Q.js.map → chunk-I56TUH3Z.js.map} +0 -0
- /package/dist/{chunk-ZZUKJ5HQ.js.map → chunk-IG2ERMSB.js.map} +0 -0
- /package/dist/{chunk-2SZ3KC5S.js.map → chunk-IMKIIS4Y.js.map} +0 -0
- /package/dist/{chunk-I57HZMMA.js.map → chunk-IXIE3H43.js.map} +0 -0
- /package/dist/{chunk-OWJD3V4V.js.map → chunk-J3YQ3554.js.map} +0 -0
- /package/dist/{chunk-BCFHMBMK.js.map → chunk-J5HZBHU4.js.map} +0 -0
- /package/dist/{chunk-UHMMRQGU.js.map → chunk-KD7GNHOP.js.map} +0 -0
- /package/dist/{chunk-RXMIXKB3.js.map → chunk-KIKAVZOW.js.map} +0 -0
- /package/dist/{chunk-X7Z4B5GK.js.map → chunk-KPL7EXS6.js.map} +0 -0
- /package/dist/{chunk-GTR2WUBA.js.map → chunk-KX6M5CJI.js.map} +0 -0
- /package/dist/{chunk-CYO5HL4C.js.map → chunk-KYL2BPS2.js.map} +0 -0
- /package/dist/{chunk-ZPZYYSHW.js.map → chunk-L7EAQGWN.js.map} +0 -0
- /package/dist/{chunk-QLDMF7MH.js.map → chunk-LC5HE7PZ.js.map} +0 -0
- /package/dist/{chunk-4ICPJZAB.js.map → chunk-LMYSYG6U.js.map} +0 -0
- /package/dist/{chunk-3ZZOE4SN.js.map → chunk-LOCDYMD6.js.map} +0 -0
- /package/dist/{chunk-DNWBLT4U.js.map → chunk-MASBMJ2M.js.map} +0 -0
- /package/dist/{chunk-6VITVQ23.js.map → chunk-MTYE2PPR.js.map} +0 -0
- /package/dist/{chunk-NZMQDUOD.js.map → chunk-MU6HYDNF.js.map} +0 -0
- /package/dist/{chunk-GMVKCEBG.js.map → chunk-MY6QEDZ2.js.map} +0 -0
- /package/dist/{chunk-SK57RWSA.js.map → chunk-MZGKQ5WV.js.map} +0 -0
- /package/dist/{chunk-HSQRYJ4P.js.map → chunk-N52SFH2K.js.map} +0 -0
- /package/dist/{chunk-CEQUNJJY.js.map → chunk-NDCP5TVN.js.map} +0 -0
- /package/dist/{chunk-MY6VZVCP.js.map → chunk-OAJX5CEL.js.map} +0 -0
- /package/dist/{chunk-BQWUXC55.js.map → chunk-OBBLVC2W.js.map} +0 -0
- /package/dist/{chunk-FH75NNLB.js.map → chunk-OZ5NAXCP.js.map} +0 -0
- /package/dist/{chunk-PEGNFZ3F.js.map → chunk-P5S777YI.js.map} +0 -0
- /package/dist/{chunk-LY6727IF.js.map → chunk-PPSVFNEA.js.map} +0 -0
- /package/dist/{chunk-K4FDZVMQ.js.map → chunk-PR42VIVY.js.map} +0 -0
- /package/dist/{chunk-I74J2UZ2.js.map → chunk-PWMZCW4H.js.map} +0 -0
- /package/dist/{chunk-6YDXUE5H.js.map → chunk-PYA2O3WS.js.map} +0 -0
- /package/dist/{chunk-GUIZDI5T.js.map → chunk-Q2PA4KZS.js.map} +0 -0
- /package/dist/{chunk-DZH2HTTB.js.map → chunk-R27GW5WV.js.map} +0 -0
- /package/dist/{chunk-7OIBLMDP.js.map → chunk-RAQISW42.js.map} +0 -0
- /package/dist/{chunk-CPKD2YGK.js.map → chunk-RNU5CUPI.js.map} +0 -0
- /package/dist/{chunk-467VRP4E.js.map → chunk-RO5YZQHZ.js.map} +0 -0
- /package/dist/{chunk-SSZROOOI.js.map → chunk-ROLXYGT5.js.map} +0 -0
- /package/dist/{chunk-F4B2YYCN.js.map → chunk-TAWFXRXB.js.map} +0 -0
- /package/dist/{chunk-G3FU2IF7.js.map → chunk-TB7UW5BB.js.map} +0 -0
- /package/dist/{chunk-NF6Q4XTN.js.map → chunk-TQF2I5SY.js.map} +0 -0
- /package/dist/{chunk-M7A226EQ.js.map → chunk-UL44R3W2.js.map} +0 -0
- /package/dist/{chunk-GTR5NLVP.js.map → chunk-VCRQPLKS.js.map} +0 -0
- /package/dist/{chunk-LG67ZNRU.js.map → chunk-WK2ZCTUF.js.map} +0 -0
- /package/dist/{chunk-XGE75VPK.js.map → chunk-WNARM3LY.js.map} +0 -0
- /package/dist/{chunk-RNBH5TSM.js.map → chunk-WQ2RTQYO.js.map} +0 -0
- /package/dist/{chunk-W57QFLQD.js.map → chunk-X6JIGNLI.js.map} +0 -0
- /package/dist/{chunk-CKA4SJRK.js.map → chunk-XIQDCQX7.js.map} +0 -0
- /package/dist/{chunk-AJ7PY4FJ.js.map → chunk-Y3GCG6CA.js.map} +0 -0
- /package/dist/{chunk-ZRB4M3GG.js.map → chunk-YC5KH7ZB.js.map} +0 -0
- /package/dist/{chunk-4RY64KQY.js.map → chunk-YJUKLLNP.js.map} +0 -0
- /package/dist/{chunk-24POWVUY.js.map → chunk-ZDJNRRVS.js.map} +0 -0
- /package/dist/{chunk-M7YEZR6X.js.map → chunk-ZITMDA4C.js.map} +0 -0
- /package/dist/{chat-interactive-block-Z22YHDRU.js.map → computer-viewer-6CRLWQHP.js.map} +0 -0
- /package/dist/{computer-viewer-BWVNDFTO.js.map → embedded-new-voice-KILV3RYM.js.map} +0 -0
- /package/dist/{embedded-new-voice-GBXSZ3TO.js.map → embedded-voice-GHDRLCFX.js.map} +0 -0
- /package/dist/{fleet-decision-row-35S3GAUN.js.map → fleet-decision-row-DQXRRBT7.js.map} +0 -0
- /package/dist/{embedded-voice-MGI7EK7C.js.map → model-policy-picker-menu-5ALKUJMR.js.map} +0 -0
- /package/dist/{platform-activity-row-ISMXR7NJ.js.map → platform-activity-row-227C764Z.js.map} +0 -0
- /package/dist/{queue-surface-implementation-ULW7DDJA.js.map → queue-surface-implementation-H3NOBXU5.js.map} +0 -0
- /package/dist/{model-policy-picker-menu-KP53N6UJ.js.map → sandbox-files-5FEOHUMD.js.map} +0 -0
- /package/dist/{sandbox-files-QYSD4XCZ.js.map → session-realtime-OHBMODNX.js.map} +0 -0
- /package/dist/{timeline-annotation-markers-OJYKHBEO.js.map → timeline-annotation-markers-NDHS7MCK.js.map} +0 -0
- /package/dist/{timeline-annotation-selection-6OAZLXFU.js.map → timeline-annotation-selection-DRQSTJOV.js.map} +0 -0
- /package/dist/{timeline-annotations-dialog-5M6DP5FX.js.map → timeline-annotations-dialog-G6J5ZJKN.js.map} +0 -0
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
import { expect, mock, test } from "bun:test";
|
|
2
|
+
import type { OpenGeniBrowserClient } from "@opengeni/sdk/browser";
|
|
3
|
+
import {
|
|
4
|
+
beginDriveConnect,
|
|
5
|
+
resolveDriveConnectReturn,
|
|
6
|
+
completeDriveConnectReturn,
|
|
7
|
+
} from "./drive-connect-return";
|
|
8
|
+
|
|
9
|
+
function fixture() {
|
|
10
|
+
const values = new Map<string, string>();
|
|
11
|
+
const storage = {
|
|
12
|
+
getItem: (key: string) => values.get(key) ?? null,
|
|
13
|
+
setItem: (key: string, value: string) => {
|
|
14
|
+
values.set(key, value);
|
|
15
|
+
},
|
|
16
|
+
removeItem: (key: string) => {
|
|
17
|
+
values.delete(key);
|
|
18
|
+
},
|
|
19
|
+
};
|
|
20
|
+
let outcome = {
|
|
21
|
+
id: "attempt",
|
|
22
|
+
providerId: "google-drive",
|
|
23
|
+
ownership: "personal",
|
|
24
|
+
state: "complete",
|
|
25
|
+
credentialsCommitted: true,
|
|
26
|
+
account: { id: "exact-account", providerId: "google-drive", ownership: "personal" },
|
|
27
|
+
} as Record<string, unknown>;
|
|
28
|
+
const beginConnect = mock(async () => ({
|
|
29
|
+
id: "attempt",
|
|
30
|
+
revision: 1,
|
|
31
|
+
state: "credential_input",
|
|
32
|
+
}));
|
|
33
|
+
const advanceConnectAttempt = mock(async () => ({
|
|
34
|
+
id: "attempt",
|
|
35
|
+
nextAction: { type: "authorize", url: "https://accounts.google.com/consent" },
|
|
36
|
+
}));
|
|
37
|
+
const getConnectAttempt = mock(async () => outcome);
|
|
38
|
+
const client = {
|
|
39
|
+
beginConnect,
|
|
40
|
+
connectTransport: () => ({ advance: advanceConnectAttempt, get: getConnectAttempt }),
|
|
41
|
+
} as unknown as OpenGeniBrowserClient;
|
|
42
|
+
return {
|
|
43
|
+
storage,
|
|
44
|
+
values,
|
|
45
|
+
client,
|
|
46
|
+
beginConnect,
|
|
47
|
+
advanceConnectAttempt,
|
|
48
|
+
getConnectAttempt,
|
|
49
|
+
setOutcome: (value: Record<string, unknown>) => {
|
|
50
|
+
outcome = value;
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
test("Drive return reads the exact durable account, scoped to its original workspace and return nonce", async () => {
|
|
56
|
+
const f = fixture();
|
|
57
|
+
expect(
|
|
58
|
+
await beginDriveConnect({
|
|
59
|
+
client: f.client,
|
|
60
|
+
workspaceId: "display",
|
|
61
|
+
connectionWorkspaceId: "owner",
|
|
62
|
+
itemId: "drive",
|
|
63
|
+
reconnectAccountId: "existing",
|
|
64
|
+
returnUrl: "https://app.example/settings?connect_item=drive",
|
|
65
|
+
storage: f.storage,
|
|
66
|
+
}),
|
|
67
|
+
).toBe("https://accounts.google.com/consent");
|
|
68
|
+
expect(f.beginConnect).toHaveBeenCalledWith(
|
|
69
|
+
"owner",
|
|
70
|
+
expect.objectContaining({
|
|
71
|
+
providerId: "google-drive",
|
|
72
|
+
ownership: "personal",
|
|
73
|
+
reconnectAccountId: "existing",
|
|
74
|
+
}),
|
|
75
|
+
);
|
|
76
|
+
expect(f.advanceConnectAttempt).toHaveBeenCalledWith(
|
|
77
|
+
"owner",
|
|
78
|
+
"attempt",
|
|
79
|
+
expect.objectContaining({ expectedRevision: 1, action: { type: "credentials", values: {} } }),
|
|
80
|
+
);
|
|
81
|
+
const receipt = JSON.parse([...f.values.values()][0]!);
|
|
82
|
+
await expect(
|
|
83
|
+
resolveDriveConnectReturn({
|
|
84
|
+
client: f.client,
|
|
85
|
+
workspaceId: "display",
|
|
86
|
+
nonce: "wrong",
|
|
87
|
+
storage: f.storage,
|
|
88
|
+
}),
|
|
89
|
+
).rejects.toThrow("could not be matched");
|
|
90
|
+
expect(f.getConnectAttempt).not.toHaveBeenCalled();
|
|
91
|
+
await expect(
|
|
92
|
+
resolveDriveConnectReturn({
|
|
93
|
+
client: f.client,
|
|
94
|
+
workspaceId: "other",
|
|
95
|
+
nonce: receipt.nonce,
|
|
96
|
+
storage: f.storage,
|
|
97
|
+
}),
|
|
98
|
+
).rejects.toThrow("could not be matched");
|
|
99
|
+
expect(
|
|
100
|
+
await resolveDriveConnectReturn({
|
|
101
|
+
client: f.client,
|
|
102
|
+
workspaceId: "display",
|
|
103
|
+
nonce: receipt.nonce,
|
|
104
|
+
storage: f.storage,
|
|
105
|
+
}),
|
|
106
|
+
).toEqual({ itemId: "drive", connectionId: "exact-account" });
|
|
107
|
+
expect(f.getConnectAttempt).toHaveBeenCalledWith("owner", "attempt");
|
|
108
|
+
// StrictMode/effect cleanup can discard the first reader. The locator remains
|
|
109
|
+
// until its active consumer confirms the connector was enabled.
|
|
110
|
+
expect(
|
|
111
|
+
await resolveDriveConnectReturn({
|
|
112
|
+
client: f.client,
|
|
113
|
+
workspaceId: "display",
|
|
114
|
+
nonce: receipt.nonce,
|
|
115
|
+
storage: f.storage,
|
|
116
|
+
}),
|
|
117
|
+
).toEqual({ itemId: "drive", connectionId: "exact-account" });
|
|
118
|
+
completeDriveConnectReturn("display", "different-return", f.storage);
|
|
119
|
+
expect(f.values.size).toBe(1);
|
|
120
|
+
completeDriveConnectReturn("display", receipt.nonce, f.storage);
|
|
121
|
+
expect(f.values.size).toBe(0);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("failed or incomplete Drive returns never produce an account to auto-enable", async () => {
|
|
125
|
+
for (const state of ["failed", "requires_user_action", "complete"]) {
|
|
126
|
+
const f = fixture();
|
|
127
|
+
await beginDriveConnect({
|
|
128
|
+
client: f.client,
|
|
129
|
+
workspaceId: "workspace",
|
|
130
|
+
itemId: "drive",
|
|
131
|
+
returnUrl: "https://app.example/settings",
|
|
132
|
+
storage: f.storage,
|
|
133
|
+
});
|
|
134
|
+
const receipt = JSON.parse([...f.values.values()][0]!);
|
|
135
|
+
f.setOutcome({
|
|
136
|
+
id: "attempt",
|
|
137
|
+
providerId: "google-drive",
|
|
138
|
+
ownership: "personal",
|
|
139
|
+
state,
|
|
140
|
+
credentialsCommitted: false,
|
|
141
|
+
error: { message: "Provider rejected access" },
|
|
142
|
+
});
|
|
143
|
+
await expect(
|
|
144
|
+
resolveDriveConnectReturn({
|
|
145
|
+
client: f.client,
|
|
146
|
+
workspaceId: "workspace",
|
|
147
|
+
nonce: receipt.nonce,
|
|
148
|
+
storage: f.storage,
|
|
149
|
+
}),
|
|
150
|
+
).rejects.toThrow();
|
|
151
|
+
expect(f.values.size).toBe(state === "failed" ? 0 : 1);
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
test("a different provider or ownership cannot satisfy a saved Drive return", async () => {
|
|
156
|
+
for (const patch of [{ providerId: "mcp-oauth" }, { ownership: "workspace" }]) {
|
|
157
|
+
const f = fixture();
|
|
158
|
+
await beginDriveConnect({
|
|
159
|
+
client: f.client,
|
|
160
|
+
workspaceId: "workspace",
|
|
161
|
+
itemId: "drive",
|
|
162
|
+
returnUrl: "https://app.example/settings",
|
|
163
|
+
storage: f.storage,
|
|
164
|
+
});
|
|
165
|
+
const receipt = JSON.parse([...f.values.values()][0]!);
|
|
166
|
+
f.setOutcome({
|
|
167
|
+
id: "attempt",
|
|
168
|
+
providerId: "google-drive",
|
|
169
|
+
ownership: "personal",
|
|
170
|
+
state: "complete",
|
|
171
|
+
credentialsCommitted: true,
|
|
172
|
+
...patch,
|
|
173
|
+
});
|
|
174
|
+
await expect(
|
|
175
|
+
resolveDriveConnectReturn({
|
|
176
|
+
client: f.client,
|
|
177
|
+
workspaceId: "workspace",
|
|
178
|
+
nonce: receipt.nonce,
|
|
179
|
+
storage: f.storage,
|
|
180
|
+
}),
|
|
181
|
+
).rejects.toThrow("account changed");
|
|
182
|
+
}
|
|
183
|
+
});
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import type { OpenGeniBrowserClient } from "@opengeni/sdk/browser";
|
|
2
|
+
|
|
3
|
+
export const DRIVE_MCP_URL = "https://drivemcp.googleapis.com/mcp/v1";
|
|
4
|
+
export const DRIVE_CONNECT_RETURN_PARAM = "drive_connect_return";
|
|
5
|
+
const storageKey = (workspaceId: string) => `opengeni.drive-connect.v1:${workspaceId}`;
|
|
6
|
+
type Receipt = { nonce: string; attemptId: string; connectionWorkspaceId: string; itemId: string };
|
|
7
|
+
type Storage = Pick<globalThis.Storage, "getItem" | "setItem" | "removeItem">;
|
|
8
|
+
|
|
9
|
+
/** Browser state is only a locator. Completion and the account come from Connect. */
|
|
10
|
+
export async function beginDriveConnect(input: {
|
|
11
|
+
client: Pick<OpenGeniBrowserClient, "beginConnect" | "connectTransport">;
|
|
12
|
+
workspaceId: string;
|
|
13
|
+
connectionWorkspaceId?: string;
|
|
14
|
+
itemId: string;
|
|
15
|
+
reconnectAccountId?: string;
|
|
16
|
+
returnUrl: string;
|
|
17
|
+
storage: Storage;
|
|
18
|
+
}): Promise<string> {
|
|
19
|
+
const nonce = crypto.randomUUID();
|
|
20
|
+
const returnUrl = new URL(input.returnUrl);
|
|
21
|
+
returnUrl.searchParams.set(DRIVE_CONNECT_RETURN_PARAM, nonce);
|
|
22
|
+
const connectionWorkspaceId = input.connectionWorkspaceId ?? input.workspaceId;
|
|
23
|
+
const created = await input.client.beginConnect(connectionWorkspaceId, {
|
|
24
|
+
providerId: "google-drive",
|
|
25
|
+
ownership: "personal",
|
|
26
|
+
returnUrl: returnUrl.toString(),
|
|
27
|
+
idempotencyKey: crypto.randomUUID(),
|
|
28
|
+
...(input.reconnectAccountId ? { reconnectAccountId: input.reconnectAccountId } : {}),
|
|
29
|
+
});
|
|
30
|
+
if (created.state !== "credential_input")
|
|
31
|
+
throw new Error("Google Drive setup did not start. Try again.");
|
|
32
|
+
const attempt = await input.client.connectTransport().advance(connectionWorkspaceId, created.id, {
|
|
33
|
+
expectedRevision: created.revision,
|
|
34
|
+
idempotencyKey: crypto.randomUUID(),
|
|
35
|
+
action: { type: "credentials", values: {} },
|
|
36
|
+
});
|
|
37
|
+
if (attempt.nextAction.type !== "authorize")
|
|
38
|
+
throw new Error(attempt.error?.message ?? "Google Drive did not return an authorization link.");
|
|
39
|
+
input.storage.setItem(
|
|
40
|
+
storageKey(input.workspaceId),
|
|
41
|
+
JSON.stringify({
|
|
42
|
+
nonce,
|
|
43
|
+
attemptId: attempt.id,
|
|
44
|
+
connectionWorkspaceId,
|
|
45
|
+
itemId: input.itemId,
|
|
46
|
+
} satisfies Receipt),
|
|
47
|
+
);
|
|
48
|
+
return attempt.nextAction.url;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function resolveDriveConnectReturn(input: {
|
|
52
|
+
client: Pick<OpenGeniBrowserClient, "connectTransport">;
|
|
53
|
+
workspaceId: string;
|
|
54
|
+
nonce: string;
|
|
55
|
+
storage: Storage;
|
|
56
|
+
}): Promise<{ itemId: string; connectionId: string }> {
|
|
57
|
+
const key = storageKey(input.workspaceId);
|
|
58
|
+
const raw = input.storage.getItem(key);
|
|
59
|
+
let receipt: Receipt;
|
|
60
|
+
try {
|
|
61
|
+
receipt = JSON.parse(raw ?? "null");
|
|
62
|
+
if (
|
|
63
|
+
!receipt ||
|
|
64
|
+
receipt.nonce !== input.nonce ||
|
|
65
|
+
![receipt.attemptId, receipt.connectionWorkspaceId, receipt.itemId].every(
|
|
66
|
+
(value) => typeof value === "string" && value.length > 0 && value.length <= 4096,
|
|
67
|
+
)
|
|
68
|
+
)
|
|
69
|
+
throw new Error();
|
|
70
|
+
} catch {
|
|
71
|
+
throw new Error(
|
|
72
|
+
"The Google Drive setup return could not be matched. Open Drive to check your connection.",
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
const attempt = await input.client
|
|
76
|
+
.connectTransport()
|
|
77
|
+
.get(receipt.connectionWorkspaceId, receipt.attemptId);
|
|
78
|
+
if (
|
|
79
|
+
attempt.id !== receipt.attemptId ||
|
|
80
|
+
attempt.providerId !== "google-drive" ||
|
|
81
|
+
attempt.ownership !== "personal"
|
|
82
|
+
) {
|
|
83
|
+
throw new Error("The Google Drive setup account changed. Open Drive to try again.");
|
|
84
|
+
}
|
|
85
|
+
if (["failed", "expired", "cancelled"].includes(attempt.state)) {
|
|
86
|
+
input.storage.removeItem(key);
|
|
87
|
+
throw new Error(
|
|
88
|
+
attempt.error?.message ??
|
|
89
|
+
"Google Drive connection was not completed. Open Drive to try again.",
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
if (
|
|
93
|
+
attempt.state !== "complete" ||
|
|
94
|
+
!attempt.credentialsCommitted ||
|
|
95
|
+
attempt.account?.providerId !== "google-drive" ||
|
|
96
|
+
attempt.account.ownership !== "personal" ||
|
|
97
|
+
!attempt.account.id
|
|
98
|
+
) {
|
|
99
|
+
throw new Error("Google Drive setup is still incomplete. Reload to check it again.");
|
|
100
|
+
}
|
|
101
|
+
return { itemId: receipt.itemId, connectionId: attempt.account.id };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Clear only after the UI has finished enabling; never consume a newer attempt. */
|
|
105
|
+
export function completeDriveConnectReturn(
|
|
106
|
+
workspaceId: string,
|
|
107
|
+
nonce: string,
|
|
108
|
+
storage: Storage,
|
|
109
|
+
): void {
|
|
110
|
+
const key = storageKey(workspaceId);
|
|
111
|
+
try {
|
|
112
|
+
if (JSON.parse(storage.getItem(key) ?? "null")?.nonce === nonce) storage.removeItem(key);
|
|
113
|
+
} catch {
|
|
114
|
+
/* An absent or replaced browser locator grants no authority. */
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -57,6 +57,7 @@ import {
|
|
|
57
57
|
// composer. Enter queues and Cmd/Ctrl+Enter steers; failed sessions stay
|
|
58
58
|
// honest (reason + retry history) and revivable from the same composer.
|
|
59
59
|
import { LightboxProvider } from "../../timeline";
|
|
60
|
+
import { connectorLogo, connectorLogoIndex } from "../../timeline/connector-logo-index";
|
|
60
61
|
import { sessionAdmissionBlocked, sessionDisplayStatus } from "../../session-status-model";
|
|
61
62
|
import { useSessionSecrets } from "../../hooks/use-vaults";
|
|
62
63
|
import type { WorkspaceTab } from "../../components/workspace-dock";
|
|
@@ -182,7 +183,6 @@ import type {
|
|
|
182
183
|
SessionEditableArtifactsStatus,
|
|
183
184
|
} from "../components/session/editable-artifacts-workspace";
|
|
184
185
|
import {
|
|
185
|
-
normalizeProviderDomain,
|
|
186
186
|
oauthConnectionOwnership,
|
|
187
187
|
oauthConnectionRef,
|
|
188
188
|
catalogConnectionAccountSelection,
|
|
@@ -1159,20 +1159,15 @@ export function SessionRoute({
|
|
|
1159
1159
|
// The workspace shell already needs the capability catalog for session tool
|
|
1160
1160
|
// policy. Reuse that authoritative read for timeline logos instead of
|
|
1161
1161
|
// downloading the same large catalog again from the session route.
|
|
1162
|
-
const providerLogos = useMemo(
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
if (!logos.has(key)) logos.set(key, url);
|
|
1170
|
-
}
|
|
1171
|
-
}
|
|
1172
|
-
return logos;
|
|
1173
|
-
}, [context.client, context.workspaceCapabilityCatalog]);
|
|
1162
|
+
const providerLogos = useMemo(
|
|
1163
|
+
() =>
|
|
1164
|
+
connectorLogoIndex(context.workspaceCapabilityCatalog, (path) =>
|
|
1165
|
+
context.client.catalogAssetUrl(path ?? null),
|
|
1166
|
+
),
|
|
1167
|
+
[context.client, context.workspaceCapabilityCatalog],
|
|
1168
|
+
);
|
|
1174
1169
|
const resolveProviderLogo = useCallback(
|
|
1175
|
-
(domain: string) => providerLogos
|
|
1170
|
+
(domain: string) => connectorLogo(providerLogos, domain),
|
|
1176
1171
|
[providerLogos],
|
|
1177
1172
|
);
|
|
1178
1173
|
// One lineage read feeds the single composer-anchored agents surface. Events
|
|
@@ -4027,6 +4022,9 @@ function SessionChatPane(props: {
|
|
|
4027
4022
|
sessionKey={props.session.id}
|
|
4028
4023
|
menuSide="top"
|
|
4029
4024
|
connectModelsHref={`/workspaces/${encodeURIComponent(props.session.workspaceId)}/settings?section=models`}
|
|
4025
|
+
claudeSubscriptionAvailable={
|
|
4026
|
+
context.clientConfig.claudeSubscriptionEnabled === true
|
|
4027
|
+
}
|
|
4030
4028
|
codexOnly={props.session.codexCompactionMode === "remote_v2"}
|
|
4031
4029
|
onModelChange={composer.setModel}
|
|
4032
4030
|
onEffortChange={composer.setReasoningEffort}
|
|
@@ -2228,6 +2228,7 @@ function SessionModelControl({
|
|
|
2228
2228
|
error={modelCatalog.error ?? policyError}
|
|
2229
2229
|
menuSide="bottom"
|
|
2230
2230
|
connectModelsHref={`/workspaces/${encodeURIComponent(workspaceId)}/settings?section=models`}
|
|
2231
|
+
claudeSubscriptionAvailable={context.clientConfig.claudeSubscriptionEnabled === true}
|
|
2231
2232
|
onModelChange={(model) => {
|
|
2232
2233
|
onPolicyChosen();
|
|
2233
2234
|
context.setModel(model);
|
|
@@ -181,7 +181,11 @@ export function ModelPolicyPickerMenu(props: ModelPolicyPickerProps) {
|
|
|
181
181
|
) : null}
|
|
182
182
|
</>
|
|
183
183
|
) : props.connectModelsHref || rows.length > 0 ? (
|
|
184
|
-
<ConnectModelsPanel
|
|
184
|
+
<ConnectModelsPanel
|
|
185
|
+
href={props.connectModelsHref}
|
|
186
|
+
claude={props.claudeSubscriptionAvailable === true}
|
|
187
|
+
messages={messages}
|
|
188
|
+
/>
|
|
185
189
|
) : (
|
|
186
190
|
<p className={MENU_NOTE_CLASS}>{messages.noModels}</p>
|
|
187
191
|
)}
|
|
@@ -204,8 +208,14 @@ export function ModelPolicyPickerMenu(props: ModelPolicyPickerProps) {
|
|
|
204
208
|
|
|
205
209
|
const CONNECT_MODEL_OPTIONS = (
|
|
206
210
|
t: OpenGeniText,
|
|
211
|
+
claude: boolean,
|
|
207
212
|
): Array<{
|
|
208
|
-
billingClass:
|
|
213
|
+
billingClass:
|
|
214
|
+
| "codex_subscription"
|
|
215
|
+
| "claude_subscription"
|
|
216
|
+
| "supergrok_subscription"
|
|
217
|
+
| "byok"
|
|
218
|
+
| "external";
|
|
209
219
|
title: string;
|
|
210
220
|
detail: string;
|
|
211
221
|
}> => [
|
|
@@ -214,6 +224,15 @@ const CONNECT_MODEL_OPTIONS = (
|
|
|
214
224
|
title: t("modelPolicyPickerMenu.codex"),
|
|
215
225
|
detail: "ChatGPT",
|
|
216
226
|
},
|
|
227
|
+
...(claude
|
|
228
|
+
? [
|
|
229
|
+
{
|
|
230
|
+
billingClass: "claude_subscription" as const,
|
|
231
|
+
title: t("modelPolicyPicker.claude"),
|
|
232
|
+
detail: "Anthropic",
|
|
233
|
+
},
|
|
234
|
+
]
|
|
235
|
+
: []),
|
|
217
236
|
{ billingClass: "supergrok_subscription", title: "SuperGrok", detail: "xAI" },
|
|
218
237
|
{
|
|
219
238
|
billingClass: "byok",
|
|
@@ -225,6 +244,7 @@ const CONNECT_MODEL_OPTIONS = (
|
|
|
225
244
|
|
|
226
245
|
function ConnectModelsPanel(props: {
|
|
227
246
|
href: string | undefined;
|
|
247
|
+
claude: boolean;
|
|
228
248
|
messages: ReturnType<typeof defaultModelPolicyPickerMessages>;
|
|
229
249
|
}) {
|
|
230
250
|
const t = useOpenGeniText();
|
|
@@ -239,7 +259,7 @@ function ConnectModelsPanel(props: {
|
|
|
239
259
|
</p>
|
|
240
260
|
</div>
|
|
241
261
|
<div className="grid grid-cols-2 gap-1">
|
|
242
|
-
{CONNECT_MODEL_OPTIONS(t).map((option) => {
|
|
262
|
+
{CONNECT_MODEL_OPTIONS(t, props.claude).map((option) => {
|
|
243
263
|
const content = (
|
|
244
264
|
<>
|
|
245
265
|
<span className="flex size-8 shrink-0 items-center justify-center rounded-og-md bg-og-surface-2 text-og-fg">
|
|
@@ -260,7 +280,8 @@ function ConnectModelsPanel(props: {
|
|
|
260
280
|
</>
|
|
261
281
|
);
|
|
262
282
|
const className =
|
|
263
|
-
|
|
283
|
+
// An odd last tile spans both columns instead of sitting alone.
|
|
284
|
+
"flex min-w-0 items-center gap-2 rounded-og-md px-1.5 py-1.5 outline-hidden transition-colors odd:last:col-span-2 hover:bg-og-hover focus-visible:ring-2 focus-visible:ring-og-accent/40";
|
|
264
285
|
return props.href ? (
|
|
265
286
|
<a key={option.title} href={props.href} className={className}>
|
|
266
287
|
{content}
|
|
@@ -96,6 +96,8 @@ export type ModelPolicyPickerProps = {
|
|
|
96
96
|
allowModelOptions?: boolean | undefined;
|
|
97
97
|
/** Settings → Models. Shown when the catalog has no model that can run. */
|
|
98
98
|
connectModelsHref?: string | undefined;
|
|
99
|
+
/** The deployment offers Claude subscriptions; the connect panel lists Claude too. */
|
|
100
|
+
claudeSubscriptionAvailable?: boolean | undefined;
|
|
99
101
|
/** Classes for the portalled menu surface. Prefer --og-* tokens for theming. */
|
|
100
102
|
contentClassName?: string | undefined;
|
|
101
103
|
/** Inline styles for the portalled menu, applied after inherited --og-* tokens. */
|
|
@@ -83,7 +83,7 @@ function ScopedCard({
|
|
|
83
83
|
onConfigured,
|
|
84
84
|
dialogOnly = false,
|
|
85
85
|
onClose,
|
|
86
|
-
personalOnly = false,
|
|
86
|
+
personalOnly: configuredPersonalOnly = false,
|
|
87
87
|
connectLabel,
|
|
88
88
|
dialogSubtitle,
|
|
89
89
|
description: hostDescription,
|
|
@@ -96,6 +96,9 @@ function ScopedCard({
|
|
|
96
96
|
);
|
|
97
97
|
const view = useConnect(controller);
|
|
98
98
|
const [item, setItem] = useState<CapabilityCatalogItem | null>(null);
|
|
99
|
+
const personalOnly =
|
|
100
|
+
configuredPersonalOnly ||
|
|
101
|
+
(item?.mcpUrl ?? item?.endpointUrl) === "https://drivemcp.googleapis.com/mcp/v1";
|
|
99
102
|
const [logo, setLogo] = useState<string | null>(null);
|
|
100
103
|
const [expanded, setExpanded] = useState(dialogOnly);
|
|
101
104
|
const [complete, setComplete] = useState(false);
|
|
@@ -151,7 +154,9 @@ function ScopedCard({
|
|
|
151
154
|
setOwnership(resolved.connectionRef.subjectScope === "subject" ? "personal" : "workspace");
|
|
152
155
|
else if (
|
|
153
156
|
!item &&
|
|
154
|
-
(personalOnly ||
|
|
157
|
+
(personalOnly ||
|
|
158
|
+
(resolved.mcpUrl ?? resolved.endpointUrl) === "https://drivemcp.googleapis.com/mcp/v1" ||
|
|
159
|
+
resolved.metadata?.defaultConnectionOwnership === "personal")
|
|
155
160
|
)
|
|
156
161
|
setOwnership("personal");
|
|
157
162
|
let accountReady = false;
|
|
@@ -399,7 +404,8 @@ function ScopedCard({
|
|
|
399
404
|
}
|
|
400
405
|
startInput.current = input;
|
|
401
406
|
let attempt = await controller.begin({
|
|
402
|
-
providerId:
|
|
407
|
+
providerId:
|
|
408
|
+
mcpUrl === "https://drivemcp.googleapis.com/mcp/v1" ? "google-drive" : "mcp-oauth",
|
|
403
409
|
ownership,
|
|
404
410
|
returnUrl,
|
|
405
411
|
idempotencyKey: startKey.current,
|
|
@@ -409,7 +415,10 @@ function ScopedCard({
|
|
|
409
415
|
remember(attempt.id);
|
|
410
416
|
if (attempt.state === "credential_input")
|
|
411
417
|
attempt = await controller.advance(
|
|
412
|
-
{
|
|
418
|
+
{
|
|
419
|
+
type: "credentials",
|
|
420
|
+
values: mcpUrl === "https://drivemcp.googleapis.com/mcp/v1" ? {} : { mcpUrl },
|
|
421
|
+
},
|
|
413
422
|
advanceKey.current,
|
|
414
423
|
);
|
|
415
424
|
if (!active()) return;
|
|
@@ -4,7 +4,12 @@ import gmailLogo from "./assets/gmail.ico";
|
|
|
4
4
|
import outlookLogo from "./assets/outlook.ico";
|
|
5
5
|
|
|
6
6
|
/** Passive service identity only. Never selects an account or grants tool access. */
|
|
7
|
-
const services: Record<string, { name: string; logo: string; mcpUrl?: string }> = {
|
|
7
|
+
const services: Record<string, { name: string; logo: string | null; mcpUrl?: string }> = {
|
|
8
|
+
"google-drive": {
|
|
9
|
+
name: "Google Drive",
|
|
10
|
+
logo: null,
|
|
11
|
+
mcpUrl: "https://drivemcp.googleapis.com/mcp/v1",
|
|
12
|
+
},
|
|
8
13
|
gmail: { name: "Gmail", logo: gmailLogo, mcpUrl: "https://gmailmcp.googleapis.com/mcp/v1" },
|
|
9
14
|
"microsoft-outlook-mail": { name: "Outlook Mail", logo: outlookLogo },
|
|
10
15
|
"microsoft-outlook-calendar": { name: "Outlook Calendar", logo: outlookLogo },
|
package/src/messages/en.ts
CHANGED
|
@@ -1817,7 +1817,7 @@ export const englishMessages = {
|
|
|
1817
1817
|
"modelPolicy.billedToTheWorkspaceProvider": "Billed to the workspace provider account",
|
|
1818
1818
|
"modelPolicy.billedToTheWorkspaceVercel": "Billed to the workspace Vercel account",
|
|
1819
1819
|
"modelPolicy.blockedByWorkspacePolicy": "Blocked by workspace policy",
|
|
1820
|
-
"modelPolicy.claudeSubscription": "Claude
|
|
1820
|
+
"modelPolicy.claudeSubscription": "Claude",
|
|
1821
1821
|
"modelPolicy.claudeSubscriptionExternalBilling": "Claude subscription · external billing",
|
|
1822
1822
|
"modelPolicy.codex": "Codex",
|
|
1823
1823
|
"modelPolicy.codexSubscriptionExternalBilling": "Codex subscription · external billing",
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
|
|
3
|
+
import { connectorLogo, connectorLogoIndex } from "./connector-logo-index";
|
|
4
|
+
|
|
5
|
+
const url = (path: string | null | undefined) => (path ? `/v1/${path}` : null);
|
|
6
|
+
|
|
7
|
+
describe("connectorLogoIndex", () => {
|
|
8
|
+
test("a connector's server host finds the provider's logo", () => {
|
|
9
|
+
const index = connectorLogoIndex(
|
|
10
|
+
[
|
|
11
|
+
{
|
|
12
|
+
providerDomain: "linear.app",
|
|
13
|
+
mcpUrl: "https://mcp.linear.app/mcp",
|
|
14
|
+
logoAssetPath: "logos/linear.png",
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
providerDomain: "notion.com",
|
|
18
|
+
connectionRef: { providerDomain: "mcp.notion.com" },
|
|
19
|
+
logoAssetPath: "logos/notion.png",
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
url,
|
|
23
|
+
);
|
|
24
|
+
expect(connectorLogo(index, "mcp.linear.app")).toBe("/v1/logos/linear.png");
|
|
25
|
+
expect(connectorLogo(index, "Linear.app")).toBe("/v1/logos/linear.png");
|
|
26
|
+
expect(connectorLogo(index, "mcp.notion.com")).toBe("/v1/logos/notion.png");
|
|
27
|
+
expect(connectorLogo(index, "example.com")).toBeNull();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
test("a provider's own domain wins over another entry's server host", () => {
|
|
31
|
+
const index = connectorLogoIndex(
|
|
32
|
+
[
|
|
33
|
+
{
|
|
34
|
+
providerDomain: "relay.example",
|
|
35
|
+
mcpUrl: "https://github.com/mcp",
|
|
36
|
+
logoAssetPath: "logos/relay.png",
|
|
37
|
+
},
|
|
38
|
+
{ providerDomain: "github.com", logoAssetPath: "logos/github.png" },
|
|
39
|
+
{ providerDomain: "nologo.app", logoAssetPath: null },
|
|
40
|
+
],
|
|
41
|
+
url,
|
|
42
|
+
);
|
|
43
|
+
expect(connectorLogo(index, "github.com")).toBe("/v1/logos/github.png");
|
|
44
|
+
expect(connectorLogo(index, "nologo.app")).toBeNull();
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Which logo belongs to a connector domain. Tool steps and sign-in cards name
|
|
3
|
+
* their connector by the domain of the connection or MCP server they used
|
|
4
|
+
* (Linear's is mcp.linear.app), while the catalog files the logo under the
|
|
5
|
+
* provider's own domain (linear.app). Indexing every domain a catalog entry is
|
|
6
|
+
* known by lets any of them find the logo. Framework-neutral, so web, native
|
|
7
|
+
* and embedded surfaces resolve logos the same way.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
/** The catalog fields that name a connector's domains and logo. */
|
|
11
|
+
export type ConnectorLogoSource = {
|
|
12
|
+
providerDomain?: string | null | undefined;
|
|
13
|
+
mcpUrl?: string | null | undefined;
|
|
14
|
+
connectionRef?: { providerDomain?: string | null | undefined } | null | undefined;
|
|
15
|
+
logoAssetPath?: string | null | undefined;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/** "WWW.Linear.app " → "linear.app". */
|
|
19
|
+
export function normalizeConnectorDomain(domain: string): string {
|
|
20
|
+
return domain
|
|
21
|
+
.trim()
|
|
22
|
+
.toLowerCase()
|
|
23
|
+
.replace(/^www\./, "");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function urlHost(url: string | null | undefined): string | null {
|
|
27
|
+
if (!url) return null;
|
|
28
|
+
try {
|
|
29
|
+
return new URL(url).hostname || null;
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Every domain each catalog entry is known by, mapped to its logo URL. The
|
|
37
|
+
* provider's own domain is claimed first across the whole catalog, so another
|
|
38
|
+
* entry's server host never takes over a provider's logo.
|
|
39
|
+
*/
|
|
40
|
+
export function connectorLogoIndex(
|
|
41
|
+
catalog: readonly ConnectorLogoSource[],
|
|
42
|
+
logoUrl: (logoAssetPath: string | null | undefined) => string | null | undefined,
|
|
43
|
+
): ReadonlyMap<string, string> {
|
|
44
|
+
const logos = new Map<string, string>();
|
|
45
|
+
const entries = catalog.flatMap((item) => {
|
|
46
|
+
const url = logoUrl(item.logoAssetPath);
|
|
47
|
+
return url ? [{ item, url }] : [];
|
|
48
|
+
});
|
|
49
|
+
const claim = (domain: string | null | undefined, url: string) => {
|
|
50
|
+
if (!domain) return;
|
|
51
|
+
const key = normalizeConnectorDomain(domain);
|
|
52
|
+
if (key && !logos.has(key)) logos.set(key, url);
|
|
53
|
+
};
|
|
54
|
+
for (const { item, url } of entries) claim(item.providerDomain, url);
|
|
55
|
+
for (const { item, url } of entries) {
|
|
56
|
+
claim(item.connectionRef?.providerDomain, url);
|
|
57
|
+
claim(urlHost(item.mcpUrl), url);
|
|
58
|
+
}
|
|
59
|
+
return logos;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** The logo for a connector domain from an index built by `connectorLogoIndex`. */
|
|
63
|
+
export function connectorLogo(
|
|
64
|
+
index: ReadonlyMap<string, string>,
|
|
65
|
+
domain: string | null | undefined,
|
|
66
|
+
): string | null {
|
|
67
|
+
return domain ? (index.get(normalizeConnectorDomain(domain)) ?? null) : null;
|
|
68
|
+
}
|
package/src/timeline-model.ts
CHANGED
|
@@ -32,6 +32,12 @@ export {
|
|
|
32
32
|
export type { ActivityGroup, ReadableWorkOptions, TurnGroup } from "./timeline/work-presentation";
|
|
33
33
|
export { timelineGroupContainsPresentedImage } from "./timeline/presented-image";
|
|
34
34
|
export { mcpToolLeaf, toolDisplayName } from "./timeline/tool-display-name";
|
|
35
|
+
export {
|
|
36
|
+
connectorLogo,
|
|
37
|
+
connectorLogoIndex,
|
|
38
|
+
normalizeConnectorDomain,
|
|
39
|
+
} from "./timeline/connector-logo-index";
|
|
40
|
+
export type { ConnectorLogoSource } from "./timeline/connector-logo-index";
|
|
35
41
|
export {
|
|
36
42
|
BUILT_IN_TOOL_STEP_NAMES,
|
|
37
43
|
builtInToolStep,
|