@parhelia/core 0.1.12556 → 0.1.12565

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 (587) hide show
  1. package/dist/agents-view/AgentCard.d.ts +6 -4
  2. package/dist/agents-view/AgentCard.js +143 -24
  3. package/dist/agents-view/AgentCard.js.map +1 -1
  4. package/dist/agents-view/AgentsInbox.d.ts +1 -1
  5. package/dist/agents-view/AgentsInbox.js +7 -92
  6. package/dist/agents-view/AgentsInbox.js.map +1 -1
  7. package/dist/agents-view/AgentsTitlebar.js +3 -2
  8. package/dist/agents-view/AgentsTitlebar.js.map +1 -1
  9. package/dist/agents-view/AgentsView.d.ts +6 -7
  10. package/dist/agents-view/AgentsView.js +191 -99
  11. package/dist/agents-view/AgentsView.js.map +1 -1
  12. package/dist/agents-view/AgentsWorkspaceView.d.ts +2 -6
  13. package/dist/agents-view/AgentsWorkspaceView.js +266 -113
  14. package/dist/agents-view/AgentsWorkspaceView.js.map +1 -1
  15. package/dist/agents-view/ProfileAgentsGroup.d.ts +2 -1
  16. package/dist/agents-view/ProfileAgentsGroup.js +4 -3
  17. package/dist/agents-view/ProfileAgentsGroup.js.map +1 -1
  18. package/dist/components/ActionButton.d.ts +1 -1
  19. package/dist/components/ActionButton.js.map +1 -1
  20. package/dist/components/FilterInput.d.ts +1 -1
  21. package/dist/components/FilterInput.js +1 -1
  22. package/dist/components/FilterInput.js.map +1 -1
  23. package/dist/components/ui/LanguageSelector.js +2 -4
  24. package/dist/components/ui/LanguageSelector.js.map +1 -1
  25. package/dist/components/ui/PlaceholderInput.js +3 -3
  26. package/dist/components/ui/PlaceholderInput.js.map +1 -1
  27. package/dist/components/ui/PlaceholderInputTypes.js +1 -1
  28. package/dist/components/ui/PlaceholderInputTypes.js.map +1 -1
  29. package/dist/components/ui/alert-dialog.d.ts +1 -1
  30. package/dist/components/ui/alert-dialog.js +6 -10
  31. package/dist/components/ui/alert-dialog.js.map +1 -1
  32. package/dist/components/ui/button.d.ts +4 -4
  33. package/dist/components/ui/button.js +4 -1
  34. package/dist/components/ui/button.js.map +1 -1
  35. package/dist/components/ui/context-menu.d.ts +1 -1
  36. package/dist/components/ui/context-menu.js +12 -4
  37. package/dist/components/ui/context-menu.js.map +1 -1
  38. package/dist/components/ui/copy-button.d.ts +2 -1
  39. package/dist/components/ui/copy-button.js +2 -2
  40. package/dist/components/ui/copy-button.js.map +1 -1
  41. package/dist/components/ui/dialog.d.ts +1 -1
  42. package/dist/components/ui/dialog.js +21 -126
  43. package/dist/components/ui/dialog.js.map +1 -1
  44. package/dist/components/ui/input.d.ts +1 -1
  45. package/dist/components/ui/input.js +5 -3
  46. package/dist/components/ui/input.js.map +1 -1
  47. package/dist/components/ui/paste-button.d.ts +2 -1
  48. package/dist/components/ui/paste-button.js +2 -2
  49. package/dist/components/ui/paste-button.js.map +1 -1
  50. package/dist/components/ui/popover.js +1 -9
  51. package/dist/components/ui/popover.js.map +1 -1
  52. package/dist/components/ui/select.js +1 -1
  53. package/dist/components/ui/select.js.map +1 -1
  54. package/dist/components/ui/styled-dialog-title.js +1 -1
  55. package/dist/components/ui/styled-dialog-title.js.map +1 -1
  56. package/dist/components/ui/tabs.d.ts +1 -1
  57. package/dist/components/ui/tabs.js +4 -11
  58. package/dist/components/ui/tabs.js.map +1 -1
  59. package/dist/config/config.d.ts +4 -2
  60. package/dist/config/config.js +250 -70
  61. package/dist/config/config.js.map +1 -1
  62. package/dist/config/notificationRoutes.js +14 -0
  63. package/dist/config/notificationRoutes.js.map +1 -1
  64. package/dist/config/types/workspace.d.ts +6 -0
  65. package/dist/config/types.d.ts +63 -12
  66. package/dist/config/types.js.map +1 -1
  67. package/dist/editor/ConfirmationDialog.js +20 -4
  68. package/dist/editor/ConfirmationDialog.js.map +1 -1
  69. package/dist/editor/ContentTree.d.ts +2 -1
  70. package/dist/editor/ContentTree.js +93 -32
  71. package/dist/editor/ContentTree.js.map +1 -1
  72. package/dist/editor/Editor.js +87 -22
  73. package/dist/editor/Editor.js.map +1 -1
  74. package/dist/editor/FieldHistory.js +84 -36
  75. package/dist/editor/FieldHistory.js.map +1 -1
  76. package/dist/editor/FieldListField.js +21 -9
  77. package/dist/editor/FieldListField.js.map +1 -1
  78. package/dist/editor/FieldListFieldWithFallbacks.js +23 -2
  79. package/dist/editor/FieldListFieldWithFallbacks.js.map +1 -1
  80. package/dist/editor/GlobalMenuBar.js +29 -2
  81. package/dist/editor/GlobalMenuBar.js.map +1 -1
  82. package/dist/editor/ImageEditor.js +5 -2
  83. package/dist/editor/ImageEditor.js.map +1 -1
  84. package/dist/editor/ItemInfo.js +36 -1
  85. package/dist/editor/ItemInfo.js.map +1 -1
  86. package/dist/editor/LinkEditorDialog.js +3 -0
  87. package/dist/editor/LinkEditorDialog.js.map +1 -1
  88. package/dist/editor/MainLayout.d.ts +0 -2
  89. package/dist/editor/MainLayout.js +65 -8
  90. package/dist/editor/MainLayout.js.map +1 -1
  91. package/dist/editor/MigrationsView.js +29 -5
  92. package/dist/editor/MigrationsView.js.map +1 -1
  93. package/dist/editor/MobileLayout.js +37 -12
  94. package/dist/editor/MobileLayout.js.map +1 -1
  95. package/dist/editor/PictureCropper.js +54 -45
  96. package/dist/editor/PictureCropper.js.map +1 -1
  97. package/dist/editor/PictureEditor.js +17 -15
  98. package/dist/editor/PictureEditor.js.map +1 -1
  99. package/dist/editor/QuickItemSwitcher.js +21 -21
  100. package/dist/editor/QuickItemSwitcher.js.map +1 -1
  101. package/dist/editor/SetupWizard.js +52 -12
  102. package/dist/editor/SetupWizard.js.map +1 -1
  103. package/dist/editor/Titlebar.js +7 -2
  104. package/dist/editor/Titlebar.js.map +1 -1
  105. package/dist/editor/ai/AgentCostDisplay.d.ts +1 -0
  106. package/dist/editor/ai/AgentCostDisplay.js +1 -1
  107. package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
  108. package/dist/editor/ai/AgentDocumentList.js +32 -14
  109. package/dist/editor/ai/AgentDocumentList.js.map +1 -1
  110. package/dist/editor/ai/AgentGreeting.js +3 -2
  111. package/dist/editor/ai/AgentGreeting.js.map +1 -1
  112. package/dist/editor/ai/AgentProfileSelector.js +2 -1
  113. package/dist/editor/ai/AgentProfileSelector.js.map +1 -1
  114. package/dist/editor/ai/AgentStatusBadge.d.ts +0 -5
  115. package/dist/editor/ai/AgentStatusBadge.js +67 -65
  116. package/dist/editor/ai/AgentStatusBadge.js.map +1 -1
  117. package/dist/editor/ai/AgentTerminal.d.ts +14 -2
  118. package/dist/editor/ai/AgentTerminal.js +2406 -496
  119. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  120. package/dist/editor/ai/AgentTerminalStatusBar.d.ts +8 -3
  121. package/dist/editor/ai/AgentTerminalStatusBar.js +481 -56
  122. package/dist/editor/ai/AgentTerminalStatusBar.js.map +1 -1
  123. package/dist/editor/ai/Agents.js +161 -113
  124. package/dist/editor/ai/Agents.js.map +1 -1
  125. package/dist/editor/ai/AiResponseMessage.d.ts +10 -1
  126. package/dist/editor/ai/AiResponseMessage.js +267 -26
  127. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  128. package/dist/editor/ai/ContextInfoBar.d.ts +2 -3
  129. package/dist/editor/ai/ContextInfoBar.js +64 -7
  130. package/dist/editor/ai/ContextInfoBar.js.map +1 -1
  131. package/dist/editor/ai/GuidanceOverlay.js +17 -11
  132. package/dist/editor/ai/GuidanceOverlay.js.map +1 -1
  133. package/dist/editor/ai/InlineAiDialog.d.ts +1 -1
  134. package/dist/editor/ai/InlineAiDialog.js +514 -192
  135. package/dist/editor/ai/InlineAiDialog.js.map +1 -1
  136. package/dist/editor/ai/InlineAiTrigger.js +115 -12
  137. package/dist/editor/ai/InlineAiTrigger.js.map +1 -1
  138. package/dist/editor/ai/MediaImage.js +40 -8
  139. package/dist/editor/ai/MediaImage.js.map +1 -1
  140. package/dist/editor/ai/SpawnedAgentsPanel.js +10 -12
  141. package/dist/editor/ai/SpawnedAgentsPanel.js.map +1 -1
  142. package/dist/editor/ai/ToolCallDisplay.d.ts +22 -2
  143. package/dist/editor/ai/ToolCallDisplay.js +542 -150
  144. package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
  145. package/dist/editor/ai/agentDiagnostics.d.ts +7 -0
  146. package/dist/editor/ai/agentDiagnostics.js.map +1 -1
  147. package/dist/editor/ai/dialogs/AgentDialogHandler.d.ts +1 -8
  148. package/dist/editor/ai/dialogs/AgentDialogHandler.js +379 -42
  149. package/dist/editor/ai/dialogs/AgentDialogHandler.js.map +1 -1
  150. package/dist/editor/ai/dialogs/QuestionnaireInline.d.ts +5 -1
  151. package/dist/editor/ai/dialogs/QuestionnaireInline.js +628 -60
  152. package/dist/editor/ai/dialogs/QuestionnaireInline.js.map +1 -1
  153. package/dist/editor/ai/dialogs/agentDialogTypes.d.ts +115 -0
  154. package/dist/editor/ai/dialogs/agentDialogTypes.js +2 -0
  155. package/dist/editor/ai/dialogs/agentDialogTypes.js.map +1 -1
  156. package/dist/editor/ai/types.d.ts +3 -1
  157. package/dist/editor/ai/useAgentStatus.d.ts +2 -1
  158. package/dist/editor/ai/useAgentStatus.js +90 -100
  159. package/dist/editor/ai/useAgentStatus.js.map +1 -1
  160. package/dist/editor/ai/useInlineAiPosition.js +45 -5
  161. package/dist/editor/ai/useInlineAiPosition.js.map +1 -1
  162. package/dist/editor/client/AboutDialog.js +4 -2
  163. package/dist/editor/client/AboutDialog.js.map +1 -1
  164. package/dist/editor/client/EditorShell.d.ts +4 -1
  165. package/dist/editor/client/EditorShell.js +770 -237
  166. package/dist/editor/client/EditorShell.js.map +1 -1
  167. package/dist/editor/client/editContext.d.ts +33 -19
  168. package/dist/editor/client/editContext.js.map +1 -1
  169. package/dist/editor/client/helpers.js +6 -0
  170. package/dist/editor/client/helpers.js.map +1 -1
  171. package/dist/editor/client/hooks/useEditorUrlSync.js +1 -2
  172. package/dist/editor/client/hooks/useEditorUrlSync.js.map +1 -1
  173. package/dist/editor/client/hooks/useEditorWebSocket.d.ts +10 -0
  174. package/dist/editor/client/hooks/useEditorWebSocket.js +209 -14
  175. package/dist/editor/client/hooks/useEditorWebSocket.js.map +1 -1
  176. package/dist/editor/client/hooks/useQuota.d.ts +8 -0
  177. package/dist/editor/client/hooks/useQuota.js.map +1 -1
  178. package/dist/editor/client/hooks/useSocketMessageHandler.js +73 -15
  179. package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
  180. package/dist/editor/client/itemsRepository.js +10 -6
  181. package/dist/editor/client/itemsRepository.js.map +1 -1
  182. package/dist/editor/client/navigation.js +35 -3
  183. package/dist/editor/client/navigation.js.map +1 -1
  184. package/dist/editor/client/operations.d.ts +6 -3
  185. package/dist/editor/client/operations.js +208 -30
  186. package/dist/editor/client/operations.js.map +1 -1
  187. package/dist/editor/client/pageModelBuilder.js +4 -31
  188. package/dist/editor/client/pageModelBuilder.js.map +1 -1
  189. package/dist/editor/client/ui/DevModeIndicator.js +2 -2
  190. package/dist/editor/client/ui/DevModeIndicator.js.map +1 -1
  191. package/dist/editor/client/ui/EditorChrome.d.ts +0 -6
  192. package/dist/editor/client/ui/EditorChrome.js +55 -72
  193. package/dist/editor/client/ui/EditorChrome.js.map +1 -1
  194. package/dist/editor/client/ui/FullscreenControls.js +5 -3
  195. package/dist/editor/client/ui/FullscreenControls.js.map +1 -1
  196. package/dist/editor/commands/commands.d.ts +11 -1
  197. package/dist/editor/commands/commands.js +12 -1
  198. package/dist/editor/commands/commands.js.map +1 -1
  199. package/dist/editor/commands/componentCommands.js +109 -55
  200. package/dist/editor/commands/componentCommands.js.map +1 -1
  201. package/dist/editor/commands/customCommandConverter.d.ts +8 -1
  202. package/dist/editor/commands/customCommandConverter.js +35 -5
  203. package/dist/editor/commands/customCommandConverter.js.map +1 -1
  204. package/dist/editor/commands/handlers/agentHandler.js +2 -1
  205. package/dist/editor/commands/handlers/agentHandler.js.map +1 -1
  206. package/dist/editor/commands/itemCommands.d.ts +3 -0
  207. package/dist/editor/commands/itemCommands.js +93 -10
  208. package/dist/editor/commands/itemCommands.js.map +1 -1
  209. package/dist/editor/commands/undo.d.ts +9 -15
  210. package/dist/editor/commands/undo.js +24 -0
  211. package/dist/editor/commands/undo.js.map +1 -1
  212. package/dist/editor/context-menu/InsertMenu.js +83 -39
  213. package/dist/editor/context-menu/InsertMenu.js.map +1 -1
  214. package/dist/editor/field-types/MultiLineText.js +1 -1
  215. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  216. package/dist/editor/field-types/RawEditor.js +1 -1
  217. package/dist/editor/field-types/RichTextEditor.js +13 -5
  218. package/dist/editor/field-types/RichTextEditor.js.map +1 -1
  219. package/dist/editor/field-types/RichTextEditorComponent.js +37 -3
  220. package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
  221. package/dist/editor/field-types/SingleLineText.js +1 -1
  222. package/dist/editor/field-types/TreeListEditor.js +3 -2
  223. package/dist/editor/field-types/TreeListEditor.js.map +1 -1
  224. package/dist/editor/field-types/richtext/components/ReactSlate.css +23 -5
  225. package/dist/editor/field-types/richtext/components/ReactSlate.d.ts +2 -0
  226. package/dist/editor/field-types/richtext/components/ReactSlate.js +28 -4
  227. package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
  228. package/dist/editor/field-types/richtext/components/ToolbarButton.js +4 -2
  229. package/dist/editor/field-types/richtext/components/ToolbarButton.js.map +1 -1
  230. package/dist/editor/field-types/richtext/contextMenuFactory.d.ts +13 -0
  231. package/dist/editor/field-types/richtext/contextMenuFactory.js +181 -24
  232. package/dist/editor/field-types/richtext/contextMenuFactory.js.map +1 -1
  233. package/dist/editor/field-types/richtext/types.d.ts +2 -0
  234. package/dist/editor/field-types/richtext/types.js.map +1 -1
  235. package/dist/editor/field-types/richtext/utils/plugins.js +4 -0
  236. package/dist/editor/field-types/richtext/utils/plugins.js.map +1 -1
  237. package/dist/editor/field-types/textContextMenuFactory.js +3 -2
  238. package/dist/editor/field-types/textContextMenuFactory.js.map +1 -1
  239. package/dist/editor/media-selector/AiImageSearchPrompt.js +4 -2
  240. package/dist/editor/media-selector/AiImageSearchPrompt.js.map +1 -1
  241. package/dist/editor/media-selector/MediaFolderBrowser.js +1 -1
  242. package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
  243. package/dist/editor/media-selector/MediaSelector.js +7 -1
  244. package/dist/editor/media-selector/MediaSelector.js.map +1 -1
  245. package/dist/editor/media-selector/TreeSelector.js +40 -35
  246. package/dist/editor/media-selector/TreeSelector.js.map +1 -1
  247. package/dist/editor/menubar/ActiveUsers.js +1 -1
  248. package/dist/editor/menubar/ActiveUsers.js.map +1 -1
  249. package/dist/editor/menubar/GenericToolbar.js +4 -2
  250. package/dist/editor/menubar/GenericToolbar.js.map +1 -1
  251. package/dist/editor/menubar/ItemLanguageVersion.js +2 -2
  252. package/dist/editor/menubar/ItemLanguageVersion.js.map +1 -1
  253. package/dist/editor/menubar/PageSelector.js +26 -147
  254. package/dist/editor/menubar/PageSelector.js.map +1 -1
  255. package/dist/editor/menubar/Separator.js +1 -1
  256. package/dist/editor/menubar/VersionSelector.js +2 -4
  257. package/dist/editor/menubar/VersionSelector.js.map +1 -1
  258. package/dist/editor/menubar/WorkflowButton.js +39 -12
  259. package/dist/editor/menubar/WorkflowButton.js.map +1 -1
  260. package/dist/editor/menubar/toolbar-sections/CustomCommandsToolbar.js +16 -38
  261. package/dist/editor/menubar/toolbar-sections/CustomCommandsToolbar.js.map +1 -1
  262. package/dist/editor/menubar/toolbar-sections/EditControls.js +3 -3
  263. package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
  264. package/dist/editor/menubar/toolbar-sections/HelpButton.js +1 -0
  265. package/dist/editor/menubar/toolbar-sections/HelpButton.js.map +1 -1
  266. package/dist/editor/menubar/toolbar-sections/ManualBrowser.d.ts +6 -10
  267. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +597 -220
  268. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js.map +1 -1
  269. package/dist/editor/menubar/toolbar-sections/UtilityControls.js +13 -2
  270. package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
  271. package/dist/editor/page-editor-chrome/CommentHighlighting.js +42 -1
  272. package/dist/editor/page-editor-chrome/CommentHighlighting.js.map +1 -1
  273. package/dist/editor/page-editor-chrome/FrameMenu.js +1 -1
  274. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  275. package/dist/editor/page-editor-chrome/InlineEditor.js +97 -48
  276. package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
  277. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +38 -17
  278. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
  279. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +17 -11
  280. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js.map +1 -1
  281. package/dist/editor/page-editor-chrome/useInlineAICompletion.js +301 -301
  282. package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -1
  283. package/dist/editor/page-viewer/DeviceToolbar.js +1 -1
  284. package/dist/editor/page-viewer/DeviceToolbar.js.map +1 -1
  285. package/dist/editor/page-viewer/EditorForm.js +69 -11
  286. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  287. package/dist/editor/page-viewer/MiniMap.d.ts +2 -4
  288. package/dist/editor/page-viewer/MiniMap.js +91 -28
  289. package/dist/editor/page-viewer/MiniMap.js.map +1 -1
  290. package/dist/editor/page-viewer/PageViewer.d.ts +3 -1
  291. package/dist/editor/page-viewer/PageViewer.js +92 -19
  292. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  293. package/dist/editor/page-viewer/PageViewerFrame.d.ts +2 -1
  294. package/dist/editor/page-viewer/PageViewerFrame.js +348 -115
  295. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  296. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js +114 -49
  297. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js.map +1 -1
  298. package/dist/editor/page-viewer/pageViewContext.d.ts +1 -0
  299. package/dist/editor/page-viewer/pageViewContext.js +51 -14
  300. package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
  301. package/dist/editor/pageModel.d.ts +14 -1
  302. package/dist/editor/reviews/Comment.js +26 -12
  303. package/dist/editor/reviews/Comment.js.map +1 -1
  304. package/dist/editor/reviews/CommentDisplayPopover.js +7 -5
  305. package/dist/editor/reviews/CommentDisplayPopover.js.map +1 -1
  306. package/dist/editor/reviews/CommentView.js +19 -4
  307. package/dist/editor/reviews/CommentView.js.map +1 -1
  308. package/dist/editor/reviews/Comments.js +89 -72
  309. package/dist/editor/reviews/Comments.js.map +1 -1
  310. package/dist/editor/reviews/CreateReviewDialog.js +281 -177
  311. package/dist/editor/reviews/CreateReviewDialog.js.map +1 -1
  312. package/dist/editor/reviews/DecisionsMatrix.js +96 -25
  313. package/dist/editor/reviews/DecisionsMatrix.js.map +1 -1
  314. package/dist/editor/reviews/DiffView.js +7 -14
  315. package/dist/editor/reviews/DiffView.js.map +1 -1
  316. package/dist/editor/reviews/EditReviewSettingsDialog.js +6 -4
  317. package/dist/editor/reviews/EditReviewSettingsDialog.js.map +1 -1
  318. package/dist/editor/reviews/MultiReviewManager.js +25 -3
  319. package/dist/editor/reviews/MultiReviewManager.js.map +1 -1
  320. package/dist/editor/reviews/PagesPanel.js +31 -15
  321. package/dist/editor/reviews/PagesPanel.js.map +1 -1
  322. package/dist/editor/reviews/PreviewInfo.js +1 -4
  323. package/dist/editor/reviews/PreviewInfo.js.map +1 -1
  324. package/dist/editor/reviews/ReviewCard.js +13 -7
  325. package/dist/editor/reviews/ReviewCard.js.map +1 -1
  326. package/dist/editor/reviews/ReviewDetail.js +3 -2
  327. package/dist/editor/reviews/ReviewDetail.js.map +1 -1
  328. package/dist/editor/reviews/ReviewsList.js +7 -3
  329. package/dist/editor/reviews/ReviewsList.js.map +1 -1
  330. package/dist/editor/reviews/SuggestedEdit.js +34 -3
  331. package/dist/editor/reviews/SuggestedEdit.js.map +1 -1
  332. package/dist/editor/reviews/SuggestionDisplayPopover.js +31 -5
  333. package/dist/editor/reviews/SuggestionDisplayPopover.js.map +1 -1
  334. package/dist/editor/reviews/commentAi.js +25 -6
  335. package/dist/editor/reviews/commentAi.js.map +1 -1
  336. package/dist/editor/reviews/reviewCommands.js +4 -1
  337. package/dist/editor/reviews/reviewCommands.js.map +1 -1
  338. package/dist/editor/reviews/useMultiReview.js +2 -2
  339. package/dist/editor/reviews/useMultiReview.js.map +1 -1
  340. package/dist/editor/reviews/useReviews.d.ts +2 -2
  341. package/dist/editor/reviews/useReviews.js +12 -30
  342. package/dist/editor/reviews/useReviews.js.map +1 -1
  343. package/dist/editor/services/agentErrorMessage.d.ts +1 -0
  344. package/dist/editor/services/agentErrorMessage.js +91 -0
  345. package/dist/editor/services/agentErrorMessage.js.map +1 -0
  346. package/dist/editor/services/agentService.d.ts +229 -5
  347. package/dist/editor/services/agentService.js +292 -39
  348. package/dist/editor/services/agentService.js.map +1 -1
  349. package/dist/editor/services/agentStatus.d.ts +1 -0
  350. package/dist/editor/services/agentStatus.js +19 -0
  351. package/dist/editor/services/agentStatus.js.map +1 -1
  352. package/dist/editor/services/aiService.d.ts +57 -1
  353. package/dist/editor/services/aiService.js +79 -6
  354. package/dist/editor/services/aiService.js.map +1 -1
  355. package/dist/editor/services/contentService.d.ts +6 -3
  356. package/dist/editor/services/contentService.js +13 -12
  357. package/dist/editor/services/contentService.js.map +1 -1
  358. package/dist/editor/services/editService.d.ts +52 -1
  359. package/dist/editor/services/editService.js +94 -2
  360. package/dist/editor/services/editService.js.map +1 -1
  361. package/dist/editor/services/indexService.js +1 -1
  362. package/dist/editor/services/indexService.js.map +1 -1
  363. package/dist/editor/services/reviewsService.d.ts +3 -6
  364. package/dist/editor/services/reviewsService.js +2 -11
  365. package/dist/editor/services/reviewsService.js.map +1 -1
  366. package/dist/editor/services/serviceHelper.d.ts +2 -1
  367. package/dist/editor/services/serviceHelper.js +112 -20
  368. package/dist/editor/services/serviceHelper.js.map +1 -1
  369. package/dist/editor/services/systemService.d.ts +2 -1
  370. package/dist/editor/services/systemService.js +3 -0
  371. package/dist/editor/services/systemService.js.map +1 -1
  372. package/dist/editor/services-server/api.d.ts +1 -2
  373. package/dist/editor/services-server/api.js +11 -6
  374. package/dist/editor/services-server/api.js.map +1 -1
  375. package/dist/editor/settings/About.js +317 -3
  376. package/dist/editor/settings/About.js.map +1 -1
  377. package/dist/editor/settings/QuotaInfo.js +210 -4
  378. package/dist/editor/settings/QuotaInfo.js.map +1 -1
  379. package/dist/editor/settings/SettingsView.js +25 -23
  380. package/dist/editor/settings/SettingsView.js.map +1 -1
  381. package/dist/editor/settings/Status.js +7 -6
  382. package/dist/editor/settings/Status.js.map +1 -1
  383. package/dist/editor/settings/index/useIndexStatus.js +20 -22
  384. package/dist/editor/settings/index/useIndexStatus.js.map +1 -1
  385. package/dist/editor/settings/panels/AgentsPanel.d.ts +0 -4
  386. package/dist/editor/settings/panels/AgentsPanel.js +95 -121
  387. package/dist/editor/settings/panels/AgentsPanel.js.map +1 -1
  388. package/dist/editor/settings/panels/ModelsPanel.js +329 -108
  389. package/dist/editor/settings/panels/ModelsPanel.js.map +1 -1
  390. package/dist/editor/settings/panels/ProvidersPanel.d.ts +1 -1
  391. package/dist/editor/settings/panels/ProvidersPanel.js +86 -59
  392. package/dist/editor/settings/panels/ProvidersPanel.js.map +1 -1
  393. package/dist/editor/settings/panels/SearchConfigPanel.js +4 -4
  394. package/dist/editor/settings/panels/SearchConfigPanel.js.map +1 -1
  395. package/dist/editor/settings/panels/index.d.ts +3 -2
  396. package/dist/editor/settings/panels/index.js +3 -2
  397. package/dist/editor/settings/panels/index.js.map +1 -1
  398. package/dist/editor/settings/status/coreStatusChecks.js +124 -19
  399. package/dist/editor/settings/status/coreStatusChecks.js.map +1 -1
  400. package/dist/editor/settings/status/useStartupChecks.d.ts +3 -1
  401. package/dist/editor/settings/status/useStartupChecks.js +9 -5
  402. package/dist/editor/settings/status/useStartupChecks.js.map +1 -1
  403. package/dist/editor/setup-wizard/steps/CompleteStep.d.ts +2 -1
  404. package/dist/editor/setup-wizard/steps/CompleteStep.js +2 -1
  405. package/dist/editor/setup-wizard/steps/CompleteStep.js.map +1 -1
  406. package/dist/editor/sidebar/ComponentPalette.js +2 -1
  407. package/dist/editor/sidebar/ComponentPalette.js.map +1 -1
  408. package/dist/editor/sidebar/ComponentTree.d.ts +8 -1
  409. package/dist/editor/sidebar/ComponentTree.js +216 -69
  410. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  411. package/dist/editor/sidebar/EditHistory.js +22 -46
  412. package/dist/editor/sidebar/EditHistory.js.map +1 -1
  413. package/dist/editor/sidebar/Favorites.js +4 -8
  414. package/dist/editor/sidebar/Favorites.js.map +1 -1
  415. package/dist/editor/sidebar/MainContentTree.js +4 -3
  416. package/dist/editor/sidebar/MainContentTree.js.map +1 -1
  417. package/dist/editor/sidebar/OperationItem.js +21 -7
  418. package/dist/editor/sidebar/OperationItem.js.map +1 -1
  419. package/dist/editor/sidebar/SidebarPanel.d.ts +3 -1
  420. package/dist/editor/sidebar/SidebarPanel.js +44 -12
  421. package/dist/editor/sidebar/SidebarPanel.js.map +1 -1
  422. package/dist/editor/sidebar/SidebarStack.d.ts +2 -1
  423. package/dist/editor/sidebar/SidebarStack.js +4 -3
  424. package/dist/editor/sidebar/SidebarStack.js.map +1 -1
  425. package/dist/editor/sidebar/Validation.js +24 -12
  426. package/dist/editor/sidebar/Validation.js.map +1 -1
  427. package/dist/editor/sidebar/Workbox.js +53 -3
  428. package/dist/editor/sidebar/Workbox.js.map +1 -1
  429. package/dist/editor/sidebar/WorkspaceRail.d.ts +0 -1
  430. package/dist/editor/sidebar/WorkspaceRail.js +56 -167
  431. package/dist/editor/sidebar/WorkspaceRail.js.map +1 -1
  432. package/dist/editor/tree-indicators/GutterColumns.d.ts +3 -1
  433. package/dist/editor/tree-indicators/GutterColumns.js +26 -5
  434. package/dist/editor/tree-indicators/GutterColumns.js.map +1 -1
  435. package/dist/editor/tree-indicators/GutterContext.d.ts +4 -0
  436. package/dist/editor/tree-indicators/GutterContext.js +23 -0
  437. package/dist/editor/tree-indicators/GutterContext.js.map +1 -1
  438. package/dist/editor/tree-indicators/index.d.ts +0 -1
  439. package/dist/editor/tree-indicators/index.js +0 -1
  440. package/dist/editor/tree-indicators/index.js.map +1 -1
  441. package/dist/editor/tree-indicators/types.d.ts +12 -1
  442. package/dist/editor/ui/CopyMoveTargetSelectorDialog.js +1 -1
  443. package/dist/editor/ui/CopyMoveTargetSelectorDialog.js.map +1 -1
  444. package/dist/editor/ui/Icons.js +1 -1
  445. package/dist/editor/ui/Icons.js.map +1 -1
  446. package/dist/editor/ui/ItemNameDialogNew.d.ts +2 -0
  447. package/dist/editor/ui/ItemNameDialogNew.js +33 -17
  448. package/dist/editor/ui/ItemNameDialogNew.js.map +1 -1
  449. package/dist/editor/ui/ItemSearch.js +7 -11
  450. package/dist/editor/ui/ItemSearch.js.map +1 -1
  451. package/dist/editor/ui/SimpleIconButton.js +1 -1
  452. package/dist/editor/ui/SimpleIconButton.js.map +1 -1
  453. package/dist/editor/ui/SimpleTabs.d.ts +1 -0
  454. package/dist/editor/ui/SimpleTabs.js +45 -25
  455. package/dist/editor/ui/SimpleTabs.js.map +1 -1
  456. package/dist/editor/ui/Splitter.d.ts +1 -0
  457. package/dist/editor/ui/Splitter.js +102 -86
  458. package/dist/editor/ui/Splitter.js.map +1 -1
  459. package/dist/editor/ui/TemplateSelectorDialog.js +4 -4
  460. package/dist/editor/ui/TemplateSelectorDialog.js.map +1 -1
  461. package/dist/editor/ui/TreeListSelector.d.ts +6 -1
  462. package/dist/editor/ui/TreeListSelector.js +2 -2
  463. package/dist/editor/ui/TreeListSelector.js.map +1 -1
  464. package/dist/editor/utils/keyboardNavigation.d.ts +6 -20
  465. package/dist/editor/utils/keyboardNavigation.js +48 -140
  466. package/dist/editor/utils/keyboardNavigation.js.map +1 -1
  467. package/dist/editor/utils.js +19 -9
  468. package/dist/editor/utils.js.map +1 -1
  469. package/dist/editor/views/CompareView.d.ts +3 -1
  470. package/dist/editor/views/CompareView.js +7 -5
  471. package/dist/editor/views/CompareView.js.map +1 -1
  472. package/dist/editor/views/EditView.js +1 -1
  473. package/dist/editor/views/EditView.js.map +1 -1
  474. package/dist/editor/views/EditorSlot.js +27 -34
  475. package/dist/editor/views/EditorSlot.js.map +1 -1
  476. package/dist/editor/views/ItemEditor.js +7 -3
  477. package/dist/editor/views/ItemEditor.js.map +1 -1
  478. package/dist/editor/views/MediaFolderEditView.js +1 -1
  479. package/dist/editor/views/MediaFolderEditView.js.map +1 -1
  480. package/dist/editor/views/ParheliaView.js +5 -6
  481. package/dist/editor/views/ParheliaView.js.map +1 -1
  482. package/dist/editor/views/SingleEditView.d.ts +2 -1
  483. package/dist/editor/views/SingleEditView.js +10 -8
  484. package/dist/editor/views/SingleEditView.js.map +1 -1
  485. package/dist/editor/views/editorSlotContext.js +35 -6
  486. package/dist/editor/views/editorSlotContext.js.map +1 -1
  487. package/dist/index.d.ts +16 -2
  488. package/dist/index.js +11 -0
  489. package/dist/index.js.map +1 -1
  490. package/dist/revision.d.ts +2 -2
  491. package/dist/revision.js +2 -2
  492. package/dist/setup/services/setupWizardService.d.ts +40 -13
  493. package/dist/setup/services/setupWizardService.js +32 -17
  494. package/dist/setup/services/setupWizardService.js.map +1 -1
  495. package/dist/setup/wizard/steps/AddModelDialog.js +12 -3
  496. package/dist/setup/wizard/steps/AddModelDialog.js.map +1 -1
  497. package/dist/setup/wizard/steps/ImportModelDialog.js +39 -22
  498. package/dist/setup/wizard/steps/ImportModelDialog.js.map +1 -1
  499. package/dist/splash-screen/ModernSplashScreen.js +112 -32
  500. package/dist/splash-screen/ModernSplashScreen.js.map +1 -1
  501. package/dist/splash-screen/NewPage.js +33 -50
  502. package/dist/splash-screen/NewPage.js.map +1 -1
  503. package/dist/splash-screen/OpenPage.js +2 -6
  504. package/dist/splash-screen/OpenPage.js.map +1 -1
  505. package/dist/splash-screen/ParheliaAssistantChat.js +12 -29
  506. package/dist/splash-screen/ParheliaAssistantChat.js.map +1 -1
  507. package/dist/splash-screen/ParheliaLogo.js +87 -37
  508. package/dist/splash-screen/ParheliaLogo.js.map +1 -1
  509. package/dist/splash-screen/RecentPages.js +3 -3
  510. package/dist/splash-screen/RecentPages.js.map +1 -1
  511. package/dist/tour/Tour.d.ts +2 -1
  512. package/dist/tour/Tour.js +256 -75
  513. package/dist/tour/Tour.js.map +1 -1
  514. package/dist/tour/default-tour.js +222 -96
  515. package/dist/tour/default-tour.js.map +1 -1
  516. package/dist/types.d.ts +63 -29
  517. package/package.json +19 -15
  518. package/styles.css +39 -10
  519. package/dist/editor/ComponentInfo.d.ts +0 -4
  520. package/dist/editor/ComponentInfo.js +0 -41
  521. package/dist/editor/ComponentInfo.js.map +0 -1
  522. package/dist/editor/ai/HelpTerminal.d.ts +0 -5
  523. package/dist/editor/ai/HelpTerminal.js +0 -166
  524. package/dist/editor/ai/HelpTerminal.js.map +0 -1
  525. package/dist/editor/field-types/ReactQuill.d.ts +0 -125
  526. package/dist/editor/field-types/ReactQuill.js +0 -385
  527. package/dist/editor/field-types/ReactQuill.js.map +0 -1
  528. package/dist/editor/services-server/graphQL.d.ts +0 -29
  529. package/dist/editor/services-server/graphQL.js +0 -53
  530. package/dist/editor/services-server/graphQL.js.map +0 -1
  531. package/dist/editor/settings/AllAgentsPanel.d.ts +0 -5
  532. package/dist/editor/settings/AllAgentsPanel.js +0 -139
  533. package/dist/editor/settings/AllAgentsPanel.js.map +0 -1
  534. package/dist/editor/settings/LatestFeedback.d.ts +0 -1
  535. package/dist/editor/settings/LatestFeedback.js +0 -136
  536. package/dist/editor/settings/LatestFeedback.js.map +0 -1
  537. package/dist/editor/settings/Setup.d.ts +0 -1
  538. package/dist/editor/settings/Setup.js +0 -211
  539. package/dist/editor/settings/Setup.js.map +0 -1
  540. package/dist/editor/settings/panels/DatabasePanel.d.ts +0 -6
  541. package/dist/editor/settings/panels/DatabasePanel.js +0 -50
  542. package/dist/editor/settings/panels/DatabasePanel.js.map +0 -1
  543. package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.d.ts +0 -2
  544. package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.js +0 -195
  545. package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.js.map +0 -1
  546. package/dist/editor/settings/setup-steps/AiSetupStep/index.d.ts +0 -2
  547. package/dist/editor/settings/setup-steps/AiSetupStep/index.js +0 -21
  548. package/dist/editor/settings/setup-steps/AiSetupStep/index.js.map +0 -1
  549. package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.d.ts +0 -1
  550. package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.js +0 -233
  551. package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.js.map +0 -1
  552. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.d.ts +0 -15
  553. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.js +0 -14
  554. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.js.map +0 -1
  555. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.d.ts +0 -1
  556. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.js +0 -94
  557. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.js.map +0 -1
  558. package/dist/editor/settings/setup-steps/AiSetupStep/types.d.ts +0 -1
  559. package/dist/editor/settings/setup-steps/AiSetupStep/types.js +0 -2
  560. package/dist/editor/settings/setup-steps/AiSetupStep/types.js.map +0 -1
  561. package/dist/editor/settings/setup-steps/AiSetupStep/utils.d.ts +0 -5
  562. package/dist/editor/settings/setup-steps/AiSetupStep/utils.js +0 -44
  563. package/dist/editor/settings/setup-steps/AiSetupStep/utils.js.map +0 -1
  564. package/dist/editor/settings/setup-steps/IndexSetupStep.d.ts +0 -2
  565. package/dist/editor/settings/setup-steps/IndexSetupStep.js +0 -36
  566. package/dist/editor/settings/setup-steps/IndexSetupStep.js.map +0 -1
  567. package/dist/editor/settings/setup-steps/SettingsSetupStep.d.ts +0 -2
  568. package/dist/editor/settings/setup-steps/SettingsSetupStep.js +0 -111
  569. package/dist/editor/settings/setup-steps/SettingsSetupStep.js.map +0 -1
  570. package/dist/editor/settings/setup-steps/SetupOverview.d.ts +0 -14
  571. package/dist/editor/settings/setup-steps/SetupOverview.js +0 -38
  572. package/dist/editor/settings/setup-steps/SetupOverview.js.map +0 -1
  573. package/dist/editor/sidebar/Debug.d.ts +0 -1
  574. package/dist/editor/sidebar/Debug.js +0 -70
  575. package/dist/editor/sidebar/Debug.js.map +0 -1
  576. package/dist/editor/sidebar/GraphQL.d.ts +0 -2
  577. package/dist/editor/sidebar/GraphQL.js +0 -234
  578. package/dist/editor/sidebar/GraphQL.js.map +0 -1
  579. package/dist/editor/sidebar/LeftToolbar.d.ts +0 -1
  580. package/dist/editor/sidebar/LeftToolbar.js +0 -12
  581. package/dist/editor/sidebar/LeftToolbar.js.map +0 -1
  582. package/dist/editor/sidebar/NavigationSidebar.d.ts +0 -4
  583. package/dist/editor/sidebar/NavigationSidebar.js +0 -254
  584. package/dist/editor/sidebar/NavigationSidebar.js.map +0 -1
  585. package/dist/editor/tree-indicators/GutterSelector.d.ts +0 -5
  586. package/dist/editor/tree-indicators/GutterSelector.js +0 -91
  587. package/dist/editor/tree-indicators/GutterSelector.js.map +0 -1
