@datalayer/agent-runtimes 1.0.29 → 1.3.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/AgentDocument.js +5 -22
- package/lib/AgentNode.d.ts +1 -1
- package/lib/AgentNode.js +193 -23
- package/lib/AgentNotebook.js +1 -1
- package/lib/App.d.ts +1 -1
- package/lib/AppExamples2.d.ts +1 -1
- package/lib/agent-node/AgentNodeGallery.d.ts +14 -1
- package/lib/agent-node/AgentNodeGallery.js +19 -15
- package/lib/agent-node/themeStore.js +7 -6
- package/lib/agents/AgentDetails.d.ts +8 -2
- package/lib/agents/AgentDetails.js +41 -11
- package/lib/agents/AgentRuntimeChat.d.ts +3 -3
- package/lib/agents/AgentRuntimeChat.js +15 -15
- package/lib/api/agents.d.ts +10 -10
- package/lib/api/agents.js +15 -15
- package/lib/api/checkpoints.d.ts +40 -0
- package/lib/api/checkpoints.js +75 -0
- package/lib/api/index.d.ts +1 -0
- package/lib/api/index.js +2 -1
- package/lib/api/notifications.d.ts +21 -6
- package/lib/api/notifications.js +32 -14
- package/lib/api/runtimes/environments.d.ts +385 -4
- package/lib/api/runtimes/environments.js +610 -9
- package/lib/api/runtimes/runtimes.d.ts +19 -19
- package/lib/api/runtimes/runtimes.js +47 -46
- package/lib/api/spacer/notebooks.js +1 -1
- package/lib/chat/Chat.d.ts +16 -4
- package/lib/chat/Chat.js +18 -5
- package/lib/chat/ChatFloating.d.ts +13 -2
- package/lib/chat/ChatFloating.js +192 -117
- package/lib/chat/ChatInline.d.ts +15 -0
- package/lib/chat/ChatInline.js +0 -18
- package/lib/chat/ChatPopupStandalone.d.ts +1 -1
- package/lib/chat/ChatSidebar.d.ts +14 -2
- package/lib/chat/ChatSidebar.js +1 -1
- package/lib/chat/ChatStandalone.d.ts +1 -1
- package/lib/chat/EphemeralSurfaceControl.d.ts +30 -0
- package/lib/chat/EphemeralSurfaceControl.js +55 -0
- package/lib/chat/base/ChatAvailability.d.ts +34 -0
- package/lib/chat/base/ChatAvailability.js +18 -0
- package/lib/chat/base/ChatBase.d.ts +2 -1
- package/lib/chat/base/ChatBase.js +657 -28
- package/lib/chat/display/EmptyState.d.ts +15 -1
- package/lib/chat/display/EmptyState.js +78 -12
- package/lib/chat/display/FloatingBrandButton.d.ts +1 -1
- package/lib/chat/display/PoweredByTag.d.ts +1 -1
- package/lib/chat/display/groupSuggestions.d.ts +21 -0
- package/lib/chat/display/groupSuggestions.js +18 -0
- package/lib/chat/document/EphemeralDocument.d.ts +70 -6
- package/lib/chat/document/EphemeralDocument.js +154 -52
- package/lib/chat/header/ChatHeader.d.ts +1 -1
- package/lib/chat/header/ChatHeaderBase.d.ts +10 -1
- package/lib/chat/header/ChatHeaderBase.js +67 -96
- package/lib/chat/index.d.ts +4 -1
- package/lib/chat/index.js +8 -1
- package/lib/chat/indicators/McpStatusIndicator.d.ts +1 -1
- package/lib/chat/indicators/SandboxStatusIndicator.d.ts +1 -1
- package/lib/chat/indicators/SandboxStatusIndicator.js +1 -1
- package/lib/chat/indicators/SkillsStatusIndicator.d.ts +1 -1
- package/lib/chat/messages/ChatMessageList.d.ts +22 -1
- package/lib/chat/messages/ChatMessageList.js +370 -166
- package/lib/chat/messages/ChatMessages.d.ts +1 -1
- package/lib/chat/messages/NotebookToolSurfaces.d.ts +68 -0
- package/lib/chat/messages/NotebookToolSurfaces.js +302 -0
- package/lib/chat/messages/TurnFooter.d.ts +42 -0
- package/lib/chat/messages/TurnFooter.js +75 -0
- package/lib/chat/messages/assistantMarkdown.d.ts +22 -0
- package/lib/chat/messages/assistantMarkdown.js +56 -0
- package/lib/chat/notebook/EphemeralNotebook.d.ts +67 -6
- package/lib/chat/notebook/EphemeralNotebook.js +194 -61
- package/lib/chat/parts/DynamicToolPart.d.ts +1 -1
- package/lib/chat/parts/MessagePart.d.ts +1 -1
- package/lib/chat/parts/ReasoningPart.d.ts +8 -1
- package/lib/chat/parts/TextPart.d.ts +1 -1
- package/lib/chat/parts/ToolPart.d.ts +8 -1
- package/lib/chat/prompt/FloatingCard.d.ts +26 -0
- package/lib/chat/prompt/FloatingCard.js +80 -0
- package/lib/chat/prompt/InputPrompt.d.ts +206 -53
- package/lib/chat/prompt/InputPrompt.js +161 -80
- package/lib/chat/prompt/InputPromptBase.d.ts +112 -0
- package/lib/chat/prompt/InputPromptBase.js +225 -0
- package/lib/chat/prompt/InputPromptLexical.d.ts +18 -1
- package/lib/chat/prompt/InputPromptLexical.js +227 -30
- package/lib/chat/prompt/InputPromptText.d.ts +10 -2
- package/lib/chat/prompt/InputPromptText.js +35 -3
- package/lib/chat/prompt/footer/BelowPromptFooter.d.ts +25 -0
- package/lib/chat/prompt/footer/BelowPromptFooter.js +12 -0
- package/lib/chat/prompt/footer/InPromptFooter.d.ts +35 -0
- package/lib/chat/prompt/footer/InPromptFooter.js +16 -0
- package/lib/chat/prompt/footer/index.d.ts +7 -0
- package/lib/chat/prompt/footer/index.js +11 -0
- package/lib/chat/prompt/header/BelowPromptHeader.d.ts +23 -0
- package/lib/chat/prompt/header/BelowPromptHeader.js +12 -0
- package/lib/chat/prompt/header/InPromptHeader.d.ts +31 -0
- package/lib/chat/prompt/header/InPromptHeader.js +14 -0
- package/lib/chat/prompt/header/index.d.ts +7 -0
- package/lib/chat/prompt/header/index.js +11 -0
- package/lib/chat/prompt/index.d.ts +8 -4
- package/lib/chat/prompt/index.js +15 -3
- package/lib/chat/prompt/menus/AgentsMenu.d.ts +14 -0
- package/lib/chat/prompt/menus/AgentsMenu.js +42 -0
- package/lib/chat/prompt/menus/InlineAgentsMenu.d.ts +22 -0
- package/lib/chat/prompt/menus/InlineAgentsMenu.js +24 -0
- package/lib/chat/prompt/menus/ModelSelector.d.ts +7 -0
- package/lib/chat/prompt/menus/ModelSelector.js +27 -0
- package/lib/chat/prompt/menus/SkillsMenu.d.ts +10 -0
- package/lib/chat/prompt/menus/SkillsMenu.js +75 -0
- package/lib/chat/prompt/menus/SuggestionsMenu.d.ts +13 -0
- package/lib/chat/prompt/menus/SuggestionsMenu.js +31 -0
- package/lib/chat/prompt/menus/ToolsMenu.d.ts +13 -0
- package/lib/chat/prompt/menus/ToolsMenu.js +103 -0
- package/lib/chat/prompt/menus/index.d.ts +15 -0
- package/lib/chat/prompt/menus/index.js +19 -0
- package/lib/chat/prompt/plugins/AgentMentionPlugin.d.ts +54 -0
- package/lib/chat/prompt/plugins/AgentMentionPlugin.js +370 -0
- package/lib/chat/prompt/plugins/CommandPlugin.d.ts +52 -0
- package/lib/chat/prompt/plugins/CommandPlugin.js +359 -0
- package/lib/chat/prompt/plugins/MentionChip.d.ts +15 -0
- package/lib/chat/prompt/plugins/MentionChip.js +24 -0
- package/lib/chat/prompt/plugins/MentionNode.d.ts +66 -0
- package/lib/chat/prompt/plugins/MentionNode.js +77 -0
- package/lib/chat/prompt/promptHistory.d.ts +52 -0
- package/lib/chat/prompt/promptHistory.js +123 -0
- package/lib/chat/prompt/stack.d.ts +81 -0
- package/lib/chat/prompt/stack.js +41 -0
- package/lib/chat/prompt/useTypingPlaceholder.d.ts +52 -0
- package/lib/chat/prompt/useTypingPlaceholder.js +180 -0
- package/lib/chat/tools/A2AAgentDialog.d.ts +60 -0
- package/lib/chat/tools/A2AAgentDialog.js +265 -0
- package/lib/chat/tools/ToolApprovalBanner.d.ts +1 -1
- package/lib/chat/tools/ToolApprovalDialog.d.ts +1 -1
- package/lib/chat/tools/ToolCallDisplay.d.ts +8 -1
- package/lib/chat/tools/index.d.ts +1 -0
- package/lib/chat/tools/index.js +1 -0
- package/lib/chat/usage/ContextPie.d.ts +17 -0
- package/lib/chat/usage/{TokenUsageBar.js → ContextPie.js} +20 -14
- package/lib/chat/useViewportDrag.d.ts +11 -0
- package/lib/chat/useViewportDrag.js +61 -0
- package/lib/chat/viewModes.d.ts +51 -0
- package/lib/chat/viewModes.js +57 -0
- package/lib/client/AgentRuntimesClient.d.ts +10 -9
- package/lib/client/AgentRuntimesClient.js +1 -1
- package/lib/client/AgentRuntimesClientContext.d.ts +1 -0
- package/lib/client/AgentsMixin.d.ts +12 -12
- package/lib/client/AgentsMixin.js +31 -31
- package/lib/client/IAgentRuntimesClient.d.ts +10 -10
- package/lib/client/SdkAgentRuntimesClient.d.ts +10 -10
- package/lib/client/SdkAgentRuntimesClient.js +10 -10
- package/lib/client/mixins/RuntimesMixin.d.ts +37 -10
- package/lib/client/mixins/RuntimesMixin.js +54 -21
- package/lib/components/a2ui/A2uiSurfaceComposed.d.ts +1 -1
- package/lib/components/agents/AgentSummary.d.ts +13 -0
- package/lib/components/agents/AgentSummary.js +21 -4
- package/lib/components/cards/NotificationEventCard.d.ts +1 -1
- package/lib/components/cards/OutputCard.d.ts +1 -1
- package/lib/components/cards/ToolApprovalCard.d.ts +2 -2
- package/lib/components/cards/ToolApprovalCard.js +1 -0
- package/lib/components/code-sandboxes/CodeSandboxCellVariables.d.ts +1 -0
- package/lib/components/code-sandboxes/CodeSandboxCellVariables.js +0 -4
- package/lib/components/code-sandboxes/CodeSandboxCellVariablesDialog.d.ts +1 -0
- package/lib/components/code-sandboxes/CodeSandboxCellVariablesDialog.js +0 -4
- package/lib/components/code-sandboxes/CodeSandboxEnvironmentSelect.d.ts +35 -0
- package/lib/components/code-sandboxes/CodeSandboxEnvironmentSelect.js +27 -29
- package/lib/components/code-sandboxes/CodeSandboxLauncher.d.ts +1 -0
- package/lib/components/code-sandboxes/CodeSandboxLauncher.js +42 -19
- package/lib/components/code-sandboxes/CodeSandboxNames.js +7 -6
- package/lib/components/code-sandboxes/CodeSandboxPicker.js +89 -42
- package/lib/components/code-sandboxes/CodeSandboxPickerCell.d.ts +1 -0
- package/lib/components/code-sandboxes/CodeSandboxPickerCell.js +0 -4
- package/lib/components/code-sandboxes/CodeSandboxReservationControl.d.ts +1 -0
- package/lib/components/code-sandboxes/CodeSandboxReservationControl.js +5 -7
- package/lib/components/code-sandboxes/CodeSandboxTransfer.d.ts +5 -0
- package/lib/components/code-sandboxes/CodeSandboxUtils.d.ts +13 -1
- package/lib/components/code-sandboxes/CodeSandboxUtils.js +10 -4
- package/lib/components/code-sandboxes/NewCodeSandboxControls.d.ts +21 -15
- package/lib/components/code-sandboxes/NewCodeSandboxControls.js +28 -51
- package/lib/components/code-sandboxes/codeSandboxAllowance.d.ts +33 -0
- package/lib/components/code-sandboxes/codeSandboxAllowance.js +37 -0
- package/lib/components/code-sandboxes/codeSandboxEnvironments.d.ts +86 -0
- package/lib/components/code-sandboxes/codeSandboxEnvironments.js +147 -0
- package/lib/components/code-sandboxes/codeSandboxProviders.js +14 -5
- package/lib/components/code-sandboxes/index.d.ts +1 -0
- package/lib/components/code-sandboxes/index.js +1 -0
- package/lib/components/context/SpaceSelect.d.ts +1 -1
- package/lib/components/flashes/FlashDisclaimer.d.ts +1 -1
- package/lib/components/icons/ArtifactIcon.d.ts +1 -0
- package/lib/components/nbgrader/NbGradesDetails.d.ts +1 -1
- package/lib/components/progress/CreditsIndicator.d.ts +1 -0
- package/lib/components/progress/CreditsIndicator.js +0 -4
- package/lib/components/scheduler/ScheduleMenu.d.ts +1 -1
- package/lib/components/snapshots/SandboxSnapshotMenu.d.ts +3 -2
- package/lib/components/snapshots/SandboxSnapshotMenu.js +11 -15
- package/lib/components/snippets/SnippetDialog.d.ts +1 -0
- package/lib/components/snippets/SnippetDialog.js +0 -4
- package/lib/components/specs/SpecPayloadPreview.d.ts +34 -0
- package/lib/components/specs/SpecPayloadPreview.js +102 -0
- package/lib/components/tokens/SpaceVariantToken.d.ts +1 -1
- package/lib/context/ContextDistribution.d.ts +1 -1
- package/lib/context/ContextInspector.d.ts +2 -1
- package/lib/context/ContextPanel.d.ts +1 -1
- package/lib/context/ContextUsage.d.ts +1 -1
- package/lib/context/CostTracker.d.ts +1 -1
- package/lib/context/CostUsageChart.d.ts +1 -1
- package/lib/context/CostUsageChart.js +4 -4
- package/lib/context/MessageHistory.d.ts +3 -2
- package/lib/context/TokenUsageChart.d.ts +1 -1
- package/lib/context/TokenUsageChart.js +4 -4
- package/lib/examples/A2UiAgentExample.js +15 -6
- package/lib/examples/A2UiComponentsGalleryExample.d.ts +10 -0
- package/lib/examples/A2UiComponentsGalleryExample.js +24 -855
- package/lib/examples/A2UiContactCardExample.d.ts +10 -0
- package/lib/examples/A2UiContactCardExample.js +23 -170
- package/lib/examples/A2UiJupyterOutputExample.d.ts +3 -0
- package/lib/examples/A2UiJupyterOutputExample.js +792 -0
- package/lib/examples/A2UiRestaurantExample.d.ts +10 -0
- package/lib/examples/A2UiRestaurantExample.js +24 -179
- package/lib/examples/A2UiViewerExample.d.ts +10 -0
- package/lib/examples/A2UiViewerExample.js +24 -378
- package/lib/examples/AgUiAgenticExample.js +2 -4
- package/lib/examples/AgUiBackendToolRenderingExample.js +3 -5
- package/lib/examples/AgUiHaikuGenUiExample.js +2 -4
- package/lib/examples/AgUiHumanInTheLoopExample.js +2 -4
- package/lib/examples/AgUiSharedStateExample.js +2 -4
- package/lib/examples/AgUiToolsBasedGenUiExample.js +2 -4
- package/lib/examples/AgentA2AExample.d.ts +17 -0
- package/lib/examples/AgentA2AExample.js +56 -0
- package/lib/examples/AgentCheckpointsExample.d.ts +9 -10
- package/lib/examples/AgentCheckpointsExample.js +72 -54
- package/lib/examples/AgentCodeSandboxExample.d.ts +7 -7
- package/lib/examples/AgentCodeSandboxExample.js +93 -303
- package/lib/examples/AgentCodeSandboxesExample.d.ts +10 -0
- package/lib/examples/AgentCodeSandboxesExample.js +56 -162
- package/lib/examples/AgentCodemodeExample.d.ts +7 -2
- package/lib/examples/AgentCodemodeExample.js +60 -125
- package/lib/examples/AgentCompactionExample.js +19 -135
- package/lib/examples/AgentEvalsExample.js +34 -66
- package/lib/examples/AgentGuardrailsExample.js +64 -112
- package/lib/examples/AgentHooksExample.d.ts +11 -0
- package/lib/examples/AgentHooksExample.js +30 -67
- package/lib/examples/AgentInferenceProviderExample.js +8 -32
- package/lib/examples/AgentLoopExample.d.ts +4 -2
- package/lib/examples/AgentLoopExample.js +45 -93
- package/lib/examples/AgentMCPExample.js +30 -79
- package/lib/examples/AgentMemoryExample.js +21 -64
- package/lib/examples/AgentMonitoringExample.js +33 -94
- package/lib/examples/AgentNotificationsExample.js +56 -95
- package/lib/examples/AgentOtelExample.js +35 -88
- package/lib/examples/AgentOutputsExample.d.ts +2 -1
- package/lib/examples/AgentOutputsExample.js +16 -137
- package/lib/examples/AgentParametersExample.js +7 -17
- package/lib/examples/AgentSkillsExample.js +40 -154
- package/lib/examples/AgentSubagentsExample.d.ts +7 -6
- package/lib/examples/AgentSubagentsExample.js +80 -191
- package/lib/examples/AgentToolApprovalsExample.js +35 -91
- package/lib/examples/AgentTriggersExample.js +43 -59
- package/lib/examples/AgentspecsExample.js +4 -4
- package/lib/examples/CellExample.d.ts +1 -1
- package/lib/examples/ChatExample.d.ts +12 -5
- package/lib/examples/ChatExample.js +38 -13
- package/lib/examples/CopilotKitLexicalExample.d.ts +1 -0
- package/lib/examples/CopilotKitLexicalExample.js +4 -41
- package/lib/examples/CopilotKitNotebookExample.d.ts +10 -0
- package/lib/examples/CopilotKitNotebookExample.js +2 -14
- package/lib/examples/DecksAgent.d.ts +21 -0
- package/lib/examples/DecksAgent.js +23 -0
- package/lib/examples/DocumentPageAgent.d.ts +19 -0
- package/lib/examples/DocumentPageAgent.js +15 -0
- package/lib/examples/Lexical2Example.d.ts +2 -2
- package/lib/examples/Lexical2Example.js +17 -38
- package/lib/examples/LexicalAgentExample.d.ts +2 -1
- package/lib/examples/LexicalAgentExample.js +29 -102
- package/lib/examples/LexicalAgentSidebarExample.d.ts +16 -5
- package/lib/examples/LexicalAgentSidebarExample.js +99 -131
- package/lib/examples/LoopShellExample.d.ts +38 -0
- package/lib/examples/LoopShellExample.js +68 -0
- package/lib/examples/LoopWorkspaceEmbed.d.ts +50 -0
- package/lib/examples/LoopWorkspaceEmbed.js +73 -0
- package/lib/examples/LoopWorkspaceExample.d.ts +150 -0
- package/lib/examples/LoopWorkspaceExample.js +151 -0
- package/lib/examples/NotebookAgentExample.d.ts +13 -0
- package/lib/examples/NotebookAgentExample.js +36 -86
- package/lib/examples/NotebookAgentSidebarExample.d.ts +10 -4
- package/lib/examples/NotebookAgentSidebarExample.js +39 -83
- package/lib/examples/NotebookCollaborationExample.d.ts +1 -1
- package/lib/examples/NotebookCollaborationExample.js +108 -20
- package/lib/examples/NotebookExample.d.ts +1 -1
- package/lib/examples/NotebookExample.js +20 -10
- package/lib/examples/NotebookPageAgent.d.ts +23 -0
- package/lib/examples/NotebookPageAgent.js +17 -0
- package/lib/examples/components/ExampleWrapper.d.ts +5 -0
- package/lib/examples/components/ExampleWrapper.js +36 -28
- package/lib/examples/components/LexicalEditor.d.ts +1 -1
- package/lib/examples/components/LexicalEditor.js +4 -24
- package/lib/examples/components/haiku/HaikuDisplay.d.ts +1 -1
- package/lib/examples/components/haiku/InlineHaikuCard.d.ts +1 -1
- package/lib/examples/components/weather/InlineWeatherCard.d.ts +1 -1
- package/lib/examples/example-selector.js +26 -6
- package/lib/examples/hooks/useCreateAgentOnce.d.ts +41 -0
- package/lib/examples/hooks/useCreateAgentOnce.js +51 -0
- package/lib/examples/hooks/useExampleAgentProtocol.d.ts +35 -0
- package/lib/examples/hooks/useExampleAgentProtocol.js +63 -0
- package/lib/examples/hooks/useExampleAgentRuntime.d.ts +16 -16
- package/lib/examples/hooks/useExampleAgentRuntime.js +26 -25
- package/lib/examples/hooks/useExampleAgentRuntimes.js +45 -61
- package/lib/examples/hooks/useExampleJupyterAgent.d.ts +91 -0
- package/lib/examples/hooks/useExampleJupyterAgent.js +151 -0
- package/lib/examples/index.d.ts +4 -0
- package/lib/examples/index.js +6 -0
- package/lib/examples/lexical/editorConfig.d.ts +2 -75
- package/lib/examples/lexical/editorConfig.js +26 -48
- package/lib/examples/lexical/lexical-theme.css +65 -47
- package/lib/examples/main.js +450 -322
- package/lib/examples/scenes/A2UiComponentsGalleryScene.d.ts +3 -0
- package/lib/examples/scenes/A2UiComponentsGalleryScene.js +872 -0
- package/lib/examples/scenes/A2UiContactCardScene.d.ts +3 -0
- package/lib/examples/scenes/A2UiContactCardScene.js +188 -0
- package/lib/examples/scenes/A2UiRestaurantScene.d.ts +3 -0
- package/lib/examples/scenes/A2UiRestaurantScene.js +196 -0
- package/lib/examples/scenes/A2UiViewerScene.d.ts +3 -0
- package/lib/examples/scenes/A2UiViewerScene.js +395 -0
- package/lib/examples/utils/agentSummaryStore.d.ts +7 -1
- package/lib/examples/utils/agents/earthquake-detector.ipynb.json +1 -1
- package/lib/examples/utils/agents/earthquake-detector.lexical.json +1 -1
- package/lib/examples/utils/agents/sales-forecaster.ipynb.json +1 -1
- package/lib/examples/utils/agents/sales-forecaster.lexical.json +1 -1
- package/lib/examples/utils/agents/social-post-generator.ipynb.json +1 -1
- package/lib/examples/utils/agents/social-post-generator.lexical.json +1 -1
- package/lib/examples/utils/exampleSurfaces.d.ts +31 -0
- package/lib/examples/utils/exampleSurfaces.js +38 -0
- package/lib/examples/utils/jupyterSandboxUrl.d.ts +27 -0
- package/lib/examples/utils/jupyterSandboxUrl.js +55 -0
- package/lib/examples/utils/localUrl.d.ts +2 -0
- package/lib/examples/utils/localUrl.js +49 -0
- package/lib/examples/utils/notebookToolbarItems.d.ts +46 -0
- package/lib/examples/utils/notebookToolbarItems.js +129 -0
- package/lib/examples/utils/runtimeTargetStore.d.ts +40 -1
- package/lib/examples/utils/runtimeTargetStore.js +82 -1
- package/lib/examples/utils/themedProvider.d.ts +7 -0
- package/lib/examples/utils/themedProvider.js +57 -4
- package/lib/examples/utils/useExampleAgentRuntimesUrl.js +30 -5
- package/lib/examples/utils/waitForAgent.d.ts +23 -0
- package/lib/examples/utils/waitForAgent.js +24 -0
- package/lib/examples2/CellExample.d.ts +1 -1
- package/lib/examples2/DatalayerNotebookExample.d.ts +1 -1
- package/lib/examples2/NotebookExample.d.ts +1 -1
- package/lib/examples2/NotebookMutationsKernel.d.ts +1 -1
- package/lib/examples2/NotebookMutationsServiceManager.d.ts +1 -1
- package/lib/examples2/main.js +24 -4
- package/lib/extensions/A2UIExtension.d.ts +1 -1
- package/lib/hooks/index.d.ts +8 -5
- package/lib/hooks/index.js +20 -5
- package/lib/hooks/useAgentRuntimes.d.ts +49 -22
- package/lib/hooks/useAgentRuntimes.js +214 -90
- package/lib/hooks/useAgentsCatalog.js +32 -4
- package/lib/hooks/useBrowserAgent.d.ts +26 -0
- package/lib/hooks/useBrowserAgent.js +75 -0
- package/lib/hooks/useBrowserInference.d.ts +27 -0
- package/lib/hooks/useBrowserInference.js +67 -0
- package/lib/hooks/useCheckpoints.d.ts +8 -17
- package/lib/hooks/useCheckpoints.js +25 -59
- package/lib/hooks/useConfig.d.ts +1 -1
- package/lib/hooks/useConfig.js +5 -1
- package/lib/hooks/useContextSnapshot.d.ts +18 -7
- package/lib/hooks/useContextSnapshot.js +14 -10
- package/lib/hooks/useNotifications.d.ts +11 -11
- package/lib/hooks/useNotifications.js +1 -1
- package/lib/hooks/useProjects.d.ts +7 -19
- package/lib/hooks/useProjects.js +5 -5
- package/lib/hooks/useSkills.d.ts +0 -1
- package/lib/hooks/useSkills.js +6 -2
- package/lib/hooks/useToolApprovals.d.ts +8 -8
- package/lib/hooks/useToolApprovals.js +3 -3
- package/lib/identity/AgentIdentity.d.ts +9 -2
- package/lib/index.d.ts +1 -0
- package/lib/index.js +4 -0
- package/lib/jupyter/index.d.ts +2 -1
- package/lib/jupyter/index.js +2 -1
- package/lib/jupyter/serverExecution.d.ts +78 -0
- package/lib/jupyter/serverExecution.js +258 -0
- package/lib/jupyter/useResumeServerExecutions.d.ts +11 -0
- package/lib/jupyter/useResumeServerExecutions.js +64 -0
- package/lib/lexical/ChatInlinePlugin.d.ts +33 -0
- package/lib/lexical/ChatInlinePlugin.js +0 -36
- package/lib/loop/core/focusRequests.d.ts +10 -0
- package/lib/loop/core/focusRequests.js +46 -0
- package/lib/loop/core/index.d.ts +894 -0
- package/lib/loop/core/index.js +264 -0
- package/lib/loop/core/promptStore.d.ts +44 -0
- package/lib/loop/core/promptStore.js +63 -0
- package/lib/loop/core/surfaceRequests.d.ts +33 -0
- package/lib/loop/core/surfaceRequests.js +29 -0
- package/lib/loop/core/toolbar.d.ts +28 -0
- package/lib/loop/core/toolbar.js +59 -0
- package/lib/loop/embed/LoopEmbed.d.ts +57 -0
- package/lib/loop/embed/LoopEmbed.js +118 -0
- package/lib/loop/extensions.d.ts +10 -0
- package/lib/loop/extensions.js +49 -0
- package/lib/loop/index.d.ts +68 -0
- package/lib/loop/index.js +87 -0
- package/lib/loop/plugins/a2a-sidebar/A2ASidebar.d.ts +48 -0
- package/lib/loop/plugins/a2a-sidebar/A2ASidebar.js +190 -0
- package/lib/loop/plugins/a2a-sidebar/index.d.ts +5 -0
- package/lib/loop/plugins/a2a-sidebar/index.js +39 -0
- package/lib/loop/plugins/a2ui/A2uiView.d.ts +12 -0
- package/lib/loop/plugins/a2ui/A2uiView.js +97 -0
- package/lib/loop/plugins/a2ui/index.d.ts +3 -0
- package/lib/loop/plugins/a2ui/index.js +50 -0
- package/lib/loop/plugins/a2ui-scene/index.d.ts +36 -0
- package/lib/loop/plugins/a2ui-scene/index.js +28 -0
- package/lib/loop/plugins/agent-a2a/index.d.ts +3 -0
- package/lib/loop/plugins/agent-a2a/index.js +26 -0
- package/lib/loop/plugins/agent-a2ui/index.d.ts +3 -0
- package/lib/loop/plugins/agent-a2ui/index.js +42 -0
- package/lib/loop/plugins/agent-a2ui-jupyter-output/index.d.ts +3 -0
- package/lib/loop/plugins/agent-a2ui-jupyter-output/index.js +53 -0
- package/lib/loop/plugins/agent-capacity/index.d.ts +39 -0
- package/lib/loop/plugins/agent-capacity/index.js +52 -0
- package/lib/loop/plugins/agent-checkpoints/index.d.ts +3 -0
- package/lib/loop/plugins/agent-checkpoints/index.js +26 -0
- package/lib/loop/plugins/agent-code-sandbox/index.d.ts +3 -0
- package/lib/loop/plugins/agent-code-sandbox/index.js +24 -0
- package/lib/loop/plugins/agent-code-sandboxes/index.d.ts +13 -0
- package/lib/loop/plugins/agent-code-sandboxes/index.js +85 -0
- package/lib/loop/plugins/agent-codemode/index.d.ts +5 -0
- package/lib/loop/plugins/agent-codemode/index.js +41 -0
- package/lib/loop/plugins/agent-compaction/index.d.ts +10 -0
- package/lib/loop/plugins/agent-compaction/index.js +34 -0
- package/lib/loop/plugins/agent-evals/index.d.ts +3 -0
- package/lib/loop/plugins/agent-evals/index.js +25 -0
- package/lib/loop/plugins/agent-guardrails/index.d.ts +3 -0
- package/lib/loop/plugins/agent-guardrails/index.js +25 -0
- package/lib/loop/plugins/agent-hooks/index.d.ts +3 -0
- package/lib/loop/plugins/agent-hooks/index.js +28 -0
- package/lib/loop/plugins/agent-inference/index.d.ts +7 -0
- package/lib/loop/plugins/agent-inference/index.js +29 -0
- package/lib/loop/plugins/agent-mcp/index.d.ts +3 -0
- package/lib/loop/plugins/agent-mcp/index.js +25 -0
- package/lib/loop/plugins/agent-memory/index.d.ts +3 -0
- package/lib/loop/plugins/agent-memory/index.js +25 -0
- package/lib/loop/plugins/agent-monitoring/index.d.ts +3 -0
- package/lib/loop/plugins/agent-monitoring/index.js +25 -0
- package/lib/loop/plugins/agent-notifications/index.d.ts +3 -0
- package/lib/loop/plugins/agent-notifications/index.js +25 -0
- package/lib/loop/plugins/agent-otel/index.d.ts +3 -0
- package/lib/loop/plugins/agent-otel/index.js +25 -0
- package/lib/loop/plugins/agent-outputs/index.d.ts +3 -0
- package/lib/loop/plugins/agent-outputs/index.js +25 -0
- package/lib/loop/plugins/agent-parameters/index.d.ts +3 -0
- package/lib/loop/plugins/agent-parameters/index.js +25 -0
- package/lib/loop/plugins/agent-skills/index.d.ts +3 -0
- package/lib/loop/plugins/agent-skills/index.js +25 -0
- package/lib/loop/plugins/agent-subagents/index.d.ts +3 -0
- package/lib/loop/plugins/agent-subagents/index.js +25 -0
- package/lib/loop/plugins/agent-tool-approvals/index.d.ts +11 -0
- package/lib/loop/plugins/agent-tool-approvals/index.js +38 -0
- package/lib/loop/plugins/agent-triggers/index.d.ts +3 -0
- package/lib/loop/plugins/agent-triggers/index.js +26 -0
- package/lib/loop/plugins/agents/AgentSummaryPanel.d.ts +20 -0
- package/lib/loop/plugins/agents/AgentSummaryPanel.js +50 -0
- package/lib/loop/plugins/agents/AnonymousKeyBadge.d.ts +35 -0
- package/lib/loop/plugins/agents/AnonymousKeyBadge.js +53 -0
- package/lib/loop/plugins/agents/DatalayerAgentBridge.d.ts +24 -0
- package/lib/loop/plugins/agents/DatalayerAgentBridge.js +102 -0
- package/lib/loop/plugins/agents/DatalayerAgentMount.d.ts +18 -0
- package/lib/loop/plugins/agents/DatalayerAgentMount.js +12 -0
- package/lib/loop/plugins/agents/SandboxSelector.d.ts +18 -0
- package/lib/loop/plugins/agents/SandboxSelector.js +99 -0
- package/lib/loop/plugins/agents/SandboxStatusBridge.d.ts +19 -0
- package/lib/loop/plugins/agents/SandboxStatusBridge.js +23 -0
- package/lib/loop/plugins/agents/SandboxView.d.ts +11 -0
- package/lib/loop/plugins/agents/SandboxView.js +43 -0
- package/lib/loop/plugins/agents/TeamMemberPicker.d.ts +17 -0
- package/lib/loop/plugins/agents/TeamMemberPicker.js +60 -0
- package/lib/loop/plugins/agents/agentIcons.d.ts +23 -0
- package/lib/loop/plugins/agents/agentIcons.js +88 -0
- package/lib/loop/plugins/agents/browserService.d.ts +50 -0
- package/lib/loop/plugins/agents/browserService.js +233 -0
- package/lib/loop/plugins/agents/index.d.ts +13 -0
- package/lib/loop/plugins/agents/index.js +17 -0
- package/lib/loop/plugins/agents/plugin.d.ts +91 -0
- package/lib/loop/plugins/agents/plugin.js +201 -0
- package/lib/loop/plugins/agents/service.d.ts +99 -0
- package/lib/loop/plugins/agents/service.js +167 -0
- package/lib/loop/plugins/agents/specSubagents.d.ts +18 -0
- package/lib/loop/plugins/agents/specSubagents.js +33 -0
- package/lib/loop/plugins/agents/switchable.d.ts +107 -0
- package/lib/loop/plugins/agents/switchable.js +405 -0
- package/lib/loop/plugins/agents/team.d.ts +87 -0
- package/lib/loop/plugins/agents/team.js +143 -0
- package/lib/loop/plugins/agents/useSandboxService.d.ts +11 -0
- package/lib/loop/plugins/agents/useSandboxService.js +36 -0
- package/lib/loop/plugins/agents/useTeamSelection.d.ts +10 -0
- package/lib/loop/plugins/agents/useTeamSelection.js +27 -0
- package/lib/loop/plugins/agentspecs/AgentspecPicker.d.ts +15 -0
- package/lib/loop/plugins/agentspecs/AgentspecPicker.js +90 -0
- package/lib/loop/plugins/agentspecs/index.d.ts +6 -0
- package/lib/loop/plugins/agentspecs/index.js +63 -0
- package/lib/loop/plugins/chat/ChatView.d.ts +20 -0
- package/lib/loop/plugins/chat/ChatView.js +1670 -0
- package/lib/loop/plugins/chat/chatViewTools.d.ts +41 -0
- package/lib/loop/plugins/chat/chatViewTools.js +92 -0
- package/lib/loop/plugins/chat/index.d.ts +111 -0
- package/lib/loop/plugins/chat/index.js +143 -0
- package/lib/loop/plugins/chat/turnState.d.ts +41 -0
- package/lib/loop/plugins/chat/turnState.js +90 -0
- package/lib/loop/plugins/chat-extras/index.d.ts +14 -0
- package/lib/loop/plugins/chat-extras/index.js +51 -0
- package/lib/loop/plugins/chat-header/HeaderView.d.ts +13 -0
- package/lib/loop/plugins/chat-header/HeaderView.js +6 -0
- package/lib/loop/plugins/chat-header/index.d.ts +3 -0
- package/lib/loop/plugins/chat-header/index.js +21 -0
- package/lib/loop/plugins/chat-view/index.d.ts +10 -0
- package/lib/loop/plugins/chat-view/index.js +29 -0
- package/lib/loop/plugins/checkpoints-sidebar/CheckpointsSidebar.d.ts +27 -0
- package/lib/loop/plugins/checkpoints-sidebar/CheckpointsSidebar.js +174 -0
- package/lib/loop/plugins/checkpoints-sidebar/index.d.ts +5 -0
- package/lib/loop/plugins/checkpoints-sidebar/index.js +39 -0
- package/lib/loop/plugins/commands/index.d.ts +20 -0
- package/lib/loop/plugins/commands/index.js +148 -0
- package/lib/loop/plugins/decks/DeckSurface.d.ts +10 -0
- package/lib/loop/plugins/decks/DeckSurface.js +6 -0
- package/lib/loop/plugins/decks/DecksMenu.d.ts +11 -0
- package/lib/loop/plugins/decks/DecksMenu.js +42 -0
- package/lib/loop/plugins/decks/index.d.ts +19 -0
- package/lib/loop/plugins/decks/index.js +133 -0
- package/lib/loop/plugins/decks/surface.d.ts +7 -0
- package/lib/loop/plugins/decks/surface.js +11 -0
- package/lib/loop/plugins/document/DocumentView.d.ts +12 -0
- package/lib/loop/plugins/document/DocumentView.js +68 -0
- package/lib/loop/plugins/document/index.d.ts +3 -0
- package/lib/loop/plugins/document/index.js +86 -0
- package/lib/loop/plugins/document/openingDocument.d.ts +28 -0
- package/lib/loop/plugins/document/openingDocument.js +122 -0
- package/lib/loop/plugins/document-toolbar/SandboxStatusItem.d.ts +16 -0
- package/lib/loop/plugins/document-toolbar/SandboxStatusItem.js +31 -0
- package/lib/loop/plugins/document-toolbar/index.d.ts +4 -0
- package/lib/loop/plugins/document-toolbar/index.js +35 -0
- package/lib/loop/plugins/document-toolbar/names.d.ts +6 -0
- package/lib/loop/plugins/document-toolbar/names.js +10 -0
- package/lib/loop/plugins/document-toolbar/plugin.d.ts +2 -0
- package/lib/loop/plugins/document-toolbar/plugin.js +38 -0
- package/lib/loop/plugins/document-view/index.d.ts +10 -0
- package/lib/loop/plugins/document-view/index.js +28 -0
- package/lib/loop/plugins/graph/GraphToggle.d.ts +17 -0
- package/lib/loop/plugins/graph/GraphToggle.js +22 -0
- package/lib/loop/plugins/graph/GraphView.d.ts +16 -0
- package/lib/loop/plugins/graph/GraphView.js +6 -0
- package/lib/loop/plugins/graph/index.d.ts +5 -0
- package/lib/loop/plugins/graph/index.js +80 -0
- package/lib/loop/plugins/graph/viewType.d.ts +10 -0
- package/lib/loop/plugins/graph/viewType.js +14 -0
- package/lib/loop/plugins/input-prompt/ComposerView.d.ts +15 -0
- package/lib/loop/plugins/input-prompt/ComposerView.js +25 -0
- package/lib/loop/plugins/input-prompt/index.d.ts +25 -0
- package/lib/loop/plugins/input-prompt/index.js +26 -0
- package/lib/loop/plugins/models/index.d.ts +3 -0
- package/lib/loop/plugins/models/index.js +49 -0
- package/lib/loop/plugins/notebook/NotebookView.d.ts +9 -0
- package/lib/loop/plugins/notebook/NotebookView.js +189 -0
- package/lib/loop/plugins/notebook/index.d.ts +3 -0
- package/lib/loop/plugins/notebook/index.js +91 -0
- package/lib/loop/plugins/notebook/openingNotebook.d.ts +72 -0
- package/lib/loop/plugins/notebook/openingNotebook.js +90 -0
- package/lib/loop/plugins/notebook-toolbar/index.d.ts +4 -0
- package/lib/loop/plugins/notebook-toolbar/index.js +45 -0
- package/lib/loop/plugins/notebook-toolbar/items.d.ts +10 -0
- package/lib/loop/plugins/notebook-toolbar/items.js +45 -0
- package/lib/loop/plugins/notebook-toolbar/names.d.ts +9 -0
- package/lib/loop/plugins/notebook-toolbar/names.js +13 -0
- package/lib/loop/plugins/notebook-toolbar/plugin.d.ts +2 -0
- package/lib/loop/plugins/notebook-toolbar/plugin.js +24 -0
- package/lib/loop/plugins/notebook-view/index.d.ts +10 -0
- package/lib/loop/plugins/notebook-view/index.js +30 -0
- package/lib/loop/plugins/page-layout/TurnPanel.d.ts +25 -0
- package/lib/loop/plugins/page-layout/TurnPanel.js +126 -0
- package/lib/loop/plugins/page-layout/index.d.ts +35 -0
- package/lib/loop/plugins/page-layout/index.js +80 -0
- package/lib/loop/plugins/plugins-panel/index.d.ts +14 -0
- package/lib/loop/plugins/plugins-panel/index.js +74 -0
- package/lib/loop/plugins/prompt/NewChatAction.d.ts +19 -0
- package/lib/loop/plugins/prompt/NewChatAction.js +10 -0
- package/lib/loop/plugins/prompt/index.d.ts +11 -0
- package/lib/loop/plugins/prompt/index.js +73 -0
- package/lib/loop/plugins/shell/editorChoice.d.ts +31 -0
- package/lib/loop/plugins/shell/editorChoice.js +58 -0
- package/lib/loop/plugins/shell/index.d.ts +47 -0
- package/lib/loop/plugins/shell/index.js +142 -0
- package/lib/loop/plugins/shell/useEditorPreviews.d.ts +32 -0
- package/lib/loop/plugins/shell/useEditorPreviews.js +217 -0
- package/lib/loop/plugins/subagent-activity/SubagentPulse.d.ts +16 -0
- package/lib/loop/plugins/subagent-activity/SubagentPulse.js +45 -0
- package/lib/loop/plugins/subagent-activity/index.d.ts +12 -0
- package/lib/loop/plugins/subagent-activity/index.js +42 -0
- package/lib/loop/plugins/view-switch/index.d.ts +46 -0
- package/lib/loop/plugins/view-switch/index.js +68 -0
- package/lib/loop/plugins/window-frame/WindowFrame.d.ts +48 -0
- package/lib/loop/plugins/window-frame/WindowFrame.js +63 -0
- package/lib/loop/plugins/window-frame/index.d.ts +6 -0
- package/lib/loop/plugins/window-frame/index.js +79 -0
- package/lib/loop/plugins/window-frame/slots.d.ts +26 -0
- package/lib/loop/plugins/window-frame/slots.js +30 -0
- package/lib/loop/presets.d.ts +167 -0
- package/lib/loop/presets.js +139 -0
- package/lib/loop/shell/LoopWorkspace.d.ts +109 -0
- package/lib/loop/shell/LoopWorkspace.js +231 -0
- package/lib/loop/shell/PluginToggles.d.ts +21 -0
- package/lib/loop/shell/PluginToggles.js +41 -0
- package/lib/loop/shell/ViewSwitcher.d.ts +24 -0
- package/lib/loop/shell/ViewSwitcher.js +43 -0
- package/lib/loop/shell/WorkspaceFullScreenAction.d.ts +25 -0
- package/lib/loop/shell/WorkspaceFullScreenAction.js +24 -0
- package/lib/loop/shell/index.d.ts +10 -0
- package/lib/loop/shell/index.js +14 -0
- package/lib/loop/shell/useWorkspaceFullScreen.d.ts +93 -0
- package/lib/loop/shell/useWorkspaceFullScreen.js +167 -0
- package/lib/loop-example-main.d.ts +1 -0
- package/lib/loop-example-main.js +42 -0
- package/lib/loop-main.d.ts +1 -0
- package/lib/loop-main.js +138 -0
- package/lib/mcp/McpServerManager.d.ts +1 -1
- package/lib/models/CodeSandboxSnapshotDTO.d.ts +1 -1
- package/lib/models/CodeSandboxVariant.d.ts +13 -5
- package/lib/models/CodeSandboxVariant.js +17 -5
- package/lib/models/Content.d.ts +26 -0
- package/lib/models/Environment.d.ts +561 -5
- package/lib/models/Environment.js +12 -1
- package/lib/models/EnvironmentDTO.d.ts +24 -6
- package/lib/models/EnvironmentDTO.js +13 -2
- package/lib/models/Exercise.d.ts +0 -2
- package/lib/models/Library.d.ts +0 -2
- package/lib/models/Notebook.d.ts +0 -2
- package/lib/models/ProjectDTO.d.ts +2 -2
- package/lib/models/ProjectDTO.js +4 -4
- package/lib/models/Runtime.d.ts +11 -11
- package/lib/models/RuntimeDTO.d.ts +16 -7
- package/lib/models/RuntimeDTO.js +9 -9
- package/lib/models/Space.d.ts +10 -0
- package/lib/models/Space.js +1 -1
- package/lib/models/SpaceDTO.d.ts +1 -1
- package/lib/models/SpaceItem.d.ts +1 -2
- package/lib/models/environments.generated.d.ts +113 -0
- package/lib/models/environments.generated.js +13 -0
- package/lib/models/index.d.ts +0 -1
- package/lib/models/index.js +0 -1
- package/lib/protocols/A2AAdapter.d.ts +39 -6
- package/lib/protocols/A2AAdapter.js +180 -11
- package/lib/protocols/ACPAdapter.js +40 -37
- package/lib/protocols/AGUIAdapter.d.ts +14 -0
- package/lib/protocols/AGUIAdapter.js +34 -9
- package/lib/protocols/BrowserAgentAdapter.d.ts +117 -0
- package/lib/protocols/BrowserAgentAdapter.js +310 -0
- package/lib/protocols/VercelAIAdapter.d.ts +10 -0
- package/lib/protocols/VercelAIAdapter.js +27 -7
- package/lib/protocols/index.d.ts +1 -0
- package/lib/protocols/index.js +1 -0
- package/lib/runtimes/actions.d.ts +3 -3
- package/lib/runtimes/actions.js +27 -14
- package/lib/runtimes/apis.d.ts +13 -4
- package/lib/runtimes/browser/agent.d.ts +54 -0
- package/lib/runtimes/browser/agent.js +63 -0
- package/lib/runtimes/browser/anonymousToken.d.ts +121 -0
- package/lib/runtimes/browser/anonymousToken.js +300 -0
- package/lib/runtimes/browser/frontendTools.d.ts +61 -0
- package/lib/runtimes/browser/frontendTools.js +152 -0
- package/lib/runtimes/browser/index.d.ts +11 -0
- package/lib/runtimes/browser/index.js +20 -0
- package/lib/runtimes/browser/model.d.ts +52 -0
- package/lib/runtimes/browser/model.js +110 -0
- package/lib/runtimes/browser/protocol.d.ts +54 -0
- package/lib/runtimes/browser/protocol.js +35 -0
- package/lib/runtimes/browser/subagents.d.ts +131 -0
- package/lib/runtimes/browser/subagents.js +241 -0
- package/lib/runtimes/index.d.ts +3 -0
- package/lib/runtimes/index.js +3 -0
- package/lib/runtimes/lifecycle.d.ts +76 -0
- package/lib/runtimes/lifecycle.js +99 -0
- package/lib/runtimes/variants.d.ts +172 -0
- package/lib/runtimes/variants.js +187 -0
- package/lib/services/reconnectToRuntime.d.ts +2 -2
- package/lib/services/reconnectToRuntime.js +3 -3
- package/lib/services/sandboxServiceManagers.d.ts +2 -2
- package/lib/services/sandboxServiceManagers.js +11 -11
- package/lib/specs/agents/agents.d.ts +128 -112
- package/lib/specs/agents/agents.js +8024 -5052
- package/lib/specs/evals.js +9 -0
- package/lib/specs/frontendTools.d.ts +3 -0
- package/lib/specs/frontendTools.js +46 -3
- package/lib/specs/mcpServers.d.ts +1 -0
- package/lib/specs/mcpServers.js +25 -2
- package/lib/specs/models.d.ts +18 -0
- package/lib/specs/models.js +154 -0
- package/lib/specs/outputs.d.ts +2 -0
- package/lib/specs/outputs.js +22 -0
- package/lib/specs/personas.js +1 -1
- package/lib/specs/teams/teams.d.ts +2 -0
- package/lib/specs/teams/teams.js +628 -111
- package/lib/state/index.d.ts +1 -1
- package/lib/state/index.js +1 -1
- package/lib/state/substates/RuntimesState.d.ts +5 -5
- package/lib/state/substates/RuntimesState.js +14 -14
- package/lib/state/substates/index.d.ts +1 -1
- package/lib/state/substates/index.js +1 -1
- package/lib/stores/agentRuntimeStore.d.ts +43 -1
- package/lib/stores/agentRuntimeStore.js +100 -11
- package/lib/stores/index.d.ts +1 -1
- package/lib/stores/index.js +1 -1
- package/lib/stories/Button.d.ts +1 -1
- package/lib/stories/Button.stories.d.ts +1 -1
- package/lib/stories/Header.d.ts +1 -1
- package/lib/stories/Header.stories.d.ts +1 -1
- package/lib/test-setup.js +66 -0
- package/lib/tools/adapters/agent-runtimes/lexicalHooks.d.ts +15 -0
- package/lib/tools/adapters/agent-runtimes/lexicalHooks.js +43 -2
- package/lib/tools/adapters/agent-runtimes/notebookHooks.d.ts +9 -0
- package/lib/tools/adapters/agent-runtimes/notebookHooks.js +12 -0
- package/lib/tools/adapters/commands/frontendCommandTools.d.ts +11 -0
- package/lib/tools/adapters/commands/frontendCommandTools.js +60 -0
- package/lib/tools/adapters/commands/index.d.ts +1 -0
- package/lib/{models/Dataset.js → tools/adapters/commands/index.js} +1 -1
- package/lib/tools/adapters/copilotkit/lexicalHooks.js +7 -3
- package/lib/tools/index.d.ts +1 -0
- package/lib/tools/index.js +1 -0
- package/lib/types/a2a.d.ts +15 -1
- package/lib/types/agents-lifecycle.d.ts +11 -6
- package/lib/types/agents.d.ts +31 -6
- package/lib/types/agentspecs.d.ts +120 -4
- package/lib/types/chat.d.ts +251 -3
- package/lib/types/checkpoints.d.ts +20 -0
- package/lib/types/config.d.ts +13 -1
- package/lib/types/connection.d.ts +1 -1
- package/lib/types/evals.d.ts +2 -0
- package/lib/types/messages.d.ts +60 -0
- package/lib/types/models.d.ts +26 -0
- package/lib/types/notifications.d.ts +15 -6
- package/lib/types/protocol.d.ts +7 -2
- package/lib/types/stream.d.ts +48 -3
- package/lib/types/stream.js +5 -0
- package/lib/types/teams.d.ts +102 -4
- package/lib/types/tool-approvals.d.ts +1 -1
- package/lib/types/tools.d.ts +8 -2
- package/lib/utils/utils.js +7 -1
- package/package.json +376 -361
- package/scripts/codegen/__pycache__/compose.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/versioning.cpython-313.pyc +0 -0
- package/scripts/codegen/compose.py +208 -0
- package/scripts/codegen/generate_agents.py +202 -31
- package/scripts/codegen/generate_evals.py +2 -0
- package/scripts/codegen/generate_frontend_tools.py +35 -6
- package/scripts/codegen/generate_models.py +40 -15
- package/scripts/codegen/generate_sandbox_agents.py +76 -62
- package/scripts/codegen/generate_teams.py +178 -4
- package/scripts/codegen/generate_tools.py +6 -4
- package/scripts/gen_workers.py +278 -110
- package/scripts/generate-environments-types.py +404 -0
- package/lib/chat/prompt/InputFooter.d.ts +0 -58
- package/lib/chat/prompt/InputFooter.js +0 -194
- package/lib/chat/prompt/InputPromptFooter.d.ts +0 -27
- package/lib/chat/prompt/InputPromptFooter.js +0 -15
- package/lib/chat/prompt/InputPromptHeader.d.ts +0 -15
- package/lib/chat/prompt/InputPromptHeader.js +0 -15
- package/lib/chat/usage/TokenUsageBar.d.ts +0 -8
- package/lib/hooks/useRuntimes.d.ts +0 -5
- package/lib/hooks/useRuntimes.js +0 -34
- package/lib/jupyter/kernelsHandler.d.ts +0 -8
- package/lib/jupyter/kernelsHandler.js +0 -39
- package/lib/models/Dataset.d.ts +0 -16
- package/lib/utils/Cells.d.ts +0 -2
- package/lib/utils/Cells.js +0 -28
|
@@ -0,0 +1,1670 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import { Box, IconButton, SegmentedControl, Text, Truncate, } from '@primer/react';
|
|
4
|
+
import { useColorPalette } from '@datalayer/primer-addons';
|
|
5
|
+
import { ScreenFullIcon, ScreenNormalIcon } from '@primer/octicons-react';
|
|
6
|
+
import { signal } from '@datalayer/reactor';
|
|
7
|
+
import { useContributions, useSignalValue, useGate, useReactorPlatform, ReactorLazy, ReactorSlot, useSlotComponents, } from '@datalayer/reactor/react';
|
|
8
|
+
import { ChatBase } from '../../../chat/base/ChatBase';
|
|
9
|
+
import { SUGGESTION_CHIP_WIDTH } from '../../../chat/display/EmptyState';
|
|
10
|
+
import { AnonymousKeyExpired } from '@datalayer/core/lib/components/anonymous/AnonymousKeyExpired';
|
|
11
|
+
import { useAnonymousSessionStore } from '../../../runtimes/browser/anonymousToken';
|
|
12
|
+
import { browserProtocolConfig } from '../../../runtimes/browser';
|
|
13
|
+
import { useBrowserInference } from '../../../hooks/useBrowserInference';
|
|
14
|
+
import { getAgentspecs } from '../../../specs/agents';
|
|
15
|
+
import { targetRunsAgentInPage, } from '../agents/switchable';
|
|
16
|
+
import { agentIcon } from '../agents/agentIcons';
|
|
17
|
+
import { specSubagents } from '../agents/specSubagents';
|
|
18
|
+
import { subagentsFor } from '../agents/team';
|
|
19
|
+
import { useOptionalTeamSelection } from '../agents/useTeamSelection';
|
|
20
|
+
/**
|
|
21
|
+
* Stands in for a workspace with no team.
|
|
22
|
+
*
|
|
23
|
+
* `useSignalValue` needs a signal on every render, and a hook cannot be
|
|
24
|
+
* skipped for the ordinary single-agent case.
|
|
25
|
+
*/
|
|
26
|
+
const EMPTY_SELECTION = signal('');
|
|
27
|
+
/* A signal to read when no example contributed chat extras, so the hook
|
|
28
|
+
below always has one — a hook cannot be called conditionally. */
|
|
29
|
+
const EMPTY_CHAT_EXTRAS = signal({});
|
|
30
|
+
import { useWorkspaceFullScreen } from '../../shell/useWorkspaceFullScreen';
|
|
31
|
+
import { CHAT_PLUGIN_NAME } from './index';
|
|
32
|
+
import { INPUT_PROMPT_PLUGIN_NAME, } from '../input-prompt';
|
|
33
|
+
import { agentRuntimeStore, useAgentRuntimeContextSnapshot, } from '../../../stores';
|
|
34
|
+
import { useIAMStore } from '../../../state';
|
|
35
|
+
import { useConfig } from '../../../hooks/useConfig';
|
|
36
|
+
import { useSkills, useSkillActions } from '../../../hooks/useSkills';
|
|
37
|
+
import { AI_MODEL_CATALOGUE } from '../../../specs/models';
|
|
38
|
+
import { LoopAgentBlueprint, LoopAgentGate, LoopChatComposer, LoopChatExtras, LoopChatHeader, LoopChatLayout, LoopChatSuggestion, LoopChatSurface, LoopChatTurn, LoopFrontendTool, LoopSlots, canOpenView, onPromptFocusRequest, onSurfaceRequest, useLoopPromptStore, } from '../../core';
|
|
39
|
+
import { turnWritersOf } from './turnState';
|
|
40
|
+
import { isInactiveSurfaceContribution, orderToolContributions, toolsForChatView, } from './chatViewTools';
|
|
41
|
+
import { useAgentCommandTools } from '../../../tools/adapters/commands';
|
|
42
|
+
import { NONE_EDITOR, seedEditorChoice, setEditorOptions, } from '../shell/editorChoice';
|
|
43
|
+
/** No editor: the conversation on its own. */
|
|
44
|
+
const NO_SURFACE = '';
|
|
45
|
+
/**
|
|
46
|
+
* How fast the full-screen control breathes once it starts asking.
|
|
47
|
+
*
|
|
48
|
+
* Slow enough to read as breathing rather than blinking — a fast pulse on a
|
|
49
|
+
* small control reads as an error state, and this one is an invitation.
|
|
50
|
+
*/
|
|
51
|
+
const FULLSCREEN_HINT_PERIOD_MS = 1400;
|
|
52
|
+
export default function ChatView({ workspace }) {
|
|
53
|
+
/* The active theme's own colours. Read from the store rather than a
|
|
54
|
+
provider, so a workspace mounted without the addons theme still gets the
|
|
55
|
+
default palette instead of throwing. */
|
|
56
|
+
const palette = useColorPalette();
|
|
57
|
+
const controlsRef = useRef(null);
|
|
58
|
+
const { setViewControls } = workspace;
|
|
59
|
+
const [transient, setTransient] = useState(null);
|
|
60
|
+
// Read from the reactor rather than taken as a prop: the placeholder and the
|
|
61
|
+
// default editor are the chat plugin's own configuration, so a host sets them
|
|
62
|
+
// where it sets anything else about this plugin.
|
|
63
|
+
const reactor = useReactorPlatform();
|
|
64
|
+
const config = reactor.getConfig(CHAT_PLUGIN_NAME);
|
|
65
|
+
const placeholder = config?.placeholder ?? 'Ask anything, type / for commands or @ for mention';
|
|
66
|
+
const defaultSurface = config?.defaultSurface ?? 'notebook';
|
|
67
|
+
/*
|
|
68
|
+
* The input-prompt plugin's own configuration, read from here rather than
|
|
69
|
+
* from that plugin's component.
|
|
70
|
+
*
|
|
71
|
+
* `firstPromptHook` has to fire for *every* way a message reaches
|
|
72
|
+
* `handleSend` below — typed and sent through the composer, a suggestion
|
|
73
|
+
* chip clicked, or a host's own `suggestLoopPrompt(text, {submit: true})`
|
|
74
|
+
* arriving through the prompt store — and this view is the only thing all
|
|
75
|
+
* three of those already go through. The composer that renders is
|
|
76
|
+
* swappable (a host may contribute a different one to `LoopChatComposer`
|
|
77
|
+
* entirely) and does not see the other two paths at all, so watching for
|
|
78
|
+
* the first send from inside it would miss most of what "first prompt"
|
|
79
|
+
* needs to mean.
|
|
80
|
+
*/
|
|
81
|
+
const inputPromptConfig = reactor.getConfig(INPUT_PROMPT_PLUGIN_NAME);
|
|
82
|
+
/*
|
|
83
|
+
* The default editor is the opening selection, not something switched on
|
|
84
|
+
* later.
|
|
85
|
+
*
|
|
86
|
+
* It used to be applied by an effect that waited for the surface to become
|
|
87
|
+
* openable, which meant the picker read `None` until a sandbox existed —
|
|
88
|
+
* a workspace configured for the notebook opened saying it was configured
|
|
89
|
+
* for nothing. What the host configured is true from the first render; what
|
|
90
|
+
* the surface can do about it yet is the surface's business.
|
|
91
|
+
*/
|
|
92
|
+
/*
|
|
93
|
+
* Nothing open yet, whatever the host asked for.
|
|
94
|
+
*
|
|
95
|
+
* Opening the default on the first render puts an editor on screen before
|
|
96
|
+
* its surface exists — plugins arrive as they activate — and before the
|
|
97
|
+
* sandbox it needs is running. What the reader saw then was not the notebook
|
|
98
|
+
* but the notebook's "needs a running sandbox" placeholder, which is a worse
|
|
99
|
+
* answer than an empty chat that fills in a moment later.
|
|
100
|
+
*
|
|
101
|
+
* The effect below opens it the moment it can actually be opened.
|
|
102
|
+
*/
|
|
103
|
+
const [surfaceId, setSurfaceId] = useState(NO_SURFACE);
|
|
104
|
+
/**
|
|
105
|
+
* Whether the reader has chosen a surface themselves.
|
|
106
|
+
*
|
|
107
|
+
* Once they have, the default is spent. Without this it re-applies every
|
|
108
|
+
* time a new surface lands — so closing the notebook would reopen it as soon
|
|
109
|
+
* as any other plugin activated, which reads as the close button not working.
|
|
110
|
+
*/
|
|
111
|
+
const surfaceChosen = useRef(false);
|
|
112
|
+
const surfaces = useContributions(LoopChatSurface);
|
|
113
|
+
/* The composer and the title bar arrive as plugins: this view assembles
|
|
114
|
+
their props, whoever contributed renders them. First contribution wins —
|
|
115
|
+
these are single-occupant surfaces, not lists. */
|
|
116
|
+
const composerEntries = useContributions(LoopChatComposer);
|
|
117
|
+
const headerEntries = useContributions(LoopChatHeader);
|
|
118
|
+
/* Live per-example chat extras — an error banner, the codemode toggle, the
|
|
119
|
+
footer's MCP/codemode status. Carried as a signal so the example can
|
|
120
|
+
update it without the plugin being rebuilt; first contribution wins. */
|
|
121
|
+
/* How the parts are arranged. No contribution: the split below. A layout
|
|
122
|
+
plugin — the page layout — takes the same wired parts and arranges them
|
|
123
|
+
its own way; first contribution wins. */
|
|
124
|
+
const layoutEntries = useContributions(LoopChatLayout);
|
|
125
|
+
const layout = layoutEntries[0]?.value;
|
|
126
|
+
/* The current turn, kept for whoever shows the conversation without the
|
|
127
|
+
transcript. The chat plugin contributed the feed at build; this view is
|
|
128
|
+
its writer — begin on send, the reply as it arrives, end when it stops. */
|
|
129
|
+
const turnEntries = useContributions(LoopChatTurn);
|
|
130
|
+
const turnFeed = turnWritersOf(turnEntries[0]?.value);
|
|
131
|
+
/* Reached through a ref from the send and loading handlers, which are
|
|
132
|
+
memoised with empty deps and must not go stale on it. */
|
|
133
|
+
const turnFeedRef = useRef(turnFeed);
|
|
134
|
+
turnFeedRef.current = turnFeed;
|
|
135
|
+
/* Who is working, for the activity line — read through a ref by a
|
|
136
|
+
handler memoised with empty deps. Set once the spec is resolved below. */
|
|
137
|
+
const agentNameRef = useRef('');
|
|
138
|
+
const extrasEntries = useContributions(LoopChatExtras);
|
|
139
|
+
const chatExtras = useSignalValue(extrasEntries[0]?.value.extras ?? EMPTY_CHAT_EXTRAS);
|
|
140
|
+
/* Which editors have ever been mounted; they are never unmounted after —
|
|
141
|
+
see the render below. A ref, not state: adding to it during render is
|
|
142
|
+
idempotent and must not schedule another one. */
|
|
143
|
+
const everMounted = useRef(new Set());
|
|
144
|
+
// Asked, not assumed: the chat cannot know whether anything is listening,
|
|
145
|
+
// and whichever plugin does answers the gate. Re-asked on every render with
|
|
146
|
+
// the live workspace, so switching the sandbox switches the prompt.
|
|
147
|
+
const usable = useGate(LoopAgentGate, workspace);
|
|
148
|
+
/* The sandbox's answer. It is not the only thing that can close the chat —
|
|
149
|
+
an anonymous visitor's trial key runs out too — so the two are combined
|
|
150
|
+
further down, once the key is known. */
|
|
151
|
+
const gateBlocked = !usable.allowed;
|
|
152
|
+
const gateReason = gateBlocked
|
|
153
|
+
? (usable.reason ?? 'Chat is unavailable for this sandbox')
|
|
154
|
+
: undefined;
|
|
155
|
+
// Stable: ChatBase calls these in effects, and a new identity every render
|
|
156
|
+
// would make it re-publish on every render.
|
|
157
|
+
/*
|
|
158
|
+
* Whether the agent is working, held here rather than read back from the
|
|
159
|
+
* shell.
|
|
160
|
+
*
|
|
161
|
+
* It used to live only in `workspace.viewControls`, written by two
|
|
162
|
+
* callbacks that each replaced the whole object — and `onSendReady` fires
|
|
163
|
+
* again during a send, because ChatBase rebuilds its `handleSend` when the
|
|
164
|
+
* send starts. So the sequence was: loading true, then a fresh
|
|
165
|
+
* `{ stop }` with no `busy` at all. The prompt went live again one tick
|
|
166
|
+
* after the agent started, which is exactly when it must not be.
|
|
167
|
+
*
|
|
168
|
+
* Local state, and the shell is told from one place below.
|
|
169
|
+
*/
|
|
170
|
+
const [busy, setBusy] = useState(false);
|
|
171
|
+
const [sendReady, setSendReady] = useState(false);
|
|
172
|
+
const handleSendReady = useCallback((controls) => {
|
|
173
|
+
controlsRef.current = controls;
|
|
174
|
+
setSendReady(!!controls);
|
|
175
|
+
}, []);
|
|
176
|
+
/*
|
|
177
|
+
* What the agent is doing, for the turn feed.
|
|
178
|
+
*
|
|
179
|
+
* The newest tool call that has not returned is the activity: "Analyst is
|
|
180
|
+
* adding a cell…". Named by what the tool does in the page rather than by
|
|
181
|
+
* the tool — a reader watching the notebook is told what to look for.
|
|
182
|
+
* Cleared when nothing is running.
|
|
183
|
+
*/
|
|
184
|
+
const handleDisplayItemsChange = useCallback((items) => {
|
|
185
|
+
const feed = turnFeedRef.current;
|
|
186
|
+
if (!feed) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const SETTLED = new Set([
|
|
190
|
+
'complete',
|
|
191
|
+
'completed',
|
|
192
|
+
'done',
|
|
193
|
+
'error',
|
|
194
|
+
'failed',
|
|
195
|
+
'cancelled',
|
|
196
|
+
'canceled',
|
|
197
|
+
'rejected',
|
|
198
|
+
'denied',
|
|
199
|
+
'stopped',
|
|
200
|
+
]);
|
|
201
|
+
const VERBS = {
|
|
202
|
+
insertcell: 'adding a cell',
|
|
203
|
+
updatecell: 'editing a cell',
|
|
204
|
+
runcell: 'running a cell',
|
|
205
|
+
deletecells: 'removing cells',
|
|
206
|
+
readallcells: 'reading the notebook',
|
|
207
|
+
readcell: 'reading a cell',
|
|
208
|
+
executecode: 'running code',
|
|
209
|
+
insertblock: 'adding a block',
|
|
210
|
+
updateblock: 'editing a block',
|
|
211
|
+
runblock: 'running a block',
|
|
212
|
+
readdocument: 'reading the document',
|
|
213
|
+
};
|
|
214
|
+
let running;
|
|
215
|
+
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
216
|
+
const item = items[index];
|
|
217
|
+
if (typeof item.toolName === 'string' && item.toolCallId) {
|
|
218
|
+
if (!SETTLED.has(String(item.status).toLowerCase())) {
|
|
219
|
+
running = item;
|
|
220
|
+
}
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
if (!running) {
|
|
225
|
+
feed.activity(undefined);
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
const key = running.toolName
|
|
229
|
+
.toLowerCase()
|
|
230
|
+
.replace(/^datalayer_/, '')
|
|
231
|
+
.replace(/_lexical$/, '')
|
|
232
|
+
.replace(/[^a-z]/g, '');
|
|
233
|
+
const verb = VERBS[key] ?? `using ${running.toolName}`;
|
|
234
|
+
feed.activity(`${agentNameRef.current || 'The agent'} is ${verb}…`);
|
|
235
|
+
}, []);
|
|
236
|
+
/* Read through a ref by the snapshot handler, memoised with empty deps. */
|
|
237
|
+
const chatExtrasRef = useRef(chatExtras);
|
|
238
|
+
chatExtrasRef.current = chatExtras;
|
|
239
|
+
// The window, for the prompt band, the turn feed, and a host counting it.
|
|
240
|
+
const handleContextSnapshot = useCallback((snapshot) => {
|
|
241
|
+
setChatUsage(snapshot);
|
|
242
|
+
turnFeedRef.current?.usage(snapshot);
|
|
243
|
+
chatExtrasRef.current.onUsage?.(snapshot);
|
|
244
|
+
}, []);
|
|
245
|
+
const handleLoadingChange = useCallback((next) => {
|
|
246
|
+
setBusy(next);
|
|
247
|
+
if (!next) {
|
|
248
|
+
turnFeedRef.current?.end('done');
|
|
249
|
+
}
|
|
250
|
+
}, []);
|
|
251
|
+
/*
|
|
252
|
+
* The reply as it arrives, for the turn feed.
|
|
253
|
+
*
|
|
254
|
+
* `onMessagesChange` reports the conversation on every change of it,
|
|
255
|
+
* streaming included; the last assistant message is the one the current
|
|
256
|
+
* turn is about. Text only — a tool call is shown where it acted, in the
|
|
257
|
+
* editor, and the panel is for what the agent *said*.
|
|
258
|
+
*/
|
|
259
|
+
const handleMessagesChange = useCallback((messages) => {
|
|
260
|
+
const feed = turnFeedRef.current;
|
|
261
|
+
if (!feed) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
/*
|
|
265
|
+
* The whole reply since the person last spoke, not the latest segment.
|
|
266
|
+
*
|
|
267
|
+
* An agent that calls a tool speaks in more than one message per turn —
|
|
268
|
+
* "I'll start…", the tool, "Good, the frame shows…" — and a reader
|
|
269
|
+
* watching the panel wants all of it, in order. Text only: the tool's
|
|
270
|
+
* work shows where it acted, in the editor.
|
|
271
|
+
*/
|
|
272
|
+
let lastUser = -1;
|
|
273
|
+
for (let index = messages.length - 1; index >= 0; index -= 1) {
|
|
274
|
+
if (messages[index].role === 'user') {
|
|
275
|
+
lastUser = index;
|
|
276
|
+
break;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
const textOf = (message) => {
|
|
280
|
+
const content = message.content;
|
|
281
|
+
return typeof content === 'string'
|
|
282
|
+
? content
|
|
283
|
+
: content
|
|
284
|
+
.map(part => typeof part === 'string'
|
|
285
|
+
? part
|
|
286
|
+
: (part.text ?? ''))
|
|
287
|
+
.join('');
|
|
288
|
+
};
|
|
289
|
+
const reply = messages
|
|
290
|
+
.slice(lastUser + 1)
|
|
291
|
+
.filter(message => message.role === 'assistant')
|
|
292
|
+
.map(textOf)
|
|
293
|
+
.filter(text => text.trim().length > 0)
|
|
294
|
+
.join('\n\n');
|
|
295
|
+
feed.assistant(reply);
|
|
296
|
+
}, []);
|
|
297
|
+
// One writer, so neither fact can erase the other.
|
|
298
|
+
useEffect(() => {
|
|
299
|
+
setViewControls(sendReady
|
|
300
|
+
? {
|
|
301
|
+
busy,
|
|
302
|
+
stop: controlsRef.current?.stop,
|
|
303
|
+
newChat: controlsRef.current?.newChat,
|
|
304
|
+
}
|
|
305
|
+
: null);
|
|
306
|
+
}, [busy, sendReady, setViewControls]);
|
|
307
|
+
// Stop reporting when the view goes away, or the shell would keep a stop
|
|
308
|
+
// button for something that is no longer there.
|
|
309
|
+
useEffect(() => () => setViewControls(null), [setViewControls]);
|
|
310
|
+
useEffect(() => workspace.prompts.subscribe(message => {
|
|
311
|
+
// A prompt typed before the adapter is ready is dropped rather than
|
|
312
|
+
// queued: silently sending it minutes later, out of context, is worse
|
|
313
|
+
// than saying nothing happened.
|
|
314
|
+
controlsRef.current?.send(message);
|
|
315
|
+
}), [workspace.prompts]);
|
|
316
|
+
/*
|
|
317
|
+
* Fired once, on the first real send, for the life of this mounted chat —
|
|
318
|
+
* a ref rather than a signal, because nothing here needs to re-render when
|
|
319
|
+
* it flips; the host's hook is the only thing that reacts.
|
|
320
|
+
*/
|
|
321
|
+
const firstPromptFired = useRef(false);
|
|
322
|
+
const handleSend = useCallback(async (message) => {
|
|
323
|
+
/*
|
|
324
|
+
* Every way a message reaches this view runs through here — typed and
|
|
325
|
+
* sent through the composer, a suggestion chip clicked, or a host's own
|
|
326
|
+
* `suggestLoopPrompt(text, {submit: true})` arriving through the prompt
|
|
327
|
+
* store (see the `suggestion.submit` effect below) — so this is the one
|
|
328
|
+
* place "the first prompt" means the same thing regardless of which one
|
|
329
|
+
* a person or a host used.
|
|
330
|
+
*/
|
|
331
|
+
if (!firstPromptFired.current) {
|
|
332
|
+
firstPromptFired.current = true;
|
|
333
|
+
inputPromptConfig?.firstPromptHook?.();
|
|
334
|
+
}
|
|
335
|
+
setTransient(null);
|
|
336
|
+
// A new turn: whatever the panel showed is gone, this message is it.
|
|
337
|
+
turnFeedRef.current?.begin(message);
|
|
338
|
+
const outcome = await workspace.submit(message);
|
|
339
|
+
if (!outcome.handled) {
|
|
340
|
+
/*
|
|
341
|
+
* Nothing took it, so the words stay where they were typed.
|
|
342
|
+
*
|
|
343
|
+
* The reason appears under the prompt; emptying the box as well would
|
|
344
|
+
* make a failed send look like a successful one and cost the person
|
|
345
|
+
* the sentence they wrote.
|
|
346
|
+
*/
|
|
347
|
+
setTransient(outcome.reason ?? null);
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
/*
|
|
351
|
+
* Sent, so the composer is empty again.
|
|
352
|
+
*
|
|
353
|
+
* `InputPromptBase` clears itself only when it owns its own text. This
|
|
354
|
+
* view hands it a controlled value — a page embedding the workspace can
|
|
355
|
+
* offer a prompt, which needs somewhere outside the editor to put it —
|
|
356
|
+
* and a controlled value is the owner's to clear. It was not being
|
|
357
|
+
* cleared at all: the sentence stayed in the box after the agent had
|
|
358
|
+
* answered it, so the next thing typed was appended to the last thing
|
|
359
|
+
* asked, and the placeholder that suggests what to ask never came back.
|
|
360
|
+
*/
|
|
361
|
+
setDraft('');
|
|
362
|
+
if (outcome.result?.content) {
|
|
363
|
+
setTransient(outcome.result.content);
|
|
364
|
+
}
|
|
365
|
+
}, [workspace, inputPromptConfig?.firstPromptHook]);
|
|
366
|
+
const active = useMemo(() => surfaces.find(entry => entry.value.surfaceId === surfaceId)?.value, [surfaces, surfaceId]);
|
|
367
|
+
/**
|
|
368
|
+
* The reader picking a surface, including picking none.
|
|
369
|
+
*
|
|
370
|
+
* It records the choice as well as making it: from here on the host's
|
|
371
|
+
* default is spent, so a surface closed stays closed.
|
|
372
|
+
*/
|
|
373
|
+
const chooseSurface = useCallback((next) => {
|
|
374
|
+
surfaceChosen.current = true;
|
|
375
|
+
setSurfaceId(next);
|
|
376
|
+
}, []);
|
|
377
|
+
/*
|
|
378
|
+
* A command asking for a surface — `/notebook`, `/document`, or their
|
|
379
|
+
* shortcuts. Treated exactly like a reader picking one from the strip, so
|
|
380
|
+
* the choice sticks the same way and the host's default is spent.
|
|
381
|
+
*/
|
|
382
|
+
useEffect(() => onSurfaceRequest(chooseSurface), [chooseSurface]);
|
|
383
|
+
/*
|
|
384
|
+
* The editor choice store — what the shell's selector, the `/editor`
|
|
385
|
+
* command and the reactor shell's cycle read — learns two things only the
|
|
386
|
+
* chat knows: which editors exist, and which one is on screen. Nobody fed
|
|
387
|
+
* it the options before, so "cycle the editors" cycled an empty list and
|
|
388
|
+
* always landed on none; and a surface opened by a tool or a plugin, past
|
|
389
|
+
* the store, left it pointing at the wrong one. Seeded, not chosen: the
|
|
390
|
+
* chat already shows it, there is nothing to announce.
|
|
391
|
+
*/
|
|
392
|
+
useEffect(() => {
|
|
393
|
+
setEditorOptions(surfaces.map(entry => entry.value.surfaceId));
|
|
394
|
+
}, [surfaces]);
|
|
395
|
+
useEffect(() => {
|
|
396
|
+
seedEditorChoice(surfaceId && surfaceId !== NO_SURFACE ? surfaceId : NONE_EDITOR);
|
|
397
|
+
}, [surfaceId]);
|
|
398
|
+
/** The surface the host asked to open, once its plugin has contributed it. */
|
|
399
|
+
const wanted = useMemo(() => defaultSurface === NO_SURFACE || defaultSurface === 'none'
|
|
400
|
+
? undefined
|
|
401
|
+
: surfaces.find(entry => entry.value.surfaceId === defaultSurface), [surfaces, defaultSurface]);
|
|
402
|
+
/*
|
|
403
|
+
* Open the default the moment it can be opened.
|
|
404
|
+
*
|
|
405
|
+
* Both conditions are the point. A surface arrives when its plugin
|
|
406
|
+
* activates, so it may not exist on the first render; and these two need a
|
|
407
|
+
* running sandbox, which takes as long as a Pyodide kernel takes to start.
|
|
408
|
+
* Waiting for both is what turns "configured for the notebook" into a
|
|
409
|
+
* notebook rather than a placeholder.
|
|
410
|
+
*/
|
|
411
|
+
useEffect(() => {
|
|
412
|
+
if (surfaceChosen.current || !wanted) {
|
|
413
|
+
return;
|
|
414
|
+
}
|
|
415
|
+
if (canOpenView(wanted.value, workspace)) {
|
|
416
|
+
setSurfaceId(defaultSurface);
|
|
417
|
+
}
|
|
418
|
+
}, [wanted, workspace, defaultSurface]);
|
|
419
|
+
/*
|
|
420
|
+
* An editor that stops being openable — its sandbox went away — should not
|
|
421
|
+
* stay on screen claiming otherwise.
|
|
422
|
+
*
|
|
423
|
+
* Closed, but remembered. This used to set the surface to none and stop
|
|
424
|
+
* there, which spent the reader's choice on an interruption: a sandbox
|
|
425
|
+
* blinking during a target switch closed the notebook, `surfaceChosen`
|
|
426
|
+
* meant the default could not reopen it, and the control settled on None
|
|
427
|
+
* for a workspace nobody had asked to empty. What was on screen and what
|
|
428
|
+
* the control said agreed with each other and with neither the reader nor
|
|
429
|
+
* the configuration.
|
|
430
|
+
*/
|
|
431
|
+
/* State, not a ref: the layout below keeps this surface's column while it
|
|
432
|
+
is away, and a ref changing re-renders nothing. */
|
|
433
|
+
const [suspendedId, setSuspendedId] = useState(null);
|
|
434
|
+
/*
|
|
435
|
+
* Full screen: the whole view, on the whole screen.
|
|
436
|
+
*
|
|
437
|
+
* Everything the workspace is showing goes — the editor as well as the
|
|
438
|
+
* conversation. An earlier version hid the editor, on the theory that a long
|
|
439
|
+
* transcript wants the room; but somebody working on a notebook who asks for
|
|
440
|
+
* more room is asking for more room *for the work*, and taking the notebook
|
|
441
|
+
* away to give it is answering a question they did not ask.
|
|
442
|
+
*
|
|
443
|
+
* Done with the Fullscreen API rather than by drawing a big box, because a
|
|
444
|
+
* component cannot know what it is inside of. `position: fixed` escapes only
|
|
445
|
+
* as far as the nearest ancestor with a transform, and a page that animates
|
|
446
|
+
* its sections in — the landing does — leaves one behind permanently: the
|
|
447
|
+
* "full screen" chat then filled the card it was already in. The API
|
|
448
|
+
* promotes the element to the browser's top layer, where no ancestor can
|
|
449
|
+
* hold it, and unlike a portal it does not move in the DOM — so every
|
|
450
|
+
* inherited theme variable the editors read still resolves.
|
|
451
|
+
*
|
|
452
|
+
* The CSS overlay is kept as the fallback for where the API is refused: an
|
|
453
|
+
* iframe without `allow="fullscreen"`, mostly. Confined to a card is a worse
|
|
454
|
+
* answer than full screen and a better one than nothing.
|
|
455
|
+
*/
|
|
456
|
+
const viewRef = useRef(null);
|
|
457
|
+
// A host's own fixed header, drawn outside this workspace entirely —
|
|
458
|
+
// reserved at the top of the overlay below, and forcing full screen onto
|
|
459
|
+
// the overlay door for good; see `ChatPluginConfig.fullScreenTopOffset`.
|
|
460
|
+
const fullScreenTopOffset = config?.fullScreenTopOffset ?? 0;
|
|
461
|
+
/* Shared with the workspace header's icon — same machinery, two doors in.
|
|
462
|
+
See `useWorkspaceFullScreen` for why it is the browser's API rather than
|
|
463
|
+
a big box, and for the CSS fallback the styling below paints. */
|
|
464
|
+
const { fullScreen, usingApi: usingFullscreenApi, toggle: toggleFullScreen, topOffsetPx: fullScreenTopOffsetPx, } = useWorkspaceFullScreen(viewRef, {
|
|
465
|
+
forceOverlay: Boolean(fullScreenTopOffset),
|
|
466
|
+
topOffset: fullScreenTopOffset,
|
|
467
|
+
});
|
|
468
|
+
/*
|
|
469
|
+
* The full-screen control, pointed at from the first run onwards.
|
|
470
|
+
*
|
|
471
|
+
* This view is usually a panel inside somebody else's page — a few hundred
|
|
472
|
+
* pixels of a landing page or an example. The moment it becomes worth more
|
|
473
|
+
* room is the moment it starts answering, and that is also the moment a
|
|
474
|
+
* reader's attention is on the transcript rather than on a small grey icon
|
|
475
|
+
* in a corner. So from the first answer the icon takes the theme's own
|
|
476
|
+
* colour and breathes.
|
|
477
|
+
*
|
|
478
|
+
* It does not stop on a timer. A reader who is deep in the first answer has
|
|
479
|
+
* not yet had the thought "this is too small", and a hint that has already
|
|
480
|
+
* expired by the time they have it is a hint that was never given. What ends
|
|
481
|
+
* it is the reader: taking the offer, or reaching the thing it was offering.
|
|
482
|
+
*
|
|
483
|
+
* `hinted` is a ref because it must not itself cause a render, and because
|
|
484
|
+
* it has to survive `busy` going true again for the second message — this
|
|
485
|
+
* starts once, on the first run, not on every turn.
|
|
486
|
+
*/
|
|
487
|
+
const [hintFullScreen, setHintFullScreen] = useState(false);
|
|
488
|
+
const hinted = useRef(false);
|
|
489
|
+
useEffect(() => {
|
|
490
|
+
if (!busy || hinted.current || fullScreen) {
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
hinted.current = true;
|
|
494
|
+
setHintFullScreen(true);
|
|
495
|
+
}, [busy, fullScreen]);
|
|
496
|
+
// Arriving is the end of it, however they got there — the button, a
|
|
497
|
+
// keyboard shortcut, the host promoting the workspace. There is nothing
|
|
498
|
+
// left to point at, and it never comes back.
|
|
499
|
+
useEffect(() => {
|
|
500
|
+
if (fullScreen) {
|
|
501
|
+
setHintFullScreen(false);
|
|
502
|
+
}
|
|
503
|
+
}, [fullScreen]);
|
|
504
|
+
useEffect(() => {
|
|
505
|
+
if (active && !canOpenView(active, workspace)) {
|
|
506
|
+
setSuspendedId(active.surfaceId);
|
|
507
|
+
setSurfaceId(NO_SURFACE);
|
|
508
|
+
}
|
|
509
|
+
}, [active, workspace]);
|
|
510
|
+
/**
|
|
511
|
+
* The surface that is closed but coming back.
|
|
512
|
+
*
|
|
513
|
+
* Held so the layout can keep its column: what is missing is the editor,
|
|
514
|
+
* not the half of the workspace it lives in.
|
|
515
|
+
*/
|
|
516
|
+
const waiting = useMemo(() => {
|
|
517
|
+
if (suspendedId) {
|
|
518
|
+
return surfaces.find(item => item.value.surfaceId === suspendedId)?.value;
|
|
519
|
+
}
|
|
520
|
+
/*
|
|
521
|
+
* The host's default, before it has managed to open.
|
|
522
|
+
*
|
|
523
|
+
* It needs a sandbox, and a sandbox takes as long as a kernel takes to
|
|
524
|
+
* start — so for the first seconds of every workspace there is a
|
|
525
|
+
* configured notebook that is not open yet. Counting it as "waiting" is
|
|
526
|
+
* what lets the control say Notebook from the outset and the column hold
|
|
527
|
+
* its half of the split, instead of both saying None and then changing
|
|
528
|
+
* their minds.
|
|
529
|
+
*
|
|
530
|
+
* Only until somebody chooses for themselves. After that the default is
|
|
531
|
+
* spent, and a reader who picked None must not be shown Notebook.
|
|
532
|
+
*/
|
|
533
|
+
if (!surfaceChosen.current && surfaceId === NO_SURFACE) {
|
|
534
|
+
return wanted?.value;
|
|
535
|
+
}
|
|
536
|
+
return undefined;
|
|
537
|
+
}, [surfaces, suspendedId, surfaceId, wanted]);
|
|
538
|
+
// And opened again when it can be. The reader asked for it once; an
|
|
539
|
+
// interruption is not them changing their mind.
|
|
540
|
+
useEffect(() => {
|
|
541
|
+
if (!suspendedId || surfaceId !== NO_SURFACE) {
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
const entry = surfaces.find(item => item.value.surfaceId === suspendedId);
|
|
545
|
+
if (entry && canOpenView(entry.value, workspace)) {
|
|
546
|
+
setSuspendedId(null);
|
|
547
|
+
setSurfaceId(suspendedId);
|
|
548
|
+
}
|
|
549
|
+
}, [surfaces, surfaceId, suspendedId, workspace]);
|
|
550
|
+
// The AG-UI mount is per agent, and `default` is the agent a server runs when
|
|
551
|
+
// it was not told otherwise, so a workspace opened without one still talks to
|
|
552
|
+
// something.
|
|
553
|
+
const agentId = workspace.agentId?.trim() || 'default';
|
|
554
|
+
/*
|
|
555
|
+
* Which protocol, decided by where the sandbox is.
|
|
556
|
+
*
|
|
557
|
+
* On the browser target there is no server to mount an agent on, so the loop
|
|
558
|
+
* turns in this page with the Vercel AI SDK and the chat calls it rather
|
|
559
|
+
* than addressing it. Everywhere else the agent lives on the runtime and the
|
|
560
|
+
* chat speaks AG-UI to it, exactly as before.
|
|
561
|
+
*
|
|
562
|
+
* The spec's own `harness` does not decide this. It says where the agent
|
|
563
|
+
* normally runs, and a page cannot turn a pydantic-ai loop whatever it asks
|
|
564
|
+
* for — so the location wins, which is the same rule the examples follow.
|
|
565
|
+
*/
|
|
566
|
+
const inPage = targetRunsAgentInPage(workspace.sandbox.target ?? 'local');
|
|
567
|
+
/*
|
|
568
|
+
* Who is being addressed, when this workspace runs a team.
|
|
569
|
+
*
|
|
570
|
+
* The team's own answer, not the workspace's: `workspace.agentId` is the
|
|
571
|
+
* session's agent, and a team has several behind one front door. Picking a
|
|
572
|
+
* member changes which spec the next prompt is answered by, and nothing
|
|
573
|
+
* else — the sandbox, the notebook and the transcript stay where they are.
|
|
574
|
+
*/
|
|
575
|
+
const team = useOptionalTeamSelection();
|
|
576
|
+
const selectedMemberId = useSignalValue(team?.selected ?? EMPTY_SELECTION);
|
|
577
|
+
const member = team?.members.find(entry => entry.id === selectedMemberId);
|
|
578
|
+
/*
|
|
579
|
+
* Looked up through `getAgentspecs`, not by subscripting the record.
|
|
580
|
+
*
|
|
581
|
+
* Agent ids reach this component versioned as often as not —
|
|
582
|
+
* `jupyter-data-analyst:0.0.1` — and the record is keyed on the bare id, so
|
|
583
|
+
* a raw subscript on a versioned id silently misses. What follows from a
|
|
584
|
+
* miss is not an error but a quiet wrong answer: no model, no openers, no
|
|
585
|
+
* name. `getAgentspecs` strips the version and retries, which is the whole
|
|
586
|
+
* reason the specs package exports it.
|
|
587
|
+
*/
|
|
588
|
+
/*
|
|
589
|
+
* The spec, found two ways.
|
|
590
|
+
*
|
|
591
|
+
* By the agent's id first — a team member's spec id, or a host that names
|
|
592
|
+
* the spec as the agent, as the Loop Shell does. Failing that, by the
|
|
593
|
+
* blueprint a capacity plugin contributed: an agent it created on a server
|
|
594
|
+
* carries an instance name (`subagents-example-agent-…`), not a spec id, and
|
|
595
|
+
* the same agent turned in this page had no prompt, no subagents and no
|
|
596
|
+
* icon for want of this lookup — it used the document tools and called
|
|
597
|
+
* nobody.
|
|
598
|
+
*/
|
|
599
|
+
const blueprintSpecId = useContributions(LoopAgentBlueprint)[0]?.value.specId;
|
|
600
|
+
const spec = getAgentspecs(member?.specId ?? agentId) ??
|
|
601
|
+
(blueprintSpecId ? getAgentspecs(blueprintSpecId) : undefined);
|
|
602
|
+
/*
|
|
603
|
+
* The team's name for a member first, then the spec's own.
|
|
604
|
+
*
|
|
605
|
+
* A spec is named for the catalogue — "Jupyter Data Analyst" — and a team
|
|
606
|
+
* names the same agent for the person working with it — "Analyst", which is
|
|
607
|
+
* what the picker in the header says. The header, the empty state and the
|
|
608
|
+
* openers' heading say the same word as the picker, or a person reads two
|
|
609
|
+
* names for one agent on one screen.
|
|
610
|
+
*/
|
|
611
|
+
agentNameRef.current = member?.name ?? spec?.name ?? agentId;
|
|
612
|
+
/*
|
|
613
|
+
* The openers: a capacity plugin's first, then the team's with the
|
|
614
|
+
* addressed member's own beneath, then — with no team — the agent's own.
|
|
615
|
+
*
|
|
616
|
+
* A contribution to `LoopChatSuggestion` is a deliberate statement about
|
|
617
|
+
* what this workspace is worth asking, so it replaces the spec's generic
|
|
618
|
+
* list rather than joining it.
|
|
619
|
+
*
|
|
620
|
+
* With a team, its openers are always shown: they are what the *team* can
|
|
621
|
+
* be asked, whoever is addressed, and a person who has just opened the
|
|
622
|
+
* workspace should see the whole before the part. Under them, in a block
|
|
623
|
+
* with its name on it, come the addressed member's own openers — picking
|
|
624
|
+
* the Reviewer used to change nothing on screen, and the reverse (the
|
|
625
|
+
* member's only) hid the team. An opener the team already lists is not
|
|
626
|
+
* repeated.
|
|
627
|
+
*/
|
|
628
|
+
const suggestionEntries = useContributions(LoopChatSuggestion);
|
|
629
|
+
const hiddenOpeners = chatExtras.hiddenOpeners;
|
|
630
|
+
const suggestions = useMemo(() => {
|
|
631
|
+
// What the host took off this page, by the chip's words — the same key
|
|
632
|
+
// the two-group merge below uses to tell openers apart.
|
|
633
|
+
const hidden = new Set(hiddenOpeners ?? []);
|
|
634
|
+
const offered = (items) => hidden.size === 0 ? items : items.filter(item => !hidden.has(item.text));
|
|
635
|
+
const contributed = suggestionEntries.flatMap(entry => entry.value.suggestions);
|
|
636
|
+
if (contributed.length > 0) {
|
|
637
|
+
return offered(contributed);
|
|
638
|
+
}
|
|
639
|
+
const own = offered(spec?.suggestions ?? []);
|
|
640
|
+
const teams = offered(team?.team.suggestions ?? []);
|
|
641
|
+
if (teams.length === 0) {
|
|
642
|
+
return own;
|
|
643
|
+
}
|
|
644
|
+
// Two groups, named for the two levels the empty state draws: the
|
|
645
|
+
// team's openers under the team's name, the member's own under its.
|
|
646
|
+
const listed = new Set(teams.map(item => item.text));
|
|
647
|
+
const teamName = team?.team.name ?? 'Team';
|
|
648
|
+
const memberName = member?.name ?? spec?.name ?? agentId;
|
|
649
|
+
return [
|
|
650
|
+
...teams.map(item => ({ ...item, group: teamName })),
|
|
651
|
+
...own
|
|
652
|
+
.filter(item => !listed.has(item.text))
|
|
653
|
+
.map(item => ({ ...item, group: memberName })),
|
|
654
|
+
];
|
|
655
|
+
}, [suggestionEntries, team, spec, member, agentId, hiddenOpeners]);
|
|
656
|
+
/*
|
|
657
|
+
* The same openers in the shape the chat's empty state asks for.
|
|
658
|
+
*
|
|
659
|
+
* `Suggestion` is a title and a message — what a chip shows and what it
|
|
660
|
+
* sends. A spec's opener with a `summary` shows that and sends its text;
|
|
661
|
+
* one without shows the whole request; a contributed opener may split the
|
|
662
|
+
* two its own way.
|
|
663
|
+
*/
|
|
664
|
+
const chatSuggestions = useMemo(() => suggestions.map(item => ({
|
|
665
|
+
title: item.summary ?? item.text,
|
|
666
|
+
message: item.message ?? item.text,
|
|
667
|
+
group: item.group,
|
|
668
|
+
})), [suggestions]);
|
|
669
|
+
/* The icon its spec asked for, at the size the empty state draws. */
|
|
670
|
+
const BrandIcon = agentIcon(spec?.icon);
|
|
671
|
+
/* And the team's, for the level above it. */
|
|
672
|
+
const TeamIcon = agentIcon(team?.team.icon);
|
|
673
|
+
/*
|
|
674
|
+
* How an in-page agent reaches the notebook.
|
|
675
|
+
*
|
|
676
|
+
* On every other target the agent runs on a server with a sandbox of its
|
|
677
|
+
* own, and "run this cell" happens there — in the same kernel the notebook
|
|
678
|
+
* is bound to, which is why it appears to work by magic. In the browser
|
|
679
|
+
* there is no server and no second kernel: the only way to touch the
|
|
680
|
+
* notebook is a frontend tool, executed here.
|
|
681
|
+
*
|
|
682
|
+
* Without these the browser agent could hold a conversation and do nothing,
|
|
683
|
+
* which is exactly how it behaved — it answered, and the cell never ran.
|
|
684
|
+
*
|
|
685
|
+
* From the chat's own extension point rather than a hard-wired notebook
|
|
686
|
+
* import: the notebook plugin contributes its cell tools, the document its
|
|
687
|
+
* lexical ones, and a chat mounted without either simply has fewer tools.
|
|
688
|
+
* Each factory runs against the live workspace, so tools addressed by
|
|
689
|
+
* `surfaceId` can never point at a different notebook than the one on
|
|
690
|
+
* screen.
|
|
691
|
+
*/
|
|
692
|
+
const toolContributions = useContributions(LoopFrontendTool);
|
|
693
|
+
const notebookTools = useMemo(() => {
|
|
694
|
+
/*
|
|
695
|
+
* One tool per name, first contribution wins — so the order is decided
|
|
696
|
+
* here rather than by which lazy import landed first. The notebook and
|
|
697
|
+
* the document each ship a kernel tool — `executeCodeInNotebook` runs in the
|
|
698
|
+
* sandbox and streams onto the conversation, the document's runs on the
|
|
699
|
+
* document's own kernel and returns silently. The editor on screen owns
|
|
700
|
+
* the shared names; with none on screen the notebook does, since its
|
|
701
|
+
* outputs are what the conversation shows. The document's block tools
|
|
702
|
+
* (runBlock, insertBlock…) keep their own names and are untouched.
|
|
703
|
+
*/
|
|
704
|
+
const merged = [];
|
|
705
|
+
const owners = new Map();
|
|
706
|
+
// Tools a contribution vouched for in the chat view; see `chatView`.
|
|
707
|
+
const keep = new Set();
|
|
708
|
+
// See `isInactiveSurfaceContribution`: with an editor on screen, only
|
|
709
|
+
// its own `${surfaceId}-tools` contribution survives this filter.
|
|
710
|
+
const surfaceIds = new Set(surfaces.map(entry => entry.value.surfaceId));
|
|
711
|
+
for (const entry of orderToolContributions(toolContributions, active?.surfaceId)) {
|
|
712
|
+
if (isInactiveSurfaceContribution(entry.value.id, surfaceIds, active?.surfaceId)) {
|
|
713
|
+
continue;
|
|
714
|
+
}
|
|
715
|
+
let tools;
|
|
716
|
+
try {
|
|
717
|
+
tools = entry.value.tools(workspace);
|
|
718
|
+
}
|
|
719
|
+
catch (error) {
|
|
720
|
+
// One broken contributor must not cost the agent every tool.
|
|
721
|
+
console.warn(`[loop] frontend tools from '${entry.plugin}' failed:`, error);
|
|
722
|
+
continue;
|
|
723
|
+
}
|
|
724
|
+
for (const tool of tools) {
|
|
725
|
+
const owner = owners.get(tool.name);
|
|
726
|
+
if (owner) {
|
|
727
|
+
console.warn(`[loop] frontend tool '${tool.name}' from '${entry.plugin}' is shadowed by the one from '${owner}'.`);
|
|
728
|
+
continue;
|
|
729
|
+
}
|
|
730
|
+
owners.set(tool.name, entry.plugin);
|
|
731
|
+
merged.push(tool);
|
|
732
|
+
if (entry.value.chatView) {
|
|
733
|
+
keep.add(tool.name);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
// A host example's own frontend tools, folded in last (first name wins),
|
|
738
|
+
// so a bespoke client tool like A2UI's run_jupyter_output_demo joins the
|
|
739
|
+
// agent's toolset without a plugin of its own.
|
|
740
|
+
for (const tool of chatExtras.frontendTools ?? []) {
|
|
741
|
+
if (owners.has(tool.name)) {
|
|
742
|
+
continue;
|
|
743
|
+
}
|
|
744
|
+
owners.set(tool.name, 'chat-extras');
|
|
745
|
+
merged.push(tool);
|
|
746
|
+
}
|
|
747
|
+
/*
|
|
748
|
+
* The view decides the toolset. With an editor on screen the agent has
|
|
749
|
+
* that editor's own tools (`belongsToInactiveSurface` above, plus
|
|
750
|
+
* whatever isn't tied to a surface at all); with none — the chat view —
|
|
751
|
+
* it keeps only what leaves the editors alone: the `executeCodeIn…`
|
|
752
|
+
* tools, whose outputs stream onto the conversation as surfaces, and
|
|
753
|
+
* the read tools. The editors stay mounted out of sight either way, so
|
|
754
|
+
* what it can still call, works.
|
|
755
|
+
*/
|
|
756
|
+
return active ? merged : toolsForChatView(merged, keep);
|
|
757
|
+
// The workspace object is rebuilt when its facts change; surfaceId is
|
|
758
|
+
// the one the factories address by.
|
|
759
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
760
|
+
}, [
|
|
761
|
+
toolContributions,
|
|
762
|
+
workspace.surfaceId,
|
|
763
|
+
workspace.agentId,
|
|
764
|
+
chatExtras.frontendTools,
|
|
765
|
+
active?.surfaceId,
|
|
766
|
+
surfaces,
|
|
767
|
+
]);
|
|
768
|
+
/*
|
|
769
|
+
* The commands the mounted plugins declared for an agent — the reactor's
|
|
770
|
+
* `AgentTools` point — executed on this workspace's own reactor. Folded in
|
|
771
|
+
* after the contributions, which keep their names, so a plugin with a
|
|
772
|
+
* richer in-page implementation of the same tool (the decks plugin's) is
|
|
773
|
+
* what the agent gets, and the bundle's command is the fallback. The agent
|
|
774
|
+
* spec names none of these: they follow the plugin.
|
|
775
|
+
*/
|
|
776
|
+
const commandTools = useAgentCommandTools();
|
|
777
|
+
const agentTools = useMemo(() => {
|
|
778
|
+
if (commandTools.length === 0) {
|
|
779
|
+
return notebookTools;
|
|
780
|
+
}
|
|
781
|
+
const taken = new Set(notebookTools.map(tool => tool.name));
|
|
782
|
+
return [
|
|
783
|
+
...notebookTools,
|
|
784
|
+
...commandTools.filter(tool => !taken.has(tool.name)),
|
|
785
|
+
];
|
|
786
|
+
}, [notebookTools, commandTools]);
|
|
787
|
+
/*
|
|
788
|
+
* Where this workspace's agent actually lives.
|
|
789
|
+
*
|
|
790
|
+
* A Datalayer runtime brings its own agent-runtimes server: the agent is
|
|
791
|
+
* created on the pod from an agentspec, not on the server the workspace was
|
|
792
|
+
* opened against. Addressing the latter would be talking to a machine that
|
|
793
|
+
* has never heard of it. The same value for every other target, where the
|
|
794
|
+
* two are the same server.
|
|
795
|
+
*/
|
|
796
|
+
const agentServerUrl = workspace.sandbox.agentBaseUrl || workspace.serverUrl;
|
|
797
|
+
/*
|
|
798
|
+
* Who a single request may be addressed to.
|
|
799
|
+
*
|
|
800
|
+
* The same set the harness gets as tools, and deliberately so: a menu that
|
|
801
|
+
* offered a name the model could not reach would be a menu that produces
|
|
802
|
+
* silent no-ops. Derived here rather than in the plugin because it changes
|
|
803
|
+
* with the selected member.
|
|
804
|
+
*/
|
|
805
|
+
const mentionable = useMemo(() => {
|
|
806
|
+
if (!team || !member) {
|
|
807
|
+
// No team: the spec's own subagents, which the in-page loop can reach
|
|
808
|
+
// and a server-side one already could.
|
|
809
|
+
return specSubagents(spec).map(subagent => ({
|
|
810
|
+
name: subagent.name,
|
|
811
|
+
description: subagent.description,
|
|
812
|
+
icon: agentIcon(subagent.icon),
|
|
813
|
+
}));
|
|
814
|
+
}
|
|
815
|
+
/*
|
|
816
|
+
* The team, plus whatever specialists the selected member brings.
|
|
817
|
+
*
|
|
818
|
+
* It used to be `subagentsFor(member)` alone, which made the list read as
|
|
819
|
+
* arbitrary: a team of two offered one name, and the name was whichever
|
|
820
|
+
* member you were *not* talking to. Everyone the team contains is listed,
|
|
821
|
+
* because "who is on this team" is the question a person types `@` to
|
|
822
|
+
* ask; the one already being addressed is shown greyed rather than
|
|
823
|
+
* dropped, so the list is stable as the selection moves.
|
|
824
|
+
*/
|
|
825
|
+
const roster = team.members.map(entry => ({
|
|
826
|
+
name: entry.name,
|
|
827
|
+
description: entry.description,
|
|
828
|
+
emoji: entry.emoji,
|
|
829
|
+
icon: agentIcon(entry.icon),
|
|
830
|
+
disabled: entry.id === member.id,
|
|
831
|
+
disabledReason: entry.id === member.id
|
|
832
|
+
? `You are already talking to ${entry.name}`
|
|
833
|
+
: undefined,
|
|
834
|
+
}));
|
|
835
|
+
const specialists = subagentsFor(team.team, member.id)
|
|
836
|
+
.filter(subagent => !team.members.some(m => m.name === subagent.name))
|
|
837
|
+
.map(subagent => ({
|
|
838
|
+
name: subagent.name,
|
|
839
|
+
description: subagent.description,
|
|
840
|
+
icon: agentIcon(subagent.icon),
|
|
841
|
+
}));
|
|
842
|
+
return [...roster, ...specialists];
|
|
843
|
+
}, [team, member, spec]);
|
|
844
|
+
/*
|
|
845
|
+
* Where the model is reached, and on whose key.
|
|
846
|
+
*
|
|
847
|
+
* Told whether an in-page agent is going to ask at all: a workspace on a
|
|
848
|
+
* server-backed agent authenticates itself, and minting a visitor's one
|
|
849
|
+
* trial key for it would start a clock against a conversation that never
|
|
850
|
+
* spends it.
|
|
851
|
+
*/
|
|
852
|
+
const { inference, anonymous } = useBrowserInference(inPage);
|
|
853
|
+
/* Read from the store rather than passed in: the sign-in form inside the
|
|
854
|
+
expiry panel writes here, so the same render that gains a member gains
|
|
855
|
+
the wording that goes with one. */
|
|
856
|
+
const signedInUser = useIAMStore(state => state.user);
|
|
857
|
+
/* The trial is over, and this chat is the thing that stops working. Only
|
|
858
|
+
for an in-page agent: everywhere else the runtime holds its own
|
|
859
|
+
credentials and never saw the visitor's key. */
|
|
860
|
+
const keyExpired = inPage && anonymous.status === 'expired';
|
|
861
|
+
/*
|
|
862
|
+
* Whose key ran out, which decides what the chat is allowed to call it.
|
|
863
|
+
*
|
|
864
|
+
* The anonymous store only ever holds the trial key, so reaching this
|
|
865
|
+
* through `anonymous.status` means the reader never signed in. A member
|
|
866
|
+
* whose own session expires arrives by a different route and must not be
|
|
867
|
+
* told a temporary key ran out — they never had one, and would go looking
|
|
868
|
+
* for something that was never theirs.
|
|
869
|
+
*/
|
|
870
|
+
const expiredKeyIsTemporary = !signedInUser;
|
|
871
|
+
const chatDisabled = gateBlocked || keyExpired;
|
|
872
|
+
const disabledReason = keyExpired
|
|
873
|
+
? expiredKeyIsTemporary
|
|
874
|
+
? 'This demo runs on a shared key, and its time is up. Sign in to keep going.'
|
|
875
|
+
: 'Your key has expired. Sign in to keep going.'
|
|
876
|
+
: gateReason;
|
|
877
|
+
/*
|
|
878
|
+
* The prompt's text, held here rather than inside `InputPrompt`.
|
|
879
|
+
*
|
|
880
|
+
* A page embedding the workspace can offer a request — "try asking it
|
|
881
|
+
* this" — and the only honest place to put such an offer is the box the
|
|
882
|
+
* visitor would have typed it into, where they can read it, change their
|
|
883
|
+
* mind, or send it. That needs a controlled prompt, which needs the text to
|
|
884
|
+
* live somewhere both the suggestion and the composer can reach.
|
|
885
|
+
*/
|
|
886
|
+
const [draft, setDraft] = useState('');
|
|
887
|
+
/*
|
|
888
|
+
* The model this conversation is on.
|
|
889
|
+
*
|
|
890
|
+
* Three answers, and the order matters more than it looks. The reader's own
|
|
891
|
+
* pick wins, then the *agent's spec*, then whatever the host mounted the
|
|
892
|
+
* workspace with.
|
|
893
|
+
*
|
|
894
|
+
* The spec used to come last, behind `workspace.model` — which is a
|
|
895
|
+
* workspace-wide default, set once for whichever agent happens to be
|
|
896
|
+
* addressed. So launching an agent whose spec names one model showed a
|
|
897
|
+
* different one ticked in the menu, and the footer disagreed with the thing
|
|
898
|
+
* actually answering. An agent's spec is the definition of that agent; a
|
|
899
|
+
* host default is a fallback for when there is no definition, and a fallback
|
|
900
|
+
* that overrides the definition is not a fallback.
|
|
901
|
+
*
|
|
902
|
+
* Held here rather than in the workspace because the choice belongs to the
|
|
903
|
+
* conversation: it is what the next message is sent with, and it resets when
|
|
904
|
+
* the member being addressed changes.
|
|
905
|
+
*/
|
|
906
|
+
const [pickedModel, setPickedModel] = useState();
|
|
907
|
+
const activeModel = pickedModel ?? spec?.model ?? workspace.model ?? '';
|
|
908
|
+
/*
|
|
909
|
+
* The team, in the shape the footer asks for.
|
|
910
|
+
*
|
|
911
|
+
* The footer knows nothing about teamspecs, and should not: it renders a
|
|
912
|
+
* list of names and reports which one was chosen. Everything that makes a
|
|
913
|
+
* member a member stays on this side of the boundary.
|
|
914
|
+
*/
|
|
915
|
+
/* What is left of the context window, as the runtime last reported it. The
|
|
916
|
+
footer shows nothing at all until there is a real number, so a workspace
|
|
917
|
+
whose agent has not answered yet simply has no bar. */
|
|
918
|
+
/*
|
|
919
|
+
* The window, and only for the agent being addressed now.
|
|
920
|
+
*
|
|
921
|
+
* The store keeps one snapshot rather than one per agent, so switching
|
|
922
|
+
* member left the previous one's numbers on screen under the new one's
|
|
923
|
+
* name. Ignored until the store hands back a different object, which is the
|
|
924
|
+
* only evidence available that the new agent has reported.
|
|
925
|
+
*/
|
|
926
|
+
const storedUsage = useAgentRuntimeContextSnapshot();
|
|
927
|
+
const usageAtSwitch = useRef(storedUsage);
|
|
928
|
+
const addressedAgent = useRef(selectedMemberId);
|
|
929
|
+
const storedUsageRef = useRef(storedUsage);
|
|
930
|
+
storedUsageRef.current = storedUsage;
|
|
931
|
+
useEffect(() => {
|
|
932
|
+
if (addressedAgent.current === selectedMemberId) {
|
|
933
|
+
return;
|
|
934
|
+
}
|
|
935
|
+
addressedAgent.current = selectedMemberId;
|
|
936
|
+
usageAtSwitch.current = storedUsageRef.current;
|
|
937
|
+
// And the model goes back to whatever the new agent's spec asks for. A
|
|
938
|
+
// pick made for one member is not a statement about the next.
|
|
939
|
+
setPickedModel(undefined);
|
|
940
|
+
}, [selectedMemberId]);
|
|
941
|
+
const fromStore = storedUsage && storedUsage !== usageAtSwitch.current ? storedUsage : null;
|
|
942
|
+
/*
|
|
943
|
+
* What the chat itself accounts for, when nothing else does.
|
|
944
|
+
*
|
|
945
|
+
* A server-side runtime pushes a snapshot over the socket and that is what
|
|
946
|
+
* `fromStore` holds. An in-page agent has no server to push one, and its
|
|
947
|
+
* only account of the window is the totals its harness reports — which
|
|
948
|
+
* `ChatBase` assembles and hands out. Without this the browser agent's
|
|
949
|
+
* footer had no numbers at all, and the space kept for them showed as a
|
|
950
|
+
* white stripe under the prompt.
|
|
951
|
+
*/
|
|
952
|
+
const [chatUsage, setChatUsage] = useState();
|
|
953
|
+
const contextUsage = fromStore ?? chatUsage ?? null;
|
|
954
|
+
// Read to decide whether the strip exists at all, not to render it.
|
|
955
|
+
const inpromptMenu = useSlotComponents(LoopSlots.inpromptMenu);
|
|
956
|
+
// Buttons for the prompt's footer bar, beside the session menus. Asked
|
|
957
|
+
// whether anyone contributed before anything is drawn, for the same reason
|
|
958
|
+
// as the in-prompt menu: an empty slot is still an element.
|
|
959
|
+
const promptActions = useSlotComponents(LoopSlots.promptAction);
|
|
960
|
+
// Same question for the chat's own title bar, which plugins may add to.
|
|
961
|
+
const chatHeaderItems = useSlotComponents(LoopSlots.chatHeader);
|
|
962
|
+
const footerAgents = useMemo(() => team?.members.map(entry => ({
|
|
963
|
+
id: entry.id,
|
|
964
|
+
name: entry.name,
|
|
965
|
+
description: entry.description,
|
|
966
|
+
icon: agentIcon(entry.icon),
|
|
967
|
+
})) ?? [], [team]);
|
|
968
|
+
const suggestion = useLoopPromptStore(state => state.pending);
|
|
969
|
+
/*
|
|
970
|
+
* `/prompt`, or its keystroke, asking for the caret.
|
|
971
|
+
*
|
|
972
|
+
* A command runs outside the component tree, so the ask arrives on the
|
|
973
|
+
* focus channel; each one bumps a nonce that joins the suggestion's in
|
|
974
|
+
* `focusTrigger` below. The chat answers wherever its composer is —
|
|
975
|
+
* docked, in-column, or floating.
|
|
976
|
+
*/
|
|
977
|
+
const [focusAsked, setFocusAsked] = useState(0);
|
|
978
|
+
useEffect(() => onPromptFocusRequest(() => setFocusAsked(nonce => nonce + 1)), []);
|
|
979
|
+
useEffect(() => {
|
|
980
|
+
if (!suggestion) {
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
setDraft(suggestion.text);
|
|
984
|
+
// Taken, so a second click on the same button offers it again rather than
|
|
985
|
+
// finding the store already holding it.
|
|
986
|
+
useLoopPromptStore.getState().consume();
|
|
987
|
+
if (suggestion.submit) {
|
|
988
|
+
void handleSend(suggestion.text);
|
|
989
|
+
}
|
|
990
|
+
// `handleSend` is stable for the life of a connection; re-running this on
|
|
991
|
+
// a new identity would resend the last suggestion.
|
|
992
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
993
|
+
}, [suggestion]);
|
|
994
|
+
const protocol = useMemo(() => inPage
|
|
995
|
+
? browserProtocolConfig({
|
|
996
|
+
agentId: member?.specId ?? agentId,
|
|
997
|
+
instructions: spec?.systemPrompt,
|
|
998
|
+
model: activeModel || undefined,
|
|
999
|
+
inference,
|
|
1000
|
+
// Who this member may hand work to, and what they are told. Both
|
|
1001
|
+
// come from the teamspec: the supervisor routes to the others, a
|
|
1002
|
+
// member reaches only its own specialists, and `context.sharing`
|
|
1003
|
+
// decides whether the child sees this conversation.
|
|
1004
|
+
// The agent runs them itself, so they go to the harness rather
|
|
1005
|
+
// than to the chat — handing them to both would run each tool
|
|
1006
|
+
// twice.
|
|
1007
|
+
frontendTools: agentTools,
|
|
1008
|
+
// A team's members and specialists when there is a team; the
|
|
1009
|
+
// spec's own subagents otherwise — a researcher and a writer are
|
|
1010
|
+
// as much the agent's as its prompt is.
|
|
1011
|
+
subagents: team && member
|
|
1012
|
+
? subagentsFor(team.team, member.id)
|
|
1013
|
+
: specSubagents(spec),
|
|
1014
|
+
sharing: team?.sharing,
|
|
1015
|
+
// What a delegated run does lands in the same store the
|
|
1016
|
+
// server's monitoring stream fills, so the side panel and the
|
|
1017
|
+
// composer's pulse draw an in-page delegation like any other.
|
|
1018
|
+
onSubagentEvent: event => agentRuntimeStore.getState().appendSubagentEvent(event),
|
|
1019
|
+
})
|
|
1020
|
+
: {
|
|
1021
|
+
type: 'ag-ui',
|
|
1022
|
+
endpoint: `${agentServerUrl}/api/v1/ag-ui/${agentId}/`,
|
|
1023
|
+
agentId,
|
|
1024
|
+
// `/api/v1/configure`, not `/api/v1/configure/config`: the hooks
|
|
1025
|
+
// strip one trailing `config`/`configure` segment to find the API
|
|
1026
|
+
// base, so the longer form doubles it.
|
|
1027
|
+
configEndpoint: `${agentServerUrl}/api/v1/configure`,
|
|
1028
|
+
enableConfigQuery: true,
|
|
1029
|
+
}, [
|
|
1030
|
+
agentId,
|
|
1031
|
+
inPage,
|
|
1032
|
+
inference,
|
|
1033
|
+
member,
|
|
1034
|
+
agentTools,
|
|
1035
|
+
team,
|
|
1036
|
+
spec,
|
|
1037
|
+
activeModel,
|
|
1038
|
+
agentServerUrl,
|
|
1039
|
+
]);
|
|
1040
|
+
/*
|
|
1041
|
+
* The model catalogue, and the builtin tools and skills the agent reports.
|
|
1042
|
+
*
|
|
1043
|
+
* Read here rather than left to `ChatBase`, because this view draws its own
|
|
1044
|
+
* prompt: the toolbar under it can only offer what this component hands it.
|
|
1045
|
+
*
|
|
1046
|
+
* `useSkills` reads the runtime's codemode status out of the store, so it
|
|
1047
|
+
* needs no endpoint and works for an in-page agent too. `useConfig` does
|
|
1048
|
+
* need one, and a browser agent has none — hence the menus below being
|
|
1049
|
+
* gated on the data rather than switched on unconditionally.
|
|
1050
|
+
*/
|
|
1051
|
+
const configQuery = useConfig(!inPage, protocol.type === 'ag-ui' ? protocol.configEndpoint : undefined, undefined, agentId);
|
|
1052
|
+
const skillsQuery = useSkills(true);
|
|
1053
|
+
const skillActions = useSkillActions(agentId);
|
|
1054
|
+
const [catalogModels, setCatalogModels] = useState([]);
|
|
1055
|
+
useEffect(() => {
|
|
1056
|
+
/*
|
|
1057
|
+
* Only where there is a server to ask.
|
|
1058
|
+
*
|
|
1059
|
+
* An in-page agent has none, and `agentServerUrl` then holds whatever the
|
|
1060
|
+
* host defaulted to — on a public page that was a developer's localhost,
|
|
1061
|
+
* so every visitor's console carried a connection-refused error for a
|
|
1062
|
+
* catalogue that was never going to arrive. The agentspecs catalogue below
|
|
1063
|
+
* is the answer for this case and needs no request at all.
|
|
1064
|
+
*/
|
|
1065
|
+
if (inPage) {
|
|
1066
|
+
return undefined;
|
|
1067
|
+
}
|
|
1068
|
+
let cancelled = false;
|
|
1069
|
+
void fetch(`${agentServerUrl}/api/v1/configure/models`)
|
|
1070
|
+
.then(response => (response.ok ? response.json() : { models: [] }))
|
|
1071
|
+
.then(payload => {
|
|
1072
|
+
if (!cancelled) {
|
|
1073
|
+
setCatalogModels(payload.models ?? []);
|
|
1074
|
+
}
|
|
1075
|
+
})
|
|
1076
|
+
// No catalogue is not an error worth a banner: the menu simply has
|
|
1077
|
+
// nothing to offer, and the agent answers on whatever it was given.
|
|
1078
|
+
.catch(() => undefined);
|
|
1079
|
+
return () => {
|
|
1080
|
+
cancelled = true;
|
|
1081
|
+
};
|
|
1082
|
+
}, [agentServerUrl, inPage]);
|
|
1083
|
+
/*
|
|
1084
|
+
* What to offer when the server has no catalogue of its own.
|
|
1085
|
+
*
|
|
1086
|
+
* `/configure/models` answers for a server-backed agent and is
|
|
1087
|
+
* authoritative. An in-page agent has no server to ask, and a model menu
|
|
1088
|
+
* with nothing in it is a control that opens onto an empty list — so the
|
|
1089
|
+
* agentspecs catalogue stands in, filtered to the models marked available.
|
|
1090
|
+
*/
|
|
1091
|
+
const offeredModels = useMemo(() => catalogModels.length > 0
|
|
1092
|
+
? catalogModels
|
|
1093
|
+
: Object.values(AI_MODEL_CATALOGUE)
|
|
1094
|
+
.filter(model => model.available)
|
|
1095
|
+
.map(model => ({
|
|
1096
|
+
id: model.id,
|
|
1097
|
+
name: model.name,
|
|
1098
|
+
provider: model.provider,
|
|
1099
|
+
})), [catalogModels]);
|
|
1100
|
+
/*
|
|
1101
|
+
* The tools the agent can call, from both sides.
|
|
1102
|
+
*
|
|
1103
|
+
* The server's builtins, and the notebook tools this workspace hands the
|
|
1104
|
+
* harness — which are the only ones an in-page agent has, and the reason
|
|
1105
|
+
* "run this cell" works at all. Listing a tool and running it are different
|
|
1106
|
+
* jobs, so a tool the chat does not execute still belongs in the menu.
|
|
1107
|
+
*/
|
|
1108
|
+
const offeredTools = useMemo(() => {
|
|
1109
|
+
const fromServer = configQuery.data?.builtinTools ?? [];
|
|
1110
|
+
const seen = new Set(fromServer.map(tool => tool.name));
|
|
1111
|
+
return [
|
|
1112
|
+
...fromServer,
|
|
1113
|
+
...agentTools
|
|
1114
|
+
.filter(tool => !seen.has(tool.name))
|
|
1115
|
+
.map(tool => ({ id: tool.name, name: tool.name })),
|
|
1116
|
+
];
|
|
1117
|
+
}, [configQuery.data?.builtinTools, agentTools]);
|
|
1118
|
+
/* Which skills are on, as the runtime last reported them. Derived rather
|
|
1119
|
+
than held: the source of truth is the agent, and a local copy would drift
|
|
1120
|
+
the moment a skill was enabled from anywhere else. */
|
|
1121
|
+
const enabledSkills = useMemo(() => new Set((skillsQuery.data?.skills ?? [])
|
|
1122
|
+
// `enabled` and `loaded` both mean the agent has it; `available`
|
|
1123
|
+
// means it could. There is no `disabled` — a skill that is off is
|
|
1124
|
+
// simply one that is merely available.
|
|
1125
|
+
.filter(skill => skill.status === 'enabled' || skill.status === 'loaded')
|
|
1126
|
+
.map(skill => skill.id)), [skillsQuery.data]);
|
|
1127
|
+
/*
|
|
1128
|
+
* Taken here first, then told to the server.
|
|
1129
|
+
*
|
|
1130
|
+
* Locally, because an in-page agent has no server to tell and its model is
|
|
1131
|
+
* whatever this view hands the harness — without recording the choice, the
|
|
1132
|
+
* menu ticked a row for a moment and the next message went to the old model.
|
|
1133
|
+
* And on the server for the agents that live there, where the chip in its
|
|
1134
|
+
* header makes the same call: whichever control a person used, the other has
|
|
1135
|
+
* to agree.
|
|
1136
|
+
*/
|
|
1137
|
+
const selectModel = useCallback(async (model) => {
|
|
1138
|
+
setPickedModel(model);
|
|
1139
|
+
if (inPage) {
|
|
1140
|
+
return;
|
|
1141
|
+
}
|
|
1142
|
+
await fetch(`${agentServerUrl}/api/v1/configure/inference/provider`, {
|
|
1143
|
+
method: 'PUT',
|
|
1144
|
+
headers: { 'content-type': 'application/json' },
|
|
1145
|
+
body: JSON.stringify({ model }),
|
|
1146
|
+
}).catch(() => undefined);
|
|
1147
|
+
}, [agentServerUrl, inPage]);
|
|
1148
|
+
/* `bottom-chat` keeps the prompt in the chat column; `bottom`, the default,
|
|
1149
|
+
spans the workspace. */
|
|
1150
|
+
const besideChat = config?.promptPlacement === 'bottom-chat';
|
|
1151
|
+
/* `floating` renders the same composer in a draggable card over the
|
|
1152
|
+
workspace — the prompt below simply gains `draggable`, and the card takes
|
|
1153
|
+
itself out of the layout. */
|
|
1154
|
+
const floatingPrompt = config?.promptPlacement === 'floating';
|
|
1155
|
+
/* `top` puts the composer above the editors as a horizontal bar, with the
|
|
1156
|
+
openers as chips under it — for a host whose editor is the point. */
|
|
1157
|
+
const topPrompt = config?.promptPlacement === 'top';
|
|
1158
|
+
/* No prompt at all, for a workspace where something else owns the typing —
|
|
1159
|
+
by configuration, or live through the extras channel when the host
|
|
1160
|
+
changes its mind while the workspace is running. */
|
|
1161
|
+
const promptHidden = config?.hidePrompt === true || chatExtras.hidePrompt === true;
|
|
1162
|
+
/*
|
|
1163
|
+
* The composer, assembled here and rendered by whoever contributed to the
|
|
1164
|
+
* `LoopChatComposer` point — the input-prompt plugin, normally.
|
|
1165
|
+
*
|
|
1166
|
+
* The wiring is this view's knowledge: the controlled draft (so an outside
|
|
1167
|
+
* suggestion has somewhere to land, with `focusTrigger` putting the caret
|
|
1168
|
+
* back), the lexical variant (the `@` menu needs it), the menus' data with
|
|
1169
|
+
* every control drawn even when its list is empty (a menu that opens onto
|
|
1170
|
+
* "0" is a statement; a missing control is not), the `promptAction` and
|
|
1171
|
+
* `inpromptMenu` slots (asked whether anyone contributed first — an empty
|
|
1172
|
+
* `ReactorSlot` is still an element and would pad out a band of nothing).
|
|
1173
|
+
* The surface is the plugin's: no contribution, no composer, which is the
|
|
1174
|
+
* honest outcome for a workspace mounted without one.
|
|
1175
|
+
*/
|
|
1176
|
+
const composerProps = {
|
|
1177
|
+
onSend: () => void handleSend(draft),
|
|
1178
|
+
input: draft,
|
|
1179
|
+
setInput: setDraft,
|
|
1180
|
+
focusTrigger: suggestion || focusAsked
|
|
1181
|
+
? (suggestion?.nonce ?? 0) + focusAsked
|
|
1182
|
+
: undefined,
|
|
1183
|
+
// In a draggable card over the workspace when the host asked for the
|
|
1184
|
+
// floating placement; the dragging is the prompt's own feature.
|
|
1185
|
+
placement: floatingPrompt ||
|
|
1186
|
+
layout?.prompt === 'floating' ||
|
|
1187
|
+
layout?.prompt === 'floating-top'
|
|
1188
|
+
? 'floating'
|
|
1189
|
+
: 'docked',
|
|
1190
|
+
// Where a floating card starts: the page layout wants it over the top
|
|
1191
|
+
// of the sheet, like a document's title bar; everyone else, bottom.
|
|
1192
|
+
floatingAnchor: layout?.prompt === 'floating-top' ? 'top' : 'bottom',
|
|
1193
|
+
// The agent is working: `isLoading` disables the editor and turns the
|
|
1194
|
+
// send button into a stop — the person keeps a way out.
|
|
1195
|
+
isLoading: busy,
|
|
1196
|
+
onStop: () => workspace.viewControls.stop?.(),
|
|
1197
|
+
// The reason where the typing would go, read before the person types.
|
|
1198
|
+
placeholder: disabledReason ?? placeholder,
|
|
1199
|
+
typingSuggestions: suggestions.map(item => item.text),
|
|
1200
|
+
// The same openers behind the composer's suggestions control.
|
|
1201
|
+
suggestions: chatSuggestions,
|
|
1202
|
+
disableInputPrompt: chatDisabled,
|
|
1203
|
+
connectionConfirmed: !chatDisabled,
|
|
1204
|
+
// Tighter on top: every pixel of the composer is a pixel of the editor
|
|
1205
|
+
// under it pushed down, and the bar reads as a command line at this size.
|
|
1206
|
+
// Both of the page layout's placements are that bar — one floating over
|
|
1207
|
+
// the page, one docked above it — so both take the tighter padding.
|
|
1208
|
+
padding: topPrompt ||
|
|
1209
|
+
layout?.prompt === 'floating-top' ||
|
|
1210
|
+
layout?.prompt === 'docked-top'
|
|
1211
|
+
? 2
|
|
1212
|
+
: 3,
|
|
1213
|
+
promptVariant: 'lexical',
|
|
1214
|
+
// True unless the host asked otherwise: the workspace is there to be typed
|
|
1215
|
+
// into. A page that embeds the loop as one section among many turns it off,
|
|
1216
|
+
// because focusing an input on mount scrolls the browser to it.
|
|
1217
|
+
autoFocus: config?.autoFocusPrompt ?? true,
|
|
1218
|
+
mentionableAgents: mentionable,
|
|
1219
|
+
headerContent: inpromptMenu.length > 0 ? (_jsx(ReactorSlot, { slot: LoopSlots.inpromptMenu, props: { workspace } })) : undefined,
|
|
1220
|
+
footerExtras: promptActions.length > 0 ? (_jsx(ReactorSlot, { slot: LoopSlots.promptAction, props: { workspace } })) : undefined,
|
|
1221
|
+
showAgentsMenu: true,
|
|
1222
|
+
showInlineAgentsMenu: false,
|
|
1223
|
+
agents: footerAgents,
|
|
1224
|
+
selectedAgentId: selectedMemberId,
|
|
1225
|
+
onSelectAgent: id => team?.select(id),
|
|
1226
|
+
// On unless the host said otherwise: a public page turns the counters
|
|
1227
|
+
// off, because they answer questions a visitor is not asking.
|
|
1228
|
+
showTokenUsage: chatExtras.showTokenUsage ?? true,
|
|
1229
|
+
showContextRing: true,
|
|
1230
|
+
agentUsage: contextUsage ?? undefined,
|
|
1231
|
+
showModelSelector: true,
|
|
1232
|
+
models: offeredModels,
|
|
1233
|
+
selectedModel: activeModel,
|
|
1234
|
+
onModelSelect: model => void selectModel(model),
|
|
1235
|
+
showToolsMenu: true,
|
|
1236
|
+
availableTools: offeredTools,
|
|
1237
|
+
mcpServers: configQuery.data?.mcpServers ?? [],
|
|
1238
|
+
// Live from the host example, when one feeds it: the footer's MCP status
|
|
1239
|
+
// indicator. (Codemode surfaces through the toggle below, not a blob.)
|
|
1240
|
+
mcpStatusData: chatExtras.mcpStatusData ?? undefined,
|
|
1241
|
+
showSkillsMenu: true,
|
|
1242
|
+
skills: skillsQuery.data?.skills ?? [],
|
|
1243
|
+
skillsLoading: skillsQuery.isLoading,
|
|
1244
|
+
enabledSkills,
|
|
1245
|
+
onToggleSkill: skillId => enabledSkills.has(skillId)
|
|
1246
|
+
? skillActions.disableSkill(skillId)
|
|
1247
|
+
: skillActions.enableSkill(skillId),
|
|
1248
|
+
onToggleAllSkills: (skillIds, enable) => skillIds.forEach(skillId => enable
|
|
1249
|
+
? skillActions.enableSkill(skillId)
|
|
1250
|
+
: skillActions.disableSkill(skillId)),
|
|
1251
|
+
codemodeEnabled: chatExtras.codemodeEnabled ?? false,
|
|
1252
|
+
onToggleCodemode: chatExtras.onToggleCodemode,
|
|
1253
|
+
isA2AProtocol: false,
|
|
1254
|
+
hasConfigData: !!configQuery.data,
|
|
1255
|
+
hasSkillsData: !!skillsQuery.data,
|
|
1256
|
+
};
|
|
1257
|
+
const ComposerComponent = composerEntries[0]?.value.Component;
|
|
1258
|
+
const HeaderComponent = headerEntries[0]?.value.Component;
|
|
1259
|
+
const prompt = ComposerComponent ? (_jsx(ComposerComponent, { workspace: workspace, composer: composerProps })) : null;
|
|
1260
|
+
/*
|
|
1261
|
+
* The parts, named, so a layout can arrange them.
|
|
1262
|
+
*
|
|
1263
|
+
* Each is wired here — the surfaces' hidden mounts, the transcript's
|
|
1264
|
+
* stream, the composer's draft — and only *placed* below: by the split
|
|
1265
|
+
* this view has always drawn, or by whatever `LoopChatLayout` contributed.
|
|
1266
|
+
*/
|
|
1267
|
+
const pickerEl = surfaces.length > 0 && config?.showSurfaceSelector !== false ? (_jsx(SurfacePicker, { surfaces: surfaces.map(entry => entry.value),
|
|
1268
|
+
/*
|
|
1269
|
+
What is on screen, not what was asked for.
|
|
1270
|
+
|
|
1271
|
+
It read `surfaceId` — the request — while the column beside it
|
|
1272
|
+
renders `active`, the surface that request actually resolved to.
|
|
1273
|
+
The two are the same once everything has settled and differ
|
|
1274
|
+
exactly while it has not: a surface still arriving, or one closed
|
|
1275
|
+
because its sandbox went away. So the control could say None over
|
|
1276
|
+
a notebook, which is the one thing a control must never do.
|
|
1277
|
+
|
|
1278
|
+
Reporting the rendered surface makes them agree by construction.
|
|
1279
|
+
*/
|
|
1280
|
+
active: active?.surfaceId ?? waiting?.surfaceId ?? NO_SURFACE, onChange: chooseSurface, workspace: workspace })) : null;
|
|
1281
|
+
const editorsEl = (_jsxs(_Fragment, { children: [surfaces.map(entry => {
|
|
1282
|
+
const surface = entry.value;
|
|
1283
|
+
const shown = active?.surfaceId === surface.surfaceId;
|
|
1284
|
+
const mount = shown ||
|
|
1285
|
+
everMounted.current.has(surface.surfaceId) ||
|
|
1286
|
+
canOpenView(surface, workspace);
|
|
1287
|
+
if (!mount) {
|
|
1288
|
+
return null;
|
|
1289
|
+
}
|
|
1290
|
+
everMounted.current.add(surface.surfaceId);
|
|
1291
|
+
return (_jsx(Box, { sx: shown
|
|
1292
|
+
? { flex: '1 1 0', minWidth: 0, minHeight: 0 }
|
|
1293
|
+
: {
|
|
1294
|
+
position: 'absolute',
|
|
1295
|
+
inset: 0,
|
|
1296
|
+
visibility: 'hidden',
|
|
1297
|
+
pointerEvents: 'none',
|
|
1298
|
+
zIndex: -1,
|
|
1299
|
+
}, children: _jsx(ReactorLazy, { load: surface.load, props: { surfaceId: surface.surfaceId, workspace }, fallback: _jsxs(Centered, { children: ["Loading ", surface.title, "\u2026"] }), errorFallback: error => (_jsxs(Centered, { children: [surface.title, " failed to load: ", error.message] })) }) }, surface.surfaceId));
|
|
1300
|
+
}), !active && waiting ? (_jsx(Box, { sx: { flex: '1 1 0', minWidth: 0, minHeight: 0 }, children: _jsxs(Centered, { children: ["Starting ", waiting.title, "\u2026"] }) })) : null] }));
|
|
1301
|
+
const transcriptEl = (_jsx(_Fragment, { children: _jsxs(Box, { sx: {
|
|
1302
|
+
/*
|
|
1303
|
+
Beside an editor, with the prompt on top, the conversation is
|
|
1304
|
+
the narrower column: the editor is what a `top` host put the
|
|
1305
|
+
prompt over, and the transcript reads fine at a third of the
|
|
1306
|
+
width. Every other placement splits the row evenly.
|
|
1307
|
+
*/
|
|
1308
|
+
flex: topPrompt && active ? '0 0 34%' : '1 1 0',
|
|
1309
|
+
maxWidth: topPrompt && active ? 460 : undefined,
|
|
1310
|
+
minWidth: 0,
|
|
1311
|
+
minHeight: 0,
|
|
1312
|
+
display: 'flex',
|
|
1313
|
+
flexDirection: 'column',
|
|
1314
|
+
borderLeft: active ? '1px solid' : undefined,
|
|
1315
|
+
borderColor: 'border.default',
|
|
1316
|
+
// So the expired-key panel below can cover exactly this column and
|
|
1317
|
+
// nothing else: the notebook beside it still works.
|
|
1318
|
+
position: 'relative',
|
|
1319
|
+
}, children: [chatExtras.errorBanner ? (_jsx(Box, { sx: {
|
|
1320
|
+
flexShrink: 0,
|
|
1321
|
+
px: 3,
|
|
1322
|
+
py: 2,
|
|
1323
|
+
fontSize: 1,
|
|
1324
|
+
borderBottom: '1px solid',
|
|
1325
|
+
bg: chatExtras.errorBanner.variant === 'warning'
|
|
1326
|
+
? 'attention.subtle'
|
|
1327
|
+
: 'danger.subtle',
|
|
1328
|
+
color: chatExtras.errorBanner.variant === 'warning'
|
|
1329
|
+
? 'attention.fg'
|
|
1330
|
+
: 'danger.fg',
|
|
1331
|
+
borderColor: chatExtras.errorBanner.variant === 'warning'
|
|
1332
|
+
? 'attention.muted'
|
|
1333
|
+
: 'danger.muted',
|
|
1334
|
+
}, children: chatExtras.errorBanner.message })) : null, _jsx(Box, { sx: { flex: '1 1 auto', minHeight: 0, display: 'flex' }, children: _jsx(ChatBase
|
|
1335
|
+
// The header says why, beside the title, for the same reason the
|
|
1336
|
+
// placeholder does: a dead control with no explanation is worse
|
|
1337
|
+
// than an absent one.
|
|
1338
|
+
, {
|
|
1339
|
+
// The header says why, beside the title, for the same reason the
|
|
1340
|
+
// placeholder does: a dead control with no explanation is worse
|
|
1341
|
+
// than an absent one.
|
|
1342
|
+
disabled: chatDisabled, disableReason: disabledReason, protocol: protocol,
|
|
1343
|
+
/*
|
|
1344
|
+
Who is answering, in the words its spec uses.
|
|
1345
|
+
|
|
1346
|
+
`ChatBase` falls back to "Start a conversation with the AI
|
|
1347
|
+
agent", which is true of every chat ever built and says
|
|
1348
|
+
nothing about this one. The empty state is the first thing a
|
|
1349
|
+
person sees and the only place the agent introduces itself.
|
|
1350
|
+
*/
|
|
1351
|
+
title: member?.name ?? spec?.name ?? agentId, description: spec?.description,
|
|
1352
|
+
/*
|
|
1353
|
+
Two sizes, because it is drawn in two places.
|
|
1354
|
+
|
|
1355
|
+
The header wants a mark beside a line of text; the empty state
|
|
1356
|
+
wants the thing a person's eye lands on first. One `brandIcon`
|
|
1357
|
+
served both, so a 48px icon meant for the empty state sat in
|
|
1358
|
+
the header at three times the height of the words next to it.
|
|
1359
|
+
*/
|
|
1360
|
+
// Big enough to read as the agent's mark rather than as
|
|
1361
|
+
// punctuation before its name, and still short enough not to
|
|
1362
|
+
// set the header's height.
|
|
1363
|
+
brandIcon: _jsx(BrandIcon, { size: 20 }), emptyState: {
|
|
1364
|
+
icon: _jsx(BrandIcon, { size: 48 }),
|
|
1365
|
+
// `ChatEmptyState` reads its heading from here and nowhere
|
|
1366
|
+
// else — the `title` above reaches the header only — so
|
|
1367
|
+
// without this the agent introduced itself as "Start a
|
|
1368
|
+
// conversation".
|
|
1369
|
+
title: member?.name ?? spec?.name ?? agentId,
|
|
1370
|
+
/*
|
|
1371
|
+
Two levels when there is a team: the team first — its name,
|
|
1372
|
+
what it is for, what it can be asked — and under it the
|
|
1373
|
+
member being addressed, with its own description and its
|
|
1374
|
+
own openers. The groups match the ones the openers carry.
|
|
1375
|
+
*/
|
|
1376
|
+
...(team && member
|
|
1377
|
+
? {
|
|
1378
|
+
sections: [
|
|
1379
|
+
{
|
|
1380
|
+
group: team.team.name,
|
|
1381
|
+
icon: _jsx(TeamIcon, { size: 48 }),
|
|
1382
|
+
title: team.team.name,
|
|
1383
|
+
subtitle: team.team.description,
|
|
1384
|
+
},
|
|
1385
|
+
{
|
|
1386
|
+
group: member.name,
|
|
1387
|
+
icon: _jsx(BrandIcon, { size: 32 }),
|
|
1388
|
+
title: member.name,
|
|
1389
|
+
subtitle: spec?.description,
|
|
1390
|
+
},
|
|
1391
|
+
],
|
|
1392
|
+
}
|
|
1393
|
+
: null),
|
|
1394
|
+
},
|
|
1395
|
+
/*
|
|
1396
|
+
And what it can be asked. From the team rather than the
|
|
1397
|
+
member: the supervisor answers first, and somebody who has
|
|
1398
|
+
just opened the workspace does not yet know there are two
|
|
1399
|
+
agents behind it.
|
|
1400
|
+
*/
|
|
1401
|
+
// With the prompt on top the openers are already chips under
|
|
1402
|
+
// it; the empty state repeating them was the same three buttons
|
|
1403
|
+
// twice on one screen.
|
|
1404
|
+
suggestions: topPrompt || layout ? [] : chatSuggestions,
|
|
1405
|
+
/*
|
|
1406
|
+
The title bar arrives as a plugin: the chat-header plugin
|
|
1407
|
+
contributes the component, this view hands it the assembled
|
|
1408
|
+
props through `renderHeader`. No contribution — or a host
|
|
1409
|
+
that set `hideHeader` — means no bar.
|
|
1410
|
+
*/
|
|
1411
|
+
showHeader: !config?.hideHeader && !!HeaderComponent, renderHeader: HeaderComponent
|
|
1412
|
+
? headerProps => (_jsx(HeaderComponent, { workspace: workspace, header: headerProps }))
|
|
1413
|
+
: undefined,
|
|
1414
|
+
/*
|
|
1415
|
+
The (i), which opens the agent's details over the transcript.
|
|
1416
|
+
|
|
1417
|
+
Worth having here in particular: this workspace can be moved
|
|
1418
|
+
between four runtimes and several agents, and the details pane
|
|
1419
|
+
is the only place that says which one is actually answering
|
|
1420
|
+
and where it is running.
|
|
1421
|
+
*/
|
|
1422
|
+
showInformation: true,
|
|
1423
|
+
/*
|
|
1424
|
+
Full screen: this view, on the whole screen, editor and all.
|
|
1425
|
+
See `toggleFullScreen` for why it is the browser's API rather
|
|
1426
|
+
than a big box.
|
|
1427
|
+
*/
|
|
1428
|
+
headerActions: _jsxs(Box, { sx: { display: 'inline-flex', alignItems: 'center', gap: 2 }, children: [workspace.chatHeaderActions, chatHeaderItems.length > 0 ? (_jsx(ReactorSlot, { slot: LoopSlots.chatHeader, props: { workspace } })) : null, _jsx(IconButton, { icon: fullScreen ? ScreenNormalIcon : ScreenFullIcon, "aria-label": fullScreen ? 'Exit full screen' : 'Enter full screen', variant: "invisible", size: "small", onClick: () => {
|
|
1429
|
+
// Whether or not it takes, the hint has been answered:
|
|
1430
|
+
// they found the control, which is all it was for.
|
|
1431
|
+
setHintFullScreen(false);
|
|
1432
|
+
toggleFullScreen();
|
|
1433
|
+
}, sx: hintFullScreen
|
|
1434
|
+
? {
|
|
1435
|
+
/*
|
|
1436
|
+
Colour and opacity, never geometry.
|
|
1437
|
+
|
|
1438
|
+
The colour is the theme's own `primary` — the
|
|
1439
|
+
same brand the page around this workspace is
|
|
1440
|
+
already using — because a grey icon in a row of
|
|
1441
|
+
grey icons cannot be picked out however hard it
|
|
1442
|
+
fades. Colour is what makes it findable; the
|
|
1443
|
+
fade is what makes it look like it is asking.
|
|
1444
|
+
|
|
1445
|
+
It never fades to nothing: a control that
|
|
1446
|
+
vanishes and returns reads as a rendering fault,
|
|
1447
|
+
not an invitation. And nothing here moves or
|
|
1448
|
+
resizes, so the header does not reflow and the
|
|
1449
|
+
controls beside it stay where a reader last saw
|
|
1450
|
+
them.
|
|
1451
|
+
*/
|
|
1452
|
+
'@keyframes dla-fullscreen-hint': {
|
|
1453
|
+
'0%, 100%': { opacity: 1 },
|
|
1454
|
+
'50%': { opacity: 0.4 },
|
|
1455
|
+
},
|
|
1456
|
+
/* `&&` doubles the specificity, because Primer
|
|
1457
|
+
ships Button as a CSS module and
|
|
1458
|
+
`prc-Button-*` outranks a single generated
|
|
1459
|
+
class — without it the keyframes register and
|
|
1460
|
+
the button sits there at `animation: none`.
|
|
1461
|
+
The colour needs the same treatment: an
|
|
1462
|
+
invisible IconButton sets `color` itself.
|
|
1463
|
+
|
|
1464
|
+
And no `:focus` or `:hover` clause to stop it:
|
|
1465
|
+
anything that matches the button while it is
|
|
1466
|
+
being pointed at would cancel the pointing —
|
|
1467
|
+
which is exactly how the first version of this
|
|
1468
|
+
silently did nothing. It ends when the reader
|
|
1469
|
+
takes the offer, or arrives without it. */
|
|
1470
|
+
'&&': {
|
|
1471
|
+
/*
|
|
1472
|
+
The brand on the box, not on the glyph.
|
|
1473
|
+
|
|
1474
|
+
Painting the icon `palette.primary` was the
|
|
1475
|
+
obvious reading and it is measurably
|
|
1476
|
+
backwards: in the theme this workspace ships
|
|
1477
|
+
on, `primary` is #FFC107, which sits at 1.55
|
|
1478
|
+
contrast against the header — where the
|
|
1479
|
+
icon's ordinary olive sits at 5.07. Recolouring
|
|
1480
|
+
the stroke made the control roughly three
|
|
1481
|
+
times harder to see, and at the bottom of the
|
|
1482
|
+
fade it was 1.2 and simply gone.
|
|
1483
|
+
|
|
1484
|
+
A ring and a wash carry the same colour on a
|
|
1485
|
+
far larger area, and they do it without asking
|
|
1486
|
+
the glyph to be legible in a brand colour that
|
|
1487
|
+
changes with every theme — this palette has
|
|
1488
|
+
six, from a dark teal to this amber, and a
|
|
1489
|
+
rule that works for one fails for another. The
|
|
1490
|
+
glyph keeps the colour it can be read in.
|
|
1491
|
+
*/
|
|
1492
|
+
bg: `color-mix(in srgb, ${palette.primary} 16%, transparent)`,
|
|
1493
|
+
boxShadow: `inset 0 0 0 1.5px ${palette.primary}`,
|
|
1494
|
+
animation: `dla-fullscreen-hint ${FULLSCREEN_HINT_PERIOD_MS}ms ease-in-out infinite`,
|
|
1495
|
+
},
|
|
1496
|
+
// A reader who asked the machine to hold still
|
|
1497
|
+
// keeps the colour and loses the knocking: the
|
|
1498
|
+
// control is still the findable one, it just
|
|
1499
|
+
// holds still.
|
|
1500
|
+
'@media (prefers-reduced-motion: reduce)': {
|
|
1501
|
+
'&&': { animation: 'none' },
|
|
1502
|
+
},
|
|
1503
|
+
}
|
|
1504
|
+
: undefined })] }),
|
|
1505
|
+
// This view owns the prompt, below, so the chat does not draw a
|
|
1506
|
+
// second one: two input boxes on one screen is one too many.
|
|
1507
|
+
showInput: false, onSendReady: handleSendReady,
|
|
1508
|
+
/*
|
|
1509
|
+
The notebook tools' results, drawn into the transcript — the
|
|
1510
|
+
chat's own machinery, switched on only while no editor is on
|
|
1511
|
+
screen: the hidden notebook is where the agent works, and the
|
|
1512
|
+
transcript is then the one place the reader can see what a
|
|
1513
|
+
tool did. With an editor open the change is visible where it
|
|
1514
|
+
happened, and the default tool row is enough.
|
|
1515
|
+
*/
|
|
1516
|
+
notebookToolSurfacesId: active ? undefined : workspace.surfaceId,
|
|
1517
|
+
// A host example's own tool-result renderer, when it feeds one
|
|
1518
|
+
// through the extras channel — the A2UI examples draw their
|
|
1519
|
+
// surface here. Wins over the notebook surfaces in ChatBase.
|
|
1520
|
+
renderToolResult: chatExtras.renderToolResult, showTurnFooter: chatExtras.showTokenUsage ?? true, onContextSnapshot: handleContextSnapshot, onLoadingChange: handleLoadingChange, onItemsChange: handleMessagesChange, onDisplayItemsChange: handleDisplayItemsChange, enableStreaming: true }) }), besideChat && !promptHidden ? prompt : null] }) }));
|
|
1521
|
+
const chipsEl = chatSuggestions.length > 0 ? (_jsx(Box, { "aria-label": "Suggested prompts", sx: { display: 'flex', flexWrap: 'wrap', gap: 2, px: 2, pb: '6px' }, children: chatSuggestions.map(item => (_jsx(Box, { as: "button", type: "button",
|
|
1522
|
+
// The whole request, where the chip shows only its summary.
|
|
1523
|
+
title: item.message, disabled: busy || chatDisabled, onClick: () => void handleSend(item.message), sx: {
|
|
1524
|
+
appearance: 'none',
|
|
1525
|
+
font: 'inherit',
|
|
1526
|
+
fontSize: 0,
|
|
1527
|
+
fontWeight: 'semibold',
|
|
1528
|
+
// Room for descenders: the truncated span clips what falls
|
|
1529
|
+
// below its line, and at a line-height of one that is the
|
|
1530
|
+
// tail of every "y".
|
|
1531
|
+
lineHeight: 1.25,
|
|
1532
|
+
px: '10px',
|
|
1533
|
+
py: '5px',
|
|
1534
|
+
width: SUGGESTION_CHIP_WIDTH,
|
|
1535
|
+
flex: `0 0 ${SUGGESTION_CHIP_WIDTH}px`,
|
|
1536
|
+
borderRadius: '999px',
|
|
1537
|
+
border: '1px solid',
|
|
1538
|
+
borderColor: 'border.default',
|
|
1539
|
+
bg: 'canvas.subtle',
|
|
1540
|
+
color: 'fg.default',
|
|
1541
|
+
cursor: 'pointer',
|
|
1542
|
+
whiteSpace: 'nowrap',
|
|
1543
|
+
'&:hover:not(:disabled)': {
|
|
1544
|
+
borderColor: 'accent.fg',
|
|
1545
|
+
color: 'accent.fg',
|
|
1546
|
+
},
|
|
1547
|
+
'&:disabled': { opacity: 0.5, cursor: 'default' },
|
|
1548
|
+
}, children: _jsx(Truncate, { title: item.title, maxWidth: "100%", sx: { minWidth: 0 }, children: item.title }) }, item.title))) })) : null;
|
|
1549
|
+
const transientEl = transient ? (_jsx(Box, { sx: {
|
|
1550
|
+
flex: '0 0 auto',
|
|
1551
|
+
px: 3,
|
|
1552
|
+
py: 2,
|
|
1553
|
+
borderTop: '1px solid',
|
|
1554
|
+
borderColor: 'border.default',
|
|
1555
|
+
bg: 'canvas.subtle',
|
|
1556
|
+
fontSize: 1,
|
|
1557
|
+
maxHeight: '40%',
|
|
1558
|
+
overflowY: 'auto',
|
|
1559
|
+
}, children: transient })) : null;
|
|
1560
|
+
return (_jsxs(Box, { ref: viewRef, sx: {
|
|
1561
|
+
height: '100%',
|
|
1562
|
+
minHeight: 0,
|
|
1563
|
+
display: 'flex',
|
|
1564
|
+
flexDirection: 'column',
|
|
1565
|
+
// The positioned root the expiry panel below covers.
|
|
1566
|
+
position: 'relative',
|
|
1567
|
+
/*
|
|
1568
|
+
A background of its own once it is full screen, in both modes.
|
|
1569
|
+
|
|
1570
|
+
The element is promoted out of whatever was painting behind it — the
|
|
1571
|
+
browser's top layer, or the page — and a workspace that inherited its
|
|
1572
|
+
canvas from an ancestor would arrive transparent over black.
|
|
1573
|
+
*/
|
|
1574
|
+
...(fullScreen ? { bg: 'canvas.default' } : null),
|
|
1575
|
+
// The fallback, for a host where the API was refused, or where
|
|
1576
|
+
// `fullScreenTopOffset` asked for the overlay outright. Covers as
|
|
1577
|
+
// much as the nearest transformed ancestor allows; see the note
|
|
1578
|
+
// above. `top` leaves room for a host's own fixed header rather than
|
|
1579
|
+
// `inset: 0`'s full viewport — 0 (no offset given) is the same rect
|
|
1580
|
+
// either way.
|
|
1581
|
+
...(fullScreen && !usingFullscreenApi.current
|
|
1582
|
+
? {
|
|
1583
|
+
position: 'fixed',
|
|
1584
|
+
top: fullScreenTopOffsetPx,
|
|
1585
|
+
right: 0,
|
|
1586
|
+
bottom: 0,
|
|
1587
|
+
left: 0,
|
|
1588
|
+
// Over the `height: '100%'` above, which would otherwise win
|
|
1589
|
+
// over `top`/`bottom` and push the bottom off the window.
|
|
1590
|
+
height: 'auto',
|
|
1591
|
+
zIndex: 1000,
|
|
1592
|
+
}
|
|
1593
|
+
: null),
|
|
1594
|
+
}, children: [layout ? (_jsx(layout.Component, { workspace: workspace, editors: editorsEl, hasEditor: !!active, transcript: transcriptEl, prompt: promptHidden ? null : prompt, chips: promptHidden ? null : chipsEl, picker: pickerEl, transient: transientEl })) : (_jsxs(_Fragment, { children: [topPrompt && !promptHidden ? (_jsxs(Box, { sx: {
|
|
1595
|
+
flex: '0 0 auto',
|
|
1596
|
+
borderBottom: '1px solid',
|
|
1597
|
+
borderColor: 'border.default',
|
|
1598
|
+
}, children: [prompt, chipsEl] })) : null, pickerEl, _jsxs(Box, { sx: {
|
|
1599
|
+
flex: '1 1 auto',
|
|
1600
|
+
minHeight: 0,
|
|
1601
|
+
display: 'flex',
|
|
1602
|
+
// The hidden editors position themselves against this row.
|
|
1603
|
+
position: 'relative',
|
|
1604
|
+
}, children: [editorsEl, transcriptEl] }), transientEl, besideChat || promptHidden || topPrompt ? null : prompt] })), keyExpired ? (_jsx(Box, { sx: {
|
|
1605
|
+
position: 'absolute',
|
|
1606
|
+
inset: 0,
|
|
1607
|
+
zIndex: 5,
|
|
1608
|
+
bg: 'canvas.default',
|
|
1609
|
+
display: 'flex',
|
|
1610
|
+
flexDirection: 'column',
|
|
1611
|
+
minHeight: 0,
|
|
1612
|
+
}, children: _jsx(AnonymousKeyExpired, { agentName: member?.name ?? spec?.name,
|
|
1613
|
+
// On the browser target the kernel is in this page and owes the
|
|
1614
|
+
// inference service nothing, so the notebook is genuinely
|
|
1615
|
+
// unaffected and the panel may say so.
|
|
1616
|
+
sandboxStillRuns: inPage, temporary: expiredKeyIsTemporary,
|
|
1617
|
+
// The trial key is spent once somebody signs in, and a session
|
|
1618
|
+
// left `expired` would keep this panel over a working chat.
|
|
1619
|
+
onSignedIn: () => useAnonymousSessionStore.getState().clear() }) })) : null] }));
|
|
1620
|
+
}
|
|
1621
|
+
/**
|
|
1622
|
+
* Which editor sits beside the conversation.
|
|
1623
|
+
*
|
|
1624
|
+
* Rendered only when a plugin contributed one — a chat on its own has nothing
|
|
1625
|
+
* to pick between, and a control with a single option is noise.
|
|
1626
|
+
*/
|
|
1627
|
+
function SurfacePicker({ surfaces, active, onChange, workspace, }) {
|
|
1628
|
+
const ordered = [...surfaces].sort((left, right) => (left.order ?? 100) - (right.order ?? 100));
|
|
1629
|
+
return (_jsxs(Box, { sx: {
|
|
1630
|
+
flex: '0 0 auto',
|
|
1631
|
+
display: 'flex',
|
|
1632
|
+
alignItems: 'center',
|
|
1633
|
+
gap: 2,
|
|
1634
|
+
px: 3,
|
|
1635
|
+
py: 2,
|
|
1636
|
+
borderBottom: '1px solid',
|
|
1637
|
+
borderColor: 'border.default',
|
|
1638
|
+
}, children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Beside the chat" }), _jsxs(SegmentedControl, { "aria-label": "Editor beside the chat", size: "small", children: [_jsx(SegmentedControl.Button, { selected: active === NO_SURFACE, onClick: () => onChange(NO_SURFACE), children: "Chat" }), ordered.map(surface => {
|
|
1639
|
+
const openable = canOpenView(surface, workspace);
|
|
1640
|
+
const reason = openable
|
|
1641
|
+
? undefined
|
|
1642
|
+
: (surface.unavailableReason?.(workspace) ??
|
|
1643
|
+
'Not available right now');
|
|
1644
|
+
return (_jsx(SegmentedControl.Button, { selected: active === surface.surfaceId, "aria-disabled": !openable,
|
|
1645
|
+
// The native attribute rather than Primer's `Tooltip`: that
|
|
1646
|
+
// component requires its child to *be* the interactive element,
|
|
1647
|
+
// and `SegmentedControl.Button` renders a list item around its
|
|
1648
|
+
// button — wrapping it throws before the workspace can paint.
|
|
1649
|
+
title: reason ?? surface.title,
|
|
1650
|
+
// Primer types this as its own icon shape; a contribution may
|
|
1651
|
+
// bring any component, which is the point of the extension point.
|
|
1652
|
+
leadingIcon: surface.icon, onClick: () => {
|
|
1653
|
+
if (openable) {
|
|
1654
|
+
onChange(surface.surfaceId);
|
|
1655
|
+
}
|
|
1656
|
+
}, children: surface.title }, surface.surfaceId));
|
|
1657
|
+
})] })] }));
|
|
1658
|
+
}
|
|
1659
|
+
function Centered({ children }) {
|
|
1660
|
+
return (_jsx(Box, { sx: {
|
|
1661
|
+
height: '100%',
|
|
1662
|
+
display: 'flex',
|
|
1663
|
+
alignItems: 'center',
|
|
1664
|
+
justifyContent: 'center',
|
|
1665
|
+
color: 'fg.muted',
|
|
1666
|
+
fontSize: 1,
|
|
1667
|
+
px: 3,
|
|
1668
|
+
textAlign: 'center',
|
|
1669
|
+
}, children: children }));
|
|
1670
|
+
}
|