@parhelia/core 0.1.12570 → 0.1.12572
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 +21 -21
- package/dist/editor/QuickItemSwitcher.js.map +1 -1
- package/dist/editor/SetupWizard.js +52 -12
- package/dist/editor/SetupWizard.js.map +1 -1
- package/dist/editor/Titlebar.js +7 -2
- package/dist/editor/Titlebar.js.map +1 -1
- package/dist/editor/ai/AgentCostDisplay.d.ts +1 -0
- package/dist/editor/ai/AgentCostDisplay.js +1 -1
- package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
- package/dist/editor/ai/AgentDocumentList.js +32 -14
- package/dist/editor/ai/AgentDocumentList.js.map +1 -1
- package/dist/editor/ai/AgentGreeting.js +3 -2
- package/dist/editor/ai/AgentGreeting.js.map +1 -1
- package/dist/editor/ai/AgentProfileSelector.js +2 -1
- package/dist/editor/ai/AgentProfileSelector.js.map +1 -1
- package/dist/editor/ai/AgentStatusBadge.d.ts +0 -5
- package/dist/editor/ai/AgentStatusBadge.js +67 -65
- package/dist/editor/ai/AgentStatusBadge.js.map +1 -1
- package/dist/editor/ai/AgentTerminal.d.ts +14 -2
- package/dist/editor/ai/AgentTerminal.js +2424 -496
- package/dist/editor/ai/AgentTerminal.js.map +1 -1
- package/dist/editor/ai/AgentTerminalStatusBar.d.ts +8 -3
- 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/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 +617 -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 +115 -0
- package/dist/editor/ai/dialogs/agentDialogTypes.js +2 -0
- package/dist/editor/ai/dialogs/agentDialogTypes.js.map +1 -1
- package/dist/editor/ai/types.d.ts +3 -1
- package/dist/editor/ai/useAgentStatus.d.ts +2 -1
- package/dist/editor/ai/useAgentStatus.js +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 +837 -253
- 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.js +26 -12
- package/dist/editor/reviews/Comment.js.map +1 -1
- package/dist/editor/reviews/CommentDisplayPopover.js +7 -5
- package/dist/editor/reviews/CommentDisplayPopover.js.map +1 -1
- package/dist/editor/reviews/CommentView.js +19 -4
- package/dist/editor/reviews/CommentView.js.map +1 -1
- package/dist/editor/reviews/Comments.js +89 -72
- package/dist/editor/reviews/Comments.js.map +1 -1
- package/dist/editor/reviews/CreateReviewDialog.js +281 -177
- package/dist/editor/reviews/CreateReviewDialog.js.map +1 -1
- package/dist/editor/reviews/DecisionsMatrix.js +96 -25
- package/dist/editor/reviews/DecisionsMatrix.js.map +1 -1
- package/dist/editor/reviews/DiffView.js +7 -14
- package/dist/editor/reviews/DiffView.js.map +1 -1
- package/dist/editor/reviews/EditReviewSettingsDialog.js +6 -4
- package/dist/editor/reviews/EditReviewSettingsDialog.js.map +1 -1
- package/dist/editor/reviews/MultiReviewManager.js +25 -3
- package/dist/editor/reviews/MultiReviewManager.js.map +1 -1
- package/dist/editor/reviews/PagesPanel.js +31 -15
- package/dist/editor/reviews/PagesPanel.js.map +1 -1
- package/dist/editor/reviews/PreviewInfo.js +1 -4
- package/dist/editor/reviews/PreviewInfo.js.map +1 -1
- package/dist/editor/reviews/ReviewCard.js +13 -7
- package/dist/editor/reviews/ReviewCard.js.map +1 -1
- package/dist/editor/reviews/ReviewDetail.js +3 -2
- package/dist/editor/reviews/ReviewDetail.js.map +1 -1
- package/dist/editor/reviews/ReviewsList.js +7 -3
- package/dist/editor/reviews/ReviewsList.js.map +1 -1
- package/dist/editor/reviews/SuggestedEdit.js +34 -3
- package/dist/editor/reviews/SuggestedEdit.js.map +1 -1
- package/dist/editor/reviews/SuggestionDisplayPopover.js +31 -5
- package/dist/editor/reviews/SuggestionDisplayPopover.js.map +1 -1
- package/dist/editor/reviews/commentAi.js +25 -6
- package/dist/editor/reviews/commentAi.js.map +1 -1
- package/dist/editor/reviews/reviewCommands.js +4 -1
- package/dist/editor/reviews/reviewCommands.js.map +1 -1
- package/dist/editor/reviews/useMultiReview.js +2 -2
- package/dist/editor/reviews/useMultiReview.js.map +1 -1
- package/dist/editor/reviews/useReviews.d.ts +2 -2
- package/dist/editor/reviews/useReviews.js +12 -30
- package/dist/editor/reviews/useReviews.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +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/ModelsPanel.js +329 -108
- package/dist/editor/settings/panels/ModelsPanel.js.map +1 -1
- package/dist/editor/settings/panels/ProvidersPanel.d.ts +1 -1
- package/dist/editor/settings/panels/ProvidersPanel.js +86 -59
- package/dist/editor/settings/panels/ProvidersPanel.js.map +1 -1
- package/dist/editor/settings/panels/SearchConfigPanel.js +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 +49 -13
- 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 +39 -22
- package/dist/setup/wizard/steps/ImportModelDialog.js.map +1 -1
- package/dist/splash-screen/ModernSplashScreen.js +112 -32
- package/dist/splash-screen/ModernSplashScreen.js.map +1 -1
- package/dist/splash-screen/NewPage.js +33 -50
- package/dist/splash-screen/NewPage.js.map +1 -1
- package/dist/splash-screen/OpenPage.js +2 -6
- package/dist/splash-screen/OpenPage.js.map +1 -1
- package/dist/splash-screen/ParheliaAssistantChat.js +12 -29
- package/dist/splash-screen/ParheliaAssistantChat.js.map +1 -1
- package/dist/splash-screen/ParheliaLogo.js +87 -37
- package/dist/splash-screen/ParheliaLogo.js.map +1 -1
- package/dist/splash-screen/RecentPages.js +3 -3
- package/dist/splash-screen/RecentPages.js.map +1 -1
- package/dist/tour/Tour.d.ts +2 -1
- package/dist/tour/Tour.js +256 -75
- package/dist/tour/Tour.js.map +1 -1
- package/dist/tour/default-tour.js +222 -96
- package/dist/tour/default-tour.js.map +1 -1
- package/dist/types.d.ts +69 -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,145 @@ 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 userMessageMarkdownComponents = {
|
|
36
|
+
h1: (props) => (_jsx("h1", { ...props, className: "mb-2 text-sm leading-5 font-semibold text-gray-900" })),
|
|
37
|
+
h2: (props) => (_jsx("h2", { ...props, className: "mb-1.5 text-[13px] leading-5 font-semibold text-gray-900" })),
|
|
38
|
+
h3: (props) => (_jsx("h3", { ...props, className: "mb-1 text-[12px] leading-5 font-semibold text-gray-900" })),
|
|
39
|
+
h4: (props) => (_jsx("h4", { ...props, className: "mb-1 text-[12px] leading-5 font-medium text-gray-800" })),
|
|
40
|
+
p: (props) => (_jsx("p", { ...props, className: "my-1 text-[12px] leading-5 text-gray-700" })),
|
|
41
|
+
ul: (props) => (_jsx("ul", { ...props, className: "my-2 ml-5 list-disc space-y-1 text-[12px] leading-5 text-gray-700" })),
|
|
42
|
+
ol: (props) => (_jsx("ol", { ...props, className: "my-2 ml-5 list-decimal space-y-1 text-[12px] leading-5 text-gray-700" })),
|
|
43
|
+
li: (props) => (_jsx("li", { ...props, className: "text-[12px] leading-5 text-gray-700" })),
|
|
44
|
+
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" })),
|
|
45
|
+
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 })),
|
|
46
|
+
};
|
|
47
|
+
function buildPlaceholderAgentDetails(agentStub) {
|
|
48
|
+
const now = new Date().toISOString();
|
|
49
|
+
const updated = agentStub.updatedDate || now;
|
|
50
|
+
// AgentDetails has required fields, but some workspaces only pass an Agent stub initially.
|
|
51
|
+
// This placeholder keeps streaming/tool-call UI working until `getAgent()` returns full details.
|
|
52
|
+
return {
|
|
53
|
+
...agentStub,
|
|
54
|
+
name: agentStub.name || "Agent",
|
|
55
|
+
userId: agentStub.userId || "",
|
|
56
|
+
updatedDate: updated,
|
|
57
|
+
profileName: agentStub.profileName || "",
|
|
58
|
+
model: agentStub.model || "",
|
|
59
|
+
createdDate: agentStub.createdDate || updated,
|
|
60
|
+
totalTokensUsed: 0,
|
|
61
|
+
totalInputTokens: 0,
|
|
62
|
+
totalOutputTokens: 0,
|
|
63
|
+
totalCachedInputTokens: 0,
|
|
64
|
+
totalInputTokenCost: 0,
|
|
65
|
+
totalOutputTokenCost: 0,
|
|
66
|
+
totalCachedInputTokenCost: 0,
|
|
67
|
+
totalImageCost: 0,
|
|
68
|
+
totalCost: 0,
|
|
69
|
+
currency: agentStub.currency || "USD",
|
|
70
|
+
messageCount: agentStub.messageCount || 0,
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function normalizeDialogAgentId(value) {
|
|
74
|
+
return value?.trim().toLowerCase() || "";
|
|
75
|
+
}
|
|
76
|
+
function formatAllowanceSource(source) {
|
|
77
|
+
const normalized = source?.trim();
|
|
78
|
+
if (!normalized)
|
|
79
|
+
return null;
|
|
80
|
+
if (normalized === "user")
|
|
81
|
+
return "User granted";
|
|
82
|
+
if (normalized.startsWith("preconfigured:profile:"))
|
|
83
|
+
return "Profile";
|
|
84
|
+
if (normalized.startsWith("preconfigured:skill:"))
|
|
85
|
+
return "Skill";
|
|
86
|
+
if (normalized.startsWith("system:")) {
|
|
87
|
+
return normalized.slice("system:".length).replace(/[-_]+/g, " ").trim();
|
|
88
|
+
}
|
|
89
|
+
return normalized;
|
|
90
|
+
}
|
|
91
|
+
function formatAllowanceLabel(allowance) {
|
|
92
|
+
return `${allowance.operationType || "*"}${"itemPath" in allowance
|
|
93
|
+
? ` ${allowance.itemPath}`
|
|
94
|
+
: ` ${allowance.normalizedPath}`}`;
|
|
95
|
+
}
|
|
96
|
+
function getAgentRunMessageAgentId(payload) {
|
|
97
|
+
const agentId = payload?.agentId;
|
|
98
|
+
return typeof agentId === "string" ? normalizeDialogAgentId(agentId) : null;
|
|
99
|
+
}
|
|
100
|
+
function getAgentRunMessageSeq(payload) {
|
|
101
|
+
const seq = payload?.seq;
|
|
102
|
+
return typeof seq === "number" ? seq : null;
|
|
103
|
+
}
|
|
104
|
+
function getAgentRunMessageDetail(type, payload) {
|
|
105
|
+
if (type === "agent:run:delta") {
|
|
106
|
+
return payload?.type || null;
|
|
107
|
+
}
|
|
108
|
+
if (type === "agent:run:status") {
|
|
109
|
+
return payload?.data?.state || payload?.data?.status || null;
|
|
110
|
+
}
|
|
111
|
+
if (type === "agent:run:error") {
|
|
112
|
+
return payload?.error || null;
|
|
113
|
+
}
|
|
114
|
+
if (type === "agent:run:complete") {
|
|
115
|
+
return payload?.finalStatus || null;
|
|
116
|
+
}
|
|
117
|
+
if (type === "agent:run:start") {
|
|
118
|
+
return payload?.agentName || null;
|
|
119
|
+
}
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
function isHeartbeatRunEventMessage(message) {
|
|
123
|
+
if (!message)
|
|
124
|
+
return false;
|
|
125
|
+
if (message.type === "agent:run:delta") {
|
|
126
|
+
const deltaType = message.payload?.type;
|
|
127
|
+
return String(deltaType || "").toLowerCase() === "heartbeat";
|
|
128
|
+
}
|
|
129
|
+
if (message.type === "agent:run:status") {
|
|
130
|
+
const state = message.payload?.data?.state || message.payload?.data?.status;
|
|
131
|
+
return String(state || "").toLowerCase() === "heartbeat";
|
|
132
|
+
}
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
function getVisibleDialogRegistry() {
|
|
136
|
+
const registry = globalThis.__agentDialogVisibleCallbacks;
|
|
137
|
+
return registry && typeof registry === "object" ? registry : {};
|
|
138
|
+
}
|
|
139
|
+
function isAgentErrorStatusValue(status) {
|
|
140
|
+
return status === "error";
|
|
141
|
+
}
|
|
25
142
|
// Simple user message component
|
|
26
143
|
const UserMessage = ({ message }) => {
|
|
144
|
+
const content = message.content || "";
|
|
145
|
+
const [isTriggerExpanded, setIsTriggerExpanded] = useState(false);
|
|
146
|
+
// Trigger-sourced prompts are prefixed by backend as "[Trigger: {name}]: {content}"
|
|
147
|
+
const triggerPattern = /^\[Trigger: ([^\]]+)\]:\s*(.*)$/s;
|
|
148
|
+
const triggerMatch = content.match(triggerPattern);
|
|
149
|
+
const triggerName = triggerMatch?.[1]?.trim() || "";
|
|
150
|
+
const triggerContent = triggerMatch?.[2] || "";
|
|
151
|
+
const isTriggerMessage = triggerName.length > 0;
|
|
152
|
+
if (isTriggerMessage) {
|
|
153
|
+
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 }) }))] }) }));
|
|
154
|
+
}
|
|
27
155
|
// Parse source agent name from content if it starts with "[From ...]:"
|
|
28
156
|
// Backend formats messages from other agents as "[From {sourceAgentName}]: {content}"
|
|
29
|
-
const content = message.content || "";
|
|
30
157
|
const fromPattern = /^\[From ([^\]]+)\]:\s*(.*)$/s;
|
|
31
158
|
const match = content.match(fromPattern);
|
|
32
159
|
let sourceAgentName;
|
|
@@ -51,7 +178,10 @@ const UserMessage = ({ message }) => {
|
|
|
51
178
|
message.sourceAgent?.name;
|
|
52
179
|
}
|
|
53
180
|
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 })] })] }));
|
|
181
|
+
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 }) })] })] }));
|
|
182
|
+
};
|
|
183
|
+
const HeartbeatMessage = ({ message }) => {
|
|
184
|
+
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
185
|
};
|
|
56
186
|
// Helper to extract todos from potentially incomplete JSON during streaming
|
|
57
187
|
const extractPartialTodos = (jsonText) => {
|
|
@@ -285,14 +415,6 @@ const extractTodosFromMessages = (messages) => {
|
|
|
285
415
|
todoMap.set(key, todo);
|
|
286
416
|
}
|
|
287
417
|
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
418
|
return result;
|
|
297
419
|
};
|
|
298
420
|
// TodoListPanel component
|
|
@@ -300,12 +422,9 @@ const TodoListPanel = ({ messages, agentMetadata, }) => {
|
|
|
300
422
|
const [isExpanded, setIsExpanded] = useState(true);
|
|
301
423
|
// First try to get todos from agent metadata (real-time updates)
|
|
302
424
|
// Server sends additionalData.todoList directly via contextChanged status
|
|
303
|
-
// Also check top-level todoList for backward compatibility with stored contexts
|
|
304
425
|
const metadataTodos = (() => {
|
|
305
426
|
try {
|
|
306
|
-
|
|
307
|
-
const todoList = agentMetadata?.additionalData?.todoList ||
|
|
308
|
-
agentMetadata?.todoList;
|
|
427
|
+
const todoList = agentMetadata?.additionalData?.todoList;
|
|
309
428
|
if (todoList?.items && Array.isArray(todoList.items)) {
|
|
310
429
|
const rawItems = todoList.items
|
|
311
430
|
.map((item, idx) => ({
|
|
@@ -433,6 +552,17 @@ const groupConsecutiveMessages = (agentMessages) => {
|
|
|
433
552
|
// Add user message
|
|
434
553
|
groups.push({ type: "user", messages: [message] });
|
|
435
554
|
}
|
|
555
|
+
else if (message.messageType === "heartbeat" ||
|
|
556
|
+
message.role === "system") {
|
|
557
|
+
if (currentAssistantGroup.length > 0) {
|
|
558
|
+
groups.push({
|
|
559
|
+
type: "assistant-group",
|
|
560
|
+
messages: currentAssistantGroup,
|
|
561
|
+
});
|
|
562
|
+
currentAssistantGroup = [];
|
|
563
|
+
}
|
|
564
|
+
groups.push({ type: "heartbeat", messages: [message] });
|
|
565
|
+
}
|
|
436
566
|
else if (message.role === "assistant") {
|
|
437
567
|
// Add to current assistant group
|
|
438
568
|
currentAssistantGroup.push(message);
|
|
@@ -500,6 +630,7 @@ const calculateTotalTokens = (messages) => {
|
|
|
500
630
|
outputCost: acc.outputCost + (message.outputTokenCost || 0),
|
|
501
631
|
cachedCost: acc.cachedCost + (message.cachedInputTokenCost || 0),
|
|
502
632
|
cacheWriteCost: acc.cacheWriteCost,
|
|
633
|
+
imageCost: acc.imageCost,
|
|
503
634
|
totalCost: acc.totalCost + (message.totalCost || 0),
|
|
504
635
|
};
|
|
505
636
|
}, {
|
|
@@ -511,6 +642,7 @@ const calculateTotalTokens = (messages) => {
|
|
|
511
642
|
outputCost: 0,
|
|
512
643
|
cachedCost: 0,
|
|
513
644
|
cacheWriteCost: 0,
|
|
645
|
+
imageCost: 0,
|
|
514
646
|
totalCost: 0,
|
|
515
647
|
});
|
|
516
648
|
return totals;
|
|
@@ -521,6 +653,84 @@ const getOperationsForMessageGroup = (messages, agentOperations) => {
|
|
|
521
653
|
const matched = agentOperations.filter((op) => op.toolCallId && toolCallIds.has(op.toolCallId));
|
|
522
654
|
return matched;
|
|
523
655
|
};
|
|
656
|
+
const stringifyToolField = (value) => {
|
|
657
|
+
if (value === undefined || value === null)
|
|
658
|
+
return undefined;
|
|
659
|
+
if (typeof value === "string") {
|
|
660
|
+
return value.trim().length > 0 ? value : undefined;
|
|
661
|
+
}
|
|
662
|
+
try {
|
|
663
|
+
return JSON.stringify(value);
|
|
664
|
+
}
|
|
665
|
+
catch {
|
|
666
|
+
return String(value);
|
|
667
|
+
}
|
|
668
|
+
};
|
|
669
|
+
const parseToolResultValue = (value) => {
|
|
670
|
+
if (value === undefined || value === null) {
|
|
671
|
+
return undefined;
|
|
672
|
+
}
|
|
673
|
+
if (typeof value === "object") {
|
|
674
|
+
return value;
|
|
675
|
+
}
|
|
676
|
+
if (typeof value !== "string") {
|
|
677
|
+
return String(value);
|
|
678
|
+
}
|
|
679
|
+
const trimmed = value.trim();
|
|
680
|
+
if (!trimmed) {
|
|
681
|
+
return undefined;
|
|
682
|
+
}
|
|
683
|
+
try {
|
|
684
|
+
let parsed = JSON.parse(trimmed);
|
|
685
|
+
if (typeof parsed === "string" &&
|
|
686
|
+
(parsed.startsWith("{") || parsed.startsWith("["))) {
|
|
687
|
+
parsed = JSON.parse(parsed);
|
|
688
|
+
}
|
|
689
|
+
if (parsed && typeof parsed === "object") {
|
|
690
|
+
return parsed;
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
catch {
|
|
694
|
+
// Fall back to the original string when the payload is plain text.
|
|
695
|
+
}
|
|
696
|
+
return value;
|
|
697
|
+
};
|
|
698
|
+
const getFirstToolCallEnvelope = (data) => {
|
|
699
|
+
if (!data || typeof data !== "object")
|
|
700
|
+
return undefined;
|
|
701
|
+
const direct = data.toolCall || data.tool_call;
|
|
702
|
+
if (direct)
|
|
703
|
+
return direct;
|
|
704
|
+
const arrayCandidates = [data.tool_calls, data.toolCalls];
|
|
705
|
+
for (const candidate of arrayCandidates) {
|
|
706
|
+
if (Array.isArray(candidate) && candidate.length > 0) {
|
|
707
|
+
return candidate[0];
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
return undefined;
|
|
711
|
+
};
|
|
712
|
+
const extractToolCallFields = (data) => {
|
|
713
|
+
const envelope = getFirstToolCallEnvelope(data);
|
|
714
|
+
const functionPayload = data?.function || envelope?.function;
|
|
715
|
+
const functionName = data?.functionName ||
|
|
716
|
+
data?.name ||
|
|
717
|
+
functionPayload?.name ||
|
|
718
|
+
envelope?.functionName ||
|
|
719
|
+
envelope?.name ||
|
|
720
|
+
"unknown";
|
|
721
|
+
const toolCallId = data?.toolCallId || data?.id || envelope?.id;
|
|
722
|
+
const functionArguments = stringifyToolField(data?.functionArguments) ||
|
|
723
|
+
stringifyToolField(data?.arguments) ||
|
|
724
|
+
stringifyToolField(functionPayload?.arguments) ||
|
|
725
|
+
stringifyToolField(envelope?.functionArguments) ||
|
|
726
|
+
stringifyToolField(envelope?.arguments) ||
|
|
727
|
+
"{}";
|
|
728
|
+
return {
|
|
729
|
+
toolCallId,
|
|
730
|
+
functionName,
|
|
731
|
+
functionArguments,
|
|
732
|
+
};
|
|
733
|
+
};
|
|
524
734
|
// Convert agent messages to AI terminal format for a response group
|
|
525
735
|
const convertAgentMessagesToAiFormat = (agentMessages) => {
|
|
526
736
|
return agentMessages.map((agentMessage) => {
|
|
@@ -541,28 +751,39 @@ const convertAgentMessagesToAiFormat = (agentMessages) => {
|
|
|
541
751
|
role: agentMessage.role,
|
|
542
752
|
createdDate: agentMessage.createdDate,
|
|
543
753
|
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
|
-
|
|
754
|
+
? agentMessage.toolCalls.map((toolCall) => {
|
|
755
|
+
const isPruned = !!toolCall.isPruned ||
|
|
756
|
+
/^PRUNED$/i.test(toolCall.functionError || "");
|
|
757
|
+
const displayResult = parseToolResultValue(toolCall.functionResultRichContent) ?? toolCall.functionResult;
|
|
758
|
+
return {
|
|
759
|
+
id: toolCall.toolCallId,
|
|
760
|
+
displayName: toolCall.functionName,
|
|
761
|
+
function: {
|
|
762
|
+
name: toolCall.functionName,
|
|
763
|
+
arguments: toolCall.functionArguments,
|
|
764
|
+
result: displayResult,
|
|
765
|
+
error: toolCall.functionError,
|
|
766
|
+
},
|
|
767
|
+
// Pass through approval info if present on the tool call
|
|
768
|
+
requiresApproval: toolCall.requiresApproval,
|
|
769
|
+
// Pass through prune metadata so the terminal can render a neutral state
|
|
770
|
+
isPruned,
|
|
771
|
+
prunedAt: toolCall.prunedAt,
|
|
772
|
+
// Pass through isCompleted so ToolCallDisplay knows when to hide spinner
|
|
773
|
+
isCompleted: toolCall.isCompleted,
|
|
774
|
+
// Tool call is streaming if message is not completed and tool call has no result yet
|
|
775
|
+
isStreaming: !agentMessage.isCompleted &&
|
|
776
|
+
!toolCall.isCompleted &&
|
|
777
|
+
!displayResult &&
|
|
778
|
+
!toolCall.functionError &&
|
|
779
|
+
!isPruned,
|
|
780
|
+
// Pass through message IDs for approval/rejection events
|
|
781
|
+
messageId: toolCall.messageId,
|
|
782
|
+
dbMessageId: toolCall.dbMessageId,
|
|
783
|
+
responseTimeMs: toolCall.responseTimeMs,
|
|
784
|
+
createdDate: toolCall.createdDate,
|
|
785
|
+
};
|
|
786
|
+
})
|
|
566
787
|
: [],
|
|
567
788
|
};
|
|
568
789
|
if (agentMessage.toolCallId) {
|
|
@@ -574,10 +795,10 @@ const convertAgentMessagesToAiFormat = (agentMessages) => {
|
|
|
574
795
|
// interface AgentTerminalProps {
|
|
575
796
|
// agentStub: Agent;
|
|
576
797
|
// }
|
|
577
|
-
export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive = true, compact = false, hideContext = false, hideBottomControls = false, hideGreeting = false, simpleMode = false, className, initialPrompt, onAgentUpdate, }) {
|
|
798
|
+
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
799
|
const editContext = useEditContext();
|
|
579
800
|
const fieldsContext = useFieldsEditContext();
|
|
580
|
-
const [agent, setAgent] = useState(
|
|
801
|
+
const [agent, setAgent] = useState(() => buildPlaceholderAgentDetails(agentStub));
|
|
581
802
|
const [messages, setMessages] = useState([]);
|
|
582
803
|
const [agentOperations, setAgentOperations] = useState([]);
|
|
583
804
|
const [prompt, setPrompt] = useState("");
|
|
@@ -588,6 +809,35 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
588
809
|
const [activePlaceholderInput, setActivePlaceholderInput] = useState(null);
|
|
589
810
|
const [allPlaceholdersFilled, setAllPlaceholdersFilled] = useState(false);
|
|
590
811
|
const [agentMetadata, setAgentMetadata] = useState(null);
|
|
812
|
+
const [isBrowserClaimMutationPending, setIsBrowserClaimMutationPending] = useState(false);
|
|
813
|
+
const [pendingBrowserCaptureDialogType, setPendingBrowserCaptureDialogType] = useState(null);
|
|
814
|
+
// Ensure we always have an agent object for streaming handlers, even before `getAgent()` resolves.
|
|
815
|
+
// This prevents early tool calls (e.g., ask-questionnaire) from being dropped in compact/workspace UIs.
|
|
816
|
+
useEffect(() => {
|
|
817
|
+
setAgent((prev) => {
|
|
818
|
+
if (prev?.id === agentStub.id)
|
|
819
|
+
return prev;
|
|
820
|
+
return buildPlaceholderAgentDetails(agentStub);
|
|
821
|
+
});
|
|
822
|
+
}, [agentStub.id]);
|
|
823
|
+
const observedMessageIdsRef = useRef(new Set());
|
|
824
|
+
useEffect(() => {
|
|
825
|
+
observedMessageIdsRef.current = new Set();
|
|
826
|
+
}, [agentStub.id]);
|
|
827
|
+
useEffect(() => {
|
|
828
|
+
if (!onMessage)
|
|
829
|
+
return;
|
|
830
|
+
for (const message of messages) {
|
|
831
|
+
if (!message?.id)
|
|
832
|
+
continue;
|
|
833
|
+
if (!message.isCompleted)
|
|
834
|
+
continue;
|
|
835
|
+
if (observedMessageIdsRef.current.has(message.id))
|
|
836
|
+
continue;
|
|
837
|
+
observedMessageIdsRef.current.add(message.id);
|
|
838
|
+
onMessage(message);
|
|
839
|
+
}
|
|
840
|
+
}, [messages, onMessage]);
|
|
591
841
|
// Generate a stable clientSessionId per component instance for stream deduplication
|
|
592
842
|
const clientSessionIdRef = useRef(null);
|
|
593
843
|
if (!clientSessionIdRef.current) {
|
|
@@ -599,6 +849,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
599
849
|
const [isListening, setIsListening] = useState(false);
|
|
600
850
|
const recognitionRef = useRef(null);
|
|
601
851
|
const prevPlaceholderRef = useRef(null);
|
|
852
|
+
const promptBeforeVoiceRef = useRef("");
|
|
602
853
|
// Voice button press-and-hold tracking
|
|
603
854
|
const voicePressStartRef = useRef(null);
|
|
604
855
|
const voiceHoldTimerRef = useRef(null);
|
|
@@ -608,6 +859,70 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
608
859
|
const [showCompressionPopover, setShowCompressionPopover] = useState(false);
|
|
609
860
|
// Agent inline dialog state (for component type selector, etc.)
|
|
610
861
|
const [activeInlineDialog, setActiveInlineDialog] = useState(null);
|
|
862
|
+
const dialogTerminalInstanceIdRef = useRef("");
|
|
863
|
+
if (!dialogTerminalInstanceIdRef.current) {
|
|
864
|
+
dialogTerminalInstanceIdRef.current = crypto.randomUUID();
|
|
865
|
+
}
|
|
866
|
+
const activeInlineDialogRef = useRef(activeInlineDialog);
|
|
867
|
+
const isQuestionnaireDialogOpen = activeInlineDialog?.request.dialogType === "questionnaire";
|
|
868
|
+
const orphanTimeoutRef = useRef(null);
|
|
869
|
+
useEffect(() => {
|
|
870
|
+
activeInlineDialogRef.current = activeInlineDialog;
|
|
871
|
+
const visibleRegistry = { ...getVisibleDialogRegistry() };
|
|
872
|
+
const callbackId = activeInlineDialog?.request.callbackId || null;
|
|
873
|
+
const terminalInstanceId = dialogTerminalInstanceIdRef.current;
|
|
874
|
+
const agentKeys = [
|
|
875
|
+
normalizeDialogAgentId(agentStubIdRefForDialogs.current),
|
|
876
|
+
normalizeDialogAgentId(agentIdRefForDialogs.current),
|
|
877
|
+
].filter(Boolean);
|
|
878
|
+
agentKeys.forEach((key) => {
|
|
879
|
+
if (callbackId) {
|
|
880
|
+
visibleRegistry[key] = {
|
|
881
|
+
callbackId,
|
|
882
|
+
terminalInstanceId,
|
|
883
|
+
};
|
|
884
|
+
}
|
|
885
|
+
else {
|
|
886
|
+
delete visibleRegistry[key];
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
globalThis.__agentDialogVisibleCallbacks = visibleRegistry;
|
|
890
|
+
}, [activeInlineDialog]);
|
|
891
|
+
useEffect(() => {
|
|
892
|
+
onQuestionnaireOpenChange?.(isQuestionnaireDialogOpen);
|
|
893
|
+
}, [isQuestionnaireDialogOpen, onQuestionnaireOpenChange]);
|
|
894
|
+
useLayoutEffect(() => {
|
|
895
|
+
if (displayMode !== "summary" || !isQuestionnaireDialogOpen) {
|
|
896
|
+
return;
|
|
897
|
+
}
|
|
898
|
+
const scrollSummaryTerminalToTop = () => {
|
|
899
|
+
const container = messagesContainerRef.current;
|
|
900
|
+
if (!container) {
|
|
901
|
+
return;
|
|
902
|
+
}
|
|
903
|
+
container.scrollTop = 0;
|
|
904
|
+
};
|
|
905
|
+
scrollSummaryTerminalToTop();
|
|
906
|
+
const frame1 = requestAnimationFrame(() => {
|
|
907
|
+
scrollSummaryTerminalToTop();
|
|
908
|
+
});
|
|
909
|
+
let frame3 = 0;
|
|
910
|
+
const frame2 = requestAnimationFrame(() => {
|
|
911
|
+
frame3 = requestAnimationFrame(() => {
|
|
912
|
+
scrollSummaryTerminalToTop();
|
|
913
|
+
});
|
|
914
|
+
});
|
|
915
|
+
return () => {
|
|
916
|
+
cancelAnimationFrame(frame1);
|
|
917
|
+
cancelAnimationFrame(frame2);
|
|
918
|
+
cancelAnimationFrame(frame3);
|
|
919
|
+
};
|
|
920
|
+
}, [displayMode, isQuestionnaireDialogOpen]);
|
|
921
|
+
useEffect(() => {
|
|
922
|
+
return () => {
|
|
923
|
+
onQuestionnaireOpenChange?.(false);
|
|
924
|
+
};
|
|
925
|
+
}, [onQuestionnaireOpenChange]);
|
|
611
926
|
const isWaitingRef = useRef(false);
|
|
612
927
|
useEffect(() => {
|
|
613
928
|
isWaitingRef.current = isWaitingForResponse;
|
|
@@ -618,10 +933,64 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
618
933
|
const isStoppingRef = useRef(false);
|
|
619
934
|
// Server-driven state: true when agent is actively processing (set by WebSocket messages)
|
|
620
935
|
const [isAgentThinking, setIsAgentThinking] = useState(false);
|
|
936
|
+
useEffect(() => {
|
|
937
|
+
if (!initialMetadata)
|
|
938
|
+
return;
|
|
939
|
+
setAgentMetadata((prev) => sanitizeAgentMetadata({
|
|
940
|
+
...(prev || {}),
|
|
941
|
+
...initialMetadata,
|
|
942
|
+
additionalData: {
|
|
943
|
+
...(prev?.additionalData || {}),
|
|
944
|
+
...(initialMetadata?.additionalData || {}),
|
|
945
|
+
},
|
|
946
|
+
}));
|
|
947
|
+
}, [initialMetadata]);
|
|
621
948
|
const hasActiveStreaming = useCallback(() => {
|
|
622
949
|
const current = messagesRef.current || [];
|
|
623
950
|
return current.some((m) => !m.isCompleted && m.messageType === "streaming");
|
|
624
951
|
}, []);
|
|
952
|
+
const currentAgentId = agent?.id || agentStub.id;
|
|
953
|
+
const recentAgentRunEvents = useMemo(() => {
|
|
954
|
+
const normalizedAgentId = normalizeDialogAgentId(currentAgentId);
|
|
955
|
+
if (!normalizedAgentId) {
|
|
956
|
+
return [];
|
|
957
|
+
}
|
|
958
|
+
if (!editContext) {
|
|
959
|
+
return [];
|
|
960
|
+
}
|
|
961
|
+
return (editContext.webSocketMessages || [])
|
|
962
|
+
.filter((message) => {
|
|
963
|
+
if (!message?.type?.startsWith("agent:run:")) {
|
|
964
|
+
return false;
|
|
965
|
+
}
|
|
966
|
+
if (isHeartbeatRunEventMessage(message)) {
|
|
967
|
+
return false;
|
|
968
|
+
}
|
|
969
|
+
return getAgentRunMessageAgentId(message.payload) === normalizedAgentId;
|
|
970
|
+
})
|
|
971
|
+
.slice(-8)
|
|
972
|
+
.map((message) => ({
|
|
973
|
+
timestamp: message.timestamp,
|
|
974
|
+
type: message.type,
|
|
975
|
+
seq: getAgentRunMessageSeq(message.payload),
|
|
976
|
+
detail: getAgentRunMessageDetail(message.type, message.payload),
|
|
977
|
+
}));
|
|
978
|
+
}, [currentAgentId, editContext?.webSocketMessages]);
|
|
979
|
+
const appendToolUiEvent = useCallback((type, detail, seq) => {
|
|
980
|
+
const timestamp = new Date().toISOString();
|
|
981
|
+
setRecentToolUiEvents((prev) => {
|
|
982
|
+
const next = [
|
|
983
|
+
...prev,
|
|
984
|
+
{
|
|
985
|
+
timestamp,
|
|
986
|
+
type,
|
|
987
|
+
detail: detail || null,
|
|
988
|
+
seq: seq ?? null,
|
|
989
|
+
},
|
|
990
|
+
];
|
|
991
|
+
return next.slice(-40);
|
|
992
|
+
});
|
|
993
|
+
}, []);
|
|
625
994
|
// Collect all pending tool calls for batch approval functionality
|
|
626
995
|
const allPendingApprovals = useMemo(() => {
|
|
627
996
|
const pending = [];
|
|
@@ -663,41 +1032,82 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
663
1032
|
}, [allPendingApprovals]);
|
|
664
1033
|
// Handle mode switch to autonomous
|
|
665
1034
|
const handleSwitchToAutonomous = useCallback(() => {
|
|
666
|
-
|
|
667
|
-
|
|
1035
|
+
const nextMode = "autonomous";
|
|
1036
|
+
setMode(nextMode);
|
|
1037
|
+
setAgentMetadata((prev) => ({
|
|
1038
|
+
...(prev ?? {}),
|
|
1039
|
+
mode: nextMode,
|
|
1040
|
+
}));
|
|
1041
|
+
setAgent((prev) => {
|
|
1042
|
+
if (!prev)
|
|
1043
|
+
return prev;
|
|
1044
|
+
return {
|
|
1045
|
+
...prev,
|
|
1046
|
+
mode: nextMode,
|
|
1047
|
+
};
|
|
1048
|
+
});
|
|
1049
|
+
}, [setAgent, setAgentMetadata]);
|
|
668
1050
|
const [resolvedPageName, setResolvedPageName] = useState(undefined);
|
|
669
1051
|
const [resolvedComponentName, setResolvedComponentName] = useState(undefined);
|
|
670
1052
|
const [resolvedFieldName, setResolvedFieldName] = useState(undefined);
|
|
671
1053
|
const [promptHistory, setPromptHistory] = useState(() => {
|
|
672
|
-
|
|
673
|
-
return JSON.parse(localStorage.getItem("editor.agent.promptHistory") || "[]");
|
|
674
|
-
}
|
|
675
|
-
return [];
|
|
1054
|
+
return (localStorageService.getItem("editor.agent.promptHistory") || []);
|
|
676
1055
|
});
|
|
677
1056
|
const [currentHistoryIndex, setCurrentHistoryIndex] = useState(-1);
|
|
678
1057
|
const [showPredefined, setShowPredefined] = useState(false);
|
|
679
1058
|
const [activeProfile, setActiveProfile] = useState(undefined);
|
|
680
1059
|
const [selectedModelId, setSelectedModelId] = useState(undefined);
|
|
1060
|
+
const normalizeAgentMode = (value) => {
|
|
1061
|
+
if (value === "autonomous" ||
|
|
1062
|
+
value === "read-only" ||
|
|
1063
|
+
value === "supervised") {
|
|
1064
|
+
return value;
|
|
1065
|
+
}
|
|
1066
|
+
return null;
|
|
1067
|
+
};
|
|
681
1068
|
const [mode, setMode] = useState("supervised");
|
|
682
1069
|
const [queuedPrompts, setQueuedPrompts] = useState([]);
|
|
683
|
-
const
|
|
1070
|
+
const [expandedQueuedTriggerIds, setExpandedQueuedTriggerIds] = useState({});
|
|
684
1071
|
const [contextPanelsActiveTab, setContextPanelsActiveTab] = useState(0);
|
|
685
1072
|
const [hiddenContextPanelTabIds, setHiddenContextPanelTabIds] = useState(new Set());
|
|
686
1073
|
const [showCostAndAgent, setShowCostAndAgent] = useState(false);
|
|
1074
|
+
const [showAgentSettings, setShowAgentSettings] = useState(false);
|
|
1075
|
+
const [showSkillPicker, setShowSkillPicker] = useState(false);
|
|
1076
|
+
const [availableSkills, setAvailableSkills] = useState([]);
|
|
1077
|
+
const [skillRootIds, setSkillRootIds] = useState([]);
|
|
1078
|
+
const [selectableTemplateIds, setSelectableTemplateIds] = useState([]);
|
|
1079
|
+
const [skillsLoading, setSkillsLoading] = useState(false);
|
|
1080
|
+
const [skillsError, setSkillsError] = useState(null);
|
|
1081
|
+
const [skillActionError, setSkillActionError] = useState(null);
|
|
1082
|
+
const [triggerSubscriptions, setTriggerSubscriptions] = useState([]);
|
|
1083
|
+
const [availableTools, setAvailableTools] = useState([]);
|
|
1084
|
+
const [operationAllowances, setOperationAllowances] = useState({
|
|
1085
|
+
sitecore: [],
|
|
1086
|
+
filesystem: [],
|
|
1087
|
+
});
|
|
1088
|
+
const [triggerSubscriptionsLoading, setTriggerSubscriptionsLoading] = useState(false);
|
|
1089
|
+
const [availableToolsLoading, setAvailableToolsLoading] = useState(false);
|
|
1090
|
+
const [operationAllowancesLoading, setOperationAllowancesLoading] = useState(false);
|
|
1091
|
+
const [triggerSubscriptionsError, setTriggerSubscriptionsError] = useState(null);
|
|
1092
|
+
const [availableToolsError, setAvailableToolsError] = useState(null);
|
|
1093
|
+
const [operationAllowancesError, setOperationAllowancesError] = useState(null);
|
|
1094
|
+
const [toolsSectionExpanded, setToolsSectionExpanded] = useState(false);
|
|
1095
|
+
const [allowancesSectionExpanded, setAllowancesSectionExpanded] = useState(false);
|
|
1096
|
+
const [subscribedTriggersSectionExpanded, setSubscribedTriggersSectionExpanded,] = useState(false);
|
|
1097
|
+
const isPersistedAgent = !!agent?.userId;
|
|
1098
|
+
const isLocalOnlyDraftAgent = agent?.status === "new" && !isPersistedAgent;
|
|
687
1099
|
const hasSpawnedAgents = useMemo(() => {
|
|
688
1100
|
if (!agentMetadata)
|
|
689
1101
|
return false;
|
|
690
|
-
const childAgents = agentMetadata?.
|
|
691
|
-
agentMetadata?.childAgents;
|
|
1102
|
+
const childAgents = agentMetadata?.childAgents;
|
|
692
1103
|
if (!Array.isArray(childAgents) || childAgents.length === 0)
|
|
693
1104
|
return false;
|
|
694
|
-
return childAgents.some((a) => a != null && typeof a === "object" &&
|
|
1105
|
+
return childAgents.some((a) => a != null && typeof a === "object" && a.agentId);
|
|
695
1106
|
}, [agentMetadata]);
|
|
696
1107
|
const hasTodoContent = useMemo(() => {
|
|
697
1108
|
const metadataTodos = (() => {
|
|
698
1109
|
try {
|
|
699
|
-
const todoList = agentMetadata?.additionalData?.todoList
|
|
700
|
-
agentMetadata?.todoList;
|
|
1110
|
+
const todoList = agentMetadata?.additionalData?.todoList;
|
|
701
1111
|
if (todoList?.items && Array.isArray(todoList.items)) {
|
|
702
1112
|
const raw = todoList.items.filter((item) => item?.title || item?.text || item?.label || item?.task);
|
|
703
1113
|
return raw.length > 0;
|
|
@@ -745,7 +1155,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
745
1155
|
.then((result) => {
|
|
746
1156
|
if (cancelled)
|
|
747
1157
|
return;
|
|
748
|
-
if (result.type === "success" &&
|
|
1158
|
+
if (result.type === "success" &&
|
|
1159
|
+
result.data &&
|
|
1160
|
+
result.data.length > 0) {
|
|
749
1161
|
setHasHistoryContent(true);
|
|
750
1162
|
}
|
|
751
1163
|
else {
|
|
@@ -776,6 +1188,38 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
776
1188
|
});
|
|
777
1189
|
return () => unsubscribe();
|
|
778
1190
|
}, [agent?.id, editContext?.addSocketMessageListener]);
|
|
1191
|
+
useEffect(() => {
|
|
1192
|
+
let active = true;
|
|
1193
|
+
const loadSkills = async () => {
|
|
1194
|
+
try {
|
|
1195
|
+
setSkillsLoading(true);
|
|
1196
|
+
setSkillsError(null);
|
|
1197
|
+
const catalog = await getAgentSkillCatalog(false);
|
|
1198
|
+
if (active) {
|
|
1199
|
+
setAvailableSkills(catalog.skills.filter((s) => !s.disabled));
|
|
1200
|
+
setSkillRootIds(catalog.rootIds);
|
|
1201
|
+
setSelectableTemplateIds(catalog.selectableTemplateIds);
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
catch (e) {
|
|
1205
|
+
if (active) {
|
|
1206
|
+
setSkillsError(e?.message || "Failed to load skills");
|
|
1207
|
+
setAvailableSkills([]);
|
|
1208
|
+
setSkillRootIds([]);
|
|
1209
|
+
setSelectableTemplateIds([]);
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
finally {
|
|
1213
|
+
if (active) {
|
|
1214
|
+
setSkillsLoading(false);
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
};
|
|
1218
|
+
void loadSkills();
|
|
1219
|
+
return () => {
|
|
1220
|
+
active = false;
|
|
1221
|
+
};
|
|
1222
|
+
}, []);
|
|
779
1223
|
const modeOptions = useMemo(() => [
|
|
780
1224
|
{
|
|
781
1225
|
value: "supervised",
|
|
@@ -810,6 +1254,239 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
810
1254
|
value: m.id,
|
|
811
1255
|
label: m.name,
|
|
812
1256
|
})) || []).sort((a, b) => a.label.localeCompare(b.label)), [activeProfile]);
|
|
1257
|
+
const metadataSelectedSkillIds = useMemo(() => {
|
|
1258
|
+
const rawSkillIds = agentMetadata?.additionalData?.skillIds ?? [];
|
|
1259
|
+
if (!Array.isArray(rawSkillIds)) {
|
|
1260
|
+
return [];
|
|
1261
|
+
}
|
|
1262
|
+
return rawSkillIds
|
|
1263
|
+
.map((x) => String(x || "").trim())
|
|
1264
|
+
.filter((x) => x.length > 0);
|
|
1265
|
+
}, [agentMetadata]);
|
|
1266
|
+
const backendAssignedSkillIds = useMemo(() => {
|
|
1267
|
+
const rawSkillIds = agent?.assignedSkillIds ?? [];
|
|
1268
|
+
if (!Array.isArray(rawSkillIds)) {
|
|
1269
|
+
return [];
|
|
1270
|
+
}
|
|
1271
|
+
return rawSkillIds
|
|
1272
|
+
.map((x) => String(x || "").trim())
|
|
1273
|
+
.filter((x) => x.length > 0);
|
|
1274
|
+
}, [agent]);
|
|
1275
|
+
const preloadedSkillIds = useMemo(() => {
|
|
1276
|
+
const rawSkillIds = activeProfile?.preloadSkills ?? [];
|
|
1277
|
+
if (!Array.isArray(rawSkillIds)) {
|
|
1278
|
+
return [];
|
|
1279
|
+
}
|
|
1280
|
+
return rawSkillIds
|
|
1281
|
+
.map((skill) => String(skill?.id || "").trim())
|
|
1282
|
+
.filter((id) => id.length > 0);
|
|
1283
|
+
}, [activeProfile?.preloadSkills]);
|
|
1284
|
+
const autoAssignedSkillIds = useMemo(() => {
|
|
1285
|
+
const preloadedIdSet = new Set(preloadedSkillIds.map((id) => id.toLowerCase()));
|
|
1286
|
+
const all = isLocalOnlyDraftAgent
|
|
1287
|
+
? preloadedSkillIds
|
|
1288
|
+
: backendAssignedSkillIds.filter((id) => preloadedIdSet.has(id.toLowerCase()));
|
|
1289
|
+
const seen = new Set();
|
|
1290
|
+
const unique = [];
|
|
1291
|
+
for (const id of all) {
|
|
1292
|
+
const key = id.toLowerCase();
|
|
1293
|
+
if (seen.has(key))
|
|
1294
|
+
continue;
|
|
1295
|
+
seen.add(key);
|
|
1296
|
+
unique.push(id);
|
|
1297
|
+
}
|
|
1298
|
+
return unique;
|
|
1299
|
+
}, [backendAssignedSkillIds, isLocalOnlyDraftAgent, preloadedSkillIds]);
|
|
1300
|
+
const selectedSkillIds = useMemo(() => {
|
|
1301
|
+
const all = [
|
|
1302
|
+
...autoAssignedSkillIds,
|
|
1303
|
+
...backendAssignedSkillIds,
|
|
1304
|
+
...metadataSelectedSkillIds,
|
|
1305
|
+
];
|
|
1306
|
+
const seen = new Set();
|
|
1307
|
+
const unique = [];
|
|
1308
|
+
for (const id of all) {
|
|
1309
|
+
const key = id.toLowerCase();
|
|
1310
|
+
if (seen.has(key))
|
|
1311
|
+
continue;
|
|
1312
|
+
seen.add(key);
|
|
1313
|
+
unique.push(id);
|
|
1314
|
+
}
|
|
1315
|
+
return unique;
|
|
1316
|
+
}, [autoAssignedSkillIds, backendAssignedSkillIds, metadataSelectedSkillIds]);
|
|
1317
|
+
useEffect(() => {
|
|
1318
|
+
let active = true;
|
|
1319
|
+
if (!showAgentSettings) {
|
|
1320
|
+
return () => {
|
|
1321
|
+
active = false;
|
|
1322
|
+
};
|
|
1323
|
+
}
|
|
1324
|
+
if (!agent?.id || isLocalOnlyDraftAgent) {
|
|
1325
|
+
setTriggerSubscriptions([]);
|
|
1326
|
+
setTriggerSubscriptionsLoading(false);
|
|
1327
|
+
setTriggerSubscriptionsError(null);
|
|
1328
|
+
setAvailableTools([]);
|
|
1329
|
+
setAvailableToolsLoading(false);
|
|
1330
|
+
setAvailableToolsError(null);
|
|
1331
|
+
setOperationAllowances({ sitecore: [], filesystem: [] });
|
|
1332
|
+
setOperationAllowancesLoading(false);
|
|
1333
|
+
setOperationAllowancesError(null);
|
|
1334
|
+
return () => {
|
|
1335
|
+
active = false;
|
|
1336
|
+
};
|
|
1337
|
+
}
|
|
1338
|
+
const loadTriggerSubscriptions = async () => {
|
|
1339
|
+
try {
|
|
1340
|
+
setTriggerSubscriptionsLoading(true);
|
|
1341
|
+
setTriggerSubscriptionsError(null);
|
|
1342
|
+
const subscriptions = await getAgentTriggerSubscriptions(agent.id);
|
|
1343
|
+
if (active) {
|
|
1344
|
+
setTriggerSubscriptions(subscriptions);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
catch (e) {
|
|
1348
|
+
if (active) {
|
|
1349
|
+
setTriggerSubscriptionsError(e?.message || "Failed to load trigger subscriptions");
|
|
1350
|
+
}
|
|
1351
|
+
}
|
|
1352
|
+
finally {
|
|
1353
|
+
if (active) {
|
|
1354
|
+
setTriggerSubscriptionsLoading(false);
|
|
1355
|
+
}
|
|
1356
|
+
}
|
|
1357
|
+
};
|
|
1358
|
+
const loadOperationAllowances = async () => {
|
|
1359
|
+
try {
|
|
1360
|
+
setOperationAllowancesLoading(true);
|
|
1361
|
+
setOperationAllowancesError(null);
|
|
1362
|
+
const allowances = await getAgentOperationAllowances(agent.id);
|
|
1363
|
+
if (active) {
|
|
1364
|
+
setOperationAllowances(allowances);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
catch (e) {
|
|
1368
|
+
if (active) {
|
|
1369
|
+
setOperationAllowancesError(e?.message || "Failed to load allowances");
|
|
1370
|
+
}
|
|
1371
|
+
}
|
|
1372
|
+
finally {
|
|
1373
|
+
if (active) {
|
|
1374
|
+
setOperationAllowancesLoading(false);
|
|
1375
|
+
}
|
|
1376
|
+
}
|
|
1377
|
+
};
|
|
1378
|
+
const loadAvailableTools = async () => {
|
|
1379
|
+
try {
|
|
1380
|
+
setAvailableToolsLoading(true);
|
|
1381
|
+
setAvailableToolsError(null);
|
|
1382
|
+
const tools = await getAgentAvailableTools(agent.id);
|
|
1383
|
+
if (active) {
|
|
1384
|
+
setAvailableTools(tools);
|
|
1385
|
+
}
|
|
1386
|
+
}
|
|
1387
|
+
catch (e) {
|
|
1388
|
+
if (active) {
|
|
1389
|
+
setAvailableToolsError(e?.message || "Failed to load available tools");
|
|
1390
|
+
}
|
|
1391
|
+
}
|
|
1392
|
+
finally {
|
|
1393
|
+
if (active) {
|
|
1394
|
+
setAvailableToolsLoading(false);
|
|
1395
|
+
}
|
|
1396
|
+
}
|
|
1397
|
+
};
|
|
1398
|
+
void loadTriggerSubscriptions();
|
|
1399
|
+
void loadAvailableTools();
|
|
1400
|
+
void loadOperationAllowances();
|
|
1401
|
+
return () => {
|
|
1402
|
+
active = false;
|
|
1403
|
+
};
|
|
1404
|
+
}, [
|
|
1405
|
+
showAgentSettings,
|
|
1406
|
+
agent?.id,
|
|
1407
|
+
agent?.status,
|
|
1408
|
+
agent?.userId,
|
|
1409
|
+
agent?.profileId,
|
|
1410
|
+
mode,
|
|
1411
|
+
selectedSkillIds,
|
|
1412
|
+
]);
|
|
1413
|
+
const activeTriggerSubscriptions = useMemo(() => triggerSubscriptions.filter((sub) => sub.isActive), [triggerSubscriptions]);
|
|
1414
|
+
const allowanceGroups = useMemo(() => [
|
|
1415
|
+
{
|
|
1416
|
+
key: "sitecore",
|
|
1417
|
+
label: "Sitecore",
|
|
1418
|
+
rows: operationAllowances.sitecore,
|
|
1419
|
+
},
|
|
1420
|
+
{
|
|
1421
|
+
key: "filesystem",
|
|
1422
|
+
label: "Filesystem",
|
|
1423
|
+
rows: operationAllowances.filesystem,
|
|
1424
|
+
},
|
|
1425
|
+
], [operationAllowances]);
|
|
1426
|
+
const hasAnyAllowances = useMemo(() => operationAllowances.sitecore.length > 0 ||
|
|
1427
|
+
operationAllowances.filesystem.length > 0, [operationAllowances]);
|
|
1428
|
+
const allowancesTotalCount = useMemo(() => operationAllowances.sitecore.length +
|
|
1429
|
+
operationAllowances.filesystem.length, [operationAllowances]);
|
|
1430
|
+
const listedProfileSkillIdSet = useMemo(() => {
|
|
1431
|
+
const ids = [
|
|
1432
|
+
...(activeProfile?.availableSkills ?? []),
|
|
1433
|
+
...(activeProfile?.allowedSkills ?? []),
|
|
1434
|
+
]
|
|
1435
|
+
.map((skill) => String(skill?.id || "").toLowerCase())
|
|
1436
|
+
.filter((id) => id.length > 0);
|
|
1437
|
+
return new Set(ids);
|
|
1438
|
+
}, [activeProfile?.availableSkills, activeProfile?.allowedSkills]);
|
|
1439
|
+
const profileFilteredSkills = useMemo(() => {
|
|
1440
|
+
if (listedProfileSkillIdSet.size === 0) {
|
|
1441
|
+
return [];
|
|
1442
|
+
}
|
|
1443
|
+
return availableSkills.filter((skill) => listedProfileSkillIdSet.has(skill.id.toLowerCase()));
|
|
1444
|
+
}, [availableSkills, listedProfileSkillIdSet]);
|
|
1445
|
+
const profileFilteredSkillIdSet = useMemo(() => new Set(profileFilteredSkills.map((s) => s.id.toLowerCase())), [profileFilteredSkills]);
|
|
1446
|
+
const manuallyAssignableSkillIdSet = useMemo(() => new Set((activeProfile?.allowedSkills ?? [])
|
|
1447
|
+
.map((skill) => String(skill?.id || "").toLowerCase())
|
|
1448
|
+
.filter((id) => id.length > 0)), [activeProfile?.allowedSkills]);
|
|
1449
|
+
const selectableTemplateIdSet = useMemo(() => new Set(selectableTemplateIds.map((id) => id.toLowerCase())), [selectableTemplateIds]);
|
|
1450
|
+
const selectedSkills = useMemo(() => selectedSkillIds
|
|
1451
|
+
.map((id) => availableSkills.find((s) => s.id.toLowerCase() === id.toLowerCase()))
|
|
1452
|
+
.filter((s) => !!s), [availableSkills, selectedSkillIds]);
|
|
1453
|
+
const selectedSkillSet = useMemo(() => new Set(selectedSkillIds.map((id) => id.toLowerCase())), [selectedSkillIds]);
|
|
1454
|
+
const autoAssignedSkillSet = useMemo(() => new Set(autoAssignedSkillIds.map((id) => id.toLowerCase())), [autoAssignedSkillIds]);
|
|
1455
|
+
const previewAvailableTools = useMemo(() => {
|
|
1456
|
+
const baseToolNames = mode === "read-only"
|
|
1457
|
+
? (activeProfile?.readOnlyToolNames ?? [])
|
|
1458
|
+
: (activeProfile?.allowedToolNames ?? []);
|
|
1459
|
+
const toolNames = new Set();
|
|
1460
|
+
for (const toolName of baseToolNames) {
|
|
1461
|
+
const normalized = String(toolName || "").trim();
|
|
1462
|
+
if (normalized) {
|
|
1463
|
+
toolNames.add(normalized);
|
|
1464
|
+
}
|
|
1465
|
+
}
|
|
1466
|
+
for (const skill of selectedSkills) {
|
|
1467
|
+
for (const toolName of skill.additionalAllowedTools ?? []) {
|
|
1468
|
+
const normalized = String(toolName || "").trim();
|
|
1469
|
+
if (normalized) {
|
|
1470
|
+
toolNames.add(normalized);
|
|
1471
|
+
}
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
return Array.from(toolNames).sort((a, b) => a.localeCompare(b));
|
|
1475
|
+
}, [
|
|
1476
|
+
activeProfile?.allowedToolNames,
|
|
1477
|
+
activeProfile?.readOnlyToolNames,
|
|
1478
|
+
mode,
|
|
1479
|
+
selectedSkills,
|
|
1480
|
+
]);
|
|
1481
|
+
const displayedAvailableTools = isLocalOnlyDraftAgent
|
|
1482
|
+
? previewAvailableTools
|
|
1483
|
+
: availableTools;
|
|
1484
|
+
const displayedAvailableToolsLoading = isLocalOnlyDraftAgent
|
|
1485
|
+
? false
|
|
1486
|
+
: availableToolsLoading;
|
|
1487
|
+
const displayedAvailableToolsError = isLocalOnlyDraftAgent
|
|
1488
|
+
? null
|
|
1489
|
+
: availableToolsError;
|
|
813
1490
|
// Remove deprecated cost limit fields from metadata to avoid confusion with agent/profile settings
|
|
814
1491
|
const sanitizeAgentMetadata = useCallback((meta) => {
|
|
815
1492
|
try {
|
|
@@ -834,28 +1511,239 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
834
1511
|
return meta;
|
|
835
1512
|
}
|
|
836
1513
|
}, []);
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
1514
|
+
const getSkillActionErrorMessage = useCallback((error) => {
|
|
1515
|
+
const message = error instanceof Error && error.message.trim()
|
|
1516
|
+
? error.message.trim()
|
|
1517
|
+
: "Failed to update skill";
|
|
1518
|
+
if (message.includes("Skill is not available for this agent profile")) {
|
|
1519
|
+
return "This skill cannot be added for the current agent profile.";
|
|
1520
|
+
}
|
|
1521
|
+
return message;
|
|
1522
|
+
}, []);
|
|
1523
|
+
const clearLegacySelectedSkillsFromMetadata = useCallback(async () => {
|
|
1524
|
+
const legacySkillIds = metadataSelectedSkillIds;
|
|
1525
|
+
if (!agent?.id || legacySkillIds.length === 0) {
|
|
1526
|
+
return;
|
|
1527
|
+
}
|
|
1528
|
+
const current = agentMetadata || {};
|
|
1529
|
+
const currentAdditionalData = current.additionalData ||
|
|
1530
|
+
{};
|
|
1531
|
+
const nextAdditionalData = Object.fromEntries(Object.entries(currentAdditionalData).filter(([key]) => key.toLowerCase() !== "skillids"));
|
|
1532
|
+
const next = {
|
|
1533
|
+
...current,
|
|
849
1534
|
};
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
1535
|
+
if (Object.keys(nextAdditionalData).length > 0) {
|
|
1536
|
+
next.additionalData = nextAdditionalData;
|
|
1537
|
+
}
|
|
1538
|
+
else {
|
|
1539
|
+
delete next.additionalData;
|
|
1540
|
+
}
|
|
1541
|
+
try {
|
|
1542
|
+
await updateAgentContext(agent.id, next);
|
|
1543
|
+
setAgentMetadata(next);
|
|
1544
|
+
setAgent((prev) => prev ? { ...prev, agentContext: JSON.stringify(next) } : prev);
|
|
1545
|
+
}
|
|
1546
|
+
catch (e) {
|
|
1547
|
+
console.error("Failed to clear legacy selected skills", e);
|
|
1548
|
+
}
|
|
1549
|
+
}, [
|
|
1550
|
+
agent?.id,
|
|
1551
|
+
agentMetadata,
|
|
1552
|
+
metadataSelectedSkillIds,
|
|
1553
|
+
setAgent,
|
|
1554
|
+
setAgentMetadata,
|
|
1555
|
+
]);
|
|
1556
|
+
const parsePersistedAgentContext = (contextJson) => {
|
|
1557
|
+
try {
|
|
1558
|
+
if (!contextJson)
|
|
1559
|
+
return null;
|
|
1560
|
+
const parsedContext = JSON.parse(contextJson);
|
|
1561
|
+
if (!parsedContext || typeof parsedContext !== "object") {
|
|
1562
|
+
return null;
|
|
1563
|
+
}
|
|
1564
|
+
return sanitizeAgentMetadata(parsedContext);
|
|
1565
|
+
}
|
|
1566
|
+
catch {
|
|
1567
|
+
return null;
|
|
1568
|
+
}
|
|
1569
|
+
};
|
|
1570
|
+
const buildDraftPersistenceContext = () => {
|
|
1571
|
+
let effectiveContext = agentMetadata;
|
|
1572
|
+
try {
|
|
1573
|
+
const normalizedAgentProfileId = agent?.profileId?.toLowerCase();
|
|
1574
|
+
const profile = activeProfile ||
|
|
1575
|
+
profiles.find((p) => p.id?.toLowerCase() === normalizedAgentProfileId);
|
|
1576
|
+
const isLiveMode = profile?.editorContextMode === "live";
|
|
1577
|
+
if (isLiveMode && typeof buildCurrentContext === "function") {
|
|
1578
|
+
const freshContext = buildCurrentContext();
|
|
1579
|
+
if (freshContext) {
|
|
1580
|
+
effectiveContext = {
|
|
1581
|
+
...(agentMetadata || {}),
|
|
1582
|
+
items: freshContext.items,
|
|
1583
|
+
currentItemId: freshContext.currentItemId,
|
|
1584
|
+
components: freshContext.components,
|
|
1585
|
+
field: freshContext.field,
|
|
1586
|
+
activeWorkspace: freshContext.activeWorkspace,
|
|
1587
|
+
hasPageLoaded: freshContext.hasPageLoaded,
|
|
1588
|
+
openSidebars: freshContext.openSidebars,
|
|
1589
|
+
};
|
|
1590
|
+
}
|
|
1591
|
+
}
|
|
1592
|
+
}
|
|
1593
|
+
catch (e) {
|
|
1594
|
+
console.warn("[AgentTerminal] Failed to compute draft context:", e);
|
|
1595
|
+
}
|
|
1596
|
+
return sanitizeAgentMetadata(effectiveContext || null);
|
|
1597
|
+
};
|
|
1598
|
+
const ensureDraftAgentPersisted = async () => {
|
|
1599
|
+
if (!agent?.id) {
|
|
1600
|
+
throw new Error("Agent not ready. Please try again.");
|
|
1601
|
+
}
|
|
1602
|
+
if (!isLocalOnlyDraftAgent) {
|
|
1603
|
+
return agent;
|
|
1604
|
+
}
|
|
1605
|
+
const requestSettings = getPendingRequestSettings();
|
|
1606
|
+
if (!requestSettings.profileId) {
|
|
1607
|
+
throw new Error("Select an agent profile before adding a skill.");
|
|
1608
|
+
}
|
|
1609
|
+
const effectiveContext = buildDraftPersistenceContext();
|
|
1610
|
+
const persistedAgent = await persistDraftAgent({
|
|
1611
|
+
agentId: agent.id,
|
|
1612
|
+
sessionId: editContext?.sessionId || undefined,
|
|
1613
|
+
profileId: requestSettings.profileId,
|
|
1614
|
+
mode: requestSettings.mode,
|
|
1615
|
+
model: requestSettings.modelId || undefined,
|
|
1616
|
+
name: agent.name,
|
|
1617
|
+
context: effectiveContext,
|
|
1618
|
+
});
|
|
1619
|
+
pendingSettingsRef.current = null;
|
|
1620
|
+
const persistedMetadata = parsePersistedAgentContext(persistedAgent.agentContext) ??
|
|
1621
|
+
effectiveContext;
|
|
1622
|
+
setAgentMetadata(persistedMetadata ? { ...persistedMetadata } : null);
|
|
1623
|
+
setAgent((prev) => {
|
|
1624
|
+
const next = {
|
|
1625
|
+
...(prev || {}),
|
|
1626
|
+
...persistedAgent,
|
|
1627
|
+
};
|
|
1628
|
+
if (prev?.messages?.length && !persistedAgent.messages?.length) {
|
|
1629
|
+
next.messages = prev.messages;
|
|
1630
|
+
}
|
|
1631
|
+
return next;
|
|
1632
|
+
});
|
|
1633
|
+
onAgentUpdate?.(persistedAgent);
|
|
1634
|
+
return persistedAgent;
|
|
1635
|
+
};
|
|
1636
|
+
const handleAddSkill = useCallback(async (skillId) => {
|
|
1637
|
+
if (selectedSkillSet.has(skillId.toLowerCase()))
|
|
1638
|
+
return;
|
|
1639
|
+
if (!agent?.id)
|
|
1640
|
+
return;
|
|
1641
|
+
try {
|
|
1642
|
+
setSkillActionError(null);
|
|
1643
|
+
const persistedAgent = await ensureDraftAgentPersisted();
|
|
1644
|
+
await assignAgentSkill({ agentId: persistedAgent.id, skillId });
|
|
1645
|
+
setAgent((prev) => {
|
|
1646
|
+
if (!prev)
|
|
1647
|
+
return prev;
|
|
1648
|
+
const currentAssigned = Array.isArray(prev.assignedSkillIds)
|
|
1649
|
+
? prev.assignedSkillIds
|
|
1650
|
+
: [];
|
|
1651
|
+
if (currentAssigned.some((existingId) => String(existingId).toLowerCase() === skillId.toLowerCase())) {
|
|
1652
|
+
return prev;
|
|
1653
|
+
}
|
|
1654
|
+
return {
|
|
1655
|
+
...prev,
|
|
1656
|
+
assignedSkillIds: [...currentAssigned, skillId],
|
|
1657
|
+
};
|
|
1658
|
+
});
|
|
1659
|
+
await clearLegacySelectedSkillsFromMetadata();
|
|
1660
|
+
return true;
|
|
1661
|
+
}
|
|
1662
|
+
catch (e) {
|
|
1663
|
+
setSkillActionError(getSkillActionErrorMessage(e));
|
|
1664
|
+
return false;
|
|
1665
|
+
}
|
|
1666
|
+
}, [
|
|
1667
|
+
agent?.id,
|
|
1668
|
+
assignAgentSkill,
|
|
1669
|
+
clearLegacySelectedSkillsFromMetadata,
|
|
1670
|
+
ensureDraftAgentPersisted,
|
|
1671
|
+
getSkillActionErrorMessage,
|
|
1672
|
+
setAgent,
|
|
1673
|
+
selectedSkillSet,
|
|
1674
|
+
]);
|
|
1675
|
+
const handleRemoveSkill = useCallback(async (skillId) => {
|
|
1676
|
+
if (!agent?.id)
|
|
1677
|
+
return;
|
|
1678
|
+
try {
|
|
1679
|
+
setSkillActionError(null);
|
|
1680
|
+
await revokeAgentSkill({ agentId: agent.id, skillId });
|
|
1681
|
+
setAgent((prev) => {
|
|
1682
|
+
if (!prev)
|
|
1683
|
+
return prev;
|
|
1684
|
+
const currentAssigned = Array.isArray(prev.assignedSkillIds)
|
|
1685
|
+
? prev.assignedSkillIds
|
|
1686
|
+
: [];
|
|
1687
|
+
return {
|
|
1688
|
+
...prev,
|
|
1689
|
+
assignedSkillIds: currentAssigned.filter((existingId) => String(existingId).toLowerCase() !== skillId.toLowerCase()),
|
|
1690
|
+
};
|
|
1691
|
+
});
|
|
1692
|
+
await clearLegacySelectedSkillsFromMetadata();
|
|
1693
|
+
}
|
|
1694
|
+
catch (e) {
|
|
1695
|
+
setSkillActionError(getSkillActionErrorMessage(e));
|
|
1696
|
+
}
|
|
1697
|
+
}, [
|
|
1698
|
+
agent?.id,
|
|
1699
|
+
clearLegacySelectedSkillsFromMetadata,
|
|
1700
|
+
getSkillActionErrorMessage,
|
|
1701
|
+
revokeAgentSkill,
|
|
1702
|
+
setAgent,
|
|
1703
|
+
]);
|
|
1704
|
+
const handleOpenProfileSettings = useCallback(async () => {
|
|
1705
|
+
if (!editContext || !activeProfile?.id)
|
|
1706
|
+
return;
|
|
1707
|
+
const lang = editContext.currentItemDescriptor?.language || "en";
|
|
1708
|
+
await editContext.loadItem({
|
|
1709
|
+
id: activeProfile.id,
|
|
1710
|
+
language: lang,
|
|
1711
|
+
version: 0,
|
|
1712
|
+
});
|
|
1713
|
+
editContext.switchWorkspace("editor");
|
|
1714
|
+
}, [editContext, activeProfile?.id]);
|
|
1715
|
+
const handleEditProfileSideBySide = useCallback(async () => {
|
|
1716
|
+
if (!editContext || !activeProfile?.id)
|
|
1717
|
+
return;
|
|
1718
|
+
const lang = editContext.currentItemDescriptor?.language || "en";
|
|
1719
|
+
const profileItem = {
|
|
1720
|
+
id: activeProfile.id,
|
|
1721
|
+
language: lang,
|
|
1722
|
+
version: 0,
|
|
855
1723
|
};
|
|
856
|
-
|
|
1724
|
+
if (editContext.workspaceId === "agents") {
|
|
1725
|
+
editContext.setShowAgentsWorkspaceEditor(true);
|
|
1726
|
+
await editContext.loadItem(profileItem);
|
|
1727
|
+
return;
|
|
1728
|
+
}
|
|
1729
|
+
await editContext.loadItem(profileItem, {
|
|
1730
|
+
openInNewSlot: true,
|
|
1731
|
+
});
|
|
1732
|
+
editContext.switchWorkspace("editor");
|
|
1733
|
+
}, [editContext, activeProfile?.id]);
|
|
1734
|
+
const handleOpenSkillItem = useCallback(async (skillId) => {
|
|
1735
|
+
if (!editContext || !skillId)
|
|
1736
|
+
return;
|
|
1737
|
+
const lang = editContext.currentItemDescriptor?.language || "en";
|
|
1738
|
+
await editContext.loadItem({
|
|
1739
|
+
id: skillId,
|
|
1740
|
+
language: lang,
|
|
1741
|
+
version: 0,
|
|
1742
|
+
});
|
|
1743
|
+
editContext.switchWorkspace("editor");
|
|
1744
|
+
}, [editContext]);
|
|
857
1745
|
useEffect(() => {
|
|
858
|
-
|
|
1746
|
+
localStorageService.setItem("editor.agent.promptHistory", promptHistory);
|
|
859
1747
|
}, [promptHistory]);
|
|
860
1748
|
useEffect(() => {
|
|
861
1749
|
// Keep messagesRef synchronized with messages state
|
|
@@ -867,6 +1755,32 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
867
1755
|
const [liveTotals, setLiveTotals] = useState(null);
|
|
868
1756
|
// Context window status from backend (extracted from delta cost object)
|
|
869
1757
|
const [contextWindowStatus, setContextWindowStatus] = useState(null);
|
|
1758
|
+
const effectiveModelName = useMemo(() => {
|
|
1759
|
+
const fromContextWindow = contextWindowStatus?.model?.trim();
|
|
1760
|
+
if (fromContextWindow)
|
|
1761
|
+
return fromContextWindow;
|
|
1762
|
+
const fromAgent = agent?.model?.trim();
|
|
1763
|
+
if (fromAgent)
|
|
1764
|
+
return fromAgent;
|
|
1765
|
+
const models = activeProfile?.models || [];
|
|
1766
|
+
const selectedModelName = selectedModelId
|
|
1767
|
+
? models.find((m) => m.id === selectedModelId)?.name?.trim()
|
|
1768
|
+
: undefined;
|
|
1769
|
+
if (selectedModelName)
|
|
1770
|
+
return selectedModelName;
|
|
1771
|
+
const defaultModelName = activeProfile?.defaultModelId
|
|
1772
|
+
? models.find((m) => m.id === activeProfile.defaultModelId)?.name?.trim()
|
|
1773
|
+
: undefined;
|
|
1774
|
+
if (defaultModelName)
|
|
1775
|
+
return defaultModelName;
|
|
1776
|
+
return models[0]?.name?.trim() || undefined;
|
|
1777
|
+
}, [
|
|
1778
|
+
contextWindowStatus?.model,
|
|
1779
|
+
agent?.model,
|
|
1780
|
+
activeProfile?.models,
|
|
1781
|
+
activeProfile?.defaultModelId,
|
|
1782
|
+
selectedModelId,
|
|
1783
|
+
]);
|
|
870
1784
|
// Flag to track when we should create a new message
|
|
871
1785
|
const shouldCreateNewMessage = useRef(false);
|
|
872
1786
|
// Keep a ref to the current messages for immediate access
|
|
@@ -877,12 +1791,17 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
877
1791
|
const placeholderInputRef = useRef(null);
|
|
878
1792
|
const promptPlaceholderInputRef = useRef(null);
|
|
879
1793
|
const messagesContainerRef = useRef(null);
|
|
1794
|
+
const inlineDialogContainerRef = useRef(null);
|
|
880
1795
|
const [shouldAutoScroll, setShouldAutoScroll] = useState(true);
|
|
1796
|
+
const [recentToolUiEvents, setRecentToolUiEvents] = useState([]);
|
|
881
1797
|
// WebSocket subscription state for agent streaming
|
|
882
1798
|
const seenMessageIdsRef = useRef(new Set());
|
|
883
1799
|
const lastSeqRef = useRef(0);
|
|
884
1800
|
const subscribedAgentIdRef = useRef(null);
|
|
885
|
-
|
|
1801
|
+
const reconcileServerStateInFlightRef = useRef(false);
|
|
1802
|
+
const toolCallFirstSeenAtRef = useRef({});
|
|
1803
|
+
const pendingToolCompletionTimersRef = useRef({});
|
|
1804
|
+
// Cache mode/model/profile changes made while the agent is still "new" (not yet persisted)
|
|
886
1805
|
const pendingSettingsRef = useRef(null);
|
|
887
1806
|
// Track whether textarea should maintain focus during re-renders
|
|
888
1807
|
const shouldMaintainFocusRef = useRef(false);
|
|
@@ -904,11 +1823,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
904
1823
|
setIsVoiceSupported(!!SR);
|
|
905
1824
|
if (SR === undefined) {
|
|
906
1825
|
setIsVoiceDisabled(true);
|
|
907
|
-
|
|
1826
|
+
localStorageService.setString(VOICE_DISABLED_KEY, "true");
|
|
908
1827
|
return;
|
|
909
1828
|
}
|
|
910
1829
|
else {
|
|
911
|
-
const wasDisabled =
|
|
1830
|
+
const wasDisabled = localStorageService.getString(VOICE_DISABLED_KEY) === "true";
|
|
912
1831
|
setIsVoiceDisabled(wasDisabled);
|
|
913
1832
|
}
|
|
914
1833
|
}
|
|
@@ -919,7 +1838,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
919
1838
|
// Auto-focus terminal input on mount
|
|
920
1839
|
useEffect(() => {
|
|
921
1840
|
if (textareaRef.current) {
|
|
922
|
-
|
|
1841
|
+
try {
|
|
1842
|
+
textareaRef.current.focus({ preventScroll: true });
|
|
1843
|
+
}
|
|
1844
|
+
catch {
|
|
1845
|
+
textareaRef.current.focus();
|
|
1846
|
+
}
|
|
923
1847
|
}
|
|
924
1848
|
}, []);
|
|
925
1849
|
// Start voice recognition
|
|
@@ -938,20 +1862,29 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
938
1862
|
r.lang = editContext?.currentItemDescriptor?.language || "en-US";
|
|
939
1863
|
r.continuous = true;
|
|
940
1864
|
r.interimResults = true;
|
|
1865
|
+
promptBeforeVoiceRef.current = prompt;
|
|
941
1866
|
r.onstart = () => {
|
|
942
1867
|
setIsListening(true);
|
|
943
1868
|
prevPlaceholderRef.current = inputPlaceholder;
|
|
944
1869
|
setInputPlaceholder("Listening...");
|
|
945
1870
|
};
|
|
1871
|
+
// Desktop Chrome: single result at index 0, transitions interim→final once.
|
|
1872
|
+
// Mobile Chrome: new result index per event, ALL immediately isFinal,
|
|
1873
|
+
// each containing the full cumulative transcript. We always take the last
|
|
1874
|
+
// non-empty final result and REPLACE the prompt to handle both patterns.
|
|
946
1875
|
r.onresult = (event) => {
|
|
947
|
-
let
|
|
1876
|
+
let lastFinalTranscript = "";
|
|
948
1877
|
let interimText = "";
|
|
949
|
-
for (let i = event.
|
|
1878
|
+
for (let i = event.results.length - 1; i >= 0; i--) {
|
|
950
1879
|
const res = event.results[i];
|
|
951
|
-
if (res.isFinal
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
1880
|
+
if (res.isFinal &&
|
|
1881
|
+
!lastFinalTranscript &&
|
|
1882
|
+
(res[0]?.transcript || "").trim()) {
|
|
1883
|
+
lastFinalTranscript = res[0].transcript;
|
|
1884
|
+
}
|
|
1885
|
+
if (!res.isFinal) {
|
|
1886
|
+
interimText = (res[0]?.transcript || "") + interimText;
|
|
1887
|
+
}
|
|
955
1888
|
}
|
|
956
1889
|
if (interimText) {
|
|
957
1890
|
setInputPlaceholder(`Listening... ${interimText.trim()}`);
|
|
@@ -959,11 +1892,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
959
1892
|
else {
|
|
960
1893
|
setInputPlaceholder("Listening...");
|
|
961
1894
|
}
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
1895
|
+
const base = promptBeforeVoiceRef.current;
|
|
1896
|
+
const separator = base && !base.endsWith(" ") ? " " : "";
|
|
1897
|
+
const recognized = lastFinalTranscript.trim();
|
|
1898
|
+
if (recognized) {
|
|
1899
|
+
setPrompt(base + separator + recognized + " ");
|
|
967
1900
|
if (textareaRef.current) {
|
|
968
1901
|
try {
|
|
969
1902
|
const v = textareaRef.current.value || "";
|
|
@@ -979,7 +1912,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
979
1912
|
// network error also comes from incompatible chromium client
|
|
980
1913
|
if (e?.error === "network") {
|
|
981
1914
|
try {
|
|
982
|
-
|
|
1915
|
+
localStorageService.setString(VOICE_DISABLED_KEY, "true");
|
|
983
1916
|
setIsVoiceDisabled(true);
|
|
984
1917
|
}
|
|
985
1918
|
catch { }
|
|
@@ -1148,25 +2081,18 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1148
2081
|
// Shared stream message handlers with messageId support
|
|
1149
2082
|
const createNewStreamMessage = useCallback((messageId, agentData) => {
|
|
1150
2083
|
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
|
-
}
|
|
2084
|
+
const effectiveAgentId = currentAgent?.id || agentStub.id;
|
|
1159
2085
|
// Reduced: avoid verbose logging during streaming
|
|
1160
2086
|
return {
|
|
1161
2087
|
id: messageId,
|
|
1162
|
-
agentId:
|
|
2088
|
+
agentId: effectiveAgentId,
|
|
1163
2089
|
messageIndex: -1,
|
|
1164
2090
|
role: "assistant",
|
|
1165
2091
|
content: "",
|
|
1166
2092
|
name: "agent",
|
|
1167
2093
|
messageType: "streaming",
|
|
1168
2094
|
isCompleted: false,
|
|
1169
|
-
model: currentAgent
|
|
2095
|
+
model: currentAgent?.model || "",
|
|
1170
2096
|
tokensUsed: 0,
|
|
1171
2097
|
inputTokens: 0,
|
|
1172
2098
|
outputTokens: 0,
|
|
@@ -1175,16 +2101,71 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1175
2101
|
outputTokenCost: 0,
|
|
1176
2102
|
cachedInputTokenCost: 0,
|
|
1177
2103
|
totalCost: 0,
|
|
1178
|
-
currency: currentAgent
|
|
2104
|
+
currency: currentAgent?.currency || "USD",
|
|
1179
2105
|
createdDate: new Date().toISOString(),
|
|
1180
2106
|
toolCalls: [],
|
|
1181
2107
|
};
|
|
1182
|
-
}, [agent]);
|
|
2108
|
+
}, [agent, agentStub.id]);
|
|
2109
|
+
const stripHeartbeatMessages = useCallback((currentMessages) => currentMessages.filter((message) => message.messageType !== "heartbeat"), []);
|
|
2110
|
+
const handleHeartbeatMessage = useCallback((message) => {
|
|
2111
|
+
const heartbeatText = (message.data?.message ||
|
|
2112
|
+
message.data?.Message ||
|
|
2113
|
+
"Still working on your request. This is taking a little longer than usual.").trim() ||
|
|
2114
|
+
"Still working on your request. This is taking a little longer than usual.";
|
|
2115
|
+
const createdDate = message.timestamp || new Date().toISOString();
|
|
2116
|
+
const heartbeatId = `heartbeat-${agent?.id || agentStub.id}`;
|
|
2117
|
+
setMessages((prev) => {
|
|
2118
|
+
const withoutHeartbeats = stripHeartbeatMessages(prev);
|
|
2119
|
+
const updated = [
|
|
2120
|
+
...withoutHeartbeats,
|
|
2121
|
+
{
|
|
2122
|
+
id: heartbeatId,
|
|
2123
|
+
agentId: agent?.id || agentStub.id,
|
|
2124
|
+
messageIndex: -1,
|
|
2125
|
+
role: "system",
|
|
2126
|
+
content: heartbeatText,
|
|
2127
|
+
name: "system",
|
|
2128
|
+
messageType: "heartbeat",
|
|
2129
|
+
isCompleted: true,
|
|
2130
|
+
model: "",
|
|
2131
|
+
tokensUsed: 0,
|
|
2132
|
+
inputTokens: 0,
|
|
2133
|
+
outputTokens: 0,
|
|
2134
|
+
cachedInputTokens: 0,
|
|
2135
|
+
inputTokenCost: 0,
|
|
2136
|
+
outputTokenCost: 0,
|
|
2137
|
+
cachedInputTokenCost: 0,
|
|
2138
|
+
totalCost: 0,
|
|
2139
|
+
currency: "USD",
|
|
2140
|
+
createdDate,
|
|
2141
|
+
toolCalls: [],
|
|
2142
|
+
},
|
|
2143
|
+
];
|
|
2144
|
+
messagesRef.current = updated;
|
|
2145
|
+
return updated;
|
|
2146
|
+
});
|
|
2147
|
+
}, [agent?.id, agentStub.id, stripHeartbeatMessages]);
|
|
2148
|
+
const clearHeartbeatMessages = useCallback(() => {
|
|
2149
|
+
setMessages((prev) => {
|
|
2150
|
+
const updated = stripHeartbeatMessages(prev);
|
|
2151
|
+
if (updated.length === prev.length) {
|
|
2152
|
+
return prev;
|
|
2153
|
+
}
|
|
2154
|
+
messagesRef.current = updated;
|
|
2155
|
+
return updated;
|
|
2156
|
+
});
|
|
2157
|
+
}, [stripHeartbeatMessages]);
|
|
1183
2158
|
const handleContentChunk = useCallback((message, agentData) => {
|
|
1184
2159
|
// Get messageId from data, or generate one from agent ID (for backward compatibility)
|
|
1185
2160
|
// If no messageId is provided, we'll use the last assistant message or create a new one
|
|
1186
2161
|
let messageId = message.data?.messageId;
|
|
1187
2162
|
if (!messageId && agentData?.id) {
|
|
2163
|
+
console.warn("[AgentTerminal] Content chunk missing messageId; falling back to local resolution", {
|
|
2164
|
+
agentId: agentData.id,
|
|
2165
|
+
isIncremental: message.data?.isIncremental,
|
|
2166
|
+
previousContentLength: message.data?.previousContentLength,
|
|
2167
|
+
totalContentLength: message.data?.totalContentLength,
|
|
2168
|
+
});
|
|
1188
2169
|
// For backward compatibility: if no messageId, find or create the current streaming message
|
|
1189
2170
|
// This handles cases where the backend doesn't send messageId
|
|
1190
2171
|
const currentMessages = messagesRef.current;
|
|
@@ -1198,7 +2179,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1198
2179
|
// If the agent isn't currently running (e.g., we switched tabs after the run
|
|
1199
2180
|
// completed), skip creating a new streaming message to avoid duplicates.
|
|
1200
2181
|
const currentAgentStatus = (agentData || agent)?.status;
|
|
1201
|
-
const isAgentRunning = currentAgentStatus === "running"
|
|
2182
|
+
const isAgentRunning = currentAgentStatus === "running";
|
|
1202
2183
|
if (!isAgentRunning) {
|
|
1203
2184
|
return;
|
|
1204
2185
|
}
|
|
@@ -1231,6 +2212,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1231
2212
|
outputCost: Number(cost.output) || 0,
|
|
1232
2213
|
cachedCost: Number(cost.cached) || 0,
|
|
1233
2214
|
cacheWriteCost: Number(cost.cacheWrite) || 0,
|
|
2215
|
+
imageCost: Number(cost.imageCost ?? cost.totalImageCost) ||
|
|
2216
|
+
0,
|
|
1234
2217
|
totalCost: Number(cost.total) || 0,
|
|
1235
2218
|
currency: "USD",
|
|
1236
2219
|
};
|
|
@@ -1254,11 +2237,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1254
2237
|
setIsWaitingForResponse(false);
|
|
1255
2238
|
shouldCreateNewMessage.current = false;
|
|
1256
2239
|
}
|
|
1257
|
-
// Extract context window info from cost object
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
2240
|
+
// Extract context window info from cost object.
|
|
2241
|
+
// Note: contextUsed can legitimately be 0 (especially early in OpenAI streams),
|
|
2242
|
+
// so we must check for null/undefined instead of truthiness.
|
|
2243
|
+
const contextWindowRaw = cost.contextWindow;
|
|
2244
|
+
const contextUsedRaw = cost.contextUsed;
|
|
2245
|
+
if (contextWindowRaw !== undefined &&
|
|
2246
|
+
contextWindowRaw !== null &&
|
|
2247
|
+
contextUsedRaw !== undefined &&
|
|
2248
|
+
contextUsedRaw !== null) {
|
|
2249
|
+
const contextWindowValue = Number(contextWindowRaw);
|
|
2250
|
+
const contextUsedValue = Number(contextUsedRaw);
|
|
2251
|
+
if (contextWindowValue > 0 &&
|
|
2252
|
+
Number.isFinite(contextUsedValue) &&
|
|
2253
|
+
contextUsedValue >= 0) {
|
|
1262
2254
|
setContextWindowStatus({
|
|
1263
2255
|
contextWindowTokens: contextWindowValue,
|
|
1264
2256
|
estimatedInputTokens: contextUsedValue,
|
|
@@ -1274,6 +2266,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1274
2266
|
const existingMessageIndex = prev.findIndex((msg) => msg.id === messageId);
|
|
1275
2267
|
if (existingMessageIndex === -1) {
|
|
1276
2268
|
// Message doesn't exist - create new streaming message
|
|
2269
|
+
const previousContentLength = message.data?.previousContentLength || 0;
|
|
2270
|
+
if (message.data?.isIncremental && previousContentLength > 0) {
|
|
2271
|
+
console.warn("[AgentTerminal] Incremental chunk arrived before its base message existed", {
|
|
2272
|
+
messageId,
|
|
2273
|
+
previousContentLength,
|
|
2274
|
+
totalContentLength: message.data?.totalContentLength,
|
|
2275
|
+
deltaLength: (message.data?.deltaContent || "").length,
|
|
2276
|
+
});
|
|
2277
|
+
}
|
|
1277
2278
|
const newStreamMessage = createNewStreamMessage(messageId, agentData);
|
|
1278
2279
|
// Set the content for the new message
|
|
1279
2280
|
const updatedNewMessage = { ...newStreamMessage };
|
|
@@ -1296,8 +2297,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1296
2297
|
return prev;
|
|
1297
2298
|
// Check if existing content is already longer than what we're trying to stream
|
|
1298
2299
|
const currentContentLength = existingMessage.content?.length || 0;
|
|
2300
|
+
const previousContentLength = message.data?.previousContentLength || 0;
|
|
1299
2301
|
const totalContentLength = message.data?.totalContentLength || 0;
|
|
1300
|
-
if (
|
|
2302
|
+
if (message.data?.isIncremental &&
|
|
2303
|
+
previousContentLength !== currentContentLength &&
|
|
2304
|
+
(previousContentLength > 0 || currentContentLength > 0)) {
|
|
2305
|
+
console.warn("[AgentTerminal] Content chunk length mismatch", {
|
|
2306
|
+
messageId,
|
|
2307
|
+
previousContentLength,
|
|
2308
|
+
currentContentLength,
|
|
2309
|
+
totalContentLength,
|
|
2310
|
+
deltaLength: (message.data?.deltaContent || "").length,
|
|
2311
|
+
});
|
|
2312
|
+
}
|
|
2313
|
+
if (message.data?.isIncremental &&
|
|
2314
|
+
currentContentLength >= totalContentLength &&
|
|
1301
2315
|
totalContentLength > 0) {
|
|
1302
2316
|
return prev;
|
|
1303
2317
|
}
|
|
@@ -1321,10 +2335,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1321
2335
|
});
|
|
1322
2336
|
}, [createNewStreamMessage, agent]);
|
|
1323
2337
|
const handleToolCall = useCallback((message, agentData) => {
|
|
1324
|
-
const
|
|
2338
|
+
const extractedToolCall = extractToolCallFields(message.data);
|
|
2339
|
+
const toolCallId = extractedToolCall.toolCallId || crypto.randomUUID();
|
|
1325
2340
|
// Prefer provided messageId, otherwise fall back to the last streaming assistant message
|
|
1326
2341
|
let toolCallMessageId = message.data?.messageId;
|
|
1327
2342
|
if (!toolCallMessageId) {
|
|
2343
|
+
console.warn("[AgentTerminal] Tool call missing messageId; falling back", {
|
|
2344
|
+
toolCallId,
|
|
2345
|
+
toolName: message.data?.name || message.data?.displayName,
|
|
2346
|
+
});
|
|
1328
2347
|
const current = messagesRef.current;
|
|
1329
2348
|
const lastStreaming = [...current]
|
|
1330
2349
|
.reverse()
|
|
@@ -1332,7 +2351,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1332
2351
|
if (lastStreaming?.id) {
|
|
1333
2352
|
toolCallMessageId = lastStreaming.id;
|
|
1334
2353
|
}
|
|
2354
|
+
else {
|
|
2355
|
+
// Tool calls can arrive before any assistant content chunk (common for dialog tools like ask-questionnaire).
|
|
2356
|
+
// Create a synthetic streaming message so the UI can render the tool call immediately.
|
|
2357
|
+
toolCallMessageId = crypto.randomUUID();
|
|
2358
|
+
}
|
|
1335
2359
|
}
|
|
2360
|
+
appendToolUiEvent("ui:tool-call-targeted", `${extractedToolCall.functionName || "unknown"} toolCallId=${toolCallId} targetMessageId=${toolCallMessageId || "none"} providedMessageId=${String(message.data?.messageId || "none")}`);
|
|
1336
2361
|
// Find or create the target message for this tool call
|
|
1337
2362
|
if (toolCallMessageId) {
|
|
1338
2363
|
const currentMessages = messagesRef.current;
|
|
@@ -1359,24 +2384,25 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1359
2384
|
}
|
|
1360
2385
|
// Add tool call to the message in the array
|
|
1361
2386
|
if (toolCallMessageId && message.data && toolCallId) {
|
|
1362
|
-
const
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
2387
|
+
const toolCallError = message.data.functionError || message.data.error || "";
|
|
2388
|
+
const isPruned = !!message.data?.isPruned || /^PRUNED$/i.test(String(toolCallError));
|
|
2389
|
+
const toolCallCreatedDate = message.data.createdDate ||
|
|
2390
|
+
message.timestamp ||
|
|
2391
|
+
new Date().toISOString();
|
|
1366
2392
|
const toolCall = {
|
|
1367
2393
|
id: toolCallId,
|
|
1368
2394
|
messageId: toolCallMessageId,
|
|
1369
2395
|
dbMessageId: message.data.messageId, // Database message ID for approval/rejection
|
|
1370
2396
|
toolCallId: toolCallId,
|
|
1371
|
-
functionName: functionName,
|
|
1372
|
-
functionArguments:
|
|
1373
|
-
message.data.arguments ||
|
|
1374
|
-
JSON.stringify(message.data.function?.arguments || {}),
|
|
2397
|
+
functionName: extractedToolCall.functionName,
|
|
2398
|
+
functionArguments: extractedToolCall.functionArguments,
|
|
1375
2399
|
functionResult: message.data.functionResult || message.data.result || "",
|
|
1376
|
-
|
|
2400
|
+
functionResultRichContent: message.data.richContent || undefined,
|
|
2401
|
+
functionError: toolCallError,
|
|
2402
|
+
isPruned,
|
|
1377
2403
|
isCompleted: false,
|
|
1378
2404
|
responseTimeMs: message.data.responseTimeMs,
|
|
1379
|
-
createdDate:
|
|
2405
|
+
createdDate: toolCallCreatedDate,
|
|
1380
2406
|
requiresApproval: message.data?.requiresApproval,
|
|
1381
2407
|
};
|
|
1382
2408
|
// Check for existing tool call - search across ALL messages by toolCallId first
|
|
@@ -1415,14 +2441,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1415
2441
|
// Check if the new data has more information than what we have
|
|
1416
2442
|
const newArgs = toolCall.functionArguments;
|
|
1417
2443
|
const existingArgs = existingToolCall.functionArguments;
|
|
1418
|
-
const
|
|
2444
|
+
const newArgsText = stringifyToolField(newArgs) || "";
|
|
2445
|
+
const existingArgsText = stringifyToolField(existingArgs) || "";
|
|
2446
|
+
const hasMoreCompleteArgs = (newArgsText.length > existingArgsText.length &&
|
|
2447
|
+
newArgsText !== existingArgsText) ||
|
|
2448
|
+
(existingArgsText === "{}" && newArgsText !== "{}");
|
|
1419
2449
|
const hasNewResult = toolCall.functionResult && !existingToolCall.functionResult;
|
|
2450
|
+
const hasNewRichContent = toolCall.functionResultRichContent &&
|
|
2451
|
+
!existingToolCall.functionResultRichContent;
|
|
1420
2452
|
const hasNewError = toolCall.functionError && !existingToolCall.functionError;
|
|
1421
2453
|
const hasNewApprovalInfo = toolCall.requiresApproval && !existingToolCall.requiresApproval;
|
|
1422
2454
|
const hasNewDbMessageId = toolCall.dbMessageId && !existingToolCall.dbMessageId;
|
|
1423
2455
|
// Only update if there's meaningful new data
|
|
1424
2456
|
if (hasMoreCompleteArgs ||
|
|
1425
2457
|
hasNewResult ||
|
|
2458
|
+
hasNewRichContent ||
|
|
1426
2459
|
hasNewError ||
|
|
1427
2460
|
hasNewApprovalInfo ||
|
|
1428
2461
|
hasNewDbMessageId) {
|
|
@@ -1439,9 +2472,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1439
2472
|
updatedToolCalls[idx] = {
|
|
1440
2473
|
...existing,
|
|
1441
2474
|
functionArguments: hasMoreCompleteArgs
|
|
1442
|
-
?
|
|
1443
|
-
: existing.functionArguments,
|
|
2475
|
+
? newArgsText
|
|
2476
|
+
: existingArgsText || existing.functionArguments,
|
|
1444
2477
|
functionResult: toolCall.functionResult || existing.functionResult,
|
|
2478
|
+
functionResultRichContent: toolCall.functionResultRichContent ||
|
|
2479
|
+
existing.functionResultRichContent,
|
|
1445
2480
|
functionError: toolCall.functionError || existing.functionError,
|
|
1446
2481
|
requiresApproval: toolCall.requiresApproval || existing.requiresApproval,
|
|
1447
2482
|
};
|
|
@@ -1459,27 +2494,36 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1459
2494
|
}
|
|
1460
2495
|
return; // Done updating existing tool call
|
|
1461
2496
|
}
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
2497
|
+
flushSync(() => {
|
|
2498
|
+
setMessages((prev) => {
|
|
2499
|
+
const updated = prev.map((msg) => {
|
|
2500
|
+
if (msg.id !== toolCallMessageId)
|
|
2501
|
+
return msg;
|
|
2502
|
+
const existingToolCalls = msg.toolCalls || [];
|
|
2503
|
+
return { ...msg, toolCalls: [...existingToolCalls, toolCall] };
|
|
2504
|
+
});
|
|
2505
|
+
messagesRef.current = updated;
|
|
2506
|
+
return updated;
|
|
1468
2507
|
});
|
|
1469
|
-
messagesRef.current = updated;
|
|
1470
|
-
return updated;
|
|
1471
2508
|
});
|
|
2509
|
+
const messageWithToolCall = messagesRef.current.find((msg) => msg.id === toolCallMessageId);
|
|
2510
|
+
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
2511
|
// If tool requires approval, agent is now waiting for user action - stop thinking
|
|
1473
2512
|
if (message.data?.requiresApproval) {
|
|
1474
2513
|
setIsAgentThinking(false);
|
|
1475
2514
|
}
|
|
1476
2515
|
}
|
|
1477
|
-
}, [createNewStreamMessage]);
|
|
2516
|
+
}, [appendToolUiEvent, createNewStreamMessage]);
|
|
1478
2517
|
const handleToolResult = useCallback((message, agentData) => {
|
|
1479
|
-
const
|
|
2518
|
+
const extractedToolCall = extractToolCallFields(message.data);
|
|
2519
|
+
const resultToolCallId = extractedToolCall.toolCallId || crypto.randomUUID();
|
|
1480
2520
|
// Prefer provided messageId, otherwise fall back to the last streaming assistant message
|
|
1481
2521
|
let resultMessageId = message.data?.messageId;
|
|
1482
2522
|
if (!resultMessageId) {
|
|
2523
|
+
console.warn("[AgentTerminal] Tool result missing messageId; falling back", {
|
|
2524
|
+
toolCallId: resultToolCallId,
|
|
2525
|
+
toolName: message.data?.functionName || message.data?.displayName,
|
|
2526
|
+
});
|
|
1483
2527
|
const current = messagesRef.current;
|
|
1484
2528
|
const lastStreaming = [...current]
|
|
1485
2529
|
.reverse()
|
|
@@ -1501,6 +2545,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1501
2545
|
outputCost: Number(cost.output) || 0,
|
|
1502
2546
|
cachedCost: Number(cost.cached) || 0,
|
|
1503
2547
|
cacheWriteCost: Number(cost.cacheWrite) || 0,
|
|
2548
|
+
imageCost: Number(cost.imageCost) || 0,
|
|
1504
2549
|
totalCost: Number(cost.total) || 0,
|
|
1505
2550
|
currency: "USD",
|
|
1506
2551
|
};
|
|
@@ -1512,11 +2557,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1512
2557
|
if (anyNonZero) {
|
|
1513
2558
|
setLiveTotals(nextTotals);
|
|
1514
2559
|
}
|
|
1515
|
-
// Extract context window info from cost object
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
2560
|
+
// Extract context window info from cost object.
|
|
2561
|
+
// Note: contextUsed can legitimately be 0 (especially early in OpenAI streams),
|
|
2562
|
+
// so we must check for null/undefined instead of truthiness.
|
|
2563
|
+
const contextWindowRaw = cost.contextWindow;
|
|
2564
|
+
const contextUsedRaw = cost.contextUsed;
|
|
2565
|
+
if (contextWindowRaw !== undefined &&
|
|
2566
|
+
contextWindowRaw !== null &&
|
|
2567
|
+
contextUsedRaw !== undefined &&
|
|
2568
|
+
contextUsedRaw !== null) {
|
|
2569
|
+
const contextWindowValue = Number(contextWindowRaw);
|
|
2570
|
+
const contextUsedValue = Number(contextUsedRaw);
|
|
2571
|
+
if (contextWindowValue > 0 &&
|
|
2572
|
+
Number.isFinite(contextUsedValue) &&
|
|
2573
|
+
contextUsedValue >= 0) {
|
|
1520
2574
|
setContextWindowStatus({
|
|
1521
2575
|
contextWindowTokens: contextWindowValue,
|
|
1522
2576
|
estimatedInputTokens: contextUsedValue,
|
|
@@ -1540,6 +2594,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1540
2594
|
outputCost: Number(data.totalOutputTokenCost) || 0,
|
|
1541
2595
|
cachedCost: Number(data.totalCachedTokenCost) || 0,
|
|
1542
2596
|
cacheWriteCost: Number(data.totalCacheWriteTokenCost) || 0,
|
|
2597
|
+
imageCost: Number(data.totalImageCost) || 0,
|
|
1543
2598
|
totalCost: Number(data.totalCost) || 0,
|
|
1544
2599
|
currency: data.currency || "USD",
|
|
1545
2600
|
};
|
|
@@ -1555,8 +2610,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1555
2610
|
}
|
|
1556
2611
|
// Update tool result directly in the messages array
|
|
1557
2612
|
if (!resultMessageId) {
|
|
2613
|
+
appendToolUiEvent("ui:tool-result-dropped", `${extractedToolCall.functionName || "unknown"} toolCallId=${resultToolCallId} reason=no-result-message-id`);
|
|
1558
2614
|
return;
|
|
1559
2615
|
}
|
|
2616
|
+
appendToolUiEvent("ui:tool-result-targeted", `${extractedToolCall.functionName || "unknown"} toolCallId=${resultToolCallId} targetMessageId=${resultMessageId}`);
|
|
1560
2617
|
// Update the message with tool result
|
|
1561
2618
|
setMessages((prev) => {
|
|
1562
2619
|
const updated = prev.map((msg) => {
|
|
@@ -1572,13 +2629,22 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1572
2629
|
const existingToolCall = updatedMessage.toolCalls[toolCallIndex];
|
|
1573
2630
|
if (existingToolCall && message.data) {
|
|
1574
2631
|
const updatedToolCalls = [...updatedMessage.toolCalls];
|
|
2632
|
+
const nextArgsText = stringifyToolField(extractedToolCall.functionArguments) || "";
|
|
2633
|
+
const existingArgsText = stringifyToolField(existingToolCall.functionArguments) || "";
|
|
2634
|
+
const hasMoreCompleteArgs = (nextArgsText.length > existingArgsText.length &&
|
|
2635
|
+
nextArgsText !== existingArgsText) ||
|
|
2636
|
+
(existingArgsText === "{}" && nextArgsText !== "{}");
|
|
1575
2637
|
const toolCall = {
|
|
1576
2638
|
id: existingToolCall.id,
|
|
1577
2639
|
messageId: existingToolCall.messageId,
|
|
1578
2640
|
toolCallId: existingToolCall.toolCallId,
|
|
1579
2641
|
functionName: existingToolCall.functionName,
|
|
1580
|
-
functionArguments:
|
|
2642
|
+
functionArguments: hasMoreCompleteArgs
|
|
2643
|
+
? nextArgsText
|
|
2644
|
+
: existingToolCall.functionArguments,
|
|
1581
2645
|
functionResult: message.data.functionResult || message.data.result || "",
|
|
2646
|
+
functionResultRichContent: message.data.richContent ||
|
|
2647
|
+
existingToolCall.functionResultRichContent,
|
|
1582
2648
|
functionError: message.data.functionError || message.data.error || "",
|
|
1583
2649
|
isCompleted: true,
|
|
1584
2650
|
responseTimeMs: message.data.responseTimeMs,
|
|
@@ -1595,23 +2661,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1595
2661
|
}
|
|
1596
2662
|
else if (message.data && resultToolCallId && resultMessageId) {
|
|
1597
2663
|
// Create new tool call if it doesn't exist
|
|
1598
|
-
const
|
|
1599
|
-
message.
|
|
1600
|
-
|
|
1601
|
-
"unknown";
|
|
2664
|
+
const toolCallCreatedDate = message.data.createdDate ||
|
|
2665
|
+
message.timestamp ||
|
|
2666
|
+
new Date().toISOString();
|
|
1602
2667
|
const toolCall = {
|
|
1603
2668
|
id: resultToolCallId,
|
|
1604
2669
|
messageId: resultMessageId,
|
|
1605
2670
|
toolCallId: resultToolCallId,
|
|
1606
|
-
functionName: functionName,
|
|
1607
|
-
functionArguments:
|
|
1608
|
-
message.data.arguments ||
|
|
1609
|
-
JSON.stringify(message.data.function?.arguments || {}),
|
|
2671
|
+
functionName: extractedToolCall.functionName,
|
|
2672
|
+
functionArguments: extractedToolCall.functionArguments,
|
|
1610
2673
|
functionResult: message.data.functionResult || message.data.result || "",
|
|
2674
|
+
functionResultRichContent: message.data.richContent || undefined,
|
|
1611
2675
|
functionError: message.data.functionError || message.data.error || "",
|
|
1612
2676
|
isCompleted: true,
|
|
1613
2677
|
responseTimeMs: message.data.responseTimeMs,
|
|
1614
|
-
createdDate:
|
|
2678
|
+
createdDate: toolCallCreatedDate,
|
|
1615
2679
|
};
|
|
1616
2680
|
updatedMessage.toolCalls = [...updatedMessage.toolCalls, toolCall];
|
|
1617
2681
|
}
|
|
@@ -1619,9 +2683,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1619
2683
|
return updatedMessage;
|
|
1620
2684
|
});
|
|
1621
2685
|
messagesRef.current = updated;
|
|
2686
|
+
const messageWithToolResult = updated.find((msg) => msg.id === resultMessageId);
|
|
2687
|
+
const matchingToolCall = messageWithToolResult?.toolCalls?.find((tc) => tc.toolCallId === resultToolCallId);
|
|
2688
|
+
appendToolUiEvent("ui:tool-result-applied", `${extractedToolCall.functionName || "unknown"} toolCallId=${resultToolCallId} targetMessageId=${resultMessageId} completed=${matchingToolCall?.isCompleted ? "yes" : "no"} messageToolCalls=${messageWithToolResult?.toolCalls?.length || 0}`);
|
|
1622
2689
|
return updated;
|
|
1623
2690
|
});
|
|
1624
|
-
}, []);
|
|
2691
|
+
}, [appendToolUiEvent]);
|
|
1625
2692
|
// Listen for local approval resolution to update UI
|
|
1626
2693
|
useEffect(() => {
|
|
1627
2694
|
const onApprovalResolved = (ev) => {
|
|
@@ -1702,7 +2769,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1702
2769
|
// The agent might have been persisted after sending a prompt
|
|
1703
2770
|
// Only treat as "new" if backend returns 404
|
|
1704
2771
|
const hasExistingMessages = messagesRef.current.length > 0;
|
|
1705
|
-
if (agentStub.status === "new" &&
|
|
2772
|
+
if (agentStub.status === "new" &&
|
|
2773
|
+
!agentStub.userId &&
|
|
2774
|
+
!hasExistingMessages) {
|
|
1706
2775
|
// Only initialize as new if we have no messages yet (initial mount)
|
|
1707
2776
|
// Derive initial profile from provided metadata if present
|
|
1708
2777
|
const initialProfileIdFromMeta = (() => {
|
|
@@ -1742,6 +2811,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1742
2811
|
totalInputTokenCost: 0,
|
|
1743
2812
|
totalOutputTokenCost: 0,
|
|
1744
2813
|
totalCachedInputTokenCost: 0,
|
|
2814
|
+
totalImageCost: 0,
|
|
1745
2815
|
totalCost: 0,
|
|
1746
2816
|
messageCount: 0,
|
|
1747
2817
|
});
|
|
@@ -1784,48 +2854,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1784
2854
|
}
|
|
1785
2855
|
})();
|
|
1786
2856
|
// 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;
|
|
2857
|
+
const localCtx = item && shouldSeedContext ? buildEditorContextPayload(item) : null;
|
|
1829
2858
|
let nextMetadata = null;
|
|
1830
2859
|
if (initialMetadata) {
|
|
1831
2860
|
// Merge initial metadata with local context (using top-level structure)
|
|
@@ -1901,8 +2930,16 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1901
2930
|
seenMessageIdsRef.current.add(msg.id.toLowerCase());
|
|
1902
2931
|
}
|
|
1903
2932
|
});
|
|
1904
|
-
// Keep local streaming
|
|
2933
|
+
// Keep local streaming if the agent is still active (running/waiting); otherwise discard locals.
|
|
2934
|
+
// This is important for dialog-style tools (e.g., ask-questionnaire) where the agent may be
|
|
2935
|
+
// "waiting" but we still want to keep the in-flight tool call UI visible.
|
|
1905
2936
|
const isRunning = agentData.status === "running" || agentData.status === 1;
|
|
2937
|
+
const isWaiting = agentData.status === "waitingForApproval" ||
|
|
2938
|
+
agentData.status === 2 ||
|
|
2939
|
+
agentData.status === "waitingForInput" ||
|
|
2940
|
+
agentData.status === "costLimitReached" ||
|
|
2941
|
+
agentData.status === 7;
|
|
2942
|
+
const keepLocalStreaming = isRunning || isWaiting;
|
|
1906
2943
|
// Filter local messages to only include streaming/incomplete messages that aren't in DB
|
|
1907
2944
|
// This prevents duplicates when reloading - DB messages are source of truth for completed messages
|
|
1908
2945
|
const localStreaming = isRunning
|
|
@@ -1920,7 +2957,19 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1920
2957
|
// Don't keep completed messages from local state - DB is source of truth
|
|
1921
2958
|
return false;
|
|
1922
2959
|
})
|
|
1923
|
-
:
|
|
2960
|
+
: keepLocalStreaming
|
|
2961
|
+
? messagesRef.current.filter((localMsg) => {
|
|
2962
|
+
if (!localMsg.id)
|
|
2963
|
+
return false;
|
|
2964
|
+
if (!localMsg.isCompleted &&
|
|
2965
|
+
localMsg.messageType === "streaming") {
|
|
2966
|
+
const existsInDb = dbMessages.some((dbMsg) => dbMsg.id &&
|
|
2967
|
+
dbMsg.id.toLowerCase() === localMsg.id.toLowerCase());
|
|
2968
|
+
return !existsInDb;
|
|
2969
|
+
}
|
|
2970
|
+
return false;
|
|
2971
|
+
})
|
|
2972
|
+
: [];
|
|
1924
2973
|
const merged = mergeMessagesById(dbMessages, localStreaming);
|
|
1925
2974
|
messagesRef.current = merged;
|
|
1926
2975
|
setMessages(merged);
|
|
@@ -1931,6 +2980,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1931
2980
|
const runningNow = agentData.status === "running" || agentData.status === 1;
|
|
1932
2981
|
const waitingForApprovalNow = agentData.status === "waitingForApproval" ||
|
|
1933
2982
|
agentData.status === 2;
|
|
2983
|
+
const waitingForInputNow = agentData.status === "waitingForInput";
|
|
1934
2984
|
const hasStreamingNow = merged.some((m) => !m.isCompleted && m.messageType === "streaming");
|
|
1935
2985
|
if (runningNow || hasStreamingNow) {
|
|
1936
2986
|
setIsWaitingForResponse(true);
|
|
@@ -1938,11 +2988,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1938
2988
|
// Agent is actively running, show thinking dots
|
|
1939
2989
|
setIsAgentThinking(true);
|
|
1940
2990
|
}
|
|
1941
|
-
else if (waitingForApprovalNow) {
|
|
2991
|
+
else if (waitingForApprovalNow || waitingForInputNow) {
|
|
1942
2992
|
setIsWaitingForResponse(false);
|
|
1943
2993
|
isWaitingRef.current = false;
|
|
1944
2994
|
setIsConnecting(false);
|
|
1945
|
-
// Agent is waiting for user approval, hide thinking dots
|
|
2995
|
+
// Agent is waiting for user input/approval, hide thinking dots
|
|
1946
2996
|
setIsAgentThinking(false);
|
|
1947
2997
|
}
|
|
1948
2998
|
else {
|
|
@@ -1991,19 +3041,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
1991
3041
|
if (!contextJson)
|
|
1992
3042
|
return null;
|
|
1993
3043
|
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
3044
|
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;
|
|
3045
|
+
return parsedContext;
|
|
2007
3046
|
}
|
|
2008
3047
|
return null;
|
|
2009
3048
|
}
|
|
@@ -2049,6 +3088,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2049
3088
|
setIsLoading(false);
|
|
2050
3089
|
}
|
|
2051
3090
|
}, [agentStub.id]);
|
|
3091
|
+
const reconcileServerStateRef = useRef(null);
|
|
3092
|
+
useEffect(() => {
|
|
3093
|
+
reconcileServerStateRef.current = async () => {
|
|
3094
|
+
if (reconcileServerStateInFlightRef.current) {
|
|
3095
|
+
return;
|
|
3096
|
+
}
|
|
3097
|
+
reconcileServerStateInFlightRef.current = true;
|
|
3098
|
+
try {
|
|
3099
|
+
await loadAgent();
|
|
3100
|
+
}
|
|
3101
|
+
finally {
|
|
3102
|
+
reconcileServerStateInFlightRef.current = false;
|
|
3103
|
+
}
|
|
3104
|
+
};
|
|
3105
|
+
}, [loadAgent]);
|
|
2052
3106
|
// Initial load
|
|
2053
3107
|
useEffect(() => {
|
|
2054
3108
|
loadAgent();
|
|
@@ -2089,7 +3143,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2089
3143
|
return;
|
|
2090
3144
|
}
|
|
2091
3145
|
// Check if cost limit exceeded based on status or cost values
|
|
2092
|
-
const statusIndicatesLimit = agent.status === "costLimitReached"
|
|
3146
|
+
const statusIndicatesLimit = agent.status === "costLimitReached";
|
|
2093
3147
|
// Use liveTotals.totalCost as fallback if agent.totalCost is missing or 0
|
|
2094
3148
|
const effectiveTotalCost = agent.totalCost || liveTotals?.totalCost || 0;
|
|
2095
3149
|
const costExceedsLimit = agent.costLimit &&
|
|
@@ -2174,9 +3228,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2174
3228
|
// Handle agent:profile:switched (profile changed via switch-profile function)
|
|
2175
3229
|
if (messageType === "agent:profile:switched") {
|
|
2176
3230
|
const payload = message.payload || {};
|
|
2177
|
-
const switchedAgentId = payload.agentId
|
|
2178
|
-
const newProfileId = payload.newProfileId
|
|
2179
|
-
const newProfileName = payload.newProfileName
|
|
3231
|
+
const switchedAgentId = payload.agentId;
|
|
3232
|
+
const newProfileId = payload.newProfileId;
|
|
3233
|
+
const newProfileName = payload.newProfileName;
|
|
2180
3234
|
if (switchedAgentId === agent.id && newProfileId) {
|
|
2181
3235
|
// Update the agent's profile
|
|
2182
3236
|
setAgent((prev) => {
|
|
@@ -2217,9 +3271,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2217
3271
|
return;
|
|
2218
3272
|
}
|
|
2219
3273
|
// For other agent messages, check if this is for our agent
|
|
2220
|
-
const agentId = message.payload?.agentId
|
|
2221
|
-
if (agentId !== agent.id)
|
|
3274
|
+
const agentId = message.payload?.agentId;
|
|
3275
|
+
if (agentId !== agent.id) {
|
|
2222
3276
|
return;
|
|
3277
|
+
}
|
|
2223
3278
|
// Handle agent:run:start
|
|
2224
3279
|
if (messageType === "agent:run:start") {
|
|
2225
3280
|
// If a stop operation is in progress, ignore this message to prevent
|
|
@@ -2228,6 +3283,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2228
3283
|
console.log("[AgentTerminal] Ignoring agent:run:start during stop operation");
|
|
2229
3284
|
return;
|
|
2230
3285
|
}
|
|
3286
|
+
const currentStatus = agentRef.current?.status;
|
|
3287
|
+
if (currentStatus === "waitingForInput" ||
|
|
3288
|
+
currentStatus === "waitingForApproval" ||
|
|
3289
|
+
currentStatus === "costLimitReached") {
|
|
3290
|
+
// Replayed start messages arrive before the buffered status payload when
|
|
3291
|
+
// reopening an already-paused agent. Preserve the attention state instead
|
|
3292
|
+
// of flashing "running" until the follow-up status update lands.
|
|
3293
|
+
return;
|
|
3294
|
+
}
|
|
2231
3295
|
// Reset run-scoped deduplication so new run seq values (starting at 1)
|
|
2232
3296
|
// are not discarded due to previous run's lastSeqRef
|
|
2233
3297
|
lastSeqRef.current = 0;
|
|
@@ -2323,6 +3387,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2323
3387
|
if (messageType === "agent:prompt:queued") {
|
|
2324
3388
|
const { queueEntry } = message.payload;
|
|
2325
3389
|
if (queueEntry) {
|
|
3390
|
+
if (shouldSuppressQueuedPrompt(queueEntry)) {
|
|
3391
|
+
return;
|
|
3392
|
+
}
|
|
2326
3393
|
// Add the new prompt to the queued prompts list
|
|
2327
3394
|
setQueuedPrompts((prev) => {
|
|
2328
3395
|
// Check if prompt already exists (deduplication)
|
|
@@ -2351,17 +3418,29 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2351
3418
|
return;
|
|
2352
3419
|
}
|
|
2353
3420
|
const { seq, type, data, cost } = message.payload;
|
|
3421
|
+
if (type === "ToolCall" || type === "toolCall") {
|
|
3422
|
+
}
|
|
2354
3423
|
// Always allow ContextUpdate messages (metadata updates) regardless of agent status
|
|
2355
3424
|
const isContextUpdate = type === "ContextUpdate" || type === "contextUpdate";
|
|
3425
|
+
const isHeartbeat = type === "Heartbeat" || type === "heartbeat";
|
|
3426
|
+
const shouldClearHeartbeat = type === "ContentChunk" ||
|
|
3427
|
+
type === "contentChunk" ||
|
|
3428
|
+
type === "ToolCall" ||
|
|
3429
|
+
type === "toolCall" ||
|
|
3430
|
+
type === "ToolResult" ||
|
|
3431
|
+
type === "toolResult";
|
|
2356
3432
|
// Allow deltas if the agent is running OR if we already have an active streaming message
|
|
2357
3433
|
// OR if the server provided a messageId for targeted updates.
|
|
2358
3434
|
// This avoids dropping early deltas that may arrive before the 'running' status update.
|
|
2359
3435
|
// ContextUpdate messages are always allowed as they're metadata updates, not content.
|
|
2360
3436
|
const isRunning = agent.status === "running" || agent.status === 1;
|
|
2361
3437
|
const hasStreaming = messagesRef.current.some((m) => !m.isCompleted && m.messageType === "streaming");
|
|
2362
|
-
const hasMessageId = !!message?.payload?.data?.messageId
|
|
2363
|
-
|
|
2364
|
-
|
|
3438
|
+
const hasMessageId = !!message?.payload?.data?.messageId;
|
|
3439
|
+
if (!isContextUpdate &&
|
|
3440
|
+
!isHeartbeat &&
|
|
3441
|
+
!isRunning &&
|
|
3442
|
+
!hasStreaming &&
|
|
3443
|
+
!hasMessageId) {
|
|
2365
3444
|
return; // ignore only if we cannot safely target an existing streaming message
|
|
2366
3445
|
}
|
|
2367
3446
|
// Deduplicate by sequence
|
|
@@ -2378,15 +3457,23 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2378
3457
|
cost,
|
|
2379
3458
|
timestamp: new Date().toISOString(),
|
|
2380
3459
|
};
|
|
3460
|
+
if (shouldClearHeartbeat) {
|
|
3461
|
+
clearHeartbeatMessages();
|
|
3462
|
+
}
|
|
2381
3463
|
if (type === "ContentChunk" || type === "contentChunk") {
|
|
2382
3464
|
handleContentChunk(agentStreamMessage, agent);
|
|
2383
3465
|
}
|
|
2384
3466
|
else if (type === "ToolCall" || type === "toolCall") {
|
|
3467
|
+
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
3468
|
handleToolCall(agentStreamMessage, agent);
|
|
2386
3469
|
}
|
|
2387
3470
|
else if (type === "ToolResult" || type === "toolResult") {
|
|
3471
|
+
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
3472
|
handleToolResult(agentStreamMessage, agent);
|
|
2389
3473
|
}
|
|
3474
|
+
else if (type === "Heartbeat" || type === "heartbeat") {
|
|
3475
|
+
handleHeartbeatMessage(agentStreamMessage);
|
|
3476
|
+
}
|
|
2390
3477
|
else if (type === "ContextUpdate" || type === "contextUpdate") {
|
|
2391
3478
|
// Handle context updates from streaming - data contains additionalData.todoList and ChildAgents
|
|
2392
3479
|
const contextData = data;
|
|
@@ -2396,10 +3483,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2396
3483
|
const current = (prev || {});
|
|
2397
3484
|
const updated = { ...current };
|
|
2398
3485
|
// Merge additionalData if present (deep merge to preserve existing values)
|
|
2399
|
-
if (contextData.additionalData
|
|
2400
|
-
const sourceAdditionalData = contextData.additionalData ||
|
|
2401
|
-
contextData.AdditionalData ||
|
|
2402
|
-
{};
|
|
3486
|
+
if (contextData.additionalData) {
|
|
3487
|
+
const sourceAdditionalData = contextData.additionalData || {};
|
|
2403
3488
|
updated.additionalData = {
|
|
2404
3489
|
...(current.additionalData || {}),
|
|
2405
3490
|
...sourceAdditionalData,
|
|
@@ -2407,10 +3492,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2407
3492
|
}
|
|
2408
3493
|
// Merge ChildAgents if present (for spawned agents) - replace entire array
|
|
2409
3494
|
// Backend sends the complete updated list, not just additions
|
|
2410
|
-
if (contextData.
|
|
2411
|
-
const childAgents = contextData.
|
|
3495
|
+
if (contextData.childAgents) {
|
|
3496
|
+
const childAgents = contextData.childAgents;
|
|
2412
3497
|
if (Array.isArray(childAgents)) {
|
|
2413
|
-
updated.
|
|
3498
|
+
updated.childAgents = childAgents;
|
|
2414
3499
|
}
|
|
2415
3500
|
}
|
|
2416
3501
|
return updated;
|
|
@@ -2436,15 +3521,18 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2436
3521
|
// Route based on statusData.state
|
|
2437
3522
|
try {
|
|
2438
3523
|
// Normalize various status shapes and handle Cancelled uniformly
|
|
2439
|
-
const normalizedStatus = statusData?.state ||
|
|
2440
|
-
statusData?.
|
|
2441
|
-
|
|
2442
|
-
if (normalizedStatus === "Cancelled" ||
|
|
2443
|
-
normalizedStatus === "canceled") {
|
|
3524
|
+
const normalizedStatus = parseAgentStatus(statusData?.state) ||
|
|
3525
|
+
parseAgentStatus(statusData?.status);
|
|
3526
|
+
if (normalizedStatus === "idle") {
|
|
2444
3527
|
// Stop indicators and mark any in-progress streaming messages as completed
|
|
3528
|
+
clearHeartbeatMessages();
|
|
3529
|
+
setAgent((prev) => (prev ? { ...prev, status: "idle" } : prev));
|
|
2445
3530
|
setIsWaitingForResponse(false);
|
|
2446
3531
|
isWaitingRef.current = false;
|
|
2447
3532
|
setIsConnecting(false);
|
|
3533
|
+
setIsSubmitting(false);
|
|
3534
|
+
shouldCreateNewMessage.current = false;
|
|
3535
|
+
setIsAgentThinking(false);
|
|
2448
3536
|
setMessages((prev) => {
|
|
2449
3537
|
const updated = prev.map((msg) => !msg.isCompleted && msg.messageType === "streaming"
|
|
2450
3538
|
? {
|
|
@@ -2456,6 +3544,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2456
3544
|
messagesRef.current = updated;
|
|
2457
3545
|
return updated;
|
|
2458
3546
|
});
|
|
3547
|
+
void reconcileServerStateRef.current?.();
|
|
2459
3548
|
return;
|
|
2460
3549
|
}
|
|
2461
3550
|
if (statusData?.state === "streamOpen") {
|
|
@@ -2487,6 +3576,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2487
3576
|
outputCost: Number(totals.totalOutputTokenCost) || 0,
|
|
2488
3577
|
cachedCost: Number(totals.totalCachedInputTokenCost) || 0,
|
|
2489
3578
|
cacheWriteCost: Number(totals.totalCacheWriteTokenCost) || 0,
|
|
3579
|
+
imageCost: Number(totals.totalImageCost) || 0,
|
|
2490
3580
|
totalCost: totalCost,
|
|
2491
3581
|
currency: totals.currency,
|
|
2492
3582
|
};
|
|
@@ -2498,6 +3588,26 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2498
3588
|
if (anyNonZero) {
|
|
2499
3589
|
setLiveTotals(nextTotals);
|
|
2500
3590
|
}
|
|
3591
|
+
// Fallback context usage update for providers that do not include
|
|
3592
|
+
// context usage in every stream delta (for example some OpenAI streams).
|
|
3593
|
+
// Prefer explicit status values when present; otherwise derive from totals.
|
|
3594
|
+
const contextWindowValue = Number(statusData?.contextWindow ??
|
|
3595
|
+
agent?.contextWindowTokens ??
|
|
3596
|
+
0);
|
|
3597
|
+
const contextUsedValue = Number(statusData?.contextUsed ??
|
|
3598
|
+
(Number(totals.totalInputTokens) || 0) +
|
|
3599
|
+
(Number(totals.totalCachedInputTokens) || 0) +
|
|
3600
|
+
(Number(totals.totalCacheWriteTokens) || 0));
|
|
3601
|
+
if (contextWindowValue > 0 &&
|
|
3602
|
+
Number.isFinite(contextUsedValue) &&
|
|
3603
|
+
contextUsedValue >= 0) {
|
|
3604
|
+
setContextWindowStatus({
|
|
3605
|
+
contextWindowTokens: contextWindowValue,
|
|
3606
|
+
estimatedInputTokens: contextUsedValue,
|
|
3607
|
+
contextUsedPercent: (contextUsedValue / contextWindowValue) * 100,
|
|
3608
|
+
model: agent?.model || "",
|
|
3609
|
+
});
|
|
3610
|
+
}
|
|
2501
3611
|
// If server provides costLimit along with totals, persist it locally
|
|
2502
3612
|
if (statusCostLimit) {
|
|
2503
3613
|
setAgent((prev) => prev &&
|
|
@@ -2519,6 +3629,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2519
3629
|
return;
|
|
2520
3630
|
}
|
|
2521
3631
|
if (statusData?.state === "ToolApprovalsRequired") {
|
|
3632
|
+
setPendingBrowserCaptureDialogType(null);
|
|
2522
3633
|
const msgId = statusData.messageId;
|
|
2523
3634
|
const ids = statusData.toolCallIds || [];
|
|
2524
3635
|
if (msgId && Array.isArray(ids) && ids.length > 0) {
|
|
@@ -2550,16 +3661,41 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2550
3661
|
setIsWaitingForResponse(false);
|
|
2551
3662
|
return;
|
|
2552
3663
|
}
|
|
2553
|
-
// Handle
|
|
2554
|
-
if (
|
|
2555
|
-
|
|
3664
|
+
// Handle waiting states explicitly
|
|
3665
|
+
if (normalizedStatus === "waitingForApproval") {
|
|
3666
|
+
setPendingBrowserCaptureDialogType(null);
|
|
2556
3667
|
setAgent((prev) => prev ? { ...prev, status: "waitingForApproval" } : prev);
|
|
2557
3668
|
setIsConnecting(false);
|
|
2558
3669
|
setIsWaitingForResponse(false);
|
|
2559
3670
|
setIsAgentThinking(false);
|
|
3671
|
+
void reconcileServerStateRef.current?.();
|
|
3672
|
+
return;
|
|
3673
|
+
}
|
|
3674
|
+
if (normalizedStatus === "waitingForInput") {
|
|
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);
|
|
2560
3695
|
return;
|
|
2561
3696
|
}
|
|
2562
|
-
if (
|
|
3697
|
+
if (normalizedStatus === "costLimitReached") {
|
|
3698
|
+
setPendingBrowserCaptureDialogType(null);
|
|
2563
3699
|
const totalCost = Number(statusData.totalCost) || 0;
|
|
2564
3700
|
const costLimit = Number(statusData.costLimit) || agent?.costLimit || 0;
|
|
2565
3701
|
setCostLimitExceeded({
|
|
@@ -2577,7 +3713,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2577
3713
|
// Server sends additionalData directly (with todoList inside)
|
|
2578
3714
|
// Also may include ChildAgents for spawned agents
|
|
2579
3715
|
try {
|
|
2580
|
-
const serverAdditionalData = statusData.additionalData ||
|
|
3716
|
+
const serverAdditionalData = statusData.additionalData || {};
|
|
2581
3717
|
setAgentMetadata((prev) => {
|
|
2582
3718
|
const current = (prev || {});
|
|
2583
3719
|
const updated = { ...current };
|
|
@@ -2591,10 +3727,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2591
3727
|
}
|
|
2592
3728
|
// Merge ChildAgents if present (for spawned agents) - replace entire array
|
|
2593
3729
|
// Backend sends the complete updated list, not just additions
|
|
2594
|
-
if (statusData.
|
|
2595
|
-
const childAgents = statusData.
|
|
3730
|
+
if (statusData.childAgents) {
|
|
3731
|
+
const childAgents = statusData.childAgents;
|
|
2596
3732
|
if (Array.isArray(childAgents)) {
|
|
2597
|
-
updated.
|
|
3733
|
+
updated.childAgents = childAgents;
|
|
2598
3734
|
}
|
|
2599
3735
|
}
|
|
2600
3736
|
return updated;
|
|
@@ -2606,10 +3742,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2606
3742
|
return;
|
|
2607
3743
|
}
|
|
2608
3744
|
// Handle "completed" state (fallback for legacy code paths that send status instead of lifecycle event)
|
|
2609
|
-
if (normalizedStatus === "completed"
|
|
2610
|
-
normalizedStatus === "Completed") {
|
|
3745
|
+
if (normalizedStatus === "completed") {
|
|
2611
3746
|
// Reset deduplication for the next run
|
|
2612
3747
|
lastSeqRef.current = 0;
|
|
3748
|
+
clearHeartbeatMessages();
|
|
2613
3749
|
// Mark the last assistant message as completed
|
|
2614
3750
|
setMessages((prev) => {
|
|
2615
3751
|
const updated = prev.map((msg) => msg.role === "assistant" && !msg.isCompleted
|
|
@@ -2632,30 +3768,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2632
3768
|
setIsAgentThinking(false);
|
|
2633
3769
|
return;
|
|
2634
3770
|
}
|
|
2635
|
-
// Handle "Idle" state - agent has finished processing
|
|
2636
|
-
if (normalizedStatus === "idle" || normalizedStatus === "Idle") {
|
|
2637
|
-
// Update agent status to idle
|
|
2638
|
-
setAgent((prev) => (prev ? { ...prev, status: "idle" } : prev));
|
|
2639
|
-
setIsWaitingForResponse(false);
|
|
2640
|
-
isWaitingRef.current = false;
|
|
2641
|
-
setIsConnecting(false);
|
|
2642
|
-
shouldCreateNewMessage.current = false;
|
|
2643
|
-
setIsAgentThinking(false);
|
|
2644
|
-
return;
|
|
2645
|
-
}
|
|
2646
3771
|
// Handle "Running" state - agent is actively processing
|
|
2647
|
-
if (normalizedStatus === "running"
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
3772
|
+
if (normalizedStatus === "running") {
|
|
3773
|
+
// Update agent status to running and clear any previous error statusMessage
|
|
3774
|
+
setAgent((prev) => prev
|
|
3775
|
+
? { ...prev, status: "running", statusMessage: undefined }
|
|
3776
|
+
: prev);
|
|
2651
3777
|
setIsWaitingForResponse(true);
|
|
2652
3778
|
isWaitingRef.current = true;
|
|
2653
3779
|
setIsAgentThinking(true);
|
|
2654
3780
|
return;
|
|
2655
3781
|
}
|
|
2656
3782
|
// Handle "Error" state
|
|
2657
|
-
if (normalizedStatus === "error"
|
|
2658
|
-
const errorMsg = statusData?.statusMessage
|
|
3783
|
+
if (normalizedStatus === "error") {
|
|
3784
|
+
const errorMsg = toUserFacingAgentErrorMessage(statusData?.statusMessage ?? statusData?.error) || "Unknown error";
|
|
3785
|
+
clearHeartbeatMessages();
|
|
2659
3786
|
setAgent((prev) => prev
|
|
2660
3787
|
? { ...prev, status: "error", statusMessage: errorMsg }
|
|
2661
3788
|
: prev);
|
|
@@ -2675,6 +3802,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2675
3802
|
if (messageType === "agent:run:complete") {
|
|
2676
3803
|
// Reset deduplication for the next run
|
|
2677
3804
|
lastSeqRef.current = 0;
|
|
3805
|
+
clearHeartbeatMessages();
|
|
2678
3806
|
// Mark the last assistant message as completed
|
|
2679
3807
|
setMessages((prev) => {
|
|
2680
3808
|
const updated = prev.map((msg) => msg.role === "assistant" && !msg.isCompleted
|
|
@@ -2699,7 +3827,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2699
3827
|
}
|
|
2700
3828
|
// Lifecycle: agent:run:error
|
|
2701
3829
|
if (messageType === "agent:run:error") {
|
|
2702
|
-
const errorMsg = message.payload?.error ||
|
|
3830
|
+
const errorMsg = toUserFacingAgentErrorMessage(message.payload?.error) ||
|
|
3831
|
+
"AI could not complete this request.";
|
|
3832
|
+
clearHeartbeatMessages();
|
|
2703
3833
|
// Reset deduplication for the next run after an error
|
|
2704
3834
|
lastSeqRef.current = 0;
|
|
2705
3835
|
setError(errorMsg);
|
|
@@ -2714,7 +3844,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2714
3844
|
}
|
|
2715
3845
|
}, [
|
|
2716
3846
|
agent,
|
|
3847
|
+
clearHeartbeatMessages,
|
|
2717
3848
|
handleContentChunk,
|
|
3849
|
+
handleHeartbeatMessage,
|
|
2718
3850
|
handleToolCall,
|
|
2719
3851
|
handleToolResult,
|
|
2720
3852
|
onAgentUpdate,
|
|
@@ -2769,6 +3901,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2769
3901
|
const isRunning = currentAgent.status === "running" || currentAgent.status === 1;
|
|
2770
3902
|
const isWaitingForApproval = currentAgent.status === "waitingForApproval" ||
|
|
2771
3903
|
currentAgent.status === 2;
|
|
3904
|
+
const isWaitingForInput = currentAgent.status === "waitingForInput";
|
|
2772
3905
|
if (isRunning) {
|
|
2773
3906
|
setIsWaitingForResponse(true);
|
|
2774
3907
|
isWaitingRef.current = true;
|
|
@@ -2776,10 +3909,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2776
3909
|
// Agent is currently running, show thinking dots
|
|
2777
3910
|
setIsAgentThinking(true);
|
|
2778
3911
|
}
|
|
2779
|
-
else if (isWaitingForApproval) {
|
|
3912
|
+
else if (isWaitingForApproval || isWaitingForInput) {
|
|
2780
3913
|
setIsWaitingForResponse(false);
|
|
2781
3914
|
isWaitingRef.current = false;
|
|
2782
|
-
// Agent is waiting for user approval, hide thinking dots
|
|
3915
|
+
// Agent is waiting for user input/approval, hide thinking dots
|
|
2783
3916
|
setIsAgentThinking(false);
|
|
2784
3917
|
}
|
|
2785
3918
|
else {
|
|
@@ -2826,36 +3959,157 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2826
3959
|
window.addEventListener("editor:focusAgentPrompt", focusHandler);
|
|
2827
3960
|
return () => window.removeEventListener("editor:focusAgentPrompt", focusHandler);
|
|
2828
3961
|
}, []);
|
|
3962
|
+
// Keep stable refs so we don't miss window events during effect re-runs.
|
|
3963
|
+
const agentIdRefForDialogs = useRef(null);
|
|
3964
|
+
const agentStubIdRefForDialogs = useRef(agentStub.id);
|
|
3965
|
+
const isActiveRefForDialogs = useRef(isActive);
|
|
3966
|
+
const scrollToBottomRefForDialogs = useRef(scrollToBottom);
|
|
3967
|
+
useEffect(() => {
|
|
3968
|
+
agentIdRefForDialogs.current = agent?.id ?? null;
|
|
3969
|
+
}, [agent?.id]);
|
|
3970
|
+
useEffect(() => {
|
|
3971
|
+
const prevId = agentStubIdRefForDialogs.current;
|
|
3972
|
+
agentStubIdRefForDialogs.current = agentStub.id;
|
|
3973
|
+
const visibleRegistry = { ...getVisibleDialogRegistry() };
|
|
3974
|
+
const normalizedPrevId = normalizeDialogAgentId(prevId);
|
|
3975
|
+
if (normalizedPrevId) {
|
|
3976
|
+
delete visibleRegistry[normalizedPrevId];
|
|
3977
|
+
globalThis.__agentDialogVisibleCallbacks = visibleRegistry;
|
|
3978
|
+
}
|
|
3979
|
+
if (prevId && prevId !== agentStub.id) {
|
|
3980
|
+
const orphanedDialog = activeInlineDialogRef.current;
|
|
3981
|
+
if (orphanedDialog) {
|
|
3982
|
+
setActiveInlineDialog(null);
|
|
3983
|
+
window.dispatchEvent(new CustomEvent("agent:dialog:orphaned", {
|
|
3984
|
+
detail: { callbackId: orphanedDialog.request.callbackId },
|
|
3985
|
+
}));
|
|
3986
|
+
}
|
|
3987
|
+
}
|
|
3988
|
+
}, [agentStub.id]);
|
|
3989
|
+
useEffect(() => {
|
|
3990
|
+
isActiveRefForDialogs.current = isActive;
|
|
3991
|
+
}, [isActive]);
|
|
3992
|
+
useEffect(() => {
|
|
3993
|
+
scrollToBottomRefForDialogs.current = scrollToBottom;
|
|
3994
|
+
}, [scrollToBottom]);
|
|
2829
3995
|
// Listen for agent inline dialog requests from AgentDialogHandler
|
|
2830
3996
|
useEffect(() => {
|
|
3997
|
+
if (orphanTimeoutRef.current) {
|
|
3998
|
+
clearTimeout(orphanTimeoutRef.current);
|
|
3999
|
+
orphanTimeoutRef.current = null;
|
|
4000
|
+
}
|
|
4001
|
+
const globalListeners = (globalThis.__agentDialogMountedAgents ?? []).filter((x) => typeof x === "string");
|
|
4002
|
+
const normalizedAgentStubId = normalizeDialogAgentId(agentStubIdRefForDialogs.current);
|
|
4003
|
+
if (normalizedAgentStubId &&
|
|
4004
|
+
!globalListeners.includes(normalizedAgentStubId)) {
|
|
4005
|
+
globalListeners.push(normalizedAgentStubId);
|
|
4006
|
+
}
|
|
4007
|
+
globalThis.__agentDialogMountedAgents = globalListeners;
|
|
2831
4008
|
const handleDialogShow = (event) => {
|
|
2832
|
-
const
|
|
2833
|
-
|
|
2834
|
-
|
|
4009
|
+
const detail = event?.detail;
|
|
4010
|
+
const request = detail?.request;
|
|
4011
|
+
const onComplete = detail?.onComplete;
|
|
4012
|
+
const onCancel = detail?.onCancel;
|
|
4013
|
+
const terminalAgentId = normalizeDialogAgentId(agentIdRefForDialogs.current);
|
|
4014
|
+
const terminalAgentStubId = normalizeDialogAgentId(agentStubIdRefForDialogs.current);
|
|
4015
|
+
const isActiveNow = isActiveRefForDialogs.current;
|
|
4016
|
+
if (!request)
|
|
4017
|
+
return;
|
|
4018
|
+
const requestAgentId = normalizeDialogAgentId(request.agentId);
|
|
4019
|
+
const agentMatch = !requestAgentId ||
|
|
4020
|
+
!terminalAgentStubId ||
|
|
4021
|
+
requestAgentId === terminalAgentStubId ||
|
|
4022
|
+
requestAgentId === terminalAgentId;
|
|
4023
|
+
if (!isActiveNow) {
|
|
4024
|
+
return;
|
|
4025
|
+
}
|
|
4026
|
+
if (!request)
|
|
4027
|
+
return;
|
|
4028
|
+
// Only handle dialog requests for this terminal's agent stub
|
|
4029
|
+
if (requestAgentId &&
|
|
4030
|
+
terminalAgentStubId &&
|
|
4031
|
+
requestAgentId !== terminalAgentStubId &&
|
|
4032
|
+
requestAgentId !== terminalAgentId) {
|
|
2835
4033
|
return;
|
|
2836
4034
|
}
|
|
2837
4035
|
console.log("[AgentTerminal] Received inline dialog request:", request);
|
|
2838
|
-
setActiveInlineDialog({
|
|
4036
|
+
setActiveInlineDialog({
|
|
4037
|
+
request,
|
|
4038
|
+
onComplete: (result) => {
|
|
4039
|
+
onComplete(result);
|
|
4040
|
+
onInteractionSubmitted?.();
|
|
4041
|
+
},
|
|
4042
|
+
onCancel: () => {
|
|
4043
|
+
onCancel();
|
|
4044
|
+
onInteractionSubmitted?.();
|
|
4045
|
+
},
|
|
4046
|
+
});
|
|
2839
4047
|
// Notify AgentDialogHandler that we accepted the dialog (stops retry mechanism)
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
4048
|
+
if (request.callbackId) {
|
|
4049
|
+
window.dispatchEvent(new CustomEvent("agent:dialog:accepted", {
|
|
4050
|
+
detail: { callbackId: request.callbackId },
|
|
4051
|
+
}));
|
|
4052
|
+
}
|
|
4053
|
+
setTimeout(() => {
|
|
4054
|
+
if (request.dialogType === "questionnaire") {
|
|
4055
|
+
scrollToBottomRefForDialogs.current?.();
|
|
4056
|
+
return;
|
|
4057
|
+
}
|
|
4058
|
+
scrollToBottomRefForDialogs.current?.();
|
|
4059
|
+
}, 100);
|
|
2845
4060
|
};
|
|
2846
4061
|
window.addEventListener("agent:dialog:show", handleDialogShow);
|
|
2847
|
-
return () =>
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
4062
|
+
return () => {
|
|
4063
|
+
const mounted = (globalThis.__agentDialogMountedAgents ?? []).filter((x) => typeof x === "string");
|
|
4064
|
+
globalThis.__agentDialogMountedAgents = mounted.filter((id) => id !== normalizeDialogAgentId(agentStubIdRefForDialogs.current));
|
|
4065
|
+
const visibleRegistry = { ...getVisibleDialogRegistry() };
|
|
4066
|
+
const idsToClear = [
|
|
4067
|
+
normalizeDialogAgentId(agentStubIdRefForDialogs.current),
|
|
4068
|
+
normalizeDialogAgentId(agentIdRefForDialogs.current),
|
|
4069
|
+
].filter(Boolean);
|
|
4070
|
+
idsToClear.forEach((id) => delete visibleRegistry[id]);
|
|
4071
|
+
globalThis.__agentDialogVisibleCallbacks = visibleRegistry;
|
|
4072
|
+
// If unmounting with an active dialog, defer the orphan event so that
|
|
4073
|
+
// React strict mode remounts can cancel it. Only real unmounts fire.
|
|
4074
|
+
const orphanedDialog = activeInlineDialogRef.current;
|
|
4075
|
+
if (orphanedDialog) {
|
|
4076
|
+
orphanTimeoutRef.current = setTimeout(() => {
|
|
4077
|
+
orphanTimeoutRef.current = null;
|
|
4078
|
+
window.dispatchEvent(new CustomEvent("agent:dialog:orphaned", {
|
|
4079
|
+
detail: { callbackId: orphanedDialog.request.callbackId },
|
|
4080
|
+
}));
|
|
4081
|
+
}, 300);
|
|
4082
|
+
}
|
|
4083
|
+
window.removeEventListener("agent:dialog:show", handleDialogShow);
|
|
4084
|
+
};
|
|
4085
|
+
}, []);
|
|
4086
|
+
// Announce when this terminal is ready to accept dialogs.
|
|
4087
|
+
// Fire immediately on mount using agentStub.id so the AgentDialogHandler
|
|
4088
|
+
// can re-dispatch pending dialogs without waiting for the async agent load.
|
|
4089
|
+
// Also fire when agent?.id becomes available in case it differs from the stub.
|
|
2851
4090
|
useEffect(() => {
|
|
2852
|
-
|
|
2853
|
-
|
|
4091
|
+
const normalizedStubId = normalizeDialogAgentId(agentStub.id);
|
|
4092
|
+
if (normalizedStubId) {
|
|
2854
4093
|
window.dispatchEvent(new CustomEvent("agent:terminal:ready", {
|
|
2855
|
-
detail: {
|
|
4094
|
+
detail: {
|
|
4095
|
+
agentId: normalizedStubId,
|
|
4096
|
+
terminalInstanceId: dialogTerminalInstanceIdRef.current,
|
|
4097
|
+
},
|
|
2856
4098
|
}));
|
|
2857
4099
|
}
|
|
2858
|
-
}, [
|
|
4100
|
+
}, [agentStub.id]);
|
|
4101
|
+
useEffect(() => {
|
|
4102
|
+
const normalizedAgentId = normalizeDialogAgentId(agent?.id);
|
|
4103
|
+
const normalizedStubId = normalizeDialogAgentId(agentStub.id);
|
|
4104
|
+
if (normalizedAgentId && normalizedAgentId !== normalizedStubId) {
|
|
4105
|
+
window.dispatchEvent(new CustomEvent("agent:terminal:ready", {
|
|
4106
|
+
detail: {
|
|
4107
|
+
agentId: normalizedAgentId,
|
|
4108
|
+
terminalInstanceId: dialogTerminalInstanceIdRef.current,
|
|
4109
|
+
},
|
|
4110
|
+
}));
|
|
4111
|
+
}
|
|
4112
|
+
}, [agent?.id, agentStub.id]);
|
|
2859
4113
|
// Profiles are provided by parent component (Agents). No local loading here.
|
|
2860
4114
|
// Select active profile based on agent.profileId or agentStub.profileId
|
|
2861
4115
|
useEffect(() => {
|
|
@@ -2866,20 +4120,47 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2866
4120
|
// Use case-insensitive comparison for GUID matching (backend may return different casing)
|
|
2867
4121
|
const normalizedProfileId = profileIdToUse?.toLowerCase();
|
|
2868
4122
|
const candidate = normalizedProfileId
|
|
2869
|
-
?
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
4123
|
+
? profiles.find((p) => p.id?.toLowerCase() === normalizedProfileId)
|
|
4124
|
+
: undefined;
|
|
4125
|
+
if (!candidate) {
|
|
4126
|
+
setActiveProfile(undefined);
|
|
4127
|
+
return;
|
|
2874
4128
|
}
|
|
2875
|
-
|
|
4129
|
+
// Keep active profile in sync whenever the matching entry in `profiles` changes —
|
|
4130
|
+
// not only when the profile id changes. Otherwise availableSkills (and similar fields)
|
|
4131
|
+
// that are merged in the parent after async loads never update (e.g. Template Builder
|
|
4132
|
+
// settings skills merged into the profile).
|
|
4133
|
+
setActiveProfile((prev) => {
|
|
4134
|
+
if (!prev || prev.id !== candidate.id)
|
|
4135
|
+
return candidate;
|
|
4136
|
+
const skillKey = (p) => JSON.stringify({
|
|
4137
|
+
allowed: (p.allowedSkills ?? []).map((s) => [
|
|
4138
|
+
String(s?.id ?? ""),
|
|
4139
|
+
String(s?.name ?? ""),
|
|
4140
|
+
]),
|
|
4141
|
+
available: (p.availableSkills ?? []).map((s) => [
|
|
4142
|
+
String(s?.id ?? ""),
|
|
4143
|
+
String(s?.name ?? ""),
|
|
4144
|
+
]),
|
|
4145
|
+
});
|
|
4146
|
+
if (skillKey(prev) === skillKey(candidate))
|
|
4147
|
+
return prev;
|
|
4148
|
+
return candidate;
|
|
4149
|
+
});
|
|
4150
|
+
}, [
|
|
4151
|
+
profiles,
|
|
4152
|
+
agent?.id,
|
|
4153
|
+
agent?.profileId,
|
|
4154
|
+
agentStub.id,
|
|
4155
|
+
agentStub.profileId,
|
|
4156
|
+
]);
|
|
2876
4157
|
// Clear queued prompts when agent changes or is new;
|
|
2877
4158
|
// initial fetch is handled by loadAgent() for better performance and reliability
|
|
2878
4159
|
useEffect(() => {
|
|
2879
|
-
if (!agent?.id ||
|
|
4160
|
+
if (!agent?.id || isLocalOnlyDraftAgent) {
|
|
2880
4161
|
setQueuedPrompts([]);
|
|
2881
4162
|
}
|
|
2882
|
-
}, [agent?.id,
|
|
4163
|
+
}, [agent?.id, isLocalOnlyDraftAgent]);
|
|
2883
4164
|
// Update selected model when the active profile or agent model changes
|
|
2884
4165
|
useEffect(() => {
|
|
2885
4166
|
if (!activeProfile)
|
|
@@ -2908,20 +4189,16 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2908
4189
|
// Initialize mode from metadata; fall back to agent.Mode from server
|
|
2909
4190
|
useEffect(() => {
|
|
2910
4191
|
try {
|
|
2911
|
-
const metaMode = agentMetadata?.mode;
|
|
2912
|
-
if (metaMode
|
|
2913
|
-
metaMode === "read-only" ||
|
|
2914
|
-
metaMode === "supervised") {
|
|
4192
|
+
const metaMode = normalizeAgentMode(agentMetadata?.mode);
|
|
4193
|
+
if (metaMode) {
|
|
2915
4194
|
setMode(metaMode);
|
|
2916
4195
|
return;
|
|
2917
4196
|
}
|
|
2918
4197
|
}
|
|
2919
4198
|
catch { }
|
|
2920
4199
|
try {
|
|
2921
|
-
const serverMode = agent?.mode;
|
|
2922
|
-
if (serverMode
|
|
2923
|
-
serverMode === "read-only" ||
|
|
2924
|
-
serverMode === "supervised") {
|
|
4200
|
+
const serverMode = normalizeAgentMode(agent?.mode);
|
|
4201
|
+
if (serverMode) {
|
|
2925
4202
|
setMode(serverMode);
|
|
2926
4203
|
}
|
|
2927
4204
|
}
|
|
@@ -2941,7 +4218,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2941
4218
|
textareaRef.current.focus();
|
|
2942
4219
|
}
|
|
2943
4220
|
}, [messages, activePlaceholderInput]);
|
|
2944
|
-
// Persist any pending settings (mode/model) once an agent exists server-side
|
|
4221
|
+
// Persist any pending settings (mode/model/profile) once an agent exists server-side
|
|
2945
4222
|
const persistPendingSettingsIfNeeded = useCallback(async () => {
|
|
2946
4223
|
try {
|
|
2947
4224
|
if (!agent?.id)
|
|
@@ -2954,6 +4231,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2954
4231
|
payload.model = pending.modelName;
|
|
2955
4232
|
if (pending.mode)
|
|
2956
4233
|
payload.mode = pending.mode;
|
|
4234
|
+
if (pending.profileId)
|
|
4235
|
+
payload.profileId = pending.profileId;
|
|
4236
|
+
if (pending.profileName != null)
|
|
4237
|
+
payload.profileName = pending.profileName;
|
|
2957
4238
|
if (Object.keys(payload).length === 0)
|
|
2958
4239
|
return;
|
|
2959
4240
|
await updateAgentSettings(agent.id, payload);
|
|
@@ -2963,6 +4244,92 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
2963
4244
|
console.error("Failed to persist pending settings", e);
|
|
2964
4245
|
}
|
|
2965
4246
|
}, [agent?.id]);
|
|
4247
|
+
const getPendingRequestSettings = useCallback(() => {
|
|
4248
|
+
const pending = pendingSettingsRef.current;
|
|
4249
|
+
const requestProfile = pending?.profileId
|
|
4250
|
+
? profiles.find((profile) => profile.id === pending.profileId) ||
|
|
4251
|
+
activeProfile ||
|
|
4252
|
+
profiles[0]
|
|
4253
|
+
: activeProfile || profiles[0];
|
|
4254
|
+
let requestModelId = selectedModelId;
|
|
4255
|
+
if (pending?.modelName) {
|
|
4256
|
+
const requestModel = (requestProfile?.models || []).find((model) => (model.name || "").trim().toLowerCase() ===
|
|
4257
|
+
pending.modelName?.trim().toLowerCase());
|
|
4258
|
+
requestModelId = requestModel?.id || requestModelId;
|
|
4259
|
+
}
|
|
4260
|
+
return {
|
|
4261
|
+
mode: (pending?.mode || mode),
|
|
4262
|
+
profileId: pending?.profileId || requestProfile?.id || "",
|
|
4263
|
+
profileName: pending?.profileName || requestProfile?.name || "",
|
|
4264
|
+
modelId: requestModelId,
|
|
4265
|
+
};
|
|
4266
|
+
}, [activeProfile, mode, profiles, selectedModelId]);
|
|
4267
|
+
const getSubmitErrorMessage = (error) => {
|
|
4268
|
+
const fallback = "Failed to submit prompt. Please try again.";
|
|
4269
|
+
if (!(error instanceof Error))
|
|
4270
|
+
return fallback;
|
|
4271
|
+
const cleaned = toUserFacingAgentErrorMessage(error.message);
|
|
4272
|
+
return cleaned || fallback;
|
|
4273
|
+
};
|
|
4274
|
+
const suppressedQueuedPromptsRef = useRef([]);
|
|
4275
|
+
const pruneSuppressedQueuedPrompts = useCallback(() => {
|
|
4276
|
+
const cutoff = Date.now() - 15_000;
|
|
4277
|
+
suppressedQueuedPromptsRef.current =
|
|
4278
|
+
suppressedQueuedPromptsRef.current.filter((entry) => entry.createdAt >= cutoff);
|
|
4279
|
+
}, []);
|
|
4280
|
+
const registerSuppressedQueuedPrompt = useCallback((agentId, promptText) => {
|
|
4281
|
+
const normalizedAgentId = agentId.trim().toLowerCase();
|
|
4282
|
+
const normalizedPrompt = promptText.trim();
|
|
4283
|
+
if (!normalizedAgentId || !normalizedPrompt) {
|
|
4284
|
+
return null;
|
|
4285
|
+
}
|
|
4286
|
+
pruneSuppressedQueuedPrompts();
|
|
4287
|
+
const token = crypto.randomUUID();
|
|
4288
|
+
suppressedQueuedPromptsRef.current = [
|
|
4289
|
+
...suppressedQueuedPromptsRef.current,
|
|
4290
|
+
{
|
|
4291
|
+
token,
|
|
4292
|
+
agentId: normalizedAgentId,
|
|
4293
|
+
prompt: normalizedPrompt,
|
|
4294
|
+
createdAt: Date.now(),
|
|
4295
|
+
},
|
|
4296
|
+
];
|
|
4297
|
+
return token;
|
|
4298
|
+
}, [pruneSuppressedQueuedPrompts]);
|
|
4299
|
+
const clearSuppressedQueuedPrompt = useCallback((token) => {
|
|
4300
|
+
if (!token) {
|
|
4301
|
+
return;
|
|
4302
|
+
}
|
|
4303
|
+
suppressedQueuedPromptsRef.current =
|
|
4304
|
+
suppressedQueuedPromptsRef.current.filter((entry) => entry.token !== token);
|
|
4305
|
+
}, []);
|
|
4306
|
+
const shouldSuppressQueuedPrompt = useCallback((queueEntry) => {
|
|
4307
|
+
const normalizedAgentId = queueEntry?.targetAgentId?.trim().toLowerCase();
|
|
4308
|
+
const normalizedPrompt = queueEntry?.prompt?.trim();
|
|
4309
|
+
if (!normalizedAgentId || !normalizedPrompt) {
|
|
4310
|
+
return false;
|
|
4311
|
+
}
|
|
4312
|
+
pruneSuppressedQueuedPrompts();
|
|
4313
|
+
const matchedEntry = suppressedQueuedPromptsRef.current.find((entry) => entry.agentId === normalizedAgentId &&
|
|
4314
|
+
entry.prompt === normalizedPrompt);
|
|
4315
|
+
if (!matchedEntry) {
|
|
4316
|
+
return false;
|
|
4317
|
+
}
|
|
4318
|
+
suppressedQueuedPromptsRef.current =
|
|
4319
|
+
suppressedQueuedPromptsRef.current.filter((entry) => entry.token !== matchedEntry.token);
|
|
4320
|
+
return true;
|
|
4321
|
+
}, [pruneSuppressedQueuedPrompts]);
|
|
4322
|
+
const cancelActiveInlineDialog = useCallback(() => {
|
|
4323
|
+
const activeDialog = activeInlineDialogRef.current;
|
|
4324
|
+
if (!activeDialog)
|
|
4325
|
+
return;
|
|
4326
|
+
try {
|
|
4327
|
+
activeDialog.onCancel();
|
|
4328
|
+
}
|
|
4329
|
+
finally {
|
|
4330
|
+
setActiveInlineDialog(null);
|
|
4331
|
+
}
|
|
4332
|
+
}, []);
|
|
2966
4333
|
const handleSubmit = async () => {
|
|
2967
4334
|
// Guard against double-submit and missing context
|
|
2968
4335
|
if (isSubmitting) {
|
|
@@ -3006,6 +4373,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3006
4373
|
setError("Agent not ready. Please try again.");
|
|
3007
4374
|
return;
|
|
3008
4375
|
}
|
|
4376
|
+
const hadQuestionnaireDialogOpen = activeInlineDialogRef.current?.request.dialogType === "questionnaire";
|
|
4377
|
+
const suppressedQueuedPromptToken = hadQuestionnaireDialogOpen && savedPrompt
|
|
4378
|
+
? registerSuppressedQueuedPrompt(agentId, savedPrompt)
|
|
4379
|
+
: null;
|
|
4380
|
+
// A new user prompt supersedes any active questionnaire/inline dialog.
|
|
4381
|
+
cancelActiveInlineDialog();
|
|
3009
4382
|
// Generate a temporary ID for optimistic UI - will be replaced by server ID
|
|
3010
4383
|
const tempMessageId = `temp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
|
|
3011
4384
|
try {
|
|
@@ -3092,26 +4465,24 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3092
4465
|
console.warn("[AgentTerminal] Failed to compute live context:", e);
|
|
3093
4466
|
}
|
|
3094
4467
|
// Add visible test IDs to context (only for Help agent)
|
|
3095
|
-
const
|
|
4468
|
+
const requestSettings = getPendingRequestSettings();
|
|
4469
|
+
const currentProfileName = requestSettings.profileName;
|
|
3096
4470
|
effectiveContext = addVisibleTestIdsToContext(effectiveContext, currentProfileName);
|
|
3097
4471
|
const request = {
|
|
3098
4472
|
agentId: agentId,
|
|
3099
4473
|
message: savedPrompt,
|
|
3100
4474
|
sessionId: editContext.sessionId,
|
|
3101
|
-
profileId:
|
|
4475
|
+
profileId: requestSettings.profileId,
|
|
3102
4476
|
profile: currentProfileName,
|
|
3103
|
-
model:
|
|
3104
|
-
mode: mode,
|
|
4477
|
+
model: requestSettings.modelId,
|
|
4478
|
+
mode: requestSettings.mode,
|
|
3105
4479
|
context: canonicalizeAgentMetadata(effectiveContext), // Use fresh live context when in live mode
|
|
3106
|
-
deterministic: deterministicFlags.deterministic,
|
|
3107
|
-
seed: deterministicFlags.seed,
|
|
3108
4480
|
};
|
|
3109
4481
|
console.log("[AgentTerminal] Calling startAgent API for agent:", agentId);
|
|
3110
4482
|
const response = await startAgent(request);
|
|
3111
4483
|
console.log("[AgentTerminal] startAgent response:", response);
|
|
3112
4484
|
// Check if prompt was queued (agent was already running)
|
|
3113
|
-
const wasQueued = response.message?.toLowerCase().includes("queued")
|
|
3114
|
-
response.status === "Queued";
|
|
4485
|
+
const wasQueued = response.message?.toLowerCase().includes("queued");
|
|
3115
4486
|
if (wasQueued) {
|
|
3116
4487
|
// Prompt was queued - show a brief notification but don't set waiting state
|
|
3117
4488
|
// The prompt will be processed when the agent becomes idle
|
|
@@ -3121,6 +4492,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3121
4492
|
isWaitingRef.current = false;
|
|
3122
4493
|
}
|
|
3123
4494
|
else {
|
|
4495
|
+
clearSuppressedQueuedPrompt(suppressedQueuedPromptToken);
|
|
3124
4496
|
// Normal submission - set waiting state to show dancing dots immediately
|
|
3125
4497
|
setIsWaitingForResponse(true);
|
|
3126
4498
|
isWaitingRef.current = true;
|
|
@@ -3135,11 +4507,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3135
4507
|
...prev.filter((p) => p !== savedPrompt).slice(0, 9),
|
|
3136
4508
|
]);
|
|
3137
4509
|
setCurrentHistoryIndex(-1);
|
|
4510
|
+
await onInteractionSubmitted?.();
|
|
3138
4511
|
// WebSocket connection is already active via subscription - no need for SSE
|
|
3139
4512
|
}
|
|
3140
4513
|
catch (err) {
|
|
3141
4514
|
console.error("[AgentTerminal] Failed to submit prompt:", err);
|
|
3142
|
-
|
|
4515
|
+
clearSuppressedQueuedPrompt(suppressedQueuedPromptToken);
|
|
4516
|
+
setError(getSubmitErrorMessage(err));
|
|
3143
4517
|
setIsWaitingForResponse(false);
|
|
3144
4518
|
isWaitingRef.current = false;
|
|
3145
4519
|
// Remove the optimistic user message on API error
|
|
@@ -3349,29 +4723,29 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3349
4723
|
console.warn("[AgentTerminal] Failed to compute live context for quick message:", e);
|
|
3350
4724
|
}
|
|
3351
4725
|
// Add visible test IDs to context (only for Help agent)
|
|
3352
|
-
const
|
|
4726
|
+
const requestSettings = getPendingRequestSettings();
|
|
4727
|
+
const currentProfileName = requestSettings.profileName;
|
|
3353
4728
|
effectiveContext = addVisibleTestIdsToContext(effectiveContext, currentProfileName);
|
|
3354
4729
|
const request = {
|
|
3355
4730
|
agentId: agent.id,
|
|
3356
4731
|
message: savedPrompt,
|
|
3357
4732
|
sessionId: editContext.sessionId,
|
|
3358
|
-
profileId:
|
|
4733
|
+
profileId: requestSettings.profileId,
|
|
3359
4734
|
profile: currentProfileName,
|
|
3360
|
-
model:
|
|
3361
|
-
mode: mode,
|
|
4735
|
+
model: requestSettings.modelId,
|
|
4736
|
+
mode: requestSettings.mode,
|
|
3362
4737
|
context: canonicalizeAgentMetadata(effectiveContext), // Use fresh live context when in live mode
|
|
3363
|
-
deterministic: deterministicFlags.deterministic,
|
|
3364
|
-
seed: deterministicFlags.seed,
|
|
3365
4738
|
};
|
|
3366
4739
|
console.log("[AgentTerminal] Calling startAgent API for quick message");
|
|
3367
4740
|
await startAgent(request);
|
|
3368
4741
|
// If user changed mode/model while the agent was new, persist them now
|
|
3369
4742
|
await persistPendingSettingsIfNeeded();
|
|
4743
|
+
await onInteractionSubmitted?.();
|
|
3370
4744
|
// WebSocket connection is already active via subscription - no need for SSE
|
|
3371
4745
|
}
|
|
3372
4746
|
catch (err) {
|
|
3373
4747
|
console.error("[AgentTerminal] Failed to submit quick message:", err);
|
|
3374
|
-
setError(
|
|
4748
|
+
setError(getSubmitErrorMessage(err));
|
|
3375
4749
|
setIsWaitingForResponse(false);
|
|
3376
4750
|
isWaitingRef.current = false;
|
|
3377
4751
|
// Remove the optimistic user message on API error
|
|
@@ -3549,66 +4923,90 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3549
4923
|
}
|
|
3550
4924
|
return context;
|
|
3551
4925
|
}, [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
|
-
},
|
|
4926
|
+
const buildPageContextItem = useCallback((item) => ({
|
|
4927
|
+
id: item.id,
|
|
4928
|
+
language: item.language,
|
|
4929
|
+
version: item.version,
|
|
4930
|
+
name: editContext?.item?.name,
|
|
4931
|
+
path: editContext?.item?.path,
|
|
4932
|
+
}), [editContext?.item?.name, editContext?.item?.path]);
|
|
4933
|
+
const buildComponentContext = useCallback((item) => {
|
|
4934
|
+
if (!editContext?.selection?.length)
|
|
4935
|
+
return undefined;
|
|
4936
|
+
return editContext.selection.map((componentId) => {
|
|
4937
|
+
let componentName;
|
|
4938
|
+
let componentType;
|
|
4939
|
+
let renderingItemId;
|
|
4940
|
+
if (editContext.page) {
|
|
4941
|
+
try {
|
|
4942
|
+
const component = getComponentById(componentId, editContext.page);
|
|
4943
|
+
componentName =
|
|
4944
|
+
component?.datasourceItem?.name || component?.name || undefined;
|
|
4945
|
+
componentType = component?.type || undefined;
|
|
4946
|
+
renderingItemId = component?.rendering?.id || undefined;
|
|
3595
4947
|
}
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
4948
|
+
catch { }
|
|
4949
|
+
}
|
|
4950
|
+
return {
|
|
4951
|
+
componentId,
|
|
4952
|
+
componentName,
|
|
4953
|
+
componentType,
|
|
4954
|
+
renderingItemId,
|
|
4955
|
+
pageItem: buildPageContextItem(item),
|
|
4956
|
+
};
|
|
4957
|
+
});
|
|
4958
|
+
}, [buildPageContextItem, editContext?.page, editContext?.selection]);
|
|
4959
|
+
const buildFieldContext = useCallback(() => {
|
|
4960
|
+
const focusedField = fieldsContext?.focusedField;
|
|
4961
|
+
if (!focusedField?.fieldId || !focusedField?.item?.id)
|
|
4962
|
+
return undefined;
|
|
4963
|
+
const fieldItem = focusedField.item;
|
|
4964
|
+
const currentItem = editContext?.currentItemDescriptor;
|
|
4965
|
+
let fieldItemName = fieldItem.id === currentItem?.id ? editContext?.item?.name : undefined;
|
|
4966
|
+
if (!fieldItemName && editContext?.page) {
|
|
4967
|
+
try {
|
|
4968
|
+
const component = getComponentById(fieldItem.id, editContext.page);
|
|
4969
|
+
fieldItemName =
|
|
4970
|
+
component?.datasourceItem?.name || component?.name || undefined;
|
|
4971
|
+
}
|
|
4972
|
+
catch { }
|
|
4973
|
+
}
|
|
4974
|
+
return {
|
|
4975
|
+
fieldId: focusedField.fieldId,
|
|
4976
|
+
fieldName: focusedField.fieldName,
|
|
4977
|
+
item: {
|
|
4978
|
+
id: fieldItem.id,
|
|
4979
|
+
language: fieldItem.language || currentItem?.language || "en",
|
|
4980
|
+
version: fieldItem.version ?? currentItem?.version ?? 0,
|
|
4981
|
+
name: fieldItem.name || fieldItemName,
|
|
4982
|
+
},
|
|
3602
4983
|
};
|
|
3603
4984
|
}, [
|
|
3604
4985
|
editContext?.currentItemDescriptor,
|
|
3605
|
-
editContext?.selection,
|
|
3606
|
-
editContext?.workspaceId,
|
|
3607
4986
|
editContext?.item?.name,
|
|
3608
|
-
editContext?.
|
|
3609
|
-
editContext?.openSidebars,
|
|
4987
|
+
editContext?.page,
|
|
3610
4988
|
fieldsContext?.focusedField,
|
|
3611
4989
|
]);
|
|
4990
|
+
const buildEditorContextPayload = useCallback((item) => ({
|
|
4991
|
+
items: item ? [buildPageContextItem(item)] : undefined,
|
|
4992
|
+
currentItemId: item?.id,
|
|
4993
|
+
components: item ? buildComponentContext(item) : undefined,
|
|
4994
|
+
field: buildFieldContext(),
|
|
4995
|
+
activeWorkspace: editContext?.workspaceId,
|
|
4996
|
+
hasPageLoaded: !!editContext?.getActiveSlotContext()?.primaryPageViewContext?.page,
|
|
4997
|
+
openSidebars: editContext?.openSidebars,
|
|
4998
|
+
}), [
|
|
4999
|
+
buildComponentContext,
|
|
5000
|
+
buildFieldContext,
|
|
5001
|
+
buildPageContextItem,
|
|
5002
|
+
editContext,
|
|
5003
|
+
]);
|
|
5004
|
+
// Helper function to build current context from editor state
|
|
5005
|
+
const buildCurrentContext = useCallback(() => {
|
|
5006
|
+
// Return context even without item - we want view info regardless
|
|
5007
|
+
const item = editContext?.currentItemDescriptor;
|
|
5008
|
+
return buildEditorContextPayload(item);
|
|
5009
|
+
}, [buildEditorContextPayload, editContext?.currentItemDescriptor]);
|
|
3612
5010
|
// Live context updates: watch for changes and update agent context when in "live" mode
|
|
3613
5011
|
const previousContextRef = useRef("");
|
|
3614
5012
|
useEffect(() => {
|
|
@@ -3708,6 +5106,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3708
5106
|
const handleRefreshContext = useCallback(async () => {
|
|
3709
5107
|
if (!agent?.id)
|
|
3710
5108
|
return;
|
|
5109
|
+
const normalizedAgentProfileId = agent?.profileId?.toLowerCase();
|
|
5110
|
+
const refreshProfile = activeProfile ||
|
|
5111
|
+
profiles.find((p) => p.id?.toLowerCase() === normalizedAgentProfileId);
|
|
5112
|
+
if (refreshProfile?.editorContextMode === "none") {
|
|
5113
|
+
editContext?.showInfoToast({
|
|
5114
|
+
summary: "This profile excludes editor context.",
|
|
5115
|
+
});
|
|
5116
|
+
return;
|
|
5117
|
+
}
|
|
3711
5118
|
try {
|
|
3712
5119
|
const currentCtx = buildCurrentContext();
|
|
3713
5120
|
if (!currentCtx) {
|
|
@@ -3748,7 +5155,73 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3748
5155
|
buildCurrentContext,
|
|
3749
5156
|
sanitizeAgentMetadata,
|
|
3750
5157
|
editContext,
|
|
5158
|
+
activeProfile,
|
|
5159
|
+
profiles,
|
|
3751
5160
|
]);
|
|
5161
|
+
const browserCaptureClaim = useMemo(() => getBrowserCaptureClaim(agentMetadata), [agentMetadata]);
|
|
5162
|
+
const isPendingBrowserCaptureWait = pendingBrowserCaptureDialogType === DIALOG_TYPES.CAPTURE_PAGE_DOM ||
|
|
5163
|
+
pendingBrowserCaptureDialogType === DIALOG_TYPES.CAPTURE_PAGE_SCREENSHOT;
|
|
5164
|
+
const currentSessionId = editContext?.sessionId?.trim() || "";
|
|
5165
|
+
const claimedSessionId = browserCaptureClaim?.sessionId?.trim() || "";
|
|
5166
|
+
const isClaimedByCurrentSession = !!currentSessionId &&
|
|
5167
|
+
!!claimedSessionId &&
|
|
5168
|
+
currentSessionId.toLowerCase() === claimedSessionId.toLowerCase();
|
|
5169
|
+
const isClaimedByAnotherBrowser = !!claimedSessionId && !isClaimedByCurrentSession;
|
|
5170
|
+
const handleClaimBrowser = useCallback(async (takeOver) => {
|
|
5171
|
+
if (!agent?.id || !editContext?.sessionId)
|
|
5172
|
+
return;
|
|
5173
|
+
setIsBrowserClaimMutationPending(true);
|
|
5174
|
+
try {
|
|
5175
|
+
const response = await claimAgentBrowser({
|
|
5176
|
+
agentId: agent.id,
|
|
5177
|
+
sessionId: editContext.sessionId,
|
|
5178
|
+
takeOver,
|
|
5179
|
+
terminalInstanceId: dialogTerminalInstanceIdRef.current,
|
|
5180
|
+
});
|
|
5181
|
+
setAgentMetadata((prev) => sanitizeAgentMetadata(setBrowserCaptureClaim(prev, response.claim || null)));
|
|
5182
|
+
}
|
|
5183
|
+
catch (err) {
|
|
5184
|
+
console.error("[AgentTerminal] Failed to claim browser:", err);
|
|
5185
|
+
editContext.showErrorToast(err);
|
|
5186
|
+
}
|
|
5187
|
+
finally {
|
|
5188
|
+
setIsBrowserClaimMutationPending(false);
|
|
5189
|
+
}
|
|
5190
|
+
}, [agent?.id, editContext, sanitizeAgentMetadata]);
|
|
5191
|
+
const handleReleaseBrowser = useCallback(async () => {
|
|
5192
|
+
if (!agent?.id || !editContext?.sessionId)
|
|
5193
|
+
return;
|
|
5194
|
+
setIsBrowserClaimMutationPending(true);
|
|
5195
|
+
try {
|
|
5196
|
+
const response = await releaseAgentBrowser({
|
|
5197
|
+
agentId: agent.id,
|
|
5198
|
+
sessionId: editContext.sessionId,
|
|
5199
|
+
terminalInstanceId: dialogTerminalInstanceIdRef.current,
|
|
5200
|
+
});
|
|
5201
|
+
setAgentMetadata((prev) => sanitizeAgentMetadata(setBrowserCaptureClaim(prev, response.claim || null)));
|
|
5202
|
+
}
|
|
5203
|
+
catch (err) {
|
|
5204
|
+
console.error("[AgentTerminal] Failed to release browser:", err);
|
|
5205
|
+
editContext.showErrorToast(err);
|
|
5206
|
+
}
|
|
5207
|
+
finally {
|
|
5208
|
+
setIsBrowserClaimMutationPending(false);
|
|
5209
|
+
}
|
|
5210
|
+
}, [agent?.id, editContext, sanitizeAgentMetadata]);
|
|
5211
|
+
useEffect(() => {
|
|
5212
|
+
return () => {
|
|
5213
|
+
if (!agent?.id || !editContext?.sessionId || !isClaimedByCurrentSession) {
|
|
5214
|
+
return;
|
|
5215
|
+
}
|
|
5216
|
+
void releaseAgentBrowser({
|
|
5217
|
+
agentId: agent.id,
|
|
5218
|
+
sessionId: editContext.sessionId,
|
|
5219
|
+
terminalInstanceId: dialogTerminalInstanceIdRef.current,
|
|
5220
|
+
}).catch((error) => {
|
|
5221
|
+
console.warn("[AgentTerminal] Failed to release browser on unmount:", error);
|
|
5222
|
+
});
|
|
5223
|
+
};
|
|
5224
|
+
}, [agent?.id, editContext?.sessionId, isClaimedByCurrentSession]);
|
|
3752
5225
|
// Stop current execution/stream safely
|
|
3753
5226
|
const handleStop = useCallback(async () => {
|
|
3754
5227
|
try {
|
|
@@ -3812,13 +5285,81 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3812
5285
|
if (effectiveCostLimit === undefined) {
|
|
3813
5286
|
effectiveCostLimit = undefined;
|
|
3814
5287
|
}
|
|
3815
|
-
// Calculate total token usage for cost display
|
|
3816
|
-
|
|
5288
|
+
// Calculate total token usage for cost display.
|
|
5289
|
+
// Message rows do not currently persist imageCost, so on refresh we fall back
|
|
5290
|
+
// to the persisted agent aggregate for that single field.
|
|
5291
|
+
const totalTokens = (() => {
|
|
5292
|
+
const totals = calculateTotalTokens(messages);
|
|
5293
|
+
return {
|
|
5294
|
+
...totals,
|
|
5295
|
+
imageCost: totals.imageCost || Number(agent?.totalImageCost) || 0,
|
|
5296
|
+
};
|
|
5297
|
+
})();
|
|
3817
5298
|
// Determine if the agent is actively executing (submitting, connecting, waiting, or streaming)
|
|
3818
5299
|
const isExecuting = isSubmitting ||
|
|
3819
5300
|
isConnecting ||
|
|
3820
5301
|
isWaitingForResponse ||
|
|
3821
5302
|
hasActiveStreaming();
|
|
5303
|
+
const assistantMessageCount = useMemo(() => messages.filter((message) => message.role === "assistant").length, [messages]);
|
|
5304
|
+
const messagesWithToolCallsCount = useMemo(() => messages.filter((message) => (message.toolCalls || []).length > 0).length, [messages]);
|
|
5305
|
+
const totalToolCallCount = useMemo(() => messages.reduce((sum, message) => sum + (message.toolCalls?.length || 0), 0), [messages]);
|
|
5306
|
+
const incompleteToolCallCount = useMemo(() => messages.reduce((sum, message) => sum +
|
|
5307
|
+
(message.toolCalls?.filter((toolCall) => !toolCall.isCompleted).length || 0), 0), [messages]);
|
|
5308
|
+
const assistantGroupsWithRenderableToolCalls = useMemo(() => {
|
|
5309
|
+
const groups = groupConsecutiveMessages(messages);
|
|
5310
|
+
return groups.filter((group) => {
|
|
5311
|
+
if (group.type !== "assistant-group")
|
|
5312
|
+
return false;
|
|
5313
|
+
const convertedMessages = convertAgentMessagesToAiFormat(group.messages);
|
|
5314
|
+
return convertedMessages.some((message) => (message.tool_calls?.length || 0) > 0);
|
|
5315
|
+
}).length;
|
|
5316
|
+
}, [messages]);
|
|
5317
|
+
const assistantGroupCount = useMemo(() => groupConsecutiveMessages(messages).filter((group) => group.type === "assistant-group").length, [messages]);
|
|
5318
|
+
const runDiagnosticsSnapshot = useMemo(() => {
|
|
5319
|
+
const lastEvent = recentAgentRunEvents[recentAgentRunEvents.length - 1];
|
|
5320
|
+
return {
|
|
5321
|
+
agentId: currentAgentId,
|
|
5322
|
+
isSubmitting,
|
|
5323
|
+
isConnecting,
|
|
5324
|
+
isWaitingForResponse,
|
|
5325
|
+
isAgentThinking,
|
|
5326
|
+
isExecuting,
|
|
5327
|
+
hasActiveStreaming: hasActiveStreaming(),
|
|
5328
|
+
isSubscribed: normalizeDialogAgentId(subscribedAgentIdRef.current) ===
|
|
5329
|
+
normalizeDialogAgentId(currentAgentId),
|
|
5330
|
+
lastSeq: lastSeqRef.current,
|
|
5331
|
+
lastEventType: lastEvent?.type ?? null,
|
|
5332
|
+
lastEventAt: lastEvent?.timestamp ?? null,
|
|
5333
|
+
recentEvents: recentAgentRunEvents,
|
|
5334
|
+
assistantMessageCount,
|
|
5335
|
+
assistantGroupCount,
|
|
5336
|
+
assistantGroupsWithRenderableToolCalls,
|
|
5337
|
+
messagesWithToolCalls: messagesWithToolCallsCount,
|
|
5338
|
+
totalToolCallCount,
|
|
5339
|
+
incompleteToolCallCount,
|
|
5340
|
+
recentToolUiEvents,
|
|
5341
|
+
};
|
|
5342
|
+
}, [
|
|
5343
|
+
assistantGroupCount,
|
|
5344
|
+
assistantGroupsWithRenderableToolCalls,
|
|
5345
|
+
assistantMessageCount,
|
|
5346
|
+
currentAgentId,
|
|
5347
|
+
hasActiveStreaming,
|
|
5348
|
+
incompleteToolCallCount,
|
|
5349
|
+
isAgentThinking,
|
|
5350
|
+
isConnecting,
|
|
5351
|
+
isExecuting,
|
|
5352
|
+
isSubmitting,
|
|
5353
|
+
isWaitingForResponse,
|
|
5354
|
+
messagesWithToolCallsCount,
|
|
5355
|
+
recentAgentRunEvents,
|
|
5356
|
+
recentToolUiEvents,
|
|
5357
|
+
totalToolCallCount,
|
|
5358
|
+
]);
|
|
5359
|
+
const showInitialThinkingSplash = messages.length === 0 &&
|
|
5360
|
+
!error &&
|
|
5361
|
+
hideGreeting &&
|
|
5362
|
+
(isSubmitting || isConnecting);
|
|
3822
5363
|
// Compute dots visibility: only show BEFORE any assistant message exists
|
|
3823
5364
|
// This prevents duplicate headers - the dots indicator has its own header,
|
|
3824
5365
|
// and we don't want to show a second header below existing messages
|
|
@@ -3833,13 +5374,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3833
5374
|
// The message with the pending approval will display its own UI for approval
|
|
3834
5375
|
if (allPendingApprovals.length > 0)
|
|
3835
5376
|
return false;
|
|
5377
|
+
// The hidden-greeting startup splash already renders its own bouncing dots.
|
|
5378
|
+
// Suppress the generic indicator so reopening/running terminals don't show two.
|
|
5379
|
+
if (showInitialThinkingSplash)
|
|
5380
|
+
return false;
|
|
3836
5381
|
// IMPORTANT: If the last message is an assistant message and we're still executing,
|
|
3837
5382
|
// the AiResponseMessage for that message will show its own activity indicator.
|
|
3838
5383
|
// We only want these global thinking dots if the last message was from the user
|
|
3839
5384
|
// or if no messages exist yet (waiting for initial response).
|
|
3840
5385
|
const lastMessage = messages.length > 0 ? messages[messages.length - 1] : null;
|
|
3841
|
-
if (isExecuting &&
|
|
5386
|
+
if (isExecuting &&
|
|
5387
|
+
lastMessage?.role === "assistant" &&
|
|
5388
|
+
!lastMessage.isCompleted) {
|
|
3842
5389
|
return false;
|
|
5390
|
+
}
|
|
3843
5391
|
// Existing check for uncompleted assistant messages
|
|
3844
5392
|
const hasActiveStreamingMessage = messages.some((m) => !m.isCompleted && m.role === "assistant");
|
|
3845
5393
|
if (hasActiveStreamingMessage)
|
|
@@ -3855,20 +5403,22 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3855
5403
|
messages,
|
|
3856
5404
|
activeInlineDialog,
|
|
3857
5405
|
allPendingApprovals,
|
|
5406
|
+
showInitialThinkingSplash,
|
|
3858
5407
|
]);
|
|
3859
5408
|
// Move useMemo hook before early return to comply with Rules of Hooks
|
|
3860
|
-
const
|
|
5409
|
+
const resolvedEditorContextMode = React.useMemo(() => {
|
|
3861
5410
|
try {
|
|
3862
5411
|
const normalizedAgentProfileId = agent?.profileId?.toLowerCase();
|
|
3863
5412
|
const profile = activeProfile ||
|
|
3864
5413
|
profiles.find((p) => p.id?.toLowerCase() === normalizedAgentProfileId);
|
|
3865
|
-
|
|
3866
|
-
return mode === "live";
|
|
5414
|
+
return profile?.editorContextMode ?? null;
|
|
3867
5415
|
}
|
|
3868
5416
|
catch {
|
|
3869
|
-
return
|
|
5417
|
+
return null;
|
|
3870
5418
|
}
|
|
3871
5419
|
}, [activeProfile, profiles, agent?.profileId]);
|
|
5420
|
+
const isLiveEditorContextMode = resolvedEditorContextMode === "live";
|
|
5421
|
+
const omitsEditorContext = resolvedEditorContextMode === "none";
|
|
3872
5422
|
// Get parent agent ID from agent or agentStub (handle both camelCase and PascalCase)
|
|
3873
5423
|
const parentAgentId = agent?.parentAgentId ||
|
|
3874
5424
|
agent?.ParentAgentId ||
|
|
@@ -3882,10 +5432,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3882
5432
|
detail: { agentId: parentAgentId },
|
|
3883
5433
|
}));
|
|
3884
5434
|
}, [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 }));
|
|
5435
|
+
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;
|
|
5436
|
+
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
5437
|
const renderCostLimitBanner = () => {
|
|
3890
5438
|
if (!costLimitExceeded)
|
|
3891
5439
|
return null;
|
|
@@ -3896,9 +5444,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3896
5444
|
try {
|
|
3897
5445
|
// Extend cost limit - backend will automatically resume the agent
|
|
3898
5446
|
const result = await updateAgentCostLimit(agent.id, "extend");
|
|
3899
|
-
// Update the agent's cost limit
|
|
5447
|
+
// Update the agent's cost limit and clear the costLimitReached
|
|
5448
|
+
// status in local state so the useEffect watcher doesn't
|
|
5449
|
+
// immediately re-show the banner before the backend status
|
|
5450
|
+
// update arrives.
|
|
3900
5451
|
if (result.success && result.costLimit !== undefined) {
|
|
3901
|
-
setAgent((prev) => prev
|
|
5452
|
+
setAgent((prev) => prev
|
|
5453
|
+
? {
|
|
5454
|
+
...prev,
|
|
5455
|
+
costLimit: result.costLimit,
|
|
5456
|
+
status: prev.status === "costLimitReached"
|
|
5457
|
+
? "running"
|
|
5458
|
+
: prev.status,
|
|
5459
|
+
}
|
|
5460
|
+
: prev);
|
|
3902
5461
|
}
|
|
3903
5462
|
// Clear the banner and set waiting state
|
|
3904
5463
|
// Agent will resume automatically via backend's ResumeAgentAsync
|
|
@@ -3918,13 +5477,257 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3918
5477
|
};
|
|
3919
5478
|
const renderErrorBanner = () => {
|
|
3920
5479
|
const currentAgent = agent || agentStub;
|
|
3921
|
-
const isErrorStatus = currentAgent?.status === "error"
|
|
3922
|
-
const
|
|
3923
|
-
|
|
5480
|
+
const isErrorStatus = currentAgent?.status === "error";
|
|
5481
|
+
const isWaitingForInputStatus = currentAgent?.status === "waitingForInput";
|
|
5482
|
+
const isWaitingForApprovalStatus = currentAgent?.status === "waitingForApproval";
|
|
5483
|
+
// Show error banner for error status, or for any terminal status that still
|
|
5484
|
+
// carries a statusMessage (e.g. agent closed after an error).
|
|
5485
|
+
const isTerminalWithError = !isErrorStatus &&
|
|
5486
|
+
!!currentAgent?.statusMessage &&
|
|
5487
|
+
currentAgent?.status !== "running" &&
|
|
5488
|
+
currentAgent?.status !== "new" &&
|
|
5489
|
+
!isWaitingForInputStatus &&
|
|
5490
|
+
!isWaitingForApprovalStatus;
|
|
5491
|
+
const rawErrorMessage = (isErrorStatus || isTerminalWithError
|
|
5492
|
+
? currentAgent?.statusMessage
|
|
5493
|
+
: null) || error;
|
|
5494
|
+
if (!rawErrorMessage)
|
|
3924
5495
|
return null;
|
|
3925
|
-
|
|
5496
|
+
// Clean the error message (statusMessage from DB may contain raw JSON)
|
|
5497
|
+
const errorMessage = toUserFacingAgentErrorMessage(rawErrorMessage) || rawErrorMessage;
|
|
5498
|
+
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
5499
|
};
|
|
3927
|
-
|
|
5500
|
+
const renderBrowserClaimBanner = (variant = "inline") => {
|
|
5501
|
+
if (!agent?.id || !editContext?.sessionId)
|
|
5502
|
+
return null;
|
|
5503
|
+
if (!isClaimedByCurrentSession && !isClaimedByAnotherBrowser) {
|
|
5504
|
+
return null;
|
|
5505
|
+
}
|
|
5506
|
+
if (isPendingBrowserCaptureWait) {
|
|
5507
|
+
return null;
|
|
5508
|
+
}
|
|
5509
|
+
const label = isClaimedByCurrentSession
|
|
5510
|
+
? "Attached to this browser"
|
|
5511
|
+
: isClaimedByAnotherBrowser
|
|
5512
|
+
? "Attached in another browser"
|
|
5513
|
+
: "No browser attached";
|
|
5514
|
+
const description = isClaimedByCurrentSession
|
|
5515
|
+
? "This browser will handle page screenshot and DOM capture requests for the agent."
|
|
5516
|
+
: isClaimedByAnotherBrowser
|
|
5517
|
+
? "Capture requests will stay with the other browser until you take over control here."
|
|
5518
|
+
: "A page capture request is waiting for a browser attachment. Attach this browser to continue.";
|
|
5519
|
+
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");
|
|
5520
|
+
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: () => {
|
|
5521
|
+
void handleReleaseBrowser();
|
|
5522
|
+
}, 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: () => {
|
|
5523
|
+
void handleClaimBrowser(isClaimedByAnotherBrowser);
|
|
5524
|
+
}, children: isClaimedByAnotherBrowser
|
|
5525
|
+
? "Take over browser control"
|
|
5526
|
+
: "Attach to this browser" })) })] }) }));
|
|
5527
|
+
};
|
|
5528
|
+
const fixedBrowserClaimBanner = renderBrowserClaimBanner("fixed");
|
|
5529
|
+
const inlineBrowserClaimBanner = null;
|
|
5530
|
+
const browserCaptureInlinePrompt = isPendingBrowserCaptureWait && !isClaimedByCurrentSession
|
|
5531
|
+
? {
|
|
5532
|
+
toolNames: [
|
|
5533
|
+
"capture-page-screenshot",
|
|
5534
|
+
"capture-parhelia-ui-screenshot",
|
|
5535
|
+
"capture-page-dom",
|
|
5536
|
+
],
|
|
5537
|
+
label: isClaimedByAnotherBrowser
|
|
5538
|
+
? "Attached in another browser"
|
|
5539
|
+
: "No browser attached",
|
|
5540
|
+
description: isClaimedByAnotherBrowser
|
|
5541
|
+
? "This capture request is waiting in another browser. Take over browser control here to continue."
|
|
5542
|
+
: "This capture request is waiting for a browser attachment. Attach this browser to continue.",
|
|
5543
|
+
actionLabel: isClaimedByAnotherBrowser
|
|
5544
|
+
? "Take over browser control"
|
|
5545
|
+
: "Attach to this browser",
|
|
5546
|
+
isPending: isBrowserClaimMutationPending,
|
|
5547
|
+
onAction: () => {
|
|
5548
|
+
void handleClaimBrowser(isClaimedByAnotherBrowser);
|
|
5549
|
+
},
|
|
5550
|
+
}
|
|
5551
|
+
: null;
|
|
5552
|
+
useEffect(() => {
|
|
5553
|
+
if (agent?.status !== "waitingForInput") {
|
|
5554
|
+
setPendingBrowserCaptureDialogType(null);
|
|
5555
|
+
}
|
|
5556
|
+
}, [agent?.status]);
|
|
5557
|
+
const renderInlineDialogContent = () => {
|
|
5558
|
+
if (!activeInlineDialog)
|
|
5559
|
+
return null;
|
|
5560
|
+
if (activeInlineDialog.request.dialogType === "questionnaire") {
|
|
5561
|
+
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) => {
|
|
5562
|
+
activeInlineDialog.onComplete(result);
|
|
5563
|
+
setActiveInlineDialog(null);
|
|
5564
|
+
void onInteractionSubmitted?.();
|
|
5565
|
+
}, onCancel: () => {
|
|
5566
|
+
activeInlineDialog.onCancel();
|
|
5567
|
+
setActiveInlineDialog(null);
|
|
5568
|
+
} }) }));
|
|
5569
|
+
}
|
|
5570
|
+
const dialogRegistration = editContext?.configuration?.editor?.agentDialogs?.find((d) => d.dialogType === activeInlineDialog.request.dialogType);
|
|
5571
|
+
if (dialogRegistration) {
|
|
5572
|
+
const DialogComponent = dialogRegistration.component;
|
|
5573
|
+
return (_jsx("div", { className: "agent-inline-dialog", children: _jsx(DialogComponent, { title: activeInlineDialog.request.title, description: activeInlineDialog.request.description, parameters: activeInlineDialog.request.parameters, onClose: (result) => {
|
|
5574
|
+
activeInlineDialog.onComplete(result);
|
|
5575
|
+
setActiveInlineDialog(null);
|
|
5576
|
+
if (activeInlineDialog.request.dialogType === "questionnaire") {
|
|
5577
|
+
void onInteractionSubmitted?.();
|
|
5578
|
+
}
|
|
5579
|
+
}, onCancel: () => {
|
|
5580
|
+
activeInlineDialog.onCancel();
|
|
5581
|
+
setActiveInlineDialog(null);
|
|
5582
|
+
} }) }));
|
|
5583
|
+
}
|
|
5584
|
+
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] }) }));
|
|
5585
|
+
};
|
|
5586
|
+
const latestSummaryAssistantGroup = useMemo(() => {
|
|
5587
|
+
if (hideSummaryMessages)
|
|
5588
|
+
return null;
|
|
5589
|
+
const groups = groupConsecutiveMessages(messages);
|
|
5590
|
+
for (let groupIndex = groups.length - 1; groupIndex >= 0; groupIndex -= 1) {
|
|
5591
|
+
const group = groups[groupIndex];
|
|
5592
|
+
if (!group || group.type !== "assistant-group")
|
|
5593
|
+
continue;
|
|
5594
|
+
const filteredMessages = group.messages.filter((msg) => {
|
|
5595
|
+
const content = msg.content || "";
|
|
5596
|
+
return !content.startsWith("⚠️") || !content.includes("Cost limit");
|
|
5597
|
+
});
|
|
5598
|
+
if (filteredMessages.length === 0)
|
|
5599
|
+
continue;
|
|
5600
|
+
return {
|
|
5601
|
+
messages: filteredMessages,
|
|
5602
|
+
isLastGroup: groupIndex === groups.length - 1,
|
|
5603
|
+
};
|
|
5604
|
+
}
|
|
5605
|
+
return null;
|
|
5606
|
+
}, [messages, hideSummaryMessages]);
|
|
5607
|
+
const summaryModeContent = displayMode === "summary"
|
|
5608
|
+
? (() => {
|
|
5609
|
+
const inlineDialog = renderInlineDialogContent();
|
|
5610
|
+
const summaryMessages = latestSummaryAssistantGroup
|
|
5611
|
+
? convertAgentMessagesToAiFormat(latestSummaryAssistantGroup.messages)
|
|
5612
|
+
: [];
|
|
5613
|
+
const summaryOperations = latestSummaryAssistantGroup
|
|
5614
|
+
? getOperationsForMessageGroup(summaryMessages, agentOperations)
|
|
5615
|
+
: [];
|
|
5616
|
+
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 &&
|
|
5617
|
+
!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: {
|
|
5618
|
+
__html: sanitizeSvg(activeProfile.svgIcon),
|
|
5619
|
+
} })) : (_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 ||
|
|
5620
|
+
activeProfile?.displayTitle ||
|
|
5621
|
+
activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, browserCaptureInlinePrompt: browserCaptureInlinePrompt, onQuickAction: (action) => {
|
|
5622
|
+
const text = (action.prompt ||
|
|
5623
|
+
action.value ||
|
|
5624
|
+
action.label ||
|
|
5625
|
+
"").trim();
|
|
5626
|
+
if (!text)
|
|
5627
|
+
return;
|
|
5628
|
+
if (isExecuting) {
|
|
5629
|
+
try {
|
|
5630
|
+
handleStop();
|
|
5631
|
+
}
|
|
5632
|
+
catch { }
|
|
5633
|
+
}
|
|
5634
|
+
sendQuickMessage(text);
|
|
5635
|
+
} }) })) : 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
|
|
5636
|
+
? "The agent is still working. The next update will appear here automatically."
|
|
5637
|
+
: agent?.statusMessage ||
|
|
5638
|
+
summaryPlaceholderMessage ||
|
|
5639
|
+
"Waiting for the next agent update." }), summaryPlaceholderActions ? (_jsx("div", { className: `flex justify-center ${compact ? "mt-2" : "mt-3"}`, children: summaryPlaceholderActions })) : null] }) })), displayMode !== "summary" &&
|
|
5640
|
+
shouldShowThinkingDots &&
|
|
5641
|
+
!inlineDialog &&
|
|
5642
|
+
!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: {
|
|
5643
|
+
__html: sanitizeSvg(activeProfile.svgIcon),
|
|
5644
|
+
} })) : (_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 ||
|
|
5645
|
+
activeProfile?.displayTitle ||
|
|
5646
|
+
activeProfile?.name ||
|
|
5647
|
+
"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) => {
|
|
5648
|
+
setActivePlaceholderInput(null);
|
|
5649
|
+
setAllPlaceholdersFilled(false);
|
|
5650
|
+
if (activePlaceholderInput.behavior === "compose" &&
|
|
5651
|
+
!hideBottomControls) {
|
|
5652
|
+
setPrompt(filledText);
|
|
5653
|
+
setInputPlaceholder("Review and edit, then press Enter to send");
|
|
5654
|
+
if (textareaRef.current) {
|
|
5655
|
+
try {
|
|
5656
|
+
textareaRef.current.focus();
|
|
5657
|
+
const v = textareaRef.current.value || "";
|
|
5658
|
+
textareaRef.current.selectionStart = v.length;
|
|
5659
|
+
textareaRef.current.selectionEnd = v.length;
|
|
5660
|
+
}
|
|
5661
|
+
catch { }
|
|
5662
|
+
}
|
|
5663
|
+
}
|
|
5664
|
+
else {
|
|
5665
|
+
if (isExecuting) {
|
|
5666
|
+
try {
|
|
5667
|
+
handleStop();
|
|
5668
|
+
}
|
|
5669
|
+
catch { }
|
|
5670
|
+
}
|
|
5671
|
+
sendQuickMessage(filledText);
|
|
5672
|
+
}
|
|
5673
|
+
}, onCancel: () => {
|
|
5674
|
+
setActivePlaceholderInput(null);
|
|
5675
|
+
setAllPlaceholdersFilled(false);
|
|
5676
|
+
} })) : prompt && /\{\{[^{}]+\}\}|<<[^<>]+>>/.test(prompt) ? (_jsx(PlaceholderInput, { ref: promptPlaceholderInputRef, text: prompt, showButtons: hideBottomControls, buttonsClassName: hideBottomControls ? "justify-end" : "", onFilledChange: setAllPlaceholdersFilled, onComplete: (filledText) => {
|
|
5677
|
+
setPrompt(filledText);
|
|
5678
|
+
setAllPlaceholdersFilled(false);
|
|
5679
|
+
if (filledText.trim()) {
|
|
5680
|
+
if (isExecuting) {
|
|
5681
|
+
try {
|
|
5682
|
+
handleStop();
|
|
5683
|
+
}
|
|
5684
|
+
catch { }
|
|
5685
|
+
}
|
|
5686
|
+
sendQuickMessage(filledText);
|
|
5687
|
+
}
|
|
5688
|
+
}, onCancel: () => {
|
|
5689
|
+
setPrompt("");
|
|
5690
|
+
setAllPlaceholdersFilled(false);
|
|
5691
|
+
setInputPlaceholder("Type your message... (Enter to send, Shift+Enter or Ctrl+Enter for new line)");
|
|
5692
|
+
} })) : (_jsx("div", { className: "flex items-stretch gap-2", children: _jsx(Textarea, { ref: textareaRef, style: { viewTransitionName: "assistant-chat-input" }, value: prompt, onChange: (e) => {
|
|
5693
|
+
setPrompt(e.target.value);
|
|
5694
|
+
if (!/\{\{[^{}]+\}\}|<<[^<>]+>>/.test(e.target.value)) {
|
|
5695
|
+
setAllPlaceholdersFilled(false);
|
|
5696
|
+
}
|
|
5697
|
+
if (currentHistoryIndex !== -1) {
|
|
5698
|
+
setCurrentHistoryIndex(-1);
|
|
5699
|
+
}
|
|
5700
|
+
}, onKeyDown: handleKeyPress, onPaste: handlePaste, onFocus: () => {
|
|
5701
|
+
shouldMaintainFocusRef.current = true;
|
|
5702
|
+
}, onBlur: () => {
|
|
5703
|
+
shouldMaintainFocusRef.current = false;
|
|
5704
|
+
}, 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 }) })), (() => {
|
|
5705
|
+
const isInPlaceholderMode = activePlaceholderInput ||
|
|
5706
|
+
(prompt && /\{\{[^{}]+\}\}|<<[^<>]+>>/.test(prompt));
|
|
5707
|
+
const placeholderShowsOwnButtons = hideBottomControls && isInPlaceholderMode;
|
|
5708
|
+
if (placeholderShowsOwnButtons)
|
|
5709
|
+
return null;
|
|
5710
|
+
return (_jsxs("div", { className: cn("mt-2 flex items-stretch gap-2", hideBottomControls ||
|
|
5711
|
+
simpleMode ||
|
|
5712
|
+
isInPlaceholderMode
|
|
5713
|
+
? "justify-end"
|
|
5714
|
+
: "justify-between"), children: [!hideBottomControls &&
|
|
5715
|
+
!simpleMode &&
|
|
5716
|
+
!isInPlaceholderMode ? (_jsx("div", { className: "flex-1" })) : null, _jsx(Button, { type: "button", size: "sm", onClick: () => {
|
|
5717
|
+
if (isExecuting) {
|
|
5718
|
+
handleStop();
|
|
5719
|
+
}
|
|
5720
|
+
else {
|
|
5721
|
+
handleSubmit();
|
|
5722
|
+
}
|
|
5723
|
+
}, disabled: !isExecuting &&
|
|
5724
|
+
!activePlaceholderInput &&
|
|
5725
|
+
(!prompt.trim() || isSubmitting), "data-testid": "agent-send-stop-button", children: isExecuting ? "Stop" : "Send" })] }));
|
|
5726
|
+
})()] })) : null] }));
|
|
5727
|
+
})()
|
|
5728
|
+
: null;
|
|
5729
|
+
const fullModeInlineDialog = displayMode === "full" ? renderInlineDialogContent() : null;
|
|
5730
|
+
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
5731
|
setPrompt(p);
|
|
3929
5732
|
// Use setTimeout to ensure state is updated before submission
|
|
3930
5733
|
setTimeout(() => {
|
|
@@ -3937,12 +5740,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3937
5740
|
handleSubmit();
|
|
3938
5741
|
}
|
|
3939
5742
|
}, 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: [(() => {
|
|
5743
|
+
} })) })), 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: {
|
|
5744
|
+
__html: sanitizeSvg(activeProfile.svgIcon),
|
|
5745
|
+
} })) : (_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
5746
|
const groups = groupConsecutiveMessages(messages);
|
|
3947
5747
|
return groups.map((group, groupIndex) => {
|
|
3948
5748
|
const isLastGroup = groupIndex === groups.length - 1;
|
|
@@ -3950,6 +5750,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3950
5750
|
// Render user message
|
|
3951
5751
|
return (_jsx(UserMessage, { message: group.messages[0] }, groupIndex));
|
|
3952
5752
|
}
|
|
5753
|
+
else if (group.type === "heartbeat" && group.messages[0]) {
|
|
5754
|
+
return (_jsx(HeartbeatMessage, { message: group.messages[0] }, group.messages[0].id || groupIndex));
|
|
5755
|
+
}
|
|
3953
5756
|
else {
|
|
3954
5757
|
// Render bundled assistant messages
|
|
3955
5758
|
// Check if this group contains any streaming message
|
|
@@ -3966,9 +5769,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
3966
5769
|
}
|
|
3967
5770
|
const convertedMessages = convertAgentMessagesToAiFormat(filteredMessages);
|
|
3968
5771
|
const operationsForGroup = getOperationsForMessageGroup(convertedMessages, agentOperations);
|
|
3969
|
-
return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isLastGroup || !isExecuting, editOperations: operationsForGroup,
|
|
5772
|
+
return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isLastGroup || !isExecuting, editOperations: operationsForGroup, defaultCollapseJson: defaultCollapseJson, profileSvgIcon: activeProfile?.svgIcon, agentId: agent?.id || agentStub.id, agentName: activeProfile?.agentName ||
|
|
3970
5773
|
activeProfile?.displayTitle ||
|
|
3971
|
-
activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, onQuickAction: (action) => {
|
|
5774
|
+
activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, browserCaptureInlinePrompt: browserCaptureInlinePrompt, onQuickAction: (action) => {
|
|
3972
5775
|
const text = (action.prompt ||
|
|
3973
5776
|
action.value ||
|
|
3974
5777
|
action.label ||
|
|
@@ -4012,13 +5815,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4012
5815
|
}
|
|
4013
5816
|
});
|
|
4014
5817
|
})(), 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,
|
|
5818
|
+
__html: sanitizeSvg(activeProfile.svgIcon),
|
|
4016
5819
|
} })) : (_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
5820
|
activeProfile?.displayTitle ||
|
|
4018
5821
|
activeProfile?.name ||
|
|
4019
5822
|
"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
5823
|
!simpleMode &&
|
|
4021
|
-
(isMobile ? (_jsx("div", { className: "border-t border-gray-200 bg-gray-50", "data-testid": "agent-context-panel-tabs", children: _jsx(SimpleTabs, { tabs: [
|
|
5824
|
+
(editContext?.isMobile ? (_jsx("div", { className: "border-t border-gray-200 bg-gray-50", "data-testid": "agent-context-panel-tabs", children: _jsx(SimpleTabs, { tabs: [
|
|
4022
5825
|
{
|
|
4023
5826
|
id: "context",
|
|
4024
5827
|
label: "Context",
|
|
@@ -4066,28 +5869,49 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4066
5869
|
hasTodoContent,
|
|
4067
5870
|
hasSpawnedAgents,
|
|
4068
5871
|
agent?.id && hasHistoryContent,
|
|
4069
|
-
].filter(Boolean).length - 1)), setActiveTab: setContextPanelsActiveTab, className: "justify-start px-4" }) })) : (_jsxs(_Fragment, { children: [renderContextInfoBar(), agent?.id && activeProfile && (_jsx(AgentDocumentList, { ref: documentListRef, agentId: agent.id, maxFileSizeMB: activeProfile.maxDocumentSizeMB ?? 10, enabled: activeProfile.enableDocumentUpload ?? false, profileId: activeProfile.id }, `${agent.id}-${agent.updatedDate || ""}-${activeProfile.id}`)), _jsx(TodoListPanel, { messages: messages, agentMetadata: agentMetadata }), _jsx(SpawnedAgentsPanel, { agentMetadata: agentMetadata }), agent?.id && _jsx(AgentEditOperationsPanel, { agentId: agent.id })] }))), queuedPrompts.length > 0 && !simpleMode && (_jsx("div", { className: "border-t border-gray-200 bg-amber-50/50", "data-testid": "queued-prompts-section", children: _jsxs("div", { className: "px-4 pt-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
|
|
5872
|
+
].filter(Boolean).length - 1)), setActiveTab: setContextPanelsActiveTab, className: "justify-start px-4" }) })) : (_jsxs(_Fragment, { children: [renderContextInfoBar(), agent?.id && activeProfile && (_jsx(AgentDocumentList, { ref: documentListRef, agentId: agent.id, maxFileSizeMB: activeProfile.maxDocumentSizeMB ?? 10, enabled: activeProfile.enableDocumentUpload ?? false, profileId: activeProfile.id }, `${agent.id}-${agent.updatedDate || ""}-${activeProfile.id}`)), _jsx(TodoListPanel, { messages: messages, agentMetadata: agentMetadata }), _jsx(SpawnedAgentsPanel, { agentMetadata: agentMetadata }), agent?.id && _jsx(AgentEditOperationsPanel, { agentId: agent.id })] }))), queuedPrompts.length > 0 && !simpleMode && (_jsx("div", { className: "border-t border-gray-200 bg-amber-50/50", "data-testid": "queued-prompts-section", children: _jsxs("div", { className: "px-4 pt-2.5 pb-2", children: [_jsxs("div", { className: "mb-1.5 flex items-center gap-1.5", children: [_jsx("div", { className: "h-1.5 w-1.5 animate-pulse rounded-full bg-amber-400" }), _jsxs("span", { className: "text-[10px] font-medium tracking-wide text-amber-600 uppercase", "data-testid": "queued-prompts-count", children: ["Queued (", queuedPrompts.length, ")"] })] }), _jsx("div", { className: "max-h-64 space-y-0.5 overflow-y-auto", children: queuedPrompts.map((qp) => {
|
|
5873
|
+
let triggerName = "";
|
|
5874
|
+
if (qp.data) {
|
|
5875
|
+
try {
|
|
5876
|
+
const parsed = JSON.parse(qp.data);
|
|
5877
|
+
triggerName = parsed?.triggerName?.trim() || "";
|
|
5878
|
+
}
|
|
5879
|
+
catch {
|
|
5880
|
+
// Ignore invalid JSON metadata and render as regular queued prompt.
|
|
5881
|
+
}
|
|
5882
|
+
}
|
|
5883
|
+
const isTriggerQueuedPrompt = !qp.sourceAgentName && triggerName.length > 0;
|
|
5884
|
+
const isTriggerExpanded = !!expandedQueuedTriggerIds[qp.id];
|
|
5885
|
+
if (isTriggerQueuedPrompt) {
|
|
5886
|
+
return (_jsxs("div", { className: "text-[11px]", "data-testid": "queued-prompt-item", children: [_jsxs("button", { type: "button", onClick: () => setExpandedQueuedTriggerIds((prev) => ({
|
|
5887
|
+
...prev,
|
|
5888
|
+
[qp.id]: !prev[qp.id],
|
|
5889
|
+
})), 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));
|
|
5890
|
+
}
|
|
5891
|
+
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 &&
|
|
5892
|
+
new Date(qp.scheduledFor).getTime() >
|
|
5893
|
+
new Date(qp.createdDate || 0).getTime() +
|
|
5894
|
+
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() ===
|
|
5895
|
+
new Date().toDateString()
|
|
5896
|
+
? formatTime(new Date(qp.scheduledFor))
|
|
5897
|
+
: formatDateTime(new Date(qp.scheduledFor))] })] }))] })] }) }) }, qp.id));
|
|
5898
|
+
}) })] }) }))] }));
|
|
5899
|
+
const showQuestionnaireSplitter = isQuestionnaireDialogOpen && !!fullModeInlineDialog;
|
|
5900
|
+
const fullModeContent = showQuestionnaireSplitter ? (_jsx(Splitter, { panels: [
|
|
5901
|
+
{
|
|
5902
|
+
name: "conversation",
|
|
5903
|
+
defaultSize: 65,
|
|
5904
|
+
content: fullModeUpperContent,
|
|
5905
|
+
},
|
|
5906
|
+
{
|
|
5907
|
+
name: "questionnaire",
|
|
5908
|
+
defaultSize: 35,
|
|
5909
|
+
content: fullModeInlineDialog,
|
|
5910
|
+
},
|
|
5911
|
+
], direction: "vertical", localStorageKey: compact
|
|
5912
|
+
? "agent-terminal-compact-questionnaire-splitter"
|
|
5913
|
+
: "agent-terminal-questionnaire-splitter", className: "min-h-0 flex-1", splitterClassName: "bg-gray-200 hover:bg-gray-300" })) : (_jsxs(_Fragment, { children: [fullModeUpperContent, fullModeInlineDialog] }));
|
|
5914
|
+
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
5915
|
// Placeholder Input (from quick actions)
|
|
4092
5916
|
// Show internal buttons only in splash mode (hideBottomControls) since external buttons won't be visible there
|
|
4093
5917
|
_jsx(PlaceholderInput, { ref: placeholderInputRef, text: activePlaceholderInput.text, showButtons: hideBottomControls, buttonsClassName: hideBottomControls ? "justify-end" : "", onFilledChange: setAllPlaceholdersFilled, onComplete: (filledText) => {
|
|
@@ -4164,122 +5988,221 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4164
5988
|
return null;
|
|
4165
5989
|
return (_jsxs("div", { className: cn("mt-2 flex items-stretch gap-2", hideBottomControls || simpleMode || isInPlaceholderMode
|
|
4166
5990
|
? "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"
|
|
5991
|
+
: "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
5992
|
? "border-green-300 bg-green-50! text-green-700 hover:bg-green-100!"
|
|
4169
5993
|
: mode === "supervised"
|
|
4170
5994
|
? "border-amber-300 bg-amber-50! text-amber-700 hover:bg-amber-100!"
|
|
4171
5995
|
: "border-red-300 bg-red-50! text-red-700 hover:bg-red-100!"), value: mode, options: modeOptions, onValueChange: async (val) => {
|
|
4172
5996
|
const nextMode = val || "supervised";
|
|
4173
|
-
// Optimistic UI update
|
|
4174
|
-
setMode(nextMode);
|
|
4175
5997
|
const current = agentMetadata || {};
|
|
4176
5998
|
const nextMeta = {
|
|
4177
5999
|
...current,
|
|
4178
6000
|
mode: nextMode,
|
|
4179
6001
|
};
|
|
4180
6002
|
try {
|
|
4181
|
-
if (!agent?.id ||
|
|
6003
|
+
if (!agent?.id || isLocalOnlyDraftAgent) {
|
|
6004
|
+
setMode(nextMode);
|
|
4182
6005
|
setAgentMetadata(nextMeta);
|
|
4183
|
-
// Cache until first start when agent is persisted
|
|
4184
6006
|
pendingSettingsRef.current = {
|
|
4185
6007
|
...(pendingSettingsRef.current || {}),
|
|
4186
6008
|
mode: nextMode,
|
|
4187
6009
|
};
|
|
4188
6010
|
return;
|
|
4189
6011
|
}
|
|
4190
|
-
await updateAgentSettings(agent.id, {
|
|
6012
|
+
const result = await updateAgentSettings(agent.id, {
|
|
4191
6013
|
mode: nextMode,
|
|
4192
6014
|
});
|
|
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
|
-
});
|
|
6015
|
+
if (result.success === false ||
|
|
6016
|
+
result.updates?.mode === false) {
|
|
6017
|
+
throw new Error("Mode change was not applied");
|
|
4229
6018
|
}
|
|
4230
|
-
|
|
6019
|
+
setMode(nextMode);
|
|
6020
|
+
setAgentMetadata(nextMeta);
|
|
4231
6021
|
setAgent((prev) => prev
|
|
4232
6022
|
? {
|
|
4233
6023
|
...prev,
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
profile: nextProfile.name,
|
|
4237
|
-
additionalData: {
|
|
4238
|
-
...(agentMetadata
|
|
4239
|
-
?.additionalData || {}),
|
|
4240
|
-
profileId: nextProfile.id,
|
|
4241
|
-
profileName: nextProfile.name,
|
|
4242
|
-
},
|
|
4243
|
-
}),
|
|
6024
|
+
mode: nextMode,
|
|
6025
|
+
metadata: JSON.stringify(nextMeta),
|
|
4244
6026
|
}
|
|
4245
6027
|
: prev);
|
|
4246
6028
|
}
|
|
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
|
-
}
|
|
6029
|
+
catch (e2) {
|
|
6030
|
+
console.error("Failed to persist mode change", e2);
|
|
4270
6031
|
}
|
|
4271
|
-
|
|
4272
|
-
|
|
6032
|
+
} }), _jsxs(Popover, { open: showAgentSettings, onOpenChange: (open) => {
|
|
6033
|
+
setShowAgentSettings(open);
|
|
6034
|
+
if (!open) {
|
|
6035
|
+
setShowSkillPicker(false);
|
|
4273
6036
|
}
|
|
4274
|
-
}
|
|
6037
|
+
}, 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) => {
|
|
6038
|
+
const target = e.target;
|
|
6039
|
+
if (target?.closest('[data-help-panel="true"]')) {
|
|
6040
|
+
e.preventDefault();
|
|
6041
|
+
}
|
|
6042
|
+
}, onPointerDownOutside: (e) => {
|
|
6043
|
+
const target = e.target;
|
|
6044
|
+
if (target?.closest('[data-help-panel="true"]')) {
|
|
6045
|
+
e.preventDefault();
|
|
6046
|
+
}
|
|
6047
|
+
}, onFocusOutside: (e) => {
|
|
6048
|
+
const target = e.target;
|
|
6049
|
+
if (target?.closest('[data-help-panel="true"]')) {
|
|
6050
|
+
e.preventDefault();
|
|
6051
|
+
}
|
|
6052
|
+
}, 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) => {
|
|
6053
|
+
const nextProfile = profiles.find((x) => x.id === val);
|
|
6054
|
+
if (!nextProfile)
|
|
6055
|
+
return;
|
|
6056
|
+
setActiveProfile(nextProfile);
|
|
6057
|
+
try {
|
|
6058
|
+
if (agent?.id && !isLocalOnlyDraftAgent) {
|
|
6059
|
+
await updateAgentSettings(agent.id, {
|
|
6060
|
+
profileId: nextProfile.id,
|
|
6061
|
+
profileName: nextProfile.name,
|
|
6062
|
+
});
|
|
6063
|
+
}
|
|
6064
|
+
else {
|
|
6065
|
+
pendingSettingsRef.current = {
|
|
6066
|
+
...(pendingSettingsRef.current || {}),
|
|
6067
|
+
profileId: nextProfile.id,
|
|
6068
|
+
profileName: nextProfile.name,
|
|
6069
|
+
};
|
|
6070
|
+
setAgentMetadata((current) => {
|
|
6071
|
+
const next = {
|
|
6072
|
+
...(current || {}),
|
|
6073
|
+
};
|
|
6074
|
+
next.profile = nextProfile.name;
|
|
6075
|
+
next.additionalData = {
|
|
6076
|
+
...(next.additionalData || {}),
|
|
6077
|
+
profileId: nextProfile.id,
|
|
6078
|
+
profileName: nextProfile.name,
|
|
6079
|
+
};
|
|
6080
|
+
return next;
|
|
6081
|
+
});
|
|
6082
|
+
}
|
|
6083
|
+
setAgent((prev) => prev
|
|
6084
|
+
? {
|
|
6085
|
+
...prev,
|
|
6086
|
+
profileId: nextProfile.id,
|
|
6087
|
+
profileName: nextProfile.name,
|
|
6088
|
+
metadata: JSON.stringify({
|
|
6089
|
+
...(agentMetadata || {}),
|
|
6090
|
+
profile: nextProfile.name,
|
|
6091
|
+
additionalData: {
|
|
6092
|
+
...(agentMetadata
|
|
6093
|
+
?.additionalData || {}),
|
|
6094
|
+
profileId: nextProfile.id,
|
|
6095
|
+
profileName: nextProfile.name,
|
|
6096
|
+
},
|
|
6097
|
+
}),
|
|
6098
|
+
}
|
|
6099
|
+
: prev);
|
|
6100
|
+
}
|
|
6101
|
+
catch (err) {
|
|
6102
|
+
console.error("Failed to persist agent profile", err);
|
|
6103
|
+
}
|
|
6104
|
+
} }), 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: () => {
|
|
6105
|
+
void handleEditProfileSideBySide();
|
|
6106
|
+
setShowAgentSettings(false);
|
|
6107
|
+
}, "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: () => {
|
|
6108
|
+
void handleOpenProfileSettings();
|
|
6109
|
+
setShowAgentSettings(false);
|
|
6110
|
+
}, 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) => {
|
|
6111
|
+
const nextId = val;
|
|
6112
|
+
setSelectedModelId(nextId);
|
|
6113
|
+
const modelName = activeProfile?.models?.find((m) => m.id === nextId)?.name || "";
|
|
6114
|
+
setAgent((prev) => prev ? { ...prev, model: modelName } : prev);
|
|
6115
|
+
try {
|
|
6116
|
+
if (agent?.id && !isLocalOnlyDraftAgent) {
|
|
6117
|
+
await updateAgentSettings(agent.id, {
|
|
6118
|
+
model: modelName,
|
|
6119
|
+
});
|
|
6120
|
+
}
|
|
6121
|
+
else {
|
|
6122
|
+
pendingSettingsRef.current = {
|
|
6123
|
+
...(pendingSettingsRef.current || {}),
|
|
6124
|
+
modelName,
|
|
6125
|
+
};
|
|
6126
|
+
}
|
|
6127
|
+
}
|
|
6128
|
+
catch (err) {
|
|
6129
|
+
console.error("Failed to persist agent model", err);
|
|
6130
|
+
}
|
|
6131
|
+
} })] })) : 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) => {
|
|
6132
|
+
setShowSkillPicker(open);
|
|
6133
|
+
if (open) {
|
|
6134
|
+
setSkillActionError(null);
|
|
6135
|
+
}
|
|
6136
|
+
}, 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) => {
|
|
6137
|
+
const selected = selection[0];
|
|
6138
|
+
if (!selected?.id)
|
|
6139
|
+
return;
|
|
6140
|
+
setSkillActionError(null);
|
|
6141
|
+
if (selectableTemplateIdSet.size > 0 &&
|
|
6142
|
+
(!selected.templateId ||
|
|
6143
|
+
!selectableTemplateIdSet.has(selected.templateId.toLowerCase()))) {
|
|
6144
|
+
return;
|
|
6145
|
+
}
|
|
6146
|
+
if (!manuallyAssignableSkillIdSet.has(selected.id.toLowerCase())) {
|
|
6147
|
+
setSkillActionError("This skill cannot be added for the current agent profile.");
|
|
6148
|
+
return;
|
|
6149
|
+
}
|
|
6150
|
+
void (async () => {
|
|
6151
|
+
const added = await handleAddSkill(selected.id);
|
|
6152
|
+
if (added) {
|
|
6153
|
+
setShowSkillPicker(false);
|
|
6154
|
+
}
|
|
6155
|
+
})();
|
|
6156
|
+
} }), skillsLoading && (_jsx("div", { className: "bg-background/70 absolute inset-0 flex items-center justify-center text-[10px] text-gray-500", children: "Loading skills..." }))] }), !skillsLoading &&
|
|
6157
|
+
!skillsError &&
|
|
6158
|
+
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 &&
|
|
6159
|
+
!skillsError &&
|
|
6160
|
+
skillRootIds.length === 0 && (_jsx("div", { className: "text-[10px] text-gray-500", children: "No skill roots available." })), !skillsLoading &&
|
|
6161
|
+
!skillsError &&
|
|
6162
|
+
profileFilteredSkills.length === 0 && (_jsx("div", { className: "text-[10px] text-gray-500", children: selectedSkillIds.length > 0
|
|
6163
|
+
? "All addable skills are selected"
|
|
6164
|
+
: "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) => {
|
|
6165
|
+
const skill = selectedSkills.find((s) => s.id === skillId);
|
|
6166
|
+
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: () => {
|
|
6167
|
+
void handleOpenSkillItem(skillId);
|
|
6168
|
+
}, 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: () => {
|
|
6169
|
+
void handleRemoveSkill(skillId);
|
|
6170
|
+
}, title: "Remove skill", "aria-label": `Remove ${skill?.name || skillId}`, children: _jsx(X, { className: "h-2.5 w-2.5", strokeWidth: 1 }) }))] }, skillId));
|
|
6171
|
+
}) }))] }), _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
|
|
6172
|
+
? "No available tools for this profile and mode"
|
|
6173
|
+
: "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) => {
|
|
6174
|
+
const sourceLabel = formatAllowanceSource(allowance.source);
|
|
6175
|
+
const pathLabel = "itemPath" in allowance
|
|
6176
|
+
? allowance.itemPath
|
|
6177
|
+
: allowance.normalizedPath;
|
|
6178
|
+
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 ||
|
|
6179
|
+
"*" }), _jsx("div", { className: "truncate text-[9px] text-gray-500", title: formatAllowanceLabel(allowance), children: pathLabel })] }), (sourceLabel ||
|
|
6180
|
+
allowance.grantedBy) && (_jsx("div", { className: "truncate pl-6 text-[9px] text-gray-400", children: [
|
|
6181
|
+
sourceLabel,
|
|
6182
|
+
allowance.grantedBy,
|
|
6183
|
+
]
|
|
6184
|
+
.filter(Boolean)
|
|
6185
|
+
.join(" · ") }))] }, `${group.key}-${allowance.operationType}-${pathLabel}-${index}`));
|
|
6186
|
+
})] }, group.key)) : null) })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isLocalOnlyDraftAgent
|
|
6187
|
+
? "Allowances are shown after the agent is created"
|
|
6188
|
+
: "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) => {
|
|
6189
|
+
const filterText = (sub.filter || "").trim();
|
|
6190
|
+
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));
|
|
6191
|
+
}) })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isLocalOnlyDraftAgent
|
|
6192
|
+
? "Subscribed triggers are shown after the agent is created"
|
|
6193
|
+
: "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
6194
|
setPrompt(p.prompt);
|
|
4276
6195
|
setShowPredefined(false);
|
|
4277
6196
|
if (textareaRef.current)
|
|
4278
6197
|
textareaRef.current.focus();
|
|
4279
|
-
}, children: p.title }, index))) }) })] })) : null, !hideBottomControls &&
|
|
4280
|
-
|
|
6198
|
+
}, children: p.title }, index))) }) })] })) : null, !hideBottomControls &&
|
|
6199
|
+
!simpleMode &&
|
|
6200
|
+
editContext?.isMobile && (_jsxs(Popover, { open: showCostAndAgent, onOpenChange: setShowCostAndAgent, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { onClick: () => {
|
|
6201
|
+
if (editContext?.isMobile)
|
|
4281
6202
|
setShowCostAndAgent((prev) => !prev);
|
|
4282
|
-
}, variant: "outline", size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", "aria-expanded": isMobile
|
|
6203
|
+
}, variant: "outline", size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", "aria-expanded": editContext?.isMobile
|
|
6204
|
+
? showCostAndAgent
|
|
6205
|
+
: 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
6206
|
? {
|
|
4284
6207
|
input: liveTotals.input,
|
|
4285
6208
|
output: liveTotals.output,
|
|
@@ -4289,9 +6212,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4289
6212
|
outputCost: liveTotals.outputCost,
|
|
4290
6213
|
cachedCost: liveTotals.cachedCost,
|
|
4291
6214
|
cacheWriteCost: liveTotals.cacheWriteCost ?? 0,
|
|
6215
|
+
imageCost: liveTotals.imageCost ?? 0,
|
|
4292
6216
|
totalCost: liveTotals.totalCost,
|
|
4293
6217
|
}
|
|
4294
|
-
: totalTokens, effectiveCostLimit: effectiveCostLimit, messages: messages,
|
|
6218
|
+
: totalTokens, effectiveCostLimit: effectiveCostLimit, messages: messages, showCompressionPopover: showCompressionPopover, setShowCompressionPopover: setShowCompressionPopover }) }) })] }))] })), _jsxs("div", { className: "flex items-center gap-1 self-end", children: [_jsx("span", { title: isVoiceDisabled
|
|
4295
6219
|
? "Your browser does not support Speech Recognition"
|
|
4296
6220
|
: isListening
|
|
4297
6221
|
? "Stop voice input"
|
|
@@ -4312,7 +6236,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4312
6236
|
: allPendingApprovals.length > 0
|
|
4313
6237
|
? "Approve or reject pending tool calls first"
|
|
4314
6238
|
: "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 &&
|
|
6239
|
+
})(), !hideBottomControls &&
|
|
6240
|
+
!simpleMode &&
|
|
6241
|
+
editContext &&
|
|
6242
|
+
!editContext.isMobile && (_jsx(AgentTerminalStatusBar, { agent: agent, contextWindowStatus: contextWindowStatus, effectiveModelName: effectiveModelName, socketDiagnostics: editContext.socketDiagnostics, runDiagnosticsSnapshot: runDiagnosticsSnapshot, liveTotals: liveTotals, totalTokens: liveTotals
|
|
4316
6243
|
? {
|
|
4317
6244
|
input: liveTotals.input,
|
|
4318
6245
|
output: liveTotals.output,
|
|
@@ -4322,8 +6249,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
|
|
|
4322
6249
|
outputCost: liveTotals.outputCost,
|
|
4323
6250
|
cachedCost: liveTotals.cachedCost,
|
|
4324
6251
|
cacheWriteCost: liveTotals.cacheWriteCost ?? 0,
|
|
6252
|
+
imageCost: liveTotals.imageCost ?? 0,
|
|
4325
6253
|
totalCost: liveTotals.totalCost,
|
|
4326
6254
|
}
|
|
4327
|
-
: totalTokens, effectiveCostLimit: effectiveCostLimit, messages: messages,
|
|
6255
|
+
: totalTokens, effectiveCostLimit: effectiveCostLimit, messages: messages, showCompressionPopover: showCompressionPopover, setShowCompressionPopover: setShowCompressionPopover }))] })] }));
|
|
4328
6256
|
}
|
|
4329
6257
|
//# sourceMappingURL=AgentTerminal.js.map
|