@parhelia/core 0.1.12570 → 0.1.12585
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 +191 -99
- 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/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 +37 -63
- 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 +6 -5
- 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 +2515 -579
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/AgentTerminalStatusBar.d.ts +9 -4
- package/dist/editor/ai/AgentTerminalStatusBar.js +481 -56
- package/dist/editor/ai/AgentTerminalStatusBar.js.map +1 -1
- package/dist/editor/ai/Agents.js +161 -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 +267 -26
- 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/EditOperationsPanel.d.ts +3 -2
- package/dist/editor/ai/EditOperationsPanel.js +21 -78
- package/dist/editor/ai/EditOperationsPanel.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 +614 -202
- 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 +117 -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 +90 -100
- 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 +853 -258
- 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 +73 -15
- 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/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.d.ts +2 -1
- package/dist/editor/reviews/Comment.js +92 -15
- package/dist/editor/reviews/Comment.js.map +1 -1
- package/dist/editor/reviews/CommentDisplayPopover.js +70 -5
- package/dist/editor/reviews/CommentDisplayPopover.js.map +1 -1
- package/dist/editor/reviews/CommentView.d.ts +3 -1
- package/dist/editor/reviews/CommentView.js +26 -6
- package/dist/editor/reviews/CommentView.js.map +1 -1
- package/dist/editor/reviews/Comments.js +140 -75
- 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 +4 -3
- package/dist/editor/reviews/useReviews.js +21 -32
- package/dist/editor/reviews/useReviews.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +240 -5
- package/dist/editor/services/agentService.js +299 -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/IndexOverview.js +3 -1
- package/dist/editor/settings/IndexOverview.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/CollectionWarningsDisplay.d.ts +10 -0
- package/dist/editor/settings/index/CollectionWarningsDisplay.js +16 -0
- package/dist/editor/settings/index/CollectionWarningsDisplay.js.map +1 -0
- package/dist/editor/settings/index/useIndexStatus.js +23 -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/ModelConfigPanel.js +1 -1
- package/dist/editor/settings/panels/ModelConfigPanel.js.map +1 -1
- package/dist/editor/settings/panels/ModelsPanel.js +324 -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 +67 -6
- package/dist/editor/settings/panels/SearchConfigPanel.js.map +1 -1
- package/dist/editor/settings/panels/StatusPanel.js +7 -2
- package/dist/editor/settings/panels/StatusPanel.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 +24 -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/template-wizard/TemplateStructureInlineEditor.js +3 -2
- package/dist/editor/template-wizard/TemplateStructureInlineEditor.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 +48 -14
- package/dist/setup/services/setupWizardService.js +52 -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 +46 -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 +70 -29
- package/package.json +19 -15
- package/styles.css +39 -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,11 @@
|
|
|
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 {
|
|
4
|
-
import {
|
|
3
|
+
import { flushSync } from "react-dom";
|
|
4
|
+
import { Send, AlertCircle, Loader2, User, Wand2, Square, Mic, MicOff, ChevronDown, ChevronUp, ListTodo, ArrowLeft, DollarSign, ExternalLink, Settings2, Target, X, Plus, } from "lucide-react";
|
|
5
|
+
import { getAgent, startAgent, claimAgentBrowser, assignAgentSkill, persistDraftAgent, updateAgentSettings, updateAgentCostLimit, updateAgentContext, getAgentSkillCatalog, getAgentAvailableTools, getAgentOperationAllowances, getAgentTriggerSubscriptions, cancelAgent, canonicalizeAgentMetadata, getPendingPrompts, releaseAgentBrowser, revokeAgentSkill, } from "../services/agentService";
|
|
6
|
+
import { parseAgentStatus } from "../services/agentStatus";
|
|
5
7
|
import { useEditContext, useFieldsEditContext } from "../client/editContext";
|
|
8
|
+
import { localStorageService } from "../services/localStorageService";
|
|
6
9
|
import { Textarea } from "../../components/ui/textarea";
|
|
7
10
|
import { Button } from "../../components/ui/button";
|
|
8
11
|
import { PlaceholderInput, } from "../../components/ui/PlaceholderInput";
|
|
@@ -12,21 +15,155 @@ import { AgentDocumentList, } from "./AgentDocumentList";
|
|
|
12
15
|
import { AgentEditOperationsPanel } from "./EditOperationsPanel";
|
|
13
16
|
import { SpawnedAgentsPanel } from "./SpawnedAgentsPanel";
|
|
14
17
|
import { getComponentById } from "../componentTreeHelper";
|
|
18
|
+
import { toUserFacingAgentErrorMessage } from "../services/agentErrorMessage";
|
|
15
19
|
import { AgentGreeting } from "./AgentGreeting";
|
|
16
20
|
import { getAgentHistory } from "../services/editService";
|
|
21
|
+
import { QuestionnaireInline } from "./dialogs/QuestionnaireInline";
|
|
22
|
+
import { getBrowserCaptureClaim, setBrowserCaptureClaim, } from "./dialogs/browserBoundCapture";
|
|
23
|
+
import { DIALOG_TYPES, } from "./dialogs/agentDialogTypes";
|
|
17
24
|
import { Popover, PopoverContent, PopoverTrigger, } from "../../components/ui/popover";
|
|
18
25
|
import { SecretAgentIcon } from "../ui/Icons";
|
|
19
26
|
import { formatTime, formatDateTime } from "../utils";
|
|
20
27
|
import { cn } from "../../lib/utils";
|
|
28
|
+
import { sanitizeSvg } from "../../lib/sanitize";
|
|
21
29
|
import { Select } from "../../components/ui/select";
|
|
22
30
|
import { AgentTerminalStatusBar } from "./AgentTerminalStatusBar";
|
|
23
|
-
import { useMediaQuery } from "../client/hooks/useMediaQuery";
|
|
24
31
|
import { SimpleTabs } from "../ui/SimpleTabs";
|
|
32
|
+
import { Splitter } from "../ui/Splitter";
|
|
33
|
+
import { ScrollingContentTree } from "../ScrollingContentTree";
|
|
34
|
+
import { MarkdownDisplay, } from "../../components/MarkdownDisplay";
|
|
35
|
+
const AGENT_HISTORY_LIMIT = 1000;
|
|
36
|
+
function mergeAgentOperationHistory(existing, incoming, limit = AGENT_HISTORY_LIMIT) {
|
|
37
|
+
const merged = new Map(existing.map((operation) => [operation.id, operation]));
|
|
38
|
+
for (const operation of incoming) {
|
|
39
|
+
merged.set(operation.id, operation);
|
|
40
|
+
}
|
|
41
|
+
return Array.from(merged.values())
|
|
42
|
+
.sort((left, right) => new Date(right.date).getTime() - new Date(left.date).getTime())
|
|
43
|
+
.slice(0, limit);
|
|
44
|
+
}
|
|
45
|
+
const userMessageMarkdownComponents = {
|
|
46
|
+
h1: (props) => (_jsx("h1", { ...props, className: "mb-2 text-sm leading-5 font-semibold text-gray-900" })),
|
|
47
|
+
h2: (props) => (_jsx("h2", { ...props, className: "mb-1.5 text-[13px] leading-5 font-semibold text-gray-900" })),
|
|
48
|
+
h3: (props) => (_jsx("h3", { ...props, className: "mb-1 text-[12px] leading-5 font-semibold text-gray-900" })),
|
|
49
|
+
h4: (props) => (_jsx("h4", { ...props, className: "mb-1 text-[12px] leading-5 font-medium text-gray-800" })),
|
|
50
|
+
p: (props) => (_jsx("p", { ...props, className: "my-1 text-[12px] leading-5 text-gray-700" })),
|
|
51
|
+
ul: (props) => (_jsx("ul", { ...props, className: "my-2 ml-5 list-disc space-y-1 text-[12px] leading-5 text-gray-700" })),
|
|
52
|
+
ol: (props) => (_jsx("ol", { ...props, className: "my-2 ml-5 list-decimal space-y-1 text-[12px] leading-5 text-gray-700" })),
|
|
53
|
+
li: (props) => (_jsx("li", { ...props, className: "text-[12px] leading-5 text-gray-700" })),
|
|
54
|
+
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" })),
|
|
55
|
+
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 })),
|
|
56
|
+
};
|
|
57
|
+
function buildPlaceholderAgentDetails(agentStub) {
|
|
58
|
+
const now = new Date().toISOString();
|
|
59
|
+
const updated = agentStub.updatedDate || now;
|
|
60
|
+
// AgentDetails has required fields, but some workspaces only pass an Agent stub initially.
|
|
61
|
+
// This placeholder keeps streaming/tool-call UI working until `getAgent()` returns full details.
|
|
62
|
+
return {
|
|
63
|
+
...agentStub,
|
|
64
|
+
name: agentStub.name || "Agent",
|
|
65
|
+
userId: agentStub.userId || "",
|
|
66
|
+
updatedDate: updated,
|
|
67
|
+
profileName: agentStub.profileName || "",
|
|
68
|
+
model: agentStub.model || "",
|
|
69
|
+
createdDate: agentStub.createdDate || updated,
|
|
70
|
+
totalTokensUsed: 0,
|
|
71
|
+
totalInputTokens: 0,
|
|
72
|
+
totalOutputTokens: 0,
|
|
73
|
+
totalCachedInputTokens: 0,
|
|
74
|
+
totalInputTokenCost: 0,
|
|
75
|
+
totalOutputTokenCost: 0,
|
|
76
|
+
totalCachedInputTokenCost: 0,
|
|
77
|
+
totalImageCost: 0,
|
|
78
|
+
totalCost: 0,
|
|
79
|
+
currency: agentStub.currency || "USD",
|
|
80
|
+
messageCount: agentStub.messageCount || 0,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function normalizeDialogAgentId(value) {
|
|
84
|
+
return value?.trim().toLowerCase() || "";
|
|
85
|
+
}
|
|
86
|
+
function formatAllowanceSource(source) {
|
|
87
|
+
const normalized = source?.trim();
|
|
88
|
+
if (!normalized)
|
|
89
|
+
return null;
|
|
90
|
+
if (normalized === "user")
|
|
91
|
+
return "User granted";
|
|
92
|
+
if (normalized.startsWith("preconfigured:profile:"))
|
|
93
|
+
return "Profile";
|
|
94
|
+
if (normalized.startsWith("preconfigured:skill:"))
|
|
95
|
+
return "Skill";
|
|
96
|
+
if (normalized.startsWith("system:")) {
|
|
97
|
+
return normalized.slice("system:".length).replace(/[-_]+/g, " ").trim();
|
|
98
|
+
}
|
|
99
|
+
return normalized;
|
|
100
|
+
}
|
|
101
|
+
function formatAllowanceLabel(allowance) {
|
|
102
|
+
return `${allowance.operationType || "*"}${"itemPath" in allowance
|
|
103
|
+
? ` ${allowance.itemPath}`
|
|
104
|
+
: ` ${allowance.normalizedPath}`}`;
|
|
105
|
+
}
|
|
106
|
+
function getAgentRunMessageAgentId(payload) {
|
|
107
|
+
const agentId = payload?.agentId;
|
|
108
|
+
return typeof agentId === "string" ? normalizeDialogAgentId(agentId) : null;
|
|
109
|
+
}
|
|
110
|
+
function getAgentRunMessageSeq(payload) {
|
|
111
|
+
const seq = payload?.seq;
|
|
112
|
+
return typeof seq === "number" ? seq : null;
|
|
113
|
+
}
|
|
114
|
+
function getAgentRunMessageDetail(type, payload) {
|
|
115
|
+
if (type === "agent:run:delta") {
|
|
116
|
+
return payload?.type || null;
|
|
117
|
+
}
|
|
118
|
+
if (type === "agent:run:status") {
|
|
119
|
+
return payload?.data?.state || payload?.data?.status || null;
|
|
120
|
+
}
|
|
121
|
+
if (type === "agent:run:error") {
|
|
122
|
+
return payload?.error || null;
|
|
123
|
+
}
|
|
124
|
+
if (type === "agent:run:complete") {
|
|
125
|
+
return payload?.finalStatus || null;
|
|
126
|
+
}
|
|
127
|
+
if (type === "agent:run:start") {
|
|
128
|
+
return payload?.agentName || null;
|
|
129
|
+
}
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
function isHeartbeatRunEventMessage(message) {
|
|
133
|
+
if (!message)
|
|
134
|
+
return false;
|
|
135
|
+
if (message.type === "agent:run:delta") {
|
|
136
|
+
const deltaType = message.payload?.type;
|
|
137
|
+
return String(deltaType || "").toLowerCase() === "heartbeat";
|
|
138
|
+
}
|
|
139
|
+
if (message.type === "agent:run:status") {
|
|
140
|
+
const state = message.payload?.data?.state || message.payload?.data?.status;
|
|
141
|
+
return String(state || "").toLowerCase() === "heartbeat";
|
|
142
|
+
}
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
function getVisibleDialogRegistry() {
|
|
146
|
+
const registry = globalThis.__agentDialogVisibleCallbacks;
|
|
147
|
+
return registry && typeof registry === "object" ? registry : {};
|
|
148
|
+
}
|
|
149
|
+
function isAgentErrorStatusValue(status) {
|
|
150
|
+
return status === "error";
|
|
151
|
+
}
|
|
25
152
|
// Simple user message component
|
|
26
153
|
const UserMessage = ({ message }) => {
|
|
154
|
+
const content = message.content || "";
|
|
155
|
+
const [isTriggerExpanded, setIsTriggerExpanded] = useState(false);
|
|
156
|
+
// Trigger-sourced prompts are prefixed by backend as "[Trigger: {name}]: {content}"
|
|
157
|
+
const triggerPattern = /^\[Trigger: ([^\]]+)\]:\s*(.*)$/s;
|
|
158
|
+
const triggerMatch = content.match(triggerPattern);
|
|
159
|
+
const triggerName = triggerMatch?.[1]?.trim() || "";
|
|
160
|
+
const triggerContent = triggerMatch?.[2] || "";
|
|
161
|
+
const isTriggerMessage = triggerName.length > 0;
|
|
162
|
+
if (isTriggerMessage) {
|
|
163
|
+
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 }) }))] }) }));
|
|
164
|
+
}
|
|
27
165
|
// Parse source agent name from content if it starts with "[From ...]:"
|
|
28
166
|
// Backend formats messages from other agents as "[From {sourceAgentName}]: {content}"
|
|
29
|
-
const content = message.content || "";
|
|
30
167
|
const fromPattern = /^\[From ([^\]]+)\]:\s*(.*)$/s;
|
|
31
168
|
const match = content.match(fromPattern);
|
|
32
169
|
let sourceAgentName;
|
|
@@ -51,7 +188,10 @@ const UserMessage = ({ message }) => {
|
|
|
51
188
|
message.sourceAgent?.name;
|
|
52
189
|
}
|
|
53
190
|
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 })] })] }));
|
|
191
|
+
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 }) })] })] }));
|
|
192
|
+
};
|
|
193
|
+
const HeartbeatMessage = ({ message }) => {
|
|
194
|
+
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
195
|
};
|
|
56
196
|
// Helper to extract todos from potentially incomplete JSON during streaming
|
|
57
197
|
const extractPartialTodos = (jsonText) => {
|
|
@@ -285,14 +425,6 @@ const extractTodosFromMessages = (messages) => {
|
|
|
285
425
|
todoMap.set(key, todo);
|
|
286
426
|
}
|
|
287
427
|
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
428
|
return result;
|
|
297
429
|
};
|
|
298
430
|
// TodoListPanel component
|
|
@@ -300,12 +432,9 @@ const TodoListPanel = ({ messages, agentMetadata, }) => {
|
|
|
300
432
|
const [isExpanded, setIsExpanded] = useState(true);
|
|
301
433
|
// First try to get todos from agent metadata (real-time updates)
|
|
302
434
|
// Server sends additionalData.todoList directly via contextChanged status
|
|
303
|
-
// Also check top-level todoList for backward compatibility with stored contexts
|
|
304
435
|
const metadataTodos = (() => {
|
|
305
436
|
try {
|
|
306
|
-
|
|
307
|
-
const todoList = agentMetadata?.additionalData?.todoList ||
|
|
308
|
-
agentMetadata?.todoList;
|
|
437
|
+
const todoList = agentMetadata?.additionalData?.todoList;
|
|
309
438
|
if (todoList?.items && Array.isArray(todoList.items)) {
|
|
310
439
|
const rawItems = todoList.items
|
|
311
440
|
.map((item, idx) => ({
|
|
@@ -433,6 +562,17 @@ const groupConsecutiveMessages = (agentMessages) => {
|
|
|
433
562
|
// Add user message
|
|
434
563
|
groups.push({ type: "user", messages: [message] });
|
|
435
564
|
}
|
|
565
|
+
else if (message.messageType === "heartbeat" ||
|
|
566
|
+
message.role === "system") {
|
|
567
|
+
if (currentAssistantGroup.length > 0) {
|
|
568
|
+
groups.push({
|
|
569
|
+
type: "assistant-group",
|
|
570
|
+
messages: currentAssistantGroup,
|
|
571
|
+
});
|
|
572
|
+
currentAssistantGroup = [];
|
|
573
|
+
}
|
|
574
|
+
groups.push({ type: "heartbeat", messages: [message] });
|
|
575
|
+
}
|
|
436
576
|
else if (message.role === "assistant") {
|
|
437
577
|
// Add to current assistant group
|
|
438
578
|
currentAssistantGroup.push(message);
|
|
@@ -500,6 +640,7 @@ const calculateTotalTokens = (messages) => {
|
|
|
500
640
|
outputCost: acc.outputCost + (message.outputTokenCost || 0),
|
|
501
641
|
cachedCost: acc.cachedCost + (message.cachedInputTokenCost || 0),
|
|
502
642
|
cacheWriteCost: acc.cacheWriteCost,
|
|
643
|
+
imageCost: acc.imageCost,
|
|
503
644
|
totalCost: acc.totalCost + (message.totalCost || 0),
|
|
504
645
|
};
|
|
505
646
|
}, {
|
|
@@ -511,6 +652,7 @@ const calculateTotalTokens = (messages) => {
|
|
|
511
652
|
outputCost: 0,
|
|
512
653
|
cachedCost: 0,
|
|
513
654
|
cacheWriteCost: 0,
|
|
655
|
+
imageCost: 0,
|
|
514
656
|
totalCost: 0,
|
|
515
657
|
});
|
|
516
658
|
return totals;
|
|
@@ -521,6 +663,84 @@ const getOperationsForMessageGroup = (messages, agentOperations) => {
|
|
|
521
663
|
const matched = agentOperations.filter((op) => op.toolCallId && toolCallIds.has(op.toolCallId));
|
|
522
664
|
return matched;
|
|
523
665
|
};
|
|
666
|
+
const stringifyToolField = (value) => {
|
|
667
|
+
if (value === undefined || value === null)
|
|
668
|
+
return undefined;
|
|
669
|
+
if (typeof value === "string") {
|
|
670
|
+
return value.trim().length > 0 ? value : undefined;
|
|
671
|
+
}
|
|
672
|
+
try {
|
|
673
|
+
return JSON.stringify(value);
|
|
674
|
+
}
|
|
675
|
+
catch {
|
|
676
|
+
return String(value);
|
|
677
|
+
}
|
|
678
|
+
};
|
|
679
|
+
const parseToolResultValue = (value) => {
|
|
680
|
+
if (value === undefined || value === null) {
|
|
681
|
+
return undefined;
|
|
682
|
+
}
|
|
683
|
+
if (typeof value === "object") {
|
|
684
|
+
return value;
|
|
685
|
+
}
|
|
686
|
+
if (typeof value !== "string") {
|
|
687
|
+
return String(value);
|
|
688
|
+
}
|
|
689
|
+
const trimmed = value.trim();
|
|
690
|
+
if (!trimmed) {
|
|
691
|
+
return undefined;
|
|
692
|
+
}
|
|
693
|
+
try {
|
|
694
|
+
let parsed = JSON.parse(trimmed);
|
|
695
|
+
if (typeof parsed === "string" &&
|
|
696
|
+
(parsed.startsWith("{") || parsed.startsWith("["))) {
|
|
697
|
+
parsed = JSON.parse(parsed);
|
|
698
|
+
}
|
|
699
|
+
if (parsed && typeof parsed === "object") {
|
|
700
|
+
return parsed;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
703
|
+
catch {
|
|
704
|
+
// Fall back to the original string when the payload is plain text.
|
|
705
|
+
}
|
|
706
|
+
return value;
|
|
707
|
+
};
|
|
708
|
+
const getFirstToolCallEnvelope = (data) => {
|
|
709
|
+
if (!data || typeof data !== "object")
|
|
710
|
+
return undefined;
|
|
711
|
+
const direct = data.toolCall || data.tool_call;
|
|
712
|
+
if (direct)
|
|
713
|
+
return direct;
|
|
714
|
+
const arrayCandidates = [data.tool_calls, data.toolCalls];
|
|
715
|
+
for (const candidate of arrayCandidates) {
|
|
716
|
+
if (Array.isArray(candidate) && candidate.length > 0) {
|
|
717
|
+
return candidate[0];
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return undefined;
|
|
721
|
+
};
|
|
722
|
+
const extractToolCallFields = (data) => {
|
|
723
|
+
const envelope = getFirstToolCallEnvelope(data);
|
|
724
|
+
const functionPayload = data?.function || envelope?.function;
|
|
725
|
+
const functionName = data?.functionName ||
|
|
726
|
+
data?.name ||
|
|
727
|
+
functionPayload?.name ||
|
|
728
|
+
envelope?.functionName ||
|
|
729
|
+
envelope?.name ||
|
|
730
|
+
"unknown";
|
|
731
|
+
const toolCallId = data?.toolCallId || data?.id || envelope?.id;
|
|
732
|
+
const functionArguments = stringifyToolField(data?.functionArguments) ||
|
|
733
|
+
stringifyToolField(data?.arguments) ||
|
|
734
|
+
stringifyToolField(functionPayload?.arguments) ||
|
|
735
|
+
stringifyToolField(envelope?.functionArguments) ||
|
|
736
|
+
stringifyToolField(envelope?.arguments) ||
|
|
737
|
+
"{}";
|
|
738
|
+
return {
|
|
739
|
+
toolCallId,
|
|
740
|
+
functionName,
|
|
741
|
+
functionArguments,
|
|
742
|
+
};
|
|
743
|
+
};
|
|
524
744
|
// Convert agent messages to AI terminal format for a response group
|
|
525
745
|
const convertAgentMessagesToAiFormat = (agentMessages) => {
|
|
526
746
|
return agentMessages.map((agentMessage) => {
|
|
@@ -541,28 +761,39 @@ const convertAgentMessagesToAiFormat = (agentMessages) => {
|
|
|
541
761
|
role: agentMessage.role,
|
|
542
762
|
createdDate: agentMessage.createdDate,
|
|
543
763
|
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
|
-
|
|
764
|
+
? agentMessage.toolCalls.map((toolCall) => {
|
|
765
|
+
const isPruned = !!toolCall.isPruned ||
|
|
766
|
+
/^PRUNED$/i.test(toolCall.functionError || "");
|
|
767
|
+
const displayResult = parseToolResultValue(toolCall.functionResultRichContent) ?? toolCall.functionResult;
|
|
768
|
+
return {
|
|
769
|
+
id: toolCall.toolCallId,
|
|
770
|
+
displayName: toolCall.functionName,
|
|
771
|
+
function: {
|
|
772
|
+
name: toolCall.functionName,
|
|
773
|
+
arguments: toolCall.functionArguments,
|
|
774
|
+
result: displayResult,
|
|
775
|
+
error: toolCall.functionError,
|
|
776
|
+
},
|
|
777
|
+
// Pass through approval info if present on the tool call
|
|
778
|
+
requiresApproval: toolCall.requiresApproval,
|
|
779
|
+
// Pass through prune metadata so the terminal can render a neutral state
|
|
780
|
+
isPruned,
|
|
781
|
+
prunedAt: toolCall.prunedAt,
|
|
782
|
+
// Pass through isCompleted so ToolCallDisplay knows when to hide spinner
|
|
783
|
+
isCompleted: toolCall.isCompleted,
|
|
784
|
+
// Tool call is streaming if message is not completed and tool call has no result yet
|
|
785
|
+
isStreaming: !agentMessage.isCompleted &&
|
|
786
|
+
!toolCall.isCompleted &&
|
|
787
|
+
!displayResult &&
|
|
788
|
+
!toolCall.functionError &&
|
|
789
|
+
!isPruned,
|
|
790
|
+
// Pass through message IDs for approval/rejection events
|
|
791
|
+
messageId: toolCall.messageId,
|
|
792
|
+
dbMessageId: toolCall.dbMessageId,
|
|
793
|
+
responseTimeMs: toolCall.responseTimeMs,
|
|
794
|
+
createdDate: toolCall.createdDate,
|
|
795
|
+
};
|
|
796
|
+
})
|
|
566
797
|
: [],
|
|
567
798
|
};
|
|
568
799
|
if (agentMessage.toolCallId) {
|
|
@@ -574,10 +805,10 @@ const convertAgentMessagesToAiFormat = (agentMessages) => {
|
|
|
574
805
|
// interface AgentTerminalProps {
|
|
575
806
|
// agentStub: Agent;
|
|
576
807
|
// }
|
|
577
|
-
export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive = true, compact = false, hideContext = false, hideBottomControls = false, hideGreeting = false, simpleMode = false, className, initialPrompt, onAgentUpdate, }) {
|
|
808
|
+
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
809
|
const editContext = useEditContext();
|
|
579
810
|
const fieldsContext = useFieldsEditContext();
|
|
580
|
-
const [agent, setAgent] = useState(
|
|
811
|
+
const [agent, setAgent] = useState(() => buildPlaceholderAgentDetails(agentStub));
|
|
581
812
|
const [messages, setMessages] = useState([]);
|
|
582
813
|
const [agentOperations, setAgentOperations] = useState([]);
|
|
583
814
|
const [prompt, setPrompt] = useState("");
|
|
@@ -588,6 +819,35 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
588
819
|
const [activePlaceholderInput, setActivePlaceholderInput] = useState(null);
|
|
589
820
|
const [allPlaceholdersFilled, setAllPlaceholdersFilled] = useState(false);
|
|
590
821
|
const [agentMetadata, setAgentMetadata] = useState(null);
|
|
822
|
+
const [isBrowserClaimMutationPending, setIsBrowserClaimMutationPending] = useState(false);
|
|
823
|
+
const [pendingBrowserCaptureDialogType, setPendingBrowserCaptureDialogType] = useState(null);
|
|
824
|
+
// Ensure we always have an agent object for streaming handlers, even before `getAgent()` resolves.
|
|
825
|
+
// This prevents early tool calls (e.g., ask-questionnaire) from being dropped in compact/workspace UIs.
|
|
826
|
+
useEffect(() => {
|
|
827
|
+
setAgent((prev) => {
|
|
828
|
+
if (prev?.id === agentStub.id)
|
|
829
|
+
return prev;
|
|
830
|
+
return buildPlaceholderAgentDetails(agentStub);
|
|
831
|
+
});
|
|
832
|
+
}, [agentStub.id]);
|
|
833
|
+
const observedMessageIdsRef = useRef(new Set());
|
|
834
|
+
useEffect(() => {
|
|
835
|
+
observedMessageIdsRef.current = new Set();
|
|
836
|
+
}, [agentStub.id]);
|
|
837
|
+
useEffect(() => {
|
|
838
|
+
if (!onMessage)
|
|
839
|
+
return;
|
|
840
|
+
for (const message of messages) {
|
|
841
|
+
if (!message?.id)
|
|
842
|
+
continue;
|
|
843
|
+
if (!message.isCompleted)
|
|
844
|
+
continue;
|
|
845
|
+
if (observedMessageIdsRef.current.has(message.id))
|
|
846
|
+
continue;
|
|
847
|
+
observedMessageIdsRef.current.add(message.id);
|
|
848
|
+
onMessage(message);
|
|
849
|
+
}
|
|
850
|
+
}, [messages, onMessage]);
|
|
591
851
|
// Generate a stable clientSessionId per component instance for stream deduplication
|
|
592
852
|
const clientSessionIdRef = useRef(null);
|
|
593
853
|
if (!clientSessionIdRef.current) {
|
|
@@ -599,6 +859,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
599
859
|
const [isListening, setIsListening] = useState(false);
|
|
600
860
|
const recognitionRef = useRef(null);
|
|
601
861
|
const prevPlaceholderRef = useRef(null);
|
|
862
|
+
const promptBeforeVoiceRef = useRef("");
|
|
602
863
|
// Voice button press-and-hold tracking
|
|
603
864
|
const voicePressStartRef = useRef(null);
|
|
604
865
|
const voiceHoldTimerRef = useRef(null);
|
|
@@ -608,6 +869,70 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
608
869
|
const [showCompressionPopover, setShowCompressionPopover] = useState(false);
|
|
609
870
|
// Agent inline dialog state (for component type selector, etc.)
|
|
610
871
|
const [activeInlineDialog, setActiveInlineDialog] = useState(null);
|
|
872
|
+
const dialogTerminalInstanceIdRef = useRef("");
|
|
873
|
+
if (!dialogTerminalInstanceIdRef.current) {
|
|
874
|
+
dialogTerminalInstanceIdRef.current = crypto.randomUUID();
|
|
875
|
+
}
|
|
876
|
+
const activeInlineDialogRef = useRef(activeInlineDialog);
|
|
877
|
+
const isQuestionnaireDialogOpen = activeInlineDialog?.request.dialogType === "questionnaire";
|
|
878
|
+
const orphanTimeoutRef = useRef(null);
|
|
879
|
+
useEffect(() => {
|
|
880
|
+
activeInlineDialogRef.current = activeInlineDialog;
|
|
881
|
+
const visibleRegistry = { ...getVisibleDialogRegistry() };
|
|
882
|
+
const callbackId = activeInlineDialog?.request.callbackId || null;
|
|
883
|
+
const terminalInstanceId = dialogTerminalInstanceIdRef.current;
|
|
884
|
+
const agentKeys = [
|
|
885
|
+
normalizeDialogAgentId(agentStubIdRefForDialogs.current),
|
|
886
|
+
normalizeDialogAgentId(agentIdRefForDialogs.current),
|
|
887
|
+
].filter(Boolean);
|
|
888
|
+
agentKeys.forEach((key) => {
|
|
889
|
+
if (callbackId) {
|
|
890
|
+
visibleRegistry[key] = {
|
|
891
|
+
callbackId,
|
|
892
|
+
terminalInstanceId,
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
else {
|
|
896
|
+
delete visibleRegistry[key];
|
|
897
|
+
}
|
|
898
|
+
});
|
|
899
|
+
globalThis.__agentDialogVisibleCallbacks = visibleRegistry;
|
|
900
|
+
}, [activeInlineDialog]);
|
|
901
|
+
useEffect(() => {
|
|
902
|
+
onQuestionnaireOpenChange?.(isQuestionnaireDialogOpen);
|
|
903
|
+
}, [isQuestionnaireDialogOpen, onQuestionnaireOpenChange]);
|
|
904
|
+
useLayoutEffect(() => {
|
|
905
|
+
if (displayMode !== "summary" || !isQuestionnaireDialogOpen) {
|
|
906
|
+
return;
|
|
907
|
+
}
|
|
908
|
+
const scrollSummaryTerminalToTop = () => {
|
|
909
|
+
const container = messagesContainerRef.current;
|
|
910
|
+
if (!container) {
|
|
911
|
+
return;
|
|
912
|
+
}
|
|
913
|
+
container.scrollTop = 0;
|
|
914
|
+
};
|
|
915
|
+
scrollSummaryTerminalToTop();
|
|
916
|
+
const frame1 = requestAnimationFrame(() => {
|
|
917
|
+
scrollSummaryTerminalToTop();
|
|
918
|
+
});
|
|
919
|
+
let frame3 = 0;
|
|
920
|
+
const frame2 = requestAnimationFrame(() => {
|
|
921
|
+
frame3 = requestAnimationFrame(() => {
|
|
922
|
+
scrollSummaryTerminalToTop();
|
|
923
|
+
});
|
|
924
|
+
});
|
|
925
|
+
return () => {
|
|
926
|
+
cancelAnimationFrame(frame1);
|
|
927
|
+
cancelAnimationFrame(frame2);
|
|
928
|
+
cancelAnimationFrame(frame3);
|
|
929
|
+
};
|
|
930
|
+
}, [displayMode, isQuestionnaireDialogOpen]);
|
|
931
|
+
useEffect(() => {
|
|
932
|
+
return () => {
|
|
933
|
+
onQuestionnaireOpenChange?.(false);
|
|
934
|
+
};
|
|
935
|
+
}, [onQuestionnaireOpenChange]);
|
|
611
936
|
const isWaitingRef = useRef(false);
|
|
612
937
|
useEffect(() => {
|
|
613
938
|
isWaitingRef.current = isWaitingForResponse;
|
|
@@ -616,12 +941,96 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
616
941
|
// while a stop operation is in progress. This prevents race conditions where
|
|
617
942
|
// messages arriving during the stop process could flip the UI back to "executing".
|
|
618
943
|
const isStoppingRef = useRef(false);
|
|
944
|
+
const [isStopGuardActive, setIsStopGuardActive] = useState(false);
|
|
945
|
+
const stopGuardReleaseTimeoutRef = useRef(null);
|
|
946
|
+
function clearStopGuard() {
|
|
947
|
+
isStoppingRef.current = false;
|
|
948
|
+
setIsStopGuardActive(false);
|
|
949
|
+
if (stopGuardReleaseTimeoutRef.current) {
|
|
950
|
+
clearTimeout(stopGuardReleaseTimeoutRef.current);
|
|
951
|
+
stopGuardReleaseTimeoutRef.current = null;
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
function armStopGuard() {
|
|
955
|
+
isStoppingRef.current = true;
|
|
956
|
+
setIsStopGuardActive(true);
|
|
957
|
+
if (stopGuardReleaseTimeoutRef.current) {
|
|
958
|
+
clearTimeout(stopGuardReleaseTimeoutRef.current);
|
|
959
|
+
}
|
|
960
|
+
stopGuardReleaseTimeoutRef.current = setTimeout(() => {
|
|
961
|
+
stopGuardReleaseTimeoutRef.current = null;
|
|
962
|
+
isStoppingRef.current = false;
|
|
963
|
+
setIsStopGuardActive(false);
|
|
964
|
+
}, 30000);
|
|
965
|
+
}
|
|
619
966
|
// Server-driven state: true when agent is actively processing (set by WebSocket messages)
|
|
620
967
|
const [isAgentThinking, setIsAgentThinking] = useState(false);
|
|
968
|
+
useEffect(() => {
|
|
969
|
+
return () => {
|
|
970
|
+
if (stopGuardReleaseTimeoutRef.current) {
|
|
971
|
+
clearTimeout(stopGuardReleaseTimeoutRef.current);
|
|
972
|
+
stopGuardReleaseTimeoutRef.current = null;
|
|
973
|
+
}
|
|
974
|
+
};
|
|
975
|
+
}, []);
|
|
976
|
+
useEffect(() => {
|
|
977
|
+
if (!initialMetadata)
|
|
978
|
+
return;
|
|
979
|
+
setAgentMetadata((prev) => sanitizeAgentMetadata({
|
|
980
|
+
...(prev || {}),
|
|
981
|
+
...initialMetadata,
|
|
982
|
+
additionalData: {
|
|
983
|
+
...(prev?.additionalData || {}),
|
|
984
|
+
...(initialMetadata?.additionalData || {}),
|
|
985
|
+
},
|
|
986
|
+
}));
|
|
987
|
+
}, [initialMetadata]);
|
|
621
988
|
const hasActiveStreaming = useCallback(() => {
|
|
622
989
|
const current = messagesRef.current || [];
|
|
623
990
|
return current.some((m) => !m.isCompleted && m.messageType === "streaming");
|
|
624
991
|
}, []);
|
|
992
|
+
const currentAgentId = agent?.id || agentStub.id;
|
|
993
|
+
const recentAgentRunEvents = useMemo(() => {
|
|
994
|
+
const normalizedAgentId = normalizeDialogAgentId(currentAgentId);
|
|
995
|
+
if (!normalizedAgentId) {
|
|
996
|
+
return [];
|
|
997
|
+
}
|
|
998
|
+
if (!editContext) {
|
|
999
|
+
return [];
|
|
1000
|
+
}
|
|
1001
|
+
return (editContext.webSocketMessages || [])
|
|
1002
|
+
.filter((message) => {
|
|
1003
|
+
if (!message?.type?.startsWith("agent:run:")) {
|
|
1004
|
+
return false;
|
|
1005
|
+
}
|
|
1006
|
+
if (isHeartbeatRunEventMessage(message)) {
|
|
1007
|
+
return false;
|
|
1008
|
+
}
|
|
1009
|
+
return getAgentRunMessageAgentId(message.payload) === normalizedAgentId;
|
|
1010
|
+
})
|
|
1011
|
+
.slice(-8)
|
|
1012
|
+
.map((message) => ({
|
|
1013
|
+
timestamp: message.timestamp,
|
|
1014
|
+
type: message.type,
|
|
1015
|
+
seq: getAgentRunMessageSeq(message.payload),
|
|
1016
|
+
detail: getAgentRunMessageDetail(message.type, message.payload),
|
|
1017
|
+
}));
|
|
1018
|
+
}, [currentAgentId, editContext?.webSocketMessages]);
|
|
1019
|
+
const appendToolUiEvent = useCallback((type, detail, seq) => {
|
|
1020
|
+
const timestamp = new Date().toISOString();
|
|
1021
|
+
setRecentToolUiEvents((prev) => {
|
|
1022
|
+
const next = [
|
|
1023
|
+
...prev,
|
|
1024
|
+
{
|
|
1025
|
+
timestamp,
|
|
1026
|
+
type,
|
|
1027
|
+
detail: detail || null,
|
|
1028
|
+
seq: seq ?? null,
|
|
1029
|
+
},
|
|
1030
|
+
];
|
|
1031
|
+
return next.slice(-40);
|
|
1032
|
+
});
|
|
1033
|
+
}, []);
|
|
625
1034
|
// Collect all pending tool calls for batch approval functionality
|
|
626
1035
|
const allPendingApprovals = useMemo(() => {
|
|
627
1036
|
const pending = [];
|
|
@@ -663,41 +1072,82 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
663
1072
|
}, [allPendingApprovals]);
|
|
664
1073
|
// Handle mode switch to autonomous
|
|
665
1074
|
const handleSwitchToAutonomous = useCallback(() => {
|
|
666
|
-
|
|
667
|
-
|
|
1075
|
+
const nextMode = "autonomous";
|
|
1076
|
+
setMode(nextMode);
|
|
1077
|
+
setAgentMetadata((prev) => ({
|
|
1078
|
+
...(prev ?? {}),
|
|
1079
|
+
mode: nextMode,
|
|
1080
|
+
}));
|
|
1081
|
+
setAgent((prev) => {
|
|
1082
|
+
if (!prev)
|
|
1083
|
+
return prev;
|
|
1084
|
+
return {
|
|
1085
|
+
...prev,
|
|
1086
|
+
mode: nextMode,
|
|
1087
|
+
};
|
|
1088
|
+
});
|
|
1089
|
+
}, [setAgent, setAgentMetadata]);
|
|
668
1090
|
const [resolvedPageName, setResolvedPageName] = useState(undefined);
|
|
669
1091
|
const [resolvedComponentName, setResolvedComponentName] = useState(undefined);
|
|
670
1092
|
const [resolvedFieldName, setResolvedFieldName] = useState(undefined);
|
|
671
1093
|
const [promptHistory, setPromptHistory] = useState(() => {
|
|
672
|
-
|
|
673
|
-
return JSON.parse(localStorage.getItem("editor.agent.promptHistory") || "[]");
|
|
674
|
-
}
|
|
675
|
-
return [];
|
|
1094
|
+
return (localStorageService.getItem("editor.agent.promptHistory") || []);
|
|
676
1095
|
});
|
|
677
1096
|
const [currentHistoryIndex, setCurrentHistoryIndex] = useState(-1);
|
|
678
1097
|
const [showPredefined, setShowPredefined] = useState(false);
|
|
679
1098
|
const [activeProfile, setActiveProfile] = useState(undefined);
|
|
680
1099
|
const [selectedModelId, setSelectedModelId] = useState(undefined);
|
|
1100
|
+
const normalizeAgentMode = (value) => {
|
|
1101
|
+
if (value === "autonomous" ||
|
|
1102
|
+
value === "read-only" ||
|
|
1103
|
+
value === "supervised") {
|
|
1104
|
+
return value;
|
|
1105
|
+
}
|
|
1106
|
+
return null;
|
|
1107
|
+
};
|
|
681
1108
|
const [mode, setMode] = useState("supervised");
|
|
682
1109
|
const [queuedPrompts, setQueuedPrompts] = useState([]);
|
|
683
|
-
const
|
|
1110
|
+
const [expandedQueuedTriggerIds, setExpandedQueuedTriggerIds] = useState({});
|
|
684
1111
|
const [contextPanelsActiveTab, setContextPanelsActiveTab] = useState(0);
|
|
685
1112
|
const [hiddenContextPanelTabIds, setHiddenContextPanelTabIds] = useState(new Set());
|
|
686
1113
|
const [showCostAndAgent, setShowCostAndAgent] = useState(false);
|
|
1114
|
+
const [showAgentSettings, setShowAgentSettings] = useState(false);
|
|
1115
|
+
const [showSkillPicker, setShowSkillPicker] = useState(false);
|
|
1116
|
+
const [availableSkills, setAvailableSkills] = useState([]);
|
|
1117
|
+
const [skillRootIds, setSkillRootIds] = useState([]);
|
|
1118
|
+
const [selectableTemplateIds, setSelectableTemplateIds] = useState([]);
|
|
1119
|
+
const [skillsLoading, setSkillsLoading] = useState(false);
|
|
1120
|
+
const [skillsError, setSkillsError] = useState(null);
|
|
1121
|
+
const [skillActionError, setSkillActionError] = useState(null);
|
|
1122
|
+
const [triggerSubscriptions, setTriggerSubscriptions] = useState([]);
|
|
1123
|
+
const [availableTools, setAvailableTools] = useState([]);
|
|
1124
|
+
const [operationAllowances, setOperationAllowances] = useState({
|
|
1125
|
+
sitecore: [],
|
|
1126
|
+
filesystem: [],
|
|
1127
|
+
});
|
|
1128
|
+
const [triggerSubscriptionsLoading, setTriggerSubscriptionsLoading] = useState(false);
|
|
1129
|
+
const [availableToolsLoading, setAvailableToolsLoading] = useState(false);
|
|
1130
|
+
const [operationAllowancesLoading, setOperationAllowancesLoading] = useState(false);
|
|
1131
|
+
const [triggerSubscriptionsError, setTriggerSubscriptionsError] = useState(null);
|
|
1132
|
+
const [availableToolsError, setAvailableToolsError] = useState(null);
|
|
1133
|
+
const [operationAllowancesError, setOperationAllowancesError] = useState(null);
|
|
1134
|
+
const [toolsSectionExpanded, setToolsSectionExpanded] = useState(false);
|
|
1135
|
+
const [allowancesSectionExpanded, setAllowancesSectionExpanded] = useState(false);
|
|
1136
|
+
const [subscribedTriggersSectionExpanded, setSubscribedTriggersSectionExpanded,] = useState(false);
|
|
1137
|
+
const isPersistedAgent = !!agent?.userId;
|
|
1138
|
+
const isLocalOnlyDraftAgent = agent?.status === "new" && !isPersistedAgent;
|
|
687
1139
|
const hasSpawnedAgents = useMemo(() => {
|
|
688
1140
|
if (!agentMetadata)
|
|
689
1141
|
return false;
|
|
690
|
-
const childAgents = agentMetadata?.
|
|
691
|
-
agentMetadata?.childAgents;
|
|
1142
|
+
const childAgents = agentMetadata?.childAgents;
|
|
692
1143
|
if (!Array.isArray(childAgents) || childAgents.length === 0)
|
|
693
1144
|
return false;
|
|
694
|
-
return childAgents.some((a) => a != null && typeof a === "object" &&
|
|
1145
|
+
return childAgents.some((a) => a != null && typeof a === "object" && a.agentId);
|
|
695
1146
|
}, [agentMetadata]);
|
|
696
1147
|
const hasTodoContent = useMemo(() => {
|
|
697
1148
|
const metadataTodos = (() => {
|
|
698
1149
|
try {
|
|
699
|
-
const todoList = agentMetadata?.additionalData?.todoList
|
|
700
|
-
agentMetadata?.todoList;
|
|
1150
|
+
const todoList = agentMetadata?.additionalData?.todoList;
|
|
701
1151
|
if (todoList?.items && Array.isArray(todoList.items)) {
|
|
702
1152
|
const raw = todoList.items.filter((item) => item?.title || item?.text || item?.label || item?.task);
|
|
703
1153
|
return raw.length > 0;
|
|
@@ -719,7 +1169,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
719
1169
|
return isUpdating;
|
|
720
1170
|
}, [agentMetadata, messages]);
|
|
721
1171
|
const prevAgentIdRef = useRef(undefined);
|
|
722
|
-
const
|
|
1172
|
+
const hasHistoryContent = agentOperations.length > 0;
|
|
723
1173
|
useEffect(() => {
|
|
724
1174
|
const currentId = agent?.id;
|
|
725
1175
|
if (prevAgentIdRef.current !== currentId) {
|
|
@@ -735,47 +1185,37 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
735
1185
|
}
|
|
736
1186
|
}, [agent?.id]);
|
|
737
1187
|
useEffect(() => {
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
setHasHistoryContent(true);
|
|
1188
|
+
let active = true;
|
|
1189
|
+
const loadSkills = async () => {
|
|
1190
|
+
try {
|
|
1191
|
+
setSkillsLoading(true);
|
|
1192
|
+
setSkillsError(null);
|
|
1193
|
+
const catalog = await getAgentSkillCatalog(false);
|
|
1194
|
+
if (active) {
|
|
1195
|
+
setAvailableSkills(catalog.skills.filter((s) => !s.disabled));
|
|
1196
|
+
setSkillRootIds(catalog.rootIds);
|
|
1197
|
+
setSelectableTemplateIds(catalog.selectableTemplateIds);
|
|
1198
|
+
}
|
|
750
1199
|
}
|
|
751
|
-
|
|
752
|
-
|
|
1200
|
+
catch (e) {
|
|
1201
|
+
if (active) {
|
|
1202
|
+
setSkillsError(e?.message || "Failed to load skills");
|
|
1203
|
+
setAvailableSkills([]);
|
|
1204
|
+
setSkillRootIds([]);
|
|
1205
|
+
setSelectableTemplateIds([]);
|
|
1206
|
+
}
|
|
753
1207
|
}
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
1208
|
+
finally {
|
|
1209
|
+
if (active) {
|
|
1210
|
+
setSkillsLoading(false);
|
|
1211
|
+
}
|
|
1212
|
+
}
|
|
1213
|
+
};
|
|
1214
|
+
void loadSkills();
|
|
759
1215
|
return () => {
|
|
760
|
-
|
|
1216
|
+
active = false;
|
|
761
1217
|
};
|
|
762
|
-
}, [
|
|
763
|
-
const agentIdForHistoryRef = useRef(agent?.id);
|
|
764
|
-
agentIdForHistoryRef.current = agent?.id;
|
|
765
|
-
useEffect(() => {
|
|
766
|
-
if (!agent?.id || !editContext?.addSocketMessageListener)
|
|
767
|
-
return;
|
|
768
|
-
const unsubscribe = editContext.addSocketMessageListener((message) => {
|
|
769
|
-
if (message.type !== "edit-operation")
|
|
770
|
-
return;
|
|
771
|
-
const op = message.payload;
|
|
772
|
-
const operationAgentId = op.agentId || op.user?.agentId;
|
|
773
|
-
if (operationAgentId === agentIdForHistoryRef.current) {
|
|
774
|
-
setHasHistoryContent(true);
|
|
775
|
-
}
|
|
776
|
-
});
|
|
777
|
-
return () => unsubscribe();
|
|
778
|
-
}, [agent?.id, editContext?.addSocketMessageListener]);
|
|
1218
|
+
}, []);
|
|
779
1219
|
const modeOptions = useMemo(() => [
|
|
780
1220
|
{
|
|
781
1221
|
value: "supervised",
|
|
@@ -810,6 +1250,239 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
810
1250
|
value: m.id,
|
|
811
1251
|
label: m.name,
|
|
812
1252
|
})) || []).sort((a, b) => a.label.localeCompare(b.label)), [activeProfile]);
|
|
1253
|
+
const metadataSelectedSkillIds = useMemo(() => {
|
|
1254
|
+
const rawSkillIds = agentMetadata?.additionalData?.skillIds ?? [];
|
|
1255
|
+
if (!Array.isArray(rawSkillIds)) {
|
|
1256
|
+
return [];
|
|
1257
|
+
}
|
|
1258
|
+
return rawSkillIds
|
|
1259
|
+
.map((x) => String(x || "").trim())
|
|
1260
|
+
.filter((x) => x.length > 0);
|
|
1261
|
+
}, [agentMetadata]);
|
|
1262
|
+
const backendAssignedSkillIds = useMemo(() => {
|
|
1263
|
+
const rawSkillIds = agent?.assignedSkillIds ?? [];
|
|
1264
|
+
if (!Array.isArray(rawSkillIds)) {
|
|
1265
|
+
return [];
|
|
1266
|
+
}
|
|
1267
|
+
return rawSkillIds
|
|
1268
|
+
.map((x) => String(x || "").trim())
|
|
1269
|
+
.filter((x) => x.length > 0);
|
|
1270
|
+
}, [agent]);
|
|
1271
|
+
const preloadedSkillIds = useMemo(() => {
|
|
1272
|
+
const rawSkillIds = activeProfile?.preloadSkills ?? [];
|
|
1273
|
+
if (!Array.isArray(rawSkillIds)) {
|
|
1274
|
+
return [];
|
|
1275
|
+
}
|
|
1276
|
+
return rawSkillIds
|
|
1277
|
+
.map((skill) => String(skill?.id || "").trim())
|
|
1278
|
+
.filter((id) => id.length > 0);
|
|
1279
|
+
}, [activeProfile?.preloadSkills]);
|
|
1280
|
+
const autoAssignedSkillIds = useMemo(() => {
|
|
1281
|
+
const preloadedIdSet = new Set(preloadedSkillIds.map((id) => id.toLowerCase()));
|
|
1282
|
+
const all = isLocalOnlyDraftAgent
|
|
1283
|
+
? preloadedSkillIds
|
|
1284
|
+
: backendAssignedSkillIds.filter((id) => preloadedIdSet.has(id.toLowerCase()));
|
|
1285
|
+
const seen = new Set();
|
|
1286
|
+
const unique = [];
|
|
1287
|
+
for (const id of all) {
|
|
1288
|
+
const key = id.toLowerCase();
|
|
1289
|
+
if (seen.has(key))
|
|
1290
|
+
continue;
|
|
1291
|
+
seen.add(key);
|
|
1292
|
+
unique.push(id);
|
|
1293
|
+
}
|
|
1294
|
+
return unique;
|
|
1295
|
+
}, [backendAssignedSkillIds, isLocalOnlyDraftAgent, preloadedSkillIds]);
|
|
1296
|
+
const selectedSkillIds = useMemo(() => {
|
|
1297
|
+
const all = [
|
|
1298
|
+
...autoAssignedSkillIds,
|
|
1299
|
+
...backendAssignedSkillIds,
|
|
1300
|
+
...metadataSelectedSkillIds,
|
|
1301
|
+
];
|
|
1302
|
+
const seen = new Set();
|
|
1303
|
+
const unique = [];
|
|
1304
|
+
for (const id of all) {
|
|
1305
|
+
const key = id.toLowerCase();
|
|
1306
|
+
if (seen.has(key))
|
|
1307
|
+
continue;
|
|
1308
|
+
seen.add(key);
|
|
1309
|
+
unique.push(id);
|
|
1310
|
+
}
|
|
1311
|
+
return unique;
|
|
1312
|
+
}, [autoAssignedSkillIds, backendAssignedSkillIds, metadataSelectedSkillIds]);
|
|
1313
|
+
useEffect(() => {
|
|
1314
|
+
let active = true;
|
|
1315
|
+
if (!showAgentSettings) {
|
|
1316
|
+
return () => {
|
|
1317
|
+
active = false;
|
|
1318
|
+
};
|
|
1319
|
+
}
|
|
1320
|
+
if (!agent?.id || isLocalOnlyDraftAgent) {
|
|
1321
|
+
setTriggerSubscriptions([]);
|
|
1322
|
+
setTriggerSubscriptionsLoading(false);
|
|
1323
|
+
setTriggerSubscriptionsError(null);
|
|
1324
|
+
setAvailableTools([]);
|
|
1325
|
+
setAvailableToolsLoading(false);
|
|
1326
|
+
setAvailableToolsError(null);
|
|
1327
|
+
setOperationAllowances({ sitecore: [], filesystem: [] });
|
|
1328
|
+
setOperationAllowancesLoading(false);
|
|
1329
|
+
setOperationAllowancesError(null);
|
|
1330
|
+
return () => {
|
|
1331
|
+
active = false;
|
|
1332
|
+
};
|
|
1333
|
+
}
|
|
1334
|
+
const loadTriggerSubscriptions = async () => {
|
|
1335
|
+
try {
|
|
1336
|
+
setTriggerSubscriptionsLoading(true);
|
|
1337
|
+
setTriggerSubscriptionsError(null);
|
|
1338
|
+
const subscriptions = await getAgentTriggerSubscriptions(agent.id);
|
|
1339
|
+
if (active) {
|
|
1340
|
+
setTriggerSubscriptions(subscriptions);
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
catch (e) {
|
|
1344
|
+
if (active) {
|
|
1345
|
+
setTriggerSubscriptionsError(e?.message || "Failed to load trigger subscriptions");
|
|
1346
|
+
}
|
|
1347
|
+
}
|
|
1348
|
+
finally {
|
|
1349
|
+
if (active) {
|
|
1350
|
+
setTriggerSubscriptionsLoading(false);
|
|
1351
|
+
}
|
|
1352
|
+
}
|
|
1353
|
+
};
|
|
1354
|
+
const loadOperationAllowances = async () => {
|
|
1355
|
+
try {
|
|
1356
|
+
setOperationAllowancesLoading(true);
|
|
1357
|
+
setOperationAllowancesError(null);
|
|
1358
|
+
const allowances = await getAgentOperationAllowances(agent.id);
|
|
1359
|
+
if (active) {
|
|
1360
|
+
setOperationAllowances(allowances);
|
|
1361
|
+
}
|
|
1362
|
+
}
|
|
1363
|
+
catch (e) {
|
|
1364
|
+
if (active) {
|
|
1365
|
+
setOperationAllowancesError(e?.message || "Failed to load allowances");
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
finally {
|
|
1369
|
+
if (active) {
|
|
1370
|
+
setOperationAllowancesLoading(false);
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1373
|
+
};
|
|
1374
|
+
const loadAvailableTools = async () => {
|
|
1375
|
+
try {
|
|
1376
|
+
setAvailableToolsLoading(true);
|
|
1377
|
+
setAvailableToolsError(null);
|
|
1378
|
+
const tools = await getAgentAvailableTools(agent.id);
|
|
1379
|
+
if (active) {
|
|
1380
|
+
setAvailableTools(tools);
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
catch (e) {
|
|
1384
|
+
if (active) {
|
|
1385
|
+
setAvailableToolsError(e?.message || "Failed to load available tools");
|
|
1386
|
+
}
|
|
1387
|
+
}
|
|
1388
|
+
finally {
|
|
1389
|
+
if (active) {
|
|
1390
|
+
setAvailableToolsLoading(false);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
};
|
|
1394
|
+
void loadTriggerSubscriptions();
|
|
1395
|
+
void loadAvailableTools();
|
|
1396
|
+
void loadOperationAllowances();
|
|
1397
|
+
return () => {
|
|
1398
|
+
active = false;
|
|
1399
|
+
};
|
|
1400
|
+
}, [
|
|
1401
|
+
showAgentSettings,
|
|
1402
|
+
agent?.id,
|
|
1403
|
+
agent?.status,
|
|
1404
|
+
agent?.userId,
|
|
1405
|
+
agent?.profileId,
|
|
1406
|
+
mode,
|
|
1407
|
+
selectedSkillIds,
|
|
1408
|
+
]);
|
|
1409
|
+
const activeTriggerSubscriptions = useMemo(() => triggerSubscriptions.filter((sub) => sub.isActive), [triggerSubscriptions]);
|
|
1410
|
+
const allowanceGroups = useMemo(() => [
|
|
1411
|
+
{
|
|
1412
|
+
key: "sitecore",
|
|
1413
|
+
label: "Sitecore",
|
|
1414
|
+
rows: operationAllowances.sitecore,
|
|
1415
|
+
},
|
|
1416
|
+
{
|
|
1417
|
+
key: "filesystem",
|
|
1418
|
+
label: "Filesystem",
|
|
1419
|
+
rows: operationAllowances.filesystem,
|
|
1420
|
+
},
|
|
1421
|
+
], [operationAllowances]);
|
|
1422
|
+
const hasAnyAllowances = useMemo(() => operationAllowances.sitecore.length > 0 ||
|
|
1423
|
+
operationAllowances.filesystem.length > 0, [operationAllowances]);
|
|
1424
|
+
const allowancesTotalCount = useMemo(() => operationAllowances.sitecore.length +
|
|
1425
|
+
operationAllowances.filesystem.length, [operationAllowances]);
|
|
1426
|
+
const listedProfileSkillIdSet = useMemo(() => {
|
|
1427
|
+
const ids = [
|
|
1428
|
+
...(activeProfile?.availableSkills ?? []),
|
|
1429
|
+
...(activeProfile?.allowedSkills ?? []),
|
|
1430
|
+
]
|
|
1431
|
+
.map((skill) => String(skill?.id || "").toLowerCase())
|
|
1432
|
+
.filter((id) => id.length > 0);
|
|
1433
|
+
return new Set(ids);
|
|
1434
|
+
}, [activeProfile?.availableSkills, activeProfile?.allowedSkills]);
|
|
1435
|
+
const profileFilteredSkills = useMemo(() => {
|
|
1436
|
+
if (listedProfileSkillIdSet.size === 0) {
|
|
1437
|
+
return [];
|
|
1438
|
+
}
|
|
1439
|
+
return availableSkills.filter((skill) => listedProfileSkillIdSet.has(skill.id.toLowerCase()));
|
|
1440
|
+
}, [availableSkills, listedProfileSkillIdSet]);
|
|
1441
|
+
const profileFilteredSkillIdSet = useMemo(() => new Set(profileFilteredSkills.map((s) => s.id.toLowerCase())), [profileFilteredSkills]);
|
|
1442
|
+
const manuallyAssignableSkillIdSet = useMemo(() => new Set((activeProfile?.allowedSkills ?? [])
|
|
1443
|
+
.map((skill) => String(skill?.id || "").toLowerCase())
|
|
1444
|
+
.filter((id) => id.length > 0)), [activeProfile?.allowedSkills]);
|
|
1445
|
+
const selectableTemplateIdSet = useMemo(() => new Set(selectableTemplateIds.map((id) => id.toLowerCase())), [selectableTemplateIds]);
|
|
1446
|
+
const selectedSkills = useMemo(() => selectedSkillIds
|
|
1447
|
+
.map((id) => availableSkills.find((s) => s.id.toLowerCase() === id.toLowerCase()))
|
|
1448
|
+
.filter((s) => !!s), [availableSkills, selectedSkillIds]);
|
|
1449
|
+
const selectedSkillSet = useMemo(() => new Set(selectedSkillIds.map((id) => id.toLowerCase())), [selectedSkillIds]);
|
|
1450
|
+
const autoAssignedSkillSet = useMemo(() => new Set(autoAssignedSkillIds.map((id) => id.toLowerCase())), [autoAssignedSkillIds]);
|
|
1451
|
+
const previewAvailableTools = useMemo(() => {
|
|
1452
|
+
const baseToolNames = mode === "read-only"
|
|
1453
|
+
? (activeProfile?.readOnlyToolNames ?? [])
|
|
1454
|
+
: (activeProfile?.allowedToolNames ?? []);
|
|
1455
|
+
const toolNames = new Set();
|
|
1456
|
+
for (const toolName of baseToolNames) {
|
|
1457
|
+
const normalized = String(toolName || "").trim();
|
|
1458
|
+
if (normalized) {
|
|
1459
|
+
toolNames.add(normalized);
|
|
1460
|
+
}
|
|
1461
|
+
}
|
|
1462
|
+
for (const skill of selectedSkills) {
|
|
1463
|
+
for (const toolName of skill.additionalAllowedTools ?? []) {
|
|
1464
|
+
const normalized = String(toolName || "").trim();
|
|
1465
|
+
if (normalized) {
|
|
1466
|
+
toolNames.add(normalized);
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
}
|
|
1470
|
+
return Array.from(toolNames).sort((a, b) => a.localeCompare(b));
|
|
1471
|
+
}, [
|
|
1472
|
+
activeProfile?.allowedToolNames,
|
|
1473
|
+
activeProfile?.readOnlyToolNames,
|
|
1474
|
+
mode,
|
|
1475
|
+
selectedSkills,
|
|
1476
|
+
]);
|
|
1477
|
+
const displayedAvailableTools = isLocalOnlyDraftAgent
|
|
1478
|
+
? previewAvailableTools
|
|
1479
|
+
: availableTools;
|
|
1480
|
+
const displayedAvailableToolsLoading = isLocalOnlyDraftAgent
|
|
1481
|
+
? false
|
|
1482
|
+
: availableToolsLoading;
|
|
1483
|
+
const displayedAvailableToolsError = isLocalOnlyDraftAgent
|
|
1484
|
+
? null
|
|
1485
|
+
: availableToolsError;
|
|
813
1486
|
// Remove deprecated cost limit fields from metadata to avoid confusion with agent/profile settings
|
|
814
1487
|
const sanitizeAgentMetadata = useCallback((meta) => {
|
|
815
1488
|
try {
|
|
@@ -834,28 +1507,239 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
834
1507
|
return meta;
|
|
835
1508
|
}
|
|
836
1509
|
}, []);
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1510
|
+
const getSkillActionErrorMessage = useCallback((error) => {
|
|
1511
|
+
const message = error instanceof Error && error.message.trim()
|
|
1512
|
+
? error.message.trim()
|
|
1513
|
+
: "Failed to update skill";
|
|
1514
|
+
if (message.includes("Skill is not available for this agent profile")) {
|
|
1515
|
+
return "This skill cannot be added for the current agent profile.";
|
|
1516
|
+
}
|
|
1517
|
+
return message;
|
|
1518
|
+
}, []);
|
|
1519
|
+
const clearLegacySelectedSkillsFromMetadata = useCallback(async () => {
|
|
1520
|
+
const legacySkillIds = metadataSelectedSkillIds;
|
|
1521
|
+
if (!agent?.id || legacySkillIds.length === 0) {
|
|
1522
|
+
return;
|
|
1523
|
+
}
|
|
1524
|
+
const current = agentMetadata || {};
|
|
1525
|
+
const currentAdditionalData = current.additionalData ||
|
|
1526
|
+
{};
|
|
1527
|
+
const nextAdditionalData = Object.fromEntries(Object.entries(currentAdditionalData).filter(([key]) => key.toLowerCase() !== "skillids"));
|
|
1528
|
+
const next = {
|
|
1529
|
+
...current,
|
|
849
1530
|
};
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1531
|
+
if (Object.keys(nextAdditionalData).length > 0) {
|
|
1532
|
+
next.additionalData = nextAdditionalData;
|
|
1533
|
+
}
|
|
1534
|
+
else {
|
|
1535
|
+
delete next.additionalData;
|
|
1536
|
+
}
|
|
1537
|
+
try {
|
|
1538
|
+
await updateAgentContext(agent.id, next);
|
|
1539
|
+
setAgentMetadata(next);
|
|
1540
|
+
setAgent((prev) => prev ? { ...prev, agentContext: JSON.stringify(next) } : prev);
|
|
1541
|
+
}
|
|
1542
|
+
catch (e) {
|
|
1543
|
+
console.error("Failed to clear legacy selected skills", e);
|
|
1544
|
+
}
|
|
1545
|
+
}, [
|
|
1546
|
+
agent?.id,
|
|
1547
|
+
agentMetadata,
|
|
1548
|
+
metadataSelectedSkillIds,
|
|
1549
|
+
setAgent,
|
|
1550
|
+
setAgentMetadata,
|
|
1551
|
+
]);
|
|
1552
|
+
const parsePersistedAgentContext = (contextJson) => {
|
|
1553
|
+
try {
|
|
1554
|
+
if (!contextJson)
|
|
1555
|
+
return null;
|
|
1556
|
+
const parsedContext = JSON.parse(contextJson);
|
|
1557
|
+
if (!parsedContext || typeof parsedContext !== "object") {
|
|
1558
|
+
return null;
|
|
1559
|
+
}
|
|
1560
|
+
return sanitizeAgentMetadata(parsedContext);
|
|
1561
|
+
}
|
|
1562
|
+
catch {
|
|
1563
|
+
return null;
|
|
1564
|
+
}
|
|
1565
|
+
};
|
|
1566
|
+
const buildDraftPersistenceContext = () => {
|
|
1567
|
+
let effectiveContext = agentMetadata;
|
|
1568
|
+
try {
|
|
1569
|
+
const normalizedAgentProfileId = agent?.profileId?.toLowerCase();
|
|
1570
|
+
const profile = activeProfile ||
|
|
1571
|
+
profiles.find((p) => p.id?.toLowerCase() === normalizedAgentProfileId);
|
|
1572
|
+
const isLiveMode = profile?.editorContextMode === "live";
|
|
1573
|
+
if (isLiveMode && typeof buildCurrentContext === "function") {
|
|
1574
|
+
const freshContext = buildCurrentContext();
|
|
1575
|
+
if (freshContext) {
|
|
1576
|
+
effectiveContext = {
|
|
1577
|
+
...(agentMetadata || {}),
|
|
1578
|
+
items: freshContext.items,
|
|
1579
|
+
currentItemId: freshContext.currentItemId,
|
|
1580
|
+
components: freshContext.components,
|
|
1581
|
+
field: freshContext.field,
|
|
1582
|
+
activeWorkspace: freshContext.activeWorkspace,
|
|
1583
|
+
hasPageLoaded: freshContext.hasPageLoaded,
|
|
1584
|
+
openSidebars: freshContext.openSidebars,
|
|
1585
|
+
};
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1588
|
+
}
|
|
1589
|
+
catch (e) {
|
|
1590
|
+
console.warn("[AgentTerminal] Failed to compute draft context:", e);
|
|
1591
|
+
}
|
|
1592
|
+
return sanitizeAgentMetadata(effectiveContext || null);
|
|
1593
|
+
};
|
|
1594
|
+
const ensureDraftAgentPersisted = async () => {
|
|
1595
|
+
if (!agent?.id) {
|
|
1596
|
+
throw new Error("Agent not ready. Please try again.");
|
|
1597
|
+
}
|
|
1598
|
+
if (!isLocalOnlyDraftAgent) {
|
|
1599
|
+
return agent;
|
|
1600
|
+
}
|
|
1601
|
+
const requestSettings = getPendingRequestSettings();
|
|
1602
|
+
if (!requestSettings.profileId) {
|
|
1603
|
+
throw new Error("Select an agent profile before adding a skill.");
|
|
1604
|
+
}
|
|
1605
|
+
const effectiveContext = buildDraftPersistenceContext();
|
|
1606
|
+
const persistedAgent = await persistDraftAgent({
|
|
1607
|
+
agentId: agent.id,
|
|
1608
|
+
sessionId: editContext?.sessionId || undefined,
|
|
1609
|
+
profileId: requestSettings.profileId,
|
|
1610
|
+
mode: requestSettings.mode,
|
|
1611
|
+
model: requestSettings.modelId || undefined,
|
|
1612
|
+
name: agent.name,
|
|
1613
|
+
context: effectiveContext,
|
|
1614
|
+
});
|
|
1615
|
+
pendingSettingsRef.current = null;
|
|
1616
|
+
const persistedMetadata = parsePersistedAgentContext(persistedAgent.agentContext) ??
|
|
1617
|
+
effectiveContext;
|
|
1618
|
+
setAgentMetadata(persistedMetadata ? { ...persistedMetadata } : null);
|
|
1619
|
+
setAgent((prev) => {
|
|
1620
|
+
const next = {
|
|
1621
|
+
...(prev || {}),
|
|
1622
|
+
...persistedAgent,
|
|
1623
|
+
};
|
|
1624
|
+
if (prev?.messages?.length && !persistedAgent.messages?.length) {
|
|
1625
|
+
next.messages = prev.messages;
|
|
1626
|
+
}
|
|
1627
|
+
return next;
|
|
1628
|
+
});
|
|
1629
|
+
onAgentUpdate?.(persistedAgent);
|
|
1630
|
+
return persistedAgent;
|
|
1631
|
+
};
|
|
1632
|
+
const handleAddSkill = useCallback(async (skillId) => {
|
|
1633
|
+
if (selectedSkillSet.has(skillId.toLowerCase()))
|
|
1634
|
+
return;
|
|
1635
|
+
if (!agent?.id)
|
|
1636
|
+
return;
|
|
1637
|
+
try {
|
|
1638
|
+
setSkillActionError(null);
|
|
1639
|
+
const persistedAgent = await ensureDraftAgentPersisted();
|
|
1640
|
+
await assignAgentSkill({ agentId: persistedAgent.id, skillId });
|
|
1641
|
+
setAgent((prev) => {
|
|
1642
|
+
if (!prev)
|
|
1643
|
+
return prev;
|
|
1644
|
+
const currentAssigned = Array.isArray(prev.assignedSkillIds)
|
|
1645
|
+
? prev.assignedSkillIds
|
|
1646
|
+
: [];
|
|
1647
|
+
if (currentAssigned.some((existingId) => String(existingId).toLowerCase() === skillId.toLowerCase())) {
|
|
1648
|
+
return prev;
|
|
1649
|
+
}
|
|
1650
|
+
return {
|
|
1651
|
+
...prev,
|
|
1652
|
+
assignedSkillIds: [...currentAssigned, skillId],
|
|
1653
|
+
};
|
|
1654
|
+
});
|
|
1655
|
+
await clearLegacySelectedSkillsFromMetadata();
|
|
1656
|
+
return true;
|
|
1657
|
+
}
|
|
1658
|
+
catch (e) {
|
|
1659
|
+
setSkillActionError(getSkillActionErrorMessage(e));
|
|
1660
|
+
return false;
|
|
1661
|
+
}
|
|
1662
|
+
}, [
|
|
1663
|
+
agent?.id,
|
|
1664
|
+
assignAgentSkill,
|
|
1665
|
+
clearLegacySelectedSkillsFromMetadata,
|
|
1666
|
+
ensureDraftAgentPersisted,
|
|
1667
|
+
getSkillActionErrorMessage,
|
|
1668
|
+
setAgent,
|
|
1669
|
+
selectedSkillSet,
|
|
1670
|
+
]);
|
|
1671
|
+
const handleRemoveSkill = useCallback(async (skillId) => {
|
|
1672
|
+
if (!agent?.id)
|
|
1673
|
+
return;
|
|
1674
|
+
try {
|
|
1675
|
+
setSkillActionError(null);
|
|
1676
|
+
await revokeAgentSkill({ agentId: agent.id, skillId });
|
|
1677
|
+
setAgent((prev) => {
|
|
1678
|
+
if (!prev)
|
|
1679
|
+
return prev;
|
|
1680
|
+
const currentAssigned = Array.isArray(prev.assignedSkillIds)
|
|
1681
|
+
? prev.assignedSkillIds
|
|
1682
|
+
: [];
|
|
1683
|
+
return {
|
|
1684
|
+
...prev,
|
|
1685
|
+
assignedSkillIds: currentAssigned.filter((existingId) => String(existingId).toLowerCase() !== skillId.toLowerCase()),
|
|
1686
|
+
};
|
|
1687
|
+
});
|
|
1688
|
+
await clearLegacySelectedSkillsFromMetadata();
|
|
1689
|
+
}
|
|
1690
|
+
catch (e) {
|
|
1691
|
+
setSkillActionError(getSkillActionErrorMessage(e));
|
|
1692
|
+
}
|
|
1693
|
+
}, [
|
|
1694
|
+
agent?.id,
|
|
1695
|
+
clearLegacySelectedSkillsFromMetadata,
|
|
1696
|
+
getSkillActionErrorMessage,
|
|
1697
|
+
revokeAgentSkill,
|
|
1698
|
+
setAgent,
|
|
1699
|
+
]);
|
|
1700
|
+
const handleOpenProfileSettings = useCallback(async () => {
|
|
1701
|
+
if (!editContext || !activeProfile?.id)
|
|
1702
|
+
return;
|
|
1703
|
+
const lang = editContext.currentItemDescriptor?.language || "en";
|
|
1704
|
+
await editContext.loadItem({
|
|
1705
|
+
id: activeProfile.id,
|
|
1706
|
+
language: lang,
|
|
1707
|
+
version: 0,
|
|
1708
|
+
});
|
|
1709
|
+
editContext.switchWorkspace("editor");
|
|
1710
|
+
}, [editContext, activeProfile?.id]);
|
|
1711
|
+
const handleEditProfileSideBySide = useCallback(async () => {
|
|
1712
|
+
if (!editContext || !activeProfile?.id)
|
|
1713
|
+
return;
|
|
1714
|
+
const lang = editContext.currentItemDescriptor?.language || "en";
|
|
1715
|
+
const profileItem = {
|
|
1716
|
+
id: activeProfile.id,
|
|
1717
|
+
language: lang,
|
|
1718
|
+
version: 0,
|
|
855
1719
|
};
|
|
856
|
-
|
|
1720
|
+
if (editContext.workspaceId === "agents") {
|
|
1721
|
+
editContext.setShowAgentsWorkspaceEditor(true);
|
|
1722
|
+
await editContext.loadItem(profileItem);
|
|
1723
|
+
return;
|
|
1724
|
+
}
|
|
1725
|
+
await editContext.loadItem(profileItem, {
|
|
1726
|
+
openInNewSlot: true,
|
|
1727
|
+
});
|
|
1728
|
+
editContext.switchWorkspace("editor");
|
|
1729
|
+
}, [editContext, activeProfile?.id]);
|
|
1730
|
+
const handleOpenSkillItem = useCallback(async (skillId) => {
|
|
1731
|
+
if (!editContext || !skillId)
|
|
1732
|
+
return;
|
|
1733
|
+
const lang = editContext.currentItemDescriptor?.language || "en";
|
|
1734
|
+
await editContext.loadItem({
|
|
1735
|
+
id: skillId,
|
|
1736
|
+
language: lang,
|
|
1737
|
+
version: 0,
|
|
1738
|
+
});
|
|
1739
|
+
editContext.switchWorkspace("editor");
|
|
1740
|
+
}, [editContext]);
|
|
857
1741
|
useEffect(() => {
|
|
858
|
-
|
|
1742
|
+
localStorageService.setItem("editor.agent.promptHistory", promptHistory);
|
|
859
1743
|
}, [promptHistory]);
|
|
860
1744
|
useEffect(() => {
|
|
861
1745
|
// Keep messagesRef synchronized with messages state
|
|
@@ -867,6 +1751,32 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
867
1751
|
const [liveTotals, setLiveTotals] = useState(null);
|
|
868
1752
|
// Context window status from backend (extracted from delta cost object)
|
|
869
1753
|
const [contextWindowStatus, setContextWindowStatus] = useState(null);
|
|
1754
|
+
const effectiveModelName = useMemo(() => {
|
|
1755
|
+
const fromContextWindow = contextWindowStatus?.model?.trim();
|
|
1756
|
+
if (fromContextWindow)
|
|
1757
|
+
return fromContextWindow;
|
|
1758
|
+
const fromAgent = agent?.model?.trim();
|
|
1759
|
+
if (fromAgent)
|
|
1760
|
+
return fromAgent;
|
|
1761
|
+
const models = activeProfile?.models || [];
|
|
1762
|
+
const selectedModelName = selectedModelId
|
|
1763
|
+
? models.find((m) => m.id === selectedModelId)?.name?.trim()
|
|
1764
|
+
: undefined;
|
|
1765
|
+
if (selectedModelName)
|
|
1766
|
+
return selectedModelName;
|
|
1767
|
+
const defaultModelName = activeProfile?.defaultModelId
|
|
1768
|
+
? models.find((m) => m.id === activeProfile.defaultModelId)?.name?.trim()
|
|
1769
|
+
: undefined;
|
|
1770
|
+
if (defaultModelName)
|
|
1771
|
+
return defaultModelName;
|
|
1772
|
+
return models[0]?.name?.trim() || undefined;
|
|
1773
|
+
}, [
|
|
1774
|
+
contextWindowStatus?.model,
|
|
1775
|
+
agent?.model,
|
|
1776
|
+
activeProfile?.models,
|
|
1777
|
+
activeProfile?.defaultModelId,
|
|
1778
|
+
selectedModelId,
|
|
1779
|
+
]);
|
|
870
1780
|
// Flag to track when we should create a new message
|
|
871
1781
|
const shouldCreateNewMessage = useRef(false);
|
|
872
1782
|
// Keep a ref to the current messages for immediate access
|
|
@@ -877,12 +1787,17 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
877
1787
|
const placeholderInputRef = useRef(null);
|
|
878
1788
|
const promptPlaceholderInputRef = useRef(null);
|
|
879
1789
|
const messagesContainerRef = useRef(null);
|
|
1790
|
+
const inlineDialogContainerRef = useRef(null);
|
|
880
1791
|
const [shouldAutoScroll, setShouldAutoScroll] = useState(true);
|
|
1792
|
+
const [recentToolUiEvents, setRecentToolUiEvents] = useState([]);
|
|
881
1793
|
// WebSocket subscription state for agent streaming
|
|
882
1794
|
const seenMessageIdsRef = useRef(new Set());
|
|
883
1795
|
const lastSeqRef = useRef(0);
|
|
884
1796
|
const subscribedAgentIdRef = useRef(null);
|
|
885
|
-
|
|
1797
|
+
const reconcileServerStateInFlightRef = useRef(false);
|
|
1798
|
+
const toolCallFirstSeenAtRef = useRef({});
|
|
1799
|
+
const pendingToolCompletionTimersRef = useRef({});
|
|
1800
|
+
// Cache mode/model/profile changes made while the agent is still "new" (not yet persisted)
|
|
886
1801
|
const pendingSettingsRef = useRef(null);
|
|
887
1802
|
// Track whether textarea should maintain focus during re-renders
|
|
888
1803
|
const shouldMaintainFocusRef = useRef(false);
|
|
@@ -904,11 +1819,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
904
1819
|
setIsVoiceSupported(!!SR);
|
|
905
1820
|
if (SR === undefined) {
|
|
906
1821
|
setIsVoiceDisabled(true);
|
|
907
|
-
|
|
1822
|
+
localStorageService.setString(VOICE_DISABLED_KEY, "true");
|
|
908
1823
|
return;
|
|
909
1824
|
}
|
|
910
1825
|
else {
|
|
911
|
-
const wasDisabled =
|
|
1826
|
+
const wasDisabled = localStorageService.getString(VOICE_DISABLED_KEY) === "true";
|
|
912
1827
|
setIsVoiceDisabled(wasDisabled);
|
|
913
1828
|
}
|
|
914
1829
|
}
|
|
@@ -919,7 +1834,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
919
1834
|
// Auto-focus terminal input on mount
|
|
920
1835
|
useEffect(() => {
|
|
921
1836
|
if (textareaRef.current) {
|
|
922
|
-
|
|
1837
|
+
try {
|
|
1838
|
+
textareaRef.current.focus({ preventScroll: true });
|
|
1839
|
+
}
|
|
1840
|
+
catch {
|
|
1841
|
+
textareaRef.current.focus();
|
|
1842
|
+
}
|
|
923
1843
|
}
|
|
924
1844
|
}, []);
|
|
925
1845
|
// Start voice recognition
|
|
@@ -938,20 +1858,29 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
938
1858
|
r.lang = editContext?.currentItemDescriptor?.language || "en-US";
|
|
939
1859
|
r.continuous = true;
|
|
940
1860
|
r.interimResults = true;
|
|
1861
|
+
promptBeforeVoiceRef.current = prompt;
|
|
941
1862
|
r.onstart = () => {
|
|
942
1863
|
setIsListening(true);
|
|
943
1864
|
prevPlaceholderRef.current = inputPlaceholder;
|
|
944
1865
|
setInputPlaceholder("Listening...");
|
|
945
1866
|
};
|
|
1867
|
+
// Desktop Chrome: single result at index 0, transitions interim→final once.
|
|
1868
|
+
// Mobile Chrome: new result index per event, ALL immediately isFinal,
|
|
1869
|
+
// each containing the full cumulative transcript. We always take the last
|
|
1870
|
+
// non-empty final result and REPLACE the prompt to handle both patterns.
|
|
946
1871
|
r.onresult = (event) => {
|
|
947
|
-
let
|
|
1872
|
+
let lastFinalTranscript = "";
|
|
948
1873
|
let interimText = "";
|
|
949
|
-
for (let i = event.
|
|
1874
|
+
for (let i = event.results.length - 1; i >= 0; i--) {
|
|
950
1875
|
const res = event.results[i];
|
|
951
|
-
if (res.isFinal
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1876
|
+
if (res.isFinal &&
|
|
1877
|
+
!lastFinalTranscript &&
|
|
1878
|
+
(res[0]?.transcript || "").trim()) {
|
|
1879
|
+
lastFinalTranscript = res[0].transcript;
|
|
1880
|
+
}
|
|
1881
|
+
if (!res.isFinal) {
|
|
1882
|
+
interimText = (res[0]?.transcript || "") + interimText;
|
|
1883
|
+
}
|
|
955
1884
|
}
|
|
956
1885
|
if (interimText) {
|
|
957
1886
|
setInputPlaceholder(`Listening... ${interimText.trim()}`);
|
|
@@ -959,11 +1888,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
959
1888
|
else {
|
|
960
1889
|
setInputPlaceholder("Listening...");
|
|
961
1890
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1891
|
+
const base = promptBeforeVoiceRef.current;
|
|
1892
|
+
const separator = base && !base.endsWith(" ") ? " " : "";
|
|
1893
|
+
const recognized = lastFinalTranscript.trim();
|
|
1894
|
+
if (recognized) {
|
|
1895
|
+
setPrompt(base + separator + recognized + " ");
|
|
967
1896
|
if (textareaRef.current) {
|
|
968
1897
|
try {
|
|
969
1898
|
const v = textareaRef.current.value || "";
|
|
@@ -979,7 +1908,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
979
1908
|
// network error also comes from incompatible chromium client
|
|
980
1909
|
if (e?.error === "network") {
|
|
981
1910
|
try {
|
|
982
|
-
|
|
1911
|
+
localStorageService.setString(VOICE_DISABLED_KEY, "true");
|
|
983
1912
|
setIsVoiceDisabled(true);
|
|
984
1913
|
}
|
|
985
1914
|
catch { }
|
|
@@ -1148,25 +2077,18 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1148
2077
|
// Shared stream message handlers with messageId support
|
|
1149
2078
|
const createNewStreamMessage = useCallback((messageId, agentData) => {
|
|
1150
2079
|
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
|
-
}
|
|
2080
|
+
const effectiveAgentId = currentAgent?.id || agentStub.id;
|
|
1159
2081
|
// Reduced: avoid verbose logging during streaming
|
|
1160
2082
|
return {
|
|
1161
2083
|
id: messageId,
|
|
1162
|
-
agentId:
|
|
2084
|
+
agentId: effectiveAgentId,
|
|
1163
2085
|
messageIndex: -1,
|
|
1164
2086
|
role: "assistant",
|
|
1165
2087
|
content: "",
|
|
1166
2088
|
name: "agent",
|
|
1167
2089
|
messageType: "streaming",
|
|
1168
2090
|
isCompleted: false,
|
|
1169
|
-
model: currentAgent
|
|
2091
|
+
model: currentAgent?.model || "",
|
|
1170
2092
|
tokensUsed: 0,
|
|
1171
2093
|
inputTokens: 0,
|
|
1172
2094
|
outputTokens: 0,
|
|
@@ -1175,16 +2097,71 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1175
2097
|
outputTokenCost: 0,
|
|
1176
2098
|
cachedInputTokenCost: 0,
|
|
1177
2099
|
totalCost: 0,
|
|
1178
|
-
currency: currentAgent
|
|
2100
|
+
currency: currentAgent?.currency || "USD",
|
|
1179
2101
|
createdDate: new Date().toISOString(),
|
|
1180
2102
|
toolCalls: [],
|
|
1181
2103
|
};
|
|
1182
|
-
}, [agent]);
|
|
2104
|
+
}, [agent, agentStub.id]);
|
|
2105
|
+
const stripHeartbeatMessages = useCallback((currentMessages) => currentMessages.filter((message) => message.messageType !== "heartbeat"), []);
|
|
2106
|
+
const handleHeartbeatMessage = useCallback((message) => {
|
|
2107
|
+
const heartbeatText = (message.data?.message ||
|
|
2108
|
+
message.data?.Message ||
|
|
2109
|
+
"Still working on your request. This is taking a little longer than usual.").trim() ||
|
|
2110
|
+
"Still working on your request. This is taking a little longer than usual.";
|
|
2111
|
+
const createdDate = message.timestamp || new Date().toISOString();
|
|
2112
|
+
const heartbeatId = `heartbeat-${agent?.id || agentStub.id}`;
|
|
2113
|
+
setMessages((prev) => {
|
|
2114
|
+
const withoutHeartbeats = stripHeartbeatMessages(prev);
|
|
2115
|
+
const updated = [
|
|
2116
|
+
...withoutHeartbeats,
|
|
2117
|
+
{
|
|
2118
|
+
id: heartbeatId,
|
|
2119
|
+
agentId: agent?.id || agentStub.id,
|
|
2120
|
+
messageIndex: -1,
|
|
2121
|
+
role: "system",
|
|
2122
|
+
content: heartbeatText,
|
|
2123
|
+
name: "system",
|
|
2124
|
+
messageType: "heartbeat",
|
|
2125
|
+
isCompleted: true,
|
|
2126
|
+
model: "",
|
|
2127
|
+
tokensUsed: 0,
|
|
2128
|
+
inputTokens: 0,
|
|
2129
|
+
outputTokens: 0,
|
|
2130
|
+
cachedInputTokens: 0,
|
|
2131
|
+
inputTokenCost: 0,
|
|
2132
|
+
outputTokenCost: 0,
|
|
2133
|
+
cachedInputTokenCost: 0,
|
|
2134
|
+
totalCost: 0,
|
|
2135
|
+
currency: "USD",
|
|
2136
|
+
createdDate,
|
|
2137
|
+
toolCalls: [],
|
|
2138
|
+
},
|
|
2139
|
+
];
|
|
2140
|
+
messagesRef.current = updated;
|
|
2141
|
+
return updated;
|
|
2142
|
+
});
|
|
2143
|
+
}, [agent?.id, agentStub.id, stripHeartbeatMessages]);
|
|
2144
|
+
const clearHeartbeatMessages = useCallback(() => {
|
|
2145
|
+
setMessages((prev) => {
|
|
2146
|
+
const updated = stripHeartbeatMessages(prev);
|
|
2147
|
+
if (updated.length === prev.length) {
|
|
2148
|
+
return prev;
|
|
2149
|
+
}
|
|
2150
|
+
messagesRef.current = updated;
|
|
2151
|
+
return updated;
|
|
2152
|
+
});
|
|
2153
|
+
}, [stripHeartbeatMessages]);
|
|
1183
2154
|
const handleContentChunk = useCallback((message, agentData) => {
|
|
1184
2155
|
// Get messageId from data, or generate one from agent ID (for backward compatibility)
|
|
1185
2156
|
// If no messageId is provided, we'll use the last assistant message or create a new one
|
|
1186
2157
|
let messageId = message.data?.messageId;
|
|
1187
2158
|
if (!messageId && agentData?.id) {
|
|
2159
|
+
console.warn("[AgentTerminal] Content chunk missing messageId; falling back to local resolution", {
|
|
2160
|
+
agentId: agentData.id,
|
|
2161
|
+
isIncremental: message.data?.isIncremental,
|
|
2162
|
+
previousContentLength: message.data?.previousContentLength,
|
|
2163
|
+
totalContentLength: message.data?.totalContentLength,
|
|
2164
|
+
});
|
|
1188
2165
|
// For backward compatibility: if no messageId, find or create the current streaming message
|
|
1189
2166
|
// This handles cases where the backend doesn't send messageId
|
|
1190
2167
|
const currentMessages = messagesRef.current;
|
|
@@ -1198,7 +2175,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1198
2175
|
// If the agent isn't currently running (e.g., we switched tabs after the run
|
|
1199
2176
|
// completed), skip creating a new streaming message to avoid duplicates.
|
|
1200
2177
|
const currentAgentStatus = (agentData || agent)?.status;
|
|
1201
|
-
const isAgentRunning = currentAgentStatus === "running"
|
|
2178
|
+
const isAgentRunning = currentAgentStatus === "running";
|
|
1202
2179
|
if (!isAgentRunning) {
|
|
1203
2180
|
return;
|
|
1204
2181
|
}
|
|
@@ -1231,6 +2208,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1231
2208
|
outputCost: Number(cost.output) || 0,
|
|
1232
2209
|
cachedCost: Number(cost.cached) || 0,
|
|
1233
2210
|
cacheWriteCost: Number(cost.cacheWrite) || 0,
|
|
2211
|
+
imageCost: Number(cost.imageCost ?? cost.totalImageCost) ||
|
|
2212
|
+
0,
|
|
1234
2213
|
totalCost: Number(cost.total) || 0,
|
|
1235
2214
|
currency: "USD",
|
|
1236
2215
|
};
|
|
@@ -1254,11 +2233,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1254
2233
|
setIsWaitingForResponse(false);
|
|
1255
2234
|
shouldCreateNewMessage.current = false;
|
|
1256
2235
|
}
|
|
1257
|
-
// Extract context window info from cost object
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
2236
|
+
// Extract context window info from cost object.
|
|
2237
|
+
// Note: contextUsed can legitimately be 0 (especially early in OpenAI streams),
|
|
2238
|
+
// so we must check for null/undefined instead of truthiness.
|
|
2239
|
+
const contextWindowRaw = cost.contextWindow;
|
|
2240
|
+
const contextUsedRaw = cost.contextUsed;
|
|
2241
|
+
if (contextWindowRaw !== undefined &&
|
|
2242
|
+
contextWindowRaw !== null &&
|
|
2243
|
+
contextUsedRaw !== undefined &&
|
|
2244
|
+
contextUsedRaw !== null) {
|
|
2245
|
+
const contextWindowValue = Number(contextWindowRaw);
|
|
2246
|
+
const contextUsedValue = Number(contextUsedRaw);
|
|
2247
|
+
if (contextWindowValue > 0 &&
|
|
2248
|
+
Number.isFinite(contextUsedValue) &&
|
|
2249
|
+
contextUsedValue >= 0) {
|
|
1262
2250
|
setContextWindowStatus({
|
|
1263
2251
|
contextWindowTokens: contextWindowValue,
|
|
1264
2252
|
estimatedInputTokens: contextUsedValue,
|
|
@@ -1274,6 +2262,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1274
2262
|
const existingMessageIndex = prev.findIndex((msg) => msg.id === messageId);
|
|
1275
2263
|
if (existingMessageIndex === -1) {
|
|
1276
2264
|
// Message doesn't exist - create new streaming message
|
|
2265
|
+
const previousContentLength = message.data?.previousContentLength || 0;
|
|
2266
|
+
if (message.data?.isIncremental && previousContentLength > 0) {
|
|
2267
|
+
console.warn("[AgentTerminal] Incremental chunk arrived before its base message existed", {
|
|
2268
|
+
messageId,
|
|
2269
|
+
previousContentLength,
|
|
2270
|
+
totalContentLength: message.data?.totalContentLength,
|
|
2271
|
+
deltaLength: (message.data?.deltaContent || "").length,
|
|
2272
|
+
});
|
|
2273
|
+
}
|
|
1277
2274
|
const newStreamMessage = createNewStreamMessage(messageId, agentData);
|
|
1278
2275
|
// Set the content for the new message
|
|
1279
2276
|
const updatedNewMessage = { ...newStreamMessage };
|
|
@@ -1296,8 +2293,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1296
2293
|
return prev;
|
|
1297
2294
|
// Check if existing content is already longer than what we're trying to stream
|
|
1298
2295
|
const currentContentLength = existingMessage.content?.length || 0;
|
|
2296
|
+
const previousContentLength = message.data?.previousContentLength || 0;
|
|
1299
2297
|
const totalContentLength = message.data?.totalContentLength || 0;
|
|
1300
|
-
if (
|
|
2298
|
+
if (message.data?.isIncremental &&
|
|
2299
|
+
previousContentLength !== currentContentLength &&
|
|
2300
|
+
(previousContentLength > 0 || currentContentLength > 0)) {
|
|
2301
|
+
console.warn("[AgentTerminal] Content chunk length mismatch", {
|
|
2302
|
+
messageId,
|
|
2303
|
+
previousContentLength,
|
|
2304
|
+
currentContentLength,
|
|
2305
|
+
totalContentLength,
|
|
2306
|
+
deltaLength: (message.data?.deltaContent || "").length,
|
|
2307
|
+
});
|
|
2308
|
+
}
|
|
2309
|
+
if (message.data?.isIncremental &&
|
|
2310
|
+
currentContentLength >= totalContentLength &&
|
|
1301
2311
|
totalContentLength > 0) {
|
|
1302
2312
|
return prev;
|
|
1303
2313
|
}
|
|
@@ -1321,10 +2331,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1321
2331
|
});
|
|
1322
2332
|
}, [createNewStreamMessage, agent]);
|
|
1323
2333
|
const handleToolCall = useCallback((message, agentData) => {
|
|
1324
|
-
const
|
|
2334
|
+
const extractedToolCall = extractToolCallFields(message.data);
|
|
2335
|
+
const toolCallId = extractedToolCall.toolCallId || crypto.randomUUID();
|
|
1325
2336
|
// Prefer provided messageId, otherwise fall back to the last streaming assistant message
|
|
1326
2337
|
let toolCallMessageId = message.data?.messageId;
|
|
1327
2338
|
if (!toolCallMessageId) {
|
|
2339
|
+
console.warn("[AgentTerminal] Tool call missing messageId; falling back", {
|
|
2340
|
+
toolCallId,
|
|
2341
|
+
toolName: message.data?.name || message.data?.displayName,
|
|
2342
|
+
});
|
|
1328
2343
|
const current = messagesRef.current;
|
|
1329
2344
|
const lastStreaming = [...current]
|
|
1330
2345
|
.reverse()
|
|
@@ -1332,7 +2347,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1332
2347
|
if (lastStreaming?.id) {
|
|
1333
2348
|
toolCallMessageId = lastStreaming.id;
|
|
1334
2349
|
}
|
|
2350
|
+
else {
|
|
2351
|
+
// Tool calls can arrive before any assistant content chunk (common for dialog tools like ask-questionnaire).
|
|
2352
|
+
// Create a synthetic streaming message so the UI can render the tool call immediately.
|
|
2353
|
+
toolCallMessageId = crypto.randomUUID();
|
|
2354
|
+
}
|
|
1335
2355
|
}
|
|
2356
|
+
appendToolUiEvent("ui:tool-call-targeted", `${extractedToolCall.functionName || "unknown"} toolCallId=${toolCallId} targetMessageId=${toolCallMessageId || "none"} providedMessageId=${String(message.data?.messageId || "none")}`);
|
|
1336
2357
|
// Find or create the target message for this tool call
|
|
1337
2358
|
if (toolCallMessageId) {
|
|
1338
2359
|
const currentMessages = messagesRef.current;
|
|
@@ -1359,24 +2380,25 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1359
2380
|
}
|
|
1360
2381
|
// Add tool call to the message in the array
|
|
1361
2382
|
if (toolCallMessageId && message.data && toolCallId) {
|
|
1362
|
-
const
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
2383
|
+
const toolCallError = message.data.functionError || message.data.error || "";
|
|
2384
|
+
const isPruned = !!message.data?.isPruned || /^PRUNED$/i.test(String(toolCallError));
|
|
2385
|
+
const toolCallCreatedDate = message.data.createdDate ||
|
|
2386
|
+
message.timestamp ||
|
|
2387
|
+
new Date().toISOString();
|
|
1366
2388
|
const toolCall = {
|
|
1367
2389
|
id: toolCallId,
|
|
1368
2390
|
messageId: toolCallMessageId,
|
|
1369
2391
|
dbMessageId: message.data.messageId, // Database message ID for approval/rejection
|
|
1370
2392
|
toolCallId: toolCallId,
|
|
1371
|
-
functionName: functionName,
|
|
1372
|
-
functionArguments:
|
|
1373
|
-
message.data.arguments ||
|
|
1374
|
-
JSON.stringify(message.data.function?.arguments || {}),
|
|
2393
|
+
functionName: extractedToolCall.functionName,
|
|
2394
|
+
functionArguments: extractedToolCall.functionArguments,
|
|
1375
2395
|
functionResult: message.data.functionResult || message.data.result || "",
|
|
1376
|
-
|
|
2396
|
+
functionResultRichContent: message.data.richContent || undefined,
|
|
2397
|
+
functionError: toolCallError,
|
|
2398
|
+
isPruned,
|
|
1377
2399
|
isCompleted: false,
|
|
1378
2400
|
responseTimeMs: message.data.responseTimeMs,
|
|
1379
|
-
createdDate:
|
|
2401
|
+
createdDate: toolCallCreatedDate,
|
|
1380
2402
|
requiresApproval: message.data?.requiresApproval,
|
|
1381
2403
|
};
|
|
1382
2404
|
// Check for existing tool call - search across ALL messages by toolCallId first
|
|
@@ -1415,14 +2437,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1415
2437
|
// Check if the new data has more information than what we have
|
|
1416
2438
|
const newArgs = toolCall.functionArguments;
|
|
1417
2439
|
const existingArgs = existingToolCall.functionArguments;
|
|
1418
|
-
const
|
|
2440
|
+
const newArgsText = stringifyToolField(newArgs) || "";
|
|
2441
|
+
const existingArgsText = stringifyToolField(existingArgs) || "";
|
|
2442
|
+
const hasMoreCompleteArgs = (newArgsText.length > existingArgsText.length &&
|
|
2443
|
+
newArgsText !== existingArgsText) ||
|
|
2444
|
+
(existingArgsText === "{}" && newArgsText !== "{}");
|
|
1419
2445
|
const hasNewResult = toolCall.functionResult && !existingToolCall.functionResult;
|
|
2446
|
+
const hasNewRichContent = toolCall.functionResultRichContent &&
|
|
2447
|
+
!existingToolCall.functionResultRichContent;
|
|
1420
2448
|
const hasNewError = toolCall.functionError && !existingToolCall.functionError;
|
|
1421
2449
|
const hasNewApprovalInfo = toolCall.requiresApproval && !existingToolCall.requiresApproval;
|
|
1422
2450
|
const hasNewDbMessageId = toolCall.dbMessageId && !existingToolCall.dbMessageId;
|
|
1423
2451
|
// Only update if there's meaningful new data
|
|
1424
2452
|
if (hasMoreCompleteArgs ||
|
|
1425
2453
|
hasNewResult ||
|
|
2454
|
+
hasNewRichContent ||
|
|
1426
2455
|
hasNewError ||
|
|
1427
2456
|
hasNewApprovalInfo ||
|
|
1428
2457
|
hasNewDbMessageId) {
|
|
@@ -1439,9 +2468,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1439
2468
|
updatedToolCalls[idx] = {
|
|
1440
2469
|
...existing,
|
|
1441
2470
|
functionArguments: hasMoreCompleteArgs
|
|
1442
|
-
?
|
|
1443
|
-
: existing.functionArguments,
|
|
2471
|
+
? newArgsText
|
|
2472
|
+
: existingArgsText || existing.functionArguments,
|
|
1444
2473
|
functionResult: toolCall.functionResult || existing.functionResult,
|
|
2474
|
+
functionResultRichContent: toolCall.functionResultRichContent ||
|
|
2475
|
+
existing.functionResultRichContent,
|
|
1445
2476
|
functionError: toolCall.functionError || existing.functionError,
|
|
1446
2477
|
requiresApproval: toolCall.requiresApproval || existing.requiresApproval,
|
|
1447
2478
|
};
|
|
@@ -1459,27 +2490,36 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1459
2490
|
}
|
|
1460
2491
|
return; // Done updating existing tool call
|
|
1461
2492
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
2493
|
+
flushSync(() => {
|
|
2494
|
+
setMessages((prev) => {
|
|
2495
|
+
const updated = prev.map((msg) => {
|
|
2496
|
+
if (msg.id !== toolCallMessageId)
|
|
2497
|
+
return msg;
|
|
2498
|
+
const existingToolCalls = msg.toolCalls || [];
|
|
2499
|
+
return { ...msg, toolCalls: [...existingToolCalls, toolCall] };
|
|
2500
|
+
});
|
|
2501
|
+
messagesRef.current = updated;
|
|
2502
|
+
return updated;
|
|
1468
2503
|
});
|
|
1469
|
-
messagesRef.current = updated;
|
|
1470
|
-
return updated;
|
|
1471
2504
|
});
|
|
2505
|
+
const messageWithToolCall = messagesRef.current.find((msg) => msg.id === toolCallMessageId);
|
|
2506
|
+
appendToolUiEvent("ui:tool-call-attached", `${extractedToolCall.functionName || "unknown"} toolCallId=${toolCallId} targetMessageId=${toolCallMessageId} messageToolCalls=${messageWithToolCall?.toolCalls?.length || 0} assistantMessages=${messagesRef.current.filter((msg) => msg.role === "assistant").length}`);
|
|
1472
2507
|
// If tool requires approval, agent is now waiting for user action - stop thinking
|
|
1473
2508
|
if (message.data?.requiresApproval) {
|
|
1474
2509
|
setIsAgentThinking(false);
|
|
1475
2510
|
}
|
|
1476
2511
|
}
|
|
1477
|
-
}, [createNewStreamMessage]);
|
|
2512
|
+
}, [appendToolUiEvent, createNewStreamMessage]);
|
|
1478
2513
|
const handleToolResult = useCallback((message, agentData) => {
|
|
1479
|
-
const
|
|
2514
|
+
const extractedToolCall = extractToolCallFields(message.data);
|
|
2515
|
+
const resultToolCallId = extractedToolCall.toolCallId || crypto.randomUUID();
|
|
1480
2516
|
// Prefer provided messageId, otherwise fall back to the last streaming assistant message
|
|
1481
2517
|
let resultMessageId = message.data?.messageId;
|
|
1482
2518
|
if (!resultMessageId) {
|
|
2519
|
+
console.warn("[AgentTerminal] Tool result missing messageId; falling back", {
|
|
2520
|
+
toolCallId: resultToolCallId,
|
|
2521
|
+
toolName: message.data?.functionName || message.data?.displayName,
|
|
2522
|
+
});
|
|
1483
2523
|
const current = messagesRef.current;
|
|
1484
2524
|
const lastStreaming = [...current]
|
|
1485
2525
|
.reverse()
|
|
@@ -1501,6 +2541,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1501
2541
|
outputCost: Number(cost.output) || 0,
|
|
1502
2542
|
cachedCost: Number(cost.cached) || 0,
|
|
1503
2543
|
cacheWriteCost: Number(cost.cacheWrite) || 0,
|
|
2544
|
+
imageCost: Number(cost.imageCost) || 0,
|
|
1504
2545
|
totalCost: Number(cost.total) || 0,
|
|
1505
2546
|
currency: "USD",
|
|
1506
2547
|
};
|
|
@@ -1512,11 +2553,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1512
2553
|
if (anyNonZero) {
|
|
1513
2554
|
setLiveTotals(nextTotals);
|
|
1514
2555
|
}
|
|
1515
|
-
// Extract context window info from cost object
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
2556
|
+
// Extract context window info from cost object.
|
|
2557
|
+
// Note: contextUsed can legitimately be 0 (especially early in OpenAI streams),
|
|
2558
|
+
// so we must check for null/undefined instead of truthiness.
|
|
2559
|
+
const contextWindowRaw = cost.contextWindow;
|
|
2560
|
+
const contextUsedRaw = cost.contextUsed;
|
|
2561
|
+
if (contextWindowRaw !== undefined &&
|
|
2562
|
+
contextWindowRaw !== null &&
|
|
2563
|
+
contextUsedRaw !== undefined &&
|
|
2564
|
+
contextUsedRaw !== null) {
|
|
2565
|
+
const contextWindowValue = Number(contextWindowRaw);
|
|
2566
|
+
const contextUsedValue = Number(contextUsedRaw);
|
|
2567
|
+
if (contextWindowValue > 0 &&
|
|
2568
|
+
Number.isFinite(contextUsedValue) &&
|
|
2569
|
+
contextUsedValue >= 0) {
|
|
1520
2570
|
setContextWindowStatus({
|
|
1521
2571
|
contextWindowTokens: contextWindowValue,
|
|
1522
2572
|
estimatedInputTokens: contextUsedValue,
|
|
@@ -1540,6 +2590,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1540
2590
|
outputCost: Number(data.totalOutputTokenCost) || 0,
|
|
1541
2591
|
cachedCost: Number(data.totalCachedTokenCost) || 0,
|
|
1542
2592
|
cacheWriteCost: Number(data.totalCacheWriteTokenCost) || 0,
|
|
2593
|
+
imageCost: Number(data.totalImageCost) || 0,
|
|
1543
2594
|
totalCost: Number(data.totalCost) || 0,
|
|
1544
2595
|
currency: data.currency || "USD",
|
|
1545
2596
|
};
|
|
@@ -1555,8 +2606,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1555
2606
|
}
|
|
1556
2607
|
// Update tool result directly in the messages array
|
|
1557
2608
|
if (!resultMessageId) {
|
|
2609
|
+
appendToolUiEvent("ui:tool-result-dropped", `${extractedToolCall.functionName || "unknown"} toolCallId=${resultToolCallId} reason=no-result-message-id`);
|
|
1558
2610
|
return;
|
|
1559
2611
|
}
|
|
2612
|
+
appendToolUiEvent("ui:tool-result-targeted", `${extractedToolCall.functionName || "unknown"} toolCallId=${resultToolCallId} targetMessageId=${resultMessageId}`);
|
|
1560
2613
|
// Update the message with tool result
|
|
1561
2614
|
setMessages((prev) => {
|
|
1562
2615
|
const updated = prev.map((msg) => {
|
|
@@ -1572,13 +2625,22 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1572
2625
|
const existingToolCall = updatedMessage.toolCalls[toolCallIndex];
|
|
1573
2626
|
if (existingToolCall && message.data) {
|
|
1574
2627
|
const updatedToolCalls = [...updatedMessage.toolCalls];
|
|
2628
|
+
const nextArgsText = stringifyToolField(extractedToolCall.functionArguments) || "";
|
|
2629
|
+
const existingArgsText = stringifyToolField(existingToolCall.functionArguments) || "";
|
|
2630
|
+
const hasMoreCompleteArgs = (nextArgsText.length > existingArgsText.length &&
|
|
2631
|
+
nextArgsText !== existingArgsText) ||
|
|
2632
|
+
(existingArgsText === "{}" && nextArgsText !== "{}");
|
|
1575
2633
|
const toolCall = {
|
|
1576
2634
|
id: existingToolCall.id,
|
|
1577
2635
|
messageId: existingToolCall.messageId,
|
|
1578
2636
|
toolCallId: existingToolCall.toolCallId,
|
|
1579
2637
|
functionName: existingToolCall.functionName,
|
|
1580
|
-
functionArguments:
|
|
2638
|
+
functionArguments: hasMoreCompleteArgs
|
|
2639
|
+
? nextArgsText
|
|
2640
|
+
: existingToolCall.functionArguments,
|
|
1581
2641
|
functionResult: message.data.functionResult || message.data.result || "",
|
|
2642
|
+
functionResultRichContent: message.data.richContent ||
|
|
2643
|
+
existingToolCall.functionResultRichContent,
|
|
1582
2644
|
functionError: message.data.functionError || message.data.error || "",
|
|
1583
2645
|
isCompleted: true,
|
|
1584
2646
|
responseTimeMs: message.data.responseTimeMs,
|
|
@@ -1595,23 +2657,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1595
2657
|
}
|
|
1596
2658
|
else if (message.data && resultToolCallId && resultMessageId) {
|
|
1597
2659
|
// Create new tool call if it doesn't exist
|
|
1598
|
-
const
|
|
1599
|
-
message.
|
|
1600
|
-
|
|
1601
|
-
"unknown";
|
|
2660
|
+
const toolCallCreatedDate = message.data.createdDate ||
|
|
2661
|
+
message.timestamp ||
|
|
2662
|
+
new Date().toISOString();
|
|
1602
2663
|
const toolCall = {
|
|
1603
2664
|
id: resultToolCallId,
|
|
1604
2665
|
messageId: resultMessageId,
|
|
1605
2666
|
toolCallId: resultToolCallId,
|
|
1606
|
-
functionName: functionName,
|
|
1607
|
-
functionArguments:
|
|
1608
|
-
message.data.arguments ||
|
|
1609
|
-
JSON.stringify(message.data.function?.arguments || {}),
|
|
2667
|
+
functionName: extractedToolCall.functionName,
|
|
2668
|
+
functionArguments: extractedToolCall.functionArguments,
|
|
1610
2669
|
functionResult: message.data.functionResult || message.data.result || "",
|
|
2670
|
+
functionResultRichContent: message.data.richContent || undefined,
|
|
1611
2671
|
functionError: message.data.functionError || message.data.error || "",
|
|
1612
2672
|
isCompleted: true,
|
|
1613
2673
|
responseTimeMs: message.data.responseTimeMs,
|
|
1614
|
-
createdDate:
|
|
2674
|
+
createdDate: toolCallCreatedDate,
|
|
1615
2675
|
};
|
|
1616
2676
|
updatedMessage.toolCalls = [...updatedMessage.toolCalls, toolCall];
|
|
1617
2677
|
}
|
|
@@ -1619,9 +2679,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1619
2679
|
return updatedMessage;
|
|
1620
2680
|
});
|
|
1621
2681
|
messagesRef.current = updated;
|
|
2682
|
+
const messageWithToolResult = updated.find((msg) => msg.id === resultMessageId);
|
|
2683
|
+
const matchingToolCall = messageWithToolResult?.toolCalls?.find((tc) => tc.toolCallId === resultToolCallId);
|
|
2684
|
+
appendToolUiEvent("ui:tool-result-applied", `${extractedToolCall.functionName || "unknown"} toolCallId=${resultToolCallId} targetMessageId=${resultMessageId} completed=${matchingToolCall?.isCompleted ? "yes" : "no"} messageToolCalls=${messageWithToolResult?.toolCalls?.length || 0}`);
|
|
1622
2685
|
return updated;
|
|
1623
2686
|
});
|
|
1624
|
-
}, []);
|
|
2687
|
+
}, [appendToolUiEvent]);
|
|
1625
2688
|
// Listen for local approval resolution to update UI
|
|
1626
2689
|
useEffect(() => {
|
|
1627
2690
|
const onApprovalResolved = (ev) => {
|
|
@@ -1638,6 +2701,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1638
2701
|
if (!messageId || !toolCallId)
|
|
1639
2702
|
return;
|
|
1640
2703
|
// Update local state to reflect approval status
|
|
2704
|
+
const currentMessages = messagesRef.current || [];
|
|
2705
|
+
const hasMatchingToolCall = currentMessages.some((m) => (m.toolCalls || []).some((tc) => tc.toolCallId === toolCallId));
|
|
2706
|
+
if (!hasMatchingToolCall) {
|
|
2707
|
+
return;
|
|
2708
|
+
}
|
|
1641
2709
|
setMessages((prev) => {
|
|
1642
2710
|
const updated = prev.map((m) => {
|
|
1643
2711
|
// Update tool calls in ANY message that contains this toolCallId
|
|
@@ -1702,7 +2770,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1702
2770
|
// The agent might have been persisted after sending a prompt
|
|
1703
2771
|
// Only treat as "new" if backend returns 404
|
|
1704
2772
|
const hasExistingMessages = messagesRef.current.length > 0;
|
|
1705
|
-
if (agentStub.status === "new" &&
|
|
2773
|
+
if (agentStub.status === "new" &&
|
|
2774
|
+
!agentStub.userId &&
|
|
2775
|
+
!hasExistingMessages) {
|
|
1706
2776
|
// Only initialize as new if we have no messages yet (initial mount)
|
|
1707
2777
|
// Derive initial profile from provided metadata if present
|
|
1708
2778
|
const initialProfileIdFromMeta = (() => {
|
|
@@ -1742,6 +2812,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1742
2812
|
totalInputTokenCost: 0,
|
|
1743
2813
|
totalOutputTokenCost: 0,
|
|
1744
2814
|
totalCachedInputTokenCost: 0,
|
|
2815
|
+
totalImageCost: 0,
|
|
1745
2816
|
totalCost: 0,
|
|
1746
2817
|
messageCount: 0,
|
|
1747
2818
|
});
|
|
@@ -1784,48 +2855,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1784
2855
|
}
|
|
1785
2856
|
})();
|
|
1786
2857
|
// 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;
|
|
2858
|
+
const localCtx = item && shouldSeedContext ? buildEditorContextPayload(item) : null;
|
|
1829
2859
|
let nextMetadata = null;
|
|
1830
2860
|
if (initialMetadata) {
|
|
1831
2861
|
// Merge initial metadata with local context (using top-level structure)
|
|
@@ -1901,8 +2931,16 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1901
2931
|
seenMessageIdsRef.current.add(msg.id.toLowerCase());
|
|
1902
2932
|
}
|
|
1903
2933
|
});
|
|
1904
|
-
// Keep local streaming
|
|
2934
|
+
// Keep local streaming if the agent is still active (running/waiting); otherwise discard locals.
|
|
2935
|
+
// This is important for dialog-style tools (e.g., ask-questionnaire) where the agent may be
|
|
2936
|
+
// "waiting" but we still want to keep the in-flight tool call UI visible.
|
|
1905
2937
|
const isRunning = agentData.status === "running" || agentData.status === 1;
|
|
2938
|
+
const isWaiting = agentData.status === "waitingForApproval" ||
|
|
2939
|
+
agentData.status === 2 ||
|
|
2940
|
+
agentData.status === "waitingForInput" ||
|
|
2941
|
+
agentData.status === "costLimitReached" ||
|
|
2942
|
+
agentData.status === 7;
|
|
2943
|
+
const keepLocalStreaming = isRunning || isWaiting;
|
|
1906
2944
|
// Filter local messages to only include streaming/incomplete messages that aren't in DB
|
|
1907
2945
|
// This prevents duplicates when reloading - DB messages are source of truth for completed messages
|
|
1908
2946
|
const localStreaming = isRunning
|
|
@@ -1920,7 +2958,19 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1920
2958
|
// Don't keep completed messages from local state - DB is source of truth
|
|
1921
2959
|
return false;
|
|
1922
2960
|
})
|
|
1923
|
-
:
|
|
2961
|
+
: keepLocalStreaming
|
|
2962
|
+
? messagesRef.current.filter((localMsg) => {
|
|
2963
|
+
if (!localMsg.id)
|
|
2964
|
+
return false;
|
|
2965
|
+
if (!localMsg.isCompleted &&
|
|
2966
|
+
localMsg.messageType === "streaming") {
|
|
2967
|
+
const existsInDb = dbMessages.some((dbMsg) => dbMsg.id &&
|
|
2968
|
+
dbMsg.id.toLowerCase() === localMsg.id.toLowerCase());
|
|
2969
|
+
return !existsInDb;
|
|
2970
|
+
}
|
|
2971
|
+
return false;
|
|
2972
|
+
})
|
|
2973
|
+
: [];
|
|
1924
2974
|
const merged = mergeMessagesById(dbMessages, localStreaming);
|
|
1925
2975
|
messagesRef.current = merged;
|
|
1926
2976
|
setMessages(merged);
|
|
@@ -1931,6 +2981,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1931
2981
|
const runningNow = agentData.status === "running" || agentData.status === 1;
|
|
1932
2982
|
const waitingForApprovalNow = agentData.status === "waitingForApproval" ||
|
|
1933
2983
|
agentData.status === 2;
|
|
2984
|
+
const waitingForInputNow = agentData.status === "waitingForInput";
|
|
1934
2985
|
const hasStreamingNow = merged.some((m) => !m.isCompleted && m.messageType === "streaming");
|
|
1935
2986
|
if (runningNow || hasStreamingNow) {
|
|
1936
2987
|
setIsWaitingForResponse(true);
|
|
@@ -1938,11 +2989,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1938
2989
|
// Agent is actively running, show thinking dots
|
|
1939
2990
|
setIsAgentThinking(true);
|
|
1940
2991
|
}
|
|
1941
|
-
else if (waitingForApprovalNow) {
|
|
2992
|
+
else if (waitingForApprovalNow || waitingForInputNow) {
|
|
1942
2993
|
setIsWaitingForResponse(false);
|
|
1943
2994
|
isWaitingRef.current = false;
|
|
1944
2995
|
setIsConnecting(false);
|
|
1945
|
-
// Agent is waiting for user approval, hide thinking dots
|
|
2996
|
+
// Agent is waiting for user input/approval, hide thinking dots
|
|
1946
2997
|
setIsAgentThinking(false);
|
|
1947
2998
|
}
|
|
1948
2999
|
else {
|
|
@@ -1991,19 +3042,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1991
3042
|
if (!contextJson)
|
|
1992
3043
|
return null;
|
|
1993
3044
|
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
3045
|
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;
|
|
3046
|
+
return parsedContext;
|
|
2007
3047
|
}
|
|
2008
3048
|
return null;
|
|
2009
3049
|
}
|
|
@@ -2049,6 +3089,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2049
3089
|
setIsLoading(false);
|
|
2050
3090
|
}
|
|
2051
3091
|
}, [agentStub.id]);
|
|
3092
|
+
const reconcileServerStateRef = useRef(null);
|
|
3093
|
+
useEffect(() => {
|
|
3094
|
+
reconcileServerStateRef.current = async () => {
|
|
3095
|
+
if (reconcileServerStateInFlightRef.current) {
|
|
3096
|
+
return;
|
|
3097
|
+
}
|
|
3098
|
+
reconcileServerStateInFlightRef.current = true;
|
|
3099
|
+
try {
|
|
3100
|
+
await loadAgent();
|
|
3101
|
+
}
|
|
3102
|
+
finally {
|
|
3103
|
+
reconcileServerStateInFlightRef.current = false;
|
|
3104
|
+
}
|
|
3105
|
+
};
|
|
3106
|
+
}, [loadAgent]);
|
|
2052
3107
|
// Initial load
|
|
2053
3108
|
useEffect(() => {
|
|
2054
3109
|
loadAgent();
|
|
@@ -2070,8 +3125,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2070
3125
|
setAgentOperations([]);
|
|
2071
3126
|
return;
|
|
2072
3127
|
}
|
|
3128
|
+
setAgentOperations([]);
|
|
2073
3129
|
try {
|
|
2074
|
-
const result = await getAgentHistory(agent.id,
|
|
3130
|
+
const result = await getAgentHistory(agent.id, AGENT_HISTORY_LIMIT);
|
|
2075
3131
|
if (result.type === "success") {
|
|
2076
3132
|
setAgentOperations(result.data ?? []);
|
|
2077
3133
|
}
|
|
@@ -2089,7 +3145,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2089
3145
|
return;
|
|
2090
3146
|
}
|
|
2091
3147
|
// Check if cost limit exceeded based on status or cost values
|
|
2092
|
-
const statusIndicatesLimit = agent.status === "costLimitReached"
|
|
3148
|
+
const statusIndicatesLimit = agent.status === "costLimitReached";
|
|
2093
3149
|
// Use liveTotals.totalCost as fallback if agent.totalCost is missing or 0
|
|
2094
3150
|
const effectiveTotalCost = agent.totalCost || liveTotals?.totalCost || 0;
|
|
2095
3151
|
const costExceedsLimit = agent.costLimit &&
|
|
@@ -2174,9 +3230,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2174
3230
|
// Handle agent:profile:switched (profile changed via switch-profile function)
|
|
2175
3231
|
if (messageType === "agent:profile:switched") {
|
|
2176
3232
|
const payload = message.payload || {};
|
|
2177
|
-
const switchedAgentId = payload.agentId
|
|
2178
|
-
const newProfileId = payload.newProfileId
|
|
2179
|
-
const newProfileName = payload.newProfileName
|
|
3233
|
+
const switchedAgentId = payload.agentId;
|
|
3234
|
+
const newProfileId = payload.newProfileId;
|
|
3235
|
+
const newProfileName = payload.newProfileName;
|
|
2180
3236
|
if (switchedAgentId === agent.id && newProfileId) {
|
|
2181
3237
|
// Update the agent's profile
|
|
2182
3238
|
setAgent((prev) => {
|
|
@@ -2207,19 +3263,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2207
3263
|
const op = message.payload;
|
|
2208
3264
|
const operationAgentId = op.agentId || op.user?.agentId;
|
|
2209
3265
|
if (operationAgentId === agent.id) {
|
|
2210
|
-
|
|
2211
|
-
getAgentHistory(agent.id, 1000).then((result) => {
|
|
2212
|
-
if (result.type === "success") {
|
|
2213
|
-
setAgentOperations(result.data ?? []);
|
|
2214
|
-
}
|
|
2215
|
-
});
|
|
3266
|
+
setAgentOperations((previous) => mergeAgentOperationHistory(previous, [op]));
|
|
2216
3267
|
}
|
|
2217
3268
|
return;
|
|
2218
3269
|
}
|
|
2219
3270
|
// For other agent messages, check if this is for our agent
|
|
2220
|
-
const agentId = message.payload?.agentId
|
|
2221
|
-
if (agentId !== agent.id)
|
|
3271
|
+
const agentId = message.payload?.agentId;
|
|
3272
|
+
if (agentId !== agent.id) {
|
|
2222
3273
|
return;
|
|
3274
|
+
}
|
|
2223
3275
|
// Handle agent:run:start
|
|
2224
3276
|
if (messageType === "agent:run:start") {
|
|
2225
3277
|
// If a stop operation is in progress, ignore this message to prevent
|
|
@@ -2228,6 +3280,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2228
3280
|
console.log("[AgentTerminal] Ignoring agent:run:start during stop operation");
|
|
2229
3281
|
return;
|
|
2230
3282
|
}
|
|
3283
|
+
const currentStatus = agentRef.current?.status;
|
|
3284
|
+
if (currentStatus === "waitingForInput" ||
|
|
3285
|
+
currentStatus === "waitingForApproval" ||
|
|
3286
|
+
currentStatus === "costLimitReached") {
|
|
3287
|
+
// Replayed start messages arrive before the buffered status payload when
|
|
3288
|
+
// reopening an already-paused agent. Preserve the attention state instead
|
|
3289
|
+
// of flashing "running" until the follow-up status update lands.
|
|
3290
|
+
return;
|
|
3291
|
+
}
|
|
2231
3292
|
// Reset run-scoped deduplication so new run seq values (starting at 1)
|
|
2232
3293
|
// are not discarded due to previous run's lastSeqRef
|
|
2233
3294
|
lastSeqRef.current = 0;
|
|
@@ -2323,6 +3384,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2323
3384
|
if (messageType === "agent:prompt:queued") {
|
|
2324
3385
|
const { queueEntry } = message.payload;
|
|
2325
3386
|
if (queueEntry) {
|
|
3387
|
+
if (shouldSuppressQueuedPrompt(queueEntry)) {
|
|
3388
|
+
return;
|
|
3389
|
+
}
|
|
2326
3390
|
// Add the new prompt to the queued prompts list
|
|
2327
3391
|
setQueuedPrompts((prev) => {
|
|
2328
3392
|
// Check if prompt already exists (deduplication)
|
|
@@ -2351,17 +3415,29 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2351
3415
|
return;
|
|
2352
3416
|
}
|
|
2353
3417
|
const { seq, type, data, cost } = message.payload;
|
|
3418
|
+
if (type === "ToolCall" || type === "toolCall") {
|
|
3419
|
+
}
|
|
2354
3420
|
// Always allow ContextUpdate messages (metadata updates) regardless of agent status
|
|
2355
3421
|
const isContextUpdate = type === "ContextUpdate" || type === "contextUpdate";
|
|
3422
|
+
const isHeartbeat = type === "Heartbeat" || type === "heartbeat";
|
|
3423
|
+
const shouldClearHeartbeat = type === "ContentChunk" ||
|
|
3424
|
+
type === "contentChunk" ||
|
|
3425
|
+
type === "ToolCall" ||
|
|
3426
|
+
type === "toolCall" ||
|
|
3427
|
+
type === "ToolResult" ||
|
|
3428
|
+
type === "toolResult";
|
|
2356
3429
|
// Allow deltas if the agent is running OR if we already have an active streaming message
|
|
2357
3430
|
// OR if the server provided a messageId for targeted updates.
|
|
2358
3431
|
// This avoids dropping early deltas that may arrive before the 'running' status update.
|
|
2359
3432
|
// ContextUpdate messages are always allowed as they're metadata updates, not content.
|
|
2360
3433
|
const isRunning = agent.status === "running" || agent.status === 1;
|
|
2361
3434
|
const hasStreaming = messagesRef.current.some((m) => !m.isCompleted && m.messageType === "streaming");
|
|
2362
|
-
const hasMessageId = !!message?.payload?.data?.messageId
|
|
2363
|
-
|
|
2364
|
-
|
|
3435
|
+
const hasMessageId = !!message?.payload?.data?.messageId;
|
|
3436
|
+
if (!isContextUpdate &&
|
|
3437
|
+
!isHeartbeat &&
|
|
3438
|
+
!isRunning &&
|
|
3439
|
+
!hasStreaming &&
|
|
3440
|
+
!hasMessageId) {
|
|
2365
3441
|
return; // ignore only if we cannot safely target an existing streaming message
|
|
2366
3442
|
}
|
|
2367
3443
|
// Deduplicate by sequence
|
|
@@ -2378,15 +3454,23 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2378
3454
|
cost,
|
|
2379
3455
|
timestamp: new Date().toISOString(),
|
|
2380
3456
|
};
|
|
3457
|
+
if (shouldClearHeartbeat) {
|
|
3458
|
+
clearHeartbeatMessages();
|
|
3459
|
+
}
|
|
2381
3460
|
if (type === "ContentChunk" || type === "contentChunk") {
|
|
2382
3461
|
handleContentChunk(agentStreamMessage, agent);
|
|
2383
3462
|
}
|
|
2384
3463
|
else if (type === "ToolCall" || type === "toolCall") {
|
|
3464
|
+
appendToolUiEvent("ui:tool-delta-received", `ToolCall:${String(data?.functionName || data?.name || data?.displayName || "unknown")} messageId=${String(data?.messageId || "none")} activeStreaming=${messagesRef.current.some((m) => !m.isCompleted && m.messageType === "streaming") ? "yes" : "no"}`, seq ?? null);
|
|
2385
3465
|
handleToolCall(agentStreamMessage, agent);
|
|
2386
3466
|
}
|
|
2387
3467
|
else if (type === "ToolResult" || type === "toolResult") {
|
|
3468
|
+
appendToolUiEvent("ui:tool-delta-received", `ToolResult:${String(data?.functionName || data?.name || data?.displayName || "unknown")} messageId=${String(data?.messageId || "none")} activeStreaming=${messagesRef.current.some((m) => !m.isCompleted && m.messageType === "streaming") ? "yes" : "no"}`, seq ?? null);
|
|
2388
3469
|
handleToolResult(agentStreamMessage, agent);
|
|
2389
3470
|
}
|
|
3471
|
+
else if (type === "Heartbeat" || type === "heartbeat") {
|
|
3472
|
+
handleHeartbeatMessage(agentStreamMessage);
|
|
3473
|
+
}
|
|
2390
3474
|
else if (type === "ContextUpdate" || type === "contextUpdate") {
|
|
2391
3475
|
// Handle context updates from streaming - data contains additionalData.todoList and ChildAgents
|
|
2392
3476
|
const contextData = data;
|
|
@@ -2396,10 +3480,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2396
3480
|
const current = (prev || {});
|
|
2397
3481
|
const updated = { ...current };
|
|
2398
3482
|
// Merge additionalData if present (deep merge to preserve existing values)
|
|
2399
|
-
if (contextData.additionalData
|
|
2400
|
-
const sourceAdditionalData = contextData.additionalData ||
|
|
2401
|
-
contextData.AdditionalData ||
|
|
2402
|
-
{};
|
|
3483
|
+
if (contextData.additionalData) {
|
|
3484
|
+
const sourceAdditionalData = contextData.additionalData || {};
|
|
2403
3485
|
updated.additionalData = {
|
|
2404
3486
|
...(current.additionalData || {}),
|
|
2405
3487
|
...sourceAdditionalData,
|
|
@@ -2407,10 +3489,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2407
3489
|
}
|
|
2408
3490
|
// Merge ChildAgents if present (for spawned agents) - replace entire array
|
|
2409
3491
|
// Backend sends the complete updated list, not just additions
|
|
2410
|
-
if (contextData.
|
|
2411
|
-
const childAgents = contextData.
|
|
3492
|
+
if (contextData.childAgents) {
|
|
3493
|
+
const childAgents = contextData.childAgents;
|
|
2412
3494
|
if (Array.isArray(childAgents)) {
|
|
2413
|
-
updated.
|
|
3495
|
+
updated.childAgents = childAgents;
|
|
2414
3496
|
}
|
|
2415
3497
|
}
|
|
2416
3498
|
return updated;
|
|
@@ -2436,15 +3518,19 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2436
3518
|
// Route based on statusData.state
|
|
2437
3519
|
try {
|
|
2438
3520
|
// Normalize various status shapes and handle Cancelled uniformly
|
|
2439
|
-
const normalizedStatus = statusData?.state ||
|
|
2440
|
-
statusData?.
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
normalizedStatus === "canceled") {
|
|
3521
|
+
const normalizedStatus = parseAgentStatus(statusData?.state) ||
|
|
3522
|
+
parseAgentStatus(statusData?.status);
|
|
3523
|
+
if (normalizedStatus === "idle") {
|
|
3524
|
+
clearStopGuard();
|
|
2444
3525
|
// Stop indicators and mark any in-progress streaming messages as completed
|
|
3526
|
+
clearHeartbeatMessages();
|
|
3527
|
+
setAgent((prev) => (prev ? { ...prev, status: "idle" } : prev));
|
|
2445
3528
|
setIsWaitingForResponse(false);
|
|
2446
3529
|
isWaitingRef.current = false;
|
|
2447
3530
|
setIsConnecting(false);
|
|
3531
|
+
setIsSubmitting(false);
|
|
3532
|
+
shouldCreateNewMessage.current = false;
|
|
3533
|
+
setIsAgentThinking(false);
|
|
2448
3534
|
setMessages((prev) => {
|
|
2449
3535
|
const updated = prev.map((msg) => !msg.isCompleted && msg.messageType === "streaming"
|
|
2450
3536
|
? {
|
|
@@ -2456,6 +3542,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2456
3542
|
messagesRef.current = updated;
|
|
2457
3543
|
return updated;
|
|
2458
3544
|
});
|
|
3545
|
+
void reconcileServerStateRef.current?.();
|
|
2459
3546
|
return;
|
|
2460
3547
|
}
|
|
2461
3548
|
if (statusData?.state === "streamOpen") {
|
|
@@ -2487,6 +3574,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2487
3574
|
outputCost: Number(totals.totalOutputTokenCost) || 0,
|
|
2488
3575
|
cachedCost: Number(totals.totalCachedInputTokenCost) || 0,
|
|
2489
3576
|
cacheWriteCost: Number(totals.totalCacheWriteTokenCost) || 0,
|
|
3577
|
+
imageCost: Number(totals.totalImageCost) || 0,
|
|
2490
3578
|
totalCost: totalCost,
|
|
2491
3579
|
currency: totals.currency,
|
|
2492
3580
|
};
|
|
@@ -2498,6 +3586,26 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2498
3586
|
if (anyNonZero) {
|
|
2499
3587
|
setLiveTotals(nextTotals);
|
|
2500
3588
|
}
|
|
3589
|
+
// Fallback context usage update for providers that do not include
|
|
3590
|
+
// context usage in every stream delta (for example some OpenAI streams).
|
|
3591
|
+
// Prefer explicit status values when present; otherwise derive from totals.
|
|
3592
|
+
const contextWindowValue = Number(statusData?.contextWindow ??
|
|
3593
|
+
agent?.contextWindowTokens ??
|
|
3594
|
+
0);
|
|
3595
|
+
const contextUsedValue = Number(statusData?.contextUsed ??
|
|
3596
|
+
(Number(totals.totalInputTokens) || 0) +
|
|
3597
|
+
(Number(totals.totalCachedInputTokens) || 0) +
|
|
3598
|
+
(Number(totals.totalCacheWriteTokens) || 0));
|
|
3599
|
+
if (contextWindowValue > 0 &&
|
|
3600
|
+
Number.isFinite(contextUsedValue) &&
|
|
3601
|
+
contextUsedValue >= 0) {
|
|
3602
|
+
setContextWindowStatus({
|
|
3603
|
+
contextWindowTokens: contextWindowValue,
|
|
3604
|
+
estimatedInputTokens: contextUsedValue,
|
|
3605
|
+
contextUsedPercent: (contextUsedValue / contextWindowValue) * 100,
|
|
3606
|
+
model: agent?.model || "",
|
|
3607
|
+
});
|
|
3608
|
+
}
|
|
2501
3609
|
// If server provides costLimit along with totals, persist it locally
|
|
2502
3610
|
if (statusCostLimit) {
|
|
2503
3611
|
setAgent((prev) => prev &&
|
|
@@ -2519,6 +3627,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2519
3627
|
return;
|
|
2520
3628
|
}
|
|
2521
3629
|
if (statusData?.state === "ToolApprovalsRequired") {
|
|
3630
|
+
setPendingBrowserCaptureDialogType(null);
|
|
2522
3631
|
const msgId = statusData.messageId;
|
|
2523
3632
|
const ids = statusData.toolCallIds || [];
|
|
2524
3633
|
if (msgId && Array.isArray(ids) && ids.length > 0) {
|
|
@@ -2550,16 +3659,44 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2550
3659
|
setIsWaitingForResponse(false);
|
|
2551
3660
|
return;
|
|
2552
3661
|
}
|
|
2553
|
-
// Handle
|
|
2554
|
-
if (
|
|
2555
|
-
|
|
3662
|
+
// Handle waiting states explicitly
|
|
3663
|
+
if (normalizedStatus === "waitingForApproval") {
|
|
3664
|
+
clearStopGuard();
|
|
3665
|
+
setPendingBrowserCaptureDialogType(null);
|
|
2556
3666
|
setAgent((prev) => prev ? { ...prev, status: "waitingForApproval" } : prev);
|
|
2557
3667
|
setIsConnecting(false);
|
|
2558
3668
|
setIsWaitingForResponse(false);
|
|
2559
3669
|
setIsAgentThinking(false);
|
|
3670
|
+
void reconcileServerStateRef.current?.();
|
|
2560
3671
|
return;
|
|
2561
3672
|
}
|
|
2562
|
-
if (
|
|
3673
|
+
if (normalizedStatus === "waitingForInput") {
|
|
3674
|
+
clearStopGuard();
|
|
3675
|
+
const dialogType = typeof statusData?.dialogType === "string"
|
|
3676
|
+
? statusData.dialogType
|
|
3677
|
+
: null;
|
|
3678
|
+
const isBrowserCaptureWait = dialogType === DIALOG_TYPES.CAPTURE_PAGE_DOM ||
|
|
3679
|
+
dialogType === DIALOG_TYPES.CAPTURE_PAGE_SCREENSHOT;
|
|
3680
|
+
setPendingBrowserCaptureDialogType(isBrowserCaptureWait ? dialogType : null);
|
|
3681
|
+
setAgent((prev) => prev
|
|
3682
|
+
? {
|
|
3683
|
+
...prev,
|
|
3684
|
+
status: "waitingForInput",
|
|
3685
|
+
statusMessage: isBrowserCaptureWait &&
|
|
3686
|
+
typeof statusData?.title === "string" &&
|
|
3687
|
+
statusData.title.trim()
|
|
3688
|
+
? statusData.title.trim()
|
|
3689
|
+
: prev.statusMessage,
|
|
3690
|
+
}
|
|
3691
|
+
: prev);
|
|
3692
|
+
setIsConnecting(false);
|
|
3693
|
+
setIsWaitingForResponse(false);
|
|
3694
|
+
setIsAgentThinking(false);
|
|
3695
|
+
return;
|
|
3696
|
+
}
|
|
3697
|
+
if (normalizedStatus === "costLimitReached") {
|
|
3698
|
+
clearStopGuard();
|
|
3699
|
+
setPendingBrowserCaptureDialogType(null);
|
|
2563
3700
|
const totalCost = Number(statusData.totalCost) || 0;
|
|
2564
3701
|
const costLimit = Number(statusData.costLimit) || agent?.costLimit || 0;
|
|
2565
3702
|
setCostLimitExceeded({
|
|
@@ -2577,7 +3714,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2577
3714
|
// Server sends additionalData directly (with todoList inside)
|
|
2578
3715
|
// Also may include ChildAgents for spawned agents
|
|
2579
3716
|
try {
|
|
2580
|
-
const serverAdditionalData = statusData.additionalData ||
|
|
3717
|
+
const serverAdditionalData = statusData.additionalData || {};
|
|
2581
3718
|
setAgentMetadata((prev) => {
|
|
2582
3719
|
const current = (prev || {});
|
|
2583
3720
|
const updated = { ...current };
|
|
@@ -2591,10 +3728,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2591
3728
|
}
|
|
2592
3729
|
// Merge ChildAgents if present (for spawned agents) - replace entire array
|
|
2593
3730
|
// Backend sends the complete updated list, not just additions
|
|
2594
|
-
if (statusData.
|
|
2595
|
-
const childAgents = statusData.
|
|
3731
|
+
if (statusData.childAgents) {
|
|
3732
|
+
const childAgents = statusData.childAgents;
|
|
2596
3733
|
if (Array.isArray(childAgents)) {
|
|
2597
|
-
updated.
|
|
3734
|
+
updated.childAgents = childAgents;
|
|
2598
3735
|
}
|
|
2599
3736
|
}
|
|
2600
3737
|
return updated;
|
|
@@ -2606,56 +3743,52 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2606
3743
|
return;
|
|
2607
3744
|
}
|
|
2608
3745
|
// Handle "completed" state (fallback for legacy code paths that send status instead of lifecycle event)
|
|
2609
|
-
if (normalizedStatus === "completed"
|
|
2610
|
-
|
|
3746
|
+
if (normalizedStatus === "completed") {
|
|
3747
|
+
clearStopGuard();
|
|
2611
3748
|
// Reset deduplication for the next run
|
|
2612
3749
|
lastSeqRef.current = 0;
|
|
3750
|
+
clearHeartbeatMessages();
|
|
2613
3751
|
// Mark the last assistant message as completed
|
|
2614
3752
|
setMessages((prev) => {
|
|
2615
3753
|
const updated = prev.map((msg) => msg.role === "assistant" && !msg.isCompleted
|
|
2616
3754
|
? {
|
|
2617
3755
|
...msg,
|
|
2618
|
-
isCompleted: true,
|
|
2619
|
-
messageType: "completed",
|
|
2620
|
-
}
|
|
2621
|
-
: msg);
|
|
2622
|
-
messagesRef.current = updated;
|
|
2623
|
-
return updated;
|
|
2624
|
-
});
|
|
2625
|
-
// Update agent status to idle
|
|
2626
|
-
setAgent((prev) => (prev ? { ...prev, status: "idle" } : prev));
|
|
2627
|
-
setIsWaitingForResponse(false);
|
|
2628
|
-
isWaitingRef.current = false;
|
|
2629
|
-
setIsConnecting(false);
|
|
2630
|
-
shouldCreateNewMessage.current = false;
|
|
2631
|
-
// Server says agent finished thinking
|
|
2632
|
-
setIsAgentThinking(false);
|
|
2633
|
-
return;
|
|
2634
|
-
}
|
|
2635
|
-
// Handle "Idle" state - agent has finished processing
|
|
2636
|
-
if (normalizedStatus === "idle" || normalizedStatus === "Idle") {
|
|
3756
|
+
isCompleted: true,
|
|
3757
|
+
messageType: "completed",
|
|
3758
|
+
}
|
|
3759
|
+
: msg);
|
|
3760
|
+
messagesRef.current = updated;
|
|
3761
|
+
return updated;
|
|
3762
|
+
});
|
|
2637
3763
|
// Update agent status to idle
|
|
2638
3764
|
setAgent((prev) => (prev ? { ...prev, status: "idle" } : prev));
|
|
2639
3765
|
setIsWaitingForResponse(false);
|
|
2640
3766
|
isWaitingRef.current = false;
|
|
2641
3767
|
setIsConnecting(false);
|
|
2642
3768
|
shouldCreateNewMessage.current = false;
|
|
3769
|
+
// Server says agent finished thinking
|
|
2643
3770
|
setIsAgentThinking(false);
|
|
2644
3771
|
return;
|
|
2645
3772
|
}
|
|
2646
3773
|
// Handle "Running" state - agent is actively processing
|
|
2647
|
-
if (normalizedStatus === "running"
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
3774
|
+
if (normalizedStatus === "running") {
|
|
3775
|
+
if (isStoppingRef.current) {
|
|
3776
|
+
return;
|
|
3777
|
+
}
|
|
3778
|
+
// Update agent status to running and clear any previous error statusMessage
|
|
3779
|
+
setAgent((prev) => prev
|
|
3780
|
+
? { ...prev, status: "running", statusMessage: undefined }
|
|
3781
|
+
: prev);
|
|
2651
3782
|
setIsWaitingForResponse(true);
|
|
2652
3783
|
isWaitingRef.current = true;
|
|
2653
3784
|
setIsAgentThinking(true);
|
|
2654
3785
|
return;
|
|
2655
3786
|
}
|
|
2656
3787
|
// Handle "Error" state
|
|
2657
|
-
if (normalizedStatus === "error"
|
|
2658
|
-
|
|
3788
|
+
if (normalizedStatus === "error") {
|
|
3789
|
+
clearStopGuard();
|
|
3790
|
+
const errorMsg = toUserFacingAgentErrorMessage(statusData?.statusMessage ?? statusData?.error) || "Unknown error";
|
|
3791
|
+
clearHeartbeatMessages();
|
|
2659
3792
|
setAgent((prev) => prev
|
|
2660
3793
|
? { ...prev, status: "error", statusMessage: errorMsg }
|
|
2661
3794
|
: prev);
|
|
@@ -2675,6 +3808,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2675
3808
|
if (messageType === "agent:run:complete") {
|
|
2676
3809
|
// Reset deduplication for the next run
|
|
2677
3810
|
lastSeqRef.current = 0;
|
|
3811
|
+
clearHeartbeatMessages();
|
|
2678
3812
|
// Mark the last assistant message as completed
|
|
2679
3813
|
setMessages((prev) => {
|
|
2680
3814
|
const updated = prev.map((msg) => msg.role === "assistant" && !msg.isCompleted
|
|
@@ -2699,7 +3833,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2699
3833
|
}
|
|
2700
3834
|
// Lifecycle: agent:run:error
|
|
2701
3835
|
if (messageType === "agent:run:error") {
|
|
2702
|
-
const errorMsg = message.payload?.error ||
|
|
3836
|
+
const errorMsg = toUserFacingAgentErrorMessage(message.payload?.error) ||
|
|
3837
|
+
"AI could not complete this request.";
|
|
3838
|
+
clearHeartbeatMessages();
|
|
2703
3839
|
// Reset deduplication for the next run after an error
|
|
2704
3840
|
lastSeqRef.current = 0;
|
|
2705
3841
|
setError(errorMsg);
|
|
@@ -2714,7 +3850,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2714
3850
|
}
|
|
2715
3851
|
}, [
|
|
2716
3852
|
agent,
|
|
3853
|
+
clearHeartbeatMessages,
|
|
2717
3854
|
handleContentChunk,
|
|
3855
|
+
handleHeartbeatMessage,
|
|
2718
3856
|
handleToolCall,
|
|
2719
3857
|
handleToolResult,
|
|
2720
3858
|
onAgentUpdate,
|
|
@@ -2769,6 +3907,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2769
3907
|
const isRunning = currentAgent.status === "running" || currentAgent.status === 1;
|
|
2770
3908
|
const isWaitingForApproval = currentAgent.status === "waitingForApproval" ||
|
|
2771
3909
|
currentAgent.status === 2;
|
|
3910
|
+
const isWaitingForInput = currentAgent.status === "waitingForInput";
|
|
2772
3911
|
if (isRunning) {
|
|
2773
3912
|
setIsWaitingForResponse(true);
|
|
2774
3913
|
isWaitingRef.current = true;
|
|
@@ -2776,10 +3915,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2776
3915
|
// Agent is currently running, show thinking dots
|
|
2777
3916
|
setIsAgentThinking(true);
|
|
2778
3917
|
}
|
|
2779
|
-
else if (isWaitingForApproval) {
|
|
3918
|
+
else if (isWaitingForApproval || isWaitingForInput) {
|
|
2780
3919
|
setIsWaitingForResponse(false);
|
|
2781
3920
|
isWaitingRef.current = false;
|
|
2782
|
-
// Agent is waiting for user approval, hide thinking dots
|
|
3921
|
+
// Agent is waiting for user input/approval, hide thinking dots
|
|
2783
3922
|
setIsAgentThinking(false);
|
|
2784
3923
|
}
|
|
2785
3924
|
else {
|
|
@@ -2826,36 +3965,157 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2826
3965
|
window.addEventListener("editor:focusAgentPrompt", focusHandler);
|
|
2827
3966
|
return () => window.removeEventListener("editor:focusAgentPrompt", focusHandler);
|
|
2828
3967
|
}, []);
|
|
3968
|
+
// Keep stable refs so we don't miss window events during effect re-runs.
|
|
3969
|
+
const agentIdRefForDialogs = useRef(null);
|
|
3970
|
+
const agentStubIdRefForDialogs = useRef(agentStub.id);
|
|
3971
|
+
const isActiveRefForDialogs = useRef(isActive);
|
|
3972
|
+
const scrollToBottomRefForDialogs = useRef(scrollToBottom);
|
|
3973
|
+
useEffect(() => {
|
|
3974
|
+
agentIdRefForDialogs.current = agent?.id ?? null;
|
|
3975
|
+
}, [agent?.id]);
|
|
3976
|
+
useEffect(() => {
|
|
3977
|
+
const prevId = agentStubIdRefForDialogs.current;
|
|
3978
|
+
agentStubIdRefForDialogs.current = agentStub.id;
|
|
3979
|
+
const visibleRegistry = { ...getVisibleDialogRegistry() };
|
|
3980
|
+
const normalizedPrevId = normalizeDialogAgentId(prevId);
|
|
3981
|
+
if (normalizedPrevId) {
|
|
3982
|
+
delete visibleRegistry[normalizedPrevId];
|
|
3983
|
+
globalThis.__agentDialogVisibleCallbacks = visibleRegistry;
|
|
3984
|
+
}
|
|
3985
|
+
if (prevId && prevId !== agentStub.id) {
|
|
3986
|
+
const orphanedDialog = activeInlineDialogRef.current;
|
|
3987
|
+
if (orphanedDialog) {
|
|
3988
|
+
setActiveInlineDialog(null);
|
|
3989
|
+
window.dispatchEvent(new CustomEvent("agent:dialog:orphaned", {
|
|
3990
|
+
detail: { callbackId: orphanedDialog.request.callbackId },
|
|
3991
|
+
}));
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
}, [agentStub.id]);
|
|
3995
|
+
useEffect(() => {
|
|
3996
|
+
isActiveRefForDialogs.current = isActive;
|
|
3997
|
+
}, [isActive]);
|
|
3998
|
+
useEffect(() => {
|
|
3999
|
+
scrollToBottomRefForDialogs.current = scrollToBottom;
|
|
4000
|
+
}, [scrollToBottom]);
|
|
2829
4001
|
// Listen for agent inline dialog requests from AgentDialogHandler
|
|
2830
4002
|
useEffect(() => {
|
|
4003
|
+
if (orphanTimeoutRef.current) {
|
|
4004
|
+
clearTimeout(orphanTimeoutRef.current);
|
|
4005
|
+
orphanTimeoutRef.current = null;
|
|
4006
|
+
}
|
|
4007
|
+
const globalListeners = (globalThis.__agentDialogMountedAgents ?? []).filter((x) => typeof x === "string");
|
|
4008
|
+
const normalizedAgentStubId = normalizeDialogAgentId(agentStubIdRefForDialogs.current);
|
|
4009
|
+
if (normalizedAgentStubId &&
|
|
4010
|
+
!globalListeners.includes(normalizedAgentStubId)) {
|
|
4011
|
+
globalListeners.push(normalizedAgentStubId);
|
|
4012
|
+
}
|
|
4013
|
+
globalThis.__agentDialogMountedAgents = globalListeners;
|
|
2831
4014
|
const handleDialogShow = (event) => {
|
|
2832
|
-
const
|
|
2833
|
-
|
|
2834
|
-
|
|
4015
|
+
const detail = event?.detail;
|
|
4016
|
+
const request = detail?.request;
|
|
4017
|
+
const onComplete = detail?.onComplete;
|
|
4018
|
+
const onCancel = detail?.onCancel;
|
|
4019
|
+
const terminalAgentId = normalizeDialogAgentId(agentIdRefForDialogs.current);
|
|
4020
|
+
const terminalAgentStubId = normalizeDialogAgentId(agentStubIdRefForDialogs.current);
|
|
4021
|
+
const isActiveNow = isActiveRefForDialogs.current;
|
|
4022
|
+
if (!request)
|
|
4023
|
+
return;
|
|
4024
|
+
const requestAgentId = normalizeDialogAgentId(request.agentId);
|
|
4025
|
+
const agentMatch = !requestAgentId ||
|
|
4026
|
+
!terminalAgentStubId ||
|
|
4027
|
+
requestAgentId === terminalAgentStubId ||
|
|
4028
|
+
requestAgentId === terminalAgentId;
|
|
4029
|
+
if (!isActiveNow) {
|
|
4030
|
+
return;
|
|
4031
|
+
}
|
|
4032
|
+
if (!request)
|
|
4033
|
+
return;
|
|
4034
|
+
// Only handle dialog requests for this terminal's agent stub
|
|
4035
|
+
if (requestAgentId &&
|
|
4036
|
+
terminalAgentStubId &&
|
|
4037
|
+
requestAgentId !== terminalAgentStubId &&
|
|
4038
|
+
requestAgentId !== terminalAgentId) {
|
|
2835
4039
|
return;
|
|
2836
4040
|
}
|
|
2837
4041
|
console.log("[AgentTerminal] Received inline dialog request:", request);
|
|
2838
|
-
setActiveInlineDialog({
|
|
4042
|
+
setActiveInlineDialog({
|
|
4043
|
+
request,
|
|
4044
|
+
onComplete: (result) => {
|
|
4045
|
+
onComplete(result);
|
|
4046
|
+
onInteractionSubmitted?.();
|
|
4047
|
+
},
|
|
4048
|
+
onCancel: () => {
|
|
4049
|
+
onCancel();
|
|
4050
|
+
onInteractionSubmitted?.();
|
|
4051
|
+
},
|
|
4052
|
+
});
|
|
2839
4053
|
// Notify AgentDialogHandler that we accepted the dialog (stops retry mechanism)
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
4054
|
+
if (request.callbackId) {
|
|
4055
|
+
window.dispatchEvent(new CustomEvent("agent:dialog:accepted", {
|
|
4056
|
+
detail: { callbackId: request.callbackId },
|
|
4057
|
+
}));
|
|
4058
|
+
}
|
|
4059
|
+
setTimeout(() => {
|
|
4060
|
+
if (request.dialogType === "questionnaire") {
|
|
4061
|
+
scrollToBottomRefForDialogs.current?.();
|
|
4062
|
+
return;
|
|
4063
|
+
}
|
|
4064
|
+
scrollToBottomRefForDialogs.current?.();
|
|
4065
|
+
}, 100);
|
|
2845
4066
|
};
|
|
2846
4067
|
window.addEventListener("agent:dialog:show", handleDialogShow);
|
|
2847
|
-
return () =>
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
4068
|
+
return () => {
|
|
4069
|
+
const mounted = (globalThis.__agentDialogMountedAgents ?? []).filter((x) => typeof x === "string");
|
|
4070
|
+
globalThis.__agentDialogMountedAgents = mounted.filter((id) => id !== normalizeDialogAgentId(agentStubIdRefForDialogs.current));
|
|
4071
|
+
const visibleRegistry = { ...getVisibleDialogRegistry() };
|
|
4072
|
+
const idsToClear = [
|
|
4073
|
+
normalizeDialogAgentId(agentStubIdRefForDialogs.current),
|
|
4074
|
+
normalizeDialogAgentId(agentIdRefForDialogs.current),
|
|
4075
|
+
].filter(Boolean);
|
|
4076
|
+
idsToClear.forEach((id) => delete visibleRegistry[id]);
|
|
4077
|
+
globalThis.__agentDialogVisibleCallbacks = visibleRegistry;
|
|
4078
|
+
// If unmounting with an active dialog, defer the orphan event so that
|
|
4079
|
+
// React strict mode remounts can cancel it. Only real unmounts fire.
|
|
4080
|
+
const orphanedDialog = activeInlineDialogRef.current;
|
|
4081
|
+
if (orphanedDialog) {
|
|
4082
|
+
orphanTimeoutRef.current = setTimeout(() => {
|
|
4083
|
+
orphanTimeoutRef.current = null;
|
|
4084
|
+
window.dispatchEvent(new CustomEvent("agent:dialog:orphaned", {
|
|
4085
|
+
detail: { callbackId: orphanedDialog.request.callbackId },
|
|
4086
|
+
}));
|
|
4087
|
+
}, 300);
|
|
4088
|
+
}
|
|
4089
|
+
window.removeEventListener("agent:dialog:show", handleDialogShow);
|
|
4090
|
+
};
|
|
4091
|
+
}, []);
|
|
4092
|
+
// Announce when this terminal is ready to accept dialogs.
|
|
4093
|
+
// Fire immediately on mount using agentStub.id so the AgentDialogHandler
|
|
4094
|
+
// can re-dispatch pending dialogs without waiting for the async agent load.
|
|
4095
|
+
// Also fire when agent?.id becomes available in case it differs from the stub.
|
|
2851
4096
|
useEffect(() => {
|
|
2852
|
-
|
|
2853
|
-
|
|
4097
|
+
const normalizedStubId = normalizeDialogAgentId(agentStub.id);
|
|
4098
|
+
if (normalizedStubId) {
|
|
2854
4099
|
window.dispatchEvent(new CustomEvent("agent:terminal:ready", {
|
|
2855
|
-
detail: {
|
|
4100
|
+
detail: {
|
|
4101
|
+
agentId: normalizedStubId,
|
|
4102
|
+
terminalInstanceId: dialogTerminalInstanceIdRef.current,
|
|
4103
|
+
},
|
|
2856
4104
|
}));
|
|
2857
4105
|
}
|
|
2858
|
-
}, [
|
|
4106
|
+
}, [agentStub.id]);
|
|
4107
|
+
useEffect(() => {
|
|
4108
|
+
const normalizedAgentId = normalizeDialogAgentId(agent?.id);
|
|
4109
|
+
const normalizedStubId = normalizeDialogAgentId(agentStub.id);
|
|
4110
|
+
if (normalizedAgentId && normalizedAgentId !== normalizedStubId) {
|
|
4111
|
+
window.dispatchEvent(new CustomEvent("agent:terminal:ready", {
|
|
4112
|
+
detail: {
|
|
4113
|
+
agentId: normalizedAgentId,
|
|
4114
|
+
terminalInstanceId: dialogTerminalInstanceIdRef.current,
|
|
4115
|
+
},
|
|
4116
|
+
}));
|
|
4117
|
+
}
|
|
4118
|
+
}, [agent?.id, agentStub.id]);
|
|
2859
4119
|
// Profiles are provided by parent component (Agents). No local loading here.
|
|
2860
4120
|
// Select active profile based on agent.profileId or agentStub.profileId
|
|
2861
4121
|
useEffect(() => {
|
|
@@ -2866,20 +4126,47 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2866
4126
|
// Use case-insensitive comparison for GUID matching (backend may return different casing)
|
|
2867
4127
|
const normalizedProfileId = profileIdToUse?.toLowerCase();
|
|
2868
4128
|
const candidate = normalizedProfileId
|
|
2869
|
-
?
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
4129
|
+
? profiles.find((p) => p.id?.toLowerCase() === normalizedProfileId)
|
|
4130
|
+
: undefined;
|
|
4131
|
+
if (!candidate) {
|
|
4132
|
+
setActiveProfile(undefined);
|
|
4133
|
+
return;
|
|
2874
4134
|
}
|
|
2875
|
-
|
|
4135
|
+
// Keep active profile in sync whenever the matching entry in `profiles` changes —
|
|
4136
|
+
// not only when the profile id changes. Otherwise availableSkills (and similar fields)
|
|
4137
|
+
// that are merged in the parent after async loads never update (e.g. Template Builder
|
|
4138
|
+
// settings skills merged into the profile).
|
|
4139
|
+
setActiveProfile((prev) => {
|
|
4140
|
+
if (!prev || prev.id !== candidate.id)
|
|
4141
|
+
return candidate;
|
|
4142
|
+
const skillKey = (p) => JSON.stringify({
|
|
4143
|
+
allowed: (p.allowedSkills ?? []).map((s) => [
|
|
4144
|
+
String(s?.id ?? ""),
|
|
4145
|
+
String(s?.name ?? ""),
|
|
4146
|
+
]),
|
|
4147
|
+
available: (p.availableSkills ?? []).map((s) => [
|
|
4148
|
+
String(s?.id ?? ""),
|
|
4149
|
+
String(s?.name ?? ""),
|
|
4150
|
+
]),
|
|
4151
|
+
});
|
|
4152
|
+
if (skillKey(prev) === skillKey(candidate))
|
|
4153
|
+
return prev;
|
|
4154
|
+
return candidate;
|
|
4155
|
+
});
|
|
4156
|
+
}, [
|
|
4157
|
+
profiles,
|
|
4158
|
+
agent?.id,
|
|
4159
|
+
agent?.profileId,
|
|
4160
|
+
agentStub.id,
|
|
4161
|
+
agentStub.profileId,
|
|
4162
|
+
]);
|
|
2876
4163
|
// Clear queued prompts when agent changes or is new;
|
|
2877
4164
|
// initial fetch is handled by loadAgent() for better performance and reliability
|
|
2878
4165
|
useEffect(() => {
|
|
2879
|
-
if (!agent?.id ||
|
|
4166
|
+
if (!agent?.id || isLocalOnlyDraftAgent) {
|
|
2880
4167
|
setQueuedPrompts([]);
|
|
2881
4168
|
}
|
|
2882
|
-
}, [agent?.id,
|
|
4169
|
+
}, [agent?.id, isLocalOnlyDraftAgent]);
|
|
2883
4170
|
// Update selected model when the active profile or agent model changes
|
|
2884
4171
|
useEffect(() => {
|
|
2885
4172
|
if (!activeProfile)
|
|
@@ -2908,20 +4195,16 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2908
4195
|
// Initialize mode from metadata; fall back to agent.Mode from server
|
|
2909
4196
|
useEffect(() => {
|
|
2910
4197
|
try {
|
|
2911
|
-
const metaMode = agentMetadata?.mode;
|
|
2912
|
-
if (metaMode
|
|
2913
|
-
metaMode === "read-only" ||
|
|
2914
|
-
metaMode === "supervised") {
|
|
4198
|
+
const metaMode = normalizeAgentMode(agentMetadata?.mode);
|
|
4199
|
+
if (metaMode) {
|
|
2915
4200
|
setMode(metaMode);
|
|
2916
4201
|
return;
|
|
2917
4202
|
}
|
|
2918
4203
|
}
|
|
2919
4204
|
catch { }
|
|
2920
4205
|
try {
|
|
2921
|
-
const serverMode = agent?.mode;
|
|
2922
|
-
if (serverMode
|
|
2923
|
-
serverMode === "read-only" ||
|
|
2924
|
-
serverMode === "supervised") {
|
|
4206
|
+
const serverMode = normalizeAgentMode(agent?.mode);
|
|
4207
|
+
if (serverMode) {
|
|
2925
4208
|
setMode(serverMode);
|
|
2926
4209
|
}
|
|
2927
4210
|
}
|
|
@@ -2941,7 +4224,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2941
4224
|
textareaRef.current.focus();
|
|
2942
4225
|
}
|
|
2943
4226
|
}, [messages, activePlaceholderInput]);
|
|
2944
|
-
// Persist any pending settings (mode/model) once an agent exists server-side
|
|
4227
|
+
// Persist any pending settings (mode/model/profile) once an agent exists server-side
|
|
2945
4228
|
const persistPendingSettingsIfNeeded = useCallback(async () => {
|
|
2946
4229
|
try {
|
|
2947
4230
|
if (!agent?.id)
|
|
@@ -2954,6 +4237,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2954
4237
|
payload.model = pending.modelName;
|
|
2955
4238
|
if (pending.mode)
|
|
2956
4239
|
payload.mode = pending.mode;
|
|
4240
|
+
if (pending.profileId)
|
|
4241
|
+
payload.profileId = pending.profileId;
|
|
4242
|
+
if (pending.profileName != null)
|
|
4243
|
+
payload.profileName = pending.profileName;
|
|
2957
4244
|
if (Object.keys(payload).length === 0)
|
|
2958
4245
|
return;
|
|
2959
4246
|
await updateAgentSettings(agent.id, payload);
|
|
@@ -2963,6 +4250,92 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2963
4250
|
console.error("Failed to persist pending settings", e);
|
|
2964
4251
|
}
|
|
2965
4252
|
}, [agent?.id]);
|
|
4253
|
+
const getPendingRequestSettings = useCallback(() => {
|
|
4254
|
+
const pending = pendingSettingsRef.current;
|
|
4255
|
+
const requestProfile = pending?.profileId
|
|
4256
|
+
? profiles.find((profile) => profile.id === pending.profileId) ||
|
|
4257
|
+
activeProfile ||
|
|
4258
|
+
profiles[0]
|
|
4259
|
+
: activeProfile || profiles[0];
|
|
4260
|
+
let requestModelId = selectedModelId;
|
|
4261
|
+
if (pending?.modelName) {
|
|
4262
|
+
const requestModel = (requestProfile?.models || []).find((model) => (model.name || "").trim().toLowerCase() ===
|
|
4263
|
+
pending.modelName?.trim().toLowerCase());
|
|
4264
|
+
requestModelId = requestModel?.id || requestModelId;
|
|
4265
|
+
}
|
|
4266
|
+
return {
|
|
4267
|
+
mode: (pending?.mode || mode),
|
|
4268
|
+
profileId: pending?.profileId || requestProfile?.id || "",
|
|
4269
|
+
profileName: pending?.profileName || requestProfile?.name || "",
|
|
4270
|
+
modelId: requestModelId,
|
|
4271
|
+
};
|
|
4272
|
+
}, [activeProfile, mode, profiles, selectedModelId]);
|
|
4273
|
+
const getSubmitErrorMessage = (error) => {
|
|
4274
|
+
const fallback = "Failed to submit prompt. Please try again.";
|
|
4275
|
+
if (!(error instanceof Error))
|
|
4276
|
+
return fallback;
|
|
4277
|
+
const cleaned = toUserFacingAgentErrorMessage(error.message);
|
|
4278
|
+
return cleaned || fallback;
|
|
4279
|
+
};
|
|
4280
|
+
const suppressedQueuedPromptsRef = useRef([]);
|
|
4281
|
+
const pruneSuppressedQueuedPrompts = useCallback(() => {
|
|
4282
|
+
const cutoff = Date.now() - 15_000;
|
|
4283
|
+
suppressedQueuedPromptsRef.current =
|
|
4284
|
+
suppressedQueuedPromptsRef.current.filter((entry) => entry.createdAt >= cutoff);
|
|
4285
|
+
}, []);
|
|
4286
|
+
const registerSuppressedQueuedPrompt = useCallback((agentId, promptText) => {
|
|
4287
|
+
const normalizedAgentId = agentId.trim().toLowerCase();
|
|
4288
|
+
const normalizedPrompt = promptText.trim();
|
|
4289
|
+
if (!normalizedAgentId || !normalizedPrompt) {
|
|
4290
|
+
return null;
|
|
4291
|
+
}
|
|
4292
|
+
pruneSuppressedQueuedPrompts();
|
|
4293
|
+
const token = crypto.randomUUID();
|
|
4294
|
+
suppressedQueuedPromptsRef.current = [
|
|
4295
|
+
...suppressedQueuedPromptsRef.current,
|
|
4296
|
+
{
|
|
4297
|
+
token,
|
|
4298
|
+
agentId: normalizedAgentId,
|
|
4299
|
+
prompt: normalizedPrompt,
|
|
4300
|
+
createdAt: Date.now(),
|
|
4301
|
+
},
|
|
4302
|
+
];
|
|
4303
|
+
return token;
|
|
4304
|
+
}, [pruneSuppressedQueuedPrompts]);
|
|
4305
|
+
const clearSuppressedQueuedPrompt = useCallback((token) => {
|
|
4306
|
+
if (!token) {
|
|
4307
|
+
return;
|
|
4308
|
+
}
|
|
4309
|
+
suppressedQueuedPromptsRef.current =
|
|
4310
|
+
suppressedQueuedPromptsRef.current.filter((entry) => entry.token !== token);
|
|
4311
|
+
}, []);
|
|
4312
|
+
const shouldSuppressQueuedPrompt = useCallback((queueEntry) => {
|
|
4313
|
+
const normalizedAgentId = queueEntry?.targetAgentId?.trim().toLowerCase();
|
|
4314
|
+
const normalizedPrompt = queueEntry?.prompt?.trim();
|
|
4315
|
+
if (!normalizedAgentId || !normalizedPrompt) {
|
|
4316
|
+
return false;
|
|
4317
|
+
}
|
|
4318
|
+
pruneSuppressedQueuedPrompts();
|
|
4319
|
+
const matchedEntry = suppressedQueuedPromptsRef.current.find((entry) => entry.agentId === normalizedAgentId &&
|
|
4320
|
+
entry.prompt === normalizedPrompt);
|
|
4321
|
+
if (!matchedEntry) {
|
|
4322
|
+
return false;
|
|
4323
|
+
}
|
|
4324
|
+
suppressedQueuedPromptsRef.current =
|
|
4325
|
+
suppressedQueuedPromptsRef.current.filter((entry) => entry.token !== matchedEntry.token);
|
|
4326
|
+
return true;
|
|
4327
|
+
}, [pruneSuppressedQueuedPrompts]);
|
|
4328
|
+
const cancelActiveInlineDialog = useCallback(() => {
|
|
4329
|
+
const activeDialog = activeInlineDialogRef.current;
|
|
4330
|
+
if (!activeDialog)
|
|
4331
|
+
return;
|
|
4332
|
+
try {
|
|
4333
|
+
activeDialog.onCancel();
|
|
4334
|
+
}
|
|
4335
|
+
finally {
|
|
4336
|
+
setActiveInlineDialog(null);
|
|
4337
|
+
}
|
|
4338
|
+
}, []);
|
|
2966
4339
|
const handleSubmit = async () => {
|
|
2967
4340
|
// Guard against double-submit and missing context
|
|
2968
4341
|
if (isSubmitting) {
|
|
@@ -3006,6 +4379,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3006
4379
|
setError("Agent not ready. Please try again.");
|
|
3007
4380
|
return;
|
|
3008
4381
|
}
|
|
4382
|
+
clearStopGuard();
|
|
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,20 +5162,90 @@ 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 {
|
|
3755
5235
|
// 1. Set the stopping guard to prevent WebSocket handlers from re-enabling states
|
|
3756
5236
|
// This must happen FIRST, before any other state changes
|
|
3757
|
-
|
|
5237
|
+
armStopGuard();
|
|
3758
5238
|
// 2. Update all UI state to reflect stopped status
|
|
3759
5239
|
setIsWaitingForResponse(false);
|
|
3760
5240
|
isWaitingRef.current = false;
|
|
3761
5241
|
setIsConnecting(false);
|
|
3762
5242
|
setIsSubmitting(false);
|
|
5243
|
+
setAgent((prev) => prev ? { ...prev, status: "idle", statusMessage: undefined } : prev);
|
|
5244
|
+
shouldCreateNewMessage.current = false;
|
|
3763
5245
|
// User stopped the agent, hide thinking dots
|
|
3764
5246
|
setIsAgentThinking(false);
|
|
5247
|
+
// Stopping an agent discards queued follow-up prompts for that run.
|
|
5248
|
+
setQueuedPrompts([]);
|
|
3765
5249
|
// 3. Mark any in-progress streaming messages as completed in UI
|
|
3766
5250
|
setMessages((prev) => {
|
|
3767
5251
|
const updated = prev.map((msg) => !msg.isCompleted && msg.messageType === "streaming"
|
|
@@ -3779,46 +5263,111 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3779
5263
|
catch (err) {
|
|
3780
5264
|
console.error("Failed to cancel agent on backend:", err);
|
|
3781
5265
|
// Continue - UI is already in stopped state, but backend may still be running
|
|
3782
|
-
// The
|
|
5266
|
+
// The stop guard has its own timeout so future runs can proceed.
|
|
3783
5267
|
}
|
|
3784
5268
|
}
|
|
3785
5269
|
}
|
|
3786
5270
|
catch (e) {
|
|
3787
5271
|
console.error("Failed to stop agent execution", e);
|
|
3788
5272
|
}
|
|
3789
|
-
finally {
|
|
3790
|
-
// Clear the stopping guard so future runs can proceed normally
|
|
3791
|
-
// This happens after backend confirmation (or error)
|
|
3792
|
-
isStoppingRef.current = false;
|
|
3793
|
-
}
|
|
3794
5273
|
}, []);
|
|
3795
|
-
//
|
|
3796
|
-
//
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
agent?.costLimit
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
for (const c of candidates) {
|
|
3804
|
-
const n = c != null ? Number(c) : 0;
|
|
3805
|
-
if (Number.isFinite(n) && n > 0) {
|
|
3806
|
-
effectiveCostLimit = n;
|
|
3807
|
-
break;
|
|
3808
|
-
}
|
|
5274
|
+
// Cost limit is owned by the persisted agent record. Profiles can provide defaults
|
|
5275
|
+
// during creation, but once an agent exists the frontend should only display the
|
|
5276
|
+
// backend-provided value for that specific agent.
|
|
5277
|
+
const agentCostLimit = (() => {
|
|
5278
|
+
try {
|
|
5279
|
+
const value = agent?.costLimit;
|
|
5280
|
+
const parsed = value != null ? Number(value) : 0;
|
|
5281
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
|
|
3809
5282
|
}
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
//
|
|
3816
|
-
|
|
5283
|
+
catch {
|
|
5284
|
+
return undefined;
|
|
5285
|
+
}
|
|
5286
|
+
})();
|
|
5287
|
+
// Calculate total token usage for cost display.
|
|
5288
|
+
// Message rows do not currently persist imageCost, so on refresh we fall back
|
|
5289
|
+
// to the persisted agent aggregate for that single field.
|
|
5290
|
+
const totalTokens = (() => {
|
|
5291
|
+
const totals = calculateTotalTokens(messages);
|
|
5292
|
+
return {
|
|
5293
|
+
...totals,
|
|
5294
|
+
imageCost: totals.imageCost || Number(agent?.totalImageCost) || 0,
|
|
5295
|
+
};
|
|
5296
|
+
})();
|
|
5297
|
+
const normalizedAgentStatus = parseAgentStatus(agent?.status);
|
|
5298
|
+
const hasSettledToNonExecutingStatus = normalizedAgentStatus === "idle" ||
|
|
5299
|
+
normalizedAgentStatus === "completed" ||
|
|
5300
|
+
normalizedAgentStatus === "error" ||
|
|
5301
|
+
normalizedAgentStatus === "closed" ||
|
|
5302
|
+
normalizedAgentStatus === "waitingForApproval" ||
|
|
5303
|
+
normalizedAgentStatus === "waitingForInput" ||
|
|
5304
|
+
normalizedAgentStatus === "costLimitReached";
|
|
3817
5305
|
// Determine if the agent is actively executing (submitting, connecting, waiting, or streaming)
|
|
3818
|
-
const isExecuting =
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
5306
|
+
const isExecuting = !isStopGuardActive &&
|
|
5307
|
+
(isSubmitting ||
|
|
5308
|
+
isConnecting ||
|
|
5309
|
+
(!hasSettledToNonExecutingStatus &&
|
|
5310
|
+
(isWaitingForResponse || hasActiveStreaming())));
|
|
5311
|
+
const assistantMessageCount = useMemo(() => messages.filter((message) => message.role === "assistant").length, [messages]);
|
|
5312
|
+
const messagesWithToolCallsCount = useMemo(() => messages.filter((message) => (message.toolCalls || []).length > 0).length, [messages]);
|
|
5313
|
+
const totalToolCallCount = useMemo(() => messages.reduce((sum, message) => sum + (message.toolCalls?.length || 0), 0), [messages]);
|
|
5314
|
+
const incompleteToolCallCount = useMemo(() => messages.reduce((sum, message) => sum +
|
|
5315
|
+
(message.toolCalls?.filter((toolCall) => !toolCall.isCompleted).length || 0), 0), [messages]);
|
|
5316
|
+
const assistantGroupsWithRenderableToolCalls = useMemo(() => {
|
|
5317
|
+
const groups = groupConsecutiveMessages(messages);
|
|
5318
|
+
return groups.filter((group) => {
|
|
5319
|
+
if (group.type !== "assistant-group")
|
|
5320
|
+
return false;
|
|
5321
|
+
const convertedMessages = convertAgentMessagesToAiFormat(group.messages);
|
|
5322
|
+
return convertedMessages.some((message) => (message.tool_calls?.length || 0) > 0);
|
|
5323
|
+
}).length;
|
|
5324
|
+
}, [messages]);
|
|
5325
|
+
const assistantGroupCount = useMemo(() => groupConsecutiveMessages(messages).filter((group) => group.type === "assistant-group").length, [messages]);
|
|
5326
|
+
const runDiagnosticsSnapshot = useMemo(() => {
|
|
5327
|
+
const lastEvent = recentAgentRunEvents[recentAgentRunEvents.length - 1];
|
|
5328
|
+
return {
|
|
5329
|
+
agentId: currentAgentId,
|
|
5330
|
+
isSubmitting,
|
|
5331
|
+
isConnecting,
|
|
5332
|
+
isWaitingForResponse,
|
|
5333
|
+
isAgentThinking,
|
|
5334
|
+
isExecuting,
|
|
5335
|
+
hasActiveStreaming: hasActiveStreaming(),
|
|
5336
|
+
isSubscribed: normalizeDialogAgentId(subscribedAgentIdRef.current) ===
|
|
5337
|
+
normalizeDialogAgentId(currentAgentId),
|
|
5338
|
+
lastSeq: lastSeqRef.current,
|
|
5339
|
+
lastEventType: lastEvent?.type ?? null,
|
|
5340
|
+
lastEventAt: lastEvent?.timestamp ?? null,
|
|
5341
|
+
recentEvents: recentAgentRunEvents,
|
|
5342
|
+
assistantMessageCount,
|
|
5343
|
+
assistantGroupCount,
|
|
5344
|
+
assistantGroupsWithRenderableToolCalls,
|
|
5345
|
+
messagesWithToolCalls: messagesWithToolCallsCount,
|
|
5346
|
+
totalToolCallCount,
|
|
5347
|
+
incompleteToolCallCount,
|
|
5348
|
+
recentToolUiEvents,
|
|
5349
|
+
};
|
|
5350
|
+
}, [
|
|
5351
|
+
assistantGroupCount,
|
|
5352
|
+
assistantGroupsWithRenderableToolCalls,
|
|
5353
|
+
assistantMessageCount,
|
|
5354
|
+
currentAgentId,
|
|
5355
|
+
hasActiveStreaming,
|
|
5356
|
+
incompleteToolCallCount,
|
|
5357
|
+
isAgentThinking,
|
|
5358
|
+
isConnecting,
|
|
5359
|
+
isExecuting,
|
|
5360
|
+
isSubmitting,
|
|
5361
|
+
isWaitingForResponse,
|
|
5362
|
+
messagesWithToolCallsCount,
|
|
5363
|
+
recentAgentRunEvents,
|
|
5364
|
+
recentToolUiEvents,
|
|
5365
|
+
totalToolCallCount,
|
|
5366
|
+
]);
|
|
5367
|
+
const showInitialThinkingSplash = messages.length === 0 &&
|
|
5368
|
+
!error &&
|
|
5369
|
+
hideGreeting &&
|
|
5370
|
+
(isSubmitting || isConnecting);
|
|
3822
5371
|
// Compute dots visibility: only show BEFORE any assistant message exists
|
|
3823
5372
|
// This prevents duplicate headers - the dots indicator has its own header,
|
|
3824
5373
|
// and we don't want to show a second header below existing messages
|
|
@@ -3833,13 +5382,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3833
5382
|
// The message with the pending approval will display its own UI for approval
|
|
3834
5383
|
if (allPendingApprovals.length > 0)
|
|
3835
5384
|
return false;
|
|
5385
|
+
// The hidden-greeting startup splash already renders its own bouncing dots.
|
|
5386
|
+
// Suppress the generic indicator so reopening/running terminals don't show two.
|
|
5387
|
+
if (showInitialThinkingSplash)
|
|
5388
|
+
return false;
|
|
3836
5389
|
// IMPORTANT: If the last message is an assistant message and we're still executing,
|
|
3837
5390
|
// the AiResponseMessage for that message will show its own activity indicator.
|
|
3838
5391
|
// We only want these global thinking dots if the last message was from the user
|
|
3839
5392
|
// or if no messages exist yet (waiting for initial response).
|
|
3840
5393
|
const lastMessage = messages.length > 0 ? messages[messages.length - 1] : null;
|
|
3841
|
-
if (isExecuting &&
|
|
5394
|
+
if (isExecuting &&
|
|
5395
|
+
lastMessage?.role === "assistant" &&
|
|
5396
|
+
!lastMessage.isCompleted) {
|
|
3842
5397
|
return false;
|
|
5398
|
+
}
|
|
3843
5399
|
// Existing check for uncompleted assistant messages
|
|
3844
5400
|
const hasActiveStreamingMessage = messages.some((m) => !m.isCompleted && m.role === "assistant");
|
|
3845
5401
|
if (hasActiveStreamingMessage)
|
|
@@ -3855,20 +5411,22 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3855
5411
|
messages,
|
|
3856
5412
|
activeInlineDialog,
|
|
3857
5413
|
allPendingApprovals,
|
|
5414
|
+
showInitialThinkingSplash,
|
|
3858
5415
|
]);
|
|
3859
5416
|
// Move useMemo hook before early return to comply with Rules of Hooks
|
|
3860
|
-
const
|
|
5417
|
+
const resolvedEditorContextMode = React.useMemo(() => {
|
|
3861
5418
|
try {
|
|
3862
5419
|
const normalizedAgentProfileId = agent?.profileId?.toLowerCase();
|
|
3863
5420
|
const profile = activeProfile ||
|
|
3864
5421
|
profiles.find((p) => p.id?.toLowerCase() === normalizedAgentProfileId);
|
|
3865
|
-
|
|
3866
|
-
return mode === "live";
|
|
5422
|
+
return profile?.editorContextMode ?? null;
|
|
3867
5423
|
}
|
|
3868
5424
|
catch {
|
|
3869
|
-
return
|
|
5425
|
+
return null;
|
|
3870
5426
|
}
|
|
3871
5427
|
}, [activeProfile, profiles, agent?.profileId]);
|
|
5428
|
+
const isLiveEditorContextMode = resolvedEditorContextMode === "live";
|
|
5429
|
+
const omitsEditorContext = resolvedEditorContextMode === "none";
|
|
3872
5430
|
// Get parent agent ID from agent or agentStub (handle both camelCase and PascalCase)
|
|
3873
5431
|
const parentAgentId = agent?.parentAgentId ||
|
|
3874
5432
|
agent?.ParentAgentId ||
|
|
@@ -3882,10 +5440,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3882
5440
|
detail: { agentId: parentAgentId },
|
|
3883
5441
|
}));
|
|
3884
5442
|
}, [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 }));
|
|
5443
|
+
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;
|
|
5444
|
+
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
5445
|
const renderCostLimitBanner = () => {
|
|
3890
5446
|
if (!costLimitExceeded)
|
|
3891
5447
|
return null;
|
|
@@ -3896,9 +5452,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3896
5452
|
try {
|
|
3897
5453
|
// Extend cost limit - backend will automatically resume the agent
|
|
3898
5454
|
const result = await updateAgentCostLimit(agent.id, "extend");
|
|
3899
|
-
// Update the agent's cost limit
|
|
5455
|
+
// Update the agent's cost limit and clear the costLimitReached
|
|
5456
|
+
// status in local state so the useEffect watcher doesn't
|
|
5457
|
+
// immediately re-show the banner before the backend status
|
|
5458
|
+
// update arrives.
|
|
3900
5459
|
if (result.success && result.costLimit !== undefined) {
|
|
3901
|
-
setAgent((prev) => prev
|
|
5460
|
+
setAgent((prev) => prev
|
|
5461
|
+
? {
|
|
5462
|
+
...prev,
|
|
5463
|
+
costLimit: result.costLimit,
|
|
5464
|
+
status: prev.status === "costLimitReached"
|
|
5465
|
+
? "running"
|
|
5466
|
+
: prev.status,
|
|
5467
|
+
}
|
|
5468
|
+
: prev);
|
|
3902
5469
|
}
|
|
3903
5470
|
// Clear the banner and set waiting state
|
|
3904
5471
|
// Agent will resume automatically via backend's ResumeAgentAsync
|
|
@@ -3918,13 +5485,257 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3918
5485
|
};
|
|
3919
5486
|
const renderErrorBanner = () => {
|
|
3920
5487
|
const currentAgent = agent || agentStub;
|
|
3921
|
-
const isErrorStatus = currentAgent?.status === "error"
|
|
3922
|
-
const
|
|
3923
|
-
|
|
5488
|
+
const isErrorStatus = currentAgent?.status === "error";
|
|
5489
|
+
const isWaitingForInputStatus = currentAgent?.status === "waitingForInput";
|
|
5490
|
+
const isWaitingForApprovalStatus = currentAgent?.status === "waitingForApproval";
|
|
5491
|
+
// Show error banner for error status, or for any terminal status that still
|
|
5492
|
+
// carries a statusMessage (e.g. agent closed after an error).
|
|
5493
|
+
const isTerminalWithError = !isErrorStatus &&
|
|
5494
|
+
!!currentAgent?.statusMessage &&
|
|
5495
|
+
currentAgent?.status !== "running" &&
|
|
5496
|
+
currentAgent?.status !== "new" &&
|
|
5497
|
+
!isWaitingForInputStatus &&
|
|
5498
|
+
!isWaitingForApprovalStatus;
|
|
5499
|
+
const rawErrorMessage = (isErrorStatus || isTerminalWithError
|
|
5500
|
+
? currentAgent?.statusMessage
|
|
5501
|
+
: null) || error;
|
|
5502
|
+
if (!rawErrorMessage)
|
|
3924
5503
|
return null;
|
|
3925
|
-
|
|
5504
|
+
// Clean the error message (statusMessage from DB may contain raw JSON)
|
|
5505
|
+
const errorMessage = toUserFacingAgentErrorMessage(rawErrorMessage) || rawErrorMessage;
|
|
5506
|
+
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
5507
|
};
|
|
3927
|
-
|
|
5508
|
+
const renderBrowserClaimBanner = (variant = "inline") => {
|
|
5509
|
+
if (!agent?.id || !editContext?.sessionId)
|
|
5510
|
+
return null;
|
|
5511
|
+
if (!isClaimedByCurrentSession && !isClaimedByAnotherBrowser) {
|
|
5512
|
+
return null;
|
|
5513
|
+
}
|
|
5514
|
+
if (isPendingBrowserCaptureWait) {
|
|
5515
|
+
return null;
|
|
5516
|
+
}
|
|
5517
|
+
const label = isClaimedByCurrentSession
|
|
5518
|
+
? "Attached to this browser"
|
|
5519
|
+
: isClaimedByAnotherBrowser
|
|
5520
|
+
? "Attached in another browser"
|
|
5521
|
+
: "No browser attached";
|
|
5522
|
+
const description = isClaimedByCurrentSession
|
|
5523
|
+
? "This browser will handle page screenshot and DOM capture requests for the agent."
|
|
5524
|
+
: isClaimedByAnotherBrowser
|
|
5525
|
+
? "Capture requests will stay with the other browser until you take over control here."
|
|
5526
|
+
: "A page capture request is waiting for a browser attachment. Attach this browser to continue.";
|
|
5527
|
+
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");
|
|
5528
|
+
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: () => {
|
|
5529
|
+
void handleReleaseBrowser();
|
|
5530
|
+
}, 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: () => {
|
|
5531
|
+
void handleClaimBrowser(isClaimedByAnotherBrowser);
|
|
5532
|
+
}, children: isClaimedByAnotherBrowser
|
|
5533
|
+
? "Take over browser control"
|
|
5534
|
+
: "Attach to this browser" })) })] }) }));
|
|
5535
|
+
};
|
|
5536
|
+
const fixedBrowserClaimBanner = renderBrowserClaimBanner("fixed");
|
|
5537
|
+
const inlineBrowserClaimBanner = null;
|
|
5538
|
+
const browserCaptureInlinePrompt = isPendingBrowserCaptureWait && !isClaimedByCurrentSession
|
|
5539
|
+
? {
|
|
5540
|
+
toolNames: [
|
|
5541
|
+
"capture-page-screenshot",
|
|
5542
|
+
"capture-parhelia-ui-screenshot",
|
|
5543
|
+
"capture-page-dom",
|
|
5544
|
+
],
|
|
5545
|
+
label: isClaimedByAnotherBrowser
|
|
5546
|
+
? "Attached in another browser"
|
|
5547
|
+
: "No browser attached",
|
|
5548
|
+
description: isClaimedByAnotherBrowser
|
|
5549
|
+
? "This capture request is waiting in another browser. Take over browser control here to continue."
|
|
5550
|
+
: "This capture request is waiting for a browser attachment. Attach this browser to continue.",
|
|
5551
|
+
actionLabel: isClaimedByAnotherBrowser
|
|
5552
|
+
? "Take over browser control"
|
|
5553
|
+
: "Attach to this browser",
|
|
5554
|
+
isPending: isBrowserClaimMutationPending,
|
|
5555
|
+
onAction: () => {
|
|
5556
|
+
void handleClaimBrowser(isClaimedByAnotherBrowser);
|
|
5557
|
+
},
|
|
5558
|
+
}
|
|
5559
|
+
: null;
|
|
5560
|
+
useEffect(() => {
|
|
5561
|
+
if (agent?.status !== "waitingForInput") {
|
|
5562
|
+
setPendingBrowserCaptureDialogType(null);
|
|
5563
|
+
}
|
|
5564
|
+
}, [agent?.status]);
|
|
5565
|
+
const renderInlineDialogContent = () => {
|
|
5566
|
+
if (!activeInlineDialog)
|
|
5567
|
+
return null;
|
|
5568
|
+
if (activeInlineDialog.request.dialogType === "questionnaire") {
|
|
5569
|
+
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) => {
|
|
5570
|
+
activeInlineDialog.onComplete(result);
|
|
5571
|
+
setActiveInlineDialog(null);
|
|
5572
|
+
void onInteractionSubmitted?.();
|
|
5573
|
+
}, onCancel: () => {
|
|
5574
|
+
activeInlineDialog.onCancel();
|
|
5575
|
+
setActiveInlineDialog(null);
|
|
5576
|
+
} }) }));
|
|
5577
|
+
}
|
|
5578
|
+
const dialogRegistration = editContext?.configuration?.editor?.agentDialogs?.find((d) => d.dialogType === activeInlineDialog.request.dialogType);
|
|
5579
|
+
if (dialogRegistration) {
|
|
5580
|
+
const DialogComponent = dialogRegistration.component;
|
|
5581
|
+
return (_jsx("div", { className: "agent-inline-dialog", children: _jsx(DialogComponent, { title: activeInlineDialog.request.title, description: activeInlineDialog.request.description, parameters: activeInlineDialog.request.parameters, onClose: (result) => {
|
|
5582
|
+
activeInlineDialog.onComplete(result);
|
|
5583
|
+
setActiveInlineDialog(null);
|
|
5584
|
+
if (activeInlineDialog.request.dialogType === "questionnaire") {
|
|
5585
|
+
void onInteractionSubmitted?.();
|
|
5586
|
+
}
|
|
5587
|
+
}, onCancel: () => {
|
|
5588
|
+
activeInlineDialog.onCancel();
|
|
5589
|
+
setActiveInlineDialog(null);
|
|
5590
|
+
} }) }));
|
|
5591
|
+
}
|
|
5592
|
+
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] }) }));
|
|
5593
|
+
};
|
|
5594
|
+
const latestSummaryAssistantGroup = useMemo(() => {
|
|
5595
|
+
if (hideSummaryMessages)
|
|
5596
|
+
return null;
|
|
5597
|
+
const groups = groupConsecutiveMessages(messages);
|
|
5598
|
+
for (let groupIndex = groups.length - 1; groupIndex >= 0; groupIndex -= 1) {
|
|
5599
|
+
const group = groups[groupIndex];
|
|
5600
|
+
if (!group || group.type !== "assistant-group")
|
|
5601
|
+
continue;
|
|
5602
|
+
const filteredMessages = group.messages.filter((msg) => {
|
|
5603
|
+
const content = msg.content || "";
|
|
5604
|
+
return !content.startsWith("⚠️") || !content.includes("Cost limit");
|
|
5605
|
+
});
|
|
5606
|
+
if (filteredMessages.length === 0)
|
|
5607
|
+
continue;
|
|
5608
|
+
return {
|
|
5609
|
+
messages: filteredMessages,
|
|
5610
|
+
isLastGroup: groupIndex === groups.length - 1,
|
|
5611
|
+
};
|
|
5612
|
+
}
|
|
5613
|
+
return null;
|
|
5614
|
+
}, [messages, hideSummaryMessages]);
|
|
5615
|
+
const summaryModeContent = displayMode === "summary"
|
|
5616
|
+
? (() => {
|
|
5617
|
+
const inlineDialog = renderInlineDialogContent();
|
|
5618
|
+
const summaryMessages = latestSummaryAssistantGroup
|
|
5619
|
+
? convertAgentMessagesToAiFormat(latestSummaryAssistantGroup.messages)
|
|
5620
|
+
: [];
|
|
5621
|
+
const summaryOperations = latestSummaryAssistantGroup
|
|
5622
|
+
? getOperationsForMessageGroup(summaryMessages, agentOperations)
|
|
5623
|
+
: [];
|
|
5624
|
+
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 &&
|
|
5625
|
+
!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: {
|
|
5626
|
+
__html: sanitizeSvg(activeProfile.svgIcon),
|
|
5627
|
+
} })) : (_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 ||
|
|
5628
|
+
activeProfile?.displayTitle ||
|
|
5629
|
+
activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, browserCaptureInlinePrompt: browserCaptureInlinePrompt, onQuickAction: (action) => {
|
|
5630
|
+
const text = (action.prompt ||
|
|
5631
|
+
action.value ||
|
|
5632
|
+
action.label ||
|
|
5633
|
+
"").trim();
|
|
5634
|
+
if (!text)
|
|
5635
|
+
return;
|
|
5636
|
+
if (isExecuting) {
|
|
5637
|
+
try {
|
|
5638
|
+
handleStop();
|
|
5639
|
+
}
|
|
5640
|
+
catch { }
|
|
5641
|
+
}
|
|
5642
|
+
sendQuickMessage(text);
|
|
5643
|
+
} }) })) : 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
|
|
5644
|
+
? "The agent is still working. The next update will appear here automatically."
|
|
5645
|
+
: agent?.statusMessage ||
|
|
5646
|
+
summaryPlaceholderMessage ||
|
|
5647
|
+
"Waiting for the next agent update." }), summaryPlaceholderActions ? (_jsx("div", { className: `flex justify-center ${compact ? "mt-2" : "mt-3"}`, children: summaryPlaceholderActions })) : null] }) })), displayMode !== "summary" &&
|
|
5648
|
+
shouldShowThinkingDots &&
|
|
5649
|
+
!inlineDialog &&
|
|
5650
|
+
!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: {
|
|
5651
|
+
__html: sanitizeSvg(activeProfile.svgIcon),
|
|
5652
|
+
} })) : (_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 ||
|
|
5653
|
+
activeProfile?.displayTitle ||
|
|
5654
|
+
activeProfile?.name ||
|
|
5655
|
+
"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) => {
|
|
5656
|
+
setActivePlaceholderInput(null);
|
|
5657
|
+
setAllPlaceholdersFilled(false);
|
|
5658
|
+
if (activePlaceholderInput.behavior === "compose" &&
|
|
5659
|
+
!hideBottomControls) {
|
|
5660
|
+
setPrompt(filledText);
|
|
5661
|
+
setInputPlaceholder("Review and edit, then press Enter to send");
|
|
5662
|
+
if (textareaRef.current) {
|
|
5663
|
+
try {
|
|
5664
|
+
textareaRef.current.focus();
|
|
5665
|
+
const v = textareaRef.current.value || "";
|
|
5666
|
+
textareaRef.current.selectionStart = v.length;
|
|
5667
|
+
textareaRef.current.selectionEnd = v.length;
|
|
5668
|
+
}
|
|
5669
|
+
catch { }
|
|
5670
|
+
}
|
|
5671
|
+
}
|
|
5672
|
+
else {
|
|
5673
|
+
if (isExecuting) {
|
|
5674
|
+
try {
|
|
5675
|
+
handleStop();
|
|
5676
|
+
}
|
|
5677
|
+
catch { }
|
|
5678
|
+
}
|
|
5679
|
+
sendQuickMessage(filledText);
|
|
5680
|
+
}
|
|
5681
|
+
}, onCancel: () => {
|
|
5682
|
+
setActivePlaceholderInput(null);
|
|
5683
|
+
setAllPlaceholdersFilled(false);
|
|
5684
|
+
} })) : prompt && /\{\{[^{}]+\}\}|<<[^<>]+>>/.test(prompt) ? (_jsx(PlaceholderInput, { ref: promptPlaceholderInputRef, text: prompt, showButtons: hideBottomControls, buttonsClassName: hideBottomControls ? "justify-end" : "", onFilledChange: setAllPlaceholdersFilled, onComplete: (filledText) => {
|
|
5685
|
+
setPrompt(filledText);
|
|
5686
|
+
setAllPlaceholdersFilled(false);
|
|
5687
|
+
if (filledText.trim()) {
|
|
5688
|
+
if (isExecuting) {
|
|
5689
|
+
try {
|
|
5690
|
+
handleStop();
|
|
5691
|
+
}
|
|
5692
|
+
catch { }
|
|
5693
|
+
}
|
|
5694
|
+
sendQuickMessage(filledText);
|
|
5695
|
+
}
|
|
5696
|
+
}, onCancel: () => {
|
|
5697
|
+
setPrompt("");
|
|
5698
|
+
setAllPlaceholdersFilled(false);
|
|
5699
|
+
setInputPlaceholder("Type your message... (Enter to send, Shift+Enter or Ctrl+Enter for new line)");
|
|
5700
|
+
} })) : (_jsx("div", { className: "flex items-stretch gap-2", children: _jsx(Textarea, { ref: textareaRef, style: { viewTransitionName: "assistant-chat-input" }, value: prompt, onChange: (e) => {
|
|
5701
|
+
setPrompt(e.target.value);
|
|
5702
|
+
if (!/\{\{[^{}]+\}\}|<<[^<>]+>>/.test(e.target.value)) {
|
|
5703
|
+
setAllPlaceholdersFilled(false);
|
|
5704
|
+
}
|
|
5705
|
+
if (currentHistoryIndex !== -1) {
|
|
5706
|
+
setCurrentHistoryIndex(-1);
|
|
5707
|
+
}
|
|
5708
|
+
}, onKeyDown: handleKeyPress, onPaste: handlePaste, onFocus: () => {
|
|
5709
|
+
shouldMaintainFocusRef.current = true;
|
|
5710
|
+
}, onBlur: () => {
|
|
5711
|
+
shouldMaintainFocusRef.current = false;
|
|
5712
|
+
}, 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 }) })), (() => {
|
|
5713
|
+
const isInPlaceholderMode = activePlaceholderInput ||
|
|
5714
|
+
(prompt && /\{\{[^{}]+\}\}|<<[^<>]+>>/.test(prompt));
|
|
5715
|
+
const placeholderShowsOwnButtons = hideBottomControls && isInPlaceholderMode;
|
|
5716
|
+
if (placeholderShowsOwnButtons)
|
|
5717
|
+
return null;
|
|
5718
|
+
return (_jsxs("div", { className: cn("mt-2 flex items-stretch gap-2", hideBottomControls ||
|
|
5719
|
+
simpleMode ||
|
|
5720
|
+
isInPlaceholderMode
|
|
5721
|
+
? "justify-end"
|
|
5722
|
+
: "justify-between"), children: [!hideBottomControls &&
|
|
5723
|
+
!simpleMode &&
|
|
5724
|
+
!isInPlaceholderMode ? (_jsx("div", { className: "flex-1" })) : null, _jsx(Button, { type: "button", size: "sm", onClick: () => {
|
|
5725
|
+
if (isExecuting) {
|
|
5726
|
+
handleStop();
|
|
5727
|
+
}
|
|
5728
|
+
else {
|
|
5729
|
+
handleSubmit();
|
|
5730
|
+
}
|
|
5731
|
+
}, disabled: !isExecuting &&
|
|
5732
|
+
!activePlaceholderInput &&
|
|
5733
|
+
(!prompt.trim() || isSubmitting), "data-testid": "agent-send-stop-button", children: isExecuting ? "Stop" : "Send" })] }));
|
|
5734
|
+
})()] })) : null] }));
|
|
5735
|
+
})()
|
|
5736
|
+
: null;
|
|
5737
|
+
const fullModeInlineDialog = displayMode === "full" ? renderInlineDialogContent() : null;
|
|
5738
|
+
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
5739
|
setPrompt(p);
|
|
3929
5740
|
// Use setTimeout to ensure state is updated before submission
|
|
3930
5741
|
setTimeout(() => {
|
|
@@ -3937,12 +5748,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3937
5748
|
handleSubmit();
|
|
3938
5749
|
}
|
|
3939
5750
|
}, 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: [(() => {
|
|
5751
|
+
} })) })), 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: {
|
|
5752
|
+
__html: sanitizeSvg(activeProfile.svgIcon),
|
|
5753
|
+
} })) : (_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
5754
|
const groups = groupConsecutiveMessages(messages);
|
|
3947
5755
|
return groups.map((group, groupIndex) => {
|
|
3948
5756
|
const isLastGroup = groupIndex === groups.length - 1;
|
|
@@ -3950,6 +5758,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3950
5758
|
// Render user message
|
|
3951
5759
|
return (_jsx(UserMessage, { message: group.messages[0] }, groupIndex));
|
|
3952
5760
|
}
|
|
5761
|
+
else if (group.type === "heartbeat" && group.messages[0]) {
|
|
5762
|
+
return (_jsx(HeartbeatMessage, { message: group.messages[0] }, group.messages[0].id || groupIndex));
|
|
5763
|
+
}
|
|
3953
5764
|
else {
|
|
3954
5765
|
// Render bundled assistant messages
|
|
3955
5766
|
// Check if this group contains any streaming message
|
|
@@ -3966,9 +5777,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3966
5777
|
}
|
|
3967
5778
|
const convertedMessages = convertAgentMessagesToAiFormat(filteredMessages);
|
|
3968
5779
|
const operationsForGroup = getOperationsForMessageGroup(convertedMessages, agentOperations);
|
|
3969
|
-
return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isLastGroup || !isExecuting, editOperations: operationsForGroup,
|
|
5780
|
+
return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isLastGroup || !isExecuting, editOperations: operationsForGroup, defaultCollapseJson: defaultCollapseJson, profileSvgIcon: activeProfile?.svgIcon, agentId: agent?.id || agentStub.id, agentName: activeProfile?.agentName ||
|
|
3970
5781
|
activeProfile?.displayTitle ||
|
|
3971
|
-
activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, onQuickAction: (action) => {
|
|
5782
|
+
activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, browserCaptureInlinePrompt: browserCaptureInlinePrompt, onQuickAction: (action) => {
|
|
3972
5783
|
const text = (action.prompt ||
|
|
3973
5784
|
action.value ||
|
|
3974
5785
|
action.label ||
|
|
@@ -4012,13 +5823,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4012
5823
|
}
|
|
4013
5824
|
});
|
|
4014
5825
|
})(), 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,
|
|
5826
|
+
__html: sanitizeSvg(activeProfile.svgIcon),
|
|
4016
5827
|
} })) : (_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
5828
|
activeProfile?.displayTitle ||
|
|
4018
5829
|
activeProfile?.name ||
|
|
4019
5830
|
"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
5831
|
!simpleMode &&
|
|
4021
|
-
(isMobile ? (_jsx("div", { className: "border-t border-gray-200 bg-gray-50", "data-testid": "agent-context-panel-tabs", children: _jsx(SimpleTabs, { tabs: [
|
|
5832
|
+
(editContext?.isMobile ? (_jsx("div", { className: "border-t border-gray-200 bg-gray-50", "data-testid": "agent-context-panel-tabs", children: _jsx(SimpleTabs, { tabs: [
|
|
4022
5833
|
{
|
|
4023
5834
|
id: "context",
|
|
4024
5835
|
label: "Context",
|
|
@@ -4056,7 +5867,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4056
5867
|
{
|
|
4057
5868
|
id: "history",
|
|
4058
5869
|
label: "History",
|
|
4059
|
-
content: (_jsx(AgentEditOperationsPanel, {
|
|
5870
|
+
content: (_jsx(AgentEditOperationsPanel, { operations: agentOperations })),
|
|
4060
5871
|
},
|
|
4061
5872
|
]
|
|
4062
5873
|
: []),
|
|
@@ -4066,28 +5877,49 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4066
5877
|
hasTodoContent,
|
|
4067
5878
|
hasSpawnedAgents,
|
|
4068
5879
|
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, {
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
5880
|
+
].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, { operations: agentOperations }))] }))), 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) => {
|
|
5881
|
+
let triggerName = "";
|
|
5882
|
+
if (qp.data) {
|
|
5883
|
+
try {
|
|
5884
|
+
const parsed = JSON.parse(qp.data);
|
|
5885
|
+
triggerName = parsed?.triggerName?.trim() || "";
|
|
5886
|
+
}
|
|
5887
|
+
catch {
|
|
5888
|
+
// Ignore invalid JSON metadata and render as regular queued prompt.
|
|
5889
|
+
}
|
|
5890
|
+
}
|
|
5891
|
+
const isTriggerQueuedPrompt = !qp.sourceAgentName && triggerName.length > 0;
|
|
5892
|
+
const isTriggerExpanded = !!expandedQueuedTriggerIds[qp.id];
|
|
5893
|
+
if (isTriggerQueuedPrompt) {
|
|
5894
|
+
return (_jsxs("div", { className: "text-[11px]", "data-testid": "queued-prompt-item", children: [_jsxs("button", { type: "button", onClick: () => setExpandedQueuedTriggerIds((prev) => ({
|
|
5895
|
+
...prev,
|
|
5896
|
+
[qp.id]: !prev[qp.id],
|
|
5897
|
+
})), 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));
|
|
5898
|
+
}
|
|
5899
|
+
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 &&
|
|
5900
|
+
new Date(qp.scheduledFor).getTime() >
|
|
5901
|
+
new Date(qp.createdDate || 0).getTime() +
|
|
5902
|
+
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() ===
|
|
5903
|
+
new Date().toDateString()
|
|
5904
|
+
? formatTime(new Date(qp.scheduledFor))
|
|
5905
|
+
: formatDateTime(new Date(qp.scheduledFor))] })] }))] })] }) }) }, qp.id));
|
|
5906
|
+
}) })] }) }))] }));
|
|
5907
|
+
const showQuestionnaireSplitter = isQuestionnaireDialogOpen && !!fullModeInlineDialog;
|
|
5908
|
+
const fullModeContent = showQuestionnaireSplitter ? (_jsx(Splitter, { panels: [
|
|
5909
|
+
{
|
|
5910
|
+
name: "conversation",
|
|
5911
|
+
defaultSize: 65,
|
|
5912
|
+
content: fullModeUpperContent,
|
|
5913
|
+
},
|
|
5914
|
+
{
|
|
5915
|
+
name: "questionnaire",
|
|
5916
|
+
defaultSize: 35,
|
|
5917
|
+
content: fullModeInlineDialog,
|
|
5918
|
+
},
|
|
5919
|
+
], direction: "vertical", localStorageKey: compact
|
|
5920
|
+
? "agent-terminal-compact-questionnaire-splitter"
|
|
5921
|
+
: "agent-terminal-questionnaire-splitter", className: "min-h-0 flex-1", splitterClassName: "bg-gray-200 hover:bg-gray-300" })) : (_jsxs(_Fragment, { children: [fullModeUpperContent, fullModeInlineDialog] }));
|
|
5922
|
+
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
5923
|
// Placeholder Input (from quick actions)
|
|
4092
5924
|
// Show internal buttons only in splash mode (hideBottomControls) since external buttons won't be visible there
|
|
4093
5925
|
_jsx(PlaceholderInput, { ref: placeholderInputRef, text: activePlaceholderInput.text, showButtons: hideBottomControls, buttonsClassName: hideBottomControls ? "justify-end" : "", onFilledChange: setAllPlaceholdersFilled, onComplete: (filledText) => {
|
|
@@ -4164,122 +5996,221 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4164
5996
|
return null;
|
|
4165
5997
|
return (_jsxs("div", { className: cn("mt-2 flex items-stretch gap-2", hideBottomControls || simpleMode || isInPlaceholderMode
|
|
4166
5998
|
? "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"
|
|
5999
|
+
: "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
6000
|
? "border-green-300 bg-green-50! text-green-700 hover:bg-green-100!"
|
|
4169
6001
|
: mode === "supervised"
|
|
4170
6002
|
? "border-amber-300 bg-amber-50! text-amber-700 hover:bg-amber-100!"
|
|
4171
6003
|
: "border-red-300 bg-red-50! text-red-700 hover:bg-red-100!"), value: mode, options: modeOptions, onValueChange: async (val) => {
|
|
4172
6004
|
const nextMode = val || "supervised";
|
|
4173
|
-
// Optimistic UI update
|
|
4174
|
-
setMode(nextMode);
|
|
4175
6005
|
const current = agentMetadata || {};
|
|
4176
6006
|
const nextMeta = {
|
|
4177
6007
|
...current,
|
|
4178
6008
|
mode: nextMode,
|
|
4179
6009
|
};
|
|
4180
6010
|
try {
|
|
4181
|
-
if (!agent?.id ||
|
|
6011
|
+
if (!agent?.id || isLocalOnlyDraftAgent) {
|
|
6012
|
+
setMode(nextMode);
|
|
4182
6013
|
setAgentMetadata(nextMeta);
|
|
4183
|
-
// Cache until first start when agent is persisted
|
|
4184
6014
|
pendingSettingsRef.current = {
|
|
4185
6015
|
...(pendingSettingsRef.current || {}),
|
|
4186
6016
|
mode: nextMode,
|
|
4187
6017
|
};
|
|
4188
6018
|
return;
|
|
4189
6019
|
}
|
|
4190
|
-
await updateAgentSettings(agent.id, {
|
|
6020
|
+
const result = await updateAgentSettings(agent.id, {
|
|
4191
6021
|
mode: nextMode,
|
|
4192
6022
|
});
|
|
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
|
-
});
|
|
6023
|
+
if (result.success === false ||
|
|
6024
|
+
result.updates?.mode === false) {
|
|
6025
|
+
throw new Error("Mode change was not applied");
|
|
4229
6026
|
}
|
|
4230
|
-
|
|
6027
|
+
setMode(nextMode);
|
|
6028
|
+
setAgentMetadata(nextMeta);
|
|
4231
6029
|
setAgent((prev) => prev
|
|
4232
6030
|
? {
|
|
4233
6031
|
...prev,
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
profile: nextProfile.name,
|
|
4237
|
-
additionalData: {
|
|
4238
|
-
...(agentMetadata
|
|
4239
|
-
?.additionalData || {}),
|
|
4240
|
-
profileId: nextProfile.id,
|
|
4241
|
-
profileName: nextProfile.name,
|
|
4242
|
-
},
|
|
4243
|
-
}),
|
|
6032
|
+
mode: nextMode,
|
|
6033
|
+
metadata: JSON.stringify(nextMeta),
|
|
4244
6034
|
}
|
|
4245
6035
|
: prev);
|
|
4246
6036
|
}
|
|
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
|
-
}
|
|
6037
|
+
catch (e2) {
|
|
6038
|
+
console.error("Failed to persist mode change", e2);
|
|
4270
6039
|
}
|
|
4271
|
-
|
|
4272
|
-
|
|
6040
|
+
} }), _jsxs(Popover, { open: showAgentSettings, onOpenChange: (open) => {
|
|
6041
|
+
setShowAgentSettings(open);
|
|
6042
|
+
if (!open) {
|
|
6043
|
+
setShowSkillPicker(false);
|
|
4273
6044
|
}
|
|
4274
|
-
}
|
|
6045
|
+
}, 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) => {
|
|
6046
|
+
const target = e.target;
|
|
6047
|
+
if (target?.closest('[data-help-panel="true"]')) {
|
|
6048
|
+
e.preventDefault();
|
|
6049
|
+
}
|
|
6050
|
+
}, onPointerDownOutside: (e) => {
|
|
6051
|
+
const target = e.target;
|
|
6052
|
+
if (target?.closest('[data-help-panel="true"]')) {
|
|
6053
|
+
e.preventDefault();
|
|
6054
|
+
}
|
|
6055
|
+
}, onFocusOutside: (e) => {
|
|
6056
|
+
const target = e.target;
|
|
6057
|
+
if (target?.closest('[data-help-panel="true"]')) {
|
|
6058
|
+
e.preventDefault();
|
|
6059
|
+
}
|
|
6060
|
+
}, 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) => {
|
|
6061
|
+
const nextProfile = profiles.find((x) => x.id === val);
|
|
6062
|
+
if (!nextProfile)
|
|
6063
|
+
return;
|
|
6064
|
+
setActiveProfile(nextProfile);
|
|
6065
|
+
try {
|
|
6066
|
+
if (agent?.id && !isLocalOnlyDraftAgent) {
|
|
6067
|
+
await updateAgentSettings(agent.id, {
|
|
6068
|
+
profileId: nextProfile.id,
|
|
6069
|
+
profileName: nextProfile.name,
|
|
6070
|
+
});
|
|
6071
|
+
}
|
|
6072
|
+
else {
|
|
6073
|
+
pendingSettingsRef.current = {
|
|
6074
|
+
...(pendingSettingsRef.current || {}),
|
|
6075
|
+
profileId: nextProfile.id,
|
|
6076
|
+
profileName: nextProfile.name,
|
|
6077
|
+
};
|
|
6078
|
+
setAgentMetadata((current) => {
|
|
6079
|
+
const next = {
|
|
6080
|
+
...(current || {}),
|
|
6081
|
+
};
|
|
6082
|
+
next.profile = nextProfile.name;
|
|
6083
|
+
next.additionalData = {
|
|
6084
|
+
...(next.additionalData || {}),
|
|
6085
|
+
profileId: nextProfile.id,
|
|
6086
|
+
profileName: nextProfile.name,
|
|
6087
|
+
};
|
|
6088
|
+
return next;
|
|
6089
|
+
});
|
|
6090
|
+
}
|
|
6091
|
+
setAgent((prev) => prev
|
|
6092
|
+
? {
|
|
6093
|
+
...prev,
|
|
6094
|
+
profileId: nextProfile.id,
|
|
6095
|
+
profileName: nextProfile.name,
|
|
6096
|
+
metadata: JSON.stringify({
|
|
6097
|
+
...(agentMetadata || {}),
|
|
6098
|
+
profile: nextProfile.name,
|
|
6099
|
+
additionalData: {
|
|
6100
|
+
...(agentMetadata
|
|
6101
|
+
?.additionalData || {}),
|
|
6102
|
+
profileId: nextProfile.id,
|
|
6103
|
+
profileName: nextProfile.name,
|
|
6104
|
+
},
|
|
6105
|
+
}),
|
|
6106
|
+
}
|
|
6107
|
+
: prev);
|
|
6108
|
+
}
|
|
6109
|
+
catch (err) {
|
|
6110
|
+
console.error("Failed to persist agent profile", err);
|
|
6111
|
+
}
|
|
6112
|
+
} }), 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: () => {
|
|
6113
|
+
void handleEditProfileSideBySide();
|
|
6114
|
+
setShowAgentSettings(false);
|
|
6115
|
+
}, "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: () => {
|
|
6116
|
+
void handleOpenProfileSettings();
|
|
6117
|
+
setShowAgentSettings(false);
|
|
6118
|
+
}, 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) => {
|
|
6119
|
+
const nextId = val;
|
|
6120
|
+
setSelectedModelId(nextId);
|
|
6121
|
+
const modelName = activeProfile?.models?.find((m) => m.id === nextId)?.name || "";
|
|
6122
|
+
setAgent((prev) => prev ? { ...prev, model: modelName } : prev);
|
|
6123
|
+
try {
|
|
6124
|
+
if (agent?.id && !isLocalOnlyDraftAgent) {
|
|
6125
|
+
await updateAgentSettings(agent.id, {
|
|
6126
|
+
model: modelName,
|
|
6127
|
+
});
|
|
6128
|
+
}
|
|
6129
|
+
else {
|
|
6130
|
+
pendingSettingsRef.current = {
|
|
6131
|
+
...(pendingSettingsRef.current || {}),
|
|
6132
|
+
modelName,
|
|
6133
|
+
};
|
|
6134
|
+
}
|
|
6135
|
+
}
|
|
6136
|
+
catch (err) {
|
|
6137
|
+
console.error("Failed to persist agent model", err);
|
|
6138
|
+
}
|
|
6139
|
+
} })] })) : 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) => {
|
|
6140
|
+
setShowSkillPicker(open);
|
|
6141
|
+
if (open) {
|
|
6142
|
+
setSkillActionError(null);
|
|
6143
|
+
}
|
|
6144
|
+
}, 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) => {
|
|
6145
|
+
const selected = selection[0];
|
|
6146
|
+
if (!selected?.id)
|
|
6147
|
+
return;
|
|
6148
|
+
setSkillActionError(null);
|
|
6149
|
+
if (selectableTemplateIdSet.size > 0 &&
|
|
6150
|
+
(!selected.templateId ||
|
|
6151
|
+
!selectableTemplateIdSet.has(selected.templateId.toLowerCase()))) {
|
|
6152
|
+
return;
|
|
6153
|
+
}
|
|
6154
|
+
if (!manuallyAssignableSkillIdSet.has(selected.id.toLowerCase())) {
|
|
6155
|
+
setSkillActionError("This skill cannot be added for the current agent profile.");
|
|
6156
|
+
return;
|
|
6157
|
+
}
|
|
6158
|
+
void (async () => {
|
|
6159
|
+
const added = await handleAddSkill(selected.id);
|
|
6160
|
+
if (added) {
|
|
6161
|
+
setShowSkillPicker(false);
|
|
6162
|
+
}
|
|
6163
|
+
})();
|
|
6164
|
+
} }), skillsLoading && (_jsx("div", { className: "bg-background/70 absolute inset-0 flex items-center justify-center text-[10px] text-gray-500", children: "Loading skills..." }))] }), !skillsLoading &&
|
|
6165
|
+
!skillsError &&
|
|
6166
|
+
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 &&
|
|
6167
|
+
!skillsError &&
|
|
6168
|
+
skillRootIds.length === 0 && (_jsx("div", { className: "text-[10px] text-gray-500", children: "No skill roots available." })), !skillsLoading &&
|
|
6169
|
+
!skillsError &&
|
|
6170
|
+
profileFilteredSkills.length === 0 && (_jsx("div", { className: "text-[10px] text-gray-500", children: selectedSkillIds.length > 0
|
|
6171
|
+
? "All addable skills are selected"
|
|
6172
|
+
: "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) => {
|
|
6173
|
+
const skill = selectedSkills.find((s) => s.id === skillId);
|
|
6174
|
+
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: () => {
|
|
6175
|
+
void handleOpenSkillItem(skillId);
|
|
6176
|
+
}, 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: () => {
|
|
6177
|
+
void handleRemoveSkill(skillId);
|
|
6178
|
+
}, title: "Remove skill", "aria-label": `Remove ${skill?.name || skillId}`, children: _jsx(X, { className: "h-2.5 w-2.5", strokeWidth: 1 }) }))] }, skillId));
|
|
6179
|
+
}) }))] }), _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
|
|
6180
|
+
? "No available tools for this profile and mode"
|
|
6181
|
+
: "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) => {
|
|
6182
|
+
const sourceLabel = formatAllowanceSource(allowance.source);
|
|
6183
|
+
const pathLabel = "itemPath" in allowance
|
|
6184
|
+
? allowance.itemPath
|
|
6185
|
+
: allowance.normalizedPath;
|
|
6186
|
+
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 ||
|
|
6187
|
+
"*" }), _jsx("div", { className: "truncate text-[9px] text-gray-500", title: formatAllowanceLabel(allowance), children: pathLabel })] }), (sourceLabel ||
|
|
6188
|
+
allowance.grantedBy) && (_jsx("div", { className: "truncate pl-6 text-[9px] text-gray-400", children: [
|
|
6189
|
+
sourceLabel,
|
|
6190
|
+
allowance.grantedBy,
|
|
6191
|
+
]
|
|
6192
|
+
.filter(Boolean)
|
|
6193
|
+
.join(" · ") }))] }, `${group.key}-${allowance.operationType}-${pathLabel}-${index}`));
|
|
6194
|
+
})] }, group.key)) : null) })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isLocalOnlyDraftAgent
|
|
6195
|
+
? "Allowances are shown after the agent is created"
|
|
6196
|
+
: "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) => {
|
|
6197
|
+
const filterText = (sub.filter || "").trim();
|
|
6198
|
+
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));
|
|
6199
|
+
}) })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isLocalOnlyDraftAgent
|
|
6200
|
+
? "Subscribed triggers are shown after the agent is created"
|
|
6201
|
+
: "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
6202
|
setPrompt(p.prompt);
|
|
4276
6203
|
setShowPredefined(false);
|
|
4277
6204
|
if (textareaRef.current)
|
|
4278
6205
|
textareaRef.current.focus();
|
|
4279
|
-
}, children: p.title }, index))) }) })] })) : null, !hideBottomControls &&
|
|
4280
|
-
|
|
6206
|
+
}, children: p.title }, index))) }) })] })) : null, !hideBottomControls &&
|
|
6207
|
+
!simpleMode &&
|
|
6208
|
+
editContext?.isMobile && (_jsxs(Popover, { open: showCostAndAgent, onOpenChange: setShowCostAndAgent, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { onClick: () => {
|
|
6209
|
+
if (editContext?.isMobile)
|
|
4281
6210
|
setShowCostAndAgent((prev) => !prev);
|
|
4282
|
-
}, variant: "outline", size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", "aria-expanded": isMobile
|
|
6211
|
+
}, variant: "outline", size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", "aria-expanded": editContext?.isMobile
|
|
6212
|
+
? showCostAndAgent
|
|
6213
|
+
: 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
6214
|
? {
|
|
4284
6215
|
input: liveTotals.input,
|
|
4285
6216
|
output: liveTotals.output,
|
|
@@ -4289,9 +6220,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4289
6220
|
outputCost: liveTotals.outputCost,
|
|
4290
6221
|
cachedCost: liveTotals.cachedCost,
|
|
4291
6222
|
cacheWriteCost: liveTotals.cacheWriteCost ?? 0,
|
|
6223
|
+
imageCost: liveTotals.imageCost ?? 0,
|
|
4292
6224
|
totalCost: liveTotals.totalCost,
|
|
4293
6225
|
}
|
|
4294
|
-
: totalTokens,
|
|
6226
|
+
: totalTokens, costLimit: agentCostLimit, messages: messages, showCompressionPopover: showCompressionPopover, setShowCompressionPopover: setShowCompressionPopover }) }) })] }))] })), _jsxs("div", { className: "flex items-center gap-1 self-end", children: [_jsx("span", { title: isVoiceDisabled
|
|
4295
6227
|
? "Your browser does not support Speech Recognition"
|
|
4296
6228
|
: isListening
|
|
4297
6229
|
? "Stop voice input"
|
|
@@ -4312,7 +6244,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4312
6244
|
: allPendingApprovals.length > 0
|
|
4313
6245
|
? "Approve or reject pending tool calls first"
|
|
4314
6246
|
: "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 &&
|
|
6247
|
+
})(), !hideBottomControls &&
|
|
6248
|
+
!simpleMode &&
|
|
6249
|
+
editContext &&
|
|
6250
|
+
!editContext.isMobile && (_jsx(AgentTerminalStatusBar, { agent: agent, contextWindowStatus: contextWindowStatus, effectiveModelName: effectiveModelName, socketDiagnostics: editContext.socketDiagnostics, runDiagnosticsSnapshot: runDiagnosticsSnapshot, liveTotals: liveTotals, totalTokens: liveTotals
|
|
4316
6251
|
? {
|
|
4317
6252
|
input: liveTotals.input,
|
|
4318
6253
|
output: liveTotals.output,
|
|
@@ -4322,8 +6257,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4322
6257
|
outputCost: liveTotals.outputCost,
|
|
4323
6258
|
cachedCost: liveTotals.cachedCost,
|
|
4324
6259
|
cacheWriteCost: liveTotals.cacheWriteCost ?? 0,
|
|
6260
|
+
imageCost: liveTotals.imageCost ?? 0,
|
|
4325
6261
|
totalCost: liveTotals.totalCost,
|
|
4326
6262
|
}
|
|
4327
|
-
: totalTokens,
|
|
6263
|
+
: totalTokens, costLimit: agentCostLimit, messages: messages, showCompressionPopover: showCompressionPopover, setShowCompressionPopover: setShowCompressionPopover }))] })] }));
|
|
4328
6264
|
}
|
|
4329
6265
|
//# sourceMappingURL=AgentTerminal.js.map
|