@@ -1,8 +1,11 @@
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
- import { Send, AlertCircle, Loader2, User, Wand2, Square, Mic, MicOff, ChevronDown, ChevronUp, ListTodo, ArrowLeft, DollarSign, } from "lucide-react";
4
- import { getAgent, startAgent, updateAgentSettings, updateAgentCostLimit, updateAgentContext, cancelAgent, canonicalizeAgentMetadata, getPendingPrompts, } from "../services/agentService";
3
+ import { flushSync } from "react-dom";
4
+ import { Send, AlertCircle, Loader2, User, Wand2, Square, Mic, MicOff, ChevronDown, ChevronUp, ListTodo, ArrowLeft, DollarSign, ExternalLink, Settings2, Target, X, Plus, } from "lucide-react";
5
+ import { getAgent, startAgent, claimAgentBrowser, assignAgentSkill, persistDraftAgent, updateAgentSettings, updateAgentCostLimit, updateAgentContext, getAgentSkillCatalog, getAgentAvailableTools, getAgentOperationAllowances, getAgentTriggerSubscriptions, cancelAgent, canonicalizeAgentMetadata, getPendingPrompts, releaseAgentBrowser, revokeAgentSkill, } from "../services/agentService";
6
+ import { parseAgentStatus } from "../services/agentStatus";
5
7
  import { useEditContext, useFieldsEditContext } from "../client/editContext";
8
+ import { localStorageService } from "../services/localStorageService";
6
9
  import { Textarea } from "../../components/ui/textarea";
7
10
  import { Button } from "../../components/ui/button";
8
11
  import { PlaceholderInput, } from "../../components/ui/PlaceholderInput";
@@ -12,21 +15,145 @@ import { AgentDocumentList, } from "./AgentDocumentList";
12
15
  import { AgentEditOperationsPanel } from "./EditOperationsPanel";
13
16
  import { SpawnedAgentsPanel } from "./SpawnedAgentsPanel";
14
17
  import { getComponentById } from "../componentTreeHelper";
18
+ import { toUserFacingAgentErrorMessage } from "../services/agentErrorMessage";
15
19
  import { AgentGreeting } from "./AgentGreeting";
16
20
  import { getAgentHistory } from "../services/editService";
21
+ import { QuestionnaireInline } from "./dialogs/QuestionnaireInline";
22
+ import { getBrowserCaptureClaim, setBrowserCaptureClaim, } from "./dialogs/browserBoundCapture";
23
+ import { DIALOG_TYPES, } from "./dialogs/agentDialogTypes";
17
24
  import { Popover, PopoverContent, PopoverTrigger, } from "../../components/ui/popover";
18
25
  import { SecretAgentIcon } from "../ui/Icons";
19
26
  import { formatTime, formatDateTime } from "../utils";
20
27
  import { cn } from "../../lib/utils";
28
+ import { sanitizeSvg } from "../../lib/sanitize";
21
29
  import { Select } from "../../components/ui/select";
22
30
  import { AgentTerminalStatusBar } from "./AgentTerminalStatusBar";
23
- import { useMediaQuery } from "../client/hooks/useMediaQuery";
24
31
  import { SimpleTabs } from "../ui/SimpleTabs";
32
+ import { Splitter } from "../ui/Splitter";
33
+ import { ScrollingContentTree } from "../ScrollingContentTree";
34
+ import { MarkdownDisplay, } from "../../components/MarkdownDisplay";
35
+ const userMessageMarkdownComponents = {
36
+ h1: (props) => (_jsx("h1", { ...props, className: "mb-2 text-sm leading-5 font-semibold text-gray-900" })),
37
+ h2: (props) => (_jsx("h2", { ...props, className: "mb-1.5 text-[13px] leading-5 font-semibold text-gray-900" })),
38
+ h3: (props) => (_jsx("h3", { ...props, className: "mb-1 text-[12px] leading-5 font-semibold text-gray-900" })),
39
+ h4: (props) => (_jsx("h4", { ...props, className: "mb-1 text-[12px] leading-5 font-medium text-gray-800" })),
40
+ p: (props) => (_jsx("p", { ...props, className: "my-1 text-[12px] leading-5 text-gray-700" })),
41
+ ul: (props) => (_jsx("ul", { ...props, className: "my-2 ml-5 list-disc space-y-1 text-[12px] leading-5 text-gray-700" })),
42
+ ol: (props) => (_jsx("ol", { ...props, className: "my-2 ml-5 list-decimal space-y-1 text-[12px] leading-5 text-gray-700" })),
43
+ li: (props) => (_jsx("li", { ...props, className: "text-[12px] leading-5 text-gray-700" })),
44
+ pre: (props) => (_jsx("pre", { ...props, className: "my-2 overflow-auto rounded-md bg-slate-100 px-3 py-2 text-[11px] leading-4 text-slate-700" })),
45
+ code: ({ inline, className, ...props }) => inline ? (_jsx("code", { ...props, className: "rounded bg-slate-100 px-1 py-0.5 text-[11px] text-slate-700" })) : (_jsx("code", { ...props, className: className })),
46
+ };
47
+ function buildPlaceholderAgentDetails(agentStub) {
48
+ const now = new Date().toISOString();
49
+ const updated = agentStub.updatedDate || now;
50
+ // AgentDetails has required fields, but some workspaces only pass an Agent stub initially.
51
+ // This placeholder keeps streaming/tool-call UI working until `getAgent()` returns full details.
52
+ return {
53
+ ...agentStub,
54
+ name: agentStub.name || "Agent",
55
+ userId: agentStub.userId || "",
56
+ updatedDate: updated,
57
+ profileName: agentStub.profileName || "",
58
+ model: agentStub.model || "",
59
+ createdDate: agentStub.createdDate || updated,
60
+ totalTokensUsed: 0,
61
+ totalInputTokens: 0,
62
+ totalOutputTokens: 0,
63
+ totalCachedInputTokens: 0,
64
+ totalInputTokenCost: 0,
65
+ totalOutputTokenCost: 0,
66
+ totalCachedInputTokenCost: 0,
67
+ totalImageCost: 0,
68
+ totalCost: 0,
69
+ currency: agentStub.currency || "USD",
70
+ messageCount: agentStub.messageCount || 0,
71
+ };
72
+ }
73
+ function normalizeDialogAgentId(value) {
74
+ return value?.trim().toLowerCase() || "";
75
+ }
76
+ function formatAllowanceSource(source) {
77
+ const normalized = source?.trim();
78
+ if (!normalized)
79
+ return null;
80
+ if (normalized === "user")
81
+ return "User granted";
82
+ if (normalized.startsWith("preconfigured:profile:"))
83
+ return "Profile";
84
+ if (normalized.startsWith("preconfigured:skill:"))
85
+ return "Skill";
86
+ if (normalized.startsWith("system:")) {
87
+ return normalized.slice("system:".length).replace(/[-_]+/g, " ").trim();
88
+ }
89
+ return normalized;
90
+ }
91
+ function formatAllowanceLabel(allowance) {
92
+ return `${allowance.operationType || "*"}${"itemPath" in allowance
93
+ ? ` ${allowance.itemPath}`
94
+ : ` ${allowance.normalizedPath}`}`;
95
+ }
96
+ function getAgentRunMessageAgentId(payload) {
97
+ const agentId = payload?.agentId;
98
+ return typeof agentId === "string" ? normalizeDialogAgentId(agentId) : null;
99
+ }
100
+ function getAgentRunMessageSeq(payload) {
101
+ const seq = payload?.seq;
102
+ return typeof seq === "number" ? seq : null;
103
+ }
104
+ function getAgentRunMessageDetail(type, payload) {
105
+ if (type === "agent:run:delta") {
106
+ return payload?.type || null;
107
+ }
108
+ if (type === "agent:run:status") {
109
+ return payload?.data?.state || payload?.data?.status || null;
110
+ }
111
+ if (type === "agent:run:error") {
112
+ return payload?.error || null;
113
+ }
114
+ if (type === "agent:run:complete") {
115
+ return payload?.finalStatus || null;
116
+ }
117
+ if (type === "agent:run:start") {
118
+ return payload?.agentName || null;
119
+ }
120
+ return null;
121
+ }
122
+ function isHeartbeatRunEventMessage(message) {
123
+ if (!message)
124
+ return false;
125
+ if (message.type === "agent:run:delta") {
126
+ const deltaType = message.payload?.type;
127
+ return String(deltaType || "").toLowerCase() === "heartbeat";
128
+ }
129
+ if (message.type === "agent:run:status") {
130
+ const state = message.payload?.data?.state || message.payload?.data?.status;
131
+ return String(state || "").toLowerCase() === "heartbeat";
132
+ }
133
+ return false;
134
+ }
135
+ function getVisibleDialogRegistry() {
136
+ const registry = globalThis.__agentDialogVisibleCallbacks;
137
+ return registry && typeof registry === "object" ? registry : {};
138
+ }
139
+ function isAgentErrorStatusValue(status) {
140
+ return status === "error";
141
+ }
25
142
  // Simple user message component
26
143
  const UserMessage = ({ message }) => {
144
+ const content = message.content || "";
145
+ const [isTriggerExpanded, setIsTriggerExpanded] = useState(false);
146
+ // Trigger-sourced prompts are prefixed by backend as "[Trigger: {name}]: {content}"
147
+ const triggerPattern = /^\[Trigger: ([^\]]+)\]:\s*(.*)$/s;
148
+ const triggerMatch = content.match(triggerPattern);
149
+ const triggerName = triggerMatch?.[1]?.trim() || "";
150
+ const triggerContent = triggerMatch?.[2] || "";
151
+ const isTriggerMessage = triggerName.length > 0;
152
+ if (isTriggerMessage) {
153
+ return (_jsx("div", { className: "px-4 py-2", children: _jsxs("div", { className: "text-[11px]", children: [_jsxs("button", { type: "button", onClick: () => setIsTriggerExpanded((expanded) => !expanded), className: "text-theme-secondary hover:bg-theme-hover flex w-full items-center gap-2 rounded-md border-l-2 border-cyan-200 px-2 py-1 text-left transition-colors", "data-testid": "trigger-message-toggle", "data-expanded": isTriggerExpanded ? "true" : "false", children: [_jsx(Target, { className: "h-3.5 w-3.5 shrink-0", strokeWidth: 1.5 }), _jsxs("span", { className: "truncate font-medium", children: ["Trigger: ", triggerName] }), message.createdDate && (_jsx("span", { className: "ml-1 shrink-0 text-[10px] text-gray-400", children: formatTime(new Date(message.createdDate)) })), _jsx("span", { className: "ml-auto shrink-0", children: isTriggerExpanded ? (_jsx(ChevronUp, { className: "h-3.5 w-3.5" })) : (_jsx(ChevronDown, { className: "h-3.5 w-3.5" })) })] }), isTriggerExpanded && (_jsx("div", { className: "mt-1 border-l-2 border-cyan-100 pl-[1.35rem] text-[11px] text-gray-600", children: _jsx(MarkdownDisplay, { source: triggerContent, components: userMessageMarkdownComponents }) }))] }) }));
154
+ }
27
155
  // Parse source agent name from content if it starts with "[From ...]:"
28
156
  // Backend formats messages from other agents as "[From {sourceAgentName}]: {content}"
29
- const content = message.content || "";
30
157
  const fromPattern = /^\[From ([^\]]+)\]:\s*(.*)$/s;
31
158
  const match = content.match(fromPattern);
32
159
  let sourceAgentName;
@@ -51,7 +178,10 @@ const UserMessage = ({ message }) => {
51
178
  message.sourceAgent?.name;
52
179
  }
53
180
  const displayName = sourceAgentName ? `[From ${sourceAgentName}]` : "You";
54
- 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 })] })] }));
181
+ 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: _jsx(MarkdownDisplay, { source: displayContent, components: userMessageMarkdownComponents }) })] })] }));
182
+ };
183
+ const HeartbeatMessage = ({ message }) => {
184
+ 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)) }))] }) }));
55
185
  };
56
186
  // Helper to extract todos from potentially incomplete JSON during streaming
57
187
  const extractPartialTodos = (jsonText) => {
@@ -285,14 +415,6 @@ const extractTodosFromMessages = (messages) => {
285
415
  todoMap.set(key, todo);
286
416
  }
287
417
  const result = Array.from(todoMap.values());
288
- // Only log when duplicates were actually removed (to reduce noise)
289
- if (todos.length > result.length) {
290
- console.log("🟡 TODO_DEBUG deduplication:", {
291
- removed: todos.length - result.length,
292
- before: todos.length,
293
- after: result.length,
294
- });
295
- }
296
418
  return result;
297
419
  };
298
420
  // TodoListPanel component
