@vertesia/ui 1.4.0-dev.20260629.130134Z → 1.4.1
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.
- package/lib/core/components/SidePanel.js +1 -1
- package/lib/core/components/SidePanel.js.map +1 -1
- package/lib/core/components/shadcn/button.d.ts +10 -3
- package/lib/core/components/shadcn/button.d.ts.map +1 -1
- package/lib/core/components/shadcn/button.js +41 -9
- package/lib/core/components/shadcn/button.js.map +1 -1
- package/lib/core/components/shadcn/modal/dialog.js +1 -1
- package/lib/core/components/shadcn/modal/dialog.js.map +1 -1
- package/lib/core/components/shadcn/resizeable.d.ts +1 -1
- package/lib/core/components/shadcn/resizeable.d.ts.map +1 -1
- package/lib/core/components/shadcn/resizeable.js +12 -2
- package/lib/core/components/shadcn/resizeable.js.map +1 -1
- package/lib/core/components/shadcn/tabs.d.ts +12 -1
- package/lib/core/components/shadcn/tabs.d.ts.map +1 -1
- package/lib/core/components/shadcn/tabs.js +12 -4
- package/lib/core/components/shadcn/tabs.js.map +1 -1
- package/lib/core/components/shadcn/theme/ThemeSwitcher.js +1 -1
- package/lib/core/components/shadcn/theme/ThemeSwitcher.js.map +1 -1
- package/lib/core/hooks/useIntersectionObserver.d.ts.map +1 -1
- package/lib/core/hooks/useIntersectionObserver.js +3 -1
- package/lib/core/hooks/useIntersectionObserver.js.map +1 -1
- package/lib/features/agent/chat/AgentChatPlaybackControls.d.ts.map +1 -1
- package/lib/features/agent/chat/AgentChatPlaybackControls.js +2 -2
- package/lib/features/agent/chat/AgentChatPlaybackControls.js.map +1 -1
- package/lib/features/agent/chat/AgentRequestInputOverlay.d.ts.map +1 -1
- package/lib/features/agent/chat/AgentRequestInputOverlay.js +2 -2
- package/lib/features/agent/chat/AgentRequestInputOverlay.js.map +1 -1
- package/lib/features/agent/chat/AgentRightPanel.js +1 -1
- package/lib/features/agent/chat/AgentRightPanel.js.map +1 -1
- package/lib/features/agent/chat/AskUserWidget.d.ts.map +1 -1
- package/lib/features/agent/chat/AskUserWidget.js +4 -3
- package/lib/features/agent/chat/AskUserWidget.js.map +1 -1
- package/lib/features/agent/chat/ImageLightbox.d.ts.map +1 -1
- package/lib/features/agent/chat/ImageLightbox.js +1 -1
- package/lib/features/agent/chat/ImageLightbox.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentConversation.js +57 -12
- package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts +8 -1
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +45 -36
- package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/Header.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/Header.js +4 -2
- package/lib/features/agent/chat/ModernAgentOutput/Header.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js +7 -3
- package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js +2 -1
- package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/WorkstreamTabs.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/WorkstreamTabs.js +158 -8
- package/lib/features/agent/chat/ModernAgentOutput/WorkstreamTabs.js.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.d.ts +6 -0
- package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.d.ts.map +1 -1
- package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.js +15 -1
- package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.js.map +1 -1
- package/lib/features/agent/chat/hooks/useFileProcessing.d.ts +1 -0
- package/lib/features/agent/chat/hooks/useFileProcessing.d.ts.map +1 -1
- package/lib/features/agent/chat/hooks/useFileProcessing.js +37 -3
- package/lib/features/agent/chat/hooks/useFileProcessing.js.map +1 -1
- package/lib/features/magic-pdf/AnnotatedImageSlider.js +13 -13
- package/lib/features/magic-pdf/AnnotatedImageSlider.js.map +1 -1
- package/lib/features/magic-pdf/DownloadPopover.js +2 -2
- package/lib/features/magic-pdf/DownloadPopover.js.map +1 -1
- package/lib/features/magic-pdf/MagicPdfView.js +3 -3
- package/lib/features/magic-pdf/MagicPdfView.js.map +1 -1
- package/lib/features/oauth/McpConnectionsButton.d.ts.map +1 -1
- package/lib/features/oauth/McpConnectionsButton.js +2 -2
- package/lib/features/oauth/McpConnectionsButton.js.map +1 -1
- package/lib/features/oauth/McpConnectionsDialog.d.ts.map +1 -1
- package/lib/features/oauth/McpConnectionsDialog.js +1 -1
- package/lib/features/oauth/McpConnectionsDialog.js.map +1 -1
- package/lib/features/oauth/RemoteMcpConnectionButton.d.ts.map +1 -1
- package/lib/features/oauth/RemoteMcpConnectionButton.js +1 -1
- package/lib/features/oauth/RemoteMcpConnectionButton.js.map +1 -1
- package/lib/features/pdf-viewer/PdfPageSlider.js +2 -2
- package/lib/features/pdf-viewer/PdfPageSlider.js.map +1 -1
- package/lib/features/pdf-viewer/SimplePdfViewer.d.ts.map +1 -1
- package/lib/features/pdf-viewer/SimplePdfViewer.js +3 -3
- package/lib/features/pdf-viewer/SimplePdfViewer.js.map +1 -1
- package/lib/features/store/objects/DocumentQuickFilter.d.ts.map +1 -1
- package/lib/features/store/objects/DocumentQuickFilter.js +5 -5
- package/lib/features/store/objects/DocumentQuickFilter.js.map +1 -1
- package/lib/features/store/objects/DocumentSearchResults.d.ts.map +1 -1
- package/lib/features/store/objects/DocumentSearchResults.js +2 -2
- package/lib/features/store/objects/DocumentSearchResults.js.map +1 -1
- package/lib/features/store/objects/components/ContentDispositionButton.d.ts.map +1 -1
- package/lib/features/store/objects/components/ContentDispositionButton.js +1 -1
- package/lib/features/store/objects/components/ContentDispositionButton.js.map +1 -1
- package/lib/features/store/objects/components/ContentOverview.js +3 -3
- package/lib/features/store/objects/components/ContentOverview.js.map +1 -1
- package/lib/features/store/objects/components/DocumentIcon.js +1 -1
- package/lib/features/store/objects/components/VectorSearchWidget.js +1 -1
- package/lib/features/store/objects/components/VectorSearchWidget.js.map +1 -1
- package/lib/features/store/objects/layout/renderers.d.ts.map +1 -1
- package/lib/features/store/objects/layout/renderers.js +2 -2
- package/lib/features/store/objects/layout/renderers.js.map +1 -1
- package/lib/features/store/objects/selection/SelectionActions.js +1 -1
- package/lib/features/store/objects/selection/SelectionActions.js.map +1 -1
- package/lib/features/store/objects/selection/actions/StartWorkflowComponent.js +4 -4
- package/lib/features/store/objects/selection/actions/StartWorkflowComponent.js.map +1 -1
- package/lib/features/store/types/ContentObjectTypesTable.d.ts.map +1 -1
- package/lib/features/store/types/ContentObjectTypesTable.js +4 -4
- package/lib/features/store/types/ContentObjectTypesTable.js.map +1 -1
- package/lib/features/store/types/SelectContentTypeModal.js +1 -1
- package/lib/features/store/types/SelectContentTypeModal.js.map +1 -1
- package/lib/features/user/UserInfo.js +7 -3
- package/lib/features/user/UserInfo.js.map +1 -1
- package/lib/router/HistoryNavigator.d.ts.map +1 -1
- package/lib/router/HistoryNavigator.js +5 -0
- package/lib/router/HistoryNavigator.js.map +1 -1
- package/lib/router/Nav.d.ts.map +1 -1
- package/lib/router/Nav.js +9 -1
- package/lib/router/Nav.js.map +1 -1
- package/lib/vertesia-ui-core.js +1 -1
- package/lib/vertesia-ui-core.js.map +1 -1
- package/lib/vertesia-ui-features.js +1 -1
- package/lib/vertesia-ui-features.js.map +1 -1
- package/lib/vertesia-ui-router.js +1 -1
- package/lib/vertesia-ui-router.js.map +1 -1
- package/lib/vertesia-ui-widgets.js +1 -1
- package/lib/vertesia-ui-widgets.js.map +1 -1
- package/lib/widgets/form/Form.js +1 -1
- package/lib/widgets/form/Form.js.map +1 -1
- package/lib/widgets/json-view/JSONSwitcher.js +1 -1
- package/lib/widgets/json-view/JSONSwitcher.js.map +1 -1
- package/lib/widgets/json-view/JSONView.js +6 -6
- package/lib/widgets/json-view/JSONView.js.map +1 -1
- package/lib/widgets/monacoEditor/MonacoEditor.d.ts.map +1 -1
- package/lib/widgets/monacoEditor/MonacoEditor.js +29 -0
- package/lib/widgets/monacoEditor/MonacoEditor.js.map +1 -1
- package/lib/widgets/xml-viewer/hooks/useXMLViewer.d.ts +9 -0
- package/lib/widgets/xml-viewer/hooks/useXMLViewer.d.ts.map +1 -1
- package/lib/widgets/xml-viewer/hooks/useXMLViewer.js +20 -17
- package/lib/widgets/xml-viewer/hooks/useXMLViewer.js.map +1 -1
- package/package.json +29 -30
- package/src/__tests__/a11y.test.tsx +73 -0
- package/src/core/components/SidePanel.tsx +1 -1
- package/src/core/components/shadcn/button.tsx +67 -12
- package/src/core/components/shadcn/modal/dialog.tsx +1 -1
- package/src/core/components/shadcn/resizeable.test.tsx +77 -0
- package/src/core/components/shadcn/resizeable.tsx +28 -2
- package/src/core/components/shadcn/tabs.test.tsx +65 -0
- package/src/core/components/shadcn/tabs.tsx +25 -3
- package/src/core/components/shadcn/theme/ThemeSwitcher.tsx +1 -1
- package/src/core/hooks/useIntersectionObserver.tsx +3 -1
- package/src/features/agent/chat/AgentChatPlaybackControls.tsx +0 -5
- package/src/features/agent/chat/AgentRequestInputOverlay.test.tsx +31 -0
- package/src/features/agent/chat/AgentRequestInputOverlay.tsx +6 -2
- package/src/features/agent/chat/AgentRightPanel.tsx +1 -1
- package/src/features/agent/chat/AskUserWidget.test.tsx +49 -0
- package/src/features/agent/chat/AskUserWidget.tsx +32 -22
- package/src/features/agent/chat/ImageLightbox.tsx +0 -1
- package/src/features/agent/chat/ModernAgentConversation.test.tsx +246 -1
- package/src/features/agent/chat/ModernAgentConversation.tsx +62 -17
- package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.test.tsx +6 -1
- package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +81 -49
- package/src/features/agent/chat/ModernAgentOutput/Header.tsx +3 -3
- package/src/features/agent/chat/ModernAgentOutput/MessageInput.test.tsx +25 -0
- package/src/features/agent/chat/ModernAgentOutput/MessageInput.tsx +5 -3
- package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +4 -1
- package/src/features/agent/chat/ModernAgentOutput/WorkstreamTabs.tsx +328 -42
- package/src/features/agent/chat/ModernAgentOutput/requestInputMessages.test.ts +80 -0
- package/src/features/agent/chat/ModernAgentOutput/requestInputMessages.ts +21 -1
- package/src/features/agent/chat/hooks/useFileProcessing.test.tsx +70 -0
- package/src/features/agent/chat/hooks/useFileProcessing.ts +37 -6
- package/src/features/magic-pdf/AnnotatedImageSlider.tsx +62 -66
- package/src/features/magic-pdf/DownloadPopover.tsx +2 -2
- package/src/features/magic-pdf/MagicPdfView.tsx +3 -3
- package/src/features/oauth/McpConnectionsButton.tsx +0 -2
- package/src/features/oauth/McpConnectionsDialog.tsx +0 -1
- package/src/features/oauth/RemoteMcpConnectionButton.tsx +0 -1
- package/src/features/pdf-viewer/PdfPageSlider.tsx +5 -5
- package/src/features/pdf-viewer/SimplePdfViewer.tsx +11 -12
- package/src/features/store/objects/DocumentQuickFilter.tsx +13 -15
- package/src/features/store/objects/DocumentSearchResults.tsx +3 -6
- package/src/features/store/objects/components/ContentDispositionButton.tsx +5 -1
- package/src/features/store/objects/components/ContentOverview.tsx +11 -11
- package/src/features/store/objects/components/DocumentIcon.tsx +1 -1
- package/src/features/store/objects/components/VectorSearchWidget.tsx +2 -2
- package/src/features/store/objects/layout/renderers.tsx +1 -2
- package/src/features/store/objects/selection/SelectionActions.tsx +1 -1
- package/src/features/store/objects/selection/actions/StartWorkflowComponent.tsx +6 -5
- package/src/features/store/types/ContentObjectTypesTable.tsx +12 -14
- package/src/features/store/types/SelectContentTypeModal.tsx +2 -2
- package/src/features/user/UserInfo.tsx +7 -7
- package/src/router/HistoryNavigator.ts +5 -0
- package/src/router/Nav.tsx +12 -3
- package/src/widgets/form/Form.tsx +1 -1
- package/src/widgets/json-view/JSONSwitcher.tsx +2 -2
- package/src/widgets/json-view/JSONView.tsx +7 -7
- package/src/widgets/monacoEditor/MonacoEditor.tsx +29 -0
- package/src/widgets/xml-viewer/hooks/useXMLViewer.test.ts +26 -0
- package/src/widgets/xml-viewer/hooks/useXMLViewer.ts +23 -18
|
@@ -104,6 +104,8 @@ const VARIANT_ICONS = {
|
|
|
104
104
|
success: CheckCircle,
|
|
105
105
|
};
|
|
106
106
|
|
|
107
|
+
const SCROLLABLE_PROMPT_CLASS = 'max-h-80 overflow-y-auto overscroll-contain pe-2';
|
|
108
|
+
|
|
107
109
|
/**
|
|
108
110
|
* AskUserWidget - A styled component for displaying agent prompts/questions to users
|
|
109
111
|
*
|
|
@@ -197,10 +199,14 @@ export function AskUserWidget({
|
|
|
197
199
|
if (answered) {
|
|
198
200
|
return (
|
|
199
201
|
<div className={cn('my-1.5 font-sans', className)}>
|
|
200
|
-
{
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
202
|
+
<div className={SCROLLABLE_PROMPT_CLASS}>
|
|
203
|
+
{compactQuestion}
|
|
204
|
+
{description && (
|
|
205
|
+
<p className={cn('mt-1 text-xs leading-5 text-muted', descriptionClassName)}>
|
|
206
|
+
{description}
|
|
207
|
+
</p>
|
|
208
|
+
)}
|
|
209
|
+
</div>
|
|
204
210
|
</div>
|
|
205
211
|
);
|
|
206
212
|
}
|
|
@@ -214,12 +220,14 @@ export function AskUserWidget({
|
|
|
214
220
|
<div className={cn('mt-1 flex-shrink-0 text-attention', iconClassName)}>{iconNode}</div>
|
|
215
221
|
)}
|
|
216
222
|
<div className="min-w-0 flex-1">
|
|
217
|
-
{
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
{
|
|
221
|
-
|
|
222
|
-
|
|
223
|
+
<div className={SCROLLABLE_PROMPT_CLASS}>
|
|
224
|
+
{compactQuestion}
|
|
225
|
+
{description && (
|
|
226
|
+
<p className={cn('mt-1 text-xs leading-5 text-muted', descriptionClassName)}>
|
|
227
|
+
{description}
|
|
228
|
+
</p>
|
|
229
|
+
)}
|
|
230
|
+
</div>
|
|
223
231
|
</div>
|
|
224
232
|
</div>
|
|
225
233
|
</div>
|
|
@@ -355,19 +363,21 @@ export function AskUserWidget({
|
|
|
355
363
|
{icon || <DefaultIcon className="size-5" />}
|
|
356
364
|
</div>
|
|
357
365
|
)}
|
|
358
|
-
<div className="
|
|
359
|
-
<div
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
<MarkdownRenderer>{question}</MarkdownRenderer>
|
|
363
|
-
</div>
|
|
364
|
-
{description && (
|
|
365
|
-
<p
|
|
366
|
-
className={`mt-1 text-sm text-gray-600 dark:text-gray-400 ${descriptionClassName || ''}`}
|
|
366
|
+
<div className="min-w-0 flex-1">
|
|
367
|
+
<div className={SCROLLABLE_PROMPT_CLASS}>
|
|
368
|
+
<div
|
|
369
|
+
className={`agent-ask-question prose prose-sm dark:prose-invert max-w-none text-gray-900 dark:text-gray-100 ${questionClassName || ''}`}
|
|
367
370
|
>
|
|
368
|
-
{
|
|
369
|
-
</
|
|
370
|
-
|
|
371
|
+
<MarkdownRenderer>{question}</MarkdownRenderer>
|
|
372
|
+
</div>
|
|
373
|
+
{description && (
|
|
374
|
+
<p
|
|
375
|
+
className={`mt-1 text-sm text-gray-600 dark:text-gray-400 ${descriptionClassName || ''}`}
|
|
376
|
+
>
|
|
377
|
+
{description}
|
|
378
|
+
</p>
|
|
379
|
+
)}
|
|
380
|
+
</div>
|
|
371
381
|
</div>
|
|
372
382
|
</div>
|
|
373
383
|
</div>
|
|
@@ -64,7 +64,6 @@ export function ImageLightboxProvider({ children }: ImageLightboxProviderProps)
|
|
|
64
64
|
className="absolute top-2 end-2 text-white bg-black/50 rounded-full p-2 hover:bg-black/70 transition-colors"
|
|
65
65
|
onClick={closeImage}
|
|
66
66
|
title={t('agent.close')}
|
|
67
|
-
aria-label={t('agent.close')}
|
|
68
67
|
>
|
|
69
68
|
<X className="size-6" />
|
|
70
69
|
</Button>
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { act, fireEvent, screen, waitFor } from '@testing-library/react';
|
|
2
|
-
import { type AgentMessage, AgentMessageType } from '@vertesia/common';
|
|
2
|
+
import { type AgentMessage, AgentMessageType, type ConversationFile, FileProcessingStatus } from '@vertesia/common';
|
|
3
3
|
import type React from 'react';
|
|
4
4
|
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
|
5
5
|
import { renderWithProviders } from '../../../__tests__/test-utils.js';
|
|
@@ -245,6 +245,7 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
245
245
|
hasProcessingFiles: false,
|
|
246
246
|
handleFileUpload: vi.fn(),
|
|
247
247
|
removeProcessingFile: vi.fn(),
|
|
248
|
+
clearProcessingFiles: vi.fn(),
|
|
248
249
|
});
|
|
249
250
|
});
|
|
250
251
|
|
|
@@ -285,6 +286,53 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
285
286
|
expect(mocks.updateOptimisticMessageStatus).toHaveBeenCalledWith(expect.any(String), 'received');
|
|
286
287
|
});
|
|
287
288
|
|
|
289
|
+
it('binds ready uploaded files into the sent message and clears them after send', async () => {
|
|
290
|
+
const clearProcessingFiles = vi.fn();
|
|
291
|
+
const readyFile: ConversationFile = {
|
|
292
|
+
id: 'file-1',
|
|
293
|
+
name: 'report.pdf',
|
|
294
|
+
content_type: 'application/pdf',
|
|
295
|
+
size: 10,
|
|
296
|
+
status: FileProcessingStatus.READY,
|
|
297
|
+
artifact_path: 'files/report.pdf',
|
|
298
|
+
reference: 'artifact:files/report.pdf',
|
|
299
|
+
started_at: 1_000,
|
|
300
|
+
};
|
|
301
|
+
mocks.useFileProcessing.mockReturnValue({
|
|
302
|
+
processingFiles: new Map([[readyFile.id, readyFile]]),
|
|
303
|
+
hasProcessingFiles: false,
|
|
304
|
+
handleFileUpload: vi.fn(),
|
|
305
|
+
removeProcessingFile: vi.fn(),
|
|
306
|
+
clearProcessingFiles,
|
|
307
|
+
});
|
|
308
|
+
mockStreamState({
|
|
309
|
+
messages: [createMessage(AgentMessageType.COMPLETE, 'done')],
|
|
310
|
+
agentRunStatus: 'COMPLETED',
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
renderConversation({ onRestart: vi.fn() });
|
|
314
|
+
|
|
315
|
+
fireEvent.click(screen.getByRole('button', { name: 'inline send' }));
|
|
316
|
+
|
|
317
|
+
await waitFor(() => {
|
|
318
|
+
expect(mocks.sendSignal).toHaveBeenCalledWith(
|
|
319
|
+
'agent-run-1',
|
|
320
|
+
'UserInput',
|
|
321
|
+
expect.objectContaining({
|
|
322
|
+
message: expect.stringContaining('Uploaded artifacts:'),
|
|
323
|
+
}),
|
|
324
|
+
);
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const userInputCall = mocks.sendSignal.mock.calls.find((call) => call[1] === 'UserInput');
|
|
328
|
+
expect((userInputCall?.[2] as { message: string }).message).toContain(
|
|
329
|
+
'[report.pdf](artifact:files/report.pdf)',
|
|
330
|
+
);
|
|
331
|
+
await waitFor(() => {
|
|
332
|
+
expect(clearProcessingFiles).toHaveBeenCalledTimes(1);
|
|
333
|
+
});
|
|
334
|
+
});
|
|
335
|
+
|
|
288
336
|
it('does not restart or send from a terminal run that cannot continue', () => {
|
|
289
337
|
mockStreamState({
|
|
290
338
|
messages: [createMessage(AgentMessageType.COMPLETE, 'done')],
|
|
@@ -425,6 +473,155 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
425
473
|
expect(screen.queryByText('Switch to full control?')).toBeNull();
|
|
426
474
|
});
|
|
427
475
|
|
|
476
|
+
it('orders a follow-up user message after an in-flight approval mode change', async () => {
|
|
477
|
+
let resolveModeSignal: (() => void) | undefined;
|
|
478
|
+
mocks.retrieve.mockResolvedValue({
|
|
479
|
+
tool_approval_mode: 'ask',
|
|
480
|
+
interactive: true,
|
|
481
|
+
disabled_mcp_collections: undefined,
|
|
482
|
+
});
|
|
483
|
+
mocks.sendSignal.mockImplementation((_runId: string, signalName: string) => {
|
|
484
|
+
if (signalName === 'ToolApprovalModeChanged') {
|
|
485
|
+
return new Promise<void>((resolve) => {
|
|
486
|
+
resolveModeSignal = resolve;
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
return Promise.resolve({});
|
|
490
|
+
});
|
|
491
|
+
mockStreamState({
|
|
492
|
+
messages: [createMessage(AgentMessageType.REQUEST_INPUT, 'What should I do next?')],
|
|
493
|
+
isCompleted: false,
|
|
494
|
+
agentRunStatus: 'RUNNING',
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
renderConversation({ hideMessageInput: false, interactive: true, allowWorkflowControl: true });
|
|
498
|
+
|
|
499
|
+
const selector = await screen.findByRole('button', { name: 'Agent approval mode' });
|
|
500
|
+
fireEvent.pointerDown(selector, {
|
|
501
|
+
button: 0,
|
|
502
|
+
ctrlKey: false,
|
|
503
|
+
});
|
|
504
|
+
fireEvent.click(await screen.findByRole('menuitemradio', { name: /Full control/ }));
|
|
505
|
+
|
|
506
|
+
await waitFor(() => {
|
|
507
|
+
expect(mocks.sendSignal).toHaveBeenCalledWith('agent-run-1', 'ToolApprovalModeChanged', {
|
|
508
|
+
mode: 'full_control',
|
|
509
|
+
});
|
|
510
|
+
expect(resolveModeSignal).toBeDefined();
|
|
511
|
+
});
|
|
512
|
+
|
|
513
|
+
fireEvent.click(screen.getByRole('button', { name: 'composer send' }));
|
|
514
|
+
|
|
515
|
+
expect(mocks.sendSignal.mock.calls.some((call) => call[1] === 'UserInput')).toBe(false);
|
|
516
|
+
|
|
517
|
+
await act(async () => {
|
|
518
|
+
resolveModeSignal?.();
|
|
519
|
+
});
|
|
520
|
+
|
|
521
|
+
await waitFor(() => {
|
|
522
|
+
expect(mocks.sendSignal).toHaveBeenCalledWith(
|
|
523
|
+
'agent-run-1',
|
|
524
|
+
'UserInput',
|
|
525
|
+
expect.objectContaining({ message: 'follow up' }),
|
|
526
|
+
);
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
const modeCallIndex = mocks.sendSignal.mock.calls.findIndex((call) => call[1] === 'ToolApprovalModeChanged');
|
|
530
|
+
const userInputCallIndex = mocks.sendSignal.mock.calls.findIndex((call) => call[1] === 'UserInput');
|
|
531
|
+
expect(modeCallIndex).toBeGreaterThanOrEqual(0);
|
|
532
|
+
expect(userInputCallIndex).toBeGreaterThan(modeCallIndex);
|
|
533
|
+
});
|
|
534
|
+
|
|
535
|
+
it('keeps a local approval mode change when stale run metadata loads later', async () => {
|
|
536
|
+
let resolveRetrieve: (run: {
|
|
537
|
+
tool_approval_mode: 'ask';
|
|
538
|
+
interactive: true;
|
|
539
|
+
disabled_mcp_collections: undefined;
|
|
540
|
+
}) => void = () => {};
|
|
541
|
+
mocks.retrieve.mockReturnValue(
|
|
542
|
+
new Promise((resolve) => {
|
|
543
|
+
resolveRetrieve = resolve;
|
|
544
|
+
}),
|
|
545
|
+
);
|
|
546
|
+
mockStreamState({
|
|
547
|
+
messages: [createMessage(AgentMessageType.ANSWER, 'still running')],
|
|
548
|
+
isCompleted: false,
|
|
549
|
+
agentRunStatus: 'RUNNING',
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
renderConversation({
|
|
553
|
+
hideMessageInput: false,
|
|
554
|
+
interactive: true,
|
|
555
|
+
allowWorkflowControl: true,
|
|
556
|
+
initialToolApprovalMode: 'ask',
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
expect(await screen.findByText('Ask for approval')).not.toBeNull();
|
|
560
|
+
fireEvent.pointerDown(screen.getByRole('button', { name: 'Agent approval mode' }), {
|
|
561
|
+
button: 0,
|
|
562
|
+
ctrlKey: false,
|
|
563
|
+
});
|
|
564
|
+
fireEvent.click(await screen.findByRole('menuitemradio', { name: /Full control/ }));
|
|
565
|
+
|
|
566
|
+
await waitFor(() => {
|
|
567
|
+
expect(mocks.sendSignal).toHaveBeenCalledWith('agent-run-1', 'ToolApprovalModeChanged', {
|
|
568
|
+
mode: 'full_control',
|
|
569
|
+
});
|
|
570
|
+
});
|
|
571
|
+
|
|
572
|
+
resolveRetrieve({
|
|
573
|
+
tool_approval_mode: 'ask',
|
|
574
|
+
interactive: true,
|
|
575
|
+
disabled_mcp_collections: undefined,
|
|
576
|
+
});
|
|
577
|
+
|
|
578
|
+
await waitFor(() => {
|
|
579
|
+
expect(screen.getByText('Full control')).not.toBeNull();
|
|
580
|
+
});
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it('reverts the local approval mode when the change signal fails', async () => {
|
|
584
|
+
let rejectSignal: (error: Error) => void = () => {};
|
|
585
|
+
mocks.retrieve.mockResolvedValue({
|
|
586
|
+
tool_approval_mode: 'ask',
|
|
587
|
+
interactive: true,
|
|
588
|
+
disabled_mcp_collections: undefined,
|
|
589
|
+
});
|
|
590
|
+
mocks.sendSignal.mockReturnValueOnce(
|
|
591
|
+
new Promise((_resolve, reject) => {
|
|
592
|
+
rejectSignal = reject;
|
|
593
|
+
}),
|
|
594
|
+
);
|
|
595
|
+
mockStreamState({
|
|
596
|
+
messages: [createMessage(AgentMessageType.ANSWER, 'still running')],
|
|
597
|
+
isCompleted: false,
|
|
598
|
+
agentRunStatus: 'RUNNING',
|
|
599
|
+
});
|
|
600
|
+
|
|
601
|
+
renderConversation({ hideMessageInput: false, interactive: true, allowWorkflowControl: true });
|
|
602
|
+
|
|
603
|
+
const selector = await screen.findByRole('button', { name: 'Agent approval mode' });
|
|
604
|
+
expect(selector.textContent).toContain('Ask for approval');
|
|
605
|
+
fireEvent.pointerDown(selector, {
|
|
606
|
+
button: 0,
|
|
607
|
+
ctrlKey: false,
|
|
608
|
+
});
|
|
609
|
+
fireEvent.click(await screen.findByRole('menuitemradio', { name: /Full control/ }));
|
|
610
|
+
|
|
611
|
+
await waitFor(() => {
|
|
612
|
+
expect(mocks.sendSignal).toHaveBeenCalledWith('agent-run-1', 'ToolApprovalModeChanged', {
|
|
613
|
+
mode: 'full_control',
|
|
614
|
+
});
|
|
615
|
+
expect(selector.textContent).toContain('Full control');
|
|
616
|
+
});
|
|
617
|
+
|
|
618
|
+
rejectSignal(new Error('Signal failed'));
|
|
619
|
+
|
|
620
|
+
await waitFor(() => {
|
|
621
|
+
expect(selector.textContent).toContain('Ask for approval');
|
|
622
|
+
});
|
|
623
|
+
});
|
|
624
|
+
|
|
428
625
|
it('does not show a full-control fallback before active run mode metadata loads', () => {
|
|
429
626
|
mocks.retrieve.mockReturnValue(new Promise(() => {}));
|
|
430
627
|
mockStreamState({
|
|
@@ -1203,6 +1400,54 @@ describe('ModernAgentConversation send handling', () => {
|
|
|
1203
1400
|
);
|
|
1204
1401
|
});
|
|
1205
1402
|
|
|
1403
|
+
it('passes approval option metadata through the request overlay signal', async () => {
|
|
1404
|
+
mockStreamState({
|
|
1405
|
+
messages: [
|
|
1406
|
+
{
|
|
1407
|
+
...createMessage(AgentMessageType.REQUEST_INPUT, 'Approve Write Artifact: quotes.md?'),
|
|
1408
|
+
details: {
|
|
1409
|
+
tool_approval: {
|
|
1410
|
+
tool_name: 'write_artifact',
|
|
1411
|
+
approval_key: 'write_artifact:name:quotes.md',
|
|
1412
|
+
},
|
|
1413
|
+
ux: {
|
|
1414
|
+
options: [
|
|
1415
|
+
{ id: 'allow_once', label: 'Allow once' },
|
|
1416
|
+
{ id: 'allow_for_run', label: 'Allow this action for this run' },
|
|
1417
|
+
{ id: 'deny', label: 'Deny' },
|
|
1418
|
+
],
|
|
1419
|
+
},
|
|
1420
|
+
},
|
|
1421
|
+
},
|
|
1422
|
+
],
|
|
1423
|
+
isCompleted: false,
|
|
1424
|
+
agentRunStatus: 'RUNNING',
|
|
1425
|
+
});
|
|
1426
|
+
|
|
1427
|
+
renderConversation();
|
|
1428
|
+
|
|
1429
|
+
fireEvent.click(screen.getByRole('button', { name: 'Allow this action for this run' }));
|
|
1430
|
+
|
|
1431
|
+
await waitFor(() => {
|
|
1432
|
+
expect(mocks.sendSignal).toHaveBeenCalledTimes(1);
|
|
1433
|
+
});
|
|
1434
|
+
expect(mocks.sendSignal).toHaveBeenCalledWith(
|
|
1435
|
+
'agent-run-1',
|
|
1436
|
+
'UserInput',
|
|
1437
|
+
expect.objectContaining({
|
|
1438
|
+
message: 'allow_for_run',
|
|
1439
|
+
metadata: expect.objectContaining({
|
|
1440
|
+
tool_approval_response: {
|
|
1441
|
+
decision: 'allow_for_run',
|
|
1442
|
+
approval_key: 'write_artifact:name:quotes.md',
|
|
1443
|
+
},
|
|
1444
|
+
id: expect.any(String),
|
|
1445
|
+
_messageId: expect.any(String),
|
|
1446
|
+
}),
|
|
1447
|
+
}),
|
|
1448
|
+
);
|
|
1449
|
+
});
|
|
1450
|
+
|
|
1206
1451
|
it('sends directly without restart while the run is active', async () => {
|
|
1207
1452
|
mockStreamState({
|
|
1208
1453
|
messages: [createMessage(AgentMessageType.ANSWER, 'still running')],
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
type CompletedWorkstreamEntry,
|
|
8
8
|
type ConversationFile,
|
|
9
9
|
type ConversationFileRef,
|
|
10
|
+
FileProcessingStatus,
|
|
10
11
|
type McpConnectUxConfig,
|
|
11
12
|
normalizeAgentToolApprovalMode,
|
|
12
13
|
type Plan,
|
|
@@ -1105,7 +1106,6 @@ function StartWorkflowView({
|
|
|
1105
1106
|
size="xs"
|
|
1106
1107
|
variant="ghost"
|
|
1107
1108
|
onClick={onClose}
|
|
1108
|
-
aria-label={t('agent.close')}
|
|
1109
1109
|
title={t('agent.close')}
|
|
1110
1110
|
className="text-muted hover:text-foreground"
|
|
1111
1111
|
>
|
|
@@ -1211,7 +1211,6 @@ function StartWorkflowView({
|
|
|
1211
1211
|
'disabled:bg-mixer-muted/25 disabled:text-muted disabled:opacity-100',
|
|
1212
1212
|
)}
|
|
1213
1213
|
title={resolvedStartButtonText}
|
|
1214
|
-
aria-label={resolvedStartButtonText}
|
|
1215
1214
|
>
|
|
1216
1215
|
{isSending ? <Spinner size="sm" /> : <ArrowUpIcon className="size-4" />}
|
|
1217
1216
|
</Button>
|
|
@@ -1359,12 +1358,8 @@ function ModernAgentConversationInner({
|
|
|
1359
1358
|
updateDocumentTitle,
|
|
1360
1359
|
} = useDocumentPanel(messages);
|
|
1361
1360
|
|
|
1362
|
-
const { processingFiles, hasProcessingFiles, handleFileUpload, removeProcessingFile } =
|
|
1363
|
-
client,
|
|
1364
|
-
agentRunId,
|
|
1365
|
-
serverFileUpdates,
|
|
1366
|
-
toast,
|
|
1367
|
-
);
|
|
1361
|
+
const { processingFiles, hasProcessingFiles, handleFileUpload, removeProcessingFile, clearProcessingFiles } =
|
|
1362
|
+
useFileProcessing(client, agentRunId, serverFileUpdates, toast);
|
|
1368
1363
|
const canUploadFiles = interactive && !hideFileUpload;
|
|
1369
1364
|
|
|
1370
1365
|
const handleRemoveProcessingFile = useCallback(
|
|
@@ -1416,6 +1411,8 @@ function ModernAgentConversationInner({
|
|
|
1416
1411
|
const workstreamFetchFailedRef = useRef(false);
|
|
1417
1412
|
const dragCounterRef = useRef(0);
|
|
1418
1413
|
const pendingPlaybackScrollRef = useRef(false);
|
|
1414
|
+
const toolApprovalModeChangeVersionRef = useRef(0);
|
|
1415
|
+
const pendingToolApprovalModeChangeRef = useRef<Promise<void> | null>(null);
|
|
1419
1416
|
|
|
1420
1417
|
useEffect(() => {
|
|
1421
1418
|
setToolApprovalMode(initialResolvedToolApprovalMode);
|
|
@@ -1426,6 +1423,8 @@ function ModernAgentConversationInner({
|
|
|
1426
1423
|
isSendingRef.current = isSending;
|
|
1427
1424
|
const hasProcessingFilesRef = useRef(hasProcessingFiles);
|
|
1428
1425
|
hasProcessingFilesRef.current = hasProcessingFiles;
|
|
1426
|
+
const processingFilesRef = useRef(processingFiles);
|
|
1427
|
+
processingFilesRef.current = processingFiles;
|
|
1429
1428
|
|
|
1430
1429
|
const lastMainMessage = useMemo(() => {
|
|
1431
1430
|
const mainMessages = messages.filter((m) => (m.workstream_id || 'main') === 'main');
|
|
@@ -1909,6 +1908,13 @@ function ModernAgentConversationInner({
|
|
|
1909
1908
|
// Handlers
|
|
1910
1909
|
// ────────────────────────────────────────────
|
|
1911
1910
|
|
|
1911
|
+
const waitForPendingToolApprovalModeChange = useCallback(async () => {
|
|
1912
|
+
const pendingChange = pendingToolApprovalModeChangeRef.current;
|
|
1913
|
+
if (pendingChange) {
|
|
1914
|
+
await pendingChange;
|
|
1915
|
+
}
|
|
1916
|
+
}, []);
|
|
1917
|
+
|
|
1912
1918
|
// Send a message to the agent
|
|
1913
1919
|
const handleSendMessage = useCallback(
|
|
1914
1920
|
(message: string, inputMetadata?: Record<string, unknown>) => {
|
|
@@ -1942,6 +1948,24 @@ function ModernAgentConversationInner({
|
|
|
1942
1948
|
messageContent = [trimmed, '', 'Attachments:', ...lines].join('\n');
|
|
1943
1949
|
}
|
|
1944
1950
|
|
|
1951
|
+
// Bind ready uploaded files to this message: embed them as an "Uploaded artifacts:"
|
|
1952
|
+
// markdown block so they render underneath the sent bubble (parsed by
|
|
1953
|
+
// parseUserMessageAttachments) and get cleared from the composer on send.
|
|
1954
|
+
const uploadedArtifactLines = Array.from(processingFilesRef.current.values())
|
|
1955
|
+
.filter((file) => file.status === FileProcessingStatus.READY)
|
|
1956
|
+
.map((file) => {
|
|
1957
|
+
const href = file.reference?.startsWith('artifact:')
|
|
1958
|
+
? file.reference
|
|
1959
|
+
: file.artifact_path
|
|
1960
|
+
? `artifact:${file.artifact_path}`
|
|
1961
|
+
: undefined;
|
|
1962
|
+
return href ? `[${file.name}](${href})` : undefined;
|
|
1963
|
+
})
|
|
1964
|
+
.filter((line): line is string => Boolean(line));
|
|
1965
|
+
if (uploadedArtifactLines.length > 0) {
|
|
1966
|
+
messageContent = [messageContent, '', 'Uploaded artifacts:', ...uploadedArtifactLines].join('\n');
|
|
1967
|
+
}
|
|
1968
|
+
|
|
1945
1969
|
const messageId = `msg-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
|
1946
1970
|
|
|
1947
1971
|
const optimisticMessage: AgentMessage = {
|
|
@@ -1973,6 +1997,7 @@ function ModernAgentConversationInner({
|
|
|
1973
1997
|
const markReceived = () => {
|
|
1974
1998
|
updateOptimisticMessageStatus(messageId, 'received');
|
|
1975
1999
|
onAttachmentsSent?.();
|
|
2000
|
+
clearProcessingFiles();
|
|
1976
2001
|
};
|
|
1977
2002
|
|
|
1978
2003
|
// When the workflow has already completed, restart it first so it resumes
|
|
@@ -1980,12 +2005,15 @@ function ModernAgentConversationInner({
|
|
|
1980
2005
|
// buffers the signal until the new run is ready to receive it. We reconnect
|
|
1981
2006
|
// the stream in place (rather than remounting via onRestart) so the existing
|
|
1982
2007
|
// timeline is preserved and the new exchange appends seamlessly at the bottom.
|
|
1983
|
-
const deliver =
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
2008
|
+
const deliver = (async () => {
|
|
2009
|
+
await waitForPendingToolApprovalModeChange();
|
|
2010
|
+
if (isWorkflowTerminalRef.current) {
|
|
2011
|
+
await client.agents.restart(agentRunId);
|
|
2012
|
+
reconnectStream();
|
|
2013
|
+
}
|
|
2014
|
+
await sendUserInput();
|
|
2015
|
+
markReceived();
|
|
2016
|
+
})();
|
|
1989
2017
|
|
|
1990
2018
|
deliver
|
|
1991
2019
|
.catch((err) => {
|
|
@@ -2008,10 +2036,12 @@ function ModernAgentConversationInner({
|
|
|
2008
2036
|
getAttachedDocs,
|
|
2009
2037
|
getMessageContext,
|
|
2010
2038
|
onAttachmentsSent,
|
|
2039
|
+
clearProcessingFiles,
|
|
2011
2040
|
reconnectStream,
|
|
2012
2041
|
addOptimisticMessage,
|
|
2013
2042
|
updateOptimisticMessageStatus,
|
|
2014
2043
|
t,
|
|
2044
|
+
waitForPendingToolApprovalModeChange,
|
|
2015
2045
|
],
|
|
2016
2046
|
);
|
|
2017
2047
|
|
|
@@ -2040,12 +2070,16 @@ function ModernAgentConversationInner({
|
|
|
2040
2070
|
const [mcpDisabled, setMcpDisabled] = useState<string[] | undefined>(undefined);
|
|
2041
2071
|
useEffect(() => {
|
|
2042
2072
|
let cancelled = false;
|
|
2073
|
+
const requestVersion = toolApprovalModeChangeVersionRef.current;
|
|
2043
2074
|
client.agents
|
|
2044
2075
|
.retrieve(agentRunId)
|
|
2045
2076
|
.then((run) => {
|
|
2046
2077
|
if (!cancelled) {
|
|
2047
2078
|
setMcpDisabled(run.disabled_mcp_collections);
|
|
2048
|
-
if (
|
|
2079
|
+
if (
|
|
2080
|
+
toolApprovalModeChangeVersionRef.current === requestVersion &&
|
|
2081
|
+
(run.tool_approval_mode !== undefined || run.interactive !== undefined)
|
|
2082
|
+
) {
|
|
2049
2083
|
setToolApprovalMode(normalizeAgentToolApprovalMode(run.tool_approval_mode, run.interactive));
|
|
2050
2084
|
}
|
|
2051
2085
|
}
|
|
@@ -2081,10 +2115,14 @@ function ModernAgentConversationInner({
|
|
|
2081
2115
|
const handleToolApprovalModeChange = useCallback(
|
|
2082
2116
|
(mode: AgentToolApprovalMode) => {
|
|
2083
2117
|
const nextMode = normalizeAgentToolApprovalMode(mode, interactive);
|
|
2118
|
+
const previousMode = toolApprovalMode;
|
|
2119
|
+
toolApprovalModeChangeVersionRef.current += 1;
|
|
2084
2120
|
setToolApprovalMode(nextMode);
|
|
2085
|
-
client.agents
|
|
2121
|
+
const signalPromise = client.agents
|
|
2086
2122
|
.sendSignal(agentRunId, 'ToolApprovalModeChanged', { mode: nextMode })
|
|
2123
|
+
.then(() => undefined)
|
|
2087
2124
|
.catch((err: unknown) => {
|
|
2125
|
+
setToolApprovalMode((currentMode) => (currentMode === nextMode ? previousMode : currentMode));
|
|
2088
2126
|
toast({
|
|
2089
2127
|
status: 'error',
|
|
2090
2128
|
title: t('agent.approvalMode.changeFailed'),
|
|
@@ -2092,8 +2130,14 @@ function ModernAgentConversationInner({
|
|
|
2092
2130
|
duration: 3000,
|
|
2093
2131
|
});
|
|
2094
2132
|
});
|
|
2133
|
+
pendingToolApprovalModeChangeRef.current = signalPromise;
|
|
2134
|
+
void signalPromise.finally(() => {
|
|
2135
|
+
if (pendingToolApprovalModeChangeRef.current === signalPromise) {
|
|
2136
|
+
pendingToolApprovalModeChangeRef.current = null;
|
|
2137
|
+
}
|
|
2138
|
+
});
|
|
2095
2139
|
},
|
|
2096
|
-
[agentRunId, client, interactive, t, toast],
|
|
2140
|
+
[agentRunId, client, interactive, t, toast, toolApprovalMode],
|
|
2097
2141
|
);
|
|
2098
2142
|
|
|
2099
2143
|
// Drag and drop handlers for full-panel file upload
|
|
@@ -2418,6 +2462,7 @@ function ModernAgentConversationInner({
|
|
|
2418
2462
|
) : (
|
|
2419
2463
|
<AllMessagesMixed
|
|
2420
2464
|
messages={displayedMessages}
|
|
2465
|
+
workstreamSourceMessages={messages}
|
|
2421
2466
|
bottomRef={bottomRef as React.RefObject<HTMLDivElement>}
|
|
2422
2467
|
isCompleted={displayedIsCompleted}
|
|
2423
2468
|
plan={getActivePlan.plan}
|
|
@@ -399,7 +399,12 @@ describe('AllMessagesMixed summary view', () => {
|
|
|
399
399
|
|
|
400
400
|
fireEvent.click(screen.getByRole('button', { name: /Allow once/ }));
|
|
401
401
|
|
|
402
|
-
expect(onSendMessage).toHaveBeenCalledWith('allow_once'
|
|
402
|
+
expect(onSendMessage).toHaveBeenCalledWith('allow_once', {
|
|
403
|
+
tool_approval_response: {
|
|
404
|
+
decision: 'allow_once',
|
|
405
|
+
approval_key: 'create_document:name:News Headlines Today',
|
|
406
|
+
},
|
|
407
|
+
});
|
|
403
408
|
});
|
|
404
409
|
|
|
405
410
|
it('keeps an approved tool approval continuation in the same active work row', () => {
|