@datalayer/agent-runtimes 1.0.4 → 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 (267) hide show
  1. package/README.md +34 -0
  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 -106
  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 +1083 -157
  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 +108 -113
  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 +0 -18
  46. package/lib/client/AgentsMixin.js +6 -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 +5 -1
  54. package/lib/config/AgentConfiguration.js +3 -3
  55. package/lib/context/ContextDistribution.d.ts +3 -1
  56. package/lib/context/ContextDistribution.js +8 -27
  57. package/lib/context/ContextInspector.d.ts +3 -1
  58. package/lib/context/ContextInspector.js +19 -67
  59. package/lib/context/ContextPanel.d.ts +3 -1
  60. package/lib/context/ContextPanel.js +104 -64
  61. package/lib/context/ContextUsage.d.ts +3 -1
  62. package/lib/context/ContextUsage.js +3 -3
  63. package/lib/context/CostTracker.d.ts +9 -3
  64. package/lib/context/CostTracker.js +26 -47
  65. package/lib/context/CostUsageChart.d.ts +12 -0
  66. package/lib/context/CostUsageChart.js +378 -0
  67. package/lib/context/GraphFlowChart.d.ts +16 -0
  68. package/lib/context/GraphFlowChart.js +182 -0
  69. package/lib/context/TokenUsageChart.d.ts +8 -1
  70. package/lib/context/TokenUsageChart.js +349 -211
  71. package/lib/context/TurnGraphChart.d.ts +39 -0
  72. package/lib/context/TurnGraphChart.js +538 -0
  73. package/lib/context/otelWsPool.d.ts +20 -0
  74. package/lib/context/otelWsPool.js +69 -0
  75. package/lib/examples/A2UiComponentGalleryExample.d.ts +0 -17
  76. package/lib/examples/A2UiComponentGalleryExample.js +315 -522
  77. package/lib/examples/A2UiContactCardExample.d.ts +0 -18
  78. package/lib/examples/A2UiContactCardExample.js +154 -411
  79. package/lib/examples/A2UiRestaurantExample.d.ts +0 -30
  80. package/lib/examples/A2UiRestaurantExample.js +114 -212
  81. package/lib/examples/A2UiViewerExample.d.ts +0 -18
  82. package/lib/examples/A2UiViewerExample.js +283 -532
  83. package/lib/examples/AgUiBackendToolRenderingExample.js +1 -1
  84. package/lib/examples/AgUiHaikuGenUiExample.d.ts +1 -1
  85. package/lib/examples/AgUiHaikuGenUiExample.js +1 -1
  86. package/lib/examples/AgentCheckpointsExample.js +13 -27
  87. package/lib/examples/AgentCodemodeExample.d.ts +4 -6
  88. package/lib/examples/AgentCodemodeExample.js +591 -169
  89. package/lib/examples/AgentEvalsExample.js +12 -16
  90. package/lib/examples/AgentGuardrailsExample.js +370 -64
  91. package/lib/examples/AgentHooksExample.d.ts +3 -0
  92. package/lib/examples/AgentHooksExample.js +104 -0
  93. package/lib/examples/AgentMCPExample.d.ts +3 -0
  94. package/lib/examples/AgentMCPExample.js +480 -0
  95. package/lib/examples/AgentMemoryExample.js +13 -17
  96. package/lib/examples/AgentMonitoringExample.js +260 -199
  97. package/lib/examples/AgentNotificationsExample.js +49 -17
  98. package/lib/examples/AgentOtelExample.js +2 -3
  99. package/lib/examples/AgentOutputsExample.d.ts +11 -6
  100. package/lib/examples/AgentOutputsExample.js +382 -81
  101. package/lib/examples/AgentParametersExample.d.ts +3 -0
  102. package/lib/examples/AgentParametersExample.js +246 -0
  103. package/lib/examples/AgentSandboxExample.d.ts +2 -2
  104. package/lib/examples/AgentSandboxExample.js +68 -40
  105. package/lib/examples/AgentSkillsExample.js +91 -99
  106. package/lib/examples/{AgentspecExample.js → AgentSpecsExample.js} +10 -21
  107. package/lib/examples/AgentSubagentsExample.d.ts +14 -0
  108. package/lib/examples/AgentSubagentsExample.js +228 -0
  109. package/lib/examples/AgentToolApprovalsExample.js +29 -557
  110. package/lib/examples/AgentTriggersExample.js +819 -565
  111. package/lib/examples/ChatCustomExample.js +11 -24
  112. package/lib/examples/ChatExample.js +7 -24
  113. package/lib/examples/CopilotKitLexicalExample.js +2 -1
  114. package/lib/examples/CopilotKitNotebookExample.js +2 -1
  115. package/lib/examples/HomeExample.d.ts +15 -0
  116. package/lib/examples/HomeExample.js +77 -0
  117. package/lib/examples/Lexical2Example.js +4 -2
  118. package/lib/examples/{LexicalExample.d.ts → LexicalAgentExample.d.ts} +4 -4
  119. package/lib/examples/{LexicalExample.js → LexicalAgentExample.js} +65 -16
  120. package/lib/examples/{LexicalSidebarExample.d.ts → LexicalAgentSidebarExample.d.ts} +5 -5
  121. package/lib/examples/LexicalAgentSidebarExample.js +261 -0
  122. package/lib/examples/NotebookAgentExample.d.ts +9 -0
  123. package/lib/examples/NotebookAgentExample.js +192 -0
  124. package/lib/examples/{NotebookSidebarExample.d.ts → NotebookAgentSidebarExample.d.ts} +2 -2
  125. package/lib/examples/NotebookAgentSidebarExample.js +221 -0
  126. package/lib/examples/{DatalayerNotebookExample.d.ts → NotebookCollaborationExample.d.ts} +4 -4
  127. package/lib/examples/{DatalayerNotebookExample.js → NotebookCollaborationExample.js} +3 -3
  128. package/lib/examples/NotebookExample.d.ts +4 -7
  129. package/lib/examples/NotebookExample.js +14 -146
  130. package/lib/examples/components/AuthRequiredView.d.ts +6 -0
  131. package/lib/examples/components/AuthRequiredView.js +33 -0
  132. package/lib/examples/components/ExampleWrapper.d.ts +7 -0
  133. package/lib/examples/components/ExampleWrapper.js +25 -6
  134. package/lib/examples/{ag-ui → components}/haiku/HaikuDisplay.js +1 -1
  135. package/lib/examples/{ag-ui → components}/haiku/InlineHaikuCard.js +1 -1
  136. package/lib/examples/{ag-ui → components}/haiku/index.d.ts +1 -1
  137. package/lib/examples/{ag-ui → components}/haiku/index.js +1 -1
  138. package/lib/examples/components/index.d.ts +3 -0
  139. package/lib/examples/components/index.js +4 -0
  140. package/lib/examples/{ag-ui → components}/weather/index.d.ts +1 -1
  141. package/lib/examples/{ag-ui → components}/weather/index.js +1 -1
  142. package/lib/examples/example-selector.d.ts +17 -4
  143. package/lib/examples/example-selector.js +107 -41
  144. package/lib/examples/index.d.ts +9 -6
  145. package/lib/examples/index.js +9 -6
  146. package/lib/examples/main.js +217 -27
  147. package/lib/examples/utils/a2ui.d.ts +18 -0
  148. package/lib/examples/utils/a2ui.js +69 -0
  149. package/lib/examples/utils/a2uiMarkdownProvider.d.ts +7 -0
  150. package/lib/examples/utils/a2uiMarkdownProvider.js +9 -0
  151. package/lib/examples/utils/agentId.d.ts +18 -0
  152. package/lib/examples/utils/agentId.js +54 -0
  153. package/lib/examples/utils/agents/earthquake-detector.json +11 -11
  154. package/lib/examples/utils/agents/sales-forecaster.json +11 -11
  155. package/lib/examples/utils/agents/social-post-generator.json +11 -11
  156. package/lib/examples/utils/agents/stock-market.json +11 -11
  157. package/lib/examples/utils/examplesStore.js +82 -27
  158. package/lib/hooks/index.d.ts +8 -8
  159. package/lib/hooks/index.js +7 -7
  160. package/lib/hooks/useA2A.d.ts +2 -3
  161. package/lib/hooks/useAIAgentsWebSocket.d.ts +43 -4
  162. package/lib/hooks/useAIAgentsWebSocket.js +118 -12
  163. package/lib/hooks/useAcp.d.ts +1 -2
  164. package/lib/hooks/useAgUi.d.ts +1 -1
  165. package/lib/hooks/{useAgents.d.ts → useAgentRuntimes.d.ts} +39 -2
  166. package/lib/hooks/{useAgents.js → useAgentRuntimes.js} +125 -15
  167. package/lib/hooks/useAgentsCatalog.js +1 -1
  168. package/lib/hooks/useAgentsService.d.ts +2 -2
  169. package/lib/hooks/useAgentsService.js +7 -7
  170. package/lib/hooks/useCheckpoints.js +1 -1
  171. package/lib/hooks/useConfig.d.ts +4 -1
  172. package/lib/hooks/useConfig.js +10 -3
  173. package/lib/hooks/useContextSnapshot.d.ts +9 -4
  174. package/lib/hooks/useContextSnapshot.js +9 -37
  175. package/lib/hooks/useMonitoring.js +3 -0
  176. package/lib/hooks/useSandbox.d.ts +20 -8
  177. package/lib/hooks/useSandbox.js +105 -40
  178. package/lib/hooks/useSkills.d.ts +23 -5
  179. package/lib/hooks/useSkills.js +94 -39
  180. package/lib/hooks/useToolApprovals.d.ts +60 -36
  181. package/lib/hooks/useToolApprovals.js +318 -69
  182. package/lib/hooks/useVercelAI.d.ts +1 -1
  183. package/lib/index.d.ts +2 -1
  184. package/lib/index.js +1 -0
  185. package/lib/inference/index.d.ts +0 -1
  186. package/lib/middleware/index.d.ts +0 -1
  187. package/lib/protocols/AGUIAdapter.js +6 -0
  188. package/lib/protocols/VercelAIAdapter.d.ts +7 -0
  189. package/lib/protocols/VercelAIAdapter.js +59 -7
  190. package/lib/specs/agents/agents.d.ts +10 -0
  191. package/lib/specs/agents/agents.js +2139 -262
  192. package/lib/specs/agents/index.js +3 -1
  193. package/lib/specs/envvars.d.ts +1 -0
  194. package/lib/specs/envvars.js +38 -20
  195. package/lib/specs/evals.js +6 -6
  196. package/lib/specs/events.d.ts +3 -10
  197. package/lib/specs/events.js +127 -84
  198. package/lib/specs/frontendTools.js +2 -2
  199. package/lib/specs/guardrails.d.ts +0 -7
  200. package/lib/specs/guardrails.js +240 -159
  201. package/lib/specs/index.d.ts +1 -0
  202. package/lib/specs/index.js +1 -0
  203. package/lib/specs/mcpServers.js +35 -6
  204. package/lib/specs/memory.d.ts +0 -2
  205. package/lib/specs/memory.js +4 -17
  206. package/lib/specs/models.js +25 -5
  207. package/lib/specs/notifications.js +102 -18
  208. package/lib/specs/outputs.js +15 -9
  209. package/lib/specs/personas.d.ts +41 -0
  210. package/lib/specs/personas.js +168 -0
  211. package/lib/specs/skills.d.ts +2 -1
  212. package/lib/specs/skills.js +41 -23
  213. package/lib/specs/teams/index.js +3 -1
  214. package/lib/specs/teams/teams.js +468 -348
  215. package/lib/specs/tools.js +4 -4
  216. package/lib/specs/triggers.js +61 -11
  217. package/lib/stores/agentRuntimeStore.d.ts +204 -0
  218. package/lib/stores/agentRuntimeStore.js +636 -0
  219. package/lib/stores/index.d.ts +1 -1
  220. package/lib/stores/index.js +1 -1
  221. package/lib/tools/adapters/copilotkit/lexicalHooks.d.ts +1 -2
  222. package/lib/tools/adapters/copilotkit/lexicalHooks.js +1 -3
  223. package/lib/tools/adapters/copilotkit/notebookHooks.d.ts +1 -2
  224. package/lib/tools/adapters/copilotkit/notebookHooks.js +1 -3
  225. package/lib/tools/index.d.ts +0 -2
  226. package/lib/tools/index.js +0 -1
  227. package/lib/types/agentspecs.d.ts +50 -1
  228. package/lib/types/chat.d.ts +309 -8
  229. package/lib/types/context.d.ts +27 -0
  230. package/lib/types/cost.d.ts +2 -2
  231. package/lib/types/index.d.ts +2 -0
  232. package/lib/types/index.js +2 -0
  233. package/lib/types/mcp.d.ts +8 -0
  234. package/lib/types/models.d.ts +2 -2
  235. package/lib/types/personas.d.ts +25 -0
  236. package/lib/types/personas.js +5 -0
  237. package/lib/types/skills.d.ts +43 -1
  238. package/lib/types/stream.d.ts +110 -0
  239. package/lib/types/stream.js +36 -0
  240. package/lib/utils/utils.d.ts +9 -5
  241. package/lib/utils/utils.js +9 -5
  242. package/package.json +13 -9
  243. package/scripts/codegen/__pycache__/generate_agents.cpython-313.pyc +0 -0
  244. package/scripts/codegen/__pycache__/generate_events.cpython-313.pyc +0 -0
  245. package/scripts/codegen/__pycache__/versioning.cpython-313.pyc +0 -0
  246. package/scripts/codegen/generate_agents.py +102 -6
  247. package/scripts/codegen/generate_events.py +35 -13
  248. package/scripts/codegen/generate_personas.py +319 -0
  249. package/scripts/codegen/generate_skills.py +9 -9
  250. package/scripts/sync-jupyter.sh +26 -7
  251. package/lib/api/tool-approvals.d.ts +0 -62
  252. package/lib/api/tool-approvals.js +0 -145
  253. package/lib/examples/LexicalSidebarExample.js +0 -163
  254. package/lib/examples/NotebookSidebarExample.js +0 -119
  255. package/lib/examples/NotebookSimpleExample.d.ts +0 -6
  256. package/lib/examples/NotebookSimpleExample.js +0 -22
  257. package/lib/examples/ag-ui/index.d.ts +0 -10
  258. package/lib/examples/ag-ui/index.js +0 -16
  259. package/lib/hooks/useAgentsRegistry.d.ts +0 -10
  260. package/lib/hooks/useAgentsRegistry.js +0 -20
  261. package/lib/stores/agentsStore.d.ts +0 -123
  262. package/lib/stores/agentsStore.js +0 -270
  263. /package/lib/examples/{AgentspecExample.d.ts → AgentSpecsExample.d.ts} +0 -0
  264. /package/lib/examples/{ag-ui → components}/haiku/HaikuDisplay.d.ts +0 -0
  265. /package/lib/examples/{ag-ui → components}/haiku/InlineHaikuCard.d.ts +0 -0
  266. /package/lib/examples/{ag-ui → components}/weather/InlineWeatherCard.d.ts +0 -0
  267. /package/lib/examples/{ag-ui → components}/weather/InlineWeatherCard.js +0 -0
