@datalayer/agent-runtimes 1.0.3 → 1.0.5
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/README.md +35 -119
- package/lib/App.js +1 -1
- package/lib/agents/AgentDetails.d.ts +22 -1
- package/lib/agents/AgentDetails.js +34 -47
- package/lib/api/index.d.ts +0 -1
- package/lib/api/index.js +4 -2
- package/lib/chat/Chat.d.ts +5 -104
- package/lib/chat/Chat.js +4 -4
- package/lib/chat/ChatFloating.d.ts +7 -140
- package/lib/chat/ChatFloating.js +2 -2
- package/lib/chat/ChatPopupStandalone.d.ts +8 -47
- package/lib/chat/ChatPopupStandalone.js +3 -3
- package/lib/chat/ChatSidebar.d.ts +4 -69
- package/lib/chat/ChatSidebar.js +2 -2
- package/lib/chat/ChatStandalone.d.ts +4 -54
- package/lib/chat/ChatStandalone.js +3 -3
- package/lib/chat/base/ChatBase.js +1118 -141
- package/lib/chat/header/ChatHeaderBase.d.ts +11 -6
- package/lib/chat/header/ChatHeaderBase.js +18 -16
- package/lib/chat/indicators/McpStatusIndicator.d.ts +7 -4
- package/lib/chat/indicators/McpStatusIndicator.js +7 -32
- package/lib/chat/indicators/SandboxStatusIndicator.d.ts +4 -1
- package/lib/chat/indicators/SandboxStatusIndicator.js +9 -9
- package/lib/chat/indicators/SkillsStatusIndicator.d.ts +7 -0
- package/lib/chat/indicators/SkillsStatusIndicator.js +88 -0
- package/lib/chat/indicators/index.d.ts +1 -0
- package/lib/chat/indicators/index.js +1 -0
- package/lib/chat/messages/ChatMessageList.d.ts +1 -1
- package/lib/chat/messages/ChatMessageList.js +110 -102
- package/lib/chat/prompt/InputFooter.d.ts +19 -6
- package/lib/chat/prompt/InputFooter.js +71 -18
- package/lib/chat/prompt/InputPrompt.d.ts +3 -1
- package/lib/chat/prompt/InputPrompt.js +4 -4
- package/lib/chat/prompt/InputPromptFooter.js +1 -1
- package/lib/chat/prompt/InputPromptLexical.d.ts +3 -1
- package/lib/chat/prompt/InputPromptLexical.js +12 -5
- package/lib/chat/prompt/InputPromptText.d.ts +3 -1
- package/lib/chat/prompt/InputPromptText.js +2 -2
- package/lib/chat/tools/ToolApprovalBanner.js +1 -1
- package/lib/chat/tools/ToolCallDisplay.d.ts +3 -1
- package/lib/chat/tools/ToolCallDisplay.js +2 -2
- package/lib/chat/usage/TokenUsageBar.js +20 -2
- package/lib/client/AgentRuntimesClientContext.d.ts +53 -0
- package/lib/client/AgentRuntimesClientContext.js +55 -0
- package/lib/client/AgentsMixin.d.ts +48 -19
- package/lib/client/AgentsMixin.js +115 -30
- package/lib/client/IAgentRuntimesClient.d.ts +215 -0
- package/lib/client/IAgentRuntimesClient.js +5 -0
- package/lib/client/SdkAgentRuntimesClient.d.ts +151 -0
- package/lib/client/SdkAgentRuntimesClient.js +134 -0
- package/lib/client/index.d.ts +4 -1
- package/lib/client/index.js +3 -1
- package/lib/components/NotificationEventCard.js +55 -26
- package/lib/components/OutputCard.js +21 -7
- package/lib/components/ToolApprovalCard.js +20 -2
- package/lib/config/AgentConfiguration.js +3 -3
- package/lib/context/ContextDistribution.d.ts +3 -1
- package/lib/context/ContextDistribution.js +8 -27
- package/lib/context/ContextInspector.d.ts +3 -1
- package/lib/context/ContextInspector.js +19 -67
- package/lib/context/ContextPanel.d.ts +3 -1
- package/lib/context/ContextPanel.js +104 -64
- package/lib/context/ContextUsage.d.ts +3 -1
- package/lib/context/ContextUsage.js +3 -3
- package/lib/context/CostTracker.d.ts +9 -3
- package/lib/context/CostTracker.js +26 -47
- package/lib/context/CostUsageChart.d.ts +12 -0
- package/lib/context/CostUsageChart.js +378 -0
- package/lib/context/GraphFlowChart.d.ts +16 -0
- package/lib/context/GraphFlowChart.js +182 -0
- package/lib/context/TokenUsageChart.d.ts +8 -1
- package/lib/context/TokenUsageChart.js +349 -211
- package/lib/context/TurnGraphChart.d.ts +39 -0
- package/lib/context/TurnGraphChart.js +538 -0
- package/lib/context/otelWsPool.d.ts +20 -0
- package/lib/context/otelWsPool.js +69 -0
- package/lib/examples/A2UiComponentGalleryExample.d.ts +0 -17
- package/lib/examples/A2UiComponentGalleryExample.js +315 -522
- package/lib/examples/A2UiContactCardExample.d.ts +0 -18
- package/lib/examples/A2UiContactCardExample.js +154 -411
- package/lib/examples/A2UiRestaurantExample.d.ts +0 -30
- package/lib/examples/A2UiRestaurantExample.js +114 -212
- package/lib/examples/A2UiViewerExample.d.ts +0 -18
- package/lib/examples/A2UiViewerExample.js +283 -532
- package/lib/examples/AgUiBackendToolRenderingExample.js +1 -1
- package/lib/examples/AgUiHaikuGenUiExample.d.ts +1 -1
- package/lib/examples/AgUiHaikuGenUiExample.js +1 -1
- package/lib/examples/AgentCheckpointsExample.js +14 -34
- package/lib/examples/AgentCodemodeExample.d.ts +4 -6
- package/lib/examples/AgentCodemodeExample.js +591 -175
- package/lib/examples/AgentEvalsExample.js +13 -23
- package/lib/examples/AgentGuardrailsExample.js +371 -71
- package/lib/examples/AgentHooksExample.d.ts +3 -0
- package/lib/examples/AgentHooksExample.js +104 -0
- package/lib/examples/AgentMCPExample.d.ts +3 -0
- package/lib/examples/AgentMCPExample.js +480 -0
- package/lib/examples/AgentMemoryExample.js +14 -24
- package/lib/examples/AgentMonitoringExample.js +261 -206
- package/lib/examples/AgentNotificationsExample.js +50 -24
- package/lib/examples/AgentOtelExample.js +2 -3
- package/lib/examples/AgentOutputsExample.d.ts +11 -6
- package/lib/examples/AgentOutputsExample.js +383 -88
- package/lib/examples/AgentParametersExample.d.ts +3 -0
- package/lib/examples/AgentParametersExample.js +246 -0
- package/lib/examples/AgentSandboxExample.d.ts +2 -2
- package/lib/examples/AgentSandboxExample.js +69 -47
- package/lib/examples/AgentSkillsExample.js +92 -106
- package/lib/examples/{AgentspecExample.js → AgentSpecsExample.js} +10 -21
- package/lib/examples/AgentSubagentsExample.d.ts +14 -0
- package/lib/examples/AgentSubagentsExample.js +228 -0
- package/lib/examples/AgentToolApprovalsExample.js +30 -493
- package/lib/examples/AgentTriggersExample.js +1067 -246
- package/lib/examples/ChatCustomExample.js +11 -24
- package/lib/examples/ChatExample.js +9 -34
- package/lib/examples/CopilotKitLexicalExample.js +2 -1
- package/lib/examples/CopilotKitNotebookExample.js +2 -1
- package/lib/examples/HomeExample.d.ts +15 -0
- package/lib/examples/HomeExample.js +77 -0
- package/lib/examples/Lexical2Example.js +4 -2
- package/lib/examples/{LexicalExample.d.ts → LexicalAgentExample.d.ts} +4 -4
- package/lib/examples/{LexicalExample.js → LexicalAgentExample.js} +65 -16
- package/lib/examples/{LexicalSidebarExample.d.ts → LexicalAgentSidebarExample.d.ts} +5 -5
- package/lib/examples/LexicalAgentSidebarExample.js +261 -0
- package/lib/examples/NotebookAgentExample.d.ts +9 -0
- package/lib/examples/NotebookAgentExample.js +192 -0
- package/lib/examples/{NotebookSidebarExample.d.ts → NotebookAgentSidebarExample.d.ts} +2 -2
- package/lib/examples/NotebookAgentSidebarExample.js +221 -0
- package/lib/examples/{DatalayerNotebookExample.d.ts → NotebookCollaborationExample.d.ts} +4 -4
- package/lib/examples/{DatalayerNotebookExample.js → NotebookCollaborationExample.js} +3 -3
- package/lib/examples/NotebookExample.d.ts +4 -7
- package/lib/examples/NotebookExample.js +14 -146
- package/lib/examples/components/AuthRequiredView.d.ts +6 -0
- package/lib/examples/components/AuthRequiredView.js +33 -0
- package/lib/examples/components/ErrorView.d.ts +14 -0
- package/lib/examples/components/ErrorView.js +20 -0
- package/lib/examples/components/ExampleWrapper.d.ts +7 -0
- package/lib/examples/components/ExampleWrapper.js +25 -6
- package/lib/examples/{ag-ui → components}/haiku/HaikuDisplay.js +1 -1
- package/lib/examples/{ag-ui → components}/haiku/InlineHaikuCard.js +1 -1
- package/lib/examples/{ag-ui → components}/haiku/index.d.ts +1 -1
- package/lib/examples/{ag-ui → components}/haiku/index.js +1 -1
- package/lib/examples/components/index.d.ts +5 -0
- package/lib/examples/components/index.js +5 -0
- package/lib/examples/{ag-ui → components}/weather/index.d.ts +1 -1
- package/lib/examples/{ag-ui → components}/weather/index.js +1 -1
- package/lib/examples/example-selector.d.ts +17 -4
- package/lib/examples/example-selector.js +107 -41
- package/lib/examples/index.d.ts +9 -6
- package/lib/examples/index.js +9 -6
- package/lib/examples/main.d.ts +1 -0
- package/lib/examples/main.js +218 -27
- package/lib/examples/utils/a2ui.d.ts +18 -0
- package/lib/examples/utils/a2ui.js +69 -0
- package/lib/examples/utils/a2uiMarkdownProvider.d.ts +7 -0
- package/lib/examples/utils/a2uiMarkdownProvider.js +9 -0
- package/lib/examples/utils/agentId.d.ts +18 -0
- package/lib/examples/utils/agentId.js +54 -0
- package/lib/examples/utils/agents/earthquake-detector.json +11 -11
- package/lib/examples/utils/agents/sales-forecaster.json +11 -11
- package/lib/examples/utils/agents/social-post-generator.json +11 -11
- package/lib/examples/utils/agents/stock-market.json +11 -11
- package/lib/examples/utils/examplesStore.js +82 -27
- package/lib/hooks/index.d.ts +8 -8
- package/lib/hooks/index.js +7 -7
- package/lib/hooks/useA2A.d.ts +2 -3
- package/lib/hooks/useAIAgentsWebSocket.d.ts +43 -4
- package/lib/hooks/useAIAgentsWebSocket.js +118 -12
- package/lib/hooks/useAcp.d.ts +1 -2
- package/lib/hooks/useAgUi.d.ts +1 -1
- package/lib/hooks/{useAgents.d.ts → useAgentRuntimes.d.ts} +39 -2
- package/lib/hooks/{useAgents.js → useAgentRuntimes.js} +125 -15
- package/lib/hooks/useAgentsCatalog.js +1 -1
- package/lib/hooks/useAgentsService.d.ts +2 -2
- package/lib/hooks/useAgentsService.js +7 -7
- package/lib/hooks/useCheckpoints.js +1 -1
- package/lib/hooks/useConfig.d.ts +4 -1
- package/lib/hooks/useConfig.js +10 -3
- package/lib/hooks/useContextSnapshot.d.ts +9 -4
- package/lib/hooks/useContextSnapshot.js +9 -37
- package/lib/hooks/useMonitoring.js +3 -0
- package/lib/hooks/useSandbox.d.ts +20 -8
- package/lib/hooks/useSandbox.js +105 -40
- package/lib/hooks/useSkills.d.ts +23 -5
- package/lib/hooks/useSkills.js +94 -39
- package/lib/hooks/useToolApprovals.d.ts +60 -36
- package/lib/hooks/useToolApprovals.js +318 -69
- package/lib/hooks/useVercelAI.d.ts +1 -1
- package/lib/index.d.ts +2 -1
- package/lib/index.js +1 -0
- package/lib/inference/index.d.ts +0 -1
- package/lib/middleware/index.d.ts +0 -1
- package/lib/protocols/AGUIAdapter.js +6 -0
- package/lib/protocols/VercelAIAdapter.d.ts +9 -0
- package/lib/protocols/VercelAIAdapter.js +144 -26
- package/lib/shims/json5.d.ts +4 -0
- package/lib/shims/json5.js +8 -0
- package/lib/specs/agents/agents.d.ts +10 -0
- package/lib/specs/agents/agents.js +752 -24
- package/lib/specs/envvars.d.ts +1 -0
- package/lib/specs/envvars.js +11 -0
- package/lib/specs/events.d.ts +1 -0
- package/lib/specs/events.js +1 -0
- package/lib/specs/index.d.ts +1 -0
- package/lib/specs/index.js +1 -0
- package/lib/specs/personas.d.ts +41 -0
- package/lib/specs/personas.js +168 -0
- package/lib/specs/skills.d.ts +2 -1
- package/lib/specs/skills.js +23 -5
- package/lib/specs/tools.js +3 -0
- package/lib/stores/agentRuntimeStore.d.ts +204 -0
- package/lib/stores/agentRuntimeStore.js +636 -0
- package/lib/stores/index.d.ts +1 -1
- package/lib/stores/index.js +1 -1
- package/lib/tools/adapters/copilotkit/lexicalHooks.d.ts +1 -2
- package/lib/tools/adapters/copilotkit/lexicalHooks.js +1 -3
- package/lib/tools/adapters/copilotkit/notebookHooks.d.ts +1 -2
- package/lib/tools/adapters/copilotkit/notebookHooks.js +1 -3
- package/lib/tools/index.d.ts +0 -2
- package/lib/tools/index.js +0 -1
- package/lib/types/agentspecs.d.ts +50 -1
- package/lib/types/chat.d.ts +309 -8
- package/lib/types/context.d.ts +27 -0
- package/lib/types/cost.d.ts +2 -2
- package/lib/types/index.d.ts +2 -0
- package/lib/types/index.js +2 -0
- package/lib/types/mcp.d.ts +8 -0
- package/lib/types/models.d.ts +2 -2
- package/lib/types/personas.d.ts +25 -0
- package/lib/types/personas.js +5 -0
- package/lib/types/skills.d.ts +43 -1
- package/lib/types/stream.d.ts +110 -0
- package/lib/types/stream.js +36 -0
- package/lib/types/tools.d.ts +2 -0
- package/lib/utils/utils.d.ts +9 -5
- package/lib/utils/utils.js +9 -5
- package/package.json +13 -9
- package/scripts/codegen/__pycache__/generate_agents.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_events.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/versioning.cpython-313.pyc +0 -0
- package/scripts/codegen/generate_agents.py +106 -7
- package/scripts/codegen/generate_events.py +47 -17
- package/scripts/codegen/generate_personas.py +319 -0
- package/scripts/codegen/generate_skills.py +9 -9
- package/scripts/codegen/generate_tools.py +20 -0
- package/scripts/sync-jupyter.sh +26 -7
- package/style/primer-primitives.css +1 -6
- package/lib/api/tool-approvals.d.ts +0 -62
- package/lib/api/tool-approvals.js +0 -145
- package/lib/examples/LexicalSidebarExample.js +0 -163
- package/lib/examples/NotebookSidebarExample.js +0 -119
- package/lib/examples/NotebookSimpleExample.d.ts +0 -6
- package/lib/examples/NotebookSimpleExample.js +0 -22
- package/lib/examples/ag-ui/index.d.ts +0 -10
- package/lib/examples/ag-ui/index.js +0 -16
- package/lib/hooks/useAgentsRegistry.d.ts +0 -10
- package/lib/hooks/useAgentsRegistry.js +0 -20
- package/lib/stores/agentsStore.d.ts +0 -123
- package/lib/stores/agentsStore.js +0 -270
- package/scripts/codegen/__pycache__/generate_envvars.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_evals.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_guardrails.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_mcp_servers.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_memory.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_models.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_notifications.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_outputs.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_skills.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_teams.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_tools.cpython-313.pyc +0 -0
- package/scripts/codegen/__pycache__/generate_triggers.cpython-313.pyc +0 -0
- /package/lib/examples/{AgentspecExample.d.ts → AgentSpecsExample.d.ts} +0 -0
- /package/lib/examples/{ag-ui → components}/haiku/HaikuDisplay.d.ts +0 -0
- /package/lib/examples/{ag-ui → components}/haiku/InlineHaikuCard.d.ts +0 -0
- /package/lib/examples/{ag-ui → components}/weather/InlineWeatherCard.d.ts +0 -0
- /package/lib/examples/{ag-ui → components}/weather/InlineWeatherCard.js +0 -0
|
@@ -15,30 +15,49 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
15
15
|
* - Lists recent trigger history and next scheduled run
|
|
16
16
|
*/
|
|
17
17
|
/// <reference types="vite/client" />
|
|
18
|
-
import { useEffect, useState, useCallback, useRef } from 'react';
|
|
18
|
+
import React, { useEffect, useState, useCallback, useRef } from 'react';
|
|
19
19
|
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
|
|
20
20
|
import { Text, Button, Spinner, Heading, Label, TextInput, Flash, Timeline, Truncate, Tooltip, } from '@primer/react';
|
|
21
|
-
import {
|
|
21
|
+
import { ClockIcon, SyncIcon, CheckCircleIcon, XCircleIcon, PlayIcon, GlobeIcon, ZapIcon, EyeIcon, EyeClosedIcon, TrashIcon, CopyIcon, } from '@primer/octicons-react';
|
|
22
22
|
import { Box } from '@datalayer/primer-addons';
|
|
23
|
+
import { AuthRequiredView, ErrorView } from './components';
|
|
23
24
|
import { ThemedProvider } from './utils/themedProvider';
|
|
25
|
+
import { uniqueAgentId } from './utils/agentId';
|
|
24
26
|
import { useSimpleAuthStore } from '@datalayer/core/lib/views/otel';
|
|
25
|
-
import { SignInSimple } from '@datalayer/core/lib/views/iam';
|
|
26
|
-
import { UserBadge } from '@datalayer/core/lib/views/profile';
|
|
27
27
|
import { Chat } from '../chat';
|
|
28
|
+
import { useConnectedIdentities } from '../identity';
|
|
29
|
+
import { ToolApprovalBanner, ToolApprovalDialog, } from '../chat/tools';
|
|
28
30
|
import { useAgentEvents, useDeleteAgentEvent, useMarkEventRead, useMarkEventUnread, useAIAgentsWebSocket, } from '../hooks';
|
|
31
|
+
import { VercelAIAdapter } from '../protocols';
|
|
32
|
+
import { createUserMessage } from '../types/messages';
|
|
29
33
|
const queryClient = new QueryClient();
|
|
30
34
|
// ─── Constants ─────────────────────────────────────────────────────────────
|
|
31
35
|
const AGENT_NAME = 'trigger-demo-agent';
|
|
32
36
|
const AGENT_SPEC_ID = 'demo-one-trigger';
|
|
37
|
+
const APPROVAL_AGENT_NAME = 'trigger-approval-demo-agent';
|
|
38
|
+
const APPROVAL_AGENT_SPEC_ID = 'demo-one-trigger-approval';
|
|
39
|
+
const ONCE_TRIGGER_PROMPT = "List the user's top 3 public and top 3 private GitHub repositories, ranked by recent activity, and provide a brief summary of each. Execute exactly two tool calls: run_skill_script(skill_name='github', script_name='list_repos', kwargs={visibility:'public', sort:'updated', limit:3, format:'json'}) and run_skill_script(skill_name='github', script_name='list_repos', kwargs={visibility:'private', sort:'updated', limit:3, format:'json'}). Do not call list_skills/load_skill/read_skill_resource. Do not retry. If a tool call fails, report failure_reason/error/stderr exactly as returned.";
|
|
40
|
+
const ONCE_TRIGGER_APPROVAL_PROMPT = 'Use the runtime_sensitive_echo tool once.';
|
|
33
41
|
const DEFAULT_LOCAL_BASE_URL = import.meta.env.VITE_BASE_URL || 'http://localhost:8765';
|
|
34
42
|
const DEFAULT_CRON = '0 8 * * *'; // daily at 08:00 UTC
|
|
35
43
|
// ─── Inner component (rendered after auth) ─────────────────────────────────
|
|
36
44
|
const AgentTriggerInner = ({ onLogout, }) => {
|
|
37
45
|
const { token } = useSimpleAuthStore();
|
|
38
|
-
const
|
|
46
|
+
const connectedIdentities = useConnectedIdentities();
|
|
47
|
+
const agentName = useRef(uniqueAgentId(AGENT_NAME)).current;
|
|
48
|
+
const approvalAgentName = useRef(uniqueAgentId(APPROVAL_AGENT_NAME)).current;
|
|
49
|
+
const identitiesForRuns = React.useMemo(() => {
|
|
50
|
+
return connectedIdentities
|
|
51
|
+
.filter(identity => identity.token?.accessToken)
|
|
52
|
+
.map(identity => ({
|
|
53
|
+
provider: identity.provider,
|
|
54
|
+
accessToken: identity.token.accessToken,
|
|
55
|
+
}));
|
|
56
|
+
}, [connectedIdentities]);
|
|
57
|
+
const [runtimeStatus, setRuntimeStatus] = useState('idle');
|
|
39
58
|
const [isReady, setIsReady] = useState(false);
|
|
40
59
|
const [hookError, setHookError] = useState(null);
|
|
41
|
-
const [agentId, setAgentId] = useState(
|
|
60
|
+
const [agentId, setAgentId] = useState(agentName);
|
|
42
61
|
const agentBaseUrl = DEFAULT_LOCAL_BASE_URL;
|
|
43
62
|
const chatAuthToken = token === null ? undefined : token;
|
|
44
63
|
// Cron state
|
|
@@ -54,24 +73,143 @@ const AgentTriggerInner = ({ onLogout, }) => {
|
|
|
54
73
|
// Once trigger state
|
|
55
74
|
const [isLaunchingOnce, setIsLaunchingOnce] = useState(false);
|
|
56
75
|
const [onceFlash, setOnceFlash] = useState(null);
|
|
76
|
+
const [lastOnceStartedAt, setLastOnceStartedAt] = useState(null);
|
|
77
|
+
const [hasTriggeredOnce, setHasTriggeredOnce] = useState(false);
|
|
78
|
+
const [streamedOnceOutput, setStreamedOnceOutput] = useState(null);
|
|
79
|
+
const [streamedOnceEndedAt, setStreamedOnceEndedAt] = useState(null);
|
|
57
80
|
// Webhook state
|
|
58
81
|
const [webhookUrl, setWebhookUrl] = useState(null);
|
|
59
82
|
const [webhookSecret, setWebhookSecret] = useState(null);
|
|
60
83
|
const [webhookEnabled, setWebhookEnabled] = useState(false);
|
|
84
|
+
// Sidebar messages state
|
|
85
|
+
const [sidebarMessages, setSidebarMessages] = useState([]);
|
|
86
|
+
const [sidebarMessagesError, setSidebarMessagesError] = useState(null);
|
|
61
87
|
// Event state
|
|
62
88
|
const [eventTopic, setEventTopic] = useState('');
|
|
63
89
|
const [eventFilter, setEventFilter] = useState('');
|
|
64
90
|
const [eventSubscribed, setEventSubscribed] = useState(false);
|
|
91
|
+
// Approval agent state
|
|
92
|
+
const [approvalAgentId, setApprovalAgentId] = useState(approvalAgentName);
|
|
93
|
+
const [approvalAgentReady, setApprovalAgentReady] = useState(false);
|
|
94
|
+
const [isLaunchingApproval, setIsLaunchingApproval] = useState(false);
|
|
95
|
+
const [hasTriggeredApproval, setHasTriggeredApproval] = useState(false);
|
|
96
|
+
const [approvalFlash, setApprovalFlash] = useState(null);
|
|
97
|
+
const toApprovalRequest = useCallback((payload) => ({
|
|
98
|
+
id: payload.id,
|
|
99
|
+
tool_name: payload.tool_name,
|
|
100
|
+
tool_args: payload.tool_args,
|
|
101
|
+
tool_call_id: payload.tool_call_id ?? undefined,
|
|
102
|
+
note: payload.note ?? undefined,
|
|
103
|
+
created_at: payload.created_at,
|
|
104
|
+
status: payload.status,
|
|
105
|
+
agent_id: payload.agent_id,
|
|
106
|
+
}), []);
|
|
107
|
+
const [approvals, setApprovals] = useState([]);
|
|
108
|
+
const [approvalLoading, setApprovalLoading] = useState(null);
|
|
109
|
+
const [approvalError, setApprovalError] = useState(null);
|
|
110
|
+
const [activeApproval, setActiveApproval] = useState(null);
|
|
111
|
+
// Approval sidebar messages
|
|
112
|
+
const [approvalSidebarMessages, setApprovalSidebarMessages] = useState([]);
|
|
113
|
+
const approvalStreamRef = useRef(null);
|
|
65
114
|
// Events hooks
|
|
66
115
|
const eventsQuery = useAgentEvents(agentId);
|
|
67
116
|
const deleteEventMutation = useDeleteAgentEvent(agentId);
|
|
68
117
|
const markReadMutation = useMarkEventRead(agentId);
|
|
69
118
|
const markUnreadMutation = useMarkEventUnread(agentId);
|
|
70
119
|
const agentEvents = eventsQuery.data?.events ?? [];
|
|
71
|
-
// WebSocket
|
|
72
|
-
|
|
120
|
+
// ── WebSocket to datalayer-ai-agents: agent events + tool approvals ─────
|
|
121
|
+
// Single connection handles both agent:{agentId} channel events and the
|
|
122
|
+
// user's own channel (auto-subscribed) for tool_approval_* events.
|
|
123
|
+
// This mirrors the approval flow used by the /agents/tool-approvals UI page
|
|
124
|
+
// so that approving from either surface produces identical behaviour.
|
|
125
|
+
const handleAIAgentsMessage = useCallback((msg) => {
|
|
126
|
+
const event = msg.event;
|
|
127
|
+
const data = msg.data;
|
|
128
|
+
// Handle tool-approvals-history response (seeds initial pending list).
|
|
129
|
+
if (msg.type === 'tool-approvals-history') {
|
|
130
|
+
const rawList = Array.isArray(data?.approvals) ? data.approvals : [];
|
|
131
|
+
const pending = rawList.filter(a => (!a.agent_id || a.agent_id === approvalAgentId) &&
|
|
132
|
+
a.status === 'pending');
|
|
133
|
+
setApprovals(pending.map(toApprovalRequest));
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
if (!event)
|
|
137
|
+
return;
|
|
138
|
+
if (event === 'tool_approval_created') {
|
|
139
|
+
const approval = toApprovalRequest(data);
|
|
140
|
+
if (approval.agent_id && approval.agent_id !== approvalAgentId)
|
|
141
|
+
return;
|
|
142
|
+
setApprovals(prev => [
|
|
143
|
+
approval,
|
|
144
|
+
...prev.filter(a => a.id !== approval.id),
|
|
145
|
+
]);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
if (event === 'tool_approval_approved' ||
|
|
149
|
+
event === 'tool_approval_rejected') {
|
|
150
|
+
const record = data;
|
|
151
|
+
if (record?.agent_id && record.agent_id !== approvalAgentId)
|
|
152
|
+
return;
|
|
153
|
+
setApprovals(prev => prev.filter(a => a.id !== record?.id));
|
|
154
|
+
// When approved and we have a live deferred-tool stream, send the
|
|
155
|
+
// continuation so the agent can execute the tool and produce output.
|
|
156
|
+
// This path fires when the user approves from a DIFFERENT UI surface
|
|
157
|
+
// (e.g. /agents/tool-approvals page) rather than this panel.
|
|
158
|
+
// handleApprove() clears approvalStreamRef before reaching here, so
|
|
159
|
+
// this block only runs for external approvals (no double-send).
|
|
160
|
+
if (event === 'tool_approval_approved') {
|
|
161
|
+
const stream = approvalStreamRef.current;
|
|
162
|
+
if (stream) {
|
|
163
|
+
approvalStreamRef.current = null;
|
|
164
|
+
const toolCallId = stream.adapter.getDeferredToolCallId(record?.tool_name ?? '');
|
|
165
|
+
if (toolCallId) {
|
|
166
|
+
void stream.adapter
|
|
167
|
+
.sendToolResult(toolCallId, {
|
|
168
|
+
toolCallId,
|
|
169
|
+
success: true,
|
|
170
|
+
result: {
|
|
171
|
+
approved: true,
|
|
172
|
+
approvalId: record?.id,
|
|
173
|
+
message: 'Approved from external UI',
|
|
174
|
+
},
|
|
175
|
+
})
|
|
176
|
+
.finally(() => {
|
|
177
|
+
stream.unsubscribe();
|
|
178
|
+
stream.adapter.disconnect();
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
stream.unsubscribe();
|
|
183
|
+
stream.adapter.disconnect();
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}, [approvalAgentId, toApprovalRequest]);
|
|
189
|
+
const { send: sendToAIAgents, connectionState: aiAgentsConnectionState } = useAIAgentsWebSocket({
|
|
73
190
|
channels: agentId ? [`agent:${agentId}`] : [],
|
|
191
|
+
onMessage: handleAIAgentsMessage,
|
|
74
192
|
});
|
|
193
|
+
// Request pending approvals for the active approval agent once connected.
|
|
194
|
+
const approvalHistoryAskedRef = useRef(false);
|
|
195
|
+
useEffect(() => {
|
|
196
|
+
if (aiAgentsConnectionState !== 'connected' ||
|
|
197
|
+
!hasTriggeredApproval ||
|
|
198
|
+
!approvalAgentId) {
|
|
199
|
+
approvalHistoryAskedRef.current = false;
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
if (approvalHistoryAskedRef.current)
|
|
203
|
+
return;
|
|
204
|
+
approvalHistoryAskedRef.current = sendToAIAgents({
|
|
205
|
+
type: 'tool-approvals-history',
|
|
206
|
+
});
|
|
207
|
+
}, [
|
|
208
|
+
aiAgentsConnectionState,
|
|
209
|
+
hasTriggeredApproval,
|
|
210
|
+
approvalAgentId,
|
|
211
|
+
sendToAIAgents,
|
|
212
|
+
]);
|
|
75
213
|
// Authenticated fetch helper (for sidecar endpoints)
|
|
76
214
|
const authFetch = useCallback((url, opts = {}) => fetch(url, {
|
|
77
215
|
...opts,
|
|
@@ -81,77 +219,394 @@ const AgentTriggerInner = ({ onLogout, }) => {
|
|
|
81
219
|
...(opts.headers ?? {}),
|
|
82
220
|
},
|
|
83
221
|
}), [token]);
|
|
84
|
-
// ── Create agent on
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
222
|
+
// ── Create agent on demand (no startup on initial page load) ────────────
|
|
223
|
+
const createAgent = useCallback(async () => {
|
|
224
|
+
setRuntimeStatus('launching');
|
|
225
|
+
setIsReady(false);
|
|
226
|
+
setHookError(null);
|
|
227
|
+
try {
|
|
228
|
+
const response = await authFetch(`${agentBaseUrl}/api/v1/agents`, {
|
|
229
|
+
method: 'POST',
|
|
230
|
+
body: JSON.stringify({
|
|
231
|
+
name: agentName,
|
|
232
|
+
description: 'Agent with cron, webhook, event, and manual triggers',
|
|
233
|
+
agent_library: 'pydantic-ai',
|
|
234
|
+
transport: 'vercel-ai',
|
|
235
|
+
agent_spec_id: AGENT_SPEC_ID,
|
|
236
|
+
}),
|
|
237
|
+
});
|
|
238
|
+
let resolvedAgentId = agentName;
|
|
239
|
+
if (response.ok) {
|
|
240
|
+
const data = await response.json();
|
|
241
|
+
resolvedAgentId = data?.id || agentName;
|
|
242
|
+
}
|
|
243
|
+
else {
|
|
244
|
+
const contentType = response.headers.get('content-type') || '';
|
|
245
|
+
let detail = '';
|
|
246
|
+
if (contentType.includes('application/json')) {
|
|
247
|
+
const data = await response.json().catch(() => null);
|
|
248
|
+
detail =
|
|
249
|
+
(typeof data?.detail === 'string' && data.detail) ||
|
|
250
|
+
(typeof data?.message === 'string' && data.message) ||
|
|
251
|
+
'';
|
|
107
252
|
}
|
|
108
253
|
else {
|
|
109
|
-
|
|
110
|
-
let detail = '';
|
|
111
|
-
if (contentType.includes('application/json')) {
|
|
112
|
-
const data = await response.json().catch(() => null);
|
|
113
|
-
detail =
|
|
114
|
-
(typeof data?.detail === 'string' && data.detail) ||
|
|
115
|
-
(typeof data?.message === 'string' && data.message) ||
|
|
116
|
-
'';
|
|
117
|
-
}
|
|
118
|
-
else {
|
|
119
|
-
detail = await response.text();
|
|
120
|
-
}
|
|
121
|
-
if (response.status === 409 || /already exists/i.test(detail || '')) {
|
|
122
|
-
// Agent already running — reuse it
|
|
123
|
-
}
|
|
124
|
-
else {
|
|
125
|
-
throw new Error(detail || `Failed to create agent: ${response.status}`);
|
|
126
|
-
}
|
|
254
|
+
detail = await response.text();
|
|
127
255
|
}
|
|
128
|
-
if (
|
|
129
|
-
|
|
130
|
-
setIsReady(true);
|
|
131
|
-
setRuntimeStatus('ready');
|
|
256
|
+
if (response.status === 409 || /already exists/i.test(detail || '')) {
|
|
257
|
+
// Agent already running — reuse it
|
|
132
258
|
}
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
if (!isCancelled) {
|
|
136
|
-
setHookError(error instanceof Error ? error.message : 'Agent failed to start');
|
|
137
|
-
setRuntimeStatus('error');
|
|
259
|
+
else {
|
|
260
|
+
throw new Error(detail || `Failed to create agent: ${response.status}`);
|
|
138
261
|
}
|
|
139
262
|
}
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
145
|
-
|
|
263
|
+
setAgentId(resolvedAgentId);
|
|
264
|
+
setIsReady(true);
|
|
265
|
+
setRuntimeStatus('ready');
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
catch (error) {
|
|
269
|
+
setHookError(error instanceof Error ? error.message : 'Agent failed to start');
|
|
270
|
+
setRuntimeStatus('error');
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
}, [agentBaseUrl, authFetch, agentName]);
|
|
274
|
+
const ensureRuntimeReady = useCallback(async () => {
|
|
275
|
+
if (isReady) {
|
|
276
|
+
return true;
|
|
277
|
+
}
|
|
278
|
+
return createAgent();
|
|
279
|
+
}, [isReady, createAgent]);
|
|
146
280
|
// ── Poll trigger metadata ─────────────────────────────────────────────
|
|
147
281
|
// TODO: enable once the ai-agents service exposes /trigger and
|
|
148
282
|
// /trigger/history endpoints on the platform API.
|
|
149
283
|
// Currently these endpoints don't exist on either the local
|
|
150
284
|
// agent-runtimes server or the ai-agents service.
|
|
285
|
+
const upsertSidebarMessage = useCallback((setMessages, message) => {
|
|
286
|
+
setMessages(prev => {
|
|
287
|
+
const idx = prev.findIndex(m => m.id === message.id);
|
|
288
|
+
if (idx >= 0) {
|
|
289
|
+
const next = [...prev];
|
|
290
|
+
next[idx] = { ...next[idx], ...message };
|
|
291
|
+
return next;
|
|
292
|
+
}
|
|
293
|
+
return [...prev, message];
|
|
294
|
+
});
|
|
295
|
+
}, []);
|
|
296
|
+
const streamRunMessages = useCallback(async (targetAgentId, prompt, setMessages, setError, options) => {
|
|
297
|
+
const endpoint = `${agentBaseUrl}/api/v1/vercel-ai/${encodeURIComponent(targetAgentId)}`;
|
|
298
|
+
if (options?.keepAliveForApproval && approvalStreamRef.current) {
|
|
299
|
+
approvalStreamRef.current.unsubscribe();
|
|
300
|
+
approvalStreamRef.current.adapter.disconnect();
|
|
301
|
+
approvalStreamRef.current = null;
|
|
302
|
+
}
|
|
303
|
+
const adapter = new VercelAIAdapter({
|
|
304
|
+
protocol: 'vercel-ai',
|
|
305
|
+
baseUrl: endpoint,
|
|
306
|
+
agentId: targetAgentId,
|
|
307
|
+
headers: token ? { Authorization: `Bearer ${token}` } : undefined,
|
|
308
|
+
});
|
|
309
|
+
let latestAssistantText = null;
|
|
310
|
+
let pendingApproval = false;
|
|
311
|
+
const unsubscribe = adapter.subscribe(event => {
|
|
312
|
+
if (event.type === 'message' && event.message) {
|
|
313
|
+
const msg = event.message;
|
|
314
|
+
const content = typeof msg.content === 'string'
|
|
315
|
+
? msg.content
|
|
316
|
+
: JSON.stringify(msg.content);
|
|
317
|
+
if (msg.role === 'assistant') {
|
|
318
|
+
latestAssistantText = content;
|
|
319
|
+
}
|
|
320
|
+
upsertSidebarMessage(setMessages, {
|
|
321
|
+
id: msg.id,
|
|
322
|
+
role: msg.role,
|
|
323
|
+
content,
|
|
324
|
+
createdAt: msg.createdAt?.toISOString(),
|
|
325
|
+
});
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
if (event.type === 'tool-call' && event.toolCall) {
|
|
329
|
+
upsertSidebarMessage(setMessages, {
|
|
330
|
+
id: `tool-call-${event.toolCall.toolCallId}`,
|
|
331
|
+
role: 'tool',
|
|
332
|
+
content: `${event.toolCall.toolName}(${JSON.stringify(event.toolCall.args)})`,
|
|
333
|
+
createdAt: event.timestamp.toISOString(),
|
|
334
|
+
});
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
if (event.type === 'tool-result' && event.toolResult) {
|
|
338
|
+
const resultObj = event.toolResult.result &&
|
|
339
|
+
typeof event.toolResult.result === 'object'
|
|
340
|
+
? event.toolResult.result
|
|
341
|
+
: undefined;
|
|
342
|
+
if (resultObj?.pending_approval === true) {
|
|
343
|
+
pendingApproval = true;
|
|
344
|
+
}
|
|
345
|
+
upsertSidebarMessage(setMessages, {
|
|
346
|
+
id: `tool-result-${event.toolResult.toolCallId}`,
|
|
347
|
+
role: 'tool',
|
|
348
|
+
content: JSON.stringify(event.toolResult.result),
|
|
349
|
+
createdAt: event.timestamp.toISOString(),
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
});
|
|
353
|
+
try {
|
|
354
|
+
setError(null);
|
|
355
|
+
await adapter.connect();
|
|
356
|
+
await adapter.sendMessage(createUserMessage(prompt), {
|
|
357
|
+
identities: identitiesForRuns,
|
|
358
|
+
});
|
|
359
|
+
return { finalAssistantText: latestAssistantText, pendingApproval };
|
|
360
|
+
}
|
|
361
|
+
catch (error) {
|
|
362
|
+
setError(error instanceof Error ? error.message : 'Streaming request failed');
|
|
363
|
+
throw error;
|
|
364
|
+
}
|
|
365
|
+
finally {
|
|
366
|
+
if (options?.keepAliveForApproval) {
|
|
367
|
+
approvalStreamRef.current = { adapter, unsubscribe };
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
unsubscribe();
|
|
371
|
+
adapter.disconnect();
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
}, [agentBaseUrl, token, identitiesForRuns, upsertSidebarMessage]);
|
|
375
|
+
useEffect(() => {
|
|
376
|
+
return () => {
|
|
377
|
+
if (approvalStreamRef.current) {
|
|
378
|
+
approvalStreamRef.current.unsubscribe();
|
|
379
|
+
approvalStreamRef.current.adapter.disconnect();
|
|
380
|
+
approvalStreamRef.current = null;
|
|
381
|
+
}
|
|
382
|
+
};
|
|
383
|
+
}, []);
|
|
384
|
+
// ── Create approval agent on demand ─────────────────────────────────────
|
|
385
|
+
const createApprovalAgent = useCallback(async () => {
|
|
386
|
+
const createRequest = () => authFetch(`${agentBaseUrl}/api/v1/agents`, {
|
|
387
|
+
method: 'POST',
|
|
388
|
+
body: JSON.stringify({
|
|
389
|
+
name: approvalAgentName,
|
|
390
|
+
description: 'Agent with once trigger and tool approval',
|
|
391
|
+
agent_library: 'pydantic-ai',
|
|
392
|
+
transport: 'vercel-ai',
|
|
393
|
+
agent_spec_id: APPROVAL_AGENT_SPEC_ID,
|
|
394
|
+
enable_codemode: false,
|
|
395
|
+
}),
|
|
396
|
+
});
|
|
397
|
+
try {
|
|
398
|
+
let response = await createRequest();
|
|
399
|
+
let resolvedId = approvalAgentName;
|
|
400
|
+
if (response.ok) {
|
|
401
|
+
const data = await response.json();
|
|
402
|
+
resolvedId = data?.id || approvalAgentName;
|
|
403
|
+
}
|
|
404
|
+
else {
|
|
405
|
+
const contentType = response.headers.get('content-type') || '';
|
|
406
|
+
let detail = '';
|
|
407
|
+
if (contentType.includes('application/json')) {
|
|
408
|
+
const data = await response.json().catch(() => null);
|
|
409
|
+
detail = data?.detail || data?.message || '';
|
|
410
|
+
}
|
|
411
|
+
else {
|
|
412
|
+
detail = await response.text();
|
|
413
|
+
}
|
|
414
|
+
const alreadyExists = response.status === 409 || /already exists/i.test(detail || '');
|
|
415
|
+
if (!alreadyExists) {
|
|
416
|
+
console.warn('Failed to create approval agent:', detail);
|
|
417
|
+
return false;
|
|
418
|
+
}
|
|
419
|
+
// Ensure latest spec/config is applied instead of reusing stale agent state.
|
|
420
|
+
await authFetch(`${agentBaseUrl}/api/v1/agents/${encodeURIComponent(approvalAgentName)}`, {
|
|
421
|
+
method: 'DELETE',
|
|
422
|
+
}).catch(() => undefined);
|
|
423
|
+
response = await createRequest();
|
|
424
|
+
if (!response.ok) {
|
|
425
|
+
console.warn('Failed to recreate approval agent after conflict');
|
|
426
|
+
return false;
|
|
427
|
+
}
|
|
428
|
+
const recreated = await response.json().catch(() => null);
|
|
429
|
+
resolvedId = recreated?.id || approvalAgentName;
|
|
430
|
+
}
|
|
431
|
+
setApprovalAgentId(resolvedId);
|
|
432
|
+
setApprovalAgentReady(true);
|
|
433
|
+
return true;
|
|
434
|
+
}
|
|
435
|
+
catch (error) {
|
|
436
|
+
console.warn('Approval agent creation failed:', error);
|
|
437
|
+
return false;
|
|
438
|
+
}
|
|
439
|
+
}, [agentBaseUrl, authFetch, approvalAgentName]);
|
|
440
|
+
// Approval sidebar messages are populated from live Vercel stream events.
|
|
441
|
+
// ── Approve / Reject handlers ───────────────────────────────────────────
|
|
442
|
+
const handleApprove = useCallback(async (requestId) => {
|
|
443
|
+
setApprovalLoading(requestId);
|
|
444
|
+
setApprovalError(null);
|
|
445
|
+
try {
|
|
446
|
+
const approval = approvals.find(a => a.id === requestId);
|
|
447
|
+
const stream = approvalStreamRef.current;
|
|
448
|
+
// Send the decision via the datalayer-ai-agents WS (same path as the
|
|
449
|
+
// /agents/tool-approvals UI page).
|
|
450
|
+
const sentDecision = sendToAIAgents({
|
|
451
|
+
type: 'tool_approval_decision',
|
|
452
|
+
approvalId: requestId,
|
|
453
|
+
approved: true,
|
|
454
|
+
});
|
|
455
|
+
if (!sentDecision && !stream) {
|
|
456
|
+
throw new Error('AI Agents WebSocket is not connected and no local approval stream is active');
|
|
457
|
+
}
|
|
458
|
+
// Send the Vercel AI continuation so the agent can execute the tool.
|
|
459
|
+
// Clear approvalStreamRef FIRST so the incoming tool_approval_approved
|
|
460
|
+
// WS event doesn't trigger a duplicate sendToolResult.
|
|
461
|
+
if (stream) {
|
|
462
|
+
approvalStreamRef.current = null;
|
|
463
|
+
const toolCallId = approval?.tool_call_id ??
|
|
464
|
+
stream.adapter.getDeferredToolCallId(approval?.tool_name ?? '');
|
|
465
|
+
if (toolCallId) {
|
|
466
|
+
await stream.adapter.sendToolResult(toolCallId, {
|
|
467
|
+
toolCallId,
|
|
468
|
+
success: true,
|
|
469
|
+
result: {
|
|
470
|
+
approved: true,
|
|
471
|
+
approvalId: requestId,
|
|
472
|
+
message: 'Approved from trigger panel',
|
|
473
|
+
},
|
|
474
|
+
});
|
|
475
|
+
}
|
|
476
|
+
stream.unsubscribe();
|
|
477
|
+
stream.adapter.disconnect();
|
|
478
|
+
}
|
|
479
|
+
setApprovals(prev => prev.filter(a => a.id !== requestId));
|
|
480
|
+
return true;
|
|
481
|
+
}
|
|
482
|
+
catch (error) {
|
|
483
|
+
setApprovalError(error instanceof Error ? error.message : 'Failed to approve');
|
|
484
|
+
return false;
|
|
485
|
+
}
|
|
486
|
+
finally {
|
|
487
|
+
setApprovalLoading(null);
|
|
488
|
+
}
|
|
489
|
+
}, [approvals, sendToAIAgents]);
|
|
490
|
+
const handleReject = useCallback(async (requestId, note) => {
|
|
491
|
+
setApprovalLoading(requestId);
|
|
492
|
+
setApprovalError(null);
|
|
493
|
+
try {
|
|
494
|
+
const approval = approvals.find(a => a.id === requestId);
|
|
495
|
+
const stream = approvalStreamRef.current;
|
|
496
|
+
// Send the decision via the datalayer-ai-agents WS (same path as the
|
|
497
|
+
// /agents/tool-approvals UI page).
|
|
498
|
+
const sentDecision = sendToAIAgents({
|
|
499
|
+
type: 'tool_approval_decision',
|
|
500
|
+
approvalId: requestId,
|
|
501
|
+
approved: false,
|
|
502
|
+
...(note ? { note } : {}),
|
|
503
|
+
});
|
|
504
|
+
if (!sentDecision && !stream) {
|
|
505
|
+
throw new Error('AI Agents WebSocket is not connected and no local approval stream is active');
|
|
506
|
+
}
|
|
507
|
+
// Send the Vercel AI continuation so the agent can record the rejection.
|
|
508
|
+
// Clear approvalStreamRef FIRST to prevent a duplicate call from the
|
|
509
|
+
// incoming tool_approval_rejected WS event.
|
|
510
|
+
if (stream) {
|
|
511
|
+
approvalStreamRef.current = null;
|
|
512
|
+
const toolCallId = approval?.tool_call_id ??
|
|
513
|
+
stream.adapter.getDeferredToolCallId(approval?.tool_name ?? '');
|
|
514
|
+
if (toolCallId) {
|
|
515
|
+
await stream.adapter.sendToolResult(toolCallId, {
|
|
516
|
+
toolCallId,
|
|
517
|
+
success: true,
|
|
518
|
+
result: {
|
|
519
|
+
approved: false,
|
|
520
|
+
approvalId: requestId,
|
|
521
|
+
message: note || 'Rejected from trigger panel',
|
|
522
|
+
},
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
stream.unsubscribe();
|
|
526
|
+
stream.adapter.disconnect();
|
|
527
|
+
}
|
|
528
|
+
setApprovals(prev => prev.filter(a => a.id !== requestId));
|
|
529
|
+
return true;
|
|
530
|
+
}
|
|
531
|
+
catch (error) {
|
|
532
|
+
setApprovalError(error instanceof Error ? error.message : 'Failed to reject');
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
finally {
|
|
536
|
+
setApprovalLoading(null);
|
|
537
|
+
}
|
|
538
|
+
}, [approvals, sendToAIAgents]);
|
|
539
|
+
// ── Launch once trigger with approval ────────────────────────────────────
|
|
540
|
+
const handleLaunchOnceApproval = useCallback(async () => {
|
|
541
|
+
if (!agentBaseUrl)
|
|
542
|
+
return;
|
|
543
|
+
const ready = await ensureRuntimeReady();
|
|
544
|
+
if (!ready)
|
|
545
|
+
return;
|
|
546
|
+
let approvalReady = approvalAgentReady;
|
|
547
|
+
if (!approvalReady) {
|
|
548
|
+
approvalReady = await createApprovalAgent();
|
|
549
|
+
}
|
|
550
|
+
if (!approvalReady)
|
|
551
|
+
return;
|
|
552
|
+
setIsLaunchingApproval(true);
|
|
553
|
+
setHasTriggeredApproval(true);
|
|
554
|
+
setApprovalFlash(null);
|
|
555
|
+
setApprovalSidebarMessages([]);
|
|
556
|
+
setSidebarMessagesError(null);
|
|
557
|
+
const runId = `once-approval-${Date.now()}`;
|
|
558
|
+
const startTime = new Date().toISOString();
|
|
559
|
+
setTriggerHistory(prev => [
|
|
560
|
+
{
|
|
561
|
+
id: runId,
|
|
562
|
+
timestamp: startTime,
|
|
563
|
+
status: 'running',
|
|
564
|
+
source: 'once',
|
|
565
|
+
},
|
|
566
|
+
...prev,
|
|
567
|
+
]);
|
|
568
|
+
try {
|
|
569
|
+
setApprovalFlash('Once trigger launched.');
|
|
570
|
+
await streamRunMessages(approvalAgentId, ONCE_TRIGGER_APPROVAL_PROMPT, setApprovalSidebarMessages, setSidebarMessagesError, { keepAliveForApproval: true });
|
|
571
|
+
setTriggerHistory(prev => prev.map(r => r.id === runId
|
|
572
|
+
? {
|
|
573
|
+
...r,
|
|
574
|
+
status: 'success',
|
|
575
|
+
duration_ms: Date.now() - new Date(startTime).getTime(),
|
|
576
|
+
}
|
|
577
|
+
: r));
|
|
578
|
+
}
|
|
579
|
+
catch {
|
|
580
|
+
setApprovalFlash('Network error');
|
|
581
|
+
setTriggerHistory(prev => prev.map(r => r.id === runId ? { ...r, status: 'failure' } : r));
|
|
582
|
+
}
|
|
583
|
+
finally {
|
|
584
|
+
setIsLaunchingApproval(false);
|
|
585
|
+
}
|
|
586
|
+
}, [
|
|
587
|
+
agentBaseUrl,
|
|
588
|
+
approvalAgentReady,
|
|
589
|
+
createApprovalAgent,
|
|
590
|
+
approvalAgentId,
|
|
591
|
+
streamRunMessages,
|
|
592
|
+
ensureRuntimeReady,
|
|
593
|
+
]);
|
|
594
|
+
// ── Pending approvals for banner/dialog ──────────────────────────────────
|
|
595
|
+
const pendingApprovals = approvals.map(req => ({
|
|
596
|
+
id: req.id,
|
|
597
|
+
toolName: req.tool_name,
|
|
598
|
+
toolDescription: req.note,
|
|
599
|
+
args: req.tool_args ?? {},
|
|
600
|
+
agentId: approvalAgentId,
|
|
601
|
+
requestedAt: req.created_at ?? new Date().toISOString(),
|
|
602
|
+
}));
|
|
151
603
|
// ── Update cron ──────────────────────────────────────────────────────────
|
|
152
604
|
const handleUpdateCron = useCallback(async () => {
|
|
153
605
|
if (!agentBaseUrl || !editCron.trim())
|
|
154
606
|
return;
|
|
607
|
+
const ready = await ensureRuntimeReady();
|
|
608
|
+
if (!ready)
|
|
609
|
+
return;
|
|
155
610
|
setIsUpdating(true);
|
|
156
611
|
try {
|
|
157
612
|
const res = await authFetch(`${agentBaseUrl}/api/v1/agents/${agentId}/trigger`, {
|
|
@@ -171,11 +626,14 @@ const AgentTriggerInner = ({ onLogout, }) => {
|
|
|
171
626
|
finally {
|
|
172
627
|
setIsUpdating(false);
|
|
173
628
|
}
|
|
174
|
-
}, [agentBaseUrl, agentId, editCron, authFetch]);
|
|
629
|
+
}, [agentBaseUrl, agentId, editCron, authFetch, ensureRuntimeReady]);
|
|
175
630
|
// ── Manual trigger ───────────────────────────────────────────────────────
|
|
176
631
|
const handleTriggerNow = useCallback(async () => {
|
|
177
632
|
if (!agentBaseUrl)
|
|
178
633
|
return;
|
|
634
|
+
const ready = await ensureRuntimeReady();
|
|
635
|
+
if (!ready)
|
|
636
|
+
return;
|
|
179
637
|
setIsTriggeringNow(true);
|
|
180
638
|
setTriggerFlash(null);
|
|
181
639
|
const runId = `manual-${Date.now()}`;
|
|
@@ -190,7 +648,13 @@ const AgentTriggerInner = ({ onLogout, }) => {
|
|
|
190
648
|
...prev,
|
|
191
649
|
]);
|
|
192
650
|
try {
|
|
193
|
-
const res = await authFetch(`${agentBaseUrl}/api/v1/agents/${agentId}/trigger/run`, {
|
|
651
|
+
const res = await authFetch(`${agentBaseUrl}/api/v1/agents/${agentId}/trigger/run`, {
|
|
652
|
+
method: 'POST',
|
|
653
|
+
body: JSON.stringify({
|
|
654
|
+
source: 'manual',
|
|
655
|
+
identities: identitiesForRuns,
|
|
656
|
+
}),
|
|
657
|
+
});
|
|
194
658
|
if (res.ok) {
|
|
195
659
|
setTriggerFlash('Trigger fired successfully');
|
|
196
660
|
setTriggerHistory(prev => prev.map(r => r.id === runId
|
|
@@ -213,11 +677,14 @@ const AgentTriggerInner = ({ onLogout, }) => {
|
|
|
213
677
|
finally {
|
|
214
678
|
setIsTriggeringNow(false);
|
|
215
679
|
}
|
|
216
|
-
}, [agentBaseUrl, agentId, authFetch]);
|
|
680
|
+
}, [agentBaseUrl, agentId, authFetch, identitiesForRuns, ensureRuntimeReady]);
|
|
217
681
|
// ── Webhook management ─────────────────────────────────────────────────
|
|
218
682
|
const handleGenerateWebhook = useCallback(async () => {
|
|
219
683
|
if (!agentBaseUrl)
|
|
220
684
|
return;
|
|
685
|
+
const ready = await ensureRuntimeReady();
|
|
686
|
+
if (!ready)
|
|
687
|
+
return;
|
|
221
688
|
setIsUpdating(true);
|
|
222
689
|
try {
|
|
223
690
|
const res = await authFetch(`${agentBaseUrl}/api/v1/agents/${agentId}/trigger/webhook`, { method: 'POST' });
|
|
@@ -234,10 +701,13 @@ const AgentTriggerInner = ({ onLogout, }) => {
|
|
|
234
701
|
finally {
|
|
235
702
|
setIsUpdating(false);
|
|
236
703
|
}
|
|
237
|
-
}, [agentBaseUrl, agentId, authFetch]);
|
|
704
|
+
}, [agentBaseUrl, agentId, authFetch, ensureRuntimeReady]);
|
|
238
705
|
const handleToggleWebhook = useCallback(async () => {
|
|
239
706
|
if (!agentBaseUrl)
|
|
240
707
|
return;
|
|
708
|
+
const ready = await ensureRuntimeReady();
|
|
709
|
+
if (!ready)
|
|
710
|
+
return;
|
|
241
711
|
try {
|
|
242
712
|
await authFetch(`${agentBaseUrl}/api/v1/agents/${agentId}/trigger/webhook`, {
|
|
243
713
|
method: 'PATCH',
|
|
@@ -248,11 +718,14 @@ const AgentTriggerInner = ({ onLogout, }) => {
|
|
|
248
718
|
catch {
|
|
249
719
|
/* ok */
|
|
250
720
|
}
|
|
251
|
-
}, [agentBaseUrl, agentId, webhookEnabled, authFetch]);
|
|
721
|
+
}, [agentBaseUrl, agentId, webhookEnabled, authFetch, ensureRuntimeReady]);
|
|
252
722
|
// ── Event subscription ─────────────────────────────────────────────────
|
|
253
723
|
const handleSubscribeEvent = useCallback(async () => {
|
|
254
724
|
if (!agentBaseUrl || !eventTopic.trim())
|
|
255
725
|
return;
|
|
726
|
+
const ready = await ensureRuntimeReady();
|
|
727
|
+
if (!ready)
|
|
728
|
+
return;
|
|
256
729
|
setIsUpdating(true);
|
|
257
730
|
try {
|
|
258
731
|
const res = await authFetch(`${agentBaseUrl}/api/v1/agents/${agentId}/trigger/event`, {
|
|
@@ -272,15 +745,31 @@ const AgentTriggerInner = ({ onLogout, }) => {
|
|
|
272
745
|
finally {
|
|
273
746
|
setIsUpdating(false);
|
|
274
747
|
}
|
|
275
|
-
}, [
|
|
748
|
+
}, [
|
|
749
|
+
agentBaseUrl,
|
|
750
|
+
agentId,
|
|
751
|
+
eventTopic,
|
|
752
|
+
eventFilter,
|
|
753
|
+
authFetch,
|
|
754
|
+
ensureRuntimeReady,
|
|
755
|
+
]);
|
|
276
756
|
// ── Launch once trigger ──────────────────────────────────────────────────
|
|
277
757
|
const handleLaunchOnce = useCallback(async () => {
|
|
278
758
|
if (!agentBaseUrl)
|
|
279
759
|
return;
|
|
760
|
+
const ready = await ensureRuntimeReady();
|
|
761
|
+
if (!ready)
|
|
762
|
+
return;
|
|
280
763
|
setIsLaunchingOnce(true);
|
|
281
764
|
setOnceFlash(null);
|
|
765
|
+
setStreamedOnceOutput(null);
|
|
766
|
+
setStreamedOnceEndedAt(null);
|
|
767
|
+
setSidebarMessages([]);
|
|
768
|
+
setSidebarMessagesError(null);
|
|
282
769
|
const runId = `once-${Date.now()}`;
|
|
283
770
|
const startTime = new Date().toISOString();
|
|
771
|
+
setLastOnceStartedAt(startTime);
|
|
772
|
+
setHasTriggeredOnce(true);
|
|
284
773
|
setTriggerHistory(prev => [
|
|
285
774
|
{
|
|
286
775
|
id: runId,
|
|
@@ -291,21 +780,17 @@ const AgentTriggerInner = ({ onLogout, }) => {
|
|
|
291
780
|
...prev,
|
|
292
781
|
]);
|
|
293
782
|
try {
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
else {
|
|
306
|
-
setOnceFlash(`Launch failed (${res.status})`);
|
|
307
|
-
setTriggerHistory(prev => prev.map(r => r.id === runId ? { ...r, status: 'failure' } : r));
|
|
308
|
-
}
|
|
783
|
+
setOnceFlash('Once trigger launched — streaming live output.');
|
|
784
|
+
const streamResult = await streamRunMessages(agentId, ONCE_TRIGGER_PROMPT, setSidebarMessages, setSidebarMessagesError);
|
|
785
|
+
setStreamedOnceOutput(streamResult.finalAssistantText);
|
|
786
|
+
setStreamedOnceEndedAt(new Date().toISOString());
|
|
787
|
+
setTriggerHistory(prev => prev.map(r => r.id === runId
|
|
788
|
+
? {
|
|
789
|
+
...r,
|
|
790
|
+
status: 'success',
|
|
791
|
+
duration_ms: Date.now() - new Date(startTime).getTime(),
|
|
792
|
+
}
|
|
793
|
+
: r));
|
|
309
794
|
}
|
|
310
795
|
catch {
|
|
311
796
|
setOnceFlash('Network error');
|
|
@@ -314,9 +799,9 @@ const AgentTriggerInner = ({ onLogout, }) => {
|
|
|
314
799
|
finally {
|
|
315
800
|
setIsLaunchingOnce(false);
|
|
316
801
|
}
|
|
317
|
-
}, [agentBaseUrl, agentId,
|
|
802
|
+
}, [agentBaseUrl, agentId, streamRunMessages, ensureRuntimeReady]);
|
|
318
803
|
// ── Loading / Error ──────────────────────────────────────────────────────
|
|
319
|
-
if (
|
|
804
|
+
if (runtimeStatus === 'launching') {
|
|
320
805
|
return (_jsxs(Box, { sx: {
|
|
321
806
|
display: 'flex',
|
|
322
807
|
flexDirection: 'column',
|
|
@@ -324,168 +809,509 @@ const AgentTriggerInner = ({ onLogout, }) => {
|
|
|
324
809
|
justifyContent: 'center',
|
|
325
810
|
height: '100vh',
|
|
326
811
|
gap: 3,
|
|
327
|
-
}, children: [_jsx(Spinner, { size: "large" }), _jsx(Text, { sx: { color: 'fg.muted' }, children:
|
|
328
|
-
? 'Launching runtime for trigger agent…'
|
|
329
|
-
: 'Creating trigger demo agent…' })] }));
|
|
812
|
+
}, children: [_jsx(Spinner, { size: "large" }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Launching runtime for trigger agent..." })] }));
|
|
330
813
|
}
|
|
331
814
|
if (runtimeStatus === 'error' || hookError) {
|
|
332
|
-
return (
|
|
815
|
+
return _jsx(ErrorView, { error: hookError, onLogout: onLogout });
|
|
816
|
+
}
|
|
817
|
+
const triggerRunCurl = `curl -N -X POST '${agentBaseUrl}/api/v1/vercel-ai/${agentId}' -H 'Content-Type: application/json' -H 'Accept: text/event-stream'${token ? " -H 'Authorization: Bearer <TOKEN>'" : ''} --data '{"messages":[{"role":"user","parts":[{"type":"text","text":"${ONCE_TRIGGER_PROMPT.replace(/"/g, '\\"')}"}]}],"trigger":"submit-message","sdkVersion":6}'`;
|
|
818
|
+
const isAgentLaunching = isLaunchingOnce || isLaunchingApproval;
|
|
819
|
+
return (_jsx("fieldset", { disabled: isAgentLaunching, style: {
|
|
820
|
+
border: 0,
|
|
821
|
+
margin: 0,
|
|
822
|
+
padding: 0,
|
|
823
|
+
minWidth: 0,
|
|
824
|
+
}, children: _jsxs(Box, { sx: {
|
|
825
|
+
height: 'calc(100vh - 60px)',
|
|
333
826
|
display: 'flex',
|
|
334
827
|
flexDirection: 'column',
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
}, children: [_jsx(Box, { sx: {
|
|
828
|
+
}, children: [_jsxs(Box, { sx: {
|
|
829
|
+
display: 'flex',
|
|
830
|
+
alignItems: 'center',
|
|
831
|
+
gap: 2,
|
|
832
|
+
px: 3,
|
|
833
|
+
py: 2,
|
|
834
|
+
borderBottom: '1px solid',
|
|
835
|
+
borderColor: 'border.default',
|
|
836
|
+
flexShrink: 0,
|
|
837
|
+
}, children: [_jsx(ClockIcon, { size: 16 }), _jsxs(Heading, { as: "h3", sx: { fontSize: 2, flex: 1 }, children: ["Triggers \u2014 ", agentId] }), !isReady && (_jsx(Button, { size: "small", variant: "primary", onClick: () => void createAgent(), children: "Start Runtime" })), _jsxs(Label, { variant: aiAgentsConnectionState === 'connected' ? 'success' : 'secondary', children: ["Approvals WS: ", aiAgentsConnectionState] })] }), _jsx(ToolApprovalBanner, { pendingApprovals: pendingApprovals, onReview: approval => {
|
|
838
|
+
const req = approvals.find(a => a.id === approval.id) || null;
|
|
839
|
+
setActiveApproval(req);
|
|
840
|
+
}, onApproveAll: async () => {
|
|
841
|
+
for (const a of approvals) {
|
|
842
|
+
await handleApprove(a.id);
|
|
843
|
+
}
|
|
844
|
+
} }), _jsx(ToolApprovalDialog, { isOpen: !!activeApproval, toolName: activeApproval?.tool_name ?? '', toolDescription: activeApproval?.note, args: activeApproval?.tool_args ?? {}, onApprove: async () => {
|
|
845
|
+
if (activeApproval) {
|
|
846
|
+
const ok = await handleApprove(activeApproval.id);
|
|
847
|
+
if (ok) {
|
|
848
|
+
setActiveApproval(null);
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}, onDeny: async () => {
|
|
852
|
+
if (activeApproval) {
|
|
853
|
+
const ok = await handleReject(activeApproval.id, 'Rejected from dialog');
|
|
854
|
+
if (ok) {
|
|
855
|
+
setActiveApproval(null);
|
|
856
|
+
}
|
|
857
|
+
}
|
|
858
|
+
}, onClose: () => setActiveApproval(null) }), approvalError && (_jsx(Box, { sx: { px: 3, py: 1 }, children: _jsx(Text, { sx: { color: 'danger.fg', fontSize: 0 }, children: approvalError }) })), _jsxs(Box, { sx: { flex: 1, minHeight: 0, display: 'flex' }, children: [_jsx(Box, { sx: { flex: 1, minWidth: 0 }, children: isReady ? (_jsx(Chat, { protocol: "vercel-ai", baseUrl: agentBaseUrl, agentId: agentId, authToken: chatAuthToken, title: "Trigger Agent", description: `View-only trigger output. Cron: ${cronExpr} | Webhook: ${webhookEnabled ? 'on' : 'off'} | Event: ${eventSubscribed ? eventTopic : 'none'}`, showHeader: true, autoFocus: false, height: "100%", runtimeId: agentId, showInput: true, disableInputPrompt: true, showModelSelector: false, showToolsMenu: true, showSkillsMenu: true })) : (_jsx(Box, { sx: {
|
|
859
|
+
height: '100%',
|
|
368
860
|
display: 'flex',
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
}, children:
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
861
|
+
alignItems: 'center',
|
|
862
|
+
justifyContent: 'center',
|
|
863
|
+
p: 4,
|
|
864
|
+
}, children: _jsx(Text, { sx: { color: 'fg.muted' }, children: "Runtime is not started yet. Use Start Runtime or launch a trigger to start it." }) })) }), _jsxs(Box, { sx: {
|
|
865
|
+
width: 380,
|
|
866
|
+
borderLeft: '1px solid',
|
|
867
|
+
borderColor: 'border.default',
|
|
868
|
+
display: 'flex',
|
|
869
|
+
flexDirection: 'column',
|
|
870
|
+
overflow: 'auto',
|
|
871
|
+
}, children: [_jsx(Box, { sx: {
|
|
872
|
+
display: 'flex',
|
|
873
|
+
borderBottom: '1px solid',
|
|
874
|
+
borderColor: 'border.default',
|
|
875
|
+
flexShrink: 0,
|
|
876
|
+
}, children: [
|
|
877
|
+
{ key: 'once', icon: ZapIcon, label: 'Once' },
|
|
878
|
+
{ key: 'cron', icon: ClockIcon, label: 'Cron' },
|
|
879
|
+
{
|
|
880
|
+
key: 'webhook',
|
|
881
|
+
icon: GlobeIcon,
|
|
882
|
+
label: 'Webhook',
|
|
883
|
+
},
|
|
884
|
+
{ key: 'event', icon: ZapIcon, label: 'Event' },
|
|
885
|
+
{
|
|
886
|
+
key: 'manual',
|
|
887
|
+
icon: PlayIcon,
|
|
888
|
+
label: 'Manual',
|
|
889
|
+
},
|
|
890
|
+
].map(t => (_jsx(Button, { size: "small", variant: "invisible", leadingVisual: t.icon, onClick: () => setActiveTab(t.key), sx: {
|
|
891
|
+
flex: 1,
|
|
892
|
+
borderRadius: 0,
|
|
893
|
+
borderBottom: activeTab === t.key
|
|
894
|
+
? '2px solid'
|
|
895
|
+
: '2px solid transparent',
|
|
896
|
+
borderColor: activeTab === t.key ? 'accent.fg' : 'transparent',
|
|
897
|
+
fontWeight: activeTab === t.key ? 'bold' : 'normal',
|
|
898
|
+
}, children: t.label }, t.key))) }), activeTab === 'once' && (_jsxs(Box, { sx: {
|
|
899
|
+
p: 3,
|
|
900
|
+
borderBottom: '1px solid',
|
|
901
|
+
borderColor: 'border.default',
|
|
902
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, mb: 2 }, children: [_jsx(ZapIcon, { size: 16 }), _jsx(Heading, { as: "h3", sx: { fontSize: 2 }, children: "Once Trigger" })] }), _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mb: 3 }, children: "Launch a single streaming run for the once-trigger prompt and watch tool calls, tool results, and assistant text update in real time." }), _jsxs(Box, { sx: {
|
|
903
|
+
bg: 'canvas.subtle',
|
|
904
|
+
border: '1px solid',
|
|
905
|
+
borderColor: 'border.default',
|
|
906
|
+
borderRadius: 2,
|
|
907
|
+
p: 2,
|
|
908
|
+
mb: 2,
|
|
909
|
+
display: 'grid',
|
|
910
|
+
gap: 1,
|
|
911
|
+
}, children: [_jsxs(Text, { sx: { fontSize: 0 }, children: [_jsx("strong", { children: "Agent ID:" }), " ", agentId] }), _jsxs(Text, { sx: { fontSize: 0 }, children: [_jsx("strong", { children: "Base URL:" }), " ", agentBaseUrl] }), lastOnceStartedAt && (_jsxs(Text, { sx: { fontSize: 0 }, children: [_jsx("strong", { children: "Last once launch:" }), ' ', new Date(lastOnceStartedAt).toLocaleString()] }))] }), _jsx(Button, { size: "small", variant: "primary", leadingVisual: ZapIcon, onClick: handleLaunchOnce, disabled: isLaunchingOnce, sx: { width: '100%' }, children: isLaunchingOnce ? 'Launching…' : 'Launch Once' }), _jsx(Button, { size: "small", variant: "danger", leadingVisual: ZapIcon, onClick: handleLaunchOnceApproval, disabled: isLaunchingApproval, sx: { width: '100%', mt: 2 }, children: isLaunchingApproval
|
|
912
|
+
? 'Launching…'
|
|
913
|
+
: 'Launch Once with Approval' }), onceFlash && (_jsx(Flash, { variant: onceFlash.includes('launched') ? 'success' : 'danger', sx: { mt: 2, fontSize: 0 }, children: onceFlash })), approvalFlash && (_jsx(Flash, { variant: approvalFlash.includes('launched') ? 'success' : 'danger', sx: { mt: 2, fontSize: 0 }, children: approvalFlash })), hasTriggeredOnce && (_jsxs(_Fragment, { children: [_jsx(Heading, { as: "h4", sx: { fontSize: 1, mt: 3, mb: 2 }, children: "Generated Output" }), (() => {
|
|
914
|
+
const outputEvent = lastOnceStartedAt
|
|
915
|
+
? [...agentEvents]
|
|
916
|
+
.filter(e => e.kind === 'agent-output' &&
|
|
917
|
+
new Date(e.created_at).getTime() >=
|
|
918
|
+
new Date(lastOnceStartedAt).getTime() - 5000)
|
|
919
|
+
.sort((a, b) => new Date(b.created_at).getTime() -
|
|
920
|
+
new Date(a.created_at).getTime())[0]
|
|
921
|
+
: undefined;
|
|
922
|
+
const hasStreamFallback = !outputEvent &&
|
|
923
|
+
!isLaunchingOnce &&
|
|
924
|
+
streamedOnceOutput !== null;
|
|
925
|
+
if (!outputEvent && !hasStreamFallback) {
|
|
926
|
+
return (_jsxs(Box, { sx: {
|
|
927
|
+
display: 'flex',
|
|
928
|
+
alignItems: 'center',
|
|
929
|
+
gap: 2,
|
|
930
|
+
mb: 2,
|
|
931
|
+
}, children: [_jsx(Spinner, { size: "small" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: "Waiting for agent output\u2026" })] }));
|
|
932
|
+
}
|
|
933
|
+
const p = outputEvent?.payload;
|
|
934
|
+
const outputText = outputEvent
|
|
935
|
+
? p?.outputs
|
|
936
|
+
? String(p.outputs)
|
|
937
|
+
: ''
|
|
938
|
+
: (streamedOnceOutput ?? '');
|
|
939
|
+
const exitStatus = outputEvent
|
|
940
|
+
? p?.exit_status
|
|
941
|
+
: 'completed';
|
|
942
|
+
const durationMs = outputEvent
|
|
943
|
+
? p?.duration_ms
|
|
944
|
+
: lastOnceStartedAt && streamedOnceEndedAt
|
|
945
|
+
? new Date(streamedOnceEndedAt).getTime() -
|
|
946
|
+
new Date(lastOnceStartedAt).getTime()
|
|
947
|
+
: undefined;
|
|
948
|
+
const endedAt = outputEvent
|
|
949
|
+
? p?.ended_at
|
|
950
|
+
: streamedOnceEndedAt;
|
|
951
|
+
return (_jsxs(Box, { sx: {
|
|
952
|
+
mb: 2,
|
|
953
|
+
border: '1px solid',
|
|
954
|
+
borderColor: exitStatus === 'error'
|
|
955
|
+
? 'danger.muted'
|
|
956
|
+
: 'success.muted',
|
|
957
|
+
borderRadius: 2,
|
|
958
|
+
overflow: 'hidden',
|
|
959
|
+
}, children: [_jsxs(Box, { sx: {
|
|
960
|
+
display: 'flex',
|
|
961
|
+
alignItems: 'center',
|
|
962
|
+
gap: 1,
|
|
963
|
+
px: 2,
|
|
964
|
+
py: 1,
|
|
965
|
+
bg: exitStatus === 'error'
|
|
966
|
+
? 'danger.subtle'
|
|
967
|
+
: 'success.subtle',
|
|
968
|
+
borderBottom: '1px solid',
|
|
969
|
+
borderColor: exitStatus === 'error'
|
|
970
|
+
? 'danger.muted'
|
|
971
|
+
: 'success.muted',
|
|
972
|
+
}, children: [_jsx(Label, { variant: exitStatus === 'error' ? 'danger' : 'success', size: "small", children: exitStatus ?? 'completed' }), durationMs != null && (_jsxs(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: [(Number(durationMs) / 1000).toFixed(1), "s"] })), endedAt && (_jsx(Text, { sx: {
|
|
973
|
+
fontSize: 0,
|
|
974
|
+
color: 'fg.muted',
|
|
975
|
+
ml: 'auto',
|
|
976
|
+
whiteSpace: 'nowrap',
|
|
977
|
+
}, children: new Date(endedAt).toLocaleString() })), _jsx(Button, { size: "small", variant: "invisible", sx: { p: 1 }, onClick: () => navigator.clipboard.writeText(outputText), children: _jsx(CopyIcon, { size: 12 }) })] }), _jsx(Box, { sx: {
|
|
978
|
+
p: 2,
|
|
979
|
+
bg: 'canvas.default',
|
|
980
|
+
maxHeight: 300,
|
|
981
|
+
overflow: 'auto',
|
|
982
|
+
}, children: _jsx(Text, { sx: {
|
|
983
|
+
fontSize: 0,
|
|
984
|
+
color: 'fg.default',
|
|
985
|
+
display: 'block',
|
|
986
|
+
whiteSpace: 'pre-wrap',
|
|
987
|
+
wordBreak: 'break-word',
|
|
988
|
+
fontFamily: 'mono',
|
|
989
|
+
}, children: outputText || '(empty output)' }) })] }));
|
|
990
|
+
})()] })), hasTriggeredOnce && (_jsxs(_Fragment, { children: [_jsxs(Heading, { as: "h4", sx: { fontSize: 1, mt: 3, mb: 2 }, children: ["Streaming Messages", isLaunchingOnce && (_jsx(Spinner, { size: "small", sx: {
|
|
991
|
+
ml: 2,
|
|
992
|
+
verticalAlign: 'middle',
|
|
993
|
+
width: 14,
|
|
994
|
+
height: 14,
|
|
995
|
+
minWidth: 14,
|
|
996
|
+
minHeight: 14,
|
|
997
|
+
} }))] }), sidebarMessagesError ? (_jsx(Flash, { variant: "danger", sx: { fontSize: 0, mb: 2 }, children: sidebarMessagesError })) : sidebarMessages.filter(msg => msg.role !== 'user')
|
|
998
|
+
.length === 0 ? (_jsxs(Box, { sx: {
|
|
999
|
+
display: 'flex',
|
|
1000
|
+
alignItems: 'center',
|
|
1001
|
+
gap: 2,
|
|
1002
|
+
mb: 2,
|
|
1003
|
+
}, children: [_jsx(Spinner, { size: "small", sx: {
|
|
1004
|
+
width: 16,
|
|
1005
|
+
height: 16,
|
|
1006
|
+
minWidth: 16,
|
|
1007
|
+
minHeight: 16,
|
|
1008
|
+
} }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: "Waiting for streaming messages\u2026" })] })) : (_jsx(Box, { sx: {
|
|
1009
|
+
display: 'flex',
|
|
1010
|
+
flexDirection: 'column',
|
|
1011
|
+
gap: 2,
|
|
1012
|
+
mb: 2,
|
|
1013
|
+
}, children: sidebarMessages
|
|
1014
|
+
.slice()
|
|
1015
|
+
.filter(msg => msg.role !== 'user')
|
|
1016
|
+
.sort((a, b) => {
|
|
1017
|
+
const ta = a.createdAt
|
|
1018
|
+
? new Date(a.createdAt).getTime()
|
|
1019
|
+
: 0;
|
|
1020
|
+
const tb = b.createdAt
|
|
1021
|
+
? new Date(b.createdAt).getTime()
|
|
1022
|
+
: 0;
|
|
1023
|
+
return tb - ta;
|
|
1024
|
+
})
|
|
1025
|
+
.slice(0, 10)
|
|
1026
|
+
.map(msg => {
|
|
1027
|
+
const content = typeof msg.content === 'string'
|
|
1028
|
+
? msg.content
|
|
1029
|
+
: JSON.stringify(msg.content);
|
|
1030
|
+
return (_jsxs(Box, { sx: {
|
|
1031
|
+
p: 2,
|
|
1032
|
+
bg: 'canvas.subtle',
|
|
1033
|
+
borderRadius: 2,
|
|
1034
|
+
border: '1px solid',
|
|
1035
|
+
borderColor: 'border.default',
|
|
1036
|
+
}, children: [_jsxs(Box, { sx: {
|
|
1037
|
+
display: 'flex',
|
|
1038
|
+
alignItems: 'center',
|
|
1039
|
+
gap: 1,
|
|
1040
|
+
mb: 1,
|
|
1041
|
+
}, children: [_jsx(Label, { size: "small", variant: msg.role === 'assistant'
|
|
1042
|
+
? 'accent'
|
|
1043
|
+
: msg.role === 'tool'
|
|
1044
|
+
? 'success'
|
|
1045
|
+
: 'secondary', children: msg.role }), msg.createdAt && (_jsx(Text, { sx: {
|
|
1046
|
+
fontSize: 0,
|
|
1047
|
+
color: 'fg.muted',
|
|
1048
|
+
marginLeft: 'auto',
|
|
1049
|
+
whiteSpace: 'nowrap',
|
|
1050
|
+
}, children: new Date(msg.createdAt).toLocaleTimeString() }))] }), _jsx(Text, { sx: {
|
|
1051
|
+
fontSize: 0,
|
|
1052
|
+
color: 'fg.default',
|
|
1053
|
+
display: 'block',
|
|
1054
|
+
whiteSpace: 'pre-wrap',
|
|
1055
|
+
wordBreak: 'break-word',
|
|
1056
|
+
}, children: content.length > 320
|
|
1057
|
+
? `${content.slice(0, 320)}…`
|
|
1058
|
+
: content })] }, `once-msg-${msg.id}`));
|
|
1059
|
+
}) }))] })), hasTriggeredApproval && approvals.length > 0 && (_jsxs(_Fragment, { children: [_jsx(Heading, { as: "h4", sx: { fontSize: 1, mt: 3, mb: 2 }, children: "Pending Tool Approvals" }), _jsx(Box, { sx: {
|
|
1060
|
+
display: 'flex',
|
|
1061
|
+
flexDirection: 'column',
|
|
1062
|
+
gap: 2,
|
|
1063
|
+
mb: 2,
|
|
1064
|
+
}, children: approvals.map(a => (_jsxs(Box, { sx: {
|
|
414
1065
|
p: 2,
|
|
1066
|
+
border: '1px solid',
|
|
1067
|
+
borderColor: 'attention.muted',
|
|
415
1068
|
borderRadius: 2,
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
1069
|
+
bg: 'attention.subtle',
|
|
1070
|
+
}, children: [_jsxs(Text, { sx: {
|
|
1071
|
+
fontWeight: 600,
|
|
1072
|
+
fontSize: 1,
|
|
1073
|
+
display: 'block',
|
|
1074
|
+
mb: 1,
|
|
1075
|
+
}, children: ["\uD83D\uDEE1\uFE0F ", a.tool_name] }), a.tool_args && (_jsx(Text, { sx: {
|
|
1076
|
+
fontSize: 0,
|
|
1077
|
+
color: 'fg.muted',
|
|
1078
|
+
display: 'block',
|
|
1079
|
+
mb: 2,
|
|
1080
|
+
fontFamily: 'mono',
|
|
1081
|
+
}, children: JSON.stringify(a.tool_args) })), _jsxs(Box, { sx: { display: 'flex', gap: 1 }, children: [_jsx(Button, { size: "small", variant: "primary", onClick: () => void handleApprove(a.id), disabled: approvalLoading === a.id, children: approvalLoading === a.id
|
|
1082
|
+
? 'Approving…'
|
|
1083
|
+
: 'Approve' }), _jsx(Button, { size: "small", variant: "danger", onClick: () => void handleReject(a.id, 'Rejected from trigger panel'), disabled: approvalLoading === a.id, children: "Reject" })] })] }, a.id))) })] })), hasTriggeredApproval && (_jsxs(_Fragment, { children: [_jsxs(Heading, { as: "h4", sx: { fontSize: 1, mt: 3, mb: 2 }, children: ["Approval Agent Messages", isLaunchingApproval && (_jsx(Spinner, { size: "small", sx: {
|
|
1084
|
+
ml: 2,
|
|
1085
|
+
verticalAlign: 'middle',
|
|
1086
|
+
width: 14,
|
|
1087
|
+
height: 14,
|
|
1088
|
+
minWidth: 14,
|
|
1089
|
+
minHeight: 14,
|
|
1090
|
+
} }))] }), hasTriggeredApproval && (_jsxs(_Fragment, { children: [_jsx(Heading, { as: "h4", sx: { fontSize: 1, mt: 3, mb: 2 }, children: "Generated Output" }), (() => {
|
|
1091
|
+
const latestAssistantOutput = approvalSidebarMessages
|
|
1092
|
+
.filter(msg => msg.role === 'assistant')
|
|
1093
|
+
.sort((a, b) => {
|
|
1094
|
+
const ta = a.createdAt
|
|
1095
|
+
? new Date(a.createdAt).getTime()
|
|
1096
|
+
: 0;
|
|
1097
|
+
const tb = b.createdAt
|
|
1098
|
+
? new Date(b.createdAt).getTime()
|
|
1099
|
+
: 0;
|
|
1100
|
+
return tb - ta;
|
|
1101
|
+
})[0];
|
|
1102
|
+
if (!latestAssistantOutput) {
|
|
1103
|
+
return (_jsxs(Box, { sx: {
|
|
1104
|
+
display: 'flex',
|
|
1105
|
+
alignItems: 'center',
|
|
1106
|
+
gap: 2,
|
|
1107
|
+
mb: 2,
|
|
1108
|
+
}, children: [_jsx(Spinner, { size: "small", sx: {
|
|
1109
|
+
width: 16,
|
|
1110
|
+
height: 16,
|
|
1111
|
+
minWidth: 16,
|
|
1112
|
+
minHeight: 16,
|
|
1113
|
+
} }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: "Waiting for agent output\u2026" })] }));
|
|
1114
|
+
}
|
|
1115
|
+
const outputText = typeof latestAssistantOutput.content === 'string'
|
|
1116
|
+
? latestAssistantOutput.content
|
|
1117
|
+
: JSON.stringify(latestAssistantOutput.content);
|
|
1118
|
+
return (_jsxs(Box, { sx: {
|
|
1119
|
+
mb: 2,
|
|
1120
|
+
border: '1px solid',
|
|
1121
|
+
borderColor: 'success.muted',
|
|
1122
|
+
borderRadius: 2,
|
|
1123
|
+
overflow: 'hidden',
|
|
1124
|
+
}, children: [_jsxs(Box, { sx: {
|
|
1125
|
+
display: 'flex',
|
|
1126
|
+
alignItems: 'center',
|
|
1127
|
+
gap: 1,
|
|
1128
|
+
px: 2,
|
|
1129
|
+
py: 1,
|
|
1130
|
+
bg: 'success.subtle',
|
|
1131
|
+
borderBottom: '1px solid',
|
|
1132
|
+
borderColor: 'success.muted',
|
|
1133
|
+
}, children: [_jsx(Label, { variant: "success", size: "small", children: "completed" }), latestAssistantOutput.createdAt && (_jsx(Text, { sx: {
|
|
1134
|
+
fontSize: 0,
|
|
1135
|
+
color: 'fg.muted',
|
|
1136
|
+
ml: 'auto',
|
|
1137
|
+
whiteSpace: 'nowrap',
|
|
1138
|
+
}, children: new Date(latestAssistantOutput.createdAt).toLocaleString() })), _jsx(Button, { size: "small", variant: "invisible", sx: { p: 1 }, onClick: () => navigator.clipboard.writeText(outputText), children: _jsx(CopyIcon, { size: 12 }) })] }), _jsx(Box, { sx: {
|
|
1139
|
+
p: 2,
|
|
1140
|
+
bg: 'canvas.default',
|
|
1141
|
+
maxHeight: 300,
|
|
1142
|
+
overflow: 'auto',
|
|
1143
|
+
}, children: _jsx(Text, { sx: {
|
|
1144
|
+
fontSize: 0,
|
|
1145
|
+
color: 'fg.default',
|
|
1146
|
+
display: 'block',
|
|
1147
|
+
whiteSpace: 'pre-wrap',
|
|
1148
|
+
wordBreak: 'break-word',
|
|
1149
|
+
fontFamily: 'mono',
|
|
1150
|
+
}, children: outputText || '(empty output)' }) })] }));
|
|
1151
|
+
})()] })), approvalSidebarMessages.filter(msg => msg.role !== 'user')
|
|
1152
|
+
.length === 0 ? (_jsxs(Box, { sx: {
|
|
443
1153
|
display: 'flex',
|
|
444
1154
|
alignItems: 'center',
|
|
445
|
-
gap:
|
|
446
|
-
mb:
|
|
447
|
-
}, children: [_jsx(
|
|
448
|
-
|
|
449
|
-
:
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
1155
|
+
gap: 2,
|
|
1156
|
+
mb: 2,
|
|
1157
|
+
}, children: [_jsx(Spinner, { size: "small", sx: {
|
|
1158
|
+
width: 16,
|
|
1159
|
+
height: 16,
|
|
1160
|
+
minWidth: 16,
|
|
1161
|
+
minHeight: 16,
|
|
1162
|
+
} }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: "Waiting for approval agent messages\u2026" })] })) : (_jsx(Box, { sx: {
|
|
1163
|
+
display: 'flex',
|
|
1164
|
+
flexDirection: 'column',
|
|
1165
|
+
gap: 2,
|
|
1166
|
+
mb: 2,
|
|
1167
|
+
}, children: approvalSidebarMessages
|
|
1168
|
+
.slice()
|
|
1169
|
+
.filter(msg => msg.role !== 'user')
|
|
1170
|
+
.sort((a, b) => {
|
|
1171
|
+
const ta = a.createdAt
|
|
1172
|
+
? new Date(a.createdAt).getTime()
|
|
1173
|
+
: 0;
|
|
1174
|
+
const tb = b.createdAt
|
|
1175
|
+
? new Date(b.createdAt).getTime()
|
|
1176
|
+
: 0;
|
|
1177
|
+
return tb - ta;
|
|
1178
|
+
})
|
|
1179
|
+
.slice(0, 10)
|
|
1180
|
+
.map(msg => {
|
|
1181
|
+
const content = typeof msg.content === 'string'
|
|
1182
|
+
? msg.content
|
|
1183
|
+
: JSON.stringify(msg.content);
|
|
1184
|
+
return (_jsxs(Box, { sx: {
|
|
1185
|
+
p: 2,
|
|
1186
|
+
bg: 'canvas.subtle',
|
|
1187
|
+
borderRadius: 2,
|
|
1188
|
+
border: '1px solid',
|
|
1189
|
+
borderColor: 'border.default',
|
|
1190
|
+
}, children: [_jsxs(Box, { sx: {
|
|
1191
|
+
display: 'flex',
|
|
1192
|
+
alignItems: 'center',
|
|
1193
|
+
gap: 1,
|
|
1194
|
+
mb: 1,
|
|
1195
|
+
}, children: [_jsx(Label, { size: "small", variant: msg.role === 'assistant'
|
|
1196
|
+
? 'accent'
|
|
1197
|
+
: msg.role === 'tool'
|
|
1198
|
+
? 'success'
|
|
1199
|
+
: 'secondary', children: msg.role }), msg.createdAt && (_jsx(Text, { sx: {
|
|
1200
|
+
fontSize: 0,
|
|
1201
|
+
color: 'fg.muted',
|
|
1202
|
+
marginLeft: 'auto',
|
|
1203
|
+
whiteSpace: 'nowrap',
|
|
1204
|
+
}, children: new Date(msg.createdAt).toLocaleTimeString() }))] }), _jsx(Text, { sx: {
|
|
1205
|
+
fontSize: 0,
|
|
1206
|
+
color: 'fg.default',
|
|
1207
|
+
display: 'block',
|
|
1208
|
+
whiteSpace: 'pre-wrap',
|
|
1209
|
+
wordBreak: 'break-word',
|
|
1210
|
+
}, children: content.length > 320
|
|
1211
|
+
? `${content.slice(0, 320)}…`
|
|
1212
|
+
: content })] }, `approval-msg-${msg.id}`));
|
|
1213
|
+
}) }))] })), _jsxs(Box, { sx: { mt: 2, display: 'grid', gap: 2 }, children: [_jsxs(Box, { children: [_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted' }, children: "Stream once (local curl)" }), _jsx(Box, { sx: {
|
|
1214
|
+
mt: 1,
|
|
1215
|
+
bg: 'canvas.subtle',
|
|
1216
|
+
borderRadius: 2,
|
|
1217
|
+
p: 2,
|
|
1218
|
+
fontFamily: 'mono',
|
|
1219
|
+
fontSize: 0,
|
|
1220
|
+
wordBreak: 'break-all',
|
|
1221
|
+
}, children: triggerRunCurl })] }), _jsx(Button, { size: "small", leadingVisual: CopyIcon, onClick: () => navigator.clipboard.writeText(triggerRunCurl), children: "Copy streaming command" })] })] })), activeTab === 'cron' && (_jsxs(Box, { sx: {
|
|
1222
|
+
p: 3,
|
|
1223
|
+
borderBottom: '1px solid',
|
|
1224
|
+
borderColor: 'border.default',
|
|
1225
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, mb: 2 }, children: [_jsx(ClockIcon, { size: 16 }), _jsx(Heading, { as: "h3", sx: { fontSize: 2 }, children: "Cron Schedule" })] }), _jsxs(Label, { variant: "primary", sx: { mb: 2, display: 'inline-block' }, children: ["Current: ", cronExpr] }), nextRun && (_jsxs(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mb: 2 }, children: ["Next run: ", new Date(nextRun).toLocaleString()] })), _jsxs(Box, { sx: { display: 'flex', gap: 2, mb: 2 }, children: [_jsx(TextInput, { value: editCron, onChange: e => setEditCron(e.target.value), placeholder: "* * * * *", sx: { flex: 1 }, size: "small" }), _jsx(Button, { size: "small", variant: "primary", leadingVisual: SyncIcon, onClick: handleUpdateCron, disabled: isUpdating, children: isUpdating ? 'Saving…' : 'Update' })] }), _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted' }, children: "Standard cron syntax: minute hour day month weekday" })] })), activeTab === 'webhook' && (_jsxs(Box, { sx: {
|
|
1226
|
+
p: 3,
|
|
1227
|
+
borderBottom: '1px solid',
|
|
1228
|
+
borderColor: 'border.default',
|
|
1229
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, mb: 2 }, children: [_jsx(GlobeIcon, { size: 16 }), _jsx(Heading, { as: "h3", sx: { fontSize: 2 }, children: "Webhook Trigger" })] }), _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mb: 3 }, children: "Generate a unique URL that triggers this agent on incoming HTTP POST requests. Useful for CI/CD pipelines, external services, or custom integrations." }), webhookUrl ? (_jsxs(_Fragment, { children: [_jsx(Label, { variant: webhookEnabled ? 'success' : 'secondary', sx: { mb: 2, display: 'inline-block' }, children: webhookEnabled ? 'Active' : 'Disabled' }), _jsx(Box, { sx: {
|
|
1230
|
+
bg: 'canvas.subtle',
|
|
1231
|
+
p: 2,
|
|
1232
|
+
borderRadius: 2,
|
|
1233
|
+
mb: 2,
|
|
1234
|
+
fontFamily: 'mono',
|
|
1235
|
+
fontSize: 0,
|
|
1236
|
+
wordBreak: 'break-all',
|
|
1237
|
+
}, children: webhookUrl }), webhookSecret && (_jsxs(Box, { sx: { mb: 2 }, children: [_jsx(Text, { sx: { fontSize: 0, fontWeight: 'bold' }, children: "Secret:" }), _jsx(Box, { sx: {
|
|
1238
|
+
bg: 'canvas.subtle',
|
|
1239
|
+
p: 2,
|
|
1240
|
+
borderRadius: 2,
|
|
1241
|
+
mt: 1,
|
|
1242
|
+
fontFamily: 'mono',
|
|
454
1243
|
fontSize: 0,
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
1244
|
+
}, children: webhookSecret })] })), _jsxs(Box, { sx: { display: 'flex', gap: 2 }, children: [_jsx(Button, { size: "small", leadingVisual: CopyIcon, onClick: () => navigator.clipboard.writeText(webhookUrl), children: "Copy URL" }), _jsx(Button, { size: "small", variant: webhookEnabled ? 'danger' : 'primary', onClick: handleToggleWebhook, children: webhookEnabled ? 'Disable' : 'Enable' })] })] })) : (_jsx(Button, { size: "small", variant: "primary", leadingVisual: GlobeIcon, onClick: handleGenerateWebhook, disabled: isUpdating, sx: { width: '100%' }, children: isUpdating ? 'Generating…' : 'Generate Webhook URL' }))] })), activeTab === 'event' && (_jsxs(Box, { sx: {
|
|
1245
|
+
p: 3,
|
|
1246
|
+
borderBottom: '1px solid',
|
|
1247
|
+
borderColor: 'border.default',
|
|
1248
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, mb: 2 }, children: [_jsx(ZapIcon, { size: 16 }), _jsx(Heading, { as: "h3", sx: { fontSize: 2 }, children: "Event Trigger" })] }), _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mb: 3 }, children: "Subscribe to a Kafka topic or internal event stream. The agent triggers on matching messages." }), eventSubscribed ? (_jsxs(_Fragment, { children: [_jsxs(Label, { variant: "success", sx: { mb: 2, display: 'inline-block' }, children: ["Subscribed to: ", eventTopic] }), eventFilter && (_jsxs(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mb: 2 }, children: ["Filter: ", eventFilter] })), _jsx(Button, { size: "small", variant: "danger", onClick: () => setEventSubscribed(false), sx: { width: '100%' }, children: "Unsubscribe" })] })) : (_jsxs(_Fragment, { children: [_jsx(TextInput, { value: eventTopic, onChange: e => setEventTopic(e.target.value), placeholder: "e.g. kpi.daily-report", sx: { width: '100%', mb: 2 }, size: "small" }), _jsx(TextInput, { value: eventFilter, onChange: e => setEventFilter(e.target.value), placeholder: "Optional JSONPath filter", sx: { width: '100%', mb: 2 }, size: "small" }), _jsx(Button, { size: "small", variant: "primary", leadingVisual: ZapIcon, onClick: handleSubscribeEvent, disabled: isUpdating || !eventTopic.trim(), sx: { width: '100%' }, children: isUpdating ? 'Subscribing…' : 'Subscribe' })] }))] })), activeTab === 'manual' && (_jsxs(Box, { sx: {
|
|
1249
|
+
p: 3,
|
|
1250
|
+
borderBottom: '1px solid',
|
|
1251
|
+
borderColor: 'border.default',
|
|
1252
|
+
}, children: [_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 1, mb: 2 }, children: [_jsx(PlayIcon, { size: 16 }), _jsx(Heading, { as: "h3", sx: { fontSize: 2 }, children: "Manual Trigger" })] }), _jsx(Text, { as: "p", sx: { fontSize: 0, color: 'fg.muted', mb: 3 }, children: "Fire the agent immediately. This is equivalent to the cron job executing but bypasses the schedule." }), _jsx(Button, { size: "small", leadingVisual: PlayIcon, onClick: handleTriggerNow, disabled: isTriggeringNow, sx: { width: '100%' }, children: isTriggeringNow ? 'Triggering…' : 'Trigger Now' }), triggerFlash && (_jsx(Flash, { variant: triggerFlash.includes('success') ? 'success' : 'danger', sx: { mt: 2, fontSize: 0 }, children: triggerFlash }))] })), _jsxs(Box, { sx: { p: 3, flex: 1, overflow: 'auto' }, children: [_jsx(Heading, { as: "h4", sx: { fontSize: 1, mb: 2 }, children: "Trigger History" }), triggerHistory.length === 0 ? (_jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: "No trigger runs recorded yet." })) : (_jsx(Timeline, { children: triggerHistory.slice(0, 20).map(rec => (_jsxs(Timeline.Item, { children: [_jsx(Timeline.Badge, { children: rec.status === 'success' ? (_jsx(CheckCircleIcon, {})) : rec.status === 'failure' ? (_jsx(XCircleIcon, {})) : (_jsx(Spinner, { size: "small" })) }), _jsx(Timeline.Body, { children: _jsxs(Text, { sx: { fontSize: 0 }, children: [new Date(rec.timestamp).toLocaleString(), ' — ', _jsx(Label, { variant: rec.status === 'success'
|
|
1253
|
+
? 'success'
|
|
1254
|
+
: rec.status === 'failure'
|
|
1255
|
+
? 'danger'
|
|
1256
|
+
: 'accent', size: "small", children: rec.status }), rec.duration_ms != null && (_jsxs(Text, { sx: { ml: 1, color: 'fg.muted' }, children: ["(", (rec.duration_ms / 1000).toFixed(1), "s)"] }))] }) })] }, rec.id))) })), _jsx(Heading, { as: "h4", sx: { fontSize: 1, mt: 3, mb: 2 }, children: "Agent Events" }), agentEvents.length === 0 ? (_jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: "No agent events yet." })) : (_jsx(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2 }, children: [...agentEvents]
|
|
1257
|
+
.sort((a, b) => new Date(b.created_at).getTime() -
|
|
1258
|
+
new Date(a.created_at).getTime())
|
|
1259
|
+
.map((evt) => (_jsxs(Box, { sx: {
|
|
1260
|
+
p: 2,
|
|
1261
|
+
bg: evt.read ? 'canvas.subtle' : 'accent.subtle',
|
|
1262
|
+
borderRadius: 2,
|
|
1263
|
+
border: '1px solid',
|
|
1264
|
+
borderColor: evt.read
|
|
1265
|
+
? 'border.default'
|
|
1266
|
+
: 'accent.muted',
|
|
1267
|
+
}, children: [_jsxs(Box, { sx: {
|
|
1268
|
+
display: 'flex',
|
|
1269
|
+
alignItems: 'center',
|
|
1270
|
+
gap: 1,
|
|
1271
|
+
mb: 1,
|
|
1272
|
+
}, children: [_jsx(Label, { variant: evt.kind === 'agent-started'
|
|
1273
|
+
? 'accent'
|
|
1274
|
+
: evt.kind === 'agent-output'
|
|
1275
|
+
? 'success'
|
|
1276
|
+
: evt.kind?.includes('alert')
|
|
1277
|
+
? 'danger'
|
|
1278
|
+
: 'attention', size: "small", children: evt.kind }), _jsx(Text, { sx: { flex: 1, fontSize: 0, fontWeight: 'bold' }, children: evt.title }), _jsx(Text, { sx: {
|
|
1279
|
+
fontSize: 0,
|
|
1280
|
+
color: 'fg.muted',
|
|
1281
|
+
whiteSpace: 'nowrap',
|
|
1282
|
+
}, children: new Date(evt.created_at).toLocaleString() }), _jsx(Button, { size: "small", variant: "invisible", onClick: () => {
|
|
1283
|
+
const payload = {
|
|
1284
|
+
eventId: String(evt.id),
|
|
1285
|
+
eventAgentId: String(evt.agent_id || agentId || ''),
|
|
1286
|
+
};
|
|
1287
|
+
if (evt.read) {
|
|
1288
|
+
markUnreadMutation.mutate(payload);
|
|
1289
|
+
}
|
|
1290
|
+
else {
|
|
1291
|
+
markReadMutation.mutate(payload);
|
|
1292
|
+
}
|
|
1293
|
+
}, sx: { p: 1 }, children: evt.read ? (_jsx(EyeClosedIcon, { size: 12 })) : (_jsx(EyeIcon, { size: 12 })) }), _jsx(Button, { size: "small", variant: "invisible", onClick: () => deleteEventMutation.mutate(evt.id), sx: { p: 1, color: 'danger.fg' }, children: _jsx(TrashIcon, { size: 12 }) })] }), evt.payload &&
|
|
1294
|
+
(() => {
|
|
1295
|
+
const p = evt.payload;
|
|
1296
|
+
return (_jsxs(Box, { sx: { fontSize: 0, color: 'fg.muted' }, children: [evt.kind === 'agent-output' && p.outputs && (_jsx(Tooltip, { text: String(p.outputs), direction: "n", children: _jsx("button", { type: "button", "aria-label": String(p.outputs), style: {
|
|
1297
|
+
all: 'unset',
|
|
1298
|
+
display: 'block',
|
|
1299
|
+
width: '100%',
|
|
1300
|
+
cursor: 'default',
|
|
1301
|
+
}, children: _jsxs(Box, { sx: {
|
|
1302
|
+
mb: 1,
|
|
1303
|
+
display: 'flex',
|
|
1304
|
+
alignItems: 'baseline',
|
|
1305
|
+
gap: 1,
|
|
1306
|
+
minWidth: 0,
|
|
1307
|
+
}, children: [_jsx(Text, { as: "span", sx: {
|
|
1308
|
+
fontWeight: 'bold',
|
|
1309
|
+
color: 'fg.default',
|
|
1310
|
+
whiteSpace: 'nowrap',
|
|
1311
|
+
}, children: "Output:" }), _jsx(Truncate, { title: String(p.outputs), maxWidth: "100%", sx: { minWidth: 0, flex: 1 }, children: String(p.outputs) })] }) }) })), evt.kind === 'agent-output' &&
|
|
1312
|
+
p.duration_ms != null && (_jsxs(Text, { as: "p", children: ["Duration:", ' ', (Number(p.duration_ms) / 1000).toFixed(1), "s"] })), evt.kind?.includes('guardrail') &&
|
|
1313
|
+
p.message && (_jsx(Text, { as: "p", children: String(p.message) }))] }));
|
|
1314
|
+
})()] }, evt.id))) }))] })] })] })] }) }));
|
|
489
1315
|
};
|
|
490
1316
|
// ─── Sync token to core IAM store ──────────────────────────────────────────
|
|
491
1317
|
const syncTokenToIamStore = (token) => {
|
|
@@ -495,7 +1321,7 @@ const syncTokenToIamStore = (token) => {
|
|
|
495
1321
|
};
|
|
496
1322
|
// ─── Main component with auth gate ─────────────────────────────────────────
|
|
497
1323
|
const AgentTriggersExample = () => {
|
|
498
|
-
const { token,
|
|
1324
|
+
const { token, clearAuth } = useSimpleAuthStore();
|
|
499
1325
|
const hasSynced = useRef(false);
|
|
500
1326
|
useEffect(() => {
|
|
501
1327
|
if (token && !hasSynced.current) {
|
|
@@ -503,11 +1329,6 @@ const AgentTriggersExample = () => {
|
|
|
503
1329
|
syncTokenToIamStore(token);
|
|
504
1330
|
}
|
|
505
1331
|
}, [token]);
|
|
506
|
-
const handleSignIn = useCallback((newToken, handle) => {
|
|
507
|
-
setAuth(newToken, handle);
|
|
508
|
-
hasSynced.current = true;
|
|
509
|
-
syncTokenToIamStore(newToken);
|
|
510
|
-
}, [setAuth]);
|
|
511
1332
|
const handleLogout = useCallback(() => {
|
|
512
1333
|
clearAuth();
|
|
513
1334
|
hasSynced.current = false;
|
|
@@ -516,7 +1337,7 @@ const AgentTriggersExample = () => {
|
|
|
516
1337
|
});
|
|
517
1338
|
}, [clearAuth]);
|
|
518
1339
|
if (!token) {
|
|
519
|
-
return (_jsx(ThemedProvider, { children: _jsx(
|
|
1340
|
+
return (_jsx(ThemedProvider, { children: _jsx(AuthRequiredView, {}) }));
|
|
520
1341
|
}
|
|
521
1342
|
return (_jsx(QueryClientProvider, { client: queryClient, children: _jsx(ThemedProvider, { children: _jsx(AgentTriggerInner, { onLogout: handleLogout }) }) }));
|
|
522
1343
|
};
|