@parhelia/core 0.1.12570 → 0.1.12585

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 (591) 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/types/workspace.d.ts +6 -0
  63. package/dist/config/types.d.ts +63 -12
  64. package/dist/config/types.js.map +1 -1
  65. package/dist/editor/ConfirmationDialog.js +20 -4
  66. package/dist/editor/ConfirmationDialog.js.map +1 -1
  67. package/dist/editor/ContentTree.d.ts +2 -1
  68. package/dist/editor/ContentTree.js +93 -32
  69. package/dist/editor/ContentTree.js.map +1 -1
  70. package/dist/editor/Editor.js +87 -22
  71. package/dist/editor/Editor.js.map +1 -1
  72. package/dist/editor/FieldHistory.js +84 -36
  73. package/dist/editor/FieldHistory.js.map +1 -1
  74. package/dist/editor/FieldListField.js +21 -9
  75. package/dist/editor/FieldListField.js.map +1 -1
  76. package/dist/editor/FieldListFieldWithFallbacks.js +23 -2
  77. package/dist/editor/FieldListFieldWithFallbacks.js.map +1 -1
  78. package/dist/editor/GlobalMenuBar.js +29 -2
  79. package/dist/editor/GlobalMenuBar.js.map +1 -1
  80. package/dist/editor/ImageEditor.js +5 -2
  81. package/dist/editor/ImageEditor.js.map +1 -1
  82. package/dist/editor/ItemInfo.js +36 -1
  83. package/dist/editor/ItemInfo.js.map +1 -1
  84. package/dist/editor/LinkEditorDialog.js +3 -0
  85. package/dist/editor/LinkEditorDialog.js.map +1 -1
  86. package/dist/editor/MainLayout.d.ts +0 -2
  87. package/dist/editor/MainLayout.js +65 -8
  88. package/dist/editor/MainLayout.js.map +1 -1
  89. package/dist/editor/MigrationsView.js +29 -5
  90. package/dist/editor/MigrationsView.js.map +1 -1
  91. package/dist/editor/MobileLayout.js +37 -12
  92. package/dist/editor/MobileLayout.js.map +1 -1
  93. package/dist/editor/PictureCropper.js +54 -45
  94. package/dist/editor/PictureCropper.js.map +1 -1
  95. package/dist/editor/PictureEditor.js +17 -15
  96. package/dist/editor/PictureEditor.js.map +1 -1
  97. package/dist/editor/QuickItemSwitcher.js +37 -63
  98. package/dist/editor/QuickItemSwitcher.js.map +1 -1
  99. package/dist/editor/SetupWizard.js +52 -12
  100. package/dist/editor/SetupWizard.js.map +1 -1
  101. package/dist/editor/Titlebar.js +7 -2
  102. package/dist/editor/Titlebar.js.map +1 -1
  103. package/dist/editor/ai/AgentCostDisplay.d.ts +1 -0
  104. package/dist/editor/ai/AgentCostDisplay.js +1 -1
  105. package/dist/editor/ai/AgentCostDisplay.js.map +1 -1
  106. package/dist/editor/ai/AgentDocumentList.js +32 -14
  107. package/dist/editor/ai/AgentDocumentList.js.map +1 -1
  108. package/dist/editor/ai/AgentGreeting.js +6 -5
  109. package/dist/editor/ai/AgentGreeting.js.map +1 -1
  110. package/dist/editor/ai/AgentProfileSelector.js +2 -1
  111. package/dist/editor/ai/AgentProfileSelector.js.map +1 -1
  112. package/dist/editor/ai/AgentStatusBadge.d.ts +0 -5
  113. package/dist/editor/ai/AgentStatusBadge.js +67 -65
  114. package/dist/editor/ai/AgentStatusBadge.js.map +1 -1
  115. package/dist/editor/ai/AgentTerminal.d.ts +14 -2
  116. package/dist/editor/ai/AgentTerminal.js +2515 -579
  117. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  118. package/dist/editor/ai/AgentTerminalStatusBar.d.ts +9 -4
  119. package/dist/editor/ai/AgentTerminalStatusBar.js +481 -56
  120. package/dist/editor/ai/AgentTerminalStatusBar.js.map +1 -1
  121. package/dist/editor/ai/Agents.js +161 -113
  122. package/dist/editor/ai/Agents.js.map +1 -1
  123. package/dist/editor/ai/AiResponseMessage.d.ts +10 -1
  124. package/dist/editor/ai/AiResponseMessage.js +267 -26
  125. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  126. package/dist/editor/ai/ContextInfoBar.d.ts +2 -3
  127. package/dist/editor/ai/ContextInfoBar.js +64 -7
  128. package/dist/editor/ai/ContextInfoBar.js.map +1 -1
  129. package/dist/editor/ai/EditOperationsPanel.d.ts +3 -2
  130. package/dist/editor/ai/EditOperationsPanel.js +21 -78
  131. package/dist/editor/ai/EditOperationsPanel.js.map +1 -1
  132. package/dist/editor/ai/GuidanceOverlay.js +17 -11
  133. package/dist/editor/ai/GuidanceOverlay.js.map +1 -1
  134. package/dist/editor/ai/InlineAiDialog.d.ts +1 -1
  135. package/dist/editor/ai/InlineAiDialog.js +514 -192
  136. package/dist/editor/ai/InlineAiDialog.js.map +1 -1
  137. package/dist/editor/ai/InlineAiTrigger.js +115 -12
  138. package/dist/editor/ai/InlineAiTrigger.js.map +1 -1
  139. package/dist/editor/ai/MediaImage.js +40 -8
  140. package/dist/editor/ai/MediaImage.js.map +1 -1
  141. package/dist/editor/ai/SpawnedAgentsPanel.js +10 -12
  142. package/dist/editor/ai/SpawnedAgentsPanel.js.map +1 -1
  143. package/dist/editor/ai/ToolCallDisplay.d.ts +22 -2
  144. package/dist/editor/ai/ToolCallDisplay.js +614 -202
  145. package/dist/editor/ai/ToolCallDisplay.js.map +1 -1
  146. package/dist/editor/ai/dialogs/AgentDialogHandler.d.ts +1 -8
  147. package/dist/editor/ai/dialogs/AgentDialogHandler.js +379 -42
  148. package/dist/editor/ai/dialogs/AgentDialogHandler.js.map +1 -1
  149. package/dist/editor/ai/dialogs/QuestionnaireInline.d.ts +5 -1
  150. package/dist/editor/ai/dialogs/QuestionnaireInline.js +628 -60
  151. package/dist/editor/ai/dialogs/QuestionnaireInline.js.map +1 -1
  152. package/dist/editor/ai/dialogs/agentDialogTypes.d.ts +117 -0
  153. package/dist/editor/ai/dialogs/agentDialogTypes.js +2 -0
  154. package/dist/editor/ai/dialogs/agentDialogTypes.js.map +1 -1
  155. package/dist/editor/ai/types.d.ts +3 -1
  156. package/dist/editor/ai/useAgentStatus.d.ts +2 -1
  157. package/dist/editor/ai/useAgentStatus.js +90 -100
  158. package/dist/editor/ai/useAgentStatus.js.map +1 -1
  159. package/dist/editor/ai/useInlineAiPosition.js +45 -5
  160. package/dist/editor/ai/useInlineAiPosition.js.map +1 -1
  161. package/dist/editor/client/AboutDialog.js +4 -2
  162. package/dist/editor/client/AboutDialog.js.map +1 -1
  163. package/dist/editor/client/EditorShell.d.ts +4 -1
  164. package/dist/editor/client/EditorShell.js +853 -258
  165. package/dist/editor/client/EditorShell.js.map +1 -1
  166. package/dist/editor/client/editContext.d.ts +33 -19
  167. package/dist/editor/client/editContext.js.map +1 -1
  168. package/dist/editor/client/helpers.js +6 -0
  169. package/dist/editor/client/helpers.js.map +1 -1
  170. package/dist/editor/client/hooks/useEditorUrlSync.js +1 -2
  171. package/dist/editor/client/hooks/useEditorUrlSync.js.map +1 -1
  172. package/dist/editor/client/hooks/useEditorWebSocket.d.ts +10 -0
  173. package/dist/editor/client/hooks/useEditorWebSocket.js +209 -14
  174. package/dist/editor/client/hooks/useEditorWebSocket.js.map +1 -1
  175. package/dist/editor/client/hooks/useQuota.d.ts +8 -0
  176. package/dist/editor/client/hooks/useQuota.js.map +1 -1
  177. package/dist/editor/client/hooks/useSocketMessageHandler.js +73 -15
  178. package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
  179. package/dist/editor/client/itemsRepository.js +10 -6
  180. package/dist/editor/client/itemsRepository.js.map +1 -1
  181. package/dist/editor/client/operations.d.ts +6 -3
  182. package/dist/editor/client/operations.js +208 -30
  183. package/dist/editor/client/operations.js.map +1 -1
  184. package/dist/editor/client/pageModelBuilder.js +4 -31
  185. package/dist/editor/client/pageModelBuilder.js.map +1 -1
  186. package/dist/editor/client/ui/DevModeIndicator.js +2 -2
  187. package/dist/editor/client/ui/DevModeIndicator.js.map +1 -1
  188. package/dist/editor/client/ui/EditorChrome.d.ts +0 -6
  189. package/dist/editor/client/ui/EditorChrome.js +55 -72
  190. package/dist/editor/client/ui/EditorChrome.js.map +1 -1
  191. package/dist/editor/client/ui/FullscreenControls.js +5 -3
  192. package/dist/editor/client/ui/FullscreenControls.js.map +1 -1
  193. package/dist/editor/commands/commands.d.ts +11 -1
  194. package/dist/editor/commands/commands.js +12 -1
  195. package/dist/editor/commands/commands.js.map +1 -1
  196. package/dist/editor/commands/componentCommands.js +109 -55
  197. package/dist/editor/commands/componentCommands.js.map +1 -1
  198. package/dist/editor/commands/customCommandConverter.d.ts +8 -1
  199. package/dist/editor/commands/customCommandConverter.js +35 -5
  200. package/dist/editor/commands/customCommandConverter.js.map +1 -1
  201. package/dist/editor/commands/handlers/agentHandler.js +2 -1
  202. package/dist/editor/commands/handlers/agentHandler.js.map +1 -1
  203. package/dist/editor/commands/itemCommands.d.ts +3 -0
  204. package/dist/editor/commands/itemCommands.js +93 -10
  205. package/dist/editor/commands/itemCommands.js.map +1 -1
  206. package/dist/editor/commands/undo.d.ts +9 -15
  207. package/dist/editor/commands/undo.js +24 -0
  208. package/dist/editor/commands/undo.js.map +1 -1
  209. package/dist/editor/context-menu/InsertMenu.js +83 -39
  210. package/dist/editor/context-menu/InsertMenu.js.map +1 -1
  211. package/dist/editor/field-types/MultiLineText.js +1 -1
  212. package/dist/editor/field-types/MultiLineText.js.map +1 -1
  213. package/dist/editor/field-types/RawEditor.js +1 -1
  214. package/dist/editor/field-types/RichTextEditor.js +13 -5
  215. package/dist/editor/field-types/RichTextEditor.js.map +1 -1
  216. package/dist/editor/field-types/RichTextEditorComponent.js +37 -3
  217. package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
  218. package/dist/editor/field-types/SingleLineText.js +1 -1
  219. package/dist/editor/field-types/TreeListEditor.js +3 -2
  220. package/dist/editor/field-types/TreeListEditor.js.map +1 -1
  221. package/dist/editor/field-types/richtext/components/ReactSlate.css +23 -5
  222. package/dist/editor/field-types/richtext/components/ReactSlate.d.ts +2 -0
  223. package/dist/editor/field-types/richtext/components/ReactSlate.js +28 -4
  224. package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
  225. package/dist/editor/field-types/richtext/components/ToolbarButton.js +4 -2
  226. package/dist/editor/field-types/richtext/components/ToolbarButton.js.map +1 -1
  227. package/dist/editor/field-types/richtext/contextMenuFactory.d.ts +13 -0
  228. package/dist/editor/field-types/richtext/contextMenuFactory.js +181 -24
  229. package/dist/editor/field-types/richtext/contextMenuFactory.js.map +1 -1
  230. package/dist/editor/field-types/richtext/types.d.ts +2 -0
  231. package/dist/editor/field-types/richtext/types.js.map +1 -1
  232. package/dist/editor/field-types/richtext/utils/plugins.js +4 -0
  233. package/dist/editor/field-types/richtext/utils/plugins.js.map +1 -1
  234. package/dist/editor/field-types/textContextMenuFactory.js +3 -2
  235. package/dist/editor/field-types/textContextMenuFactory.js.map +1 -1
  236. package/dist/editor/media-selector/AiImageSearchPrompt.js +4 -2
  237. package/dist/editor/media-selector/AiImageSearchPrompt.js.map +1 -1
  238. package/dist/editor/media-selector/MediaFolderBrowser.js +1 -1
  239. package/dist/editor/media-selector/MediaFolderBrowser.js.map +1 -1
  240. package/dist/editor/media-selector/MediaSelector.js +7 -1
  241. package/dist/editor/media-selector/MediaSelector.js.map +1 -1
  242. package/dist/editor/media-selector/TreeSelector.js +40 -35
  243. package/dist/editor/media-selector/TreeSelector.js.map +1 -1
  244. package/dist/editor/menubar/ActiveUsers.js +1 -1
  245. package/dist/editor/menubar/ActiveUsers.js.map +1 -1
  246. package/dist/editor/menubar/GenericToolbar.js +4 -2
  247. package/dist/editor/menubar/GenericToolbar.js.map +1 -1
  248. package/dist/editor/menubar/ItemLanguageVersion.js +2 -2
  249. package/dist/editor/menubar/ItemLanguageVersion.js.map +1 -1
  250. package/dist/editor/menubar/PageSelector.js +26 -147
  251. package/dist/editor/menubar/PageSelector.js.map +1 -1
  252. package/dist/editor/menubar/Separator.js +1 -1
  253. package/dist/editor/menubar/VersionSelector.js +2 -4
  254. package/dist/editor/menubar/VersionSelector.js.map +1 -1
  255. package/dist/editor/menubar/WorkflowButton.js +39 -12
  256. package/dist/editor/menubar/WorkflowButton.js.map +1 -1
  257. package/dist/editor/menubar/toolbar-sections/CustomCommandsToolbar.js +16 -38
  258. package/dist/editor/menubar/toolbar-sections/CustomCommandsToolbar.js.map +1 -1
  259. package/dist/editor/menubar/toolbar-sections/EditControls.js +3 -3
  260. package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
  261. package/dist/editor/menubar/toolbar-sections/HelpButton.js +1 -0
  262. package/dist/editor/menubar/toolbar-sections/HelpButton.js.map +1 -1
  263. package/dist/editor/menubar/toolbar-sections/ManualBrowser.d.ts +6 -10
  264. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +597 -220
  265. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js.map +1 -1
  266. package/dist/editor/menubar/toolbar-sections/UtilityControls.js +13 -2
  267. package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
  268. package/dist/editor/page-editor-chrome/CommentHighlighting.js +42 -1
  269. package/dist/editor/page-editor-chrome/CommentHighlighting.js.map +1 -1
  270. package/dist/editor/page-editor-chrome/FrameMenu.js +1 -1
  271. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  272. package/dist/editor/page-editor-chrome/InlineEditor.js +97 -48
  273. package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
  274. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +38 -17
  275. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
  276. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +17 -11
  277. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js.map +1 -1
  278. package/dist/editor/page-editor-chrome/useInlineAICompletion.js +301 -301
  279. package/dist/editor/page-editor-chrome/useInlineAICompletion.js.map +1 -1
  280. package/dist/editor/page-viewer/DeviceToolbar.js +1 -1
  281. package/dist/editor/page-viewer/DeviceToolbar.js.map +1 -1
  282. package/dist/editor/page-viewer/EditorForm.js +69 -11
  283. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  284. package/dist/editor/page-viewer/MiniMap.d.ts +2 -4
  285. package/dist/editor/page-viewer/MiniMap.js +91 -28
  286. package/dist/editor/page-viewer/MiniMap.js.map +1 -1
  287. package/dist/editor/page-viewer/PageViewer.d.ts +3 -1
  288. package/dist/editor/page-viewer/PageViewer.js +92 -19
  289. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  290. package/dist/editor/page-viewer/PageViewerFrame.d.ts +2 -1
  291. package/dist/editor/page-viewer/PageViewerFrame.js +348 -115
  292. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  293. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js +114 -49
  294. package/dist/editor/page-viewer/pageModelSkeletonBuilder.js.map +1 -1
  295. package/dist/editor/page-viewer/pageViewContext.d.ts +1 -0
  296. package/dist/editor/page-viewer/pageViewContext.js +51 -14
  297. package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
  298. package/dist/editor/pageModel.d.ts +14 -1
  299. package/dist/editor/reviews/Comment.d.ts +2 -1
  300. package/dist/editor/reviews/Comment.js +92 -15
  301. package/dist/editor/reviews/Comment.js.map +1 -1
  302. package/dist/editor/reviews/CommentDisplayPopover.js +70 -5
  303. package/dist/editor/reviews/CommentDisplayPopover.js.map +1 -1
  304. package/dist/editor/reviews/CommentView.d.ts +3 -1
  305. package/dist/editor/reviews/CommentView.js +26 -6
  306. package/dist/editor/reviews/CommentView.js.map +1 -1
  307. package/dist/editor/reviews/Comments.js +140 -75
  308. package/dist/editor/reviews/Comments.js.map +1 -1
  309. package/dist/editor/reviews/CreateReviewDialog.js +281 -177
  310. package/dist/editor/reviews/CreateReviewDialog.js.map +1 -1
  311. package/dist/editor/reviews/DecisionsMatrix.js +96 -25
  312. package/dist/editor/reviews/DecisionsMatrix.js.map +1 -1
  313. package/dist/editor/reviews/DiffView.js +7 -14
  314. package/dist/editor/reviews/DiffView.js.map +1 -1
  315. package/dist/editor/reviews/EditReviewSettingsDialog.js +6 -4
  316. package/dist/editor/reviews/EditReviewSettingsDialog.js.map +1 -1
  317. package/dist/editor/reviews/MultiReviewManager.js +25 -3
  318. package/dist/editor/reviews/MultiReviewManager.js.map +1 -1
  319. package/dist/editor/reviews/PagesPanel.js +31 -15
  320. package/dist/editor/reviews/PagesPanel.js.map +1 -1
  321. package/dist/editor/reviews/PreviewInfo.js +1 -4
  322. package/dist/editor/reviews/PreviewInfo.js.map +1 -1
  323. package/dist/editor/reviews/ReviewCard.js +13 -7
  324. package/dist/editor/reviews/ReviewCard.js.map +1 -1
  325. package/dist/editor/reviews/ReviewDetail.js +3 -2
  326. package/dist/editor/reviews/ReviewDetail.js.map +1 -1
  327. package/dist/editor/reviews/ReviewsList.js +7 -3
  328. package/dist/editor/reviews/ReviewsList.js.map +1 -1
  329. package/dist/editor/reviews/SuggestedEdit.js +34 -3
  330. package/dist/editor/reviews/SuggestedEdit.js.map +1 -1
  331. package/dist/editor/reviews/SuggestionDisplayPopover.js +31 -5
  332. package/dist/editor/reviews/SuggestionDisplayPopover.js.map +1 -1
  333. package/dist/editor/reviews/commentAi.js +25 -6
  334. package/dist/editor/reviews/commentAi.js.map +1 -1
  335. package/dist/editor/reviews/reviewCommands.js +4 -1
  336. package/dist/editor/reviews/reviewCommands.js.map +1 -1
  337. package/dist/editor/reviews/useMultiReview.js +2 -2
  338. package/dist/editor/reviews/useMultiReview.js.map +1 -1
  339. package/dist/editor/reviews/useReviews.d.ts +4 -3
  340. package/dist/editor/reviews/useReviews.js +21 -32
  341. package/dist/editor/reviews/useReviews.js.map +1 -1
  342. package/dist/editor/services/agentService.d.ts +240 -5
  343. package/dist/editor/services/agentService.js +299 -39
  344. package/dist/editor/services/agentService.js.map +1 -1
  345. package/dist/editor/services/aiService.d.ts +57 -1
  346. package/dist/editor/services/aiService.js +79 -6
  347. package/dist/editor/services/aiService.js.map +1 -1
  348. package/dist/editor/services/contentService.d.ts +6 -3
  349. package/dist/editor/services/contentService.js +13 -12
  350. package/dist/editor/services/contentService.js.map +1 -1
  351. package/dist/editor/services/editService.d.ts +52 -1
  352. package/dist/editor/services/editService.js +94 -2
  353. package/dist/editor/services/editService.js.map +1 -1
  354. package/dist/editor/services/indexService.js +1 -1
  355. package/dist/editor/services/indexService.js.map +1 -1
  356. package/dist/editor/services/reviewsService.d.ts +3 -6
  357. package/dist/editor/services/reviewsService.js +2 -11
  358. package/dist/editor/services/reviewsService.js.map +1 -1
  359. package/dist/editor/services/serviceHelper.d.ts +2 -1
  360. package/dist/editor/services/serviceHelper.js +112 -20
  361. package/dist/editor/services/serviceHelper.js.map +1 -1
  362. package/dist/editor/services/systemService.d.ts +2 -1
  363. package/dist/editor/services/systemService.js +3 -0
  364. package/dist/editor/services/systemService.js.map +1 -1
  365. package/dist/editor/services-server/api.d.ts +1 -2
  366. package/dist/editor/services-server/api.js +11 -6
  367. package/dist/editor/services-server/api.js.map +1 -1
  368. package/dist/editor/settings/About.js +317 -3
  369. package/dist/editor/settings/About.js.map +1 -1
  370. package/dist/editor/settings/IndexOverview.js +3 -1
  371. package/dist/editor/settings/IndexOverview.js.map +1 -1
  372. package/dist/editor/settings/QuotaInfo.js +210 -4
  373. package/dist/editor/settings/QuotaInfo.js.map +1 -1
  374. package/dist/editor/settings/SettingsView.js +25 -23
  375. package/dist/editor/settings/SettingsView.js.map +1 -1
  376. package/dist/editor/settings/Status.js +7 -6
  377. package/dist/editor/settings/Status.js.map +1 -1
  378. package/dist/editor/settings/index/CollectionWarningsDisplay.d.ts +10 -0
  379. package/dist/editor/settings/index/CollectionWarningsDisplay.js +16 -0
  380. package/dist/editor/settings/index/CollectionWarningsDisplay.js.map +1 -0
  381. package/dist/editor/settings/index/useIndexStatus.js +23 -22
  382. package/dist/editor/settings/index/useIndexStatus.js.map +1 -1
  383. package/dist/editor/settings/panels/AgentsPanel.d.ts +0 -4
  384. package/dist/editor/settings/panels/AgentsPanel.js +95 -121
  385. package/dist/editor/settings/panels/AgentsPanel.js.map +1 -1
  386. package/dist/editor/settings/panels/ModelConfigPanel.js +1 -1
  387. package/dist/editor/settings/panels/ModelConfigPanel.js.map +1 -1
  388. package/dist/editor/settings/panels/ModelsPanel.js +324 -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 +67 -6
  394. package/dist/editor/settings/panels/SearchConfigPanel.js.map +1 -1
  395. package/dist/editor/settings/panels/StatusPanel.js +7 -2
  396. package/dist/editor/settings/panels/StatusPanel.js.map +1 -1
  397. package/dist/editor/settings/panels/index.d.ts +3 -2
  398. package/dist/editor/settings/panels/index.js +3 -2
  399. package/dist/editor/settings/panels/index.js.map +1 -1
  400. package/dist/editor/settings/status/coreStatusChecks.js +124 -19
  401. package/dist/editor/settings/status/coreStatusChecks.js.map +1 -1
  402. package/dist/editor/settings/status/useStartupChecks.d.ts +3 -1
  403. package/dist/editor/settings/status/useStartupChecks.js +9 -5
  404. package/dist/editor/settings/status/useStartupChecks.js.map +1 -1
  405. package/dist/editor/setup-wizard/steps/CompleteStep.d.ts +2 -1
  406. package/dist/editor/setup-wizard/steps/CompleteStep.js +2 -1
  407. package/dist/editor/setup-wizard/steps/CompleteStep.js.map +1 -1
  408. package/dist/editor/sidebar/ComponentPalette.js +2 -1
  409. package/dist/editor/sidebar/ComponentPalette.js.map +1 -1
  410. package/dist/editor/sidebar/ComponentTree.d.ts +8 -1
  411. package/dist/editor/sidebar/ComponentTree.js +216 -69
  412. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  413. package/dist/editor/sidebar/EditHistory.js +22 -46
  414. package/dist/editor/sidebar/EditHistory.js.map +1 -1
  415. package/dist/editor/sidebar/Favorites.js +4 -8
  416. package/dist/editor/sidebar/Favorites.js.map +1 -1
  417. package/dist/editor/sidebar/MainContentTree.js +4 -3
  418. package/dist/editor/sidebar/MainContentTree.js.map +1 -1
  419. package/dist/editor/sidebar/OperationItem.js +21 -7
  420. package/dist/editor/sidebar/OperationItem.js.map +1 -1
  421. package/dist/editor/sidebar/SidebarPanel.d.ts +3 -1
  422. package/dist/editor/sidebar/SidebarPanel.js +44 -12
  423. package/dist/editor/sidebar/SidebarPanel.js.map +1 -1
  424. package/dist/editor/sidebar/SidebarStack.d.ts +2 -1
  425. package/dist/editor/sidebar/SidebarStack.js +4 -3
  426. package/dist/editor/sidebar/SidebarStack.js.map +1 -1
  427. package/dist/editor/sidebar/Validation.js +24 -12
  428. package/dist/editor/sidebar/Validation.js.map +1 -1
  429. package/dist/editor/sidebar/Workbox.js +53 -3
  430. package/dist/editor/sidebar/Workbox.js.map +1 -1
  431. package/dist/editor/sidebar/WorkspaceRail.d.ts +0 -1
  432. package/dist/editor/sidebar/WorkspaceRail.js +56 -167
  433. package/dist/editor/sidebar/WorkspaceRail.js.map +1 -1
  434. package/dist/editor/template-wizard/TemplateStructureInlineEditor.js +3 -2
  435. package/dist/editor/template-wizard/TemplateStructureInlineEditor.js.map +1 -1
  436. package/dist/editor/tree-indicators/GutterColumns.d.ts +3 -1
  437. package/dist/editor/tree-indicators/GutterColumns.js +26 -5
  438. package/dist/editor/tree-indicators/GutterColumns.js.map +1 -1
  439. package/dist/editor/tree-indicators/GutterContext.d.ts +4 -0
  440. package/dist/editor/tree-indicators/GutterContext.js +23 -0
  441. package/dist/editor/tree-indicators/GutterContext.js.map +1 -1
  442. package/dist/editor/tree-indicators/index.d.ts +0 -1
  443. package/dist/editor/tree-indicators/index.js +0 -1
  444. package/dist/editor/tree-indicators/index.js.map +1 -1
  445. package/dist/editor/tree-indicators/types.d.ts +12 -1
  446. package/dist/editor/ui/CopyMoveTargetSelectorDialog.js +1 -1
  447. package/dist/editor/ui/CopyMoveTargetSelectorDialog.js.map +1 -1
  448. package/dist/editor/ui/Icons.js +1 -1
  449. package/dist/editor/ui/Icons.js.map +1 -1
  450. package/dist/editor/ui/ItemNameDialogNew.d.ts +2 -0
  451. package/dist/editor/ui/ItemNameDialogNew.js +33 -17
  452. package/dist/editor/ui/ItemNameDialogNew.js.map +1 -1
  453. package/dist/editor/ui/ItemSearch.js +7 -11
  454. package/dist/editor/ui/ItemSearch.js.map +1 -1
  455. package/dist/editor/ui/SimpleIconButton.js +1 -1
  456. package/dist/editor/ui/SimpleIconButton.js.map +1 -1
  457. package/dist/editor/ui/SimpleTabs.d.ts +1 -0
  458. package/dist/editor/ui/SimpleTabs.js +45 -25
  459. package/dist/editor/ui/SimpleTabs.js.map +1 -1
  460. package/dist/editor/ui/Splitter.d.ts +1 -0
  461. package/dist/editor/ui/Splitter.js +102 -86
  462. package/dist/editor/ui/Splitter.js.map +1 -1
  463. package/dist/editor/ui/TemplateSelectorDialog.js +4 -4
  464. package/dist/editor/ui/TemplateSelectorDialog.js.map +1 -1
  465. package/dist/editor/ui/TreeListSelector.d.ts +6 -1
  466. package/dist/editor/ui/TreeListSelector.js +2 -2
  467. package/dist/editor/ui/TreeListSelector.js.map +1 -1
  468. package/dist/editor/utils/keyboardNavigation.d.ts +6 -20
  469. package/dist/editor/utils/keyboardNavigation.js +48 -140
  470. package/dist/editor/utils/keyboardNavigation.js.map +1 -1
  471. package/dist/editor/utils.js +19 -9
  472. package/dist/editor/utils.js.map +1 -1
  473. package/dist/editor/views/CompareView.d.ts +3 -1
  474. package/dist/editor/views/CompareView.js +7 -5
  475. package/dist/editor/views/CompareView.js.map +1 -1
  476. package/dist/editor/views/EditView.js +1 -1
  477. package/dist/editor/views/EditView.js.map +1 -1
  478. package/dist/editor/views/EditorSlot.js +27 -34
  479. package/dist/editor/views/EditorSlot.js.map +1 -1
  480. package/dist/editor/views/ItemEditor.js +7 -3
  481. package/dist/editor/views/ItemEditor.js.map +1 -1
  482. package/dist/editor/views/MediaFolderEditView.js +1 -1
  483. package/dist/editor/views/MediaFolderEditView.js.map +1 -1
  484. package/dist/editor/views/ParheliaView.js +5 -6
  485. package/dist/editor/views/ParheliaView.js.map +1 -1
  486. package/dist/editor/views/SingleEditView.d.ts +2 -1
  487. package/dist/editor/views/SingleEditView.js +10 -8
  488. package/dist/editor/views/SingleEditView.js.map +1 -1
  489. package/dist/editor/views/editorSlotContext.js +35 -6
  490. package/dist/editor/views/editorSlotContext.js.map +1 -1
  491. package/dist/index.d.ts +16 -2
  492. package/dist/index.js +11 -0
  493. package/dist/index.js.map +1 -1
  494. package/dist/revision.d.ts +2 -2
  495. package/dist/revision.js +2 -2
  496. package/dist/setup/services/setupWizardService.d.ts +48 -14
  497. package/dist/setup/services/setupWizardService.js +52 -17
  498. package/dist/setup/services/setupWizardService.js.map +1 -1
  499. package/dist/setup/wizard/steps/AddModelDialog.js +12 -3
  500. package/dist/setup/wizard/steps/AddModelDialog.js.map +1 -1
  501. package/dist/setup/wizard/steps/ImportModelDialog.js +46 -22
  502. package/dist/setup/wizard/steps/ImportModelDialog.js.map +1 -1
  503. package/dist/splash-screen/ModernSplashScreen.js +112 -32
  504. package/dist/splash-screen/ModernSplashScreen.js.map +1 -1
  505. package/dist/splash-screen/NewPage.js +33 -50
  506. package/dist/splash-screen/NewPage.js.map +1 -1
  507. package/dist/splash-screen/OpenPage.js +2 -6
  508. package/dist/splash-screen/OpenPage.js.map +1 -1
  509. package/dist/splash-screen/ParheliaAssistantChat.js +12 -29
  510. package/dist/splash-screen/ParheliaAssistantChat.js.map +1 -1
  511. package/dist/splash-screen/ParheliaLogo.js +87 -37
  512. package/dist/splash-screen/ParheliaLogo.js.map +1 -1
  513. package/dist/splash-screen/RecentPages.js +3 -3
  514. package/dist/splash-screen/RecentPages.js.map +1 -1
  515. package/dist/tour/Tour.d.ts +2 -1
  516. package/dist/tour/Tour.js +256 -75
  517. package/dist/tour/Tour.js.map +1 -1
  518. package/dist/tour/default-tour.js +222 -96
  519. package/dist/tour/default-tour.js.map +1 -1
  520. package/dist/types.d.ts +70 -29
  521. package/package.json +19 -15
  522. package/styles.css +39 -10
  523. package/dist/editor/ComponentInfo.d.ts +0 -4
  524. package/dist/editor/ComponentInfo.js +0 -41
  525. package/dist/editor/ComponentInfo.js.map +0 -1
  526. package/dist/editor/ai/HelpTerminal.d.ts +0 -5
  527. package/dist/editor/ai/HelpTerminal.js +0 -166
  528. package/dist/editor/ai/HelpTerminal.js.map +0 -1
  529. package/dist/editor/field-types/ReactQuill.d.ts +0 -125
  530. package/dist/editor/field-types/ReactQuill.js +0 -385
  531. package/dist/editor/field-types/ReactQuill.js.map +0 -1
  532. package/dist/editor/services-server/graphQL.d.ts +0 -29
  533. package/dist/editor/services-server/graphQL.js +0 -53
  534. package/dist/editor/services-server/graphQL.js.map +0 -1
  535. package/dist/editor/settings/AllAgentsPanel.d.ts +0 -5
  536. package/dist/editor/settings/AllAgentsPanel.js +0 -139
  537. package/dist/editor/settings/AllAgentsPanel.js.map +0 -1
  538. package/dist/editor/settings/LatestFeedback.d.ts +0 -1
  539. package/dist/editor/settings/LatestFeedback.js +0 -136
  540. package/dist/editor/settings/LatestFeedback.js.map +0 -1
  541. package/dist/editor/settings/Setup.d.ts +0 -1
  542. package/dist/editor/settings/Setup.js +0 -211
  543. package/dist/editor/settings/Setup.js.map +0 -1
  544. package/dist/editor/settings/panels/DatabasePanel.d.ts +0 -6
  545. package/dist/editor/settings/panels/DatabasePanel.js +0 -50
  546. package/dist/editor/settings/panels/DatabasePanel.js.map +0 -1
  547. package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.d.ts +0 -2
  548. package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.js +0 -195
  549. package/dist/editor/settings/setup-steps/AiSetupStep/EmbeddingsModelSection.js.map +0 -1
  550. package/dist/editor/settings/setup-steps/AiSetupStep/index.d.ts +0 -2
  551. package/dist/editor/settings/setup-steps/AiSetupStep/index.js +0 -21
  552. package/dist/editor/settings/setup-steps/AiSetupStep/index.js.map +0 -1
  553. package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.d.ts +0 -1
  554. package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.js +0 -233
  555. package/dist/editor/settings/setup-steps/AiSetupStep/provider/ProviderSection.js.map +0 -1
  556. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.d.ts +0 -15
  557. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.js +0 -14
  558. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersList.js.map +0 -1
  559. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.d.ts +0 -1
  560. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.js +0 -94
  561. package/dist/editor/settings/setup-steps/AiSetupStep/required-containers/RequiredContainersSection.js.map +0 -1
  562. package/dist/editor/settings/setup-steps/AiSetupStep/types.d.ts +0 -1
  563. package/dist/editor/settings/setup-steps/AiSetupStep/types.js +0 -2
  564. package/dist/editor/settings/setup-steps/AiSetupStep/types.js.map +0 -1
  565. package/dist/editor/settings/setup-steps/AiSetupStep/utils.d.ts +0 -5
  566. package/dist/editor/settings/setup-steps/AiSetupStep/utils.js +0 -44
  567. package/dist/editor/settings/setup-steps/AiSetupStep/utils.js.map +0 -1
  568. package/dist/editor/settings/setup-steps/IndexSetupStep.d.ts +0 -2
  569. package/dist/editor/settings/setup-steps/IndexSetupStep.js +0 -36
  570. package/dist/editor/settings/setup-steps/IndexSetupStep.js.map +0 -1
  571. package/dist/editor/settings/setup-steps/SettingsSetupStep.d.ts +0 -2
  572. package/dist/editor/settings/setup-steps/SettingsSetupStep.js +0 -111
  573. package/dist/editor/settings/setup-steps/SettingsSetupStep.js.map +0 -1
  574. package/dist/editor/settings/setup-steps/SetupOverview.d.ts +0 -14
  575. package/dist/editor/settings/setup-steps/SetupOverview.js +0 -38
  576. package/dist/editor/settings/setup-steps/SetupOverview.js.map +0 -1
  577. package/dist/editor/sidebar/Debug.d.ts +0 -1
  578. package/dist/editor/sidebar/Debug.js +0 -70
  579. package/dist/editor/sidebar/Debug.js.map +0 -1
  580. package/dist/editor/sidebar/GraphQL.d.ts +0 -2
  581. package/dist/editor/sidebar/GraphQL.js +0 -234
  582. package/dist/editor/sidebar/GraphQL.js.map +0 -1
  583. package/dist/editor/sidebar/LeftToolbar.d.ts +0 -1
  584. package/dist/editor/sidebar/LeftToolbar.js +0 -12
  585. package/dist/editor/sidebar/LeftToolbar.js.map +0 -1
  586. package/dist/editor/sidebar/NavigationSidebar.d.ts +0 -4
  587. package/dist/editor/sidebar/NavigationSidebar.js +0 -254
  588. package/dist/editor/sidebar/NavigationSidebar.js.map +0 -1
  589. package/dist/editor/tree-indicators/GutterSelector.d.ts +0 -5
  590. package/dist/editor/tree-indicators/GutterSelector.js +0 -91
  591. 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,155 @@ 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 AGENT_HISTORY_LIMIT = 1000;