@@ -300,12 +422,9 @@ const TodoListPanel = ({ messages, agentMetadata, }) => {
300
422
  const [isExpanded, setIsExpanded] = useState(true);
301
423
  // First try to get todos from agent metadata (real-time updates)
302
424
  // Server sends additionalData.todoList directly via contextChanged status
303
- // Also check top-level todoList for backward compatibility with stored contexts
304
425
  const metadataTodos = (() => {
305
426
  try {
306
- // Check both additionalData.todoList and top-level todoList (from [JsonExtensionData] serialization)
307
- const todoList = agentMetadata?.additionalData?.todoList ||
308
- agentMetadata?.todoList;
427
+ const todoList = agentMetadata?.additionalData?.todoList;
309
428
  if (todoList?.items && Array.isArray(todoList.items)) {
310
429
  const rawItems = todoList.items
311
430
  .map((item, idx) => ({
@@ -433,6 +552,17 @@ const groupConsecutiveMessages = (agentMessages) => {
433
552
  // Add user message
434
553
  groups.push({ type: "user", messages: [message] });
435
554
  }
555
+ else if (message.messageType === "heartbeat" ||
556
+ message.role === "system") {
557
+ if (currentAssistantGroup.length > 0) {
558
+ groups.push({
559
+ type: "assistant-group",
560
+ messages: currentAssistantGroup,
561
+ });
562
+ currentAssistantGroup = [];
563
+ }
564
+ groups.push({ type: "heartbeat", messages: [message] });
565
+ }
436
566
  else if (message.role === "assistant") {
437
567
  // Add to current assistant group
438
568
  currentAssistantGroup.push(message);
@@ -500,6 +630,7 @@ const calculateTotalTokens = (messages) => {
500
630
  outputCost: acc.outputCost + (message.outputTokenCost || 0),
501
631
  cachedCost: acc.cachedCost + (message.cachedInputTokenCost || 0),
502
632
  cacheWriteCost: acc.cacheWriteCost,
633
+ imageCost: acc.imageCost,
503
634
  totalCost: acc.totalCost + (message.totalCost || 0),
504
635
  };
505
636
  }, {
@@ -511,6 +642,7 @@ const calculateTotalTokens = (messages) => {
511
642
  outputCost: 0,
512
643
  cachedCost: 0,
513
644
  cacheWriteCost: 0,
645
+ imageCost: 0,
514
646
  totalCost: 0,
515
647
  });
516
648
  return totals;
@@ -521,6 +653,84 @@ const getOperationsForMessageGroup = (messages, agentOperations) => {
521
653
  const matched = agentOperations.filter((op) => op.toolCallId && toolCallIds.has(op.toolCallId));
522
654
  return matched;
523
655
  };
656
+ const stringifyToolField = (value) => {
657
+ if (value === undefined || value === null)
658
+ return undefined;
659
+ if (typeof value === "string") {
660
+ return value.trim().length > 0 ? value : undefined;
661
+ }
662
+ try {
663
+ return JSON.stringify(value);
664
+ }
665
+ catch {
666
+ return String(value);
667
+ }
668
+ };
669
+ const parseToolResultValue = (value) => {
670
+ if (value === undefined || value === null) {
671
+ return undefined;
672
+ }
673
+ if (typeof value === "object") {
674
+ return value;
675
+ }
676
+ if (typeof value !== "string") {
677
+ return String(value);
678
+ }
679
+ const trimmed = value.trim();
680
+ if (!trimmed) {
681
+ return undefined;
682
+ }
683
+ try {
684
+ let parsed = JSON.parse(trimmed);
685
+ if (typeof parsed === "string" &&
686
+ (parsed.startsWith("{") || parsed.startsWith("["))) {
687
+ parsed = JSON.parse(parsed);
688
+ }
689
+ if (parsed && typeof parsed === "object") {
690
+ return parsed;
691
+ }
692
+ }
693
+ catch {
694
+ // Fall back to the original string when the payload is plain text.
695
+ }
696
+ return value;
697
+ };
698
+ const getFirstToolCallEnvelope = (data) => {
699
+ if (!data || typeof data !== "object")
700
+ return undefined;
701
+ const direct = data.toolCall || data.tool_call;
702
+ if (direct)
703
+ return direct;
704
+ const arrayCandidates = [data.tool_calls, data.toolCalls];
705
+ for (const candidate of arrayCandidates) {
706
+ if (Array.isArray(candidate) && candidate.length > 0) {
707
+ return candidate[0];
708
+ }
709
+ }
710
+ return undefined;
711
+ };
712
+ const extractToolCallFields = (data) => {
713
+ const envelope = getFirstToolCallEnvelope(data);
714
+ const functionPayload = data?.function || envelope?.function;
715
+ const functionName = data?.functionName ||
716
+ data?.name ||
717
+ functionPayload?.name ||
718
+ envelope?.functionName ||
719
+ envelope?.name ||
720
+ "unknown";
721
+ const toolCallId = data?.toolCallId || data?.id || envelope?.id;
722
+ const functionArguments = stringifyToolField(data?.functionArguments) ||
723
+ stringifyToolField(data?.arguments) ||
724
+ stringifyToolField(functionPayload?.arguments) ||
725
+ stringifyToolField(envelope?.functionArguments) ||
726
+ stringifyToolField(envelope?.arguments) ||
727
+ "{}";
728
+ return {
729
+ toolCallId,
730
+ functionName,
731
+ functionArguments,
732
+ };
733
+ };
524
734
  // Convert agent messages to AI terminal format for a response group
525
735
  const convertAgentMessagesToAiFormat = (agentMessages) => {
526
736
  return agentMessages.map((agentMessage) => {
@@ -541,28 +751,39 @@ const convertAgentMessagesToAiFormat = (agentMessages) => {
541
751
  role: agentMessage.role,
542
752
  createdDate: agentMessage.createdDate,
543
753
  tool_calls: agentMessage.toolCalls
544
- ? agentMessage.toolCalls.map((toolCall) => ({
545
- id: toolCall.toolCallId,
546
- displayName: toolCall.functionName,
547
- function: {
548
- name: toolCall.functionName,
549
- arguments: toolCall.functionArguments,
550
- result: toolCall.functionResult,
551
- error: toolCall.functionError,
552
- },
553
- // Pass through approval info if present on the tool call
554
- requiresApproval: toolCall.requiresApproval,
555
- // Pass through isCompleted so ToolCallDisplay knows when to hide spinner
556
- isCompleted: toolCall.isCompleted,
557
- // Tool call is streaming if message is not completed and tool call has no result yet
558
- isStreaming: !agentMessage.isCompleted &&
559
- !toolCall.isCompleted &&
560
- !toolCall.functionResult &&
561
- !toolCall.functionError,
562
- // Pass through message IDs for approval/rejection events
563
- messageId: toolCall.messageId,
564
- dbMessageId: toolCall.dbMessageId,
565
- }))
754
+ ? agentMessage.toolCalls.map((toolCall) => {
755
+ const isPruned = !!toolCall.isPruned ||
756
+ /^PRUNED$/i.test(toolCall.functionError || "");
757
+ const displayResult = parseToolResultValue(toolCall.functionResultRichContent) ?? toolCall.functionResult;
758
+ return {
759
+ id: toolCall.toolCallId,
760
+ displayName: toolCall.functionName,
761
+ function: {
762
+ name: toolCall.functionName,
763
+ arguments: toolCall.functionArguments,
764
+ result: displayResult,
765
+ error: toolCall.functionError,
766
+ },
767
+ // Pass through approval info if present on the tool call
768
+ requiresApproval: toolCall.requiresApproval,
769
+ // Pass through prune metadata so the terminal can render a neutral state
770
+ isPruned,
771
+ prunedAt: toolCall.prunedAt,
772
+ // Pass through isCompleted so ToolCallDisplay knows when to hide spinner
773
+ isCompleted: toolCall.isCompleted,
774
+ // Tool call is streaming if message is not completed and tool call has no result yet
775
+ isStreaming: !agentMessage.isCompleted &&
776
+ !toolCall.isCompleted &&
777
+ !displayResult &&
778
+ !toolCall.functionError &&
779
+ !isPruned,
780
+ // Pass through message IDs for approval/rejection events
781
+ messageId: toolCall.messageId,
782
+ dbMessageId: toolCall.dbMessageId,
783
+ responseTimeMs: toolCall.responseTimeMs,
784
+ createdDate: toolCall.createdDate,
785
+ };
786
+ })
566
787
  : [],
567
788
  };
568
789
  if (agentMessage.toolCallId) {
@@ -574,10 +795,10 @@ const convertAgentMessagesToAiFormat = (agentMessages) => {
574
795
  // interface AgentTerminalProps {
575
796
  // agentStub: Agent;
576
797
  // }
577
- export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive = true, compact = false, hideContext = false, hideBottomControls = false, hideGreeting = false, simpleMode = false, className, initialPrompt, onAgentUpdate, }) {
798
+ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive = true, compact = false, displayMode = "full", showSummaryInput = false, hideContext = false, hideBottomControls = false, hideGreeting = false, defaultCollapseJson = false, simpleMode = false, className, initialPrompt, onAgentUpdate, onMessage, onInteractionSubmitted, onQuestionnaireOpenChange, questionnaireFooterActions, hideSummaryMessages = false, summaryPlaceholderActions, summaryPlaceholderMessage, hideSummaryWaitingPlaceholder = false, }) {
578
799
  const editContext = useEditContext();
579
800
  const fieldsContext = useFieldsEditContext();
580
- const [agent, setAgent] = useState(undefined);
801
+ const [agent, setAgent] = useState(() => buildPlaceholderAgentDetails(agentStub));
581
802
  const [messages, setMessages] = useState([]);
582
803
  const [agentOperations, setAgentOperations] = useState([]);
583
804
  const [prompt, setPrompt] = useState("");
@@ -588,6 +809,35 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
588
809
  const [activePlaceholderInput, setActivePlaceholderInput] = useState(null);
589
810
  const [allPlaceholdersFilled, setAllPlaceholdersFilled] = useState(false);
590
811
  const [agentMetadata, setAgentMetadata] = useState(null);
812
+ const [isBrowserClaimMutationPending, setIsBrowserClaimMutationPending] = useState(false);
813
+ const [pendingBrowserCaptureDialogType, setPendingBrowserCaptureDialogType] = useState(null);
814
+ // Ensure we always have an agent object for streaming handlers, even before `getAgent()` resolves.
815
+ // This prevents early tool calls (e.g., ask-questionnaire) from being dropped in compact/workspace UIs.
816
+ useEffect(() => {
817
+ setAgent((prev) => {
818
+ if (prev?.id === agentStub.id)
819
+ return prev;
820
+ return buildPlaceholderAgentDetails(agentStub);
821
+ });
822
+ }, [agentStub.id]);
823
+ const observedMessageIdsRef = useRef(new Set());
824
+ useEffect(() => {
825
+ observedMessageIdsRef.current = new Set();
826
+ }, [agentStub.id]);
827
+ useEffect(() => {
828
+ if (!onMessage)
829
+ return;
830
+ for (const message of messages) {
831
+ if (!message?.id)
832
+ continue;
833
+ if (!message.isCompleted)
834
+ continue;
835
+ if (observedMessageIdsRef.current.has(message.id))
836
+ continue;
837
+ observedMessageIdsRef.current.add(message.id);
838
+ onMessage(message);
839
+ }
840
+ }, [messages, onMessage]);
591
841
  // Generate a stable clientSessionId per component instance for stream deduplication
592
842
  const clientSessionIdRef = useRef(null);
593
843
  if (!clientSessionIdRef.current) {
@@ -599,6 +849,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
599
849
  const [isListening, setIsListening] = useState(false);
600
850
  const recognitionRef = useRef(null);
601
851
  const prevPlaceholderRef = useRef(null);
852
+ const promptBeforeVoiceRef = useRef("");
602
853
  // Voice button press-and-hold tracking
603
854
  const voicePressStartRef = useRef(null);
604
855
  const voiceHoldTimerRef = useRef(null);
@@ -608,6 +859,70 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
608
859
  const [showCompressionPopover, setShowCompressionPopover] = useState(false);
609
860
  // Agent inline dialog state (for component type selector, etc.)
610
861
  const [activeInlineDialog, setActiveInlineDialog] = useState(null);
862
+ const dialogTerminalInstanceIdRef = useRef("");
863
+ if (!dialogTerminalInstanceIdRef.current) {
864
+ dialogTerminalInstanceIdRef.current = crypto.randomUUID();
865
+ }
866
+ const activeInlineDialogRef = useRef(activeInlineDialog);
867
+ const isQuestionnaireDialogOpen = activeInlineDialog?.request.dialogType === "questionnaire";
868
+ const orphanTimeoutRef = useRef(null);
869
+ useEffect(() => {
870
+ activeInlineDialogRef.current = activeInlineDialog;
871
+ const visibleRegistry = { ...getVisibleDialogRegistry() };
872
+ const callbackId = activeInlineDialog?.request.callbackId || null;
873
+ const terminalInstanceId = dialogTerminalInstanceIdRef.current;
874
+ const agentKeys = [
875
+ normalizeDialogAgentId(agentStubIdRefForDialogs.current),
876
+ normalizeDialogAgentId(agentIdRefForDialogs.current),
877
+ ].filter(Boolean);
878
+ agentKeys.forEach((key) => {
879
+ if (callbackId) {
880
+ visibleRegistry[key] = {
881
+ callbackId,
882
+ terminalInstanceId,
883
+ };
884
+ }
885
+ else {
886
+ delete visibleRegistry[key];
887
+ }
888
+ });
889
+ globalThis.__agentDialogVisibleCallbacks = visibleRegistry;
890
+ }, [activeInlineDialog]);
891
+ useEffect(() => {
892
+ onQuestionnaireOpenChange?.(isQuestionnaireDialogOpen);
893
+ }, [isQuestionnaireDialogOpen, onQuestionnaireOpenChange]);
894
+ useLayoutEffect(() => {
895
+ if (displayMode !== "summary" || !isQuestionnaireDialogOpen) {
896
+ return;
897
+ }
898
+ const scrollSummaryTerminalToTop = () => {
899
+ const container = messagesContainerRef.current;
900
+ if (!container) {
901
+ return;
902
+ }
903
+ container.scrollTop = 0;
904
+ };
905
+ scrollSummaryTerminalToTop();
906
+ const frame1 = requestAnimationFrame(() => {
907
+ scrollSummaryTerminalToTop();
908
+ });
909
+ let frame3 = 0;
910
+ const frame2 = requestAnimationFrame(() => {
911
+ frame3 = requestAnimationFrame(() => {
912
+ scrollSummaryTerminalToTop();
913
+ });
914
+ });
915
+ return () => {
916
+ cancelAnimationFrame(frame1);
917
+ cancelAnimationFrame(frame2);
918
+ cancelAnimationFrame(frame3);
919
+ };
920
+ }, [displayMode, isQuestionnaireDialogOpen]);
921
+ useEffect(() => {
922
+ return () => {
923
+ onQuestionnaireOpenChange?.(false);
924
+ };
925
+ }, [onQuestionnaireOpenChange]);
611
926
  const isWaitingRef = useRef(false);
612
927
  useEffect(() => {
613
928
  isWaitingRef.current = isWaitingForResponse;
@@ -618,10 +933,64 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
618
933
  const isStoppingRef = useRef(false);
619
934
  // Server-driven state: true when agent is actively processing (set by WebSocket messages)
620
935
  const [isAgentThinking, setIsAgentThinking] = useState(false);
936
+ useEffect(() => {
937
+ if (!initialMetadata)
938
+ return;
939
+ setAgentMetadata((prev) => sanitizeAgentMetadata({
940
+ ...(prev || {}),
941
+ ...initialMetadata,
942
+ additionalData: {
943
+ ...(prev?.additionalData || {}),
944
+ ...(initialMetadata?.additionalData || {}),
945
+ },
946
+ }));
947
+ }, [initialMetadata]);
621
948
  const hasActiveStreaming = useCallback(() => {
622
949
  const current = messagesRef.current || [];
623
950
  return current.some((m) => !m.isCompleted && m.messageType === "streaming");
624
951
  }, []);
952
+ const currentAgentId = agent?.id || agentStub.id;
953
+ const recentAgentRunEvents = useMemo(() => {
954
+ const normalizedAgentId = normalizeDialogAgentId(currentAgentId);
955
+ if (!normalizedAgentId) {
956
+ return [];
957
+ }
958
+ if (!editContext) {
959
+ return [];
960
+ }
961
+ return (editContext.webSocketMessages || [])
962
+ .filter((message) => {
963
+ if (!message?.type?.startsWith("agent:run:")) {
964
+ return false;
965
+ }
966
+ if (isHeartbeatRunEventMessage(message)) {
967
+ return false;
968
+ }
969
+ return getAgentRunMessageAgentId(message.payload) === normalizedAgentId;
970
+ })
971
+ .slice(-8)
972
+ .map((message) => ({
973
+ timestamp: message.timestamp,
974
+ type: message.type,
975
+ seq: getAgentRunMessageSeq(message.payload),
976
+ detail: getAgentRunMessageDetail(message.type, message.payload),
977
+ }));
978
+ }, [currentAgentId, editContext?.webSocketMessages]);
979
+ const appendToolUiEvent = useCallback((type, detail, seq) => {
980
+ const timestamp = new Date().toISOString();
981
+ setRecentToolUiEvents((prev) => {
982
+ const next = [
983
+ ...prev,
984
+ {
985
+ timestamp,
986
+ type,
987
+ detail: detail || null,
988
+ seq: seq ?? null,
989
+ },
990
+ ];
991
+ return next.slice(-40);
992
+ });
993
+ }, []);
625
994
  // Collect all pending tool calls for batch approval functionality
626
995
  const allPendingApprovals = useMemo(() => {
627
996
  const pending = [];
@@ -663,41 +1032,82 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
663
1032
  }, [allPendingApprovals]);
664
1033
  // Handle mode switch to autonomous
665
1034
  const handleSwitchToAutonomous = useCallback(() => {
666
- setMode("autonomous");
667
- }, []);
1035
+ const nextMode = "autonomous";
1036
+ setMode(nextMode);
1037
+ setAgentMetadata((prev) => ({
1038
+ ...(prev ?? {}),
1039
+ mode: nextMode,
1040
+ }));
1041
+ setAgent((prev) => {
1042
+ if (!prev)
1043
+ return prev;
1044
+ return {
1045
+ ...prev,
1046
+ mode: nextMode,
1047
+ };
1048
+ });
1049
+ }, [setAgent, setAgentMetadata]);
668
1050
  const [resolvedPageName, setResolvedPageName] = useState(undefined);
669
1051
  const [resolvedComponentName, setResolvedComponentName] = useState(undefined);
670
1052
  const [resolvedFieldName, setResolvedFieldName] = useState(undefined);
671
1053
  const [promptHistory, setPromptHistory] = useState(() => {
672
- if (typeof window !== "undefined") {
673
- return JSON.parse(localStorage.getItem("editor.agent.promptHistory") || "[]");
674
- }
675
- return [];
1054
+ return (localStorageService.getItem("editor.agent.promptHistory") || []);
676
1055
  });
677
1056
  const [currentHistoryIndex, setCurrentHistoryIndex] = useState(-1);
678
1057
  const [showPredefined, setShowPredefined] = useState(false);
679
1058
  const [activeProfile, setActiveProfile] = useState(undefined);
680
1059
  const [selectedModelId, setSelectedModelId] = useState(undefined);
1060
+ const normalizeAgentMode = (value) => {
1061
+ if (value === "autonomous" ||
1062
+ value === "read-only" ||
1063
+ value === "supervised") {
1064
+ return value;
1065
+ }
1066
+ return null;
1067
+ };
681
1068
  const [mode, setMode] = useState("supervised");
682
1069
  const [queuedPrompts, setQueuedPrompts] = useState([]);
683
- const isMobile = useMediaQuery("(max-width: 768px)");
1070
+ const [expandedQueuedTriggerIds, setExpandedQueuedTriggerIds] = useState({});
684
1071
  const [contextPanelsActiveTab, setContextPanelsActiveTab] = useState(0);
685
1072
  const [hiddenContextPanelTabIds, setHiddenContextPanelTabIds] = useState(new Set());
686
1073
  const [showCostAndAgent, setShowCostAndAgent] = useState(false);
1074
+ const [showAgentSettings, setShowAgentSettings] = useState(false);
1075
+ const [showSkillPicker, setShowSkillPicker] = useState(false);
1076
+ const [availableSkills, setAvailableSkills] = useState([]);
1077
+ const [skillRootIds, setSkillRootIds] = useState([]);
1078
+ const [selectableTemplateIds, setSelectableTemplateIds] = useState([]);
1079
+ const [skillsLoading, setSkillsLoading] = useState(false);
1080
+ const [skillsError, setSkillsError] = useState(null);
1081
+ const [skillActionError, setSkillActionError] = useState(null);
1082
+ const [triggerSubscriptions, setTriggerSubscriptions] = useState([]);
1083
+ const [availableTools, setAvailableTools] = useState([]);
1084
+ const [operationAllowances, setOperationAllowances] = useState({
1085
+ sitecore: [],
1086
+ filesystem: [],
1087
+ });
1088
+ const [triggerSubscriptionsLoading, setTriggerSubscriptionsLoading] = useState(false);
1089
+ const [availableToolsLoading, setAvailableToolsLoading] = useState(false);
1090
+ const [operationAllowancesLoading, setOperationAllowancesLoading] = useState(false);
1091
+ const [triggerSubscriptionsError, setTriggerSubscriptionsError] = useState(null);
1092
+ const [availableToolsError, setAvailableToolsError] = useState(null);
1093
+ const [operationAllowancesError, setOperationAllowancesError] = useState(null);
1094
+ const [toolsSectionExpanded, setToolsSectionExpanded] = useState(false);
1095
+ const [allowancesSectionExpanded, setAllowancesSectionExpanded] = useState(false);
1096
+ const [subscribedTriggersSectionExpanded, setSubscribedTriggersSectionExpanded,] = useState(false);
1097
+ const isPersistedAgent = !!agent?.userId;
1098
+ const isLocalOnlyDraftAgent = agent?.status === "new" && !isPersistedAgent;
687
1099
  const hasSpawnedAgents = useMemo(() => {
688
1100
  if (!agentMetadata)
689
1101
  return false;
690
- const childAgents = agentMetadata?.ChildAgents ||
691
- agentMetadata?.childAgents;
1102
+ const childAgents = agentMetadata?.childAgents;
692
1103
  if (!Array.isArray(childAgents) || childAgents.length === 0)
693
1104
  return false;
694
- return childAgents.some((a) => a != null && typeof a === "object" && (a.AgentId || a.agentId));
1105
+ return childAgents.some((a) => a != null && typeof a === "object" && a.agentId);
695
1106
  }, [agentMetadata]);
696
1107
  const hasTodoContent = useMemo(() => {
697
1108
  const metadataTodos = (() => {
698
1109
  try {
699
- const todoList = agentMetadata?.additionalData?.todoList ||
700
- agentMetadata?.todoList;
1110
+ const todoList = agentMetadata?.additionalData?.todoList;
701
1111
  if (todoList?.items && Array.isArray(todoList.items)) {
702
1112
  const raw = todoList.items.filter((item) => item?.title || item?.text || item?.label || item?.task);
703
1113
  return raw.length > 0;
@@ -745,7 +1155,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
745
1155
  .then((result) => {
746
1156
  if (cancelled)
747
1157
  return;
748
- if (result.type === "success" && result.data && result.data.length > 0) {
1158
+ if (result.type === "success" &&
1159
+ result.data &&
1160
+ result.data.length > 0) {
749
1161
  setHasHistoryContent(true);
750
1162
  }
751
1163
  else {
@@ -776,6 +1188,38 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
776
1188
  });
777
1189
  return () => unsubscribe();
778
1190
  }, [agent?.id, editContext?.addSocketMessageListener]);
1191
+ useEffect(() => {
1192
+ let active = true;
1193
+ const loadSkills = async () => {
1194
+ try {
1195
+ setSkillsLoading(true);
1196
+ setSkillsError(null);
1197
+ const catalog = await getAgentSkillCatalog(false);
1198
+ if (active) {
1199
+ setAvailableSkills(catalog.skills.filter((s) => !s.disabled));
1200
+ setSkillRootIds(catalog.rootIds);
1201
+ setSelectableTemplateIds(catalog.selectableTemplateIds);
1202
+ }
1203
+ }
1204
+ catch (e) {
1205
+ if (active) {
1206
+ setSkillsError(e?.message || "Failed to load skills");
1207
+ setAvailableSkills([]);
1208
+ setSkillRootIds([]);
1209
+ setSelectableTemplateIds([]);
1210
+ }
1211
+ }
1212
+ finally {
1213
+ if (active) {
1214
+ setSkillsLoading(false);
1215
+ }
1216
+ }
1217
+ };
1218
+ void loadSkills();
1219
+ return () => {
1220
+ active = false;
1221
+ };
1222
+ }, []);
779
1223
  const modeOptions = useMemo(() => [
780
1224
  {
781
1225
  value: "supervised",
@@ -810,6 +1254,239 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
810
1254
  value: m.id,
811
1255
  label: m.name,
812
1256
  })) || []).sort((a, b) => a.label.localeCompare(b.label)), [activeProfile]);
1257
+ const metadataSelectedSkillIds = useMemo(() => {
1258
+ const rawSkillIds = agentMetadata?.additionalData?.skillIds ?? [];
1259
+ if (!Array.isArray(rawSkillIds)) {
1260
+ return [];
1261
+ }
1262
+ return rawSkillIds
1263
+ .map((x) => String(x || "").trim())
1264
+ .filter((x) => x.length > 0);
1265
+ }, [agentMetadata]);
1266
+ const backendAssignedSkillIds = useMemo(() => {
1267
+ const rawSkillIds = agent?.assignedSkillIds ?? [];
1268
+ if (!Array.isArray(rawSkillIds)) {
1269
+ return [];
1270
+ }
1271
+ return rawSkillIds
1272
+ .map((x) => String(x || "").trim())
1273
+ .filter((x) => x.length > 0);
1274
+ }, [agent]);
1275
+ const preloadedSkillIds = useMemo(() => {
1276
+ const rawSkillIds = activeProfile?.preloadSkills ?? [];
1277
+ if (!Array.isArray(rawSkillIds)) {
1278
+ return [];
1279
+ }
1280
+ return rawSkillIds
1281
+ .map((skill) => String(skill?.id || "").trim())
1282
+ .filter((id) => id.length > 0);
1283
+ }, [activeProfile?.preloadSkills]);
1284
+ const autoAssignedSkillIds = useMemo(() => {
1285
+ const preloadedIdSet = new Set(preloadedSkillIds.map((id) => id.toLowerCase()));
1286
+ const all = isLocalOnlyDraftAgent
1287
+ ? preloadedSkillIds
1288
+ : backendAssignedSkillIds.filter((id) => preloadedIdSet.has(id.toLowerCase()));
1289
+ const seen = new Set();
1290
+ const unique = [];
1291
+ for (const id of all) {
1292
+ const key = id.toLowerCase();
1293
+ if (seen.has(key))
1294
+ continue;
1295
+ seen.add(key);
1296
+ unique.push(id);
1297
+ }
1298
+ return unique;
1299
+ }, [backendAssignedSkillIds, isLocalOnlyDraftAgent, preloadedSkillIds]);
1300
+ const selectedSkillIds = useMemo(() => {
1301
+ const all = [
1302
+ ...autoAssignedSkillIds,
1303
+ ...backendAssignedSkillIds,
1304
+ ...metadataSelectedSkillIds,
1305
+ ];
1306
+ const seen = new Set();
1307
+ const unique = [];
1308
+ for (const id of all) {
1309
+ const key = id.toLowerCase();
1310
+ if (seen.has(key))
1311
+ continue;
1312
+ seen.add(key);
1313
+ unique.push(id);
1314
+ }
1315
+ return unique;
1316
+ }, [autoAssignedSkillIds, backendAssignedSkillIds, metadataSelectedSkillIds]);
1317
+ useEffect(() => {
1318
+ let active = true;
1319
+ if (!showAgentSettings) {
1320
+ return () => {
1321
+ active = false;
1322
+ };
1323
+ }
1324
+ if (!agent?.id || isLocalOnlyDraftAgent) {
1325
+ setTriggerSubscriptions([]);
1326
+ setTriggerSubscriptionsLoading(false);
1327
+ setTriggerSubscriptionsError(null);
1328
+ setAvailableTools([]);
1329
+ setAvailableToolsLoading(false);
1330
+ setAvailableToolsError(null);
1331
+ setOperationAllowances({ sitecore: [], filesystem: [] });
1332
+ setOperationAllowancesLoading(false);
1333
+ setOperationAllowancesError(null);
1334
+ return () => {
1335
+ active = false;
1336
+ };
1337
+ }
1338
+ const loadTriggerSubscriptions = async () => {
1339
+ try {
1340
+ setTriggerSubscriptionsLoading(true);
1341
+ setTriggerSubscriptionsError(null);
1342
+ const subscriptions = await getAgentTriggerSubscriptions(agent.id);
1343
+ if (active) {
1344
+ setTriggerSubscriptions(subscriptions);
1345
+ }
1346
+ }
1347
+ catch (e) {
1348
+ if (active) {
1349
+ setTriggerSubscriptionsError(e?.message || "Failed to load trigger subscriptions");
1350
+ }
1351
+ }
1352
+ finally {
1353
+ if (active) {
1354
+ setTriggerSubscriptionsLoading(false);
1355
+ }
1356
+ }
1357
+ };
1358
+ const loadOperationAllowances = async () => {
1359
+ try {
1360
+ setOperationAllowancesLoading(true);
1361
+ setOperationAllowancesError(null);
1362
+ const allowances = await getAgentOperationAllowances(agent.id);
1363
+ if (active) {
1364
+ setOperationAllowances(allowances);
1365
+ }
1366
+ }
1367
+ catch (e) {
1368
+ if (active) {
1369
+ setOperationAllowancesError(e?.message || "Failed to load allowances");
1370
+ }
1371
+ }
1372
+ finally {
1373
+ if (active) {
1374
+ setOperationAllowancesLoading(false);
1375
+ }
1376
+ }
1377
+ };
1378
+ const loadAvailableTools = async () => {
1379
+ try {
1380
+ setAvailableToolsLoading(true);
1381
+ setAvailableToolsError(null);
1382
+ const tools = await getAgentAvailableTools(agent.id);
1383
+ if (active) {
1384
+ setAvailableTools(tools);
1385
+ }
1386
+ }
1387
+ catch (e) {
1388
+ if (active) {
1389
+ setAvailableToolsError(e?.message || "Failed to load available tools");
1390
+ }
1391
+ }
1392
+ finally {
1393
+ if (active) {
1394
+ setAvailableToolsLoading(false);
1395
+ }
1396
+ }
1397
+ };
1398
+ void loadTriggerSubscriptions();
1399
+ void loadAvailableTools();
1400
+ void loadOperationAllowances();
1401
+ return () => {
1402
+ active = false;
1403
+ };
1404
+ }, [
1405
+ showAgentSettings,
1406
+ agent?.id,
1407
+ agent?.status,
1408
+ agent?.userId,
1409
+ agent?.profileId,
1410
+ mode,
1411
+ selectedSkillIds,
1412
+ ]);
1413
+ const activeTriggerSubscriptions = useMemo(() => triggerSubscriptions.filter((sub) => sub.isActive), [triggerSubscriptions]);
1414
+ const allowanceGroups = useMemo(() => [
1415
+ {
1416
+ key: "sitecore",
1417
+ label: "Sitecore",
1418
+ rows: operationAllowances.sitecore,
1419
+ },
1420
+ {
1421
+ key: "filesystem",
1422
+ label: "Filesystem",
1423
+ rows: operationAllowances.filesystem,
1424
+ },
1425
+ ], [operationAllowances]);
1426
+ const hasAnyAllowances = useMemo(() => operationAllowances.sitecore.length > 0 ||
1427
+ operationAllowances.filesystem.length > 0, [operationAllowances]);
1428
+ const allowancesTotalCount = useMemo(() => operationAllowances.sitecore.length +
1429
+ operationAllowances.filesystem.length, [operationAllowances]);
1430
+ const listedProfileSkillIdSet = useMemo(() => {
1431
+ const ids = [
1432
+ ...(activeProfile?.availableSkills ?? []),
1433
+ ...(activeProfile?.allowedSkills ?? []),
1434
+ ]
1435
+ .map((skill) => String(skill?.id || "").toLowerCase())
1436
+ .filter((id) => id.length > 0);
1437
+ return new Set(ids);
1438
+ }, [activeProfile?.availableSkills, activeProfile?.allowedSkills]);
1439
+ const profileFilteredSkills = useMemo(() => {
1440
+ if (listedProfileSkillIdSet.size === 0) {
1441
+ return [];
1442
+ }
1443
+ return availableSkills.filter((skill) => listedProfileSkillIdSet.has(skill.id.toLowerCase()));
1444
+ }, [availableSkills, listedProfileSkillIdSet]);
1445
+ const profileFilteredSkillIdSet = useMemo(() => new Set(profileFilteredSkills.map((s) => s.id.toLowerCase())), [profileFilteredSkills]);
1446
+ const manuallyAssignableSkillIdSet = useMemo(() => new Set((activeProfile?.allowedSkills ?? [])
1447
+ .map((skill) => String(skill?.id || "").toLowerCase())
1448
+ .filter((id) => id.length > 0)), [activeProfile?.allowedSkills]);
1449
+ const selectableTemplateIdSet = useMemo(() => new Set(selectableTemplateIds.map((id) => id.toLowerCase())), [selectableTemplateIds]);
1450
+ const selectedSkills = useMemo(() => selectedSkillIds
1451
+ .map((id) => availableSkills.find((s) => s.id.toLowerCase() === id.toLowerCase()))
1452
+ .filter((s) => !!s), [availableSkills, selectedSkillIds]);
1453
+ const selectedSkillSet = useMemo(() => new Set(selectedSkillIds.map((id) => id.toLowerCase())), [selectedSkillIds]);
1454
+ const autoAssignedSkillSet = useMemo(() => new Set(autoAssignedSkillIds.map((id) => id.toLowerCase())), [autoAssignedSkillIds]);
1455
+ const previewAvailableTools = useMemo(() => {
1456
+ const baseToolNames = mode === "read-only"
1457
+ ? (activeProfile?.readOnlyToolNames ?? [])
1458
+ : (activeProfile?.allowedToolNames ?? []);
1459
+ const toolNames = new Set();
1460
+ for (const toolName of baseToolNames) {
1461
+ const normalized = String(toolName || "").trim();
1462
+ if (normalized) {
1463
+ toolNames.add(normalized);
1464
+ }
1465
+ }
1466
+ for (const skill of selectedSkills) {
1467
+ for (const toolName of skill.additionalAllowedTools ?? []) {
1468
+ const normalized = String(toolName || "").trim();
1469
+ if (normalized) {
1470
+ toolNames.add(normalized);
1471
+ }
1472
+ }
1473
+ }
1474
+ return Array.from(toolNames).sort((a, b) => a.localeCompare(b));
1475
+ }, [
1476
+ activeProfile?.allowedToolNames,
1477
+ activeProfile?.readOnlyToolNames,
1478
+ mode,
1479
+ selectedSkills,
1480
+ ]);
1481
+ const displayedAvailableTools = isLocalOnlyDraftAgent
1482
+ ? previewAvailableTools
1483
+ : availableTools;
1484
+ const displayedAvailableToolsLoading = isLocalOnlyDraftAgent
1485
+ ? false
1486
+ : availableToolsLoading;
1487
+ const displayedAvailableToolsError = isLocalOnlyDraftAgent
1488
+ ? null
1489
+ : availableToolsError;
813
1490
  // Remove deprecated cost limit fields from metadata to avoid confusion with agent/profile settings
814
1491
  const sanitizeAgentMetadata = useCallback((meta) => {
815
1492
  try {
@@ -834,28 +1511,239 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
834
1511
  return meta;
835
1512
  }
836
1513
  }, []);
837
- // Read deterministic flags from query string once
838
- let deterministicFlags;
839
- try {
840
- const params = new URLSearchParams(window.location.search);
841
- const detParam = params.get("deterministic");
842
- const deterministic = detParam === "true" ||
843
- (detParam === null ? params.has("deterministic") : false);
844
- const seedStr = params.get("seed");
845
- const seed = seedStr ? Number(seedStr) : undefined;
846
- deterministicFlags = {
847
- deterministic,
848
- seed: Number.isFinite(seed) ? seed : undefined,
1514
+ const getSkillActionErrorMessage = useCallback((error) => {
1515
+ const message = error instanceof Error && error.message.trim()
1516
+ ? error.message.trim()
1517
+ : "Failed to update skill";
1518
+ if (message.includes("Skill is not available for this agent profile")) {
1519
+ return "This skill cannot be added for the current agent profile.";
1520
+ }
1521
+ return message;
1522
+ }, []);
1523
+ const clearLegacySelectedSkillsFromMetadata = useCallback(async () => {
1524
+ const legacySkillIds = metadataSelectedSkillIds;
1525
+ if (!agent?.id || legacySkillIds.length === 0) {
1526
+ return;
1527
+ }
1528
+ const current = agentMetadata || {};
1529
+ const currentAdditionalData = current.additionalData ||
1530
+ {};
1531
+ const nextAdditionalData = Object.fromEntries(Object.entries(currentAdditionalData).filter(([key]) => key.toLowerCase() !== "skillids"));
1532
+ const next = {
1533
+ ...current,
849
1534
  };
850
- }
851
- catch {
852
- deterministicFlags = {
853
- deterministic: false,
854
- seed: undefined,
1535
+ if (Object.keys(nextAdditionalData).length > 0) {
1536
+ next.additionalData = nextAdditionalData;
1537
+ }
1538
+ else {
1539
+ delete next.additionalData;
1540
+ }
1541
+ try {
1542
+ await updateAgentContext(agent.id, next);
1543
+ setAgentMetadata(next);
1544
+ setAgent((prev) => prev ? { ...prev, agentContext: JSON.stringify(next) } : prev);
1545
+ }
1546
+ catch (e) {
1547
+ console.error("Failed to clear legacy selected skills", e);
1548
+ }
1549
+ }, [
1550
+ agent?.id,
1551
+ agentMetadata,
1552
+ metadataSelectedSkillIds,
1553
+ setAgent,
1554
+ setAgentMetadata,
1555
+ ]);
1556
+ const parsePersistedAgentContext = (contextJson) => {
1557
+ try {
1558
+ if (!contextJson)
1559
+ return null;
1560
+ const parsedContext = JSON.parse(contextJson);
1561
+ if (!parsedContext || typeof parsedContext !== "object") {
1562
+ return null;
1563
+ }
1564
+ return sanitizeAgentMetadata(parsedContext);
1565
+ }
1566
+ catch {
1567
+ return null;
1568
+ }
1569
+ };
1570
+ const buildDraftPersistenceContext = () => {
1571
+ let effectiveContext = agentMetadata;
1572
+ try {
1573
+ const normalizedAgentProfileId = agent?.profileId?.toLowerCase();
1574
+ const profile = activeProfile ||
1575
+ profiles.find((p) => p.id?.toLowerCase() === normalizedAgentProfileId);
1576
+ const isLiveMode = profile?.editorContextMode === "live";
1577
+ if (isLiveMode && typeof buildCurrentContext === "function") {
1578
+ const freshContext = buildCurrentContext();
1579
+ if (freshContext) {
1580
+ effectiveContext = {
1581
+ ...(agentMetadata || {}),
1582
+ items: freshContext.items,
1583
+ currentItemId: freshContext.currentItemId,
1584
+ components: freshContext.components,
1585
+ field: freshContext.field,
1586
+ activeWorkspace: freshContext.activeWorkspace,
1587
+ hasPageLoaded: freshContext.hasPageLoaded,
1588
+ openSidebars: freshContext.openSidebars,
1589
+ };
1590
+ }
1591
+ }
1592
+ }
1593
+ catch (e) {
1594
+ console.warn("[AgentTerminal] Failed to compute draft context:", e);
1595
+ }
1596
+ return sanitizeAgentMetadata(effectiveContext || null);
1597
+ };
1598
+ const ensureDraftAgentPersisted = async () => {
1599
+ if (!agent?.id) {
1600
+ throw new Error("Agent not ready. Please try again.");
1601
+ }
1602
+ if (!isLocalOnlyDraftAgent) {
1603
+ return agent;
1604
+ }
1605
+ const requestSettings = getPendingRequestSettings();
1606
+ if (!requestSettings.profileId) {
1607
+ throw new Error("Select an agent profile before adding a skill.");
1608
+ }
1609
+ const effectiveContext = buildDraftPersistenceContext();
1610
+ const persistedAgent = await persistDraftAgent({
1611
+ agentId: agent.id,
1612
+ sessionId: editContext?.sessionId || undefined,
1613
+ profileId: requestSettings.profileId,
1614
+ mode: requestSettings.mode,
1615
+ model: requestSettings.modelId || undefined,
1616
+ name: agent.name,
1617
+ context: effectiveContext,
1618
+ });
1619
+ pendingSettingsRef.current = null;
1620
+ const persistedMetadata = parsePersistedAgentContext(persistedAgent.agentContext) ??
1621
+ effectiveContext;
1622
+ setAgentMetadata(persistedMetadata ? { ...persistedMetadata } : null);
1623
+ setAgent((prev) => {
1624
+ const next = {
1625
+ ...(prev || {}),
1626
+ ...persistedAgent,
1627
+ };
1628
+ if (prev?.messages?.length && !persistedAgent.messages?.length) {
1629
+ next.messages = prev.messages;
1630
+ }
1631
+ return next;
1632
+ });
1633
+ onAgentUpdate?.(persistedAgent);
1634
+ return persistedAgent;
1635
+ };
1636
+ const handleAddSkill = useCallback(async (skillId) => {
1637
+ if (selectedSkillSet.has(skillId.toLowerCase()))
1638
+ return;
1639
+ if (!agent?.id)
1640
+ return;
1641
+ try {
1642
+ setSkillActionError(null);
1643
+ const persistedAgent = await ensureDraftAgentPersisted();
1644
+ await assignAgentSkill({ agentId: persistedAgent.id, skillId });
1645
+ setAgent((prev) => {
1646
+ if (!prev)
1647
+ return prev;
1648
+ const currentAssigned = Array.isArray(prev.assignedSkillIds)
1649
+ ? prev.assignedSkillIds
1650
+ : [];
1651
+ if (currentAssigned.some((existingId) => String(existingId).toLowerCase() === skillId.toLowerCase())) {
1652
+ return prev;
1653
+ }
1654
+ return {
1655
+ ...prev,
1656
+ assignedSkillIds: [...currentAssigned, skillId],
1657
+ };
1658
+ });
1659
+ await clearLegacySelectedSkillsFromMetadata();
1660
+ return true;
1661
+ }
1662
+ catch (e) {
1663
+ setSkillActionError(getSkillActionErrorMessage(e));
1664
+ return false;
1665
+ }
1666
+ }, [
1667
+ agent?.id,
1668
+ assignAgentSkill,
1669
+ clearLegacySelectedSkillsFromMetadata,
1670
+ ensureDraftAgentPersisted,
1671
+ getSkillActionErrorMessage,
1672
+ setAgent,
1673
+ selectedSkillSet,
1674
+ ]);
1675
+ const handleRemoveSkill = useCallback(async (skillId) => {
1676
+ if (!agent?.id)
1677
+ return;
1678
+ try {
1679
+ setSkillActionError(null);
1680
+ await revokeAgentSkill({ agentId: agent.id, skillId });
1681
+ setAgent((prev) => {
1682
+ if (!prev)
1683
+ return prev;
1684
+ const currentAssigned = Array.isArray(prev.assignedSkillIds)
1685
+ ? prev.assignedSkillIds
1686
+ : [];
1687
+ return {
1688
+ ...prev,
1689
+ assignedSkillIds: currentAssigned.filter((existingId) => String(existingId).toLowerCase() !== skillId.toLowerCase()),
1690
+ };
1691
+ });
1692
+ await clearLegacySelectedSkillsFromMetadata();
1693
+ }
1694
+ catch (e) {
1695
+ setSkillActionError(getSkillActionErrorMessage(e));
1696
+ }
1697
+ }, [
1698
+ agent?.id,
1699
+ clearLegacySelectedSkillsFromMetadata,
1700
+ getSkillActionErrorMessage,
1701
+ revokeAgentSkill,
1702
+ setAgent,
1703
+ ]);
1704
+ const handleOpenProfileSettings = useCallback(async () => {
1705
+ if (!editContext || !activeProfile?.id)
1706
+ return;
1707
+ const lang = editContext.currentItemDescriptor?.language || "en";
1708
+ await editContext.loadItem({
1709
+ id: activeProfile.id,
1710
+ language: lang,
1711
+ version: 0,
1712
+ });
1713
+ editContext.switchWorkspace("editor");
1714
+ }, [editContext, activeProfile?.id]);
1715
+ const handleEditProfileSideBySide = useCallback(async () => {
1716
+ if (!editContext || !activeProfile?.id)
1717
+ return;
1718
+ const lang = editContext.currentItemDescriptor?.language || "en";
1719
+ const profileItem = {
1720
+ id: activeProfile.id,
1721
+ language: lang,
1722
+ version: 0,
855
1723
  };
856
- }
1724
+ if (editContext.workspaceId === "agents") {
1725
+ editContext.setShowAgentsWorkspaceEditor(true);
1726
+ await editContext.loadItem(profileItem);
1727
+ return;
1728
+ }
1729
+ await editContext.loadItem(profileItem, {
1730
+ openInNewSlot: true,
1731
+ });
1732
+ editContext.switchWorkspace("editor");
1733
+ }, [editContext, activeProfile?.id]);
1734
+ const handleOpenSkillItem = useCallback(async (skillId) => {
1735
+ if (!editContext || !skillId)
1736
+ return;
1737
+ const lang = editContext.currentItemDescriptor?.language || "en";
1738
+ await editContext.loadItem({
1739
+ id: skillId,
1740
+ language: lang,
1741
+ version: 0,
1742
+ });
1743
+ editContext.switchWorkspace("editor");
1744
+ }, [editContext]);
857
1745
  useEffect(() => {
858
- localStorage.setItem("editor.agent.promptHistory", JSON.stringify(promptHistory));
1746
+ localStorageService.setItem("editor.agent.promptHistory", promptHistory);
859
1747
  }, [promptHistory]);
860
1748
  useEffect(() => {
861
1749
  // Keep messagesRef synchronized with messages state
@@ -867,6 +1755,32 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
867
1755
  const [liveTotals, setLiveTotals] = useState(null);
868
1756
  // Context window status from backend (extracted from delta cost object)
869
1757
  const [contextWindowStatus, setContextWindowStatus] = useState(null);
1758
+ const effectiveModelName = useMemo(() => {
1759
+ const fromContextWindow = contextWindowStatus?.model?.trim();
1760
+ if (fromContextWindow)
1761
+ return fromContextWindow;
1762
+ const fromAgent = agent?.model?.trim();
1763
+ if (fromAgent)
1764
+ return fromAgent;
1765
+ const models = activeProfile?.models || [];
1766
+ const selectedModelName = selectedModelId
1767
+ ? models.find((m) => m.id === selectedModelId)?.name?.trim()
1768
+ : undefined;
1769
+ if (selectedModelName)
1770
+ return selectedModelName;
1771
+ const defaultModelName = activeProfile?.defaultModelId
1772
+ ? models.find((m) => m.id === activeProfile.defaultModelId)?.name?.trim()
1773
+ : undefined;
1774
+ if (defaultModelName)
1775
+ return defaultModelName;
1776
+ return models[0]?.name?.trim() || undefined;
1777
+ }, [
1778
+ contextWindowStatus?.model,
1779
+ agent?.model,
1780
+ activeProfile?.models,
1781
+ activeProfile?.defaultModelId,
1782
+ selectedModelId,
1783
+ ]);
870
1784
  // Flag to track when we should create a new message
871
1785
  const shouldCreateNewMessage = useRef(false);
872
1786
  // Keep a ref to the current messages for immediate access
@@ -877,12 +1791,16 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
877
1791
  const placeholderInputRef = useRef(null);
878
1792
  const promptPlaceholderInputRef = useRef(null);
879
1793
  const messagesContainerRef = useRef(null);
1794
+ const inlineDialogContainerRef = useRef(null);
880
1795
  const [shouldAutoScroll, setShouldAutoScroll] = useState(true);
1796
+ const [recentToolUiEvents, setRecentToolUiEvents] = useState([]);
881
1797
  // WebSocket subscription state for agent streaming
882
1798
  const seenMessageIdsRef = useRef(new Set());
883
1799
  const lastSeqRef = useRef(0);
884
1800
  const subscribedAgentIdRef = useRef(null);
885
- // Cache mode/model changes made while the agent is still "new" (not yet persisted)
1801
+ const toolCallFirstSeenAtRef = useRef({});
1802
+ const pendingToolCompletionTimersRef = useRef({});
1803
+ // Cache mode/model/profile changes made while the agent is still "new" (not yet persisted)
886
1804
  const pendingSettingsRef = useRef(null);
887
1805
  // Track whether textarea should maintain focus during re-renders
888
1806
  const shouldMaintainFocusRef = useRef(false);
@@ -904,11 +1822,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
904
1822
  setIsVoiceSupported(!!SR);
905
1823
  if (SR === undefined) {
906
1824
  setIsVoiceDisabled(true);
907
- localStorage.setItem(VOICE_DISABLED_KEY, "true");
1825
+ localStorageService.setString(VOICE_DISABLED_KEY, "true");
908
1826
  return;
909
1827
  }
910
1828
  else {
911
- const wasDisabled = localStorage.getItem(VOICE_DISABLED_KEY) === "true";
1829
+ const wasDisabled = localStorageService.getString(VOICE_DISABLED_KEY) === "true";
912
1830
  setIsVoiceDisabled(wasDisabled);
913
1831
  }
914
1832
  }
@@ -919,7 +1837,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
919
1837
  // Auto-focus terminal input on mount
920
1838
  useEffect(() => {
921
1839
  if (textareaRef.current) {
922
- textareaRef.current.focus();
1840
+ try {
1841
+ textareaRef.current.focus({ preventScroll: true });
1842
+ }
1843
+ catch {
1844
+ textareaRef.current.focus();
1845
+ }
923
1846
  }
924
1847
  }, []);
925
1848
  // Start voice recognition
@@ -938,20 +1861,29 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
938
1861
  r.lang = editContext?.currentItemDescriptor?.language || "en-US";
939
1862
  r.continuous = true;
940
1863
  r.interimResults = true;
1864
+ promptBeforeVoiceRef.current = prompt;
941
1865
  r.onstart = () => {
942
1866
  setIsListening(true);
943
1867
  prevPlaceholderRef.current = inputPlaceholder;
944
1868
  setInputPlaceholder("Listening...");
945
1869
  };
1870
+ // Desktop Chrome: single result at index 0, transitions interim→final once.
1871
+ // Mobile Chrome: new result index per event, ALL immediately isFinal,
1872
+ // each containing the full cumulative transcript. We always take the last
1873
+ // non-empty final result and REPLACE the prompt to handle both patterns.
946
1874
  r.onresult = (event) => {
947
- let finalText = "";
1875
+ let lastFinalTranscript = "";
948
1876
  let interimText = "";
949
- for (let i = event.resultIndex; i < event.results.length; i++) {
1877
+ for (let i = event.results.length - 1; i >= 0; i--) {
950
1878
  const res = event.results[i];
951
- if (res.isFinal)
952
- finalText += res[0]?.transcript || "";
953
- else
954
- interimText += res[0]?.transcript || "";
1879
+ if (res.isFinal &&
1880
+ !lastFinalTranscript &&
1881
+ (res[0]?.transcript || "").trim()) {
1882
+ lastFinalTranscript = res[0].transcript;
1883
+ }
1884
+ if (!res.isFinal) {
1885
+ interimText = (res[0]?.transcript || "") + interimText;
1886
+ }
955
1887
  }
956
1888
  if (interimText) {
957
1889
  setInputPlaceholder(`Listening... ${interimText.trim()}`);
@@ -959,11 +1891,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
959
1891
  else {
960
1892
  setInputPlaceholder("Listening...");
961
1893
  }
962
- if (finalText && finalText.trim()) {
963
- setPrompt((prev) => {
964
- const prefix = prev && !prev.endsWith(" ") ? prev + " " : prev;
965
- return (prefix || "") + finalText.trim() + " ";
966
- });
1894
+ const base = promptBeforeVoiceRef.current;
1895
+ const separator = base && !base.endsWith(" ") ? " " : "";
1896
+ const recognized = lastFinalTranscript.trim();
1897
+ if (recognized) {
1898
+ setPrompt(base + separator + recognized + " ");
967
1899
  if (textareaRef.current) {
968
1900
  try {
969
1901
  const v = textareaRef.current.value || "";
@@ -979,7 +1911,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
979
1911
  // network error also comes from incompatible chromium client
980
1912
  if (e?.error === "network") {
981
1913
  try {
982
- localStorage.setItem(VOICE_DISABLED_KEY, "true");
1914
+ localStorageService.setString(VOICE_DISABLED_KEY, "true");
983
1915
  setIsVoiceDisabled(true);
984
1916
  }
985
1917
  catch { }
@@ -1148,25 +2080,18 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1148
2080
  // Shared stream message handlers with messageId support
1149
2081
  const createNewStreamMessage = useCallback((messageId, agentData) => {
1150
2082
  const currentAgent = agentData || agent;
1151
- if (!currentAgent) {
1152
- console.error("❌ createNewStreamMessage: No agent available", {
1153
- messageId,
1154
- agentData: !!agentData,
1155
- agent: !!agent,
1156
- });
1157
- throw new Error("No agent available");
1158
- }
2083
+ const effectiveAgentId = currentAgent?.id || agentStub.id;
1159
2084
  // Reduced: avoid verbose logging during streaming
1160
2085
  return {
1161
2086
  id: messageId,
1162
- agentId: currentAgent.id,
2087
+ agentId: effectiveAgentId,
1163
2088
  messageIndex: -1,
1164
2089
  role: "assistant",
1165
2090
  content: "",
1166
2091
  name: "agent",
1167
2092
  messageType: "streaming",
1168
2093
  isCompleted: false,
1169
- model: currentAgent.model || "",
2094
+ model: currentAgent?.model || "",
1170
2095
  tokensUsed: 0,
1171
2096
  inputTokens: 0,
1172
2097
  outputTokens: 0,
@@ -1175,16 +2100,71 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1175
2100
  outputTokenCost: 0,
1176
2101
  cachedInputTokenCost: 0,
1177
2102
  totalCost: 0,
1178
- currency: currentAgent.currency || "USD",
2103
+ currency: currentAgent?.currency || "USD",
1179
2104
  createdDate: new Date().toISOString(),
1180
2105
  toolCalls: [],
1181
2106
  };
1182
- }, [agent]);
2107
+ }, [agent, agentStub.id]);
2108
+ const stripHeartbeatMessages = useCallback((currentMessages) => currentMessages.filter((message) => message.messageType !== "heartbeat"), []);
2109
+ const handleHeartbeatMessage = useCallback((message) => {
2110
+ const heartbeatText = (message.data?.message ||
2111
+ message.data?.Message ||
2112
+ "Still working on your request. This is taking a little longer than usual.").trim() ||
2113
+ "Still working on your request. This is taking a little longer than usual.";
2114
+ const createdDate = message.timestamp || new Date().toISOString();
2115
+ const heartbeatId = `heartbeat-${agent?.id || agentStub.id}`;
2116
+ setMessages((prev) => {
2117
+ const withoutHeartbeats = stripHeartbeatMessages(prev);
2118
+ const updated = [
2119
+ ...withoutHeartbeats,
2120
+ {
2121
+ id: heartbeatId,
2122
+ agentId: agent?.id || agentStub.id,
2123
+ messageIndex: -1,
2124
+ role: "system",
2125
+ content: heartbeatText,
2126
+ name: "system",
2127
+ messageType: "heartbeat",
2128
+ isCompleted: true,
2129
+ model: "",
2130
+ tokensUsed: 0,
2131
+ inputTokens: 0,
2132
+ outputTokens: 0,
2133
+ cachedInputTokens: 0,
2134
+ inputTokenCost: 0,
2135
+ outputTokenCost: 0,
2136
+ cachedInputTokenCost: 0,
2137
+ totalCost: 0,
2138
+ currency: "USD",
2139
+ createdDate,
2140
+ toolCalls: [],
2141
+ },
2142
+ ];
2143
+ messagesRef.current = updated;
2144
+ return updated;
2145
+ });
2146
+ }, [agent?.id, agentStub.id, stripHeartbeatMessages]);
2147
+ const clearHeartbeatMessages = useCallback(() => {
2148
+ setMessages((prev) => {
2149
+ const updated = stripHeartbeatMessages(prev);
2150
+ if (updated.length === prev.length) {
2151
+ return prev;
2152
+ }
2153
+ messagesRef.current = updated;
2154
+ return updated;
2155
+ });
2156
+ }, [stripHeartbeatMessages]);
1183
2157
  const handleContentChunk = useCallback((message, agentData) => {
1184
2158
  // Get messageId from data, or generate one from agent ID (for backward compatibility)
1185
2159
  // If no messageId is provided, we'll use the last assistant message or create a new one
1186
2160
  let messageId = message.data?.messageId;
1187
2161
  if (!messageId && agentData?.id) {
2162
+ console.warn("[AgentTerminal] Content chunk missing messageId; falling back to local resolution", {
2163
+ agentId: agentData.id,
2164
+ isIncremental: message.data?.isIncremental,
2165
+ previousContentLength: message.data?.previousContentLength,
2166
+ totalContentLength: message.data?.totalContentLength,
2167
+ });
1188
2168
  // For backward compatibility: if no messageId, find or create the current streaming message
1189
2169
  // This handles cases where the backend doesn't send messageId
1190
2170
  const currentMessages = messagesRef.current;
@@ -1198,7 +2178,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1198
2178
  // If the agent isn't currently running (e.g., we switched tabs after the run
1199
2179
  // completed), skip creating a new streaming message to avoid duplicates.
1200
2180
  const currentAgentStatus = (agentData || agent)?.status;
1201
- const isAgentRunning = currentAgentStatus === "running" || currentAgentStatus === 1;
2181
+ const isAgentRunning = currentAgentStatus === "running";
1202
2182
  if (!isAgentRunning) {
1203
2183
  return;
1204
2184
  }
@@ -1231,6 +2211,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1231
2211
  outputCost: Number(cost.output) || 0,
1232
2212
  cachedCost: Number(cost.cached) || 0,
1233
2213
  cacheWriteCost: Number(cost.cacheWrite) || 0,
2214
+ imageCost: Number(cost.imageCost ?? cost.totalImageCost) ||
2215
+ 0,
1234
2216
  totalCost: Number(cost.total) || 0,
1235
2217
  currency: "USD",
1236
2218
  };
@@ -1254,11 +2236,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1254
2236
  setIsWaitingForResponse(false);
1255
2237
  shouldCreateNewMessage.current = false;
1256
2238
  }
1257
- // Extract context window info from cost object
1258
- if (cost.contextWindow && cost.contextUsed) {
1259
- const contextWindowValue = Number(cost.contextWindow);
1260
- const contextUsedValue = Number(cost.contextUsed);
1261
- if (contextWindowValue > 0 && contextUsedValue >= 0) {
2239
+ // Extract context window info from cost object.
2240
+ // Note: contextUsed can legitimately be 0 (especially early in OpenAI streams),
2241
+ // so we must check for null/undefined instead of truthiness.
2242
+ const contextWindowRaw = cost.contextWindow;
2243
+ const contextUsedRaw = cost.contextUsed;
2244
+ if (contextWindowRaw !== undefined &&
2245
+ contextWindowRaw !== null &&
2246
+ contextUsedRaw !== undefined &&
2247
+ contextUsedRaw !== null) {
2248
+ const contextWindowValue = Number(contextWindowRaw);
2249
+ const contextUsedValue = Number(contextUsedRaw);
2250
+ if (contextWindowValue > 0 &&
2251
+ Number.isFinite(contextUsedValue) &&
2252
+ contextUsedValue >= 0) {
1262
2253
  setContextWindowStatus({
1263
2254
  contextWindowTokens: contextWindowValue,
1264
2255
  estimatedInputTokens: contextUsedValue,
@@ -1274,6 +2265,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1274
2265
  const existingMessageIndex = prev.findIndex((msg) => msg.id === messageId);
1275
2266
  if (existingMessageIndex === -1) {
1276
2267
  // Message doesn't exist - create new streaming message
2268
+ const previousContentLength = message.data?.previousContentLength || 0;
2269
+ if (message.data?.isIncremental && previousContentLength > 0) {
2270
+ console.warn("[AgentTerminal] Incremental chunk arrived before its base message existed", {
2271
+ messageId,
2272
+ previousContentLength,
2273
+ totalContentLength: message.data?.totalContentLength,
2274
+ deltaLength: (message.data?.deltaContent || "").length,
2275
+ });
2276
+ }
1277
2277
  const newStreamMessage = createNewStreamMessage(messageId, agentData);
1278
2278
  // Set the content for the new message
1279
2279
  const updatedNewMessage = { ...newStreamMessage };
@@ -1296,8 +2296,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1296
2296
  return prev;
1297
2297
  // Check if existing content is already longer than what we're trying to stream
1298
2298
  const currentContentLength = existingMessage.content?.length || 0;
2299
+ const previousContentLength = message.data?.previousContentLength || 0;
1299
2300
  const totalContentLength = message.data?.totalContentLength || 0;
1300
- if (currentContentLength >= totalContentLength &&
2301
+ if (message.data?.isIncremental &&
2302
+ previousContentLength !== currentContentLength &&
2303
+ (previousContentLength > 0 || currentContentLength > 0)) {
2304
+ console.warn("[AgentTerminal] Content chunk length mismatch", {
2305
+ messageId,
2306
+ previousContentLength,
2307
+ currentContentLength,
2308
+ totalContentLength,
2309
+ deltaLength: (message.data?.deltaContent || "").length,
2310
+ });
2311
+ }
2312
+ if (message.data?.isIncremental &&
2313
+ currentContentLength >= totalContentLength &&
1301
2314
  totalContentLength > 0) {
1302
2315
  return prev;
1303
2316
  }
@@ -1321,10 +2334,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1321
2334
  });
1322
2335
  }, [createNewStreamMessage, agent]);
1323
2336
  const handleToolCall = useCallback((message, agentData) => {
1324
- const toolCallId = message.data?.toolCallId || message.data?.id || crypto.randomUUID();
2337
+ const extractedToolCall = extractToolCallFields(message.data);
2338
+ const toolCallId = extractedToolCall.toolCallId || crypto.randomUUID();
1325
2339
  // Prefer provided messageId, otherwise fall back to the last streaming assistant message
1326
2340
  let toolCallMessageId = message.data?.messageId;
1327
2341
  if (!toolCallMessageId) {
2342
+ console.warn("[AgentTerminal] Tool call missing messageId; falling back", {
2343
+ toolCallId,
2344
+ toolName: message.data?.name || message.data?.displayName,
2345
+ });
1328
2346
  const current = messagesRef.current;
1329
2347
  const lastStreaming = [...current]
1330
2348
  .reverse()
@@ -1332,7 +2350,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1332
2350
  if (lastStreaming?.id) {
1333
2351
  toolCallMessageId = lastStreaming.id;
1334
2352
  }
2353
+ else {
2354
+ // Tool calls can arrive before any assistant content chunk (common for dialog tools like ask-questionnaire).
2355
+ // Create a synthetic streaming message so the UI can render the tool call immediately.
2356
+ toolCallMessageId = crypto.randomUUID();
2357
+ }
1335
2358
  }
2359
+ appendToolUiEvent("ui:tool-call-targeted", `${extractedToolCall.functionName || "unknown"} toolCallId=${toolCallId} targetMessageId=${toolCallMessageId || "none"} providedMessageId=${String(message.data?.messageId || "none")}`);
1336
2360
  // Find or create the target message for this tool call
1337
2361
  if (toolCallMessageId) {
1338
2362
  const currentMessages = messagesRef.current;
@@ -1359,24 +2383,25 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1359
2383
  }
1360
2384
  // Add tool call to the message in the array
1361
2385
  if (toolCallMessageId && message.data && toolCallId) {
1362
- const functionName = message.data.functionName ||
1363
- message.data.name ||
1364
- message.data.function?.name ||
1365
- "unknown";
2386
+ const toolCallError = message.data.functionError || message.data.error || "";
2387
+ const isPruned = !!message.data?.isPruned || /^PRUNED$/i.test(String(toolCallError));
2388
+ const toolCallCreatedDate = message.data.createdDate ||
2389
+ message.timestamp ||
2390
+ new Date().toISOString();
1366
2391
  const toolCall = {
1367
2392
  id: toolCallId,
1368
2393
  messageId: toolCallMessageId,
1369
2394
  dbMessageId: message.data.messageId, // Database message ID for approval/rejection
1370
2395
  toolCallId: toolCallId,
1371
- functionName: functionName,
1372
- functionArguments: message.data.functionArguments ||
1373
- message.data.arguments ||
1374
- JSON.stringify(message.data.function?.arguments || {}),
2396
+ functionName: extractedToolCall.functionName,
2397
+ functionArguments: extractedToolCall.functionArguments,
1375
2398
  functionResult: message.data.functionResult || message.data.result || "",
1376
- functionError: message.data.functionError || message.data.error || "",
2399
+ functionResultRichContent: message.data.richContent || undefined,
2400
+ functionError: toolCallError,
2401
+ isPruned,
1377
2402
  isCompleted: false,
1378
2403
  responseTimeMs: message.data.responseTimeMs,
1379
- createdDate: new Date().toISOString(),
2404
+ createdDate: toolCallCreatedDate,
1380
2405
  requiresApproval: message.data?.requiresApproval,
1381
2406
  };
1382
2407
  // Check for existing tool call - search across ALL messages by toolCallId first
@@ -1415,14 +2440,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1415
2440
  // Check if the new data has more information than what we have
1416
2441
  const newArgs = toolCall.functionArguments;
1417
2442
  const existingArgs = existingToolCall.functionArguments;
1418
- const hasMoreCompleteArgs = newArgs && newArgs.length > (existingArgs?.length || 0);
2443
+ const newArgsText = stringifyToolField(newArgs) || "";
2444
+ const existingArgsText = stringifyToolField(existingArgs) || "";
2445
+ const hasMoreCompleteArgs = (newArgsText.length > existingArgsText.length &&
2446
+ newArgsText !== existingArgsText) ||
2447
+ (existingArgsText === "{}" && newArgsText !== "{}");
1419
2448
  const hasNewResult = toolCall.functionResult && !existingToolCall.functionResult;
2449
+ const hasNewRichContent = toolCall.functionResultRichContent &&
2450
+ !existingToolCall.functionResultRichContent;
1420
2451
  const hasNewError = toolCall.functionError && !existingToolCall.functionError;
1421
2452
  const hasNewApprovalInfo = toolCall.requiresApproval && !existingToolCall.requiresApproval;
1422
2453
  const hasNewDbMessageId = toolCall.dbMessageId && !existingToolCall.dbMessageId;
1423
2454
  // Only update if there's meaningful new data
1424
2455
  if (hasMoreCompleteArgs ||
1425
2456
  hasNewResult ||
2457
+ hasNewRichContent ||
1426
2458
  hasNewError ||
1427
2459
  hasNewApprovalInfo ||
1428
2460
  hasNewDbMessageId) {
@@ -1439,9 +2471,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1439
2471
  updatedToolCalls[idx] = {
1440
2472
  ...existing,
1441
2473
  functionArguments: hasMoreCompleteArgs
1442
- ? newArgs
1443
- : existing.functionArguments,
2474
+ ? newArgsText
2475
+ : existingArgsText || existing.functionArguments,
1444
2476
  functionResult: toolCall.functionResult || existing.functionResult,
2477
+ functionResultRichContent: toolCall.functionResultRichContent ||
2478
+ existing.functionResultRichContent,
1445
2479
  functionError: toolCall.functionError || existing.functionError,
1446
2480
  requiresApproval: toolCall.requiresApproval || existing.requiresApproval,
1447
2481
  };
@@ -1459,27 +2493,36 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1459
2493
  }
1460
2494
  return; // Done updating existing tool call
1461
2495
  }
1462
- setMessages((prev) => {
1463
- const updated = prev.map((msg) => {
1464
- if (msg.id !== toolCallMessageId)
1465
- return msg;
1466
- const existingToolCalls = msg.toolCalls || [];
1467
- return { ...msg, toolCalls: [...existingToolCalls, toolCall] };
2496
+ flushSync(() => {
2497
+ setMessages((prev) => {
2498
+ const updated = prev.map((msg) => {
2499
+ if (msg.id !== toolCallMessageId)
2500
+ return msg;
2501
+ const existingToolCalls = msg.toolCalls || [];
2502
+ return { ...msg, toolCalls: [...existingToolCalls, toolCall] };
2503
+ });
2504
+ messagesRef.current = updated;
2505
+ return updated;
1468
2506
  });
1469
- messagesRef.current = updated;
1470
- return updated;
1471
2507
  });
2508
+ const messageWithToolCall = messagesRef.current.find((msg) => msg.id === toolCallMessageId);
2509
+ appendToolUiEvent("ui:tool-call-attached", `${extractedToolCall.functionName || "unknown"} toolCallId=${toolCallId} targetMessageId=${toolCallMessageId} messageToolCalls=${messageWithToolCall?.toolCalls?.length || 0} assistantMessages=${messagesRef.current.filter((msg) => msg.role === "assistant").length}`);
1472
2510
  // If tool requires approval, agent is now waiting for user action - stop thinking
1473
2511
  if (message.data?.requiresApproval) {
1474
2512
  setIsAgentThinking(false);
1475
2513
  }
1476
2514
  }
1477
- }, [createNewStreamMessage]);
2515
+ }, [appendToolUiEvent, createNewStreamMessage]);
1478
2516
  const handleToolResult = useCallback((message, agentData) => {
1479
- const resultToolCallId = message.data?.toolCallId || message.data?.id || crypto.randomUUID();
2517
+ const extractedToolCall = extractToolCallFields(message.data);
2518
+ const resultToolCallId = extractedToolCall.toolCallId || crypto.randomUUID();
1480
2519
  // Prefer provided messageId, otherwise fall back to the last streaming assistant message
1481
2520
  let resultMessageId = message.data?.messageId;
1482
2521
  if (!resultMessageId) {
2522
+ console.warn("[AgentTerminal] Tool result missing messageId; falling back", {
2523
+ toolCallId: resultToolCallId,
2524
+ toolName: message.data?.functionName || message.data?.displayName,
2525
+ });
1483
2526
  const current = messagesRef.current;
1484
2527
  const lastStreaming = [...current]
1485
2528
  .reverse()
@@ -1501,6 +2544,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1501
2544
  outputCost: Number(cost.output) || 0,
1502
2545
  cachedCost: Number(cost.cached) || 0,
1503
2546
  cacheWriteCost: Number(cost.cacheWrite) || 0,
2547
+ imageCost: Number(cost.imageCost) || 0,
1504
2548
  totalCost: Number(cost.total) || 0,
1505
2549
  currency: "USD",
1506
2550
  };
@@ -1512,11 +2556,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1512
2556
  if (anyNonZero) {
1513
2557
  setLiveTotals(nextTotals);
1514
2558
  }
1515
- // Extract context window info from cost object
1516
- if (cost.contextWindow && cost.contextUsed) {
1517
- const contextWindowValue = Number(cost.contextWindow);
1518
- const contextUsedValue = Number(cost.contextUsed);
1519
- if (contextWindowValue > 0 && contextUsedValue >= 0) {
2559
+ // Extract context window info from cost object.
2560
+ // Note: contextUsed can legitimately be 0 (especially early in OpenAI streams),
2561
+ // so we must check for null/undefined instead of truthiness.
2562
+ const contextWindowRaw = cost.contextWindow;
2563
+ const contextUsedRaw = cost.contextUsed;
2564
+ if (contextWindowRaw !== undefined &&
2565
+ contextWindowRaw !== null &&
2566
+ contextUsedRaw !== undefined &&
2567
+ contextUsedRaw !== null) {
2568
+ const contextWindowValue = Number(contextWindowRaw);
2569
+ const contextUsedValue = Number(contextUsedRaw);
2570
+ if (contextWindowValue > 0 &&
2571
+ Number.isFinite(contextUsedValue) &&
2572
+ contextUsedValue >= 0) {
1520
2573
  setContextWindowStatus({
1521
2574
  contextWindowTokens: contextWindowValue,
1522
2575
  estimatedInputTokens: contextUsedValue,
@@ -1540,6 +2593,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1540
2593
  outputCost: Number(data.totalOutputTokenCost) || 0,
1541
2594
  cachedCost: Number(data.totalCachedTokenCost) || 0,
1542
2595
  cacheWriteCost: Number(data.totalCacheWriteTokenCost) || 0,
2596
+ imageCost: Number(data.totalImageCost) || 0,
1543
2597
  totalCost: Number(data.totalCost) || 0,
1544
2598
  currency: data.currency || "USD",
1545
2599
  };
@@ -1555,8 +2609,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1555
2609
  }
1556
2610
  // Update tool result directly in the messages array
1557
2611
  if (!resultMessageId) {
2612
+ appendToolUiEvent("ui:tool-result-dropped", `${extractedToolCall.functionName || "unknown"} toolCallId=${resultToolCallId} reason=no-result-message-id`);
1558
2613
  return;
1559
2614
  }
2615
+ appendToolUiEvent("ui:tool-result-targeted", `${extractedToolCall.functionName || "unknown"} toolCallId=${resultToolCallId} targetMessageId=${resultMessageId}`);
1560
2616
  // Update the message with tool result
1561
2617
  setMessages((prev) => {
1562
2618
  const updated = prev.map((msg) => {
@@ -1572,13 +2628,22 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1572
2628
  const existingToolCall = updatedMessage.toolCalls[toolCallIndex];
1573
2629
  if (existingToolCall && message.data) {
1574
2630
  const updatedToolCalls = [...updatedMessage.toolCalls];
2631
+ const nextArgsText = stringifyToolField(extractedToolCall.functionArguments) || "";
2632
+ const existingArgsText = stringifyToolField(existingToolCall.functionArguments) || "";
2633
+ const hasMoreCompleteArgs = (nextArgsText.length > existingArgsText.length &&
2634
+ nextArgsText !== existingArgsText) ||
2635
+ (existingArgsText === "{}" && nextArgsText !== "{}");
1575
2636
  const toolCall = {
1576
2637
  id: existingToolCall.id,
1577
2638
  messageId: existingToolCall.messageId,
1578
2639
  toolCallId: existingToolCall.toolCallId,
1579
2640
  functionName: existingToolCall.functionName,
1580
- functionArguments: existingToolCall.functionArguments,
2641
+ functionArguments: hasMoreCompleteArgs
2642
+ ? nextArgsText
2643
+ : existingToolCall.functionArguments,
1581
2644
  functionResult: message.data.functionResult || message.data.result || "",
2645
+ functionResultRichContent: message.data.richContent ||
2646
+ existingToolCall.functionResultRichContent,
1582
2647
  functionError: message.data.functionError || message.data.error || "",
1583
2648
  isCompleted: true,
1584
2649
  responseTimeMs: message.data.responseTimeMs,
@@ -1595,23 +2660,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1595
2660
  }
1596
2661
  else if (message.data && resultToolCallId && resultMessageId) {
1597
2662
  // Create new tool call if it doesn't exist
1598
- const functionName = message.data.functionName ||
1599
- message.data.name ||
1600
- message.data.function?.name ||
1601
- "unknown";
2663
+ const toolCallCreatedDate = message.data.createdDate ||
2664
+ message.timestamp ||
2665
+ new Date().toISOString();
1602
2666
  const toolCall = {
1603
2667
  id: resultToolCallId,
1604
2668
  messageId: resultMessageId,
1605
2669
  toolCallId: resultToolCallId,
1606
- functionName: functionName,
1607
- functionArguments: message.data.functionArguments ||
1608
- message.data.arguments ||
1609
- JSON.stringify(message.data.function?.arguments || {}),
2670
+ functionName: extractedToolCall.functionName,
2671
+ functionArguments: extractedToolCall.functionArguments,
1610
2672
  functionResult: message.data.functionResult || message.data.result || "",
2673
+ functionResultRichContent: message.data.richContent || undefined,
1611
2674
  functionError: message.data.functionError || message.data.error || "",
1612
2675
  isCompleted: true,
1613
2676
  responseTimeMs: message.data.responseTimeMs,
1614
- createdDate: new Date().toISOString(),
2677
+ createdDate: toolCallCreatedDate,
1615
2678
  };
1616
2679
  updatedMessage.toolCalls = [...updatedMessage.toolCalls, toolCall];
1617
2680
  }
@@ -1619,9 +2682,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1619
2682
  return updatedMessage;
1620
2683
  });
1621
2684
  messagesRef.current = updated;
2685
+ const messageWithToolResult = updated.find((msg) => msg.id === resultMessageId);
2686
+ const matchingToolCall = messageWithToolResult?.toolCalls?.find((tc) => tc.toolCallId === resultToolCallId);
2687
+ appendToolUiEvent("ui:tool-result-applied", `${extractedToolCall.functionName || "unknown"} toolCallId=${resultToolCallId} targetMessageId=${resultMessageId} completed=${matchingToolCall?.isCompleted ? "yes" : "no"} messageToolCalls=${messageWithToolResult?.toolCalls?.length || 0}`);
1622
2688
  return updated;
1623
2689
  });
1624
- }, []);
2690
+ }, [appendToolUiEvent]);
1625
2691
  // Listen for local approval resolution to update UI
1626
2692
  useEffect(() => {
1627
2693
  const onApprovalResolved = (ev) => {
@@ -1702,7 +2768,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1702
2768
  // The agent might have been persisted after sending a prompt
1703
2769
  // Only treat as "new" if backend returns 404
1704
2770
  const hasExistingMessages = messagesRef.current.length > 0;
1705
- if (agentStub.status === "new" && !hasExistingMessages) {
2771
+ if (agentStub.status === "new" &&
2772
+ !agentStub.userId &&
2773
+ !hasExistingMessages) {
1706
2774
  // Only initialize as new if we have no messages yet (initial mount)
1707
2775
  // Derive initial profile from provided metadata if present
1708
2776
  const initialProfileIdFromMeta = (() => {
@@ -1742,6 +2810,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1742
2810
  totalInputTokenCost: 0,
1743
2811
  totalOutputTokenCost: 0,
1744
2812
  totalCachedInputTokenCost: 0,
2813
+ totalImageCost: 0,
1745
2814
  totalCost: 0,
1746
2815
  messageCount: 0,
1747
2816
  });
@@ -1784,48 +2853,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1784
2853
  }
1785
2854
  })();
1786
2855
  // Create context with top-level properties (what ContextInfoBar expects)
1787
- const localCtx = item && shouldSeedContext
1788
- ? {
1789
- items: [
1790
- {
1791
- id: item.id,
1792
- language: item.language,
1793
- version: item.version,
1794
- name: editContext?.item?.name,
1795
- path: editContext?.item?.path,
1796
- },
1797
- ],
1798
- components: editContext?.selection?.length && item
1799
- ? editContext.selection.map((componentId) => ({
1800
- componentId,
1801
- pageItem: {
1802
- id: item.id,
1803
- language: item.language,
1804
- version: item.version,
1805
- name: editContext?.item?.name,
1806
- },
1807
- }))
1808
- : undefined,
1809
- field: fieldsContext?.focusedField?.fieldId &&
1810
- fieldsContext.focusedField?.item?.id
1811
- ? {
1812
- fieldId: fieldsContext.focusedField.fieldId,
1813
- fieldName: fieldsContext.focusedField
1814
- .fieldName,
1815
- item: {
1816
- id: fieldsContext.focusedField.item.id,
1817
- language: fieldsContext.focusedField.item.language ||
1818
- editContext?.currentItemDescriptor?.language ||
1819
- "en",
1820
- version: fieldsContext.focusedField.item.version ??
1821
- editContext?.currentItemDescriptor?.version ??
1822
- 0,
1823
- name: editContext?.item?.name,
1824
- },
1825
- }
1826
- : undefined,
1827
- }
1828
- : null;
2856
+ const localCtx = item && shouldSeedContext ? buildEditorContextPayload(item) : null;
1829
2857
  let nextMetadata = null;
1830
2858
  if (initialMetadata) {
1831
2859
  // Merge initial metadata with local context (using top-level structure)
@@ -1901,8 +2929,16 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1901
2929
  seenMessageIdsRef.current.add(msg.id.toLowerCase());
1902
2930
  }
1903
2931
  });
1904
- // Keep local streaming only if the agent is still running; otherwise discard locals
2932
+ // Keep local streaming if the agent is still active (running/waiting); otherwise discard locals.
2933
+ // This is important for dialog-style tools (e.g., ask-questionnaire) where the agent may be
2934
+ // "waiting" but we still want to keep the in-flight tool call UI visible.
1905
2935
  const isRunning = agentData.status === "running" || agentData.status === 1;
2936
+ const isWaiting = agentData.status === "waitingForApproval" ||
2937
+ agentData.status === 2 ||
2938
+ agentData.status === "waitingForInput" ||
2939
+ agentData.status === "costLimitReached" ||
2940
+ agentData.status === 7;
2941
+ const keepLocalStreaming = isRunning || isWaiting;
1906
2942
  // Filter local messages to only include streaming/incomplete messages that aren't in DB
1907
2943
  // This prevents duplicates when reloading - DB messages are source of truth for completed messages
1908
2944
  const localStreaming = isRunning
@@ -1920,7 +2956,19 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1920
2956
  // Don't keep completed messages from local state - DB is source of truth
1921
2957
  return false;
1922
2958
  })
1923
- : [];
2959
+ : keepLocalStreaming
2960
+ ? messagesRef.current.filter((localMsg) => {
2961
+ if (!localMsg.id)
2962
+ return false;
2963
+ if (!localMsg.isCompleted &&
2964
+ localMsg.messageType === "streaming") {
2965
+ const existsInDb = dbMessages.some((dbMsg) => dbMsg.id &&
2966
+ dbMsg.id.toLowerCase() === localMsg.id.toLowerCase());
2967
+ return !existsInDb;
2968
+ }
2969
+ return false;
2970
+ })
2971
+ : [];
1924
2972
  const merged = mergeMessagesById(dbMessages, localStreaming);
1925
2973
  messagesRef.current = merged;
1926
2974
  setMessages(merged);
@@ -1931,6 +2979,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1931
2979
  const runningNow = agentData.status === "running" || agentData.status === 1;
1932
2980
  const waitingForApprovalNow = agentData.status === "waitingForApproval" ||
1933
2981
  agentData.status === 2;
2982
+ const waitingForInputNow = agentData.status === "waitingForInput";
1934
2983
  const hasStreamingNow = merged.some((m) => !m.isCompleted && m.messageType === "streaming");
1935
2984
  if (runningNow || hasStreamingNow) {
1936
2985
  setIsWaitingForResponse(true);
@@ -1938,11 +2987,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1938
2987
  // Agent is actively running, show thinking dots
1939
2988
  setIsAgentThinking(true);
1940
2989
  }
1941
- else if (waitingForApprovalNow) {
2990
+ else if (waitingForApprovalNow || waitingForInputNow) {
1942
2991
  setIsWaitingForResponse(false);
1943
2992
  isWaitingRef.current = false;
1944
2993
  setIsConnecting(false);
1945
- // Agent is waiting for user approval, hide thinking dots
2994
+ // Agent is waiting for user input/approval, hide thinking dots
1946
2995
  setIsAgentThinking(false);
1947
2996
  }
1948
2997
  else {
@@ -1991,19 +3040,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1991
3040
  if (!contextJson)
1992
3041
  return null;
1993
3042
  const parsedContext = JSON.parse(contextJson);
1994
- // Context is stored as flat structure with top-level properties
1995
- // Due to C# [JsonExtensionData], AdditionalData entries are serialized at top level
1996
- // We need to normalize: move todoList from top level into additionalData if present
1997
3043
  if (parsedContext && typeof parsedContext === "object") {
1998
- const normalized = { ...parsedContext };
1999
- // If todoList is at top level but not in additionalData, move it
2000
- if (normalized.todoList && !normalized.additionalData?.todoList) {
2001
- normalized.additionalData = {
2002
- ...(normalized.additionalData || {}),
2003
- todoList: normalized.todoList,
2004
- };
2005
- }
2006
- return normalized;
3044
+ return parsedContext;
2007
3045
  }
2008
3046
  return null;
2009
3047
  }
@@ -2089,7 +3127,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2089
3127
  return;
2090
3128
  }
2091
3129
  // Check if cost limit exceeded based on status or cost values
2092
- const statusIndicatesLimit = agent.status === "costLimitReached" || agent.status === 7;
3130
+ const statusIndicatesLimit = agent.status === "costLimitReached";
2093
3131
  // Use liveTotals.totalCost as fallback if agent.totalCost is missing or 0
2094
3132
  const effectiveTotalCost = agent.totalCost || liveTotals?.totalCost || 0;
2095
3133
  const costExceedsLimit = agent.costLimit &&
@@ -2174,9 +3212,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2174
3212
  // Handle agent:profile:switched (profile changed via switch-profile function)
2175
3213
  if (messageType === "agent:profile:switched") {
2176
3214
  const payload = message.payload || {};
2177
- const switchedAgentId = payload.agentId || payload.AgentId;
2178
- const newProfileId = payload.newProfileId || payload.NewProfileId;
2179
- const newProfileName = payload.newProfileName || payload.NewProfileName;
3215
+ const switchedAgentId = payload.agentId;
3216
+ const newProfileId = payload.newProfileId;
3217
+ const newProfileName = payload.newProfileName;
2180
3218
  if (switchedAgentId === agent.id && newProfileId) {
2181
3219
  // Update the agent's profile
2182
3220
  setAgent((prev) => {
@@ -2217,9 +3255,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2217
3255
  return;
2218
3256
  }
2219
3257
  // For other agent messages, check if this is for our agent
2220
- const agentId = message.payload?.agentId || message.payload?.AgentId;
2221
- if (agentId !== agent.id)
3258
+ const agentId = message.payload?.agentId;
3259
+ if (agentId !== agent.id) {
2222
3260
  return;
3261
+ }
2223
3262
  // Handle agent:run:start
2224
3263
  if (messageType === "agent:run:start") {
2225
3264
  // If a stop operation is in progress, ignore this message to prevent
@@ -2228,6 +3267,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2228
3267
  console.log("[AgentTerminal] Ignoring agent:run:start during stop operation");
2229
3268
  return;
2230
3269
  }
3270
+ const currentStatus = agentRef.current?.status;
3271
+ if (currentStatus === "waitingForInput" ||
3272
+ currentStatus === "waitingForApproval" ||
3273
+ currentStatus === "costLimitReached") {
3274
+ // Replayed start messages arrive before the buffered status payload when
3275
+ // reopening an already-paused agent. Preserve the attention state instead
3276
+ // of flashing "running" until the follow-up status update lands.
3277
+ return;
3278
+ }
2231
3279
  // Reset run-scoped deduplication so new run seq values (starting at 1)
2232
3280
  // are not discarded due to previous run's lastSeqRef
2233
3281
  lastSeqRef.current = 0;
@@ -2323,6 +3371,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2323
3371
  if (messageType === "agent:prompt:queued") {
2324
3372
  const { queueEntry } = message.payload;
2325
3373
  if (queueEntry) {
3374
+ if (shouldSuppressQueuedPrompt(queueEntry)) {
3375
+ return;
3376
+ }
2326
3377
  // Add the new prompt to the queued prompts list
2327
3378
  setQueuedPrompts((prev) => {
2328
3379
  // Check if prompt already exists (deduplication)
@@ -2351,17 +3402,29 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2351
3402
  return;
2352
3403
  }
2353
3404
  const { seq, type, data, cost } = message.payload;
3405
+ if (type === "ToolCall" || type === "toolCall") {
3406
+ }
2354
3407
  // Always allow ContextUpdate messages (metadata updates) regardless of agent status
2355
3408
  const isContextUpdate = type === "ContextUpdate" || type === "contextUpdate";
3409
+ const isHeartbeat = type === "Heartbeat" || type === "heartbeat";
3410
+ const shouldClearHeartbeat = type === "ContentChunk" ||
3411
+ type === "contentChunk" ||
3412
+ type === "ToolCall" ||
3413
+ type === "toolCall" ||
3414
+ type === "ToolResult" ||
3415
+ type === "toolResult";
2356
3416
  // Allow deltas if the agent is running OR if we already have an active streaming message
2357
3417
  // OR if the server provided a messageId for targeted updates.
2358
3418
  // This avoids dropping early deltas that may arrive before the 'running' status update.
2359
3419
  // ContextUpdate messages are always allowed as they're metadata updates, not content.
2360
3420
  const isRunning = agent.status === "running" || agent.status === 1;
2361
3421
  const hasStreaming = messagesRef.current.some((m) => !m.isCompleted && m.messageType === "streaming");
2362
- const hasMessageId = !!message?.payload?.data?.messageId ||
2363
- !!message?.payload?.data?.MessageId;
2364
- if (!isContextUpdate && !isRunning && !hasStreaming && !hasMessageId) {
3422
+ const hasMessageId = !!message?.payload?.data?.messageId;
3423
+ if (!isContextUpdate &&
3424
+ !isHeartbeat &&
3425
+ !isRunning &&
3426
+ !hasStreaming &&
3427
+ !hasMessageId) {
2365
3428
  return; // ignore only if we cannot safely target an existing streaming message
2366
3429
  }
2367
3430
  // Deduplicate by sequence
@@ -2378,15 +3441,23 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2378
3441
  cost,
2379
3442
  timestamp: new Date().toISOString(),
2380
3443
  };
3444
+ if (shouldClearHeartbeat) {
3445
+ clearHeartbeatMessages();
3446
+ }
2381
3447
  if (type === "ContentChunk" || type === "contentChunk") {
2382
3448
  handleContentChunk(agentStreamMessage, agent);
2383
3449
  }
2384
3450
  else if (type === "ToolCall" || type === "toolCall") {
3451
+ appendToolUiEvent("ui:tool-delta-received", `ToolCall:${String(data?.functionName || data?.name || data?.displayName || "unknown")} messageId=${String(data?.messageId || "none")} activeStreaming=${messagesRef.current.some((m) => !m.isCompleted && m.messageType === "streaming") ? "yes" : "no"}`, seq ?? null);
2385
3452
  handleToolCall(agentStreamMessage, agent);
2386
3453
  }
2387
3454
  else if (type === "ToolResult" || type === "toolResult") {
3455
+ appendToolUiEvent("ui:tool-delta-received", `ToolResult:${String(data?.functionName || data?.name || data?.displayName || "unknown")} messageId=${String(data?.messageId || "none")} activeStreaming=${messagesRef.current.some((m) => !m.isCompleted && m.messageType === "streaming") ? "yes" : "no"}`, seq ?? null);
2388
3456
  handleToolResult(agentStreamMessage, agent);
2389
3457
  }
3458
+ else if (type === "Heartbeat" || type === "heartbeat") {
3459
+ handleHeartbeatMessage(agentStreamMessage);
3460
+ }
2390
3461
  else if (type === "ContextUpdate" || type === "contextUpdate") {
2391
3462
  // Handle context updates from streaming - data contains additionalData.todoList and ChildAgents
2392
3463
  const contextData = data;
@@ -2396,10 +3467,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2396
3467
  const current = (prev || {});
2397
3468
  const updated = { ...current };
2398
3469
  // Merge additionalData if present (deep merge to preserve existing values)
2399
- if (contextData.additionalData || contextData.AdditionalData) {
2400
- const sourceAdditionalData = contextData.additionalData ||
2401
- contextData.AdditionalData ||
2402
- {};
3470
+ if (contextData.additionalData) {
3471
+ const sourceAdditionalData = contextData.additionalData || {};
2403
3472
  updated.additionalData = {
2404
3473
  ...(current.additionalData || {}),
2405
3474
  ...sourceAdditionalData,
@@ -2407,10 +3476,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2407
3476
  }
2408
3477
  // Merge ChildAgents if present (for spawned agents) - replace entire array
2409
3478
  // Backend sends the complete updated list, not just additions
2410
- if (contextData.ChildAgents || contextData.childAgents) {
2411
- const childAgents = contextData.ChildAgents || contextData.childAgents;
3479
+ if (contextData.childAgents) {
3480
+ const childAgents = contextData.childAgents;
2412
3481
  if (Array.isArray(childAgents)) {
2413
- updated.ChildAgents = childAgents;
3482
+ updated.childAgents = childAgents;
2414
3483
  }
2415
3484
  }
2416
3485
  return updated;
@@ -2436,15 +3505,18 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2436
3505
  // Route based on statusData.state
2437
3506
  try {
2438
3507
  // Normalize various status shapes and handle Cancelled uniformly
2439
- const normalizedStatus = statusData?.state ||
2440
- statusData?.Status ||
2441
- statusData?.status;
2442
- if (normalizedStatus === "Cancelled" ||
2443
- normalizedStatus === "canceled") {
3508
+ const normalizedStatus = parseAgentStatus(statusData?.state) ||
3509
+ parseAgentStatus(statusData?.status);
3510
+ if (normalizedStatus === "idle") {
2444
3511
  // Stop indicators and mark any in-progress streaming messages as completed
3512
+ clearHeartbeatMessages();
3513
+ setAgent((prev) => (prev ? { ...prev, status: "idle" } : prev));
2445
3514
  setIsWaitingForResponse(false);
2446
3515
  isWaitingRef.current = false;
2447
3516
  setIsConnecting(false);
3517
+ setIsSubmitting(false);
3518
+ shouldCreateNewMessage.current = false;
3519
+ setIsAgentThinking(false);
2448
3520
  setMessages((prev) => {
2449
3521
  const updated = prev.map((msg) => !msg.isCompleted && msg.messageType === "streaming"
2450
3522
  ? {
@@ -2487,6 +3559,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2487
3559
  outputCost: Number(totals.totalOutputTokenCost) || 0,
2488
3560
  cachedCost: Number(totals.totalCachedInputTokenCost) || 0,
2489
3561
  cacheWriteCost: Number(totals.totalCacheWriteTokenCost) || 0,
3562
+ imageCost: Number(totals.totalImageCost) || 0,
2490
3563
  totalCost: totalCost,
2491
3564
  currency: totals.currency,
2492
3565
  };
@@ -2498,6 +3571,26 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2498
3571
  if (anyNonZero) {
2499
3572
  setLiveTotals(nextTotals);
2500
3573
  }
3574
+ // Fallback context usage update for providers that do not include
3575
+ // context usage in every stream delta (for example some OpenAI streams).
3576
+ // Prefer explicit status values when present; otherwise derive from totals.
3577
+ const contextWindowValue = Number(statusData?.contextWindow ??
3578
+ agent?.contextWindowTokens ??
3579
+ 0);
3580
+ const contextUsedValue = Number(statusData?.contextUsed ??
3581
+ (Number(totals.totalInputTokens) || 0) +
3582
+ (Number(totals.totalCachedInputTokens) || 0) +
3583
+ (Number(totals.totalCacheWriteTokens) || 0));
3584
+ if (contextWindowValue > 0 &&
3585
+ Number.isFinite(contextUsedValue) &&
3586
+ contextUsedValue >= 0) {
3587
+ setContextWindowStatus({
3588
+ contextWindowTokens: contextWindowValue,
3589
+ estimatedInputTokens: contextUsedValue,
3590
+ contextUsedPercent: (contextUsedValue / contextWindowValue) * 100,
3591
+ model: agent?.model || "",
3592
+ });
3593
+ }
2501
3594
  // If server provides costLimit along with totals, persist it locally
2502
3595
  if (statusCostLimit) {
2503
3596
  setAgent((prev) => prev &&
@@ -2519,6 +3612,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2519
3612
  return;
2520
3613
  }
2521
3614
  if (statusData?.state === "ToolApprovalsRequired") {
3615
+ setPendingBrowserCaptureDialogType(null);
2522
3616
  const msgId = statusData.messageId;
2523
3617
  const ids = statusData.toolCallIds || [];
2524
3618
  if (msgId && Array.isArray(ids) && ids.length > 0) {
@@ -2550,16 +3644,40 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2550
3644
  setIsWaitingForResponse(false);
2551
3645
  return;
2552
3646
  }
2553
- // Handle "WaitingForApproval" state explicitly
2554
- if (statusData?.state === "WaitingForApproval" ||
2555
- statusData?.state === "waitingForApproval") {
3647
+ // Handle waiting states explicitly
3648
+ if (normalizedStatus === "waitingForApproval") {
3649
+ setPendingBrowserCaptureDialogType(null);
2556
3650
  setAgent((prev) => prev ? { ...prev, status: "waitingForApproval" } : prev);
2557
3651
  setIsConnecting(false);
2558
3652
  setIsWaitingForResponse(false);
2559
3653
  setIsAgentThinking(false);
2560
3654
  return;
2561
3655
  }
2562
- if (statusData?.state === "CostLimitReached") {
3656
+ if (normalizedStatus === "waitingForInput") {
3657
+ const dialogType = typeof statusData?.dialogType === "string"
3658
+ ? statusData.dialogType
3659
+ : null;
3660
+ const isBrowserCaptureWait = dialogType === DIALOG_TYPES.CAPTURE_PAGE_DOM ||
3661
+ dialogType === DIALOG_TYPES.CAPTURE_PAGE_SCREENSHOT;
3662
+ setPendingBrowserCaptureDialogType(isBrowserCaptureWait ? dialogType : null);
3663
+ setAgent((prev) => prev
3664
+ ? {
3665
+ ...prev,
3666
+ status: "waitingForInput",
3667
+ statusMessage: isBrowserCaptureWait &&
3668
+ typeof statusData?.title === "string" &&
3669
+ statusData.title.trim()
3670
+ ? statusData.title.trim()
3671
+ : prev.statusMessage,
3672
+ }
3673
+ : prev);
3674
+ setIsConnecting(false);
3675
+ setIsWaitingForResponse(false);
3676
+ setIsAgentThinking(false);
3677
+ return;
3678
+ }
3679
+ if (normalizedStatus === "costLimitReached") {
3680
+ setPendingBrowserCaptureDialogType(null);
2563
3681
  const totalCost = Number(statusData.totalCost) || 0;
2564
3682
  const costLimit = Number(statusData.costLimit) || agent?.costLimit || 0;
2565
3683
  setCostLimitExceeded({
@@ -2577,7 +3695,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2577
3695
  // Server sends additionalData directly (with todoList inside)
2578
3696
  // Also may include ChildAgents for spawned agents
2579
3697
  try {
2580
- const serverAdditionalData = statusData.additionalData || statusData.AdditionalData || {};
3698
+ const serverAdditionalData = statusData.additionalData || {};
2581
3699
  setAgentMetadata((prev) => {
2582
3700
  const current = (prev || {});
2583
3701
  const updated = { ...current };
@@ -2591,10 +3709,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2591
3709
  }
2592
3710
  // Merge ChildAgents if present (for spawned agents) - replace entire array
2593
3711
  // Backend sends the complete updated list, not just additions
2594
- if (statusData.ChildAgents || statusData.childAgents) {
2595
- const childAgents = statusData.ChildAgents || statusData.childAgents;
3712
+ if (statusData.childAgents) {
3713
+ const childAgents = statusData.childAgents;
2596
3714
  if (Array.isArray(childAgents)) {
2597
- updated.ChildAgents = childAgents;
3715
+ updated.childAgents = childAgents;
2598
3716
  }
2599
3717
  }
2600
3718
  return updated;
@@ -2606,10 +3724,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2606
3724
  return;
2607
3725
  }
2608
3726
  // Handle "completed" state (fallback for legacy code paths that send status instead of lifecycle event)
2609
- if (normalizedStatus === "completed" ||
2610
- normalizedStatus === "Completed") {
3727
+ if (normalizedStatus === "completed") {
2611
3728
  // Reset deduplication for the next run
2612
3729
  lastSeqRef.current = 0;
3730
+ clearHeartbeatMessages();
2613
3731
  // Mark the last assistant message as completed
2614
3732
  setMessages((prev) => {
2615
3733
  const updated = prev.map((msg) => msg.role === "assistant" && !msg.isCompleted
@@ -2632,30 +3750,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2632
3750
  setIsAgentThinking(false);
2633
3751
  return;
2634
3752
  }
2635
- // Handle "Idle" state - agent has finished processing
2636
- if (normalizedStatus === "idle" || normalizedStatus === "Idle") {
2637
- // Update agent status to idle
2638
- setAgent((prev) => (prev ? { ...prev, status: "idle" } : prev));
2639
- setIsWaitingForResponse(false);
2640
- isWaitingRef.current = false;
2641
- setIsConnecting(false);
2642
- shouldCreateNewMessage.current = false;
2643
- setIsAgentThinking(false);
2644
- return;
2645
- }
2646
3753
  // Handle "Running" state - agent is actively processing
2647
- if (normalizedStatus === "running" ||
2648
- normalizedStatus === "Running") {
2649
- // Update agent status to running
2650
- setAgent((prev) => (prev ? { ...prev, status: "running" } : prev));
3754
+ if (normalizedStatus === "running") {
3755
+ // Update agent status to running and clear any previous error statusMessage
3756
+ setAgent((prev) => prev
3757
+ ? { ...prev, status: "running", statusMessage: undefined }
3758
+ : prev);
2651
3759
  setIsWaitingForResponse(true);
2652
3760
  isWaitingRef.current = true;
2653
3761
  setIsAgentThinking(true);
2654
3762
  return;
2655
3763
  }
2656
3764
  // Handle "Error" state
2657
- if (normalizedStatus === "error" || normalizedStatus === "Error") {
2658
- const errorMsg = statusData?.statusMessage || statusData?.error || "Unknown error";
3765
+ if (normalizedStatus === "error") {
3766
+ const errorMsg = toUserFacingAgentErrorMessage(statusData?.statusMessage ?? statusData?.error) || "Unknown error";
3767
+ clearHeartbeatMessages();
2659
3768
  setAgent((prev) => prev
2660
3769
  ? { ...prev, status: "error", statusMessage: errorMsg }
2661
3770
  : prev);
@@ -2675,6 +3784,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2675
3784
  if (messageType === "agent:run:complete") {
2676
3785
  // Reset deduplication for the next run
2677
3786
  lastSeqRef.current = 0;
3787
+ clearHeartbeatMessages();
2678
3788
  // Mark the last assistant message as completed
2679
3789
  setMessages((prev) => {
2680
3790
  const updated = prev.map((msg) => msg.role === "assistant" && !msg.isCompleted
@@ -2699,7 +3809,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2699
3809
  }
2700
3810
  // Lifecycle: agent:run:error
2701
3811
  if (messageType === "agent:run:error") {
2702
- const errorMsg = message.payload?.error || "Unknown error";
3812
+ const errorMsg = toUserFacingAgentErrorMessage(message.payload?.error) ||
3813
+ "AI could not complete this request.";
3814
+ clearHeartbeatMessages();
2703
3815
  // Reset deduplication for the next run after an error
2704
3816
  lastSeqRef.current = 0;
2705
3817
  setError(errorMsg);
@@ -2714,7 +3826,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2714
3826
  }
2715
3827
  }, [
2716
3828
  agent,
3829
+ clearHeartbeatMessages,
2717
3830
  handleContentChunk,
3831
+ handleHeartbeatMessage,
2718
3832
  handleToolCall,
2719
3833
  handleToolResult,
2720
3834
  onAgentUpdate,
@@ -2769,6 +3883,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2769
3883
  const isRunning = currentAgent.status === "running" || currentAgent.status === 1;
2770
3884
  const isWaitingForApproval = currentAgent.status === "waitingForApproval" ||
2771
3885
  currentAgent.status === 2;
3886
+ const isWaitingForInput = currentAgent.status === "waitingForInput";
2772
3887
  if (isRunning) {
2773
3888
  setIsWaitingForResponse(true);
2774
3889
  isWaitingRef.current = true;
@@ -2776,10 +3891,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2776
3891
  // Agent is currently running, show thinking dots
2777
3892
  setIsAgentThinking(true);
2778
3893
  }
2779
- else if (isWaitingForApproval) {
3894
+ else if (isWaitingForApproval || isWaitingForInput) {
2780
3895
  setIsWaitingForResponse(false);
2781
3896
  isWaitingRef.current = false;
2782
- // Agent is waiting for user approval, hide thinking dots
3897
+ // Agent is waiting for user input/approval, hide thinking dots
2783
3898
  setIsAgentThinking(false);
2784
3899
  }
2785
3900
  else {
@@ -2826,36 +3941,157 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2826
3941
  window.addEventListener("editor:focusAgentPrompt", focusHandler);
2827
3942
  return () => window.removeEventListener("editor:focusAgentPrompt", focusHandler);
2828
3943
  }, []);
3944
+ // Keep stable refs so we don't miss window events during effect re-runs.
3945
+ const agentIdRefForDialogs = useRef(null);
3946
+ const agentStubIdRefForDialogs = useRef(agentStub.id);
3947
+ const isActiveRefForDialogs = useRef(isActive);
3948
+ const scrollToBottomRefForDialogs = useRef(scrollToBottom);
3949
+ useEffect(() => {
3950
+ agentIdRefForDialogs.current = agent?.id ?? null;
3951
+ }, [agent?.id]);
3952
+ useEffect(() => {
3953
+ const prevId = agentStubIdRefForDialogs.current;
3954
+ agentStubIdRefForDialogs.current = agentStub.id;
3955
+ const visibleRegistry = { ...getVisibleDialogRegistry() };
3956
+ const normalizedPrevId = normalizeDialogAgentId(prevId);
3957
+ if (normalizedPrevId) {
3958
+ delete visibleRegistry[normalizedPrevId];
3959
+ globalThis.__agentDialogVisibleCallbacks = visibleRegistry;
3960
+ }
3961
+ if (prevId && prevId !== agentStub.id) {
3962
+ const orphanedDialog = activeInlineDialogRef.current;
3963
+ if (orphanedDialog) {
3964
+ setActiveInlineDialog(null);
3965
+ window.dispatchEvent(new CustomEvent("agent:dialog:orphaned", {
3966
+ detail: { callbackId: orphanedDialog.request.callbackId },
3967
+ }));
3968
+ }
3969
+ }
3970
+ }, [agentStub.id]);
3971
+ useEffect(() => {
3972
+ isActiveRefForDialogs.current = isActive;
3973
+ }, [isActive]);
3974
+ useEffect(() => {
3975
+ scrollToBottomRefForDialogs.current = scrollToBottom;
3976
+ }, [scrollToBottom]);
2829
3977
  // Listen for agent inline dialog requests from AgentDialogHandler
2830
3978
  useEffect(() => {
3979
+ if (orphanTimeoutRef.current) {
3980
+ clearTimeout(orphanTimeoutRef.current);
3981
+ orphanTimeoutRef.current = null;
3982
+ }
3983
+ const globalListeners = (globalThis.__agentDialogMountedAgents ?? []).filter((x) => typeof x === "string");
3984
+ const normalizedAgentStubId = normalizeDialogAgentId(agentStubIdRefForDialogs.current);
3985
+ if (normalizedAgentStubId &&
3986
+ !globalListeners.includes(normalizedAgentStubId)) {
3987
+ globalListeners.push(normalizedAgentStubId);
3988
+ }
3989
+ globalThis.__agentDialogMountedAgents = globalListeners;
2831
3990
  const handleDialogShow = (event) => {
2832
- const { request, onComplete, onCancel } = event.detail;
2833
- // Only handle dialog requests for this agent
2834
- if (request.agentId && agent?.id && request.agentId !== agent.id) {
3991
+ const detail = event?.detail;
3992
+ const request = detail?.request;
3993
+ const onComplete = detail?.onComplete;
3994
+ const onCancel = detail?.onCancel;
3995
+ const terminalAgentId = normalizeDialogAgentId(agentIdRefForDialogs.current);
3996
+ const terminalAgentStubId = normalizeDialogAgentId(agentStubIdRefForDialogs.current);
3997
+ const isActiveNow = isActiveRefForDialogs.current;
3998
+ if (!request)
3999
+ return;
4000
+ const requestAgentId = normalizeDialogAgentId(request.agentId);
4001
+ const agentMatch = !requestAgentId ||
4002
+ !terminalAgentStubId ||
4003
+ requestAgentId === terminalAgentStubId ||
4004
+ requestAgentId === terminalAgentId;
4005
+ if (!isActiveNow) {
4006
+ return;
4007
+ }
4008
+ if (!request)
4009
+ return;
4010
+ // Only handle dialog requests for this terminal's agent stub
4011
+ if (requestAgentId &&
4012
+ terminalAgentStubId &&
4013
+ requestAgentId !== terminalAgentStubId &&
4014
+ requestAgentId !== terminalAgentId) {
2835
4015
  return;
2836
4016
  }
2837
4017
  console.log("[AgentTerminal] Received inline dialog request:", request);
2838
- setActiveInlineDialog({ request, onComplete, onCancel });
4018
+ setActiveInlineDialog({
4019
+ request,
4020
+ onComplete: (result) => {
4021
+ onComplete(result);
4022
+ onInteractionSubmitted?.();
4023
+ },
4024
+ onCancel: () => {
4025
+ onCancel();
4026
+ onInteractionSubmitted?.();
4027
+ },
4028
+ });
2839
4029
  // Notify AgentDialogHandler that we accepted the dialog (stops retry mechanism)
2840
- window.dispatchEvent(new CustomEvent("agent:dialog:accepted", {
2841
- detail: { callbackId: request.callbackId },
2842
- }));
2843
- // Scroll to bottom to show the dialog
2844
- setTimeout(scrollToBottom, 100);
4030
+ if (request.callbackId) {
4031
+ window.dispatchEvent(new CustomEvent("agent:dialog:accepted", {
4032
+ detail: { callbackId: request.callbackId },
4033
+ }));
4034
+ }
4035
+ setTimeout(() => {
4036
+ if (request.dialogType === "questionnaire") {
4037
+ scrollToBottomRefForDialogs.current?.();
4038
+ return;
4039
+ }
4040
+ scrollToBottomRefForDialogs.current?.();
4041
+ }, 100);
2845
4042
  };
2846
4043
  window.addEventListener("agent:dialog:show", handleDialogShow);
2847
- return () => window.removeEventListener("agent:dialog:show", handleDialogShow);
2848
- }, [agent?.id, scrollToBottom]);
2849
- // Announce when this terminal is ready to accept dialogs
2850
- // This allows AgentDialogHandler to re-dispatch pending dialogs when switching to an agent
4044
+ return () => {
4045
+ const mounted = (globalThis.__agentDialogMountedAgents ?? []).filter((x) => typeof x === "string");
4046
+ globalThis.__agentDialogMountedAgents = mounted.filter((id) => id !== normalizeDialogAgentId(agentStubIdRefForDialogs.current));
4047
+ const visibleRegistry = { ...getVisibleDialogRegistry() };
4048
+ const idsToClear = [
4049
+ normalizeDialogAgentId(agentStubIdRefForDialogs.current),
4050
+ normalizeDialogAgentId(agentIdRefForDialogs.current),
4051
+ ].filter(Boolean);
4052
+ idsToClear.forEach((id) => delete visibleRegistry[id]);
4053
+ globalThis.__agentDialogVisibleCallbacks = visibleRegistry;
4054
+ // If unmounting with an active dialog, defer the orphan event so that
4055
+ // React strict mode remounts can cancel it. Only real unmounts fire.
4056
+ const orphanedDialog = activeInlineDialogRef.current;
4057
+ if (orphanedDialog) {
4058
+ orphanTimeoutRef.current = setTimeout(() => {
4059
+ orphanTimeoutRef.current = null;
4060
+ window.dispatchEvent(new CustomEvent("agent:dialog:orphaned", {
4061
+ detail: { callbackId: orphanedDialog.request.callbackId },
4062
+ }));
4063
+ }, 300);
4064
+ }
4065
+ window.removeEventListener("agent:dialog:show", handleDialogShow);
4066
+ };
4067
+ }, []);
4068
+ // Announce when this terminal is ready to accept dialogs.
4069
+ // Fire immediately on mount using agentStub.id so the AgentDialogHandler
4070
+ // can re-dispatch pending dialogs without waiting for the async agent load.
4071
+ // Also fire when agent?.id becomes available in case it differs from the stub.
2851
4072
  useEffect(() => {
2852
- if (agent?.id) {
2853
- console.log("[AgentTerminal] Terminal ready for agent:", agent.id);
4073
+ const normalizedStubId = normalizeDialogAgentId(agentStub.id);
4074
+ if (normalizedStubId) {
2854
4075
  window.dispatchEvent(new CustomEvent("agent:terminal:ready", {
2855
- detail: { agentId: agent.id },
4076
+ detail: {
4077
+ agentId: normalizedStubId,
4078
+ terminalInstanceId: dialogTerminalInstanceIdRef.current,
4079
+ },
2856
4080
  }));
2857
4081
  }
2858
- }, [agent?.id]);
4082
+ }, [agentStub.id]);
4083
+ useEffect(() => {
4084
+ const normalizedAgentId = normalizeDialogAgentId(agent?.id);
4085
+ const normalizedStubId = normalizeDialogAgentId(agentStub.id);
4086
+ if (normalizedAgentId && normalizedAgentId !== normalizedStubId) {
4087
+ window.dispatchEvent(new CustomEvent("agent:terminal:ready", {
4088
+ detail: {
4089
+ agentId: normalizedAgentId,
4090
+ terminalInstanceId: dialogTerminalInstanceIdRef.current,
4091
+ },
4092
+ }));
4093
+ }
4094
+ }, [agent?.id, agentStub.id]);
2859
4095
  // Profiles are provided by parent component (Agents). No local loading here.
2860
4096
  // Select active profile based on agent.profileId or agentStub.profileId
2861
4097
  useEffect(() => {
@@ -2866,20 +4102,47 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2866
4102
  // Use case-insensitive comparison for GUID matching (backend may return different casing)
2867
4103
  const normalizedProfileId = profileIdToUse?.toLowerCase();
2868
4104
  const candidate = normalizedProfileId
2869
- ? (profiles.find((p) => p.id?.toLowerCase() === normalizedProfileId) ??
2870
- profiles[0])
2871
- : profiles[0];
2872
- if (candidate && (!activeProfile || activeProfile.id !== candidate.id)) {
2873
- setActiveProfile(candidate);
4105
+ ? profiles.find((p) => p.id?.toLowerCase() === normalizedProfileId)
4106
+ : undefined;
4107
+ if (!candidate) {
4108
+ setActiveProfile(undefined);
4109
+ return;
2874
4110
  }
2875
- }, [profiles, agent?.profileId, agentStub.profileId]);
4111
+ // Keep active profile in sync whenever the matching entry in `profiles` changes
4112
+ // not only when the profile id changes. Otherwise availableSkills (and similar fields)
4113
+ // that are merged in the parent after async loads never update (e.g. Template Builder
4114
+ // settings skills merged into the profile).
4115
+ setActiveProfile((prev) => {
4116
+ if (!prev || prev.id !== candidate.id)
4117
+ return candidate;
4118
+ const skillKey = (p) => JSON.stringify({
4119
+ allowed: (p.allowedSkills ?? []).map((s) => [
4120
+ String(s?.id ?? ""),
4121
+ String(s?.name ?? ""),
4122
+ ]),
4123
+ available: (p.availableSkills ?? []).map((s) => [
4124
+ String(s?.id ?? ""),
4125
+ String(s?.name ?? ""),
4126
+ ]),
4127
+ });
4128
+ if (skillKey(prev) === skillKey(candidate))
4129
+ return prev;
4130
+ return candidate;
4131
+ });
4132
+ }, [
4133
+ profiles,
4134
+ agent?.id,
4135
+ agent?.profileId,
4136
+ agentStub.id,
4137
+ agentStub.profileId,
4138
+ ]);
2876
4139
  // Clear queued prompts when agent changes or is new;
2877
4140
  // initial fetch is handled by loadAgent() for better performance and reliability
2878
4141
  useEffect(() => {
2879
- if (!agent?.id || agent.status === "new") {
4142
+ if (!agent?.id || isLocalOnlyDraftAgent) {
2880
4143
  setQueuedPrompts([]);
2881
4144
  }
2882
- }, [agent?.id, agent?.status]);
4145
+ }, [agent?.id, isLocalOnlyDraftAgent]);
2883
4146
  // Update selected model when the active profile or agent model changes
2884
4147
  useEffect(() => {
2885
4148
  if (!activeProfile)
@@ -2908,20 +4171,16 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2908
4171
  // Initialize mode from metadata; fall back to agent.Mode from server
2909
4172
  useEffect(() => {
2910
4173
  try {
2911
- const metaMode = agentMetadata?.mode;
2912
- if (metaMode === "autonomous" ||
2913
- metaMode === "read-only" ||
2914
- metaMode === "supervised") {
4174
+ const metaMode = normalizeAgentMode(agentMetadata?.mode);
4175
+ if (metaMode) {
2915
4176
  setMode(metaMode);
2916
4177
  return;
2917
4178
  }
2918
4179
  }
2919
4180
  catch { }
2920
4181
  try {
2921
- const serverMode = agent?.mode;
2922
- if (serverMode === "autonomous" ||
2923
- serverMode === "read-only" ||
2924
- serverMode === "supervised") {
4182
+ const serverMode = normalizeAgentMode(agent?.mode);
4183
+ if (serverMode) {
2925
4184
  setMode(serverMode);
2926
4185
  }
2927
4186
  }
@@ -2941,7 +4200,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2941
4200
  textareaRef.current.focus();
2942
4201
  }
2943
4202
  }, [messages, activePlaceholderInput]);
2944
- // Persist any pending settings (mode/model) once an agent exists server-side
4203
+ // Persist any pending settings (mode/model/profile) once an agent exists server-side
2945
4204
  const persistPendingSettingsIfNeeded = useCallback(async () => {
2946
4205
  try {
2947
4206
  if (!agent?.id)
@@ -2954,6 +4213,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2954
4213
  payload.model = pending.modelName;
2955
4214
  if (pending.mode)
2956
4215
  payload.mode = pending.mode;
4216
+ if (pending.profileId)
4217
+ payload.profileId = pending.profileId;
4218
+ if (pending.profileName != null)
4219
+ payload.profileName = pending.profileName;
2957
4220
  if (Object.keys(payload).length === 0)
2958
4221
  return;
2959
4222
  await updateAgentSettings(agent.id, payload);
@@ -2963,6 +4226,92 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2963
4226
  console.error("Failed to persist pending settings", e);
2964
4227
  }
2965
4228
  }, [agent?.id]);
4229
+ const getPendingRequestSettings = useCallback(() => {
4230
+ const pending = pendingSettingsRef.current;
4231
+ const requestProfile = pending?.profileId
4232
+ ? profiles.find((profile) => profile.id === pending.profileId) ||
4233
+ activeProfile ||
4234
+ profiles[0]
4235
+ : activeProfile || profiles[0];
4236
+ let requestModelId = selectedModelId;
4237
+ if (pending?.modelName) {
4238
+ const requestModel = (requestProfile?.models || []).find((model) => (model.name || "").trim().toLowerCase() ===
4239
+ pending.modelName?.trim().toLowerCase());
4240
+ requestModelId = requestModel?.id || requestModelId;
4241
+ }
4242
+ return {
4243
+ mode: (pending?.mode || mode),
4244
+ profileId: pending?.profileId || requestProfile?.id || "",
4245
+ profileName: pending?.profileName || requestProfile?.name || "",
4246
+ modelId: requestModelId,
4247
+ };
4248
+ }, [activeProfile, mode, profiles, selectedModelId]);
4249
+ const getSubmitErrorMessage = (error) => {
4250
+ const fallback = "Failed to submit prompt. Please try again.";
4251
+ if (!(error instanceof Error))
4252
+ return fallback;
4253
+ const cleaned = toUserFacingAgentErrorMessage(error.message);
4254
+ return cleaned || fallback;
4255
+ };
4256
+ const suppressedQueuedPromptsRef = useRef([]);
4257
+ const pruneSuppressedQueuedPrompts = useCallback(() => {
4258
+ const cutoff = Date.now() - 15_000;
4259
+ suppressedQueuedPromptsRef.current =
4260
+ suppressedQueuedPromptsRef.current.filter((entry) => entry.createdAt >= cutoff);
4261
+ }, []);
4262
+ const registerSuppressedQueuedPrompt = useCallback((agentId, promptText) => {
4263
+ const normalizedAgentId = agentId.trim().toLowerCase();
4264
+ const normalizedPrompt = promptText.trim();
4265
+ if (!normalizedAgentId || !normalizedPrompt) {
4266
+ return null;
4267
+ }
4268
+ pruneSuppressedQueuedPrompts();
4269
+ const token = crypto.randomUUID();
4270
+ suppressedQueuedPromptsRef.current = [
4271
+ ...suppressedQueuedPromptsRef.current,
4272
+ {
4273
+ token,
4274
+ agentId: normalizedAgentId,
4275
+ prompt: normalizedPrompt,
4276
+ createdAt: Date.now(),
4277
+ },
4278
+ ];
4279
+ return token;
4280
+ }, [pruneSuppressedQueuedPrompts]);
4281
+ const clearSuppressedQueuedPrompt = useCallback((token) => {
4282
+ if (!token) {
4283
+ return;
4284
+ }
4285
+ suppressedQueuedPromptsRef.current =
4286
+ suppressedQueuedPromptsRef.current.filter((entry) => entry.token !== token);
4287
+ }, []);
4288
+ const shouldSuppressQueuedPrompt = useCallback((queueEntry) => {
4289
+ const normalizedAgentId = queueEntry?.targetAgentId?.trim().toLowerCase();
4290
+ const normalizedPrompt = queueEntry?.prompt?.trim();
4291
+ if (!normalizedAgentId || !normalizedPrompt) {
4292
+ return false;
4293
+ }
4294
+ pruneSuppressedQueuedPrompts();
4295
+ const matchedEntry = suppressedQueuedPromptsRef.current.find((entry) => entry.agentId === normalizedAgentId &&
4296
+ entry.prompt === normalizedPrompt);
4297
+ if (!matchedEntry) {
4298
+ return false;
4299
+ }
4300
+ suppressedQueuedPromptsRef.current =
4301
+ suppressedQueuedPromptsRef.current.filter((entry) => entry.token !== matchedEntry.token);
4302
+ return true;
4303
+ }, [pruneSuppressedQueuedPrompts]);
4304
+ const cancelActiveInlineDialog = useCallback(() => {
4305
+ const activeDialog = activeInlineDialogRef.current;
4306
+ if (!activeDialog)
4307
+ return;
4308
+ try {
4309
+ activeDialog.onCancel();
4310
+ }
4311
+ finally {
4312
+ setActiveInlineDialog(null);
4313
+ }
4314
+ }, []);
2966
4315
  const handleSubmit = async () => {
2967
4316
  // Guard against double-submit and missing context
2968
4317
  if (isSubmitting) {
@@ -3006,6 +4355,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3006
4355
  setError("Agent not ready. Please try again.");
3007
4356
  return;
3008
4357
  }
4358
+ const hadQuestionnaireDialogOpen = activeInlineDialogRef.current?.request.dialogType === "questionnaire";
4359
+ const suppressedQueuedPromptToken = hadQuestionnaireDialogOpen && savedPrompt
4360
+ ? registerSuppressedQueuedPrompt(agentId, savedPrompt)
4361
+ : null;
4362
+ // A new user prompt supersedes any active questionnaire/inline dialog.
4363
+ cancelActiveInlineDialog();
3009
4364
  // Generate a temporary ID for optimistic UI - will be replaced by server ID
3010
4365
  const tempMessageId = `temp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
3011
4366
  try {
@@ -3092,26 +4447,24 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3092
4447
  console.warn("[AgentTerminal] Failed to compute live context:", e);
3093
4448
  }
3094
4449
  // Add visible test IDs to context (only for Help agent)
3095
- const currentProfileName = activeProfile?.name || profiles[0]?.name || "";
4450
+ const requestSettings = getPendingRequestSettings();
4451
+ const currentProfileName = requestSettings.profileName;
3096
4452
  effectiveContext = addVisibleTestIdsToContext(effectiveContext, currentProfileName);
3097
4453
  const request = {
3098
4454
  agentId: agentId,
3099
4455
  message: savedPrompt,
3100
4456
  sessionId: editContext.sessionId,
3101
- profileId: activeProfile?.id || profiles[0]?.id || "",
4457
+ profileId: requestSettings.profileId,
3102
4458
  profile: currentProfileName,
3103
- model: selectedModelId,
3104
- mode: mode,
4459
+ model: requestSettings.modelId,
4460
+ mode: requestSettings.mode,
3105
4461
  context: canonicalizeAgentMetadata(effectiveContext), // Use fresh live context when in live mode
3106
- deterministic: deterministicFlags.deterministic,
3107
- seed: deterministicFlags.seed,
3108
4462
  };
3109
4463
  console.log("[AgentTerminal] Calling startAgent API for agent:", agentId);
3110
4464
  const response = await startAgent(request);
3111
4465
  console.log("[AgentTerminal] startAgent response:", response);
3112
4466
  // Check if prompt was queued (agent was already running)
3113
- const wasQueued = response.message?.toLowerCase().includes("queued") ||
3114
- response.status === "Queued";
4467
+ const wasQueued = response.message?.toLowerCase().includes("queued");
3115
4468
  if (wasQueued) {
3116
4469
  // Prompt was queued - show a brief notification but don't set waiting state
3117
4470
  // The prompt will be processed when the agent becomes idle
@@ -3121,6 +4474,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3121
4474
  isWaitingRef.current = false;
3122
4475
  }
3123
4476
  else {
4477
+ clearSuppressedQueuedPrompt(suppressedQueuedPromptToken);
3124
4478
  // Normal submission - set waiting state to show dancing dots immediately
3125
4479
  setIsWaitingForResponse(true);
3126
4480
  isWaitingRef.current = true;
@@ -3135,11 +4489,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3135
4489
  ...prev.filter((p) => p !== savedPrompt).slice(0, 9),
3136
4490
  ]);
3137
4491
  setCurrentHistoryIndex(-1);
4492
+ await onInteractionSubmitted?.();
3138
4493
  // WebSocket connection is already active via subscription - no need for SSE
3139
4494
  }
3140
4495
  catch (err) {
3141
4496
  console.error("[AgentTerminal] Failed to submit prompt:", err);
3142
- setError("Failed to submit prompt. Please try again.");
4497
+ clearSuppressedQueuedPrompt(suppressedQueuedPromptToken);
4498
+ setError(getSubmitErrorMessage(err));
3143
4499
  setIsWaitingForResponse(false);
3144
4500
  isWaitingRef.current = false;
3145
4501
  // Remove the optimistic user message on API error
@@ -3349,29 +4705,29 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3349
4705
  console.warn("[AgentTerminal] Failed to compute live context for quick message:", e);
3350
4706
  }
3351
4707
  // Add visible test IDs to context (only for Help agent)
3352
- const currentProfileName = activeProfile?.name || profiles[0]?.name || "";
4708
+ const requestSettings = getPendingRequestSettings();
4709
+ const currentProfileName = requestSettings.profileName;
3353
4710
  effectiveContext = addVisibleTestIdsToContext(effectiveContext, currentProfileName);
3354
4711
  const request = {
3355
4712
  agentId: agent.id,
3356
4713
  message: savedPrompt,
3357
4714
  sessionId: editContext.sessionId,
3358
- profileId: activeProfile?.id || profiles[0]?.id || "",
4715
+ profileId: requestSettings.profileId,
3359
4716
  profile: currentProfileName,
3360
- model: selectedModelId,
3361
- mode: mode,
4717
+ model: requestSettings.modelId,
4718
+ mode: requestSettings.mode,
3362
4719
  context: canonicalizeAgentMetadata(effectiveContext), // Use fresh live context when in live mode
3363
- deterministic: deterministicFlags.deterministic,
3364
- seed: deterministicFlags.seed,
3365
4720
  };
3366
4721
  console.log("[AgentTerminal] Calling startAgent API for quick message");
3367
4722
  await startAgent(request);
3368
4723
  // If user changed mode/model while the agent was new, persist them now
3369
4724
  await persistPendingSettingsIfNeeded();
4725
+ await onInteractionSubmitted?.();
3370
4726
  // WebSocket connection is already active via subscription - no need for SSE
3371
4727
  }
3372
4728
  catch (err) {
3373
4729
  console.error("[AgentTerminal] Failed to submit quick message:", err);
3374
- setError("Failed to submit prompt. Please try again.");
4730
+ setError(getSubmitErrorMessage(err));
3375
4731
  setIsWaitingForResponse(false);
3376
4732
  isWaitingRef.current = false;
3377
4733
  // Remove the optimistic user message on API error
@@ -3549,66 +4905,90 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3549
4905
  }
3550
4906
  return context;
3551
4907
  }, [collectVisibleTestIds]);
3552
- // Helper function to build current context from editor state
3553
- const buildCurrentContext = useCallback(() => {
3554
- // Return context even without item - we want view info regardless
3555
- const item = editContext?.currentItemDescriptor;
3556
- return {
3557
- items: item
3558
- ? [
3559
- {
3560
- id: item.id,
3561
- language: item.language,
3562
- version: item.version,
3563
- name: editContext?.item?.name,
3564
- path: editContext?.item?.path,
3565
- },
3566
- ]
3567
- : undefined,
3568
- currentItemId: item?.id, // ID of the currently loaded item user is viewing
3569
- components: editContext?.selection?.length && item
3570
- ? editContext.selection.map((componentId) => ({
3571
- componentId,
3572
- pageItem: {
3573
- id: item.id,
3574
- language: item.language,
3575
- version: item.version,
3576
- name: editContext?.item?.name,
3577
- },
3578
- }))
3579
- : undefined,
3580
- field: fieldsContext?.focusedField?.fieldId &&
3581
- fieldsContext.focusedField?.item?.id
3582
- ? {
3583
- fieldId: fieldsContext.focusedField.fieldId,
3584
- fieldName: fieldsContext.focusedField.fieldName,
3585
- item: {
3586
- id: fieldsContext.focusedField.item.id,
3587
- language: fieldsContext.focusedField.item.language ||
3588
- editContext?.currentItemDescriptor?.language ||
3589
- "en",
3590
- version: fieldsContext.focusedField.item.version ??
3591
- editContext?.currentItemDescriptor?.version ??
3592
- 0,
3593
- name: editContext?.item?.name,
3594
- },
4908
+ const buildPageContextItem = useCallback((item) => ({
4909
+ id: item.id,
4910
+ language: item.language,
4911
+ version: item.version,
4912
+ name: editContext?.item?.name,
4913
+ path: editContext?.item?.path,
4914
+ }), [editContext?.item?.name, editContext?.item?.path]);
4915
+ const buildComponentContext = useCallback((item) => {
4916
+ if (!editContext?.selection?.length)
4917
+ return undefined;
4918
+ return editContext.selection.map((componentId) => {
4919
+ let componentName;
4920
+ let componentType;
4921
+ let renderingItemId;
4922
+ if (editContext.page) {
4923
+ try {
4924
+ const component = getComponentById(componentId, editContext.page);
4925
+ componentName =
4926
+ component?.datasourceItem?.name || component?.name || undefined;
4927
+ componentType = component?.type || undefined;
4928
+ renderingItemId = component?.rendering?.id || undefined;
3595
4929
  }
3596
- : undefined,
3597
- // View context - always include so agent knows editor state
3598
- activeWorkspace: editContext?.workspaceId,
3599
- hasPageLoaded: !!editContext?.getActiveSlotContext()?.primaryPageViewContext?.page,
3600
- // Open sidebars - helps agent understand what panels are currently visible
3601
- openSidebars: editContext?.openSidebars,
4930
+ catch { }
4931
+ }
4932
+ return {
4933
+ componentId,
4934
+ componentName,
4935
+ componentType,
4936
+ renderingItemId,
4937
+ pageItem: buildPageContextItem(item),
4938
+ };
4939
+ });
4940
+ }, [buildPageContextItem, editContext?.page, editContext?.selection]);
4941
+ const buildFieldContext = useCallback(() => {
4942
+ const focusedField = fieldsContext?.focusedField;
4943
+ if (!focusedField?.fieldId || !focusedField?.item?.id)
4944
+ return undefined;
4945
+ const fieldItem = focusedField.item;
4946
+ const currentItem = editContext?.currentItemDescriptor;
4947
+ let fieldItemName = fieldItem.id === currentItem?.id ? editContext?.item?.name : undefined;
4948
+ if (!fieldItemName && editContext?.page) {
4949
+ try {
4950
+ const component = getComponentById(fieldItem.id, editContext.page);
4951
+ fieldItemName =
4952
+ component?.datasourceItem?.name || component?.name || undefined;
4953
+ }
4954
+ catch { }
4955
+ }
4956
+ return {
4957
+ fieldId: focusedField.fieldId,
4958
+ fieldName: focusedField.fieldName,
4959
+ item: {
4960
+ id: fieldItem.id,
4961
+ language: fieldItem.language || currentItem?.language || "en",
4962
+ version: fieldItem.version ?? currentItem?.version ?? 0,
4963
+ name: fieldItem.name || fieldItemName,
4964
+ },
3602
4965
  };
3603
4966
  }, [
3604
4967
  editContext?.currentItemDescriptor,
3605
- editContext?.selection,
3606
- editContext?.workspaceId,
3607
4968
  editContext?.item?.name,
3608
- editContext?.activeSlotId,
3609
- editContext?.openSidebars,
4969
+ editContext?.page,
3610
4970
  fieldsContext?.focusedField,
3611
4971
  ]);
4972
+ const buildEditorContextPayload = useCallback((item) => ({
4973
+ items: item ? [buildPageContextItem(item)] : undefined,
4974
+ currentItemId: item?.id,
4975
+ components: item ? buildComponentContext(item) : undefined,
4976
+ field: buildFieldContext(),
4977
+ activeWorkspace: editContext?.workspaceId,
4978
+ hasPageLoaded: !!editContext?.getActiveSlotContext()?.primaryPageViewContext?.page,
4979
+ openSidebars: editContext?.openSidebars,
4980
+ }), [
4981
+ buildComponentContext,
4982
+ buildFieldContext,
4983
+ buildPageContextItem,
4984
+ editContext,
4985
+ ]);
4986
+ // Helper function to build current context from editor state
4987
+ const buildCurrentContext = useCallback(() => {
4988
+ // Return context even without item - we want view info regardless
4989
+ const item = editContext?.currentItemDescriptor;
4990
+ return buildEditorContextPayload(item);
4991
+ }, [buildEditorContextPayload, editContext?.currentItemDescriptor]);
3612
4992
  // Live context updates: watch for changes and update agent context when in "live" mode
3613
4993
  const previousContextRef = useRef("");
3614
4994
  useEffect(() => {
@@ -3708,6 +5088,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3708
5088
  const handleRefreshContext = useCallback(async () => {
3709
5089
  if (!agent?.id)
3710
5090
  return;
5091
+ const normalizedAgentProfileId = agent?.profileId?.toLowerCase();
5092
+ const refreshProfile = activeProfile ||
5093
+ profiles.find((p) => p.id?.toLowerCase() === normalizedAgentProfileId);
5094
+ if (refreshProfile?.editorContextMode === "none") {
5095
+ editContext?.showInfoToast({
5096
+ summary: "This profile excludes editor context.",
5097
+ });
5098
+ return;
5099
+ }
3711
5100
  try {
3712
5101
  const currentCtx = buildCurrentContext();
3713
5102
  if (!currentCtx) {
@@ -3748,7 +5137,73 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3748
5137
  buildCurrentContext,
3749
5138
  sanitizeAgentMetadata,
3750
5139
  editContext,
5140
+ activeProfile,
5141
+ profiles,
3751
5142
  ]);
5143
+ const browserCaptureClaim = useMemo(() => getBrowserCaptureClaim(agentMetadata), [agentMetadata]);
5144
+ const isPendingBrowserCaptureWait = pendingBrowserCaptureDialogType === DIALOG_TYPES.CAPTURE_PAGE_DOM ||
5145
+ pendingBrowserCaptureDialogType === DIALOG_TYPES.CAPTURE_PAGE_SCREENSHOT;
5146
+ const currentSessionId = editContext?.sessionId?.trim() || "";
5147
+ const claimedSessionId = browserCaptureClaim?.sessionId?.trim() || "";
5148
+ const isClaimedByCurrentSession = !!currentSessionId &&
5149
+ !!claimedSessionId &&
5150
+ currentSessionId.toLowerCase() === claimedSessionId.toLowerCase();
5151
+ const isClaimedByAnotherBrowser = !!claimedSessionId && !isClaimedByCurrentSession;
5152
+ const handleClaimBrowser = useCallback(async (takeOver) => {
5153
+ if (!agent?.id || !editContext?.sessionId)
5154
+ return;
5155
+ setIsBrowserClaimMutationPending(true);
5156
+ try {
5157
+ const response = await claimAgentBrowser({
5158
+ agentId: agent.id,
5159
+ sessionId: editContext.sessionId,
5160
+ takeOver,
5161
+ terminalInstanceId: dialogTerminalInstanceIdRef.current,
5162
+ });
5163
+ setAgentMetadata((prev) => sanitizeAgentMetadata(setBrowserCaptureClaim(prev, response.claim || null)));
5164
+ }
5165
+ catch (err) {
5166
+ console.error("[AgentTerminal] Failed to claim browser:", err);
5167
+ editContext.showErrorToast(err);
5168
+ }
5169
+ finally {
5170
+ setIsBrowserClaimMutationPending(false);
5171
+ }
5172
+ }, [agent?.id, editContext, sanitizeAgentMetadata]);
5173
+ const handleReleaseBrowser = useCallback(async () => {
5174
+ if (!agent?.id || !editContext?.sessionId)
5175
+ return;
5176
+ setIsBrowserClaimMutationPending(true);
5177
+ try {
5178
+ const response = await releaseAgentBrowser({
5179
+ agentId: agent.id,
5180
+ sessionId: editContext.sessionId,
5181
+ terminalInstanceId: dialogTerminalInstanceIdRef.current,
5182
+ });
5183
+ setAgentMetadata((prev) => sanitizeAgentMetadata(setBrowserCaptureClaim(prev, response.claim || null)));
5184
+ }
5185
+ catch (err) {
5186
+ console.error("[AgentTerminal] Failed to release browser:", err);
5187
+ editContext.showErrorToast(err);
5188
+ }
5189
+ finally {
5190
+ setIsBrowserClaimMutationPending(false);
5191
+ }
5192
+ }, [agent?.id, editContext, sanitizeAgentMetadata]);
5193
+ useEffect(() => {
5194
+ return () => {
5195
+ if (!agent?.id || !editContext?.sessionId || !isClaimedByCurrentSession) {
5196
+ return;
5197
+ }
5198
+ void releaseAgentBrowser({
5199
+ agentId: agent.id,
5200
+ sessionId: editContext.sessionId,
5201
+ terminalInstanceId: dialogTerminalInstanceIdRef.current,
5202
+ }).catch((error) => {
5203
+ console.warn("[AgentTerminal] Failed to release browser on unmount:", error);
5204
+ });
5205
+ };
5206
+ }, [agent?.id, editContext?.sessionId, isClaimedByCurrentSession]);
3752
5207
  // Stop current execution/stream safely
3753
5208
  const handleStop = useCallback(async () => {
3754
5209
  try {
@@ -3812,13 +5267,81 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3812
5267
  if (effectiveCostLimit === undefined) {
3813
5268
  effectiveCostLimit = undefined;
3814
5269
  }
3815
- // Calculate total token usage for cost display
3816
- const totalTokens = calculateTotalTokens(messages);
5270
+ // Calculate total token usage for cost display.
5271
+ // Message rows do not currently persist imageCost, so on refresh we fall back
5272
+ // to the persisted agent aggregate for that single field.
5273
+ const totalTokens = (() => {
5274
+ const totals = calculateTotalTokens(messages);
5275
+ return {
5276
+ ...totals,
5277
+ imageCost: totals.imageCost || Number(agent?.totalImageCost) || 0,
5278
+ };
5279
+ })();
3817
5280
  // Determine if the agent is actively executing (submitting, connecting, waiting, or streaming)
3818
5281
  const isExecuting = isSubmitting ||
3819
5282
  isConnecting ||
3820
5283
  isWaitingForResponse ||
3821
5284
  hasActiveStreaming();
5285
+ const assistantMessageCount = useMemo(() => messages.filter((message) => message.role === "assistant").length, [messages]);
5286
+ const messagesWithToolCallsCount = useMemo(() => messages.filter((message) => (message.toolCalls || []).length > 0).length, [messages]);
5287
+ const totalToolCallCount = useMemo(() => messages.reduce((sum, message) => sum + (message.toolCalls?.length || 0), 0), [messages]);
5288
+ const incompleteToolCallCount = useMemo(() => messages.reduce((sum, message) => sum +
5289
+ (message.toolCalls?.filter((toolCall) => !toolCall.isCompleted).length || 0), 0), [messages]);
5290
+ const assistantGroupsWithRenderableToolCalls = useMemo(() => {
5291
+ const groups = groupConsecutiveMessages(messages);
5292
+ return groups.filter((group) => {
5293
+ if (group.type !== "assistant-group")
5294
+ return false;
5295
+ const convertedMessages = convertAgentMessagesToAiFormat(group.messages);
5296
+ return convertedMessages.some((message) => (message.tool_calls?.length || 0) > 0);
5297
+ }).length;
5298
+ }, [messages]);
5299
+ const assistantGroupCount = useMemo(() => groupConsecutiveMessages(messages).filter((group) => group.type === "assistant-group").length, [messages]);
5300
+ const runDiagnosticsSnapshot = useMemo(() => {
5301
+ const lastEvent = recentAgentRunEvents[recentAgentRunEvents.length - 1];
5302
+ return {
5303
+ agentId: currentAgentId,
5304
+ isSubmitting,
5305
+ isConnecting,
5306
+ isWaitingForResponse,
5307
+ isAgentThinking,
5308
+ isExecuting,
5309
+ hasActiveStreaming: hasActiveStreaming(),
5310
+ isSubscribed: normalizeDialogAgentId(subscribedAgentIdRef.current) ===
5311
+ normalizeDialogAgentId(currentAgentId),
5312
+ lastSeq: lastSeqRef.current,
5313
+ lastEventType: lastEvent?.type ?? null,
5314
+ lastEventAt: lastEvent?.timestamp ?? null,
5315
+ recentEvents: recentAgentRunEvents,
5316
+ assistantMessageCount,
5317
+ assistantGroupCount,
5318
+ assistantGroupsWithRenderableToolCalls,
5319
+ messagesWithToolCalls: messagesWithToolCallsCount,
5320
+ totalToolCallCount,
5321
+ incompleteToolCallCount,
5322
+ recentToolUiEvents,
5323
+ };
5324
+ }, [
5325
+ assistantGroupCount,
5326
+ assistantGroupsWithRenderableToolCalls,
5327
+ assistantMessageCount,
5328
+ currentAgentId,
5329
+ hasActiveStreaming,
5330
+ incompleteToolCallCount,
5331
+ isAgentThinking,
5332
+ isConnecting,
5333
+ isExecuting,
5334
+ isSubmitting,
5335
+ isWaitingForResponse,
5336
+ messagesWithToolCallsCount,
5337
+ recentAgentRunEvents,
5338
+ recentToolUiEvents,
5339
+ totalToolCallCount,
5340
+ ]);
5341
+ const showInitialThinkingSplash = messages.length === 0 &&
5342
+ !error &&
5343
+ hideGreeting &&
5344
+ (isSubmitting || isConnecting);
3822
5345
  // Compute dots visibility: only show BEFORE any assistant message exists
3823
5346
  // This prevents duplicate headers - the dots indicator has its own header,
3824
5347
  // and we don't want to show a second header below existing messages
@@ -3833,13 +5356,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3833
5356
  // The message with the pending approval will display its own UI for approval
3834
5357
  if (allPendingApprovals.length > 0)
3835
5358
  return false;
5359
+ // The hidden-greeting startup splash already renders its own bouncing dots.
5360
+ // Suppress the generic indicator so reopening/running terminals don't show two.
5361
+ if (showInitialThinkingSplash)
5362
+ return false;
3836
5363
  // IMPORTANT: If the last message is an assistant message and we're still executing,
3837
5364
  // the AiResponseMessage for that message will show its own activity indicator.
3838
5365
  // We only want these global thinking dots if the last message was from the user
3839
5366
  // or if no messages exist yet (waiting for initial response).
3840
5367
  const lastMessage = messages.length > 0 ? messages[messages.length - 1] : null;
3841
- if (isExecuting && lastMessage?.role === "assistant")
5368
+ if (isExecuting &&
5369
+ lastMessage?.role === "assistant" &&
5370
+ !lastMessage.isCompleted) {
3842
5371
  return false;
5372
+ }
3843
5373
  // Existing check for uncompleted assistant messages
3844
5374
  const hasActiveStreamingMessage = messages.some((m) => !m.isCompleted && m.role === "assistant");
3845
5375
  if (hasActiveStreamingMessage)
@@ -3855,20 +5385,22 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3855
5385
  messages,
3856
5386
  activeInlineDialog,
3857
5387
  allPendingApprovals,
5388
+ showInitialThinkingSplash,
3858
5389
  ]);
3859
5390
  // Move useMemo hook before early return to comply with Rules of Hooks
3860
- const isLiveEditorContextMode = React.useMemo(() => {
5391
+ const resolvedEditorContextMode = React.useMemo(() => {
3861
5392
  try {
3862
5393
  const normalizedAgentProfileId = agent?.profileId?.toLowerCase();
3863
5394
  const profile = activeProfile ||
3864
5395
  profiles.find((p) => p.id?.toLowerCase() === normalizedAgentProfileId);
3865
- const mode = profile?.editorContextMode;
3866
- return mode === "live";
5396
+ return profile?.editorContextMode ?? null;
3867
5397
  }
3868
5398
  catch {
3869
- return false;
5399
+ return null;
3870
5400
  }
3871
5401
  }, [activeProfile, profiles, agent?.profileId]);
5402
+ const isLiveEditorContextMode = resolvedEditorContextMode === "live";
5403
+ const omitsEditorContext = resolvedEditorContextMode === "none";
3872
5404
  // Get parent agent ID from agent or agentStub (handle both camelCase and PascalCase)
3873
5405
  const parentAgentId = agent?.parentAgentId ||
3874
5406
  agent?.ParentAgentId ||
@@ -3882,10 +5414,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3882
5414
  detail: { agentId: parentAgentId },
3883
5415
  }));
3884
5416
  }, [parentAgentId]);
3885
- if (isLoading) {
3886
- return (_jsx("div", { className: "flex h-full items-center justify-center", children: _jsxs("div", { className: "flex items-center gap-2 text-[11px] text-gray-500", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin", strokeWidth: 1 }), "Loading agent..."] }) }));
3887
- }
3888
- const renderContextInfoBar = () => (_jsx(ContextInfoBar, { agent: agent, agentMetadata: agentMetadata, setAgentMetadata: setAgentMetadata, setAgent: setAgent, resolvedPageName: resolvedPageName, resolvedComponentName: resolvedComponentName, resolvedFieldName: resolvedFieldName, isLiveEditorContextMode: isLiveEditorContextMode, activeProfile: activeProfile, onRefreshContext: handleRefreshContext }));
5417
+ const loadingContent = isLoading && !activeInlineDialog ? (_jsx("div", { className: "flex h-full items-center justify-center", children: _jsxs("div", { className: "flex items-center gap-2 text-[11px] text-gray-500", children: [_jsx(Loader2, { className: "h-4 w-4 animate-spin", strokeWidth: 1 }), "Loading agent..."] }) })) : null;
5418
+ const renderContextInfoBar = () => (_jsx(ContextInfoBar, { agent: agent, agentMetadata: agentMetadata, setAgentMetadata: setAgentMetadata, setAgent: setAgent, resolvedPageName: resolvedPageName, resolvedComponentName: resolvedComponentName, resolvedFieldName: resolvedFieldName, isLiveEditorContextMode: isLiveEditorContextMode, omitEditorContext: omitsEditorContext, onRefreshContext: handleRefreshContext }));
3889
5419
  const renderCostLimitBanner = () => {
3890
5420
  if (!costLimitExceeded)
3891
5421
  return null;
@@ -3896,9 +5426,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3896
5426
  try {
3897
5427
  // Extend cost limit - backend will automatically resume the agent
3898
5428
  const result = await updateAgentCostLimit(agent.id, "extend");
3899
- // Update the agent's cost limit in local state
5429
+ // Update the agent's cost limit and clear the costLimitReached
5430
+ // status in local state so the useEffect watcher doesn't
5431
+ // immediately re-show the banner before the backend status
5432
+ // update arrives.
3900
5433
  if (result.success && result.costLimit !== undefined) {
3901
- setAgent((prev) => prev ? { ...prev, costLimit: result.costLimit } : prev);
5434
+ setAgent((prev) => prev
5435
+ ? {
5436
+ ...prev,
5437
+ costLimit: result.costLimit,
5438
+ status: prev.status === "costLimitReached"
5439
+ ? "running"
5440
+ : prev.status,
5441
+ }
5442
+ : prev);
3902
5443
  }
3903
5444
  // Clear the banner and set waiting state
3904
5445
  // Agent will resume automatically via backend's ResumeAgentAsync
@@ -3918,13 +5459,257 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3918
5459
  };
3919
5460
  const renderErrorBanner = () => {
3920
5461
  const currentAgent = agent || agentStub;
3921
- const isErrorStatus = currentAgent?.status === "error" || currentAgent?.status === 4;
3922
- const errorMessage = currentAgent?.statusMessage;
3923
- if (!isErrorStatus || !errorMessage)
5462
+ const isErrorStatus = currentAgent?.status === "error";
5463
+ const isWaitingForInputStatus = currentAgent?.status === "waitingForInput";
5464
+ const isWaitingForApprovalStatus = currentAgent?.status === "waitingForApproval";
5465
+ // Show error banner for error status, or for any terminal status that still
5466
+ // carries a statusMessage (e.g. agent closed after an error).
5467
+ const isTerminalWithError = !isErrorStatus &&
5468
+ !!currentAgent?.statusMessage &&
5469
+ currentAgent?.status !== "running" &&
5470
+ currentAgent?.status !== "new" &&
5471
+ !isWaitingForInputStatus &&
5472
+ !isWaitingForApprovalStatus;
5473
+ const rawErrorMessage = (isErrorStatus || isTerminalWithError
5474
+ ? currentAgent?.statusMessage
5475
+ : null) || error;
5476
+ if (!rawErrorMessage)
3924
5477
  return null;
3925
- 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 })] })] }) }));
5478
+ // Clean the error message (statusMessage from DB may contain raw JSON)
5479
+ const errorMessage = toUserFacingAgentErrorMessage(rawErrorMessage) || rawErrorMessage;
5480
+ return (_jsx("div", { className: "m-3 rounded border border-red-300 bg-red-50 p-3 text-[11px] text-red-900", "data-testid": "agent-error-banner", 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 })] })] }) }));
3926
5481
  };
3927
- return (_jsxs("div", { className: `flex h-full flex-col ${className || ""}`, children: [parentAgentId && !simpleMode && (_jsx("div", { className: "border-b border-gray-200 bg-gray-50 px-4 py-2", children: _jsxs("button", { onClick: handleBackToParent, className: "flex items-center gap-2 text-[11px] text-gray-600 transition-colors hover:text-gray-900", title: "Back to parent agent", children: [_jsx(ArrowLeft, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }), _jsx("span", { children: "Back to parent agent" })] }) })), _jsxs("div", { ref: messagesContainerRef, className: "flex-1 overflow-y-auto", onScroll: handleScroll, children: [error && (_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) => {
5482
+ const renderBrowserClaimBanner = (variant = "inline") => {
5483
+ if (!agent?.id || !editContext?.sessionId)
5484
+ return null;
5485
+ if (!isClaimedByCurrentSession && !isClaimedByAnotherBrowser) {
5486
+ return null;
5487
+ }
5488
+ if (isPendingBrowserCaptureWait) {
5489
+ return null;
5490
+ }
5491
+ const label = isClaimedByCurrentSession
5492
+ ? "Attached to this browser"
5493
+ : isClaimedByAnotherBrowser
5494
+ ? "Attached in another browser"
5495
+ : "No browser attached";
5496
+ const description = isClaimedByCurrentSession
5497
+ ? "This browser will handle page screenshot and DOM capture requests for the agent."
5498
+ : isClaimedByAnotherBrowser
5499
+ ? "Capture requests will stay with the other browser until you take over control here."
5500
+ : "A page capture request is waiting for a browser attachment. Attach this browser to continue.";
5501
+ 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");
5502
+ 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: () => {
5503
+ void handleReleaseBrowser();
5504
+ }, 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: () => {
5505
+ void handleClaimBrowser(isClaimedByAnotherBrowser);
5506
+ }, children: isClaimedByAnotherBrowser
5507
+ ? "Take over browser control"
5508
+ : "Attach to this browser" })) })] }) }));
5509
+ };
5510
+ const fixedBrowserClaimBanner = renderBrowserClaimBanner("fixed");
5511
+ const inlineBrowserClaimBanner = null;
5512
+ const browserCaptureInlinePrompt = isPendingBrowserCaptureWait && !isClaimedByCurrentSession
5513
+ ? {
5514
+ toolNames: [
5515
+ "capture-page-screenshot",
5516
+ "capture-parhelia-ui-screenshot",
5517
+ "capture-page-dom",
5518
+ ],
5519
+ label: isClaimedByAnotherBrowser
5520
+ ? "Attached in another browser"
5521
+ : "No browser attached",
5522
+ description: isClaimedByAnotherBrowser
5523
+ ? "This capture request is waiting in another browser. Take over browser control here to continue."
5524
+ : "This capture request is waiting for a browser attachment. Attach this browser to continue.",
5525
+ actionLabel: isClaimedByAnotherBrowser
5526
+ ? "Take over browser control"
5527
+ : "Attach to this browser",
5528
+ isPending: isBrowserClaimMutationPending,
5529
+ onAction: () => {
5530
+ void handleClaimBrowser(isClaimedByAnotherBrowser);
5531
+ },
5532
+ }
5533
+ : null;
5534
+ useEffect(() => {
5535
+ if (agent?.status !== "waitingForInput") {
5536
+ setPendingBrowserCaptureDialogType(null);
5537
+ }
5538
+ }, [agent?.status]);
5539
+ const renderInlineDialogContent = () => {
5540
+ if (!activeInlineDialog)
5541
+ return null;
5542
+ if (activeInlineDialog.request.dialogType === "questionnaire") {
5543
+ return (_jsx("div", { ref: inlineDialogContainerRef, className: cn("agent-inline-dialog min-h-0 overflow-hidden", displayMode === "full" && "h-full"), children: _jsx(QuestionnaireInline, { requestId: activeInlineDialog.request.callbackId, agentId: activeInlineDialog.request.agentId, title: activeInlineDialog.request.title, description: activeInlineDialog.request.description, parameters: activeInlineDialog.request.parameters, footerActions: questionnaireFooterActions, onClose: (result) => {
5544
+ activeInlineDialog.onComplete(result);
5545
+ setActiveInlineDialog(null);
5546
+ void onInteractionSubmitted?.();
5547
+ }, onCancel: () => {
5548
+ activeInlineDialog.onCancel();
5549
+ setActiveInlineDialog(null);
5550
+ } }) }));
5551
+ }
5552
+ const dialogRegistration = editContext?.configuration?.editor?.agentDialogs?.find((d) => d.dialogType === activeInlineDialog.request.dialogType);
5553
+ if (dialogRegistration) {
5554
+ const DialogComponent = dialogRegistration.component;
5555
+ return (_jsx("div", { className: "agent-inline-dialog", children: _jsx(DialogComponent, { title: activeInlineDialog.request.title, description: activeInlineDialog.request.description, parameters: activeInlineDialog.request.parameters, onClose: (result) => {
5556
+ activeInlineDialog.onComplete(result);
5557
+ setActiveInlineDialog(null);
5558
+ if (activeInlineDialog.request.dialogType === "questionnaire") {
5559
+ void onInteractionSubmitted?.();
5560
+ }
5561
+ }, onCancel: () => {
5562
+ activeInlineDialog.onCancel();
5563
+ setActiveInlineDialog(null);
5564
+ } }) }));
5565
+ }
5566
+ return (_jsx("div", { className: "agent-inline-dialog", children: _jsxs("div", { className: "p-4 text-sm text-red-500", children: ["Unknown dialog type: ", activeInlineDialog.request.dialogType] }) }));
5567
+ };
5568
+ const latestSummaryAssistantGroup = useMemo(() => {
5569
+ if (hideSummaryMessages)
5570
+ return null;
5571
+ const groups = groupConsecutiveMessages(messages);
5572
+ for (let groupIndex = groups.length - 1; groupIndex >= 0; groupIndex -= 1) {
5573
+ const group = groups[groupIndex];
5574
+ if (!group || group.type !== "assistant-group")
5575
+ continue;
5576
+ const filteredMessages = group.messages.filter((msg) => {
5577
+ const content = msg.content || "";
5578
+ return !content.startsWith("⚠️") || !content.includes("Cost limit");
5579
+ });
5580
+ if (filteredMessages.length === 0)
5581
+ continue;
5582
+ return {
5583
+ messages: filteredMessages,
5584
+ isLastGroup: groupIndex === groups.length - 1,
5585
+ };
5586
+ }
5587
+ return null;
5588
+ }, [messages, hideSummaryMessages]);
5589
+ const summaryModeContent = displayMode === "summary"
5590
+ ? (() => {
5591
+ const inlineDialog = renderInlineDialogContent();
5592
+ const summaryMessages = latestSummaryAssistantGroup
5593
+ ? convertAgentMessagesToAiFormat(latestSummaryAssistantGroup.messages)
5594
+ : [];
5595
+ const summaryOperations = latestSummaryAssistantGroup
5596
+ ? getOperationsForMessageGroup(summaryMessages, agentOperations)
5597
+ : [];
5598
+ 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 &&
5599
+ !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: {
5600
+ __html: sanitizeSvg(activeProfile.svgIcon),
5601
+ } })) : (_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 ||
5602
+ activeProfile?.displayTitle ||
5603
+ activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, browserCaptureInlinePrompt: browserCaptureInlinePrompt, onQuickAction: (action) => {
5604
+ const text = (action.prompt ||
5605
+ action.value ||
5606
+ action.label ||
5607
+ "").trim();
5608
+ if (!text)
5609
+ return;
5610
+ if (isExecuting) {
5611
+ try {
5612
+ handleStop();
5613
+ }
5614
+ catch { }
5615
+ }
5616
+ sendQuickMessage(text);
5617
+ } }) })) : hideSummaryWaitingPlaceholder ? (_jsx("div", { className: `flex h-full items-center justify-center ${compact ? "min-h-[100px] p-3" : "min-h-[220px] p-6"}`, children: summaryPlaceholderActions ? (_jsx("div", { className: "flex justify-center", children: summaryPlaceholderActions })) : null })) : (_jsx("div", { className: `flex h-full items-center justify-center ${compact ? "min-h-[100px] p-3" : "min-h-[220px] p-6"}`, children: _jsxs("div", { className: `max-w-md rounded-xl border border-slate-200 bg-slate-50 text-center text-slate-600 ${compact ? "px-3 py-2 text-xs" : "px-5 py-4 text-sm"}`, children: [_jsx("div", { children: shouldShowThinkingDots || isExecuting
5618
+ ? "The agent is still working. The next update will appear here automatically."
5619
+ : agent?.statusMessage ||
5620
+ summaryPlaceholderMessage ||
5621
+ "Waiting for the next agent update." }), summaryPlaceholderActions ? (_jsx("div", { className: `flex justify-center ${compact ? "mt-2" : "mt-3"}`, children: summaryPlaceholderActions })) : null] }) })), displayMode !== "summary" &&
5622
+ shouldShowThinkingDots &&
5623
+ !inlineDialog &&
5624
+ !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: {
5625
+ __html: sanitizeSvg(activeProfile.svgIcon),
5626
+ } })) : (_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 ||
5627
+ activeProfile?.displayTitle ||
5628
+ activeProfile?.name ||
5629
+ "Agent" }), _jsx("span", { className: "text-xs text-gray-400", children: formatTime(new Date()) })] }), _jsxs("div", { className: "flex items-center gap-1 pt-2", children: [_jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.3s]" }), _jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.15s]" }), _jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400" })] })] })] })), _jsx("div", { ref: messagesEndRef })] }), showSummaryInput && !activeInlineDialog ? (_jsxs("div", { className: cn("border-t border-gray-200 p-4", simpleMode && "pb-10"), children: [activePlaceholderInput ? (_jsx(PlaceholderInput, { ref: placeholderInputRef, text: activePlaceholderInput.text, showButtons: hideBottomControls, buttonsClassName: hideBottomControls ? "justify-end" : "", onFilledChange: setAllPlaceholdersFilled, onComplete: (filledText) => {
5630
+ setActivePlaceholderInput(null);
5631
+ setAllPlaceholdersFilled(false);
5632
+ if (activePlaceholderInput.behavior === "compose" &&
5633
+ !hideBottomControls) {
5634
+ setPrompt(filledText);
5635
+ setInputPlaceholder("Review and edit, then press Enter to send");
5636
+ if (textareaRef.current) {
5637
+ try {
5638
+ textareaRef.current.focus();
5639
+ const v = textareaRef.current.value || "";
5640
+ textareaRef.current.selectionStart = v.length;
5641
+ textareaRef.current.selectionEnd = v.length;
5642
+ }
5643
+ catch { }
5644
+ }
5645
+ }
5646
+ else {
5647
+ if (isExecuting) {
5648
+ try {
5649
+ handleStop();
5650
+ }
5651
+ catch { }
5652
+ }
5653
+ sendQuickMessage(filledText);
5654
+ }
5655
+ }, onCancel: () => {
5656
+ setActivePlaceholderInput(null);
5657
+ setAllPlaceholdersFilled(false);
5658
+ } })) : prompt && /\{\{[^{}]+\}\}|<<[^<>]+>>/.test(prompt) ? (_jsx(PlaceholderInput, { ref: promptPlaceholderInputRef, text: prompt, showButtons: hideBottomControls, buttonsClassName: hideBottomControls ? "justify-end" : "", onFilledChange: setAllPlaceholdersFilled, onComplete: (filledText) => {
5659
+ setPrompt(filledText);
5660
+ setAllPlaceholdersFilled(false);
5661
+ if (filledText.trim()) {
5662
+ if (isExecuting) {
5663
+ try {
5664
+ handleStop();
5665
+ }
5666
+ catch { }
5667
+ }
5668
+ sendQuickMessage(filledText);
5669
+ }
5670
+ }, onCancel: () => {
5671
+ setPrompt("");
5672
+ setAllPlaceholdersFilled(false);
5673
+ setInputPlaceholder("Type your message... (Enter to send, Shift+Enter or Ctrl+Enter for new line)");
5674
+ } })) : (_jsx("div", { className: "flex items-stretch gap-2", children: _jsx(Textarea, { ref: textareaRef, style: { viewTransitionName: "assistant-chat-input" }, value: prompt, onChange: (e) => {
5675
+ setPrompt(e.target.value);
5676
+ if (!/\{\{[^{}]+\}\}|<<[^<>]+>>/.test(e.target.value)) {
5677
+ setAllPlaceholdersFilled(false);
5678
+ }
5679
+ if (currentHistoryIndex !== -1) {
5680
+ setCurrentHistoryIndex(-1);
5681
+ }
5682
+ }, onKeyDown: handleKeyPress, onPaste: handlePaste, onFocus: () => {
5683
+ shouldMaintainFocusRef.current = true;
5684
+ }, onBlur: () => {
5685
+ shouldMaintainFocusRef.current = false;
5686
+ }, placeholder: inputPlaceholder, className: "max-h-[250px] min-h-[80px] flex-1 resize-y overflow-y-auto text-[12px] lg:max-h-[450px]", "data-testid": "agent-terminal-prompt", disabled: isSubmitting }) })), (() => {
5687
+ const isInPlaceholderMode = activePlaceholderInput ||
5688
+ (prompt && /\{\{[^{}]+\}\}|<<[^<>]+>>/.test(prompt));
5689
+ const placeholderShowsOwnButtons = hideBottomControls && isInPlaceholderMode;
5690
+ if (placeholderShowsOwnButtons)
5691
+ return null;
5692
+ return (_jsxs("div", { className: cn("mt-2 flex items-stretch gap-2", hideBottomControls ||
5693
+ simpleMode ||
5694
+ isInPlaceholderMode
5695
+ ? "justify-end"
5696
+ : "justify-between"), children: [!hideBottomControls &&
5697
+ !simpleMode &&
5698
+ !isInPlaceholderMode ? (_jsx("div", { className: "flex-1" })) : null, _jsx(Button, { type: "button", size: "sm", onClick: () => {
5699
+ if (isExecuting) {
5700
+ handleStop();
5701
+ }
5702
+ else {
5703
+ handleSubmit();
5704
+ }
5705
+ }, disabled: !isExecuting &&
5706
+ !activePlaceholderInput &&
5707
+ (!prompt.trim() || isSubmitting), "data-testid": "agent-send-stop-button", children: isExecuting ? "Stop" : "Send" })] }));
5708
+ })()] })) : null] }));
5709
+ })()
5710
+ : null;
5711
+ const fullModeInlineDialog = displayMode === "full" ? renderInlineDialogContent() : null;
5712
+ 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: [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) => {
3928
5713
  setPrompt(p);
3929
5714
  // Use setTimeout to ensure state is updated before submission
3930
5715
  setTimeout(() => {
@@ -3937,12 +5722,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3937
5722
  handleSubmit();
3938
5723
  }
3939
5724
  }, 0);
3940
- } })) })), messages.length === 0 &&
3941
- !error &&
3942
- hideGreeting &&
3943
- (isSubmitting || isConnecting) && (_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: {
3944
- __html: activeProfile.svgIcon,
3945
- } })) : (_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: [(() => {
5725
+ } })) })), 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: {
5726
+ __html: sanitizeSvg(activeProfile.svgIcon),
5727
+ } })) : (_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: [(() => {
3946
5728
  const groups = groupConsecutiveMessages(messages);
3947
5729
  return groups.map((group, groupIndex) => {
3948
5730
  const isLastGroup = groupIndex === groups.length - 1;
@@ -3950,6 +5732,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3950
5732
  // Render user message
3951
5733
  return (_jsx(UserMessage, { message: group.messages[0] }, groupIndex));
3952
5734
  }
5735
+ else if (group.type === "heartbeat" && group.messages[0]) {
5736
+ return (_jsx(HeartbeatMessage, { message: group.messages[0] }, group.messages[0].id || groupIndex));
5737
+ }
3953
5738
  else {
3954
5739
  // Render bundled assistant messages
3955
5740
  // Check if this group contains any streaming message
@@ -3966,9 +5751,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3966
5751
  }
3967
5752
  const convertedMessages = convertAgentMessagesToAiFormat(filteredMessages);
3968
5753
  const operationsForGroup = getOperationsForMessageGroup(convertedMessages, agentOperations);
3969
- return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isLastGroup || !isExecuting, editOperations: operationsForGroup, error: error || undefined, profileSvgIcon: activeProfile?.svgIcon, agentId: agent?.id || agentStub.id, agentName: activeProfile?.agentName ||
5754
+ return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isLastGroup || !isExecuting, editOperations: operationsForGroup, defaultCollapseJson: defaultCollapseJson, profileSvgIcon: activeProfile?.svgIcon, agentId: agent?.id || agentStub.id, agentName: activeProfile?.agentName ||
3970
5755
  activeProfile?.displayTitle ||
3971
- activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, onQuickAction: (action) => {
5756
+ activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, browserCaptureInlinePrompt: browserCaptureInlinePrompt, onQuickAction: (action) => {
3972
5757
  const text = (action.prompt ||
3973
5758
  action.value ||
3974
5759
  action.label ||
@@ -4012,13 +5797,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
4012
5797
  }
4013
5798
  });
4014
5799
  })(), 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: {
4015
- __html: activeProfile.svgIcon,
5800
+ __html: sanitizeSvg(activeProfile.svgIcon),
4016
5801
  } })) : (_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 ||
4017
5802
  activeProfile?.displayTitle ||
4018
5803
  activeProfile?.name ||
4019
5804
  "Agent" }), _jsx("span", { className: "text-xs text-gray-400", children: formatTime(new Date()) })] }), _jsxs("div", { className: "flex items-center gap-1 pt-2", children: [_jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.3s]" }), _jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400 [animation-delay:-0.15s]" }), _jsx("div", { className: "h-1 w-1 animate-bounce rounded-full bg-gray-400" })] })] })] }))] }), !simpleMode && renderCostLimitBanner(), _jsx("div", { ref: messagesEndRef })] }), !hideContext &&
4020
5805
  !simpleMode &&
4021
- (isMobile ? (_jsx("div", { className: "border-t border-gray-200 bg-gray-50", "data-testid": "agent-context-panel-tabs", children: _jsx(SimpleTabs, { tabs: [
5806
+ (editContext?.isMobile ? (_jsx("div", { className: "border-t border-gray-200 bg-gray-50", "data-testid": "agent-context-panel-tabs", children: _jsx(SimpleTabs, { tabs: [
4022
5807
  {
4023
5808
  id: "context",
4024
5809
  label: "Context",
@@ -4066,28 +5851,49 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
4066
5851
  hasTodoContent,
4067
5852
  hasSpawnedAgents,
4068
5853
  agent?.id && hasHistoryContent,
4069
- ].filter(Boolean).length - 1)), setActiveTab: setContextPanelsActiveTab, className: "justify-start px-4" }) })) : (_jsxs(_Fragment, { children: [renderContextInfoBar(), agent?.id && activeProfile && (_jsx(AgentDocumentList, { ref: documentListRef, agentId: agent.id, maxFileSizeMB: activeProfile.maxDocumentSizeMB ?? 10, enabled: activeProfile.enableDocumentUpload ?? false, profileId: activeProfile.id }, `${agent.id}-${agent.updatedDate || ""}-${activeProfile.id}`)), _jsx(TodoListPanel, { messages: messages, agentMetadata: agentMetadata }), _jsx(SpawnedAgentsPanel, { agentMetadata: agentMetadata }), agent?.id && _jsx(AgentEditOperationsPanel, { agentId: agent.id })] }))), queuedPrompts.length > 0 && !simpleMode && (_jsx("div", { className: "border-t border-gray-200 bg-amber-50/50", "data-testid": "queued-prompts-section", children: _jsxs("div", { className: "px-4 pt-3 pb-2", children: [_jsxs("div", { className: "mb-2 flex items-center gap-2", children: [_jsx("div", { className: "h-2 w-2 animate-pulse rounded-full bg-amber-500" }), _jsxs("span", { className: "text-[11px] font-semibold text-amber-900", "data-testid": "queued-prompts-count", children: ["Queued Messages (", queuedPrompts.length, ")"] })] }), _jsx("div", { className: "max-h-64 space-y-2 overflow-y-auto", children: queuedPrompts.map((qp) => (_jsx("div", { className: "rounded-md border border-amber-200 bg-white p-2.5 text-[11px]", "data-testid": "queued-prompt-item", children: _jsx("div", { className: "flex items-start justify-between gap-2", children: _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "mb-1 flex items-center gap-1.5", children: qp.sourceAgentName ? (_jsxs(_Fragment, { children: [_jsxs("span", { className: "font-medium text-gray-700", children: ["From ", qp.sourceAgentName] }), qp.priority > 5 && (_jsx("span", { className: "rounded bg-red-100 px-1.5 py-0.5 text-[11px] font-medium text-red-700", children: "High Priority" }))] })) : (_jsx("span", { className: "font-medium text-gray-700", children: "From User" })) }), _jsx("div", { className: "wrap-break-word whitespace-pre-wrap text-gray-600", "data-testid": "queued-prompt-text", children: qp.prompt }), _jsxs("div", { className: "mt-1.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-[11px] text-gray-400", children: [qp.createdDate && (_jsxs("span", { children: ["Queued ", formatTime(new Date(qp.createdDate))] })), qp.scheduledFor &&
4070
- new Date(qp.scheduledFor).getTime() >
4071
- new Date(qp.createdDate || 0).getTime() + 1000 && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-gray-300", children: "\u2022" }), _jsxs("span", { className: "font-medium text-amber-600", children: ["Scheduled for", " ", new Date(qp.scheduledFor).toDateString() ===
4072
- new Date().toDateString()
4073
- ? formatTime(new Date(qp.scheduledFor))
4074
- : formatDateTime(new Date(qp.scheduledFor))] })] }))] })] }) }) }, qp.id))) })] }) })), activeInlineDialog &&
4075
- (() => {
4076
- // Look up dialog component from config
4077
- const dialogRegistration = editContext?.configuration?.editor?.agentDialogs?.find((d) => d.dialogType === activeInlineDialog.request.dialogType);
4078
- if (dialogRegistration) {
4079
- const DialogComponent = dialogRegistration.component;
4080
- return (_jsx("div", { className: "agent-inline-dialog", children: _jsx(DialogComponent, { title: activeInlineDialog.request.title, description: activeInlineDialog.request.description, parameters: activeInlineDialog.request.parameters, onClose: (result) => {
4081
- activeInlineDialog.onComplete(result);
4082
- setActiveInlineDialog(null);
4083
- }, onCancel: () => {
4084
- activeInlineDialog.onCancel();
4085
- setActiveInlineDialog(null);
4086
- } }) }));
4087
- }
4088
- // Fallback for unknown dialog types
4089
- return (_jsx("div", { className: "agent-inline-dialog", children: _jsxs("div", { className: "p-4 text-sm text-red-500", children: ["Unknown dialog type: ", activeInlineDialog.request.dialogType] }) }));
4090
- })(), _jsxs("div", { className: cn("border-t border-gray-200 p-4", simpleMode && "pb-10"), children: [activePlaceholderInput ? (
5854
+ ].filter(Boolean).length - 1)), setActiveTab: setContextPanelsActiveTab, className: "justify-start px-4" }) })) : (_jsxs(_Fragment, { children: [renderContextInfoBar(), agent?.id && activeProfile && (_jsx(AgentDocumentList, { ref: documentListRef, agentId: agent.id, maxFileSizeMB: activeProfile.maxDocumentSizeMB ?? 10, enabled: activeProfile.enableDocumentUpload ?? false, profileId: activeProfile.id }, `${agent.id}-${agent.updatedDate || ""}-${activeProfile.id}`)), _jsx(TodoListPanel, { messages: messages, agentMetadata: agentMetadata }), _jsx(SpawnedAgentsPanel, { agentMetadata: agentMetadata }), agent?.id && _jsx(AgentEditOperationsPanel, { agentId: agent.id })] }))), queuedPrompts.length > 0 && !simpleMode && (_jsx("div", { className: "border-t border-gray-200 bg-amber-50/50", "data-testid": "queued-prompts-section", children: _jsxs("div", { className: "px-4 pt-2.5 pb-2", children: [_jsxs("div", { className: "mb-1.5 flex items-center gap-1.5", children: [_jsx("div", { className: "h-1.5 w-1.5 animate-pulse rounded-full bg-amber-400" }), _jsxs("span", { className: "text-[10px] font-medium tracking-wide text-amber-600 uppercase", "data-testid": "queued-prompts-count", children: ["Queued (", queuedPrompts.length, ")"] })] }), _jsx("div", { className: "max-h-64 space-y-0.5 overflow-y-auto", children: queuedPrompts.map((qp) => {
5855
+ let triggerName = "";
5856
+ if (qp.data) {
5857
+ try {
5858
+ const parsed = JSON.parse(qp.data);
5859
+ triggerName = parsed?.triggerName?.trim() || "";
5860
+ }
5861
+ catch {
5862
+ // Ignore invalid JSON metadata and render as regular queued prompt.
5863
+ }
5864
+ }
5865
+ const isTriggerQueuedPrompt = !qp.sourceAgentName && triggerName.length > 0;
5866
+ const isTriggerExpanded = !!expandedQueuedTriggerIds[qp.id];
5867
+ if (isTriggerQueuedPrompt) {
5868
+ return (_jsxs("div", { className: "text-[11px]", "data-testid": "queued-prompt-item", children: [_jsxs("button", { type: "button", onClick: () => setExpandedQueuedTriggerIds((prev) => ({
5869
+ ...prev,
5870
+ [qp.id]: !prev[qp.id],
5871
+ })), className: "flex w-full items-center gap-1.5 rounded px-1 py-0.5 text-left text-amber-800 transition-colors hover:bg-amber-100/60", "data-testid": "queued-trigger-toggle", "data-expanded": isTriggerExpanded ? "true" : "false", children: [_jsx(Target, { className: "h-3 w-3 shrink-0 text-amber-500", strokeWidth: 2 }), _jsx("span", { className: "truncate font-medium", children: triggerName }), qp.createdDate && (_jsx("span", { className: "ml-1 shrink-0 text-[10px] text-amber-500", children: formatTime(new Date(qp.createdDate)) })), _jsx("span", { className: "ml-auto shrink-0 text-amber-400", children: isTriggerExpanded ? (_jsx(ChevronUp, { className: "h-3 w-3" })) : (_jsx(ChevronDown, { className: "h-3 w-3" })) })] }), isTriggerExpanded && (_jsx("div", { className: "mt-0.5 border-l-2 border-amber-200 pl-5 text-[11px] text-amber-700/80", children: qp.prompt }))] }, qp.id));
5872
+ }
5873
+ return (_jsx("div", { className: "rounded-md border border-amber-200 bg-white p-2.5 text-[11px]", "data-testid": "queued-prompt-item", children: _jsx("div", { className: "flex items-start justify-between gap-2", children: _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "mb-1 flex items-center gap-1.5", children: qp.sourceAgentName ? (_jsxs(_Fragment, { children: [_jsxs("span", { className: "font-medium text-gray-700", children: ["From ", qp.sourceAgentName] }), qp.priority > 5 && (_jsx("span", { className: "rounded bg-red-100 px-1.5 py-0.5 text-[11px] font-medium text-red-700", children: "High Priority" }))] })) : (_jsx("span", { className: "font-medium text-gray-700", children: "From User" })) }), _jsx("div", { className: "wrap-break-word whitespace-pre-wrap text-gray-600", "data-testid": "queued-prompt-text", children: qp.prompt }), _jsxs("div", { className: "mt-1.5 flex flex-wrap items-center gap-x-2 gap-y-0.5 text-[11px] text-gray-400", children: [qp.createdDate && (_jsxs("span", { children: ["Queued ", formatTime(new Date(qp.createdDate))] })), qp.scheduledFor &&
5874
+ new Date(qp.scheduledFor).getTime() >
5875
+ new Date(qp.createdDate || 0).getTime() +
5876
+ 1000 && (_jsxs(_Fragment, { children: [_jsx("span", { className: "text-gray-300", children: "\u2022" }), _jsxs("span", { className: "font-medium text-amber-600", children: ["Scheduled for", " ", new Date(qp.scheduledFor).toDateString() ===
5877
+ new Date().toDateString()
5878
+ ? formatTime(new Date(qp.scheduledFor))
5879
+ : formatDateTime(new Date(qp.scheduledFor))] })] }))] })] }) }) }, qp.id));
5880
+ }) })] }) }))] }));
5881
+ const showQuestionnaireSplitter = isQuestionnaireDialogOpen && !!fullModeInlineDialog;
5882
+ const fullModeContent = showQuestionnaireSplitter ? (_jsx(Splitter, { panels: [
5883
+ {
5884
+ name: "conversation",
5885
+ defaultSize: 65,
5886
+ content: fullModeUpperContent,
5887
+ },
5888
+ {
5889
+ name: "questionnaire",
5890
+ defaultSize: 35,
5891
+ content: fullModeInlineDialog,
5892
+ },
5893
+ ], direction: "vertical", localStorageKey: compact
5894
+ ? "agent-terminal-compact-questionnaire-splitter"
5895
+ : "agent-terminal-questionnaire-splitter", className: "min-h-0 flex-1", splitterClassName: "bg-gray-200 hover:bg-gray-300" })) : (_jsxs(_Fragment, { children: [fullModeUpperContent, fullModeInlineDialog] }));
5896
+ return loadingContent ? (loadingContent) : displayMode === "summary" && summaryModeContent ? (summaryModeContent) : (_jsxs("div", { className: `flex h-full min-h-0 flex-col ${className || ""}`, children: [parentAgentId && !simpleMode && (_jsx("div", { className: "border-b border-gray-200 bg-gray-50 px-4 py-2", children: _jsxs("button", { onClick: handleBackToParent, className: "flex items-center gap-2 text-[11px] text-gray-600 transition-colors hover:text-gray-900", title: "Back to parent agent", children: [_jsx(ArrowLeft, { className: "h-3.5 w-3.5", strokeWidth: 1.5 }), _jsx("span", { children: "Back to parent agent" })] }) })), fullModeContent, _jsxs("div", { className: cn("border-t border-gray-200 p-4", simpleMode && "pb-10"), children: [activePlaceholderInput ? (
4091
5897
  // Placeholder Input (from quick actions)
4092
5898
  // Show internal buttons only in splash mode (hideBottomControls) since external buttons won't be visible there
4093
5899
  _jsx(PlaceholderInput, { ref: placeholderInputRef, text: activePlaceholderInput.text, showButtons: hideBottomControls, buttonsClassName: hideBottomControls ? "justify-end" : "", onFilledChange: setAllPlaceholdersFilled, onComplete: (filledText) => {
@@ -4164,122 +5970,221 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
4164
5970
  return null;
4165
5971
  return (_jsxs("div", { className: cn("mt-2 flex items-stretch gap-2", hideBottomControls || simpleMode || isInPlaceholderMode
4166
5972
  ? "justify-end"
4167
- : "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"
5973
+ : "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"
4168
5974
  ? "border-green-300 bg-green-50! text-green-700 hover:bg-green-100!"
4169
5975
  : mode === "supervised"
4170
5976
  ? "border-amber-300 bg-amber-50! text-amber-700 hover:bg-amber-100!"
4171
5977
  : "border-red-300 bg-red-50! text-red-700 hover:bg-red-100!"), value: mode, options: modeOptions, onValueChange: async (val) => {
4172
5978
  const nextMode = val || "supervised";
4173
- // Optimistic UI update
4174
- setMode(nextMode);
4175
5979
  const current = agentMetadata || {};
4176
5980
  const nextMeta = {
4177
5981
  ...current,
4178
5982
  mode: nextMode,
4179
5983
  };
4180
5984
  try {
4181
- if (!agent?.id || agent.status === "new") {
5985
+ if (!agent?.id || isLocalOnlyDraftAgent) {
5986
+ setMode(nextMode);
4182
5987
  setAgentMetadata(nextMeta);
4183
- // Cache until first start when agent is persisted
4184
5988
  pendingSettingsRef.current = {
4185
5989
  ...(pendingSettingsRef.current || {}),
4186
5990
  mode: nextMode,
4187
5991
  };
4188
5992
  return;
4189
5993
  }
4190
- await updateAgentSettings(agent.id, {
5994
+ const result = await updateAgentSettings(agent.id, {
4191
5995
  mode: nextMode,
4192
5996
  });
4193
- setAgentMetadata(nextMeta);
4194
- setAgent((prev) => prev
4195
- ? { ...prev, metadata: JSON.stringify(nextMeta) }
4196
- : prev);
4197
- }
4198
- catch (e2) {
4199
- console.error("Failed to persist mode change", e2);
4200
- }
4201
- } }), profiles?.length > 0 && (_jsx(Select, { size: "xs", maxWidth: 200, searchable: profiles.length > 5, searchPlaceholder: "Filter profiles...", className: "h-5 w-auto min-w-[120px] rounded border px-1.5 text-[11px] text-gray-500", value: activeProfile?.id || "", options: profileOptions, "data-testid": "agent-profile-selector", onValueChange: async (val) => {
4202
- const nextProfile = profiles.find((x) => x.id === val);
4203
- if (!nextProfile)
4204
- return;
4205
- setActiveProfile(nextProfile);
4206
- try {
4207
- if (agent?.id && agent.status !== "new") {
4208
- await updateAgentSettings(agent.id, {
4209
- profileId: nextProfile.id,
4210
- profileName: nextProfile.name,
4211
- });
4212
- }
4213
- else {
4214
- // cache until first start
4215
- pendingSettingsRef.current = {
4216
- ...(pendingSettingsRef.current || {}),
4217
- // we cache profile by updating local metadata
4218
- };
4219
- setAgentMetadata((current) => {
4220
- const next = { ...(current || {}) };
4221
- next.profile = nextProfile.name;
4222
- next.additionalData = {
4223
- ...(next.additionalData || {}),
4224
- profileId: nextProfile.id,
4225
- profileName: nextProfile.name,
4226
- };
4227
- return next;
4228
- });
5997
+ if (result.success === false ||
5998
+ result.updates?.mode === false) {
5999
+ throw new Error("Mode change was not applied");
4229
6000
  }
4230
- // reflect in local agent stub so tabs and titles can use it if needed
6001
+ setMode(nextMode);
6002
+ setAgentMetadata(nextMeta);
4231
6003
  setAgent((prev) => prev
4232
6004
  ? {
4233
6005
  ...prev,
4234
- metadata: JSON.stringify({
4235
- ...(agentMetadata || {}),
4236
- profile: nextProfile.name,
4237
- additionalData: {
4238
- ...(agentMetadata
4239
- ?.additionalData || {}),
4240
- profileId: nextProfile.id,
4241
- profileName: nextProfile.name,
4242
- },
4243
- }),
6006
+ mode: nextMode,
6007
+ metadata: JSON.stringify(nextMeta),
4244
6008
  }
4245
6009
  : prev);
4246
6010
  }
4247
- catch (err) {
4248
- console.error("Failed to persist agent profile", err);
4249
- }
4250
- } })), activeProfile?.models?.length ? (_jsx(Select, { size: "xs", maxWidth: 300, searchable: activeProfile.models.length > 5, searchPlaceholder: "Filter models...", className: "h-5 w-auto min-w-[120px] rounded border px-1.5 text-[11px] text-gray-500", value: selectedModelId || "", options: modelOptions, onValueChange: async (val) => {
4251
- const nextId = val;
4252
- setSelectedModelId(nextId);
4253
- const modelName = activeProfile?.models?.find((m) => m.id === nextId)
4254
- ?.name || "";
4255
- // Update local agent state immediately for UX and to reflect in streaming stub
4256
- setAgent((prev) => prev ? { ...prev, model: modelName } : prev);
4257
- // Persist only for existing agents; otherwise cache until first start
4258
- try {
4259
- if (agent?.id && agent.status !== "new") {
4260
- await updateAgentSettings(agent.id, {
4261
- model: modelName,
4262
- });
4263
- }
4264
- else {
4265
- pendingSettingsRef.current = {
4266
- ...(pendingSettingsRef.current || {}),
4267
- modelName,
4268
- };
4269
- }
6011
+ catch (e2) {
6012
+ console.error("Failed to persist mode change", e2);
4270
6013
  }
4271
- catch (err) {
4272
- console.error("Failed to persist agent model", err);
6014
+ } }), _jsxs(Popover, { open: showAgentSettings, onOpenChange: (open) => {
6015
+ setShowAgentSettings(open);
6016
+ if (!open) {
6017
+ setShowSkillPicker(false);
4273
6018
  }
4274
- } })) : null, 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: () => {
6019
+ }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { size: "xs", variant: "outline", className: "h-5 rounded border px-1.5 text-[11px] text-gray-600", "data-testid": "agent-settings-popover-trigger", children: [_jsx(Settings2, { className: "mr-1 h-3 w-3", strokeWidth: 1 }), "Agent settings"] }) }), _jsx(PopoverContent, { className: "w-80 p-3", align: "start", onInteractOutside: (e) => {
6020
+ const target = e.target;
6021
+ if (target?.closest('[data-help-panel="true"]')) {
6022
+ e.preventDefault();
6023
+ }
6024
+ }, onPointerDownOutside: (e) => {
6025
+ const target = e.target;
6026
+ if (target?.closest('[data-help-panel="true"]')) {
6027
+ e.preventDefault();
6028
+ }
6029
+ }, onFocusOutside: (e) => {
6030
+ const target = e.target;
6031
+ if (target?.closest('[data-help-panel="true"]')) {
6032
+ e.preventDefault();
6033
+ }
6034
+ }, children: _jsxs("div", { className: "space-y-3", children: [profiles?.length > 0 && (_jsxs("div", { children: [_jsx("div", { className: "mb-1 text-[11px] font-medium text-gray-700", children: "Agent profile" }), _jsx(Select, { size: "xs", maxWidth: 300, searchable: profiles.length > 5, searchPlaceholder: "Filter profiles...", className: "h-6 w-full rounded border px-1.5 text-[11px] text-gray-500", value: activeProfile?.id || "", options: profileOptions, "data-testid": "agent-profile-selector", onValueChange: async (val) => {
6035
+ const nextProfile = profiles.find((x) => x.id === val);
6036
+ if (!nextProfile)
6037
+ return;
6038
+ setActiveProfile(nextProfile);
6039
+ try {
6040
+ if (agent?.id && !isLocalOnlyDraftAgent) {
6041
+ await updateAgentSettings(agent.id, {
6042
+ profileId: nextProfile.id,
6043
+ profileName: nextProfile.name,
6044
+ });
6045
+ }
6046
+ else {
6047
+ pendingSettingsRef.current = {
6048
+ ...(pendingSettingsRef.current || {}),
6049
+ profileId: nextProfile.id,
6050
+ profileName: nextProfile.name,
6051
+ };
6052
+ setAgentMetadata((current) => {
6053
+ const next = {
6054
+ ...(current || {}),
6055
+ };
6056
+ next.profile = nextProfile.name;
6057
+ next.additionalData = {
6058
+ ...(next.additionalData || {}),
6059
+ profileId: nextProfile.id,
6060
+ profileName: nextProfile.name,
6061
+ };
6062
+ return next;
6063
+ });
6064
+ }
6065
+ setAgent((prev) => prev
6066
+ ? {
6067
+ ...prev,
6068
+ profileId: nextProfile.id,
6069
+ profileName: nextProfile.name,
6070
+ metadata: JSON.stringify({
6071
+ ...(agentMetadata || {}),
6072
+ profile: nextProfile.name,
6073
+ additionalData: {
6074
+ ...(agentMetadata
6075
+ ?.additionalData || {}),
6076
+ profileId: nextProfile.id,
6077
+ profileName: nextProfile.name,
6078
+ },
6079
+ }),
6080
+ }
6081
+ : prev);
6082
+ }
6083
+ catch (err) {
6084
+ console.error("Failed to persist agent profile", err);
6085
+ }
6086
+ } }), activeProfile && (_jsxs("div", { className: "mt-1 flex flex-wrap items-center gap-2", children: [_jsx(Button, { type: "button", size: "sm", variant: "outline", className: "h-6 px-2 text-[10px]", onClick: () => {
6087
+ void handleEditProfileSideBySide();
6088
+ setShowAgentSettings(false);
6089
+ }, "data-testid": "agent-profile-edit-button", children: "Edit" }), _jsxs("button", { type: "button", className: "inline-flex items-center gap-1 text-[10px] text-gray-500 hover:text-gray-700", onClick: () => {
6090
+ void handleOpenProfileSettings();
6091
+ setShowAgentSettings(false);
6092
+ }, children: ["Open profile settings", _jsx(ExternalLink, { className: "h-2.5 w-2.5", strokeWidth: 1.5 })] })] }))] })), activeProfile?.models?.length ? (_jsxs("div", { children: [_jsx("div", { className: "mb-1 text-[11px] font-medium text-gray-700", children: "Model" }), _jsx(Select, { "data-testid": "agent-model-selector", size: "xs", maxWidth: 300, searchable: activeProfile.models.length > 5, searchPlaceholder: "Filter models...", className: "h-6 w-full rounded border px-1.5 text-[11px] text-gray-500", value: selectedModelId || "", options: modelOptions, onValueChange: async (val) => {
6093
+ const nextId = val;
6094
+ setSelectedModelId(nextId);
6095
+ const modelName = activeProfile?.models?.find((m) => m.id === nextId)?.name || "";
6096
+ setAgent((prev) => prev ? { ...prev, model: modelName } : prev);
6097
+ try {
6098
+ if (agent?.id && !isLocalOnlyDraftAgent) {
6099
+ await updateAgentSettings(agent.id, {
6100
+ model: modelName,
6101
+ });
6102
+ }
6103
+ else {
6104
+ pendingSettingsRef.current = {
6105
+ ...(pendingSettingsRef.current || {}),
6106
+ modelName,
6107
+ };
6108
+ }
6109
+ }
6110
+ catch (err) {
6111
+ console.error("Failed to persist agent model", err);
6112
+ }
6113
+ } })] })) : null, _jsxs("div", { children: [_jsxs("div", { className: "mb-1 flex items-center justify-between gap-2", children: [_jsxs("div", { className: "flex items-center gap-1 text-[11px] font-medium text-gray-700", children: [_jsx(Target, { className: "h-3 w-3", strokeWidth: 1 }), "Skills"] }), _jsxs(Popover, { open: showSkillPicker, onOpenChange: (open) => {
6114
+ setShowSkillPicker(open);
6115
+ if (open) {
6116
+ setSkillActionError(null);
6117
+ }
6118
+ }, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsxs(Button, { size: "xs", variant: "outline", className: "h-5 rounded border px-1.5 text-[10px] text-gray-600", "data-testid": "agent-skill-picker-trigger", children: [_jsx(Plus, { className: "mr-1 h-3 w-3", strokeWidth: 1.5 }), "Add"] }) }), _jsx(PopoverContent, { className: "w-88 p-2", align: "end", side: "bottom", children: _jsxs("div", { className: "space-y-2", children: [_jsx("div", { className: "text-[11px] font-medium text-gray-700", children: "Select a skill" }), _jsxs("div", { className: "relative h-56 rounded border border-gray-200 bg-gray-50", children: [_jsx(ScrollingContentTree, { rootItemIds: skillRootIds, selectedItemId: selectedSkillIds[selectedSkillIds.length - 1] || undefined, expandedItemId: selectedSkillIds[selectedSkillIds.length - 1] || skillRootIds[0], scrollToSelected: true, hideRootNodes: false, onSelectionChange: (selection) => {
6119
+ const selected = selection[0];
6120
+ if (!selected?.id)
6121
+ return;
6122
+ setSkillActionError(null);
6123
+ if (selectableTemplateIdSet.size > 0 &&
6124
+ (!selected.templateId ||
6125
+ !selectableTemplateIdSet.has(selected.templateId.toLowerCase()))) {
6126
+ return;
6127
+ }
6128
+ if (!manuallyAssignableSkillIdSet.has(selected.id.toLowerCase())) {
6129
+ setSkillActionError("This skill cannot be added for the current agent profile.");
6130
+ return;
6131
+ }
6132
+ void (async () => {
6133
+ const added = await handleAddSkill(selected.id);
6134
+ if (added) {
6135
+ setShowSkillPicker(false);
6136
+ }
6137
+ })();
6138
+ } }), skillsLoading && (_jsx("div", { className: "bg-background/70 absolute inset-0 flex items-center justify-center text-[10px] text-gray-500", children: "Loading skills..." }))] }), !skillsLoading &&
6139
+ !skillsError &&
6140
+ profileFilteredSkills.length > 0 && (_jsx("div", { className: "text-[10px] text-gray-500", children: "Click a skill item in the tree to add it." })), skillsError && (_jsx("div", { className: "text-[10px] text-red-600", children: skillsError })), !skillsError && skillActionError && (_jsx("div", { className: "text-[10px] text-red-600", children: skillActionError })), !skillsLoading &&
6141
+ !skillsError &&
6142
+ skillRootIds.length === 0 && (_jsx("div", { className: "text-[10px] text-gray-500", children: "No skill roots available." })), !skillsLoading &&
6143
+ !skillsError &&
6144
+ profileFilteredSkills.length === 0 && (_jsx("div", { className: "text-[10px] text-gray-500", children: selectedSkillIds.length > 0
6145
+ ? "All addable skills are selected"
6146
+ : "No skills can be added for this profile" }))] }) })] })] }), selectedSkillIds.length > 0 && (_jsx("div", { className: "mb-2 flex flex-wrap gap-1", children: selectedSkillIds.map((skillId) => {
6147
+ const skill = selectedSkills.find((s) => s.id === skillId);
6148
+ 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: () => {
6149
+ void handleOpenSkillItem(skillId);
6150
+ }, 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: () => {
6151
+ void handleRemoveSkill(skillId);
6152
+ }, title: "Remove skill", "aria-label": `Remove ${skill?.name || skillId}`, children: _jsx(X, { className: "h-2.5 w-2.5", strokeWidth: 1 }) }))] }, skillId));
6153
+ }) }))] }), _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: [isLocalOnlyDraftAgent && (_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: isLocalOnlyDraftAgent
6154
+ ? "No available tools for this profile and mode"
6155
+ : "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) => {
6156
+ const sourceLabel = formatAllowanceSource(allowance.source);
6157
+ const pathLabel = "itemPath" in allowance
6158
+ ? allowance.itemPath
6159
+ : allowance.normalizedPath;
6160
+ return (_jsxs("div", { className: "rounded px-1 py-0.5 transition-colors hover:bg-white/60", "data-testid": `agent-allowance-row-${group.key}`, children: [_jsxs("div", { className: "flex items-baseline gap-1.5", children: [_jsx("div", { className: "shrink-0 text-[10px] font-medium text-gray-700", children: allowance.operationType ||
6161
+ "*" }), _jsx("div", { className: "truncate text-[9px] text-gray-500", title: formatAllowanceLabel(allowance), children: pathLabel })] }), (sourceLabel ||
6162
+ allowance.grantedBy) && (_jsx("div", { className: "truncate pl-6 text-[9px] text-gray-400", children: [
6163
+ sourceLabel,
6164
+ allowance.grantedBy,
6165
+ ]
6166
+ .filter(Boolean)
6167
+ .join(" · ") }))] }, `${group.key}-${allowance.operationType}-${pathLabel}-${index}`));
6168
+ })] }, group.key)) : null) })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isLocalOnlyDraftAgent
6169
+ ? "Allowances are shown after the agent is created"
6170
+ : "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) => {
6171
+ const filterText = (sub.filter || "").trim();
6172
+ 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));
6173
+ }) })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isLocalOnlyDraftAgent
6174
+ ? "Subscribed triggers are shown after the agent is created"
6175
+ : "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: () => {
4275
6176
  setPrompt(p.prompt);
4276
6177
  setShowPredefined(false);
4277
6178
  if (textareaRef.current)
4278
6179
  textareaRef.current.focus();
4279
- }, children: p.title }, index))) }) })] })) : null, !hideBottomControls && !simpleMode && isMobile && (_jsxs(Popover, { open: showCostAndAgent, onOpenChange: setShowCostAndAgent, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { onClick: () => {
4280
- if (isMobile)
6180
+ }, children: p.title }, index))) }) })] })) : null, !hideBottomControls &&
6181
+ !simpleMode &&
6182
+ editContext?.isMobile && (_jsxs(Popover, { open: showCostAndAgent, onOpenChange: setShowCostAndAgent, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { onClick: () => {
6183
+ if (editContext?.isMobile)
4281
6184
  setShowCostAndAgent((prev) => !prev);
4282
- }, variant: "outline", size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", "aria-expanded": isMobile ? showCostAndAgent : undefined, "aria-label": "Toggle cost and context info", children: _jsx(DollarSign, { className: "size-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: _jsx(AgentTerminalStatusBar, { agent: agent, contextWindowStatus: contextWindowStatus, liveTotals: liveTotals, totalTokens: liveTotals
6185
+ }, variant: "outline", size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", "aria-expanded": editContext?.isMobile
6186
+ ? showCostAndAgent
6187
+ : undefined, "aria-label": "Toggle cost and context info", children: _jsx(DollarSign, { className: "size-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: _jsx(AgentTerminalStatusBar, { agent: agent, contextWindowStatus: contextWindowStatus, effectiveModelName: effectiveModelName, socketDiagnostics: editContext.socketDiagnostics, runDiagnosticsSnapshot: runDiagnosticsSnapshot, liveTotals: liveTotals, totalTokens: liveTotals
4283
6188
  ? {
4284
6189
  input: liveTotals.input,
4285
6190
  output: liveTotals.output,
@@ -4289,9 +6194,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
4289
6194
  outputCost: liveTotals.outputCost,
4290
6195
  cachedCost: liveTotals.cachedCost,
4291
6196
  cacheWriteCost: liveTotals.cacheWriteCost ?? 0,
6197
+ imageCost: liveTotals.imageCost ?? 0,
4292
6198
  totalCost: liveTotals.totalCost,
4293
6199
  }
4294
- : totalTokens, effectiveCostLimit: effectiveCostLimit, messages: messages, activeProfile: activeProfile, showCompressionPopover: showCompressionPopover, setShowCompressionPopover: setShowCompressionPopover }) }) })] }))] })), _jsxs("div", { className: "flex items-center gap-1 self-end", children: [_jsx("span", { title: isVoiceDisabled
6200
+ : totalTokens, effectiveCostLimit: effectiveCostLimit, messages: messages, showCompressionPopover: showCompressionPopover, setShowCompressionPopover: setShowCompressionPopover }) }) })] }))] })), _jsxs("div", { className: "flex items-center gap-1 self-end", children: [_jsx("span", { title: isVoiceDisabled
4295
6201
  ? "Your browser does not support Speech Recognition"
4296
6202
  : isListening
4297
6203
  ? "Stop voice input"
@@ -4312,7 +6218,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
4312
6218
  : allPendingApprovals.length > 0
4313
6219
  ? "Approve or reject pending tool calls first"
4314
6220
  : "Send", "aria-label": isExecuting ? "Stop" : "Send", "data-testid": "agent-send-stop-button", "data-executing": isExecuting ? "true" : "false", children: isExecuting ? (_jsx(Square, { className: "size-3", strokeWidth: 1 })) : (_jsx(Send, { className: "size-3", strokeWidth: 1 })) })] })] }));
4315
- })(), !hideBottomControls && !simpleMode && !isMobile && (_jsx(AgentTerminalStatusBar, { agent: agent, contextWindowStatus: contextWindowStatus, liveTotals: liveTotals, totalTokens: liveTotals
6221
+ })(), !hideBottomControls &&
6222
+ !simpleMode &&
6223
+ editContext &&
6224
+ !editContext.isMobile && (_jsx(AgentTerminalStatusBar, { agent: agent, contextWindowStatus: contextWindowStatus, effectiveModelName: effectiveModelName, socketDiagnostics: editContext.socketDiagnostics, runDiagnosticsSnapshot: runDiagnosticsSnapshot, liveTotals: liveTotals, totalTokens: liveTotals
4316
6225
  ? {
4317
6226
  input: liveTotals.input,
4318
6227
  output: liveTotals.output,
@@ -4322,8 +6231,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
4322
6231
  outputCost: liveTotals.outputCost,
4323
6232
  cachedCost: liveTotals.cachedCost,
4324
6233
  cacheWriteCost: liveTotals.cacheWriteCost ?? 0,
6234
+ imageCost: liveTotals.imageCost ?? 0,
4325
6235
  totalCost: liveTotals.totalCost,
4326
6236
  }
4327
- : totalTokens, effectiveCostLimit: effectiveCostLimit, messages: messages, activeProfile: activeProfile, showCompressionPopover: showCompressionPopover, setShowCompressionPopover: setShowCompressionPopover }))] })] }));
6237
+ : totalTokens, effectiveCostLimit: effectiveCostLimit, messages: messages, showCompressionPopover: showCompressionPopover, setShowCompressionPopover: setShowCompressionPopover }))] })] }));
4328
6238
  }
4329
6239
  //# sourceMappingURL=AgentTerminal.js.map