@parhelia/core 0.1.12556 → 0.1.12560
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/agents-view/AgentCard.d.ts +6 -4
- package/dist/agents-view/AgentCard.js +143 -24
- package/dist/agents-view/AgentCard.js.map +1 -1
- package/dist/agents-view/AgentsInbox.d.ts +1 -1
- package/dist/agents-view/AgentsInbox.js +7 -92
- package/dist/agents-view/AgentsInbox.js.map +1 -1
- package/dist/agents-view/AgentsTitlebar.js +3 -2
- package/dist/agents-view/AgentsTitlebar.js.map +1 -1
- package/dist/agents-view/AgentsView.d.ts +6 -7
- package/dist/agents-view/AgentsView.js +187 -98
- package/dist/agents-view/AgentsView.js.map +1 -1
- package/dist/agents-view/AgentsWorkspaceView.d.ts +2 -6
- package/dist/agents-view/AgentsWorkspaceView.js +266 -113
- package/dist/agents-view/AgentsWorkspaceView.js.map +1 -1
- package/dist/agents-view/ProfileAgentsGroup.d.ts +2 -1
- package/dist/agents-view/ProfileAgentsGroup.js +4 -3
- package/dist/agents-view/ProfileAgentsGroup.js.map +1 -1
- package/dist/components/ActionButton.d.ts +1 -1
- package/dist/components/ActionButton.js.map +1 -1
- package/dist/components/FilterInput.d.ts +1 -1
- package/dist/components/FilterInput.js +1 -1
- package/dist/components/FilterInput.js.map +1 -1
- package/dist/components/ui/LanguageSelector.js +2 -4
- package/dist/components/ui/LanguageSelector.js.map +1 -1
- package/dist/components/ui/PlaceholderInput.js +3 -3
- package/dist/components/ui/PlaceholderInput.js.map +1 -1
- package/dist/components/ui/PlaceholderInputTypes.js +1 -1
- package/dist/components/ui/PlaceholderInputTypes.js.map +1 -1
- package/dist/components/ui/alert-dialog.d.ts +1 -1
- package/dist/components/ui/alert-dialog.js +6 -10
- package/dist/components/ui/alert-dialog.js.map +1 -1
- package/dist/components/ui/button.d.ts +4 -4
- package/dist/components/ui/button.js +4 -1
- package/dist/components/ui/button.js.map +1 -1
- package/dist/components/ui/context-menu.d.ts +1 -1
- package/dist/components/ui/context-menu.js +12 -4
- package/dist/components/ui/context-menu.js.map +1 -1
- package/dist/components/ui/copy-button.d.ts +2 -1
- package/dist/components/ui/copy-button.js +2 -2
- package/dist/components/ui/copy-button.js.map +1 -1
- package/dist/components/ui/dialog.d.ts +1 -1
- package/dist/components/ui/dialog.js +21 -126
- package/dist/components/ui/dialog.js.map +1 -1
- package/dist/components/ui/input.d.ts +1 -1
- package/dist/components/ui/input.js +5 -3
- package/dist/components/ui/input.js.map +1 -1
- package/dist/components/ui/paste-button.d.ts +2 -1
- package/dist/components/ui/paste-button.js +2 -2
- package/dist/components/ui/paste-button.js.map +1 -1
- package/dist/components/ui/popover.js +1 -9
- package/dist/components/ui/popover.js.map +1 -1
- package/dist/components/ui/select.js +1 -1
- package/dist/components/ui/select.js.map +1 -1
- package/dist/components/ui/styled-dialog-title.js +1 -1
- package/dist/components/ui/styled-dialog-title.js.map +1 -1
- package/dist/components/ui/tabs.d.ts +1 -1
- package/dist/components/ui/tabs.js +4 -11
- package/dist/components/ui/tabs.js.map +1 -1
- package/dist/config/config.d.ts +4 -2
- package/dist/config/config.js +250 -70
- package/dist/config/config.js.map +1 -1
- package/dist/config/notificationRoutes.js +14 -0
- package/dist/config/notificationRoutes.js.map +1 -1
- package/dist/config/types/workspace.d.ts +6 -0
- package/dist/config/types.d.ts +63 -12
- package/dist/config/types.js.map +1 -1
- package/dist/editor/ConfirmationDialog.js +20 -4
- package/dist/editor/ConfirmationDialog.js.map +1 -1
- package/dist/editor/ContentTree.d.ts +2 -1
- package/dist/editor/ContentTree.js +93 -32
- package/dist/editor/ContentTree.js.map +1 -1
- package/dist/editor/Editor.js +87 -22
- package/dist/editor/Editor.js.map +1 -1
- package/dist/editor/FieldHistory.js +84 -36
- package/dist/editor/FieldHistory.js.map +1 -1
- package/dist/editor/FieldListField.js +21 -9
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/FieldListFieldWithFallbacks.js +23 -2
- package/dist/editor/FieldListFieldWithFallbacks.js.map +1 -1
- package/dist/editor/GlobalMenuBar.js +29 -2
- package/dist/editor/GlobalMenuBar.js.map +1 -1
- package/dist/editor/ImageEditor.js +5 -2
- package/dist/editor/ImageEditor.js.map +1 -1
- package/dist/editor/ItemInfo.js +36 -1
- package/dist/editor/ItemInfo.js.map +1 -1
- package/dist/editor/LinkEditorDialog.js +3 -0
- package/dist/editor/LinkEditorDialog.js.map +1 -1
- package/dist/editor/MainLayout.d.ts +0 -2
- package/dist/editor/MainLayout.js +65 -8
- package/dist/editor/MainLayout.js.map +1 -1
- package/dist/editor/MigrationsView.js +29 -5
- package/dist/editor/MigrationsView.js.map +1 -1
- package/dist/editor/MobileLayout.js +37 -12
- package/dist/editor/MobileLayout.js.map +1 -1
- package/dist/editor/PictureCropper.js +54 -45
- package/dist/editor/PictureCropper.js.map +1 -1
- package/dist/editor/PictureEditor.js +17 -15
- package/dist/editor/PictureEditor.js.map +1 -1
- package/dist/editor/QuickItemSwitcher.js +21 -21
- package/dist/editor/QuickItemSwitcher.js.map +1 -1
- package/dist/editor/SetupWizard.js +52 -12
- package/dist/editor/SetupWizard.js.map +1 -1
- package/dist/editor/Titlebar.js +7 -2
- package/dist/editor/Titlebar.js.map +1 -1
- package/dist/editor/ai/AgentCostDisplay.d.ts +1 -0
- package/dist/editor/ai/AgentCostDisplay.js +1 -1
- package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
- package/dist/editor/ai/AgentDocumentList.js +32 -14
- package/dist/editor/ai/AgentDocumentList.js.map +1 -1
- package/dist/editor/ai/AgentGreeting.js +3 -2
- package/dist/editor/ai/AgentGreeting.js.map +1 -1
- package/dist/editor/ai/AgentProfileSelector.js +2 -1
- package/dist/editor/ai/AgentProfileSelector.js.map +1 -1
- package/dist/editor/ai/AgentStatusBadge.d.ts +0 -5
- package/dist/editor/ai/AgentStatusBadge.js +67 -65
- package/dist/editor/ai/AgentStatusBadge.js.map +1 -1
- package/dist/editor/ai/AgentTerminal.d.ts +14 -2
- package/dist/editor/ai/AgentTerminal.js +2377 -483
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/AgentTerminalStatusBar.d.ts +8 -3
- package/dist/editor/ai/AgentTerminalStatusBar.js +460 -56
- package/dist/editor/ai/AgentTerminalStatusBar.js.map +1 -1
- package/dist/editor/ai/Agents.js +150 -113
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/AiResponseMessage.d.ts +10 -1
- package/dist/editor/ai/AiResponseMessage.js +238 -23
- package/dist/editor/ai/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/ContextInfoBar.d.ts +2 -3
- package/dist/editor/ai/ContextInfoBar.js +64 -7
- package/dist/editor/ai/ContextInfoBar.js.map +1 -1
- package/dist/editor/ai/GuidanceOverlay.js +17 -11
- package/dist/editor/ai/GuidanceOverlay.js.map +1 -1
- package/dist/editor/ai/InlineAiDialog.d.ts +1 -1
- package/dist/editor/ai/InlineAiDialog.js +514 -192
- package/dist/editor/ai/InlineAiDialog.js.map +1 -1
- package/dist/editor/ai/InlineAiTrigger.js +115 -12
- package/dist/editor/ai/InlineAiTrigger.js.map +1 -1
- package/dist/editor/ai/MediaImage.js +40 -8
- package/dist/editor/ai/MediaImage.js.map +1 -1
- package/dist/editor/ai/SpawnedAgentsPanel.js +10 -12
- package/dist/editor/ai/SpawnedAgentsPanel.js.map +1 -1
- package/dist/editor/ai/ToolCallDisplay.d.ts +22 -2
- package/dist/editor/ai/ToolCallDisplay.js +518 -147
- package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
- package/dist/editor/ai/dialogs/AgentDialogHandler.d.ts +1 -8
- package/dist/editor/ai/dialogs/AgentDialogHandler.js +379 -42
- package/dist/editor/ai/dialogs/AgentDialogHandler.js.map +1 -1
- package/dist/editor/ai/dialogs/QuestionnaireInline.d.ts +5 -1
- package/dist/editor/ai/dialogs/QuestionnaireInline.js +628 -60
- package/dist/editor/ai/dialogs/QuestionnaireInline.js.map +1 -1
- package/dist/editor/ai/dialogs/agentDialogTypes.d.ts +115 -0
- package/dist/editor/ai/dialogs/agentDialogTypes.js +2 -0
- package/dist/editor/ai/dialogs/agentDialogTypes.js.map +1 -1
- package/dist/editor/ai/types.d.ts +3 -1
- package/dist/editor/ai/useAgentStatus.d.ts +2 -1
- package/dist/editor/ai/useAgentStatus.js +86 -99
- package/dist/editor/ai/useAgentStatus.js.map +1 -1
- package/dist/editor/ai/useInlineAiPosition.js +45 -5
- package/dist/editor/ai/useInlineAiPosition.js.map +1 -1
- package/dist/editor/client/AboutDialog.js +4 -2
- package/dist/editor/client/AboutDialog.js.map +1 -1
- package/dist/editor/client/EditorShell.d.ts +4 -1
- package/dist/editor/client/EditorShell.js +770 -237
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +33 -19
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/helpers.js +6 -0
- package/dist/editor/client/helpers.js.map +1 -1
- package/dist/editor/client/hooks/useEditorUrlSync.js +1 -2
- package/dist/editor/client/hooks/useEditorUrlSync.js.map +1 -1
- package/dist/editor/client/hooks/useEditorWebSocket.d.ts +10 -0
- package/dist/editor/client/hooks/useEditorWebSocket.js +209 -14
- package/dist/editor/client/hooks/useEditorWebSocket.js.map +1 -1
- package/dist/editor/client/hooks/useQuota.d.ts +8 -0
- package/dist/editor/client/hooks/useQuota.js.map +1 -1
- package/dist/editor/client/hooks/useSocketMessageHandler.js +68 -7
- package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
- package/dist/editor/client/itemsRepository.js +10 -6
- package/dist/editor/client/itemsRepository.js.map +1 -1
- package/dist/editor/client/navigation.js +35 -3
- package/dist/editor/client/navigation.js.map +1 -1
- package/dist/editor/client/operations.d.ts +6 -3
- package/dist/editor/client/operations.js +208 -30
- package/dist/editor/client/operations.js.map +1 -1
- package/dist/editor/client/pageModelBuilder.js +4 -31
- package/dist/editor/client/pageModelBuilder.js.map +1 -1
- package/dist/editor/client/ui/DevModeIndicator.js +2 -2
- package/dist/editor/client/ui/DevModeIndicator.js.map +1 -1
- package/dist/editor/client/ui/EditorChrome.d.ts +0 -6
- package/dist/editor/client/ui/EditorChrome.js +55 -72
- package/dist/editor/client/ui/EditorChrome.js.map +1 -1
- package/dist/editor/client/ui/FullscreenControls.js +5 -3
- package/dist/editor/client/ui/FullscreenControls.js.map +1 -1
- package/dist/editor/commands/commands.d.ts +11 -1
- package/dist/editor/commands/commands.js +12 -1
- package/dist/editor/commands/commands.js.map +1 -1
- package/dist/editor/commands/componentCommands.js +109 -55
- package/dist/editor/commands/componentCommands.js.map +1 -1
- package/dist/editor/commands/customCommandConverter.d.ts +8 -1
- package/dist/editor/commands/customCommandConverter.js +35 -5
- package/dist/editor/commands/customCommandConverter.js.map +1 -1
- package/dist/editor/commands/handlers/agentHandler.js +2 -1
- package/dist/editor/commands/handlers/agentHandler.js.map +1 -1
- package/dist/editor/commands/itemCommands.d.ts +3 -0
- package/dist/editor/commands/itemCommands.js +93 -10
- package/dist/editor/commands/itemCommands.js.map +1 -1
- package/dist/editor/commands/undo.d.ts +9 -15
- package/dist/editor/commands/undo.js +24 -0
- package/dist/editor/commands/undo.js.map +1 -1
- package/dist/editor/context-menu/InsertMenu.js +83 -39
- package/dist/editor/context-menu/InsertMenu.js.map +1 -1
- package/dist/editor/field-types/MultiLineText.js +1 -1
- package/dist/editor/field-types/MultiLineText.js.map +1 -1
- package/dist/editor/field-types/RawEditor.js +1 -1
- package/dist/editor/field-types/RichTextEditor.js +13 -5
- package/dist/editor/field-types/RichTextEditor.js.map +1 -1
- package/dist/editor/field-types/RichTextEditorComponent.js +37 -3
- package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
- package/dist/editor/field-types/SingleLineText.js +1 -1
- package/dist/editor/field-types/TreeListEditor.js +3 -2
- package/dist/editor/field-types/TreeListEditor.js.map +1 -1
- package/dist/editor/field-types/richtext/components/ReactSlate.css +23 -5
- package/dist/editor/field-types/richtext/components/ReactSlate.d.ts +2 -0
- package/dist/editor/field-types/richtext/components/ReactSlate.js +28 -4
- package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
- package/dist/editor/field-types/richtext/components/ToolbarButton.js +4 -2
- package/dist/editor/field-types/richtext/components/ToolbarButton.js.map +1 -1
- package/dist/editor/field-types/richtext/contextMenuFactory.d.ts +13 -0
- package/dist/editor/field-types/richtext/contextMenuFactory.js +181 -24
- package/dist/editor/field-types/richtext/contextMenuFactory.js.map +1 -1
- package/dist/editor/field-types/richtext/types.d.ts +2 -0
- package/dist/editor/field-types/richtext/types.js.map +1 -1
- package/dist/editor/field-types/richtext/utils/plugins.js +4 -0
- package/dist/editor/field-types/richtext/utils/plugins.js.map +1 -1
- package/dist/editor/field-types/textContextMenuFactory.js +3 -2
- package/dist/editor/field-types/textContextMenuFactory.js.map +1 -1
- package/dist/editor/media-selector/AiImageSearchPrompt.js +4 -2
- package/dist/editor/media-selector/AiImageSearchPrompt.js.map +1 -1
- package/dist/editor/media-selector/MediaFolderBrowser.js +1 -1
- package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
- package/dist/editor/media-selector/MediaSelector.js +7 -1
- package/dist/editor/media-selector/MediaSelector.js.map +1 -1
- package/dist/editor/media-selector/TreeSelector.js +40 -35
- package/dist/editor/media-selector/TreeSelector.js.map +1 -1
- package/dist/editor/menubar/ActiveUsers.js +1 -1
- package/dist/editor/menubar/ActiveUsers.js.map +1 -1
- package/dist/editor/menubar/GenericToolbar.js +4 -2
- package/dist/editor/menubar/GenericToolbar.js.map +1 -1
- package/dist/editor/menubar/ItemLanguageVersion.js +2 -2
- package/dist/editor/menubar/ItemLanguageVersion.js.map +1 -1
- package/dist/editor/menubar/PageSelector.js +26 -147
- package/dist/editor/menubar/PageSelector.js.map +1 -1
- package/dist/editor/menubar/Separator.js +1 -1
- package/dist/editor/menubar/VersionSelector.js +2 -4
- package/dist/editor/menubar/VersionSelector.js.map +1 -1
- package/dist/editor/menubar/WorkflowButton.js +39 -12
- package/dist/editor/menubar/WorkflowButton.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/CustomCommandsToolbar.js +16 -38
- package/dist/editor/menubar/toolbar-sections/CustomCommandsToolbar.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/EditControls.js +3 -3
- package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/HelpButton.js +1 -0
- package/dist/editor/menubar/toolbar-sections/HelpButton.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.d.ts +6 -10
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +597 -220
- package/dist/editor/menubar/toolbar-sections/ManualBrowser.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js +13 -2
- package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
- package/dist/editor/page-editor-chrome/CommentHighlighting.js +42 -1
- package/dist/editor/page-editor-chrome/CommentHighlighting.js.map +1 -1
- package/dist/editor/page-editor-chrome/FrameMenu.js +1 -1
- package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
- package/dist/editor/page-editor-chrome/InlineEditor.js +97 -48
- package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +38 -17
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +17 -11
- package/dist/editor/page-editor-chrome/PlaceholderDropZones.js.map +1 -1
- package/dist/editor/page-editor-chrome/useInlineAICompletion.js +301 -301
- package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -1
- package/dist/editor/page-viewer/DeviceToolbar.js +1 -1
- package/dist/editor/page-viewer/DeviceToolbar.js.map +1 -1
- package/dist/editor/page-viewer/EditorForm.js +69 -11
- package/dist/editor/page-viewer/EditorForm.js.map +1 -1
- package/dist/editor/page-viewer/MiniMap.d.ts +2 -4
- package/dist/editor/page-viewer/MiniMap.js +91 -28
- package/dist/editor/page-viewer/MiniMap.js.map +1 -1
- package/dist/editor/page-viewer/PageViewer.d.ts +3 -1
- package/dist/editor/page-viewer/PageViewer.js +92 -19
- package/dist/editor/page-viewer/PageViewer.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.d.ts +2 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +348 -115
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/page-viewer/pageModelSkeletonBuilder.js +114 -49
- package/dist/editor/page-viewer/pageModelSkeletonBuilder.js.map +1 -1
- package/dist/editor/page-viewer/pageViewContext.d.ts +1 -0
- package/dist/editor/page-viewer/pageViewContext.js +51 -14
- package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
- package/dist/editor/pageModel.d.ts +14 -1
- package/dist/editor/reviews/Comment.js +26 -12
- package/dist/editor/reviews/Comment.js.map +1 -1
- package/dist/editor/reviews/CommentDisplayPopover.js +7 -5
- package/dist/editor/reviews/CommentDisplayPopover.js.map +1 -1
- package/dist/editor/reviews/CommentView.js +19 -4
- package/dist/editor/reviews/CommentView.js.map +1 -1
- package/dist/editor/reviews/Comments.js +89 -72
- package/dist/editor/reviews/Comments.js.map +1 -1
- package/dist/editor/reviews/CreateReviewDialog.js +281 -177
- package/dist/editor/reviews/CreateReviewDialog.js.map +1 -1
- package/dist/editor/reviews/DecisionsMatrix.js +96 -25
- package/dist/editor/reviews/DecisionsMatrix.js.map +1 -1
- package/dist/editor/reviews/DiffView.js +7 -14
- package/dist/editor/reviews/DiffView.js.map +1 -1
- package/dist/editor/reviews/EditReviewSettingsDialog.js +6 -4
- package/dist/editor/reviews/EditReviewSettingsDialog.js.map +1 -1
- package/dist/editor/reviews/MultiReviewManager.js +25 -3
- package/dist/editor/reviews/MultiReviewManager.js.map +1 -1
- package/dist/editor/reviews/PagesPanel.js +31 -15
- package/dist/editor/reviews/PagesPanel.js.map +1 -1
- package/dist/editor/reviews/PreviewInfo.js +1 -4
- package/dist/editor/reviews/PreviewInfo.js.map +1 -1
- package/dist/editor/reviews/ReviewCard.js +13 -7
- package/dist/editor/reviews/ReviewCard.js.map +1 -1
- package/dist/editor/reviews/ReviewDetail.js +3 -2
- package/dist/editor/reviews/ReviewDetail.js.map +1 -1
- package/dist/editor/reviews/ReviewsList.js +7 -3
- package/dist/editor/reviews/ReviewsList.js.map +1 -1
- package/dist/editor/reviews/SuggestedEdit.js +34 -3
- package/dist/editor/reviews/SuggestedEdit.js.map +1 -1
- package/dist/editor/reviews/SuggestionDisplayPopover.js +31 -5
- package/dist/editor/reviews/SuggestionDisplayPopover.js.map +1 -1
- package/dist/editor/reviews/commentAi.js +25 -6
- package/dist/editor/reviews/commentAi.js.map +1 -1
- package/dist/editor/reviews/reviewCommands.js +4 -1
- package/dist/editor/reviews/reviewCommands.js.map +1 -1
- package/dist/editor/reviews/useMultiReview.js +2 -2
- package/dist/editor/reviews/useMultiReview.js.map +1 -1
- package/dist/editor/reviews/useReviews.d.ts +2 -2
- package/dist/editor/reviews/useReviews.js +12 -30
- package/dist/editor/reviews/useReviews.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +229 -5
- package/dist/editor/services/agentService.js +292 -39
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/services/aiService.d.ts +57 -1
- package/dist/editor/services/aiService.js +79 -6
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/editor/services/contentService.d.ts +6 -3
- package/dist/editor/services/contentService.js +13 -12
- package/dist/editor/services/contentService.js.map +1 -1
- package/dist/editor/services/editService.d.ts +52 -1
- package/dist/editor/services/editService.js +94 -2
- package/dist/editor/services/editService.js.map +1 -1
- package/dist/editor/services/indexService.js +1 -1
- package/dist/editor/services/indexService.js.map +1 -1
- package/dist/editor/services/reviewsService.d.ts +3 -6
- package/dist/editor/services/reviewsService.js +2 -11
- package/dist/editor/services/reviewsService.js.map +1 -1
- package/dist/editor/services/serviceHelper.d.ts +2 -1
- package/dist/editor/services/serviceHelper.js +112 -20
- package/dist/editor/services/serviceHelper.js.map +1 -1
- package/dist/editor/services/systemService.d.ts +2 -1
- package/dist/editor/services/systemService.js +3 -0
- package/dist/editor/services/systemService.js.map +1 -1
- package/dist/editor/services-server/api.d.ts +1 -2
- package/dist/editor/services-server/api.js +11 -6
- package/dist/editor/services-server/api.js.map +1 -1
- package/dist/editor/settings/About.js +317 -3
- package/dist/editor/settings/About.js.map +1 -1
- package/dist/editor/settings/QuotaInfo.js +210 -4
- package/dist/editor/settings/QuotaInfo.js.map +1 -1
- package/dist/editor/settings/SettingsView.js +25 -23
- package/dist/editor/settings/SettingsView.js.map +1 -1
- package/dist/editor/settings/Status.js +7 -6
- package/dist/editor/settings/Status.js.map +1 -1
- package/dist/editor/settings/index/useIndexStatus.js +20 -22
- package/dist/editor/settings/index/useIndexStatus.js.map +1 -1
- package/dist/editor/settings/panels/AgentsPanel.d.ts +0 -4
- package/dist/editor/settings/panels/AgentsPanel.js +95 -121
- package/dist/editor/settings/panels/AgentsPanel.js.map +1 -1
- package/dist/editor/settings/panels/ModelsPanel.js +329 -108
- package/dist/editor/settings/panels/ModelsPanel.js.map +1 -1
- package/dist/editor/settings/panels/ProvidersPanel.d.ts +1 -1
- package/dist/editor/settings/panels/ProvidersPanel.js +86 -59
- package/dist/editor/settings/panels/ProvidersPanel.js.map +1 -1
- package/dist/editor/settings/panels/SearchConfigPanel.js +4 -4
- package/dist/editor/settings/panels/SearchConfigPanel.js.map +1 -1
- package/dist/editor/settings/panels/index.d.ts +3 -2
- package/dist/editor/settings/panels/index.js +3 -2
- package/dist/editor/settings/panels/index.js.map +1 -1
- package/dist/editor/settings/status/coreStatusChecks.js +124 -19
- package/dist/editor/settings/status/coreStatusChecks.js.map +1 -1
- package/dist/editor/settings/status/useStartupChecks.d.ts +3 -1
- package/dist/editor/settings/status/useStartupChecks.js +9 -5
- package/dist/editor/settings/status/useStartupChecks.js.map +1 -1
- package/dist/editor/setup-wizard/steps/CompleteStep.d.ts +2 -1
- package/dist/editor/setup-wizard/steps/CompleteStep.js +2 -1
- package/dist/editor/setup-wizard/steps/CompleteStep.js.map +1 -1
- package/dist/editor/sidebar/ComponentPalette.js +2 -1
- package/dist/editor/sidebar/ComponentPalette.js.map +1 -1
- package/dist/editor/sidebar/ComponentTree.d.ts +8 -1
- package/dist/editor/sidebar/ComponentTree.js +216 -69
- package/dist/editor/sidebar/ComponentTree.js.map +1 -1
- package/dist/editor/sidebar/EditHistory.js +22 -46
- package/dist/editor/sidebar/EditHistory.js.map +1 -1
- package/dist/editor/sidebar/Favorites.js +4 -8
- package/dist/editor/sidebar/Favorites.js.map +1 -1
- package/dist/editor/sidebar/MainContentTree.js +4 -3
- package/dist/editor/sidebar/MainContentTree.js.map +1 -1
- package/dist/editor/sidebar/OperationItem.js +21 -7
- package/dist/editor/sidebar/OperationItem.js.map +1 -1
- package/dist/editor/sidebar/SidebarPanel.d.ts +3 -1
- package/dist/editor/sidebar/SidebarPanel.js +44 -12
- package/dist/editor/sidebar/SidebarPanel.js.map +1 -1
- package/dist/editor/sidebar/SidebarStack.d.ts +2 -1
- package/dist/editor/sidebar/SidebarStack.js +4 -3
- package/dist/editor/sidebar/SidebarStack.js.map +1 -1
- package/dist/editor/sidebar/Validation.js +22 -12
- package/dist/editor/sidebar/Validation.js.map +1 -1
- package/dist/editor/sidebar/Workbox.js +53 -3
- package/dist/editor/sidebar/Workbox.js.map +1 -1
- package/dist/editor/sidebar/WorkspaceRail.d.ts +0 -1
- package/dist/editor/sidebar/WorkspaceRail.js +56 -167
- package/dist/editor/sidebar/WorkspaceRail.js.map +1 -1
- package/dist/editor/tree-indicators/GutterColumns.d.ts +3 -1
- package/dist/editor/tree-indicators/GutterColumns.js +26 -5
- package/dist/editor/tree-indicators/GutterColumns.js.map +1 -1
- package/dist/editor/tree-indicators/GutterContext.d.ts +4 -0
- package/dist/editor/tree-indicators/GutterContext.js +23 -0
- package/dist/editor/tree-indicators/GutterContext.js.map +1 -1
- package/dist/editor/tree-indicators/index.d.ts +0 -1
- package/dist/editor/tree-indicators/index.js +0 -1
- package/dist/editor/tree-indicators/index.js.map +1 -1
- package/dist/editor/tree-indicators/types.d.ts +12 -1
- package/dist/editor/ui/CopyMoveTargetSelectorDialog.js +1 -1
- package/dist/editor/ui/CopyMoveTargetSelectorDialog.js.map +1 -1
- package/dist/editor/ui/Icons.js +1 -1
- package/dist/editor/ui/Icons.js.map +1 -1
- package/dist/editor/ui/ItemNameDialogNew.d.ts +2 -0
- package/dist/editor/ui/ItemNameDialogNew.js +33 -17
- package/dist/editor/ui/ItemNameDialogNew.js.map +1 -1
- package/dist/editor/ui/ItemSearch.js +7 -11
- package/dist/editor/ui/ItemSearch.js.map +1 -1
- package/dist/editor/ui/SimpleIconButton.js +1 -1
- package/dist/editor/ui/SimpleIconButton.js.map +1 -1
- package/dist/editor/ui/SimpleTabs.d.ts +1 -0
- package/dist/editor/ui/SimpleTabs.js +45 -25
- package/dist/editor/ui/SimpleTabs.js.map +1 -1
- package/dist/editor/ui/Splitter.d.ts +1 -0
- package/dist/editor/ui/Splitter.js +102 -86
- package/dist/editor/ui/Splitter.js.map +1 -1
- package/dist/editor/ui/TemplateSelectorDialog.js +4 -4
- package/dist/editor/ui/TemplateSelectorDialog.js.map +1 -1
- package/dist/editor/ui/TreeListSelector.d.ts +6 -1
- package/dist/editor/ui/TreeListSelector.js +2 -2
- package/dist/editor/ui/TreeListSelector.js.map +1 -1
- package/dist/editor/utils/keyboardNavigation.d.ts +6 -20
- package/dist/editor/utils/keyboardNavigation.js +48 -140
- package/dist/editor/utils/keyboardNavigation.js.map +1 -1
- package/dist/editor/utils.js +19 -9
- package/dist/editor/utils.js.map +1 -1
- package/dist/editor/views/CompareView.d.ts +3 -1
- package/dist/editor/views/CompareView.js +7 -5
- package/dist/editor/views/CompareView.js.map +1 -1
- package/dist/editor/views/EditView.js +1 -1
- package/dist/editor/views/EditView.js.map +1 -1
- package/dist/editor/views/EditorSlot.js +27 -34
- package/dist/editor/views/EditorSlot.js.map +1 -1
- package/dist/editor/views/ItemEditor.js +7 -3
- package/dist/editor/views/ItemEditor.js.map +1 -1
- package/dist/editor/views/MediaFolderEditView.js +1 -1
- package/dist/editor/views/MediaFolderEditView.js.map +1 -1
- package/dist/editor/views/ParheliaView.js +5 -6
- package/dist/editor/views/ParheliaView.js.map +1 -1
- package/dist/editor/views/SingleEditView.d.ts +2 -1
- package/dist/editor/views/SingleEditView.js +10 -8
- package/dist/editor/views/SingleEditView.js.map +1 -1
- package/dist/editor/views/editorSlotContext.js +35 -6
- package/dist/editor/views/editorSlotContext.js.map +1 -1
- package/dist/index.d.ts +16 -2
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/setup/services/setupWizardService.d.ts +40 -13
- package/dist/setup/services/setupWizardService.js +32 -17
- package/dist/setup/services/setupWizardService.js.map +1 -1
- package/dist/setup/wizard/steps/AddModelDialog.js +12 -3
- package/dist/setup/wizard/steps/AddModelDialog.js.map +1 -1
- package/dist/setup/wizard/steps/ImportModelDialog.js +39 -22
- package/dist/setup/wizard/steps/ImportModelDialog.js.map +1 -1
- package/dist/splash-screen/ModernSplashScreen.js +112 -32
- package/dist/splash-screen/ModernSplashScreen.js.map +1 -1
- package/dist/splash-screen/NewPage.js +33 -50
- package/dist/splash-screen/NewPage.js.map +1 -1
- package/dist/splash-screen/OpenPage.js +2 -6
- package/dist/splash-screen/OpenPage.js.map +1 -1
- package/dist/splash-screen/ParheliaAssistantChat.js +12 -29
- package/dist/splash-screen/ParheliaAssistantChat.js.map +1 -1
- package/dist/splash-screen/ParheliaLogo.js +87 -37
- package/dist/splash-screen/ParheliaLogo.js.map +1 -1
- package/dist/splash-screen/RecentPages.js +3 -3
- package/dist/splash-screen/RecentPages.js.map +1 -1
- package/dist/tour/Tour.d.ts +2 -1
- package/dist/tour/Tour.js +256 -75
- package/dist/tour/Tour.js.map +1 -1
- package/dist/tour/default-tour.js +222 -96
- package/dist/tour/default-tour.js.map +1 -1
- package/dist/types.d.ts +63 -29
- package/package.json +19 -15
- package/styles.css +14 -10
- package/dist/editor/ComponentInfo.d.ts +0 -4
- package/dist/editor/ComponentInfo.js +0 -41
- package/dist/editor/ComponentInfo.js.map +0 -1
- package/dist/editor/ai/HelpTerminal.d.ts +0 -5
- package/dist/editor/ai/HelpTerminal.js +0 -166
- package/dist/editor/ai/HelpTerminal.js.map +0 -1
- package/dist/editor/field-types/ReactQuill.d.ts +0 -125
- package/dist/editor/field-types/ReactQuill.js +0 -385
- package/dist/editor/field-types/ReactQuill.js.map +0 -1
- package/dist/editor/services-server/graphQL.d.ts +0 -29
- package/dist/editor/services-server/graphQL.js +0 -53
- package/dist/editor/services-server/graphQL.js.map +0 -1
- package/dist/editor/settings/AllAgentsPanel.d.ts +0 -5
- package/dist/editor/settings/AllAgentsPanel.js +0 -139
- package/dist/editor/settings/AllAgentsPanel.js.map +0 -1
- package/dist/editor/settings/LatestFeedback.d.ts +0 -1
- package/dist/editor/settings/LatestFeedback.js +0 -136
- package/dist/editor/settings/LatestFeedback.js.map +0 -1
- package/dist/editor/settings/Setup.d.ts +0 -1
- package/dist/editor/settings/Setup.js +0 -211
- package/dist/editor/settings/Setup.js.map +0 -1
- package/dist/editor/settings/panels/DatabasePanel.d.ts +0 -6
- package/dist/editor/settings/panels/DatabasePanel.js +0 -50
- package/dist/editor/settings/panels/DatabasePanel.js.map +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.d.ts +0 -2
- package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.js +0 -195
- package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.js.map +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/index.d.ts +0 -2
- package/dist/editor/settings/setup-steps/AiSetupStep/index.js +0 -21
- package/dist/editor/settings/setup-steps/AiSetupStep/index.js.map +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.d.ts +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.js +0 -233
- package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.js.map +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.d.ts +0 -15
- package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.js +0 -14
- package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.js.map +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.d.ts +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.js +0 -94
- package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.js.map +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/types.d.ts +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/types.js +0 -2
- package/dist/editor/settings/setup-steps/AiSetupStep/types.js.map +0 -1
- package/dist/editor/settings/setup-steps/AiSetupStep/utils.d.ts +0 -5
- package/dist/editor/settings/setup-steps/AiSetupStep/utils.js +0 -44
- package/dist/editor/settings/setup-steps/AiSetupStep/utils.js.map +0 -1
- package/dist/editor/settings/setup-steps/IndexSetupStep.d.ts +0 -2
- package/dist/editor/settings/setup-steps/IndexSetupStep.js +0 -36
- package/dist/editor/settings/setup-steps/IndexSetupStep.js.map +0 -1
- package/dist/editor/settings/setup-steps/SettingsSetupStep.d.ts +0 -2
- package/dist/editor/settings/setup-steps/SettingsSetupStep.js +0 -111
- package/dist/editor/settings/setup-steps/SettingsSetupStep.js.map +0 -1
- package/dist/editor/settings/setup-steps/SetupOverview.d.ts +0 -14
- package/dist/editor/settings/setup-steps/SetupOverview.js +0 -38
- package/dist/editor/settings/setup-steps/SetupOverview.js.map +0 -1
- package/dist/editor/sidebar/Debug.d.ts +0 -1
- package/dist/editor/sidebar/Debug.js +0 -70
- package/dist/editor/sidebar/Debug.js.map +0 -1
- package/dist/editor/sidebar/GraphQL.d.ts +0 -2
- package/dist/editor/sidebar/GraphQL.js +0 -234
- package/dist/editor/sidebar/GraphQL.js.map +0 -1
- package/dist/editor/sidebar/LeftToolbar.d.ts +0 -1
- package/dist/editor/sidebar/LeftToolbar.js +0 -12
- package/dist/editor/sidebar/LeftToolbar.js.map +0 -1
- package/dist/editor/sidebar/NavigationSidebar.d.ts +0 -4
- package/dist/editor/sidebar/NavigationSidebar.js +0 -254
- package/dist/editor/sidebar/NavigationSidebar.js.map +0 -1
- package/dist/editor/tree-indicators/GutterSelector.d.ts +0 -5
- package/dist/editor/tree-indicators/GutterSelector.js +0 -91
- package/dist/editor/tree-indicators/GutterSelector.js.map +0 -1
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import React, { useEffect, useState, useRef, useCallback, useLayoutEffect, useMemo, } from "react";
|
|
3
|
-
import { Send, AlertCircle, Loader2, User, Wand2, Square, Mic, MicOff, ChevronDown, ChevronUp, ListTodo, ArrowLeft, DollarSign, } from "lucide-react";
|
|
4
|
-
import { getAgent, startAgent, updateAgentSettings, updateAgentCostLimit, updateAgentContext, cancelAgent, canonicalizeAgentMetadata, getPendingPrompts, } from "../services/agentService";
|
|
3
|
+
import { Send, AlertCircle, Loader2, User, Wand2, Square, Mic, MicOff, ChevronDown, ChevronUp, ListTodo, ArrowLeft, DollarSign, ExternalLink, Settings2, Target, X, Plus, } from "lucide-react";
|
|
4
|
+
import { getAgent, startAgent, claimAgentBrowser, assignAgentSkill, persistDraftAgent, updateAgentSettings, updateAgentCostLimit, updateAgentContext, getAgentSkillCatalog, getAgentAvailableTools, getAgentOperationAllowances, getAgentTriggerSubscriptions, cancelAgent, canonicalizeAgentMetadata, getPendingPrompts, releaseAgentBrowser, revokeAgentSkill, } from "../services/agentService";
|
|
5
|
+
import { parseAgentStatus } from "../services/agentStatus";
|
|
5
6
|
import { useEditContext, useFieldsEditContext } from "../client/editContext";
|
|
7
|
+
import { localStorageService } from "../services/localStorageService";
|
|
6
8
|
import { Textarea } from "../../components/ui/textarea";
|
|
7
9
|
import { Button } from "../../components/ui/button";
|
|
8
10
|
import { PlaceholderInput, } from "../../components/ui/PlaceholderInput";
|
|
@@ -14,19 +16,210 @@ import { SpawnedAgentsPanel } from "./SpawnedAgentsPanel";
|
|
|
14
16
|
import { getComponentById } from "../componentTreeHelper";
|
|
15
17
|
import { AgentGreeting } from "./AgentGreeting";
|
|
16
18
|
import { getAgentHistory } from "../services/editService";
|
|
19
|
+
import { QuestionnaireInline } from "./dialogs/QuestionnaireInline";
|
|
20
|
+
import { getBrowserCaptureClaim, setBrowserCaptureClaim, } from "./dialogs/browserBoundCapture";
|
|
21
|
+
import { DIALOG_TYPES, } from "./dialogs/agentDialogTypes";
|
|
17
22
|
import { Popover, PopoverContent, PopoverTrigger, } from "../../components/ui/popover";
|
|
18
23
|
import { SecretAgentIcon } from "../ui/Icons";
|
|
19
24
|
import { formatTime, formatDateTime } from "../utils";
|
|
20
25
|
import { cn } from "../../lib/utils";
|
|
26
|
+
import { sanitizeSvg } from "../../lib/sanitize";
|
|
21
27
|
import { Select } from "../../components/ui/select";
|
|
22
28
|
import { AgentTerminalStatusBar } from "./AgentTerminalStatusBar";
|
|
23
|
-
import { useMediaQuery } from "../client/hooks/useMediaQuery";
|
|
24
29
|
import { SimpleTabs } from "../ui/SimpleTabs";
|
|
30
|
+
import { Splitter } from "../ui/Splitter";
|
|
31
|
+
import { ScrollingContentTree } from "../ScrollingContentTree";
|
|
32
|
+
import { MarkdownDisplay, } from "../../components/MarkdownDisplay";
|
|
33
|
+
const userMessageMarkdownComponents = {
|
|
34
|
+
h1: (props) => (_jsx("h1", { ...props, className: "mb-2 text-sm leading-5 font-semibold text-gray-900" })),
|
|
35
|
+
h2: (props) => (_jsx("h2", { ...props, className: "mb-1.5 text-[13px] leading-5 font-semibold text-gray-900" })),
|
|
36
|
+
h3: (props) => (_jsx("h3", { ...props, className: "mb-1 text-[12px] leading-5 font-semibold text-gray-900" })),
|
|
37
|
+
h4: (props) => (_jsx("h4", { ...props, className: "mb-1 text-[12px] leading-5 font-medium text-gray-800" })),
|
|
38
|
+
p: (props) => (_jsx("p", { ...props, className: "my-1 text-[12px] leading-5 text-gray-700" })),
|
|
39
|
+
ul: (props) => (_jsx("ul", { ...props, className: "my-2 ml-5 list-disc space-y-1 text-[12px] leading-5 text-gray-700" })),
|
|
40
|
+
ol: (props) => (_jsx("ol", { ...props, className: "my-2 ml-5 list-decimal space-y-1 text-[12px] leading-5 text-gray-700" })),
|
|
41
|
+
li: (props) => (_jsx("li", { ...props, className: "text-[12px] leading-5 text-gray-700" })),
|
|
42
|
+
pre: (props) => (_jsx("pre", { ...props, className: "my-2 overflow-auto rounded-md bg-slate-100 px-3 py-2 text-[11px] leading-4 text-slate-700" })),
|
|
43
|
+
code: ({ inline, className, ...props }) => inline ? (_jsx("code", { ...props, className: "rounded bg-slate-100 px-1 py-0.5 text-[11px] text-slate-700" })) : (_jsx("code", { ...props, className: className })),
|
|
44
|
+
};
|
|
45
|
+
function buildPlaceholderAgentDetails(agentStub) {
|
|
46
|
+
const now = new Date().toISOString();
|
|
47
|
+
const updated = agentStub.updatedDate || now;
|
|
48
|
+
// AgentDetails has required fields, but some workspaces only pass an Agent stub initially.
|
|
49
|
+
// This placeholder keeps streaming/tool-call UI working until `getAgent()` returns full details.
|
|
50
|
+
return {
|
|
51
|
+
...agentStub,
|
|
52
|
+
name: agentStub.name || "Agent",
|
|
53
|
+
userId: agentStub.userId || "",
|
|
54
|
+
updatedDate: updated,
|
|
55
|
+
profileName: agentStub.profileName || "",
|
|
56
|
+
model: agentStub.model || "",
|
|
57
|
+
createdDate: agentStub.createdDate || updated,
|
|
58
|
+
totalTokensUsed: 0,
|
|
59
|
+
totalInputTokens: 0,
|
|
60
|
+
totalOutputTokens: 0,
|
|
61
|
+
totalCachedInputTokens: 0,
|
|
62
|
+
totalInputTokenCost: 0,
|
|
63
|
+
totalOutputTokenCost: 0,
|
|
64
|
+
totalCachedInputTokenCost: 0,
|
|
65
|
+
totalImageCost: 0,
|
|
66
|
+
totalCost: 0,
|
|
67
|
+
currency: agentStub.currency || "USD",
|
|
68
|
+
messageCount: agentStub.messageCount || 0,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
function normalizeDialogAgentId(value) {
|
|
72
|
+
return value?.trim().toLowerCase() || "";
|
|
73
|
+
}
|
|
74
|
+
function formatAllowanceSource(source) {
|
|
75
|
+
const normalized = source?.trim();
|
|
76
|
+
if (!normalized)
|
|
77
|
+
return null;
|
|
78
|
+
if (normalized === "user")
|
|
79
|
+
return "User granted";
|
|
80
|
+
if (normalized.startsWith("preconfigured:profile:"))
|
|
81
|
+
return "Profile";
|
|
82
|
+
if (normalized.startsWith("preconfigured:skill:"))
|
|
83
|
+
return "Skill";
|
|
84
|
+
if (normalized.startsWith("system:")) {
|
|
85
|
+
return normalized.slice("system:".length).replace(/[-_]+/g, " ").trim();
|
|
86
|
+
}
|
|
87
|
+
return normalized;
|
|
88
|
+
}
|
|
89
|
+
function formatAllowanceLabel(allowance) {
|
|
90
|
+
return `${allowance.operationType || "*"}${"itemPath" in allowance
|
|
91
|
+
? ` ${allowance.itemPath}`
|
|
92
|
+
: ` ${allowance.normalizedPath}`}`;
|
|
93
|
+
}
|
|
94
|
+
function getAgentRunMessageAgentId(payload) {
|
|
95
|
+
const agentId = payload?.agentId;
|
|
96
|
+
return typeof agentId === "string" ? normalizeDialogAgentId(agentId) : null;
|
|
97
|
+
}
|
|
98
|
+
function getAgentRunMessageSeq(payload) {
|
|
99
|
+
const seq = payload?.seq;
|
|
100
|
+
return typeof seq === "number" ? seq : null;
|
|
101
|
+
}
|
|
102
|
+
function getAgentRunMessageDetail(type, payload) {
|
|
103
|
+
if (type === "agent:run:delta") {
|
|
104
|
+
return payload?.type || null;
|
|
105
|
+
}
|
|
106
|
+
if (type === "agent:run:status") {
|
|
107
|
+
return payload?.data?.state || payload?.data?.status || null;
|
|
108
|
+
}
|
|
109
|
+
if (type === "agent:run:error") {
|
|
110
|
+
return payload?.error || null;
|
|
111
|
+
}
|
|
112
|
+
if (type === "agent:run:complete") {
|
|
113
|
+
return payload?.finalStatus || null;
|
|
114
|
+
}
|
|
115
|
+
if (type === "agent:run:start") {
|
|
116
|
+
return payload?.agentName || null;
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
function isHeartbeatRunEventMessage(message) {
|
|
121
|
+
if (!message)
|
|
122
|
+
return false;
|
|
123
|
+
if (message.type === "agent:run:delta") {
|
|
124
|
+
const deltaType = message.payload?.type;
|
|
125
|
+
return String(deltaType || "").toLowerCase() === "heartbeat";
|
|
126
|
+
}
|
|
127
|
+
if (message.type === "agent:run:status") {
|
|
128
|
+
const state = message.payload?.data?.state || message.payload?.data?.status;
|
|
129
|
+
return String(state || "").toLowerCase() === "heartbeat";
|
|
130
|
+
}
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
function getVisibleDialogRegistry() {
|
|
134
|
+
const registry = globalThis.__agentDialogVisibleCallbacks;
|
|
135
|
+
return registry && typeof registry === "object" ? registry : {};
|
|
136
|
+
}
|
|
137
|
+
function decodeHtmlEntities(value) {
|
|
138
|
+
if (typeof document === "undefined") {
|
|
139
|
+
return value;
|
|
140
|
+
}
|
|
141
|
+
const textarea = document.createElement("textarea");
|
|
142
|
+
textarea.innerHTML = value;
|
|
143
|
+
return textarea.value;
|
|
144
|
+
}
|
|
145
|
+
function toUserFacingAgentErrorMessage(value) {
|
|
146
|
+
if (!value)
|
|
147
|
+
return "";
|
|
148
|
+
const normalizedValue = decodeHtmlEntities(value.replace(/<br\s*\/?>/gi, "\n")).trim();
|
|
149
|
+
if (!normalizedValue)
|
|
150
|
+
return "";
|
|
151
|
+
const trimmed = normalizedValue.trim();
|
|
152
|
+
const maybeJson = trimmed.startsWith("{") ||
|
|
153
|
+
trimmed.startsWith("[") ||
|
|
154
|
+
trimmed.startsWith('"');
|
|
155
|
+
if (maybeJson) {
|
|
156
|
+
try {
|
|
157
|
+
const parsed = JSON.parse(trimmed);
|
|
158
|
+
const structuredMessage = typeof parsed === "string"
|
|
159
|
+
? parsed
|
|
160
|
+
: typeof parsed?.error === "object" && parsed?.error
|
|
161
|
+
? (() => {
|
|
162
|
+
const errObj = parsed.error;
|
|
163
|
+
// Extract detailed message from metadata.raw (OpenRouter-style nested errors)
|
|
164
|
+
if (typeof errObj.metadata?.raw ===
|
|
165
|
+
"string") {
|
|
166
|
+
return String(errObj.metadata.raw);
|
|
167
|
+
}
|
|
168
|
+
// Fall back to error.message
|
|
169
|
+
if (typeof errObj.message === "string") {
|
|
170
|
+
return errObj.message;
|
|
171
|
+
}
|
|
172
|
+
return "";
|
|
173
|
+
})()
|
|
174
|
+
: typeof parsed?.error === "string"
|
|
175
|
+
? parsed.error
|
|
176
|
+
: typeof parsed?.message === "string"
|
|
177
|
+
? parsed.message
|
|
178
|
+
: typeof parsed?.detail === "string"
|
|
179
|
+
? parsed.detail
|
|
180
|
+
: typeof parsed?.error_description === "string"
|
|
181
|
+
? parsed.error_description
|
|
182
|
+
: "";
|
|
183
|
+
if (structuredMessage.trim()) {
|
|
184
|
+
value = structuredMessage;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Fall through to plain-text cleanup when the provider error isn't valid JSON.
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const firstLine = value
|
|
192
|
+
.replace(/<br\s*\/?>/gi, "\n")
|
|
193
|
+
.split(/\r?\n/)
|
|
194
|
+
.map((line) => line.trim())
|
|
195
|
+
.find(Boolean);
|
|
196
|
+
const cleaned = decodeHtmlEntities(firstLine || value)
|
|
197
|
+
.replace(/^Failed to start agent:\s*/i, "")
|
|
198
|
+
.replace(/^Unknown error\s*/i, "")
|
|
199
|
+
.replace(/^Error:\s*/i, "")
|
|
200
|
+
.replace(/^Error\s+/i, "")
|
|
201
|
+
.replace(/\s+/g, " ")
|
|
202
|
+
.trim();
|
|
203
|
+
return cleaned;
|
|
204
|
+
}
|
|
205
|
+
function isAgentErrorStatusValue(status) {
|
|
206
|
+
return status === "error";
|
|
207
|
+
}
|
|
25
208
|
// Simple user message component
|
|
26
209
|
const UserMessage = ({ message }) => {
|
|
210
|
+
const content = message.content || "";
|
|
211
|
+
const [isTriggerExpanded, setIsTriggerExpanded] = useState(false);
|
|
212
|
+
// Trigger-sourced prompts are prefixed by backend as "[Trigger: {name}]: {content}"
|
|
213
|
+
const triggerPattern = /^\[Trigger: ([^\]]+)\]:\s*(.*)$/s;
|
|
214
|
+
const triggerMatch = content.match(triggerPattern);
|
|
215
|
+
const triggerName = triggerMatch?.[1]?.trim() || "";
|
|
216
|
+
const triggerContent = triggerMatch?.[2] || "";
|
|
217
|
+
const isTriggerMessage = triggerName.length > 0;
|
|
218
|
+
if (isTriggerMessage) {
|
|
219
|
+
return (_jsx("div", { className: "px-4 py-2", children: _jsxs("div", { className: "text-[11px]", children: [_jsxs("button", { type: "button", onClick: () => setIsTriggerExpanded((expanded) => !expanded), className: "text-theme-secondary hover:bg-theme-hover flex w-full items-center gap-2 rounded-md border-l-2 border-cyan-200 px-2 py-1 text-left transition-colors", "data-testid": "trigger-message-toggle", "data-expanded": isTriggerExpanded ? "true" : "false", children: [_jsx(Target, { className: "h-3.5 w-3.5 shrink-0", strokeWidth: 1.5 }), _jsxs("span", { className: "truncate font-medium", children: ["Trigger: ", triggerName] }), message.createdDate && (_jsx("span", { className: "ml-1 shrink-0 text-[10px] text-gray-400", children: formatTime(new Date(message.createdDate)) })), _jsx("span", { className: "ml-auto shrink-0", children: isTriggerExpanded ? (_jsx(ChevronUp, { className: "h-3.5 w-3.5" })) : (_jsx(ChevronDown, { className: "h-3.5 w-3.5" })) })] }), isTriggerExpanded && (_jsx("div", { className: "mt-1 border-l-2 border-cyan-100 pl-[1.35rem] text-[11px] text-gray-600", children: _jsx(MarkdownDisplay, { source: triggerContent, components: userMessageMarkdownComponents }) }))] }) }));
|
|
220
|
+
}
|
|
27
221
|
// Parse source agent name from content if it starts with "[From ...]:"
|
|
28
222
|
// Backend formats messages from other agents as "[From {sourceAgentName}]: {content}"
|
|
29
|
-
const content = message.content || "";
|
|
30
223
|
const fromPattern = /^\[From ([^\]]+)\]:\s*(.*)$/s;
|
|
31
224
|
const match = content.match(fromPattern);
|
|
32
225
|
let sourceAgentName;
|
|
@@ -51,7 +244,10 @@ const UserMessage = ({ message }) => {
|
|
|
51
244
|
message.sourceAgent?.name;
|
|
52
245
|
}
|
|
53
246
|
const displayName = sourceAgentName ? `[From ${sourceAgentName}]` : "You";
|
|
54
|
-
return (_jsxs("div", { className: "flex gap-3 p-4", children: [_jsx("div", { className: "shrink-0", children: _jsx(User, { className: "text-theme-secondary h-5 w-5", strokeWidth: 1 }) }), _jsxs("div", { className: "min-w-0 flex-1 select-text", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [_jsx("span", { className: "text-[12px] font-medium text-gray-900", children: displayName }), message.createdDate && (_jsx("span", { className: "text-[12px] text-gray-400", "data-testid": "user-message-timestamp", "data-timestamp": message.createdDate, children: formatTime(new Date(message.createdDate)) }))] }), _jsx("div", { className: "prose prose max-w-none text-[12px] text-gray-700 select-text", children: displayContent })] })] }));
|
|
247
|
+
return (_jsxs("div", { className: "flex gap-3 p-4", children: [_jsx("div", { className: "shrink-0", children: _jsx(User, { className: "text-theme-secondary h-5 w-5", strokeWidth: 1 }) }), _jsxs("div", { className: "min-w-0 flex-1 select-text", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [_jsx("span", { className: "text-[12px] font-medium text-gray-900", children: displayName }), message.createdDate && (_jsx("span", { className: "text-[12px] text-gray-400", "data-testid": "user-message-timestamp", "data-timestamp": message.createdDate, children: formatTime(new Date(message.createdDate)) }))] }), _jsx("div", { className: "prose prose max-w-none text-[12px] text-gray-700 select-text", children: _jsx(MarkdownDisplay, { source: displayContent, components: userMessageMarkdownComponents }) })] })] }));
|
|
248
|
+
};
|
|
249
|
+
const HeartbeatMessage = ({ message }) => {
|
|
250
|
+
return (_jsx("div", { className: "px-4 py-2", "data-testid": "agent-heartbeat-message", children: _jsxs("div", { className: "flex items-center gap-2 rounded-md border border-sky-100 bg-sky-50/80 px-3 py-2 text-[11px] text-sky-700", children: [_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin", strokeWidth: 1.5 }), _jsx("span", { className: "min-w-0 flex-1", children: message.content }), message.createdDate && (_jsx("span", { className: "shrink-0 text-[10px] text-sky-500", children: formatTime(new Date(message.createdDate)) }))] }) }));
|
|
55
251
|
};
|
|
56
252
|
// Helper to extract todos from potentially incomplete JSON during streaming
|
|
57
253
|
const extractPartialTodos = (jsonText) => {
|
|
@@ -285,14 +481,6 @@ const extractTodosFromMessages = (messages) => {
|
|
|
285
481
|
todoMap.set(key, todo);
|
|
286
482
|
}
|
|
287
483
|
const result = Array.from(todoMap.values());
|
|
288
|
-
// Only log when duplicates were actually removed (to reduce noise)
|
|
289
|
-
if (todos.length > result.length) {
|
|
290
|
-
console.log("🟡 TODO_DEBUG deduplication:", {
|
|
291
|
-
removed: todos.length - result.length,
|
|
292
|
-
before: todos.length,
|
|
293
|
-
after: result.length,
|
|
294
|
-
});
|
|
295
|
-
}
|
|
296
484
|
return result;
|
|
297
485
|
};
|
|
298
486
|
// TodoListPanel component
|
|
@@ -300,12 +488,9 @@ const TodoListPanel = ({ messages, agentMetadata, }) => {
|
|
|
300
488
|
const [isExpanded, setIsExpanded] = useState(true);
|
|
301
489
|
// First try to get todos from agent metadata (real-time updates)
|
|
302
490
|
// Server sends additionalData.todoList directly via contextChanged status
|
|
303
|
-
// Also check top-level todoList for backward compatibility with stored contexts
|
|
304
491
|
const metadataTodos = (() => {
|
|
305
492
|
try {
|
|
306
|
-
|
|
307
|
-
const todoList = agentMetadata?.additionalData?.todoList ||
|
|
308
|
-
agentMetadata?.todoList;
|
|
493
|
+
const todoList = agentMetadata?.additionalData?.todoList;
|
|
309
494
|
if (todoList?.items && Array.isArray(todoList.items)) {
|
|
310
495
|
const rawItems = todoList.items
|
|
311
496
|
.map((item, idx) => ({
|
|
@@ -433,6 +618,17 @@ const groupConsecutiveMessages = (agentMessages) => {
|
|
|
433
618
|
// Add user message
|
|
434
619
|
groups.push({ type: "user", messages: [message] });
|
|
435
620
|
}
|
|
621
|
+
else if (message.messageType === "heartbeat" ||
|
|
622
|
+
message.role === "system") {
|
|
623
|
+
if (currentAssistantGroup.length > 0) {
|
|
624
|
+
groups.push({
|
|
625
|
+
type: "assistant-group",
|
|
626
|
+
messages: currentAssistantGroup,
|
|
627
|
+
});
|
|
628
|
+
currentAssistantGroup = [];
|
|
629
|
+
}
|
|
630
|
+
groups.push({ type: "heartbeat", messages: [message] });
|
|
631
|
+
}
|
|
436
632
|
else if (message.role === "assistant") {
|
|
437
633
|
// Add to current assistant group
|
|
438
634
|
currentAssistantGroup.push(message);
|
|
@@ -500,6 +696,7 @@ const calculateTotalTokens = (messages) => {
|
|
|
500
696
|
outputCost: acc.outputCost + (message.outputTokenCost || 0),
|
|
501
697
|
cachedCost: acc.cachedCost + (message.cachedInputTokenCost || 0),
|
|
502
698
|
cacheWriteCost: acc.cacheWriteCost,
|
|
699
|
+
imageCost: acc.imageCost,
|
|
503
700
|
totalCost: acc.totalCost + (message.totalCost || 0),
|
|
504
701
|
};
|
|
505
702
|
}, {
|
|
@@ -511,6 +708,7 @@ const calculateTotalTokens = (messages) => {
|
|
|
511
708
|
outputCost: 0,
|
|
512
709
|
cachedCost: 0,
|
|
513
710
|
cacheWriteCost: 0,
|
|
711
|
+
imageCost: 0,
|
|
514
712
|
totalCost: 0,
|
|
515
713
|
});
|
|
516
714
|
return totals;
|
|
@@ -521,6 +719,84 @@ const getOperationsForMessageGroup = (messages, agentOperations) => {
|
|
|
521
719
|
const matched = agentOperations.filter((op) => op.toolCallId && toolCallIds.has(op.toolCallId));
|
|
522
720
|
return matched;
|
|
523
721
|
};
|
|
722
|
+
const stringifyToolField = (value) => {
|
|
723
|
+
if (value === undefined || value === null)
|
|
724
|
+
return undefined;
|
|
725
|
+
if (typeof value === "string") {
|
|
726
|
+
return value.trim().length > 0 ? value : undefined;
|
|
727
|
+
}
|
|
728
|
+
try {
|
|
729
|
+
return JSON.stringify(value);
|
|
730
|
+
}
|
|
731
|
+
catch {
|
|
732
|
+
return String(value);
|
|
733
|
+
}
|
|
734
|
+
};
|
|
735
|
+
const parseToolResultValue = (value) => {
|
|
736
|
+
if (value === undefined || value === null) {
|
|
737
|
+
return undefined;
|
|
738
|
+
}
|
|
739
|
+
if (typeof value === "object") {
|
|
740
|
+
return value;
|
|
741
|
+
}
|
|
742
|
+
if (typeof value !== "string") {
|
|
743
|
+
return String(value);
|
|
744
|
+
}
|
|
745
|
+
const trimmed = value.trim();
|
|
746
|
+
if (!trimmed) {
|
|
747
|
+
return undefined;
|
|
748
|
+
}
|
|
749
|
+
try {
|
|
750
|
+
let parsed = JSON.parse(trimmed);
|
|
751
|
+
if (typeof parsed === "string" &&
|
|
752
|
+
(parsed.startsWith("{") || parsed.startsWith("["))) {
|
|
753
|
+
parsed = JSON.parse(parsed);
|
|
754
|
+
}
|
|
755
|
+
if (parsed && typeof parsed === "object") {
|
|
756
|
+
return parsed;
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
catch {
|
|
760
|
+
// Fall back to the original string when the payload is plain text.
|
|
761
|
+
}
|
|
762
|
+
return value;
|
|
763
|
+
};
|
|
764
|
+
const getFirstToolCallEnvelope = (data) => {
|
|
765
|
+
if (!data || typeof data !== "object")
|
|
766
|
+
return undefined;
|
|
767
|
+
const direct = data.toolCall || data.tool_call;
|
|
768
|
+
if (direct)
|
|
769
|
+
return direct;
|
|
770
|
+
const arrayCandidates = [data.tool_calls, data.toolCalls];
|
|
771
|
+
for (const candidate of arrayCandidates) {
|
|
772
|
+
if (Array.isArray(candidate) && candidate.length > 0) {
|
|
773
|
+
return candidate[0];
|
|
774
|
+
}
|
|
775
|
+
}
|
|
776
|
+
return undefined;
|
|
777
|
+
};
|
|
778
|
+
const extractToolCallFields = (data) => {
|
|
779
|
+
const envelope = getFirstToolCallEnvelope(data);
|
|
780
|
+
const functionPayload = data?.function || envelope?.function;
|
|
781
|
+
const functionName = data?.functionName ||
|
|
782
|
+
data?.name ||
|
|
783
|
+
functionPayload?.name ||
|
|
784
|
+
envelope?.functionName ||
|
|
785
|
+
envelope?.name ||
|
|
786
|
+
"unknown";
|
|
787
|
+
const toolCallId = data?.toolCallId || data?.id || envelope?.id;
|
|
788
|
+
const functionArguments = stringifyToolField(data?.functionArguments) ||
|
|
789
|
+
stringifyToolField(data?.arguments) ||
|
|
790
|
+
stringifyToolField(functionPayload?.arguments) ||
|
|
791
|
+
stringifyToolField(envelope?.functionArguments) ||
|
|
792
|
+
stringifyToolField(envelope?.arguments) ||
|
|
793
|
+
"{}";
|
|
794
|
+
return {
|
|
795
|
+
toolCallId,
|
|
796
|
+
functionName,
|
|
797
|
+
functionArguments,
|
|
798
|
+
};
|
|
799
|
+
};
|
|
524
800
|
// Convert agent messages to AI terminal format for a response group
|
|
525
801
|
const convertAgentMessagesToAiFormat = (agentMessages) => {
|
|
526
802
|
return agentMessages.map((agentMessage) => {
|
|
@@ -541,28 +817,39 @@ const convertAgentMessagesToAiFormat = (agentMessages) => {
|
|
|
541
817
|
role: agentMessage.role,
|
|
542
818
|
createdDate: agentMessage.createdDate,
|
|
543
819
|
tool_calls: agentMessage.toolCalls
|
|
544
|
-
? agentMessage.toolCalls.map((toolCall) =>
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
820
|
+
? agentMessage.toolCalls.map((toolCall) => {
|
|
821
|
+
const isPruned = !!toolCall.isPruned ||
|
|
822
|
+
/^PRUNED$/i.test(toolCall.functionError || "");
|
|
823
|
+
const displayResult = parseToolResultValue(toolCall.functionResultRichContent) ?? toolCall.functionResult;
|
|
824
|
+
return {
|
|
825
|
+
id: toolCall.toolCallId,
|
|
826
|
+
displayName: toolCall.functionName,
|
|
827
|
+
function: {
|
|
828
|
+
name: toolCall.functionName,
|
|
829
|
+
arguments: toolCall.functionArguments,
|
|
830
|
+
result: displayResult,
|
|
831
|
+
error: toolCall.functionError,
|
|
832
|
+
},
|
|
833
|
+
// Pass through approval info if present on the tool call
|
|
834
|
+
requiresApproval: toolCall.requiresApproval,
|
|
835
|
+
// Pass through prune metadata so the terminal can render a neutral state
|
|
836
|
+
isPruned,
|
|
837
|
+
prunedAt: toolCall.prunedAt,
|
|
838
|
+
// Pass through isCompleted so ToolCallDisplay knows when to hide spinner
|
|
839
|
+
isCompleted: toolCall.isCompleted,
|
|
840
|
+
// Tool call is streaming if message is not completed and tool call has no result yet
|
|
841
|
+
isStreaming: !agentMessage.isCompleted &&
|
|
842
|
+
!toolCall.isCompleted &&
|
|
843
|
+
!displayResult &&
|
|
844
|
+
!toolCall.functionError &&
|
|
845
|
+
!isPruned,
|
|
846
|
+
// Pass through message IDs for approval/rejection events
|
|
847
|
+
messageId: toolCall.messageId,
|
|
848
|
+
dbMessageId: toolCall.dbMessageId,
|
|
849
|
+
responseTimeMs: toolCall.responseTimeMs,
|
|
850
|
+
createdDate: toolCall.createdDate,
|
|
851
|
+
};
|
|
852
|
+
})
|
|
566
853
|
: [],
|
|
567
854
|
};
|
|
568
855
|
if (agentMessage.toolCallId) {
|
|
@@ -574,10 +861,10 @@ const convertAgentMessagesToAiFormat = (agentMessages) => {
|
|
|
574
861
|
// interface AgentTerminalProps {
|
|
575
862
|
// agentStub: Agent;
|
|
576
863
|
// }
|
|
577
|
-
export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive = true, compact = false, hideContext = false, hideBottomControls = false, hideGreeting = false, simpleMode = false, className, initialPrompt, onAgentUpdate, }) {
|
|
864
|
+
export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive = true, compact = false, displayMode = "full", showSummaryInput = false, hideContext = false, hideBottomControls = false, hideGreeting = false, defaultCollapseJson = false, simpleMode = false, className, initialPrompt, onAgentUpdate, onMessage, onInteractionSubmitted, onQuestionnaireOpenChange, questionnaireFooterActions, hideSummaryMessages = false, summaryPlaceholderActions, summaryPlaceholderMessage, hideSummaryWaitingPlaceholder = false, }) {
|
|
578
865
|
const editContext = useEditContext();
|
|
579
866
|
const fieldsContext = useFieldsEditContext();
|
|
580
|
-
const [agent, setAgent] = useState(
|
|
867
|
+
const [agent, setAgent] = useState(() => buildPlaceholderAgentDetails(agentStub));
|
|
581
868
|
const [messages, setMessages] = useState([]);
|
|
582
869
|
const [agentOperations, setAgentOperations] = useState([]);
|
|
583
870
|
const [prompt, setPrompt] = useState("");
|
|
@@ -588,6 +875,35 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
588
875
|
const [activePlaceholderInput, setActivePlaceholderInput] = useState(null);
|
|
589
876
|
const [allPlaceholdersFilled, setAllPlaceholdersFilled] = useState(false);
|
|
590
877
|
const [agentMetadata, setAgentMetadata] = useState(null);
|
|
878
|
+
const [isBrowserClaimMutationPending, setIsBrowserClaimMutationPending] = useState(false);
|
|
879
|
+
const [pendingBrowserCaptureDialogType, setPendingBrowserCaptureDialogType] = useState(null);
|
|
880
|
+
// Ensure we always have an agent object for streaming handlers, even before `getAgent()` resolves.
|
|
881
|
+
// This prevents early tool calls (e.g., ask-questionnaire) from being dropped in compact/workspace UIs.
|
|
882
|
+
useEffect(() => {
|
|
883
|
+
setAgent((prev) => {
|
|
884
|
+
if (prev?.id === agentStub.id)
|
|
885
|
+
return prev;
|
|
886
|
+
return buildPlaceholderAgentDetails(agentStub);
|
|
887
|
+
});
|
|
888
|
+
}, [agentStub.id]);
|
|
889
|
+
const observedMessageIdsRef = useRef(new Set());
|
|
890
|
+
useEffect(() => {
|
|
891
|
+
observedMessageIdsRef.current = new Set();
|
|
892
|
+
}, [agentStub.id]);
|
|
893
|
+
useEffect(() => {
|
|
894
|
+
if (!onMessage)
|
|
895
|
+
return;
|
|
896
|
+
for (const message of messages) {
|
|
897
|
+
if (!message?.id)
|
|
898
|
+
continue;
|
|
899
|
+
if (!message.isCompleted)
|
|
900
|
+
continue;
|
|
901
|
+
if (observedMessageIdsRef.current.has(message.id))
|
|
902
|
+
continue;
|
|
903
|
+
observedMessageIdsRef.current.add(message.id);
|
|
904
|
+
onMessage(message);
|
|
905
|
+
}
|
|
906
|
+
}, [messages, onMessage]);
|
|
591
907
|
// Generate a stable clientSessionId per component instance for stream deduplication
|
|
592
908
|
const clientSessionIdRef = useRef(null);
|
|
593
909
|
if (!clientSessionIdRef.current) {
|
|
@@ -599,6 +915,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
599
915
|
const [isListening, setIsListening] = useState(false);
|
|
600
916
|
const recognitionRef = useRef(null);
|
|
601
917
|
const prevPlaceholderRef = useRef(null);
|
|
918
|
+
const promptBeforeVoiceRef = useRef("");
|
|
602
919
|
// Voice button press-and-hold tracking
|
|
603
920
|
const voicePressStartRef = useRef(null);
|
|
604
921
|
const voiceHoldTimerRef = useRef(null);
|
|
@@ -608,6 +925,70 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
608
925
|
const [showCompressionPopover, setShowCompressionPopover] = useState(false);
|
|
609
926
|
// Agent inline dialog state (for component type selector, etc.)
|
|
610
927
|
const [activeInlineDialog, setActiveInlineDialog] = useState(null);
|
|
928
|
+
const dialogTerminalInstanceIdRef = useRef("");
|
|
929
|
+
if (!dialogTerminalInstanceIdRef.current) {
|
|
930
|
+
dialogTerminalInstanceIdRef.current = crypto.randomUUID();
|
|
931
|
+
}
|
|
932
|
+
const activeInlineDialogRef = useRef(activeInlineDialog);
|
|
933
|
+
const isQuestionnaireDialogOpen = activeInlineDialog?.request.dialogType === "questionnaire";
|
|
934
|
+
const orphanTimeoutRef = useRef(null);
|
|
935
|
+
useEffect(() => {
|
|
936
|
+
activeInlineDialogRef.current = activeInlineDialog;
|
|
937
|
+
const visibleRegistry = { ...getVisibleDialogRegistry() };
|
|
938
|
+
const callbackId = activeInlineDialog?.request.callbackId || null;
|
|
939
|
+
const terminalInstanceId = dialogTerminalInstanceIdRef.current;
|
|
940
|
+
const agentKeys = [
|
|
941
|
+
normalizeDialogAgentId(agentStubIdRefForDialogs.current),
|
|
942
|
+
normalizeDialogAgentId(agentIdRefForDialogs.current),
|
|
943
|
+
].filter(Boolean);
|
|
944
|
+
agentKeys.forEach((key) => {
|
|
945
|
+
if (callbackId) {
|
|
946
|
+
visibleRegistry[key] = {
|
|
947
|
+
callbackId,
|
|
948
|
+
terminalInstanceId,
|
|
949
|
+
};
|
|
950
|
+
}
|
|
951
|
+
else {
|
|
952
|
+
delete visibleRegistry[key];
|
|
953
|
+
}
|
|
954
|
+
});
|
|
955
|
+
globalThis.__agentDialogVisibleCallbacks = visibleRegistry;
|
|
956
|
+
}, [activeInlineDialog]);
|
|
957
|
+
useEffect(() => {
|
|
958
|
+
onQuestionnaireOpenChange?.(isQuestionnaireDialogOpen);
|
|
959
|
+
}, [isQuestionnaireDialogOpen, onQuestionnaireOpenChange]);
|
|
960
|
+
useLayoutEffect(() => {
|
|
961
|
+
if (displayMode !== "summary" || !isQuestionnaireDialogOpen) {
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
const scrollSummaryTerminalToTop = () => {
|
|
965
|
+
const container = messagesContainerRef.current;
|
|
966
|
+
if (!container) {
|
|
967
|
+
return;
|
|
968
|
+
}
|
|
969
|
+
container.scrollTop = 0;
|
|
970
|
+
};
|
|
971
|
+
scrollSummaryTerminalToTop();
|
|
972
|
+
const frame1 = requestAnimationFrame(() => {
|
|
973
|
+
scrollSummaryTerminalToTop();
|
|
974
|
+
});
|
|
975
|
+
let frame3 = 0;
|
|
976
|
+
const frame2 = requestAnimationFrame(() => {
|
|
977
|
+
frame3 = requestAnimationFrame(() => {
|
|
978
|
+
scrollSummaryTerminalToTop();
|
|
979
|
+
});
|
|
980
|
+
});
|
|
981
|
+
return () => {
|
|
982
|
+
cancelAnimationFrame(frame1);
|
|
983
|
+
cancelAnimationFrame(frame2);
|
|
984
|
+
cancelAnimationFrame(frame3);
|
|
985
|
+
};
|
|
986
|
+
}, [displayMode, isQuestionnaireDialogOpen]);
|
|
987
|
+
useEffect(() => {
|
|
988
|
+
return () => {
|
|
989
|
+
onQuestionnaireOpenChange?.(false);
|
|
990
|
+
};
|
|
991
|
+
}, [onQuestionnaireOpenChange]);
|
|
611
992
|
const isWaitingRef = useRef(false);
|
|
612
993
|
useEffect(() => {
|
|
613
994
|
isWaitingRef.current = isWaitingForResponse;
|
|
@@ -618,10 +999,49 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
618
999
|
const isStoppingRef = useRef(false);
|
|
619
1000
|
// Server-driven state: true when agent is actively processing (set by WebSocket messages)
|
|
620
1001
|
const [isAgentThinking, setIsAgentThinking] = useState(false);
|
|
1002
|
+
useEffect(() => {
|
|
1003
|
+
if (!initialMetadata)
|
|
1004
|
+
return;
|
|
1005
|
+
setAgentMetadata((prev) => sanitizeAgentMetadata({
|
|
1006
|
+
...(prev || {}),
|
|
1007
|
+
...initialMetadata,
|
|
1008
|
+
additionalData: {
|
|
1009
|
+
...(prev?.additionalData || {}),
|
|
1010
|
+
...(initialMetadata?.additionalData || {}),
|
|
1011
|
+
},
|
|
1012
|
+
}));
|
|
1013
|
+
}, [initialMetadata]);
|
|
621
1014
|
const hasActiveStreaming = useCallback(() => {
|
|
622
1015
|
const current = messagesRef.current || [];
|
|
623
1016
|
return current.some((m) => !m.isCompleted && m.messageType === "streaming");
|
|
624
1017
|
}, []);
|
|
1018
|
+
const currentAgentId = agent?.id || agentStub.id;
|
|
1019
|
+
const recentAgentRunEvents = useMemo(() => {
|
|
1020
|
+
const normalizedAgentId = normalizeDialogAgentId(currentAgentId);
|
|
1021
|
+
if (!normalizedAgentId) {
|
|
1022
|
+
return [];
|
|
1023
|
+
}
|
|
1024
|
+
if (!editContext) {
|
|
1025
|
+
return [];
|
|
1026
|
+
}
|
|
1027
|
+
return (editContext.webSocketMessages || [])
|
|
1028
|
+
.filter((message) => {
|
|
1029
|
+
if (!message?.type?.startsWith("agent:run:")) {
|
|
1030
|
+
return false;
|
|
1031
|
+
}
|
|
1032
|
+
if (isHeartbeatRunEventMessage(message)) {
|
|
1033
|
+
return false;
|
|
1034
|
+
}
|
|
1035
|
+
return getAgentRunMessageAgentId(message.payload) === normalizedAgentId;
|
|
1036
|
+
})
|
|
1037
|
+
.slice(-8)
|
|
1038
|
+
.map((message) => ({
|
|
1039
|
+
timestamp: message.timestamp,
|
|
1040
|
+
type: message.type,
|
|
1041
|
+
seq: getAgentRunMessageSeq(message.payload),
|
|
1042
|
+
detail: getAgentRunMessageDetail(message.type, message.payload),
|
|
1043
|
+
}));
|
|
1044
|
+
}, [currentAgentId, editContext?.webSocketMessages]);
|
|
625
1045
|
// Collect all pending tool calls for batch approval functionality
|
|
626
1046
|
const allPendingApprovals = useMemo(() => {
|
|
627
1047
|
const pending = [];
|
|
@@ -663,41 +1083,82 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
663
1083
|
}, [allPendingApprovals]);
|
|
664
1084
|
// Handle mode switch to autonomous
|
|
665
1085
|
const handleSwitchToAutonomous = useCallback(() => {
|
|
666
|
-
|
|
667
|
-
|
|
1086
|
+
const nextMode = "autonomous";
|
|
1087
|
+
setMode(nextMode);
|
|
1088
|
+
setAgentMetadata((prev) => ({
|
|
1089
|
+
...(prev ?? {}),
|
|
1090
|
+
mode: nextMode,
|
|
1091
|
+
}));
|
|
1092
|
+
setAgent((prev) => {
|
|
1093
|
+
if (!prev)
|
|
1094
|
+
return prev;
|
|
1095
|
+
return {
|
|
1096
|
+
...prev,
|
|
1097
|
+
mode: nextMode,
|
|
1098
|
+
};
|
|
1099
|
+
});
|
|
1100
|
+
}, [setAgent, setAgentMetadata]);
|
|
668
1101
|
const [resolvedPageName, setResolvedPageName] = useState(undefined);
|
|
669
1102
|
const [resolvedComponentName, setResolvedComponentName] = useState(undefined);
|
|
670
1103
|
const [resolvedFieldName, setResolvedFieldName] = useState(undefined);
|
|
671
1104
|
const [promptHistory, setPromptHistory] = useState(() => {
|
|
672
|
-
|
|
673
|
-
return JSON.parse(localStorage.getItem("editor.agent.promptHistory") || "[]");
|
|
674
|
-
}
|
|
675
|
-
return [];
|
|
1105
|
+
return (localStorageService.getItem("editor.agent.promptHistory") || []);
|
|
676
1106
|
});
|
|
677
1107
|
const [currentHistoryIndex, setCurrentHistoryIndex] = useState(-1);
|
|
678
1108
|
const [showPredefined, setShowPredefined] = useState(false);
|
|
679
1109
|
const [activeProfile, setActiveProfile] = useState(undefined);
|
|
680
1110
|
const [selectedModelId, setSelectedModelId] = useState(undefined);
|
|
1111
|
+
const normalizeAgentMode = (value) => {
|
|
1112
|
+
if (value === "autonomous" ||
|
|
1113
|
+
value === "read-only" ||
|
|
1114
|
+
value === "supervised") {
|
|
1115
|
+
return value;
|
|
1116
|
+
}
|
|
1117
|
+
return null;
|
|
1118
|
+
};
|
|
681
1119
|
const [mode, setMode] = useState("supervised");
|
|
682
1120
|
const [queuedPrompts, setQueuedPrompts] = useState([]);
|
|
683
|
-
const
|
|
1121
|
+
const [expandedQueuedTriggerIds, setExpandedQueuedTriggerIds] = useState({});
|
|
684
1122
|
const [contextPanelsActiveTab, setContextPanelsActiveTab] = useState(0);
|
|
685
1123
|
const [hiddenContextPanelTabIds, setHiddenContextPanelTabIds] = useState(new Set());
|
|
686
1124
|
const [showCostAndAgent, setShowCostAndAgent] = useState(false);
|
|
1125
|
+
const [showAgentSettings, setShowAgentSettings] = useState(false);
|
|
1126
|
+
const [showSkillPicker, setShowSkillPicker] = useState(false);
|
|
1127
|
+
const [availableSkills, setAvailableSkills] = useState([]);
|
|
1128
|
+
const [skillRootIds, setSkillRootIds] = useState([]);
|
|
1129
|
+
const [selectableTemplateIds, setSelectableTemplateIds] = useState([]);
|
|
1130
|
+
const [skillsLoading, setSkillsLoading] = useState(false);
|
|
1131
|
+
const [skillsError, setSkillsError] = useState(null);
|
|
1132
|
+
const [skillActionError, setSkillActionError] = useState(null);
|
|
1133
|
+
const [triggerSubscriptions, setTriggerSubscriptions] = useState([]);
|
|
1134
|
+
const [availableTools, setAvailableTools] = useState([]);
|
|
1135
|
+
const [operationAllowances, setOperationAllowances] = useState({
|
|
1136
|
+
sitecore: [],
|
|
1137
|
+
filesystem: [],
|
|
1138
|
+
});
|
|
1139
|
+
const [triggerSubscriptionsLoading, setTriggerSubscriptionsLoading] = useState(false);
|
|
1140
|
+
const [availableToolsLoading, setAvailableToolsLoading] = useState(false);
|
|
1141
|
+
const [operationAllowancesLoading, setOperationAllowancesLoading] = useState(false);
|
|
1142
|
+
const [triggerSubscriptionsError, setTriggerSubscriptionsError] = useState(null);
|
|
1143
|
+
const [availableToolsError, setAvailableToolsError] = useState(null);
|
|
1144
|
+
const [operationAllowancesError, setOperationAllowancesError] = useState(null);
|
|
1145
|
+
const [toolsSectionExpanded, setToolsSectionExpanded] = useState(false);
|
|
1146
|
+
const [allowancesSectionExpanded, setAllowancesSectionExpanded] = useState(false);
|
|
1147
|
+
const [subscribedTriggersSectionExpanded, setSubscribedTriggersSectionExpanded,] = useState(false);
|
|
1148
|
+
const isPersistedAgent = !!agent?.userId;
|
|
1149
|
+
const isLocalOnlyDraftAgent = agent?.status === "new" && !isPersistedAgent;
|
|
687
1150
|
const hasSpawnedAgents = useMemo(() => {
|
|
688
1151
|
if (!agentMetadata)
|
|
689
1152
|
return false;
|
|
690
|
-
const childAgents = agentMetadata?.
|
|
691
|
-
agentMetadata?.childAgents;
|
|
1153
|
+
const childAgents = agentMetadata?.childAgents;
|
|
692
1154
|
if (!Array.isArray(childAgents) || childAgents.length === 0)
|
|
693
1155
|
return false;
|
|
694
|
-
return childAgents.some((a) => a != null && typeof a === "object" &&
|
|
1156
|
+
return childAgents.some((a) => a != null && typeof a === "object" && a.agentId);
|
|
695
1157
|
}, [agentMetadata]);
|
|
696
1158
|
const hasTodoContent = useMemo(() => {
|
|
697
1159
|
const metadataTodos = (() => {
|
|
698
1160
|
try {
|
|
699
|
-
const todoList = agentMetadata?.additionalData?.todoList
|
|
700
|
-
agentMetadata?.todoList;
|
|
1161
|
+
const todoList = agentMetadata?.additionalData?.todoList;
|
|
701
1162
|
if (todoList?.items && Array.isArray(todoList.items)) {
|
|
702
1163
|
const raw = todoList.items.filter((item) => item?.title || item?.text || item?.label || item?.task);
|
|
703
1164
|
return raw.length > 0;
|
|
@@ -745,7 +1206,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
745
1206
|
.then((result) => {
|
|
746
1207
|
if (cancelled)
|
|
747
1208
|
return;
|
|
748
|
-
if (result.type === "success" &&
|
|
1209
|
+
if (result.type === "success" &&
|
|
1210
|
+
result.data &&
|
|
1211
|
+
result.data.length > 0) {
|
|
749
1212
|
setHasHistoryContent(true);
|
|
750
1213
|
}
|
|
751
1214
|
else {
|
|
@@ -776,6 +1239,38 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
776
1239
|
});
|
|
777
1240
|
return () => unsubscribe();
|
|
778
1241
|
}, [agent?.id, editContext?.addSocketMessageListener]);
|
|
1242
|
+
useEffect(() => {
|
|
1243
|
+
let active = true;
|
|
1244
|
+
const loadSkills = async () => {
|
|
1245
|
+
try {
|
|
1246
|
+
setSkillsLoading(true);
|
|
1247
|
+
setSkillsError(null);
|
|
1248
|
+
const catalog = await getAgentSkillCatalog(false);
|
|
1249
|
+
if (active) {
|
|
1250
|
+
setAvailableSkills(catalog.skills.filter((s) => !s.disabled));
|
|
1251
|
+
setSkillRootIds(catalog.rootIds);
|
|
1252
|
+
setSelectableTemplateIds(catalog.selectableTemplateIds);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
catch (e) {
|
|
1256
|
+
if (active) {
|
|
1257
|
+
setSkillsError(e?.message || "Failed to load skills");
|
|
1258
|
+
setAvailableSkills([]);
|
|
1259
|
+
setSkillRootIds([]);
|
|
1260
|
+
setSelectableTemplateIds([]);
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
finally {
|
|
1264
|
+
if (active) {
|
|
1265
|
+
setSkillsLoading(false);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
};
|
|
1269
|
+
void loadSkills();
|
|
1270
|
+
return () => {
|
|
1271
|
+
active = false;
|
|
1272
|
+
};
|
|
1273
|
+
}, []);
|
|
779
1274
|
const modeOptions = useMemo(() => [
|
|
780
1275
|
{
|
|
781
1276
|
value: "supervised",
|
|
@@ -810,6 +1305,239 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
810
1305
|
value: m.id,
|
|
811
1306
|
label: m.name,
|
|
812
1307
|
})) || []).sort((a, b) => a.label.localeCompare(b.label)), [activeProfile]);
|
|
1308
|
+
const metadataSelectedSkillIds = useMemo(() => {
|
|
1309
|
+
const rawSkillIds = agentMetadata?.additionalData?.skillIds ?? [];
|
|
1310
|
+
if (!Array.isArray(rawSkillIds)) {
|
|
1311
|
+
return [];
|
|
1312
|
+
}
|
|
1313
|
+
return rawSkillIds
|
|
1314
|
+
.map((x) => String(x || "").trim())
|
|
1315
|
+
.filter((x) => x.length > 0);
|
|
1316
|
+
}, [agentMetadata]);
|
|
1317
|
+
const backendAssignedSkillIds = useMemo(() => {
|
|
1318
|
+
const rawSkillIds = agent?.assignedSkillIds ?? [];
|
|
1319
|
+
if (!Array.isArray(rawSkillIds)) {
|
|
1320
|
+
return [];
|
|
1321
|
+
}
|
|
1322
|
+
return rawSkillIds
|
|
1323
|
+
.map((x) => String(x || "").trim())
|
|
1324
|
+
.filter((x) => x.length > 0);
|
|
1325
|
+
}, [agent]);
|
|
1326
|
+
const preloadedSkillIds = useMemo(() => {
|
|
1327
|
+
const rawSkillIds = activeProfile?.preloadSkills ?? [];
|
|
1328
|
+
if (!Array.isArray(rawSkillIds)) {
|
|
1329
|
+
return [];
|
|
1330
|
+
}
|
|
1331
|
+
return rawSkillIds
|
|
1332
|
+
.map((skill) => String(skill?.id || "").trim())
|
|
1333
|
+
.filter((id) => id.length > 0);
|
|
1334
|
+
}, [activeProfile?.preloadSkills]);
|
|
1335
|
+
const autoAssignedSkillIds = useMemo(() => {
|
|
1336
|
+
const preloadedIdSet = new Set(preloadedSkillIds.map((id) => id.toLowerCase()));
|
|
1337
|
+
const all = isLocalOnlyDraftAgent
|
|
1338
|
+
? preloadedSkillIds
|
|
1339
|
+
: backendAssignedSkillIds.filter((id) => preloadedIdSet.has(id.toLowerCase()));
|
|
1340
|
+
const seen = new Set();
|
|
1341
|
+
const unique = [];
|
|
1342
|
+
for (const id of all) {
|
|
1343
|
+
const key = id.toLowerCase();
|
|
1344
|
+
if (seen.has(key))
|
|
1345
|
+
continue;
|
|
1346
|
+
seen.add(key);
|
|
1347
|
+
unique.push(id);
|
|
1348
|
+
}
|
|
1349
|
+
return unique;
|
|
1350
|
+
}, [backendAssignedSkillIds, isLocalOnlyDraftAgent, preloadedSkillIds]);
|
|
1351
|
+
const selectedSkillIds = useMemo(() => {
|
|
1352
|
+
const all = [
|
|
1353
|
+
...autoAssignedSkillIds,
|
|
1354
|
+
...backendAssignedSkillIds,
|
|
1355
|
+
...metadataSelectedSkillIds,
|
|
1356
|
+
];
|
|
1357
|
+
const seen = new Set();
|
|
1358
|
+
const unique = [];
|
|
1359
|
+
for (const id of all) {
|
|
1360
|
+
const key = id.toLowerCase();
|
|
1361
|
+
if (seen.has(key))
|
|
1362
|
+
continue;
|
|
1363
|
+
seen.add(key);
|
|
1364
|
+
unique.push(id);
|
|
1365
|
+
}
|
|
1366
|
+
return unique;
|
|
1367
|
+
}, [autoAssignedSkillIds, backendAssignedSkillIds, metadataSelectedSkillIds]);
|
|
1368
|
+
useEffect(() => {
|
|
1369
|
+
let active = true;
|
|
1370
|
+
if (!showAgentSettings) {
|
|
1371
|
+
return () => {
|
|
1372
|
+
active = false;
|
|
1373
|
+
};
|
|
1374
|
+
}
|
|
1375
|
+
if (!agent?.id || isLocalOnlyDraftAgent) {
|
|
1376
|
+
setTriggerSubscriptions([]);
|
|
1377
|
+
setTriggerSubscriptionsLoading(false);
|
|
1378
|
+
setTriggerSubscriptionsError(null);
|
|
1379
|
+
setAvailableTools([]);
|
|
1380
|
+
setAvailableToolsLoading(false);
|
|
1381
|
+
setAvailableToolsError(null);
|
|
1382
|
+
setOperationAllowances({ sitecore: [], filesystem: [] });
|
|
1383
|
+
setOperationAllowancesLoading(false);
|
|
1384
|
+
setOperationAllowancesError(null);
|
|
1385
|
+
return () => {
|
|
1386
|
+
active = false;
|
|
1387
|
+
};
|
|
1388
|
+
}
|
|
1389
|
+
const loadTriggerSubscriptions = async () => {
|
|
1390
|
+
try {
|
|
1391
|
+
setTriggerSubscriptionsLoading(true);
|
|
1392
|
+
setTriggerSubscriptionsError(null);
|
|
1393
|
+
const subscriptions = await getAgentTriggerSubscriptions(agent.id);
|
|
1394
|
+
if (active) {
|
|
1395
|
+
setTriggerSubscriptions(subscriptions);
|
|
1396
|
+
}
|
|
1397
|
+
}
|
|
1398
|
+
catch (e) {
|
|
1399
|
+
if (active) {
|
|
1400
|
+
setTriggerSubscriptionsError(e?.message || "Failed to load trigger subscriptions");
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
finally {
|
|
1404
|
+
if (active) {
|
|
1405
|
+
setTriggerSubscriptionsLoading(false);
|
|
1406
|
+
}
|
|
1407
|
+
}
|
|
1408
|
+
};
|
|
1409
|
+
const loadOperationAllowances = async () => {
|
|
1410
|
+
try {
|
|
1411
|
+
setOperationAllowancesLoading(true);
|
|
1412
|
+
setOperationAllowancesError(null);
|
|
1413
|
+
const allowances = await getAgentOperationAllowances(agent.id);
|
|
1414
|
+
if (active) {
|
|
1415
|
+
setOperationAllowances(allowances);
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
catch (e) {
|
|
1419
|
+
if (active) {
|
|
1420
|
+
setOperationAllowancesError(e?.message || "Failed to load allowances");
|
|
1421
|
+
}
|
|
1422
|
+
}
|
|
1423
|
+
finally {
|
|
1424
|
+
if (active) {
|
|
1425
|
+
setOperationAllowancesLoading(false);
|
|
1426
|
+
}
|
|
1427
|
+
}
|
|
1428
|
+
};
|
|
1429
|
+
const loadAvailableTools = async () => {
|
|
1430
|
+
try {
|
|
1431
|
+
setAvailableToolsLoading(true);
|
|
1432
|
+
setAvailableToolsError(null);
|
|
1433
|
+
const tools = await getAgentAvailableTools(agent.id);
|
|
1434
|
+
if (active) {
|
|
1435
|
+
setAvailableTools(tools);
|
|
1436
|
+
}
|
|
1437
|
+
}
|
|
1438
|
+
catch (e) {
|
|
1439
|
+
if (active) {
|
|
1440
|
+
setAvailableToolsError(e?.message || "Failed to load available tools");
|
|
1441
|
+
}
|
|
1442
|
+
}
|
|
1443
|
+
finally {
|
|
1444
|
+
if (active) {
|
|
1445
|
+
setAvailableToolsLoading(false);
|
|
1446
|
+
}
|
|
1447
|
+
}
|
|
1448
|
+
};
|
|
1449
|
+
void loadTriggerSubscriptions();
|
|
1450
|
+
void loadAvailableTools();
|
|
1451
|
+
void loadOperationAllowances();
|
|
1452
|
+
return () => {
|
|
1453
|
+
active = false;
|
|
1454
|
+
};
|
|
1455
|
+
}, [
|
|
1456
|
+
showAgentSettings,
|
|
1457
|
+
agent?.id,
|
|
1458
|
+
agent?.status,
|
|
1459
|
+
agent?.userId,
|
|
1460
|
+
agent?.profileId,
|
|
1461
|
+
mode,
|
|
1462
|
+
selectedSkillIds,
|
|
1463
|
+
]);
|
|
1464
|
+
const activeTriggerSubscriptions = useMemo(() => triggerSubscriptions.filter((sub) => sub.isActive), [triggerSubscriptions]);
|
|
1465
|
+
const allowanceGroups = useMemo(() => [
|
|
1466
|
+
{
|
|
1467
|
+
key: "sitecore",
|
|
1468
|
+
label: "Sitecore",
|
|
1469
|
+
rows: operationAllowances.sitecore,
|
|
1470
|
+
},
|
|
1471
|
+
{
|
|
1472
|
+
key: "filesystem",
|
|
1473
|
+
label: "Filesystem",
|
|
1474
|
+
rows: operationAllowances.filesystem,
|
|
1475
|
+
},
|
|
1476
|
+
], [operationAllowances]);
|
|
1477
|
+
const hasAnyAllowances = useMemo(() => operationAllowances.sitecore.length > 0 ||
|
|
1478
|
+
operationAllowances.filesystem.length > 0, [operationAllowances]);
|
|
1479
|
+
const allowancesTotalCount = useMemo(() => operationAllowances.sitecore.length +
|
|
1480
|
+
operationAllowances.filesystem.length, [operationAllowances]);
|
|
1481
|
+
const listedProfileSkillIdSet = useMemo(() => {
|
|
1482
|
+
const ids = [
|
|
1483
|
+
...(activeProfile?.availableSkills ?? []),
|
|
1484
|
+
...(activeProfile?.allowedSkills ?? []),
|
|
1485
|
+
]
|
|
1486
|
+
.map((skill) => String(skill?.id || "").toLowerCase())
|
|
1487
|
+
.filter((id) => id.length > 0);
|
|
1488
|
+
return new Set(ids);
|
|
1489
|
+
}, [activeProfile?.availableSkills, activeProfile?.allowedSkills]);
|
|
1490
|
+
const profileFilteredSkills = useMemo(() => {
|
|
1491
|
+
if (listedProfileSkillIdSet.size === 0) {
|
|
1492
|
+
return [];
|
|
1493
|
+
}
|
|
1494
|
+
return availableSkills.filter((skill) => listedProfileSkillIdSet.has(skill.id.toLowerCase()));
|
|
1495
|
+
}, [availableSkills, listedProfileSkillIdSet]);
|
|
1496
|
+
const profileFilteredSkillIdSet = useMemo(() => new Set(profileFilteredSkills.map((s) => s.id.toLowerCase())), [profileFilteredSkills]);
|
|
1497
|
+
const manuallyAssignableSkillIdSet = useMemo(() => new Set((activeProfile?.allowedSkills ?? [])
|
|
1498
|
+
.map((skill) => String(skill?.id || "").toLowerCase())
|
|
1499
|
+
.filter((id) => id.length > 0)), [activeProfile?.allowedSkills]);
|
|
1500
|
+
const selectableTemplateIdSet = useMemo(() => new Set(selectableTemplateIds.map((id) => id.toLowerCase())), [selectableTemplateIds]);
|
|
1501
|
+
const selectedSkills = useMemo(() => selectedSkillIds
|
|
1502
|
+
.map((id) => availableSkills.find((s) => s.id.toLowerCase() === id.toLowerCase()))
|
|
1503
|
+
.filter((s) => !!s), [availableSkills, selectedSkillIds]);
|
|
1504
|
+
const selectedSkillSet = useMemo(() => new Set(selectedSkillIds.map((id) => id.toLowerCase())), [selectedSkillIds]);
|
|
1505
|
+
const autoAssignedSkillSet = useMemo(() => new Set(autoAssignedSkillIds.map((id) => id.toLowerCase())), [autoAssignedSkillIds]);
|
|
1506
|
+
const previewAvailableTools = useMemo(() => {
|
|
1507
|
+
const baseToolNames = mode === "read-only"
|
|
1508
|
+
? (activeProfile?.readOnlyToolNames ?? [])
|
|
1509
|
+
: (activeProfile?.allowedToolNames ?? []);
|
|
1510
|
+
const toolNames = new Set();
|
|
1511
|
+
for (const toolName of baseToolNames) {
|
|
1512
|
+
const normalized = String(toolName || "").trim();
|
|
1513
|
+
if (normalized) {
|
|
1514
|
+
toolNames.add(normalized);
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
for (const skill of selectedSkills) {
|
|
1518
|
+
for (const toolName of skill.additionalAllowedTools ?? []) {
|
|
1519
|
+
const normalized = String(toolName || "").trim();
|
|
1520
|
+
if (normalized) {
|
|
1521
|
+
toolNames.add(normalized);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
}
|
|
1525
|
+
return Array.from(toolNames).sort((a, b) => a.localeCompare(b));
|
|
1526
|
+
}, [
|
|
1527
|
+
activeProfile?.allowedToolNames,
|
|
1528
|
+
activeProfile?.readOnlyToolNames,
|
|
1529
|
+
mode,
|
|
1530
|
+
selectedSkills,
|
|
1531
|
+
]);
|
|
1532
|
+
const displayedAvailableTools = isLocalOnlyDraftAgent
|
|
1533
|
+
? previewAvailableTools
|
|
1534
|
+
: availableTools;
|
|
1535
|
+
const displayedAvailableToolsLoading = isLocalOnlyDraftAgent
|
|
1536
|
+
? false
|
|
1537
|
+
: availableToolsLoading;
|
|
1538
|
+
const displayedAvailableToolsError = isLocalOnlyDraftAgent
|
|
1539
|
+
? null
|
|
1540
|
+
: availableToolsError;
|
|
813
1541
|
// Remove deprecated cost limit fields from metadata to avoid confusion with agent/profile settings
|
|
814
1542
|
const sanitizeAgentMetadata = useCallback((meta) => {
|
|
815
1543
|
try {
|
|
@@ -834,28 +1562,239 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
834
1562
|
return meta;
|
|
835
1563
|
}
|
|
836
1564
|
}, []);
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1565
|
+
const getSkillActionErrorMessage = useCallback((error) => {
|
|
1566
|
+
const message = error instanceof Error && error.message.trim()
|
|
1567
|
+
? error.message.trim()
|
|
1568
|
+
: "Failed to update skill";
|
|
1569
|
+
if (message.includes("Skill is not available for this agent profile")) {
|
|
1570
|
+
return "This skill cannot be added for the current agent profile.";
|
|
1571
|
+
}
|
|
1572
|
+
return message;
|
|
1573
|
+
}, []);
|
|
1574
|
+
const clearLegacySelectedSkillsFromMetadata = useCallback(async () => {
|
|
1575
|
+
const legacySkillIds = metadataSelectedSkillIds;
|
|
1576
|
+
if (!agent?.id || legacySkillIds.length === 0) {
|
|
1577
|
+
return;
|
|
1578
|
+
}
|
|
1579
|
+
const current = agentMetadata || {};
|
|
1580
|
+
const currentAdditionalData = current.additionalData ||
|
|
1581
|
+
{};
|
|
1582
|
+
const nextAdditionalData = Object.fromEntries(Object.entries(currentAdditionalData).filter(([key]) => key.toLowerCase() !== "skillids"));
|
|
1583
|
+
const next = {
|
|
1584
|
+
...current,
|
|
849
1585
|
};
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1586
|
+
if (Object.keys(nextAdditionalData).length > 0) {
|
|
1587
|
+
next.additionalData = nextAdditionalData;
|
|
1588
|
+
}
|
|
1589
|
+
else {
|
|
1590
|
+
delete next.additionalData;
|
|
1591
|
+
}
|
|
1592
|
+
try {
|
|
1593
|
+
await updateAgentContext(agent.id, next);
|
|
1594
|
+
setAgentMetadata(next);
|
|
1595
|
+
setAgent((prev) => prev ? { ...prev, agentContext: JSON.stringify(next) } : prev);
|
|
1596
|
+
}
|
|
1597
|
+
catch (e) {
|
|
1598
|
+
console.error("Failed to clear legacy selected skills", e);
|
|
1599
|
+
}
|
|
1600
|
+
}, [
|
|
1601
|
+
agent?.id,
|
|
1602
|
+
agentMetadata,
|
|
1603
|
+
metadataSelectedSkillIds,
|
|
1604
|
+
setAgent,
|
|
1605
|
+
setAgentMetadata,
|
|
1606
|
+
]);
|
|
1607
|
+
const parsePersistedAgentContext = (contextJson) => {
|
|
1608
|
+
try {
|
|
1609
|
+
if (!contextJson)
|
|
1610
|
+
return null;
|
|
1611
|
+
const parsedContext = JSON.parse(contextJson);
|
|
1612
|
+
if (!parsedContext || typeof parsedContext !== "object") {
|
|
1613
|
+
return null;
|
|
1614
|
+
}
|
|
1615
|
+
return sanitizeAgentMetadata(parsedContext);
|
|
1616
|
+
}
|
|
1617
|
+
catch {
|
|
1618
|
+
return null;
|
|
1619
|
+
}
|
|
1620
|
+
};
|
|
1621
|
+
const buildDraftPersistenceContext = () => {
|
|
1622
|
+
let effectiveContext = agentMetadata;
|
|
1623
|
+
try {
|
|
1624
|
+
const normalizedAgentProfileId = agent?.profileId?.toLowerCase();
|
|
1625
|
+
const profile = activeProfile ||
|
|
1626
|
+
profiles.find((p) => p.id?.toLowerCase() === normalizedAgentProfileId);
|
|
1627
|
+
const isLiveMode = profile?.editorContextMode === "live";
|
|
1628
|
+
if (isLiveMode && typeof buildCurrentContext === "function") {
|
|
1629
|
+
const freshContext = buildCurrentContext();
|
|
1630
|
+
if (freshContext) {
|
|
1631
|
+
effectiveContext = {
|
|
1632
|
+
...(agentMetadata || {}),
|
|
1633
|
+
items: freshContext.items,
|
|
1634
|
+
currentItemId: freshContext.currentItemId,
|
|
1635
|
+
components: freshContext.components,
|
|
1636
|
+
field: freshContext.field,
|
|
1637
|
+
activeWorkspace: freshContext.activeWorkspace,
|
|
1638
|
+
hasPageLoaded: freshContext.hasPageLoaded,
|
|
1639
|
+
openSidebars: freshContext.openSidebars,
|
|
1640
|
+
};
|
|
1641
|
+
}
|
|
1642
|
+
}
|
|
1643
|
+
}
|
|
1644
|
+
catch (e) {
|
|
1645
|
+
console.warn("[AgentTerminal] Failed to compute draft context:", e);
|
|
1646
|
+
}
|
|
1647
|
+
return sanitizeAgentMetadata(effectiveContext || null);
|
|
1648
|
+
};
|
|
1649
|
+
const ensureDraftAgentPersisted = async () => {
|
|
1650
|
+
if (!agent?.id) {
|
|
1651
|
+
throw new Error("Agent not ready. Please try again.");
|
|
1652
|
+
}
|
|
1653
|
+
if (!isLocalOnlyDraftAgent) {
|
|
1654
|
+
return agent;
|
|
1655
|
+
}
|
|
1656
|
+
const requestSettings = getPendingRequestSettings();
|
|
1657
|
+
if (!requestSettings.profileId) {
|
|
1658
|
+
throw new Error("Select an agent profile before adding a skill.");
|
|
1659
|
+
}
|
|
1660
|
+
const effectiveContext = buildDraftPersistenceContext();
|
|
1661
|
+
const persistedAgent = await persistDraftAgent({
|
|
1662
|
+
agentId: agent.id,
|
|
1663
|
+
sessionId: editContext?.sessionId || undefined,
|
|
1664
|
+
profileId: requestSettings.profileId,
|
|
1665
|
+
mode: requestSettings.mode,
|
|
1666
|
+
model: requestSettings.modelId || undefined,
|
|
1667
|
+
name: agent.name,
|
|
1668
|
+
context: effectiveContext,
|
|
1669
|
+
});
|
|
1670
|
+
pendingSettingsRef.current = null;
|
|
1671
|
+
const persistedMetadata = parsePersistedAgentContext(persistedAgent.agentContext) ??
|
|
1672
|
+
effectiveContext;
|
|
1673
|
+
setAgentMetadata(persistedMetadata ? { ...persistedMetadata } : null);
|
|
1674
|
+
setAgent((prev) => {
|
|
1675
|
+
const next = {
|
|
1676
|
+
...(prev || {}),
|
|
1677
|
+
...persistedAgent,
|
|
1678
|
+
};
|
|
1679
|
+
if (prev?.messages?.length && !persistedAgent.messages?.length) {
|
|
1680
|
+
next.messages = prev.messages;
|
|
1681
|
+
}
|
|
1682
|
+
return next;
|
|
1683
|
+
});
|
|
1684
|
+
onAgentUpdate?.(persistedAgent);
|
|
1685
|
+
return persistedAgent;
|
|
1686
|
+
};
|
|
1687
|
+
const handleAddSkill = useCallback(async (skillId) => {
|
|
1688
|
+
if (selectedSkillSet.has(skillId.toLowerCase()))
|
|
1689
|
+
return;
|
|
1690
|
+
if (!agent?.id)
|
|
1691
|
+
return;
|
|
1692
|
+
try {
|
|
1693
|
+
setSkillActionError(null);
|
|
1694
|
+
const persistedAgent = await ensureDraftAgentPersisted();
|
|
1695
|
+
await assignAgentSkill({ agentId: persistedAgent.id, skillId });
|
|
1696
|
+
setAgent((prev) => {
|
|
1697
|
+
if (!prev)
|
|
1698
|
+
return prev;
|
|
1699
|
+
const currentAssigned = Array.isArray(prev.assignedSkillIds)
|
|
1700
|
+
? prev.assignedSkillIds
|
|
1701
|
+
: [];
|
|
1702
|
+
if (currentAssigned.some((existingId) => String(existingId).toLowerCase() === skillId.toLowerCase())) {
|
|
1703
|
+
return prev;
|
|
1704
|
+
}
|
|
1705
|
+
return {
|
|
1706
|
+
...prev,
|
|
1707
|
+
assignedSkillIds: [...currentAssigned, skillId],
|
|
1708
|
+
};
|
|
1709
|
+
});
|
|
1710
|
+
await clearLegacySelectedSkillsFromMetadata();
|
|
1711
|
+
return true;
|
|
1712
|
+
}
|
|
1713
|
+
catch (e) {
|
|
1714
|
+
setSkillActionError(getSkillActionErrorMessage(e));
|
|
1715
|
+
return false;
|
|
1716
|
+
}
|
|
1717
|
+
}, [
|
|
1718
|
+
agent?.id,
|
|
1719
|
+
assignAgentSkill,
|
|
1720
|
+
clearLegacySelectedSkillsFromMetadata,
|
|
1721
|
+
ensureDraftAgentPersisted,
|
|
1722
|
+
getSkillActionErrorMessage,
|
|
1723
|
+
setAgent,
|
|
1724
|
+
selectedSkillSet,
|
|
1725
|
+
]);
|
|
1726
|
+
const handleRemoveSkill = useCallback(async (skillId) => {
|
|
1727
|
+
if (!agent?.id)
|
|
1728
|
+
return;
|
|
1729
|
+
try {
|
|
1730
|
+
setSkillActionError(null);
|
|
1731
|
+
await revokeAgentSkill({ agentId: agent.id, skillId });
|
|
1732
|
+
setAgent((prev) => {
|
|
1733
|
+
if (!prev)
|
|
1734
|
+
return prev;
|
|
1735
|
+
const currentAssigned = Array.isArray(prev.assignedSkillIds)
|
|
1736
|
+
? prev.assignedSkillIds
|
|
1737
|
+
: [];
|
|
1738
|
+
return {
|
|
1739
|
+
...prev,
|
|
1740
|
+
assignedSkillIds: currentAssigned.filter((existingId) => String(existingId).toLowerCase() !== skillId.toLowerCase()),
|
|
1741
|
+
};
|
|
1742
|
+
});
|
|
1743
|
+
await clearLegacySelectedSkillsFromMetadata();
|
|
1744
|
+
}
|
|
1745
|
+
catch (e) {
|
|
1746
|
+
setSkillActionError(getSkillActionErrorMessage(e));
|
|
1747
|
+
}
|
|
1748
|
+
}, [
|
|
1749
|
+
agent?.id,
|
|
1750
|
+
clearLegacySelectedSkillsFromMetadata,
|
|
1751
|
+
getSkillActionErrorMessage,
|
|
1752
|
+
revokeAgentSkill,
|
|
1753
|
+
setAgent,
|
|
1754
|
+
]);
|
|
1755
|
+
const handleOpenProfileSettings = useCallback(async () => {
|
|
1756
|
+
if (!editContext || !activeProfile?.id)
|
|
1757
|
+
return;
|
|
1758
|
+
const lang = editContext.currentItemDescriptor?.language || "en";
|
|
1759
|
+
await editContext.loadItem({
|
|
1760
|
+
id: activeProfile.id,
|
|
1761
|
+
language: lang,
|
|
1762
|
+
version: 0,
|
|
1763
|
+
});
|
|
1764
|
+
editContext.switchWorkspace("editor");
|
|
1765
|
+
}, [editContext, activeProfile?.id]);
|
|
1766
|
+
const handleEditProfileSideBySide = useCallback(async () => {
|
|
1767
|
+
if (!editContext || !activeProfile?.id)
|
|
1768
|
+
return;
|
|
1769
|
+
const lang = editContext.currentItemDescriptor?.language || "en";
|
|
1770
|
+
const profileItem = {
|
|
1771
|
+
id: activeProfile.id,
|
|
1772
|
+
language: lang,
|
|
1773
|
+
version: 0,
|
|
855
1774
|
};
|
|
856
|
-
|
|
1775
|
+
if (editContext.workspaceId === "agents") {
|
|
1776
|
+
editContext.setShowAgentsWorkspaceEditor(true);
|
|
1777
|
+
await editContext.loadItem(profileItem);
|
|
1778
|
+
return;
|
|
1779
|
+
}
|
|
1780
|
+
await editContext.loadItem(profileItem, {
|
|
1781
|
+
openInNewSlot: true,
|
|
1782
|
+
});
|
|
1783
|
+
editContext.switchWorkspace("editor");
|
|
1784
|
+
}, [editContext, activeProfile?.id]);
|
|
1785
|
+
const handleOpenSkillItem = useCallback(async (skillId) => {
|
|
1786
|
+
if (!editContext || !skillId)
|
|
1787
|
+
return;
|
|
1788
|
+
const lang = editContext.currentItemDescriptor?.language || "en";
|
|
1789
|
+
await editContext.loadItem({
|
|
1790
|
+
id: skillId,
|
|
1791
|
+
language: lang,
|
|
1792
|
+
version: 0,
|
|
1793
|
+
});
|
|
1794
|
+
editContext.switchWorkspace("editor");
|
|
1795
|
+
}, [editContext]);
|
|
857
1796
|
useEffect(() => {
|
|
858
|
-
|
|
1797
|
+
localStorageService.setItem("editor.agent.promptHistory", promptHistory);
|
|
859
1798
|
}, [promptHistory]);
|
|
860
1799
|
useEffect(() => {
|
|
861
1800
|
// Keep messagesRef synchronized with messages state
|
|
@@ -867,6 +1806,32 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
867
1806
|
const [liveTotals, setLiveTotals] = useState(null);
|
|
868
1807
|
// Context window status from backend (extracted from delta cost object)
|
|
869
1808
|
const [contextWindowStatus, setContextWindowStatus] = useState(null);
|
|
1809
|
+
const effectiveModelName = useMemo(() => {
|
|
1810
|
+
const fromContextWindow = contextWindowStatus?.model?.trim();
|
|
1811
|
+
if (fromContextWindow)
|
|
1812
|
+
return fromContextWindow;
|
|
1813
|
+
const fromAgent = agent?.model?.trim();
|
|
1814
|
+
if (fromAgent)
|
|
1815
|
+
return fromAgent;
|
|
1816
|
+
const models = activeProfile?.models || [];
|
|
1817
|
+
const selectedModelName = selectedModelId
|
|
1818
|
+
? models.find((m) => m.id === selectedModelId)?.name?.trim()
|
|
1819
|
+
: undefined;
|
|
1820
|
+
if (selectedModelName)
|
|
1821
|
+
return selectedModelName;
|
|
1822
|
+
const defaultModelName = activeProfile?.defaultModelId
|
|
1823
|
+
? models.find((m) => m.id === activeProfile.defaultModelId)?.name?.trim()
|
|
1824
|
+
: undefined;
|
|
1825
|
+
if (defaultModelName)
|
|
1826
|
+
return defaultModelName;
|
|
1827
|
+
return models[0]?.name?.trim() || undefined;
|
|
1828
|
+
}, [
|
|
1829
|
+
contextWindowStatus?.model,
|
|
1830
|
+
agent?.model,
|
|
1831
|
+
activeProfile?.models,
|
|
1832
|
+
activeProfile?.defaultModelId,
|
|
1833
|
+
selectedModelId,
|
|
1834
|
+
]);
|
|
870
1835
|
// Flag to track when we should create a new message
|
|
871
1836
|
const shouldCreateNewMessage = useRef(false);
|
|
872
1837
|
// Keep a ref to the current messages for immediate access
|
|
@@ -877,12 +1842,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
877
1842
|
const placeholderInputRef = useRef(null);
|
|
878
1843
|
const promptPlaceholderInputRef = useRef(null);
|
|
879
1844
|
const messagesContainerRef = useRef(null);
|
|
1845
|
+
const inlineDialogContainerRef = useRef(null);
|
|
880
1846
|
const [shouldAutoScroll, setShouldAutoScroll] = useState(true);
|
|
881
1847
|
// WebSocket subscription state for agent streaming
|
|
882
1848
|
const seenMessageIdsRef = useRef(new Set());
|
|
883
1849
|
const lastSeqRef = useRef(0);
|
|
884
1850
|
const subscribedAgentIdRef = useRef(null);
|
|
885
|
-
// Cache mode/model changes made while the agent is still "new" (not yet persisted)
|
|
1851
|
+
// Cache mode/model/profile changes made while the agent is still "new" (not yet persisted)
|
|
886
1852
|
const pendingSettingsRef = useRef(null);
|
|
887
1853
|
// Track whether textarea should maintain focus during re-renders
|
|
888
1854
|
const shouldMaintainFocusRef = useRef(false);
|
|
@@ -904,11 +1870,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
904
1870
|
setIsVoiceSupported(!!SR);
|
|
905
1871
|
if (SR === undefined) {
|
|
906
1872
|
setIsVoiceDisabled(true);
|
|
907
|
-
|
|
1873
|
+
localStorageService.setString(VOICE_DISABLED_KEY, "true");
|
|
908
1874
|
return;
|
|
909
1875
|
}
|
|
910
1876
|
else {
|
|
911
|
-
const wasDisabled =
|
|
1877
|
+
const wasDisabled = localStorageService.getString(VOICE_DISABLED_KEY) === "true";
|
|
912
1878
|
setIsVoiceDisabled(wasDisabled);
|
|
913
1879
|
}
|
|
914
1880
|
}
|
|
@@ -919,7 +1885,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
919
1885
|
// Auto-focus terminal input on mount
|
|
920
1886
|
useEffect(() => {
|
|
921
1887
|
if (textareaRef.current) {
|
|
922
|
-
|
|
1888
|
+
try {
|
|
1889
|
+
textareaRef.current.focus({ preventScroll: true });
|
|
1890
|
+
}
|
|
1891
|
+
catch {
|
|
1892
|
+
textareaRef.current.focus();
|
|
1893
|
+
}
|
|
923
1894
|
}
|
|
924
1895
|
}, []);
|
|
925
1896
|
// Start voice recognition
|
|
@@ -938,20 +1909,29 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
938
1909
|
r.lang = editContext?.currentItemDescriptor?.language || "en-US";
|
|
939
1910
|
r.continuous = true;
|
|
940
1911
|
r.interimResults = true;
|
|
1912
|
+
promptBeforeVoiceRef.current = prompt;
|
|
941
1913
|
r.onstart = () => {
|
|
942
1914
|
setIsListening(true);
|
|
943
1915
|
prevPlaceholderRef.current = inputPlaceholder;
|
|
944
1916
|
setInputPlaceholder("Listening...");
|
|
945
1917
|
};
|
|
1918
|
+
// Desktop Chrome: single result at index 0, transitions interim→final once.
|
|
1919
|
+
// Mobile Chrome: new result index per event, ALL immediately isFinal,
|
|
1920
|
+
// each containing the full cumulative transcript. We always take the last
|
|
1921
|
+
// non-empty final result and REPLACE the prompt to handle both patterns.
|
|
946
1922
|
r.onresult = (event) => {
|
|
947
|
-
let
|
|
1923
|
+
let lastFinalTranscript = "";
|
|
948
1924
|
let interimText = "";
|
|
949
|
-
for (let i = event.
|
|
1925
|
+
for (let i = event.results.length - 1; i >= 0; i--) {
|
|
950
1926
|
const res = event.results[i];
|
|
951
|
-
if (res.isFinal
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1927
|
+
if (res.isFinal &&
|
|
1928
|
+
!lastFinalTranscript &&
|
|
1929
|
+
(res[0]?.transcript || "").trim()) {
|
|
1930
|
+
lastFinalTranscript = res[0].transcript;
|
|
1931
|
+
}
|
|
1932
|
+
if (!res.isFinal) {
|
|
1933
|
+
interimText = (res[0]?.transcript || "") + interimText;
|
|
1934
|
+
}
|
|
955
1935
|
}
|
|
956
1936
|
if (interimText) {
|
|
957
1937
|
setInputPlaceholder(`Listening... ${interimText.trim()}`);
|
|
@@ -959,11 +1939,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
959
1939
|
else {
|
|
960
1940
|
setInputPlaceholder("Listening...");
|
|
961
1941
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1942
|
+
const base = promptBeforeVoiceRef.current;
|
|
1943
|
+
const separator = base && !base.endsWith(" ") ? " " : "";
|
|
1944
|
+
const recognized = lastFinalTranscript.trim();
|
|
1945
|
+
if (recognized) {
|
|
1946
|
+
setPrompt(base + separator + recognized + " ");
|
|
967
1947
|
if (textareaRef.current) {
|
|
968
1948
|
try {
|
|
969
1949
|
const v = textareaRef.current.value || "";
|
|
@@ -979,7 +1959,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
979
1959
|
// network error also comes from incompatible chromium client
|
|
980
1960
|
if (e?.error === "network") {
|
|
981
1961
|
try {
|
|
982
|
-
|
|
1962
|
+
localStorageService.setString(VOICE_DISABLED_KEY, "true");
|
|
983
1963
|
setIsVoiceDisabled(true);
|
|
984
1964
|
}
|
|
985
1965
|
catch { }
|
|
@@ -1148,25 +2128,18 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1148
2128
|
// Shared stream message handlers with messageId support
|
|
1149
2129
|
const createNewStreamMessage = useCallback((messageId, agentData) => {
|
|
1150
2130
|
const currentAgent = agentData || agent;
|
|
1151
|
-
|
|
1152
|
-
console.error("❌ createNewStreamMessage: No agent available", {
|
|
1153
|
-
messageId,
|
|
1154
|
-
agentData: !!agentData,
|
|
1155
|
-
agent: !!agent,
|
|
1156
|
-
});
|
|
1157
|
-
throw new Error("No agent available");
|
|
1158
|
-
}
|
|
2131
|
+
const effectiveAgentId = currentAgent?.id || agentStub.id;
|
|
1159
2132
|
// Reduced: avoid verbose logging during streaming
|
|
1160
2133
|
return {
|
|
1161
2134
|
id: messageId,
|
|
1162
|
-
agentId:
|
|
2135
|
+
agentId: effectiveAgentId,
|
|
1163
2136
|
messageIndex: -1,
|
|
1164
2137
|
role: "assistant",
|
|
1165
2138
|
content: "",
|
|
1166
2139
|
name: "agent",
|
|
1167
2140
|
messageType: "streaming",
|
|
1168
2141
|
isCompleted: false,
|
|
1169
|
-
model: currentAgent
|
|
2142
|
+
model: currentAgent?.model || "",
|
|
1170
2143
|
tokensUsed: 0,
|
|
1171
2144
|
inputTokens: 0,
|
|
1172
2145
|
outputTokens: 0,
|
|
@@ -1175,16 +2148,71 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1175
2148
|
outputTokenCost: 0,
|
|
1176
2149
|
cachedInputTokenCost: 0,
|
|
1177
2150
|
totalCost: 0,
|
|
1178
|
-
currency: currentAgent
|
|
2151
|
+
currency: currentAgent?.currency || "USD",
|
|
1179
2152
|
createdDate: new Date().toISOString(),
|
|
1180
2153
|
toolCalls: [],
|
|
1181
2154
|
};
|
|
1182
|
-
}, [agent]);
|
|
2155
|
+
}, [agent, agentStub.id]);
|
|
2156
|
+
const stripHeartbeatMessages = useCallback((currentMessages) => currentMessages.filter((message) => message.messageType !== "heartbeat"), []);
|
|
2157
|
+
const handleHeartbeatMessage = useCallback((message) => {
|
|
2158
|
+
const heartbeatText = (message.data?.message ||
|
|
2159
|
+
message.data?.Message ||
|
|
2160
|
+
"Still working on your request. This is taking a little longer than usual.").trim() ||
|
|
2161
|
+
"Still working on your request. This is taking a little longer than usual.";
|
|
2162
|
+
const createdDate = message.timestamp || new Date().toISOString();
|
|
2163
|
+
const heartbeatId = `heartbeat-${agent?.id || agentStub.id}`;
|
|
2164
|
+
setMessages((prev) => {
|
|
2165
|
+
const withoutHeartbeats = stripHeartbeatMessages(prev);
|
|
2166
|
+
const updated = [
|
|
2167
|
+
...withoutHeartbeats,
|
|
2168
|
+
{
|
|
2169
|
+
id: heartbeatId,
|
|
2170
|
+
agentId: agent?.id || agentStub.id,
|
|
2171
|
+
messageIndex: -1,
|
|
2172
|
+
role: "system",
|
|
2173
|
+
content: heartbeatText,
|
|
2174
|
+
name: "system",
|
|
2175
|
+
messageType: "heartbeat",
|
|
2176
|
+
isCompleted: true,
|
|
2177
|
+
model: "",
|
|
2178
|
+
tokensUsed: 0,
|
|
2179
|
+
inputTokens: 0,
|
|
2180
|
+
outputTokens: 0,
|
|
2181
|
+
cachedInputTokens: 0,
|
|
2182
|
+
inputTokenCost: 0,
|
|
2183
|
+
outputTokenCost: 0,
|
|
2184
|
+
cachedInputTokenCost: 0,
|
|
2185
|
+
totalCost: 0,
|
|
2186
|
+
currency: "USD",
|
|
2187
|
+
createdDate,
|
|
2188
|
+
toolCalls: [],
|
|
2189
|
+
},
|
|
2190
|
+
];
|
|
2191
|
+
messagesRef.current = updated;
|
|
2192
|
+
return updated;
|
|
2193
|
+
});
|
|
2194
|
+
}, [agent?.id, agentStub.id, stripHeartbeatMessages]);
|
|
2195
|
+
const clearHeartbeatMessages = useCallback(() => {
|
|
2196
|
+
setMessages((prev) => {
|
|
2197
|
+
const updated = stripHeartbeatMessages(prev);
|
|
2198
|
+
if (updated.length === prev.length) {
|
|
2199
|
+
return prev;
|
|
2200
|
+
}
|
|
2201
|
+
messagesRef.current = updated;
|
|
2202
|
+
return updated;
|
|
2203
|
+
});
|
|
2204
|
+
}, [stripHeartbeatMessages]);
|
|
1183
2205
|
const handleContentChunk = useCallback((message, agentData) => {
|
|
1184
2206
|
// Get messageId from data, or generate one from agent ID (for backward compatibility)
|
|
1185
2207
|
// If no messageId is provided, we'll use the last assistant message or create a new one
|
|
1186
2208
|
let messageId = message.data?.messageId;
|
|
1187
2209
|
if (!messageId && agentData?.id) {
|
|
2210
|
+
console.warn("[AgentTerminal] Content chunk missing messageId; falling back to local resolution", {
|
|
2211
|
+
agentId: agentData.id,
|
|
2212
|
+
isIncremental: message.data?.isIncremental,
|
|
2213
|
+
previousContentLength: message.data?.previousContentLength,
|
|
2214
|
+
totalContentLength: message.data?.totalContentLength,
|
|
2215
|
+
});
|
|
1188
2216
|
// For backward compatibility: if no messageId, find or create the current streaming message
|
|
1189
2217
|
// This handles cases where the backend doesn't send messageId
|
|
1190
2218
|
const currentMessages = messagesRef.current;
|
|
@@ -1198,7 +2226,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1198
2226
|
// If the agent isn't currently running (e.g., we switched tabs after the run
|
|
1199
2227
|
// completed), skip creating a new streaming message to avoid duplicates.
|
|
1200
2228
|
const currentAgentStatus = (agentData || agent)?.status;
|
|
1201
|
-
const isAgentRunning = currentAgentStatus === "running"
|
|
2229
|
+
const isAgentRunning = currentAgentStatus === "running";
|
|
1202
2230
|
if (!isAgentRunning) {
|
|
1203
2231
|
return;
|
|
1204
2232
|
}
|
|
@@ -1231,6 +2259,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1231
2259
|
outputCost: Number(cost.output) || 0,
|
|
1232
2260
|
cachedCost: Number(cost.cached) || 0,
|
|
1233
2261
|
cacheWriteCost: Number(cost.cacheWrite) || 0,
|
|
2262
|
+
imageCost: Number(cost.imageCost ?? cost.totalImageCost) ||
|
|
2263
|
+
0,
|
|
1234
2264
|
totalCost: Number(cost.total) || 0,
|
|
1235
2265
|
currency: "USD",
|
|
1236
2266
|
};
|
|
@@ -1254,11 +2284,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1254
2284
|
setIsWaitingForResponse(false);
|
|
1255
2285
|
shouldCreateNewMessage.current = false;
|
|
1256
2286
|
}
|
|
1257
|
-
// Extract context window info from cost object
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
2287
|
+
// Extract context window info from cost object.
|
|
2288
|
+
// Note: contextUsed can legitimately be 0 (especially early in OpenAI streams),
|
|
2289
|
+
// so we must check for null/undefined instead of truthiness.
|
|
2290
|
+
const contextWindowRaw = cost.contextWindow;
|
|
2291
|
+
const contextUsedRaw = cost.contextUsed;
|
|
2292
|
+
if (contextWindowRaw !== undefined &&
|
|
2293
|
+
contextWindowRaw !== null &&
|
|
2294
|
+
contextUsedRaw !== undefined &&
|
|
2295
|
+
contextUsedRaw !== null) {
|
|
2296
|
+
const contextWindowValue = Number(contextWindowRaw);
|
|
2297
|
+
const contextUsedValue = Number(contextUsedRaw);
|
|
2298
|
+
if (contextWindowValue > 0 &&
|
|
2299
|
+
Number.isFinite(contextUsedValue) &&
|
|
2300
|
+
contextUsedValue >= 0) {
|
|
1262
2301
|
setContextWindowStatus({
|
|
1263
2302
|
contextWindowTokens: contextWindowValue,
|
|
1264
2303
|
estimatedInputTokens: contextUsedValue,
|
|
@@ -1274,6 +2313,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1274
2313
|
const existingMessageIndex = prev.findIndex((msg) => msg.id === messageId);
|
|
1275
2314
|
if (existingMessageIndex === -1) {
|
|
1276
2315
|
// Message doesn't exist - create new streaming message
|
|
2316
|
+
const previousContentLength = message.data?.previousContentLength || 0;
|
|
2317
|
+
if (message.data?.isIncremental && previousContentLength > 0) {
|
|
2318
|
+
console.warn("[AgentTerminal] Incremental chunk arrived before its base message existed", {
|
|
2319
|
+
messageId,
|
|
2320
|
+
previousContentLength,
|
|
2321
|
+
totalContentLength: message.data?.totalContentLength,
|
|
2322
|
+
deltaLength: (message.data?.deltaContent || "").length,
|
|
2323
|
+
});
|
|
2324
|
+
}
|
|
1277
2325
|
const newStreamMessage = createNewStreamMessage(messageId, agentData);
|
|
1278
2326
|
// Set the content for the new message
|
|
1279
2327
|
const updatedNewMessage = { ...newStreamMessage };
|
|
@@ -1296,8 +2344,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1296
2344
|
return prev;
|
|
1297
2345
|
// Check if existing content is already longer than what we're trying to stream
|
|
1298
2346
|
const currentContentLength = existingMessage.content?.length || 0;
|
|
2347
|
+
const previousContentLength = message.data?.previousContentLength || 0;
|
|
1299
2348
|
const totalContentLength = message.data?.totalContentLength || 0;
|
|
1300
|
-
if (
|
|
2349
|
+
if (message.data?.isIncremental &&
|
|
2350
|
+
previousContentLength !== currentContentLength &&
|
|
2351
|
+
(previousContentLength > 0 || currentContentLength > 0)) {
|
|
2352
|
+
console.warn("[AgentTerminal] Content chunk length mismatch", {
|
|
2353
|
+
messageId,
|
|
2354
|
+
previousContentLength,
|
|
2355
|
+
currentContentLength,
|
|
2356
|
+
totalContentLength,
|
|
2357
|
+
deltaLength: (message.data?.deltaContent || "").length,
|
|
2358
|
+
});
|
|
2359
|
+
}
|
|
2360
|
+
if (message.data?.isIncremental &&
|
|
2361
|
+
currentContentLength >= totalContentLength &&
|
|
1301
2362
|
totalContentLength > 0) {
|
|
1302
2363
|
return prev;
|
|
1303
2364
|
}
|
|
@@ -1321,10 +2382,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1321
2382
|
});
|
|
1322
2383
|
}, [createNewStreamMessage, agent]);
|
|
1323
2384
|
const handleToolCall = useCallback((message, agentData) => {
|
|
1324
|
-
const
|
|
2385
|
+
const extractedToolCall = extractToolCallFields(message.data);
|
|
2386
|
+
const toolCallId = extractedToolCall.toolCallId || crypto.randomUUID();
|
|
1325
2387
|
// Prefer provided messageId, otherwise fall back to the last streaming assistant message
|
|
1326
2388
|
let toolCallMessageId = message.data?.messageId;
|
|
1327
2389
|
if (!toolCallMessageId) {
|
|
2390
|
+
console.warn("[AgentTerminal] Tool call missing messageId; falling back", {
|
|
2391
|
+
toolCallId,
|
|
2392
|
+
toolName: message.data?.name || message.data?.displayName,
|
|
2393
|
+
});
|
|
1328
2394
|
const current = messagesRef.current;
|
|
1329
2395
|
const lastStreaming = [...current]
|
|
1330
2396
|
.reverse()
|
|
@@ -1332,6 +2398,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1332
2398
|
if (lastStreaming?.id) {
|
|
1333
2399
|
toolCallMessageId = lastStreaming.id;
|
|
1334
2400
|
}
|
|
2401
|
+
else {
|
|
2402
|
+
// Tool calls can arrive before any assistant content chunk (common for dialog tools like ask-questionnaire).
|
|
2403
|
+
// Create a synthetic streaming message so the UI can render the tool call immediately.
|
|
2404
|
+
toolCallMessageId = crypto.randomUUID();
|
|
2405
|
+
}
|
|
1335
2406
|
}
|
|
1336
2407
|
// Find or create the target message for this tool call
|
|
1337
2408
|
if (toolCallMessageId) {
|
|
@@ -1359,24 +2430,25 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1359
2430
|
}
|
|
1360
2431
|
// Add tool call to the message in the array
|
|
1361
2432
|
if (toolCallMessageId && message.data && toolCallId) {
|
|
1362
|
-
const
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
2433
|
+
const toolCallError = message.data.functionError || message.data.error || "";
|
|
2434
|
+
const isPruned = !!message.data?.isPruned || /^PRUNED$/i.test(String(toolCallError));
|
|
2435
|
+
const toolCallCreatedDate = message.data.createdDate ||
|
|
2436
|
+
message.timestamp ||
|
|
2437
|
+
new Date().toISOString();
|
|
1366
2438
|
const toolCall = {
|
|
1367
2439
|
id: toolCallId,
|
|
1368
2440
|
messageId: toolCallMessageId,
|
|
1369
2441
|
dbMessageId: message.data.messageId, // Database message ID for approval/rejection
|
|
1370
2442
|
toolCallId: toolCallId,
|
|
1371
|
-
functionName: functionName,
|
|
1372
|
-
functionArguments:
|
|
1373
|
-
message.data.arguments ||
|
|
1374
|
-
JSON.stringify(message.data.function?.arguments || {}),
|
|
2443
|
+
functionName: extractedToolCall.functionName,
|
|
2444
|
+
functionArguments: extractedToolCall.functionArguments,
|
|
1375
2445
|
functionResult: message.data.functionResult || message.data.result || "",
|
|
1376
|
-
|
|
2446
|
+
functionResultRichContent: message.data.richContent || undefined,
|
|
2447
|
+
functionError: toolCallError,
|
|
2448
|
+
isPruned,
|
|
1377
2449
|
isCompleted: false,
|
|
1378
2450
|
responseTimeMs: message.data.responseTimeMs,
|
|
1379
|
-
createdDate:
|
|
2451
|
+
createdDate: toolCallCreatedDate,
|
|
1380
2452
|
requiresApproval: message.data?.requiresApproval,
|
|
1381
2453
|
};
|
|
1382
2454
|
// Check for existing tool call - search across ALL messages by toolCallId first
|
|
@@ -1415,14 +2487,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1415
2487
|
// Check if the new data has more information than what we have
|
|
1416
2488
|
const newArgs = toolCall.functionArguments;
|
|
1417
2489
|
const existingArgs = existingToolCall.functionArguments;
|
|
1418
|
-
const
|
|
2490
|
+
const newArgsText = stringifyToolField(newArgs) || "";
|
|
2491
|
+
const existingArgsText = stringifyToolField(existingArgs) || "";
|
|
2492
|
+
const hasMoreCompleteArgs = (newArgsText.length > existingArgsText.length &&
|
|
2493
|
+
newArgsText !== existingArgsText) ||
|
|
2494
|
+
(existingArgsText === "{}" && newArgsText !== "{}");
|
|
1419
2495
|
const hasNewResult = toolCall.functionResult && !existingToolCall.functionResult;
|
|
2496
|
+
const hasNewRichContent = toolCall.functionResultRichContent &&
|
|
2497
|
+
!existingToolCall.functionResultRichContent;
|
|
1420
2498
|
const hasNewError = toolCall.functionError && !existingToolCall.functionError;
|
|
1421
2499
|
const hasNewApprovalInfo = toolCall.requiresApproval && !existingToolCall.requiresApproval;
|
|
1422
2500
|
const hasNewDbMessageId = toolCall.dbMessageId && !existingToolCall.dbMessageId;
|
|
1423
2501
|
// Only update if there's meaningful new data
|
|
1424
2502
|
if (hasMoreCompleteArgs ||
|
|
1425
2503
|
hasNewResult ||
|
|
2504
|
+
hasNewRichContent ||
|
|
1426
2505
|
hasNewError ||
|
|
1427
2506
|
hasNewApprovalInfo ||
|
|
1428
2507
|
hasNewDbMessageId) {
|
|
@@ -1439,9 +2518,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1439
2518
|
updatedToolCalls[idx] = {
|
|
1440
2519
|
...existing,
|
|
1441
2520
|
functionArguments: hasMoreCompleteArgs
|
|
1442
|
-
?
|
|
1443
|
-
: existing.functionArguments,
|
|
2521
|
+
? newArgsText
|
|
2522
|
+
: existingArgsText || existing.functionArguments,
|
|
1444
2523
|
functionResult: toolCall.functionResult || existing.functionResult,
|
|
2524
|
+
functionResultRichContent: toolCall.functionResultRichContent ||
|
|
2525
|
+
existing.functionResultRichContent,
|
|
1445
2526
|
functionError: toolCall.functionError || existing.functionError,
|
|
1446
2527
|
requiresApproval: toolCall.requiresApproval || existing.requiresApproval,
|
|
1447
2528
|
};
|
|
@@ -1476,10 +2557,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1476
2557
|
}
|
|
1477
2558
|
}, [createNewStreamMessage]);
|
|
1478
2559
|
const handleToolResult = useCallback((message, agentData) => {
|
|
1479
|
-
const
|
|
2560
|
+
const extractedToolCall = extractToolCallFields(message.data);
|
|
2561
|
+
const resultToolCallId = extractedToolCall.toolCallId || crypto.randomUUID();
|
|
1480
2562
|
// Prefer provided messageId, otherwise fall back to the last streaming assistant message
|
|
1481
2563
|
let resultMessageId = message.data?.messageId;
|
|
1482
2564
|
if (!resultMessageId) {
|
|
2565
|
+
console.warn("[AgentTerminal] Tool result missing messageId; falling back", {
|
|
2566
|
+
toolCallId: resultToolCallId,
|
|
2567
|
+
toolName: message.data?.functionName || message.data?.displayName,
|
|
2568
|
+
});
|
|
1483
2569
|
const current = messagesRef.current;
|
|
1484
2570
|
const lastStreaming = [...current]
|
|
1485
2571
|
.reverse()
|
|
@@ -1501,6 +2587,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1501
2587
|
outputCost: Number(cost.output) || 0,
|
|
1502
2588
|
cachedCost: Number(cost.cached) || 0,
|
|
1503
2589
|
cacheWriteCost: Number(cost.cacheWrite) || 0,
|
|
2590
|
+
imageCost: Number(cost.imageCost) || 0,
|
|
1504
2591
|
totalCost: Number(cost.total) || 0,
|
|
1505
2592
|
currency: "USD",
|
|
1506
2593
|
};
|
|
@@ -1512,11 +2599,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1512
2599
|
if (anyNonZero) {
|
|
1513
2600
|
setLiveTotals(nextTotals);
|
|
1514
2601
|
}
|
|
1515
|
-
// Extract context window info from cost object
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
2602
|
+
// Extract context window info from cost object.
|
|
2603
|
+
// Note: contextUsed can legitimately be 0 (especially early in OpenAI streams),
|
|
2604
|
+
// so we must check for null/undefined instead of truthiness.
|
|
2605
|
+
const contextWindowRaw = cost.contextWindow;
|
|
2606
|
+
const contextUsedRaw = cost.contextUsed;
|
|
2607
|
+
if (contextWindowRaw !== undefined &&
|
|
2608
|
+
contextWindowRaw !== null &&
|
|
2609
|
+
contextUsedRaw !== undefined &&
|
|
2610
|
+
contextUsedRaw !== null) {
|
|
2611
|
+
const contextWindowValue = Number(contextWindowRaw);
|
|
2612
|
+
const contextUsedValue = Number(contextUsedRaw);
|
|
2613
|
+
if (contextWindowValue > 0 &&
|
|
2614
|
+
Number.isFinite(contextUsedValue) &&
|
|
2615
|
+
contextUsedValue >= 0) {
|
|
1520
2616
|
setContextWindowStatus({
|
|
1521
2617
|
contextWindowTokens: contextWindowValue,
|
|
1522
2618
|
estimatedInputTokens: contextUsedValue,
|
|
@@ -1540,6 +2636,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1540
2636
|
outputCost: Number(data.totalOutputTokenCost) || 0,
|
|
1541
2637
|
cachedCost: Number(data.totalCachedTokenCost) || 0,
|
|
1542
2638
|
cacheWriteCost: Number(data.totalCacheWriteTokenCost) || 0,
|
|
2639
|
+
imageCost: Number(data.totalImageCost) || 0,
|
|
1543
2640
|
totalCost: Number(data.totalCost) || 0,
|
|
1544
2641
|
currency: data.currency || "USD",
|
|
1545
2642
|
};
|
|
@@ -1572,13 +2669,22 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1572
2669
|
const existingToolCall = updatedMessage.toolCalls[toolCallIndex];
|
|
1573
2670
|
if (existingToolCall && message.data) {
|
|
1574
2671
|
const updatedToolCalls = [...updatedMessage.toolCalls];
|
|
2672
|
+
const nextArgsText = stringifyToolField(extractedToolCall.functionArguments) || "";
|
|
2673
|
+
const existingArgsText = stringifyToolField(existingToolCall.functionArguments) || "";
|
|
2674
|
+
const hasMoreCompleteArgs = (nextArgsText.length > existingArgsText.length &&
|
|
2675
|
+
nextArgsText !== existingArgsText) ||
|
|
2676
|
+
(existingArgsText === "{}" && nextArgsText !== "{}");
|
|
1575
2677
|
const toolCall = {
|
|
1576
2678
|
id: existingToolCall.id,
|
|
1577
2679
|
messageId: existingToolCall.messageId,
|
|
1578
2680
|
toolCallId: existingToolCall.toolCallId,
|
|
1579
2681
|
functionName: existingToolCall.functionName,
|
|
1580
|
-
functionArguments:
|
|
2682
|
+
functionArguments: hasMoreCompleteArgs
|
|
2683
|
+
? nextArgsText
|
|
2684
|
+
: existingToolCall.functionArguments,
|
|
1581
2685
|
functionResult: message.data.functionResult || message.data.result || "",
|
|
2686
|
+
functionResultRichContent: message.data.richContent ||
|
|
2687
|
+
existingToolCall.functionResultRichContent,
|
|
1582
2688
|
functionError: message.data.functionError || message.data.error || "",
|
|
1583
2689
|
isCompleted: true,
|
|
1584
2690
|
responseTimeMs: message.data.responseTimeMs,
|
|
@@ -1595,23 +2701,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1595
2701
|
}
|
|
1596
2702
|
else if (message.data && resultToolCallId && resultMessageId) {
|
|
1597
2703
|
// Create new tool call if it doesn't exist
|
|
1598
|
-
const
|
|
1599
|
-
message.
|
|
1600
|
-
|
|
1601
|
-
"unknown";
|
|
2704
|
+
const toolCallCreatedDate = message.data.createdDate ||
|
|
2705
|
+
message.timestamp ||
|
|
2706
|
+
new Date().toISOString();
|
|
1602
2707
|
const toolCall = {
|
|
1603
2708
|
id: resultToolCallId,
|
|
1604
2709
|
messageId: resultMessageId,
|
|
1605
2710
|
toolCallId: resultToolCallId,
|
|
1606
|
-
functionName: functionName,
|
|
1607
|
-
functionArguments:
|
|
1608
|
-
message.data.arguments ||
|
|
1609
|
-
JSON.stringify(message.data.function?.arguments || {}),
|
|
2711
|
+
functionName: extractedToolCall.functionName,
|
|
2712
|
+
functionArguments: extractedToolCall.functionArguments,
|
|
1610
2713
|
functionResult: message.data.functionResult || message.data.result || "",
|
|
2714
|
+
functionResultRichContent: message.data.richContent || undefined,
|
|
1611
2715
|
functionError: message.data.functionError || message.data.error || "",
|
|
1612
2716
|
isCompleted: true,
|
|
1613
2717
|
responseTimeMs: message.data.responseTimeMs,
|
|
1614
|
-
createdDate:
|
|
2718
|
+
createdDate: toolCallCreatedDate,
|
|
1615
2719
|
};
|
|
1616
2720
|
updatedMessage.toolCalls = [...updatedMessage.toolCalls, toolCall];
|
|
1617
2721
|
}
|
|
@@ -1702,7 +2806,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1702
2806
|
// The agent might have been persisted after sending a prompt
|
|
1703
2807
|
// Only treat as "new" if backend returns 404
|
|
1704
2808
|
const hasExistingMessages = messagesRef.current.length > 0;
|
|
1705
|
-
if (agentStub.status === "new" &&
|
|
2809
|
+
if (agentStub.status === "new" &&
|
|
2810
|
+
!agentStub.userId &&
|
|
2811
|
+
!hasExistingMessages) {
|
|
1706
2812
|
// Only initialize as new if we have no messages yet (initial mount)
|
|
1707
2813
|
// Derive initial profile from provided metadata if present
|
|
1708
2814
|
const initialProfileIdFromMeta = (() => {
|
|
@@ -1742,6 +2848,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1742
2848
|
totalInputTokenCost: 0,
|
|
1743
2849
|
totalOutputTokenCost: 0,
|
|
1744
2850
|
totalCachedInputTokenCost: 0,
|
|
2851
|
+
totalImageCost: 0,
|
|
1745
2852
|
totalCost: 0,
|
|
1746
2853
|
messageCount: 0,
|
|
1747
2854
|
});
|
|
@@ -1784,48 +2891,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1784
2891
|
}
|
|
1785
2892
|
})();
|
|
1786
2893
|
// Create context with top-level properties (what ContextInfoBar expects)
|
|
1787
|
-
const localCtx = item && shouldSeedContext
|
|
1788
|
-
? {
|
|
1789
|
-
items: [
|
|
1790
|
-
{
|
|
1791
|
-
id: item.id,
|
|
1792
|
-
language: item.language,
|
|
1793
|
-
version: item.version,
|
|
1794
|
-
name: editContext?.item?.name,
|
|
1795
|
-
path: editContext?.item?.path,
|
|
1796
|
-
},
|
|
1797
|
-
],
|
|
1798
|
-
components: editContext?.selection?.length && item
|
|
1799
|
-
? editContext.selection.map((componentId) => ({
|
|
1800
|
-
componentId,
|
|
1801
|
-
pageItem: {
|
|
1802
|
-
id: item.id,
|
|
1803
|
-
language: item.language,
|
|
1804
|
-
version: item.version,
|
|
1805
|
-
name: editContext?.item?.name,
|
|
1806
|
-
},
|
|
1807
|
-
}))
|
|
1808
|
-
: undefined,
|
|
1809
|
-
field: fieldsContext?.focusedField?.fieldId &&
|
|
1810
|
-
fieldsContext.focusedField?.item?.id
|
|
1811
|
-
? {
|
|
1812
|
-
fieldId: fieldsContext.focusedField.fieldId,
|
|
1813
|
-
fieldName: fieldsContext.focusedField
|
|
1814
|
-
.fieldName,
|
|
1815
|
-
item: {
|
|
1816
|
-
id: fieldsContext.focusedField.item.id,
|
|
1817
|
-
language: fieldsContext.focusedField.item.language ||
|
|
1818
|
-
editContext?.currentItemDescriptor?.language ||
|
|
1819
|
-
"en",
|
|
1820
|
-
version: fieldsContext.focusedField.item.version ??
|
|
1821
|
-
editContext?.currentItemDescriptor?.version ??
|
|
1822
|
-
0,
|
|
1823
|
-
name: editContext?.item?.name,
|
|
1824
|
-
},
|
|
1825
|
-
}
|
|
1826
|
-
: undefined,
|
|
1827
|
-
}
|
|
1828
|
-
: null;
|
|
2894
|
+
const localCtx = item && shouldSeedContext ? buildEditorContextPayload(item) : null;
|
|
1829
2895
|
let nextMetadata = null;
|
|
1830
2896
|
if (initialMetadata) {
|
|
1831
2897
|
// Merge initial metadata with local context (using top-level structure)
|
|
@@ -1901,8 +2967,16 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1901
2967
|
seenMessageIdsRef.current.add(msg.id.toLowerCase());
|
|
1902
2968
|
}
|
|
1903
2969
|
});
|
|
1904
|
-
// Keep local streaming
|
|
2970
|
+
// Keep local streaming if the agent is still active (running/waiting); otherwise discard locals.
|
|
2971
|
+
// This is important for dialog-style tools (e.g., ask-questionnaire) where the agent may be
|
|
2972
|
+
// "waiting" but we still want to keep the in-flight tool call UI visible.
|
|
1905
2973
|
const isRunning = agentData.status === "running" || agentData.status === 1;
|
|
2974
|
+
const isWaiting = agentData.status === "waitingForApproval" ||
|
|
2975
|
+
agentData.status === 2 ||
|
|
2976
|
+
agentData.status === "waitingForInput" ||
|
|
2977
|
+
agentData.status === "costLimitReached" ||
|
|
2978
|
+
agentData.status === 7;
|
|
2979
|
+
const keepLocalStreaming = isRunning || isWaiting;
|
|
1906
2980
|
// Filter local messages to only include streaming/incomplete messages that aren't in DB
|
|
1907
2981
|
// This prevents duplicates when reloading - DB messages are source of truth for completed messages
|
|
1908
2982
|
const localStreaming = isRunning
|
|
@@ -1920,7 +2994,19 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1920
2994
|
// Don't keep completed messages from local state - DB is source of truth
|
|
1921
2995
|
return false;
|
|
1922
2996
|
})
|
|
1923
|
-
:
|
|
2997
|
+
: keepLocalStreaming
|
|
2998
|
+
? messagesRef.current.filter((localMsg) => {
|
|
2999
|
+
if (!localMsg.id)
|
|
3000
|
+
return false;
|
|
3001
|
+
if (!localMsg.isCompleted &&
|
|
3002
|
+
localMsg.messageType === "streaming") {
|
|
3003
|
+
const existsInDb = dbMessages.some((dbMsg) => dbMsg.id &&
|
|
3004
|
+
dbMsg.id.toLowerCase() === localMsg.id.toLowerCase());
|
|
3005
|
+
return !existsInDb;
|
|
3006
|
+
}
|
|
3007
|
+
return false;
|
|
3008
|
+
})
|
|
3009
|
+
: [];
|
|
1924
3010
|
const merged = mergeMessagesById(dbMessages, localStreaming);
|
|
1925
3011
|
messagesRef.current = merged;
|
|
1926
3012
|
setMessages(merged);
|
|
@@ -1931,6 +3017,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1931
3017
|
const runningNow = agentData.status === "running" || agentData.status === 1;
|
|
1932
3018
|
const waitingForApprovalNow = agentData.status === "waitingForApproval" ||
|
|
1933
3019
|
agentData.status === 2;
|
|
3020
|
+
const waitingForInputNow = agentData.status === "waitingForInput";
|
|
1934
3021
|
const hasStreamingNow = merged.some((m) => !m.isCompleted && m.messageType === "streaming");
|
|
1935
3022
|
if (runningNow || hasStreamingNow) {
|
|
1936
3023
|
setIsWaitingForResponse(true);
|
|
@@ -1938,11 +3025,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1938
3025
|
// Agent is actively running, show thinking dots
|
|
1939
3026
|
setIsAgentThinking(true);
|
|
1940
3027
|
}
|
|
1941
|
-
else if (waitingForApprovalNow) {
|
|
3028
|
+
else if (waitingForApprovalNow || waitingForInputNow) {
|
|
1942
3029
|
setIsWaitingForResponse(false);
|
|
1943
3030
|
isWaitingRef.current = false;
|
|
1944
3031
|
setIsConnecting(false);
|
|
1945
|
-
// Agent is waiting for user approval, hide thinking dots
|
|
3032
|
+
// Agent is waiting for user input/approval, hide thinking dots
|
|
1946
3033
|
setIsAgentThinking(false);
|
|
1947
3034
|
}
|
|
1948
3035
|
else {
|
|
@@ -1991,19 +3078,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1991
3078
|
if (!contextJson)
|
|
1992
3079
|
return null;
|
|
1993
3080
|
const parsedContext = JSON.parse(contextJson);
|
|
1994
|
-
// Context is stored as flat structure with top-level properties
|
|
1995
|
-
// Due to C# [JsonExtensionData], AdditionalData entries are serialized at top level
|
|
1996
|
-
// We need to normalize: move todoList from top level into additionalData if present
|
|
1997
3081
|
if (parsedContext && typeof parsedContext === "object") {
|
|
1998
|
-
|
|
1999
|
-
// If todoList is at top level but not in additionalData, move it
|
|
2000
|
-
if (normalized.todoList && !normalized.additionalData?.todoList) {
|
|
2001
|
-
normalized.additionalData = {
|
|
2002
|
-
...(normalized.additionalData || {}),
|
|
2003
|
-
todoList: normalized.todoList,
|
|
2004
|
-
};
|
|
2005
|
-
}
|
|
2006
|
-
return normalized;
|
|
3082
|
+
return parsedContext;
|
|
2007
3083
|
}
|
|
2008
3084
|
return null;
|
|
2009
3085
|
}
|
|
@@ -2089,7 +3165,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2089
3165
|
return;
|
|
2090
3166
|
}
|
|
2091
3167
|
// Check if cost limit exceeded based on status or cost values
|
|
2092
|
-
const statusIndicatesLimit = agent.status === "costLimitReached"
|
|
3168
|
+
const statusIndicatesLimit = agent.status === "costLimitReached";
|
|
2093
3169
|
// Use liveTotals.totalCost as fallback if agent.totalCost is missing or 0
|
|
2094
3170
|
const effectiveTotalCost = agent.totalCost || liveTotals?.totalCost || 0;
|
|
2095
3171
|
const costExceedsLimit = agent.costLimit &&
|
|
@@ -2174,9 +3250,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2174
3250
|
// Handle agent:profile:switched (profile changed via switch-profile function)
|
|
2175
3251
|
if (messageType === "agent:profile:switched") {
|
|
2176
3252
|
const payload = message.payload || {};
|
|
2177
|
-
const switchedAgentId = payload.agentId
|
|
2178
|
-
const newProfileId = payload.newProfileId
|
|
2179
|
-
const newProfileName = payload.newProfileName
|
|
3253
|
+
const switchedAgentId = payload.agentId;
|
|
3254
|
+
const newProfileId = payload.newProfileId;
|
|
3255
|
+
const newProfileName = payload.newProfileName;
|
|
2180
3256
|
if (switchedAgentId === agent.id && newProfileId) {
|
|
2181
3257
|
// Update the agent's profile
|
|
2182
3258
|
setAgent((prev) => {
|
|
@@ -2217,9 +3293,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2217
3293
|
return;
|
|
2218
3294
|
}
|
|
2219
3295
|
// For other agent messages, check if this is for our agent
|
|
2220
|
-
const agentId = message.payload?.agentId
|
|
2221
|
-
if (agentId !== agent.id)
|
|
3296
|
+
const agentId = message.payload?.agentId;
|
|
3297
|
+
if (agentId !== agent.id) {
|
|
2222
3298
|
return;
|
|
3299
|
+
}
|
|
2223
3300
|
// Handle agent:run:start
|
|
2224
3301
|
if (messageType === "agent:run:start") {
|
|
2225
3302
|
// If a stop operation is in progress, ignore this message to prevent
|
|
@@ -2323,6 +3400,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2323
3400
|
if (messageType === "agent:prompt:queued") {
|
|
2324
3401
|
const { queueEntry } = message.payload;
|
|
2325
3402
|
if (queueEntry) {
|
|
3403
|
+
if (shouldSuppressQueuedPrompt(queueEntry)) {
|
|
3404
|
+
return;
|
|
3405
|
+
}
|
|
2326
3406
|
// Add the new prompt to the queued prompts list
|
|
2327
3407
|
setQueuedPrompts((prev) => {
|
|
2328
3408
|
// Check if prompt already exists (deduplication)
|
|
@@ -2351,17 +3431,29 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2351
3431
|
return;
|
|
2352
3432
|
}
|
|
2353
3433
|
const { seq, type, data, cost } = message.payload;
|
|
3434
|
+
if (type === "ToolCall" || type === "toolCall") {
|
|
3435
|
+
}
|
|
2354
3436
|
// Always allow ContextUpdate messages (metadata updates) regardless of agent status
|
|
2355
3437
|
const isContextUpdate = type === "ContextUpdate" || type === "contextUpdate";
|
|
3438
|
+
const isHeartbeat = type === "Heartbeat" || type === "heartbeat";
|
|
3439
|
+
const shouldClearHeartbeat = type === "ContentChunk" ||
|
|
3440
|
+
type === "contentChunk" ||
|
|
3441
|
+
type === "ToolCall" ||
|
|
3442
|
+
type === "toolCall" ||
|
|
3443
|
+
type === "ToolResult" ||
|
|
3444
|
+
type === "toolResult";
|
|
2356
3445
|
// Allow deltas if the agent is running OR if we already have an active streaming message
|
|
2357
3446
|
// OR if the server provided a messageId for targeted updates.
|
|
2358
3447
|
// This avoids dropping early deltas that may arrive before the 'running' status update.
|
|
2359
3448
|
// ContextUpdate messages are always allowed as they're metadata updates, not content.
|
|
2360
3449
|
const isRunning = agent.status === "running" || agent.status === 1;
|
|
2361
3450
|
const hasStreaming = messagesRef.current.some((m) => !m.isCompleted && m.messageType === "streaming");
|
|
2362
|
-
const hasMessageId = !!message?.payload?.data?.messageId
|
|
2363
|
-
|
|
2364
|
-
|
|
3451
|
+
const hasMessageId = !!message?.payload?.data?.messageId;
|
|
3452
|
+
if (!isContextUpdate &&
|
|
3453
|
+
!isHeartbeat &&
|
|
3454
|
+
!isRunning &&
|
|
3455
|
+
!hasStreaming &&
|
|
3456
|
+
!hasMessageId) {
|
|
2365
3457
|
return; // ignore only if we cannot safely target an existing streaming message
|
|
2366
3458
|
}
|
|
2367
3459
|
// Deduplicate by sequence
|
|
@@ -2378,6 +3470,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2378
3470
|
cost,
|
|
2379
3471
|
timestamp: new Date().toISOString(),
|
|
2380
3472
|
};
|
|
3473
|
+
if (shouldClearHeartbeat) {
|
|
3474
|
+
clearHeartbeatMessages();
|
|
3475
|
+
}
|
|
2381
3476
|
if (type === "ContentChunk" || type === "contentChunk") {
|
|
2382
3477
|
handleContentChunk(agentStreamMessage, agent);
|
|
2383
3478
|
}
|
|
@@ -2387,6 +3482,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2387
3482
|
else if (type === "ToolResult" || type === "toolResult") {
|
|
2388
3483
|
handleToolResult(agentStreamMessage, agent);
|
|
2389
3484
|
}
|
|
3485
|
+
else if (type === "Heartbeat" || type === "heartbeat") {
|
|
3486
|
+
handleHeartbeatMessage(agentStreamMessage);
|
|
3487
|
+
}
|
|
2390
3488
|
else if (type === "ContextUpdate" || type === "contextUpdate") {
|
|
2391
3489
|
// Handle context updates from streaming - data contains additionalData.todoList and ChildAgents
|
|
2392
3490
|
const contextData = data;
|
|
@@ -2396,10 +3494,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2396
3494
|
const current = (prev || {});
|
|
2397
3495
|
const updated = { ...current };
|
|
2398
3496
|
// Merge additionalData if present (deep merge to preserve existing values)
|
|
2399
|
-
if (contextData.additionalData
|
|
2400
|
-
const sourceAdditionalData = contextData.additionalData ||
|
|
2401
|
-
contextData.AdditionalData ||
|
|
2402
|
-
{};
|
|
3497
|
+
if (contextData.additionalData) {
|
|
3498
|
+
const sourceAdditionalData = contextData.additionalData || {};
|
|
2403
3499
|
updated.additionalData = {
|
|
2404
3500
|
...(current.additionalData || {}),
|
|
2405
3501
|
...sourceAdditionalData,
|
|
@@ -2407,10 +3503,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2407
3503
|
}
|
|
2408
3504
|
// Merge ChildAgents if present (for spawned agents) - replace entire array
|
|
2409
3505
|
// Backend sends the complete updated list, not just additions
|
|
2410
|
-
if (contextData.
|
|
2411
|
-
const childAgents = contextData.
|
|
3506
|
+
if (contextData.childAgents) {
|
|
3507
|
+
const childAgents = contextData.childAgents;
|
|
2412
3508
|
if (Array.isArray(childAgents)) {
|
|
2413
|
-
updated.
|
|
3509
|
+
updated.childAgents = childAgents;
|
|
2414
3510
|
}
|
|
2415
3511
|
}
|
|
2416
3512
|
return updated;
|
|
@@ -2436,15 +3532,18 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2436
3532
|
// Route based on statusData.state
|
|
2437
3533
|
try {
|
|
2438
3534
|
// Normalize various status shapes and handle Cancelled uniformly
|
|
2439
|
-
const normalizedStatus = statusData?.state ||
|
|
2440
|
-
statusData?.
|
|
2441
|
-
|
|
2442
|
-
if (normalizedStatus === "Cancelled" ||
|
|
2443
|
-
normalizedStatus === "canceled") {
|
|
3535
|
+
const normalizedStatus = parseAgentStatus(statusData?.state) ||
|
|
3536
|
+
parseAgentStatus(statusData?.status);
|
|
3537
|
+
if (normalizedStatus === "idle") {
|
|
2444
3538
|
// Stop indicators and mark any in-progress streaming messages as completed
|
|
3539
|
+
clearHeartbeatMessages();
|
|
3540
|
+
setAgent((prev) => (prev ? { ...prev, status: "idle" } : prev));
|
|
2445
3541
|
setIsWaitingForResponse(false);
|
|
2446
3542
|
isWaitingRef.current = false;
|
|
2447
3543
|
setIsConnecting(false);
|
|
3544
|
+
setIsSubmitting(false);
|
|
3545
|
+
shouldCreateNewMessage.current = false;
|
|
3546
|
+
setIsAgentThinking(false);
|
|
2448
3547
|
setMessages((prev) => {
|
|
2449
3548
|
const updated = prev.map((msg) => !msg.isCompleted && msg.messageType === "streaming"
|
|
2450
3549
|
? {
|
|
@@ -2487,6 +3586,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2487
3586
|
outputCost: Number(totals.totalOutputTokenCost) || 0,
|
|
2488
3587
|
cachedCost: Number(totals.totalCachedInputTokenCost) || 0,
|
|
2489
3588
|
cacheWriteCost: Number(totals.totalCacheWriteTokenCost) || 0,
|
|
3589
|
+
imageCost: Number(totals.totalImageCost) || 0,
|
|
2490
3590
|
totalCost: totalCost,
|
|
2491
3591
|
currency: totals.currency,
|
|
2492
3592
|
};
|
|
@@ -2498,6 +3598,26 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2498
3598
|
if (anyNonZero) {
|
|
2499
3599
|
setLiveTotals(nextTotals);
|
|
2500
3600
|
}
|
|
3601
|
+
// Fallback context usage update for providers that do not include
|
|
3602
|
+
// context usage in every stream delta (for example some OpenAI streams).
|
|
3603
|
+
// Prefer explicit status values when present; otherwise derive from totals.
|
|
3604
|
+
const contextWindowValue = Number(statusData?.contextWindow ??
|
|
3605
|
+
agent?.contextWindowTokens ??
|
|
3606
|
+
0);
|
|
3607
|
+
const contextUsedValue = Number(statusData?.contextUsed ??
|
|
3608
|
+
(Number(totals.totalInputTokens) || 0) +
|
|
3609
|
+
(Number(totals.totalCachedInputTokens) || 0) +
|
|
3610
|
+
(Number(totals.totalCacheWriteTokens) || 0));
|
|
3611
|
+
if (contextWindowValue > 0 &&
|
|
3612
|
+
Number.isFinite(contextUsedValue) &&
|
|
3613
|
+
contextUsedValue >= 0) {
|
|
3614
|
+
setContextWindowStatus({
|
|
3615
|
+
contextWindowTokens: contextWindowValue,
|
|
3616
|
+
estimatedInputTokens: contextUsedValue,
|
|
3617
|
+
contextUsedPercent: (contextUsedValue / contextWindowValue) * 100,
|
|
3618
|
+
model: agent?.model || "",
|
|
3619
|
+
});
|
|
3620
|
+
}
|
|
2501
3621
|
// If server provides costLimit along with totals, persist it locally
|
|
2502
3622
|
if (statusCostLimit) {
|
|
2503
3623
|
setAgent((prev) => prev &&
|
|
@@ -2519,6 +3639,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2519
3639
|
return;
|
|
2520
3640
|
}
|
|
2521
3641
|
if (statusData?.state === "ToolApprovalsRequired") {
|
|
3642
|
+
setPendingBrowserCaptureDialogType(null);
|
|
2522
3643
|
const msgId = statusData.messageId;
|
|
2523
3644
|
const ids = statusData.toolCallIds || [];
|
|
2524
3645
|
if (msgId && Array.isArray(ids) && ids.length > 0) {
|
|
@@ -2550,16 +3671,40 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2550
3671
|
setIsWaitingForResponse(false);
|
|
2551
3672
|
return;
|
|
2552
3673
|
}
|
|
2553
|
-
// Handle
|
|
2554
|
-
if (
|
|
2555
|
-
|
|
3674
|
+
// Handle waiting states explicitly
|
|
3675
|
+
if (normalizedStatus === "waitingForApproval") {
|
|
3676
|
+
setPendingBrowserCaptureDialogType(null);
|
|
2556
3677
|
setAgent((prev) => prev ? { ...prev, status: "waitingForApproval" } : prev);
|
|
2557
3678
|
setIsConnecting(false);
|
|
2558
3679
|
setIsWaitingForResponse(false);
|
|
2559
3680
|
setIsAgentThinking(false);
|
|
2560
3681
|
return;
|
|
2561
3682
|
}
|
|
2562
|
-
if (
|
|
3683
|
+
if (normalizedStatus === "waitingForInput") {
|
|
3684
|
+
const dialogType = typeof statusData?.dialogType === "string"
|
|
3685
|
+
? statusData.dialogType
|
|
3686
|
+
: null;
|
|
3687
|
+
const isBrowserCaptureWait = dialogType === DIALOG_TYPES.CAPTURE_PAGE_DOM ||
|
|
3688
|
+
dialogType === DIALOG_TYPES.CAPTURE_PAGE_SCREENSHOT;
|
|
3689
|
+
setPendingBrowserCaptureDialogType(isBrowserCaptureWait ? dialogType : null);
|
|
3690
|
+
setAgent((prev) => prev
|
|
3691
|
+
? {
|
|
3692
|
+
...prev,
|
|
3693
|
+
status: "waitingForInput",
|
|
3694
|
+
statusMessage: isBrowserCaptureWait &&
|
|
3695
|
+
typeof statusData?.title === "string" &&
|
|
3696
|
+
statusData.title.trim()
|
|
3697
|
+
? statusData.title.trim()
|
|
3698
|
+
: prev.statusMessage,
|
|
3699
|
+
}
|
|
3700
|
+
: prev);
|
|
3701
|
+
setIsConnecting(false);
|
|
3702
|
+
setIsWaitingForResponse(false);
|
|
3703
|
+
setIsAgentThinking(false);
|
|
3704
|
+
return;
|
|
3705
|
+
}
|
|
3706
|
+
if (normalizedStatus === "costLimitReached") {
|
|
3707
|
+
setPendingBrowserCaptureDialogType(null);
|
|
2563
3708
|
const totalCost = Number(statusData.totalCost) || 0;
|
|
2564
3709
|
const costLimit = Number(statusData.costLimit) || agent?.costLimit || 0;
|
|
2565
3710
|
setCostLimitExceeded({
|
|
@@ -2577,7 +3722,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2577
3722
|
// Server sends additionalData directly (with todoList inside)
|
|
2578
3723
|
// Also may include ChildAgents for spawned agents
|
|
2579
3724
|
try {
|
|
2580
|
-
const serverAdditionalData = statusData.additionalData ||
|
|
3725
|
+
const serverAdditionalData = statusData.additionalData || {};
|
|
2581
3726
|
setAgentMetadata((prev) => {
|
|
2582
3727
|
const current = (prev || {});
|
|
2583
3728
|
const updated = { ...current };
|
|
@@ -2591,10 +3736,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2591
3736
|
}
|
|
2592
3737
|
// Merge ChildAgents if present (for spawned agents) - replace entire array
|
|
2593
3738
|
// Backend sends the complete updated list, not just additions
|
|
2594
|
-
if (statusData.
|
|
2595
|
-
const childAgents = statusData.
|
|
3739
|
+
if (statusData.childAgents) {
|
|
3740
|
+
const childAgents = statusData.childAgents;
|
|
2596
3741
|
if (Array.isArray(childAgents)) {
|
|
2597
|
-
updated.
|
|
3742
|
+
updated.childAgents = childAgents;
|
|
2598
3743
|
}
|
|
2599
3744
|
}
|
|
2600
3745
|
return updated;
|
|
@@ -2606,10 +3751,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2606
3751
|
return;
|
|
2607
3752
|
}
|
|
2608
3753
|
// Handle "completed" state (fallback for legacy code paths that send status instead of lifecycle event)
|
|
2609
|
-
if (normalizedStatus === "completed"
|
|
2610
|
-
normalizedStatus === "Completed") {
|
|
3754
|
+
if (normalizedStatus === "completed") {
|
|
2611
3755
|
// Reset deduplication for the next run
|
|
2612
3756
|
lastSeqRef.current = 0;
|
|
3757
|
+
clearHeartbeatMessages();
|
|
2613
3758
|
// Mark the last assistant message as completed
|
|
2614
3759
|
setMessages((prev) => {
|
|
2615
3760
|
const updated = prev.map((msg) => msg.role === "assistant" && !msg.isCompleted
|
|
@@ -2632,20 +3777,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2632
3777
|
setIsAgentThinking(false);
|
|
2633
3778
|
return;
|
|
2634
3779
|
}
|
|
2635
|
-
// Handle "Idle" state - agent has finished processing
|
|
2636
|
-
if (normalizedStatus === "idle" || normalizedStatus === "Idle") {
|
|
2637
|
-
// Update agent status to idle
|
|
2638
|
-
setAgent((prev) => (prev ? { ...prev, status: "idle" } : prev));
|
|
2639
|
-
setIsWaitingForResponse(false);
|
|
2640
|
-
isWaitingRef.current = false;
|
|
2641
|
-
setIsConnecting(false);
|
|
2642
|
-
shouldCreateNewMessage.current = false;
|
|
2643
|
-
setIsAgentThinking(false);
|
|
2644
|
-
return;
|
|
2645
|
-
}
|
|
2646
3780
|
// Handle "Running" state - agent is actively processing
|
|
2647
|
-
if (normalizedStatus === "running"
|
|
2648
|
-
normalizedStatus === "Running") {
|
|
3781
|
+
if (normalizedStatus === "running") {
|
|
2649
3782
|
// Update agent status to running
|
|
2650
3783
|
setAgent((prev) => (prev ? { ...prev, status: "running" } : prev));
|
|
2651
3784
|
setIsWaitingForResponse(true);
|
|
@@ -2654,8 +3787,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2654
3787
|
return;
|
|
2655
3788
|
}
|
|
2656
3789
|
// Handle "Error" state
|
|
2657
|
-
if (normalizedStatus === "error"
|
|
3790
|
+
if (normalizedStatus === "error") {
|
|
2658
3791
|
const errorMsg = statusData?.statusMessage || statusData?.error || "Unknown error";
|
|
3792
|
+
clearHeartbeatMessages();
|
|
2659
3793
|
setAgent((prev) => prev
|
|
2660
3794
|
? { ...prev, status: "error", statusMessage: errorMsg }
|
|
2661
3795
|
: prev);
|
|
@@ -2675,6 +3809,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2675
3809
|
if (messageType === "agent:run:complete") {
|
|
2676
3810
|
// Reset deduplication for the next run
|
|
2677
3811
|
lastSeqRef.current = 0;
|
|
3812
|
+
clearHeartbeatMessages();
|
|
2678
3813
|
// Mark the last assistant message as completed
|
|
2679
3814
|
setMessages((prev) => {
|
|
2680
3815
|
const updated = prev.map((msg) => msg.role === "assistant" && !msg.isCompleted
|
|
@@ -2699,7 +3834,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2699
3834
|
}
|
|
2700
3835
|
// Lifecycle: agent:run:error
|
|
2701
3836
|
if (messageType === "agent:run:error") {
|
|
2702
|
-
const errorMsg = message.payload?.error ||
|
|
3837
|
+
const errorMsg = toUserFacingAgentErrorMessage(message.payload?.error) ||
|
|
3838
|
+
"AI could not complete this request.";
|
|
3839
|
+
clearHeartbeatMessages();
|
|
2703
3840
|
// Reset deduplication for the next run after an error
|
|
2704
3841
|
lastSeqRef.current = 0;
|
|
2705
3842
|
setError(errorMsg);
|
|
@@ -2714,7 +3851,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2714
3851
|
}
|
|
2715
3852
|
}, [
|
|
2716
3853
|
agent,
|
|
3854
|
+
clearHeartbeatMessages,
|
|
2717
3855
|
handleContentChunk,
|
|
3856
|
+
handleHeartbeatMessage,
|
|
2718
3857
|
handleToolCall,
|
|
2719
3858
|
handleToolResult,
|
|
2720
3859
|
onAgentUpdate,
|
|
@@ -2769,6 +3908,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2769
3908
|
const isRunning = currentAgent.status === "running" || currentAgent.status === 1;
|
|
2770
3909
|
const isWaitingForApproval = currentAgent.status === "waitingForApproval" ||
|
|
2771
3910
|
currentAgent.status === 2;
|
|
3911
|
+
const isWaitingForInput = currentAgent.status === "waitingForInput";
|
|
2772
3912
|
if (isRunning) {
|
|
2773
3913
|
setIsWaitingForResponse(true);
|
|
2774
3914
|
isWaitingRef.current = true;
|
|
@@ -2776,10 +3916,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2776
3916
|
// Agent is currently running, show thinking dots
|
|
2777
3917
|
setIsAgentThinking(true);
|
|
2778
3918
|
}
|
|
2779
|
-
else if (isWaitingForApproval) {
|
|
3919
|
+
else if (isWaitingForApproval || isWaitingForInput) {
|
|
2780
3920
|
setIsWaitingForResponse(false);
|
|
2781
3921
|
isWaitingRef.current = false;
|
|
2782
|
-
// Agent is waiting for user approval, hide thinking dots
|
|
3922
|
+
// Agent is waiting for user input/approval, hide thinking dots
|
|
2783
3923
|
setIsAgentThinking(false);
|
|
2784
3924
|
}
|
|
2785
3925
|
else {
|
|
@@ -2826,36 +3966,157 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2826
3966
|
window.addEventListener("editor:focusAgentPrompt", focusHandler);
|
|
2827
3967
|
return () => window.removeEventListener("editor:focusAgentPrompt", focusHandler);
|
|
2828
3968
|
}, []);
|
|
3969
|
+
// Keep stable refs so we don't miss window events during effect re-runs.
|
|
3970
|
+
const agentIdRefForDialogs = useRef(null);
|
|
3971
|
+
const agentStubIdRefForDialogs = useRef(agentStub.id);
|
|
3972
|
+
const isActiveRefForDialogs = useRef(isActive);
|
|
3973
|
+
const scrollToBottomRefForDialogs = useRef(scrollToBottom);
|
|
3974
|
+
useEffect(() => {
|
|
3975
|
+
agentIdRefForDialogs.current = agent?.id ?? null;
|
|
3976
|
+
}, [agent?.id]);
|
|
3977
|
+
useEffect(() => {
|
|
3978
|
+
const prevId = agentStubIdRefForDialogs.current;
|
|
3979
|
+
agentStubIdRefForDialogs.current = agentStub.id;
|
|
3980
|
+
const visibleRegistry = { ...getVisibleDialogRegistry() };
|
|
3981
|
+
const normalizedPrevId = normalizeDialogAgentId(prevId);
|
|
3982
|
+
if (normalizedPrevId) {
|
|
3983
|
+
delete visibleRegistry[normalizedPrevId];
|
|
3984
|
+
globalThis.__agentDialogVisibleCallbacks = visibleRegistry;
|
|
3985
|
+
}
|
|
3986
|
+
if (prevId && prevId !== agentStub.id) {
|
|
3987
|
+
const orphanedDialog = activeInlineDialogRef.current;
|
|
3988
|
+
if (orphanedDialog) {
|
|
3989
|
+
setActiveInlineDialog(null);
|
|
3990
|
+
window.dispatchEvent(new CustomEvent("agent:dialog:orphaned", {
|
|
3991
|
+
detail: { callbackId: orphanedDialog.request.callbackId },
|
|
3992
|
+
}));
|
|
3993
|
+
}
|
|
3994
|
+
}
|
|
3995
|
+
}, [agentStub.id]);
|
|
3996
|
+
useEffect(() => {
|
|
3997
|
+
isActiveRefForDialogs.current = isActive;
|
|
3998
|
+
}, [isActive]);
|
|
3999
|
+
useEffect(() => {
|
|
4000
|
+
scrollToBottomRefForDialogs.current = scrollToBottom;
|
|
4001
|
+
}, [scrollToBottom]);
|
|
2829
4002
|
// Listen for agent inline dialog requests from AgentDialogHandler
|
|
2830
4003
|
useEffect(() => {
|
|
4004
|
+
if (orphanTimeoutRef.current) {
|
|
4005
|
+
clearTimeout(orphanTimeoutRef.current);
|
|
4006
|
+
orphanTimeoutRef.current = null;
|
|
4007
|
+
}
|
|
4008
|
+
const globalListeners = (globalThis.__agentDialogMountedAgents ?? []).filter((x) => typeof x === "string");
|
|
4009
|
+
const normalizedAgentStubId = normalizeDialogAgentId(agentStubIdRefForDialogs.current);
|
|
4010
|
+
if (normalizedAgentStubId &&
|
|
4011
|
+
!globalListeners.includes(normalizedAgentStubId)) {
|
|
4012
|
+
globalListeners.push(normalizedAgentStubId);
|
|
4013
|
+
}
|
|
4014
|
+
globalThis.__agentDialogMountedAgents = globalListeners;
|
|
2831
4015
|
const handleDialogShow = (event) => {
|
|
2832
|
-
const
|
|
2833
|
-
|
|
2834
|
-
|
|
4016
|
+
const detail = event?.detail;
|
|
4017
|
+
const request = detail?.request;
|
|
4018
|
+
const onComplete = detail?.onComplete;
|
|
4019
|
+
const onCancel = detail?.onCancel;
|
|
4020
|
+
const terminalAgentId = normalizeDialogAgentId(agentIdRefForDialogs.current);
|
|
4021
|
+
const terminalAgentStubId = normalizeDialogAgentId(agentStubIdRefForDialogs.current);
|
|
4022
|
+
const isActiveNow = isActiveRefForDialogs.current;
|
|
4023
|
+
if (!request)
|
|
4024
|
+
return;
|
|
4025
|
+
const requestAgentId = normalizeDialogAgentId(request.agentId);
|
|
4026
|
+
const agentMatch = !requestAgentId ||
|
|
4027
|
+
!terminalAgentStubId ||
|
|
4028
|
+
requestAgentId === terminalAgentStubId ||
|
|
4029
|
+
requestAgentId === terminalAgentId;
|
|
4030
|
+
if (!isActiveNow) {
|
|
4031
|
+
return;
|
|
4032
|
+
}
|
|
4033
|
+
if (!request)
|
|
4034
|
+
return;
|
|
4035
|
+
// Only handle dialog requests for this terminal's agent stub
|
|
4036
|
+
if (requestAgentId &&
|
|
4037
|
+
terminalAgentStubId &&
|
|
4038
|
+
requestAgentId !== terminalAgentStubId &&
|
|
4039
|
+
requestAgentId !== terminalAgentId) {
|
|
2835
4040
|
return;
|
|
2836
4041
|
}
|
|
2837
4042
|
console.log("[AgentTerminal] Received inline dialog request:", request);
|
|
2838
|
-
setActiveInlineDialog({
|
|
4043
|
+
setActiveInlineDialog({
|
|
4044
|
+
request,
|
|
4045
|
+
onComplete: (result) => {
|
|
4046
|
+
onComplete(result);
|
|
4047
|
+
onInteractionSubmitted?.();
|
|
4048
|
+
},
|
|
4049
|
+
onCancel: () => {
|
|
4050
|
+
onCancel();
|
|
4051
|
+
onInteractionSubmitted?.();
|
|
4052
|
+
},
|
|
4053
|
+
});
|
|
2839
4054
|
// Notify AgentDialogHandler that we accepted the dialog (stops retry mechanism)
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
4055
|
+
if (request.callbackId) {
|
|
4056
|
+
window.dispatchEvent(new CustomEvent("agent:dialog:accepted", {
|
|
4057
|
+
detail: { callbackId: request.callbackId },
|
|
4058
|
+
}));
|
|
4059
|
+
}
|
|
4060
|
+
setTimeout(() => {
|
|
4061
|
+
if (request.dialogType === "questionnaire") {
|
|
4062
|
+
scrollToBottomRefForDialogs.current?.();
|
|
4063
|
+
return;
|
|
4064
|
+
}
|
|
4065
|
+
scrollToBottomRefForDialogs.current?.();
|
|
4066
|
+
}, 100);
|
|
2845
4067
|
};
|
|
2846
4068
|
window.addEventListener("agent:dialog:show", handleDialogShow);
|
|
2847
|
-
return () =>
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
4069
|
+
return () => {
|
|
4070
|
+
const mounted = (globalThis.__agentDialogMountedAgents ?? []).filter((x) => typeof x === "string");
|
|
4071
|
+
globalThis.__agentDialogMountedAgents = mounted.filter((id) => id !== normalizeDialogAgentId(agentStubIdRefForDialogs.current));
|
|
4072
|
+
const visibleRegistry = { ...getVisibleDialogRegistry() };
|
|
4073
|
+
const idsToClear = [
|
|
4074
|
+
normalizeDialogAgentId(agentStubIdRefForDialogs.current),
|
|
4075
|
+
normalizeDialogAgentId(agentIdRefForDialogs.current),
|
|
4076
|
+
].filter(Boolean);
|
|
4077
|
+
idsToClear.forEach((id) => delete visibleRegistry[id]);
|
|
4078
|
+
globalThis.__agentDialogVisibleCallbacks = visibleRegistry;
|
|
4079
|
+
// If unmounting with an active dialog, defer the orphan event so that
|
|
4080
|
+
// React strict mode remounts can cancel it. Only real unmounts fire.
|
|
4081
|
+
const orphanedDialog = activeInlineDialogRef.current;
|
|
4082
|
+
if (orphanedDialog) {
|
|
4083
|
+
orphanTimeoutRef.current = setTimeout(() => {
|
|
4084
|
+
orphanTimeoutRef.current = null;
|
|
4085
|
+
window.dispatchEvent(new CustomEvent("agent:dialog:orphaned", {
|
|
4086
|
+
detail: { callbackId: orphanedDialog.request.callbackId },
|
|
4087
|
+
}));
|
|
4088
|
+
}, 300);
|
|
4089
|
+
}
|
|
4090
|
+
window.removeEventListener("agent:dialog:show", handleDialogShow);
|
|
4091
|
+
};
|
|
4092
|
+
}, []);
|
|
4093
|
+
// Announce when this terminal is ready to accept dialogs.
|
|
4094
|
+
// Fire immediately on mount using agentStub.id so the AgentDialogHandler
|
|
4095
|
+
// can re-dispatch pending dialogs without waiting for the async agent load.
|
|
4096
|
+
// Also fire when agent?.id becomes available in case it differs from the stub.
|
|
2851
4097
|
useEffect(() => {
|
|
2852
|
-
|
|
2853
|
-
|
|
4098
|
+
const normalizedStubId = normalizeDialogAgentId(agentStub.id);
|
|
4099
|
+
if (normalizedStubId) {
|
|
2854
4100
|
window.dispatchEvent(new CustomEvent("agent:terminal:ready", {
|
|
2855
|
-
detail: {
|
|
4101
|
+
detail: {
|
|
4102
|
+
agentId: normalizedStubId,
|
|
4103
|
+
terminalInstanceId: dialogTerminalInstanceIdRef.current,
|
|
4104
|
+
},
|
|
2856
4105
|
}));
|
|
2857
4106
|
}
|
|
2858
|
-
}, [
|
|
4107
|
+
}, [agentStub.id]);
|
|
4108
|
+
useEffect(() => {
|
|
4109
|
+
const normalizedAgentId = normalizeDialogAgentId(agent?.id);
|
|
4110
|
+
const normalizedStubId = normalizeDialogAgentId(agentStub.id);
|
|
4111
|
+
if (normalizedAgentId && normalizedAgentId !== normalizedStubId) {
|
|
4112
|
+
window.dispatchEvent(new CustomEvent("agent:terminal:ready", {
|
|
4113
|
+
detail: {
|
|
4114
|
+
agentId: normalizedAgentId,
|
|
4115
|
+
terminalInstanceId: dialogTerminalInstanceIdRef.current,
|
|
4116
|
+
},
|
|
4117
|
+
}));
|
|
4118
|
+
}
|
|
4119
|
+
}, [agent?.id, agentStub.id]);
|
|
2859
4120
|
// Profiles are provided by parent component (Agents). No local loading here.
|
|
2860
4121
|
// Select active profile based on agent.profileId or agentStub.profileId
|
|
2861
4122
|
useEffect(() => {
|
|
@@ -2866,20 +4127,47 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2866
4127
|
// Use case-insensitive comparison for GUID matching (backend may return different casing)
|
|
2867
4128
|
const normalizedProfileId = profileIdToUse?.toLowerCase();
|
|
2868
4129
|
const candidate = normalizedProfileId
|
|
2869
|
-
?
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
4130
|
+
? profiles.find((p) => p.id?.toLowerCase() === normalizedProfileId)
|
|
4131
|
+
: undefined;
|
|
4132
|
+
if (!candidate) {
|
|
4133
|
+
setActiveProfile(undefined);
|
|
4134
|
+
return;
|
|
2874
4135
|
}
|
|
2875
|
-
|
|
4136
|
+
// Keep active profile in sync whenever the matching entry in `profiles` changes —
|
|
4137
|
+
// not only when the profile id changes. Otherwise availableSkills (and similar fields)
|
|
4138
|
+
// that are merged in the parent after async loads never update (e.g. Template Builder
|
|
4139
|
+
// settings skills merged into the profile).
|
|
4140
|
+
setActiveProfile((prev) => {
|
|
4141
|
+
if (!prev || prev.id !== candidate.id)
|
|
4142
|
+
return candidate;
|
|
4143
|
+
const skillKey = (p) => JSON.stringify({
|
|
4144
|
+
allowed: (p.allowedSkills ?? []).map((s) => [
|
|
4145
|
+
String(s?.id ?? ""),
|
|
4146
|
+
String(s?.name ?? ""),
|
|
4147
|
+
]),
|
|
4148
|
+
available: (p.availableSkills ?? []).map((s) => [
|
|
4149
|
+
String(s?.id ?? ""),
|
|
4150
|
+
String(s?.name ?? ""),
|
|
4151
|
+
]),
|
|
4152
|
+
});
|
|
4153
|
+
if (skillKey(prev) === skillKey(candidate))
|
|
4154
|
+
return prev;
|
|
4155
|
+
return candidate;
|
|
4156
|
+
});
|
|
4157
|
+
}, [
|
|
4158
|
+
profiles,
|
|
4159
|
+
agent?.id,
|
|
4160
|
+
agent?.profileId,
|
|
4161
|
+
agentStub.id,
|
|
4162
|
+
agentStub.profileId,
|
|
4163
|
+
]);
|
|
2876
4164
|
// Clear queued prompts when agent changes or is new;
|
|
2877
4165
|
// initial fetch is handled by loadAgent() for better performance and reliability
|
|
2878
4166
|
useEffect(() => {
|
|
2879
|
-
if (!agent?.id ||
|
|
4167
|
+
if (!agent?.id || isLocalOnlyDraftAgent) {
|
|
2880
4168
|
setQueuedPrompts([]);
|
|
2881
4169
|
}
|
|
2882
|
-
}, [agent?.id,
|
|
4170
|
+
}, [agent?.id, isLocalOnlyDraftAgent]);
|
|
2883
4171
|
// Update selected model when the active profile or agent model changes
|
|
2884
4172
|
useEffect(() => {
|
|
2885
4173
|
if (!activeProfile)
|
|
@@ -2908,20 +4196,16 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2908
4196
|
// Initialize mode from metadata; fall back to agent.Mode from server
|
|
2909
4197
|
useEffect(() => {
|
|
2910
4198
|
try {
|
|
2911
|
-
const metaMode = agentMetadata?.mode;
|
|
2912
|
-
if (metaMode
|
|
2913
|
-
metaMode === "read-only" ||
|
|
2914
|
-
metaMode === "supervised") {
|
|
4199
|
+
const metaMode = normalizeAgentMode(agentMetadata?.mode);
|
|
4200
|
+
if (metaMode) {
|
|
2915
4201
|
setMode(metaMode);
|
|
2916
4202
|
return;
|
|
2917
4203
|
}
|
|
2918
4204
|
}
|
|
2919
4205
|
catch { }
|
|
2920
4206
|
try {
|
|
2921
|
-
const serverMode = agent?.mode;
|
|
2922
|
-
if (serverMode
|
|
2923
|
-
serverMode === "read-only" ||
|
|
2924
|
-
serverMode === "supervised") {
|
|
4207
|
+
const serverMode = normalizeAgentMode(agent?.mode);
|
|
4208
|
+
if (serverMode) {
|
|
2925
4209
|
setMode(serverMode);
|
|
2926
4210
|
}
|
|
2927
4211
|
}
|
|
@@ -2941,7 +4225,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2941
4225
|
textareaRef.current.focus();
|
|
2942
4226
|
}
|
|
2943
4227
|
}, [messages, activePlaceholderInput]);
|
|
2944
|
-
// Persist any pending settings (mode/model) once an agent exists server-side
|
|
4228
|
+
// Persist any pending settings (mode/model/profile) once an agent exists server-side
|
|
2945
4229
|
const persistPendingSettingsIfNeeded = useCallback(async () => {
|
|
2946
4230
|
try {
|
|
2947
4231
|
if (!agent?.id)
|
|
@@ -2954,6 +4238,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2954
4238
|
payload.model = pending.modelName;
|
|
2955
4239
|
if (pending.mode)
|
|
2956
4240
|
payload.mode = pending.mode;
|
|
4241
|
+
if (pending.profileId)
|
|
4242
|
+
payload.profileId = pending.profileId;
|
|
4243
|
+
if (pending.profileName != null)
|
|
4244
|
+
payload.profileName = pending.profileName;
|
|
2957
4245
|
if (Object.keys(payload).length === 0)
|
|
2958
4246
|
return;
|
|
2959
4247
|
await updateAgentSettings(agent.id, payload);
|
|
@@ -2963,6 +4251,92 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2963
4251
|
console.error("Failed to persist pending settings", e);
|
|
2964
4252
|
}
|
|
2965
4253
|
}, [agent?.id]);
|
|
4254
|
+
const getPendingRequestSettings = useCallback(() => {
|
|
4255
|
+
const pending = pendingSettingsRef.current;
|
|
4256
|
+
const requestProfile = pending?.profileId
|
|
4257
|
+
? profiles.find((profile) => profile.id === pending.profileId) ||
|
|
4258
|
+
activeProfile ||
|
|
4259
|
+
profiles[0]
|
|
4260
|
+
: activeProfile || profiles[0];
|
|
4261
|
+
let requestModelId = selectedModelId;
|
|
4262
|
+
if (pending?.modelName) {
|
|
4263
|
+
const requestModel = (requestProfile?.models || []).find((model) => (model.name || "").trim().toLowerCase() ===
|
|
4264
|
+
pending.modelName?.trim().toLowerCase());
|
|
4265
|
+
requestModelId = requestModel?.id || requestModelId;
|
|
4266
|
+
}
|
|
4267
|
+
return {
|
|
4268
|
+
mode: (pending?.mode || mode),
|
|
4269
|
+
profileId: pending?.profileId || requestProfile?.id || "",
|
|
4270
|
+
profileName: pending?.profileName || requestProfile?.name || "",
|
|
4271
|
+
modelId: requestModelId,
|
|
4272
|
+
};
|
|
4273
|
+
}, [activeProfile, mode, profiles, selectedModelId]);
|
|
4274
|
+
const getSubmitErrorMessage = (error) => {
|
|
4275
|
+
const fallback = "Failed to submit prompt. Please try again.";
|
|
4276
|
+
if (!(error instanceof Error))
|
|
4277
|
+
return fallback;
|
|
4278
|
+
const cleaned = toUserFacingAgentErrorMessage(error.message);
|
|
4279
|
+
return cleaned || fallback;
|
|
4280
|
+
};
|
|
4281
|
+
const suppressedQueuedPromptsRef = useRef([]);
|
|
4282
|
+
const pruneSuppressedQueuedPrompts = useCallback(() => {
|
|
4283
|
+
const cutoff = Date.now() - 15_000;
|
|
4284
|
+
suppressedQueuedPromptsRef.current =
|
|
4285
|
+
suppressedQueuedPromptsRef.current.filter((entry) => entry.createdAt >= cutoff);
|
|
4286
|
+
}, []);
|
|
4287
|
+
const registerSuppressedQueuedPrompt = useCallback((agentId, promptText) => {
|
|
4288
|
+
const normalizedAgentId = agentId.trim().toLowerCase();
|
|
4289
|
+
const normalizedPrompt = promptText.trim();
|
|
4290
|
+
if (!normalizedAgentId || !normalizedPrompt) {
|
|
4291
|
+
return null;
|
|
4292
|
+
}
|
|
4293
|
+
pruneSuppressedQueuedPrompts();
|
|
4294
|
+
const token = crypto.randomUUID();
|
|
4295
|
+
suppressedQueuedPromptsRef.current = [
|
|
4296
|
+
...suppressedQueuedPromptsRef.current,
|
|
4297
|
+
{
|
|
4298
|
+
token,
|
|
4299
|
+
agentId: normalizedAgentId,
|
|
4300
|
+
prompt: normalizedPrompt,
|
|
4301
|
+
createdAt: Date.now(),
|
|
4302
|
+
},
|
|
4303
|
+
];
|
|
4304
|
+
return token;
|
|
4305
|
+
}, [pruneSuppressedQueuedPrompts]);
|
|
4306
|
+
const clearSuppressedQueuedPrompt = useCallback((token) => {
|
|
4307
|
+
if (!token) {
|
|
4308
|
+
return;
|
|
4309
|
+
}
|
|
4310
|
+
suppressedQueuedPromptsRef.current =
|
|
4311
|
+
suppressedQueuedPromptsRef.current.filter((entry) => entry.token !== token);
|
|
4312
|
+
}, []);
|
|
4313
|
+
const shouldSuppressQueuedPrompt = useCallback((queueEntry) => {
|
|
4314
|
+
const normalizedAgentId = queueEntry?.targetAgentId?.trim().toLowerCase();
|
|
4315
|
+
const normalizedPrompt = queueEntry?.prompt?.trim();
|
|
4316
|
+
if (!normalizedAgentId || !normalizedPrompt) {
|
|
4317
|
+
return false;
|
|
4318
|
+
}
|
|
4319
|
+
pruneSuppressedQueuedPrompts();
|
|
4320
|
+
const matchedEntry = suppressedQueuedPromptsRef.current.find((entry) => entry.agentId === normalizedAgentId &&
|
|
4321
|
+
entry.prompt === normalizedPrompt);
|
|
4322
|
+
if (!matchedEntry) {
|
|
4323
|
+
return false;
|
|
4324
|
+
}
|
|
4325
|
+
suppressedQueuedPromptsRef.current =
|
|
4326
|
+
suppressedQueuedPromptsRef.current.filter((entry) => entry.token !== matchedEntry.token);
|
|
4327
|
+
return true;
|
|
4328
|
+
}, [pruneSuppressedQueuedPrompts]);
|
|
4329
|
+
const cancelActiveInlineDialog = useCallback(() => {
|
|
4330
|
+
const activeDialog = activeInlineDialogRef.current;
|
|
4331
|
+
if (!activeDialog)
|
|
4332
|
+
return;
|
|
4333
|
+
try {
|
|
4334
|
+
activeDialog.onCancel();
|
|
4335
|
+
}
|
|
4336
|
+
finally {
|
|
4337
|
+
setActiveInlineDialog(null);
|
|
4338
|
+
}
|
|
4339
|
+
}, []);
|
|
2966
4340
|
const handleSubmit = async () => {
|
|
2967
4341
|
// Guard against double-submit and missing context
|
|
2968
4342
|
if (isSubmitting) {
|
|
@@ -3006,6 +4380,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3006
4380
|
setError("Agent not ready. Please try again.");
|
|
3007
4381
|
return;
|
|
3008
4382
|
}
|
|
4383
|
+
const hadQuestionnaireDialogOpen = activeInlineDialogRef.current?.request.dialogType === "questionnaire";
|
|
4384
|
+
const suppressedQueuedPromptToken = hadQuestionnaireDialogOpen && savedPrompt
|
|
4385
|
+
? registerSuppressedQueuedPrompt(agentId, savedPrompt)
|
|
4386
|
+
: null;
|
|
4387
|
+
// A new user prompt supersedes any active questionnaire/inline dialog.
|
|
4388
|
+
cancelActiveInlineDialog();
|
|
3009
4389
|
// Generate a temporary ID for optimistic UI - will be replaced by server ID
|
|
3010
4390
|
const tempMessageId = `temp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
3011
4391
|
try {
|
|
@@ -3092,26 +4472,24 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3092
4472
|
console.warn("[AgentTerminal] Failed to compute live context:", e);
|
|
3093
4473
|
}
|
|
3094
4474
|
// Add visible test IDs to context (only for Help agent)
|
|
3095
|
-
const
|
|
4475
|
+
const requestSettings = getPendingRequestSettings();
|
|
4476
|
+
const currentProfileName = requestSettings.profileName;
|
|
3096
4477
|
effectiveContext = addVisibleTestIdsToContext(effectiveContext, currentProfileName);
|
|
3097
4478
|
const request = {
|
|
3098
4479
|
agentId: agentId,
|
|
3099
4480
|
message: savedPrompt,
|
|
3100
4481
|
sessionId: editContext.sessionId,
|
|
3101
|
-
profileId:
|
|
4482
|
+
profileId: requestSettings.profileId,
|
|
3102
4483
|
profile: currentProfileName,
|
|
3103
|
-
model:
|
|
3104
|
-
mode: mode,
|
|
4484
|
+
model: requestSettings.modelId,
|
|
4485
|
+
mode: requestSettings.mode,
|
|
3105
4486
|
context: canonicalizeAgentMetadata(effectiveContext), // Use fresh live context when in live mode
|
|
3106
|
-
deterministic: deterministicFlags.deterministic,
|
|
3107
|
-
seed: deterministicFlags.seed,
|
|
3108
4487
|
};
|
|
3109
4488
|
console.log("[AgentTerminal] Calling startAgent API for agent:", agentId);
|
|
3110
4489
|
const response = await startAgent(request);
|
|
3111
4490
|
console.log("[AgentTerminal] startAgent response:", response);
|
|
3112
4491
|
// Check if prompt was queued (agent was already running)
|
|
3113
|
-
const wasQueued = response.message?.toLowerCase().includes("queued")
|
|
3114
|
-
response.status === "Queued";
|
|
4492
|
+
const wasQueued = response.message?.toLowerCase().includes("queued");
|
|
3115
4493
|
if (wasQueued) {
|
|
3116
4494
|
// Prompt was queued - show a brief notification but don't set waiting state
|
|
3117
4495
|
// The prompt will be processed when the agent becomes idle
|
|
@@ -3121,6 +4499,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3121
4499
|
isWaitingRef.current = false;
|
|
3122
4500
|
}
|
|
3123
4501
|
else {
|
|
4502
|
+
clearSuppressedQueuedPrompt(suppressedQueuedPromptToken);
|
|
3124
4503
|
// Normal submission - set waiting state to show dancing dots immediately
|
|
3125
4504
|
setIsWaitingForResponse(true);
|
|
3126
4505
|
isWaitingRef.current = true;
|
|
@@ -3135,11 +4514,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3135
4514
|
...prev.filter((p) => p !== savedPrompt).slice(0, 9),
|
|
3136
4515
|
]);
|
|
3137
4516
|
setCurrentHistoryIndex(-1);
|
|
4517
|
+
await onInteractionSubmitted?.();
|
|
3138
4518
|
// WebSocket connection is already active via subscription - no need for SSE
|
|
3139
4519
|
}
|
|
3140
4520
|
catch (err) {
|
|
3141
4521
|
console.error("[AgentTerminal] Failed to submit prompt:", err);
|
|
3142
|
-
|
|
4522
|
+
clearSuppressedQueuedPrompt(suppressedQueuedPromptToken);
|
|
4523
|
+
setError(getSubmitErrorMessage(err));
|
|
3143
4524
|
setIsWaitingForResponse(false);
|
|
3144
4525
|
isWaitingRef.current = false;
|
|
3145
4526
|
// Remove the optimistic user message on API error
|
|
@@ -3349,29 +4730,29 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3349
4730
|
console.warn("[AgentTerminal] Failed to compute live context for quick message:", e);
|
|
3350
4731
|
}
|
|
3351
4732
|
// Add visible test IDs to context (only for Help agent)
|
|
3352
|
-
const
|
|
4733
|
+
const requestSettings = getPendingRequestSettings();
|
|
4734
|
+
const currentProfileName = requestSettings.profileName;
|
|
3353
4735
|
effectiveContext = addVisibleTestIdsToContext(effectiveContext, currentProfileName);
|
|
3354
4736
|
const request = {
|
|
3355
4737
|
agentId: agent.id,
|
|
3356
4738
|
message: savedPrompt,
|
|
3357
4739
|
sessionId: editContext.sessionId,
|
|
3358
|
-
profileId:
|
|
4740
|
+
profileId: requestSettings.profileId,
|
|
3359
4741
|
profile: currentProfileName,
|
|
3360
|
-
model:
|
|
3361
|
-
mode: mode,
|
|
4742
|
+
model: requestSettings.modelId,
|
|
4743
|
+
mode: requestSettings.mode,
|
|
3362
4744
|
context: canonicalizeAgentMetadata(effectiveContext), // Use fresh live context when in live mode
|
|
3363
|
-
deterministic: deterministicFlags.deterministic,
|
|
3364
|
-
seed: deterministicFlags.seed,
|
|
3365
4745
|
};
|
|
3366
4746
|
console.log("[AgentTerminal] Calling startAgent API for quick message");
|
|
3367
4747
|
await startAgent(request);
|
|
3368
4748
|
// If user changed mode/model while the agent was new, persist them now
|
|
3369
4749
|
await persistPendingSettingsIfNeeded();
|
|
4750
|
+
await onInteractionSubmitted?.();
|
|
3370
4751
|
// WebSocket connection is already active via subscription - no need for SSE
|
|
3371
4752
|
}
|
|
3372
4753
|
catch (err) {
|
|
3373
4754
|
console.error("[AgentTerminal] Failed to submit quick message:", err);
|
|
3374
|
-
setError(
|
|
4755
|
+
setError(getSubmitErrorMessage(err));
|
|
3375
4756
|
setIsWaitingForResponse(false);
|
|
3376
4757
|
isWaitingRef.current = false;
|
|
3377
4758
|
// Remove the optimistic user message on API error
|
|
@@ -3549,66 +4930,90 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3549
4930
|
}
|
|
3550
4931
|
return context;
|
|
3551
4932
|
}, [collectVisibleTestIds]);
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
|
|
3563
|
-
|
|
3564
|
-
|
|
3565
|
-
|
|
3566
|
-
|
|
3567
|
-
|
|
3568
|
-
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
id: item.id,
|
|
3574
|
-
language: item.language,
|
|
3575
|
-
version: item.version,
|
|
3576
|
-
name: editContext?.item?.name,
|
|
3577
|
-
},
|
|
3578
|
-
}))
|
|
3579
|
-
: undefined,
|
|
3580
|
-
field: fieldsContext?.focusedField?.fieldId &&
|
|
3581
|
-
fieldsContext.focusedField?.item?.id
|
|
3582
|
-
? {
|
|
3583
|
-
fieldId: fieldsContext.focusedField.fieldId,
|
|
3584
|
-
fieldName: fieldsContext.focusedField.fieldName,
|
|
3585
|
-
item: {
|
|
3586
|
-
id: fieldsContext.focusedField.item.id,
|
|
3587
|
-
language: fieldsContext.focusedField.item.language ||
|
|
3588
|
-
editContext?.currentItemDescriptor?.language ||
|
|
3589
|
-
"en",
|
|
3590
|
-
version: fieldsContext.focusedField.item.version ??
|
|
3591
|
-
editContext?.currentItemDescriptor?.version ??
|
|
3592
|
-
0,
|
|
3593
|
-
name: editContext?.item?.name,
|
|
3594
|
-
},
|
|
4933
|
+
const buildPageContextItem = useCallback((item) => ({
|
|
4934
|
+
id: item.id,
|
|
4935
|
+
language: item.language,
|
|
4936
|
+
version: item.version,
|
|
4937
|
+
name: editContext?.item?.name,
|
|
4938
|
+
path: editContext?.item?.path,
|
|
4939
|
+
}), [editContext?.item?.name, editContext?.item?.path]);
|
|
4940
|
+
const buildComponentContext = useCallback((item) => {
|
|
4941
|
+
if (!editContext?.selection?.length)
|
|
4942
|
+
return undefined;
|
|
4943
|
+
return editContext.selection.map((componentId) => {
|
|
4944
|
+
let componentName;
|
|
4945
|
+
let componentType;
|
|
4946
|
+
let renderingItemId;
|
|
4947
|
+
if (editContext.page) {
|
|
4948
|
+
try {
|
|
4949
|
+
const component = getComponentById(componentId, editContext.page);
|
|
4950
|
+
componentName =
|
|
4951
|
+
component?.datasourceItem?.name || component?.name || undefined;
|
|
4952
|
+
componentType = component?.type || undefined;
|
|
4953
|
+
renderingItemId = component?.rendering?.id || undefined;
|
|
3595
4954
|
}
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
4955
|
+
catch { }
|
|
4956
|
+
}
|
|
4957
|
+
return {
|
|
4958
|
+
componentId,
|
|
4959
|
+
componentName,
|
|
4960
|
+
componentType,
|
|
4961
|
+
renderingItemId,
|
|
4962
|
+
pageItem: buildPageContextItem(item),
|
|
4963
|
+
};
|
|
4964
|
+
});
|
|
4965
|
+
}, [buildPageContextItem, editContext?.page, editContext?.selection]);
|
|
4966
|
+
const buildFieldContext = useCallback(() => {
|
|
4967
|
+
const focusedField = fieldsContext?.focusedField;
|
|
4968
|
+
if (!focusedField?.fieldId || !focusedField?.item?.id)
|
|
4969
|
+
return undefined;
|
|
4970
|
+
const fieldItem = focusedField.item;
|
|
4971
|
+
const currentItem = editContext?.currentItemDescriptor;
|
|
4972
|
+
let fieldItemName = fieldItem.id === currentItem?.id ? editContext?.item?.name : undefined;
|
|
4973
|
+
if (!fieldItemName && editContext?.page) {
|
|
4974
|
+
try {
|
|
4975
|
+
const component = getComponentById(fieldItem.id, editContext.page);
|
|
4976
|
+
fieldItemName =
|
|
4977
|
+
component?.datasourceItem?.name || component?.name || undefined;
|
|
4978
|
+
}
|
|
4979
|
+
catch { }
|
|
4980
|
+
}
|
|
4981
|
+
return {
|
|
4982
|
+
fieldId: focusedField.fieldId,
|
|
4983
|
+
fieldName: focusedField.fieldName,
|
|
4984
|
+
item: {
|
|
4985
|
+
id: fieldItem.id,
|
|
4986
|
+
language: fieldItem.language || currentItem?.language || "en",
|
|
4987
|
+
version: fieldItem.version ?? currentItem?.version ?? 0,
|
|
4988
|
+
name: fieldItem.name || fieldItemName,
|
|
4989
|
+
},
|
|
3602
4990
|
};
|
|
3603
4991
|
}, [
|
|
3604
4992
|
editContext?.currentItemDescriptor,
|
|
3605
|
-
editContext?.selection,
|
|
3606
|
-
editContext?.workspaceId,
|
|
3607
4993
|
editContext?.item?.name,
|
|
3608
|
-
editContext?.
|
|
3609
|
-
editContext?.openSidebars,
|
|
4994
|
+
editContext?.page,
|
|
3610
4995
|
fieldsContext?.focusedField,
|
|
3611
4996
|
]);
|
|
4997
|
+
const buildEditorContextPayload = useCallback((item) => ({
|
|
4998
|
+
items: item ? [buildPageContextItem(item)] : undefined,
|
|
4999
|
+
currentItemId: item?.id,
|
|
5000
|
+
components: item ? buildComponentContext(item) : undefined,
|
|
5001
|
+
field: buildFieldContext(),
|
|
5002
|
+
activeWorkspace: editContext?.workspaceId,
|
|
5003
|
+
hasPageLoaded: !!editContext?.getActiveSlotContext()?.primaryPageViewContext?.page,
|
|
5004
|
+
openSidebars: editContext?.openSidebars,
|
|
5005
|
+
}), [
|
|
5006
|
+
buildComponentContext,
|
|
5007
|
+
buildFieldContext,
|
|
5008
|
+
buildPageContextItem,
|
|
5009
|
+
editContext,
|
|
5010
|
+
]);
|
|
5011
|
+
// Helper function to build current context from editor state
|
|
5012
|
+
const buildCurrentContext = useCallback(() => {
|
|
5013
|
+
// Return context even without item - we want view info regardless
|
|
5014
|
+
const item = editContext?.currentItemDescriptor;
|
|
5015
|
+
return buildEditorContextPayload(item);
|
|
5016
|
+
}, [buildEditorContextPayload, editContext?.currentItemDescriptor]);
|
|
3612
5017
|
// Live context updates: watch for changes and update agent context when in "live" mode
|
|
3613
5018
|
const previousContextRef = useRef("");
|
|
3614
5019
|
useEffect(() => {
|
|
@@ -3708,6 +5113,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3708
5113
|
const handleRefreshContext = useCallback(async () => {
|
|
3709
5114
|
if (!agent?.id)
|
|
3710
5115
|
return;
|
|
5116
|
+
const normalizedAgentProfileId = agent?.profileId?.toLowerCase();
|
|
5117
|
+
const refreshProfile = activeProfile ||
|
|
5118
|
+
profiles.find((p) => p.id?.toLowerCase() === normalizedAgentProfileId);
|
|
5119
|
+
if (refreshProfile?.editorContextMode === "none") {
|
|
5120
|
+
editContext?.showInfoToast({
|
|
5121
|
+
summary: "This profile excludes editor context.",
|
|
5122
|
+
});
|
|
5123
|
+
return;
|
|
5124
|
+
}
|
|
3711
5125
|
try {
|
|
3712
5126
|
const currentCtx = buildCurrentContext();
|
|
3713
5127
|
if (!currentCtx) {
|
|
@@ -3748,7 +5162,73 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3748
5162
|
buildCurrentContext,
|
|
3749
5163
|
sanitizeAgentMetadata,
|
|
3750
5164
|
editContext,
|
|
5165
|
+
activeProfile,
|
|
5166
|
+
profiles,
|
|
3751
5167
|
]);
|
|
5168
|
+
const browserCaptureClaim = useMemo(() => getBrowserCaptureClaim(agentMetadata), [agentMetadata]);
|
|
5169
|
+
const isPendingBrowserCaptureWait = pendingBrowserCaptureDialogType === DIALOG_TYPES.CAPTURE_PAGE_DOM ||
|
|
5170
|
+
pendingBrowserCaptureDialogType === DIALOG_TYPES.CAPTURE_PAGE_SCREENSHOT;
|
|
5171
|
+
const currentSessionId = editContext?.sessionId?.trim() || "";
|
|
5172
|
+
const claimedSessionId = browserCaptureClaim?.sessionId?.trim() || "";
|
|
5173
|
+
const isClaimedByCurrentSession = !!currentSessionId &&
|
|
5174
|
+
!!claimedSessionId &&
|
|
5175
|
+
currentSessionId.toLowerCase() === claimedSessionId.toLowerCase();
|
|
5176
|
+
const isClaimedByAnotherBrowser = !!claimedSessionId && !isClaimedByCurrentSession;
|
|
5177
|
+
const handleClaimBrowser = useCallback(async (takeOver) => {
|
|
5178
|
+
if (!agent?.id || !editContext?.sessionId)
|
|
5179
|
+
return;
|
|
5180
|
+
setIsBrowserClaimMutationPending(true);
|
|
5181
|
+
try {
|
|
5182
|
+
const response = await claimAgentBrowser({
|
|
5183
|
+
agentId: agent.id,
|
|
5184
|
+
sessionId: editContext.sessionId,
|
|
5185
|
+
takeOver,
|
|
5186
|
+
terminalInstanceId: dialogTerminalInstanceIdRef.current,
|
|
5187
|
+
});
|
|
5188
|
+
setAgentMetadata((prev) => sanitizeAgentMetadata(setBrowserCaptureClaim(prev, response.claim || null)));
|
|
5189
|
+
}
|
|
5190
|
+
catch (err) {
|
|
5191
|
+
console.error("[AgentTerminal] Failed to claim browser:", err);
|
|
5192
|
+
editContext.showErrorToast(err);
|
|
5193
|
+
}
|
|
5194
|
+
finally {
|
|
5195
|
+
setIsBrowserClaimMutationPending(false);
|
|
5196
|
+
}
|
|
5197
|
+
}, [agent?.id, editContext, sanitizeAgentMetadata]);
|
|
5198
|
+
const handleReleaseBrowser = useCallback(async () => {
|
|
5199
|
+
if (!agent?.id || !editContext?.sessionId)
|
|
5200
|
+
return;
|
|
5201
|
+
setIsBrowserClaimMutationPending(true);
|
|
5202
|
+
try {
|
|
5203
|
+
const response = await releaseAgentBrowser({
|
|
5204
|
+
agentId: agent.id,
|
|
5205
|
+
sessionId: editContext.sessionId,
|
|
5206
|
+
terminalInstanceId: dialogTerminalInstanceIdRef.current,
|
|
5207
|
+
});
|
|
5208
|
+
setAgentMetadata((prev) => sanitizeAgentMetadata(setBrowserCaptureClaim(prev, response.claim || null)));
|
|
5209
|
+
}
|
|
5210
|
+
catch (err) {
|
|
5211
|
+
console.error("[AgentTerminal] Failed to release browser:", err);
|
|
5212
|
+
editContext.showErrorToast(err);
|
|
5213
|
+
}
|
|
5214
|
+
finally {
|
|
5215
|
+
setIsBrowserClaimMutationPending(false);
|
|
5216
|
+
}
|
|
5217
|
+
}, [agent?.id, editContext, sanitizeAgentMetadata]);
|
|
5218
|
+
useEffect(() => {
|
|
5219
|
+
return () => {
|
|
5220
|
+
if (!agent?.id || !editContext?.sessionId || !isClaimedByCurrentSession) {
|
|
5221
|
+
return;
|
|
5222
|
+
}
|
|
5223
|
+
void releaseAgentBrowser({
|
|
5224
|
+
agentId: agent.id,
|
|
5225
|
+
sessionId: editContext.sessionId,
|
|
5226
|
+
terminalInstanceId: dialogTerminalInstanceIdRef.current,
|
|
5227
|
+
}).catch((error) => {
|
|
5228
|
+
console.warn("[AgentTerminal] Failed to release browser on unmount:", error);
|
|
5229
|
+
});
|
|
5230
|
+
};
|
|
5231
|
+
}, [agent?.id, editContext?.sessionId, isClaimedByCurrentSession]);
|
|
3752
5232
|
// Stop current execution/stream safely
|
|
3753
5233
|
const handleStop = useCallback(async () => {
|
|
3754
5234
|
try {
|
|
@@ -3812,13 +5292,52 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3812
5292
|
if (effectiveCostLimit === undefined) {
|
|
3813
5293
|
effectiveCostLimit = undefined;
|
|
3814
5294
|
}
|
|
3815
|
-
// Calculate total token usage for cost display
|
|
3816
|
-
|
|
5295
|
+
// Calculate total token usage for cost display.
|
|
5296
|
+
// Message rows do not currently persist imageCost, so on refresh we fall back
|
|
5297
|
+
// to the persisted agent aggregate for that single field.
|
|
5298
|
+
const totalTokens = (() => {
|
|
5299
|
+
const totals = calculateTotalTokens(messages);
|
|
5300
|
+
return {
|
|
5301
|
+
...totals,
|
|
5302
|
+
imageCost: totals.imageCost || Number(agent?.totalImageCost) || 0,
|
|
5303
|
+
};
|
|
5304
|
+
})();
|
|
3817
5305
|
// Determine if the agent is actively executing (submitting, connecting, waiting, or streaming)
|
|
3818
5306
|
const isExecuting = isSubmitting ||
|
|
3819
5307
|
isConnecting ||
|
|
3820
5308
|
isWaitingForResponse ||
|
|
3821
5309
|
hasActiveStreaming();
|
|
5310
|
+
const runDiagnosticsSnapshot = useMemo(() => {
|
|
5311
|
+
const lastEvent = recentAgentRunEvents[recentAgentRunEvents.length - 1];
|
|
5312
|
+
return {
|
|
5313
|
+
agentId: currentAgentId,
|
|
5314
|
+
isSubmitting,
|
|
5315
|
+
isConnecting,
|
|
5316
|
+
isWaitingForResponse,
|
|
5317
|
+
isAgentThinking,
|
|
5318
|
+
isExecuting,
|
|
5319
|
+
hasActiveStreaming: hasActiveStreaming(),
|
|
5320
|
+
isSubscribed: normalizeDialogAgentId(subscribedAgentIdRef.current) ===
|
|
5321
|
+
normalizeDialogAgentId(currentAgentId),
|
|
5322
|
+
lastSeq: lastSeqRef.current,
|
|
5323
|
+
lastEventType: lastEvent?.type ?? null,
|
|
5324
|
+
lastEventAt: lastEvent?.timestamp ?? null,
|
|
5325
|
+
recentEvents: recentAgentRunEvents,
|
|
5326
|
+
};
|
|
5327
|
+
}, [
|
|
5328
|
+
currentAgentId,
|
|
5329
|
+
hasActiveStreaming,
|
|
5330
|
+
isAgentThinking,
|
|
5331
|
+
isConnecting,
|
|
5332
|
+
isExecuting,
|
|
5333
|
+
isSubmitting,
|
|
5334
|
+
isWaitingForResponse,
|
|
5335
|
+
recentAgentRunEvents,
|
|
5336
|
+
]);
|
|
5337
|
+
const showInitialThinkingSplash = messages.length === 0 &&
|
|
5338
|
+
!error &&
|
|
5339
|
+
hideGreeting &&
|
|
5340
|
+
(isSubmitting || isConnecting);
|
|
3822
5341
|
// Compute dots visibility: only show BEFORE any assistant message exists
|
|
3823
5342
|
// This prevents duplicate headers - the dots indicator has its own header,
|
|
3824
5343
|
// and we don't want to show a second header below existing messages
|
|
@@ -3833,13 +5352,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3833
5352
|
// The message with the pending approval will display its own UI for approval
|
|
3834
5353
|
if (allPendingApprovals.length > 0)
|
|
3835
5354
|
return false;
|
|
5355
|
+
// The hidden-greeting startup splash already renders its own bouncing dots.
|
|
5356
|
+
// Suppress the generic indicator so reopening/running terminals don't show two.
|
|
5357
|
+
if (showInitialThinkingSplash)
|
|
5358
|
+
return false;
|
|
3836
5359
|
// IMPORTANT: If the last message is an assistant message and we're still executing,
|
|
3837
5360
|
// the AiResponseMessage for that message will show its own activity indicator.
|
|
3838
5361
|
// We only want these global thinking dots if the last message was from the user
|
|
3839
5362
|
// or if no messages exist yet (waiting for initial response).
|
|
3840
5363
|
const lastMessage = messages.length > 0 ? messages[messages.length - 1] : null;
|
|
3841
|
-
if (isExecuting &&
|
|
5364
|
+
if (isExecuting &&
|
|
5365
|
+
lastMessage?.role === "assistant" &&
|
|
5366
|
+
!lastMessage.isCompleted) {
|
|
3842
5367
|
return false;
|
|
5368
|
+
}
|
|
3843
5369
|
// Existing check for uncompleted assistant messages
|
|
3844
5370
|
const hasActiveStreamingMessage = messages.some((m) => !m.isCompleted && m.role === "assistant");
|
|
3845
5371
|
if (hasActiveStreamingMessage)
|
|
@@ -3855,20 +5381,22 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3855
5381
|
messages,
|
|
3856
5382
|
activeInlineDialog,
|
|
3857
5383
|
allPendingApprovals,
|
|
5384
|
+
showInitialThinkingSplash,
|
|
3858
5385
|
]);
|
|
3859
5386
|
// Move useMemo hook before early return to comply with Rules of Hooks
|
|
3860
|
-
const
|
|
5387
|
+
const resolvedEditorContextMode = React.useMemo(() => {
|
|
3861
5388
|
try {
|
|
3862
5389
|
const normalizedAgentProfileId = agent?.profileId?.toLowerCase();
|
|
3863
5390
|
const profile = activeProfile ||
|
|
3864
5391
|
profiles.find((p) => p.id?.toLowerCase() === normalizedAgentProfileId);
|
|
3865
|
-
|
|
3866
|
-
return mode === "live";
|
|
5392
|
+
return profile?.editorContextMode ?? null;
|
|
3867
5393
|
}
|
|
3868
5394
|
catch {
|
|
3869
|
-
return
|
|
5395
|
+
return null;
|
|
3870
5396
|
}
|
|
3871
5397
|
}, [activeProfile, profiles, agent?.profileId]);
|
|
5398
|
+
const isLiveEditorContextMode = resolvedEditorContextMode === "live";
|
|
5399
|
+
const omitsEditorContext = resolvedEditorContextMode === "none";
|
|
3872
5400
|
// Get parent agent ID from agent or agentStub (handle both camelCase and PascalCase)
|
|
3873
5401
|
const parentAgentId = agent?.parentAgentId ||
|
|
3874
5402
|
agent?.ParentAgentId ||
|
|
@@ -3882,10 +5410,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3882
5410
|
detail: { agentId: parentAgentId },
|
|
3883
5411
|
}));
|
|
3884
5412
|
}, [parentAgentId]);
|
|
3885
|
-
|
|
3886
|
-
|
|
3887
|
-
}
|
|
3888
|
-
const renderContextInfoBar = () => (_jsx(ContextInfoBar, { agent: agent, agentMetadata: agentMetadata, setAgentMetadata: setAgentMetadata, setAgent: setAgent, resolvedPageName: resolvedPageName, resolvedComponentName: resolvedComponentName, resolvedFieldName: resolvedFieldName, isLiveEditorContextMode: isLiveEditorContextMode, activeProfile: activeProfile, onRefreshContext: handleRefreshContext }));
|
|
5413
|
+
const loadingContent = isLoading && !activeInlineDialog ? (_jsx("div", { className: "flex h-full items-center justify-center", children: _jsxs("div", { className: "flex items-center gap-2 text-[11px] text-gray-500", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin", strokeWidth: 1 }), "Loading agent..."] }) })) : null;
|
|
5414
|
+
const renderContextInfoBar = () => (_jsx(ContextInfoBar, { agent: agent, agentMetadata: agentMetadata, setAgentMetadata: setAgentMetadata, setAgent: setAgent, resolvedPageName: resolvedPageName, resolvedComponentName: resolvedComponentName, resolvedFieldName: resolvedFieldName, isLiveEditorContextMode: isLiveEditorContextMode, omitEditorContext: omitsEditorContext, onRefreshContext: handleRefreshContext }));
|
|
3889
5415
|
const renderCostLimitBanner = () => {
|
|
3890
5416
|
if (!costLimitExceeded)
|
|
3891
5417
|
return null;
|
|
@@ -3896,9 +5422,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3896
5422
|
try {
|
|
3897
5423
|
// Extend cost limit - backend will automatically resume the agent
|
|
3898
5424
|
const result = await updateAgentCostLimit(agent.id, "extend");
|
|
3899
|
-
// Update the agent's cost limit
|
|
5425
|
+
// Update the agent's cost limit and clear the costLimitReached
|
|
5426
|
+
// status in local state so the useEffect watcher doesn't
|
|
5427
|
+
// immediately re-show the banner before the backend status
|
|
5428
|
+
// update arrives.
|
|
3900
5429
|
if (result.success && result.costLimit !== undefined) {
|
|
3901
|
-
setAgent((prev) => prev
|
|
5430
|
+
setAgent((prev) => prev
|
|
5431
|
+
? {
|
|
5432
|
+
...prev,
|
|
5433
|
+
costLimit: result.costLimit,
|
|
5434
|
+
status: prev.status === "costLimitReached"
|
|
5435
|
+
? "running"
|
|
5436
|
+
: prev.status,
|
|
5437
|
+
}
|
|
5438
|
+
: prev);
|
|
3902
5439
|
}
|
|
3903
5440
|
// Clear the banner and set waiting state
|
|
3904
5441
|
// Agent will resume automatically via backend's ResumeAgentAsync
|
|
@@ -3918,13 +5455,245 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3918
5455
|
};
|
|
3919
5456
|
const renderErrorBanner = () => {
|
|
3920
5457
|
const currentAgent = agent || agentStub;
|
|
3921
|
-
const isErrorStatus = currentAgent?.status === "error"
|
|
3922
|
-
const
|
|
3923
|
-
if (!
|
|
5458
|
+
const isErrorStatus = currentAgent?.status === "error";
|
|
5459
|
+
const rawErrorMessage = (isErrorStatus ? currentAgent?.statusMessage : null) || error;
|
|
5460
|
+
if (!rawErrorMessage)
|
|
3924
5461
|
return null;
|
|
3925
|
-
|
|
5462
|
+
// Clean the error message (statusMessage from DB may contain raw JSON)
|
|
5463
|
+
const errorMessage = toUserFacingAgentErrorMessage(rawErrorMessage) || rawErrorMessage;
|
|
5464
|
+
return (_jsx("div", { className: "m-3 rounded border border-red-300 bg-red-50 p-3 text-[11px] text-red-900", "data-testid": "agent-error-banner", children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx(AlertCircle, { className: "mt-0.5 h-4 w-4 shrink-0 text-red-500", strokeWidth: 1 }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "mb-1 font-semibold", children: "Agent Error" }), _jsx("div", { className: "text-red-800", children: errorMessage })] })] }) }));
|
|
3926
5465
|
};
|
|
3927
|
-
|
|
5466
|
+
const renderBrowserClaimBanner = (variant = "inline") => {
|
|
5467
|
+
if (!agent?.id || !editContext?.sessionId)
|
|
5468
|
+
return null;
|
|
5469
|
+
if (!isClaimedByCurrentSession && !isClaimedByAnotherBrowser) {
|
|
5470
|
+
return null;
|
|
5471
|
+
}
|
|
5472
|
+
if (isPendingBrowserCaptureWait) {
|
|
5473
|
+
return null;
|
|
5474
|
+
}
|
|
5475
|
+
const label = isClaimedByCurrentSession
|
|
5476
|
+
? "Attached to this browser"
|
|
5477
|
+
: isClaimedByAnotherBrowser
|
|
5478
|
+
? "Attached in another browser"
|
|
5479
|
+
: "No browser attached";
|
|
5480
|
+
const description = isClaimedByCurrentSession
|
|
5481
|
+
? "This browser will handle page screenshot and DOM capture requests for the agent."
|
|
5482
|
+
: isClaimedByAnotherBrowser
|
|
5483
|
+
? "Capture requests will stay with the other browser until you take over control here."
|
|
5484
|
+
: "A page capture request is waiting for a browser attachment. Attach this browser to continue.";
|
|
5485
|
+
const bannerClassName = cn("rounded border border-blue-200 bg-blue-50 p-3 text-[11px] text-blue-900", variant === "fixed" ? "mx-3 mt-3 mb-2 shrink-0" : "m-3", isClaimedByCurrentSession && "border-blue-300");
|
|
5486
|
+
return (_jsx("div", { className: bannerClassName, children: _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "font-semibold", children: label }), _jsx("div", { className: "mt-1 text-blue-800", children: description })] }), _jsx("div", { className: "flex shrink-0 items-center gap-2", children: isClaimedByCurrentSession ? (_jsx("button", { type: "button", className: "rounded border border-blue-300 bg-white px-2 py-1 text-[11px] font-medium text-blue-900 disabled:cursor-not-allowed disabled:opacity-60", disabled: isBrowserClaimMutationPending, onClick: () => {
|
|
5487
|
+
void handleReleaseBrowser();
|
|
5488
|
+
}, children: "Release" })) : (_jsx("button", { type: "button", className: "rounded border border-blue-300 bg-white px-2 py-1 text-[11px] font-medium text-blue-900 disabled:cursor-not-allowed disabled:opacity-60", disabled: isBrowserClaimMutationPending, onClick: () => {
|
|
5489
|
+
void handleClaimBrowser(isClaimedByAnotherBrowser);
|
|
5490
|
+
}, children: isClaimedByAnotherBrowser
|
|
5491
|
+
? "Take over browser control"
|
|
5492
|
+
: "Attach to this browser" })) })] }) }));
|
|
5493
|
+
};
|
|
5494
|
+
const fixedBrowserClaimBanner = renderBrowserClaimBanner("fixed");
|
|
5495
|
+
const inlineBrowserClaimBanner = null;
|
|
5496
|
+
const browserCaptureInlinePrompt = isPendingBrowserCaptureWait && !isClaimedByCurrentSession
|
|
5497
|
+
? {
|
|
5498
|
+
toolNames: [
|
|
5499
|
+
"capture-page-screenshot",
|
|
5500
|
+
"capture-parhelia-ui-screenshot",
|
|
5501
|
+
"capture-page-dom",
|
|
5502
|
+
],
|
|
5503
|
+
label: isClaimedByAnotherBrowser
|
|
5504
|
+
? "Attached in another browser"
|
|
5505
|
+
: "No browser attached",
|
|
5506
|
+
description: isClaimedByAnotherBrowser
|
|
5507
|
+
? "This capture request is waiting in another browser. Take over browser control here to continue."
|
|
5508
|
+
: "This capture request is waiting for a browser attachment. Attach this browser to continue.",
|
|
5509
|
+
actionLabel: isClaimedByAnotherBrowser
|
|
5510
|
+
? "Take over browser control"
|
|
5511
|
+
: "Attach to this browser",
|
|
5512
|
+
isPending: isBrowserClaimMutationPending,
|
|
5513
|
+
onAction: () => {
|
|
5514
|
+
void handleClaimBrowser(isClaimedByAnotherBrowser);
|
|
5515
|
+
},
|
|
5516
|
+
}
|
|
5517
|
+
: null;
|
|
5518
|
+
useEffect(() => {
|
|
5519
|
+
if (agent?.status !== "waitingForInput") {
|
|
5520
|
+
setPendingBrowserCaptureDialogType(null);
|
|
5521
|
+
}
|
|
5522
|
+
}, [agent?.status]);
|
|
5523
|
+
const renderInlineDialogContent = () => {
|
|
5524
|
+
if (!activeInlineDialog)
|
|
5525
|
+
return null;
|
|
5526
|
+
if (activeInlineDialog.request.dialogType === "questionnaire") {
|
|
5527
|
+
return (_jsx("div", { ref: inlineDialogContainerRef, className: cn("agent-inline-dialog min-h-0 overflow-hidden", displayMode === "full" && "h-full"), children: _jsx(QuestionnaireInline, { requestId: activeInlineDialog.request.callbackId, agentId: activeInlineDialog.request.agentId, title: activeInlineDialog.request.title, description: activeInlineDialog.request.description, parameters: activeInlineDialog.request.parameters, footerActions: questionnaireFooterActions, onClose: (result) => {
|
|
5528
|
+
activeInlineDialog.onComplete(result);
|
|
5529
|
+
setActiveInlineDialog(null);
|
|
5530
|
+
void onInteractionSubmitted?.();
|
|
5531
|
+
}, onCancel: () => {
|
|
5532
|
+
activeInlineDialog.onCancel();
|
|
5533
|
+
setActiveInlineDialog(null);
|
|
5534
|
+
} }) }));
|
|
5535
|
+
}
|
|
5536
|
+
const dialogRegistration = editContext?.configuration?.editor?.agentDialogs?.find((d) => d.dialogType === activeInlineDialog.request.dialogType);
|
|
5537
|
+
if (dialogRegistration) {
|
|
5538
|
+
const DialogComponent = dialogRegistration.component;
|
|
5539
|
+
return (_jsx("div", { className: "agent-inline-dialog", children: _jsx(DialogComponent, { title: activeInlineDialog.request.title, description: activeInlineDialog.request.description, parameters: activeInlineDialog.request.parameters, onClose: (result) => {
|
|
5540
|
+
activeInlineDialog.onComplete(result);
|
|
5541
|
+
setActiveInlineDialog(null);
|
|
5542
|
+
if (activeInlineDialog.request.dialogType === "questionnaire") {
|
|
5543
|
+
void onInteractionSubmitted?.();
|
|
5544
|
+
}
|
|
5545
|
+
}, onCancel: () => {
|
|
5546
|
+
activeInlineDialog.onCancel();
|
|
5547
|
+
setActiveInlineDialog(null);
|
|
5548
|
+
} }) }));
|
|
5549
|
+
}
|
|
5550
|
+
return (_jsx("div", { className: "agent-inline-dialog", children: _jsxs("div", { className: "p-4 text-sm text-red-500", children: ["Unknown dialog type: ", activeInlineDialog.request.dialogType] }) }));
|
|
5551
|
+
};
|
|
5552
|
+
const latestSummaryAssistantGroup = useMemo(() => {
|
|
5553
|
+
if (hideSummaryMessages)
|
|
5554
|
+
return null;
|
|
5555
|
+
const groups = groupConsecutiveMessages(messages);
|
|
5556
|
+
for (let groupIndex = groups.length - 1; groupIndex >= 0; groupIndex -= 1) {
|
|
5557
|
+
const group = groups[groupIndex];
|
|
5558
|
+
if (!group || group.type !== "assistant-group")
|
|
5559
|
+
continue;
|
|
5560
|
+
const filteredMessages = group.messages.filter((msg) => {
|
|
5561
|
+
const content = msg.content || "";
|
|
5562
|
+
return !content.startsWith("⚠️") || !content.includes("Cost limit");
|
|
5563
|
+
});
|
|
5564
|
+
if (filteredMessages.length === 0)
|
|
5565
|
+
continue;
|
|
5566
|
+
return {
|
|
5567
|
+
messages: filteredMessages,
|
|
5568
|
+
isLastGroup: groupIndex === groups.length - 1,
|
|
5569
|
+
};
|
|
5570
|
+
}
|
|
5571
|
+
return null;
|
|
5572
|
+
}, [messages, hideSummaryMessages]);
|
|
5573
|
+
const summaryModeContent = displayMode === "summary"
|
|
5574
|
+
? (() => {
|
|
5575
|
+
const inlineDialog = renderInlineDialogContent();
|
|
5576
|
+
const summaryMessages = latestSummaryAssistantGroup
|
|
5577
|
+
? convertAgentMessagesToAiFormat(latestSummaryAssistantGroup.messages)
|
|
5578
|
+
: [];
|
|
5579
|
+
const summaryOperations = latestSummaryAssistantGroup
|
|
5580
|
+
? getOperationsForMessageGroup(summaryMessages, agentOperations)
|
|
5581
|
+
: [];
|
|
5582
|
+
return (_jsxs("div", { className: `flex h-full min-h-0 flex-col ${className || ""}`, children: [fixedBrowserClaimBanner, _jsxs("div", { ref: messagesContainerRef, className: "flex-1 overflow-y-auto", onScroll: handleScroll, children: [error &&
|
|
5583
|
+
!isAgentErrorStatusValue((agent || agentStub)?.status) && (_jsx("div", { className: "m-4 rounded-lg border-l-4 border-red-500 bg-red-50 p-3 select-text", children: _jsxs("div", { className: "flex items-start", children: [_jsx(AlertCircle, { className: "mt-0.5 h-5 w-5 text-red-400", strokeWidth: 1 }), _jsxs("div", { className: "ml-3", children: [_jsx("p", { className: "text-[11px] font-medium text-red-800", children: "Error" }), _jsx("p", { className: "mt-1 text-[11px] text-red-700", children: error })] })] }) })), showInitialThinkingSplash && (_jsx("div", { className: "flex h-full items-center justify-center p-8", children: _jsxs("div", { className: "flex flex-col items-center gap-4", children: [activeProfile?.svgIcon ? (_jsx("div", { className: "flex h-16 w-16 items-center justify-center text-gray-400 [&>svg]:h-full [&>svg]:w-full", dangerouslySetInnerHTML: {
|
|
5584
|
+
__html: sanitizeSvg(activeProfile.svgIcon),
|
|
5585
|
+
} })) : (_jsx(SecretAgentIcon, { size: 64, strokeWidth: 1, className: "text-gray-400" })), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.3s]" }), _jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.15s]" }), _jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400" })] })] }) })), inlineBrowserClaimBanner, renderErrorBanner(), inlineDialog ? (inlineDialog) : latestSummaryAssistantGroup ? (_jsx("div", { className: "space-y-0 divide-y divide-gray-100 select-text", children: _jsx(AiResponseMessage, { messages: summaryMessages, finished: !latestSummaryAssistantGroup.isLastGroup || !isExecuting, editOperations: summaryOperations, defaultCollapseJson: defaultCollapseJson, profileSvgIcon: activeProfile?.svgIcon, agentId: agent?.id || agentStub.id, agentName: activeProfile?.agentName ||
|
|
5586
|
+
activeProfile?.displayTitle ||
|
|
5587
|
+
activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, browserCaptureInlinePrompt: browserCaptureInlinePrompt, onQuickAction: (action) => {
|
|
5588
|
+
const text = (action.prompt ||
|
|
5589
|
+
action.value ||
|
|
5590
|
+
action.label ||
|
|
5591
|
+
"").trim();
|
|
5592
|
+
if (!text)
|
|
5593
|
+
return;
|
|
5594
|
+
if (isExecuting) {
|
|
5595
|
+
try {
|
|
5596
|
+
handleStop();
|
|
5597
|
+
}
|
|
5598
|
+
catch { }
|
|
5599
|
+
}
|
|
5600
|
+
sendQuickMessage(text);
|
|
5601
|
+
} }) })) : hideSummaryWaitingPlaceholder ? (_jsx("div", { className: `flex h-full items-center justify-center ${compact ? "min-h-[100px] p-3" : "min-h-[220px] p-6"}`, children: summaryPlaceholderActions ? (_jsx("div", { className: "flex justify-center", children: summaryPlaceholderActions })) : null })) : (_jsx("div", { className: `flex h-full items-center justify-center ${compact ? "min-h-[100px] p-3" : "min-h-[220px] p-6"}`, children: _jsxs("div", { className: `max-w-md rounded-xl border border-slate-200 bg-slate-50 text-center text-slate-600 ${compact ? "px-3 py-2 text-xs" : "px-5 py-4 text-sm"}`, children: [_jsx("div", { children: shouldShowThinkingDots || isExecuting
|
|
5602
|
+
? "The agent is still working. The next update will appear here automatically."
|
|
5603
|
+
: agent?.statusMessage ||
|
|
5604
|
+
summaryPlaceholderMessage ||
|
|
5605
|
+
"Waiting for the next agent update." }), summaryPlaceholderActions ? (_jsx("div", { className: `flex justify-center ${compact ? "mt-2" : "mt-3"}`, children: summaryPlaceholderActions })) : null] }) })), displayMode !== "summary" &&
|
|
5606
|
+
shouldShowThinkingDots &&
|
|
5607
|
+
!inlineDialog &&
|
|
5608
|
+
!latestSummaryAssistantGroup && (_jsxs("div", { className: "flex gap-3 px-4 py-3", "data-testid": "agent-thinking-dots", children: [_jsx("div", { className: "shrink-0", children: activeProfile?.svgIcon ? (_jsx("div", { className: "text-gray-2 flex h-6 w-6 items-center justify-center [&>svg]:h-full [&>svg]:w-full", dangerouslySetInnerHTML: {
|
|
5609
|
+
__html: sanitizeSvg(activeProfile.svgIcon),
|
|
5610
|
+
} })) : (_jsx(SecretAgentIcon, { size: 20, strokeWidth: 1, className: "text-gray-2" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [_jsx("span", { className: "text-dark text-xs font-medium", children: activeProfile?.agentName ||
|
|
5611
|
+
activeProfile?.displayTitle ||
|
|
5612
|
+
activeProfile?.name ||
|
|
5613
|
+
"Agent" }), _jsx("span", { className: "text-xs text-gray-400", children: formatTime(new Date()) })] }), _jsxs("div", { className: "flex items-center gap-1 pt-2", children: [_jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.3s]" }), _jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.15s]" }), _jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400" })] })] })] })), _jsx("div", { ref: messagesEndRef })] }), showSummaryInput && !activeInlineDialog ? (_jsxs("div", { className: cn("border-t border-gray-200 p-4", simpleMode && "pb-10"), children: [activePlaceholderInput ? (_jsx(PlaceholderInput, { ref: placeholderInputRef, text: activePlaceholderInput.text, showButtons: hideBottomControls, buttonsClassName: hideBottomControls ? "justify-end" : "", onFilledChange: setAllPlaceholdersFilled, onComplete: (filledText) => {
|
|
5614
|
+
setActivePlaceholderInput(null);
|
|
5615
|
+
setAllPlaceholdersFilled(false);
|
|
5616
|
+
if (activePlaceholderInput.behavior === "compose" &&
|
|
5617
|
+
!hideBottomControls) {
|
|
5618
|
+
setPrompt(filledText);
|
|
5619
|
+
setInputPlaceholder("Review and edit, then press Enter to send");
|
|
5620
|
+
if (textareaRef.current) {
|
|
5621
|
+
try {
|
|
5622
|
+
textareaRef.current.focus();
|
|
5623
|
+
const v = textareaRef.current.value || "";
|
|
5624
|
+
textareaRef.current.selectionStart = v.length;
|
|
5625
|
+
textareaRef.current.selectionEnd = v.length;
|
|
5626
|
+
}
|
|
5627
|
+
catch { }
|
|
5628
|
+
}
|
|
5629
|
+
}
|
|
5630
|
+
else {
|
|
5631
|
+
if (isExecuting) {
|
|
5632
|
+
try {
|
|
5633
|
+
handleStop();
|
|
5634
|
+
}
|
|
5635
|
+
catch { }
|
|
5636
|
+
}
|
|
5637
|
+
sendQuickMessage(filledText);
|
|
5638
|
+
}
|
|
5639
|
+
}, onCancel: () => {
|
|
5640
|
+
setActivePlaceholderInput(null);
|
|
5641
|
+
setAllPlaceholdersFilled(false);
|
|
5642
|
+
} })) : prompt && /\{\{[^{}]+\}\}|<<[^<>]+>>/.test(prompt) ? (_jsx(PlaceholderInput, { ref: promptPlaceholderInputRef, text: prompt, showButtons: hideBottomControls, buttonsClassName: hideBottomControls ? "justify-end" : "", onFilledChange: setAllPlaceholdersFilled, onComplete: (filledText) => {
|
|
5643
|
+
setPrompt(filledText);
|
|
5644
|
+
setAllPlaceholdersFilled(false);
|
|
5645
|
+
if (filledText.trim()) {
|
|
5646
|
+
if (isExecuting) {
|
|
5647
|
+
try {
|
|
5648
|
+
handleStop();
|
|
5649
|
+
}
|
|
5650
|
+
catch { }
|
|
5651
|
+
}
|
|
5652
|
+
sendQuickMessage(filledText);
|
|
5653
|
+
}
|
|
5654
|
+
}, onCancel: () => {
|
|
5655
|
+
setPrompt("");
|
|
5656
|
+
setAllPlaceholdersFilled(false);
|
|
5657
|
+
setInputPlaceholder("Type your message... (Enter to send, Shift+Enter or Ctrl+Enter for new line)");
|
|
5658
|
+
} })) : (_jsx("div", { className: "flex items-stretch gap-2", children: _jsx(Textarea, { ref: textareaRef, style: { viewTransitionName: "assistant-chat-input" }, value: prompt, onChange: (e) => {
|
|
5659
|
+
setPrompt(e.target.value);
|
|
5660
|
+
if (!/\{\{[^{}]+\}\}|<<[^<>]+>>/.test(e.target.value)) {
|
|
5661
|
+
setAllPlaceholdersFilled(false);
|
|
5662
|
+
}
|
|
5663
|
+
if (currentHistoryIndex !== -1) {
|
|
5664
|
+
setCurrentHistoryIndex(-1);
|
|
5665
|
+
}
|
|
5666
|
+
}, onKeyDown: handleKeyPress, onPaste: handlePaste, onFocus: () => {
|
|
5667
|
+
shouldMaintainFocusRef.current = true;
|
|
5668
|
+
}, onBlur: () => {
|
|
5669
|
+
shouldMaintainFocusRef.current = false;
|
|
5670
|
+
}, placeholder: inputPlaceholder, className: "max-h-[250px] min-h-[80px] flex-1 resize-y overflow-y-auto text-[12px] lg:max-h-[450px]", "data-testid": "agent-terminal-prompt", disabled: isSubmitting }) })), (() => {
|
|
5671
|
+
const isInPlaceholderMode = activePlaceholderInput ||
|
|
5672
|
+
(prompt && /\{\{[^{}]+\}\}|<<[^<>]+>>/.test(prompt));
|
|
5673
|
+
const placeholderShowsOwnButtons = hideBottomControls && isInPlaceholderMode;
|
|
5674
|
+
if (placeholderShowsOwnButtons)
|
|
5675
|
+
return null;
|
|
5676
|
+
return (_jsxs("div", { className: cn("mt-2 flex items-stretch gap-2", hideBottomControls ||
|
|
5677
|
+
simpleMode ||
|
|
5678
|
+
isInPlaceholderMode
|
|
5679
|
+
? "justify-end"
|
|
5680
|
+
: "justify-between"), children: [!hideBottomControls &&
|
|
5681
|
+
!simpleMode &&
|
|
5682
|
+
!isInPlaceholderMode ? (_jsx("div", { className: "flex-1" })) : null, _jsx(Button, { type: "button", size: "sm", onClick: () => {
|
|
5683
|
+
if (isExecuting) {
|
|
5684
|
+
handleStop();
|
|
5685
|
+
}
|
|
5686
|
+
else {
|
|
5687
|
+
handleSubmit();
|
|
5688
|
+
}
|
|
5689
|
+
}, disabled: !isExecuting &&
|
|
5690
|
+
!activePlaceholderInput &&
|
|
5691
|
+
(!prompt.trim() || isSubmitting), "data-testid": "agent-send-stop-button", children: isExecuting ? "Stop" : "Send" })] }));
|
|
5692
|
+
})()] })) : null] }));
|
|
5693
|
+
})()
|
|
5694
|
+
: null;
|
|
5695
|
+
const fullModeInlineDialog = displayMode === "full" ? renderInlineDialogContent() : null;
|
|
5696
|
+
const fullModeUpperContent = (_jsxs("div", { className: "flex h-full min-h-0 flex-1 flex-col", children: [fixedBrowserClaimBanner, _jsxs("div", { ref: messagesContainerRef, className: "flex-1 overflow-y-auto", onScroll: handleScroll, children: [messages.length === 0 && !error && !hideGreeting && (_jsx("div", { className: "flex h-full items-center justify-center", children: !activeProfile ? (_jsx(Loader2, { className: "mx-auto h-8 w-8 animate-spin text-gray-400" })) : (_jsx(AgentGreeting, { profile: activeProfile, onPromptClick: (p) => {
|
|
3928
5697
|
setPrompt(p);
|
|
3929
5698
|
// Use setTimeout to ensure state is updated before submission
|
|
3930
5699
|
setTimeout(() => {
|
|
@@ -3937,12 +5706,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3937
5706
|
handleSubmit();
|
|
3938
5707
|
}
|
|
3939
5708
|
}, 0);
|
|
3940
|
-
} })) })),
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
(isSubmitting || isConnecting) && (_jsx("div", { className: "flex h-full items-center justify-center p-8", children: _jsxs("div", { className: "flex flex-col items-center gap-4", children: [activeProfile?.svgIcon ? (_jsx("div", { className: "flex h-16 w-16 items-center justify-center text-gray-400 [&>svg]:h-full [&>svg]:w-full", dangerouslySetInnerHTML: {
|
|
3944
|
-
__html: activeProfile.svgIcon,
|
|
3945
|
-
} })) : (_jsx(SecretAgentIcon, { size: 64, strokeWidth: 1, className: "text-gray-400" })), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.3s]" }), _jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.15s]" }), _jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400" })] })] }) })), renderErrorBanner(), _jsxs("div", { className: "space-y-0 divide-y divide-gray-100 select-text", children: [(() => {
|
|
5709
|
+
} })) })), showInitialThinkingSplash && (_jsx("div", { className: "flex h-full items-center justify-center p-8", children: _jsxs("div", { className: "flex flex-col items-center gap-4", children: [activeProfile?.svgIcon ? (_jsx("div", { className: "flex h-16 w-16 items-center justify-center text-gray-400 [&>svg]:h-full [&>svg]:w-full", dangerouslySetInnerHTML: {
|
|
5710
|
+
__html: sanitizeSvg(activeProfile.svgIcon),
|
|
5711
|
+
} })) : (_jsx(SecretAgentIcon, { size: 64, strokeWidth: 1, className: "text-gray-400" })), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.3s]" }), _jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.15s]" }), _jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400" })] })] }) })), inlineBrowserClaimBanner, renderErrorBanner(), _jsxs("div", { className: "space-y-0 divide-y divide-gray-100 select-text", children: [(() => {
|
|
3946
5712
|
const groups = groupConsecutiveMessages(messages);
|
|
3947
5713
|
return groups.map((group, groupIndex) => {
|
|
3948
5714
|
const isLastGroup = groupIndex === groups.length - 1;
|
|
@@ -3950,6 +5716,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3950
5716
|
// Render user message
|
|
3951
5717
|
return (_jsx(UserMessage, { message: group.messages[0] }, groupIndex));
|
|
3952
5718
|
}
|
|
5719
|
+
else if (group.type === "heartbeat" && group.messages[0]) {
|
|
5720
|
+
return (_jsx(HeartbeatMessage, { message: group.messages[0] }, group.messages[0].id || groupIndex));
|
|
5721
|
+
}
|
|
3953
5722
|
else {
|
|
3954
5723
|
// Render bundled assistant messages
|
|
3955
5724
|
// Check if this group contains any streaming message
|
|
@@ -3966,9 +5735,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3966
5735
|
}
|
|
3967
5736
|
const convertedMessages = convertAgentMessagesToAiFormat(filteredMessages);
|
|
3968
5737
|
const operationsForGroup = getOperationsForMessageGroup(convertedMessages, agentOperations);
|
|
3969
|
-
return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isLastGroup || !isExecuting, editOperations: operationsForGroup,
|
|
5738
|
+
return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isLastGroup || !isExecuting, editOperations: operationsForGroup, defaultCollapseJson: defaultCollapseJson, profileSvgIcon: activeProfile?.svgIcon, agentId: agent?.id || agentStub.id, agentName: activeProfile?.agentName ||
|
|
3970
5739
|
activeProfile?.displayTitle ||
|
|
3971
|
-
activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, onQuickAction: (action) => {
|
|
5740
|
+
activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, browserCaptureInlinePrompt: browserCaptureInlinePrompt, onQuickAction: (action) => {
|
|
3972
5741
|
const text = (action.prompt ||
|
|
3973
5742
|
action.value ||
|
|
3974
5743
|
action.label ||
|
|
@@ -4012,13 +5781,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4012
5781
|
}
|
|
4013
5782
|
});
|
|
4014
5783
|
})(), shouldShowThinkingDots && (_jsxs("div", { className: "flex gap-3 px-4 py-3", "data-testid": "agent-thinking-dots", children: [_jsx("div", { className: "shrink-0", children: activeProfile?.svgIcon ? (_jsx("div", { className: "text-gray-2 flex h-6 w-6 items-center justify-center [&>svg]:h-full [&>svg]:w-full", dangerouslySetInnerHTML: {
|
|
4015
|
-
__html: activeProfile.svgIcon,
|
|
5784
|
+
__html: sanitizeSvg(activeProfile.svgIcon),
|
|
4016
5785
|
} })) : (_jsx(SecretAgentIcon, { size: 20, strokeWidth: 1, className: "text-gray-2" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [_jsx("span", { className: "text-dark text-xs font-medium", children: activeProfile?.agentName ||
|
|
4017
5786
|
activeProfile?.displayTitle ||
|
|
4018
5787
|
activeProfile?.name ||
|
|
4019
5788
|
"Agent" }), _jsx("span", { className: "text-xs text-gray-400", children: formatTime(new Date()) })] }), _jsxs("div", { className: "flex items-center gap-1 pt-2", children: [_jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.3s]" }), _jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.15s]" }), _jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400" })] })] })] }))] }), !simpleMode && renderCostLimitBanner(), _jsx("div", { ref: messagesEndRef })] }), !hideContext &&
|
|
4020
5789
|
!simpleMode &&
|
|
4021
|
-
(isMobile ? (_jsx("div", { className: "border-t border-gray-200 bg-gray-50", "data-testid": "agent-context-panel-tabs", children: _jsx(SimpleTabs, { tabs: [
|
|
5790
|
+
(editContext?.isMobile ? (_jsx("div", { className: "border-t border-gray-200 bg-gray-50", "data-testid": "agent-context-panel-tabs", children: _jsx(SimpleTabs, { tabs: [
|
|
4022
5791
|
{
|
|
4023
5792
|
id: "context",
|
|
4024
5793
|
label: "Context",
|
|
@@ -4066,28 +5835,49 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4066
5835
|
hasTodoContent,
|
|
4067
5836
|
hasSpawnedAgents,
|
|
4068
5837
|
agent?.id && hasHistoryContent,
|
|
4069
|
-
].filter(Boolean).length - 1)), setActiveTab: setContextPanelsActiveTab, className: "justify-start px-4" }) })) : (_jsxs(_Fragment, { children: [renderContextInfoBar(), agent?.id && activeProfile && (_jsx(AgentDocumentList, { ref: documentListRef, agentId: agent.id, maxFileSizeMB: activeProfile.maxDocumentSizeMB ?? 10, enabled: activeProfile.enableDocumentUpload ?? false, profileId: activeProfile.id }, `${agent.id}-${agent.updatedDate || ""}-${activeProfile.id}`)), _jsx(TodoListPanel, { messages: messages, agentMetadata: agentMetadata }), _jsx(SpawnedAgentsPanel, { agentMetadata: agentMetadata }), agent?.id && _jsx(AgentEditOperationsPanel, { agentId: agent.id })] }))), queuedPrompts.length > 0 && !simpleMode && (_jsx("div", { className: "border-t border-gray-200 bg-amber-50/50", "data-testid": "queued-prompts-section", children: _jsxs("div", { className: "px-4 pt-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
5838
|
+
].filter(Boolean).length - 1)), setActiveTab: setContextPanelsActiveTab, className: "justify-start px-4" }) })) : (_jsxs(_Fragment, { children: [renderContextInfoBar(), agent?.id && activeProfile && (_jsx(AgentDocumentList, { ref: documentListRef, agentId: agent.id, maxFileSizeMB: activeProfile.maxDocumentSizeMB ?? 10, enabled: activeProfile.enableDocumentUpload ?? false, profileId: activeProfile.id }, `${agent.id}-${agent.updatedDate || ""}-${activeProfile.id}`)), _jsx(TodoListPanel, { messages: messages, agentMetadata: agentMetadata }), _jsx(SpawnedAgentsPanel, { agentMetadata: agentMetadata }), agent?.id && _jsx(AgentEditOperationsPanel, { agentId: agent.id })] }))), queuedPrompts.length > 0 && !simpleMode && (_jsx("div", { className: "border-t border-gray-200 bg-amber-50/50", "data-testid": "queued-prompts-section", children: _jsxs("div", { className: "px-4 pt-2.5 pb-2", children: [_jsxs("div", { className: "mb-1.5 flex items-center gap-1.5", children: [_jsx("div", { className: "h-1.5 w-1.5 animate-pulse rounded-full bg-amber-400" }), _jsxs("span", { className: "text-[10px] font-medium tracking-wide text-amber-600 uppercase", "data-testid": "queued-prompts-count", children: ["Queued (", queuedPrompts.length, ")"] })] }), _jsx("div", { className: "max-h-64 space-y-0.5 overflow-y-auto", children: queuedPrompts.map((qp) => {
|
|
5839
|
+
let triggerName = "";
|
|
5840
|
+
if (qp.data) {
|
|
5841
|
+
try {
|
|
5842
|
+
const parsed = JSON.parse(qp.data);
|
|
5843
|
+
triggerName = parsed?.triggerName?.trim() || "";
|
|
5844
|
+
}
|
|
5845
|
+
catch {
|
|
5846
|
+
// Ignore invalid JSON metadata and render as regular queued prompt.
|
|
5847
|
+
}
|
|
5848
|
+
}
|
|
5849
|
+
const isTriggerQueuedPrompt = !qp.sourceAgentName && triggerName.length > 0;
|
|
5850
|
+
const isTriggerExpanded = !!expandedQueuedTriggerIds[qp.id];
|
|
5851
|
+
if (isTriggerQueuedPrompt) {
|
|
5852
|
+
return (_jsxs("div", { className: "text-[11px]", "data-testid": "queued-prompt-item", children: [_jsxs("button", { type: "button", onClick: () => setExpandedQueuedTriggerIds((prev) => ({
|
|
5853
|
+
...prev,
|
|
5854
|
+
[qp.id]: !prev[qp.id],
|
|
5855
|
+
})), className: "flex w-full items-center gap-1.5 rounded px-1 py-0.5 text-left text-amber-800 transition-colors hover:bg-amber-100/60", "data-testid": "queued-trigger-toggle", "data-expanded": isTriggerExpanded ? "true" : "false", children: [_jsx(Target, { className: "h-3 w-3 shrink-0 text-amber-500", strokeWidth: 2 }), _jsx("span", { className: "truncate font-medium", children: triggerName }), qp.createdDate && (_jsx("span", { className: "ml-1 shrink-0 text-[10px] text-amber-500", children: formatTime(new Date(qp.createdDate)) })), _jsx("span", { className: "ml-auto shrink-0 text-amber-400", children: isTriggerExpanded ? (_jsx(ChevronUp, { className: "h-3 w-3" })) : (_jsx(ChevronDown, { className: "h-3 w-3" })) })] }), isTriggerExpanded && (_jsx("div", { className: "mt-0.5 border-l-2 border-amber-200 pl-5 text-[11px] text-amber-700/80", children: qp.prompt }))] }, qp.id));
|
|
5856
|
+
}
|
|
5857
|
+
return (_jsx("div", { className: "rounded-md border border-amber-200 bg-white p-2.5 text-[11px]", "data-testid": "queued-prompt-item", children: _jsx("div", { className: "flex items-start justify-between gap-2", children: _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "mb-1 flex items-center gap-1.5", children: qp.sourceAgentName ? (_jsxs(_Fragment, { children: [_jsxs("span", { className: "font-medium text-gray-700", children: ["From ", qp.sourceAgentName] }), qp.priority > 5 && (_jsx("span", { className: "rounded bg-red-100 px-1.5 py-0.5 text-[11px] font-medium text-red-700", children: "High Priority" }))] })) : (_jsx("span", { className: "font-medium text-gray-700", children: "From User" })) }), _jsx("div", { className: "wrap-break-word whitespace-pre-wrap text-gray-600", "data-testid": "queued-prompt-text", children: qp.prompt }), _jsxs("div", { className: "mt-1.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-[11px] text-gray-400", children: [qp.createdDate && (_jsxs("span", { children: ["Queued ", formatTime(new Date(qp.createdDate))] })), qp.scheduledFor &&
|
|
5858
|
+
new Date(qp.scheduledFor).getTime() >
|
|
5859
|
+
new Date(qp.createdDate || 0).getTime() +
|
|
5860
|
+
1000 && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-gray-300", children: "\u2022" }), _jsxs("span", { className: "font-medium text-amber-600", children: ["Scheduled for", " ", new Date(qp.scheduledFor).toDateString() ===
|
|
5861
|
+
new Date().toDateString()
|
|
5862
|
+
? formatTime(new Date(qp.scheduledFor))
|
|
5863
|
+
: formatDateTime(new Date(qp.scheduledFor))] })] }))] })] }) }) }, qp.id));
|
|
5864
|
+
}) })] }) }))] }));
|
|
5865
|
+
const showQuestionnaireSplitter = isQuestionnaireDialogOpen && !!fullModeInlineDialog;
|
|
5866
|
+
const fullModeContent = showQuestionnaireSplitter ? (_jsx(Splitter, { panels: [
|
|
5867
|
+
{
|
|
5868
|
+
name: "conversation",
|
|
5869
|
+
defaultSize: 65,
|
|
5870
|
+
content: fullModeUpperContent,
|
|
5871
|
+
},
|
|
5872
|
+
{
|
|
5873
|
+
name: "questionnaire",
|
|
5874
|
+
defaultSize: 35,
|
|
5875
|
+
content: fullModeInlineDialog,
|
|
5876
|
+
},
|
|
5877
|
+
], direction: "vertical", localStorageKey: compact
|
|
5878
|
+
? "agent-terminal-compact-questionnaire-splitter"
|
|
5879
|
+
: "agent-terminal-questionnaire-splitter", className: "min-h-0 flex-1", splitterClassName: "bg-gray-200 hover:bg-gray-300" })) : (_jsxs(_Fragment, { children: [fullModeUpperContent, fullModeInlineDialog] }));
|
|
5880
|
+
return loadingContent ? (loadingContent) : displayMode === "summary" && summaryModeContent ? (summaryModeContent) : (_jsxs("div", { className: `flex h-full min-h-0 flex-col ${className || ""}`, children: [parentAgentId && !simpleMode && (_jsx("div", { className: "border-b border-gray-200 bg-gray-50 px-4 py-2", children: _jsxs("button", { onClick: handleBackToParent, className: "flex items-center gap-2 text-[11px] text-gray-600 transition-colors hover:text-gray-900", title: "Back to parent agent", children: [_jsx(ArrowLeft, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }), _jsx("span", { children: "Back to parent agent" })] }) })), fullModeContent, _jsxs("div", { className: cn("border-t border-gray-200 p-4", simpleMode && "pb-10"), children: [activePlaceholderInput ? (
|
|
4091
5881
|
// Placeholder Input (from quick actions)
|
|
4092
5882
|
// Show internal buttons only in splash mode (hideBottomControls) since external buttons won't be visible there
|
|
4093
5883
|
_jsx(PlaceholderInput, { ref: placeholderInputRef, text: activePlaceholderInput.text, showButtons: hideBottomControls, buttonsClassName: hideBottomControls ? "justify-end" : "", onFilledChange: setAllPlaceholdersFilled, onComplete: (filledText) => {
|
|
@@ -4164,122 +5954,221 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4164
5954
|
return null;
|
|
4165
5955
|
return (_jsxs("div", { className: cn("mt-2 flex items-stretch gap-2", hideBottomControls || simpleMode || isInPlaceholderMode
|
|
4166
5956
|
? "justify-end"
|
|
4167
|
-
: "justify-between"), children: [!hideBottomControls && !simpleMode && !isInPlaceholderMode && (_jsxs("div", { className: "flex flex-wrap items-center justify-start gap-2", children: [_jsx(Select, { size: "xs", maxWidth: 240, className: cn("h-5 w-auto min-w-[95px] rounded border px-1.5 text-[11px] font-normal", mode === "read-only"
|
|
5957
|
+
: "justify-between"), children: [!hideBottomControls && !simpleMode && !isInPlaceholderMode && (_jsxs("div", { className: "flex flex-wrap items-center justify-start gap-2", children: [_jsx(Select, { "data-testid": "agent-mode-selector", size: "xs", maxWidth: 240, className: cn("h-5 w-auto min-w-[95px] rounded border px-1.5 text-[11px] font-normal", mode === "read-only"
|
|
4168
5958
|
? "border-green-300 bg-green-50! text-green-700 hover:bg-green-100!"
|
|
4169
5959
|
: mode === "supervised"
|
|
4170
5960
|
? "border-amber-300 bg-amber-50! text-amber-700 hover:bg-amber-100!"
|
|
4171
5961
|
: "border-red-300 bg-red-50! text-red-700 hover:bg-red-100!"), value: mode, options: modeOptions, onValueChange: async (val) => {
|
|
4172
5962
|
const nextMode = val || "supervised";
|
|
4173
|
-
// Optimistic UI update
|
|
4174
|
-
setMode(nextMode);
|
|
4175
5963
|
const current = agentMetadata || {};
|
|
4176
5964
|
const nextMeta = {
|
|
4177
5965
|
...current,
|
|
4178
5966
|
mode: nextMode,
|
|
4179
5967
|
};
|
|
4180
5968
|
try {
|
|
4181
|
-
if (!agent?.id ||
|
|
5969
|
+
if (!agent?.id || isLocalOnlyDraftAgent) {
|
|
5970
|
+
setMode(nextMode);
|
|
4182
5971
|
setAgentMetadata(nextMeta);
|
|
4183
|
-
// Cache until first start when agent is persisted
|
|
4184
5972
|
pendingSettingsRef.current = {
|
|
4185
5973
|
...(pendingSettingsRef.current || {}),
|
|
4186
5974
|
mode: nextMode,
|
|
4187
5975
|
};
|
|
4188
5976
|
return;
|
|
4189
5977
|
}
|
|
4190
|
-
await updateAgentSettings(agent.id, {
|
|
5978
|
+
const result = await updateAgentSettings(agent.id, {
|
|
4191
5979
|
mode: nextMode,
|
|
4192
5980
|
});
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
: prev);
|
|
4197
|
-
}
|
|
4198
|
-
catch (e2) {
|
|
4199
|
-
console.error("Failed to persist mode change", e2);
|
|
4200
|
-
}
|
|
4201
|
-
} }), profiles?.length > 0 && (_jsx(Select, { size: "xs", maxWidth: 200, searchable: profiles.length > 5, searchPlaceholder: "Filter profiles...", className: "h-5 w-auto min-w-[120px] rounded border px-1.5 text-[11px] text-gray-500", value: activeProfile?.id || "", options: profileOptions, "data-testid": "agent-profile-selector", onValueChange: async (val) => {
|
|
4202
|
-
const nextProfile = profiles.find((x) => x.id === val);
|
|
4203
|
-
if (!nextProfile)
|
|
4204
|
-
return;
|
|
4205
|
-
setActiveProfile(nextProfile);
|
|
4206
|
-
try {
|
|
4207
|
-
if (agent?.id && agent.status !== "new") {
|
|
4208
|
-
await updateAgentSettings(agent.id, {
|
|
4209
|
-
profileId: nextProfile.id,
|
|
4210
|
-
profileName: nextProfile.name,
|
|
4211
|
-
});
|
|
4212
|
-
}
|
|
4213
|
-
else {
|
|
4214
|
-
// cache until first start
|
|
4215
|
-
pendingSettingsRef.current = {
|
|
4216
|
-
...(pendingSettingsRef.current || {}),
|
|
4217
|
-
// we cache profile by updating local metadata
|
|
4218
|
-
};
|
|
4219
|
-
setAgentMetadata((current) => {
|
|
4220
|
-
const next = { ...(current || {}) };
|
|
4221
|
-
next.profile = nextProfile.name;
|
|
4222
|
-
next.additionalData = {
|
|
4223
|
-
...(next.additionalData || {}),
|
|
4224
|
-
profileId: nextProfile.id,
|
|
4225
|
-
profileName: nextProfile.name,
|
|
4226
|
-
};
|
|
4227
|
-
return next;
|
|
4228
|
-
});
|
|
5981
|
+
if (result.success === false ||
|
|
5982
|
+
result.updates?.mode === false) {
|
|
5983
|
+
throw new Error("Mode change was not applied");
|
|
4229
5984
|
}
|
|
4230
|
-
|
|
5985
|
+
setMode(nextMode);
|
|
5986
|
+
setAgentMetadata(nextMeta);
|
|
4231
5987
|
setAgent((prev) => prev
|
|
4232
5988
|
? {
|
|
4233
5989
|
...prev,
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
profile: nextProfile.name,
|
|
4237
|
-
additionalData: {
|
|
4238
|
-
...(agentMetadata
|
|
4239
|
-
?.additionalData || {}),
|
|
4240
|
-
profileId: nextProfile.id,
|
|
4241
|
-
profileName: nextProfile.name,
|
|
4242
|
-
},
|
|
4243
|
-
}),
|
|
5990
|
+
mode: nextMode,
|
|
5991
|
+
metadata: JSON.stringify(nextMeta),
|
|
4244
5992
|
}
|
|
4245
5993
|
: prev);
|
|
4246
5994
|
}
|
|
4247
|
-
catch (
|
|
4248
|
-
console.error("Failed to persist
|
|
4249
|
-
}
|
|
4250
|
-
} })), activeProfile?.models?.length ? (_jsx(Select, { size: "xs", maxWidth: 300, searchable: activeProfile.models.length > 5, searchPlaceholder: "Filter models...", className: "h-5 w-auto min-w-[120px] rounded border px-1.5 text-[11px] text-gray-500", value: selectedModelId || "", options: modelOptions, onValueChange: async (val) => {
|
|
4251
|
-
const nextId = val;
|
|
4252
|
-
setSelectedModelId(nextId);
|
|
4253
|
-
const modelName = activeProfile?.models?.find((m) => m.id === nextId)
|
|
4254
|
-
?.name || "";
|
|
4255
|
-
// Update local agent state immediately for UX and to reflect in streaming stub
|
|
4256
|
-
setAgent((prev) => prev ? { ...prev, model: modelName } : prev);
|
|
4257
|
-
// Persist only for existing agents; otherwise cache until first start
|
|
4258
|
-
try {
|
|
4259
|
-
if (agent?.id && agent.status !== "new") {
|
|
4260
|
-
await updateAgentSettings(agent.id, {
|
|
4261
|
-
model: modelName,
|
|
4262
|
-
});
|
|
4263
|
-
}
|
|
4264
|
-
else {
|
|
4265
|
-
pendingSettingsRef.current = {
|
|
4266
|
-
...(pendingSettingsRef.current || {}),
|
|
4267
|
-
modelName,
|
|
4268
|
-
};
|
|
4269
|
-
}
|
|
5995
|
+
catch (e2) {
|
|
5996
|
+
console.error("Failed to persist mode change", e2);
|
|
4270
5997
|
}
|
|
4271
|
-
|
|
4272
|
-
|
|
5998
|
+
} }), _jsxs(Popover, { open: showAgentSettings, onOpenChange: (open) => {
|
|
5999
|
+
setShowAgentSettings(open);
|
|
6000
|
+
if (!open) {
|
|
6001
|
+
setShowSkillPicker(false);
|
|
4273
6002
|
}
|
|
4274
|
-
}
|
|
6003
|
+
}, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { size: "xs", variant: "outline", className: "h-5 rounded border px-1.5 text-[11px] text-gray-600", "data-testid": "agent-settings-popover-trigger", children: [_jsx(Settings2, { className: "mr-1 h-3 w-3", strokeWidth: 1 }), "Agent settings"] }) }), _jsx(PopoverContent, { className: "w-80 p-3", align: "start", onInteractOutside: (e) => {
|
|
6004
|
+
const target = e.target;
|
|
6005
|
+
if (target?.closest('[data-help-panel="true"]')) {
|
|
6006
|
+
e.preventDefault();
|
|
6007
|
+
}
|
|
6008
|
+
}, onPointerDownOutside: (e) => {
|
|
6009
|
+
const target = e.target;
|
|
6010
|
+
if (target?.closest('[data-help-panel="true"]')) {
|
|
6011
|
+
e.preventDefault();
|
|
6012
|
+
}
|
|
6013
|
+
}, onFocusOutside: (e) => {
|
|
6014
|
+
const target = e.target;
|
|
6015
|
+
if (target?.closest('[data-help-panel="true"]')) {
|
|
6016
|
+
e.preventDefault();
|
|
6017
|
+
}
|
|
6018
|
+
}, children: _jsxs("div", { className: "space-y-3", children: [profiles?.length > 0 && (_jsxs("div", { children: [_jsx("div", { className: "mb-1 text-[11px] font-medium text-gray-700", children: "Agent profile" }), _jsx(Select, { size: "xs", maxWidth: 300, searchable: profiles.length > 5, searchPlaceholder: "Filter profiles...", className: "h-6 w-full rounded border px-1.5 text-[11px] text-gray-500", value: activeProfile?.id || "", options: profileOptions, "data-testid": "agent-profile-selector", onValueChange: async (val) => {
|
|
6019
|
+
const nextProfile = profiles.find((x) => x.id === val);
|
|
6020
|
+
if (!nextProfile)
|
|
6021
|
+
return;
|
|
6022
|
+
setActiveProfile(nextProfile);
|
|
6023
|
+
try {
|
|
6024
|
+
if (agent?.id && !isLocalOnlyDraftAgent) {
|
|
6025
|
+
await updateAgentSettings(agent.id, {
|
|
6026
|
+
profileId: nextProfile.id,
|
|
6027
|
+
profileName: nextProfile.name,
|
|
6028
|
+
});
|
|
6029
|
+
}
|
|
6030
|
+
else {
|
|
6031
|
+
pendingSettingsRef.current = {
|
|
6032
|
+
...(pendingSettingsRef.current || {}),
|
|
6033
|
+
profileId: nextProfile.id,
|
|
6034
|
+
profileName: nextProfile.name,
|
|
6035
|
+
};
|
|
6036
|
+
setAgentMetadata((current) => {
|
|
6037
|
+
const next = {
|
|
6038
|
+
...(current || {}),
|
|
6039
|
+
};
|
|
6040
|
+
next.profile = nextProfile.name;
|
|
6041
|
+
next.additionalData = {
|
|
6042
|
+
...(next.additionalData || {}),
|
|
6043
|
+
profileId: nextProfile.id,
|
|
6044
|
+
profileName: nextProfile.name,
|
|
6045
|
+
};
|
|
6046
|
+
return next;
|
|
6047
|
+
});
|
|
6048
|
+
}
|
|
6049
|
+
setAgent((prev) => prev
|
|
6050
|
+
? {
|
|
6051
|
+
...prev,
|
|
6052
|
+
profileId: nextProfile.id,
|
|
6053
|
+
profileName: nextProfile.name,
|
|
6054
|
+
metadata: JSON.stringify({
|
|
6055
|
+
...(agentMetadata || {}),
|
|
6056
|
+
profile: nextProfile.name,
|
|
6057
|
+
additionalData: {
|
|
6058
|
+
...(agentMetadata
|
|
6059
|
+
?.additionalData || {}),
|
|
6060
|
+
profileId: nextProfile.id,
|
|
6061
|
+
profileName: nextProfile.name,
|
|
6062
|
+
},
|
|
6063
|
+
}),
|
|
6064
|
+
}
|
|
6065
|
+
: prev);
|
|
6066
|
+
}
|
|
6067
|
+
catch (err) {
|
|
6068
|
+
console.error("Failed to persist agent profile", err);
|
|
6069
|
+
}
|
|
6070
|
+
} }), activeProfile && (_jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-2", children: [_jsx(Button, { type: "button", size: "sm", variant: "outline", className: "h-6 px-2 text-[10px]", onClick: () => {
|
|
6071
|
+
void handleEditProfileSideBySide();
|
|
6072
|
+
setShowAgentSettings(false);
|
|
6073
|
+
}, "data-testid": "agent-profile-edit-button", children: "Edit" }), _jsxs("button", { type: "button", className: "inline-flex items-center gap-1 text-[10px] text-gray-500 hover:text-gray-700", onClick: () => {
|
|
6074
|
+
void handleOpenProfileSettings();
|
|
6075
|
+
setShowAgentSettings(false);
|
|
6076
|
+
}, children: ["Open profile settings", _jsx(ExternalLink, { className: "h-2.5 w-2.5", strokeWidth: 1.5 })] })] }))] })), activeProfile?.models?.length ? (_jsxs("div", { children: [_jsx("div", { className: "mb-1 text-[11px] font-medium text-gray-700", children: "Model" }), _jsx(Select, { "data-testid": "agent-model-selector", size: "xs", maxWidth: 300, searchable: activeProfile.models.length > 5, searchPlaceholder: "Filter models...", className: "h-6 w-full rounded border px-1.5 text-[11px] text-gray-500", value: selectedModelId || "", options: modelOptions, onValueChange: async (val) => {
|
|
6077
|
+
const nextId = val;
|
|
6078
|
+
setSelectedModelId(nextId);
|
|
6079
|
+
const modelName = activeProfile?.models?.find((m) => m.id === nextId)?.name || "";
|
|
6080
|
+
setAgent((prev) => prev ? { ...prev, model: modelName } : prev);
|
|
6081
|
+
try {
|
|
6082
|
+
if (agent?.id && !isLocalOnlyDraftAgent) {
|
|
6083
|
+
await updateAgentSettings(agent.id, {
|
|
6084
|
+
model: modelName,
|
|
6085
|
+
});
|
|
6086
|
+
}
|
|
6087
|
+
else {
|
|
6088
|
+
pendingSettingsRef.current = {
|
|
6089
|
+
...(pendingSettingsRef.current || {}),
|
|
6090
|
+
modelName,
|
|
6091
|
+
};
|
|
6092
|
+
}
|
|
6093
|
+
}
|
|
6094
|
+
catch (err) {
|
|
6095
|
+
console.error("Failed to persist agent model", err);
|
|
6096
|
+
}
|
|
6097
|
+
} })] })) : null, _jsxs("div", { children: [_jsxs("div", { className: "mb-1 flex items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-1 text-[11px] font-medium text-gray-700", children: [_jsx(Target, { className: "h-3 w-3", strokeWidth: 1 }), "Skills"] }), _jsxs(Popover, { open: showSkillPicker, onOpenChange: (open) => {
|
|
6098
|
+
setShowSkillPicker(open);
|
|
6099
|
+
if (open) {
|
|
6100
|
+
setSkillActionError(null);
|
|
6101
|
+
}
|
|
6102
|
+
}, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { size: "xs", variant: "outline", className: "h-5 rounded border px-1.5 text-[10px] text-gray-600", "data-testid": "agent-skill-picker-trigger", children: [_jsx(Plus, { className: "mr-1 h-3 w-3", strokeWidth: 1.5 }), "Add"] }) }), _jsx(PopoverContent, { className: "w-88 p-2", align: "end", side: "bottom", children: _jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "text-[11px] font-medium text-gray-700", children: "Select a skill" }), _jsxs("div", { className: "relative h-56 rounded border border-gray-200 bg-gray-50", children: [_jsx(ScrollingContentTree, { rootItemIds: skillRootIds, selectedItemId: selectedSkillIds[selectedSkillIds.length - 1] || undefined, expandedItemId: selectedSkillIds[selectedSkillIds.length - 1] || skillRootIds[0], scrollToSelected: true, hideRootNodes: false, onSelectionChange: (selection) => {
|
|
6103
|
+
const selected = selection[0];
|
|
6104
|
+
if (!selected?.id)
|
|
6105
|
+
return;
|
|
6106
|
+
setSkillActionError(null);
|
|
6107
|
+
if (selectableTemplateIdSet.size > 0 &&
|
|
6108
|
+
(!selected.templateId ||
|
|
6109
|
+
!selectableTemplateIdSet.has(selected.templateId.toLowerCase()))) {
|
|
6110
|
+
return;
|
|
6111
|
+
}
|
|
6112
|
+
if (!manuallyAssignableSkillIdSet.has(selected.id.toLowerCase())) {
|
|
6113
|
+
setSkillActionError("This skill cannot be added for the current agent profile.");
|
|
6114
|
+
return;
|
|
6115
|
+
}
|
|
6116
|
+
void (async () => {
|
|
6117
|
+
const added = await handleAddSkill(selected.id);
|
|
6118
|
+
if (added) {
|
|
6119
|
+
setShowSkillPicker(false);
|
|
6120
|
+
}
|
|
6121
|
+
})();
|
|
6122
|
+
} }), skillsLoading && (_jsx("div", { className: "bg-background/70 absolute inset-0 flex items-center justify-center text-[10px] text-gray-500", children: "Loading skills..." }))] }), !skillsLoading &&
|
|
6123
|
+
!skillsError &&
|
|
6124
|
+
profileFilteredSkills.length > 0 && (_jsx("div", { className: "text-[10px] text-gray-500", children: "Click a skill item in the tree to add it." })), skillsError && (_jsx("div", { className: "text-[10px] text-red-600", children: skillsError })), !skillsError && skillActionError && (_jsx("div", { className: "text-[10px] text-red-600", children: skillActionError })), !skillsLoading &&
|
|
6125
|
+
!skillsError &&
|
|
6126
|
+
skillRootIds.length === 0 && (_jsx("div", { className: "text-[10px] text-gray-500", children: "No skill roots available." })), !skillsLoading &&
|
|
6127
|
+
!skillsError &&
|
|
6128
|
+
profileFilteredSkills.length === 0 && (_jsx("div", { className: "text-[10px] text-gray-500", children: selectedSkillIds.length > 0
|
|
6129
|
+
? "All addable skills are selected"
|
|
6130
|
+
: "No skills can be added for this profile" }))] }) })] })] }), selectedSkillIds.length > 0 && (_jsx("div", { className: "mb-2 flex flex-wrap gap-1", children: selectedSkillIds.map((skillId) => {
|
|
6131
|
+
const skill = selectedSkills.find((s) => s.id === skillId);
|
|
6132
|
+
return (_jsxs("div", { className: "inline-flex items-center gap-1 rounded-full border border-gray-200 bg-gray-100 px-1.5 py-0.5 text-[10px] text-gray-700", children: [_jsx("span", { children: skill?.name || skillId }), _jsx("button", { type: "button", className: "rounded p-0.5 text-gray-500 hover:bg-gray-200 hover:text-gray-700", title: "Open skill item", "aria-label": `Open ${skill?.name || skillId}`, onClick: () => {
|
|
6133
|
+
void handleOpenSkillItem(skillId);
|
|
6134
|
+
}, children: _jsx(ExternalLink, { className: "h-2.5 w-2.5", strokeWidth: 1.5 }) }), autoAssignedSkillSet.has(skillId.toLowerCase()) ? (_jsx("span", { className: "text-[9px] text-gray-500", children: "auto" })) : (_jsx("button", { type: "button", className: "rounded p-0.5 text-gray-500 hover:bg-gray-200 hover:text-gray-700", onClick: () => {
|
|
6135
|
+
void handleRemoveSkill(skillId);
|
|
6136
|
+
}, title: "Remove skill", "aria-label": `Remove ${skill?.name || skillId}`, children: _jsx(X, { className: "h-2.5 w-2.5", strokeWidth: 1 }) }))] }, skillId));
|
|
6137
|
+
}) }))] }), _jsxs("div", { children: [_jsxs("button", { type: "button", onClick: () => setToolsSectionExpanded((open) => !open), className: "mb-0.5 flex w-full items-center justify-between rounded px-0.5 py-0.5 text-left text-[11px] font-medium text-gray-700 hover:bg-gray-100/80", "aria-expanded": toolsSectionExpanded, "data-testid": "agent-tools-section-toggle", children: [_jsxs("span", { children: ["Available tools (", displayedAvailableTools.length, ")"] }), toolsSectionExpanded ? (_jsx(ChevronUp, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 })) : (_jsx(ChevronDown, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 }))] }), toolsSectionExpanded && (_jsx("div", { className: "max-h-36 overflow-y-auto rounded border border-gray-100 bg-gray-50/50 p-1", "data-testid": "agent-tools-section", children: displayedAvailableToolsLoading ? (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "Loading available tools..." })) : displayedAvailableTools.length > 0 ? (_jsxs("div", { className: "space-y-0.5", children: [isLocalOnlyDraftAgent && (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "Preview based on the current profile, mode, and selected skills." })), displayedAvailableTools.map((toolName) => (_jsx("div", { className: "rounded px-1 py-0.5 transition-colors hover:bg-white/60", "data-testid": "agent-tool-row", title: toolName, children: _jsx("div", { className: "truncate text-[10px] text-gray-700", children: toolName }) }, toolName)))] })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isLocalOnlyDraftAgent
|
|
6138
|
+
? "No available tools for this profile and mode"
|
|
6139
|
+
: "No available tools" })) })), displayedAvailableToolsError && (_jsx("div", { className: "mt-1 text-[10px] text-red-600", children: displayedAvailableToolsError }))] }), _jsxs("div", { children: [_jsxs("button", { type: "button", onClick: () => setAllowancesSectionExpanded((open) => !open), className: "mb-0.5 flex w-full items-center justify-between rounded px-0.5 py-0.5 text-left text-[11px] font-medium text-gray-700 hover:bg-gray-100/80", "aria-expanded": allowancesSectionExpanded, "data-testid": "agent-allowances-section-toggle", children: [_jsxs("span", { children: ["Allowances (", allowancesTotalCount, ")"] }), allowancesSectionExpanded ? (_jsx(ChevronUp, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 })) : (_jsx(ChevronDown, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 }))] }), allowancesSectionExpanded && (_jsx("div", { className: "max-h-36 overflow-y-auto rounded border border-gray-100 bg-gray-50/50 p-1", "data-testid": "agent-allowances-section", children: operationAllowancesLoading ? (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "Loading allowances..." })) : hasAnyAllowances ? (_jsx("div", { className: "space-y-1", children: allowanceGroups.map((group) => group.rows.length > 0 ? (_jsxs("div", { className: "space-y-0.5", children: [_jsx("div", { className: "px-1 text-[9px] font-medium tracking-wide text-gray-400 uppercase", children: group.label }), group.rows.map((allowance, index) => {
|
|
6140
|
+
const sourceLabel = formatAllowanceSource(allowance.source);
|
|
6141
|
+
const pathLabel = "itemPath" in allowance
|
|
6142
|
+
? allowance.itemPath
|
|
6143
|
+
: allowance.normalizedPath;
|
|
6144
|
+
return (_jsxs("div", { className: "rounded px-1 py-0.5 transition-colors hover:bg-white/60", "data-testid": `agent-allowance-row-${group.key}`, children: [_jsxs("div", { className: "flex items-baseline gap-1.5", children: [_jsx("div", { className: "shrink-0 text-[10px] font-medium text-gray-700", children: allowance.operationType ||
|
|
6145
|
+
"*" }), _jsx("div", { className: "truncate text-[9px] text-gray-500", title: formatAllowanceLabel(allowance), children: pathLabel })] }), (sourceLabel ||
|
|
6146
|
+
allowance.grantedBy) && (_jsx("div", { className: "truncate pl-6 text-[9px] text-gray-400", children: [
|
|
6147
|
+
sourceLabel,
|
|
6148
|
+
allowance.grantedBy,
|
|
6149
|
+
]
|
|
6150
|
+
.filter(Boolean)
|
|
6151
|
+
.join(" · ") }))] }, `${group.key}-${allowance.operationType}-${pathLabel}-${index}`));
|
|
6152
|
+
})] }, group.key)) : null) })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isLocalOnlyDraftAgent
|
|
6153
|
+
? "Allowances are shown after the agent is created"
|
|
6154
|
+
: "No active allowances" })) })), operationAllowancesError && (_jsx("div", { className: "mt-1 text-[10px] text-red-600", children: operationAllowancesError }))] }), _jsxs("div", { children: [_jsxs("button", { type: "button", onClick: () => setSubscribedTriggersSectionExpanded((open) => !open), className: "mb-0.5 flex w-full items-center justify-between rounded px-0.5 py-0.5 text-left text-[11px] font-medium text-gray-700 hover:bg-gray-100/80", "aria-expanded": subscribedTriggersSectionExpanded, "data-testid": "agent-subscribed-triggers-section-toggle", children: [_jsx("span", { children: `Subscribed triggers (${activeTriggerSubscriptions.length})` }), subscribedTriggersSectionExpanded ? (_jsx(ChevronUp, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 })) : (_jsx(ChevronDown, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 }))] }), subscribedTriggersSectionExpanded && (_jsx("div", { className: "max-h-28 overflow-y-auto rounded border border-gray-100 bg-gray-50/50 p-1", "data-testid": "agent-subscribed-triggers-section", children: triggerSubscriptionsLoading ? (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "Loading subscribed triggers..." })) : activeTriggerSubscriptions.length > 0 ? (_jsx("div", { className: "space-y-0.5", children: activeTriggerSubscriptions.map((sub) => {
|
|
6155
|
+
const filterText = (sub.filter || "").trim();
|
|
6156
|
+
return (_jsxs("div", { className: "flex items-baseline gap-1.5 rounded px-1 py-0.5 transition-colors hover:bg-white/60", children: [_jsx("div", { className: "shrink-0 text-[10px] font-medium text-gray-700", children: sub.triggerName }), filterText.length > 0 && (_jsx("div", { className: "truncate text-[9px] text-gray-400", title: filterText, children: filterText }))] }, sub.id));
|
|
6157
|
+
}) })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isLocalOnlyDraftAgent
|
|
6158
|
+
? "Subscribed triggers are shown after the agent is created"
|
|
6159
|
+
: "No active trigger subscriptions" })) })), triggerSubscriptionsError && (_jsx("div", { className: "mt-1 text-[10px] text-red-600", children: triggerSubscriptionsError }))] })] }) })] }), activeProfile?.prompts?.length ? (_jsxs(Popover, { open: showPredefined, onOpenChange: setShowPredefined, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { className: "rounded p-1 hover:bg-gray-100", onClick: () => { }, title: "Predefined prompts", "aria-label": "Predefined prompts", type: "button", children: _jsx(Wand2, { className: "h-3 w-3", strokeWidth: 1 }) }) }), _jsx(PopoverContent, { className: "w-64 p-0", align: "start", children: _jsx("div", { className: "max-h-56 overflow-y-auto p-2", children: activeProfile.prompts.map((p, index) => (_jsx("div", { className: "cursor-pointer rounded p-1.5 text-[10px] text-gray-700 hover:bg-gray-100", onClick: () => {
|
|
4275
6160
|
setPrompt(p.prompt);
|
|
4276
6161
|
setShowPredefined(false);
|
|
4277
6162
|
if (textareaRef.current)
|
|
4278
6163
|
textareaRef.current.focus();
|
|
4279
|
-
}, children: p.title }, index))) }) })] })) : null, !hideBottomControls &&
|
|
4280
|
-
|
|
6164
|
+
}, children: p.title }, index))) }) })] })) : null, !hideBottomControls &&
|
|
6165
|
+
!simpleMode &&
|
|
6166
|
+
editContext?.isMobile && (_jsxs(Popover, { open: showCostAndAgent, onOpenChange: setShowCostAndAgent, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { onClick: () => {
|
|
6167
|
+
if (editContext?.isMobile)
|
|
4281
6168
|
setShowCostAndAgent((prev) => !prev);
|
|
4282
|
-
}, variant: "outline", size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", "aria-expanded": isMobile
|
|
6169
|
+
}, variant: "outline", size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", "aria-expanded": editContext?.isMobile
|
|
6170
|
+
? showCostAndAgent
|
|
6171
|
+
: undefined, "aria-label": "Toggle cost and context info", children: _jsx(DollarSign, { className: "size-3", strokeWidth: 1 }) }) }), _jsx(PopoverContent, { className: "w-64 p-0", align: "start", children: _jsx("div", { className: "max-h-56 overflow-y-auto p-2", children: _jsx(AgentTerminalStatusBar, { agent: agent, contextWindowStatus: contextWindowStatus, effectiveModelName: effectiveModelName, socketDiagnostics: editContext.socketDiagnostics, runDiagnosticsSnapshot: runDiagnosticsSnapshot, liveTotals: liveTotals, totalTokens: liveTotals
|
|
4283
6172
|
? {
|
|
4284
6173
|
input: liveTotals.input,
|
|
4285
6174
|
output: liveTotals.output,
|
|
@@ -4289,9 +6178,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4289
6178
|
outputCost: liveTotals.outputCost,
|
|
4290
6179
|
cachedCost: liveTotals.cachedCost,
|
|
4291
6180
|
cacheWriteCost: liveTotals.cacheWriteCost ?? 0,
|
|
6181
|
+
imageCost: liveTotals.imageCost ?? 0,
|
|
4292
6182
|
totalCost: liveTotals.totalCost,
|
|
4293
6183
|
}
|
|
4294
|
-
: totalTokens, effectiveCostLimit: effectiveCostLimit, messages: messages,
|
|
6184
|
+
: totalTokens, effectiveCostLimit: effectiveCostLimit, messages: messages, showCompressionPopover: showCompressionPopover, setShowCompressionPopover: setShowCompressionPopover }) }) })] }))] })), _jsxs("div", { className: "flex items-center gap-1 self-end", children: [_jsx("span", { title: isVoiceDisabled
|
|
4295
6185
|
? "Your browser does not support Speech Recognition"
|
|
4296
6186
|
: isListening
|
|
4297
6187
|
? "Stop voice input"
|
|
@@ -4312,7 +6202,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4312
6202
|
: allPendingApprovals.length > 0
|
|
4313
6203
|
? "Approve or reject pending tool calls first"
|
|
4314
6204
|
: "Send", "aria-label": isExecuting ? "Stop" : "Send", "data-testid": "agent-send-stop-button", "data-executing": isExecuting ? "true" : "false", children: isExecuting ? (_jsx(Square, { className: "size-3", strokeWidth: 1 })) : (_jsx(Send, { className: "size-3", strokeWidth: 1 })) })] })] }));
|
|
4315
|
-
})(), !hideBottomControls &&
|
|
6205
|
+
})(), !hideBottomControls &&
|
|
6206
|
+
!simpleMode &&
|
|
6207
|
+
editContext &&
|
|
6208
|
+
!editContext.isMobile && (_jsx(AgentTerminalStatusBar, { agent: agent, contextWindowStatus: contextWindowStatus, effectiveModelName: effectiveModelName, socketDiagnostics: editContext.socketDiagnostics, runDiagnosticsSnapshot: runDiagnosticsSnapshot, liveTotals: liveTotals, totalTokens: liveTotals
|
|
4316
6209
|
? {
|
|
4317
6210
|
input: liveTotals.input,
|
|
4318
6211
|
output: liveTotals.output,
|
|
@@ -4322,8 +6215,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4322
6215
|
outputCost: liveTotals.outputCost,
|
|
4323
6216
|
cachedCost: liveTotals.cachedCost,
|
|
4324
6217
|
cacheWriteCost: liveTotals.cacheWriteCost ?? 0,
|
|
6218
|
+
imageCost: liveTotals.imageCost ?? 0,
|
|
4325
6219
|
totalCost: liveTotals.totalCost,
|
|
4326
6220
|
}
|
|
4327
|
-
: totalTokens, effectiveCostLimit: effectiveCostLimit, messages: messages,
|
|
6221
|
+
: totalTokens, effectiveCostLimit: effectiveCostLimit, messages: messages, showCompressionPopover: showCompressionPopover, setShowCompressionPopover: setShowCompressionPopover }))] })] }));
|
|
4328
6222
|
}
|
|
4329
6223
|
//# sourceMappingURL=AgentTerminal.js.map
|