@@ -15,17 +15,17 @@ 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 { 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 { ErrorView } from './components';
23
+ import { AuthRequiredView, ErrorView } from './components';
24
24
  import { ThemedProvider } from './utils/themedProvider';
25
+ import { uniqueAgentId } from './utils/agentId';
25
26
  import { useSimpleAuthStore } from '@datalayer/core/lib/views/otel';
26
- import { SignInSimple } from '@datalayer/core/lib/views/iam';
27
- import { UserBadge } from '@datalayer/core/lib/views/profile';
28
27
  import { Chat } from '../chat';
28
+ import { useConnectedIdentities } from '../identity';
29
29
  import { ToolApprovalBanner, ToolApprovalDialog, } from '../chat/tools';
30
30
  import { useAgentEvents, useDeleteAgentEvent, useMarkEventRead, useMarkEventUnread, useAIAgentsWebSocket, } from '../hooks';
31
31
  import { VercelAIAdapter } from '../protocols';
@@ -36,17 +36,28 @@ const AGENT_NAME = 'trigger-demo-agent';
36
36
  const AGENT_SPEC_ID = 'demo-one-trigger';
37
37
  const APPROVAL_AGENT_NAME = 'trigger-approval-demo-agent';
38
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.";
40
- const ONCE_TRIGGER_APPROVAL_PROMPT = "Call runtime_sensitive_echo exactly once with message='Tool approval demo executed' and reason='audit'. Do not call any other tool.";
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.';
41
41
  const DEFAULT_LOCAL_BASE_URL = import.meta.env.VITE_BASE_URL || 'http://localhost:8765';
42
42
  const DEFAULT_CRON = '0 8 * * *'; // daily at 08:00 UTC
43
43
  // ─── Inner component (rendered after auth) ─────────────────────────────────
44
44
  const AgentTriggerInner = ({ onLogout, }) => {
45
45
  const { token } = useSimpleAuthStore();
46
- 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');
47
58
  const [isReady, setIsReady] = useState(false);
48
59
  const [hookError, setHookError] = useState(null);
49
- const [agentId, setAgentId] = useState(AGENT_NAME);
60
+ const [agentId, setAgentId] = useState(agentName);
50
61
  const agentBaseUrl = DEFAULT_LOCAL_BASE_URL;
51
62
  const chatAuthToken = token === null ? undefined : token;
52
63
  // Cron state
@@ -78,11 +89,21 @@ const AgentTriggerInner = ({ onLogout, }) => {
78
89
  const [eventFilter, setEventFilter] = useState('');
79
90
  const [eventSubscribed, setEventSubscribed] = useState(false);
80
91
  // Approval agent state
81
- const [approvalAgentId, setApprovalAgentId] = useState(APPROVAL_AGENT_NAME);
92
+ const [approvalAgentId, setApprovalAgentId] = useState(approvalAgentName);
82
93
  const [approvalAgentReady, setApprovalAgentReady] = useState(false);
83
94
  const [isLaunchingApproval, setIsLaunchingApproval] = useState(false);
84
- const [approvalFlash, setApprovalFlash] = useState(null);
85
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
+ }), []);
86
107
  const [approvals, setApprovals] = useState([]);