36
+ function mergeAgentOperationHistory(existing, incoming, limit = AGENT_HISTORY_LIMIT) {
37
+ const merged = new Map(existing.map((operation) => [operation.id, operation]));
38
+ for (const operation of incoming) {
39
+ merged.set(operation.id, operation);
40
+ }
41
+ return Array.from(merged.values())
42
+ .sort((left, right) => new Date(right.date).getTime() - new Date(left.date).getTime())
43
+ .slice(0, limit);
44
+ }
45
+ const userMessageMarkdownComponents = {
46
+ h1: (props) => (_jsx("h1", { ...props, className: "mb-2 text-sm leading-5 font-semibold text-gray-900" })),
47
+ h2: (props) => (_jsx("h2", { ...props, className: "mb-1.5 text-[13px] leading-5 font-semibold text-gray-900" })),
48
+ h3: (props) => (_jsx("h3", { ...props, className: "mb-1 text-[12px] leading-5 font-semibold text-gray-900" })),
49
+ h4: (props) => (_jsx("h4", { ...props, className: "mb-1 text-[12px] leading-5 font-medium text-gray-800" })),
50
+ p: (props) => (_jsx("p", { ...props, className: "my-1 text-[12px] leading-5 text-gray-700" })),
51
+ ul: (props) => (_jsx("ul", { ...props, className: "my-2 ml-5 list-disc space-y-1 text-[12px] leading-5 text-gray-700" })),
52
+ ol: (props) => (_jsx("ol", { ...props, className: "my-2 ml-5 list-decimal space-y-1 text-[12px] leading-5 text-gray-700" })),
53
+ li: (props) => (_jsx("li", { ...props, className: "text-[12px] leading-5 text-gray-700" })),
54
+ 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" })),
55
+ 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 })),
56
+ };
57
+ function buildPlaceholderAgentDetails(agentStub) {
58
+ const now = new Date().toISOString();
59
+ const updated = agentStub.updatedDate || now;
60
+ // AgentDetails has required fields, but some workspaces only pass an Agent stub initially.
61
+ // This placeholder keeps streaming/tool-call UI working until `getAgent()` returns full details.
62
+ return {
63
+ ...agentStub,
64
+ name: agentStub.name || "Agent",
65
+ userId: agentStub.userId || "",
66
+ updatedDate: updated,
67
+ profileName: agentStub.profileName || "",
68
+ model: agentStub.model || "",
69
+ createdDate: agentStub.createdDate || updated,
70
+ totalTokensUsed: 0,
71
+ totalInputTokens: 0,
72
+ totalOutputTokens: 0,
73
+ totalCachedInputTokens: 0,
74
+ totalInputTokenCost: 0,
75
+ totalOutputTokenCost: 0,
76
+ totalCachedInputTokenCost: 0,
77
+ totalImageCost: 0,
78
+ totalCost: 0,
79
+ currency: agentStub.currency || "USD",
80
+ messageCount: agentStub.messageCount || 0,
81
+ };
82
+ }
83
+ function normalizeDialogAgentId(value) {
84
+ return value?.trim().toLowerCase() || "";
85
+ }
86
+ function formatAllowanceSource(source) {
87
+ const normalized = source?.trim();
88
+ if (!normalized)
89
+ return null;
90
+ if (normalized === "user")
91
+ return "User granted";
92
+ if (normalized.startsWith("preconfigured:profile:"))
93
+ return "Profile";
94
+ if (normalized.startsWith("preconfigured:skill:"))
95
+ return "Skill";
96
+ if (normalized.startsWith("system:")) {
97
+ return normalized.slice("system:".length).replace(/[-_]+/g, " ").trim();
98
+ }
99
+ return normalized;
100
+ }
101
+ function formatAllowanceLabel(allowance) {
102
+ return `${allowance.operationType || "*"}${"itemPath" in allowance
103
+ ? ` ${allowance.itemPath}`
104
+ : ` ${allowance.normalizedPath}`}`;
105
+ }
106
+ function getAgentRunMessageAgentId(payload) {
107
+ const agentId = payload?.agentId;
108
+ return typeof agentId === "string" ? normalizeDialogAgentId(agentId) : null;
109
+ }
110
+ function getAgentRunMessageSeq(payload) {
111
+ const seq = payload?.seq;
112
+ return typeof seq === "number" ? seq : null;
113
+ }
114
+ function getAgentRunMessageDetail(type, payload) {
115
+ if (type === "agent:run:delta") {
116
+ return payload?.type || null;
117
+ }
118
+ if (type === "agent:run:status") {
119
+ return payload?.data?.state || payload?.data?.status || null;
120
+ }
121
+ if (type === "agent:run:error") {
122
+ return payload?.error || null;
123
+ }
124
+ if (type === "agent:run:complete") {
125
+ return payload?.finalStatus || null;
126
+ }
127
+ if (type === "agent:run:start") {
128
+ return payload?.agentName || null;
129
+ }
130
+ return null;
131
+ }
132
+ function isHeartbeatRunEventMessage(message) {
133
+ if (!message)
134
+ return false;
135
+ if (message.type === "agent:run:delta") {
136
+ const deltaType = message.payload?.type;
137
+ return String(deltaType || "").toLowerCase() === "heartbeat";
138
+ }
139
+ if (message.type === "agent:run:status") {
140
+ const state = message.payload?.data?.state || message.payload?.data?.status;
141
+ return String(state || "").toLowerCase() === "heartbeat";
142
+ }
143
+ return false;
144
+ }
145
+ function getVisibleDialogRegistry() {
146
+ const registry = globalThis.__agentDialogVisibleCallbacks;
147
+ return registry && typeof registry === "object" ? registry : {};
148
+ }
149
+ function isAgentErrorStatusValue(status) {
150
+ return status === "error";
151
+ }
25
152
  // Simple user message component
26
153
  const UserMessage = ({ message }) => {
154
+ const content = message.content || "";
155
+ const [isTriggerExpanded, setIsTriggerExpanded] = useState(false);
156
+ // Trigger-sourced prompts are prefixed by backend as "[Trigger: {name}]: {content}"
157
+ const triggerPattern = /^\[Trigger: ([^\]]+)\]:\s*(.*)$/s;
158
+ const triggerMatch = content.match(triggerPattern);
159
+ const triggerName = triggerMatch?.[1]?.trim() || "";
160
+ const triggerContent = triggerMatch?.[2] || "";
161
+ const isTriggerMessage = triggerName.length > 0;
162
+ if (isTriggerMessage) {
163
+ 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 }) }))] }) }));
164
+ }
27
165
  // Parse source agent name from content if it starts with "[From ...]:"
28
166
  // Backend formats messages from other agents as "[From {sourceAgentName}]: {content}"
29
- const content = message.content || "";
30
167
  const fromPattern = /^\[From ([^\]]+)\]:\s*(.*)$/s;
31
168
  const match = content.match(fromPattern);
32
169
  let sourceAgentName;
@@ -51,7 +188,10 @@ const UserMessage = ({ message }) => {
51
188
  message.sourceAgent?.name;
52
189
  }
53
190
  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 })] })] }));
191
+ 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 }) })] })] }));
192
+ };
193
+ const HeartbeatMessage = ({ message }) => {
194
+ 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
195
  };
56
196
  // Helper to extract todos from potentially incomplete JSON during streaming
57
197
  const extractPartialTodos = (jsonText) => {
@@ -285,14 +425,6 @@ const extractTodosFromMessages = (messages) => {
285
425
  todoMap.set(key, todo);
286
426
  }
287
427
  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
428
  return result;
297
429
  };
298
430
  // TodoListPanel component
