@parhelia/core 0.1.12485 → 0.1.12496

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 (207) hide show
  1. package/dist/agents-view/AgentCard.js +2 -1
  2. package/dist/agents-view/AgentCard.js.map +1 -1
  3. package/dist/agents-view/AgentsView.js +2 -1
  4. package/dist/agents-view/AgentsView.js.map +1 -1
  5. package/dist/agents-view/AgentsWorkspaceView.js +12 -5
  6. package/dist/agents-view/AgentsWorkspaceView.js.map +1 -1
  7. package/dist/agents-view/ProfileAgentsGroup.js +2 -1
  8. package/dist/agents-view/ProfileAgentsGroup.js.map +1 -1
  9. package/dist/components/ui/input.js +1 -1
  10. package/dist/components/ui/input.js.map +1 -1
  11. package/dist/components/ui/tabs.d.ts +1 -1
  12. package/dist/components/ui/tabs.js +4 -11
  13. package/dist/components/ui/tabs.js.map +1 -1
  14. package/dist/config/config.js +33 -8
  15. package/dist/config/config.js.map +1 -1
  16. package/dist/config/types.d.ts +7 -0
  17. package/dist/config/types.js.map +1 -1
  18. package/dist/editor/FieldHistory.js +49 -31
  19. package/dist/editor/FieldHistory.js.map +1 -1
  20. package/dist/editor/ai/AgentDocumentList.js +32 -14
  21. package/dist/editor/ai/AgentDocumentList.js.map +1 -1
  22. package/dist/editor/ai/AgentGreeting.js +3 -2
  23. package/dist/editor/ai/AgentGreeting.js.map +1 -1
  24. package/dist/editor/ai/AgentProfileSelector.js +2 -1
  25. package/dist/editor/ai/AgentProfileSelector.js.map +1 -1
  26. package/dist/editor/ai/AgentTerminal.js +544 -169
  27. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  28. package/dist/editor/ai/Agents.js +26 -26
  29. package/dist/editor/ai/Agents.js.map +1 -1
  30. package/dist/editor/ai/AiResponseMessage.js +3 -4
  31. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  32. package/dist/editor/ai/GuidanceOverlay.js +17 -11
  33. package/dist/editor/ai/GuidanceOverlay.js.map +1 -1
  34. package/dist/editor/ai/InlineAiDialog.js +3 -2
  35. package/dist/editor/ai/InlineAiDialog.js.map +1 -1
  36. package/dist/editor/ai/MediaImage.js +40 -8
  37. package/dist/editor/ai/MediaImage.js.map +1 -1
  38. package/dist/editor/ai/SpawnedAgentsPanel.js +10 -12
  39. package/dist/editor/ai/SpawnedAgentsPanel.js.map +1 -1
  40. package/dist/editor/ai/ToolCallDisplay.js +4 -1
  41. package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
  42. package/dist/editor/ai/agentDiagnostics.js +1 -3
  43. package/dist/editor/ai/agentDiagnostics.js.map +1 -1
  44. package/dist/editor/ai/dialogs/AgentDialogHandler.d.ts +1 -8
  45. package/dist/editor/ai/dialogs/AgentDialogHandler.js +89 -12
  46. package/dist/editor/ai/dialogs/AgentDialogHandler.js.map +1 -1
  47. package/dist/editor/ai/dialogs/agentDialogTypes.d.ts +62 -0
  48. package/dist/editor/ai/dialogs/agentDialogTypes.js +2 -0
  49. package/dist/editor/ai/dialogs/agentDialogTypes.js.map +1 -1
  50. package/dist/editor/ai/dialogs/browserBoundCapture.d.ts +29 -0
  51. package/dist/editor/ai/dialogs/browserBoundCapture.js +117 -0
  52. package/dist/editor/ai/dialogs/browserBoundCapture.js.map +1 -0
  53. package/dist/editor/ai/dialogs/capturePageDom.d.ts +3 -0
  54. package/dist/editor/ai/dialogs/capturePageDom.js +64 -0
  55. package/dist/editor/ai/dialogs/capturePageDom.js.map +1 -0
  56. package/dist/editor/ai/dialogs/capturePageScreenshot.d.ts +3 -0
  57. package/dist/editor/ai/dialogs/capturePageScreenshot.js +446 -0
  58. package/dist/editor/ai/dialogs/capturePageScreenshot.js.map +1 -0
  59. package/dist/editor/ai/useAgentStatus.js +11 -0
  60. package/dist/editor/ai/useAgentStatus.js.map +1 -1
  61. package/dist/editor/client/EditorShell.js +39 -41
  62. package/dist/editor/client/EditorShell.js.map +1 -1
  63. package/dist/editor/client/editContext.d.ts +5 -1
  64. package/dist/editor/client/editContext.js.map +1 -1
  65. package/dist/editor/client/hooks/useEditorWebSocket.js +56 -44
  66. package/dist/editor/client/hooks/useEditorWebSocket.js.map +1 -1
  67. package/dist/editor/client/itemsRepository.js +10 -6
  68. package/dist/editor/client/itemsRepository.js.map +1 -1
  69. package/dist/editor/client/pageModelBuilder.js +1 -1
  70. package/dist/editor/client/pageModelBuilder.js.map +1 -1
  71. package/dist/editor/commands/componentCommands.js +3 -49
  72. package/dist/editor/commands/componentCommands.js.map +1 -1
  73. package/dist/editor/commands/customCommandConverter.js +2 -1
  74. package/dist/editor/commands/customCommandConverter.js.map +1 -1
  75. package/dist/editor/commands/handlers/agentHandler.js +2 -1
  76. package/dist/editor/commands/handlers/agentHandler.js.map +1 -1
  77. package/dist/editor/page-editor-chrome/FrameMenu.js +1 -1
  78. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  79. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +11 -11
  80. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
  81. package/dist/editor/page-viewer/PageViewerFrame.js +21 -8
  82. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  83. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js +107 -49
  84. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js.map +1 -1
  85. package/dist/editor/page-viewer/pageViewContext.d.ts +1 -0
  86. package/dist/editor/page-viewer/pageViewContext.js +51 -14
  87. package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
  88. package/dist/editor/reviews/CreateReviewDialog.js +1 -1
  89. package/dist/editor/reviews/CreateReviewDialog.js.map +1 -1
  90. package/dist/editor/reviews/DiffView.js +7 -14
  91. package/dist/editor/reviews/DiffView.js.map +1 -1
  92. package/dist/editor/reviews/useMultiReview.js +2 -2
  93. package/dist/editor/reviews/useMultiReview.js.map +1 -1
  94. package/dist/editor/services/agentService.d.ts +31 -1
  95. package/dist/editor/services/agentService.js +107 -72
  96. package/dist/editor/services/agentService.js.map +1 -1
  97. package/dist/editor/services/aiService.d.ts +3 -1
  98. package/dist/editor/services/aiService.js.map +1 -1
  99. package/dist/editor/services/indexService.js +1 -1
  100. package/dist/editor/services/indexService.js.map +1 -1
  101. package/dist/editor/settings/SettingsView.js +18 -16
  102. package/dist/editor/settings/SettingsView.js.map +1 -1
  103. package/dist/editor/settings/Status.js +5 -4
  104. package/dist/editor/settings/Status.js.map +1 -1
  105. package/dist/editor/settings/index/useIndexStatus.js +19 -21
  106. package/dist/editor/settings/index/useIndexStatus.js.map +1 -1
  107. package/dist/editor/settings/panels/CreateJavaScriptToolDialog.d.ts +7 -0
  108. package/dist/editor/settings/panels/CreateJavaScriptToolDialog.js +48 -0
  109. package/dist/editor/settings/panels/CreateJavaScriptToolDialog.js.map +1 -0
  110. package/dist/editor/settings/panels/GroupedFieldConfigPanel.d.ts +2 -1
  111. package/dist/editor/settings/panels/GroupedFieldConfigPanel.js +2 -2
  112. package/dist/editor/settings/panels/GroupedFieldConfigPanel.js.map +1 -1
  113. package/dist/editor/settings/panels/JavaScriptToolAgentPanel.d.ts +12 -0
  114. package/dist/editor/settings/panels/JavaScriptToolAgentPanel.js +46 -0
  115. package/dist/editor/settings/panels/JavaScriptToolAgentPanel.js.map +1 -0
  116. package/dist/editor/settings/panels/JavaScriptToolConfigPanel.d.ts +9 -0
  117. package/dist/editor/settings/panels/JavaScriptToolConfigPanel.js +34 -0
  118. package/dist/editor/settings/panels/JavaScriptToolConfigPanel.js.map +1 -0
  119. package/dist/editor/settings/panels/JavaScriptToolsPanel.d.ts +2 -0
  120. package/dist/editor/settings/panels/JavaScriptToolsPanel.js +285 -0
  121. package/dist/editor/settings/panels/JavaScriptToolsPanel.js.map +1 -0
  122. package/dist/editor/settings/panels/ModelConfigPanel.d.ts +2 -1
  123. package/dist/editor/settings/panels/ModelConfigPanel.js +88 -7
  124. package/dist/editor/settings/panels/ModelConfigPanel.js.map +1 -1
  125. package/dist/editor/settings/panels/ModelsPanel.js +129 -70
  126. package/dist/editor/settings/panels/ModelsPanel.js.map +1 -1
  127. package/dist/editor/settings/panels/ProjectTemplateAgentPanel.d.ts +1 -4
  128. package/dist/editor/settings/panels/ProjectTemplateAgentPanel.js +3 -3
  129. package/dist/editor/settings/panels/ProjectTemplateAgentPanel.js.map +1 -1
  130. package/dist/editor/settings/panels/ProjectTemplatesPanel.js +78 -22
  131. package/dist/editor/settings/panels/ProjectTemplatesPanel.js.map +1 -1
  132. package/dist/editor/settings/panels/ProvidersPanel.d.ts +1 -1
  133. package/dist/editor/settings/panels/ProvidersPanel.js +40 -55
  134. package/dist/editor/settings/panels/ProvidersPanel.js.map +1 -1
  135. package/dist/editor/settings/panels/index.d.ts +1 -0
  136. package/dist/editor/settings/panels/index.js +1 -0
  137. package/dist/editor/settings/panels/index.js.map +1 -1
  138. package/dist/editor/settings/status/coreStatusChecks.js +28 -17
  139. package/dist/editor/settings/status/coreStatusChecks.js.map +1 -1
  140. package/dist/editor/sidebar/ComponentPalette.js +2 -1
  141. package/dist/editor/sidebar/ComponentPalette.js.map +1 -1
  142. package/dist/editor/sidebar/ComponentTree.js +9 -9
  143. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  144. package/dist/editor/sidebar/EditHistory.js +3 -38
  145. package/dist/editor/sidebar/EditHistory.js.map +1 -1
  146. package/dist/editor/sidebar/MainContentTree.js +1 -1
  147. package/dist/editor/sidebar/MainContentTree.js.map +1 -1
  148. package/dist/editor/sidebar/NavigationPanelItem.js +2 -5
  149. package/dist/editor/sidebar/NavigationPanelItem.js.map +1 -1
  150. package/dist/editor/tree-indicators/GutterColumns.d.ts +3 -1
  151. package/dist/editor/tree-indicators/GutterColumns.js +4 -3
  152. package/dist/editor/tree-indicators/GutterColumns.js.map +1 -1
  153. package/dist/editor/tree-indicators/types.d.ts +1 -0
  154. package/dist/editor/views/CompareView.js +3 -1
  155. package/dist/editor/views/CompareView.js.map +1 -1
  156. package/dist/editor/views/EditorSlot.js +2 -2
  157. package/dist/editor/views/EditorSlot.js.map +1 -1
  158. package/dist/editor/views/ParheliaView.js +5 -6
  159. package/dist/editor/views/ParheliaView.js.map +1 -1
  160. package/dist/editor/views/SingleEditView.js +2 -0
  161. package/dist/editor/views/SingleEditView.js.map +1 -1
  162. package/dist/editor/views/editorSlotContext.js +35 -6
  163. package/dist/editor/views/editorSlotContext.js.map +1 -1
  164. package/dist/index.d.ts +4 -1
  165. package/dist/index.js +1 -0
  166. package/dist/index.js.map +1 -1
  167. package/dist/lib/sanitize.d.ts +10 -0
  168. package/dist/lib/sanitize.js +40 -0
  169. package/dist/lib/sanitize.js.map +1 -0
  170. package/dist/revision.d.ts +2 -2
  171. package/dist/revision.js +2 -2
  172. package/dist/setup/services/setupWizardService.d.ts +28 -0
  173. package/dist/setup/services/setupWizardService.js +34 -0
  174. package/dist/setup/services/setupWizardService.js.map +1 -1
  175. package/dist/setup/wizard/steps/AddModelDialog.js +9 -1
  176. package/dist/setup/wizard/steps/AddModelDialog.js.map +1 -1
  177. package/dist/setup/wizard/steps/ImportModelDialog.js +3 -1
  178. package/dist/setup/wizard/steps/ImportModelDialog.js.map +1 -1
  179. package/dist/splash-screen/NewPage.js +24 -24
  180. package/dist/splash-screen/NewPage.js.map +1 -1
  181. package/dist/task-board/TaskBoardWorkspace.js +29 -10
  182. package/dist/task-board/TaskBoardWorkspace.js.map +1 -1
  183. package/dist/task-board/components/ItemCollectionEditorDialog.js +5 -12
  184. package/dist/task-board/components/ItemCollectionEditorDialog.js.map +1 -1
  185. package/dist/task-board/components/ProjectAgentsPanel.js +1 -1
  186. package/dist/task-board/components/ProjectAgentsPanel.js.map +1 -1
  187. package/dist/task-board/components/ProjectOverviewContent.js +2 -2
  188. package/dist/task-board/components/ProjectOverviewContent.js.map +1 -1
  189. package/dist/task-board/components/ProjectPropertiesPanel.js +1 -1
  190. package/dist/task-board/components/ProjectPropertiesPanel.js.map +1 -1
  191. package/dist/task-board/components/TaskDetailPanel.js +1 -1
  192. package/dist/task-board/components/TaskDetailPanel.js.map +1 -1
  193. package/dist/task-board/components/TaskboardPersistentLogPanel.js +3 -1
  194. package/dist/task-board/components/TaskboardPersistentLogPanel.js.map +1 -1
  195. package/dist/task-board/taskAgentLink.js +1 -3
  196. package/dist/task-board/taskAgentLink.js.map +1 -1
  197. package/dist/task-board/views/DependencyGraphView.js +19 -1
  198. package/dist/task-board/views/DependencyGraphView.js.map +1 -1
  199. package/dist/tour/Tour.js +10 -4
  200. package/dist/tour/Tour.js.map +1 -1
  201. package/dist/tour/default-tour.js +51 -11
  202. package/dist/tour/default-tour.js.map +1 -1
  203. package/dist/types.d.ts +2 -0
  204. package/package.json +4 -1
  205. package/dist/editor/ComponentInfo.d.ts +0 -4
  206. package/dist/editor/ComponentInfo.js +0 -41
  207. package/dist/editor/ComponentInfo.js.map +0 -1