87
108
  const [approvalLoading, setApprovalLoading] = useState(null);
88
109
  const [approvalError, setApprovalError] = useState(null);
@@ -96,10 +117,99 @@ const AgentTriggerInner = ({ onLogout, }) => {
96
117
  const markReadMutation = useMarkEventRead(agentId);
97
118
  const markUnreadMutation = useMarkEventUnread(agentId);
98
119
  const agentEvents = eventsQuery.data?.events ?? [];
99
- // WebSocket for real-time event updates
100
- 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({
101
190
  channels: agentId ? [`agent:${agentId}`] : [],
191
+ onMessage: handleAIAgentsMessage,
102
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
+ ]);
103
213
  // Authenticated fetch helper (for sidecar endpoints)
104
214
  const authFetch = useCallback((url, opts = {}) => fetch(url, {
105
215
  ...opts,
@@ -109,68 +219,64 @@ const AgentTriggerInner = ({ onLogout, }) => {
109
219
  ...(opts.headers ?? {}),
110
220
  },
111
221
  }), [token]);
112
- // ── Create agent on local server ─────────────────────────────────────────
113
- useEffect(() => {
114
- let isCancelled = false;
115
- const createAgent = async () => {
116
- setRuntimeStatus('launching');
117
- setIsReady(false);
118
- setHookError(null);
119
- try {
120
- const response = await authFetch(`${agentBaseUrl}/api/v1/agents`, {
121
- method: 'POST',
122
- body: JSON.stringify({
123
- name: AGENT_NAME,
124
- description: 'Agent with cron, webhook, event, and manual triggers',
125
- agent_library: 'pydantic-ai',
126
- transport: 'vercel-ai',
127
- agent_spec_id: AGENT_SPEC_ID,
128
- tools: [],
129
- }),
130
- });
131
- let resolvedAgentId = AGENT_NAME;
132
- if (response.ok) {
133
- const data = await response.json();
134
- 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
+ '';
135
252
  }
136
253
  else {
137
- const contentType = response.headers.get('content-type') || '';
138
- let detail = '';
139
- if (contentType.includes('application/json')) {
140
- const data = await response.json().catch(() => null);
141
- detail =
142
- (typeof data?.detail === 'string' && data.detail) ||
143
- (typeof data?.message === 'string' && data.message) ||
144
- '';
145
- }
146
- else {
147
- detail = await response.text();
148
- }
149
- if (response.status === 409 || /already exists/i.test(detail || '')) {
150
- // Agent already running — reuse it
151
- }
152
- else {
153
- throw new Error(detail || `Failed to create agent: ${response.status}`);
154
- }
254
+ detail = await response.text();
155
255
  }
156
- if (!isCancelled) {
157
- setAgentId(resolvedAgentId);
158
- setIsReady(true);
159
- setRuntimeStatus('ready');
256
+ if (response.status === 409 || /already exists/i.test(detail || '')) {
257
+ // Agent already running — reuse it
160
258
  }
161
- }
162
- catch (error) {
163
- if (!isCancelled) {
164
- setHookError(error instanceof Error ? error.message : 'Agent failed to start');
165
- setRuntimeStatus('error');
259
+ else {
260
+ throw new Error(detail || `Failed to create agent: ${response.status}`);
166
261
  }
167
262
  }
168
- };
169
- void createAgent();
170
- return () => {
171
- isCancelled = true;
172
- };
173
- }, [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]);
174
280
  // ── Poll trigger metadata ─────────────────────────────────────────────
175
281
  // TODO: enable once the ai-agents service exposes /trigger and
176
282
  // /trigger/history endpoints on the platform API.
@@ -247,7 +353,9 @@ const AgentTriggerInner = ({ onLogout, }) => {
247
353
  try {
248
354
  setError(null);
249
355
  await adapter.connect();
250
- await adapter.sendMessage(createUserMessage(prompt));
356
+ await adapter.sendMessage(createUserMessage(prompt), {
357
+ identities: identitiesForRuns,
358
+ });
251
359
  return { finalAssistantText: latestAssistantText, pendingApproval };
252
360
  }
253
361
  catch (error) {
@@ -255,7 +363,7 @@ const AgentTriggerInner = ({ onLogout, }) => {
255
363
  throw error;
256
364
  }
257
365
  finally {
258
- if (options?.keepAliveForApproval && pendingApproval) {
366
+ if (options?.keepAliveForApproval) {
259
367
  approvalStreamRef.current = { adapter, unsubscribe };
260
368
  }
261
369
  else {
@@ -263,7 +371,7 @@ const AgentTriggerInner = ({ onLogout, }) => {
263
371
  adapter.disconnect();
264
372
  }
265
373
  }
266
- }, [agentBaseUrl, token, upsertSidebarMessage]);
374
+ }, [agentBaseUrl, token, identitiesForRuns, upsertSidebarMessage]);
267
375
  useEffect(() => {
268
376
  return () => {
269
377
  if (approvalStreamRef.current) {
@@ -273,83 +381,62 @@ const AgentTriggerInner = ({ onLogout, }) => {
273
381
  }
274
382
  };
275
383
  }, []);
276
- // ── Create approval agent on local server ────────────────────────────────
277
- useEffect(() => {
278
- if (!isReady)
279
- return;
280
- let isCancelled = false;
281
- const createApprovalAgent = async () => {
282
- try {
283
- const response = await authFetch(`${agentBaseUrl}/api/v1/agents`, {
284
- method: 'POST',
285
- body: JSON.stringify({
286
- name: APPROVAL_AGENT_NAME,
287
- description: 'Agent with once trigger and tool approval',
288
- agent_library: 'pydantic-ai',
289
- transport: 'vercel-ai',
290
- agent_spec_id: APPROVAL_AGENT_SPEC_ID,
291
- tools: ['runtime-sensitive-echo'],
292
- }),
293
- });
294
- let resolvedId = APPROVAL_AGENT_NAME;
295
- if (response.ok) {
296
- const data = await response.json();
297
- resolvedId = data?.id || APPROVAL_AGENT_NAME;
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 || '';
298
410
  }
299
411
  else {
300
- const contentType = response.headers.get('content-type') || '';
301
- let detail = '';
302
- if (contentType.includes('application/json')) {
303
- const data = await response.json().catch(() => null);
304
- detail = data?.detail || data?.message || '';
305
- }
306
- else {
307
- detail = await response.text();
308
- }
309
- if (response.status === 409 || /already exists/i.test(detail || '')) {
310
- // Already running — reuse
311
- }
312
- else {
313
- console.warn('Failed to create approval agent:', detail);
314
- return;
315
- }
412
+ detail = await response.text();
316
413
  }
317
- if (!isCancelled) {
318
- setApprovalAgentId(resolvedId);
319
- setApprovalAgentReady(true);
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;
320
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;
321
430
  }
322
- catch (error) {
323
- console.warn('Approval agent creation failed:', error);
324
- }
325
- };
326
- void createApprovalAgent();
327
- return () => { isCancelled = true; };
328
- }, [isReady, agentBaseUrl, authFetch]);
329
- // ── Poll tool approvals ─────────────────────────────────────────────────
330
- const pollApprovals = useCallback(async () => {
331
- if (!approvalAgentReady)
332
- return;
333
- try {
334
- const res = await authFetch(`${agentBaseUrl}/api/v1/tool-approvals`);
335
- if (!res.ok)
336
- return;
337
- const data = await res.json();
338
- const all = Array.isArray(data) ? data : (data.approvals ?? data.requests ?? []);
339
- const pending = all.filter((a) => a.status === 'pending' && (!a.agent_id || a.agent_id === approvalAgentId));
340
- setApprovals(pending);
431
+ setApprovalAgentId(resolvedId);
432
+ setApprovalAgentReady(true);
433
+ return true;
341
434
  }
342
- catch {
343
- // Non-fatal
435
+ catch (error) {
436
+ console.warn('Approval agent creation failed:', error);
437
+ return false;
344
438
  }
345
- }, [approvalAgentReady, agentBaseUrl, approvalAgentId, authFetch]);
346
- useEffect(() => {
347
- if (!hasTriggeredApproval)
348
- return;
349
- void pollApprovals();
350
- const interval = setInterval(pollApprovals, 2000);
351
- return () => clearInterval(interval);
352
- }, [hasTriggeredApproval, pollApprovals]);
439
+ }, [agentBaseUrl, authFetch, approvalAgentName]);
353
440
  // Approval sidebar messages are populated from live Vercel stream events.
354
441
  // ── Approve / Reject handlers ───────────────────────────────────────────
355
442
  const handleApprove = useCallback(async (requestId) => {
@@ -357,76 +444,116 @@ const AgentTriggerInner = ({ onLogout, }) => {
357
444
  setApprovalError(null);
358
445
  try {
359
446
  const approval = approvals.find(a => a.id === requestId);
360
- const res = await authFetch(`${agentBaseUrl}/api/v1/tool-approvals/${requestId}/approve`, { method: 'POST', body: JSON.stringify({}) });
361
- if (!res.ok) {
362
- throw new Error(`Approve failed (${res.status})`);
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');
363
457
  }
364
- if (approval?.tool_call_id && approvalStreamRef.current) {
365
- await approvalStreamRef.current.adapter.sendToolResult(approval.tool_call_id, {
366
- toolCallId: approval.tool_call_id,
367
- success: true,
368
- result: {
369
- approved: true,
370
- approvalId: requestId,
371
- message: 'Approved from trigger panel',
372
- },
373
- });
374
- approvalStreamRef.current.unsubscribe();
375
- approvalStreamRef.current.adapter.disconnect();
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) {
376
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();
377
478
  }
378
479
  setApprovals(prev => prev.filter(a => a.id !== requestId));
379
- void pollApprovals();
480
+ return true;
380
481
  }
381
482
  catch (error) {
382
483
  setApprovalError(error instanceof Error ? error.message : 'Failed to approve');
484
+ return false;
383
485
  }
384
486
  finally {
385
487
  setApprovalLoading(null);
386
488
  }
387
- }, [agentBaseUrl, authFetch, pollApprovals, approvals]);
489
+ }, [approvals, sendToAIAgents]);
388
490
  const handleReject = useCallback(async (requestId, note) => {
389
491
  setApprovalLoading(requestId);
390
492
  setApprovalError(null);
391
493
  try {
392
494
  const approval = approvals.find(a => a.id === requestId);
393
- const res = await authFetch(`${agentBaseUrl}/api/v1/tool-approvals/${requestId}/reject`, { method: 'POST', body: JSON.stringify(note ? { note } : {}) });
394
- if (!res.ok) {
395
- throw new Error(`Reject failed (${res.status})`);
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');
396
506
  }
397
- if (approval?.tool_call_id && approvalStreamRef.current) {
398
- await approvalStreamRef.current.adapter.sendToolResult(approval.tool_call_id, {
399
- toolCallId: approval.tool_call_id,
400
- success: true,
401
- result: {
402
- approved: false,
403
- approvalId: requestId,
404
- message: note || 'Rejected from trigger panel',
405
- },
406
- });
407
- approvalStreamRef.current.unsubscribe();
408
- approvalStreamRef.current.adapter.disconnect();
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) {
409
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();
410
527
  }
411
528
  setApprovals(prev => prev.filter(a => a.id !== requestId));
412
- void pollApprovals();
529
+ return true;
413
530
  }
414
531
  catch (error) {
415
532
  setApprovalError(error instanceof Error ? error.message : 'Failed to reject');
533
+ return false;
416
534
  }
417
535
  finally {
418
536
  setApprovalLoading(null);
419
537
  }
420
- }, [agentBaseUrl, authFetch, pollApprovals, approvals]);
538
+ }, [approvals, sendToAIAgents]);
421
539
  // ── Launch once trigger with approval ────────────────────────────────────
422
540
  const handleLaunchOnceApproval = useCallback(async () => {
423
- if (!agentBaseUrl || !approvalAgentReady)
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)
424
551
  return;
425
552
  setIsLaunchingApproval(true);
553
+ setHasTriggeredApproval(true);
426
554
  setApprovalFlash(null);
427
555
  setApprovalSidebarMessages([]);
428
556
  setSidebarMessagesError(null);
429
- setHasTriggeredApproval(true);
430
557
  const runId = `once-approval-${Date.now()}`;
431
558
  const startTime = new Date().toISOString();
432
559
  setTriggerHistory(prev => [
@@ -439,13 +566,8 @@ const AgentTriggerInner = ({ onLogout, }) => {
439
566
  ...prev,
440
567
  ]);
441
568
  try {
442
- setApprovalFlash('Once trigger with approval launched — waiting for tool approval.');
443
- const streamResult = await streamRunMessages(approvalAgentId, ONCE_TRIGGER_APPROVAL_PROMPT, setApprovalSidebarMessages, setSidebarMessagesError, { keepAliveForApproval: true });
444
- if (!streamResult.pendingApproval && approvalStreamRef.current) {
445
- approvalStreamRef.current.unsubscribe();
446
- approvalStreamRef.current.adapter.disconnect();
447
- approvalStreamRef.current = null;
448
- }
569
+ setApprovalFlash('Once trigger launched.');
570
+ await streamRunMessages(approvalAgentId, ONCE_TRIGGER_APPROVAL_PROMPT, setApprovalSidebarMessages, setSidebarMessagesError, { keepAliveForApproval: true });
449
571
  setTriggerHistory(prev => prev.map(r => r.id === runId
450
572
  ? {
451
573
  ...r,
@@ -464,8 +586,10 @@ const AgentTriggerInner = ({ onLogout, }) => {
464
586
  }, [
465
587
  agentBaseUrl,
466
588
  approvalAgentReady,
589
+ createApprovalAgent,
467
590
  approvalAgentId,
468
591
  streamRunMessages,
592
+ ensureRuntimeReady,
469
593
  ]);
470
594
  // ── Pending approvals for banner/dialog ──────────────────────────────────
471
595
  const pendingApprovals = approvals.map(req => ({
@@ -480,6 +604,9 @@ const AgentTriggerInner = ({ onLogout, }) => {
480
604
  const handleUpdateCron = useCallback(async () => {
481
605
  if (!agentBaseUrl || !editCron.trim())
482
606
  return;
607
+ const ready = await ensureRuntimeReady();
608
+ if (!ready)
609
+ return;
483
610
  setIsUpdating(true);
484
611
  try {
485
612
  const res = await authFetch(`${agentBaseUrl}/api/v1/agents/${agentId}/trigger`, {
@@ -499,11 +626,14 @@ const AgentTriggerInner = ({ onLogout, }) => {
499
626
  finally {
500
627
  setIsUpdating(false);
501
628
  }
502
- }, [agentBaseUrl, agentId, editCron, authFetch]);
629
+ }, [agentBaseUrl, agentId, editCron, authFetch, ensureRuntimeReady]);
503
630
  // ── Manual trigger ───────────────────────────────────────────────────────
504
631
  const handleTriggerNow = useCallback(async () => {
505
632
  if (!agentBaseUrl)
506
633
  return;
634
+ const ready = await ensureRuntimeReady();
635
+ if (!ready)
636
+ return;
507
637
  setIsTriggeringNow(true);
508
638
  setTriggerFlash(null);
509
639
  const runId = `manual-${Date.now()}`;
@@ -518,7 +648,13 @@ const AgentTriggerInner = ({ onLogout, }) => {
518
648
  ...prev,
519
649
  ]);
520
650
  try {
521
- 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
+ });
522
658
  if (res.ok) {
523
659
  setTriggerFlash('Trigger fired successfully');
524
660
  setTriggerHistory(prev => prev.map(r => r.id === runId
@@ -541,11 +677,14 @@ const AgentTriggerInner = ({ onLogout, }) => {
541
677
  finally {
542
678
  setIsTriggeringNow(false);
543
679
  }
544
- }, [agentBaseUrl, agentId, authFetch]);
680
+ }, [agentBaseUrl, agentId, authFetch, identitiesForRuns, ensureRuntimeReady]);
545
681
  // ── Webhook management ─────────────────────────────────────────────────
546
682
  const handleGenerateWebhook = useCallback(async () => {
547
683
  if (!agentBaseUrl)
548
684
  return;
685
+ const ready = await ensureRuntimeReady();
686
+ if (!ready)
687
+ return;
549
688
  setIsUpdating(true);
550
689
  try {
551
690
  const res = await authFetch(`${agentBaseUrl}/api/v1/agents/${agentId}/trigger/webhook`, { method: 'POST' });
@@ -562,10 +701,13 @@ const AgentTriggerInner = ({ onLogout, }) => {
562
701
  finally {
563
702
  setIsUpdating(false);
564
703
  }
565
- }, [agentBaseUrl, agentId, authFetch]);
704
+ }, [agentBaseUrl, agentId, authFetch, ensureRuntimeReady]);
566
705
  const handleToggleWebhook = useCallback(async () => {
567
706
  if (!agentBaseUrl)
568
707
  return;
708
+ const ready = await ensureRuntimeReady();
709
+ if (!ready)
710
+ return;
569
711
  try {
570
712
  await authFetch(`${agentBaseUrl}/api/v1/agents/${agentId}/trigger/webhook`, {
571
713
  method: 'PATCH',
@@ -576,11 +718,14 @@ const AgentTriggerInner = ({ onLogout, }) => {
576
718
  catch {
577
719
  /* ok */
578
720
  }
579
- }, [agentBaseUrl, agentId, webhookEnabled, authFetch]);
721
+ }, [agentBaseUrl, agentId, webhookEnabled, authFetch, ensureRuntimeReady]);
580
722
  // ── Event subscription ─────────────────────────────────────────────────
581
723
  const handleSubscribeEvent = useCallback(async () => {
582
724
  if (!agentBaseUrl || !eventTopic.trim())
583
725
  return;
726
+ const ready = await ensureRuntimeReady();
727
+ if (!ready)
728
+ return;
584
729
  setIsUpdating(true);
585
730
  try {
586
731
  const res = await authFetch(`${agentBaseUrl}/api/v1/agents/${agentId}/trigger/event`, {
@@ -600,11 +745,21 @@ const AgentTriggerInner = ({ onLogout, }) => {
600
745
  finally {
601
746
  setIsUpdating(false);
602
747
  }
603
- }, [agentBaseUrl, agentId, eventTopic, eventFilter, authFetch]);
748
+ }, [
749
+ agentBaseUrl,
750
+ agentId,
751
+ eventTopic,
752
+ eventFilter,
753
+ authFetch,
754
+ ensureRuntimeReady,
755
+ ]);
604
756
  // ── Launch once trigger ──────────────────────────────────────────────────
605
757
  const handleLaunchOnce = useCallback(async () => {
606
758
  if (!agentBaseUrl)
607
759
  return;
760
+ const ready = await ensureRuntimeReady();
761
+ if (!ready)
762
+ return;
608
763
  setIsLaunchingOnce(true);
609
764
  setOnceFlash(null);
610
765
  setStreamedOnceOutput(null);
@@ -644,9 +799,9 @@ const AgentTriggerInner = ({ onLogout, }) => {
644
799
  finally {
645
800
  setIsLaunchingOnce(false);
646
801
  }
647
- }, [agentBaseUrl, agentId, streamRunMessages]);
802
+ }, [agentBaseUrl, agentId, streamRunMessages, ensureRuntimeReady]);
648
803
  // ── Loading / Error ──────────────────────────────────────────────────────
649
- if (!isReady && runtimeStatus !== 'error') {
804
+ if (runtimeStatus === 'launching') {
650
805
  return (_jsxs(Box, { sx: {
651
806
  display: 'flex',
652
807
  flexDirection: 'column',
@@ -654,405 +809,509 @@ const AgentTriggerInner = ({ onLogout, }) => {
654
809
  justifyContent: 'center',
655
810
  height: '100vh',
656
811
  gap: 3,
657
- }, children: [_jsx(Spinner, { size: "large" }), _jsx(Text, { sx: { color: 'fg.muted' }, children: runtimeStatus === 'launching'
658
- ? 'Launching runtime for trigger agent…'
659
- : 'Creating trigger demo agent…' })] }));
812
+ }, children: [_jsx(Spinner, { size: "large" }), _jsx(Text, { sx: { color: 'fg.muted' }, children: "Launching runtime for trigger agent..." })] }));
660
813
  }
661
814
  if (runtimeStatus === 'error' || hookError) {
662
815
  return _jsx(ErrorView, { error: hookError, onLogout: onLogout });
663
816
  }
664
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}'`;
665
- return (_jsxs(Box, { sx: {
666
- height: 'calc(100vh - 60px)',
667
- display: 'flex',
668
- flexDirection: 'column',
669
- }, children: [_jsxs(Box, { sx: {
670
- display: 'flex',
671
- alignItems: 'center',
672
- gap: 2,
673
- px: 3,
674
- py: 2,
675
- borderBottom: '1px solid',
676
- borderColor: 'border.default',
677
- flexShrink: 0,
678
- }, 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" })] }), _jsx(ToolApprovalBanner, { pendingApprovals: pendingApprovals, onReview: approval => {
679
- const req = approvals.find(a => a.id === approval.id) || null;
680
- setActiveApproval(req);
681
- }, onApproveAll: async () => {
682
- for (const a of approvals) {
683
- await handleApprove(a.id);
684
- }
685
- } }), _jsx(ToolApprovalDialog, { isOpen: !!activeApproval, toolName: activeApproval?.tool_name ?? '', toolDescription: activeApproval?.note, args: activeApproval?.tool_args ?? {}, onApprove: async () => {
686
- if (activeApproval) {
687
- await handleApprove(activeApproval.id);
688
- setActiveApproval(null);
689
- }
690
- }, onDeny: async () => {
691
- if (activeApproval) {
692
- await handleReject(activeApproval.id, 'Rejected from dialog');
693
- setActiveApproval(null);
694
- }
695
- }, 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: _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: false, showModelSelector: false, showToolsMenu: false, showSkillsMenu: false }) }), _jsxs(Box, { sx: {
696
- width: 380,
697
- borderLeft: '1px solid',
698
- borderColor: 'border.default',
699
- display: 'flex',
700
- flexDirection: 'column',
701
- overflow: 'auto',
702
- }, children: [_jsx(Box, { sx: {
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)',
826
+ display: 'flex',
827
+ flexDirection: 'column',
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%',
703
860
  display: 'flex',
704
- borderBottom: '1px solid',
705
- borderColor: 'border.default',
706
- flexShrink: 0,
707
- }, children: [
708
- { key: 'once', icon: ZapIcon, label: 'Once' },
709
- { key: 'cron', icon: ClockIcon, label: 'Cron' },
710
- {
711
- key: 'webhook',
712
- icon: GlobeIcon,
713
- label: 'Webhook',
714
- },
715
- { key: 'event', icon: ZapIcon, label: 'Event' },
716
- {
717
- key: 'manual',
718
- icon: PlayIcon,
719
- label: 'Manual',
720
- },
721
- ].map(t => (_jsx(Button, { size: "small", variant: "invisible", leadingVisual: t.icon, onClick: () => setActiveTab(t.key), sx: {
722
- flex: 1,
723
- borderRadius: 0,
724
- borderBottom: activeTab === t.key ? '2px solid' : '2px solid transparent',
725
- borderColor: activeTab === t.key ? 'accent.fg' : 'transparent',
726
- fontWeight: activeTab === t.key ? 'bold' : 'normal',
727
- }, children: t.label }, t.key))) }), activeTab === 'once' && (_jsxs(Box, { sx: {
728
- p: 3,
729
- borderBottom: '1px solid',
730
- borderColor: 'border.default',
731
- }, 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: {
732
- bg: 'canvas.subtle',
733
- border: '1px solid',
734
- borderColor: 'border.default',
735
- borderRadius: 2,
736
- p: 2,
737
- mb: 2,
738
- display: 'grid',
739
- gap: 1,
740
- }, 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 || !approvalAgentReady, sx: { width: '100%', mt: 2 }, children: isLaunchingApproval ? 'Launching…' : '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" }), (() => {
741
- const outputEvent = lastOnceStartedAt
742
- ? [...agentEvents]
743
- .filter(e => e.kind === 'agent-output' &&
744
- new Date(e.created_at).getTime() >=
745
- new Date(lastOnceStartedAt).getTime() - 5000)
746
- .sort((a, b) => new Date(b.created_at).getTime() -
747
- new Date(a.created_at).getTime())[0]
748
- : undefined;
749
- const hasStreamFallback = !outputEvent && !isLaunchingOnce && streamedOnceOutput !== null;
750
- if (!outputEvent && !hasStreamFallback) {
751
- return (_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2, mb: 2 }, children: [_jsx(Spinner, { size: "small" }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: "Waiting for agent output\u2026" })] }));
752
- }
753
- const p = outputEvent?.payload;
754
- const outputText = outputEvent
755
- ? (p?.outputs ? String(p.outputs) : '')
756
- : (streamedOnceOutput ?? '');
757
- const exitStatus = outputEvent ? p?.exit_status : 'completed';
758
- const durationMs = outputEvent
759
- ? p?.duration_ms
760
- : (lastOnceStartedAt && streamedOnceEndedAt
761
- ? new Date(streamedOnceEndedAt).getTime() -
762
- new Date(lastOnceStartedAt).getTime()
763
- : undefined);
764
- const endedAt = outputEvent ? p?.ended_at : streamedOnceEndedAt;
765
- return (_jsxs(Box, { sx: {
766
- mb: 2,
767
- border: '1px solid',
768
- borderColor: exitStatus === 'error'
769
- ? 'danger.muted'
770
- : 'success.muted',
771
- borderRadius: 2,
772
- overflow: 'hidden',
773
- }, children: [_jsxs(Box, { sx: {
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: {
774
927
  display: 'flex',
775
928
  alignItems: 'center',
776
- gap: 1,
777
- px: 2,
778
- py: 1,
779
- bg: exitStatus === 'error'
780
- ? 'danger.subtle'
781
- : 'success.subtle',
782
- borderBottom: '1px solid',
783
- borderColor: exitStatus === 'error'
784
- ? 'danger.muted'
785
- : 'success.muted',
786
- }, 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: {
787
- fontSize: 0,
788
- color: 'fg.muted',
789
- ml: 'auto',
790
- whiteSpace: 'nowrap',
791
- }, 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: {
792
- p: 2,
793
- bg: 'canvas.default',
794
- maxHeight: 300,
795
- overflow: 'auto',
796
- }, children: _jsx(Text, { sx: {
797
- fontSize: 0,
798
- color: 'fg.default',
799
- display: 'block',
800
- whiteSpace: 'pre-wrap',
801
- wordBreak: 'break-word',
802
- fontFamily: 'mono',
803
- }, children: outputText || '(empty output)' }) })] }));
804
- })()] })), hasTriggeredOnce && (_jsxs(_Fragment, { children: [_jsxs(Heading, { as: "h4", sx: { fontSize: 1, mt: 3, mb: 2 }, children: ["Streaming Messages", isLaunchingOnce && (_jsx(Spinner, { size: "small", sx: {
805
- ml: 2,
806
- verticalAlign: 'middle',
807
- width: 14,
808
- height: 14,
809
- minWidth: 14,
810
- minHeight: 14,
811
- } }))] }), sidebarMessagesError ? (_jsx(Flash, { variant: "danger", sx: { fontSize: 0, mb: 2 }, children: sidebarMessagesError })) : sidebarMessages.filter(msg => msg.role !== 'user').length === 0 ? (_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2, mb: 2 }, children: [_jsx(Spinner, { size: "small", sx: { width: 16, height: 16, minWidth: 16, minHeight: 16 } }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: "Waiting for streaming messages\u2026" })] })) : (_jsx(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2, mb: 2 }, children: sidebarMessages
812
- .slice()
813
- .filter(msg => msg.role !== 'user')
814
- .sort((a, b) => {
815
- const ta = a.createdAt ? new Date(a.createdAt).getTime() : 0;
816
- const tb = b.createdAt ? new Date(b.createdAt).getTime() : 0;
817
- return tb - ta;
818
- })
819
- .slice(0, 10)
820
- .map(msg => {
821
- const content = typeof msg.content === 'string'
822
- ? msg.content
823
- : JSON.stringify(msg.content);
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;
824
951
  return (_jsxs(Box, { sx: {
825
- p: 2,
826
- bg: 'canvas.subtle',
827
- borderRadius: 2,
952
+ mb: 2,
828
953
  border: '1px solid',
829
- borderColor: 'border.default',
954
+ borderColor: exitStatus === 'error'
955
+ ? 'danger.muted'
956
+ : 'success.muted',
957
+ borderRadius: 2,
958
+ overflow: 'hidden',
830
959
  }, children: [_jsxs(Box, { sx: {
831
960
  display: 'flex',
832
961
  alignItems: 'center',
833
962
  gap: 1,
834
- mb: 1,
835
- }, children: [_jsx(Label, { size: "small", variant: msg.role === 'assistant'
836
- ? 'accent'
837
- : msg.role === 'tool'
838
- ? 'success'
839
- : 'secondary', children: msg.role }), msg.createdAt && (_jsx(Text, { sx: {
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: {
840
973
  fontSize: 0,
841
974
  color: 'fg.muted',
842
- marginLeft: 'auto',
975
+ ml: 'auto',
843
976
  whiteSpace: 'nowrap',
844
- }, children: new Date(msg.createdAt).toLocaleTimeString() }))] }), _jsx(Text, { sx: {
845
- fontSize: 0,
846
- color: 'fg.default',
847
- display: 'block',
848
- whiteSpace: 'pre-wrap',
849
- wordBreak: 'break-word',
850
- }, children: content.length > 320
851
- ? `${content.slice(0, 320)}…`
852
- : content })] }, `once-msg-${msg.id}`));
853
- }) }))] })), 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: { display: 'flex', flexDirection: 'column', gap: 2, mb: 2 }, children: approvals.map(a => (_jsxs(Box, { sx: {
854
- p: 2,
855
- border: '1px solid',
856
- borderColor: 'attention.muted',
857
- borderRadius: 2,
858
- bg: 'attention.subtle',
859
- }, children: [_jsxs(Text, { sx: { fontWeight: 600, fontSize: 1, display: 'block', mb: 1 }, children: ["\uD83D\uDEE1\uFE0F ", a.tool_name] }), a.tool_args && (_jsx(Text, { sx: { fontSize: 0, color: 'fg.muted', display: 'block', mb: 2, fontFamily: 'mono' }, 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 ? 'Approving…' : '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: {
860
- ml: 2,
861
- verticalAlign: 'middle',
862
- width: 14,
863
- height: 14,
864
- minWidth: 14,
865
- minHeight: 14,
866
- } }))] }), hasTriggeredApproval && (_jsxs(_Fragment, { children: [_jsx(Heading, { as: "h4", sx: { fontSize: 1, mt: 3, mb: 2 }, children: "Generated Output" }), (() => {
867
- const latestAssistantOutput = approvalSidebarMessages
868
- .filter(msg => msg.role === 'assistant')
869
- .sort((a, b) => {
870
- const ta = a.createdAt ? new Date(a.createdAt).getTime() : 0;
871
- const tb = b.createdAt ? new Date(b.createdAt).getTime() : 0;
872
- return tb - ta;
873
- })[0];
874
- if (!latestAssistantOutput) {
875
- return (_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2, mb: 2 }, children: [_jsx(Spinner, { size: "small", sx: { width: 16, height: 16, minWidth: 16, minHeight: 16 } }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: "Waiting for agent output\u2026" })] }));
876
- }
877
- const outputText = typeof latestAssistantOutput.content === 'string'
878
- ? latestAssistantOutput.content
879
- : JSON.stringify(latestAssistantOutput.content);
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);
880
1030
  return (_jsxs(Box, { sx: {
881
- mb: 2,
882
- border: '1px solid',
883
- borderColor: 'success.muted',
1031
+ p: 2,
1032
+ bg: 'canvas.subtle',
884
1033
  borderRadius: 2,
885
- overflow: 'hidden',
1034
+ border: '1px solid',
1035
+ borderColor: 'border.default',
886
1036
  }, children: [_jsxs(Box, { sx: {
887
1037
  display: 'flex',
888
1038
  alignItems: 'center',
889
1039
  gap: 1,
890
- px: 2,
891
- py: 1,
892
- bg: 'success.subtle',
893
- borderBottom: '1px solid',
894
- borderColor: 'success.muted',
895
- }, children: [_jsx(Label, { variant: "success", size: "small", children: "completed" }), latestAssistantOutput.createdAt && (_jsx(Text, { sx: {
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: {
896
1046
  fontSize: 0,
897
1047
  color: 'fg.muted',
898
- ml: 'auto',
1048
+ marginLeft: 'auto',
899
1049
  whiteSpace: 'nowrap',
900
- }, 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: {
901
- p: 2,
902
- bg: 'canvas.default',
903
- maxHeight: 300,
904
- overflow: 'auto',
905
- }, children: _jsx(Text, { sx: {
906
- fontSize: 0,
907
- color: 'fg.default',
908
- display: 'block',
909
- whiteSpace: 'pre-wrap',
910
- wordBreak: 'break-word',
911
- fontFamily: 'mono',
912
- }, children: outputText || '(empty output)' }) })] }));
913
- })()] })), approvalSidebarMessages.filter(msg => msg.role !== 'user').length === 0 ? (_jsxs(Box, { sx: { display: 'flex', alignItems: 'center', gap: 2, mb: 2 }, children: [_jsx(Spinner, { size: "small", sx: { width: 16, height: 16, minWidth: 16, minHeight: 16 } }), _jsx(Text, { sx: { color: 'fg.muted', fontSize: 0 }, children: "Waiting for approval agent messages\u2026" })] })) : (_jsx(Box, { sx: { display: 'flex', flexDirection: 'column', gap: 2, mb: 2 }, children: approvalSidebarMessages
914
- .slice()
915
- .filter(msg => msg.role !== 'user')
916
- .sort((a, b) => {
917
- const ta = a.createdAt ? new Date(a.createdAt).getTime() : 0;
918
- const tb = b.createdAt ? new Date(b.createdAt).getTime() : 0;
919
- return tb - ta;
920
- })
921
- .slice(0, 10)
922
- .map(msg => {
923
- const content = typeof msg.content === 'string'
924
- ? msg.content
925
- : JSON.stringify(msg.content);
926
- return (_jsxs(Box, { sx: {
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: {
927
1065
  p: 2,
928
- bg: 'canvas.subtle',
929
- borderRadius: 2,
930
1066
  border: '1px solid',
931
- borderColor: 'border.default',
932
- }, children: [_jsxs(Box, { sx: {
933
- display: 'flex',
934
- alignItems: 'center',
935
- gap: 1,
1067
+ borderColor: 'attention.muted',
1068
+ borderRadius: 2,
1069
+ bg: 'attention.subtle',
1070
+ }, children: [_jsxs(Text, { sx: {
1071
+ fontWeight: 600,
1072
+ fontSize: 1,
1073
+ display: 'block',
936
1074
  mb: 1,
937
- }, children: [_jsx(Label, { size: "small", variant: msg.role === 'assistant'
938
- ? 'accent'
939
- : msg.role === 'tool'
940
- ? 'success'
941
- : 'secondary', children: msg.role }), msg.createdAt && (_jsx(Text, { sx: {
942
- fontSize: 0,
943
- color: 'fg.muted',
944
- marginLeft: 'auto',
945
- whiteSpace: 'nowrap',
946
- }, children: new Date(msg.createdAt).toLocaleTimeString() }))] }), _jsx(Text, { sx: {
1075
+ }, children: ["\uD83D\uDEE1\uFE0F ", a.tool_name] }), a.tool_args && (_jsx(Text, { sx: {
947
1076
  fontSize: 0,
948
- color: 'fg.default',
1077
+ color: 'fg.muted',
949
1078
  display: 'block',
950
- whiteSpace: 'pre-wrap',
951
- wordBreak: 'break-word',
952
- }, children: content.length > 320
953
- ? `${content.slice(0, 320)}…`
954
- : content })] }, `approval-msg-${msg.id}`));
955
- }) }))] })), _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: {
956
- mt: 1,
957
- bg: 'canvas.subtle',
958
- borderRadius: 2,
959
- p: 2,
960
- fontFamily: 'mono',
961
- fontSize: 0,
962
- wordBreak: 'break-all',
963
- }, children: triggerRunCurl })] }), _jsx(Button, { size: "small", leadingVisual: CopyIcon, onClick: () => navigator.clipboard.writeText(triggerRunCurl), children: "Copy streaming command" })] })] })), activeTab === 'cron' && (_jsxs(Box, { sx: {
964
- p: 3,
965
- borderBottom: '1px solid',
966
- borderColor: 'border.default',
967
- }, 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: {
968
- p: 3,
969
- borderBottom: '1px solid',
970
- borderColor: 'border.default',
971
- }, 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: {
972
- bg: 'canvas.subtle',
973
- p: 2,
974
- borderRadius: 2,
975
- mb: 2,
976
- fontFamily: 'mono',
977
- fontSize: 0,
978
- wordBreak: 'break-all',
979
- }, children: webhookUrl }), webhookSecret && (_jsxs(Box, { sx: { mb: 2 }, children: [_jsx(Text, { sx: { fontSize: 0, fontWeight: 'bold' }, children: "Secret:" }), _jsx(Box, { sx: {
980
- bg: 'canvas.subtle',
981
- p: 2,
982
- borderRadius: 2,
983
- mt: 1,
984
- fontFamily: 'mono',
985
- fontSize: 0,
986
- }, 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: {
987
- p: 3,
988
- borderBottom: '1px solid',
989
- borderColor: 'border.default',
990
- }, 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: {
991
- p: 3,
992
- borderBottom: '1px solid',
993
- borderColor: 'border.default',
994
- }, 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'
995
- ? 'success'
996
- : rec.status === 'failure'
997
- ? 'danger'
998
- : '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]
999
- .sort((a, b) => new Date(b.created_at).getTime() -
1000
- new Date(a.created_at).getTime())
1001
- .map((evt) => (_jsxs(Box, { sx: {
1002
- p: 2,
1003
- bg: evt.read ? 'canvas.subtle' : 'accent.subtle',
1004
- borderRadius: 2,
1005
- border: '1px solid',
1006
- borderColor: evt.read
1007
- ? 'border.default'
1008
- : 'accent.muted',
1009
- }, children: [_jsxs(Box, { sx: {
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: {
1010
1153
  display: 'flex',
1011
1154
  alignItems: 'center',
1012
- gap: 1,
1013
- mb: 1,
1014
- }, children: [_jsx(Label, { variant: evt.kind === 'agent-started'
1015
- ? 'accent'
1016
- : evt.kind === 'agent-output'
1017
- ? 'success'
1018
- : evt.kind?.includes('alert')
1019
- ? 'danger'
1020
- : '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',
1021
1243
  fontSize: 0,
1022
- color: 'fg.muted',
1023
- whiteSpace: 'nowrap',
1024
- }, children: new Date(evt.created_at).toLocaleString() }), _jsx(Button, { size: "small", variant: "invisible", onClick: () => {
1025
- const payload = {
1026
- eventId: String(evt.id),
1027
- eventAgentId: String(evt.agent_id || agentId || ''),
1028
- };
1029
- if (evt.read) {
1030
- markUnreadMutation.mutate(payload);
1031
- }
1032
- else {
1033
- markReadMutation.mutate(payload);
1034
- }
1035
- }, 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 &&
1036
- (() => {
1037
- const p = evt.payload;
1038
- 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: {
1039
- all: 'unset',
1040
- display: 'block',
1041
- width: '100%',
1042
- cursor: 'default',
1043
- }, children: _jsxs(Box, { sx: {
1044
- mb: 1,
1045
- display: 'flex',
1046
- alignItems: 'baseline',
1047
- gap: 1,
1048
- minWidth: 0,
1049
- }, children: [_jsx(Text, { as: "span", sx: {
1050
- fontWeight: 'bold',
1051
- color: 'fg.default',
1052
- whiteSpace: 'nowrap',
1053
- }, children: "Output:" }), _jsx(Truncate, { title: String(p.outputs), maxWidth: "100%", sx: { minWidth: 0, flex: 1 }, children: String(p.outputs) })] }) }) })), evt.kind === 'agent-output' &&
1054
- 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) }))] }));
1055
- })()] }, 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))) }))] })] })] })] }) }));
1056
1315
  };
