@vertesia/ui 1.5.0-dev.20260725.083715Z → 1.5.0-dev.20260804.124748Z

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 (176) hide show
  1. package/lib/core/hooks/useScrollableSearch.d.ts +1 -1
  2. package/lib/core/hooks/useScrollableSearch.d.ts.map +1 -1
  3. package/lib/core/hooks/useScrollableSearch.js +15 -2
  4. package/lib/core/hooks/useScrollableSearch.js.map +1 -1
  5. package/lib/env/index.d.ts +4 -0
  6. package/lib/env/index.d.ts.map +1 -1
  7. package/lib/env/index.js.map +1 -1
  8. package/lib/features/agent/chat/AgentRequestInputOverlay.d.ts +1 -1
  9. package/lib/features/agent/chat/AgentRequestInputOverlay.d.ts.map +1 -1
  10. package/lib/features/agent/chat/AgentRequestInputOverlay.js +3 -8
  11. package/lib/features/agent/chat/AgentRequestInputOverlay.js.map +1 -1
  12. package/lib/features/agent/chat/ModernAgentConversation.d.ts +8 -1
  13. package/lib/features/agent/chat/ModernAgentConversation.d.ts.map +1 -1
  14. package/lib/features/agent/chat/ModernAgentConversation.js +53 -17
  15. package/lib/features/agent/chat/ModernAgentConversation.js.map +1 -1
  16. package/lib/features/agent/chat/ModernAgentOutput/ActiveWorkstreamsSummary.d.ts +2 -1
  17. package/lib/features/agent/chat/ModernAgentOutput/ActiveWorkstreamsSummary.d.ts.map +1 -1
  18. package/lib/features/agent/chat/ModernAgentOutput/ActiveWorkstreamsSummary.js +25 -10
  19. package/lib/features/agent/chat/ModernAgentOutput/ActiveWorkstreamsSummary.js.map +1 -1
  20. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.d.ts.map +1 -1
  21. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js +24 -9
  22. package/lib/features/agent/chat/ModernAgentOutput/AllMessagesMixed.js.map +1 -1
  23. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.d.ts.map +1 -1
  24. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.js +15 -3
  25. package/lib/features/agent/chat/ModernAgentOutput/AttachmentPreview.js.map +1 -1
  26. package/lib/features/agent/chat/ModernAgentOutput/Header.js +2 -2
  27. package/lib/features/agent/chat/ModernAgentOutput/Header.js.map +1 -1
  28. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts +2 -1
  29. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.d.ts.map +1 -1
  30. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js +2 -2
  31. package/lib/features/agent/chat/ModernAgentOutput/MessageInput.js.map +1 -1
  32. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.d.ts.map +1 -1
  33. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js +2 -2
  34. package/lib/features/agent/chat/ModernAgentOutput/MessageItem.js.map +1 -1
  35. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.d.ts +9 -1
  36. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.d.ts.map +1 -1
  37. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.js +97 -2
  38. package/lib/features/agent/chat/ModernAgentOutput/requestInputMessages.js.map +1 -1
  39. package/lib/features/agent/chat/hooks/useAgentStream.d.ts.map +1 -1
  40. package/lib/features/agent/chat/hooks/useAgentStream.js +12 -3
  41. package/lib/features/agent/chat/hooks/useAgentStream.js.map +1 -1
  42. package/lib/features/agent/chat/index.d.ts +1 -1
  43. package/lib/features/agent/chat/index.d.ts.map +1 -1
  44. package/lib/features/agent/chat/index.js.map +1 -1
  45. package/lib/features/facets/PromptsFacetsNav.d.ts +8 -6
  46. package/lib/features/facets/PromptsFacetsNav.d.ts.map +1 -1
  47. package/lib/features/facets/PromptsFacetsNav.js +8 -3
  48. package/lib/features/facets/PromptsFacetsNav.js.map +1 -1
  49. package/lib/features/store/objects/components/ContentOverview.js +4 -1
  50. package/lib/features/store/objects/components/ContentOverview.js.map +1 -1
  51. package/lib/features/store/objects/components/useContentPanelHooks.d.ts +31 -3
  52. package/lib/features/store/objects/components/useContentPanelHooks.d.ts.map +1 -1
  53. package/lib/features/store/objects/search/DocumentSearchContext.d.ts +128 -6
  54. package/lib/features/store/objects/search/DocumentSearchContext.d.ts.map +1 -1
  55. package/lib/features/store/objects/selection/ObjectsActionContext.d.ts +2 -2
  56. package/lib/features/store/objects/selection/ObjectsActionContext.d.ts.map +1 -1
  57. package/lib/features/store/objects/selection/ObjectsActionSpec.d.ts +2 -2
  58. package/lib/features/store/objects/selection/ObjectsActionSpec.d.ts.map +1 -1
  59. package/lib/features/store/objects/selection/SelectionActions.d.ts +2 -2
  60. package/lib/features/store/objects/selection/SelectionActions.d.ts.map +1 -1
  61. package/lib/features/store/objects/upload/DocumentUploadModal.js.map +1 -1
  62. package/lib/features/store/types/IntakePolicyEditor.d.ts +2 -2
  63. package/lib/features/store/types/IntakePolicyEditor.d.ts.map +1 -1
  64. package/lib/features/store/types/IntakePolicyEditor.js +5 -1
  65. package/lib/features/store/types/IntakePolicyEditor.js.map +1 -1
  66. package/lib/features/store/types/IntakePolicyForm.d.ts.map +1 -1
  67. package/lib/features/store/types/IntakePolicyForm.js +2 -2
  68. package/lib/features/store/types/IntakePolicyForm.js.map +1 -1
  69. package/lib/features/store/types/ObjectSchemaEditor.d.ts +2 -2
  70. package/lib/features/store/types/ObjectSchemaEditor.d.ts.map +1 -1
  71. package/lib/features/store/types/SelectContentType.d.ts +2 -2
  72. package/lib/features/store/types/SelectContentType.d.ts.map +1 -1
  73. package/lib/features/store/types/SelectContentType.js.map +1 -1
  74. package/lib/features/store/types/SelectContentTypeModal.d.ts +2 -2
  75. package/lib/features/store/types/SelectContentTypeModal.d.ts.map +1 -1
  76. package/lib/features/store/types/SelectContentTypeModal.js.map +1 -1
  77. package/lib/features/store/types/TableLayoutEditor.d.ts +2 -2
  78. package/lib/features/store/types/TableLayoutEditor.d.ts.map +1 -1
  79. package/lib/features/store/types/TypeRegistry.d.ts +1267 -6
  80. package/lib/features/store/types/TypeRegistry.d.ts.map +1 -1
  81. package/lib/features/store/types/TypeRegistry.js +5 -0
  82. package/lib/features/store/types/TypeRegistry.js.map +1 -1
  83. package/lib/features/store/types/intake-policy-editor.logic.d.ts +9 -1
  84. package/lib/features/store/types/intake-policy-editor.logic.d.ts.map +1 -1
  85. package/lib/features/store/types/intake-policy-editor.logic.js +9 -1
  86. package/lib/features/store/types/intake-policy-editor.logic.js.map +1 -1
  87. package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts +1251 -1
  88. package/lib/features/store/types/search/ObjectTypeSearchContext.d.ts.map +1 -1
  89. package/lib/features/store/types/typeCatalogCache.d.ts +2 -2
  90. package/lib/features/store/types/typeCatalogCache.d.ts.map +1 -1
  91. package/lib/features/store/types/typeCatalogCache.js +4 -0
  92. package/lib/features/store/types/typeCatalogCache.js.map +1 -1
  93. package/lib/features/user/UserInfo.d.ts +118 -7
  94. package/lib/features/user/UserInfo.d.ts.map +1 -1
  95. package/lib/i18n/locales/ar.json +2 -1
  96. package/lib/i18n/locales/de.json +2 -1
  97. package/lib/i18n/locales/en.json +5 -1
  98. package/lib/i18n/locales/es.json +2 -1
  99. package/lib/i18n/locales/fr.json +2 -1
  100. package/lib/i18n/locales/it.json +2 -1
  101. package/lib/i18n/locales/ja.json +2 -1
  102. package/lib/i18n/locales/ko.json +2 -1
  103. package/lib/i18n/locales/pt.json +2 -1
  104. package/lib/i18n/locales/ru.json +2 -1
  105. package/lib/i18n/locales/tr.json +2 -1
  106. package/lib/i18n/locales/zh-TW.json +2 -1
  107. package/lib/i18n/locales/zh.json +2 -1
  108. package/lib/session/UserSession.d.ts +18 -4
  109. package/lib/session/UserSession.d.ts.map +1 -1
  110. package/lib/shell/apps/AppInstallationProvider.d.ts +222 -2
  111. package/lib/shell/apps/AppInstallationProvider.d.ts.map +1 -1
  112. package/lib/shell/login/InviteAcceptModal.js.map +1 -1
  113. package/lib/vertesia-ui-core.js +1 -1
  114. package/lib/vertesia-ui-core.js.map +1 -1
  115. package/lib/vertesia-ui-features.js +34 -34
  116. package/lib/vertesia-ui-features.js.map +1 -1
  117. package/lib/vertesia-ui-i18n.js +13 -13
  118. package/lib/vertesia-ui-widgets.js +18 -18
  119. package/lib/vertesia-ui-widgets.js.map +1 -1
  120. package/lib/widgets/monacoEditor/MonacoEditor.d.ts +7 -1
  121. package/lib/widgets/monacoEditor/MonacoEditor.d.ts.map +1 -1
  122. package/lib/widgets/monacoEditor/MonacoEditor.js +62 -6
  123. package/lib/widgets/monacoEditor/MonacoEditor.js.map +1 -1
  124. package/package.json +31 -31
  125. package/src/core/hooks/useScrollableSearch.test.tsx +139 -0
  126. package/src/core/hooks/useScrollableSearch.tsx +17 -3
  127. package/src/env/index.ts +2 -0
  128. package/src/features/agent/chat/AgentChatPlaybackControls.test.tsx +2 -0
  129. package/src/features/agent/chat/AgentRequestInputOverlay.test.tsx +51 -6
  130. package/src/features/agent/chat/AgentRequestInputOverlay.tsx +5 -7
  131. package/src/features/agent/chat/ModernAgentConversation.test.tsx +195 -3
  132. package/src/features/agent/chat/ModernAgentConversation.tsx +106 -22
  133. package/src/features/agent/chat/ModernAgentOutput/ActiveWorkstreamsSummary.tsx +75 -20
  134. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.test.tsx +112 -0
  135. package/src/features/agent/chat/ModernAgentOutput/AllMessagesMixed.tsx +62 -10
  136. package/src/features/agent/chat/ModernAgentOutput/AttachmentPreview.tsx +36 -4
  137. package/src/features/agent/chat/ModernAgentOutput/Header.tsx +2 -2
  138. package/src/features/agent/chat/ModernAgentOutput/MessageInput.test.tsx +71 -0
  139. package/src/features/agent/chat/ModernAgentOutput/MessageInput.tsx +3 -1
  140. package/src/features/agent/chat/ModernAgentOutput/MessageItem.tsx +18 -4
  141. package/src/features/agent/chat/ModernAgentOutput/requestInputMessages.test.ts +95 -0
  142. package/src/features/agent/chat/ModernAgentOutput/requestInputMessages.ts +111 -1
  143. package/src/features/agent/chat/hooks/useAgentStream.test.tsx +57 -0
  144. package/src/features/agent/chat/hooks/useAgentStream.ts +12 -3
  145. package/src/features/agent/chat/index.ts +1 -0
  146. package/src/features/facets/PromptsFacetsNav.tsx +17 -9
  147. package/src/features/store/objects/components/ContentOverview.tsx +4 -1
  148. package/src/features/store/objects/selection/ObjectsActionContext.tsx +2 -2
  149. package/src/features/store/objects/selection/ObjectsActionSpec.ts +2 -2
  150. package/src/features/store/objects/selection/SelectionActions.tsx +2 -2
  151. package/src/features/store/objects/upload/DocumentUploadModal.tsx +2 -2
  152. package/src/features/store/types/IntakePolicyEditor.tsx +12 -4
  153. package/src/features/store/types/IntakePolicyForm.tsx +25 -0
  154. package/src/features/store/types/ObjectSchemaEditor.tsx +2 -2
  155. package/src/features/store/types/SelectContentType.tsx +9 -9
  156. package/src/features/store/types/SelectContentTypeModal.tsx +5 -5
  157. package/src/features/store/types/TableLayoutEditor.tsx +2 -2
  158. package/src/features/store/types/TypeRegistry.ts +8 -3
  159. package/src/features/store/types/intake-policy-editor.logic.ts +10 -2
  160. package/src/features/store/types/typeCatalogCache.ts +10 -3
  161. package/src/i18n/locales/ar.json +2 -1
  162. package/src/i18n/locales/de.json +2 -1
  163. package/src/i18n/locales/en.json +5 -1
  164. package/src/i18n/locales/es.json +2 -1
  165. package/src/i18n/locales/fr.json +2 -1
  166. package/src/i18n/locales/it.json +2 -1
  167. package/src/i18n/locales/ja.json +2 -1
  168. package/src/i18n/locales/ko.json +2 -1
  169. package/src/i18n/locales/pt.json +2 -1
  170. package/src/i18n/locales/ru.json +2 -1
  171. package/src/i18n/locales/tr.json +2 -1
  172. package/src/i18n/locales/zh-TW.json +2 -1
  173. package/src/i18n/locales/zh.json +2 -1
  174. package/src/shell/login/InviteAcceptModal.tsx +4 -4
  175. package/src/widgets/markdown/ArtifactEditingSurface.test.tsx +13 -9
  176. package/src/widgets/monacoEditor/MonacoEditor.tsx +83 -7