@@ -1,7 +1,7 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
2
  import React, { useEffect, useState, useRef, useCallback, useLayoutEffect, useMemo, } from "react";
3
3
  import { Send, AlertCircle, Loader2, User, Wand2, Square, Mic, MicOff, ChevronDown, ChevronUp, ListTodo, ArrowLeft, DollarSign, ExternalLink, Settings2, Target, X, Plus, } from "lucide-react";
4
- import { getAgent, startAgent, updateAgentSettings, updateAgentCostLimit, updateAgentContext, getAgentSkillCatalog, getAgentOperationAllowances, getAgentTriggerSubscriptions, cancelAgent, canonicalizeAgentMetadata, getPendingPrompts, } from "../services/agentService";
4
+ import { getAgent, startAgent, claimAgentBrowser, updateAgentSettings, updateAgentCostLimit, updateAgentContext, getAgentSkillCatalog, getAgentAvailableTools, getAgentOperationAllowances, getAgentTriggerSubscriptions, cancelAgent, canonicalizeAgentMetadata, getPendingPrompts, releaseAgentBrowser, } from "../services/agentService";
5
5
  import { useEditContext, useFieldsEditContext } from "../client/editContext";
6
6
  import { localStorageService } from "../services/localStorageService";
7
7
  import { Textarea } from "../../components/ui/textarea";
@@ -16,10 +16,13 @@ import { getComponentById } from "../componentTreeHelper";
16
16
  import { AgentGreeting } from "./AgentGreeting";
17
17
  import { getAgentHistory } from "../services/editService";
18
18
  import { QuestionnaireInline } from "./dialogs/QuestionnaireInline";
19
+ import { getBrowserCaptureClaim, setBrowserCaptureClaim, } from "./dialogs/browserBoundCapture";
20
+ import { DIALOG_TYPES, } from "./dialogs/agentDialogTypes";
19
21
  import { Popover, PopoverContent, PopoverTrigger, } from "../../components/ui/popover";
20
22
  import { SecretAgentIcon } from "../ui/Icons";
21
23
  import { formatTime, formatDateTime } from "../utils";
22
24
  import { cn } from "../../lib/utils";
25
+ import { sanitizeSvg } from "../../lib/sanitize";
23
26
  import { Select } from "../../components/ui/select";
24
27
  import { AgentTerminalStatusBar } from "./AgentTerminalStatusBar";
25
28
  import { SimpleTabs } from "../ui/SimpleTabs";