1057
1316
  // ─── Sync token to core IAM store ──────────────────────────────────────────
1058
1317
  const syncTokenToIamStore = (token) => {
@@ -1062,7 +1321,7 @@ const syncTokenToIamStore = (token) => {
1062
1321
  };
1063
1322
  // ─── Main component with auth gate ─────────────────────────────────────────
1064
1323
  const AgentTriggersExample = () => {
1065
- const { token, setAuth, clearAuth } = useSimpleAuthStore();
1324
+ const { token, clearAuth } = useSimpleAuthStore();
1066
1325
  const hasSynced = useRef(false);
1067
1326
  useEffect(() => {
1068
1327
  if (token && !hasSynced.current) {
@@ -1070,11 +1329,6 @@ const AgentTriggersExample = () => {
1070
1329
  syncTokenToIamStore(token);
1071
1330
  }
1072
1331
  }, [token]);
1073
- const handleSignIn = useCallback((newToken, handle) => {
1074
- setAuth(newToken, handle);
1075
- hasSynced.current = true;
1076
- syncTokenToIamStore(newToken);
1077
- }, [setAuth]);
1078
1332
  const handleLogout = useCallback(() => {
1079
1333
  clearAuth();
1080
1334
  hasSynced.current = false;
@@ -1083,7 +1337,7 @@ const AgentTriggersExample = () => {
1083
1337
  });
1084
1338
  }, [clearAuth]);
1085
1339
  if (!token) {
1086
- 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, {}) }));
1087
1341
  }
1088
1342
  return (_jsx(QueryClientProvider, { client: queryClient, children: _jsx(ThemedProvider, { children: _jsx(AgentTriggerInner, { onLogout: handleLogout }) }) }));
1089
1343
  };