@vertesia/ui 1.4.0-dev.20260615.051508Z → 1.4.0-dev.20260629.130134Z
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/lib/core/components/shadcn/dropdown.d.ts.map +1 -1
- package/lib/core/components/shadcn/dropdown.js +6 -2
- package/lib/core/components/shadcn/dropdown.js.map +1 -1
- package/lib/core/components/shadcn/filters/filterBar.d.ts.map +1 -1
- package/lib/core/components/shadcn/filters/filterBar.js +9 -2
- package/lib/core/components/shadcn/filters/filterBar.js.map +1 -1
- package/lib/core/components/shadcn/modal/ConfirmModal.d.ts +16 -1
- package/lib/core/components/shadcn/modal/ConfirmModal.d.ts.map +1 -1
- package/lib/core/components/shadcn/modal/ConfirmModal.js +25 -3
- package/lib/core/components/shadcn/modal/ConfirmModal.js.map +1 -1
- package/lib/core/components/shadcn/modal/dialog.js +1 -1
- package/lib/core/components/shadcn/modal/dialog.js.map +1 -1
- package/lib/core/components/shadcn/popover.d.ts.map +1 -1
- package/lib/core/components/shadcn/popover.js +3 -1
- package/lib/core/components/shadcn/popover.js.map +1 -1
- package/lib/core/components/shadcn/switch.js +3 -3
- package/lib/core/components/shadcn/switch.js.map +1 -1
- package/lib/core/components/shadcn/tabs.js +1 -1
- package/lib/core/components/shadcn/tabs.js.map +1 -1
- package/lib/core/components/shadcn/tooltip.d.ts.map +1 -1
- package/lib/core/components/shadcn/tooltip.js +3 -1
- package/lib/core/components/shadcn/tooltip.js.map +1 -1
- package/lib/env/index.d.ts +0 -19
- package/lib/env/index.d.ts.map +1 -1
- package/lib/env/index.js +0 -6
- package/lib/env/index.js.map +1 -1
- package/lib/features/agent/PayloadBuilder.d.ts +4 -0
- package/lib/features/agent/PayloadBuilder.d.ts.map +1 -1
- package/lib/features/agent/PayloadBuilder.js +16 -0
- package/lib/features/agent/PayloadBuilder.js.map +1 -1
- package/lib/features/agent/chat/AgentApprovalModeSelector.d.ts +10 -0
- package/lib/features/agent/chat/AgentApprovalModeSelector.d.ts.map +1 -0
- package/lib/features/agent/chat/AgentApprovalModeSelector.js +50 -0
- package/lib/features/agent/chat/AgentApprovalModeSelector.js.map +1 -0
- package/lib/features/agent/chat/AgentChatFixtureReplay.d.ts +2 -1
- package/lib/features/agent/chat/AgentChatFixtureReplay.d.ts.map +1 -1
- package/lib/features/agent/chat/AgentChatFixtureReplay.js +3 -3
- package/lib/features/agent/chat/AgentChatFixtureReplay.js.map +1 -1
- package/lib/features/agent/chat/AgentRequestInputOverlay.d.ts +5 -2
- package/lib/features/agent/chat/AgentRequestInputOverlay.d.ts.map +1 -1
- package/lib/features/agent/chat/AgentRequestInputOverlay.js +41 -7
- package/lib/features/agent/chat/AgentRequestInputOverlay.js.map +1 -1
- package/lib/features/agent/chat/AgentRightPanel.d.ts.map +1 -1
- package/lib/features/agent/chat/AgentRightPanel.js +133 -12
- package/lib/features/agent/chat/AgentRightPanel.js.map +1 -1
- package/lib/features/agent/chat/AnimatedThinkingDots.js +1 -1
- package/lib/features/agent/chat/AnimatedThinkingDots.js.map +1 -1
- package/lib/features/agent/chat/ArtifactsTab.d.ts.map +1 -1
- package/lib/features/agent/chat/ArtifactsTab.js +11 -65
- package/lib/features/agent/chat/ArtifactsTab.js.map +1 -1
- package/lib/features/agent/chat/AskUserWidget.d.ts +3 -1
- package/lib/features/agent/chat/AskUserWidget.d.ts.map +1 -1
- package/lib/features/agent/chat/AskUserWidget.js +4 -3
- package/lib/features/agent/chat/AskUserWidget.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentConversation.d.ts +10 -2
- package/lib/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentConversation.js +526 -117
- package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/ActiveWorkstreamsSummary.d.ts +8 -0
- package/lib/features/agent/chat/ModernAgentOutput/ActiveWorkstreamsSummary.d.ts.map +1 -0
- package/lib/features/agent/chat/ModernAgentOutput/ActiveWorkstreamsSummary.js +19 -0
- package/lib/features/agent/chat/ModernAgentOutput/ActiveWorkstreamsSummary.js.map +1 -0
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts +8 -2
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +551 -92
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.js +7 -6
- package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/Header.js +2 -2
- package/lib/features/agent/chat/ModernAgentOutput/Header.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageDeliveryStatus.d.ts +8 -0
- package/lib/features/agent/chat/ModernAgentOutput/MessageDeliveryStatus.d.ts.map +1 -0
- package/lib/features/agent/chat/ModernAgentOutput/MessageDeliveryStatus.js +61 -0
- package/lib/features/agent/chat/ModernAgentOutput/MessageDeliveryStatus.js.map +1 -0
- package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts +15 -2
- package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js +28 -38
- package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js +3 -2
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/SlideInPanel.js +2 -1
- package/lib/features/agent/chat/ModernAgentOutput/SlideInPanel.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/SlidingMessages.js +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/SlidingMessages.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.d.ts +5 -0
- package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.js +59 -4
- package/lib/features/agent/chat/ModernAgentOutput/SummaryConversation.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/WorkstreamTabs.js +2 -2
- package/lib/features/agent/chat/ModernAgentOutput/WorkstreamTabs.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.d.ts +9 -0
- package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.js +180 -0
- package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/utils.d.ts +6 -0
- package/lib/features/agent/chat/ModernAgentOutput/utils.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/utils.js +92 -2
- package/lib/features/agent/chat/ModernAgentOutput/utils.js.map +1 -1
- package/lib/features/agent/chat/clipboardFiles.d.ts +15 -0
- package/lib/features/agent/chat/clipboardFiles.d.ts.map +1 -0
- package/lib/features/agent/chat/clipboardFiles.js +35 -0
- package/lib/features/agent/chat/clipboardFiles.js.map +1 -0
- package/lib/features/agent/chat/hooks/useAgentStream.d.ts +6 -0
- package/lib/features/agent/chat/hooks/useAgentStream.d.ts.map +1 -1
- package/lib/features/agent/chat/hooks/useAgentStream.js +218 -37
- package/lib/features/agent/chat/hooks/useAgentStream.js.map +1 -1
- package/lib/features/agent/chat/hooks/useArtifacts.d.ts +1 -8
- package/lib/features/agent/chat/hooks/useArtifacts.d.ts.map +1 -1
- package/lib/features/agent/chat/hooks/useArtifacts.js +10 -28
- package/lib/features/agent/chat/hooks/useArtifacts.js.map +1 -1
- package/lib/features/agent/chat/index.d.ts +2 -1
- package/lib/features/agent/chat/index.d.ts.map +1 -1
- package/lib/features/agent/chat/index.js +2 -1
- package/lib/features/agent/chat/index.js.map +1 -1
- package/lib/features/agent/chat/workstreams.d.ts +1 -0
- package/lib/features/agent/chat/workstreams.d.ts.map +1 -1
- package/lib/features/agent/chat/workstreams.js +28 -0
- package/lib/features/agent/chat/workstreams.js.map +1 -1
- package/lib/features/facets/AgentRunnerFacetsNav.d.ts +10 -1
- package/lib/features/facets/AgentRunnerFacetsNav.d.ts.map +1 -1
- package/lib/features/facets/AgentRunnerFacetsNav.js +6 -3
- package/lib/features/facets/AgentRunnerFacetsNav.js.map +1 -1
- package/lib/features/facets/CollectionsFacetsNav.d.ts +10 -1
- package/lib/features/facets/CollectionsFacetsNav.d.ts.map +1 -1
- package/lib/features/facets/CollectionsFacetsNav.js +14 -5
- package/lib/features/facets/CollectionsFacetsNav.js.map +1 -1
- package/lib/features/facets/PromptsFacetsNav.d.ts +10 -1
- package/lib/features/facets/PromptsFacetsNav.d.ts.map +1 -1
- package/lib/features/facets/PromptsFacetsNav.js +14 -5
- package/lib/features/facets/PromptsFacetsNav.js.map +1 -1
- package/lib/features/facets/RunsFacetsNav.d.ts +10 -1
- package/lib/features/facets/RunsFacetsNav.d.ts.map +1 -1
- package/lib/features/facets/RunsFacetsNav.js +14 -5
- package/lib/features/facets/RunsFacetsNav.js.map +1 -1
- package/lib/features/index.d.ts +0 -1
- package/lib/features/index.d.ts.map +1 -1
- package/lib/features/index.js +0 -1
- package/lib/features/index.js.map +1 -1
- package/lib/features/oauth/McpConnectionsButton.d.ts +30 -0
- package/lib/features/oauth/McpConnectionsButton.d.ts.map +1 -0
- package/lib/features/oauth/McpConnectionsButton.js +70 -0
- package/lib/features/oauth/McpConnectionsButton.js.map +1 -0
- package/lib/features/oauth/McpConnectionsDialog.d.ts +25 -0
- package/lib/features/oauth/McpConnectionsDialog.d.ts.map +1 -0
- package/lib/features/oauth/McpConnectionsDialog.js +26 -0
- package/lib/features/oauth/McpConnectionsDialog.js.map +1 -0
- package/lib/features/oauth/RemoteMcpConnectionButton.d.ts.map +1 -1
- package/lib/features/oauth/RemoteMcpConnectionButton.js +14 -6
- package/lib/features/oauth/RemoteMcpConnectionButton.js.map +1 -1
- package/lib/features/oauth/index.d.ts +3 -0
- package/lib/features/oauth/index.d.ts.map +1 -1
- package/lib/features/oauth/index.js +3 -0
- package/lib/features/oauth/index.js.map +1 -1
- package/lib/features/oauth/useMcpConnections.d.ts +44 -0
- package/lib/features/oauth/useMcpConnections.d.ts.map +1 -0
- package/lib/features/oauth/useMcpConnections.js +168 -0
- package/lib/features/oauth/useMcpConnections.js.map +1 -0
- package/lib/features/permissions/UserPermissionsProvider.d.ts +4 -6
- package/lib/features/permissions/UserPermissionsProvider.d.ts.map +1 -1
- package/lib/features/permissions/UserPermissionsProvider.js +40 -42
- package/lib/features/permissions/UserPermissionsProvider.js.map +1 -1
- package/lib/features/store/objects/DocumentPreviewPanel.d.ts.map +1 -1
- package/lib/features/store/objects/DocumentPreviewPanel.js +2 -1
- package/lib/features/store/objects/DocumentPreviewPanel.js.map +1 -1
- package/lib/features/store/objects/DocumentQuickFilter.d.ts +23 -0
- package/lib/features/store/objects/DocumentQuickFilter.d.ts.map +1 -0
- package/lib/features/store/objects/DocumentQuickFilter.js +30 -0
- package/lib/features/store/objects/DocumentQuickFilter.js.map +1 -0
- package/lib/features/store/objects/DocumentSearchResults.d.ts.map +1 -1
- package/lib/features/store/objects/DocumentSearchResults.js +17 -1
- package/lib/features/store/objects/DocumentSearchResults.js.map +1 -1
- package/lib/features/store/objects/components/ContentOverview.d.ts.map +1 -1
- package/lib/features/store/objects/components/ContentOverview.js +17 -74
- package/lib/features/store/objects/components/ContentOverview.js.map +1 -1
- package/lib/features/store/objects/layout/DocumentTableColumn.d.ts +7 -1
- package/lib/features/store/objects/layout/DocumentTableColumn.d.ts.map +1 -1
- package/lib/features/store/objects/layout/DocumentTableColumn.js +27 -3
- package/lib/features/store/objects/layout/DocumentTableColumn.js.map +1 -1
- package/lib/features/store/objects/layout/documentLayout.js +1 -1
- package/lib/features/store/objects/layout/renderers.d.ts +2 -1
- package/lib/features/store/objects/layout/renderers.d.ts.map +1 -1
- package/lib/features/store/objects/layout/renderers.js +27 -20
- package/lib/features/store/objects/layout/renderers.js.map +1 -1
- package/lib/features/store/types/ContentObjectTypesSearch.d.ts.map +1 -1
- package/lib/features/store/types/ContentObjectTypesSearch.js +4 -1
- package/lib/features/store/types/ContentObjectTypesSearch.js.map +1 -1
- package/lib/features/store/types/ContentObjectTypesTable.d.ts +2 -1
- package/lib/features/store/types/ContentObjectTypesTable.d.ts.map +1 -1
- package/lib/features/store/types/ContentObjectTypesTable.js +8 -3
- package/lib/features/store/types/ContentObjectTypesTable.js.map +1 -1
- package/lib/i18n/instance.d.ts.map +1 -1
- package/lib/i18n/instance.js +1 -0
- package/lib/i18n/instance.js.map +1 -1
- package/lib/i18n/locales/ar.json +72 -45
- package/lib/i18n/locales/de.json +65 -34
- package/lib/i18n/locales/en.json +63 -47
- package/lib/i18n/locales/es.json +65 -35
- package/lib/i18n/locales/fr.json +65 -35
- package/lib/i18n/locales/it.json +72 -42
- package/lib/i18n/locales/ja.json +72 -41
- package/lib/i18n/locales/ko.json +72 -41
- package/lib/i18n/locales/pt.json +72 -42
- package/lib/i18n/locales/ru.json +72 -43
- package/lib/i18n/locales/tr.json +72 -41
- package/lib/i18n/locales/zh-TW.json +72 -41
- package/lib/i18n/locales/zh.json +72 -41
- package/lib/session/UserSession.d.ts +0 -6
- package/lib/session/UserSession.d.ts.map +1 -1
- package/lib/session/UserSession.js +0 -15
- package/lib/session/UserSession.js.map +1 -1
- package/lib/session/UserSessionProvider.d.ts.map +1 -1
- package/lib/session/UserSessionProvider.js +39 -98
- package/lib/session/UserSessionProvider.js.map +1 -1
- package/lib/session/auth/composable.d.ts.map +1 -1
- package/lib/session/auth/composable.js +3 -33
- package/lib/session/auth/composable.js.map +1 -1
- package/lib/shell/login/SignInPrimitives.d.ts.map +1 -1
- package/lib/shell/login/SignInPrimitives.js +3 -2
- package/lib/shell/login/SignInPrimitives.js.map +1 -1
- package/lib/vertesia-ui-core.js +1 -1
- package/lib/vertesia-ui-core.js.map +1 -1
- package/lib/vertesia-ui-env.js +1 -1
- package/lib/vertesia-ui-env.js.map +1 -1
- package/lib/vertesia-ui-features.js +1 -1
- package/lib/vertesia-ui-features.js.map +1 -1
- package/lib/vertesia-ui-i18n.js +1 -1
- package/lib/vertesia-ui-i18n.js.map +1 -1
- package/lib/vertesia-ui-session.js +1 -1
- package/lib/vertesia-ui-session.js.map +1 -1
- package/lib/vertesia-ui-shell.js +1 -1
- package/lib/vertesia-ui-shell.js.map +1 -1
- package/lib/vertesia-ui-widgets.js +1 -1
- package/lib/vertesia-ui-widgets.js.map +1 -1
- package/lib/widgets/schema-editor/editor/Editable.js +1 -1
- package/lib/widgets/schema-editor/editor/Editable.js.map +1 -1
- package/llms.txt +70 -70
- package/package.json +35 -32
- package/src/core/components/shadcn/dropdown.tsx +2 -0
- package/src/core/components/shadcn/filters/filterBar.tsx +9 -2
- package/src/core/components/shadcn/modal/ConfirmModal.tsx +65 -3
- package/src/core/components/shadcn/modal/dialog.tsx +1 -1
- package/src/core/components/shadcn/popover.tsx +1 -0
- package/src/core/components/shadcn/switch.tsx +4 -4
- package/src/core/components/shadcn/tabs.tsx +1 -1
- package/src/core/components/shadcn/tooltip.tsx +1 -0
- package/src/env/index.ts +0 -24
- package/src/features/agent/PayloadBuilder.tsx +18 -0
- package/src/features/agent/chat/AgentApprovalModeSelector.tsx +130 -0
- package/src/features/agent/chat/AgentChatFixtureReplay.test.tsx +22 -2
- package/src/features/agent/chat/AgentChatFixtureReplay.tsx +33 -27
- package/src/features/agent/chat/AgentRequestInputOverlay.test.tsx +122 -0
- package/src/features/agent/chat/AgentRequestInputOverlay.tsx +91 -17
- package/src/features/agent/chat/AgentRightPanel.tsx +238 -50
- package/src/features/agent/chat/AnimatedThinkingDots.tsx +1 -1
- package/src/features/agent/chat/ArtifactsTab.tsx +21 -155
- package/src/features/agent/chat/AskUserWidget.tsx +6 -2
- package/src/features/agent/chat/ModernAgentConversation.test.tsx +1157 -47
- package/src/features/agent/chat/ModernAgentConversation.tsx +703 -136
- package/src/features/agent/chat/ModernAgentOutput/ActiveWorkstreamsSummary.tsx +74 -0
- package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.test.tsx +1420 -126
- package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +934 -111
- package/src/features/agent/chat/ModernAgentOutput/AttachmentPreview.tsx +31 -40
- package/src/features/agent/chat/ModernAgentOutput/Header.tsx +28 -28
- package/src/features/agent/chat/ModernAgentOutput/MessageDeliveryStatus.tsx +83 -0
- package/src/features/agent/chat/ModernAgentOutput/MessageInput.test.tsx +38 -2
- package/src/features/agent/chat/ModernAgentOutput/MessageInput.tsx +121 -98
- package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +7 -1
- package/src/features/agent/chat/ModernAgentOutput/SlideInPanel.tsx +3 -3
- package/src/features/agent/chat/ModernAgentOutput/SlidingMessages.tsx +1 -1
- package/src/features/agent/chat/ModernAgentOutput/SummaryConversation.ts +82 -2
- package/src/features/agent/chat/ModernAgentOutput/WorkstreamTabs.tsx +2 -2
- package/src/features/agent/chat/ModernAgentOutput/requestInputMessages.ts +216 -0
- package/src/features/agent/chat/ModernAgentOutput/utils.test.ts +149 -0
- package/src/features/agent/chat/ModernAgentOutput/utils.ts +103 -2
- package/src/features/agent/chat/clipboardFiles.ts +33 -0
- package/src/features/agent/chat/hooks/useAgentStream.test.tsx +355 -4
- package/src/features/agent/chat/hooks/useAgentStream.ts +270 -43
- package/src/features/agent/chat/hooks/useArtifacts.ts +11 -44
- package/src/features/agent/chat/index.ts +10 -1
- package/src/features/agent/chat/workstreams.ts +42 -0
- package/src/features/facets/AgentRunnerFacetsNav.tsx +23 -3
- package/src/features/facets/CollectionsFacetsNav.tsx +30 -5
- package/src/features/facets/PromptsFacetsNav.tsx +30 -5
- package/src/features/facets/RunsFacetsNav.tsx +32 -5
- package/src/features/index.ts +0 -1
- package/src/features/oauth/McpConnectionsButton.test.tsx +248 -0
- package/src/features/oauth/McpConnectionsButton.tsx +269 -0
- package/src/features/oauth/McpConnectionsDialog.tsx +142 -0
- package/src/features/oauth/RemoteMcpConnectionButton.tsx +37 -14
- package/src/features/oauth/index.ts +3 -0
- package/src/features/oauth/useMcpConnections.test.ts +94 -0
- package/src/features/oauth/useMcpConnections.ts +210 -0
- package/src/features/permissions/UserPermissionsProvider.tsx +41 -59
- package/src/features/store/objects/DocumentPreviewPanel.tsx +4 -2
- package/src/features/store/objects/DocumentQuickFilter.tsx +72 -0
- package/src/features/store/objects/DocumentSearchResults.tsx +30 -11
- package/src/features/store/objects/components/ContentOverview.tsx +30 -99
- package/src/features/store/objects/layout/DocumentTableColumn.tsx +29 -4
- package/src/features/store/objects/layout/documentLayout.tsx +1 -1
- package/src/features/store/objects/layout/renderers.tsx +80 -20
- package/src/features/store/types/ContentObjectTypesSearch.tsx +7 -1
- package/src/features/store/types/ContentObjectTypesTable.tsx +34 -4
- package/src/i18n/instance.ts +1 -0
- package/src/i18n/locales/ar.json +72 -45
- package/src/i18n/locales/de.json +65 -34
- package/src/i18n/locales/en.json +63 -47
- package/src/i18n/locales/es.json +65 -35
- package/src/i18n/locales/fr.json +65 -35
- package/src/i18n/locales/it.json +72 -42
- package/src/i18n/locales/ja.json +72 -41
- package/src/i18n/locales/ko.json +72 -41
- package/src/i18n/locales/pt.json +72 -42
- package/src/i18n/locales/ru.json +72 -43
- package/src/i18n/locales/tr.json +72 -41
- package/src/i18n/locales/zh-TW.json +72 -41
- package/src/i18n/locales/zh.json +72 -41
- package/src/i18n/rtl.test.ts +57 -1
- package/src/session/UserSession.ts +0 -16
- package/src/session/UserSessionProvider.tsx +51 -123
- package/src/session/auth/composable.ts +3 -34
- package/src/shell/login/SignInPrimitives.tsx +3 -2
- package/src/widgets/schema-editor/editor/Editable.tsx +1 -1
- package/lib/features/document-viewer/UniversalDocumentViewer.d.ts +0 -73
- package/lib/features/document-viewer/UniversalDocumentViewer.d.ts.map +0 -1
- package/lib/features/document-viewer/UniversalDocumentViewer.js +0 -415
- package/lib/features/document-viewer/UniversalDocumentViewer.js.map +0 -1
- package/lib/features/document-viewer/index.d.ts +0 -2
- package/lib/features/document-viewer/index.d.ts.map +0 -1
- package/lib/features/document-viewer/index.js +0 -2
- package/lib/features/document-viewer/index.js.map +0 -1
- package/src/features/document-viewer/UniversalDocumentViewer.test.tsx +0 -123
- package/src/features/document-viewer/UniversalDocumentViewer.tsx +0 -746
- package/src/features/document-viewer/index.ts +0 -1
- package/src/session/auth/composable.test.ts +0 -79
|
@@ -7,6 +7,7 @@ import { ModernAgentConversation } from './ModernAgentConversation';
|
|
|
7
7
|
|
|
8
8
|
const mocks = vi.hoisted(() => ({
|
|
9
9
|
addOptimisticMessage: vi.fn(),
|
|
10
|
+
updateOptimisticMessageStatus: vi.fn(),
|
|
10
11
|
removeOptimisticMessages: vi.fn(),
|
|
11
12
|
reconnect: vi.fn(),
|
|
12
13
|
restart: vi.fn(),
|
|
@@ -19,6 +20,8 @@ const mocks = vi.hoisted(() => ({
|
|
|
19
20
|
useAgentPlans: vi.fn(),
|
|
20
21
|
useDocumentPanel: vi.fn(),
|
|
21
22
|
useFileProcessing: vi.fn(),
|
|
23
|
+
getActiveWorkstreams: vi.fn(),
|
|
24
|
+
retrieve: vi.fn(),
|
|
22
25
|
}));
|
|
23
26
|
|
|
24
27
|
vi.mock('@vertesia/ui/session', () => ({
|
|
@@ -27,7 +30,8 @@ vi.mock('@vertesia/ui/session', () => ({
|
|
|
27
30
|
agents: {
|
|
28
31
|
restart: mocks.restart,
|
|
29
32
|
sendSignal: mocks.sendSignal,
|
|
30
|
-
getActiveWorkstreams:
|
|
33
|
+
getActiveWorkstreams: mocks.getActiveWorkstreams,
|
|
34
|
+
retrieve: mocks.retrieve,
|
|
31
35
|
},
|
|
32
36
|
},
|
|
33
37
|
project: undefined,
|
|
@@ -58,12 +62,25 @@ vi.mock('./ModernAgentOutput/MessageInput', () => ({
|
|
|
58
62
|
activeWorkstreams?: Array<{ workstream_id: string; status: string }>;
|
|
59
63
|
isCompleted?: boolean;
|
|
60
64
|
isStreaming?: boolean;
|
|
65
|
+
placeholder?: string;
|
|
66
|
+
contextWindowUsage?: {
|
|
67
|
+
usedTokens: number;
|
|
68
|
+
checkpointTokens: number;
|
|
69
|
+
usedPercent: number;
|
|
70
|
+
remainingPercent: number;
|
|
71
|
+
};
|
|
72
|
+
onCompactContext?: () => void;
|
|
73
|
+
isCompactingContext?: boolean;
|
|
74
|
+
approvalModeSlot?: React.ReactNode;
|
|
61
75
|
}) => {
|
|
62
76
|
mocks.messageInputProps(props);
|
|
63
77
|
return (
|
|
64
|
-
<
|
|
65
|
-
|
|
66
|
-
|
|
78
|
+
<div>
|
|
79
|
+
{props.approvalModeSlot}
|
|
80
|
+
<button type="button" onClick={() => props.onSend('follow up')}>
|
|
81
|
+
composer send
|
|
82
|
+
</button>
|
|
83
|
+
</div>
|
|
67
84
|
);
|
|
68
85
|
},
|
|
69
86
|
}));
|
|
@@ -72,17 +89,34 @@ vi.mock('./ModernAgentOutput/AllMessagesMixed', () => ({
|
|
|
72
89
|
default: ({
|
|
73
90
|
messages,
|
|
74
91
|
streamingMessages,
|
|
92
|
+
isCompleted,
|
|
75
93
|
bottomRef,
|
|
76
94
|
onSendMessage,
|
|
95
|
+
showInitialRequest,
|
|
77
96
|
renderRequestInputControls,
|
|
97
|
+
activeWorkstream,
|
|
98
|
+
onActiveWorkstreamChange,
|
|
78
99
|
}: {
|
|
79
100
|
messages: AgentMessage[];
|
|
80
101
|
streamingMessages: Map<string, unknown>;
|
|
102
|
+
isCompleted?: boolean;
|
|
81
103
|
bottomRef: React.RefObject<HTMLDivElement>;
|
|
82
|
-
onSendMessage?: (message: string) => void;
|
|
104
|
+
onSendMessage?: (message: string, metadata?: Record<string, unknown>) => void;
|
|
105
|
+
showInitialRequest?: boolean;
|
|
83
106
|
renderRequestInputControls?: boolean;
|
|
107
|
+
activeWorkstream?: string;
|
|
108
|
+
onActiveWorkstreamChange?: (workstreamId: string) => void;
|
|
84
109
|
}) => {
|
|
85
|
-
mocks.allMessagesMixedProps({
|
|
110
|
+
mocks.allMessagesMixedProps({
|
|
111
|
+
messages,
|
|
112
|
+
streamingMessages,
|
|
113
|
+
isCompleted,
|
|
114
|
+
onSendMessage,
|
|
115
|
+
showInitialRequest,
|
|
116
|
+
renderRequestInputControls,
|
|
117
|
+
activeWorkstream,
|
|
118
|
+
onActiveWorkstreamChange,
|
|
119
|
+
});
|
|
86
120
|
return (
|
|
87
121
|
<div>
|
|
88
122
|
<div data-testid="rendered-message-count">{messages.length}</div>
|
|
@@ -132,6 +166,7 @@ function createMessage(type: AgentMessageType, message: string): AgentMessage {
|
|
|
132
166
|
function mockStreamState(options: {
|
|
133
167
|
messages: AgentMessage[];
|
|
134
168
|
isCompleted?: boolean;
|
|
169
|
+
initialHistoryStatus?: 'loading' | 'empty' | 'has_messages' | 'error';
|
|
135
170
|
agentRunStatus?: string | null;
|
|
136
171
|
streamingMessages?: Map<string, unknown>;
|
|
137
172
|
}) {
|
|
@@ -139,8 +174,10 @@ function mockStreamState(options: {
|
|
|
139
174
|
messages: options.messages,
|
|
140
175
|
streamingMessages: options.streamingMessages ?? new Map(),
|
|
141
176
|
isCompleted: options.isCompleted ?? true,
|
|
177
|
+
initialHistoryStatus: options.initialHistoryStatus ?? 'has_messages',
|
|
142
178
|
debugChunkFlash: false,
|
|
143
179
|
addOptimisticMessage: mocks.addOptimisticMessage,
|
|
180
|
+
updateOptimisticMessageStatus: mocks.updateOptimisticMessageStatus,
|
|
144
181
|
removeOptimisticMessages: mocks.removeOptimisticMessages,
|
|
145
182
|
reconnect: mocks.reconnect,
|
|
146
183
|
agentRunStatus: options.agentRunStatus ?? null,
|
|
@@ -162,11 +199,27 @@ function renderConversation(props: Partial<React.ComponentProps<typeof ModernAge
|
|
|
162
199
|
);
|
|
163
200
|
}
|
|
164
201
|
|
|
202
|
+
function latestAllMessagesMixedProps() {
|
|
203
|
+
const calls = mocks.allMessagesMixedProps.mock.calls;
|
|
204
|
+
return calls[calls.length - 1]?.[0] as
|
|
205
|
+
| {
|
|
206
|
+
messages: AgentMessage[];
|
|
207
|
+
streamingMessages: Map<string, unknown>;
|
|
208
|
+
isCompleted?: boolean;
|
|
209
|
+
showInitialRequest?: boolean;
|
|
210
|
+
activeWorkstream?: string;
|
|
211
|
+
onActiveWorkstreamChange?: (workstreamId: string) => void;
|
|
212
|
+
}
|
|
213
|
+
| undefined;
|
|
214
|
+
}
|
|
215
|
+
|
|
165
216
|
describe('ModernAgentConversation send handling', () => {
|
|
166
217
|
beforeEach(() => {
|
|
167
218
|
vi.clearAllMocks();
|
|
168
219
|
mocks.restart.mockResolvedValue({ id: 'agent-run-1' });
|
|
169
220
|
mocks.sendSignal.mockResolvedValue({});
|
|
221
|
+
mocks.getActiveWorkstreams.mockResolvedValue({ running: [] });
|
|
222
|
+
mocks.retrieve.mockResolvedValue({ disabled_mcp_collections: undefined });
|
|
170
223
|
mocks.useAgentPlans.mockReturnValue({
|
|
171
224
|
plans: [],
|
|
172
225
|
activePlanIndex: 0,
|
|
@@ -220,13 +273,16 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
220
273
|
'UserInput',
|
|
221
274
|
expect.objectContaining({
|
|
222
275
|
message: 'follow up',
|
|
276
|
+
client_message_id: expect.any(String),
|
|
223
277
|
metadata: expect.objectContaining({
|
|
278
|
+
id: expect.any(String),
|
|
224
279
|
_messageId: expect.any(String),
|
|
225
280
|
}),
|
|
226
281
|
}),
|
|
227
282
|
);
|
|
228
283
|
expect(mocks.restart.mock.invocationCallOrder[0]).toBeLessThan(mocks.sendSignal.mock.invocationCallOrder[0]);
|
|
229
284
|
expect(mocks.reconnect.mock.invocationCallOrder[0]).toBeLessThan(mocks.sendSignal.mock.invocationCallOrder[0]);
|
|
285
|
+
expect(mocks.updateOptimisticMessageStatus).toHaveBeenCalledWith(expect.any(String), 'received');
|
|
230
286
|
});
|
|
231
287
|
|
|
232
288
|
it('does not restart or send from a terminal run that cannot continue', () => {
|
|
@@ -284,6 +340,128 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
284
340
|
expect(screen.queryByRole('button', { name: 'composer send' })).toBeNull();
|
|
285
341
|
});
|
|
286
342
|
|
|
343
|
+
it('passes the selected approval mode when starting a new interactive run', async () => {
|
|
344
|
+
const startWorkflow = vi.fn().mockResolvedValue(undefined);
|
|
345
|
+
renderWithProviders(
|
|
346
|
+
<ModernAgentConversation startWorkflow={startWorkflow} hideHeader hideFileUpload initialMessage="" />,
|
|
347
|
+
);
|
|
348
|
+
|
|
349
|
+
const selector = screen.getByRole('button', { name: 'Agent approval mode' });
|
|
350
|
+
expect(selector.getAttribute('title')).toBeNull();
|
|
351
|
+
|
|
352
|
+
fireEvent.pointerDown(selector, {
|
|
353
|
+
button: 0,
|
|
354
|
+
ctrlKey: false,
|
|
355
|
+
});
|
|
356
|
+
fireEvent.click(await screen.findByRole('menuitemradio', { name: /Approve for me/ }));
|
|
357
|
+
fireEvent.change(screen.getByRole('textbox'), { target: { value: 'Draft the release notes' } });
|
|
358
|
+
fireEvent.click(screen.getByRole('button', { name: 'Start Agent' }));
|
|
359
|
+
|
|
360
|
+
await waitFor(() => {
|
|
361
|
+
expect(startWorkflow).toHaveBeenCalledWith('Draft the release notes', {
|
|
362
|
+
tool_approval_mode: 'auto_review',
|
|
363
|
+
});
|
|
364
|
+
});
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('signals approval mode changes for an active interactive run', async () => {
|
|
368
|
+
mocks.retrieve.mockResolvedValue({
|
|
369
|
+
tool_approval_mode: 'ask',
|
|
370
|
+
interactive: true,
|
|
371
|
+
disabled_mcp_collections: undefined,
|
|
372
|
+
});
|
|
373
|
+
mockStreamState({
|
|
374
|
+
messages: [createMessage(AgentMessageType.ANSWER, 'still running')],
|
|
375
|
+
isCompleted: false,
|
|
376
|
+
agentRunStatus: 'RUNNING',
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
renderConversation({ hideMessageInput: false, interactive: true, allowWorkflowControl: true });
|
|
380
|
+
|
|
381
|
+
await waitFor(() => {
|
|
382
|
+
expect(screen.getByText('Ask for approval')).not.toBeNull();
|
|
383
|
+
});
|
|
384
|
+
fireEvent.pointerDown(screen.getByRole('button', { name: 'Agent approval mode' }), {
|
|
385
|
+
button: 0,
|
|
386
|
+
ctrlKey: false,
|
|
387
|
+
});
|
|
388
|
+
fireEvent.click(await screen.findByRole('menuitemradio', { name: /Approve for me/ }));
|
|
389
|
+
|
|
390
|
+
await waitFor(() => {
|
|
391
|
+
expect(mocks.sendSignal).toHaveBeenCalledWith('agent-run-1', 'ToolApprovalModeChanged', {
|
|
392
|
+
mode: 'auto_review',
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
});
|
|
396
|
+
|
|
397
|
+
it('switches to full control without a confirmation modal', async () => {
|
|
398
|
+
mocks.retrieve.mockResolvedValue({
|
|
399
|
+
tool_approval_mode: 'ask',
|
|
400
|
+
interactive: true,
|
|
401
|
+
disabled_mcp_collections: undefined,
|
|
402
|
+
});
|
|
403
|
+
mockStreamState({
|
|
404
|
+
messages: [createMessage(AgentMessageType.ANSWER, 'still running')],
|
|
405
|
+
isCompleted: false,
|
|
406
|
+
agentRunStatus: 'RUNNING',
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
renderConversation({ hideMessageInput: false, interactive: true, allowWorkflowControl: true });
|
|
410
|
+
|
|
411
|
+
await waitFor(() => {
|
|
412
|
+
expect(screen.getByText('Ask for approval')).not.toBeNull();
|
|
413
|
+
});
|
|
414
|
+
fireEvent.pointerDown(screen.getByRole('button', { name: 'Agent approval mode' }), {
|
|
415
|
+
button: 0,
|
|
416
|
+
ctrlKey: false,
|
|
417
|
+
});
|
|
418
|
+
fireEvent.click(await screen.findByRole('menuitemradio', { name: /Full control/ }));
|
|
419
|
+
|
|
420
|
+
await waitFor(() => {
|
|
421
|
+
expect(mocks.sendSignal).toHaveBeenCalledWith('agent-run-1', 'ToolApprovalModeChanged', {
|
|
422
|
+
mode: 'full_control',
|
|
423
|
+
});
|
|
424
|
+
});
|
|
425
|
+
expect(screen.queryByText('Switch to full control?')).toBeNull();
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
it('does not show a full-control fallback before active run mode metadata loads', () => {
|
|
429
|
+
mocks.retrieve.mockReturnValue(new Promise(() => {}));
|
|
430
|
+
mockStreamState({
|
|
431
|
+
messages: [createMessage(AgentMessageType.ANSWER, 'still running')],
|
|
432
|
+
isCompleted: false,
|
|
433
|
+
agentRunStatus: 'RUNNING',
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
renderConversation({ hideMessageInput: false, interactive: true, allowWorkflowControl: true });
|
|
437
|
+
|
|
438
|
+
expect(screen.queryByRole('button', { name: 'Agent approval mode' })).toBeNull();
|
|
439
|
+
expect(screen.queryByText('Full control')).toBeNull();
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
it('disables the active-run approval selector when a run is completed', async () => {
|
|
443
|
+
mocks.retrieve.mockResolvedValue({
|
|
444
|
+
tool_approval_mode: 'ask',
|
|
445
|
+
interactive: true,
|
|
446
|
+
disabled_mcp_collections: undefined,
|
|
447
|
+
});
|
|
448
|
+
mockStreamState({
|
|
449
|
+
messages: [createMessage(AgentMessageType.COMPLETE, 'done')],
|
|
450
|
+
isCompleted: true,
|
|
451
|
+
agentRunStatus: 'COMPLETED',
|
|
452
|
+
});
|
|
453
|
+
|
|
454
|
+
renderConversation({
|
|
455
|
+
hideMessageInput: false,
|
|
456
|
+
interactive: true,
|
|
457
|
+
allowWorkflowControl: true,
|
|
458
|
+
onRestart: vi.fn(),
|
|
459
|
+
});
|
|
460
|
+
|
|
461
|
+
const selector = await screen.findByRole('button', { name: 'Agent approval mode' });
|
|
462
|
+
expect((selector as HTMLButtonElement).disabled).toBe(true);
|
|
463
|
+
});
|
|
464
|
+
|
|
287
465
|
it('unlocks the composer when an idle marker arrives before the stream completion flag updates', () => {
|
|
288
466
|
mockStreamState({
|
|
289
467
|
messages: [
|
|
@@ -295,6 +473,7 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
295
473
|
isCompleted: false,
|
|
296
474
|
agentRunStatus: 'RUNNING',
|
|
297
475
|
});
|
|
476
|
+
mocks.sendSignal.mockReturnValue(new Promise(() => {}));
|
|
298
477
|
|
|
299
478
|
renderConversation({ hideMessageInput: false });
|
|
300
479
|
|
|
@@ -307,7 +486,204 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
307
486
|
expect(latestMessageInputProps.isStreaming).toBe(false);
|
|
308
487
|
});
|
|
309
488
|
|
|
310
|
-
it('
|
|
489
|
+
it('keeps live run controls enabled while the agent is idle waiting for user input', async () => {
|
|
490
|
+
mocks.retrieve.mockResolvedValue({
|
|
491
|
+
tool_approval_mode: 'ask',
|
|
492
|
+
interactive: true,
|
|
493
|
+
disabled_mcp_collections: undefined,
|
|
494
|
+
});
|
|
495
|
+
mockStreamState({
|
|
496
|
+
messages: [
|
|
497
|
+
{
|
|
498
|
+
...createMessage(AgentMessageType.THOUGHT, 'Planning the next tool call'),
|
|
499
|
+
details: {
|
|
500
|
+
token_usage: {
|
|
501
|
+
total: 50_000,
|
|
502
|
+
},
|
|
503
|
+
checkpoint_at: 100_000,
|
|
504
|
+
},
|
|
505
|
+
},
|
|
506
|
+
createMessage(AgentMessageType.IDLE, 'Waiting for your command...'),
|
|
507
|
+
],
|
|
508
|
+
isCompleted: false,
|
|
509
|
+
agentRunStatus: 'RUNNING',
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
renderConversation({
|
|
513
|
+
hideMessageInput: false,
|
|
514
|
+
interactive: true,
|
|
515
|
+
allowWorkflowControl: true,
|
|
516
|
+
});
|
|
517
|
+
|
|
518
|
+
const selector = await screen.findByRole('button', { name: 'Agent approval mode' });
|
|
519
|
+
expect((selector as HTMLButtonElement).disabled).toBe(false);
|
|
520
|
+
|
|
521
|
+
const latestMessageInputProps = mocks.messageInputProps.mock.lastCall?.[0] as {
|
|
522
|
+
contextWindowUsage?: {
|
|
523
|
+
usedTokens: number;
|
|
524
|
+
checkpointTokens: number;
|
|
525
|
+
usedPercent: number;
|
|
526
|
+
remainingPercent: number;
|
|
527
|
+
};
|
|
528
|
+
onCompactContext?: () => void | Promise<void>;
|
|
529
|
+
};
|
|
530
|
+
|
|
531
|
+
expect(latestMessageInputProps.contextWindowUsage).toEqual({
|
|
532
|
+
usedTokens: 50_000,
|
|
533
|
+
checkpointTokens: 100_000,
|
|
534
|
+
usedPercent: 50,
|
|
535
|
+
remainingPercent: 50,
|
|
536
|
+
});
|
|
537
|
+
expect(latestMessageInputProps.onCompactContext).toBeTypeOf('function');
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
it('derives context usage from persisted messages and sends manual compact signal', async () => {
|
|
541
|
+
mockStreamState({
|
|
542
|
+
messages: [
|
|
543
|
+
{
|
|
544
|
+
...createMessage(AgentMessageType.THOUGHT, 'Planning the next tool call'),
|
|
545
|
+
details: {
|
|
546
|
+
token_usage: {
|
|
547
|
+
prompt: 40_000,
|
|
548
|
+
result: 10_000,
|
|
549
|
+
total: 50_000,
|
|
550
|
+
},
|
|
551
|
+
checkpoint_at: 100_000,
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
],
|
|
555
|
+
isCompleted: false,
|
|
556
|
+
agentRunStatus: 'RUNNING',
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
renderConversation({ hideMessageInput: false });
|
|
560
|
+
|
|
561
|
+
expect(mocks.messageInputProps).toHaveBeenCalled();
|
|
562
|
+
|
|
563
|
+
const latestMessageInputProps = mocks.messageInputProps.mock.lastCall?.[0] as {
|
|
564
|
+
contextWindowUsage?: {
|
|
565
|
+
usedTokens: number;
|
|
566
|
+
checkpointTokens: number;
|
|
567
|
+
usedPercent: number;
|
|
568
|
+
remainingPercent: number;
|
|
569
|
+
};
|
|
570
|
+
onCompactContext?: () => void | Promise<void>;
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
expect(latestMessageInputProps.contextWindowUsage).toEqual({
|
|
574
|
+
usedTokens: 50_000,
|
|
575
|
+
checkpointTokens: 100_000,
|
|
576
|
+
usedPercent: 50,
|
|
577
|
+
remainingPercent: 50,
|
|
578
|
+
});
|
|
579
|
+
|
|
580
|
+
act(() => {
|
|
581
|
+
void latestMessageInputProps.onCompactContext?.();
|
|
582
|
+
});
|
|
583
|
+
|
|
584
|
+
expect(mocks.sendSignal).toHaveBeenCalledWith('agent-run-1', 'TriggerCheckpoint', {
|
|
585
|
+
reason: 'manual user request',
|
|
586
|
+
});
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
it('derives context usage from approval request messages when no preamble was posted', () => {
|
|
590
|
+
mockStreamState({
|
|
591
|
+
messages: [
|
|
592
|
+
{
|
|
593
|
+
...createMessage(AgentMessageType.REQUEST_INPUT, 'Approve Create Document?'),
|
|
594
|
+
details: {
|
|
595
|
+
tool_approval: {
|
|
596
|
+
tool_name: 'create_document',
|
|
597
|
+
tool_title: 'Create Document',
|
|
598
|
+
target: 'name:Budget plan',
|
|
599
|
+
},
|
|
600
|
+
token_usage: {
|
|
601
|
+
total: 82_000,
|
|
602
|
+
},
|
|
603
|
+
checkpoint_threshold: 100_000,
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
],
|
|
607
|
+
isCompleted: false,
|
|
608
|
+
agentRunStatus: 'RUNNING',
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
renderConversation({ hideMessageInput: false, interactive: true, allowWorkflowControl: true });
|
|
612
|
+
|
|
613
|
+
const latestMessageInputProps = mocks.messageInputProps.mock.lastCall?.[0] as {
|
|
614
|
+
contextWindowUsage?: {
|
|
615
|
+
usedTokens: number;
|
|
616
|
+
checkpointTokens: number;
|
|
617
|
+
usedPercent: number;
|
|
618
|
+
remainingPercent: number;
|
|
619
|
+
};
|
|
620
|
+
onCompactContext?: () => void | Promise<void>;
|
|
621
|
+
};
|
|
622
|
+
|
|
623
|
+
expect(latestMessageInputProps.contextWindowUsage).toEqual({
|
|
624
|
+
usedTokens: 82_000,
|
|
625
|
+
checkpointTokens: 100_000,
|
|
626
|
+
usedPercent: 82,
|
|
627
|
+
remainingPercent: 18,
|
|
628
|
+
});
|
|
629
|
+
expect(latestMessageInputProps.onCompactContext).toBeTypeOf('function');
|
|
630
|
+
});
|
|
631
|
+
|
|
632
|
+
it('makes it explicit that the composer still messages the main agent while viewing a workstream', async () => {
|
|
633
|
+
mockStreamState({
|
|
634
|
+
messages: [
|
|
635
|
+
createMessage(AgentMessageType.QUESTION, 'main request'),
|
|
636
|
+
{
|
|
637
|
+
...createMessage(AgentMessageType.UPDATE, 'France workstream launched'),
|
|
638
|
+
timestamp: 2_000,
|
|
639
|
+
workstream_id: 'france_news_agent',
|
|
640
|
+
details: {
|
|
641
|
+
event_class: 'activity',
|
|
642
|
+
workstream_event: 'launched',
|
|
643
|
+
workstream_id: 'france_news_agent',
|
|
644
|
+
interaction: 'sys:AdhocTaskAgent',
|
|
645
|
+
},
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
...createMessage(AgentMessageType.THOUGHT, 'France internal search'),
|
|
649
|
+
timestamp: 3_000,
|
|
650
|
+
workstream_id: 'france_news_agent',
|
|
651
|
+
},
|
|
652
|
+
],
|
|
653
|
+
isCompleted: false,
|
|
654
|
+
agentRunStatus: 'RUNNING',
|
|
655
|
+
});
|
|
656
|
+
|
|
657
|
+
renderConversation({ hideMessageInput: false });
|
|
658
|
+
|
|
659
|
+
act(() => {
|
|
660
|
+
latestAllMessagesMixedProps()?.onActiveWorkstreamChange?.('france_news_agent');
|
|
661
|
+
});
|
|
662
|
+
|
|
663
|
+
const latestMessageInputProps = mocks.messageInputProps.mock.lastCall?.[0] as {
|
|
664
|
+
placeholder?: string;
|
|
665
|
+
};
|
|
666
|
+
|
|
667
|
+
expect(latestMessageInputProps.placeholder).toBe('Message @Main Agent (viewing France News Agent)...');
|
|
668
|
+
|
|
669
|
+
fireEvent.click(screen.getByRole('button', { name: 'composer send' }));
|
|
670
|
+
|
|
671
|
+
await waitFor(() => {
|
|
672
|
+
expect(mocks.sendSignal).toHaveBeenCalledWith(
|
|
673
|
+
'agent-run-1',
|
|
674
|
+
'UserInput',
|
|
675
|
+
expect.objectContaining({ message: 'follow up' }),
|
|
676
|
+
);
|
|
677
|
+
});
|
|
678
|
+
expect(mocks.addOptimisticMessage).toHaveBeenCalledWith(
|
|
679
|
+
expect.objectContaining({
|
|
680
|
+
message: 'follow up',
|
|
681
|
+
workstream_id: 'main',
|
|
682
|
+
}),
|
|
683
|
+
);
|
|
684
|
+
});
|
|
685
|
+
|
|
686
|
+
it('uses message-derived workstreams for panel history while the composer only counts active workstreams', async () => {
|
|
311
687
|
mockStreamState({
|
|
312
688
|
messages: [
|
|
313
689
|
createMessage(AgentMessageType.QUESTION, 'main request'),
|
|
@@ -322,6 +698,7 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
322
698
|
renderConversation({
|
|
323
699
|
hideMessageInput: false,
|
|
324
700
|
hideWorkstreamTabs: false,
|
|
701
|
+
onRestart: vi.fn(),
|
|
325
702
|
showRightPanel: true,
|
|
326
703
|
});
|
|
327
704
|
|
|
@@ -341,6 +718,7 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
341
718
|
expect(latestRightPanelProps.activeWorkstreams).toEqual([
|
|
342
719
|
expect.objectContaining({ workstream_id: 'alpha', status: 'running' }),
|
|
343
720
|
expect.objectContaining({ workstream_id: 'beta', status: 'running' }),
|
|
721
|
+
expect.objectContaining({ workstream_id: 'gamma', status: 'completed' }),
|
|
344
722
|
]);
|
|
345
723
|
expect(latestRightPanelProps.activeTab).toBe('plan');
|
|
346
724
|
expect(latestMessageInputProps.activeTaskCount).toBe(2);
|
|
@@ -350,7 +728,7 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
350
728
|
]);
|
|
351
729
|
});
|
|
352
730
|
|
|
353
|
-
it('
|
|
731
|
+
it('shows completed workstream lifecycle messages in the panel without counting them as active', async () => {
|
|
354
732
|
mockStreamState({
|
|
355
733
|
messages: [
|
|
356
734
|
createMessage(AgentMessageType.QUESTION, 'main request'),
|
|
@@ -407,6 +785,7 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
407
785
|
|
|
408
786
|
expect(latestRightPanelProps.activeWorkstreams).toEqual([
|
|
409
787
|
expect.objectContaining({ workstream_id: 'qa_assignee', status: 'running' }),
|
|
788
|
+
expect.objectContaining({ workstream_id: 'qa_tasks', status: 'completed' }),
|
|
410
789
|
]);
|
|
411
790
|
expect(latestMessageInputProps.activeTaskCount).toBe(1);
|
|
412
791
|
expect(latestMessageInputProps.activeWorkstreams).toEqual([
|
|
@@ -414,32 +793,85 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
414
793
|
]);
|
|
415
794
|
});
|
|
416
795
|
|
|
417
|
-
it('keeps
|
|
796
|
+
it('keeps message-derived workstream history when the live workstream query fails', async () => {
|
|
797
|
+
const warn = vi.spyOn(console, 'warn').mockImplementation(() => {});
|
|
798
|
+
mocks.getActiveWorkstreams.mockRejectedValueOnce(new Error('workflow no longer queryable'));
|
|
418
799
|
mockStreamState({
|
|
419
800
|
messages: [
|
|
420
801
|
createMessage(AgentMessageType.QUESTION, 'main request'),
|
|
421
802
|
{
|
|
422
|
-
...createMessage(
|
|
423
|
-
|
|
424
|
-
'Workstream "ImageGeneratorAgent-16b7f73a-8e8e-40b7-b891-cb47a78c38c6" launched',
|
|
425
|
-
),
|
|
426
|
-
workstream_id: 'ImageGeneratorAgent-16b7f73a-8e8e-40b7-b891-cb47a78c38c6',
|
|
803
|
+
...createMessage(AgentMessageType.UPDATE, 'Workstream "qa_tasks" launched'),
|
|
804
|
+
workstream_id: 'qa_tasks',
|
|
427
805
|
details: {
|
|
428
806
|
workstream_event: 'launched',
|
|
429
|
-
workstream_id: '
|
|
430
|
-
launch_id: 'launch-
|
|
431
|
-
|
|
807
|
+
workstream_id: 'qa_tasks',
|
|
808
|
+
launch_id: 'launch-qa-tasks',
|
|
809
|
+
},
|
|
810
|
+
},
|
|
811
|
+
{
|
|
812
|
+
...createMessage(AgentMessageType.UPDATE, 'Workstream "qa_tasks" completed'),
|
|
813
|
+
workstream_id: 'qa_tasks',
|
|
814
|
+
details: {
|
|
815
|
+
workstream_event: 'completed',
|
|
816
|
+
workstream_id: 'qa_tasks',
|
|
817
|
+
launch_id: 'launch-qa-tasks',
|
|
818
|
+
status: 'completed',
|
|
819
|
+
},
|
|
820
|
+
},
|
|
821
|
+
],
|
|
822
|
+
isCompleted: false,
|
|
823
|
+
agentRunStatus: 'RUNNING',
|
|
824
|
+
});
|
|
825
|
+
|
|
826
|
+
try {
|
|
827
|
+
renderConversation({
|
|
828
|
+
hideMessageInput: false,
|
|
829
|
+
hideWorkstreamTabs: false,
|
|
830
|
+
showRightPanel: true,
|
|
831
|
+
});
|
|
832
|
+
|
|
833
|
+
await waitFor(() => {
|
|
834
|
+
expect(mocks.getActiveWorkstreams).toHaveBeenCalled();
|
|
835
|
+
const latestRightPanelProps = mocks.rightPanelProps.mock.lastCall?.[0] as {
|
|
836
|
+
activeWorkstreams?: Array<{ workstream_id: string; status: string }>;
|
|
837
|
+
};
|
|
838
|
+
expect(latestRightPanelProps.activeWorkstreams).toEqual([
|
|
839
|
+
expect.objectContaining({ workstream_id: 'qa_tasks', status: 'completed' }),
|
|
840
|
+
]);
|
|
841
|
+
});
|
|
842
|
+
|
|
843
|
+
const latestMessageInputProps = mocks.messageInputProps.mock.lastCall?.[0] as {
|
|
844
|
+
activeTaskCount?: number;
|
|
845
|
+
activeWorkstreams?: Array<{ workstream_id: string; status: string }>;
|
|
846
|
+
};
|
|
847
|
+
expect(latestMessageInputProps.activeTaskCount).toBe(0);
|
|
848
|
+
expect(latestMessageInputProps.activeWorkstreams).toEqual([]);
|
|
849
|
+
} finally {
|
|
850
|
+
warn.mockRestore();
|
|
851
|
+
}
|
|
852
|
+
});
|
|
853
|
+
|
|
854
|
+
it('marks failed pre-launch workstream activities as failed instead of active', async () => {
|
|
855
|
+
mockStreamState({
|
|
856
|
+
messages: [
|
|
857
|
+
createMessage(AgentMessageType.QUESTION, 'main request'),
|
|
858
|
+
{
|
|
859
|
+
...createMessage(AgentMessageType.UPDATE, 'Provisioning browser sandbox for "Create Bookmark"...'),
|
|
860
|
+
workstream_id: 'create_bookmark',
|
|
861
|
+
details: {
|
|
862
|
+
event_class: 'activity',
|
|
863
|
+
workstream_id: 'create_bookmark',
|
|
432
864
|
},
|
|
433
865
|
},
|
|
434
866
|
{
|
|
435
867
|
...createMessage(
|
|
436
|
-
AgentMessageType.
|
|
437
|
-
'
|
|
868
|
+
AgentMessageType.ERROR,
|
|
869
|
+
'Failed to provision browser sandbox: Request failed with status code 502',
|
|
438
870
|
),
|
|
439
|
-
workstream_id: '
|
|
871
|
+
workstream_id: 'create_bookmark',
|
|
440
872
|
details: {
|
|
441
|
-
event_class: '
|
|
442
|
-
|
|
873
|
+
event_class: 'activity',
|
|
874
|
+
workstream_id: 'create_bookmark',
|
|
443
875
|
},
|
|
444
876
|
},
|
|
445
877
|
],
|
|
@@ -454,43 +886,234 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
454
886
|
});
|
|
455
887
|
|
|
456
888
|
await waitFor(() => {
|
|
457
|
-
|
|
889
|
+
const latestRightPanelProps = mocks.rightPanelProps.mock.lastCall?.[0] as {
|
|
890
|
+
activeWorkstreams?: Array<{ workstream_id: string; status: string }>;
|
|
891
|
+
};
|
|
892
|
+
expect(latestRightPanelProps.activeWorkstreams).toEqual([
|
|
893
|
+
expect.objectContaining({ workstream_id: 'create_bookmark', status: 'failed' }),
|
|
894
|
+
]);
|
|
458
895
|
});
|
|
459
896
|
|
|
460
897
|
const latestMessageInputProps = mocks.messageInputProps.mock.lastCall?.[0] as {
|
|
461
898
|
activeTaskCount?: number;
|
|
462
|
-
activeWorkstreams?: Array<{ workstream_id: string; status: string
|
|
899
|
+
activeWorkstreams?: Array<{ workstream_id: string; status: string }>;
|
|
463
900
|
};
|
|
464
|
-
|
|
465
|
-
expect(latestMessageInputProps.
|
|
466
|
-
expect(latestMessageInputProps.activeWorkstreams).toEqual([
|
|
467
|
-
expect.objectContaining({
|
|
468
|
-
workstream_id: 'ImageGeneratorAgent-16b7f73a-8e8e-40b7-b891-cb47a78c38c6',
|
|
469
|
-
interaction: 'ImageGeneratorAgent',
|
|
470
|
-
status: 'running',
|
|
471
|
-
}),
|
|
472
|
-
]);
|
|
901
|
+
expect(latestMessageInputProps.activeTaskCount).toBe(0);
|
|
902
|
+
expect(latestMessageInputProps.activeWorkstreams).toEqual([]);
|
|
473
903
|
});
|
|
474
904
|
|
|
475
|
-
it('
|
|
905
|
+
it('does not let stale active workstream queries override terminal message-derived state', async () => {
|
|
906
|
+
mocks.getActiveWorkstreams.mockResolvedValue({
|
|
907
|
+
running: [
|
|
908
|
+
{
|
|
909
|
+
launch_id: 'launch-create-bookmark',
|
|
910
|
+
workstream_id: 'create_bookmark',
|
|
911
|
+
interaction: 'sys:BrowserAgent',
|
|
912
|
+
started_at: Date.now() - 5000,
|
|
913
|
+
elapsed_ms: 5000,
|
|
914
|
+
deadline_ms: 30000,
|
|
915
|
+
status: 'running',
|
|
916
|
+
},
|
|
917
|
+
],
|
|
918
|
+
});
|
|
476
919
|
mockStreamState({
|
|
477
920
|
messages: [
|
|
921
|
+
createMessage(AgentMessageType.QUESTION, 'main request'),
|
|
478
922
|
{
|
|
479
|
-
...createMessage(AgentMessageType.
|
|
923
|
+
...createMessage(AgentMessageType.UPDATE, 'Provisioning browser sandbox for "Create Bookmark"...'),
|
|
924
|
+
workstream_id: 'create_bookmark',
|
|
480
925
|
details: {
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
{ id: 'red', label: 'Red' },
|
|
484
|
-
{ id: 'blue', label: 'Blue' },
|
|
485
|
-
],
|
|
486
|
-
},
|
|
926
|
+
event_class: 'activity',
|
|
927
|
+
workstream_id: 'create_bookmark',
|
|
487
928
|
},
|
|
488
929
|
},
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
930
|
+
{
|
|
931
|
+
...createMessage(
|
|
932
|
+
AgentMessageType.ERROR,
|
|
933
|
+
'Failed to provision browser sandbox: Request failed with status code 502',
|
|
934
|
+
),
|
|
935
|
+
workstream_id: 'create_bookmark',
|
|
936
|
+
details: {
|
|
937
|
+
event_class: 'activity',
|
|
938
|
+
workstream_id: 'create_bookmark',
|
|
939
|
+
},
|
|
940
|
+
},
|
|
941
|
+
],
|
|
942
|
+
isCompleted: false,
|
|
943
|
+
agentRunStatus: 'RUNNING',
|
|
944
|
+
});
|
|
945
|
+
|
|
946
|
+
renderConversation({
|
|
947
|
+
hideMessageInput: false,
|
|
948
|
+
hideWorkstreamTabs: false,
|
|
949
|
+
showRightPanel: true,
|
|
950
|
+
});
|
|
951
|
+
|
|
952
|
+
await waitFor(() => {
|
|
953
|
+
const latestRightPanelProps = mocks.rightPanelProps.mock.lastCall?.[0] as {
|
|
954
|
+
activeWorkstreams?: Array<{ workstream_id: string; status: string; interaction?: string }>;
|
|
955
|
+
};
|
|
956
|
+
expect(latestRightPanelProps.activeWorkstreams).toEqual([
|
|
957
|
+
expect.objectContaining({
|
|
958
|
+
workstream_id: 'create_bookmark',
|
|
959
|
+
status: 'failed',
|
|
960
|
+
interaction: 'sys:BrowserAgent',
|
|
961
|
+
}),
|
|
962
|
+
]);
|
|
963
|
+
});
|
|
964
|
+
|
|
965
|
+
const latestMessageInputProps = mocks.messageInputProps.mock.lastCall?.[0] as {
|
|
966
|
+
activeTaskCount?: number;
|
|
967
|
+
activeWorkstreams?: Array<{ workstream_id: string; status: string }>;
|
|
968
|
+
};
|
|
969
|
+
expect(latestMessageInputProps.activeTaskCount).toBe(0);
|
|
970
|
+
expect(latestMessageInputProps.activeWorkstreams).toEqual([]);
|
|
971
|
+
});
|
|
972
|
+
|
|
973
|
+
it('uses the live workstream query to enrich message-derived running workstreams', async () => {
|
|
974
|
+
mocks.getActiveWorkstreams.mockResolvedValue({
|
|
975
|
+
running: [
|
|
976
|
+
{
|
|
977
|
+
launch_id: 'launch-alpha',
|
|
978
|
+
workstream_id: 'alpha',
|
|
979
|
+
interaction: 'sys:AlphaAgent',
|
|
980
|
+
started_at: Date.now() - 5000,
|
|
981
|
+
elapsed_ms: 5000,
|
|
982
|
+
deadline_ms: 30000,
|
|
983
|
+
status: 'running',
|
|
984
|
+
latest_progress: {
|
|
985
|
+
launch_id: 'launch-alpha',
|
|
986
|
+
workstream_id: 'alpha',
|
|
987
|
+
phase: 'executing_tool',
|
|
988
|
+
updated_at: Date.now(),
|
|
989
|
+
},
|
|
990
|
+
child_workflow_id: 'child-alpha',
|
|
991
|
+
child_workflow_run_id: 'child-run-alpha',
|
|
992
|
+
},
|
|
993
|
+
],
|
|
994
|
+
});
|
|
995
|
+
mockStreamState({
|
|
996
|
+
messages: [
|
|
997
|
+
createMessage(AgentMessageType.QUESTION, 'main request'),
|
|
998
|
+
{
|
|
999
|
+
...createMessage(AgentMessageType.UPDATE, 'Workstream "alpha" launched'),
|
|
1000
|
+
workstream_id: 'alpha',
|
|
1001
|
+
details: {
|
|
1002
|
+
workstream_event: 'launched',
|
|
1003
|
+
workstream_id: 'alpha',
|
|
1004
|
+
launch_id: 'launch-alpha',
|
|
1005
|
+
},
|
|
1006
|
+
},
|
|
1007
|
+
],
|
|
1008
|
+
isCompleted: false,
|
|
1009
|
+
agentRunStatus: 'RUNNING',
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
renderConversation({
|
|
1013
|
+
hideMessageInput: false,
|
|
1014
|
+
hideWorkstreamTabs: false,
|
|
1015
|
+
showRightPanel: true,
|
|
1016
|
+
});
|
|
1017
|
+
|
|
1018
|
+
await waitFor(() => {
|
|
1019
|
+
const latestRightPanelProps = mocks.rightPanelProps.mock.lastCall?.[0] as {
|
|
1020
|
+
activeWorkstreams?: Array<{
|
|
1021
|
+
workstream_id: string;
|
|
1022
|
+
status: string;
|
|
1023
|
+
elapsed_ms?: number;
|
|
1024
|
+
phase?: string;
|
|
1025
|
+
child_workflow_run_id?: string;
|
|
1026
|
+
}>;
|
|
1027
|
+
};
|
|
1028
|
+
expect(latestRightPanelProps.activeWorkstreams).toEqual([
|
|
1029
|
+
expect.objectContaining({
|
|
1030
|
+
workstream_id: 'alpha',
|
|
1031
|
+
status: 'running',
|
|
1032
|
+
elapsed_ms: 5000,
|
|
1033
|
+
phase: 'executing_tool',
|
|
1034
|
+
child_workflow_run_id: 'child-run-alpha',
|
|
1035
|
+
}),
|
|
1036
|
+
]);
|
|
1037
|
+
});
|
|
1038
|
+
});
|
|
1039
|
+
|
|
1040
|
+
it('keeps launched workstreams active when their child transcript emits JSON results', async () => {
|
|
1041
|
+
mockStreamState({
|
|
1042
|
+
messages: [
|
|
1043
|
+
createMessage(AgentMessageType.QUESTION, 'main request'),
|
|
1044
|
+
{
|
|
1045
|
+
...createMessage(
|
|
1046
|
+
AgentMessageType.UPDATE,
|
|
1047
|
+
'Workstream "ImageGeneratorAgent-16b7f73a-8e8e-40b7-b891-cb47a78c38c6" launched',
|
|
1048
|
+
),
|
|
1049
|
+
workstream_id: 'ImageGeneratorAgent-16b7f73a-8e8e-40b7-b891-cb47a78c38c6',
|
|
1050
|
+
details: {
|
|
1051
|
+
workstream_event: 'launched',
|
|
1052
|
+
workstream_id: 'ImageGeneratorAgent-16b7f73a-8e8e-40b7-b891-cb47a78c38c6',
|
|
1053
|
+
launch_id: 'launch-image',
|
|
1054
|
+
interaction: 'ImageGeneratorAgent',
|
|
1055
|
+
},
|
|
1056
|
+
},
|
|
1057
|
+
{
|
|
1058
|
+
...createMessage(
|
|
1059
|
+
AgentMessageType.COMPLETE,
|
|
1060
|
+
'{\n "generated_images": ["store:65dbb885-d76f-431a-a01e-0511d70730a5"]\n}',
|
|
1061
|
+
),
|
|
1062
|
+
workstream_id: 'ImageGeneratorAgent-16b7f73a-8e8e-40b7-b891-cb47a78c38c6',
|
|
1063
|
+
details: {
|
|
1064
|
+
event_class: 'user_content',
|
|
1065
|
+
streamed: true,
|
|
1066
|
+
},
|
|
1067
|
+
},
|
|
1068
|
+
],
|
|
1069
|
+
isCompleted: false,
|
|
1070
|
+
agentRunStatus: 'RUNNING',
|
|
1071
|
+
});
|
|
1072
|
+
|
|
1073
|
+
renderConversation({
|
|
1074
|
+
hideMessageInput: false,
|
|
1075
|
+
hideWorkstreamTabs: false,
|
|
1076
|
+
showRightPanel: true,
|
|
1077
|
+
});
|
|
1078
|
+
|
|
1079
|
+
await waitFor(() => {
|
|
1080
|
+
expect(mocks.rightPanelProps).toHaveBeenCalled();
|
|
1081
|
+
});
|
|
1082
|
+
|
|
1083
|
+
const latestMessageInputProps = mocks.messageInputProps.mock.lastCall?.[0] as {
|
|
1084
|
+
activeTaskCount?: number;
|
|
1085
|
+
activeWorkstreams?: Array<{ workstream_id: string; status: string; interaction?: string }>;
|
|
1086
|
+
};
|
|
1087
|
+
|
|
1088
|
+
expect(latestMessageInputProps.activeTaskCount).toBe(1);
|
|
1089
|
+
expect(latestMessageInputProps.activeWorkstreams).toEqual([
|
|
1090
|
+
expect.objectContaining({
|
|
1091
|
+
workstream_id: 'ImageGeneratorAgent-16b7f73a-8e8e-40b7-b891-cb47a78c38c6',
|
|
1092
|
+
interaction: 'ImageGeneratorAgent',
|
|
1093
|
+
status: 'running',
|
|
1094
|
+
}),
|
|
1095
|
+
]);
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
it('shows a bottom request overlay for pending ask input even when normal input is hidden', async () => {
|
|
1099
|
+
mockStreamState({
|
|
1100
|
+
messages: [
|
|
1101
|
+
{
|
|
1102
|
+
...createMessage(AgentMessageType.REQUEST_INPUT, 'What is your favorite color?'),
|
|
1103
|
+
details: {
|
|
1104
|
+
ux: {
|
|
1105
|
+
options: [
|
|
1106
|
+
{ id: 'red', label: 'Red' },
|
|
1107
|
+
{ id: 'blue', label: 'Blue' },
|
|
1108
|
+
],
|
|
1109
|
+
},
|
|
1110
|
+
},
|
|
1111
|
+
},
|
|
1112
|
+
],
|
|
1113
|
+
isCompleted: false,
|
|
1114
|
+
agentRunStatus: 'RUNNING',
|
|
1115
|
+
});
|
|
1116
|
+
|
|
494
1117
|
renderConversation();
|
|
495
1118
|
|
|
496
1119
|
expect(screen.queryByRole('button', { name: 'composer send' })).toBeNull();
|
|
@@ -508,7 +1131,75 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
508
1131
|
expect(mocks.sendSignal).toHaveBeenCalledWith(
|
|
509
1132
|
'agent-run-1',
|
|
510
1133
|
'UserInput',
|
|
511
|
-
expect.objectContaining({
|
|
1134
|
+
expect.objectContaining({
|
|
1135
|
+
message: 'blue',
|
|
1136
|
+
client_message_id: expect.any(String),
|
|
1137
|
+
metadata: expect.objectContaining({
|
|
1138
|
+
id: expect.any(String),
|
|
1139
|
+
_messageId: expect.any(String),
|
|
1140
|
+
}),
|
|
1141
|
+
}),
|
|
1142
|
+
);
|
|
1143
|
+
});
|
|
1144
|
+
|
|
1145
|
+
it('passes commented approval denial metadata through the request overlay signal', async () => {
|
|
1146
|
+
mockStreamState({
|
|
1147
|
+
messages: [
|
|
1148
|
+
{
|
|
1149
|
+
...createMessage(AgentMessageType.REQUEST_INPUT, 'Approve Write Artifact: quotes.md?'),
|
|
1150
|
+
details: {
|
|
1151
|
+
tool_approval: {
|
|
1152
|
+
tool_name: 'write_artifact',
|
|
1153
|
+
approval_key: 'write_artifact:name:quotes.md',
|
|
1154
|
+
},
|
|
1155
|
+
ux: {
|
|
1156
|
+
options: [
|
|
1157
|
+
{ id: 'allow_once', label: 'Allow once' },
|
|
1158
|
+
{ id: 'allow_for_run', label: 'Allow this action for this run' },
|
|
1159
|
+
{ id: 'deny', label: 'Deny' },
|
|
1160
|
+
],
|
|
1161
|
+
free_response: {
|
|
1162
|
+
placeholder: 'No, and tell the agent what to do differently',
|
|
1163
|
+
submit_label: 'Submit',
|
|
1164
|
+
metadata: {
|
|
1165
|
+
tool_approval_response: {
|
|
1166
|
+
decision: 'deny_with_feedback',
|
|
1167
|
+
approval_key: 'write_artifact:name:quotes.md',
|
|
1168
|
+
},
|
|
1169
|
+
},
|
|
1170
|
+
},
|
|
1171
|
+
},
|
|
1172
|
+
},
|
|
1173
|
+
},
|
|
1174
|
+
],
|
|
1175
|
+
isCompleted: false,
|
|
1176
|
+
agentRunStatus: 'RUNNING',
|
|
1177
|
+
});
|
|
1178
|
+
|
|
1179
|
+
renderConversation();
|
|
1180
|
+
|
|
1181
|
+
fireEvent.change(screen.getByPlaceholderText('No, and tell the agent what to do differently'), {
|
|
1182
|
+
target: { value: 'Do not write a file. Put the summary in chat.' },
|
|
1183
|
+
});
|
|
1184
|
+
fireEvent.click(screen.getByRole('button', { name: 'Submit' }));
|
|
1185
|
+
|
|
1186
|
+
await waitFor(() => {
|
|
1187
|
+
expect(mocks.sendSignal).toHaveBeenCalledTimes(1);
|
|
1188
|
+
});
|
|
1189
|
+
expect(mocks.sendSignal).toHaveBeenCalledWith(
|
|
1190
|
+
'agent-run-1',
|
|
1191
|
+
'UserInput',
|
|
1192
|
+
expect.objectContaining({
|
|
1193
|
+
message: 'Do not write a file. Put the summary in chat.',
|
|
1194
|
+
metadata: expect.objectContaining({
|
|
1195
|
+
tool_approval_response: {
|
|
1196
|
+
decision: 'deny_with_feedback',
|
|
1197
|
+
approval_key: 'write_artifact:name:quotes.md',
|
|
1198
|
+
},
|
|
1199
|
+
id: expect.any(String),
|
|
1200
|
+
_messageId: expect.any(String),
|
|
1201
|
+
}),
|
|
1202
|
+
}),
|
|
512
1203
|
);
|
|
513
1204
|
});
|
|
514
1205
|
|
|
@@ -535,6 +1226,137 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
535
1226
|
);
|
|
536
1227
|
});
|
|
537
1228
|
|
|
1229
|
+
it('marks a sent message as sending and then received', async () => {
|
|
1230
|
+
mockStreamState({
|
|
1231
|
+
messages: [createMessage(AgentMessageType.ANSWER, 'still running')],
|
|
1232
|
+
agentRunStatus: 'RUNNING',
|
|
1233
|
+
});
|
|
1234
|
+
|
|
1235
|
+
renderConversation({ onRestart: vi.fn() });
|
|
1236
|
+
|
|
1237
|
+
fireEvent.click(screen.getByRole('button', { name: 'inline send' }));
|
|
1238
|
+
|
|
1239
|
+
const optimisticMessage = mocks.addOptimisticMessage.mock.calls[0]?.[0] as AgentMessage | undefined;
|
|
1240
|
+
expect(optimisticMessage).toEqual(
|
|
1241
|
+
expect.objectContaining({
|
|
1242
|
+
type: AgentMessageType.QUESTION,
|
|
1243
|
+
message: 'follow up',
|
|
1244
|
+
details: expect.objectContaining({
|
|
1245
|
+
_optimistic: true,
|
|
1246
|
+
_messageId: expect.any(String),
|
|
1247
|
+
_deliveryStatus: 'sending',
|
|
1248
|
+
}),
|
|
1249
|
+
}),
|
|
1250
|
+
);
|
|
1251
|
+
|
|
1252
|
+
await waitFor(() => {
|
|
1253
|
+
expect(mocks.updateOptimisticMessageStatus).toHaveBeenCalledWith(
|
|
1254
|
+
optimisticMessage?.details?._messageId,
|
|
1255
|
+
'received',
|
|
1256
|
+
);
|
|
1257
|
+
});
|
|
1258
|
+
});
|
|
1259
|
+
|
|
1260
|
+
it('marks a failed send without removing the optimistic message', async () => {
|
|
1261
|
+
mocks.sendSignal.mockRejectedValueOnce(new Error('signal failed'));
|
|
1262
|
+
mockStreamState({
|
|
1263
|
+
messages: [createMessage(AgentMessageType.ANSWER, 'still running')],
|
|
1264
|
+
agentRunStatus: 'RUNNING',
|
|
1265
|
+
});
|
|
1266
|
+
|
|
1267
|
+
renderConversation({ onRestart: vi.fn() });
|
|
1268
|
+
|
|
1269
|
+
fireEvent.click(screen.getByRole('button', { name: 'inline send' }));
|
|
1270
|
+
|
|
1271
|
+
const optimisticMessage = mocks.addOptimisticMessage.mock.calls[0]?.[0] as AgentMessage | undefined;
|
|
1272
|
+
|
|
1273
|
+
await waitFor(() => {
|
|
1274
|
+
expect(mocks.updateOptimisticMessageStatus).toHaveBeenCalledWith(
|
|
1275
|
+
optimisticMessage?.details?._messageId,
|
|
1276
|
+
'failed',
|
|
1277
|
+
);
|
|
1278
|
+
});
|
|
1279
|
+
expect(mocks.removeOptimisticMessages).not.toHaveBeenCalled();
|
|
1280
|
+
});
|
|
1281
|
+
|
|
1282
|
+
it('adds an optimistic stop marker and marks it received when Stop is signaled', async () => {
|
|
1283
|
+
const stopRef = { current: null } as React.MutableRefObject<(() => void) | null>;
|
|
1284
|
+
mockStreamState({
|
|
1285
|
+
messages: [createMessage(AgentMessageType.THOUGHT, 'working')],
|
|
1286
|
+
isCompleted: false,
|
|
1287
|
+
agentRunStatus: 'RUNNING',
|
|
1288
|
+
});
|
|
1289
|
+
|
|
1290
|
+
renderConversation({ stopRef });
|
|
1291
|
+
|
|
1292
|
+
await waitFor(() => {
|
|
1293
|
+
expect(stopRef.current).toBeTypeOf('function');
|
|
1294
|
+
});
|
|
1295
|
+
|
|
1296
|
+
act(() => {
|
|
1297
|
+
stopRef.current?.();
|
|
1298
|
+
});
|
|
1299
|
+
|
|
1300
|
+
const optimisticMessage = mocks.addOptimisticMessage.mock.calls[0]?.[0] as AgentMessage | undefined;
|
|
1301
|
+
expect(optimisticMessage).toEqual(
|
|
1302
|
+
expect.objectContaining({
|
|
1303
|
+
type: AgentMessageType.IDLE,
|
|
1304
|
+
message: 'Stopped. Waiting for your command...',
|
|
1305
|
+
details: expect.objectContaining({
|
|
1306
|
+
_optimistic: true,
|
|
1307
|
+
_messageId: expect.any(String),
|
|
1308
|
+
_deliveryStatus: 'sending',
|
|
1309
|
+
status_reason: 'user_stopped',
|
|
1310
|
+
}),
|
|
1311
|
+
}),
|
|
1312
|
+
);
|
|
1313
|
+
await waitFor(() => {
|
|
1314
|
+
expect(mocks.sendSignal).toHaveBeenCalledWith(
|
|
1315
|
+
'agent-run-1',
|
|
1316
|
+
'Stop',
|
|
1317
|
+
expect.objectContaining({
|
|
1318
|
+
client_message_id: optimisticMessage?.details?._messageId,
|
|
1319
|
+
metadata: expect.objectContaining({
|
|
1320
|
+
id: optimisticMessage?.details?._messageId,
|
|
1321
|
+
_messageId: optimisticMessage?.details?._messageId,
|
|
1322
|
+
}),
|
|
1323
|
+
}),
|
|
1324
|
+
);
|
|
1325
|
+
});
|
|
1326
|
+
expect(mocks.updateOptimisticMessageStatus).toHaveBeenCalledWith(
|
|
1327
|
+
optimisticMessage?.details?._messageId,
|
|
1328
|
+
'received',
|
|
1329
|
+
);
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
it('marks an optimistic stop marker failed when Stop signaling fails', async () => {
|
|
1333
|
+
const stopRef = { current: null } as React.MutableRefObject<(() => void) | null>;
|
|
1334
|
+
mocks.sendSignal.mockRejectedValueOnce(new Error('stop failed'));
|
|
1335
|
+
mockStreamState({
|
|
1336
|
+
messages: [createMessage(AgentMessageType.THOUGHT, 'working')],
|
|
1337
|
+
isCompleted: false,
|
|
1338
|
+
agentRunStatus: 'RUNNING',
|
|
1339
|
+
});
|
|
1340
|
+
|
|
1341
|
+
renderConversation({ stopRef });
|
|
1342
|
+
|
|
1343
|
+
await waitFor(() => {
|
|
1344
|
+
expect(stopRef.current).toBeTypeOf('function');
|
|
1345
|
+
});
|
|
1346
|
+
|
|
1347
|
+
act(() => {
|
|
1348
|
+
stopRef.current?.();
|
|
1349
|
+
});
|
|
1350
|
+
|
|
1351
|
+
const optimisticMessage = mocks.addOptimisticMessage.mock.calls[0]?.[0] as AgentMessage | undefined;
|
|
1352
|
+
await waitFor(() => {
|
|
1353
|
+
expect(mocks.updateOptimisticMessageStatus).toHaveBeenCalledWith(
|
|
1354
|
+
optimisticMessage?.details?._messageId,
|
|
1355
|
+
'failed',
|
|
1356
|
+
);
|
|
1357
|
+
});
|
|
1358
|
+
});
|
|
1359
|
+
|
|
538
1360
|
it('playback controls slice rendered messages and scroll forward without mutating the live stream', async () => {
|
|
539
1361
|
const originalScrollIntoView = Element.prototype.scrollIntoView;
|
|
540
1362
|
const scrollIntoView = vi.fn();
|
|
@@ -569,6 +1391,7 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
569
1391
|
|
|
570
1392
|
expect(screen.getByTestId('rendered-message-count').textContent).toBe('1');
|
|
571
1393
|
expect(screen.getByTestId('rendered-streaming-count').textContent).toBe('0');
|
|
1394
|
+
expect(latestAllMessagesMixedProps()?.isCompleted).toBe(false);
|
|
572
1395
|
expect(scrollIntoView).not.toHaveBeenCalled();
|
|
573
1396
|
|
|
574
1397
|
const playbackPositionInput = screen.getByRole('textbox', { name: 'Playback position' });
|
|
@@ -592,6 +1415,7 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
592
1415
|
|
|
593
1416
|
expect(screen.getByTestId('rendered-message-count').textContent).toBe('5');
|
|
594
1417
|
expect(screen.getByTestId('rendered-streaming-count').textContent).toBe('0');
|
|
1418
|
+
expect(latestAllMessagesMixedProps()?.isCompleted).toBe(true);
|
|
595
1419
|
await waitFor(() => expect(scrollIntoView).toHaveBeenCalled());
|
|
596
1420
|
scrollIntoView.mockClear();
|
|
597
1421
|
|
|
@@ -605,6 +1429,197 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
605
1429
|
}
|
|
606
1430
|
});
|
|
607
1431
|
|
|
1432
|
+
it('scopes playback controls to the active workstream', () => {
|
|
1433
|
+
mockStreamState({
|
|
1434
|
+
messages: [
|
|
1435
|
+
createMessage(AgentMessageType.QUESTION, 'Compare France and Japan news.'),
|
|
1436
|
+
{
|
|
1437
|
+
...createMessage(AgentMessageType.UPDATE, 'Japan workstream launched.'),
|
|
1438
|
+
timestamp: 2_000,
|
|
1439
|
+
workstream_id: 'japan_news',
|
|
1440
|
+
details: {
|
|
1441
|
+
event_class: 'activity',
|
|
1442
|
+
workstream_event: 'launched',
|
|
1443
|
+
workstream_id: 'japan_news',
|
|
1444
|
+
interaction: 'sys:AdhocTaskAgent',
|
|
1445
|
+
},
|
|
1446
|
+
},
|
|
1447
|
+
{
|
|
1448
|
+
...createMessage(AgentMessageType.THOUGHT, 'Japan internal search.'),
|
|
1449
|
+
timestamp: 3_000,
|
|
1450
|
+
workstream_id: 'japan_news',
|
|
1451
|
+
},
|
|
1452
|
+
{
|
|
1453
|
+
...createMessage(AgentMessageType.ANSWER, 'Japan internal answer.'),
|
|
1454
|
+
timestamp: 4_000,
|
|
1455
|
+
workstream_id: 'japan_news',
|
|
1456
|
+
},
|
|
1457
|
+
{
|
|
1458
|
+
...createMessage(AgentMessageType.UPDATE, 'France workstream launched.'),
|
|
1459
|
+
timestamp: 5_000,
|
|
1460
|
+
workstream_id: 'france_news',
|
|
1461
|
+
details: {
|
|
1462
|
+
event_class: 'activity',
|
|
1463
|
+
workstream_event: 'launched',
|
|
1464
|
+
workstream_id: 'france_news',
|
|
1465
|
+
interaction: 'sys:AdhocTaskAgent',
|
|
1466
|
+
},
|
|
1467
|
+
},
|
|
1468
|
+
{
|
|
1469
|
+
...createMessage(AgentMessageType.THOUGHT, 'France internal search.'),
|
|
1470
|
+
timestamp: 6_000,
|
|
1471
|
+
workstream_id: 'france_news',
|
|
1472
|
+
},
|
|
1473
|
+
createMessage(AgentMessageType.ANSWER, 'Main monitoring update.'),
|
|
1474
|
+
],
|
|
1475
|
+
});
|
|
1476
|
+
|
|
1477
|
+
renderConversation({ enablePlayback: true });
|
|
1478
|
+
|
|
1479
|
+
const controls = screen.getByTestId('agent-test-playback-controls');
|
|
1480
|
+
expect(controls.getAttribute('data-live-message-count')).toBe('4');
|
|
1481
|
+
expect(screen.getByTestId('rendered-message-count').textContent).toBe('4');
|
|
1482
|
+
|
|
1483
|
+
act(() => {
|
|
1484
|
+
latestAllMessagesMixedProps()?.onActiveWorkstreamChange?.('japan_news');
|
|
1485
|
+
});
|
|
1486
|
+
|
|
1487
|
+
expect(screen.getByTestId('agent-test-playback-controls').getAttribute('data-live-message-count')).toBe('2');
|
|
1488
|
+
expect(screen.getByTestId('rendered-message-count').textContent).toBe('2');
|
|
1489
|
+
expect(latestAllMessagesMixedProps()?.activeWorkstream).toBe('japan_news');
|
|
1490
|
+
|
|
1491
|
+
fireEvent.click(screen.getByRole('button', { name: 'Jump to first message' }));
|
|
1492
|
+
|
|
1493
|
+
expect(screen.getByTestId('rendered-message-count').textContent).toBe('1');
|
|
1494
|
+
});
|
|
1495
|
+
|
|
1496
|
+
it('only enables the synthetic initial request after empty history while no real messages exist', () => {
|
|
1497
|
+
mockStreamState({
|
|
1498
|
+
messages: [],
|
|
1499
|
+
isCompleted: false,
|
|
1500
|
+
initialHistoryStatus: 'empty',
|
|
1501
|
+
agentRunStatus: 'RUNNING',
|
|
1502
|
+
});
|
|
1503
|
+
|
|
1504
|
+
renderConversation({
|
|
1505
|
+
initialRequestData: { task: 'What are the news headlines in France today?' },
|
|
1506
|
+
});
|
|
1507
|
+
|
|
1508
|
+
expect(latestAllMessagesMixedProps()?.showInitialRequest).toBe(true);
|
|
1509
|
+
});
|
|
1510
|
+
|
|
1511
|
+
it('disables the synthetic initial request when real messages arrive after an empty history race', () => {
|
|
1512
|
+
mockStreamState({
|
|
1513
|
+
messages: [createMessage(AgentMessageType.ANSWER, 'Current French headlines...')],
|
|
1514
|
+
isCompleted: true,
|
|
1515
|
+
initialHistoryStatus: 'empty',
|
|
1516
|
+
agentRunStatus: 'COMPLETED',
|
|
1517
|
+
});
|
|
1518
|
+
|
|
1519
|
+
renderConversation({
|
|
1520
|
+
initialRequestData: { task: 'What are the news headlines in France today?' },
|
|
1521
|
+
});
|
|
1522
|
+
|
|
1523
|
+
expect(latestAllMessagesMixedProps()?.showInitialRequest).toBe(false);
|
|
1524
|
+
});
|
|
1525
|
+
|
|
1526
|
+
it('does not enable the synthetic initial request while history is still loading', () => {
|
|
1527
|
+
mockStreamState({
|
|
1528
|
+
messages: [],
|
|
1529
|
+
isCompleted: false,
|
|
1530
|
+
initialHistoryStatus: 'loading',
|
|
1531
|
+
agentRunStatus: 'RUNNING',
|
|
1532
|
+
});
|
|
1533
|
+
|
|
1534
|
+
renderConversation({
|
|
1535
|
+
initialRequestData: { task: 'What are the news headlines in France today?' },
|
|
1536
|
+
});
|
|
1537
|
+
|
|
1538
|
+
expect(latestAllMessagesMixedProps()?.showInitialRequest).toBe(false);
|
|
1539
|
+
});
|
|
1540
|
+
|
|
1541
|
+
it('does not poll live workstreams while initial history is loading', () => {
|
|
1542
|
+
mockStreamState({
|
|
1543
|
+
messages: [],
|
|
1544
|
+
isCompleted: false,
|
|
1545
|
+
initialHistoryStatus: 'loading',
|
|
1546
|
+
agentRunStatus: 'RUNNING',
|
|
1547
|
+
});
|
|
1548
|
+
|
|
1549
|
+
renderConversation();
|
|
1550
|
+
|
|
1551
|
+
expect(mocks.getActiveWorkstreams).not.toHaveBeenCalled();
|
|
1552
|
+
});
|
|
1553
|
+
|
|
1554
|
+
it('stops polling live workstreams when the server marks the query unavailable', async () => {
|
|
1555
|
+
vi.useFakeTimers();
|
|
1556
|
+
mocks.getActiveWorkstreams.mockResolvedValue({ running: [], completed: [], unavailable: true });
|
|
1557
|
+
mockStreamState({
|
|
1558
|
+
messages: [createMessage(AgentMessageType.QUESTION, 'main request')],
|
|
1559
|
+
isCompleted: false,
|
|
1560
|
+
initialHistoryStatus: 'has_messages',
|
|
1561
|
+
agentRunStatus: 'RUNNING',
|
|
1562
|
+
});
|
|
1563
|
+
|
|
1564
|
+
try {
|
|
1565
|
+
renderConversation();
|
|
1566
|
+
|
|
1567
|
+
await act(async () => {
|
|
1568
|
+
await Promise.resolve();
|
|
1569
|
+
});
|
|
1570
|
+
const callCountAfterUnavailable = mocks.getActiveWorkstreams.mock.calls.length;
|
|
1571
|
+
expect(callCountAfterUnavailable).toBeGreaterThan(0);
|
|
1572
|
+
|
|
1573
|
+
await act(async () => {
|
|
1574
|
+
vi.advanceTimersByTime(10_000);
|
|
1575
|
+
});
|
|
1576
|
+
expect(mocks.getActiveWorkstreams).toHaveBeenCalledTimes(callCountAfterUnavailable);
|
|
1577
|
+
} finally {
|
|
1578
|
+
vi.useRealTimers();
|
|
1579
|
+
}
|
|
1580
|
+
});
|
|
1581
|
+
|
|
1582
|
+
it('does not overlap live workstream polls while a previous request is pending', async () => {
|
|
1583
|
+
vi.useFakeTimers();
|
|
1584
|
+
const resolveQueries: Array<(value: { running: [] }) => void> = [];
|
|
1585
|
+
mocks.getActiveWorkstreams.mockImplementation(
|
|
1586
|
+
() =>
|
|
1587
|
+
new Promise<{ running: [] }>((resolve) => {
|
|
1588
|
+
resolveQueries.push(resolve);
|
|
1589
|
+
}),
|
|
1590
|
+
);
|
|
1591
|
+
mockStreamState({
|
|
1592
|
+
messages: [createMessage(AgentMessageType.QUESTION, 'main request')],
|
|
1593
|
+
isCompleted: false,
|
|
1594
|
+
initialHistoryStatus: 'has_messages',
|
|
1595
|
+
agentRunStatus: 'RUNNING',
|
|
1596
|
+
});
|
|
1597
|
+
|
|
1598
|
+
try {
|
|
1599
|
+
renderConversation();
|
|
1600
|
+
|
|
1601
|
+
const initialCallCount = mocks.getActiveWorkstreams.mock.calls.length;
|
|
1602
|
+
expect(initialCallCount).toBeGreaterThan(0);
|
|
1603
|
+
|
|
1604
|
+
await act(async () => {
|
|
1605
|
+
vi.advanceTimersByTime(10_000);
|
|
1606
|
+
});
|
|
1607
|
+
expect(mocks.getActiveWorkstreams).toHaveBeenCalledTimes(initialCallCount);
|
|
1608
|
+
|
|
1609
|
+
await act(async () => {
|
|
1610
|
+
for (const resolveQuery of resolveQueries) {
|
|
1611
|
+
resolveQuery({ running: [] });
|
|
1612
|
+
}
|
|
1613
|
+
});
|
|
1614
|
+
await act(async () => {
|
|
1615
|
+
vi.advanceTimersByTime(10_000);
|
|
1616
|
+
});
|
|
1617
|
+
expect(mocks.getActiveWorkstreams.mock.calls.length).toBeGreaterThan(initialCallCount);
|
|
1618
|
+
} finally {
|
|
1619
|
+
vi.useRealTimers();
|
|
1620
|
+
}
|
|
1621
|
+
});
|
|
1622
|
+
|
|
608
1623
|
it('hides the composer while viewing rewound playback history', () => {
|
|
609
1624
|
mockStreamState({
|
|
610
1625
|
messages: [
|
|
@@ -628,6 +1643,101 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
628
1643
|
expect(screen.getByRole('button', { name: 'composer send' })).not.toBeNull();
|
|
629
1644
|
});
|
|
630
1645
|
|
|
1646
|
+
it('shows a disabled MCP request overlay while viewing rewound playback history', async () => {
|
|
1647
|
+
mockStreamState({
|
|
1648
|
+
messages: [
|
|
1649
|
+
{
|
|
1650
|
+
...createMessage(AgentMessageType.REQUEST_INPUT, 'Connect to Jira to continue.'),
|
|
1651
|
+
timestamp: 1_000,
|
|
1652
|
+
details: {
|
|
1653
|
+
ux: {
|
|
1654
|
+
mcp_connect: {
|
|
1655
|
+
app_install_id: 'app1',
|
|
1656
|
+
collection_id: 'jira',
|
|
1657
|
+
name: 'Jira',
|
|
1658
|
+
},
|
|
1659
|
+
},
|
|
1660
|
+
},
|
|
1661
|
+
},
|
|
1662
|
+
{
|
|
1663
|
+
...createMessage(AgentMessageType.QUESTION, "I've connected to Jira."),
|
|
1664
|
+
timestamp: 2_000,
|
|
1665
|
+
},
|
|
1666
|
+
],
|
|
1667
|
+
isCompleted: false,
|
|
1668
|
+
agentRunStatus: 'RUNNING',
|
|
1669
|
+
});
|
|
1670
|
+
|
|
1671
|
+
renderConversation({ enablePlayback: true, hideMessageInput: false });
|
|
1672
|
+
|
|
1673
|
+
expect(screen.getByRole('button', { name: 'composer send' })).not.toBeNull();
|
|
1674
|
+
|
|
1675
|
+
fireEvent.click(screen.getByRole('button', { name: 'Jump to first message' }));
|
|
1676
|
+
|
|
1677
|
+
await waitFor(() => {
|
|
1678
|
+
expect(screen.getByText('Connect to Jira to continue.')).not.toBeNull();
|
|
1679
|
+
});
|
|
1680
|
+
expect(screen.queryByRole('button', { name: 'composer send' })).toBeNull();
|
|
1681
|
+
expect((screen.getByRole('button', { name: /connect/i }) as HTMLButtonElement).disabled).toBe(true);
|
|
1682
|
+
expect((screen.getByRole('button', { name: /decline/i }) as HTMLButtonElement).disabled).toBe(true);
|
|
1683
|
+
expect(mocks.allMessagesMixedProps.mock.lastCall?.[0]).toEqual(
|
|
1684
|
+
expect.objectContaining({ renderRequestInputControls: false }),
|
|
1685
|
+
);
|
|
1686
|
+
});
|
|
1687
|
+
|
|
1688
|
+
it('shows active workstreams for the rewound playback cursor', () => {
|
|
1689
|
+
mockStreamState({
|
|
1690
|
+
messages: [
|
|
1691
|
+
createMessage(AgentMessageType.QUESTION, 'Compare France and Japan news.'),
|
|
1692
|
+
{
|
|
1693
|
+
...createMessage(AgentMessageType.UPDATE, 'Japan workstream launched.'),
|
|
1694
|
+
timestamp: 2_000,
|
|
1695
|
+
workstream_id: 'japan_news',
|
|
1696
|
+
details: {
|
|
1697
|
+
event_class: 'activity',
|
|
1698
|
+
workstream_event: 'launched',
|
|
1699
|
+
workstream_id: 'japan_news',
|
|
1700
|
+
interaction: 'sys:AdhocTaskAgent',
|
|
1701
|
+
},
|
|
1702
|
+
},
|
|
1703
|
+
{
|
|
1704
|
+
...createMessage(AgentMessageType.THOUGHT, 'Japan internal search.'),
|
|
1705
|
+
timestamp: 3_000,
|
|
1706
|
+
workstream_id: 'japan_news',
|
|
1707
|
+
},
|
|
1708
|
+
{
|
|
1709
|
+
...createMessage(AgentMessageType.UPDATE, 'France workstream launched.'),
|
|
1710
|
+
timestamp: 4_000,
|
|
1711
|
+
workstream_id: 'france_news',
|
|
1712
|
+
details: {
|
|
1713
|
+
event_class: 'activity',
|
|
1714
|
+
workstream_event: 'launched',
|
|
1715
|
+
workstream_id: 'france_news',
|
|
1716
|
+
interaction: 'sys:AdhocTaskAgent',
|
|
1717
|
+
},
|
|
1718
|
+
},
|
|
1719
|
+
],
|
|
1720
|
+
isCompleted: false,
|
|
1721
|
+
agentRunStatus: 'RUNNING',
|
|
1722
|
+
});
|
|
1723
|
+
|
|
1724
|
+
renderConversation({ enablePlayback: true, hideMessageInput: false });
|
|
1725
|
+
|
|
1726
|
+
expect(document.querySelector('[data-agent-active-workstreams]')).toBeNull();
|
|
1727
|
+
|
|
1728
|
+
const playbackPositionInput = screen.getByRole('textbox', { name: 'Playback position' });
|
|
1729
|
+
fireEvent.change(playbackPositionInput, {
|
|
1730
|
+
target: { value: '2' },
|
|
1731
|
+
});
|
|
1732
|
+
fireEvent.blur(playbackPositionInput, {
|
|
1733
|
+
target: { value: '2' },
|
|
1734
|
+
});
|
|
1735
|
+
|
|
1736
|
+
expect(document.querySelector('[data-agent-active-workstreams]')).not.toBeNull();
|
|
1737
|
+
expect(screen.getByText('Japan News')).not.toBeNull();
|
|
1738
|
+
expect(screen.queryByRole('button', { name: 'composer send' })).toBeNull();
|
|
1739
|
+
});
|
|
1740
|
+
|
|
631
1741
|
it('exposes a local header toggle for playback controls', () => {
|
|
632
1742
|
mockStreamState({
|
|
633
1743
|
messages: [
|