@@ -75,23 +78,19 @@ function formatAllowanceLabel(allowance) {
75
78
  : ` ${allowance.normalizedPath}`}`;
76
79
  }
77
80
  function getAgentRunMessageAgentId(payload) {
78
- const agentId = payload?.agentId || payload?.AgentId;
81
+ const agentId = payload?.agentId;
79
82
  return typeof agentId === "string" ? normalizeDialogAgentId(agentId) : null;
80
83
  }
81
84
  function getAgentRunMessageSeq(payload) {
82
- const seq = payload?.seq ?? payload?.Seq;
85
+ const seq = payload?.seq;
83
86
  return typeof seq === "number" ? seq : null;
84
87
  }
85
88
  function getAgentRunMessageDetail(type, payload) {
86
89
  if (type === "agent:run:delta") {
87
- return payload?.type || payload?.Type || null;
90
+ return payload?.type || null;
88
91
  }
89
92
  if (type === "agent:run:status") {
90
- return (payload?.data?.state ||
91
- payload?.data?.State ||
92
- payload?.data?.status ||
93
- payload?.data?.Status ||
94
- null);
93
+ return payload?.data?.state || payload?.data?.status || null;
95
94
  }
96
95
  if (type === "agent:run:error") {
97
96
  return payload?.error || null;
@@ -104,6 +103,19 @@ function getAgentRunMessageDetail(type, payload) {
104
103
  }
105
104
  return null;
106
105
  }
106
+ function isHeartbeatRunEventMessage(message) {
107
+ if (!message)
108
+ return false;
109
+ if (message.type === "agent:run:delta") {
110
+ const deltaType = message.payload?.type;
111
+ return String(deltaType || "").toLowerCase() === "heartbeat";
112
+ }
113
+ if (message.type === "agent:run:status") {
114
+ const state = message.payload?.data?.state || message.payload?.data?.status;
115
+ return String(state || "").toLowerCase() === "heartbeat";
116
+ }
117
+ return false;
118
+ }
107
119
  function getVisibleDialogRegistry() {
108
120
  const registry = globalThis.__agentDialogVisibleCallbacks;
109
121
  return registry && typeof registry === "object" ? registry : {};
@@ -119,6 +131,38 @@ function decodeHtmlEntities(value) {
119
131
  function toUserFacingAgentErrorMessage(value) {
120
132
  if (!value)
121
133
  return "";
134
+ const normalizedValue = decodeHtmlEntities(value.replace(/<br\s*\/?>/gi, "\n")).trim();
135
+ if (!normalizedValue)
136
+ return "";
137
+ const trimmed = normalizedValue.trim();
138
+ const maybeJson = trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed.startsWith('"');
139
+ if (maybeJson) {
140
+ try {
141
+ const parsed = JSON.parse(trimmed);
142
+ const structuredMessage = typeof parsed === "string"
143
+ ? parsed
144
+ : typeof parsed?.error === "string"
145
+ ? parsed.error
146
+ : typeof parsed?.message === "string"
147
+ ? parsed.message
148
+ : typeof parsed?.detail === "string"
149
+ ? parsed.detail
150
+ : typeof parsed?.error_description === "string"
151
+ ? parsed.error_description
152
+ : typeof parsed?.error === "object" &&
153
+ parsed?.error &&
154
+ typeof parsed.error.message ===
155
+ "string"
156
+ ? String(parsed.error.message)
157
+ : "";
158
+ if (structuredMessage.trim()) {
159
+ value = structuredMessage;
160
+ }
161
+ }
162
+ catch {
163
+ // Fall through to plain-text cleanup when the provider error isn't valid JSON.
164
+ }
165
+ }
122
166
  const firstLine = value
123
167
  .replace(/<br\s*\/?>/gi, "\n")
124
168
  .split(/\r?\n/)
@@ -177,6 +221,9 @@ const UserMessage = ({ message }) => {
177
221
  const displayName = sourceAgentName ? `[From ${sourceAgentName}]` : "You";
178
222
  return (_jsxs("div", { className: "flex gap-3 p-4", children: [_jsx("div", { className: "shrink-0", children: _jsx(User, { className: "text-theme-secondary h-5 w-5", strokeWidth: 1 }) }), _jsxs("div", { className: "min-w-0 flex-1 select-text", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [_jsx("span", { className: "text-[12px] font-medium text-gray-900", children: displayName }), message.createdDate && (_jsx("span", { className: "text-[12px] text-gray-400", "data-testid": "user-message-timestamp", "data-timestamp": message.createdDate, children: formatTime(new Date(message.createdDate)) }))] }), _jsx("div", { className: "prose prose max-w-none text-[12px] text-gray-700 select-text", children: displayContent })] })] }));
179
223
  };
224
+ const HeartbeatMessage = ({ message }) => {
225
+ return (_jsx("div", { className: "px-4 py-2", "data-testid": "agent-heartbeat-message", children: _jsxs("div", { className: "flex items-center gap-2 rounded-md border border-sky-100 bg-sky-50/80 px-3 py-2 text-[11px] text-sky-700", children: [_jsx(Loader2, { className: "h-3.5 w-3.5 animate-spin", strokeWidth: 1.5 }), _jsx("span", { className: "min-w-0 flex-1", children: message.content }), message.createdDate && (_jsx("span", { className: "shrink-0 text-[10px] text-sky-500", children: formatTime(new Date(message.createdDate)) }))] }) }));
226
+ };
180
227
  // Helper to extract todos from potentially incomplete JSON during streaming
181
228
  const extractPartialTodos = (jsonText) => {
182
229
  // First try to parse complete JSON
@@ -549,6 +596,16 @@ const groupConsecutiveMessages = (agentMessages) => {
549
596
  // Add user message
550
597
  groups.push({ type: "user", messages: [message] });
551
598
  }
599
+ else if (message.messageType === "heartbeat" || message.role === "system") {
600
+ if (currentAssistantGroup.length > 0) {
601
+ groups.push({
602
+ type: "assistant-group",
603
+ messages: currentAssistantGroup,
604
+ });
605
+ currentAssistantGroup = [];
606
+ }
607
+ groups.push({ type: "heartbeat", messages: [message] });
608
+ }
552
609
  else if (message.role === "assistant") {
553
610
  // Add to current assistant group
554
611
  currentAssistantGroup.push(message);
@@ -655,10 +712,10 @@ const stringifyToolField = (value) => {
655
712
  const getFirstToolCallEnvelope = (data) => {
656
713
  if (!data || typeof data !== "object")
657
714
  return undefined;
658
- const direct = data.toolCall || data.tool_call || data.ToolCall;
715
+ const direct = data.toolCall || data.tool_call;
659
716
  if (direct)
660
717
  return direct;
661
- const arrayCandidates = [data.tool_calls, data.toolCalls, data.ToolCalls];
718
+ const arrayCandidates = [data.tool_calls, data.toolCalls];
662
719
  for (const candidate of arrayCandidates) {
663
720
  if (Array.isArray(candidate) && candidate.length > 0) {
664
721
  return candidate[0];
@@ -668,27 +725,18 @@ const getFirstToolCallEnvelope = (data) => {
668
725
  };
669
726
  const extractToolCallFields = (data) => {
670
727
  const envelope = getFirstToolCallEnvelope(data);
671
- const functionPayload = data?.function ||
672
- data?.Function ||
673
- envelope?.function ||
674
- envelope?.Function;
728
+ const functionPayload = data?.function || envelope?.function;
675
729
  const functionName = data?.functionName ||
676
730
  data?.name ||
677
731
  functionPayload?.name ||
678
- functionPayload?.Name ||
679
732
  envelope?.functionName ||
680
733
  envelope?.name ||
681
- envelope?.FunctionName ||
682
- envelope?.Name ||
683
734
  "unknown";
684
- const toolCallId = data?.toolCallId || data?.id || envelope?.id || envelope?.Id;
735
+ const toolCallId = data?.toolCallId || data?.id || envelope?.id;
685
736
  const functionArguments = stringifyToolField(data?.functionArguments) ||
686
- stringifyToolField(data?.Arguments) ||
687
737
  stringifyToolField(data?.arguments) ||
688
738
  stringifyToolField(functionPayload?.arguments) ||
689
- stringifyToolField(functionPayload?.Arguments) ||
690
739
  stringifyToolField(envelope?.functionArguments) ||
691
- stringifyToolField(envelope?.Arguments) ||
692
740
  stringifyToolField(envelope?.arguments) ||
693
741
  "{}";
694
742
  return {
@@ -774,6 +822,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
774
822
  const [activePlaceholderInput, setActivePlaceholderInput] = useState(null);
775
823
  const [allPlaceholdersFilled, setAllPlaceholdersFilled] = useState(false);
776
824
  const [agentMetadata, setAgentMetadata] = useState(null);
825
+ const [isBrowserClaimMutationPending, setIsBrowserClaimMutationPending] = useState(false);
826
+ const [pendingBrowserCaptureDialogType, setPendingBrowserCaptureDialogType] = useState(null);
777
827
  // Ensure we always have an agent object for streaming handlers, even before `getAgent()` resolves.
778
828
  // This prevents early tool calls (e.g., ask-questionnaire) from being dropped in compact/workspace UIs.
779
829
  useEffect(() => {
@@ -926,6 +976,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
926
976
  if (!message?.type?.startsWith("agent:run:")) {
927
977
  return false;
928
978
  }
979
+ if (isHeartbeatRunEventMessage(message)) {
980
+ return false;
981
+ }
929
982
  return getAgentRunMessageAgentId(message.payload) === normalizedAgentId;
930
983
  })
931
984
  .slice(-8)
@@ -1029,24 +1082,28 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1029
1082
  const [skillsLoading, setSkillsLoading] = useState(false);
1030
1083
  const [skillsError, setSkillsError] = useState(null);
1031
1084
  const [triggerSubscriptions, setTriggerSubscriptions] = useState([]);
1085
+ const [availableTools, setAvailableTools] = useState([]);
1032
1086
  const [operationAllowances, setOperationAllowances] = useState({
1033
1087
  sitecore: [],
1034
1088
  filesystem: [],
1035
1089
  });
1036
1090
  const [triggerSubscriptionsLoading, setTriggerSubscriptionsLoading] = useState(false);
1091
+ const [availableToolsLoading, setAvailableToolsLoading] = useState(false);
1037
1092
  const [operationAllowancesLoading, setOperationAllowancesLoading] = useState(false);
1038
1093
  const [triggerSubscriptionsError, setTriggerSubscriptionsError] = useState(null);
1094
+ const [availableToolsError, setAvailableToolsError] = useState(null);
1039
1095
  const [operationAllowancesError, setOperationAllowancesError] = useState(null);
1096
+ const [toolsSectionExpanded, setToolsSectionExpanded] = useState(false);
1040
1097
  const [allowancesSectionExpanded, setAllowancesSectionExpanded] = useState(false);
1041
1098
  const [subscribedTriggersSectionExpanded, setSubscribedTriggersSectionExpanded,] = useState(false);
1099
+ const isNewAgent = agent?.status === "new";
1042
1100
  const hasSpawnedAgents = useMemo(() => {
1043
1101
  if (!agentMetadata)
1044
1102
  return false;
1045
- const childAgents = agentMetadata?.ChildAgents ||
1046
- agentMetadata?.childAgents;
1103
+ const childAgents = agentMetadata?.childAgents;
1047
1104
  if (!Array.isArray(childAgents) || childAgents.length === 0)
1048
1105
  return false;
1049
- return childAgents.some((a) => a != null && typeof a === "object" && (a.AgentId || a.agentId));
1106
+ return childAgents.some((a) => a != null && typeof a === "object" && a.agentId);
1050
1107
  }, [agentMetadata]);
1051
1108
  const hasTodoContent = useMemo(() => {
1052
1109
  const metadataTodos = (() => {
@@ -1165,70 +1222,6 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1165
1222
  active = false;
1166
1223
  };
1167
1224
  }, []);
1168
- useEffect(() => {
1169
- let active = true;
1170
- if (!showAgentSettings) {
1171
- return () => {
1172
- active = false;
1173
- };
1174
- }
1175
- if (!agent?.id || agent.status === "new") {
1176
- setTriggerSubscriptions([]);
1177
- setTriggerSubscriptionsLoading(false);
1178
- setTriggerSubscriptionsError(null);
1179
- setOperationAllowances({ sitecore: [], filesystem: [] });
1180
- setOperationAllowancesLoading(false);
1181
- setOperationAllowancesError(null);
1182
- return () => {
1183
- active = false;
1184
- };
1185
- }
1186
- const loadTriggerSubscriptions = async () => {
1187
- try {
1188
- setTriggerSubscriptionsLoading(true);
1189
- setTriggerSubscriptionsError(null);
1190
- const subscriptions = await getAgentTriggerSubscriptions(agent.id);
1191
- if (active) {
1192
- setTriggerSubscriptions(subscriptions);
1193
- }
1194
- }
1195
- catch (e) {
1196
- if (active) {
1197
- setTriggerSubscriptionsError(e?.message || "Failed to load trigger subscriptions");
1198
- }
1199
- }
1200
- finally {
1201
- if (active) {
1202
- setTriggerSubscriptionsLoading(false);
1203
- }
1204
- }
1205
- };
1206
- const loadOperationAllowances = async () => {
1207
- try {
1208
- setOperationAllowancesLoading(true);
1209
- setOperationAllowancesError(null);
1210
- const allowances = await getAgentOperationAllowances(agent.id);
1211
- if (active) {
1212
- setOperationAllowances(allowances);
1213
- }
1214
- }
1215
- catch (e) {
1216
- if (active) {
1217
- setOperationAllowancesError(e?.message || "Failed to load allowances");
1218
- }
1219
- }
1220
- finally {
1221
- if (active) {
1222
- setOperationAllowancesLoading(false);
1223
- }
1224
- }
1225
- };
1226
- void loadTriggerSubscriptions();
1227
- void loadOperationAllowances();
1228
- return () => {
1229
- active = false;
1230
- };
1231
- }, [showAgentSettings, agent?.id, agent?.status]);
1232
1225
  const modeOptions = useMemo(() => [
1233
1226
  {
1234
1227
  value: "supervised",
@@ -1265,9 +1258,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1265
1258
  })) || []).sort((a, b) => a.label.localeCompare(b.label)), [activeProfile]);
1266
1259
  const metadataSelectedSkillIds = useMemo(() => {
1267
1260
  const rawSkillIds = agentMetadata?.additionalData?.skillIds ??
1268
- agentMetadata?.AdditionalData?.skillIds ??
1269
1261
  agentMetadata?.skillIds ??
1270
- agentMetadata?.SkillIds ??
1271
1262
  [];
1272
1263
  if (!Array.isArray(rawSkillIds)) {
1273
1264
  return [];
@@ -1277,9 +1268,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1277
1268
  .filter((x) => x.length > 0);
1278
1269
  }, [agentMetadata]);
1279
1270
  const backendAssignedSkillIds = useMemo(() => {
1280
- const rawSkillIds = agent?.assignedSkillIds ??
1281
- agent?.AssignedSkillIds ??
1282
- [];
1271
+ const rawSkillIds = agent?.assignedSkillIds ?? [];
1283
1272
  if (!Array.isArray(rawSkillIds)) {
1284
1273
  return [];
1285
1274
  }
@@ -1287,8 +1276,32 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1287
1276
  .map((x) => String(x || "").trim())
1288
1277
  .filter((x) => x.length > 0);
1289
1278
  }, [agent]);
1279
+ const preloadedSkillIds = useMemo(() => {
1280
+ const rawSkillIds = activeProfile?.preloadSkills ?? [];
1281
+ if (!Array.isArray(rawSkillIds)) {
1282
+ return [];
1283
+ }
1284
+ return rawSkillIds
1285
+ .map((skill) => String(skill?.id || "").trim())
1286
+ .filter((id) => id.length > 0);
1287
+ }, [activeProfile?.preloadSkills]);
1288
+ const autoAssignedSkillIds = useMemo(() => {
1289
+ const all = isNewAgent
1290
+ ? [...preloadedSkillIds, ...backendAssignedSkillIds]
1291
+ : backendAssignedSkillIds;
1292
+ const seen = new Set();
1293
+ const unique = [];
1294
+ for (const id of all) {
1295
+ const key = id.toLowerCase();
1296
+ if (seen.has(key))
1297
+ continue;
1298
+ seen.add(key);
1299
+ unique.push(id);
1300
+ }
1301
+ return unique;
1302
+ }, [backendAssignedSkillIds, isNewAgent, preloadedSkillIds]);
1290
1303
  const selectedSkillIds = useMemo(() => {
1291
- const all = [...backendAssignedSkillIds, ...metadataSelectedSkillIds];
1304
+ const all = [...autoAssignedSkillIds, ...metadataSelectedSkillIds];
1292
1305
  const seen = new Set();
1293
1306
  const unique = [];
1294
1307
  for (const id of all) {
@@ -1299,7 +1312,102 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1299
1312
  unique.push(id);
1300
1313
  }
1301
1314
  return unique;
1302
- }, [backendAssignedSkillIds, metadataSelectedSkillIds]);
1315
+ }, [autoAssignedSkillIds, metadataSelectedSkillIds]);
1316
+ useEffect(() => {
1317
+ let active = true;
1318
+ if (!showAgentSettings) {
1319
+ return () => {
1320
+ active = false;
1321
+ };
1322
+ }
1323
+ if (!agent?.id || agent.status === "new") {
1324
+ setTriggerSubscriptions([]);
1325
+ setTriggerSubscriptionsLoading(false);
1326
+ setTriggerSubscriptionsError(null);
1327
+ setAvailableTools([]);
1328
+ setAvailableToolsLoading(false);
1329
+ setAvailableToolsError(null);
1330
+ setOperationAllowances({ sitecore: [], filesystem: [] });
1331
+ setOperationAllowancesLoading(false);
1332
+ setOperationAllowancesError(null);
1333
+ return () => {
1334
+ active = false;
1335
+ };
1336
+ }
1337
+ const loadTriggerSubscriptions = async () => {
1338
+ try {
1339
+ setTriggerSubscriptionsLoading(true);
1340
+ setTriggerSubscriptionsError(null);
1341
+ const subscriptions = await getAgentTriggerSubscriptions(agent.id);
1342
+ if (active) {
1343
+ setTriggerSubscriptions(subscriptions);
1344
+ }
1345
+ }
1346
+ catch (e) {
1347
+ if (active) {
1348
+ setTriggerSubscriptionsError(e?.message || "Failed to load trigger subscriptions");
1349
+ }
1350
+ }
1351
+ finally {
1352
+ if (active) {
1353
+ setTriggerSubscriptionsLoading(false);
1354
+ }
1355
+ }
1356
+ };
1357
+ const loadOperationAllowances = async () => {
1358
+ try {
1359
+ setOperationAllowancesLoading(true);
1360
+ setOperationAllowancesError(null);
1361
+ const allowances = await getAgentOperationAllowances(agent.id);
1362
+ if (active) {
1363
+ setOperationAllowances(allowances);
1364
+ }
1365
+ }
1366
+ catch (e) {
1367
+ if (active) {
1368
+ setOperationAllowancesError(e?.message || "Failed to load allowances");
1369
+ }
1370
+ }
1371
+ finally {
1372
+ if (active) {
1373
+ setOperationAllowancesLoading(false);
1374
+ }
1375
+ }
1376
+ };
1377
+ const loadAvailableTools = async () => {
1378
+ try {
1379
+ setAvailableToolsLoading(true);
1380
+ setAvailableToolsError(null);
1381
+ const tools = await getAgentAvailableTools(agent.id);
1382
+ if (active) {
1383
+ setAvailableTools(tools);
1384
+ }
1385
+ }
1386
+ catch (e) {
1387
+ if (active) {
1388
+ setAvailableToolsError(e?.message || "Failed to load available tools");
1389
+ }
1390
+ }
1391
+ finally {
1392
+ if (active) {
1393
+ setAvailableToolsLoading(false);
1394
+ }
1395
+ }
1396
+ };
1397
+ void loadTriggerSubscriptions();
1398
+ void loadAvailableTools();
1399
+ void loadOperationAllowances();
1400
+ return () => {
1401
+ active = false;
1402
+ };
1403
+ }, [
1404
+ showAgentSettings,
1405
+ agent?.id,
1406
+ agent?.status,
1407
+ agent?.profileId,
1408
+ mode,
1409
+ selectedSkillIds,
1410
+ ]);
1303
1411
  const activeTriggerSubscriptions = useMemo(() => triggerSubscriptions.filter((sub) => sub.isActive), [triggerSubscriptions]);
1304
1412
  const allowanceGroups = useMemo(() => [
1305
1413
  {
@@ -1317,18 +1425,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1317
1425
  operationAllowances.filesystem.length > 0, [operationAllowances]);
1318
1426
  const allowancesTotalCount = useMemo(() => operationAllowances.sitecore.length +
1319
1427
  operationAllowances.filesystem.length, [operationAllowances]);
1320
- const allowedProfileSkillIdSet = useMemo(() => {
1321
- const ids = activeProfile?.allowedSkills
1322
- ?.map((skill) => String(skill?.id || "").toLowerCase())
1323
- .filter((id) => id.length > 0) || [];
1428
+ const listedProfileSkillIdSet = useMemo(() => {
1429
+ const ids = [
1430
+ ...(activeProfile?.availableSkills ?? []),
1431
+ ...(activeProfile?.allowedSkills ?? []),
1432
+ ]
1433
+ .map((skill) => String(skill?.id || "").toLowerCase())
1434
+ .filter((id) => id.length > 0);
1324
1435
  return new Set(ids);
1325
- }, [activeProfile?.allowedSkills]);
1436
+ }, [activeProfile?.availableSkills, activeProfile?.allowedSkills]);
1326
1437
  const profileFilteredSkills = useMemo(() => {
1327
- if (allowedProfileSkillIdSet.size === 0) {
1328
- return availableSkills;
1438
+ if (listedProfileSkillIdSet.size === 0) {
1439
+ return [];
1329
1440
  }
1330
- return availableSkills.filter((skill) => allowedProfileSkillIdSet.has(skill.id.toLowerCase()));
1331
- }, [availableSkills, allowedProfileSkillIdSet]);
1441
+ return availableSkills.filter((skill) => listedProfileSkillIdSet.has(skill.id.toLowerCase()));
1442
+ }, [availableSkills, listedProfileSkillIdSet]);
1332
1443
  const profileFilteredSkillIdSet = useMemo(() => new Set(profileFilteredSkills.map((s) => s.id.toLowerCase())), [profileFilteredSkills]);
1333
1444
  const availableSkillIdSet = useMemo(() => new Set(availableSkills.map((skill) => skill.id.toLowerCase())), [availableSkills]);
1334
1445
  const selectableTemplateIdSet = useMemo(() => new Set(selectableTemplateIds.map((id) => id.toLowerCase())), [selectableTemplateIds]);
@@ -1336,7 +1447,40 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1336
1447
  .map((id) => availableSkills.find((s) => s.id.toLowerCase() === id.toLowerCase()))
1337
1448
  .filter((s) => !!s), [availableSkills, selectedSkillIds]);
1338
1449
  const selectedSkillSet = useMemo(() => new Set(selectedSkillIds.map((id) => id.toLowerCase())), [selectedSkillIds]);
1339
- const backendAssignedSkillSet = useMemo(() => new Set(backendAssignedSkillIds.map((id) => id.toLowerCase())), [backendAssignedSkillIds]);
1450
+ const autoAssignedSkillSet = useMemo(() => new Set(autoAssignedSkillIds.map((id) => id.toLowerCase())), [autoAssignedSkillIds]);
1451
+ const previewAvailableTools = useMemo(() => {
1452
+ const baseToolNames = mode === "read-only"
1453
+ ? activeProfile?.readOnlyToolNames ?? []
1454
+ : activeProfile?.allowedToolNames ?? [];
1455
+ const toolNames = new Set();
1456
+ for (const toolName of baseToolNames) {
1457
+ const normalized = String(toolName || "").trim();
1458
+ if (normalized) {
1459
+ toolNames.add(normalized);
1460
+ }
1461
+ }
1462
+ for (const skill of selectedSkills) {
1463
+ for (const toolName of skill.additionalAllowedTools ?? []) {
1464
+ const normalized = String(toolName || "").trim();
1465
+ if (normalized) {
1466
+ toolNames.add(normalized);
1467
+ }
1468
+ }
1469
+ }
1470
+ return Array.from(toolNames).sort((a, b) => a.localeCompare(b));
1471
+ }, [
1472
+ activeProfile?.allowedToolNames,
1473
+ activeProfile?.readOnlyToolNames,
1474
+ mode,
1475
+ selectedSkills,
1476
+ ]);
1477
+ const displayedAvailableTools = isNewAgent
1478
+ ? previewAvailableTools
1479
+ : availableTools;
1480
+ const displayedAvailableToolsLoading = isNewAgent
1481
+ ? false
1482
+ : availableToolsLoading;
1483
+ const displayedAvailableToolsError = isNewAgent ? null : availableToolsError;
1340
1484
  // Remove deprecated cost limit fields from metadata to avoid confusion with agent/profile settings
1341
1485
  const sanitizeAgentMetadata = useCallback((meta) => {
1342
1486
  try {
@@ -1365,9 +1509,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1365
1509
  if (!agent?.id)
1366
1510
  return;
1367
1511
  const current = agentMetadata || {};
1368
- const currentAdditionalData = current.additionalData ||
1369
- current.AdditionalData ||
1370
- {};
1512
+ const currentAdditionalData = current.additionalData || {};
1371
1513
  const nextAdditionalData = {
1372
1514
  ...currentAdditionalData,
1373
1515
  };
@@ -1382,11 +1524,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1382
1524
  };
1383
1525
  if (Object.keys(nextAdditionalData).length > 0) {
1384
1526
  next.additionalData = nextAdditionalData;
1385
- delete next.AdditionalData;
1386
1527
  }
1387
1528
  else {
1388
1529
  delete next.additionalData;
1389
- delete next.AdditionalData;
1390
1530
  }
1391
1531
  try {
1392
1532
  if (agent.status === "new") {
@@ -1805,6 +1945,55 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1805
1945
  toolCalls: [],
1806
1946
  };
1807
1947
  }, [agent, agentStub.id]);
1948
+ const stripHeartbeatMessages = useCallback((currentMessages) => currentMessages.filter((message) => message.messageType !== "heartbeat"), []);
1949
+ const handleHeartbeatMessage = useCallback((message) => {
1950
+ const heartbeatText = (message.data?.message ||
1951
+ message.data?.Message ||
1952
+ "Still working on your request. This is taking a little longer than usual.").trim() ||
1953
+ "Still working on your request. This is taking a little longer than usual.";
1954
+ const createdDate = message.timestamp || new Date().toISOString();
1955
+ const heartbeatId = `heartbeat-${agent?.id || agentStub.id}`;
1956
+ setMessages((prev) => {
1957
+ const withoutHeartbeats = stripHeartbeatMessages(prev);
1958
+ const updated = [
1959
+ ...withoutHeartbeats,
1960
+ {
1961
+ id: heartbeatId,
1962
+ agentId: agent?.id || agentStub.id,
1963
+ messageIndex: -1,
1964
+ role: "system",
1965
+ content: heartbeatText,
1966
+ name: "system",
1967
+ messageType: "heartbeat",
1968
+ isCompleted: true,
1969
+ model: "",
1970
+ tokensUsed: 0,
1971
+ inputTokens: 0,
1972
+ outputTokens: 0,
1973
+ cachedInputTokens: 0,
1974
+ inputTokenCost: 0,
1975
+ outputTokenCost: 0,
1976
+ cachedInputTokenCost: 0,
1977
+ totalCost: 0,
1978
+ currency: "USD",
1979
+ createdDate,
1980
+ toolCalls: [],
1981
+ },
1982
+ ];
1983
+ messagesRef.current = updated;
1984
+ return updated;
1985
+ });
1986
+ }, [agent?.id, agentStub.id, stripHeartbeatMessages]);
1987
+ const clearHeartbeatMessages = useCallback(() => {
1988
+ setMessages((prev) => {
1989
+ const updated = stripHeartbeatMessages(prev);
1990
+ if (updated.length === prev.length) {
1991
+ return prev;
1992
+ }
1993
+ messagesRef.current = updated;
1994
+ return updated;
1995
+ });
1996
+ }, [stripHeartbeatMessages]);
1808
1997
  const handleContentChunk = useCallback((message, agentData) => {
1809
1998
  // Get messageId from data, or generate one from agent ID (for backward compatibility)
1810
1999
  // If no messageId is provided, we'll use the last assistant message or create a new one
@@ -2823,9 +3012,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2823
3012
  // Handle agent:profile:switched (profile changed via switch-profile function)
2824
3013
  if (messageType === "agent:profile:switched") {
2825
3014
  const payload = message.payload || {};
2826
- const switchedAgentId = payload.agentId || payload.AgentId;
2827
- const newProfileId = payload.newProfileId || payload.NewProfileId;
2828
- const newProfileName = payload.newProfileName || payload.NewProfileName;
3015
+ const switchedAgentId = payload.agentId;
3016
+ const newProfileId = payload.newProfileId;
3017
+ const newProfileName = payload.newProfileName;
2829
3018
  if (switchedAgentId === agent.id && newProfileId) {
2830
3019
  // Update the agent's profile
2831
3020
  setAgent((prev) => {
@@ -2866,7 +3055,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2866
3055
  return;
2867
3056
  }
2868
3057
  // For other agent messages, check if this is for our agent
2869
- const agentId = message.payload?.agentId || message.payload?.AgentId;
3058
+ const agentId = message.payload?.agentId;
2870
3059
  if (agentId !== agent.id) {
2871
3060
  return;
2872
3061
  }
@@ -3008,15 +3197,25 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3008
3197
  }
3009
3198
  // Always allow ContextUpdate messages (metadata updates) regardless of agent status
3010
3199
  const isContextUpdate = type === "ContextUpdate" || type === "contextUpdate";
3200
+ const isHeartbeat = type === "Heartbeat" || type === "heartbeat";
3201
+ const shouldClearHeartbeat = type === "ContentChunk" ||
3202
+ type === "contentChunk" ||
3203
+ type === "ToolCall" ||
3204
+ type === "toolCall" ||
3205
+ type === "ToolResult" ||
3206
+ type === "toolResult";
3011
3207
  // Allow deltas if the agent is running OR if we already have an active streaming message
3012
3208
  // OR if the server provided a messageId for targeted updates.
3013
3209
  // This avoids dropping early deltas that may arrive before the 'running' status update.
3014
3210
  // ContextUpdate messages are always allowed as they're metadata updates, not content.
3015
3211
  const isRunning = agent.status === "running" || agent.status === 1;
3016
3212
  const hasStreaming = messagesRef.current.some((m) => !m.isCompleted && m.messageType === "streaming");
3017
- const hasMessageId = !!message?.payload?.data?.messageId ||
3018
- !!message?.payload?.data?.MessageId;
3019
- if (!isContextUpdate && !isRunning && !hasStreaming && !hasMessageId) {
3213
+ const hasMessageId = !!message?.payload?.data?.messageId;
3214
+ if (!isContextUpdate &&
3215
+ !isHeartbeat &&
3216
+ !isRunning &&
3217
+ !hasStreaming &&
3218
+ !hasMessageId) {
3020
3219
  return; // ignore only if we cannot safely target an existing streaming message
3021
3220
  }
3022
3221
  // Deduplicate by sequence
@@ -3033,6 +3232,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3033
3232
  cost,
3034
3233
  timestamp: new Date().toISOString(),
3035
3234
  };
3235
+ if (shouldClearHeartbeat) {
3236
+ clearHeartbeatMessages();
3237
+ }
3036
3238
  if (type === "ContentChunk" || type === "contentChunk") {
3037
3239
  handleContentChunk(agentStreamMessage, agent);
3038
3240
  }
@@ -3042,6 +3244,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3042
3244
  else if (type === "ToolResult" || type === "toolResult") {
3043
3245
  handleToolResult(agentStreamMessage, agent);
3044
3246
  }
3247
+ else if (type === "Heartbeat" || type === "heartbeat") {
3248
+ handleHeartbeatMessage(agentStreamMessage);
3249
+ }
3045
3250
  else if (type === "ContextUpdate" || type === "contextUpdate") {
3046
3251
  // Handle context updates from streaming - data contains additionalData.todoList and ChildAgents
3047
3252
  const contextData = data;
@@ -3051,10 +3256,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3051
3256
  const current = (prev || {});
3052
3257
  const updated = { ...current };
3053
3258
  // Merge additionalData if present (deep merge to preserve existing values)
3054
- if (contextData.additionalData || contextData.AdditionalData) {
3055
- const sourceAdditionalData = contextData.additionalData ||
3056
- contextData.AdditionalData ||
3057
- {};
3259
+ if (contextData.additionalData) {
3260
+ const sourceAdditionalData = contextData.additionalData || {};
3058
3261
  updated.additionalData = {
3059
3262
  ...(current.additionalData || {}),
3060
3263
  ...sourceAdditionalData,
@@ -3062,10 +3265,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3062
3265
  }
3063
3266
  // Merge ChildAgents if present (for spawned agents) - replace entire array
3064
3267
  // Backend sends the complete updated list, not just additions
3065
- if (contextData.ChildAgents || contextData.childAgents) {
3066
- const childAgents = contextData.ChildAgents || contextData.childAgents;
3268
+ if (contextData.childAgents) {
3269
+ const childAgents = contextData.childAgents;
3067
3270
  if (Array.isArray(childAgents)) {
3068
- updated.ChildAgents = childAgents;
3271
+ updated.childAgents = childAgents;
3069
3272
  }
3070
3273
  }
3071
3274
  return updated;
@@ -3092,13 +3295,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3092
3295
  try {
3093
3296
  // Normalize various status shapes and handle Cancelled uniformly
3094
3297
  const normalizedStatus = statusData?.state ||
3095
- statusData?.Status ||
3096
3298
  statusData?.status;
3097
3299
  if (normalizedStatus === "Cancelled" ||
3098
3300
  normalizedStatus === "canceled" ||
3099
3301
  normalizedStatus === "stopped" ||
3100
3302
  normalizedStatus === "Stopped") {
3101
3303
  // Stop indicators and mark any in-progress streaming messages as completed
3304
+ clearHeartbeatMessages();
3102
3305
  setAgent((prev) => (prev ? { ...prev, status: "idle" } : prev));
3103
3306
  setIsWaitingForResponse(false);
3104
3307
  isWaitingRef.current = false;
@@ -3201,6 +3404,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3201
3404
  return;
3202
3405
  }
3203
3406
  if (statusData?.state === "ToolApprovalsRequired") {
3407
+ setPendingBrowserCaptureDialogType(null);
3204
3408
  const msgId = statusData.messageId;
3205
3409
  const ids = statusData.toolCallIds || [];
3206
3410
  if (msgId && Array.isArray(ids) && ids.length > 0) {
@@ -3235,6 +3439,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3235
3439
  // Handle waiting states explicitly
3236
3440
  if (statusData?.state === "WaitingForApproval" ||
3237
3441
  statusData?.state === "waitingForApproval") {
3442
+ setPendingBrowserCaptureDialogType(null);
3238
3443
  setAgent((prev) => prev ? { ...prev, status: "waitingForApproval" } : prev);
3239
3444
  setIsConnecting(false);
3240
3445
  setIsWaitingForResponse(false);
@@ -3243,13 +3448,30 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3243
3448
  }
3244
3449
  if (statusData?.state === "WaitingForInput" ||
3245
3450
  statusData?.state === "waitingForInput") {
3246
- setAgent((prev) => prev ? { ...prev, status: "waitingForInput" } : prev);
3451
+ const dialogType = typeof statusData?.dialogType === "string"
3452
+ ? statusData.dialogType
3453
+ : null;
3454
+ const isBrowserCaptureWait = dialogType === DIALOG_TYPES.CAPTURE_PAGE_DOM ||
3455
+ dialogType === DIALOG_TYPES.CAPTURE_PAGE_SCREENSHOT;
3456
+ setPendingBrowserCaptureDialogType(isBrowserCaptureWait ? dialogType : null);
3457
+ setAgent((prev) => prev
3458
+ ? {
3459
+ ...prev,
3460
+ status: "waitingForInput",
3461
+ statusMessage: isBrowserCaptureWait &&
3462
+ typeof statusData?.title === "string" &&
3463
+ statusData.title.trim()
3464
+ ? statusData.title.trim()
3465
+ : prev.statusMessage,
3466
+ }
3467
+ : prev);
3247
3468
  setIsConnecting(false);
3248
3469
  setIsWaitingForResponse(false);
3249
3470
  setIsAgentThinking(false);
3250
3471
  return;
3251
3472
  }
3252
3473
  if (statusData?.state === "CostLimitReached") {
3474
+ setPendingBrowserCaptureDialogType(null);
3253
3475
  const totalCost = Number(statusData.totalCost) || 0;
3254
3476
  const costLimit = Number(statusData.costLimit) || agent?.costLimit || 0;
3255
3477
  setCostLimitExceeded({
@@ -3267,7 +3489,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3267
3489
  // Server sends additionalData directly (with todoList inside)
3268
3490
  // Also may include ChildAgents for spawned agents
3269
3491
  try {
3270
- const serverAdditionalData = statusData.additionalData || statusData.AdditionalData || {};
3492
+ const serverAdditionalData = statusData.additionalData || {};
3271
3493
  setAgentMetadata((prev) => {
3272
3494
  const current = (prev || {});
3273
3495
  const updated = { ...current };
@@ -3281,10 +3503,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3281
3503
  }
3282
3504
  // Merge ChildAgents if present (for spawned agents) - replace entire array
3283
3505
  // Backend sends the complete updated list, not just additions
3284
- if (statusData.ChildAgents || statusData.childAgents) {
3285
- const childAgents = statusData.ChildAgents || statusData.childAgents;
3506
+ if (statusData.childAgents) {
3507
+ const childAgents = statusData.childAgents;
3286
3508
  if (Array.isArray(childAgents)) {
3287
- updated.ChildAgents = childAgents;
3509
+ updated.childAgents = childAgents;
3288
3510
  }
3289
3511
  }
3290
3512
  return updated;
@@ -3300,6 +3522,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3300
3522
  normalizedStatus === "Completed") {
3301
3523
  // Reset deduplication for the next run
3302
3524
  lastSeqRef.current = 0;
3525
+ clearHeartbeatMessages();
3303
3526
  // Mark the last assistant message as completed
3304
3527
  setMessages((prev) => {
3305
3528
  const updated = prev.map((msg) => msg.role === "assistant" && !msg.isCompleted
@@ -3325,6 +3548,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3325
3548
  // Handle "Idle" state - agent has finished processing
3326
3549
  if (normalizedStatus === "idle" || normalizedStatus === "Idle") {
3327
3550
  // Update agent status to idle
3551
+ clearHeartbeatMessages();
3328
3552
  setAgent((prev) => (prev ? { ...prev, status: "idle" } : prev));
3329
3553
  setIsWaitingForResponse(false);
3330
3554
  isWaitingRef.current = false;
@@ -3346,6 +3570,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3346
3570
  // Handle "Error" state
3347
3571
  if (normalizedStatus === "error" || normalizedStatus === "Error") {
3348
3572
  const errorMsg = statusData?.statusMessage || statusData?.error || "Unknown error";
3573
+ clearHeartbeatMessages();
3349
3574
  setAgent((prev) => prev
3350
3575
  ? { ...prev, status: "error", statusMessage: errorMsg }
3351
3576
  : prev);
@@ -3365,6 +3590,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3365
3590
  if (messageType === "agent:run:complete") {
3366
3591
  // Reset deduplication for the next run
3367
3592
  lastSeqRef.current = 0;
3593
+ clearHeartbeatMessages();
3368
3594
  // Mark the last assistant message as completed
3369
3595
  setMessages((prev) => {
3370
3596
  const updated = prev.map((msg) => msg.role === "assistant" && !msg.isCompleted
@@ -3391,6 +3617,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3391
3617
  if (messageType === "agent:run:error") {
3392
3618
  const errorMsg = toUserFacingAgentErrorMessage(message.payload?.error) ||
3393
3619
  "AI could not complete this request.";
3620
+ clearHeartbeatMessages();
3394
3621
  // Reset deduplication for the next run after an error
3395
3622
  lastSeqRef.current = 0;
3396
3623
  setError(errorMsg);
@@ -3405,7 +3632,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3405
3632
  }
3406
3633
  }, [
3407
3634
  agent,
3635
+ clearHeartbeatMessages,
3408
3636
  handleContentChunk,
3637
+ handleHeartbeatMessage,
3409
3638
  handleToolCall,
3410
3639
  handleToolResult,
3411
3640
  onAgentUpdate,
@@ -3687,16 +3916,22 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3687
3916
  if (!candidate)
3688
3917
  return;
3689
3918
  // Keep active profile in sync whenever the matching entry in `profiles` changes —
3690
- // not only when the profile id changes. Otherwise allowedSkills (and similar fields)
3919
+ // not only when the profile id changes. Otherwise availableSkills (and similar fields)
3691
3920
  // that are merged in the parent after async loads never update (e.g. Template Builder
3692
3921
  // settings skills merged into the profile).
3693
3922
  setActiveProfile((prev) => {
3694
3923
  if (!prev || prev.id !== candidate.id)
3695
3924
  return candidate;
3696
- const skillKey = (p) => JSON.stringify((p.allowedSkills ?? []).map((s) => [
3697
- String(s?.id ?? ""),
3698
- String(s?.name ?? ""),
3699
- ]));
3925
+ const skillKey = (p) => JSON.stringify({
3926
+ allowed: (p.allowedSkills ?? []).map((s) => [
3927
+ String(s?.id ?? ""),
3928
+ String(s?.name ?? ""),
3929
+ ]),
3930
+ available: (p.availableSkills ?? []).map((s) => [
3931
+ String(s?.id ?? ""),
3932
+ String(s?.name ?? ""),
3933
+ ]),
3934
+ });
3700
3935
  if (skillKey(prev) === skillKey(candidate))
3701
3936
  return prev;
3702
3937
  return candidate;
@@ -3792,6 +4027,26 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3792
4027
  console.error("Failed to persist pending settings", e);
3793
4028
  }
3794
4029
  }, [agent?.id]);
4030
+ const getPendingRequestSettings = useCallback(() => {
4031
+ const pending = pendingSettingsRef.current;
4032
+ const requestProfile = pending?.profileId
4033
+ ? profiles.find((profile) => profile.id === pending.profileId) ||
4034
+ activeProfile ||
4035
+ profiles[0]
4036
+ : activeProfile || profiles[0];
4037
+ let requestModelId = selectedModelId;
4038
+ if (pending?.modelName) {
4039
+ const requestModel = (requestProfile?.models || []).find((model) => (model.name || "").trim().toLowerCase() ===
4040
+ pending.modelName?.trim().toLowerCase());
4041
+ requestModelId = requestModel?.id || requestModelId;
4042
+ }
4043
+ return {
4044
+ mode: (pending?.mode || mode),
4045
+ profileId: pending?.profileId || requestProfile?.id || "",
4046
+ profileName: pending?.profileName || requestProfile?.name || "",
4047
+ modelId: requestModelId,
4048
+ };
4049
+ }, [activeProfile, mode, profiles, selectedModelId]);
3795
4050
  const getSubmitErrorMessage = (error) => {
3796
4051
  const fallback = "Failed to submit prompt. Please try again.";
3797
4052
  if (!(error instanceof Error))
@@ -3993,16 +4248,17 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3993
4248
  console.warn("[AgentTerminal] Failed to compute live context:", e);
3994
4249
  }
3995
4250
  // Add visible test IDs to context (only for Help agent)
3996
- const currentProfileName = activeProfile?.name || profiles[0]?.name || "";
4251
+ const requestSettings = getPendingRequestSettings();
4252
+ const currentProfileName = requestSettings.profileName;
3997
4253
  effectiveContext = addVisibleTestIdsToContext(effectiveContext, currentProfileName);
3998
4254
  const request = {
3999
4255
  agentId: agentId,
4000
4256
  message: savedPrompt,
4001
4257
  sessionId: editContext.sessionId,
4002
- profileId: activeProfile?.id || profiles[0]?.id || "",
4258
+ profileId: requestSettings.profileId,
4003
4259
  profile: currentProfileName,
4004
- model: selectedModelId,
4005
- mode: mode,
4260
+ model: requestSettings.modelId,
4261
+ mode: requestSettings.mode,
4006
4262
  context: canonicalizeAgentMetadata(effectiveContext), // Use fresh live context when in live mode
4007
4263
  };
4008
4264
  console.log("[AgentTerminal] Calling startAgent API for agent:", agentId);
@@ -4251,16 +4507,17 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
4251
4507
  console.warn("[AgentTerminal] Failed to compute live context for quick message:", e);
4252
4508
  }
4253
4509
  // Add visible test IDs to context (only for Help agent)
4254
- const currentProfileName = activeProfile?.name || profiles[0]?.name || "";
4510
+ const requestSettings = getPendingRequestSettings();
4511
+ const currentProfileName = requestSettings.profileName;
4255
4512
  effectiveContext = addVisibleTestIdsToContext(effectiveContext, currentProfileName);
4256
4513
  const request = {
4257
4514
  agentId: agent.id,
4258
4515
  message: savedPrompt,
4259
4516
  sessionId: editContext.sessionId,
4260
- profileId: activeProfile?.id || profiles[0]?.id || "",
4517
+ profileId: requestSettings.profileId,
4261
4518
  profile: currentProfileName,
4262
- model: selectedModelId,
4263
- mode: mode,
4519
+ model: requestSettings.modelId,
4520
+ mode: requestSettings.mode,
4264
4521
  context: canonicalizeAgentMetadata(effectiveContext), // Use fresh live context when in live mode
4265
4522
  };
4266
4523
  console.log("[AgentTerminal] Calling startAgent API for quick message");
@@ -4685,6 +4942,70 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
4685
4942
  activeProfile,
4686
4943
  profiles,
4687
4944
  ]);
4945
+ const browserCaptureClaim = useMemo(() => getBrowserCaptureClaim(agentMetadata), [agentMetadata]);
4946
+ const isPendingBrowserCaptureWait = pendingBrowserCaptureDialogType === DIALOG_TYPES.CAPTURE_PAGE_DOM ||
4947
+ pendingBrowserCaptureDialogType === DIALOG_TYPES.CAPTURE_PAGE_SCREENSHOT;
4948
+ const currentSessionId = editContext?.sessionId?.trim() || "";
4949
+ const claimedSessionId = browserCaptureClaim?.sessionId?.trim() || "";
4950
+ const isClaimedByCurrentSession = !!currentSessionId &&
4951
+ !!claimedSessionId &&
4952
+ currentSessionId.toLowerCase() === claimedSessionId.toLowerCase();
4953
+ const isClaimedByAnotherBrowser = !!claimedSessionId && !isClaimedByCurrentSession;
4954
+ const handleClaimBrowser = useCallback(async (takeOver) => {
4955
+ if (!agent?.id || !editContext?.sessionId)
4956
+ return;
4957
+ setIsBrowserClaimMutationPending(true);
4958
+ try {
4959
+ const response = await claimAgentBrowser({
4960
+ agentId: agent.id,
4961
+ sessionId: editContext.sessionId,
4962
+ takeOver,
4963
+ terminalInstanceId: dialogTerminalInstanceIdRef.current,
4964
+ });
4965
+ setAgentMetadata((prev) => sanitizeAgentMetadata(setBrowserCaptureClaim(prev, response.claim || null)));
4966
+ }
4967
+ catch (err) {
4968
+ console.error("[AgentTerminal] Failed to claim browser:", err);
4969
+ editContext.showErrorToast(err);
4970
+ }
4971
+ finally {
4972
+ setIsBrowserClaimMutationPending(false);
4973
+ }
4974
+ }, [agent?.id, editContext, sanitizeAgentMetadata]);
4975
+ const handleReleaseBrowser = useCallback(async () => {
4976
+ if (!agent?.id || !editContext?.sessionId)
4977
+ return;
4978
+ setIsBrowserClaimMutationPending(true);
4979
+ try {
4980
+ const response = await releaseAgentBrowser({
4981
+ agentId: agent.id,
4982
+ sessionId: editContext.sessionId,
4983
+ terminalInstanceId: dialogTerminalInstanceIdRef.current,
4984
+ });
4985
+ setAgentMetadata((prev) => sanitizeAgentMetadata(setBrowserCaptureClaim(prev, response.claim || null)));
4986
+ }
4987
+ catch (err) {
4988
+ console.error("[AgentTerminal] Failed to release browser:", err);
4989
+ editContext.showErrorToast(err);
4990
+ }
4991
+ finally {
4992
+ setIsBrowserClaimMutationPending(false);
4993
+ }
4994
+ }, [agent?.id, editContext, sanitizeAgentMetadata]);
4995
+ useEffect(() => {
4996
+ return () => {
4997
+ if (!agent?.id || !editContext?.sessionId || !isClaimedByCurrentSession) {
4998
+ return;
4999
+ }
5000
+ void releaseAgentBrowser({
5001
+ agentId: agent.id,
5002
+ sessionId: editContext.sessionId,
5003
+ terminalInstanceId: dialogTerminalInstanceIdRef.current,
5004
+ }).catch((error) => {
5005
+ console.warn("[AgentTerminal] Failed to release browser on unmount:", error);
5006
+ });
5007
+ };
5008
+ }, [agent?.id, editContext?.sessionId, isClaimedByCurrentSession]);
4688
5009
  // Stop current execution/stream safely
4689
5010
  const handleStop = useCallback(async () => {
4690
5011
  try {
@@ -4817,8 +5138,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
4817
5138
  // We only want these global thinking dots if the last message was from the user
4818
5139
  // or if no messages exist yet (waiting for initial response).
4819
5140
  const lastMessage = messages.length > 0 ? messages[messages.length - 1] : null;
4820
- if (isExecuting && lastMessage?.role === "assistant")
5141
+ if (isExecuting &&
5142
+ lastMessage?.role === "assistant" &&
5143
+ !lastMessage.isCompleted) {
4821
5144
  return false;
5145
+ }
4822
5146
  // Existing check for uncompleted assistant messages
4823
5147
  const hasActiveStreamingMessage = messages.some((m) => !m.isCompleted && m.role === "assistant");
4824
5148
  if (hasActiveStreamingMessage)
@@ -4898,11 +5222,49 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
4898
5222
  const renderErrorBanner = () => {
4899
5223
  const currentAgent = agent || agentStub;
4900
5224
  const isErrorStatus = currentAgent?.status === "error" || currentAgent?.status === 4;
4901
- const errorMessage = currentAgent?.statusMessage;
4902
- if (!isErrorStatus || !errorMessage)
5225
+ const errorMessage = (isErrorStatus ? currentAgent?.statusMessage : null) || error;
5226
+ if (!errorMessage)
4903
5227
  return null;
4904
5228
  return (_jsx("div", { className: "m-3 rounded border border-red-300 bg-red-50 p-3 text-[11px] text-red-900", children: _jsxs("div", { className: "flex items-start gap-2", children: [_jsx(AlertCircle, { className: "mt-0.5 h-4 w-4 shrink-0 text-red-500", strokeWidth: 1 }), _jsxs("div", { className: "flex-1", children: [_jsx("div", { className: "mb-1 font-semibold", children: "Agent Error" }), _jsx("div", { className: "text-red-800", children: errorMessage })] })] }) }));
4905
5229
  };
5230
+ const renderBrowserClaimBanner = (variant = "inline") => {
5231
+ if (!agent?.id || !editContext?.sessionId)
5232
+ return null;
5233
+ if (!isClaimedByCurrentSession &&
5234
+ !isClaimedByAnotherBrowser &&
5235
+ !isPendingBrowserCaptureWait) {
5236
+ return null;
5237
+ }
5238
+ const label = isClaimedByCurrentSession
5239
+ ? "Attached to this browser"
5240
+ : isClaimedByAnotherBrowser
5241
+ ? "Attached in another browser"
5242
+ : "No browser attached";
5243
+ const description = isClaimedByCurrentSession
5244
+ ? "This browser will handle page screenshot and DOM capture requests for the agent."
5245
+ : isClaimedByAnotherBrowser
5246
+ ? "Capture requests will stay with the other browser until you take over control here."
5247
+ : "A page capture request is waiting for a browser attachment. Attach this browser to continue.";
5248
+ const bannerClassName = cn("rounded border border-blue-200 bg-blue-50 p-3 text-[11px] text-blue-900", variant === "fixed" ? "mx-3 mt-3 mb-2 shrink-0" : "m-3", isClaimedByCurrentSession && "border-blue-300");
5249
+ return (_jsx("div", { className: bannerClassName, children: _jsxs("div", { className: "flex items-center justify-between gap-3", children: [_jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "font-semibold", children: label }), _jsx("div", { className: "mt-1 text-blue-800", children: description })] }), _jsx("div", { className: "flex shrink-0 items-center gap-2", children: isClaimedByCurrentSession ? (_jsx("button", { type: "button", className: "rounded border border-blue-300 bg-white px-2 py-1 text-[11px] font-medium text-blue-900 disabled:cursor-not-allowed disabled:opacity-60", disabled: isBrowserClaimMutationPending, onClick: () => {
5250
+ void handleReleaseBrowser();
5251
+ }, children: "Release" })) : (_jsx("button", { type: "button", className: "rounded border border-blue-300 bg-white px-2 py-1 text-[11px] font-medium text-blue-900 disabled:cursor-not-allowed disabled:opacity-60", disabled: isBrowserClaimMutationPending, onClick: () => {
5252
+ void handleClaimBrowser(isClaimedByAnotherBrowser);
5253
+ }, children: isClaimedByAnotherBrowser
5254
+ ? "Take over browser control"
5255
+ : "Attach to this browser" })) })] }) }));
5256
+ };
5257
+ const fixedBrowserClaimBanner = isClaimedByCurrentSession
5258
+ ? renderBrowserClaimBanner("fixed")
5259
+ : null;
5260
+ const inlineBrowserClaimBanner = !isClaimedByCurrentSession
5261
+ ? renderBrowserClaimBanner("inline")
5262
+ : null;
5263
+ useEffect(() => {
5264
+ if (agent?.status !== "waitingForInput") {
5265
+ setPendingBrowserCaptureDialogType(null);
5266
+ }
5267
+ }, [agent?.status]);
4906
5268
  const renderInlineDialogContent = () => {
4907
5269
  if (!activeInlineDialog)
4908
5270
  return null;
@@ -4962,10 +5324,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
4962
5324
  const summaryOperations = latestSummaryAssistantGroup
4963
5325
  ? getOperationsForMessageGroup(summaryMessages, agentOperations)
4964
5326
  : [];
4965
- return (_jsxs("div", { className: `flex h-full min-h-0 flex-col ${className || ""}`, children: [_jsxs("div", { ref: messagesContainerRef, className: "flex-1 overflow-y-auto", onScroll: handleScroll, children: [error &&
5327
+ return (_jsxs("div", { className: `flex h-full min-h-0 flex-col ${className || ""}`, children: [fixedBrowserClaimBanner, _jsxs("div", { ref: messagesContainerRef, className: "flex-1 overflow-y-auto", onScroll: handleScroll, children: [error &&
4966
5328
  !isAgentErrorStatusValue((agent || agentStub)?.status) && (_jsx("div", { className: "m-4 rounded-lg border-l-4 border-red-500 bg-red-50 p-3 select-text", children: _jsxs("div", { className: "flex items-start", children: [_jsx(AlertCircle, { className: "mt-0.5 h-5 w-5 text-red-400", strokeWidth: 1 }), _jsxs("div", { className: "ml-3", children: [_jsx("p", { className: "text-[11px] font-medium text-red-800", children: "Error" }), _jsx("p", { className: "mt-1 text-[11px] text-red-700", children: error })] })] }) })), showInitialThinkingSplash && (_jsx("div", { className: "flex h-full items-center justify-center p-8", children: _jsxs("div", { className: "flex flex-col items-center gap-4", children: [activeProfile?.svgIcon ? (_jsx("div", { className: "flex h-16 w-16 items-center justify-center text-gray-400 [&>svg]:h-full [&>svg]:w-full", dangerouslySetInnerHTML: {
4967
- __html: activeProfile.svgIcon,
4968
- } })) : (_jsx(SecretAgentIcon, { size: 64, strokeWidth: 1, className: "text-gray-400" })), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.3s]" }), _jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.15s]" }), _jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400" })] })] }) })), renderErrorBanner(), inlineDialog ? (inlineDialog) : latestSummaryAssistantGroup ? (_jsx("div", { className: "space-y-0 divide-y divide-gray-100 select-text", children: _jsx(AiResponseMessage, { messages: summaryMessages, finished: !latestSummaryAssistantGroup.isLastGroup || !isExecuting, editOperations: summaryOperations, error: error || undefined, defaultCollapseJson: defaultCollapseJson, profileSvgIcon: activeProfile?.svgIcon, agentId: agent?.id || agentStub.id, agentName: activeProfile?.agentName ||
5329
+ __html: sanitizeSvg(activeProfile.svgIcon),
5330
+ } })) : (_jsx(SecretAgentIcon, { size: 64, strokeWidth: 1, className: "text-gray-400" })), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.3s]" }), _jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.15s]" }), _jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400" })] })] }) })), inlineBrowserClaimBanner, renderErrorBanner(), inlineDialog ? (inlineDialog) : latestSummaryAssistantGroup ? (_jsx("div", { className: "space-y-0 divide-y divide-gray-100 select-text", children: _jsx(AiResponseMessage, { messages: summaryMessages, finished: !latestSummaryAssistantGroup.isLastGroup || !isExecuting, editOperations: summaryOperations, defaultCollapseJson: defaultCollapseJson, profileSvgIcon: activeProfile?.svgIcon, agentId: agent?.id || agentStub.id, agentName: activeProfile?.agentName ||
4969
5331
  activeProfile?.displayTitle ||
4970
5332
  activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, onQuickAction: (action) => {
4971
5333
  const text = (action.prompt ||
@@ -4989,7 +5351,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
4989
5351
  shouldShowThinkingDots &&
4990
5352
  !inlineDialog &&
4991
5353
  !latestSummaryAssistantGroup && (_jsxs("div", { className: "flex gap-3 px-4 py-3", "data-testid": "agent-thinking-dots", children: [_jsx("div", { className: "shrink-0", children: activeProfile?.svgIcon ? (_jsx("div", { className: "text-gray-2 flex h-6 w-6 items-center justify-center [&>svg]:h-full [&>svg]:w-full", dangerouslySetInnerHTML: {
4992
- __html: activeProfile.svgIcon,
5354
+ __html: sanitizeSvg(activeProfile.svgIcon),
4993
5355
  } })) : (_jsx(SecretAgentIcon, { size: 20, strokeWidth: 1, className: "text-gray-2" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [_jsx("span", { className: "text-dark text-xs font-medium", children: activeProfile?.agentName ||
4994
5356
  activeProfile?.displayTitle ||
4995
5357
  activeProfile?.name ||
@@ -5076,7 +5438,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5076
5438
  })()
5077
5439
  : null;
5078
5440
  const fullModeInlineDialog = displayMode === "full" ? renderInlineDialogContent() : null;
5079
- const fullModeUpperContent = (_jsxs("div", { className: "flex h-full min-h-0 flex-1 flex-col", children: [_jsxs("div", { ref: messagesContainerRef, className: "flex-1 overflow-y-auto", onScroll: handleScroll, children: [error && !isAgentErrorStatusValue((agent || agentStub)?.status) && (_jsx("div", { className: "m-4 rounded-lg border-l-4 border-red-500 bg-red-50 p-3 select-text", children: _jsxs("div", { className: "flex items-start", children: [_jsx(AlertCircle, { className: "mt-0.5 h-5 w-5 text-red-400", strokeWidth: 1 }), _jsxs("div", { className: "ml-3", children: [_jsx("p", { className: "text-[11px] font-medium text-red-800", children: "Error" }), _jsx("p", { className: "mt-1 text-[11px] text-red-700", children: error })] })] }) })), messages.length === 0 && !error && !hideGreeting && (_jsx("div", { className: "flex h-full items-center justify-center", children: !activeProfile ? (_jsx(Loader2, { className: "mx-auto h-8 w-8 animate-spin text-gray-400" })) : (_jsx(AgentGreeting, { profile: activeProfile, onPromptClick: (p) => {
5441
+ const fullModeUpperContent = (_jsxs("div", { className: "flex h-full min-h-0 flex-1 flex-col", children: [fixedBrowserClaimBanner, _jsxs("div", { ref: messagesContainerRef, className: "flex-1 overflow-y-auto", onScroll: handleScroll, children: [error && !isAgentErrorStatusValue((agent || agentStub)?.status) && (_jsx("div", { className: "m-4 rounded-lg border-l-4 border-red-500 bg-red-50 p-3 select-text", children: _jsxs("div", { className: "flex items-start", children: [_jsx(AlertCircle, { className: "mt-0.5 h-5 w-5 text-red-400", strokeWidth: 1 }), _jsxs("div", { className: "ml-3", children: [_jsx("p", { className: "text-[11px] font-medium text-red-800", children: "Error" }), _jsx("p", { className: "mt-1 text-[11px] text-red-700", children: error })] })] }) })), messages.length === 0 && !error && !hideGreeting && (_jsx("div", { className: "flex h-full items-center justify-center", children: !activeProfile ? (_jsx(Loader2, { className: "mx-auto h-8 w-8 animate-spin text-gray-400" })) : (_jsx(AgentGreeting, { profile: activeProfile, onPromptClick: (p) => {
5080
5442
  setPrompt(p);
5081
5443
  // Use setTimeout to ensure state is updated before submission
5082
5444
  setTimeout(() => {
@@ -5090,8 +5452,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5090
5452
  }
5091
5453
  }, 0);
5092
5454
  } })) })), showInitialThinkingSplash && (_jsx("div", { className: "flex h-full items-center justify-center p-8", children: _jsxs("div", { className: "flex flex-col items-center gap-4", children: [activeProfile?.svgIcon ? (_jsx("div", { className: "flex h-16 w-16 items-center justify-center text-gray-400 [&>svg]:h-full [&>svg]:w-full", dangerouslySetInnerHTML: {
5093
- __html: activeProfile.svgIcon,
5094
- } })) : (_jsx(SecretAgentIcon, { size: 64, strokeWidth: 1, className: "text-gray-400" })), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.3s]" }), _jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.15s]" }), _jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400" })] })] }) })), renderErrorBanner(), _jsxs("div", { className: "space-y-0 divide-y divide-gray-100 select-text", children: [(() => {
5455
+ __html: sanitizeSvg(activeProfile.svgIcon),
5456
+ } })) : (_jsx(SecretAgentIcon, { size: 64, strokeWidth: 1, className: "text-gray-400" })), _jsxs("div", { className: "flex items-center gap-1", children: [_jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.3s]" }), _jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.15s]" }), _jsx("span", { className: "h-2 w-2 animate-bounce rounded-full bg-gray-400" })] })] }) })), inlineBrowserClaimBanner, renderErrorBanner(), _jsxs("div", { className: "space-y-0 divide-y divide-gray-100 select-text", children: [(() => {
5095
5457
  const groups = groupConsecutiveMessages(messages);
5096
5458
  return groups.map((group, groupIndex) => {
5097
5459
  const isLastGroup = groupIndex === groups.length - 1;
@@ -5099,6 +5461,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5099
5461
  // Render user message
5100
5462
  return (_jsx(UserMessage, { message: group.messages[0] }, groupIndex));
5101
5463
  }
5464
+ else if (group.type === "heartbeat" && group.messages[0]) {
5465
+ return (_jsx(HeartbeatMessage, { message: group.messages[0] }, group.messages[0].id || groupIndex));
5466
+ }
5102
5467
  else {
5103
5468
  // Render bundled assistant messages
5104
5469
  // Check if this group contains any streaming message
@@ -5115,7 +5480,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5115
5480
  }
5116
5481
  const convertedMessages = convertAgentMessagesToAiFormat(filteredMessages);
5117
5482
  const operationsForGroup = getOperationsForMessageGroup(convertedMessages, agentOperations);
5118
- return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isLastGroup || !isExecuting, editOperations: operationsForGroup, error: error || undefined, defaultCollapseJson: defaultCollapseJson, profileSvgIcon: activeProfile?.svgIcon, agentId: agent?.id || agentStub.id, agentName: activeProfile?.agentName ||
5483
+ return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isLastGroup || !isExecuting, editOperations: operationsForGroup, defaultCollapseJson: defaultCollapseJson, profileSvgIcon: activeProfile?.svgIcon, agentId: agent?.id || agentStub.id, agentName: activeProfile?.agentName ||
5119
5484
  activeProfile?.displayTitle ||
5120
5485
  activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, onQuickAction: (action) => {
5121
5486
  const text = (action.prompt ||
@@ -5161,7 +5526,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5161
5526
  }
5162
5527
  });
5163
5528
  })(), shouldShowThinkingDots && (_jsxs("div", { className: "flex gap-3 px-4 py-3", "data-testid": "agent-thinking-dots", children: [_jsx("div", { className: "shrink-0", children: activeProfile?.svgIcon ? (_jsx("div", { className: "text-gray-2 flex h-6 w-6 items-center justify-center [&>svg]:h-full [&>svg]:w-full", dangerouslySetInnerHTML: {
5164
- __html: activeProfile.svgIcon,
5529
+ __html: sanitizeSvg(activeProfile.svgIcon),
5165
5530
  } })) : (_jsx(SecretAgentIcon, { size: 20, strokeWidth: 1, className: "text-gray-2" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsxs("div", { className: "mb-1 flex items-center gap-2", children: [_jsx("span", { className: "text-dark text-xs font-medium", children: activeProfile?.agentName ||
5166
5531
  activeProfile?.displayTitle ||
5167
5532
  activeProfile?.name ||
@@ -5220,10 +5585,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5220
5585
  if (qp.data) {
5221
5586
  try {
5222
5587
  const parsed = JSON.parse(qp.data);
5223
- triggerName =
5224
- parsed?.TriggerName?.trim() ||
5225
- parsed?.triggerName?.trim() ||
5226
- "";
5588
+ triggerName = parsed?.triggerName?.trim() || "";
5227
5589
  }
5228
5590
  catch {
5229
5591
  // Ignore invalid JSON metadata and render as regular queued prompt.
@@ -5337,13 +5699,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5337
5699
  return null;
5338
5700
  return (_jsxs("div", { className: cn("mt-2 flex items-stretch gap-2", hideBottomControls || simpleMode || isInPlaceholderMode
5339
5701
  ? "justify-end"
5340
- : "justify-between"), children: [!hideBottomControls && !simpleMode && !isInPlaceholderMode && (_jsxs("div", { className: "flex flex-wrap items-center justify-start gap-2", children: [_jsx(Select, { size: "xs", maxWidth: 240, className: cn("h-5 w-auto min-w-[95px] rounded border px-1.5 text-[11px] font-normal", mode === "read-only"
5702
+ : "justify-between"), children: [!hideBottomControls && !simpleMode && !isInPlaceholderMode && (_jsxs("div", { className: "flex flex-wrap items-center justify-start gap-2", children: [_jsx(Select, { "data-testid": "agent-mode-selector", size: "xs", maxWidth: 240, className: cn("h-5 w-auto min-w-[95px] rounded border px-1.5 text-[11px] font-normal", mode === "read-only"
5341
5703
  ? "border-green-300 bg-green-50! text-green-700 hover:bg-green-100!"
5342
5704
  : mode === "supervised"
5343
5705
  ? "border-amber-300 bg-amber-50! text-amber-700 hover:bg-amber-100!"
5344
5706
  : "border-red-300 bg-red-50! text-red-700 hover:bg-red-100!"), value: mode, options: modeOptions, onValueChange: async (val) => {
5345
5707
  const nextMode = val || "supervised";
5346
- setMode(nextMode);
5347
5708
  const current = agentMetadata || {};
5348
5709
  const nextMeta = {
5349
5710
  ...current,
@@ -5351,6 +5712,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5351
5712
  };
5352
5713
  try {
5353
5714
  if (!agent?.id || agent.status === "new") {
5715
+ setMode(nextMode);
5354
5716
  setAgentMetadata(nextMeta);
5355
5717
  pendingSettingsRef.current = {
5356
5718
  ...(pendingSettingsRef.current || {}),
@@ -5358,13 +5720,18 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5358
5720
  };
5359
5721
  return;
5360
5722
  }
5361
- await updateAgentSettings(agent.id, {
5723
+ const result = await updateAgentSettings(agent.id, {
5362
5724
  mode: nextMode,
5363
5725
  });
5726
+ if (result.success === false || result.updates?.mode === false) {
5727
+ throw new Error("Mode change was not applied");
5728
+ }
5729
+ setMode(nextMode);
5364
5730
  setAgentMetadata(nextMeta);
5365
5731
  setAgent((prev) => prev
5366
5732
  ? {
5367
5733
  ...prev,
5734
+ mode: nextMode,
5368
5735
  metadata: JSON.stringify(nextMeta),
5369
5736
  }
5370
5737
  : prev);
@@ -5411,6 +5778,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5411
5778
  setAgent((prev) => prev
5412
5779
  ? {
5413
5780
  ...prev,
5781
+ profileId: nextProfile.id,
5782
+ profileName: nextProfile.name,
5414
5783
  metadata: JSON.stringify({
5415
5784
  ...(agentMetadata || {}),
5416
5785
  profile: nextProfile.name,
@@ -5475,15 +5844,17 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5475
5844
  skillRootIds.length === 0 && (_jsx("div", { className: "text-[10px] text-gray-500", children: "No skill roots available." })), !skillsLoading &&
5476
5845
  !skillsError &&
5477
5846
  profileFilteredSkills.length === 0 && (_jsx("div", { className: "text-[10px] text-gray-500", children: selectedSkillIds.length > 0
5478
- ? "All allowed skills are selected"
5479
- : "No skills available for this profile" }))] }) })] })] }), selectedSkillIds.length > 0 && (_jsx("div", { className: "mb-2 flex flex-wrap gap-1", children: selectedSkillIds.map((skillId) => {
5847
+ ? "All available/allowed skills are selected"
5848
+ : "No available or allowed skills for this profile" }))] }) })] })] }), selectedSkillIds.length > 0 && (_jsx("div", { className: "mb-2 flex flex-wrap gap-1", children: selectedSkillIds.map((skillId) => {
5480
5849
  const skill = selectedSkills.find((s) => s.id === skillId);
5481
5850
  return (_jsxs("div", { className: "inline-flex items-center gap-1 rounded-full border border-gray-200 bg-gray-100 px-1.5 py-0.5 text-[10px] text-gray-700", children: [_jsx("span", { children: skill?.name || skillId }), _jsx("button", { type: "button", className: "rounded p-0.5 text-gray-500 hover:bg-gray-200 hover:text-gray-700", title: "Open skill item", "aria-label": `Open ${skill?.name || skillId}`, onClick: () => {
5482
5851
  void handleOpenSkillItem(skillId);
5483
- }, children: _jsx(ExternalLink, { className: "h-2.5 w-2.5", strokeWidth: 1.5 }) }), backendAssignedSkillSet.has(skillId.toLowerCase()) ? (_jsx("span", { className: "text-[9px] text-gray-500", children: "auto" })) : (_jsx("button", { type: "button", className: "rounded p-0.5 text-gray-500 hover:bg-gray-200 hover:text-gray-700", onClick: () => {
5852
+ }, children: _jsx(ExternalLink, { className: "h-2.5 w-2.5", strokeWidth: 1.5 }) }), autoAssignedSkillSet.has(skillId.toLowerCase()) ? (_jsx("span", { className: "text-[9px] text-gray-500", children: "auto" })) : (_jsx("button", { type: "button", className: "rounded p-0.5 text-gray-500 hover:bg-gray-200 hover:text-gray-700", onClick: () => {
5484
5853
  void handleRemoveSkill(skillId);
5485
5854
  }, title: "Remove skill", "aria-label": `Remove ${skill?.name || skillId}`, children: _jsx(X, { className: "h-2.5 w-2.5", strokeWidth: 1 }) }))] }, skillId));
5486
- }) }))] }), _jsxs("div", { children: [_jsxs("button", { type: "button", onClick: () => setAllowancesSectionExpanded((open) => !open), className: "mb-0.5 flex w-full items-center justify-between rounded px-0.5 py-0.5 text-left text-[11px] font-medium text-gray-700 hover:bg-gray-100/80", "aria-expanded": allowancesSectionExpanded, "data-testid": "agent-allowances-section-toggle", children: [_jsxs("span", { children: ["Allowances (", allowancesTotalCount, ")"] }), allowancesSectionExpanded ? (_jsx(ChevronUp, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 })) : (_jsx(ChevronDown, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 }))] }), allowancesSectionExpanded && (_jsx("div", { className: "max-h-36 overflow-y-auto rounded border border-gray-100 bg-gray-50/50 p-1", "data-testid": "agent-allowances-section", children: operationAllowancesLoading ? (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "Loading allowances..." })) : hasAnyAllowances ? (_jsx("div", { className: "space-y-1", children: allowanceGroups.map((group) => group.rows.length > 0 ? (_jsxs("div", { className: "space-y-0.5", children: [_jsx("div", { className: "px-1 text-[9px] font-medium tracking-wide text-gray-400 uppercase", children: group.label }), group.rows.map((allowance, index) => {
5855
+ }) }))] }), _jsxs("div", { children: [_jsxs("button", { type: "button", onClick: () => setToolsSectionExpanded((open) => !open), className: "mb-0.5 flex w-full items-center justify-between rounded px-0.5 py-0.5 text-left text-[11px] font-medium text-gray-700 hover:bg-gray-100/80", "aria-expanded": toolsSectionExpanded, "data-testid": "agent-tools-section-toggle", children: [_jsxs("span", { children: ["Available tools (", displayedAvailableTools.length, ")"] }), toolsSectionExpanded ? (_jsx(ChevronUp, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 })) : (_jsx(ChevronDown, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 }))] }), toolsSectionExpanded && (_jsx("div", { className: "max-h-36 overflow-y-auto rounded border border-gray-100 bg-gray-50/50 p-1", "data-testid": "agent-tools-section", children: displayedAvailableToolsLoading ? (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "Loading available tools..." })) : displayedAvailableTools.length > 0 ? (_jsxs("div", { className: "space-y-0.5", children: [isNewAgent && (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "Preview based on the current profile, mode, and selected skills." })), displayedAvailableTools.map((toolName) => (_jsx("div", { className: "rounded px-1 py-0.5 transition-colors hover:bg-white/60", "data-testid": "agent-tool-row", title: toolName, children: _jsx("div", { className: "truncate text-[10px] text-gray-700", children: toolName }) }, toolName)))] })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isNewAgent
5856
+ ? "No available tools for this profile and mode"
5857
+ : "No available tools" })) })), displayedAvailableToolsError && (_jsx("div", { className: "mt-1 text-[10px] text-red-600", children: displayedAvailableToolsError }))] }), _jsxs("div", { children: [_jsxs("button", { type: "button", onClick: () => setAllowancesSectionExpanded((open) => !open), className: "mb-0.5 flex w-full items-center justify-between rounded px-0.5 py-0.5 text-left text-[11px] font-medium text-gray-700 hover:bg-gray-100/80", "aria-expanded": allowancesSectionExpanded, "data-testid": "agent-allowances-section-toggle", children: [_jsxs("span", { children: ["Allowances (", allowancesTotalCount, ")"] }), allowancesSectionExpanded ? (_jsx(ChevronUp, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 })) : (_jsx(ChevronDown, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 }))] }), allowancesSectionExpanded && (_jsx("div", { className: "max-h-36 overflow-y-auto rounded border border-gray-100 bg-gray-50/50 p-1", "data-testid": "agent-allowances-section", children: operationAllowancesLoading ? (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "Loading allowances..." })) : hasAnyAllowances ? (_jsx("div", { className: "space-y-1", children: allowanceGroups.map((group) => group.rows.length > 0 ? (_jsxs("div", { className: "space-y-0.5", children: [_jsx("div", { className: "px-1 text-[9px] font-medium tracking-wide text-gray-400 uppercase", children: group.label }), group.rows.map((allowance, index) => {
5487
5858
  const sourceLabel = formatAllowanceSource(allowance.source);
5488
5859
  const pathLabel = "itemPath" in allowance
5489
5860
  ? allowance.itemPath
@@ -5496,10 +5867,14 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
5496
5867
  ]
5497
5868
  .filter(Boolean)
5498
5869
  .join(" · ") }))] }, `${group.key}-${allowance.operationType}-${pathLabel}-${index}`));
5499
- })] }, group.key)) : null) })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "No active allowances" })) })), operationAllowancesError && (_jsx("div", { className: "mt-1 text-[10px] text-red-600", children: operationAllowancesError }))] }), _jsxs("div", { children: [_jsxs("button", { type: "button", onClick: () => setSubscribedTriggersSectionExpanded((open) => !open), className: "mb-0.5 flex w-full items-center justify-between rounded px-0.5 py-0.5 text-left text-[11px] font-medium text-gray-700 hover:bg-gray-100/80", "aria-expanded": subscribedTriggersSectionExpanded, "data-testid": "agent-subscribed-triggers-section-toggle", children: [_jsx("span", { children: `Subscribed triggers (${activeTriggerSubscriptions.length})` }), subscribedTriggersSectionExpanded ? (_jsx(ChevronUp, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 })) : (_jsx(ChevronDown, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 }))] }), subscribedTriggersSectionExpanded && (_jsx("div", { className: "max-h-28 overflow-y-auto rounded border border-gray-100 bg-gray-50/50 p-1", "data-testid": "agent-subscribed-triggers-section", children: triggerSubscriptionsLoading ? (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "Loading subscribed triggers..." })) : activeTriggerSubscriptions.length > 0 ? (_jsx("div", { className: "space-y-0.5", children: activeTriggerSubscriptions.map((sub) => {
5870
+ })] }, group.key)) : null) })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isNewAgent
5871
+ ? "Allowances are shown after the agent is created"
5872
+ : "No active allowances" })) })), operationAllowancesError && (_jsx("div", { className: "mt-1 text-[10px] text-red-600", children: operationAllowancesError }))] }), _jsxs("div", { children: [_jsxs("button", { type: "button", onClick: () => setSubscribedTriggersSectionExpanded((open) => !open), className: "mb-0.5 flex w-full items-center justify-between rounded px-0.5 py-0.5 text-left text-[11px] font-medium text-gray-700 hover:bg-gray-100/80", "aria-expanded": subscribedTriggersSectionExpanded, "data-testid": "agent-subscribed-triggers-section-toggle", children: [_jsx("span", { children: `Subscribed triggers (${activeTriggerSubscriptions.length})` }), subscribedTriggersSectionExpanded ? (_jsx(ChevronUp, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 })) : (_jsx(ChevronDown, { className: "h-3 w-3 shrink-0 text-gray-400", strokeWidth: 1.5 }))] }), subscribedTriggersSectionExpanded && (_jsx("div", { className: "max-h-28 overflow-y-auto rounded border border-gray-100 bg-gray-50/50 p-1", "data-testid": "agent-subscribed-triggers-section", children: triggerSubscriptionsLoading ? (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "Loading subscribed triggers..." })) : activeTriggerSubscriptions.length > 0 ? (_jsx("div", { className: "space-y-0.5", children: activeTriggerSubscriptions.map((sub) => {
5500
5873
  const filterText = (sub.filter || "").trim();
5501
5874
  return (_jsxs("div", { className: "flex items-baseline gap-1.5 rounded px-1 py-0.5 transition-colors hover:bg-white/60", children: [_jsx("div", { className: "shrink-0 text-[10px] font-medium text-gray-700", children: sub.triggerName }), filterText.length > 0 && (_jsx("div", { className: "truncate text-[9px] text-gray-400", title: filterText, children: filterText }))] }, sub.id));
5502
- }) })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: "No active trigger subscriptions" })) })), triggerSubscriptionsError && (_jsx("div", { className: "mt-1 text-[10px] text-red-600", children: triggerSubscriptionsError }))] })] }) })] }), activeProfile?.prompts?.length ? (_jsxs(Popover, { open: showPredefined, onOpenChange: setShowPredefined, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { className: "rounded p-1 hover:bg-gray-100", onClick: () => { }, title: "Predefined prompts", "aria-label": "Predefined prompts", type: "button", children: _jsx(Wand2, { className: "h-3 w-3", strokeWidth: 1 }) }) }), _jsx(PopoverContent, { className: "w-64 p-0", align: "start", children: _jsx("div", { className: "max-h-56 overflow-y-auto p-2", children: activeProfile.prompts.map((p, index) => (_jsx("div", { className: "cursor-pointer rounded p-1.5 text-[10px] text-gray-700 hover:bg-gray-100", onClick: () => {
5875
+ }) })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isNewAgent
5876
+ ? "Subscribed triggers are shown after the agent is created"
5877
+ : "No active trigger subscriptions" })) })), triggerSubscriptionsError && (_jsx("div", { className: "mt-1 text-[10px] text-red-600", children: triggerSubscriptionsError }))] })] }) })] }), activeProfile?.prompts?.length ? (_jsxs(Popover, { open: showPredefined, onOpenChange: setShowPredefined, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx("button", { className: "rounded p-1 hover:bg-gray-100", onClick: () => { }, title: "Predefined prompts", "aria-label": "Predefined prompts", type: "button", children: _jsx(Wand2, { className: "h-3 w-3", strokeWidth: 1 }) }) }), _jsx(PopoverContent, { className: "w-64 p-0", align: "start", children: _jsx("div", { className: "max-h-56 overflow-y-auto p-2", children: activeProfile.prompts.map((p, index) => (_jsx("div", { className: "cursor-pointer rounded p-1.5 text-[10px] text-gray-700 hover:bg-gray-100", onClick: () => {
5503
5878
  setPrompt(p.prompt);
5504
5879
  setShowPredefined(false);
5505
5880
  if (textareaRef.current)