@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.
Files changed (275) hide show
  1. package/README.md +35 -119
  2. package/lib/App.js +1 -1
  3. package/lib/agents/AgentDetails.d.ts +22 -1
  4. package/lib/agents/AgentDetails.js +34 -47
  5. package/lib/api/index.d.ts +0 -1
  6. package/lib/api/index.js +4 -2
  7. package/lib/chat/Chat.d.ts +5 -104
  8. package/lib/chat/Chat.js +4 -4
  9. package/lib/chat/ChatFloating.d.ts +7 -140
  10. package/lib/chat/ChatFloating.js +2 -2
  11. package/lib/chat/ChatPopupStandalone.d.ts +8 -47
  12. package/lib/chat/ChatPopupStandalone.js +3 -3
  13. package/lib/chat/ChatSidebar.d.ts +4 -69
  14. package/lib/chat/ChatSidebar.js +2 -2
  15. package/lib/chat/ChatStandalone.d.ts +4 -54
  16. package/lib/chat/ChatStandalone.js +3 -3
  17. package/lib/chat/base/ChatBase.js +1118 -141
  18. package/lib/chat/header/ChatHeaderBase.d.ts +11 -6
  19. package/lib/chat/header/ChatHeaderBase.js +18 -16
  20. package/lib/chat/indicators/McpStatusIndicator.d.ts +7 -4
  21. package/lib/chat/indicators/McpStatusIndicator.js +7 -32
  22. package/lib/chat/indicators/SandboxStatusIndicator.d.ts +4 -1
  23. package/lib/chat/indicators/SandboxStatusIndicator.js +9 -9
  24. package/lib/chat/indicators/SkillsStatusIndicator.d.ts +7 -0
  25. package/lib/chat/indicators/SkillsStatusIndicator.js +88 -0
  26. package/lib/chat/indicators/index.d.ts +1 -0
  27. package/lib/chat/indicators/index.js +1 -0
  28. package/lib/chat/messages/ChatMessageList.d.ts +1 -1
  29. package/lib/chat/messages/ChatMessageList.js +110 -102
  30. package/lib/chat/prompt/InputFooter.d.ts +19 -6
  31. package/lib/chat/prompt/InputFooter.js +71 -18
  32. package/lib/chat/prompt/InputPrompt.d.ts +3 -1
  33. package/lib/chat/prompt/InputPrompt.js +4 -4
  34. package/lib/chat/prompt/InputPromptFooter.js +1 -1
  35. package/lib/chat/prompt/InputPromptLexical.d.ts +3 -1
  36. package/lib/chat/prompt/InputPromptLexical.js +12 -5
  37. package/lib/chat/prompt/InputPromptText.d.ts +3 -1
  38. package/lib/chat/prompt/InputPromptText.js +2 -2
  39. package/lib/chat/tools/ToolApprovalBanner.js +1 -1
  40. package/lib/chat/tools/ToolCallDisplay.d.ts +3 -1
  41. package/lib/chat/tools/ToolCallDisplay.js +2 -2
  42. package/lib/chat/usage/TokenUsageBar.js +20 -2
  43. package/lib/client/AgentRuntimesClientContext.d.ts +53 -0
  44. package/lib/client/AgentRuntimesClientContext.js +55 -0
  45. package/lib/client/AgentsMixin.d.ts +48 -19
  46. package/lib/client/AgentsMixin.js +115 -30
  47. package/lib/client/IAgentRuntimesClient.d.ts +215 -0
  48. package/lib/client/IAgentRuntimesClient.js +5 -0
  49. package/lib/client/SdkAgentRuntimesClient.d.ts +151 -0
  50. package/lib/client/SdkAgentRuntimesClient.js +134 -0
  51. package/lib/client/index.d.ts +4 -1
  52. package/lib/client/index.js +3 -1
  53. package/lib/components/NotificationEventCard.js +55 -26
  54. package/lib/components/OutputCard.js +21 -7
  55. package/lib/components/ToolApprovalCard.js +20 -2
  56. package/lib/config/AgentConfiguration.js +3 -3
  57. package/lib/context/ContextDistribution.d.ts +3 -1
  58. package/lib/context/ContextDistribution.js +8 -27
  59. package/lib/context/ContextInspector.d.ts +3 -1
  60. package/lib/context/ContextInspector.js +19 -67
  61. package/lib/context/ContextPanel.d.ts +3 -1
  62. package/lib/context/ContextPanel.js +104 -64
  63. package/lib/context/ContextUsage.d.ts +3 -1
  64. package/lib/context/ContextUsage.js +3 -3
  65. package/lib/context/CostTracker.d.ts +9 -3
  66. package/lib/context/CostTracker.js +26 -47
  67. package/lib/context/CostUsageChart.d.ts +12 -0
  68. package/lib/context/CostUsageChart.js +378 -0
  69. package/lib/context/GraphFlowChart.d.ts +16 -0
  70. package/lib/context/GraphFlowChart.js +182 -0
  71. package/lib/context/TokenUsageChart.d.ts +8 -1
  72. package/lib/context/TokenUsageChart.js +349 -211
  73. package/lib/context/TurnGraphChart.d.ts +39 -0
  74. package/lib/context/TurnGraphChart.js +538 -0
  75. package/lib/context/otelWsPool.d.ts +20 -0
  76. package/lib/context/otelWsPool.js +69 -0
  77. package/lib/examples/A2UiComponentGalleryExample.d.ts +0 -17
  78. package/lib/examples/A2UiComponentGalleryExample.js +315 -522
  79. package/lib/examples/A2UiContactCardExample.d.ts +0 -18
  80. package/lib/examples/A2UiContactCardExample.js +154 -411
  81. package/lib/examples/A2UiRestaurantExample.d.ts +0 -30
  82. package/lib/examples/A2UiRestaurantExample.js +114 -212
  83. package/lib/examples/A2UiViewerExample.d.ts +0 -18
  84. package/lib/examples/A2UiViewerExample.js +283 -532
  85. package/lib/examples/AgUiBackendToolRenderingExample.js +1 -1
  86. package/lib/examples/AgUiHaikuGenUiExample.d.ts +1 -1
  87. package/lib/examples/AgUiHaikuGenUiExample.js +1 -1
  88. package/lib/examples/AgentCheckpointsExample.js +14 -34
  89. package/lib/examples/AgentCodemodeExample.d.ts +4 -6
  90. package/lib/examples/AgentCodemodeExample.js +591 -175
  91. package/lib/examples/AgentEvalsExample.js +13 -23
  92. package/lib/examples/AgentGuardrailsExample.js +371 -71
  93. package/lib/examples/AgentHooksExample.d.ts +3 -0
  94. package/lib/examples/AgentHooksExample.js +104 -0
  95. package/lib/examples/AgentMCPExample.d.ts +3 -0
  96. package/lib/examples/AgentMCPExample.js +480 -0
  97. package/lib/examples/AgentMemoryExample.js +14 -24
  98. package/lib/examples/AgentMonitoringExample.js +261 -206
  99. package/lib/examples/AgentNotificationsExample.js +50 -24
  100. package/lib/examples/AgentOtelExample.js +2 -3
  101. package/lib/examples/AgentOutputsExample.d.ts +11 -6
  102. package/lib/examples/AgentOutputsExample.js +383 -88
  103. package/lib/examples/AgentParametersExample.d.ts +3 -0
  104. package/lib/examples/AgentParametersExample.js +246 -0
  105. package/lib/examples/AgentSandboxExample.d.ts +2 -2
  106. package/lib/examples/AgentSandboxExample.js +69 -47
  107. package/lib/examples/AgentSkillsExample.js +92 -106
  108. package/lib/examples/{AgentspecExample.js → AgentSpecsExample.js} +10 -21
  109. package/lib/examples/AgentSubagentsExample.d.ts +14 -0
  110. package/lib/examples/AgentSubagentsExample.js +228 -0
  111. package/lib/examples/AgentToolApprovalsExample.js +30 -493
  112. package/lib/examples/AgentTriggersExample.js +1067 -246
  113. package/lib/examples/ChatCustomExample.js +11 -24
  114. package/lib/examples/ChatExample.js +9 -34
  115. package/lib/examples/CopilotKitLexicalExample.js +2 -1
  116. package/lib/examples/CopilotKitNotebookExample.js +2 -1
  117. package/lib/examples/HomeExample.d.ts +15 -0
  118. package/lib/examples/HomeExample.js +77 -0
  119. package/lib/examples/Lexical2Example.js +4 -2
  120. package/lib/examples/{LexicalExample.d.ts → LexicalAgentExample.d.ts} +4 -4
  121. package/lib/examples/{LexicalExample.js → LexicalAgentExample.js} +65 -16
  122. package/lib/examples/{LexicalSidebarExample.d.ts → LexicalAgentSidebarExample.d.ts} +5 -5
  123. package/lib/examples/LexicalAgentSidebarExample.js +261 -0
  124. package/lib/examples/NotebookAgentExample.d.ts +9 -0
  125. package/lib/examples/NotebookAgentExample.js +192 -0
  126. package/lib/examples/{NotebookSidebarExample.d.ts → NotebookAgentSidebarExample.d.ts} +2 -2
  127. package/lib/examples/NotebookAgentSidebarExample.js +221 -0
  128. package/lib/examples/{DatalayerNotebookExample.d.ts → NotebookCollaborationExample.d.ts} +4 -4
  129. package/lib/examples/{DatalayerNotebookExample.js → NotebookCollaborationExample.js} +3 -3
  130. package/lib/examples/NotebookExample.d.ts +4 -7
  131. package/lib/examples/NotebookExample.js +14 -146
  132. package/lib/examples/components/AuthRequiredView.d.ts +6 -0
  133. package/lib/examples/components/AuthRequiredView.js +33 -0
  134. package/lib/examples/components/ErrorView.d.ts +14 -0
  135. package/lib/examples/components/ErrorView.js +20 -0
  136. package/lib/examples/components/ExampleWrapper.d.ts +7 -0
  137. package/lib/examples/components/ExampleWrapper.js +25 -6
  138. package/lib/examples/{ag-ui → components}/haiku/HaikuDisplay.js +1 -1
  139. package/lib/examples/{ag-ui → components}/haiku/InlineHaikuCard.js +1 -1
  140. package/lib/examples/{ag-ui → components}/haiku/index.d.ts +1 -1
  141. package/lib/examples/{ag-ui → components}/haiku/index.js +1 -1
  142. package/lib/examples/components/index.d.ts +5 -0
  143. package/lib/examples/components/index.js +5 -0
  144. package/lib/examples/{ag-ui → components}/weather/index.d.ts +1 -1
  145. package/lib/examples/{ag-ui → components}/weather/index.js +1 -1
  146. package/lib/examples/example-selector.d.ts +17 -4
  147. package/lib/examples/example-selector.js +107 -41
  148. package/lib/examples/index.d.ts +9 -6
  149. package/lib/examples/index.js +9 -6
  150. package/lib/examples/main.d.ts +1 -0
  151. package/lib/examples/main.js +218 -27
  152. package/lib/examples/utils/a2ui.d.ts +18 -0
  153. package/lib/examples/utils/a2ui.js +69 -0
  154. package/lib/examples/utils/a2uiMarkdownProvider.d.ts +7 -0
  155. package/lib/examples/utils/a2uiMarkdownProvider.js +9 -0
  156. package/lib/examples/utils/agentId.d.ts +18 -0
  157. package/lib/examples/utils/agentId.js +54 -0
  158. package/lib/examples/utils/agents/earthquake-detector.json +11 -11
  159. package/lib/examples/utils/agents/sales-forecaster.json +11 -11
  160. package/lib/examples/utils/agents/social-post-generator.json +11 -11
  161. package/lib/examples/utils/agents/stock-market.json +11 -11
  162. package/lib/examples/utils/examplesStore.js +82 -27
  163. package/lib/hooks/index.d.ts +8 -8
  164. package/lib/hooks/index.js +7 -7
  165. package/lib/hooks/useA2A.d.ts +2 -3
  166. package/lib/hooks/useAIAgentsWebSocket.d.ts +43 -4
  167. package/lib/hooks/useAIAgentsWebSocket.js +118 -12
  168. package/lib/hooks/useAcp.d.ts +1 -2
  169. package/lib/hooks/useAgUi.d.ts +1 -1
  170. package/lib/hooks/{useAgents.d.ts → useAgentRuntimes.d.ts} +39 -2
  171. package/lib/hooks/{useAgents.js → useAgentRuntimes.js} +125 -15
  172. package/lib/hooks/useAgentsCatalog.js +1 -1
  173. package/lib/hooks/useAgentsService.d.ts +2 -2
  174. package/lib/hooks/useAgentsService.js +7 -7
  175. package/lib/hooks/useCheckpoints.js +1 -1
  176. package/lib/hooks/useConfig.d.ts +4 -1
  177. package/lib/hooks/useConfig.js +10 -3
  178. package/lib/hooks/useContextSnapshot.d.ts +9 -4
  179. package/lib/hooks/useContextSnapshot.js +9 -37
  180. package/lib/hooks/useMonitoring.js +3 -0
  181. package/lib/hooks/useSandbox.d.ts +20 -8
  182. package/lib/hooks/useSandbox.js +105 -40
  183. package/lib/hooks/useSkills.d.ts +23 -5
  184. package/lib/hooks/useSkills.js +94 -39
  185. package/lib/hooks/useToolApprovals.d.ts +60 -36
  186. package/lib/hooks/useToolApprovals.js +318 -69
  187. package/lib/hooks/useVercelAI.d.ts +1 -1
  188. package/lib/index.d.ts +2 -1
  189. package/lib/index.js +1 -0
  190. package/lib/inference/index.d.ts +0 -1
  191. package/lib/middleware/index.d.ts +0 -1
  192. package/lib/protocols/AGUIAdapter.js +6 -0
  193. package/lib/protocols/VercelAIAdapter.d.ts +9 -0
  194. package/lib/protocols/VercelAIAdapter.js +144 -26
  195. package/lib/shims/json5.d.ts +4 -0
  196. package/lib/shims/json5.js +8 -0
  197. package/lib/specs/agents/agents.d.ts +10 -0
  198. package/lib/specs/agents/agents.js +752 -24
  199. package/lib/specs/envvars.d.ts +1 -0
  200. package/lib/specs/envvars.js +11 -0
  201. package/lib/specs/events.d.ts +1 -0
  202. package/lib/specs/events.js +1 -0
  203. package/lib/specs/index.d.ts +1 -0
  204. package/lib/specs/index.js +1 -0
  205. package/lib/specs/personas.d.ts +41 -0
  206. package/lib/specs/personas.js +168 -0
  207. package/lib/specs/skills.d.ts +2 -1
  208. package/lib/specs/skills.js +23 -5
  209. package/lib/specs/tools.js +3 -0
  210. package/lib/stores/agentRuntimeStore.d.ts +204 -0
  211. package/lib/stores/agentRuntimeStore.js +636 -0
  212. package/lib/stores/index.d.ts +1 -1
  213. package/lib/stores/index.js +1 -1
  214. package/lib/tools/adapters/copilotkit/lexicalHooks.d.ts +1 -2
  215. package/lib/tools/adapters/copilotkit/lexicalHooks.js +1 -3
  216. package/lib/tools/adapters/copilotkit/notebookHooks.d.ts +1 -2
  217. package/lib/tools/adapters/copilotkit/notebookHooks.js +1 -3
  218. package/lib/tools/index.d.ts +0 -2
  219. package/lib/tools/index.js +0 -1
  220. package/lib/types/agentspecs.d.ts +50 -1
  221. package/lib/types/chat.d.ts +309 -8
  222. package/lib/types/context.d.ts +27 -0
  223. package/lib/types/cost.d.ts +2 -2
  224. package/lib/types/index.d.ts +2 -0
  225. package/lib/types/index.js +2 -0
  226. package/lib/types/mcp.d.ts +8 -0
  227. package/lib/types/models.d.ts +2 -2
  228. package/lib/types/personas.d.ts +25 -0
  229. package/lib/types/personas.js +5 -0
  230. package/lib/types/skills.d.ts +43 -1
  231. package/lib/types/stream.d.ts +110 -0
  232. package/lib/types/stream.js +36 -0
  233. package/lib/types/tools.d.ts +2 -0
  234. package/lib/utils/utils.d.ts +9 -5
  235. package/lib/utils/utils.js +9 -5
  236. package/package.json +13 -9
  237. package/scripts/codegen/__pycache__/generate_agents.cpython-313.pyc +0 -0
  238. package/scripts/codegen/__pycache__/generate_events.cpython-313.pyc +0 -0
  239. package/scripts/codegen/__pycache__/versioning.cpython-313.pyc +0 -0
  240. package/scripts/codegen/generate_agents.py +106 -7
  241. package/scripts/codegen/generate_events.py +47 -17
  242. package/scripts/codegen/generate_personas.py +319 -0
  243. package/scripts/codegen/generate_skills.py +9 -9
  244. package/scripts/codegen/generate_tools.py +20 -0
  245. package/scripts/sync-jupyter.sh +26 -7
  246. package/style/primer-primitives.css +1 -6
  247. package/lib/api/tool-approvals.d.ts +0 -62
  248. package/lib/api/tool-approvals.js +0 -145
  249. package/lib/examples/LexicalSidebarExample.js +0 -163
  250. package/lib/examples/NotebookSidebarExample.js +0 -119
  251. package/lib/examples/NotebookSimpleExample.d.ts +0 -6
  252. package/lib/examples/NotebookSimpleExample.js +0 -22
  253. package/lib/examples/ag-ui/index.d.ts +0 -10
  254. package/lib/examples/ag-ui/index.js +0 -16
  255. package/lib/hooks/useAgentsRegistry.d.ts +0 -10
  256. package/lib/hooks/useAgentsRegistry.js +0 -20
  257. package/lib/stores/agentsStore.d.ts +0 -123
  258. package/lib/stores/agentsStore.js +0 -270
  259. package/scripts/codegen/__pycache__/generate_envvars.cpython-313.pyc +0 -0
  260. package/scripts/codegen/__pycache__/generate_evals.cpython-313.pyc +0 -0
  261. package/scripts/codegen/__pycache__/generate_guardrails.cpython-313.pyc +0 -0
  262. package/scripts/codegen/__pycache__/generate_mcp_servers.cpython-313.pyc +0 -0
  263. package/scripts/codegen/__pycache__/generate_memory.cpython-313.pyc +0 -0
  264. package/scripts/codegen/__pycache__/generate_models.cpython-313.pyc +0 -0
  265. package/scripts/codegen/__pycache__/generate_notifications.cpython-313.pyc +0 -0
  266. package/scripts/codegen/__pycache__/generate_outputs.cpython-313.pyc +0 -0
  267. package/scripts/codegen/__pycache__/generate_skills.cpython-313.pyc +0 -0
  268. package/scripts/codegen/__pycache__/generate_teams.cpython-313.pyc +0 -0
  269. package/scripts/codegen/__pycache__/generate_tools.cpython-313.pyc +0 -0
  270. package/scripts/codegen/__pycache__/generate_triggers.cpython-313.pyc +0 -0
  271. /package/lib/examples/{AgentspecExample.d.ts → AgentSpecsExample.d.ts} +0 -0
  272. /package/lib/examples/{ag-ui → components}/haiku/HaikuDisplay.d.ts +0 -0
  273. /package/lib/examples/{ag-ui → components}/haiku/InlineHaikuCard.d.ts +0 -0
  274. /package/lib/examples/{ag-ui → components}/weather/InlineWeatherCard.d.ts +0 -0
  275. /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 { AlertIcon, ClockIcon, SyncIcon, CheckCircleIcon, XCircleIcon, PlayIcon, SignOutIcon, GlobeIcon, ZapIcon, EyeIcon, EyeClosedIcon, TrashIcon, CopyIcon, } from '@primer/octicons-react';
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 [runtimeStatus, setRuntimeStatus] = useState('launching');
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(AGENT_NAME);
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 for real-time event updates
72
- useAIAgentsWebSocket({
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 local server ─────────────────────────────────────────
85
- useEffect(() => {
86
- let isCancelled = false;
87
- const createAgent = async () => {
88
- setRuntimeStatus('launching');
89
- setIsReady(false);
90
- setHookError(null);
91
- try {
92
- const response = await authFetch(`${agentBaseUrl}/api/v1/agents`, {
93
- method: 'POST',
94
- body: JSON.stringify({
95
- name: AGENT_NAME,
96
- description: 'Agent with cron, webhook, event, and manual triggers',
97
- agent_library: 'pydantic-ai',
98
- transport: 'ag-ui',
99
- agent_spec_id: AGENT_SPEC_ID,
100
- tools: [],
101
- }),
102
- });
103
- let resolvedAgentId = AGENT_NAME;
104
- if (response.ok) {
105
- const data = await response.json();
106
- resolvedAgentId = data?.id || AGENT_NAME;
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
- const contentType = response.headers.get('content-type') || '';
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 (!isCancelled) {
129
- setAgentId(resolvedAgentId);
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
- catch (error) {
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
- void createAgent();
142
- return () => {
143
- isCancelled = true;
144
- };
145
- }, [agentBaseUrl, authFetch]);
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`, { method: 'POST', body: JSON.stringify({ source: 'manual' }) });
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
- }, [agentBaseUrl, agentId, eventTopic, eventFilter, authFetch]);
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
- const res = await authFetch(`${agentBaseUrl}/api/v1/agents/${agentId}/trigger/run`, { method: 'POST', body: JSON.stringify({ source: 'once' }) });
295
- if (res.ok) {
296
- setOnceFlash('Once trigger launched — agent will run and terminate.');
297
- setTriggerHistory(prev => prev.map(r => r.id === runId
298
- ? {
299
- ...r,
300
- status: 'success',
301
- duration_ms: Date.now() - new Date(startTime).getTime(),
302
- }
303
- : r));
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, authFetch]);
802
+ }, [agentBaseUrl, agentId, streamRunMessages, ensureRuntimeReady]);
318
803
  // ── Loading / Error ──────────────────────────────────────────────────────
319
- if (!isReady && runtimeStatus !== 'error') {
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: runtimeStatus === 'launching'
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 (_jsxs(Box, { sx: {
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
- alignItems: 'center',
336
- justifyContent: 'center',
337
- height: '100vh',
338
- gap: 3,
339
- }, children: [_jsx(AlertIcon, { size: 48 }), _jsx(Text, { sx: { color: 'danger.fg' }, children: hookError || 'Agent failed to start' })] }));
340
- }
341
- return (_jsxs(Box, { sx: {
342
- height: 'calc(100vh - 60px)',
343
- display: 'flex',
344
- flexDirection: 'column',
345
- }, children: [_jsxs(Box, { sx: {
346
- display: 'flex',
347
- alignItems: 'center',
348
- gap: 2,
349
- px: 3,
350
- py: 2,
351
- borderBottom: '1px solid',
352
- borderColor: 'border.default',
353
- flexShrink: 0,
354
- }, children: [_jsx(ClockIcon, { size: 16 }), _jsxs(Heading, { as: "h3", sx: { fontSize: 2, flex: 1 }, children: ["Triggers \u2014 ", agentId] }), token && _jsx(UserBadge, { token: token, variant: "small" }), _jsx(Button, { size: "small", variant: "invisible", onClick: onLogout, leadingVisual: SignOutIcon, sx: { color: 'fg.muted' }, children: "Sign out" })] }), _jsxs(Box, { sx: { flex: 1, minHeight: 0, display: 'flex' }, children: [_jsx(Box, { sx: { flex: 1, minWidth: 0 }, children: _jsx(Chat, { protocol: "ag-ui", baseUrl: agentBaseUrl, agentId: agentId, authToken: chatAuthToken, title: "Trigger Agent", placeholder: "Ask about your scheduled or event-driven KPI reports\u2026", description: `Cron: ${cronExpr} | Webhook: ${webhookEnabled ? 'on' : 'off'} | Event: ${eventSubscribed ? eventTopic : 'none'}`, showHeader: true, autoFocus: true, height: "100%", runtimeId: agentId, historyEndpoint: `${agentBaseUrl}/api/v1/history`, suggestions: [
355
- {
356
- title: 'Last run',
357
- message: 'What happened in the last scheduled run?',
358
- },
359
- { title: 'KPIs today', message: "Show me today's KPI summary" },
360
- ], submitOnSuggestionClick: true }) }), _jsxs(Box, { sx: {
361
- width: 380,
362
- borderLeft: '1px solid',
363
- borderColor: 'border.default',
364
- display: 'flex',
365
- flexDirection: 'column',
366
- overflow: 'auto',
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
- borderBottom: '1px solid',
370
- borderColor: 'border.default',
371
- flexShrink: 0,
372
- }, children: [
373
- { key: 'once', icon: ZapIcon, label: 'Once' },
374
- { key: 'cron', icon: ClockIcon, label: 'Cron' },
375
- {
376
- key: 'webhook',
377
- icon: GlobeIcon,
378
- label: 'Webhook',
379
- },
380
- { key: 'event', icon: ZapIcon, label: 'Event' },
381
- {
382
- key: 'manual',
383
- icon: PlayIcon,
384
- label: 'Manual',
385
- },
386
- ].map(t => (_jsx(Button, { size: "small", variant: "invisible", leadingVisual: t.icon, onClick: () => setActiveTab(t.key), sx: {
387
- flex: 1,
388
- borderRadius: 0,
389
- borderBottom: activeTab === t.key ? '2px solid' : '2px solid transparent',
390
- borderColor: activeTab === t.key ? 'accent.fg' : 'transparent',
391
- fontWeight: activeTab === t.key ? 'bold' : 'normal',
392
- }, children: t.label }, t.key))) }), activeTab === 'once' && (_jsxs(Box, { sx: {
393
- p: 3,
394
- borderBottom: '1px solid',
395
- borderColor: 'border.default',
396
- }, 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 the agent once. It will execute its trigger prompt, emit lifecycle events (AGENT_STARTED / AGENT_ENDED), and then terminate the runtime automatically." }), _jsx(Button, { size: "small", variant: "primary", leadingVisual: ZapIcon, onClick: handleLaunchOnce, disabled: isLaunchingOnce, sx: { width: '100%' }, children: isLaunchingOnce ? 'Launching…' : 'Launch Once' }), onceFlash && (_jsx(Flash, { variant: onceFlash.includes('launched') ? 'success' : 'danger', sx: { mt: 2, fontSize: 0 }, children: onceFlash }))] })), activeTab === 'cron' && (_jsxs(Box, { sx: {
397
- p: 3,
398
- borderBottom: '1px solid',
399
- borderColor: 'border.default',
400
- }, 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: {
401
- p: 3,
402
- borderBottom: '1px solid',
403
- borderColor: 'border.default',
404
- }, 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: {
405
- bg: 'canvas.subtle',
406
- p: 2,
407
- borderRadius: 2,
408
- mb: 2,
409
- fontFamily: 'mono',
410
- fontSize: 0,
411
- wordBreak: 'break-all',
412
- }, children: webhookUrl }), webhookSecret && (_jsxs(Box, { sx: { mb: 2 }, children: [_jsx(Text, { sx: { fontSize: 0, fontWeight: 'bold' }, children: "Secret:" }), _jsx(Box, { sx: {
413
- bg: 'canvas.subtle',
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
- mt: 1,
417
- fontFamily: 'mono',
418
- fontSize: 0,
419
- }, 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: {
420
- p: 3,
421
- borderBottom: '1px solid',
422
- borderColor: 'border.default',
423
- }, 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: {
424
- p: 3,
425
- borderBottom: '1px solid',
426
- borderColor: 'border.default',
427
- }, 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'
428
- ? 'success'
429
- : rec.status === 'failure'
430
- ? 'danger'
431
- : '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]
432
- .sort((a, b) => new Date(b.created_at).getTime() -
433
- new Date(a.created_at).getTime())
434
- .map((evt) => (_jsxs(Box, { sx: {
435
- p: 2,
436
- bg: evt.read ? 'canvas.subtle' : 'accent.subtle',
437
- borderRadius: 2,
438
- border: '1px solid',
439
- borderColor: evt.read
440
- ? 'border.default'
441
- : 'accent.muted',
442
- }, children: [_jsxs(Box, { sx: {
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: 1,
446
- mb: 1,
447
- }, children: [_jsx(Label, { variant: evt.kind === 'agent-started'
448
- ? 'accent'
449
- : evt.kind === 'agent-ended'
450
- ? 'success'
451
- : evt.kind?.includes('alert')
452
- ? 'danger'
453
- : 'attention', size: "small", children: evt.kind }), _jsx(Text, { sx: { flex: 1, fontSize: 0, fontWeight: 'bold' }, children: evt.title }), _jsx(Text, { sx: {
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
- color: 'fg.muted',
456
- whiteSpace: 'nowrap',
457
- }, children: new Date(evt.created_at).toLocaleString() }), _jsx(Button, { size: "small", variant: "invisible", onClick: () => {
458
- const payload = {
459
- eventId: String(evt.id),
460
- eventAgentId: String(evt.agent_id || agentId || ''),
461
- };
462
- if (evt.read) {
463
- markUnreadMutation.mutate(payload);
464
- }
465
- else {
466
- markReadMutation.mutate(payload);
467
- }
468
- }, 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 &&
469
- (() => {
470
- const p = evt.payload;
471
- return (_jsxs(Box, { sx: { fontSize: 0, color: 'fg.muted' }, children: [evt.kind === 'agent-ended' && p.outputs && (_jsx(Tooltip, { text: String(p.outputs), direction: "n", children: _jsx("button", { type: "button", "aria-label": String(p.outputs), style: {
472
- all: 'unset',
473
- display: 'block',
474
- width: '100%',
475
- cursor: 'default',
476
- }, children: _jsxs(Box, { sx: {
477
- mb: 1,
478
- display: 'flex',
479
- alignItems: 'baseline',
480
- gap: 1,
481
- minWidth: 0,
482
- }, children: [_jsx(Text, { as: "span", sx: {
483
- fontWeight: 'bold',
484
- color: 'fg.default',
485
- whiteSpace: 'nowrap',
486
- }, children: "Output:" }), _jsx(Truncate, { title: String(p.outputs), maxWidth: "100%", sx: { minWidth: 0, flex: 1 }, children: String(p.outputs) })] }) }) })), evt.kind === 'agent-ended' &&
487
- p.duration_ms != null && (_jsxs(Text, { as: "p", children: ["Duration:", ' ', (Number(p.duration_ms) / 1000).toFixed(1), "s"] })), evt.kind?.includes('guardrail') && p.message && (_jsx(Text, { as: "p", children: String(p.message) }))] }));
488
- })()] }, evt.id))) }))] })] })] })] }));
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, setAuth, clearAuth } = useSimpleAuthStore();
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(SignInSimple, { onSignIn: handleSignIn, onApiKeySignIn: apiKey => handleSignIn(apiKey, 'api-key-user'), title: "Agent Triggers", description: "Sign in to configure cron, webhook, event, and manual triggers.", leadingIcon: _jsx(ClockIcon, { size: 24 }) }) }));
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
  };