@@ -300,12 +432,9 @@ const TodoListPanel = ({ messages, agentMetadata, }) => {
300
432
  const [isExpanded, setIsExpanded] = useState(true);
301
433
  // First try to get todos from agent metadata (real-time updates)
302
434
  // Server sends additionalData.todoList directly via contextChanged status
303
- // Also check top-level todoList for backward compatibility with stored contexts
304
435
  const metadataTodos = (() => {
305
436
  try {
306
- // Check both additionalData.todoList and top-level todoList (from [JsonExtensionData] serialization)
307
- const todoList = agentMetadata?.additionalData?.todoList ||
308
- agentMetadata?.todoList;
437
+ const todoList = agentMetadata?.additionalData?.todoList;
309
438
  if (todoList?.items && Array.isArray(todoList.items)) {
310
439
  const rawItems = todoList.items
311
440
  .map((item, idx) => ({
@@ -433,6 +562,17 @@ const groupConsecutiveMessages = (agentMessages) => {
433
562
  // Add user message
434
563
  groups.push({ type: "user", messages: [message] });
435
564
  }
565
+ else if (message.messageType === "heartbeat" ||
566
+ message.role === "system") {
567
+ if (currentAssistantGroup.length > 0) {
568
+ groups.push({
569
+ type: "assistant-group",
570
+ messages: currentAssistantGroup,
571
+ });
572
+ currentAssistantGroup = [];
573
+ }
574
+ groups.push({ type: "heartbeat", messages: [message] });
575
+ }
436
576
  else if (message.role === "assistant") {
437
577
  // Add to current assistant group
438
578
  currentAssistantGroup.push(message);
@@ -500,6 +640,7 @@ const calculateTotalTokens = (messages) => {
500
640
  outputCost: acc.outputCost + (message.outputTokenCost || 0),
501
641
  cachedCost: acc.cachedCost + (message.cachedInputTokenCost || 0),
502
642
  cacheWriteCost: acc.cacheWriteCost,
643
+ imageCost: acc.imageCost,
503
644
  totalCost: acc.totalCost + (message.totalCost || 0),
504
645
  };
505
646
  }, {
@@ -511,6 +652,7 @@ const calculateTotalTokens = (messages) => {
511
652
  outputCost: 0,
512
653
  cachedCost: 0,
513
654
  cacheWriteCost: 0,
655
+ imageCost: 0,
514
656
  totalCost: 0,
515
657
  });
516
658
  return totals;
@@ -521,6 +663,84 @@ const getOperationsForMessageGroup = (messages, agentOperations) => {
521
663
  const matched = agentOperations.filter((op) => op.toolCallId && toolCallIds.has(op.toolCallId));
522
664
  return matched;
523
665
  };
666
+ const stringifyToolField = (value) => {
667
+ if (value === undefined || value === null)
668
+ return undefined;
669
+ if (typeof value === "string") {
670
+ return value.trim().length > 0 ? value : undefined;
671
+ }
672
+ try {
673
+ return JSON.stringify(value);
674
+ }
675
+ catch {
676
+ return String(value);
677
+ }
678
+ };
679
+ const parseToolResultValue = (value) => {
680
+ if (value === undefined || value === null) {
681
+ return undefined;
682
+ }
683
+ if (typeof value === "object") {
684
+ return value;
685
+ }
686
+ if (typeof value !== "string") {
687
+ return String(value);
688
+ }
689
+ const trimmed = value.trim();
690
+ if (!trimmed) {
691
+ return undefined;
692
+ }
693
+ try {
694
+ let parsed = JSON.parse(trimmed);
695
+ if (typeof parsed === "string" &&
696
+ (parsed.startsWith("{") || parsed.startsWith("["))) {
697
+ parsed = JSON.parse(parsed);
698
+ }
699
+ if (parsed && typeof parsed === "object") {
700
+ return parsed;
701
+ }
702
+ }
703
+ catch {
704
+ // Fall back to the original string when the payload is plain text.
705
+ }
706
+ return value;
707
+ };
708
+ const getFirstToolCallEnvelope = (data) => {
709
+ if (!data || typeof data !== "object")
710
+ return undefined;
711
+ const direct = data.toolCall || data.tool_call;
712
+ if (direct)
713
+ return direct;
714
+ const arrayCandidates = [data.tool_calls, data.toolCalls];
715
+ for (const candidate of arrayCandidates) {
716
+ if (Array.isArray(candidate) && candidate.length > 0) {
717
+ return candidate[0];
718
+ }
719
+ }
720
+ return undefined;
721
+ };
722
+ const extractToolCallFields = (data) => {
723
+ const envelope = getFirstToolCallEnvelope(data);
724
+ const functionPayload = data?.function || envelope?.function;
725
+ const functionName = data?.functionName ||
726
+ data?.name ||
727
+ functionPayload?.name ||
728
+ envelope?.functionName ||
729
+ envelope?.name ||
730
+ "unknown";
731
+ const toolCallId = data?.toolCallId || data?.id || envelope?.id;
732
+ const functionArguments = stringifyToolField(data?.functionArguments) ||
733
+ stringifyToolField(data?.arguments) ||
734
+ stringifyToolField(functionPayload?.arguments) ||
735
+ stringifyToolField(envelope?.functionArguments) ||
736
+ stringifyToolField(envelope?.arguments) ||
737
+ "{}";
738
+ return {
739
+ toolCallId,
740
+ functionName,
741
+ functionArguments,
742
+ };
743
+ };
524
744
  // Convert agent messages to AI terminal format for a response group
525
745
  const convertAgentMessagesToAiFormat = (agentMessages) => {
526
746
  return agentMessages.map((agentMessage) => {
@@ -541,28 +761,39 @@ const convertAgentMessagesToAiFormat = (agentMessages) => {
541
761
  role: agentMessage.role,
542
762
  createdDate: agentMessage.createdDate,
543
763
  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
- }))
764
+ ? agentMessage.toolCalls.map((toolCall) => {
765
+ const isPruned = !!toolCall.isPruned ||
766
+ /^PRUNED$/i.test(toolCall.functionError || "");
767
+ const displayResult = parseToolResultValue(toolCall.functionResultRichContent) ?? toolCall.functionResult;
768
+ return {
769
+ id: toolCall.toolCallId,
770
+ displayName: toolCall.functionName,
771
+ function: {
772
+ name: toolCall.functionName,
773
+ arguments: toolCall.functionArguments,
774
+ result: displayResult,
775
+ error: toolCall.functionError,
776
+ },
777
+ // Pass through approval info if present on the tool call
778
+ requiresApproval: toolCall.requiresApproval,
779
+ // Pass through prune metadata so the terminal can render a neutral state
780
+ isPruned,
781
+ prunedAt: toolCall.prunedAt,
782
+ // Pass through isCompleted so ToolCallDisplay knows when to hide spinner
783
+ isCompleted: toolCall.isCompleted,
784
+ // Tool call is streaming if message is not completed and tool call has no result yet
785
+ isStreaming: !agentMessage.isCompleted &&
786
+ !toolCall.isCompleted &&
787
+ !displayResult &&
788
+ !toolCall.functionError &&
789
+ !isPruned,
790
+ // Pass through message IDs for approval/rejection events
791
+ messageId: toolCall.messageId,
792
+ dbMessageId: toolCall.dbMessageId,
793
+ responseTimeMs: toolCall.responseTimeMs,
794
+ createdDate: toolCall.createdDate,
795
+ };
796
+ })
566
797
  : [],
567
798
  };
568
799
  if (agentMessage.toolCallId) {
@@ -574,10 +805,10 @@ const convertAgentMessagesToAiFormat = (agentMessages) => {
574
805
  // interface AgentTerminalProps {
575
806
  // agentStub: Agent;
576
807
  // }
577
- export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive = true, compact = false, hideContext = false, hideBottomControls = false, hideGreeting = false, simpleMode = false, className, initialPrompt, onAgentUpdate, }) {
808
+ 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
809
  const editContext = useEditContext();
579
810
  const fieldsContext = useFieldsEditContext();
580
- const [agent, setAgent] = useState(undefined);
811
+ const [agent, setAgent] = useState(() => buildPlaceholderAgentDetails(agentStub));
581
812
  const [messages, setMessages] = useState([]);
582
813
  const [agentOperations, setAgentOperations] = useState([]);
583
814
  const [prompt, setPrompt] = useState("");
@@ -588,6 +819,35 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
588
819
  const [activePlaceholderInput, setActivePlaceholderInput] = useState(null);
589
820
  const [allPlaceholdersFilled, setAllPlaceholdersFilled] = useState(false);
590
821
  const [agentMetadata, setAgentMetadata] = useState(null);
822
+ const [isBrowserClaimMutationPending, setIsBrowserClaimMutationPending] = useState(false);
823
+ const [pendingBrowserCaptureDialogType, setPendingBrowserCaptureDialogType] = useState(null);
824
+ // Ensure we always have an agent object for streaming handlers, even before `getAgent()` resolves.
825
+ // This prevents early tool calls (e.g., ask-questionnaire) from being dropped in compact/workspace UIs.
826
+ useEffect(() => {
827
+ setAgent((prev) => {
828
+ if (prev?.id === agentStub.id)
829
+ return prev;
830
+ return buildPlaceholderAgentDetails(agentStub);
831
+ });
832
+ }, [agentStub.id]);
833
+ const observedMessageIdsRef = useRef(new Set());
834
+ useEffect(() => {
835
+ observedMessageIdsRef.current = new Set();
836
+ }, [agentStub.id]);
837
+ useEffect(() => {
838
+ if (!onMessage)
839
+ return;
840
+ for (const message of messages) {
841
+ if (!message?.id)
842
+ continue;
843
+ if (!message.isCompleted)
844
+ continue;
845
+ if (observedMessageIdsRef.current.has(message.id))
846
+ continue;
847
+ observedMessageIdsRef.current.add(message.id);
848
+ onMessage(message);
849
+ }
850
+ }, [messages, onMessage]);
591
851
  // Generate a stable clientSessionId per component instance for stream deduplication
592
852
  const clientSessionIdRef = useRef(null);
593
853
  if (!clientSessionIdRef.current) {
@@ -599,6 +859,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
599
859
  const [isListening, setIsListening] = useState(false);
600
860
  const recognitionRef = useRef(null);
601
861
  const prevPlaceholderRef = useRef(null);
862
+ const promptBeforeVoiceRef = useRef("");
602
863
  // Voice button press-and-hold tracking
603
864
  const voicePressStartRef = useRef(null);
604
865
  const voiceHoldTimerRef = useRef(null);
@@ -608,6 +869,70 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
608
869
  const [showCompressionPopover, setShowCompressionPopover] = useState(false);
609
870
  // Agent inline dialog state (for component type selector, etc.)
610
871
  const [activeInlineDialog, setActiveInlineDialog] = useState(null);
872
+ const dialogTerminalInstanceIdRef = useRef("");
873
+ if (!dialogTerminalInstanceIdRef.current) {
874
+ dialogTerminalInstanceIdRef.current = crypto.randomUUID();
875
+ }
876
+ const activeInlineDialogRef = useRef(activeInlineDialog);
877
+ const isQuestionnaireDialogOpen = activeInlineDialog?.request.dialogType === "questionnaire";
878
+ const orphanTimeoutRef = useRef(null);
879
+ useEffect(() => {
880
+ activeInlineDialogRef.current = activeInlineDialog;
881
+ const visibleRegistry = { ...getVisibleDialogRegistry() };
882
+ const callbackId = activeInlineDialog?.request.callbackId || null;
883
+ const terminalInstanceId = dialogTerminalInstanceIdRef.current;
884
+ const agentKeys = [
885
+ normalizeDialogAgentId(agentStubIdRefForDialogs.current),
886
+ normalizeDialogAgentId(agentIdRefForDialogs.current),
887
+ ].filter(Boolean);
888
+ agentKeys.forEach((key) => {
889
+ if (callbackId) {
890
+ visibleRegistry[key] = {
891
+ callbackId,
892
+ terminalInstanceId,
893
+ };
894
+ }
895
+ else {
896
+ delete visibleRegistry[key];
897
+ }
898
+ });
899
+ globalThis.__agentDialogVisibleCallbacks = visibleRegistry;
900
+ }, [activeInlineDialog]);
901
+ useEffect(() => {
902
+ onQuestionnaireOpenChange?.(isQuestionnaireDialogOpen);
903
+ }, [isQuestionnaireDialogOpen, onQuestionnaireOpenChange]);
904
+ useLayoutEffect(() => {
905
+ if (displayMode !== "summary" || !isQuestionnaireDialogOpen) {
906
+ return;
907
+ }
908
+ const scrollSummaryTerminalToTop = () => {
909
+ const container = messagesContainerRef.current;
910
+ if (!container) {
911
+ return;
912
+ }
913
+ container.scrollTop = 0;
914
+ };
915
+ scrollSummaryTerminalToTop();
916
+ const frame1 = requestAnimationFrame(() => {
917
+ scrollSummaryTerminalToTop();
918
+ });
919
+ let frame3 = 0;
920
+ const frame2 = requestAnimationFrame(() => {
921
+ frame3 = requestAnimationFrame(() => {
922
+ scrollSummaryTerminalToTop();
923
+ });
924
+ });
925
+ return () => {
926
+ cancelAnimationFrame(frame1);
927
+ cancelAnimationFrame(frame2);
928
+ cancelAnimationFrame(frame3);
929
+ };
930
+ }, [displayMode, isQuestionnaireDialogOpen]);
931
+ useEffect(() => {
932
+ return () => {
933
+ onQuestionnaireOpenChange?.(false);
934
+ };
935
+ }, [onQuestionnaireOpenChange]);
611
936
  const isWaitingRef = useRef(false);
612
937
  useEffect(() => {
613
938
  isWaitingRef.current = isWaitingForResponse;
@@ -616,12 +941,96 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
616
941
  // while a stop operation is in progress. This prevents race conditions where
617
942
  // messages arriving during the stop process could flip the UI back to "executing".
618
943
  const isStoppingRef = useRef(false);
944
+ const [isStopGuardActive, setIsStopGuardActive] = useState(false);
945
+ const stopGuardReleaseTimeoutRef = useRef(null);
946
+ function clearStopGuard() {
947
+ isStoppingRef.current = false;
948
+ setIsStopGuardActive(false);
949
+ if (stopGuardReleaseTimeoutRef.current) {
950
+ clearTimeout(stopGuardReleaseTimeoutRef.current);
951
+ stopGuardReleaseTimeoutRef.current = null;
952
+ }
953
+ }
954
+ function armStopGuard() {
955
+ isStoppingRef.current = true;
956
+ setIsStopGuardActive(true);
957
+ if (stopGuardReleaseTimeoutRef.current) {
958
+ clearTimeout(stopGuardReleaseTimeoutRef.current);
959
+ }
960
+ stopGuardReleaseTimeoutRef.current = setTimeout(() => {
961
+ stopGuardReleaseTimeoutRef.current = null;
962
+ isStoppingRef.current = false;
963
+ setIsStopGuardActive(false);
964
+ }, 30000);
965
+ }
619
966
  // Server-driven state: true when agent is actively processing (set by WebSocket messages)
620
967
  const [isAgentThinking, setIsAgentThinking] = useState(false);
968
+ useEffect(() => {
969
+ return () => {
970
+ if (stopGuardReleaseTimeoutRef.current) {
971
+ clearTimeout(stopGuardReleaseTimeoutRef.current);
972
+ stopGuardReleaseTimeoutRef.current = null;
973
+ }
974
+ };
975
+ }, []);
976
+ useEffect(() => {
977
+ if (!initialMetadata)
978
+ return;
979
+ setAgentMetadata((prev) => sanitizeAgentMetadata({
980
+ ...(prev || {}),
981
+ ...initialMetadata,
982
+ additionalData: {
983
+ ...(prev?.additionalData || {}),
984
+ ...(initialMetadata?.additionalData || {}),
985
+ },
986
+ }));
987
+ }, [initialMetadata]);
621
988
  const hasActiveStreaming = useCallback(() => {
622
989
  const current = messagesRef.current || [];
623
990
  return current.some((m) => !m.isCompleted && m.messageType === "streaming");
624
991
  }, []);
992
+ const currentAgentId = agent?.id || agentStub.id;
993
+ const recentAgentRunEvents = useMemo(() => {
994
+ const normalizedAgentId = normalizeDialogAgentId(currentAgentId);
995
+ if (!normalizedAgentId) {
996
+ return [];
997
+ }
998
+ if (!editContext) {
999
+ return [];
1000
+ }
1001
+ return (editContext.webSocketMessages || [])
1002
+ .filter((message) => {
1003
+ if (!message?.type?.startsWith("agent:run:")) {
1004
+ return false;
1005
+ }
1006
+ if (isHeartbeatRunEventMessage(message)) {
1007
+ return false;
1008
+ }
1009
+ return getAgentRunMessageAgentId(message.payload) === normalizedAgentId;
1010
+ })
1011
+ .slice(-8)
1012
+ .map((message) => ({
1013
+ timestamp: message.timestamp,
1014
+ type: message.type,
1015
+ seq: getAgentRunMessageSeq(message.payload),
1016
+ detail: getAgentRunMessageDetail(message.type, message.payload),
1017
+ }));
1018
+ }, [currentAgentId, editContext?.webSocketMessages]);
1019
+ const appendToolUiEvent = useCallback((type, detail, seq) => {
1020
+ const timestamp = new Date().toISOString();
1021
+ setRecentToolUiEvents((prev) => {
1022
+ const next = [
1023
+ ...prev,
1024
+ {
1025
+ timestamp,
1026
+ type,
1027
+ detail: detail || null,
1028
+ seq: seq ?? null,
1029
+ },
1030
+ ];
1031
+ return next.slice(-40);
1032
+ });
1033
+ }, []);
625
1034
  // Collect all pending tool calls for batch approval functionality
626
1035
  const allPendingApprovals = useMemo(() => {
627
1036
  const pending = [];
@@ -663,41 +1072,82 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
663
1072
  }, [allPendingApprovals]);
664
1073
  // Handle mode switch to autonomous
665
1074
  const handleSwitchToAutonomous = useCallback(() => {
666
- setMode("autonomous");
667
- }, []);
1075
+ const nextMode = "autonomous";
1076
+ setMode(nextMode);
1077
+ setAgentMetadata((prev) => ({
1078
+ ...(prev ?? {}),
1079
+ mode: nextMode,
1080
+ }));
1081
+ setAgent((prev) => {
1082
+ if (!prev)
1083
+ return prev;
1084
+ return {
1085
+ ...prev,
1086
+ mode: nextMode,
1087
+ };
1088
+ });
1089
+ }, [setAgent, setAgentMetadata]);
668
1090
  const [resolvedPageName, setResolvedPageName] = useState(undefined);
669
1091
  const [resolvedComponentName, setResolvedComponentName] = useState(undefined);
670
1092
  const [resolvedFieldName, setResolvedFieldName] = useState(undefined);
671
1093
  const [promptHistory, setPromptHistory] = useState(() => {
672
- if (typeof window !== "undefined") {
673
- return JSON.parse(localStorage.getItem("editor.agent.promptHistory") || "[]");
674
- }
675
- return [];
1094
+ return (localStorageService.getItem("editor.agent.promptHistory") || []);
676
1095
  });
677
1096
  const [currentHistoryIndex, setCurrentHistoryIndex] = useState(-1);
678
1097
  const [showPredefined, setShowPredefined] = useState(false);
679
1098
  const [activeProfile, setActiveProfile] = useState(undefined);
680
1099
  const [selectedModelId, setSelectedModelId] = useState(undefined);
1100
+ const normalizeAgentMode = (value) => {
1101
+ if (value === "autonomous" ||
1102
+ value === "read-only" ||
1103
+ value === "supervised") {
1104
+ return value;
1105
+ }
1106
+ return null;
1107
+ };
681
1108
  const [mode, setMode] = useState("supervised");
682
1109
  const [queuedPrompts, setQueuedPrompts] = useState([]);
683
- const isMobile = useMediaQuery("(max-width: 768px)");
1110
+ const [expandedQueuedTriggerIds, setExpandedQueuedTriggerIds] = useState({});
684
1111
  const [contextPanelsActiveTab, setContextPanelsActiveTab] = useState(0);
685
1112
  const [hiddenContextPanelTabIds, setHiddenContextPanelTabIds] = useState(new Set());
686
1113
  const [showCostAndAgent, setShowCostAndAgent] = useState(false);
1114
+ const [showAgentSettings, setShowAgentSettings] = useState(false);
1115
+ const [showSkillPicker, setShowSkillPicker] = useState(false);
1116
+ const [availableSkills, setAvailableSkills] = useState([]);
1117
+ const [skillRootIds, setSkillRootIds] = useState([]);
1118
+ const [selectableTemplateIds, setSelectableTemplateIds] = useState([]);
1119
+ const [skillsLoading, setSkillsLoading] = useState(false);
1120
+ const [skillsError, setSkillsError] = useState(null);
1121
+ const [skillActionError, setSkillActionError] = useState(null);
1122
+ const [triggerSubscriptions, setTriggerSubscriptions] = useState([]);
1123
+ const [availableTools, setAvailableTools] = useState([]);
1124
+ const [operationAllowances, setOperationAllowances] = useState({
1125
+ sitecore: [],
1126
+ filesystem: [],
1127
+ });
1128
+ const [triggerSubscriptionsLoading, setTriggerSubscriptionsLoading] = useState(false);
1129
+ const [availableToolsLoading, setAvailableToolsLoading] = useState(false);
1130
+ const [operationAllowancesLoading, setOperationAllowancesLoading] = useState(false);
1131
+ const [triggerSubscriptionsError, setTriggerSubscriptionsError] = useState(null);
1132
+ const [availableToolsError, setAvailableToolsError] = useState(null);
1133
+ const [operationAllowancesError, setOperationAllowancesError] = useState(null);
1134
+ const [toolsSectionExpanded, setToolsSectionExpanded] = useState(false);
1135
+ const [allowancesSectionExpanded, setAllowancesSectionExpanded] = useState(false);
1136
+ const [subscribedTriggersSectionExpanded, setSubscribedTriggersSectionExpanded,] = useState(false);
1137
+ const isPersistedAgent = !!agent?.userId;
1138
+ const isLocalOnlyDraftAgent = agent?.status === "new" && !isPersistedAgent;
687
1139
  const hasSpawnedAgents = useMemo(() => {
688
1140
  if (!agentMetadata)
689
1141
  return false;
690
- const childAgents = agentMetadata?.ChildAgents ||
691
- agentMetadata?.childAgents;
1142
+ const childAgents = agentMetadata?.childAgents;
692
1143
  if (!Array.isArray(childAgents) || childAgents.length === 0)
693
1144
  return false;
694
- return childAgents.some((a) => a != null && typeof a === "object" && (a.AgentId || a.agentId));
1145
+ return childAgents.some((a) => a != null && typeof a === "object" && a.agentId);
695
1146
  }, [agentMetadata]);
696
1147
  const hasTodoContent = useMemo(() => {
697
1148
  const metadataTodos = (() => {
698
1149
  try {
699
- const todoList = agentMetadata?.additionalData?.todoList ||
700
- agentMetadata?.todoList;
1150
+ const todoList = agentMetadata?.additionalData?.todoList;
701
1151
  if (todoList?.items && Array.isArray(todoList.items)) {
702
1152
  const raw = todoList.items.filter((item) => item?.title || item?.text || item?.label || item?.task);
703
1153
  return raw.length > 0;
@@ -719,7 +1169,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
719
1169
  return isUpdating;
720
1170
  }, [agentMetadata, messages]);
721
1171
  const prevAgentIdRef = useRef(undefined);
722
- const [hasHistoryContent, setHasHistoryContent] = useState(false);
1172
+ const hasHistoryContent = agentOperations.length > 0;
723
1173
  useEffect(() => {
724
1174
  const currentId = agent?.id;
725
1175
  if (prevAgentIdRef.current !== currentId) {
@@ -735,47 +1185,37 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
735
1185
  }
736
1186
  }, [agent?.id]);
737
1187
  useEffect(() => {
738
- if (!agent?.id) {
739
- setHasHistoryContent(false);
740
- return;
741
- }
742
- setHasHistoryContent(false);
743
- let cancelled = false;
744
- getAgentHistory(agent.id, 10)
745
- .then((result) => {
746
- if (cancelled)
747
- return;
748
- if (result.type === "success" && result.data && result.data.length > 0) {
749
- setHasHistoryContent(true);
1188
+ let active = true;
1189
+ const loadSkills = async () => {
1190
+ try {
1191
+ setSkillsLoading(true);
1192
+ setSkillsError(null);
1193
+ const catalog = await getAgentSkillCatalog(false);
1194
+ if (active) {
1195
+ setAvailableSkills(catalog.skills.filter((s) => !s.disabled));
1196
+ setSkillRootIds(catalog.rootIds);
1197
+ setSelectableTemplateIds(catalog.selectableTemplateIds);
1198
+ }
750
1199
  }
751
- else {
752
- setHasHistoryContent(false);
1200
+ catch (e) {
1201
+ if (active) {
1202
+ setSkillsError(e?.message || "Failed to load skills");
1203
+ setAvailableSkills([]);
1204
+ setSkillRootIds([]);
1205
+ setSelectableTemplateIds([]);
1206
+ }
753
1207
  }
754
- })
755
- .catch(() => {
756
- if (!cancelled)
757
- setHasHistoryContent(false);
758
- });
1208
+ finally {
1209
+ if (active) {
1210
+ setSkillsLoading(false);
1211
+ }
1212
+ }
1213
+ };
1214
+ void loadSkills();
759
1215
  return () => {
760
- cancelled = true;
1216
+ active = false;
761
1217
  };
762
- }, [agent?.id]);
763
- const agentIdForHistoryRef = useRef(agent?.id);
764
- agentIdForHistoryRef.current = agent?.id;
765
- useEffect(() => {
766
- if (!agent?.id || !editContext?.addSocketMessageListener)
767
- return;
768
- const unsubscribe = editContext.addSocketMessageListener((message) => {
769
- if (message.type !== "edit-operation")
770
- return;
771
- const op = message.payload;
772
- const operationAgentId = op.agentId || op.user?.agentId;
773
- if (operationAgentId === agentIdForHistoryRef.current) {
774
- setHasHistoryContent(true);
775
- }
776
- });
777
- return () => unsubscribe();
778
- }, [agent?.id, editContext?.addSocketMessageListener]);
1218
+ }, []);
779
1219
  const modeOptions = useMemo(() => [
780
1220
  {
781
1221
  value: "supervised",
@@ -810,6 +1250,239 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
810
1250
  value: m.id,
811
1251
  label: m.name,
812
1252
  })) || []).sort((a, b) => a.label.localeCompare(b.label)), [activeProfile]);
1253
+ const metadataSelectedSkillIds = useMemo(() => {
1254
+ const rawSkillIds = agentMetadata?.additionalData?.skillIds ?? [];
1255
+ if (!Array.isArray(rawSkillIds)) {
1256
+ return [];
1257
+ }
1258
+ return rawSkillIds
1259
+ .map((x) => String(x || "").trim())
1260
+ .filter((x) => x.length > 0);
1261
+ }, [agentMetadata]);
1262
+ const backendAssignedSkillIds = useMemo(() => {
1263
+ const rawSkillIds = agent?.assignedSkillIds ?? [];
1264
+ if (!Array.isArray(rawSkillIds)) {
1265
+ return [];
1266
+ }
1267
+ return rawSkillIds
1268
+ .map((x) => String(x || "").trim())
1269
+ .filter((x) => x.length > 0);
1270
+ }, [agent]);
1271
+ const preloadedSkillIds = useMemo(() => {
1272
+ const rawSkillIds = activeProfile?.preloadSkills ?? [];
1273
+ if (!Array.isArray(rawSkillIds)) {
1274
+ return [];
1275
+ }
1276
+ return rawSkillIds
1277
+ .map((skill) => String(skill?.id || "").trim())
1278
+ .filter((id) => id.length > 0);
1279
+ }, [activeProfile?.preloadSkills]);
1280
+ const autoAssignedSkillIds = useMemo(() => {
1281
+ const preloadedIdSet = new Set(preloadedSkillIds.map((id) => id.toLowerCase()));
1282
+ const all = isLocalOnlyDraftAgent
1283
+ ? preloadedSkillIds
1284
+ : backendAssignedSkillIds.filter((id) => preloadedIdSet.has(id.toLowerCase()));
1285
+ const seen = new Set();
1286
+ const unique = [];
1287
+ for (const id of all) {
1288
+ const key = id.toLowerCase();
1289
+ if (seen.has(key))
1290
+ continue;
1291
+ seen.add(key);
1292
+ unique.push(id);
1293
+ }
1294
+ return unique;
1295
+ }, [backendAssignedSkillIds, isLocalOnlyDraftAgent, preloadedSkillIds]);
1296
+ const selectedSkillIds = useMemo(() => {
1297
+ const all = [
1298
+ ...autoAssignedSkillIds,
1299
+ ...backendAssignedSkillIds,
1300
+ ...metadataSelectedSkillIds,
1301
+ ];
1302
+ const seen = new Set();
1303
+ const unique = [];
1304
+ for (const id of all) {
1305
+ const key = id.toLowerCase();
1306
+ if (seen.has(key))
1307
+ continue;
1308
+ seen.add(key);
1309
+ unique.push(id);
1310
+ }
1311
+ return unique;
1312
+ }, [autoAssignedSkillIds, backendAssignedSkillIds, metadataSelectedSkillIds]);
1313
+ useEffect(() => {
1314
+ let active = true;
1315
+ if (!showAgentSettings) {
1316
+ return () => {
1317
+ active = false;
1318
+ };
1319
+ }
1320
+ if (!agent?.id || isLocalOnlyDraftAgent) {
1321
+ setTriggerSubscriptions([]);
1322
+ setTriggerSubscriptionsLoading(false);
1323
+ setTriggerSubscriptionsError(null);
1324
+ setAvailableTools([]);
1325
+ setAvailableToolsLoading(false);
1326
+ setAvailableToolsError(null);
1327
+ setOperationAllowances({ sitecore: [], filesystem: [] });
1328
+ setOperationAllowancesLoading(false);
1329
+ setOperationAllowancesError(null);
1330
+ return () => {
1331
+ active = false;
1332
+ };
1333
+ }
1334
+ const loadTriggerSubscriptions = async () => {
1335
+ try {
1336
+ setTriggerSubscriptionsLoading(true);
1337
+ setTriggerSubscriptionsError(null);
1338
+ const subscriptions = await getAgentTriggerSubscriptions(agent.id);
1339
+ if (active) {
1340
+ setTriggerSubscriptions(subscriptions);
1341
+ }
1342
+ }
1343
+ catch (e) {
1344
+ if (active) {
1345
+ setTriggerSubscriptionsError(e?.message || "Failed to load trigger subscriptions");
1346
+ }
1347
+ }
1348
+ finally {
1349
+ if (active) {
1350
+ setTriggerSubscriptionsLoading(false);
1351
+ }
1352
+ }
1353
+ };
1354
+ const loadOperationAllowances = async () => {
1355
+ try {
1356
+ setOperationAllowancesLoading(true);
1357
+ setOperationAllowancesError(null);
1358
+ const allowances = await getAgentOperationAllowances(agent.id);
1359
+ if (active) {
1360
+ setOperationAllowances(allowances);
1361
+ }
1362
+ }
1363
+ catch (e) {
1364
+ if (active) {
1365
+ setOperationAllowancesError(e?.message || "Failed to load allowances");
1366
+ }
1367
+ }
1368
+ finally {
1369
+ if (active) {
1370
+ setOperationAllowancesLoading(false);
1371
+ }
1372
+ }
1373
+ };
1374
+ const loadAvailableTools = async () => {
1375
+ try {
1376
+ setAvailableToolsLoading(true);
1377
+ setAvailableToolsError(null);
1378
+ const tools = await getAgentAvailableTools(agent.id);
1379
+ if (active) {
1380
+ setAvailableTools(tools);
1381
+ }
1382
+ }
1383
+ catch (e) {
1384
+ if (active) {
1385
+ setAvailableToolsError(e?.message || "Failed to load available tools");
1386
+ }
1387
+ }
1388
+ finally {
1389
+ if (active) {
1390
+ setAvailableToolsLoading(false);
1391
+ }
1392
+ }
1393
+ };
1394
+ void loadTriggerSubscriptions();
1395
+ void loadAvailableTools();
1396
+ void loadOperationAllowances();
1397
+ return () => {
1398
+ active = false;
1399
+ };
1400
+ }, [
1401
+ showAgentSettings,
1402
+ agent?.id,
1403
+ agent?.status,
1404
+ agent?.userId,
1405
+ agent?.profileId,
1406
+ mode,
1407
+ selectedSkillIds,
1408
+ ]);
1409
+ const activeTriggerSubscriptions = useMemo(() => triggerSubscriptions.filter((sub) => sub.isActive), [triggerSubscriptions]);
1410
+ const allowanceGroups = useMemo(() => [
1411
+ {
1412
+ key: "sitecore",
1413
+ label: "Sitecore",
1414
+ rows: operationAllowances.sitecore,
1415
+ },
1416
+ {
1417
+ key: "filesystem",
1418
+ label: "Filesystem",
1419
+ rows: operationAllowances.filesystem,
1420
+ },
1421
+ ], [operationAllowances]);
1422
+ const hasAnyAllowances = useMemo(() => operationAllowances.sitecore.length > 0 ||
1423
+ operationAllowances.filesystem.length > 0, [operationAllowances]);
1424
+ const allowancesTotalCount = useMemo(() => operationAllowances.sitecore.length +
1425
+ operationAllowances.filesystem.length, [operationAllowances]);
1426
+ const listedProfileSkillIdSet = useMemo(() => {
1427
+ const ids = [
1428
+ ...(activeProfile?.availableSkills ?? []),
1429
+ ...(activeProfile?.allowedSkills ?? []),
1430
+ ]
1431
+ .map((skill) => String(skill?.id || "").toLowerCase())
1432
+ .filter((id) => id.length > 0);
1433
+ return new Set(ids);
1434
+ }, [activeProfile?.availableSkills, activeProfile?.allowedSkills]);
1435
+ const profileFilteredSkills = useMemo(() => {
1436
+ if (listedProfileSkillIdSet.size === 0) {
1437
+ return [];
1438
+ }
1439
+ return availableSkills.filter((skill) => listedProfileSkillIdSet.has(skill.id.toLowerCase()));
1440
+ }, [availableSkills, listedProfileSkillIdSet]);
1441
+ const profileFilteredSkillIdSet = useMemo(() => new Set(profileFilteredSkills.map((s) => s.id.toLowerCase())), [profileFilteredSkills]);
1442
+ const manuallyAssignableSkillIdSet = useMemo(() => new Set((activeProfile?.allowedSkills ?? [])
1443
+ .map((skill) => String(skill?.id || "").toLowerCase())
1444
+ .filter((id) => id.length > 0)), [activeProfile?.allowedSkills]);
1445
+ const selectableTemplateIdSet = useMemo(() => new Set(selectableTemplateIds.map((id) => id.toLowerCase())), [selectableTemplateIds]);
1446
+ const selectedSkills = useMemo(() => selectedSkillIds
1447
+ .map((id) => availableSkills.find((s) => s.id.toLowerCase() === id.toLowerCase()))
1448
+ .filter((s) => !!s), [availableSkills, selectedSkillIds]);
1449
+ const selectedSkillSet = useMemo(() => new Set(selectedSkillIds.map((id) => id.toLowerCase())), [selectedSkillIds]);
1450
+ const autoAssignedSkillSet = useMemo(() => new Set(autoAssignedSkillIds.map((id) => id.toLowerCase())), [autoAssignedSkillIds]);
1451
+ const previewAvailableTools = useMemo(() => {
1452
+ const baseToolNames = mode === "read-only"
1453
+ ? (activeProfile?.readOnlyToolNames ?? [])
1454
+ : (activeProfile?.allowedToolNames ?? []);
1455
+ const toolNames = new Set();
1456
+ for (const toolName of baseToolNames) {
1457
+ const normalized = String(toolName || "").trim();
1458
+ if (normalized) {
1459
+ toolNames.add(normalized);
1460
+ }
1461
+ }
1462
+ for (const skill of selectedSkills) {
1463
+ for (const toolName of skill.additionalAllowedTools ?? []) {
1464
+ const normalized = String(toolName || "").trim();
1465
+ if (normalized) {
1466
+ toolNames.add(normalized);
1467
+ }
1468
+ }
1469
+ }
1470
+ return Array.from(toolNames).sort((a, b) => a.localeCompare(b));
1471
+ }, [
1472
+ activeProfile?.allowedToolNames,
1473
+ activeProfile?.readOnlyToolNames,
1474
+ mode,
1475
+ selectedSkills,
1476
+ ]);
1477
+ const displayedAvailableTools = isLocalOnlyDraftAgent
1478
+ ? previewAvailableTools
1479
+ : availableTools;
1480
+ const displayedAvailableToolsLoading = isLocalOnlyDraftAgent
1481
+ ? false
1482
+ : availableToolsLoading;
1483
+ const displayedAvailableToolsError = isLocalOnlyDraftAgent
1484
+ ? null
1485
+ : availableToolsError;
813
1486
  // Remove deprecated cost limit fields from metadata to avoid confusion with agent/profile settings
814
1487
  const sanitizeAgentMetadata = useCallback((meta) => {
815
1488
  try {
@@ -834,28 +1507,239 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
834
1507
  return meta;
835
1508
  }
836
1509
  }, []);
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,
1510
+ const getSkillActionErrorMessage = useCallback((error) => {
1511
+ const message = error instanceof Error && error.message.trim()
1512
+ ? error.message.trim()
1513
+ : "Failed to update skill";
1514
+ if (message.includes("Skill is not available for this agent profile")) {
1515
+ return "This skill cannot be added for the current agent profile.";
1516
+ }
1517
+ return message;
1518
+ }, []);
1519
+ const clearLegacySelectedSkillsFromMetadata = useCallback(async () => {
1520
+ const legacySkillIds = metadataSelectedSkillIds;
1521
+ if (!agent?.id || legacySkillIds.length === 0) {
1522
+ return;
1523
+ }
1524
+ const current = agentMetadata || {};
1525
+ const currentAdditionalData = current.additionalData ||
1526
+ {};
1527
+ const nextAdditionalData = Object.fromEntries(Object.entries(currentAdditionalData).filter(([key]) => key.toLowerCase() !== "skillids"));
1528
+ const next = {
1529
+ ...current,
849
1530
  };
850
- }
851
- catch {
852
- deterministicFlags = {
853
- deterministic: false,
854
- seed: undefined,
1531
+ if (Object.keys(nextAdditionalData).length > 0) {
1532
+ next.additionalData = nextAdditionalData;
1533
+ }
1534
+ else {
1535
+ delete next.additionalData;
1536
+ }
1537
+ try {
1538
+ await updateAgentContext(agent.id, next);
1539
+ setAgentMetadata(next);
1540
+ setAgent((prev) => prev ? { ...prev, agentContext: JSON.stringify(next) } : prev);
1541
+ }
1542
+ catch (e) {
1543
+ console.error("Failed to clear legacy selected skills", e);
1544
+ }
1545
+ }, [
1546
+ agent?.id,
1547
+ agentMetadata,
1548
+ metadataSelectedSkillIds,
1549
+ setAgent,
1550
+ setAgentMetadata,
1551
+ ]);
1552
+ const parsePersistedAgentContext = (contextJson) => {
1553
+ try {
1554
+ if (!contextJson)
1555
+ return null;
1556
+ const parsedContext = JSON.parse(contextJson);
1557
+ if (!parsedContext || typeof parsedContext !== "object") {
1558
+ return null;
1559
+ }
1560
+ return sanitizeAgentMetadata(parsedContext);
1561
+ }
1562
+ catch {
1563
+ return null;
1564
+ }
1565
+ };
1566
+ const buildDraftPersistenceContext = () => {
1567
+ let effectiveContext = agentMetadata;
1568
+ try {
1569
+ const normalizedAgentProfileId = agent?.profileId?.toLowerCase();
1570
+ const profile = activeProfile ||
1571
+ profiles.find((p) => p.id?.toLowerCase() === normalizedAgentProfileId);
1572
+ const isLiveMode = profile?.editorContextMode === "live";
1573
+ if (isLiveMode && typeof buildCurrentContext === "function") {
1574
+ const freshContext = buildCurrentContext();
1575
+ if (freshContext) {
1576
+ effectiveContext = {
1577
+ ...(agentMetadata || {}),
1578
+ items: freshContext.items,
1579
+ currentItemId: freshContext.currentItemId,
1580
+ components: freshContext.components,
1581
+ field: freshContext.field,
1582
+ activeWorkspace: freshContext.activeWorkspace,
1583
+ hasPageLoaded: freshContext.hasPageLoaded,
1584
+ openSidebars: freshContext.openSidebars,
1585
+ };
1586
+ }
1587
+ }
1588
+ }
1589
+ catch (e) {
1590
+ console.warn("[AgentTerminal] Failed to compute draft context:", e);
1591
+ }
1592
+ return sanitizeAgentMetadata(effectiveContext || null);
1593
+ };
1594
+ const ensureDraftAgentPersisted = async () => {
1595
+ if (!agent?.id) {
1596
+ throw new Error("Agent not ready. Please try again.");
1597
+ }
1598
+ if (!isLocalOnlyDraftAgent) {
1599
+ return agent;
1600
+ }
1601
+ const requestSettings = getPendingRequestSettings();
1602
+ if (!requestSettings.profileId) {
1603
+ throw new Error("Select an agent profile before adding a skill.");
1604
+ }
1605
+ const effectiveContext = buildDraftPersistenceContext();
1606
+ const persistedAgent = await persistDraftAgent({
1607
+ agentId: agent.id,
1608
+ sessionId: editContext?.sessionId || undefined,
1609
+ profileId: requestSettings.profileId,
1610
+ mode: requestSettings.mode,
1611
+ model: requestSettings.modelId || undefined,
1612
+ name: agent.name,
1613
+ context: effectiveContext,
1614
+ });
1615
+ pendingSettingsRef.current = null;
1616
+ const persistedMetadata = parsePersistedAgentContext(persistedAgent.agentContext) ??
1617
+ effectiveContext;
1618
+ setAgentMetadata(persistedMetadata ? { ...persistedMetadata } : null);
1619
+ setAgent((prev) => {
1620
+ const next = {
1621
+ ...(prev || {}),
1622
+ ...persistedAgent,
1623
+ };
1624
+ if (prev?.messages?.length && !persistedAgent.messages?.length) {
1625
+ next.messages = prev.messages;
1626
+ }
1627
+ return next;
1628
+ });
1629
+ onAgentUpdate?.(persistedAgent);
1630
+ return persistedAgent;
1631
+ };
1632
+ const handleAddSkill = useCallback(async (skillId) => {
1633
+ if (selectedSkillSet.has(skillId.toLowerCase()))
1634
+ return;
1635
+ if (!agent?.id)
1636
+ return;
1637
+ try {
1638
+ setSkillActionError(null);
1639
+ const persistedAgent = await ensureDraftAgentPersisted();
1640
+ await assignAgentSkill({ agentId: persistedAgent.id, skillId });
1641
+ setAgent((prev) => {
1642
+ if (!prev)
1643
+ return prev;
1644
+ const currentAssigned = Array.isArray(prev.assignedSkillIds)
1645
+ ? prev.assignedSkillIds
1646
+ : [];
1647
+ if (currentAssigned.some((existingId) => String(existingId).toLowerCase() === skillId.toLowerCase())) {
1648
+ return prev;
1649
+ }
1650
+ return {
1651
+ ...prev,
1652
+ assignedSkillIds: [...currentAssigned, skillId],
1653
+ };
1654
+ });
1655
+ await clearLegacySelectedSkillsFromMetadata();
1656
+ return true;
1657
+ }
1658
+ catch (e) {
1659
+ setSkillActionError(getSkillActionErrorMessage(e));
1660
+ return false;
1661
+ }
1662
+ }, [
1663
+ agent?.id,
1664
+ assignAgentSkill,
1665
+ clearLegacySelectedSkillsFromMetadata,
1666
+ ensureDraftAgentPersisted,
1667
+ getSkillActionErrorMessage,
1668
+ setAgent,
1669
+ selectedSkillSet,
1670
+ ]);
1671
+ const handleRemoveSkill = useCallback(async (skillId) => {
1672
+ if (!agent?.id)
1673
+ return;
1674
+ try {
1675
+ setSkillActionError(null);
1676
+ await revokeAgentSkill({ agentId: agent.id, skillId });
1677
+ setAgent((prev) => {
1678
+ if (!prev)
1679
+ return prev;
1680
+ const currentAssigned = Array.isArray(prev.assignedSkillIds)
1681
+ ? prev.assignedSkillIds
1682
+ : [];
1683
+ return {
1684
+ ...prev,
1685
+ assignedSkillIds: currentAssigned.filter((existingId) => String(existingId).toLowerCase() !== skillId.toLowerCase()),
1686
+ };
1687
+ });
1688
+ await clearLegacySelectedSkillsFromMetadata();
1689
+ }
1690
+ catch (e) {
1691
+ setSkillActionError(getSkillActionErrorMessage(e));
1692
+ }
1693
+ }, [
1694
+ agent?.id,
1695
+ clearLegacySelectedSkillsFromMetadata,
1696
+ getSkillActionErrorMessage,
1697
+ revokeAgentSkill,
1698
+ setAgent,
1699
+ ]);
1700
+ const handleOpenProfileSettings = useCallback(async () => {
1701
+ if (!editContext || !activeProfile?.id)
1702
+ return;
1703
+ const lang = editContext.currentItemDescriptor?.language || "en";
1704
+ await editContext.loadItem({
1705
+ id: activeProfile.id,
1706
+ language: lang,
1707
+ version: 0,
1708
+ });
1709
+ editContext.switchWorkspace("editor");
1710
+ }, [editContext, activeProfile?.id]);
1711
+ const handleEditProfileSideBySide = useCallback(async () => {
1712
+ if (!editContext || !activeProfile?.id)
1713
+ return;
1714
+ const lang = editContext.currentItemDescriptor?.language || "en";
1715
+ const profileItem = {
1716
+ id: activeProfile.id,
1717
+ language: lang,
1718
+ version: 0,
855
1719
  };
856
- }
1720
+ if (editContext.workspaceId === "agents") {
1721
+ editContext.setShowAgentsWorkspaceEditor(true);
1722
+ await editContext.loadItem(profileItem);
1723
+ return;
1724
+ }
1725
+ await editContext.loadItem(profileItem, {
1726
+ openInNewSlot: true,
1727
+ });
1728
+ editContext.switchWorkspace("editor");
1729
+ }, [editContext, activeProfile?.id]);
1730
+ const handleOpenSkillItem = useCallback(async (skillId) => {
1731
+ if (!editContext || !skillId)
1732
+ return;
1733
+ const lang = editContext.currentItemDescriptor?.language || "en";
1734
+ await editContext.loadItem({
1735
+ id: skillId,
1736
+ language: lang,
1737
+ version: 0,
1738
+ });
1739
+ editContext.switchWorkspace("editor");
1740
+ }, [editContext]);
857
1741
  useEffect(() => {
858
- localStorage.setItem("editor.agent.promptHistory", JSON.stringify(promptHistory));
1742
+ localStorageService.setItem("editor.agent.promptHistory", promptHistory);
859
1743
  }, [promptHistory]);
860
1744
  useEffect(() => {
861
1745
  // Keep messagesRef synchronized with messages state
@@ -867,6 +1751,32 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
867
1751
  const [liveTotals, setLiveTotals] = useState(null);
868
1752
  // Context window status from backend (extracted from delta cost object)
869
1753
  const [contextWindowStatus, setContextWindowStatus] = useState(null);
1754
+ const effectiveModelName = useMemo(() => {
1755
+ const fromContextWindow = contextWindowStatus?.model?.trim();
1756
+ if (fromContextWindow)
1757
+ return fromContextWindow;
1758
+ const fromAgent = agent?.model?.trim();
1759
+ if (fromAgent)
1760
+ return fromAgent;
1761
+ const models = activeProfile?.models || [];
1762
+ const selectedModelName = selectedModelId
1763
+ ? models.find((m) => m.id === selectedModelId)?.name?.trim()
1764
+ : undefined;
1765
+ if (selectedModelName)
1766
+ return selectedModelName;
1767
+ const defaultModelName = activeProfile?.defaultModelId
1768
+ ? models.find((m) => m.id === activeProfile.defaultModelId)?.name?.trim()
1769
+ : undefined;
1770
+ if (defaultModelName)
1771
+ return defaultModelName;
1772
+ return models[0]?.name?.trim() || undefined;
1773
+ }, [
1774
+ contextWindowStatus?.model,
1775
+ agent?.model,
1776
+ activeProfile?.models,
1777
+ activeProfile?.defaultModelId,
1778
+ selectedModelId,
1779
+ ]);
870
1780
  // Flag to track when we should create a new message
871
1781
  const shouldCreateNewMessage = useRef(false);
872
1782
  // Keep a ref to the current messages for immediate access
@@ -877,12 +1787,17 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
877
1787
  const placeholderInputRef = useRef(null);
878
1788
  const promptPlaceholderInputRef = useRef(null);
879
1789
  const messagesContainerRef = useRef(null);
1790
+ const inlineDialogContainerRef = useRef(null);
880
1791
  const [shouldAutoScroll, setShouldAutoScroll] = useState(true);
1792
+ const [recentToolUiEvents, setRecentToolUiEvents] = useState([]);
881
1793
  // WebSocket subscription state for agent streaming
882
1794
  const seenMessageIdsRef = useRef(new Set());
883
1795
  const lastSeqRef = useRef(0);
884
1796
  const subscribedAgentIdRef = useRef(null);
885
- // Cache mode/model changes made while the agent is still "new" (not yet persisted)
1797
+ const reconcileServerStateInFlightRef = useRef(false);
1798
+ const toolCallFirstSeenAtRef = useRef({});
1799
+ const pendingToolCompletionTimersRef = useRef({});
1800
+ // Cache mode/model/profile changes made while the agent is still "new" (not yet persisted)
886
1801
  const pendingSettingsRef = useRef(null);
887
1802
  // Track whether textarea should maintain focus during re-renders
888
1803
  const shouldMaintainFocusRef = useRef(false);
@@ -904,11 +1819,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
904
1819
  setIsVoiceSupported(!!SR);
905
1820
  if (SR === undefined) {
906
1821
  setIsVoiceDisabled(true);
907
- localStorage.setItem(VOICE_DISABLED_KEY, "true");
1822
+ localStorageService.setString(VOICE_DISABLED_KEY, "true");
908
1823
  return;
909
1824
  }
910
1825
  else {
911
- const wasDisabled = localStorage.getItem(VOICE_DISABLED_KEY) === "true";
1826
+ const wasDisabled = localStorageService.getString(VOICE_DISABLED_KEY) === "true";
912
1827
  setIsVoiceDisabled(wasDisabled);
913
1828
  }
914
1829
  }
@@ -919,7 +1834,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
919
1834
  // Auto-focus terminal input on mount
920
1835
  useEffect(() => {
921
1836
  if (textareaRef.current) {
922
- textareaRef.current.focus();
1837
+ try {
1838
+ textareaRef.current.focus({ preventScroll: true });
1839
+ }
1840
+ catch {
1841
+ textareaRef.current.focus();
1842
+ }
923
1843
  }
924
1844
  }, []);
925
1845
  // Start voice recognition
@@ -938,20 +1858,29 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
938
1858
  r.lang = editContext?.currentItemDescriptor?.language || "en-US";
939
1859
  r.continuous = true;
940
1860
  r.interimResults = true;
1861
+ promptBeforeVoiceRef.current = prompt;
941
1862
  r.onstart = () => {
942
1863
  setIsListening(true);
943
1864
  prevPlaceholderRef.current = inputPlaceholder;
944
1865
  setInputPlaceholder("Listening...");
945
1866
  };
1867
+ // Desktop Chrome: single result at index 0, transitions interim→final once.
1868
+ // Mobile Chrome: new result index per event, ALL immediately isFinal,
1869
+ // each containing the full cumulative transcript. We always take the last
1870
+ // non-empty final result and REPLACE the prompt to handle both patterns.
946
1871
  r.onresult = (event) => {
947
- let finalText = "";
1872
+ let lastFinalTranscript = "";
948
1873
  let interimText = "";
949
- for (let i = event.resultIndex; i < event.results.length; i++) {
1874
+ for (let i = event.results.length - 1; i >= 0; i--) {
950
1875
  const res = event.results[i];
951
- if (res.isFinal)
952
- finalText += res[0]?.transcript || "";
953
- else
954
- interimText += res[0]?.transcript || "";
1876
+ if (res.isFinal &&
1877
+ !lastFinalTranscript &&
1878
+ (res[0]?.transcript || "").trim()) {
1879
+ lastFinalTranscript = res[0].transcript;
1880
+ }
1881
+ if (!res.isFinal) {
1882
+ interimText = (res[0]?.transcript || "") + interimText;
1883
+ }
955
1884
  }
956
1885
  if (interimText) {
957
1886
  setInputPlaceholder(`Listening... ${interimText.trim()}`);
@@ -959,11 +1888,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
959
1888
  else {
960
1889
  setInputPlaceholder("Listening...");
961
1890
  }
962
- if (finalText && finalText.trim()) {
963
- setPrompt((prev) => {
964
- const prefix = prev && !prev.endsWith(" ") ? prev + " " : prev;
965
- return (prefix || "") + finalText.trim() + " ";
966
- });
1891
+ const base = promptBeforeVoiceRef.current;
1892
+ const separator = base && !base.endsWith(" ") ? " " : "";
1893
+ const recognized = lastFinalTranscript.trim();
1894
+ if (recognized) {
1895
+ setPrompt(base + separator + recognized + " ");
967
1896
  if (textareaRef.current) {
968
1897
  try {
969
1898
  const v = textareaRef.current.value || "";
@@ -979,7 +1908,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
979
1908
  // network error also comes from incompatible chromium client
980
1909
  if (e?.error === "network") {
981
1910
  try {
982
- localStorage.setItem(VOICE_DISABLED_KEY, "true");
1911
+ localStorageService.setString(VOICE_DISABLED_KEY, "true");
983
1912
  setIsVoiceDisabled(true);
984
1913
  }
985
1914
  catch { }
@@ -1148,25 +2077,18 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1148
2077
  // Shared stream message handlers with messageId support
1149
2078
  const createNewStreamMessage = useCallback((messageId, agentData) => {
1150
2079
  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
- }
2080
+ const effectiveAgentId = currentAgent?.id || agentStub.id;
1159
2081
  // Reduced: avoid verbose logging during streaming
1160
2082
  return {
1161
2083
  id: messageId,
1162
- agentId: currentAgent.id,
2084
+ agentId: effectiveAgentId,
1163
2085
  messageIndex: -1,
1164
2086
  role: "assistant",
1165
2087
  content: "",
1166
2088
  name: "agent",
1167
2089
  messageType: "streaming",
1168
2090
  isCompleted: false,
1169
- model: currentAgent.model || "",
2091
+ model: currentAgent?.model || "",
1170
2092
  tokensUsed: 0,
1171
2093
  inputTokens: 0,
1172
2094
  outputTokens: 0,
@@ -1175,16 +2097,71 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1175
2097
  outputTokenCost: 0,
1176
2098
  cachedInputTokenCost: 0,
1177
2099
  totalCost: 0,
1178
- currency: currentAgent.currency || "USD",
2100
+ currency: currentAgent?.currency || "USD",
1179
2101
  createdDate: new Date().toISOString(),
1180
2102
  toolCalls: [],
1181
2103
  };
1182
- }, [agent]);
2104
+ }, [agent, agentStub.id]);
2105
+ const stripHeartbeatMessages = useCallback((currentMessages) => currentMessages.filter((message) => message.messageType !== "heartbeat"), []);
2106
+ const handleHeartbeatMessage = useCallback((message) => {
2107
+ const heartbeatText = (message.data?.message ||
2108
+ message.data?.Message ||
2109
+ "Still working on your request. This is taking a little longer than usual.").trim() ||
2110
+ "Still working on your request. This is taking a little longer than usual.";
2111
+ const createdDate = message.timestamp || new Date().toISOString();
2112
+ const heartbeatId = `heartbeat-${agent?.id || agentStub.id}`;
2113
+ setMessages((prev) => {
2114
+ const withoutHeartbeats = stripHeartbeatMessages(prev);
2115
+ const updated = [
2116
+ ...withoutHeartbeats,
2117
+ {
2118
+ id: heartbeatId,
2119
+ agentId: agent?.id || agentStub.id,
2120
+ messageIndex: -1,
2121
+ role: "system",
2122
+ content: heartbeatText,
2123
+ name: "system",
2124
+ messageType: "heartbeat",
2125
+ isCompleted: true,
2126
+ model: "",
2127
+ tokensUsed: 0,
2128
+ inputTokens: 0,
2129
+ outputTokens: 0,
2130
+ cachedInputTokens: 0,
2131
+ inputTokenCost: 0,
2132
+ outputTokenCost: 0,
2133
+ cachedInputTokenCost: 0,
2134
+ totalCost: 0,
2135
+ currency: "USD",
2136
+ createdDate,
2137
+ toolCalls: [],
2138
+ },
2139
+ ];
2140
+ messagesRef.current = updated;
2141
+ return updated;
2142
+ });
2143
+ }, [agent?.id, agentStub.id, stripHeartbeatMessages]);
2144
+ const clearHeartbeatMessages = useCallback(() => {
2145
+ setMessages((prev) => {
2146
+ const updated = stripHeartbeatMessages(prev);
2147
+ if (updated.length === prev.length) {
2148
+ return prev;
2149
+ }
2150
+ messagesRef.current = updated;
2151
+ return updated;
2152
+ });
2153
+ }, [stripHeartbeatMessages]);
1183
2154
  const handleContentChunk = useCallback((message, agentData) => {
1184
2155
  // Get messageId from data, or generate one from agent ID (for backward compatibility)
1185
2156
  // If no messageId is provided, we'll use the last assistant message or create a new one
1186
2157
  let messageId = message.data?.messageId;
1187
2158
  if (!messageId && agentData?.id) {
2159
+ console.warn("[AgentTerminal] Content chunk missing messageId; falling back to local resolution", {
2160
+ agentId: agentData.id,
2161
+ isIncremental: message.data?.isIncremental,
2162
+ previousContentLength: message.data?.previousContentLength,
2163
+ totalContentLength: message.data?.totalContentLength,
2164
+ });
1188
2165
  // For backward compatibility: if no messageId, find or create the current streaming message
1189
2166
  // This handles cases where the backend doesn't send messageId
1190
2167
  const currentMessages = messagesRef.current;
@@ -1198,7 +2175,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1198
2175
  // If the agent isn't currently running (e.g., we switched tabs after the run
1199
2176
  // completed), skip creating a new streaming message to avoid duplicates.
1200
2177
  const currentAgentStatus = (agentData || agent)?.status;
1201
- const isAgentRunning = currentAgentStatus === "running" || currentAgentStatus === 1;
2178
+ const isAgentRunning = currentAgentStatus === "running";
1202
2179
  if (!isAgentRunning) {
1203
2180
  return;
1204
2181
  }
@@ -1231,6 +2208,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1231
2208
  outputCost: Number(cost.output) || 0,
1232
2209
  cachedCost: Number(cost.cached) || 0,
1233
2210
  cacheWriteCost: Number(cost.cacheWrite) || 0,
2211
+ imageCost: Number(cost.imageCost ?? cost.totalImageCost) ||
2212
+ 0,
1234
2213
  totalCost: Number(cost.total) || 0,
1235
2214
  currency: "USD",
1236
2215
  };
@@ -1254,11 +2233,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1254
2233
  setIsWaitingForResponse(false);
1255
2234
  shouldCreateNewMessage.current = false;
1256
2235
  }
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) {
2236
+ // Extract context window info from cost object.
2237
+ // Note: contextUsed can legitimately be 0 (especially early in OpenAI streams),
2238
+ // so we must check for null/undefined instead of truthiness.
2239
+ const contextWindowRaw = cost.contextWindow;
2240
+ const contextUsedRaw = cost.contextUsed;
2241
+ if (contextWindowRaw !== undefined &&
2242
+ contextWindowRaw !== null &&
2243
+ contextUsedRaw !== undefined &&
2244
+ contextUsedRaw !== null) {
2245
+ const contextWindowValue = Number(contextWindowRaw);
2246
+ const contextUsedValue = Number(contextUsedRaw);
2247
+ if (contextWindowValue > 0 &&
2248
+ Number.isFinite(contextUsedValue) &&
2249
+ contextUsedValue >= 0) {
1262
2250
  setContextWindowStatus({
1263
2251
  contextWindowTokens: contextWindowValue,
1264
2252
  estimatedInputTokens: contextUsedValue,
@@ -1274,6 +2262,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1274
2262
  const existingMessageIndex = prev.findIndex((msg) => msg.id === messageId);
1275
2263
  if (existingMessageIndex === -1) {
1276
2264
  // Message doesn't exist - create new streaming message
2265
+ const previousContentLength = message.data?.previousContentLength || 0;
2266
+ if (message.data?.isIncremental && previousContentLength > 0) {
2267
+ console.warn("[AgentTerminal] Incremental chunk arrived before its base message existed", {
2268
+ messageId,
2269
+ previousContentLength,
2270
+ totalContentLength: message.data?.totalContentLength,
2271
+ deltaLength: (message.data?.deltaContent || "").length,
2272
+ });
2273
+ }
1277
2274
  const newStreamMessage = createNewStreamMessage(messageId, agentData);
1278
2275
  // Set the content for the new message
1279
2276
  const updatedNewMessage = { ...newStreamMessage };
@@ -1296,8 +2293,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1296
2293
  return prev;
1297
2294
  // Check if existing content is already longer than what we're trying to stream
1298
2295
  const currentContentLength = existingMessage.content?.length || 0;
2296
+ const previousContentLength = message.data?.previousContentLength || 0;
1299
2297
  const totalContentLength = message.data?.totalContentLength || 0;
1300
- if (currentContentLength >= totalContentLength &&
2298
+ if (message.data?.isIncremental &&
2299
+ previousContentLength !== currentContentLength &&
2300
+ (previousContentLength > 0 || currentContentLength > 0)) {
2301
+ console.warn("[AgentTerminal] Content chunk length mismatch", {
2302
+ messageId,
2303
+ previousContentLength,
2304
+ currentContentLength,
2305
+ totalContentLength,
2306
+ deltaLength: (message.data?.deltaContent || "").length,
2307
+ });
2308
+ }
2309
+ if (message.data?.isIncremental &&
2310
+ currentContentLength >= totalContentLength &&
1301
2311
  totalContentLength > 0) {
1302
2312
  return prev;
1303
2313
  }
@@ -1321,10 +2331,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1321
2331
  });
1322
2332
  }, [createNewStreamMessage, agent]);
1323
2333
  const handleToolCall = useCallback((message, agentData) => {
1324
- const toolCallId = message.data?.toolCallId || message.data?.id || crypto.randomUUID();
2334
+ const extractedToolCall = extractToolCallFields(message.data);
2335
+ const toolCallId = extractedToolCall.toolCallId || crypto.randomUUID();
1325
2336
  // Prefer provided messageId, otherwise fall back to the last streaming assistant message
1326
2337
  let toolCallMessageId = message.data?.messageId;
1327
2338
  if (!toolCallMessageId) {
2339
+ console.warn("[AgentTerminal] Tool call missing messageId; falling back", {
2340
+ toolCallId,
2341
+ toolName: message.data?.name || message.data?.displayName,
2342
+ });
1328
2343
  const current = messagesRef.current;
1329
2344
  const lastStreaming = [...current]
1330
2345
  .reverse()
@@ -1332,7 +2347,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1332
2347
  if (lastStreaming?.id) {
1333
2348
  toolCallMessageId = lastStreaming.id;
1334
2349
  }
2350
+ else {
2351
+ // Tool calls can arrive before any assistant content chunk (common for dialog tools like ask-questionnaire).
2352
+ // Create a synthetic streaming message so the UI can render the tool call immediately.
2353
+ toolCallMessageId = crypto.randomUUID();
2354
+ }
1335
2355
  }
2356
+ appendToolUiEvent("ui:tool-call-targeted", `${extractedToolCall.functionName || "unknown"} toolCallId=${toolCallId} targetMessageId=${toolCallMessageId || "none"} providedMessageId=${String(message.data?.messageId || "none")}`);
1336
2357
  // Find or create the target message for this tool call
1337
2358
  if (toolCallMessageId) {
1338
2359
  const currentMessages = messagesRef.current;
@@ -1359,24 +2380,25 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1359
2380
  }
1360
2381
  // Add tool call to the message in the array
1361
2382
  if (toolCallMessageId && message.data && toolCallId) {
1362
- const functionName = message.data.functionName ||
1363
- message.data.name ||
1364
- message.data.function?.name ||
1365
- "unknown";
2383
+ const toolCallError = message.data.functionError || message.data.error || "";
2384
+ const isPruned = !!message.data?.isPruned || /^PRUNED$/i.test(String(toolCallError));
2385
+ const toolCallCreatedDate = message.data.createdDate ||
2386
+ message.timestamp ||
2387
+ new Date().toISOString();
1366
2388
  const toolCall = {
1367
2389
  id: toolCallId,
1368
2390
  messageId: toolCallMessageId,
1369
2391
  dbMessageId: message.data.messageId, // Database message ID for approval/rejection
1370
2392
  toolCallId: toolCallId,
1371
- functionName: functionName,
1372
- functionArguments: message.data.functionArguments ||
1373
- message.data.arguments ||
1374
- JSON.stringify(message.data.function?.arguments || {}),
2393
+ functionName: extractedToolCall.functionName,
2394
+ functionArguments: extractedToolCall.functionArguments,
1375
2395
  functionResult: message.data.functionResult || message.data.result || "",
1376
- functionError: message.data.functionError || message.data.error || "",
2396
+ functionResultRichContent: message.data.richContent || undefined,
2397
+ functionError: toolCallError,
2398
+ isPruned,
1377
2399
  isCompleted: false,
1378
2400
  responseTimeMs: message.data.responseTimeMs,
1379
- createdDate: new Date().toISOString(),
2401
+ createdDate: toolCallCreatedDate,
1380
2402
  requiresApproval: message.data?.requiresApproval,
1381
2403
  };
1382
2404
  // Check for existing tool call - search across ALL messages by toolCallId first
@@ -1415,14 +2437,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1415
2437
  // Check if the new data has more information than what we have
1416
2438
  const newArgs = toolCall.functionArguments;
1417
2439
  const existingArgs = existingToolCall.functionArguments;
1418
- const hasMoreCompleteArgs = newArgs && newArgs.length > (existingArgs?.length || 0);
2440
+ const newArgsText = stringifyToolField(newArgs) || "";
2441
+ const existingArgsText = stringifyToolField(existingArgs) || "";
2442
+ const hasMoreCompleteArgs = (newArgsText.length > existingArgsText.length &&
2443
+ newArgsText !== existingArgsText) ||
2444
+ (existingArgsText === "{}" && newArgsText !== "{}");
1419
2445
  const hasNewResult = toolCall.functionResult && !existingToolCall.functionResult;
2446
+ const hasNewRichContent = toolCall.functionResultRichContent &&
2447
+ !existingToolCall.functionResultRichContent;
1420
2448
  const hasNewError = toolCall.functionError && !existingToolCall.functionError;
1421
2449
  const hasNewApprovalInfo = toolCall.requiresApproval && !existingToolCall.requiresApproval;
1422
2450
  const hasNewDbMessageId = toolCall.dbMessageId && !existingToolCall.dbMessageId;
1423
2451
  // Only update if there's meaningful new data
1424
2452
  if (hasMoreCompleteArgs ||
1425
2453
  hasNewResult ||
2454
+ hasNewRichContent ||
1426
2455
  hasNewError ||
1427
2456
  hasNewApprovalInfo ||
1428
2457
  hasNewDbMessageId) {
@@ -1439,9 +2468,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1439
2468
  updatedToolCalls[idx] = {
1440
2469
  ...existing,
1441
2470
  functionArguments: hasMoreCompleteArgs
1442
- ? newArgs
1443
- : existing.functionArguments,
2471
+ ? newArgsText
2472
+ : existingArgsText || existing.functionArguments,
1444
2473
  functionResult: toolCall.functionResult || existing.functionResult,
2474
+ functionResultRichContent: toolCall.functionResultRichContent ||
2475
+ existing.functionResultRichContent,
1445
2476
  functionError: toolCall.functionError || existing.functionError,
1446
2477
  requiresApproval: toolCall.requiresApproval || existing.requiresApproval,
1447
2478
  };
@@ -1459,27 +2490,36 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1459
2490
  }
1460
2491
  return; // Done updating existing tool call
1461
2492
  }
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] };
2493
+ flushSync(() => {
2494
+ setMessages((prev) => {
2495
+ const updated = prev.map((msg) => {
2496
+ if (msg.id !== toolCallMessageId)
2497
+ return msg;
2498
+ const existingToolCalls = msg.toolCalls || [];
2499
+ return { ...msg, toolCalls: [...existingToolCalls, toolCall] };
2500
+ });
2501
+ messagesRef.current = updated;
2502
+ return updated;
1468
2503
  });
1469
- messagesRef.current = updated;
1470
- return updated;
1471
2504
  });
2505
+ const messageWithToolCall = messagesRef.current.find((msg) => msg.id === toolCallMessageId);
2506
+ 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
2507
  // If tool requires approval, agent is now waiting for user action - stop thinking
1473
2508
  if (message.data?.requiresApproval) {
1474
2509
  setIsAgentThinking(false);
1475
2510
  }
1476
2511
  }
1477
- }, [createNewStreamMessage]);
2512
+ }, [appendToolUiEvent, createNewStreamMessage]);
1478
2513
  const handleToolResult = useCallback((message, agentData) => {
1479
- const resultToolCallId = message.data?.toolCallId || message.data?.id || crypto.randomUUID();
2514
+ const extractedToolCall = extractToolCallFields(message.data);
2515
+ const resultToolCallId = extractedToolCall.toolCallId || crypto.randomUUID();
1480
2516
  // Prefer provided messageId, otherwise fall back to the last streaming assistant message
1481
2517
  let resultMessageId = message.data?.messageId;
1482
2518
  if (!resultMessageId) {
2519
+ console.warn("[AgentTerminal] Tool result missing messageId; falling back", {
2520
+ toolCallId: resultToolCallId,
2521
+ toolName: message.data?.functionName || message.data?.displayName,
2522
+ });
1483
2523
  const current = messagesRef.current;
1484
2524
  const lastStreaming = [...current]
1485
2525
  .reverse()
@@ -1501,6 +2541,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1501
2541
  outputCost: Number(cost.output) || 0,
1502
2542
  cachedCost: Number(cost.cached) || 0,
1503
2543
  cacheWriteCost: Number(cost.cacheWrite) || 0,
2544
+ imageCost: Number(cost.imageCost) || 0,
1504
2545
  totalCost: Number(cost.total) || 0,
1505
2546
  currency: "USD",
1506
2547
  };
@@ -1512,11 +2553,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1512
2553
  if (anyNonZero) {
1513
2554
  setLiveTotals(nextTotals);
1514
2555
  }
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) {
2556
+ // Extract context window info from cost object.
2557
+ // Note: contextUsed can legitimately be 0 (especially early in OpenAI streams),
2558
+ // so we must check for null/undefined instead of truthiness.
2559
+ const contextWindowRaw = cost.contextWindow;
2560
+ const contextUsedRaw = cost.contextUsed;
2561
+ if (contextWindowRaw !== undefined &&
2562
+ contextWindowRaw !== null &&
2563
+ contextUsedRaw !== undefined &&
2564
+ contextUsedRaw !== null) {
2565
+ const contextWindowValue = Number(contextWindowRaw);
2566
+ const contextUsedValue = Number(contextUsedRaw);
2567
+ if (contextWindowValue > 0 &&
2568
+ Number.isFinite(contextUsedValue) &&
2569
+ contextUsedValue >= 0) {
1520
2570
  setContextWindowStatus({
1521
2571
  contextWindowTokens: contextWindowValue,
1522
2572
  estimatedInputTokens: contextUsedValue,
@@ -1540,6 +2590,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1540
2590
  outputCost: Number(data.totalOutputTokenCost) || 0,
1541
2591
  cachedCost: Number(data.totalCachedTokenCost) || 0,
1542
2592
  cacheWriteCost: Number(data.totalCacheWriteTokenCost) || 0,
2593
+ imageCost: Number(data.totalImageCost) || 0,
1543
2594
  totalCost: Number(data.totalCost) || 0,
1544
2595
  currency: data.currency || "USD",
1545
2596
  };
@@ -1555,8 +2606,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1555
2606
  }
1556
2607
  // Update tool result directly in the messages array
1557
2608
  if (!resultMessageId) {
2609
+ appendToolUiEvent("ui:tool-result-dropped", `${extractedToolCall.functionName || "unknown"} toolCallId=${resultToolCallId} reason=no-result-message-id`);
1558
2610
  return;
1559
2611
  }
2612
+ appendToolUiEvent("ui:tool-result-targeted", `${extractedToolCall.functionName || "unknown"} toolCallId=${resultToolCallId} targetMessageId=${resultMessageId}`);
1560
2613
  // Update the message with tool result
1561
2614
  setMessages((prev) => {
1562
2615
  const updated = prev.map((msg) => {
@@ -1572,13 +2625,22 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1572
2625
  const existingToolCall = updatedMessage.toolCalls[toolCallIndex];
1573
2626
  if (existingToolCall && message.data) {
1574
2627
  const updatedToolCalls = [...updatedMessage.toolCalls];
2628
+ const nextArgsText = stringifyToolField(extractedToolCall.functionArguments) || "";
2629
+ const existingArgsText = stringifyToolField(existingToolCall.functionArguments) || "";
2630
+ const hasMoreCompleteArgs = (nextArgsText.length > existingArgsText.length &&
2631
+ nextArgsText !== existingArgsText) ||
2632
+ (existingArgsText === "{}" && nextArgsText !== "{}");
1575
2633
  const toolCall = {
1576
2634
  id: existingToolCall.id,
1577
2635
  messageId: existingToolCall.messageId,
1578
2636
  toolCallId: existingToolCall.toolCallId,
1579
2637
  functionName: existingToolCall.functionName,
1580
- functionArguments: existingToolCall.functionArguments,
2638
+ functionArguments: hasMoreCompleteArgs
2639
+ ? nextArgsText
2640
+ : existingToolCall.functionArguments,
1581
2641
  functionResult: message.data.functionResult || message.data.result || "",
2642
+ functionResultRichContent: message.data.richContent ||
2643
+ existingToolCall.functionResultRichContent,
1582
2644
  functionError: message.data.functionError || message.data.error || "",
1583
2645
  isCompleted: true,
1584
2646
  responseTimeMs: message.data.responseTimeMs,
@@ -1595,23 +2657,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1595
2657
  }
1596
2658
  else if (message.data && resultToolCallId && resultMessageId) {
1597
2659
  // 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";
2660
+ const toolCallCreatedDate = message.data.createdDate ||
2661
+ message.timestamp ||
2662
+ new Date().toISOString();
1602
2663
  const toolCall = {
1603
2664
  id: resultToolCallId,
1604
2665
  messageId: resultMessageId,
1605
2666
  toolCallId: resultToolCallId,
1606
- functionName: functionName,
1607
- functionArguments: message.data.functionArguments ||
1608
- message.data.arguments ||
1609
- JSON.stringify(message.data.function?.arguments || {}),
2667
+ functionName: extractedToolCall.functionName,
2668
+ functionArguments: extractedToolCall.functionArguments,
1610
2669
  functionResult: message.data.functionResult || message.data.result || "",
2670
+ functionResultRichContent: message.data.richContent || undefined,
1611
2671
  functionError: message.data.functionError || message.data.error || "",
1612
2672
  isCompleted: true,
1613
2673
  responseTimeMs: message.data.responseTimeMs,
1614
- createdDate: new Date().toISOString(),
2674
+ createdDate: toolCallCreatedDate,
1615
2675
  };
1616
2676
  updatedMessage.toolCalls = [...updatedMessage.toolCalls, toolCall];
1617
2677
  }
@@ -1619,9 +2679,12 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1619
2679
  return updatedMessage;
1620
2680
  });
1621
2681
  messagesRef.current = updated;
2682
+ const messageWithToolResult = updated.find((msg) => msg.id === resultMessageId);
2683
+ const matchingToolCall = messageWithToolResult?.toolCalls?.find((tc) => tc.toolCallId === resultToolCallId);
2684
+ appendToolUiEvent("ui:tool-result-applied", `${extractedToolCall.functionName || "unknown"} toolCallId=${resultToolCallId} targetMessageId=${resultMessageId} completed=${matchingToolCall?.isCompleted ? "yes" : "no"} messageToolCalls=${messageWithToolResult?.toolCalls?.length || 0}`);
1622
2685
  return updated;
1623
2686
  });
1624
- }, []);
2687
+ }, [appendToolUiEvent]);
1625
2688
  // Listen for local approval resolution to update UI
1626
2689
  useEffect(() => {
1627
2690
  const onApprovalResolved = (ev) => {
@@ -1638,6 +2701,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1638
2701
  if (!messageId || !toolCallId)
1639
2702
  return;
1640
2703
  // Update local state to reflect approval status
2704
+ const currentMessages = messagesRef.current || [];
2705
+ const hasMatchingToolCall = currentMessages.some((m) => (m.toolCalls || []).some((tc) => tc.toolCallId === toolCallId));
2706
+ if (!hasMatchingToolCall) {
2707
+ return;
2708
+ }
1641
2709
  setMessages((prev) => {
1642
2710
  const updated = prev.map((m) => {
1643
2711
  // Update tool calls in ANY message that contains this toolCallId
@@ -1702,7 +2770,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1702
2770
  // The agent might have been persisted after sending a prompt
1703
2771
  // Only treat as "new" if backend returns 404
1704
2772
  const hasExistingMessages = messagesRef.current.length > 0;
1705
- if (agentStub.status === "new" && !hasExistingMessages) {
2773
+ if (agentStub.status === "new" &&
2774
+ !agentStub.userId &&
2775
+ !hasExistingMessages) {
1706
2776
  // Only initialize as new if we have no messages yet (initial mount)
1707
2777
  // Derive initial profile from provided metadata if present
1708
2778
  const initialProfileIdFromMeta = (() => {
@@ -1742,6 +2812,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1742
2812
  totalInputTokenCost: 0,
1743
2813
  totalOutputTokenCost: 0,
1744
2814
  totalCachedInputTokenCost: 0,
2815
+ totalImageCost: 0,
1745
2816
  totalCost: 0,
1746
2817
  messageCount: 0,
1747
2818
  });
@@ -1784,48 +2855,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1784
2855
  }
1785
2856
  })();
1786
2857
  // 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;
2858
+ const localCtx = item && shouldSeedContext ? buildEditorContextPayload(item) : null;
1829
2859
  let nextMetadata = null;
1830
2860
  if (initialMetadata) {
1831
2861
  // Merge initial metadata with local context (using top-level structure)
@@ -1901,8 +2931,16 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1901
2931
  seenMessageIdsRef.current.add(msg.id.toLowerCase());
1902
2932
  }
1903
2933
  });
1904
- // Keep local streaming only if the agent is still running; otherwise discard locals
2934
+ // Keep local streaming if the agent is still active (running/waiting); otherwise discard locals.
2935
+ // This is important for dialog-style tools (e.g., ask-questionnaire) where the agent may be
2936
+ // "waiting" but we still want to keep the in-flight tool call UI visible.
1905
2937
  const isRunning = agentData.status === "running" || agentData.status === 1;
2938
+ const isWaiting = agentData.status === "waitingForApproval" ||
2939
+ agentData.status === 2 ||
2940
+ agentData.status === "waitingForInput" ||
2941
+ agentData.status === "costLimitReached" ||
2942
+ agentData.status === 7;
2943
+ const keepLocalStreaming = isRunning || isWaiting;
1906
2944
  // Filter local messages to only include streaming/incomplete messages that aren't in DB
1907
2945
  // This prevents duplicates when reloading - DB messages are source of truth for completed messages
1908
2946
  const localStreaming = isRunning
@@ -1920,7 +2958,19 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1920
2958
  // Don't keep completed messages from local state - DB is source of truth
1921
2959
  return false;
1922
2960
  })
1923
- : [];
2961
+ : keepLocalStreaming
2962
+ ? messagesRef.current.filter((localMsg) => {
2963
+ if (!localMsg.id)
2964
+ return false;
2965
+ if (!localMsg.isCompleted &&
2966
+ localMsg.messageType === "streaming") {
2967
+ const existsInDb = dbMessages.some((dbMsg) => dbMsg.id &&
2968
+ dbMsg.id.toLowerCase() === localMsg.id.toLowerCase());
2969
+ return !existsInDb;
2970
+ }
2971
+ return false;
2972
+ })
2973
+ : [];
1924
2974
  const merged = mergeMessagesById(dbMessages, localStreaming);
1925
2975
  messagesRef.current = merged;
1926
2976
  setMessages(merged);
@@ -1931,6 +2981,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1931
2981
  const runningNow = agentData.status === "running" || agentData.status === 1;
1932
2982
  const waitingForApprovalNow = agentData.status === "waitingForApproval" ||
1933
2983
  agentData.status === 2;
2984
+ const waitingForInputNow = agentData.status === "waitingForInput";
1934
2985
  const hasStreamingNow = merged.some((m) => !m.isCompleted && m.messageType === "streaming");
1935
2986
  if (runningNow || hasStreamingNow) {
1936
2987
  setIsWaitingForResponse(true);
@@ -1938,11 +2989,11 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1938
2989
  // Agent is actively running, show thinking dots
1939
2990
  setIsAgentThinking(true);
1940
2991
  }
1941
- else if (waitingForApprovalNow) {
2992
+ else if (waitingForApprovalNow || waitingForInputNow) {
1942
2993
  setIsWaitingForResponse(false);
1943
2994
  isWaitingRef.current = false;
1944
2995
  setIsConnecting(false);
1945
- // Agent is waiting for user approval, hide thinking dots
2996
+ // Agent is waiting for user input/approval, hide thinking dots
1946
2997
  setIsAgentThinking(false);
1947
2998
  }
1948
2999
  else {
@@ -1991,19 +3042,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
1991
3042
  if (!contextJson)
1992
3043
  return null;
1993
3044
  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
3045
  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;
3046
+ return parsedContext;
2007
3047
  }
2008
3048
  return null;
2009
3049
  }
@@ -2049,6 +3089,21 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2049
3089
  setIsLoading(false);
2050
3090
  }
2051
3091
  }, [agentStub.id]);
3092
+ const reconcileServerStateRef = useRef(null);
3093
+ useEffect(() => {
3094
+ reconcileServerStateRef.current = async () => {
3095
+ if (reconcileServerStateInFlightRef.current) {
3096
+ return;
3097
+ }
3098
+ reconcileServerStateInFlightRef.current = true;
3099
+ try {
3100
+ await loadAgent();
3101
+ }
3102
+ finally {
3103
+ reconcileServerStateInFlightRef.current = false;
3104
+ }
3105
+ };
3106
+ }, [loadAgent]);
2052
3107
  // Initial load
2053
3108
  useEffect(() => {
2054
3109
  loadAgent();
@@ -2070,8 +3125,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2070
3125
  setAgentOperations([]);
2071
3126
  return;
2072
3127
  }
3128
+ setAgentOperations([]);
2073
3129
  try {
2074
- const result = await getAgentHistory(agent.id, 1000);
3130
+ const result = await getAgentHistory(agent.id, AGENT_HISTORY_LIMIT);
2075
3131
  if (result.type === "success") {
2076
3132
  setAgentOperations(result.data ?? []);
2077
3133
  }
@@ -2089,7 +3145,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2089
3145
  return;
2090
3146
  }
2091
3147
  // Check if cost limit exceeded based on status or cost values
2092
- const statusIndicatesLimit = agent.status === "costLimitReached" || agent.status === 7;
3148
+ const statusIndicatesLimit = agent.status === "costLimitReached";
2093
3149
  // Use liveTotals.totalCost as fallback if agent.totalCost is missing or 0
2094
3150
  const effectiveTotalCost = agent.totalCost || liveTotals?.totalCost || 0;
2095
3151
  const costExceedsLimit = agent.costLimit &&
@@ -2174,9 +3230,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2174
3230
  // Handle agent:profile:switched (profile changed via switch-profile function)
2175
3231
  if (messageType === "agent:profile:switched") {
2176
3232
  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;
3233
+ const switchedAgentId = payload.agentId;
3234
+ const newProfileId = payload.newProfileId;
3235
+ const newProfileName = payload.newProfileName;
2180
3236
  if (switchedAgentId === agent.id && newProfileId) {
2181
3237
  // Update the agent's profile
2182
3238
  setAgent((prev) => {
@@ -2207,19 +3263,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2207
3263
  const op = message.payload;
2208
3264
  const operationAgentId = op.agentId || op.user?.agentId;
2209
3265
  if (operationAgentId === agent.id) {
2210
- // Reload operations when this agent performs an operation
2211
- getAgentHistory(agent.id, 1000).then((result) => {
2212
- if (result.type === "success") {
2213
- setAgentOperations(result.data ?? []);
2214
- }
2215
- });
3266
+ setAgentOperations((previous) => mergeAgentOperationHistory(previous, [op]));
2216
3267
  }
2217
3268
  return;
2218
3269
  }
2219
3270
  // 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)
3271
+ const agentId = message.payload?.agentId;
3272
+ if (agentId !== agent.id) {
2222
3273
  return;
3274
+ }
2223
3275
  // Handle agent:run:start
2224
3276
  if (messageType === "agent:run:start") {
2225
3277
  // If a stop operation is in progress, ignore this message to prevent
@@ -2228,6 +3280,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2228
3280
  console.log("[AgentTerminal] Ignoring agent:run:start during stop operation");
2229
3281
  return;
2230
3282
  }
3283
+ const currentStatus = agentRef.current?.status;
3284
+ if (currentStatus === "waitingForInput" ||
3285
+ currentStatus === "waitingForApproval" ||
3286
+ currentStatus === "costLimitReached") {
3287
+ // Replayed start messages arrive before the buffered status payload when
3288
+ // reopening an already-paused agent. Preserve the attention state instead
3289
+ // of flashing "running" until the follow-up status update lands.
3290
+ return;
3291
+ }
2231
3292
  // Reset run-scoped deduplication so new run seq values (starting at 1)
2232
3293
  // are not discarded due to previous run's lastSeqRef
2233
3294
  lastSeqRef.current = 0;
@@ -2323,6 +3384,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2323
3384
  if (messageType === "agent:prompt:queued") {
2324
3385
  const { queueEntry } = message.payload;
2325
3386
  if (queueEntry) {
3387
+ if (shouldSuppressQueuedPrompt(queueEntry)) {
3388
+ return;
3389
+ }
2326
3390
  // Add the new prompt to the queued prompts list
2327
3391
  setQueuedPrompts((prev) => {
2328
3392
  // Check if prompt already exists (deduplication)
@@ -2351,17 +3415,29 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2351
3415
  return;
2352
3416
  }
2353
3417
  const { seq, type, data, cost } = message.payload;
3418
+ if (type === "ToolCall" || type === "toolCall") {
3419
+ }
2354
3420
  // Always allow ContextUpdate messages (metadata updates) regardless of agent status
2355
3421
  const isContextUpdate = type === "ContextUpdate" || type === "contextUpdate";
3422
+ const isHeartbeat = type === "Heartbeat" || type === "heartbeat";
3423
+ const shouldClearHeartbeat = type === "ContentChunk" ||
3424
+ type === "contentChunk" ||
3425
+ type === "ToolCall" ||
3426
+ type === "toolCall" ||
3427
+ type === "ToolResult" ||
3428
+ type === "toolResult";
2356
3429
  // Allow deltas if the agent is running OR if we already have an active streaming message
2357
3430
  // OR if the server provided a messageId for targeted updates.
2358
3431
  // This avoids dropping early deltas that may arrive before the 'running' status update.
2359
3432
  // ContextUpdate messages are always allowed as they're metadata updates, not content.
2360
3433
  const isRunning = agent.status === "running" || agent.status === 1;
2361
3434
  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) {
3435
+ const hasMessageId = !!message?.payload?.data?.messageId;
3436
+ if (!isContextUpdate &&
3437
+ !isHeartbeat &&
3438
+ !isRunning &&
3439
+ !hasStreaming &&
3440
+ !hasMessageId) {
2365
3441
  return; // ignore only if we cannot safely target an existing streaming message
2366
3442
  }
2367
3443
  // Deduplicate by sequence
@@ -2378,15 +3454,23 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2378
3454
  cost,
2379
3455
  timestamp: new Date().toISOString(),
2380
3456
  };
3457
+ if (shouldClearHeartbeat) {
3458
+ clearHeartbeatMessages();
3459
+ }
2381
3460
  if (type === "ContentChunk" || type === "contentChunk") {
2382
3461
  handleContentChunk(agentStreamMessage, agent);
2383
3462
  }
2384
3463
  else if (type === "ToolCall" || type === "toolCall") {
3464
+ 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
3465
  handleToolCall(agentStreamMessage, agent);
2386
3466
  }
2387
3467
  else if (type === "ToolResult" || type === "toolResult") {
3468
+ 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
3469
  handleToolResult(agentStreamMessage, agent);
2389
3470
  }
3471
+ else if (type === "Heartbeat" || type === "heartbeat") {
3472
+ handleHeartbeatMessage(agentStreamMessage);
3473
+ }
2390
3474
  else if (type === "ContextUpdate" || type === "contextUpdate") {
2391
3475
  // Handle context updates from streaming - data contains additionalData.todoList and ChildAgents
2392
3476
  const contextData = data;
@@ -2396,10 +3480,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2396
3480
  const current = (prev || {});
2397
3481
  const updated = { ...current };
2398
3482
  // 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
- {};
3483
+ if (contextData.additionalData) {
3484
+ const sourceAdditionalData = contextData.additionalData || {};
2403
3485
  updated.additionalData = {
2404
3486
  ...(current.additionalData || {}),
2405
3487
  ...sourceAdditionalData,
@@ -2407,10 +3489,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2407
3489
  }
2408
3490
  // Merge ChildAgents if present (for spawned agents) - replace entire array
2409
3491
  // Backend sends the complete updated list, not just additions
2410
- if (contextData.ChildAgents || contextData.childAgents) {
2411
- const childAgents = contextData.ChildAgents || contextData.childAgents;
3492
+ if (contextData.childAgents) {
3493
+ const childAgents = contextData.childAgents;
2412
3494
  if (Array.isArray(childAgents)) {
2413
- updated.ChildAgents = childAgents;
3495
+ updated.childAgents = childAgents;
2414
3496
  }
2415
3497
  }
2416
3498
  return updated;
@@ -2436,15 +3518,19 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2436
3518
  // Route based on statusData.state
2437
3519
  try {
2438
3520
  // 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") {
3521
+ const normalizedStatus = parseAgentStatus(statusData?.state) ||
3522
+ parseAgentStatus(statusData?.status);
3523
+ if (normalizedStatus === "idle") {
3524
+ clearStopGuard();
2444
3525
  // Stop indicators and mark any in-progress streaming messages as completed
3526
+ clearHeartbeatMessages();
3527
+ setAgent((prev) => (prev ? { ...prev, status: "idle" } : prev));
2445
3528
  setIsWaitingForResponse(false);
2446
3529
  isWaitingRef.current = false;
2447
3530
  setIsConnecting(false);
3531
+ setIsSubmitting(false);
3532
+ shouldCreateNewMessage.current = false;
3533
+ setIsAgentThinking(false);
2448
3534
  setMessages((prev) => {
2449
3535
  const updated = prev.map((msg) => !msg.isCompleted && msg.messageType === "streaming"
2450
3536
  ? {
@@ -2456,6 +3542,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2456
3542
  messagesRef.current = updated;
2457
3543
  return updated;
2458
3544
  });
3545
+ void reconcileServerStateRef.current?.();
2459
3546
  return;
2460
3547
  }
2461
3548
  if (statusData?.state === "streamOpen") {
@@ -2487,6 +3574,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2487
3574
  outputCost: Number(totals.totalOutputTokenCost) || 0,
2488
3575
  cachedCost: Number(totals.totalCachedInputTokenCost) || 0,
2489
3576
  cacheWriteCost: Number(totals.totalCacheWriteTokenCost) || 0,
3577
+ imageCost: Number(totals.totalImageCost) || 0,
2490
3578
  totalCost: totalCost,
2491
3579
  currency: totals.currency,
2492
3580
  };
@@ -2498,6 +3586,26 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2498
3586
  if (anyNonZero) {
2499
3587
  setLiveTotals(nextTotals);
2500
3588
  }
3589
+ // Fallback context usage update for providers that do not include
3590
+ // context usage in every stream delta (for example some OpenAI streams).
3591
+ // Prefer explicit status values when present; otherwise derive from totals.
3592
+ const contextWindowValue = Number(statusData?.contextWindow ??
3593
+ agent?.contextWindowTokens ??
3594
+ 0);
3595
+ const contextUsedValue = Number(statusData?.contextUsed ??
3596
+ (Number(totals.totalInputTokens) || 0) +
3597
+ (Number(totals.totalCachedInputTokens) || 0) +
3598
+ (Number(totals.totalCacheWriteTokens) || 0));
3599
+ if (contextWindowValue > 0 &&
3600
+ Number.isFinite(contextUsedValue) &&
3601
+ contextUsedValue >= 0) {
3602
+ setContextWindowStatus({
3603
+ contextWindowTokens: contextWindowValue,
3604
+ estimatedInputTokens: contextUsedValue,
3605
+ contextUsedPercent: (contextUsedValue / contextWindowValue) * 100,
3606
+ model: agent?.model || "",
3607
+ });
3608
+ }
2501
3609
  // If server provides costLimit along with totals, persist it locally
2502
3610
  if (statusCostLimit) {
2503
3611
  setAgent((prev) => prev &&
@@ -2519,6 +3627,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2519
3627
  return;
2520
3628
  }
2521
3629
  if (statusData?.state === "ToolApprovalsRequired") {
3630
+ setPendingBrowserCaptureDialogType(null);
2522
3631
  const msgId = statusData.messageId;
2523
3632
  const ids = statusData.toolCallIds || [];
2524
3633
  if (msgId && Array.isArray(ids) && ids.length > 0) {
@@ -2550,16 +3659,44 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2550
3659
  setIsWaitingForResponse(false);
2551
3660
  return;
2552
3661
  }
2553
- // Handle "WaitingForApproval" state explicitly
2554
- if (statusData?.state === "WaitingForApproval" ||
2555
- statusData?.state === "waitingForApproval") {
3662
+ // Handle waiting states explicitly
3663
+ if (normalizedStatus === "waitingForApproval") {
3664
+ clearStopGuard();
3665
+ setPendingBrowserCaptureDialogType(null);
2556
3666
  setAgent((prev) => prev ? { ...prev, status: "waitingForApproval" } : prev);
2557
3667
  setIsConnecting(false);
2558
3668
  setIsWaitingForResponse(false);
2559
3669
  setIsAgentThinking(false);
3670
+ void reconcileServerStateRef.current?.();
2560
3671
  return;
2561
3672
  }
2562
- if (statusData?.state === "CostLimitReached") {
3673
+ if (normalizedStatus === "waitingForInput") {
3674
+ clearStopGuard();
3675
+ const dialogType = typeof statusData?.dialogType === "string"
3676
+ ? statusData.dialogType
3677
+ : null;
3678
+ const isBrowserCaptureWait = dialogType === DIALOG_TYPES.CAPTURE_PAGE_DOM ||
3679
+ dialogType === DIALOG_TYPES.CAPTURE_PAGE_SCREENSHOT;
3680
+ setPendingBrowserCaptureDialogType(isBrowserCaptureWait ? dialogType : null);
3681
+ setAgent((prev) => prev
3682
+ ? {
3683
+ ...prev,
3684
+ status: "waitingForInput",
3685
+ statusMessage: isBrowserCaptureWait &&
3686
+ typeof statusData?.title === "string" &&
3687
+ statusData.title.trim()
3688
+ ? statusData.title.trim()
3689
+ : prev.statusMessage,
3690
+ }
3691
+ : prev);
3692
+ setIsConnecting(false);
3693
+ setIsWaitingForResponse(false);
3694
+ setIsAgentThinking(false);
3695
+ return;
3696
+ }
3697
+ if (normalizedStatus === "costLimitReached") {
3698
+ clearStopGuard();
3699
+ setPendingBrowserCaptureDialogType(null);
2563
3700
  const totalCost = Number(statusData.totalCost) || 0;
2564
3701
  const costLimit = Number(statusData.costLimit) || agent?.costLimit || 0;
2565
3702
  setCostLimitExceeded({
@@ -2577,7 +3714,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2577
3714
  // Server sends additionalData directly (with todoList inside)
2578
3715
  // Also may include ChildAgents for spawned agents
2579
3716
  try {
2580
- const serverAdditionalData = statusData.additionalData || statusData.AdditionalData || {};
3717
+ const serverAdditionalData = statusData.additionalData || {};
2581
3718
  setAgentMetadata((prev) => {
2582
3719
  const current = (prev || {});
2583
3720
  const updated = { ...current };
@@ -2591,10 +3728,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2591
3728
  }
2592
3729
  // Merge ChildAgents if present (for spawned agents) - replace entire array
2593
3730
  // Backend sends the complete updated list, not just additions
2594
- if (statusData.ChildAgents || statusData.childAgents) {
2595
- const childAgents = statusData.ChildAgents || statusData.childAgents;
3731
+ if (statusData.childAgents) {
3732
+ const childAgents = statusData.childAgents;
2596
3733
  if (Array.isArray(childAgents)) {
2597
- updated.ChildAgents = childAgents;
3734
+ updated.childAgents = childAgents;
2598
3735
  }
2599
3736
  }
2600
3737
  return updated;
@@ -2606,56 +3743,52 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2606
3743
  return;
2607
3744
  }
2608
3745
  // Handle "completed" state (fallback for legacy code paths that send status instead of lifecycle event)
2609
- if (normalizedStatus === "completed" ||
2610
- normalizedStatus === "Completed") {
3746
+ if (normalizedStatus === "completed") {
3747
+ clearStopGuard();
2611
3748
  // Reset deduplication for the next run
2612
3749
  lastSeqRef.current = 0;
3750
+ clearHeartbeatMessages();
2613
3751
  // Mark the last assistant message as completed
2614
3752
  setMessages((prev) => {
2615
3753
  const updated = prev.map((msg) => msg.role === "assistant" && !msg.isCompleted
2616
3754
  ? {
2617
3755
  ...msg,
2618
- isCompleted: true,
2619
- messageType: "completed",
2620
- }
2621
- : msg);
2622
- messagesRef.current = updated;
2623
- return updated;
2624
- });
2625
- // Update agent status to idle
2626
- setAgent((prev) => (prev ? { ...prev, status: "idle" } : prev));
2627
- setIsWaitingForResponse(false);
2628
- isWaitingRef.current = false;
2629
- setIsConnecting(false);
2630
- shouldCreateNewMessage.current = false;
2631
- // Server says agent finished thinking
2632
- setIsAgentThinking(false);
2633
- return;
2634
- }
2635
- // Handle "Idle" state - agent has finished processing
2636
- if (normalizedStatus === "idle" || normalizedStatus === "Idle") {
3756
+ isCompleted: true,
3757
+ messageType: "completed",
3758
+ }
3759
+ : msg);
3760
+ messagesRef.current = updated;
3761
+ return updated;
3762
+ });
2637
3763
  // Update agent status to idle
2638
3764
  setAgent((prev) => (prev ? { ...prev, status: "idle" } : prev));
2639
3765
  setIsWaitingForResponse(false);
2640
3766
  isWaitingRef.current = false;
2641
3767
  setIsConnecting(false);
2642
3768
  shouldCreateNewMessage.current = false;
3769
+ // Server says agent finished thinking
2643
3770
  setIsAgentThinking(false);
2644
3771
  return;
2645
3772
  }
2646
3773
  // 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));
3774
+ if (normalizedStatus === "running") {
3775
+ if (isStoppingRef.current) {
3776
+ return;
3777
+ }
3778
+ // Update agent status to running and clear any previous error statusMessage
3779
+ setAgent((prev) => prev
3780
+ ? { ...prev, status: "running", statusMessage: undefined }
3781
+ : prev);
2651
3782
  setIsWaitingForResponse(true);
2652
3783
  isWaitingRef.current = true;
2653
3784
  setIsAgentThinking(true);
2654
3785
  return;
2655
3786
  }
2656
3787
  // Handle "Error" state
2657
- if (normalizedStatus === "error" || normalizedStatus === "Error") {
2658
- const errorMsg = statusData?.statusMessage || statusData?.error || "Unknown error";
3788
+ if (normalizedStatus === "error") {
3789
+ clearStopGuard();
3790
+ const errorMsg = toUserFacingAgentErrorMessage(statusData?.statusMessage ?? statusData?.error) || "Unknown error";
3791
+ clearHeartbeatMessages();
2659
3792
  setAgent((prev) => prev
2660
3793
  ? { ...prev, status: "error", statusMessage: errorMsg }
2661
3794
  : prev);
@@ -2675,6 +3808,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2675
3808
  if (messageType === "agent:run:complete") {
2676
3809
  // Reset deduplication for the next run
2677
3810
  lastSeqRef.current = 0;
3811
+ clearHeartbeatMessages();
2678
3812
  // Mark the last assistant message as completed
2679
3813
  setMessages((prev) => {
2680
3814
  const updated = prev.map((msg) => msg.role === "assistant" && !msg.isCompleted
@@ -2699,7 +3833,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2699
3833
  }
2700
3834
  // Lifecycle: agent:run:error
2701
3835
  if (messageType === "agent:run:error") {
2702
- const errorMsg = message.payload?.error || "Unknown error";
3836
+ const errorMsg = toUserFacingAgentErrorMessage(message.payload?.error) ||
3837
+ "AI could not complete this request.";
3838
+ clearHeartbeatMessages();
2703
3839
  // Reset deduplication for the next run after an error
2704
3840
  lastSeqRef.current = 0;
2705
3841
  setError(errorMsg);
@@ -2714,7 +3850,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2714
3850
  }
2715
3851
  }, [
2716
3852
  agent,
3853
+ clearHeartbeatMessages,
2717
3854
  handleContentChunk,
3855
+ handleHeartbeatMessage,
2718
3856
  handleToolCall,
2719
3857
  handleToolResult,
2720
3858
  onAgentUpdate,
@@ -2769,6 +3907,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2769
3907
  const isRunning = currentAgent.status === "running" || currentAgent.status === 1;
2770
3908
  const isWaitingForApproval = currentAgent.status === "waitingForApproval" ||
2771
3909
  currentAgent.status === 2;
3910
+ const isWaitingForInput = currentAgent.status === "waitingForInput";
2772
3911
  if (isRunning) {
2773
3912
  setIsWaitingForResponse(true);
2774
3913
  isWaitingRef.current = true;
@@ -2776,10 +3915,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2776
3915
  // Agent is currently running, show thinking dots
2777
3916
  setIsAgentThinking(true);
2778
3917
  }
2779
- else if (isWaitingForApproval) {
3918
+ else if (isWaitingForApproval || isWaitingForInput) {
2780
3919
  setIsWaitingForResponse(false);
2781
3920
  isWaitingRef.current = false;
2782
- // Agent is waiting for user approval, hide thinking dots
3921
+ // Agent is waiting for user input/approval, hide thinking dots
2783
3922
  setIsAgentThinking(false);
2784
3923
  }
2785
3924
  else {
@@ -2826,36 +3965,157 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2826
3965
  window.addEventListener("editor:focusAgentPrompt", focusHandler);
2827
3966
  return () => window.removeEventListener("editor:focusAgentPrompt", focusHandler);
2828
3967
  }, []);
3968
+ // Keep stable refs so we don't miss window events during effect re-runs.
3969
+ const agentIdRefForDialogs = useRef(null);
3970
+ const agentStubIdRefForDialogs = useRef(agentStub.id);
3971
+ const isActiveRefForDialogs = useRef(isActive);
3972
+ const scrollToBottomRefForDialogs = useRef(scrollToBottom);
3973
+ useEffect(() => {
3974
+ agentIdRefForDialogs.current = agent?.id ?? null;
3975
+ }, [agent?.id]);
3976
+ useEffect(() => {
3977
+ const prevId = agentStubIdRefForDialogs.current;
3978
+ agentStubIdRefForDialogs.current = agentStub.id;
3979
+ const visibleRegistry = { ...getVisibleDialogRegistry() };
3980
+ const normalizedPrevId = normalizeDialogAgentId(prevId);
3981
+ if (normalizedPrevId) {
3982
+ delete visibleRegistry[normalizedPrevId];
3983
+ globalThis.__agentDialogVisibleCallbacks = visibleRegistry;
3984
+ }
3985
+ if (prevId && prevId !== agentStub.id) {
3986
+ const orphanedDialog = activeInlineDialogRef.current;
3987
+ if (orphanedDialog) {
3988
+ setActiveInlineDialog(null);
3989
+ window.dispatchEvent(new CustomEvent("agent:dialog:orphaned", {
3990
+ detail: { callbackId: orphanedDialog.request.callbackId },
3991
+ }));
3992
+ }
3993
+ }
3994
+ }, [agentStub.id]);
3995
+ useEffect(() => {
3996
+ isActiveRefForDialogs.current = isActive;
3997
+ }, [isActive]);
3998
+ useEffect(() => {
3999
+ scrollToBottomRefForDialogs.current = scrollToBottom;
4000
+ }, [scrollToBottom]);
2829
4001
  // Listen for agent inline dialog requests from AgentDialogHandler
2830
4002
  useEffect(() => {
4003
+ if (orphanTimeoutRef.current) {
4004
+ clearTimeout(orphanTimeoutRef.current);
4005
+ orphanTimeoutRef.current = null;
4006
+ }
4007
+ const globalListeners = (globalThis.__agentDialogMountedAgents ?? []).filter((x) => typeof x === "string");
4008
+ const normalizedAgentStubId = normalizeDialogAgentId(agentStubIdRefForDialogs.current);
4009
+ if (normalizedAgentStubId &&
4010
+ !globalListeners.includes(normalizedAgentStubId)) {
4011
+ globalListeners.push(normalizedAgentStubId);
4012
+ }
4013
+ globalThis.__agentDialogMountedAgents = globalListeners;
2831
4014
  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) {
4015
+ const detail = event?.detail;
4016
+ const request = detail?.request;
4017
+ const onComplete = detail?.onComplete;
4018
+ const onCancel = detail?.onCancel;
4019
+ const terminalAgentId = normalizeDialogAgentId(agentIdRefForDialogs.current);
4020
+ const terminalAgentStubId = normalizeDialogAgentId(agentStubIdRefForDialogs.current);
4021
+ const isActiveNow = isActiveRefForDialogs.current;
4022
+ if (!request)
4023
+ return;
4024
+ const requestAgentId = normalizeDialogAgentId(request.agentId);
4025
+ const agentMatch = !requestAgentId ||
4026
+ !terminalAgentStubId ||
4027
+ requestAgentId === terminalAgentStubId ||
4028
+ requestAgentId === terminalAgentId;
4029
+ if (!isActiveNow) {
4030
+ return;
4031
+ }
4032
+ if (!request)
4033
+ return;
4034
+ // Only handle dialog requests for this terminal's agent stub
4035
+ if (requestAgentId &&
4036
+ terminalAgentStubId &&
4037
+ requestAgentId !== terminalAgentStubId &&
4038
+ requestAgentId !== terminalAgentId) {
2835
4039
  return;
2836
4040
  }
2837
4041
  console.log("[AgentTerminal] Received inline dialog request:", request);
2838
- setActiveInlineDialog({ request, onComplete, onCancel });
4042
+ setActiveInlineDialog({
4043
+ request,
4044
+ onComplete: (result) => {
4045
+ onComplete(result);
4046
+ onInteractionSubmitted?.();
4047
+ },
4048
+ onCancel: () => {
4049
+ onCancel();
4050
+ onInteractionSubmitted?.();
4051
+ },
4052
+ });
2839
4053
  // 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);
4054
+ if (request.callbackId) {
4055
+ window.dispatchEvent(new CustomEvent("agent:dialog:accepted", {
4056
+ detail: { callbackId: request.callbackId },
4057
+ }));
4058
+ }
4059
+ setTimeout(() => {
4060
+ if (request.dialogType === "questionnaire") {
4061
+ scrollToBottomRefForDialogs.current?.();
4062
+ return;
4063
+ }
4064
+ scrollToBottomRefForDialogs.current?.();
4065
+ }, 100);
2845
4066
  };
2846
4067
  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
4068
+ return () => {
4069
+ const mounted = (globalThis.__agentDialogMountedAgents ?? []).filter((x) => typeof x === "string");
4070
+ globalThis.__agentDialogMountedAgents = mounted.filter((id) => id !== normalizeDialogAgentId(agentStubIdRefForDialogs.current));
4071
+ const visibleRegistry = { ...getVisibleDialogRegistry() };
4072
+ const idsToClear = [
4073
+ normalizeDialogAgentId(agentStubIdRefForDialogs.current),
4074
+ normalizeDialogAgentId(agentIdRefForDialogs.current),
4075
+ ].filter(Boolean);
4076
+ idsToClear.forEach((id) => delete visibleRegistry[id]);
4077
+ globalThis.__agentDialogVisibleCallbacks = visibleRegistry;
4078
+ // If unmounting with an active dialog, defer the orphan event so that
4079
+ // React strict mode remounts can cancel it. Only real unmounts fire.
4080
+ const orphanedDialog = activeInlineDialogRef.current;
4081
+ if (orphanedDialog) {
4082
+ orphanTimeoutRef.current = setTimeout(() => {
4083
+ orphanTimeoutRef.current = null;
4084
+ window.dispatchEvent(new CustomEvent("agent:dialog:orphaned", {
4085
+ detail: { callbackId: orphanedDialog.request.callbackId },
4086
+ }));
4087
+ }, 300);
4088
+ }
4089
+ window.removeEventListener("agent:dialog:show", handleDialogShow);
4090
+ };
4091
+ }, []);
4092
+ // Announce when this terminal is ready to accept dialogs.
4093
+ // Fire immediately on mount using agentStub.id so the AgentDialogHandler
4094
+ // can re-dispatch pending dialogs without waiting for the async agent load.
4095
+ // Also fire when agent?.id becomes available in case it differs from the stub.
2851
4096
  useEffect(() => {
2852
- if (agent?.id) {
2853
- console.log("[AgentTerminal] Terminal ready for agent:", agent.id);
4097
+ const normalizedStubId = normalizeDialogAgentId(agentStub.id);
4098
+ if (normalizedStubId) {
2854
4099
  window.dispatchEvent(new CustomEvent("agent:terminal:ready", {
2855
- detail: { agentId: agent.id },
4100
+ detail: {
4101
+ agentId: normalizedStubId,
4102
+ terminalInstanceId: dialogTerminalInstanceIdRef.current,
4103
+ },
2856
4104
  }));
2857
4105
  }
2858
- }, [agent?.id]);
4106
+ }, [agentStub.id]);
4107
+ useEffect(() => {
4108
+ const normalizedAgentId = normalizeDialogAgentId(agent?.id);
4109
+ const normalizedStubId = normalizeDialogAgentId(agentStub.id);
4110
+ if (normalizedAgentId && normalizedAgentId !== normalizedStubId) {
4111
+ window.dispatchEvent(new CustomEvent("agent:terminal:ready", {
4112
+ detail: {
4113
+ agentId: normalizedAgentId,
4114
+ terminalInstanceId: dialogTerminalInstanceIdRef.current,
4115
+ },
4116
+ }));
4117
+ }
4118
+ }, [agent?.id, agentStub.id]);
2859
4119
  // Profiles are provided by parent component (Agents). No local loading here.
2860
4120
  // Select active profile based on agent.profileId or agentStub.profileId
2861
4121
  useEffect(() => {
@@ -2866,20 +4126,47 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2866
4126
  // Use case-insensitive comparison for GUID matching (backend may return different casing)
2867
4127
  const normalizedProfileId = profileIdToUse?.toLowerCase();
2868
4128
  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);
4129
+ ? profiles.find((p) => p.id?.toLowerCase() === normalizedProfileId)
4130
+ : undefined;
4131
+ if (!candidate) {
4132
+ setActiveProfile(undefined);
4133
+ return;
2874
4134
  }
2875
- }, [profiles, agent?.profileId, agentStub.profileId]);
4135
+ // Keep active profile in sync whenever the matching entry in `profiles` changes
4136
+ // not only when the profile id changes. Otherwise availableSkills (and similar fields)
4137
+ // that are merged in the parent after async loads never update (e.g. Template Builder
4138
+ // settings skills merged into the profile).
4139
+ setActiveProfile((prev) => {
4140
+ if (!prev || prev.id !== candidate.id)
4141
+ return candidate;
4142
+ const skillKey = (p) => JSON.stringify({
4143
+ allowed: (p.allowedSkills ?? []).map((s) => [
4144
+ String(s?.id ?? ""),
4145
+ String(s?.name ?? ""),
4146
+ ]),
4147
+ available: (p.availableSkills ?? []).map((s) => [
4148
+ String(s?.id ?? ""),
4149
+ String(s?.name ?? ""),
4150
+ ]),
4151
+ });
4152
+ if (skillKey(prev) === skillKey(candidate))
4153
+ return prev;
4154
+ return candidate;
4155
+ });
4156
+ }, [
4157
+ profiles,
4158
+ agent?.id,
4159
+ agent?.profileId,
4160
+ agentStub.id,
4161
+ agentStub.profileId,
4162
+ ]);
2876
4163
  // Clear queued prompts when agent changes or is new;
2877
4164
  // initial fetch is handled by loadAgent() for better performance and reliability
2878
4165
  useEffect(() => {
2879
- if (!agent?.id || agent.status === "new") {
4166
+ if (!agent?.id || isLocalOnlyDraftAgent) {
2880
4167
  setQueuedPrompts([]);
2881
4168
  }
2882
- }, [agent?.id, agent?.status]);
4169
+ }, [agent?.id, isLocalOnlyDraftAgent]);
2883
4170
  // Update selected model when the active profile or agent model changes
2884
4171
  useEffect(() => {
2885
4172
  if (!activeProfile)
@@ -2908,20 +4195,16 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2908
4195
  // Initialize mode from metadata; fall back to agent.Mode from server
2909
4196
  useEffect(() => {
2910
4197
  try {
2911
- const metaMode = agentMetadata?.mode;
2912
- if (metaMode === "autonomous" ||
2913
- metaMode === "read-only" ||
2914
- metaMode === "supervised") {
4198
+ const metaMode = normalizeAgentMode(agentMetadata?.mode);
4199
+ if (metaMode) {
2915
4200
  setMode(metaMode);
2916
4201
  return;
2917
4202
  }
2918
4203
  }
2919
4204
  catch { }
2920
4205
  try {
2921
- const serverMode = agent?.mode;
2922
- if (serverMode === "autonomous" ||
2923
- serverMode === "read-only" ||
2924
- serverMode === "supervised") {
4206
+ const serverMode = normalizeAgentMode(agent?.mode);
4207
+ if (serverMode) {
2925
4208
  setMode(serverMode);
2926
4209
  }
2927
4210
  }
@@ -2941,7 +4224,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2941
4224
  textareaRef.current.focus();
2942
4225
  }
2943
4226
  }, [messages, activePlaceholderInput]);
2944
- // Persist any pending settings (mode/model) once an agent exists server-side
4227
+ // Persist any pending settings (mode/model/profile) once an agent exists server-side
2945
4228
  const persistPendingSettingsIfNeeded = useCallback(async () => {
2946
4229
  try {
2947
4230
  if (!agent?.id)
@@ -2954,6 +4237,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2954
4237
  payload.model = pending.modelName;
2955
4238
  if (pending.mode)
2956
4239
  payload.mode = pending.mode;
4240
+ if (pending.profileId)
4241
+ payload.profileId = pending.profileId;
4242
+ if (pending.profileName != null)
4243
+ payload.profileName = pending.profileName;
2957
4244
  if (Object.keys(payload).length === 0)
2958
4245
  return;
2959
4246
  await updateAgentSettings(agent.id, payload);
@@ -2963,6 +4250,92 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
2963
4250
  console.error("Failed to persist pending settings", e);
2964
4251
  }
2965
4252
  }, [agent?.id]);
4253
+ const getPendingRequestSettings = useCallback(() => {
4254
+ const pending = pendingSettingsRef.current;
4255
+ const requestProfile = pending?.profileId
4256
+ ? profiles.find((profile) => profile.id === pending.profileId) ||
4257
+ activeProfile ||
4258
+ profiles[0]
4259
+ : activeProfile || profiles[0];
4260
+ let requestModelId = selectedModelId;
4261
+ if (pending?.modelName) {
4262
+ const requestModel = (requestProfile?.models || []).find((model) => (model.name || "").trim().toLowerCase() ===
4263
+ pending.modelName?.trim().toLowerCase());
4264
+ requestModelId = requestModel?.id || requestModelId;
4265
+ }
4266
+ return {
4267
+ mode: (pending?.mode || mode),
4268
+ profileId: pending?.profileId || requestProfile?.id || "",
4269
+ profileName: pending?.profileName || requestProfile?.name || "",
4270
+ modelId: requestModelId,
4271
+ };
4272
+ }, [activeProfile, mode, profiles, selectedModelId]);
4273
+ const getSubmitErrorMessage = (error) => {
4274
+ const fallback = "Failed to submit prompt. Please try again.";
4275
+ if (!(error instanceof Error))
4276
+ return fallback;
4277
+ const cleaned = toUserFacingAgentErrorMessage(error.message);
4278
+ return cleaned || fallback;
4279
+ };
4280
+ const suppressedQueuedPromptsRef = useRef([]);
4281
+ const pruneSuppressedQueuedPrompts = useCallback(() => {
4282
+ const cutoff = Date.now() - 15_000;
4283
+ suppressedQueuedPromptsRef.current =
4284
+ suppressedQueuedPromptsRef.current.filter((entry) => entry.createdAt >= cutoff);
4285
+ }, []);
4286
+ const registerSuppressedQueuedPrompt = useCallback((agentId, promptText) => {
4287
+ const normalizedAgentId = agentId.trim().toLowerCase();
4288
+ const normalizedPrompt = promptText.trim();
4289
+ if (!normalizedAgentId || !normalizedPrompt) {
4290
+ return null;
4291
+ }
4292
+ pruneSuppressedQueuedPrompts();
4293
+ const token = crypto.randomUUID();
4294
+ suppressedQueuedPromptsRef.current = [
4295
+ ...suppressedQueuedPromptsRef.current,
4296
+ {
4297
+ token,
4298
+ agentId: normalizedAgentId,
4299
+ prompt: normalizedPrompt,
4300
+ createdAt: Date.now(),
4301
+ },
4302
+ ];
4303
+ return token;
4304
+ }, [pruneSuppressedQueuedPrompts]);
4305
+ const clearSuppressedQueuedPrompt = useCallback((token) => {
4306
+ if (!token) {
4307
+ return;
4308
+ }
4309
+ suppressedQueuedPromptsRef.current =
4310
+ suppressedQueuedPromptsRef.current.filter((entry) => entry.token !== token);
4311
+ }, []);
4312
+ const shouldSuppressQueuedPrompt = useCallback((queueEntry) => {
4313
+ const normalizedAgentId = queueEntry?.targetAgentId?.trim().toLowerCase();
4314
+ const normalizedPrompt = queueEntry?.prompt?.trim();
4315
+ if (!normalizedAgentId || !normalizedPrompt) {
4316
+ return false;
4317
+ }
4318
+ pruneSuppressedQueuedPrompts();
4319
+ const matchedEntry = suppressedQueuedPromptsRef.current.find((entry) => entry.agentId === normalizedAgentId &&
4320
+ entry.prompt === normalizedPrompt);
4321
+ if (!matchedEntry) {
4322
+ return false;
4323
+ }
4324
+ suppressedQueuedPromptsRef.current =
4325
+ suppressedQueuedPromptsRef.current.filter((entry) => entry.token !== matchedEntry.token);
4326
+ return true;
4327
+ }, [pruneSuppressedQueuedPrompts]);
4328
+ const cancelActiveInlineDialog = useCallback(() => {
4329
+ const activeDialog = activeInlineDialogRef.current;
4330
+ if (!activeDialog)
4331
+ return;
4332
+ try {
4333
+ activeDialog.onCancel();
4334
+ }
4335
+ finally {
4336
+ setActiveInlineDialog(null);
4337
+ }
4338
+ }, []);
2966
4339
  const handleSubmit = async () => {
2967
4340
  // Guard against double-submit and missing context
2968
4341
  if (isSubmitting) {
@@ -3006,6 +4379,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3006
4379
  setError("Agent not ready. Please try again.");
3007
4380
  return;
3008
4381
  }
4382
+ clearStopGuard();
4383
+ const hadQuestionnaireDialogOpen = activeInlineDialogRef.current?.request.dialogType === "questionnaire";
4384
+ const suppressedQueuedPromptToken = hadQuestionnaireDialogOpen && savedPrompt
4385
+ ? registerSuppressedQueuedPrompt(agentId, savedPrompt)
4386
+ : null;
4387
+ // A new user prompt supersedes any active questionnaire/inline dialog.
4388
+ cancelActiveInlineDialog();
3009
4389
  // Generate a temporary ID for optimistic UI - will be replaced by server ID
3010
4390
  const tempMessageId = `temp-${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;
3011
4391
  try {
@@ -3092,26 +4472,24 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3092
4472
  console.warn("[AgentTerminal] Failed to compute live context:", e);
3093
4473
  }
3094
4474
  // Add visible test IDs to context (only for Help agent)
3095
- const currentProfileName = activeProfile?.name || profiles[0]?.name || "";
4475
+ const requestSettings = getPendingRequestSettings();
4476
+ const currentProfileName = requestSettings.profileName;
3096
4477
  effectiveContext = addVisibleTestIdsToContext(effectiveContext, currentProfileName);
3097
4478
  const request = {
3098
4479
  agentId: agentId,
3099
4480
  message: savedPrompt,
3100
4481
  sessionId: editContext.sessionId,
3101
- profileId: activeProfile?.id || profiles[0]?.id || "",
4482
+ profileId: requestSettings.profileId,
3102
4483
  profile: currentProfileName,
3103
- model: selectedModelId,
3104
- mode: mode,
4484
+ model: requestSettings.modelId,
4485
+ mode: requestSettings.mode,
3105
4486
  context: canonicalizeAgentMetadata(effectiveContext), // Use fresh live context when in live mode
3106
- deterministic: deterministicFlags.deterministic,
3107
- seed: deterministicFlags.seed,
3108
4487
  };
3109
4488
  console.log("[AgentTerminal] Calling startAgent API for agent:", agentId);
3110
4489
  const response = await startAgent(request);
3111
4490
  console.log("[AgentTerminal] startAgent response:", response);
3112
4491
  // Check if prompt was queued (agent was already running)
3113
- const wasQueued = response.message?.toLowerCase().includes("queued") ||
3114
- response.status === "Queued";
4492
+ const wasQueued = response.message?.toLowerCase().includes("queued");
3115
4493
  if (wasQueued) {
3116
4494
  // Prompt was queued - show a brief notification but don't set waiting state
3117
4495
  // The prompt will be processed when the agent becomes idle
@@ -3121,6 +4499,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3121
4499
  isWaitingRef.current = false;
3122
4500
  }
3123
4501
  else {
4502
+ clearSuppressedQueuedPrompt(suppressedQueuedPromptToken);
3124
4503
  // Normal submission - set waiting state to show dancing dots immediately
3125
4504
  setIsWaitingForResponse(true);
3126
4505
  isWaitingRef.current = true;
@@ -3135,11 +4514,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3135
4514
  ...prev.filter((p) => p !== savedPrompt).slice(0, 9),
3136
4515
  ]);
3137
4516
  setCurrentHistoryIndex(-1);
4517
+ await onInteractionSubmitted?.();
3138
4518
  // WebSocket connection is already active via subscription - no need for SSE
3139
4519
  }
3140
4520
  catch (err) {
3141
4521
  console.error("[AgentTerminal] Failed to submit prompt:", err);
3142
- setError("Failed to submit prompt. Please try again.");
4522
+ clearSuppressedQueuedPrompt(suppressedQueuedPromptToken);
4523
+ setError(getSubmitErrorMessage(err));
3143
4524
  setIsWaitingForResponse(false);
3144
4525
  isWaitingRef.current = false;
3145
4526
  // Remove the optimistic user message on API error
@@ -3349,29 +4730,29 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3349
4730
  console.warn("[AgentTerminal] Failed to compute live context for quick message:", e);
3350
4731
  }
3351
4732
  // Add visible test IDs to context (only for Help agent)
3352
- const currentProfileName = activeProfile?.name || profiles[0]?.name || "";
4733
+ const requestSettings = getPendingRequestSettings();
4734
+ const currentProfileName = requestSettings.profileName;
3353
4735
  effectiveContext = addVisibleTestIdsToContext(effectiveContext, currentProfileName);
3354
4736
  const request = {
3355
4737
  agentId: agent.id,
3356
4738
  message: savedPrompt,
3357
4739
  sessionId: editContext.sessionId,
3358
- profileId: activeProfile?.id || profiles[0]?.id || "",
4740
+ profileId: requestSettings.profileId,
3359
4741
  profile: currentProfileName,
3360
- model: selectedModelId,
3361
- mode: mode,
4742
+ model: requestSettings.modelId,
4743
+ mode: requestSettings.mode,
3362
4744
  context: canonicalizeAgentMetadata(effectiveContext), // Use fresh live context when in live mode
3363
- deterministic: deterministicFlags.deterministic,
3364
- seed: deterministicFlags.seed,
3365
4745
  };
3366
4746
  console.log("[AgentTerminal] Calling startAgent API for quick message");
3367
4747
  await startAgent(request);
3368
4748
  // If user changed mode/model while the agent was new, persist them now
3369
4749
  await persistPendingSettingsIfNeeded();
4750
+ await onInteractionSubmitted?.();
3370
4751
  // WebSocket connection is already active via subscription - no need for SSE
3371
4752
  }
3372
4753
  catch (err) {
3373
4754
  console.error("[AgentTerminal] Failed to submit quick message:", err);
3374
- setError("Failed to submit prompt. Please try again.");
4755
+ setError(getSubmitErrorMessage(err));
3375
4756
  setIsWaitingForResponse(false);
3376
4757
  isWaitingRef.current = false;
3377
4758
  // Remove the optimistic user message on API error
@@ -3549,66 +4930,90 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3549
4930
  }
3550
4931
  return context;
3551
4932
  }, [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
- },
4933
+ const buildPageContextItem = useCallback((item) => ({
4934
+ id: item.id,
4935
+ language: item.language,
4936
+ version: item.version,
4937
+ name: editContext?.item?.name,
4938
+ path: editContext?.item?.path,
4939
+ }), [editContext?.item?.name, editContext?.item?.path]);
4940
+ const buildComponentContext = useCallback((item) => {
4941
+ if (!editContext?.selection?.length)
4942
+ return undefined;
4943
+ return editContext.selection.map((componentId) => {
4944
+ let componentName;
4945
+ let componentType;
4946
+ let renderingItemId;
4947
+ if (editContext.page) {
4948
+ try {
4949
+ const component = getComponentById(componentId, editContext.page);
4950
+ componentName =
4951
+ component?.datasourceItem?.name || component?.name || undefined;
4952
+ componentType = component?.type || undefined;
4953
+ renderingItemId = component?.rendering?.id || undefined;
3595
4954
  }
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,
4955
+ catch { }
4956
+ }
4957
+ return {
4958
+ componentId,
4959
+ componentName,
4960
+ componentType,
4961
+ renderingItemId,
4962
+ pageItem: buildPageContextItem(item),
4963
+ };
4964
+ });
4965
+ }, [buildPageContextItem, editContext?.page, editContext?.selection]);
4966
+ const buildFieldContext = useCallback(() => {
4967
+ const focusedField = fieldsContext?.focusedField;
4968
+ if (!focusedField?.fieldId || !focusedField?.item?.id)
4969
+ return undefined;
4970
+ const fieldItem = focusedField.item;
4971
+ const currentItem = editContext?.currentItemDescriptor;
4972
+ let fieldItemName = fieldItem.id === currentItem?.id ? editContext?.item?.name : undefined;
4973
+ if (!fieldItemName && editContext?.page) {
4974
+ try {
4975
+ const component = getComponentById(fieldItem.id, editContext.page);
4976
+ fieldItemName =
4977
+ component?.datasourceItem?.name || component?.name || undefined;
4978
+ }
4979
+ catch { }
4980
+ }
4981
+ return {
4982
+ fieldId: focusedField.fieldId,
4983
+ fieldName: focusedField.fieldName,
4984
+ item: {
4985
+ id: fieldItem.id,
4986
+ language: fieldItem.language || currentItem?.language || "en",
4987
+ version: fieldItem.version ?? currentItem?.version ?? 0,
4988
+ name: fieldItem.name || fieldItemName,
4989
+ },
3602
4990
  };
3603
4991
  }, [
3604
4992
  editContext?.currentItemDescriptor,
3605
- editContext?.selection,
3606
- editContext?.workspaceId,
3607
4993
  editContext?.item?.name,
3608
- editContext?.activeSlotId,
3609
- editContext?.openSidebars,
4994
+ editContext?.page,
3610
4995
  fieldsContext?.focusedField,
3611
4996
  ]);
4997
+ const buildEditorContextPayload = useCallback((item) => ({
4998
+ items: item ? [buildPageContextItem(item)] : undefined,
4999
+ currentItemId: item?.id,
5000
+ components: item ? buildComponentContext(item) : undefined,
5001
+ field: buildFieldContext(),
5002
+ activeWorkspace: editContext?.workspaceId,
5003
+ hasPageLoaded: !!editContext?.getActiveSlotContext()?.primaryPageViewContext?.page,
5004
+ openSidebars: editContext?.openSidebars,
5005
+ }), [
5006
+ buildComponentContext,
5007
+ buildFieldContext,
5008
+ buildPageContextItem,
5009
+ editContext,
5010
+ ]);
5011
+ // Helper function to build current context from editor state
5012
+ const buildCurrentContext = useCallback(() => {
5013
+ // Return context even without item - we want view info regardless
5014
+ const item = editContext?.currentItemDescriptor;
5015
+ return buildEditorContextPayload(item);
5016
+ }, [buildEditorContextPayload, editContext?.currentItemDescriptor]);
3612
5017
  // Live context updates: watch for changes and update agent context when in "live" mode
3613
5018
  const previousContextRef = useRef("");
3614
5019
  useEffect(() => {
@@ -3708,6 +5113,15 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3708
5113
  const handleRefreshContext = useCallback(async () => {
3709
5114
  if (!agent?.id)
3710
5115
  return;
5116
+ const normalizedAgentProfileId = agent?.profileId?.toLowerCase();
5117
+ const refreshProfile = activeProfile ||
5118
+ profiles.find((p) => p.id?.toLowerCase() === normalizedAgentProfileId);
5119
+ if (refreshProfile?.editorContextMode === "none") {
5120
+ editContext?.showInfoToast({
5121
+ summary: "This profile excludes editor context.",
5122
+ });
5123
+ return;
5124
+ }
3711
5125
  try {
3712
5126
  const currentCtx = buildCurrentContext();
3713
5127
  if (!currentCtx) {
@@ -3748,20 +5162,90 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3748
5162
  buildCurrentContext,
3749
5163
  sanitizeAgentMetadata,
3750
5164
  editContext,
5165
+ activeProfile,
5166
+ profiles,
3751
5167
  ]);
5168
+ const browserCaptureClaim = useMemo(() => getBrowserCaptureClaim(agentMetadata), [agentMetadata]);
5169
+ const isPendingBrowserCaptureWait = pendingBrowserCaptureDialogType === DIALOG_TYPES.CAPTURE_PAGE_DOM ||
5170
+ pendingBrowserCaptureDialogType === DIALOG_TYPES.CAPTURE_PAGE_SCREENSHOT;
5171
+ const currentSessionId = editContext?.sessionId?.trim() || "";
5172
+ const claimedSessionId = browserCaptureClaim?.sessionId?.trim() || "";
5173
+ const isClaimedByCurrentSession = !!currentSessionId &&
5174
+ !!claimedSessionId &&
5175
+ currentSessionId.toLowerCase() === claimedSessionId.toLowerCase();
5176
+ const isClaimedByAnotherBrowser = !!claimedSessionId && !isClaimedByCurrentSession;
5177
+ const handleClaimBrowser = useCallback(async (takeOver) => {
5178
+ if (!agent?.id || !editContext?.sessionId)
5179
+ return;
5180
+ setIsBrowserClaimMutationPending(true);
5181
+ try {
5182
+ const response = await claimAgentBrowser({
5183
+ agentId: agent.id,
5184
+ sessionId: editContext.sessionId,
5185
+ takeOver,
5186
+ terminalInstanceId: dialogTerminalInstanceIdRef.current,
5187
+ });
5188
+ setAgentMetadata((prev) => sanitizeAgentMetadata(setBrowserCaptureClaim(prev, response.claim || null)));
5189
+ }
5190
+ catch (err) {
5191
+ console.error("[AgentTerminal] Failed to claim browser:", err);
5192
+ editContext.showErrorToast(err);
5193
+ }
5194
+ finally {
5195
+ setIsBrowserClaimMutationPending(false);
5196
+ }
5197
+ }, [agent?.id, editContext, sanitizeAgentMetadata]);
5198
+ const handleReleaseBrowser = useCallback(async () => {
5199
+ if (!agent?.id || !editContext?.sessionId)
5200
+ return;
5201
+ setIsBrowserClaimMutationPending(true);
5202
+ try {
5203
+ const response = await releaseAgentBrowser({
5204
+ agentId: agent.id,
5205
+ sessionId: editContext.sessionId,
5206
+ terminalInstanceId: dialogTerminalInstanceIdRef.current,
5207
+ });
5208
+ setAgentMetadata((prev) => sanitizeAgentMetadata(setBrowserCaptureClaim(prev, response.claim || null)));
5209
+ }
5210
+ catch (err) {
5211
+ console.error("[AgentTerminal] Failed to release browser:", err);
5212
+ editContext.showErrorToast(err);
5213
+ }
5214
+ finally {
5215
+ setIsBrowserClaimMutationPending(false);
5216
+ }
5217
+ }, [agent?.id, editContext, sanitizeAgentMetadata]);
5218
+ useEffect(() => {
5219
+ return () => {
5220
+ if (!agent?.id || !editContext?.sessionId || !isClaimedByCurrentSession) {
5221
+ return;
5222
+ }
5223
+ void releaseAgentBrowser({
5224
+ agentId: agent.id,
5225
+ sessionId: editContext.sessionId,
5226
+ terminalInstanceId: dialogTerminalInstanceIdRef.current,
5227
+ }).catch((error) => {
5228
+ console.warn("[AgentTerminal] Failed to release browser on unmount:", error);
5229
+ });
5230
+ };
5231
+ }, [agent?.id, editContext?.sessionId, isClaimedByCurrentSession]);
3752
5232
  // Stop current execution/stream safely
3753
5233
  const handleStop = useCallback(async () => {
3754
5234
  try {
3755
5235
  // 1. Set the stopping guard to prevent WebSocket handlers from re-enabling states
3756
5236
  // This must happen FIRST, before any other state changes
3757
- isStoppingRef.current = true;
5237
+ armStopGuard();
3758
5238
  // 2. Update all UI state to reflect stopped status
3759
5239
  setIsWaitingForResponse(false);
3760
5240
  isWaitingRef.current = false;
3761
5241
  setIsConnecting(false);
3762
5242
  setIsSubmitting(false);
5243
+ setAgent((prev) => prev ? { ...prev, status: "idle", statusMessage: undefined } : prev);
5244
+ shouldCreateNewMessage.current = false;
3763
5245
  // User stopped the agent, hide thinking dots
3764
5246
  setIsAgentThinking(false);
5247
+ // Stopping an agent discards queued follow-up prompts for that run.
5248
+ setQueuedPrompts([]);
3765
5249
  // 3. Mark any in-progress streaming messages as completed in UI
3766
5250
  setMessages((prev) => {
3767
5251
  const updated = prev.map((msg) => !msg.isCompleted && msg.messageType === "streaming"
@@ -3779,46 +5263,111 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3779
5263
  catch (err) {
3780
5264
  console.error("Failed to cancel agent on backend:", err);
3781
5265
  // Continue - UI is already in stopped state, but backend may still be running
3782
- // The isStoppingRef guard will be cleared below so future runs can proceed
5266
+ // The stop guard has its own timeout so future runs can proceed.
3783
5267
  }
3784
5268
  }
3785
5269
  }
3786
5270
  catch (e) {
3787
5271
  console.error("Failed to stop agent execution", e);
3788
5272
  }
3789
- finally {
3790
- // Clear the stopping guard so future runs can proceed normally
3791
- // This happens after backend confirmation (or error)
3792
- isStoppingRef.current = false;
3793
- }
3794
5273
  }, []);
3795
- // Determine effective cost limit from agent, profile, or metadata so the cost display
3796
- // is visible immediately even before any messages or server-side persistence.
3797
- let effectiveCostLimit;
3798
- try {
3799
- const candidates = [
3800
- agent?.costLimit,
3801
- activeProfile?.costLimit,
3802
- ];
3803
- for (const c of candidates) {
3804
- const n = c != null ? Number(c) : 0;
3805
- if (Number.isFinite(n) && n > 0) {
3806
- effectiveCostLimit = n;
3807
- break;
3808
- }
5274
+ // Cost limit is owned by the persisted agent record. Profiles can provide defaults
5275
+ // during creation, but once an agent exists the frontend should only display the
5276
+ // backend-provided value for that specific agent.
5277
+ const agentCostLimit = (() => {
5278
+ try {
5279
+ const value = agent?.costLimit;
5280
+ const parsed = value != null ? Number(value) : 0;
5281
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : undefined;
3809
5282
  }
3810
- }
3811
- catch { }
3812
- if (effectiveCostLimit === undefined) {
3813
- effectiveCostLimit = undefined;
3814
- }
3815
- // Calculate total token usage for cost display
3816
- const totalTokens = calculateTotalTokens(messages);
5283
+ catch {
5284
+ return undefined;
5285
+ }
5286
+ })();
5287
+ // Calculate total token usage for cost display.
5288
+ // Message rows do not currently persist imageCost, so on refresh we fall back
5289
+ // to the persisted agent aggregate for that single field.
5290
+ const totalTokens = (() => {
5291
+ const totals = calculateTotalTokens(messages);
5292
+ return {
5293
+ ...totals,
5294
+ imageCost: totals.imageCost || Number(agent?.totalImageCost) || 0,
5295
+ };
5296
+ })();
5297
+ const normalizedAgentStatus = parseAgentStatus(agent?.status);
5298
+ const hasSettledToNonExecutingStatus = normalizedAgentStatus === "idle" ||
5299
+ normalizedAgentStatus === "completed" ||
5300
+ normalizedAgentStatus === "error" ||
5301
+ normalizedAgentStatus === "closed" ||
5302
+ normalizedAgentStatus === "waitingForApproval" ||
5303
+ normalizedAgentStatus === "waitingForInput" ||
5304
+ normalizedAgentStatus === "costLimitReached";
3817
5305
  // Determine if the agent is actively executing (submitting, connecting, waiting, or streaming)
3818
- const isExecuting = isSubmitting ||
3819
- isConnecting ||
3820
- isWaitingForResponse ||
3821
- hasActiveStreaming();
5306
+ const isExecuting = !isStopGuardActive &&
5307
+ (isSubmitting ||
5308
+ isConnecting ||
5309
+ (!hasSettledToNonExecutingStatus &&
5310
+ (isWaitingForResponse || hasActiveStreaming())));
5311
+ const assistantMessageCount = useMemo(() => messages.filter((message) => message.role === "assistant").length, [messages]);
5312
+ const messagesWithToolCallsCount = useMemo(() => messages.filter((message) => (message.toolCalls || []).length > 0).length, [messages]);
5313
+ const totalToolCallCount = useMemo(() => messages.reduce((sum, message) => sum + (message.toolCalls?.length || 0), 0), [messages]);
5314
+ const incompleteToolCallCount = useMemo(() => messages.reduce((sum, message) => sum +
5315
+ (message.toolCalls?.filter((toolCall) => !toolCall.isCompleted).length || 0), 0), [messages]);
5316
+ const assistantGroupsWithRenderableToolCalls = useMemo(() => {
5317
+ const groups = groupConsecutiveMessages(messages);
5318
+ return groups.filter((group) => {
5319
+ if (group.type !== "assistant-group")
5320
+ return false;
5321
+ const convertedMessages = convertAgentMessagesToAiFormat(group.messages);
5322
+ return convertedMessages.some((message) => (message.tool_calls?.length || 0) > 0);
5323
+ }).length;
5324
+ }, [messages]);
5325
+ const assistantGroupCount = useMemo(() => groupConsecutiveMessages(messages).filter((group) => group.type === "assistant-group").length, [messages]);
5326
+ const runDiagnosticsSnapshot = useMemo(() => {
5327
+ const lastEvent = recentAgentRunEvents[recentAgentRunEvents.length - 1];
5328
+ return {
5329
+ agentId: currentAgentId,
5330
+ isSubmitting,
5331
+ isConnecting,
5332
+ isWaitingForResponse,
5333
+ isAgentThinking,
5334
+ isExecuting,
5335
+ hasActiveStreaming: hasActiveStreaming(),
5336
+ isSubscribed: normalizeDialogAgentId(subscribedAgentIdRef.current) ===
5337
+ normalizeDialogAgentId(currentAgentId),
5338
+ lastSeq: lastSeqRef.current,
5339
+ lastEventType: lastEvent?.type ?? null,
5340
+ lastEventAt: lastEvent?.timestamp ?? null,
5341
+ recentEvents: recentAgentRunEvents,
5342
+ assistantMessageCount,
5343
+ assistantGroupCount,
5344
+ assistantGroupsWithRenderableToolCalls,
5345
+ messagesWithToolCalls: messagesWithToolCallsCount,
5346
+ totalToolCallCount,
5347
+ incompleteToolCallCount,
5348
+ recentToolUiEvents,
5349
+ };
5350
+ }, [
5351
+ assistantGroupCount,
5352
+ assistantGroupsWithRenderableToolCalls,
5353
+ assistantMessageCount,
5354
+ currentAgentId,
5355
+ hasActiveStreaming,
5356
+ incompleteToolCallCount,
5357
+ isAgentThinking,
5358
+ isConnecting,
5359
+ isExecuting,
5360
+ isSubmitting,
5361
+ isWaitingForResponse,
5362
+ messagesWithToolCallsCount,
5363
+ recentAgentRunEvents,
5364
+ recentToolUiEvents,
5365
+ totalToolCallCount,
5366
+ ]);
5367
+ const showInitialThinkingSplash = messages.length === 0 &&
5368
+ !error &&
5369
+ hideGreeting &&
5370
+ (isSubmitting || isConnecting);
3822
5371
  // Compute dots visibility: only show BEFORE any assistant message exists
3823
5372
  // This prevents duplicate headers - the dots indicator has its own header,
3824
5373
  // and we don't want to show a second header below existing messages
@@ -3833,13 +5382,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3833
5382
  // The message with the pending approval will display its own UI for approval
3834
5383
  if (allPendingApprovals.length > 0)
3835
5384
  return false;
5385
+ // The hidden-greeting startup splash already renders its own bouncing dots.
5386
+ // Suppress the generic indicator so reopening/running terminals don't show two.
5387
+ if (showInitialThinkingSplash)
5388
+ return false;
3836
5389
  // IMPORTANT: If the last message is an assistant message and we're still executing,
3837
5390
  // the AiResponseMessage for that message will show its own activity indicator.
3838
5391
  // We only want these global thinking dots if the last message was from the user
3839
5392
  // or if no messages exist yet (waiting for initial response).
3840
5393
  const lastMessage = messages.length > 0 ? messages[messages.length - 1] : null;
3841
- if (isExecuting && lastMessage?.role === "assistant")
5394
+ if (isExecuting &&
5395
+ lastMessage?.role === "assistant" &&
5396
+ !lastMessage.isCompleted) {
3842
5397
  return false;
5398
+ }
3843
5399
  // Existing check for uncompleted assistant messages
3844
5400
  const hasActiveStreamingMessage = messages.some((m) => !m.isCompleted && m.role === "assistant");
3845
5401
  if (hasActiveStreamingMessage)
@@ -3855,20 +5411,22 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3855
5411
  messages,
3856
5412
  activeInlineDialog,
3857
5413
  allPendingApprovals,
5414
+ showInitialThinkingSplash,
3858
5415
  ]);
3859
5416
  // Move useMemo hook before early return to comply with Rules of Hooks
3860
- const isLiveEditorContextMode = React.useMemo(() => {
5417
+ const resolvedEditorContextMode = React.useMemo(() => {
3861
5418
  try {
3862
5419
  const normalizedAgentProfileId = agent?.profileId?.toLowerCase();
3863
5420
  const profile = activeProfile ||
3864
5421
  profiles.find((p) => p.id?.toLowerCase() === normalizedAgentProfileId);
3865
- const mode = profile?.editorContextMode;
3866
- return mode === "live";
5422
+ return profile?.editorContextMode ?? null;
3867
5423
  }
3868
5424
  catch {
3869
- return false;
5425
+ return null;
3870
5426
  }
3871
5427
  }, [activeProfile, profiles, agent?.profileId]);
5428
+ const isLiveEditorContextMode = resolvedEditorContextMode === "live";
5429
+ const omitsEditorContext = resolvedEditorContextMode === "none";
3872
5430
  // Get parent agent ID from agent or agentStub (handle both camelCase and PascalCase)
3873
5431
  const parentAgentId = agent?.parentAgentId ||
3874
5432
  agent?.ParentAgentId ||
@@ -3882,10 +5440,8 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3882
5440
  detail: { agentId: parentAgentId },
3883
5441
  }));
3884
5442
  }, [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 }));
5443
+ 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;
5444
+ 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
5445
  const renderCostLimitBanner = () => {
3890
5446
  if (!costLimitExceeded)
3891
5447
  return null;
@@ -3896,9 +5452,20 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3896
5452
  try {
3897
5453
  // Extend cost limit - backend will automatically resume the agent
3898
5454
  const result = await updateAgentCostLimit(agent.id, "extend");
3899
- // Update the agent's cost limit in local state
5455
+ // Update the agent's cost limit and clear the costLimitReached
5456
+ // status in local state so the useEffect watcher doesn't
5457
+ // immediately re-show the banner before the backend status
5458
+ // update arrives.
3900
5459
  if (result.success && result.costLimit !== undefined) {
3901
- setAgent((prev) => prev ? { ...prev, costLimit: result.costLimit } : prev);
5460
+ setAgent((prev) => prev
5461
+ ? {
5462
+ ...prev,
5463
+ costLimit: result.costLimit,
5464
+ status: prev.status === "costLimitReached"
5465
+ ? "running"
5466
+ : prev.status,
5467
+ }
5468
+ : prev);
3902
5469
  }
3903
5470
  // Clear the banner and set waiting state
3904
5471
  // Agent will resume automatically via backend's ResumeAgentAsync
@@ -3918,13 +5485,257 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3918
5485
  };
3919
5486
  const renderErrorBanner = () => {
3920
5487
  const currentAgent = agent || agentStub;
3921
- const isErrorStatus = currentAgent?.status === "error" || currentAgent?.status === 4;
3922
- const errorMessage = currentAgent?.statusMessage;
3923
- if (!isErrorStatus || !errorMessage)
5488
+ const isErrorStatus = currentAgent?.status === "error";
5489
+ const isWaitingForInputStatus = currentAgent?.status === "waitingForInput";
5490
+ const isWaitingForApprovalStatus = currentAgent?.status === "waitingForApproval";
5491
+ // Show error banner for error status, or for any terminal status that still
5492
+ // carries a statusMessage (e.g. agent closed after an error).
5493
+ const isTerminalWithError = !isErrorStatus &&
5494
+ !!currentAgent?.statusMessage &&
5495
+ currentAgent?.status !== "running" &&
5496
+ currentAgent?.status !== "new" &&
5497
+ !isWaitingForInputStatus &&
5498
+ !isWaitingForApprovalStatus;
5499
+ const rawErrorMessage = (isErrorStatus || isTerminalWithError
5500
+ ? currentAgent?.statusMessage
5501
+ : null) || error;
5502
+ if (!rawErrorMessage)
3924
5503
  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 })] })] }) }));
5504
+ // Clean the error message (statusMessage from DB may contain raw JSON)
5505
+ const errorMessage = toUserFacingAgentErrorMessage(rawErrorMessage) || rawErrorMessage;
5506
+ 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
5507
  };
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) => {
5508
+ const renderBrowserClaimBanner = (variant = "inline") => {
5509
+ if (!agent?.id || !editContext?.sessionId)
5510
+ return null;
5511
+ if (!isClaimedByCurrentSession && !isClaimedByAnotherBrowser) {
5512
+ return null;
5513
+ }
5514
+ if (isPendingBrowserCaptureWait) {
5515
+ return null;
5516
+ }
5517
+ const label = isClaimedByCurrentSession
5518
+ ? "Attached to this browser"
5519
+ : isClaimedByAnotherBrowser
5520
+ ? "Attached in another browser"
5521
+ : "No browser attached";
5522
+ const description = isClaimedByCurrentSession
5523
+ ? "This browser will handle page screenshot and DOM capture requests for the agent."
5524
+ : isClaimedByAnotherBrowser
5525
+ ? "Capture requests will stay with the other browser until you take over control here."
5526
+ : "A page capture request is waiting for a browser attachment. Attach this browser to continue.";
5527
+ 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");
5528
+ 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: () => {
5529
+ void handleReleaseBrowser();
5530
+ }, 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: () => {
5531
+ void handleClaimBrowser(isClaimedByAnotherBrowser);
5532
+ }, children: isClaimedByAnotherBrowser
5533
+ ? "Take over browser control"
5534
+ : "Attach to this browser" })) })] }) }));
5535
+ };
5536
+ const fixedBrowserClaimBanner = renderBrowserClaimBanner("fixed");
5537
+ const inlineBrowserClaimBanner = null;
5538
+ const browserCaptureInlinePrompt = isPendingBrowserCaptureWait && !isClaimedByCurrentSession
5539
+ ? {
5540
+ toolNames: [
5541
+ "capture-page-screenshot",
5542
+ "capture-parhelia-ui-screenshot",
5543
+ "capture-page-dom",
5544
+ ],
5545
+ label: isClaimedByAnotherBrowser
5546
+ ? "Attached in another browser"
5547
+ : "No browser attached",
5548
+ description: isClaimedByAnotherBrowser
5549
+ ? "This capture request is waiting in another browser. Take over browser control here to continue."
5550
+ : "This capture request is waiting for a browser attachment. Attach this browser to continue.",
5551
+ actionLabel: isClaimedByAnotherBrowser
5552
+ ? "Take over browser control"
5553
+ : "Attach to this browser",
5554
+ isPending: isBrowserClaimMutationPending,
5555
+ onAction: () => {
5556
+ void handleClaimBrowser(isClaimedByAnotherBrowser);
5557
+ },
5558
+ }
5559
+ : null;
5560
+ useEffect(() => {
5561
+ if (agent?.status !== "waitingForInput") {
5562
+ setPendingBrowserCaptureDialogType(null);
5563
+ }
5564
+ }, [agent?.status]);
5565
+ const renderInlineDialogContent = () => {
5566
+ if (!activeInlineDialog)
5567
+ return null;
5568
+ if (activeInlineDialog.request.dialogType === "questionnaire") {
5569
+ 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) => {
5570
+ activeInlineDialog.onComplete(result);
5571
+ setActiveInlineDialog(null);
5572
+ void onInteractionSubmitted?.();
5573
+ }, onCancel: () => {
5574
+ activeInlineDialog.onCancel();
5575
+ setActiveInlineDialog(null);
5576
+ } }) }));
5577
+ }
5578
+ const dialogRegistration = editContext?.configuration?.editor?.agentDialogs?.find((d) => d.dialogType === activeInlineDialog.request.dialogType);
5579
+ if (dialogRegistration) {
5580
+ const DialogComponent = dialogRegistration.component;
5581
+ return (_jsx("div", { className: "agent-inline-dialog", children: _jsx(DialogComponent, { title: activeInlineDialog.request.title, description: activeInlineDialog.request.description, parameters: activeInlineDialog.request.parameters, onClose: (result) => {
5582
+ activeInlineDialog.onComplete(result);
5583
+ setActiveInlineDialog(null);
5584
+ if (activeInlineDialog.request.dialogType === "questionnaire") {
5585
+ void onInteractionSubmitted?.();
5586
+ }
5587
+ }, onCancel: () => {
5588
+ activeInlineDialog.onCancel();
5589
+ setActiveInlineDialog(null);
5590
+ } }) }));
5591
+ }
5592
+ 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] }) }));
5593
+ };
5594
+ const latestSummaryAssistantGroup = useMemo(() => {
5595
+ if (hideSummaryMessages)
5596
+ return null;
5597
+ const groups = groupConsecutiveMessages(messages);
5598
+ for (let groupIndex = groups.length - 1; groupIndex >= 0; groupIndex -= 1) {
5599
+ const group = groups[groupIndex];
5600
+ if (!group || group.type !== "assistant-group")
5601
+ continue;
5602
+ const filteredMessages = group.messages.filter((msg) => {
5603
+ const content = msg.content || "";
5604
+ return !content.startsWith("⚠️") || !content.includes("Cost limit");
5605
+ });
5606
+ if (filteredMessages.length === 0)
5607
+ continue;
5608
+ return {
5609
+ messages: filteredMessages,
5610
+ isLastGroup: groupIndex === groups.length - 1,
5611
+ };
5612
+ }
5613
+ return null;
5614
+ }, [messages, hideSummaryMessages]);
5615
+ const summaryModeContent = displayMode === "summary"
5616
+ ? (() => {
5617
+ const inlineDialog = renderInlineDialogContent();
5618
+ const summaryMessages = latestSummaryAssistantGroup
5619
+ ? convertAgentMessagesToAiFormat(latestSummaryAssistantGroup.messages)
5620
+ : [];
5621
+ const summaryOperations = latestSummaryAssistantGroup
5622
+ ? getOperationsForMessageGroup(summaryMessages, agentOperations)
5623
+ : [];
5624
+ 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 &&
5625
+ !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: {
5626
+ __html: sanitizeSvg(activeProfile.svgIcon),
5627
+ } })) : (_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 ||
5628
+ activeProfile?.displayTitle ||
5629
+ activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, browserCaptureInlinePrompt: browserCaptureInlinePrompt, onQuickAction: (action) => {
5630
+ const text = (action.prompt ||
5631
+ action.value ||
5632
+ action.label ||
5633
+ "").trim();
5634
+ if (!text)
5635
+ return;
5636
+ if (isExecuting) {
5637
+ try {
5638
+ handleStop();
5639
+ }
5640
+ catch { }
5641
+ }
5642
+ sendQuickMessage(text);
5643
+ } }) })) : 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
5644
+ ? "The agent is still working. The next update will appear here automatically."
5645
+ : agent?.statusMessage ||
5646
+ summaryPlaceholderMessage ||
5647
+ "Waiting for the next agent update." }), summaryPlaceholderActions ? (_jsx("div", { className: `flex justify-center ${compact ? "mt-2" : "mt-3"}`, children: summaryPlaceholderActions })) : null] }) })), displayMode !== "summary" &&
5648
+ shouldShowThinkingDots &&
5649
+ !inlineDialog &&
5650
+ !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: {
5651
+ __html: sanitizeSvg(activeProfile.svgIcon),
5652
+ } })) : (_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 ||
5653
+ activeProfile?.displayTitle ||
5654
+ activeProfile?.name ||
5655
+ "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) => {
5656
+ setActivePlaceholderInput(null);
5657
+ setAllPlaceholdersFilled(false);
5658
+ if (activePlaceholderInput.behavior === "compose" &&
5659
+ !hideBottomControls) {
5660
+ setPrompt(filledText);
5661
+ setInputPlaceholder("Review and edit, then press Enter to send");
5662
+ if (textareaRef.current) {
5663
+ try {
5664
+ textareaRef.current.focus();
5665
+ const v = textareaRef.current.value || "";
5666
+ textareaRef.current.selectionStart = v.length;
5667
+ textareaRef.current.selectionEnd = v.length;
5668
+ }
5669
+ catch { }
5670
+ }
5671
+ }
5672
+ else {
5673
+ if (isExecuting) {
5674
+ try {
5675
+ handleStop();
5676
+ }
5677
+ catch { }
5678
+ }
5679
+ sendQuickMessage(filledText);
5680
+ }
5681
+ }, onCancel: () => {
5682
+ setActivePlaceholderInput(null);
5683
+ setAllPlaceholdersFilled(false);
5684
+ } })) : prompt && /\{\{[^{}]+\}\}|<<[^<>]+>>/.test(prompt) ? (_jsx(PlaceholderInput, { ref: promptPlaceholderInputRef, text: prompt, showButtons: hideBottomControls, buttonsClassName: hideBottomControls ? "justify-end" : "", onFilledChange: setAllPlaceholdersFilled, onComplete: (filledText) => {
5685
+ setPrompt(filledText);
5686
+ setAllPlaceholdersFilled(false);
5687
+ if (filledText.trim()) {
5688
+ if (isExecuting) {
5689
+ try {
5690
+ handleStop();
5691
+ }
5692
+ catch { }
5693
+ }
5694
+ sendQuickMessage(filledText);
5695
+ }
5696
+ }, onCancel: () => {
5697
+ setPrompt("");
5698
+ setAllPlaceholdersFilled(false);
5699
+ setInputPlaceholder("Type your message... (Enter to send, Shift+Enter or Ctrl+Enter for new line)");
5700
+ } })) : (_jsx("div", { className: "flex items-stretch gap-2", children: _jsx(Textarea, { ref: textareaRef, style: { viewTransitionName: "assistant-chat-input" }, value: prompt, onChange: (e) => {
5701
+ setPrompt(e.target.value);
5702
+ if (!/\{\{[^{}]+\}\}|<<[^<>]+>>/.test(e.target.value)) {
5703
+ setAllPlaceholdersFilled(false);
5704
+ }
5705
+ if (currentHistoryIndex !== -1) {
5706
+ setCurrentHistoryIndex(-1);
5707
+ }
5708
+ }, onKeyDown: handleKeyPress, onPaste: handlePaste, onFocus: () => {
5709
+ shouldMaintainFocusRef.current = true;
5710
+ }, onBlur: () => {
5711
+ shouldMaintainFocusRef.current = false;
5712
+ }, 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 }) })), (() => {
5713
+ const isInPlaceholderMode = activePlaceholderInput ||
5714
+ (prompt && /\{\{[^{}]+\}\}|<<[^<>]+>>/.test(prompt));
5715
+ const placeholderShowsOwnButtons = hideBottomControls && isInPlaceholderMode;
5716
+ if (placeholderShowsOwnButtons)
5717
+ return null;
5718
+ return (_jsxs("div", { className: cn("mt-2 flex items-stretch gap-2", hideBottomControls ||
5719
+ simpleMode ||
5720
+ isInPlaceholderMode
5721
+ ? "justify-end"
5722
+ : "justify-between"), children: [!hideBottomControls &&
5723
+ !simpleMode &&
5724
+ !isInPlaceholderMode ? (_jsx("div", { className: "flex-1" })) : null, _jsx(Button, { type: "button", size: "sm", onClick: () => {
5725
+ if (isExecuting) {
5726
+ handleStop();
5727
+ }
5728
+ else {
5729
+ handleSubmit();
5730
+ }
5731
+ }, disabled: !isExecuting &&
5732
+ !activePlaceholderInput &&
5733
+ (!prompt.trim() || isSubmitting), "data-testid": "agent-send-stop-button", children: isExecuting ? "Stop" : "Send" })] }));
5734
+ })()] })) : null] }));
5735
+ })()
5736
+ : null;
5737
+ const fullModeInlineDialog = displayMode === "full" ? renderInlineDialogContent() : null;
5738
+ 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
5739
  setPrompt(p);
3929
5740
  // Use setTimeout to ensure state is updated before submission
3930
5741
  setTimeout(() => {
@@ -3937,12 +5748,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3937
5748
  handleSubmit();
3938
5749
  }
3939
5750
  }, 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: [(() => {
5751
+ } })) })), 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: {
5752
+ __html: sanitizeSvg(activeProfile.svgIcon),
5753
+ } })) : (_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
5754
  const groups = groupConsecutiveMessages(messages);
3947
5755
  return groups.map((group, groupIndex) => {
3948
5756
  const isLastGroup = groupIndex === groups.length - 1;
@@ -3950,6 +5758,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3950
5758
  // Render user message
3951
5759
  return (_jsx(UserMessage, { message: group.messages[0] }, groupIndex));
3952
5760
  }
5761
+ else if (group.type === "heartbeat" && group.messages[0]) {
5762
+ return (_jsx(HeartbeatMessage, { message: group.messages[0] }, group.messages[0].id || groupIndex));
5763
+ }
3953
5764
  else {
3954
5765
  // Render bundled assistant messages
3955
5766
  // Check if this group contains any streaming message
@@ -3966,9 +5777,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
3966
5777
  }
3967
5778
  const convertedMessages = convertAgentMessagesToAiFormat(filteredMessages);
3968
5779
  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 ||
5780
+ return (_jsx(AiResponseMessage, { messages: convertedMessages, finished: !isLastGroup || !isExecuting, editOperations: operationsForGroup, defaultCollapseJson: defaultCollapseJson, profileSvgIcon: activeProfile?.svgIcon, agentId: agent?.id || agentStub.id, agentName: activeProfile?.agentName ||
3970
5781
  activeProfile?.displayTitle ||
3971
- activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, onQuickAction: (action) => {
5782
+ activeProfile?.name, allPendingApprovals: allPendingApprovals, onSwitchToAutonomous: handleSwitchToAutonomous, browserCaptureInlinePrompt: browserCaptureInlinePrompt, onQuickAction: (action) => {
3972
5783
  const text = (action.prompt ||
3973
5784
  action.value ||
3974
5785
  action.label ||
@@ -4012,13 +5823,13 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
4012
5823
  }
4013
5824
  });
4014
5825
  })(), 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,
5826
+ __html: sanitizeSvg(activeProfile.svgIcon),
4016
5827
  } })) : (_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
5828
  activeProfile?.displayTitle ||
4018
5829
  activeProfile?.name ||
4019
5830
  "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
5831
  !simpleMode &&
4021
- (isMobile ? (_jsx("div", { className: "border-t border-gray-200 bg-gray-50", "data-testid": "agent-context-panel-tabs", children: _jsx(SimpleTabs, { tabs: [
5832
+ (editContext?.isMobile ? (_jsx("div", { className: "border-t border-gray-200 bg-gray-50", "data-testid": "agent-context-panel-tabs", children: _jsx(SimpleTabs, { tabs: [
4022
5833
  {
4023
5834
  id: "context",
4024
5835
  label: "Context",
@@ -4056,7 +5867,7 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
4056
5867
  {
4057
5868
  id: "history",
4058
5869
  label: "History",
4059
- content: (_jsx(AgentEditOperationsPanel, { agentId: agent.id })),
5870
+ content: (_jsx(AgentEditOperationsPanel, { operations: agentOperations })),
4060
5871
  },
4061
5872
  ]
4062
5873
  : []),
@@ -4066,28 +5877,49 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
4066
5877
  hasTodoContent,
4067
5878
  hasSpawnedAgents,
4068
5879
  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 ? (
5880
+ ].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, { operations: agentOperations }))] }))), 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) => {
5881
+ let triggerName = "";
5882
+ if (qp.data) {
5883
+ try {
5884
+ const parsed = JSON.parse(qp.data);
5885
+ triggerName = parsed?.triggerName?.trim() || "";
5886
+ }
5887
+ catch {
5888
+ // Ignore invalid JSON metadata and render as regular queued prompt.
5889
+ }
5890
+ }
5891
+ const isTriggerQueuedPrompt = !qp.sourceAgentName && triggerName.length > 0;
5892
+ const isTriggerExpanded = !!expandedQueuedTriggerIds[qp.id];
5893
+ if (isTriggerQueuedPrompt) {
5894
+ return (_jsxs("div", { className: "text-[11px]", "data-testid": "queued-prompt-item", children: [_jsxs("button", { type: "button", onClick: () => setExpandedQueuedTriggerIds((prev) => ({
5895
+ ...prev,
5896
+ [qp.id]: !prev[qp.id],
5897
+ })), 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));
5898
+ }
5899
+ 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 &&
5900
+ new Date(qp.scheduledFor).getTime() >
5901
+ new Date(qp.createdDate || 0).getTime() +
5902
+ 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() ===
5903
+ new Date().toDateString()
5904
+ ? formatTime(new Date(qp.scheduledFor))
5905
+ : formatDateTime(new Date(qp.scheduledFor))] })] }))] })] }) }) }, qp.id));
5906
+ }) })] }) }))] }));
5907
+ const showQuestionnaireSplitter = isQuestionnaireDialogOpen && !!fullModeInlineDialog;
5908
+ const fullModeContent = showQuestionnaireSplitter ? (_jsx(Splitter, { panels: [
5909
+ {
5910
+ name: "conversation",
5911
+ defaultSize: 65,
5912
+ content: fullModeUpperContent,
5913
+ },
5914
+ {
5915
+ name: "questionnaire",
5916
+ defaultSize: 35,
5917
+ content: fullModeInlineDialog,
5918
+ },
5919
+ ], direction: "vertical", localStorageKey: compact
5920
+ ? "agent-terminal-compact-questionnaire-splitter"
5921
+ : "agent-terminal-questionnaire-splitter", className: "min-h-0 flex-1", splitterClassName: "bg-gray-200 hover:bg-gray-300" })) : (_jsxs(_Fragment, { children: [fullModeUpperContent, fullModeInlineDialog] }));
5922
+ 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
5923
  // Placeholder Input (from quick actions)
4092
5924
  // Show internal buttons only in splash mode (hideBottomControls) since external buttons won't be visible there
4093
5925
  _jsx(PlaceholderInput, { ref: placeholderInputRef, text: activePlaceholderInput.text, showButtons: hideBottomControls, buttonsClassName: hideBottomControls ? "justify-end" : "", onFilledChange: setAllPlaceholdersFilled, onComplete: (filledText) => {
@@ -4164,122 +5996,221 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
4164
5996
  return null;
4165
5997
  return (_jsxs("div", { className: cn("mt-2 flex items-stretch gap-2", hideBottomControls || simpleMode || isInPlaceholderMode
4166
5998
  ? "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"
5999
+ : "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
6000
  ? "border-green-300 bg-green-50! text-green-700 hover:bg-green-100!"
4169
6001
  : mode === "supervised"
4170
6002
  ? "border-amber-300 bg-amber-50! text-amber-700 hover:bg-amber-100!"
4171
6003
  : "border-red-300 bg-red-50! text-red-700 hover:bg-red-100!"), value: mode, options: modeOptions, onValueChange: async (val) => {
4172
6004
  const nextMode = val || "supervised";
4173
- // Optimistic UI update
4174
- setMode(nextMode);
4175
6005
  const current = agentMetadata || {};
4176
6006
  const nextMeta = {
4177
6007
  ...current,
4178
6008
  mode: nextMode,
4179
6009
  };
4180
6010
  try {
4181
- if (!agent?.id || agent.status === "new") {
6011
+ if (!agent?.id || isLocalOnlyDraftAgent) {
6012
+ setMode(nextMode);
4182
6013
  setAgentMetadata(nextMeta);
4183
- // Cache until first start when agent is persisted
4184
6014
  pendingSettingsRef.current = {
4185
6015
  ...(pendingSettingsRef.current || {}),
4186
6016
  mode: nextMode,
4187
6017
  };
4188
6018
  return;
4189
6019
  }
4190
- await updateAgentSettings(agent.id, {
6020
+ const result = await updateAgentSettings(agent.id, {
4191
6021
  mode: nextMode,
4192
6022
  });
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
- });
6023
+ if (result.success === false ||
6024
+ result.updates?.mode === false) {
6025
+ throw new Error("Mode change was not applied");
4229
6026
  }
4230
- // reflect in local agent stub so tabs and titles can use it if needed
6027
+ setMode(nextMode);
6028
+ setAgentMetadata(nextMeta);
4231
6029
  setAgent((prev) => prev
4232
6030
  ? {
4233
6031
  ...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
- }),
6032
+ mode: nextMode,
6033
+ metadata: JSON.stringify(nextMeta),
4244
6034
  }
4245
6035
  : prev);
4246
6036
  }
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
- }
6037
+ catch (e2) {
6038
+ console.error("Failed to persist mode change", e2);
4270
6039
  }
4271
- catch (err) {
4272
- console.error("Failed to persist agent model", err);
6040
+ } }), _jsxs(Popover, { open: showAgentSettings, onOpenChange: (open) => {
6041
+ setShowAgentSettings(open);
6042
+ if (!open) {
6043
+ setShowSkillPicker(false);
4273
6044
  }
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: () => {
6045
+ }, 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) => {
6046
+ const target = e.target;
6047
+ if (target?.closest('[data-help-panel="true"]')) {
6048
+ e.preventDefault();
6049
+ }
6050
+ }, onPointerDownOutside: (e) => {
6051
+ const target = e.target;
6052
+ if (target?.closest('[data-help-panel="true"]')) {
6053
+ e.preventDefault();
6054
+ }
6055
+ }, onFocusOutside: (e) => {
6056
+ const target = e.target;
6057
+ if (target?.closest('[data-help-panel="true"]')) {
6058
+ e.preventDefault();
6059
+ }
6060
+ }, 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) => {
6061
+ const nextProfile = profiles.find((x) => x.id === val);
6062
+ if (!nextProfile)
6063
+ return;
6064
+ setActiveProfile(nextProfile);
6065
+ try {
6066
+ if (agent?.id && !isLocalOnlyDraftAgent) {
6067
+ await updateAgentSettings(agent.id, {
6068
+ profileId: nextProfile.id,
6069
+ profileName: nextProfile.name,
6070
+ });
6071
+ }
6072
+ else {
6073
+ pendingSettingsRef.current = {
6074
+ ...(pendingSettingsRef.current || {}),
6075
+ profileId: nextProfile.id,
6076
+ profileName: nextProfile.name,
6077
+ };
6078
+ setAgentMetadata((current) => {
6079
+ const next = {
6080
+ ...(current || {}),
6081
+ };
6082
+ next.profile = nextProfile.name;
6083
+ next.additionalData = {
6084
+ ...(next.additionalData || {}),
6085
+ profileId: nextProfile.id,
6086
+ profileName: nextProfile.name,
6087
+ };
6088
+ return next;
6089
+ });
6090
+ }
6091
+ setAgent((prev) => prev
6092
+ ? {
6093
+ ...prev,
6094
+ profileId: nextProfile.id,
6095
+ profileName: nextProfile.name,
6096
+ metadata: JSON.stringify({
6097
+ ...(agentMetadata || {}),
6098
+ profile: nextProfile.name,
6099
+ additionalData: {
6100
+ ...(agentMetadata
6101
+ ?.additionalData || {}),
6102
+ profileId: nextProfile.id,
6103
+ profileName: nextProfile.name,
6104
+ },
6105
+ }),
6106
+ }
6107
+ : prev);
6108
+ }
6109
+ catch (err) {
6110
+ console.error("Failed to persist agent profile", err);
6111
+ }
6112
+ } }), 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: () => {
6113
+ void handleEditProfileSideBySide();
6114
+ setShowAgentSettings(false);
6115
+ }, "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: () => {
6116
+ void handleOpenProfileSettings();
6117
+ setShowAgentSettings(false);
6118
+ }, 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) => {
6119
+ const nextId = val;
6120
+ setSelectedModelId(nextId);
6121
+ const modelName = activeProfile?.models?.find((m) => m.id === nextId)?.name || "";
6122
+ setAgent((prev) => prev ? { ...prev, model: modelName } : prev);
6123
+ try {
6124
+ if (agent?.id && !isLocalOnlyDraftAgent) {
6125
+ await updateAgentSettings(agent.id, {
6126
+ model: modelName,
6127
+ });
6128
+ }
6129
+ else {
6130
+ pendingSettingsRef.current = {
6131
+ ...(pendingSettingsRef.current || {}),
6132
+ modelName,
6133
+ };
6134
+ }
6135
+ }
6136
+ catch (err) {
6137
+ console.error("Failed to persist agent model", err);
6138
+ }
6139
+ } })] })) : 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) => {
6140
+ setShowSkillPicker(open);
6141
+ if (open) {
6142
+ setSkillActionError(null);
6143
+ }
6144
+ }, 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) => {
6145
+ const selected = selection[0];
6146
+ if (!selected?.id)
6147
+ return;
6148
+ setSkillActionError(null);
6149
+ if (selectableTemplateIdSet.size > 0 &&
6150
+ (!selected.templateId ||
6151
+ !selectableTemplateIdSet.has(selected.templateId.toLowerCase()))) {
6152
+ return;
6153
+ }
6154
+ if (!manuallyAssignableSkillIdSet.has(selected.id.toLowerCase())) {
6155
+ setSkillActionError("This skill cannot be added for the current agent profile.");
6156
+ return;
6157
+ }
6158
+ void (async () => {
6159
+ const added = await handleAddSkill(selected.id);
6160
+ if (added) {
6161
+ setShowSkillPicker(false);
6162
+ }
6163
+ })();
6164
+ } }), skillsLoading && (_jsx("div", { className: "bg-background/70 absolute inset-0 flex items-center justify-center text-[10px] text-gray-500", children: "Loading skills..." }))] }), !skillsLoading &&
6165
+ !skillsError &&
6166
+ 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 &&
6167
+ !skillsError &&
6168
+ skillRootIds.length === 0 && (_jsx("div", { className: "text-[10px] text-gray-500", children: "No skill roots available." })), !skillsLoading &&
6169
+ !skillsError &&
6170
+ profileFilteredSkills.length === 0 && (_jsx("div", { className: "text-[10px] text-gray-500", children: selectedSkillIds.length > 0
6171
+ ? "All addable skills are selected"
6172
+ : "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) => {
6173
+ const skill = selectedSkills.find((s) => s.id === skillId);
6174
+ 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: () => {
6175
+ void handleOpenSkillItem(skillId);
6176
+ }, 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: () => {
6177
+ void handleRemoveSkill(skillId);
6178
+ }, title: "Remove skill", "aria-label": `Remove ${skill?.name || skillId}`, children: _jsx(X, { className: "h-2.5 w-2.5", strokeWidth: 1 }) }))] }, skillId));
6179
+ }) }))] }), _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
6180
+ ? "No available tools for this profile and mode"
6181
+ : "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) => {
6182
+ const sourceLabel = formatAllowanceSource(allowance.source);
6183
+ const pathLabel = "itemPath" in allowance
6184
+ ? allowance.itemPath
6185
+ : allowance.normalizedPath;
6186
+ 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 ||
6187
+ "*" }), _jsx("div", { className: "truncate text-[9px] text-gray-500", title: formatAllowanceLabel(allowance), children: pathLabel })] }), (sourceLabel ||
6188
+ allowance.grantedBy) && (_jsx("div", { className: "truncate pl-6 text-[9px] text-gray-400", children: [
6189
+ sourceLabel,
6190
+ allowance.grantedBy,
6191
+ ]
6192
+ .filter(Boolean)
6193
+ .join(" · ") }))] }, `${group.key}-${allowance.operationType}-${pathLabel}-${index}`));
6194
+ })] }, group.key)) : null) })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isLocalOnlyDraftAgent
6195
+ ? "Allowances are shown after the agent is created"
6196
+ : "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) => {
6197
+ const filterText = (sub.filter || "").trim();
6198
+ 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));
6199
+ }) })) : (_jsx("div", { className: "px-1 text-[10px] text-gray-500", children: isLocalOnlyDraftAgent
6200
+ ? "Subscribed triggers are shown after the agent is created"
6201
+ : "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
6202
  setPrompt(p.prompt);
4276
6203
  setShowPredefined(false);
4277
6204
  if (textareaRef.current)
4278
6205
  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)
6206
+ }, children: p.title }, index))) }) })] })) : null, !hideBottomControls &&
6207
+ !simpleMode &&
6208
+ editContext?.isMobile && (_jsxs(Popover, { open: showCostAndAgent, onOpenChange: setShowCostAndAgent, children: [_jsx(PopoverTrigger, { asChild: true, children: _jsx(Button, { onClick: () => {
6209
+ if (editContext?.isMobile)
4281
6210
  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
6211
+ }, variant: "outline", size: "sm", className: "h-5.5 w-5.5 cursor-pointer rounded-full", "aria-expanded": editContext?.isMobile
6212
+ ? showCostAndAgent
6213
+ : 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
6214
  ? {
4284
6215
  input: liveTotals.input,
4285
6216
  output: liveTotals.output,
@@ -4289,9 +6220,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
4289
6220
  outputCost: liveTotals.outputCost,
4290
6221
  cachedCost: liveTotals.cachedCost,
4291
6222
  cacheWriteCost: liveTotals.cacheWriteCost ?? 0,
6223
+ imageCost: liveTotals.imageCost ?? 0,
4292
6224
  totalCost: liveTotals.totalCost,
4293
6225
  }
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
6226
+ : totalTokens, costLimit: agentCostLimit, messages: messages, showCompressionPopover: showCompressionPopover, setShowCompressionPopover: setShowCompressionPopover }) }) })] }))] })), _jsxs("div", { className: "flex items-center gap-1 self-end", children: [_jsx("span", { title: isVoiceDisabled
4295
6227
  ? "Your browser does not support Speech Recognition"
4296
6228
  : isListening
4297
6229
  ? "Stop voice input"
@@ -4312,7 +6244,10 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
4312
6244
  : allPendingApprovals.length > 0
4313
6245
  ? "Approve or reject pending tool calls first"
4314
6246
  : "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
6247
+ })(), !hideBottomControls &&
6248
+ !simpleMode &&
6249
+ editContext &&
6250
+ !editContext.isMobile && (_jsx(AgentTerminalStatusBar, { agent: agent, contextWindowStatus: contextWindowStatus, effectiveModelName: effectiveModelName, socketDiagnostics: editContext.socketDiagnostics, runDiagnosticsSnapshot: runDiagnosticsSnapshot, liveTotals: liveTotals, totalTokens: liveTotals
4316
6251
  ? {
4317
6252
  input: liveTotals.input,
4318
6253
  output: liveTotals.output,
@@ -4322,8 +6257,9 @@ export function AgentTerminal({ agentStub, initialMetadata, profiles, isActive =
4322
6257
  outputCost: liveTotals.outputCost,
4323
6258
  cachedCost: liveTotals.cachedCost,
4324
6259
  cacheWriteCost: liveTotals.cacheWriteCost ?? 0,
6260
+ imageCost: liveTotals.imageCost ?? 0,
4325
6261
  totalCost: liveTotals.totalCost,
4326
6262
  }
4327
- : totalTokens, effectiveCostLimit: effectiveCostLimit, messages: messages, activeProfile: activeProfile, showCompressionPopover: showCompressionPopover, setShowCompressionPopover: setShowCompressionPopover }))] })] }));
6263
+ : totalTokens, costLimit: agentCostLimit, messages: messages, showCompressionPopover: showCompressionPopover, setShowCompressionPopover: setShowCompressionPopover }))] })] }));
4328
6264
  }
4329
6265
  //# sourceMappingURL=AgentTerminal.js.map