@@ -7,15 +7,17 @@ import { RemoteMcpConnectionButton } from '../../oauth/RemoteMcpConnectionButton
7
7
  import { AskUserWidget } from './AskUserWidget';
8
8
  import {
9
9
  getRequestInputDisplayText,
10
+ getRequestInputResponseMetadata,
10
11
  getToolApprovalResponseMetadata,
11
12
  type RequestInputMessageWithUx,
13
+ sendRequestInputResponse,
12
14
  } from './ModernAgentOutput/requestInputMessages';
13
15
 
14
16
  export interface AgentRequestInputOverlayProps {
15
17
  message?: RequestInputMessageWithUx;
16
18
  onSendMessage?: (message: string, metadata?: Record<string, unknown>) => void;
17
19
  /** Called after the user connects the MCP server requested by request_mcp_connection. */
18
- onMcpConnected?: (cfg: McpConnectUxConfig) => void;
20
+ onMcpConnected?: (cfg: McpConnectUxConfig, metadata?: Record<string, unknown>) => void;
19
21
  isLoading?: boolean;
20
22
  disabled?: boolean;
21
23
  className?: string;
@@ -81,11 +83,7 @@ export function AgentRequestInputOverlay({
81
83
  const displayText = getRequestInputDisplayText(message);
82
84
  const send = (value: string, metadata?: Record<string, unknown>) => {
83
85
  if (isDisabled) return;
84
- if (metadata) {
85
- onSendMessage?.(value, metadata);
86
- } else {
87
- onSendMessage?.(value);
88
- }
86
+ sendRequestInputResponse(onSendMessage, message, value, metadata);
89
87
  };
90
88
 
91
89
  const wrapperClassName = cn(
@@ -102,7 +100,7 @@ export function AgentRequestInputOverlay({
102
100
  <div className="min-w-0 text-sm leading-6 text-foreground/85">{displayText}</div>
103
101
  <McpRequestInputControls
104
102
  mcpConnect={mcpConnect}
105
- onMcpConnected={onMcpConnected}
103
+ onMcpConnected={(cfg) => onMcpConnected?.(cfg, getRequestInputResponseMetadata(message))}
106
104
  onDecline={() => send(t('agent.mcpDeclinedMessage', { name: mcpConnect.name }))}
107
105
  disabled={isDisabled}
108
106
  />
@@ -60,6 +60,7 @@ vi.mock('./ModernAgentOutput/MessageInput', () => ({
60
60
  onSend: (message: string) => void;
61
61
  activeTaskCount?: number;
62
62
  activeWorkstreams?: Array<{ workstream_id: string; status: string }>;
63
+ onSelectWorkstream?: (workstreamId: string) => void;
63
64
  isCompleted?: boolean;
64
65
  isStreaming?: boolean;
65
66
  placeholder?: string;
@@ -90,6 +91,7 @@ vi.mock('./ModernAgentOutput/AllMessagesMixed', async () => {
90
91
  return {
91
92
  default: ({
92
93
  messages,
94
+ workstreamSourceMessages,
93
95
  streamingMessages,
94
96
  isCompleted,
95
97
  bottomRef,
@@ -101,6 +103,7 @@ vi.mock('./ModernAgentOutput/AllMessagesMixed', async () => {
101
103
  onActiveWorkstreamChange,
102
104
  }: {
103
105
  messages: AgentMessage[];
106
+ workstreamSourceMessages?: AgentMessage[];
104
107
  streamingMessages: Map<string, unknown>;
105
108
  isCompleted?: boolean;
106
109
  bottomRef: React.RefObject<HTMLDivElement>;
@@ -118,6 +121,7 @@ vi.mock('./ModernAgentOutput/AllMessagesMixed', async () => {
118
121
  );
119
122
  mocks.allMessagesMixedProps({
120
123
  messages,
124
+ workstreamSourceMessages,
121
125
  streamingMessages,
122
126
  isCompleted,
123
127
  onSendMessage,
@@ -230,6 +234,7 @@ function latestAllMessagesMixedProps() {
230
234
  return calls[calls.length - 1]?.[0] as
231
235
  | {
232
236
  messages: AgentMessage[];
237
+ workstreamSourceMessages?: AgentMessage[];
233
238
  streamingMessages: Map<string, unknown>;
234
239
  isCompleted?: boolean;
235
240
  showInitialRequest?: boolean;
@@ -253,6 +258,7 @@ function latestRightPanelProps() {
253
258
  describe('ModernAgentConversation send handling', () => {
254
259
  beforeEach(() => {
255
260
  vi.clearAllMocks();
261
+ window.sessionStorage.clear();
256
262
  mocks.restart.mockResolvedValue({ id: 'agent-run-1' });
257
263
  mocks.sendSignal.mockResolvedValue({});
258
264
  mocks.getActiveWorkstreams.mockResolvedValue({ running: [] });
@@ -561,6 +567,37 @@ describe('ModernAgentConversation send handling', () => {
561
567
  });
562
568
  });
563
569
 
570
+ it('collapses staged files in the start composer', () => {
571
+ const { container } = renderWithProviders(
572
+ <ModernAgentConversation startWorkflow={vi.fn()} hideHeader initialMessage="" />,
573
+ );
574
+ const input = container.querySelector('input[type="file"]');
575
+ const files = ['alpha.txt', 'beta.txt', 'gamma.txt', 'delta.txt', 'epsilon.txt'].map(
576
+ (name) => new File([name], name, { type: 'text/plain' }),
577
+ );
578
+
579
+ if (!input) {
580
+ throw new Error('Expected hidden file input to be rendered');
581
+ }
582
+
583
+ fireEvent.change(input, {
584
+ target: {
585
+ files,
586
+ },
587
+ });
588
+
589
+ expect(screen.getByText('alpha.txt')).not.toBeNull();
590
+ expect(screen.getByText('gamma.txt')).not.toBeNull();
591
+ expect(screen.queryByText('delta.txt')).toBeNull();
592
+ expect(screen.getByText('+2')).not.toBeNull();
593
+
594
+ fireEvent.click(screen.getByRole('button', { name: /show more/i }));
595
+
596
+ expect(screen.getByText('delta.txt')).not.toBeNull();
597
+ expect(screen.getByText('epsilon.txt')).not.toBeNull();
598
+ expect(screen.getByRole('button', { name: /show less/i })).not.toBeNull();
599
+ });
600
+
564
601
  it('signals approval mode changes for an active interactive run', async () => {
565
602
  mocks.retrieve.mockResolvedValue({
566
603
  tool_approval_mode: 'ask',
@@ -916,11 +953,13 @@ describe('ModernAgentConversation send handling', () => {
916
953
  onCompactContext?: () => void | Promise<void>;
917
954
  };
918
955
 
956
+ // Prompt-based, matching the server's checkpoint trigger: `total`
957
+ // includes reasoning/result tokens that never re-enter context.
919
958
  expect(latestMessageInputProps.contextWindowUsage).toEqual({
920
- usedTokens: 50_000,
959
+ usedTokens: 40_000,
921
960
  checkpointTokens: 100_000,
922
- usedPercent: 50,
923
- remainingPercent: 50,
961
+ usedPercent: 40,
962
+ remainingPercent: 60,
924
963
  });
925
964
 
926
965
  act(() => {
@@ -1059,6 +1098,7 @@ describe('ModernAgentConversation send handling', () => {
1059
1098
  const latestMessageInputProps = mocks.messageInputProps.mock.lastCall?.[0] as {
1060
1099
  activeTaskCount?: number;
1061
1100
  activeWorkstreams?: Array<{ workstream_id: string; status: string }>;
1101
+ onSelectWorkstream?: (workstreamId: string) => void;
1062
1102
  };
1063
1103
 
1064
1104
  expect(latestRightPanelProps.activeWorkstreams).toEqual([
@@ -1072,6 +1112,12 @@ describe('ModernAgentConversation send handling', () => {
1072
1112
  expect.objectContaining({ workstream_id: 'alpha', status: 'running' }),
1073
1113
  expect.objectContaining({ workstream_id: 'beta', status: 'running' }),
1074
1114
  ]);
1115
+
1116
+ act(() => {
1117
+ latestMessageInputProps.onSelectWorkstream?.('beta');
1118
+ });
1119
+
1120
+ expect(latestAllMessagesMixedProps()?.activeWorkstream).toBe('beta');
1075
1121
  });
1076
1122
 
1077
1123
  it('shows completed workstream lifecycle messages in the panel without counting them as active', async () => {
@@ -1488,6 +1534,80 @@ describe('ModernAgentConversation send handling', () => {
1488
1534
  );
1489
1535
  });
1490
1536
 
1537
+ it('does not resurrect an answered request overlay after remounting before the persisted echo arrives', async () => {
1538
+ const requestMessage = {
1539
+ ...createMessage(AgentMessageType.REQUEST_INPUT, 'What is your favorite color?'),
1540
+ details: {
1541
+ request_id: 'ask-user-1',
1542
+ ux: {
1543
+ options: [
1544
+ { id: 'red', label: 'Red' },
1545
+ { id: 'blue', label: 'Blue' },
1546
+ ],
1547
+ },
1548
+ },
1549
+ };
1550
+ mockStreamState({
1551
+ messages: [requestMessage],
1552
+ isCompleted: false,
1553
+ agentRunStatus: 'RUNNING',
1554
+ });
1555
+
1556
+ const firstView = renderConversation();
1557
+ fireEvent.click(screen.getByRole('button', { name: /Blue/ }));
1558
+
1559
+ await waitFor(() => expect(mocks.sendSignal).toHaveBeenCalledTimes(1));
1560
+ expect(mocks.sendSignal).toHaveBeenCalledWith(
1561
+ 'agent-run-1',
1562
+ 'UserInput',
1563
+ expect.objectContaining({
1564
+ metadata: expect.objectContaining({
1565
+ request_input_response: { request_id: 'ask-user-1' },
1566
+ }),
1567
+ }),
1568
+ );
1569
+
1570
+ firstView.unmount();
1571
+ renderConversation();
1572
+
1573
+ expect(screen.queryByText('What is your favorite color?')).toBeNull();
1574
+ expect(screen.queryByRole('button', { name: /Blue/ })).toBeNull();
1575
+ });
1576
+
1577
+ it('restores a request overlay after its response fails to send', async () => {
1578
+ mocks.sendSignal.mockRejectedValueOnce(new Error('offline'));
1579
+ const requestMessage = {
1580
+ ...createMessage(AgentMessageType.REQUEST_INPUT, 'What is your favorite color?'),
1581
+ details: {
1582
+ request_id: 'ask-user-1',
1583
+ ux: {
1584
+ options: [
1585
+ { id: 'red', label: 'Red' },
1586
+ { id: 'blue', label: 'Blue' },
1587
+ ],
1588
+ },
1589
+ },
1590
+ };
1591
+ mockStreamState({
1592
+ messages: [requestMessage],
1593
+ isCompleted: false,
1594
+ agentRunStatus: 'RUNNING',
1595
+ });
1596
+
1597
+ const firstView = renderConversation();
1598
+ fireEvent.click(screen.getByRole('button', { name: /Blue/ }));
1599
+
1600
+ await waitFor(() => {
1601
+ expect(mocks.updateOptimisticMessageStatus).toHaveBeenCalledWith(expect.any(String), 'failed');
1602
+ });
1603
+
1604
+ firstView.unmount();
1605
+ renderConversation();
1606
+
1607
+ expect(screen.getByText('What is your favorite color?')).not.toBeNull();
1608
+ expect(screen.getByRole('button', { name: /Blue/ })).not.toBeNull();
1609
+ });
1610
+
1491
1611
  it('hides a stale request overlay when a terminal run cannot continue', () => {
1492
1612
  mockStreamState({
1493
1613
  messages: [
@@ -2213,6 +2333,78 @@ describe('ModernAgentConversation send handling', () => {
2213
2333
  );
2214
2334
  });
2215
2335
 
2336
+ it('hides the debug button when the playback toggle is disabled', () => {
2337
+ mockStreamState({
2338
+ messages: [
2339
+ createMessage(AgentMessageType.QUESTION, 'first question'),
2340
+ createMessage(AgentMessageType.ANSWER, 'first answer'),
2341
+ ],
2342
+ });
2343
+
2344
+ renderConversation({ hideHeader: false, showPlaybackToggle: false });
2345
+
2346
+ expect(mocks.headerProps).toHaveBeenLastCalledWith(
2347
+ expect.objectContaining({
2348
+ showPlaybackButton: false,
2349
+ }),
2350
+ );
2351
+ });
2352
+
2353
+ it('filters transcript messages as conversation history updates', () => {
2354
+ const question = createMessage(AgentMessageType.QUESTION, 'Summarize the launch plan.');
2355
+ const internalUpdate = createMessage(AgentMessageType.UPDATE, 'Inspecting internal records.');
2356
+ const answer = createMessage(AgentMessageType.ANSWER, 'The launch plan is ready.');
2357
+ const messageFilter = (message: AgentMessage) => message.type !== AgentMessageType.UPDATE;
2358
+
2359
+ mockStreamState({ messages: [question, internalUpdate] });
2360
+ const view = renderConversation({ messageFilter });
2361
+
2362
+ expect(latestAllMessagesMixedProps()?.messages).toEqual([question]);
2363
+ expect(latestAllMessagesMixedProps()?.workstreamSourceMessages).toEqual([question]);
2364
+
2365
+ mockStreamState({ messages: [question, internalUpdate, answer] });
2366
+ view.rerender(
2367
+ <ModernAgentConversation
2368
+ agentRunId="agent-run-1"
2369
+ title="Agent"
2370
+ hideHeader
2371
+ hideMessageInput
2372
+ showRightPanel={false}
2373
+ messageFilter={messageFilter}
2374
+ />,
2375
+ );
2376
+
2377
+ expect(latestAllMessagesMixedProps()?.messages).toEqual([question, answer]);
2378
+ expect(latestAllMessagesMixedProps()?.workstreamSourceMessages).toEqual([question, answer]);
2379
+ expect(document.querySelector('[data-agent-live-message-count="3"]')).not.toBeNull();
2380
+ expect(document.querySelector('[data-agent-rendered-message-count="2"]')).not.toBeNull();
2381
+ });
2382
+
2383
+ it('keeps pending request input actionable when its transcript row is filtered out', () => {
2384
+ const requestMessage = {
2385
+ ...createMessage(AgentMessageType.REQUEST_INPUT, 'Choose a deployment region.'),
2386
+ details: {
2387
+ ux: {
2388
+ options: [
2389
+ { id: 'us', label: 'United States' },
2390
+ { id: 'eu', label: 'Europe' },
2391
+ ],
2392
+ },
2393
+ },
2394
+ };
2395
+ mockStreamState({
2396
+ messages: [requestMessage],
2397
+ isCompleted: false,
2398
+ agentRunStatus: 'RUNNING',
2399
+ });
2400
+
2401
+ renderConversation({ messageFilter: () => false });
2402
+
2403
+ expect(latestAllMessagesMixedProps()?.messages).toEqual([]);
2404
+ expect(screen.getByText('Choose a deployment region.')).not.toBeNull();
2405
+ expect(screen.getByRole('button', { name: /Europe/ })).not.toBeNull();
2406
+ });
2407
+
2216
2408
  it('keeps the replay fixture export action available while messages are empty', () => {
2217
2409
  mockStreamState({
2218
2410
  messages: [],
@@ -35,7 +35,7 @@ import {
35
35
  AgentResourceResolverProvider,
36
36
  isArtifactRefreshEvent,
37
37
  } from '@vertesia/ui/widgets';
38
- import { ArrowUpIcon, Bot, CheckCircle, Cpu, FileTextIcon, UploadIcon, XIcon } from 'lucide-react';
38
+ import { ArrowUpIcon, Bot, CheckCircle, ChevronDown, Cpu, FileTextIcon, UploadIcon, XIcon } from 'lucide-react';
39
39
  import type React from 'react';
40
40
  import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
41
41
  import { McpConnectionsActionMenu } from '../../oauth/McpConnectionsButton.js';
@@ -65,7 +65,14 @@ import MessageInput, {
65
65
  type UploadedFile,
66
66
  } from './ModernAgentOutput/MessageInput';
67
67
  import type { MessageItemClassNames } from './ModernAgentOutput/MessageItem';
68
- import { getPendingRequestInputMessage } from './ModernAgentOutput/requestInputMessages';
68
+ import {
69
+ clearRequestInputIdAnsweredForSession,
70
+ getPendingRequestInputMessage,
71
+ getRequestInputResolutionKey,
72
+ getRequestInputResponseIdFromMetadata,
73
+ isRequestInputAnsweredForSession,
74
+ markRequestInputIdAnsweredForSession,
75
+ } from './ModernAgentOutput/requestInputMessages';
69
76
  import type { StreamingMessageClassNames } from './ModernAgentOutput/StreamingMessage';
70
77
  import type { ToolCallGroupClassNames } from './ModernAgentOutput/ToolCallGroup';
71
78
  import {
@@ -98,6 +105,9 @@ export interface StartWorkflowOptions {
98
105
  tool_approval_mode?: AgentToolApprovalMode;
99
106
  }
100
107
 
108
+ /** Controls whether a persisted message is included in the rendered conversation transcript. */
109
+ export type AgentMessageFilter = (message: AgentMessage) => boolean;
110
+
101
111
  export type StartWorkflowFn = (
102
112
  initialMessage?: string,
103
113
  options?: StartWorkflowOptions,
@@ -106,6 +116,7 @@ export type StartWorkflowFn = (
106
116
  export type SendAgentMessageFn = (message: string, inputMetadata?: Record<string, unknown>) => void;
107
117
 
108
118
  const EMPTY_STREAMING_MESSAGES = new Map<string, never>();
119
+ const COLLAPSED_STAGED_FILE_COUNT = 3;
109
120
 
110
121
  function getTimestampMs(timestamp: number | string | undefined): number {
111
122
  if (typeof timestamp === 'number') return timestamp;
@@ -137,8 +148,13 @@ function toContextWindowUsage(messages: AgentMessage[]): ContextWindowUsage | un
137
148
  const details = messages[index].details;
138
149
  if (!details) continue;
139
150
 
140
- const tokenUsage = details.token_usage as { total?: unknown } | undefined;
141
- const usedTokens = getNumberDetail(tokenUsage?.total);
151
+ const tokenUsage = details.token_usage as { prompt?: unknown; total?: unknown } | undefined;
152
+ // Mirror the server's checkpoint trigger (getContextPressureTokens):
153
+ // the last call's prompt size is the actual context; `total` includes
154
+ // reasoning output that never re-enters context and would overstate
155
+ // pressure on reasoning models. Fall back to total when no breakdown.
156
+ const promptTokens = getNumberDetail(tokenUsage?.prompt);
157
+ const usedTokens = promptTokens && promptTokens > 0 ? promptTokens : getNumberDetail(tokenUsage?.total);
142
158
  const checkpointTokens =
143
159
  getNumberDetail(details.checkpoint_threshold) ?? getNumberDetail(details.checkpoint_at);
144
160
 
@@ -648,6 +664,11 @@ export interface ModernAgentConversationProps {
648
664
  hideDocumentPanel?: boolean;
649
665
  /** Message types to exclude from the conversation view */
650
666
  hiddenMessageTypes?: AgentMessageType[];
667
+ /**
668
+ * Optional presentation-only predicate for transcript messages. Returning false hides the message without
669
+ * changing the underlying conversation stream, workflow state, plans, or pending input.
670
+ */
671
+ messageFilter?: AgentMessageFilter;
651
672
 
652
673
  // Callback to get attached documents when sending messages
653
674
  // Returns array of { id, name } to include in message metadata and display
@@ -727,7 +748,7 @@ export interface ModernAgentConversationProps {
727
748
  initialToolApprovalMode?: AgentToolApprovalMode;
728
749
  /** Force display playback controls on or off. When omitted, local playback can be toggled from the header. */
729
750
  enablePlayback?: boolean;
730
- /** Show a local toggle for display playback controls in the conversation action rail. */
751
+ /** Show the Debug button for local playback controls. Defaults to true; set to false to hide it. */
731
752
  showPlaybackToggle?: boolean;
732
753
  }
733
754
 
@@ -817,6 +838,9 @@ function StartWorkflowView({
817
838
 
818
839
  // Staged files - stored locally until workflow starts
819
840
  const [stagedFiles, setStagedFiles] = useState<File[]>([]);
841
+ const [areStagedFilesExpanded, setAreStagedFilesExpanded] = useState(false);
842
+ const visibleStagedFiles = areStagedFilesExpanded ? stagedFiles : stagedFiles.slice(0, COLLAPSED_STAGED_FILE_COUNT);
843
+ const hiddenStagedFileCount = stagedFiles.length - COLLAPSED_STAGED_FILE_COUNT;
820
844
 
821
845
  useEffect(() => {
822
846
  onAgentWorkingChange?.(isSending);
@@ -914,6 +938,12 @@ function StartWorkflowView({
914
938
  setStagedFiles((prev) => prev.filter((_, i) => i !== index));
915
939
  }, []);
916
940
 
941
+ useEffect(() => {
942
+ if (stagedFiles.length <= COLLAPSED_STAGED_FILE_COUNT) {
943
+ setAreStagedFilesExpanded(false);
944
+ }
945
+ }, [stagedFiles.length]);
946
+
917
947
  useEffect(() => {
918
948
  // Focus the input field when component mounts
919
949
  if (inputRef.current) {
@@ -1208,7 +1238,7 @@ function StartWorkflowView({
1208
1238
  {/* Staged files display */}
1209
1239
  {canStageFiles && stagedFiles.length > 0 && (
1210
1240
  <div className="flex flex-wrap gap-2">
1211
- {stagedFiles.map((file, index) => (
1241
+ {visibleStagedFiles.map((file, index) => (
1212
1242
  <div
1213
1243
  key={`${file.name}-${file.size}-${file.lastModified}`}
1214
1244
  className="flex items-center gap-1.5 rounded-md bg-attention/10 px-2 py-1 text-sm text-attention"
@@ -1227,6 +1257,26 @@ function StartWorkflowView({
1227
1257
  </Button>
1228
1258
  </div>
1229
1259
  ))}
1260
+ {stagedFiles.length > COLLAPSED_STAGED_FILE_COUNT && (
1261
+ <Button
1262
+ type="button"
1263
+ variant="ghost"
1264
+ size="sm"
1265
+ aria-expanded={areStagedFilesExpanded}
1266
+ onClick={() => setAreStagedFilesExpanded((expanded) => !expanded)}
1267
+ className="h-8 shrink-0 gap-1 rounded-xl px-2.5 text-xs text-muted"
1268
+ >
1269
+ {!areStagedFilesExpanded && <span>+{hiddenStagedFileCount}</span>}
1270
+ {areStagedFilesExpanded ? t('agent.showLess') : t('agent.showMore')}
1271
+ <ChevronDown
1272
+ className={cn(
1273
+ 'size-3.5 transition-transform',
1274
+ areStagedFilesExpanded && 'rotate-180',
1275
+ )}
1276
+ aria-hidden="true"
1277
+ />
1278
+ </Button>
1279
+ )}
1230
1280
  </div>
1231
1281
  )}
1232
1282
 
@@ -1392,6 +1442,7 @@ function ModernAgentConversationInner({
1392
1442
  initialToolApprovalMode,
1393
1443
  enablePlayback,
1394
1444
  showPlaybackToggle = true,
1445
+ messageFilter,
1395
1446
  }: ModernAgentConversationProps & { agentRunId: string }) {
1396
1447
  const { t } = useUITranslation();
1397
1448
  const { client } = useUserSession();
@@ -1621,13 +1672,16 @@ function ModernAgentConversationInner({
1621
1672
 
1622
1673
  const canShowPlaybackToggle = showPlaybackToggle && enablePlayback === undefined && isAgentChatPlaybackAvailable();
1623
1674
  const isPlaybackEnabled = enablePlayback ?? (isAgentChatPlaybackEnabled() || isPlaybackToggleEnabled);
1624
- const playbackSourceMessages = useMemo(() => {
1675
+ const transcriptSourceMessages = useMemo(() => {
1625
1676
  // Passive artifact autosaves are surfaced by the editor's own save indicator, not the chat.
1626
- const visibleMessages = messages.filter(
1677
+ return messages.filter(
1627
1678
  (message) => !isPassiveArtifactUpdate(message) && !(hiddenMessageTypes?.includes(message.type) ?? false),
1628
1679
  );
1629
- return filterMessagesForActiveWorkstream(visibleMessages, activeWorkstream);
1630
- }, [activeWorkstream, hiddenMessageTypes, messages]);
1680
+ }, [hiddenMessageTypes, messages]);
1681
+ const playbackSourceMessages = useMemo(
1682
+ () => filterMessagesForActiveWorkstream(transcriptSourceMessages, activeWorkstream),
1683
+ [activeWorkstream, transcriptSourceMessages],
1684
+ );
1631
1685
  const playbackState = useMemo(
1632
1686
  () => createPlaybackState(playbackSourceMessages, playbackCursor, isPlaybackEnabled),
1633
1687
  [isPlaybackEnabled, playbackCursor, playbackSourceMessages],
@@ -1637,6 +1691,17 @@ function ModernAgentConversationInner({
1637
1691
  const isPlaybackAtLatest =
1638
1692
  isPlaybackEnabled && !isPlaybackLive && playbackState.cursorIndex === playbackSourceMessages.length - 1;
1639
1693
  const displayedMessages = playbackState.displayedMessages;
1694
+ const renderedMessages = useMemo(
1695
+ () => (messageFilter ? displayedMessages.filter((message) => messageFilter(message)) : displayedMessages),
1696
+ [displayedMessages, messageFilter],
1697
+ );
1698
+ const renderedWorkstreamSourceMessages = useMemo(
1699
+ () =>
1700
+ messageFilter
1701
+ ? transcriptSourceMessages.filter((message) => messageFilter(message))
1702
+ : transcriptSourceMessages,
1703
+ [messageFilter, transcriptSourceMessages],
1704
+ );
1640
1705
  const displayedStreamingMessages = isPlaybackLive ? streamingMessages : EMPTY_STREAMING_MESSAGES;
1641
1706
  const playbackDerivedWorkstreams = useMemo(
1642
1707
  () => deriveWorkstreamsFromMessages(displayedMessages),
@@ -1653,10 +1718,15 @@ function ModernAgentConversationInner({
1653
1718
  useEffect(() => {
1654
1719
  onAgentWorkingChange?.(isAgentWorking);
1655
1720
  }, [isAgentWorking, onAgentWorkingChange]);
1656
- const pendingRequestInputMessage = useMemo(
1657
- () => getPendingRequestInputMessage(displayedMessages),
1658
- [displayedMessages],
1659
- );
1721
+ const pendingRequestInputMessage = useMemo(() => {
1722
+ const answeredRequestInputKeys = new Set<string>();
1723
+ for (const message of displayedMessages) {
1724
+ if (isRequestInputAnsweredForSession(agentRunId, message)) {
1725
+ answeredRequestInputKeys.add(getRequestInputResolutionKey(message));
1726
+ }
1727
+ }
1728
+ return getPendingRequestInputMessage(displayedMessages, 'main', answeredRequestInputKeys);
1729
+ }, [agentRunId, displayedMessages]);
1660
1730
  // A terminal run silently drops user input unless it can be continued (see the guard in
1661
1731
  // handleSendMessage), so never surface an actionable question overlay in that state —
1662
1732
  // the terminal status box renders instead.
@@ -1675,7 +1745,7 @@ function ModernAgentConversationInner({
1675
1745
  agentRunId,
1676
1746
  messageCount: messages.length,
1677
1747
  playbackMessageCount: playbackSourceMessages.length,
1678
- displayedMessageCount: displayedMessages.length,
1748
+ displayedMessageCount: renderedMessages.length,
1679
1749
  streamingCount: streamingMessages.size,
1680
1750
  displayedStreamingCount: displayedStreamingMessages.size,
1681
1751
  initialHistoryStatus,
@@ -1699,7 +1769,7 @@ function ModernAgentConversationInner({
1699
1769
  agentRunStatus,
1700
1770
  clampedPlaybackCursor,
1701
1771
  displayedIsCompleted,
1702
- displayedMessages.length,
1772
+ renderedMessages.length,
1703
1773
  displayedStreamingMessages.size,
1704
1774
  effectiveIsCompleted,
1705
1775
  effectiveWorkflowStatus,
@@ -2078,6 +2148,7 @@ function ModernAgentConversationInner({
2078
2148
  return;
2079
2149
  }
2080
2150
 
2151
+ const requestInputId = getRequestInputResponseIdFromMetadata(inputMetadata);
2081
2152
  setIsSending(true);
2082
2153
 
2083
2154
  const attachedDocs = getAttachedDocs?.() || [];
@@ -2120,6 +2191,9 @@ function ModernAgentConversationInner({
2120
2191
  _messageId: messageId,
2121
2192
  _deliveryStatus: 'sending',
2122
2193
  ...(inputMetadata?.editing_action ? { editing_action: inputMetadata.editing_action } : {}),
2194
+ ...(inputMetadata?.request_input_response
2195
+ ? { request_input_response: inputMetadata.request_input_response }
2196
+ : {}),
2123
2197
  },
2124
2198
  };
2125
2199
 
@@ -2151,6 +2225,9 @@ function ModernAgentConversationInner({
2151
2225
  // buffers the signal until the new run is ready to receive it. We reconnect
2152
2226
  // the stream in place (rather than remounting via onRestart) so the existing
2153
2227
  // timeline is preserved and the new exchange appends seamlessly at the bottom.
2228
+ if (requestInputId) {
2229
+ markRequestInputIdAnsweredForSession(agentRunId, requestInputId);
2230
+ }
2154
2231
  const deliver = (async () => {
2155
2232
  await waitForPendingToolApprovalModeChange();
2156
2233
  if (isWorkflowTerminalRef.current) {
@@ -2163,6 +2240,9 @@ function ModernAgentConversationInner({
2163
2240
 
2164
2241
  deliver
2165
2242
  .catch((err) => {
2243
+ if (requestInputId) {
2244
+ clearRequestInputIdAnsweredForSession(agentRunId, requestInputId);
2245
+ }
2166
2246
  updateOptimisticMessageStatus(messageId, 'failed');
2167
2247
  toast({
2168
2248
  status: 'error',
@@ -2202,7 +2282,7 @@ function ModernAgentConversationInner({
2202
2282
  // conversation for tool re-discovery and resume it with a confirmation message so the agent
2203
2283
  // continues automatically with the newly-available tools.
2204
2284
  const handleMcpConnected = useCallback(
2205
- async (cfg: McpConnectUxConfig) => {
2285
+ async (cfg: McpConnectUxConfig, metadata?: Record<string, unknown>) => {
2206
2286
  // Await the dirty-flag signal BEFORE sending the follow-up message so Temporal records
2207
2287
  // McpConfigChanged ahead of the UserInput. Otherwise the resume turn could run before
2208
2288
  // the flag is set and use the stale tool catalog. An omitted disabled list preserves
@@ -2212,7 +2292,7 @@ function ModernAgentConversationInner({
2212
2292
  } catch (err: unknown) {
2213
2293
  console.error('Failed to signal MCP config change', err);
2214
2294
  }
2215
- handleSendMessage(t('agent.mcpConnectedMessage', { name: cfg.name }));
2295
+ handleSendMessage(t('agent.mcpConnectedMessage', { name: cfg.name }), metadata);
2216
2296
  },
2217
2297
  [client, agentRunId, handleSendMessage, t],
2218
2298
  );
@@ -2594,7 +2674,7 @@ function ModernAgentConversationInner({
2594
2674
  data-agent-playback-enabled={isPlaybackEnabled || undefined}
2595
2675
  data-agent-playback-cursor={isPlaybackEnabled ? clampedPlaybackCursor : undefined}
2596
2676
  data-agent-live-message-count={messages.length}
2597
- data-agent-rendered-message-count={displayedMessages.length}
2677
+ data-agent-rendered-message-count={renderedMessages.length}
2598
2678
  className={cn(
2599
2679
  'flex flex-col min-h-0 min-w-0 border-0',
2600
2680
  conversationTab
@@ -2629,8 +2709,8 @@ function ModernAgentConversationInner({
2629
2709
  <PendingStartConversation message={pendingStartMessage} startedAt={pendingStartTimestamp} />
2630
2710
  ) : (
2631
2711
  <AllMessagesMixed
2632
- messages={displayedMessages}
2633
- workstreamSourceMessages={messages}
2712
+ messages={renderedMessages}
2713
+ workstreamSourceMessages={renderedWorkstreamSourceMessages}
2634
2714
  bottomRef={bottomRef as React.RefObject<HTMLDivElement>}
2635
2715
  isCompleted={displayedIsCompleted}
2636
2716
  plan={getActivePlan.plan}
@@ -2681,7 +2761,10 @@ function ModernAgentConversationInner({
2681
2761
  />
2682
2762
  ) : isViewingPlaybackHistory && playbackActiveWorkstreams.length > 0 ? (
2683
2763
  <div className="flex-shrink-0 pb-safe-area">
2684
- <ActiveWorkstreamsSummary activeWorkstreams={playbackActiveWorkstreams} />
2764
+ <ActiveWorkstreamsSummary
2765
+ activeWorkstreams={playbackActiveWorkstreams}
2766
+ onSelectWorkstream={setActiveWorkstream}
2767
+ />
2685
2768
  </div>
2686
2769
  ) : (
2687
2770
  shouldRenderLiveMessageInputArea && (
@@ -2749,6 +2832,7 @@ function ModernAgentConversationInner({
2749
2832
  isCompactingContext={isCompactingContext}
2750
2833
  activeTaskCount={activeTaskCount}
2751
2834
  activeWorkstreams={composerActiveWorkstreams}
2835
+ onSelectWorkstream={setActiveWorkstream}
2752
2836
  placeholder={composerPlaceholder}
2753
2837
  onFilesSelected={canUploadFiles ? handleFileUpload : undefined}
2754
2838
  uploadedFiles={uploadedFiles}