@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
@@ -1,7 +1,7 @@
1
- import { cn } from '@vertesia/ui/core';
1
+ import { Button, cn } from '@vertesia/ui/core';
2
2
  import { useUITranslation } from '@vertesia/ui/i18n';
3
- import { Bot } from 'lucide-react';
4
- import { useMemo } from 'react';
3
+ import { Bot, ChevronDown, ChevronRight } from 'lucide-react';
4
+ import { useEffect, useId, useMemo, useState } from 'react';
5
5
  import {
6
6
  formatWorkstreamName,
7
7
  getWorkstreamDisplayName,
@@ -11,17 +11,35 @@ import {
11
11
 
12
12
  interface ActiveWorkstreamsSummaryProps {
13
13
  activeWorkstreams: WorkstreamInfo[];
14
+ onSelectWorkstream?: (workstreamId: string) => void;
14
15
  className?: string;
15
16
  }
16
17
 
17
- export function ActiveWorkstreamsSummary({ activeWorkstreams, className }: ActiveWorkstreamsSummaryProps) {
18
+ const COLLAPSED_WORKSTREAM_COUNT = 3;
19
+
20
+ export function ActiveWorkstreamsSummary({
21
+ activeWorkstreams,
22
+ onSelectWorkstream,
23
+ className,
24
+ }: ActiveWorkstreamsSummaryProps) {
18
25
  const { t } = useUITranslation();
26
+ const [isExpanded, setIsExpanded] = useState(false);
27
+ const listId = useId();
19
28
  const runningWorkstreams = useMemo(
20
29
  () => activeWorkstreams.filter((ws) => ws.status === 'running' || ws.status === 'canceling'),
21
30
  [activeWorkstreams],
22
31
  );
23
- const visibleRunningWorkstreams = runningWorkstreams.slice(0, 3);
32
+ const visibleRunningWorkstreams = isExpanded
33
+ ? runningWorkstreams
34
+ : runningWorkstreams.slice(0, COLLAPSED_WORKSTREAM_COUNT);
24
35
  const hiddenRunningWorkstreamCount = Math.max(0, runningWorkstreams.length - visibleRunningWorkstreams.length);
36
+ const canExpand = runningWorkstreams.length > COLLAPSED_WORKSTREAM_COUNT;
37
+
38
+ useEffect(() => {
39
+ if (!canExpand) {
40
+ setIsExpanded(false);
41
+ }
42
+ }, [canExpand]);
25
43
 
26
44
  if (runningWorkstreams.length === 0) return null;
27
45
 
@@ -31,23 +49,20 @@ export function ActiveWorkstreamsSummary({ activeWorkstreams, className }: Activ
31
49
  className="flex flex-col gap-1.5 rounded-2xl border border-border/70 bg-background/95 p-2 text-xs text-muted shadow-lg shadow-black/5"
32
50
  aria-live="polite"
33
51
  >
34
- <div className="flex items-center gap-2 px-1 font-medium">
35
- <Bot className="size-3.5 text-muted" aria-hidden="true" />
36
- <span>{t('agent.activeWorkstreams', { count: runningWorkstreams.length })}</span>
52
+ <div className="flex min-w-0 items-center gap-2 px-1 font-medium">
53
+ <Bot className="size-3.5 shrink-0 text-muted" aria-hidden="true" />
54
+ <span className="truncate">
55
+ {t('agent.activeWorkstreams', { count: runningWorkstreams.length })}
56
+ </span>
37
57
  </div>
38
- <div className="flex flex-col gap-0.5">
58
+ <div id={listId} className="flex flex-col gap-0.5">
39
59
  {visibleRunningWorkstreams.map((workstream) => {
40
60
  const workstreamName = getWorkstreamDisplayName(
41
61
  workstream.workstream_id,
42
62
  workstream.interaction,
43
63
  );
44
-
45
- return (
46
- <span
47
- key={workstream.launch_id || workstream.workstream_id}
48
- className="flex min-w-0 items-center gap-2 rounded-lg px-1 py-1 text-sm text-foreground/80"
49
- title={workstreamName}
50
- >
64
+ const content = (
65
+ <>
51
66
  <span
52
67
  className={cn(
53
68
  'size-1.5 shrink-0 rounded-full',
@@ -61,13 +76,53 @@ export function ActiveWorkstreamsSummary({ activeWorkstreams, className }: Activ
61
76
  {formatWorkstreamName(workstream.phase)}
62
77
  </span>
63
78
  )}
64
- </span>
79
+ </>
80
+ );
81
+
82
+ return onSelectWorkstream ? (
83
+ <Button
84
+ key={workstream.launch_id || workstream.workstream_id}
85
+ type="button"
86
+ variant="ghost"
87
+ className="h-auto min-w-0 justify-start gap-2 rounded-lg px-1 py-1 text-sm text-foreground/80"
88
+ title={workstreamName}
89
+ onClick={() => {
90
+ setIsExpanded(false);
91
+ onSelectWorkstream(workstream.workstream_id);
92
+ }}
93
+ >
94
+ {content}
95
+ <ChevronRight className="ms-auto size-3.5 shrink-0 rtl:rotate-180" aria-hidden="true" />
96
+ </Button>
97
+ ) : (
98
+ <div
99
+ key={workstream.launch_id || workstream.workstream_id}
100
+ className="flex min-w-0 items-center gap-2 rounded-lg px-1 py-1 text-sm text-foreground/80"
101
+ title={workstreamName}
102
+ >
103
+ {content}
104
+ </div>
65
105
  );
66
106
  })}
67
- {hiddenRunningWorkstreamCount > 0 && (
68
- <span className="px-1 py-1 text-xs text-muted">+{hiddenRunningWorkstreamCount}</span>
69
- )}
70
107
  </div>
108
+ {canExpand && (
109
+ <Button
110
+ type="button"
111
+ variant="ghost"
112
+ size="sm"
113
+ className="h-8 w-fit shrink-0 gap-1 rounded-xl px-2.5 text-xs text-muted"
114
+ aria-expanded={isExpanded}
115
+ aria-controls={listId}
116
+ onClick={() => setIsExpanded((expanded) => !expanded)}
117
+ >
118
+ {!isExpanded && <span>+{hiddenRunningWorkstreamCount}</span>}
119
+ {isExpanded ? t('agent.showLess') : t('agent.showMore')}
120
+ <ChevronDown
121
+ className={cn('size-3.5 transition-transform', isExpanded && 'rotate-180')}
122
+ aria-hidden="true"
123
+ />
124
+ </Button>
125
+ )}
71
126
  </output>
72
127
  </div>
73
128
  );
@@ -258,6 +258,114 @@ describe('AllMessagesMixed summary view', () => {
258
258
  expect(screen.getByText('Found 5 results')).not.toBeNull();
259
259
  });
260
260
 
261
+ it('does not show post-tool thinking while the latest tool is still running', () => {
262
+ renderSummary(
263
+ [
264
+ makeMessage({
265
+ timestamp: 1_000,
266
+ type: AgentMessageType.QUESTION,
267
+ message: 'Find Japan news.',
268
+ }),
269
+ makeMessage({
270
+ timestamp: 2_000,
271
+ message: 'Searching for Japan news',
272
+ details: {
273
+ tool: 'web_search_serper',
274
+ tool_status: 'running',
275
+ tool_run_id: 'tool-1',
276
+ },
277
+ }),
278
+ ],
279
+ false,
280
+ );
281
+
282
+ expect(screen.getAllByRole('button', { name: /Working.*for/ })).toHaveLength(1);
283
+ expect(screen.queryByTestId('post-tool-thinking-indicator')).toBeNull();
284
+ });
285
+
286
+ it('shows thinking below the active work group after its latest tool completes', () => {
287
+ renderSummary(
288
+ [
289
+ makeMessage({
290
+ timestamp: 1_000,
291
+ type: AgentMessageType.QUESTION,
292
+ message: 'Find Japan news.',
293
+ }),
294
+ makeMessage({
295
+ timestamp: 2_000,
296
+ message: 'Searching for Japan news',
297
+ details: {
298
+ tool: 'web_search_serper',
299
+ tool_status: 'running',
300
+ tool_run_id: 'tool-1',
301
+ },
302
+ }),
303
+ makeMessage({
304
+ timestamp: 3_000,
305
+ message: 'Found Japan news',
306
+ details: {
307
+ tool: 'web_search_serper',
308
+ tool_status: 'completed',
309
+ tool_run_id: 'tool-1',
310
+ },
311
+ }),
312
+ ],
313
+ false,
314
+ );
315
+
316
+ expect(screen.queryByRole('button', { name: /Worked\s*for/ })).toBeNull();
317
+ expect(screen.getAllByRole('button', { name: /Working.*for/ })).toHaveLength(1);
318
+ expect(screen.getByTestId('post-tool-thinking-indicator').textContent).toBe('Thinking...');
319
+ });
320
+
321
+ it('keeps showing thinking when the current display-role marker follows a completed tool', () => {
322
+ renderSummary(
323
+ [
324
+ makeMessage({
325
+ timestamp: 1_000,
326
+ type: AgentMessageType.QUESTION,
327
+ message: 'Find Japan news.',
328
+ }),
329
+ makeMessage({
330
+ timestamp: 2_000,
331
+ message: 'Searching for Japan news',
332
+ details: {
333
+ event_class: 'activity',
334
+ tool: 'web_search_serper',
335
+ tool_status: 'running',
336
+ tool_run_id: 'tool-1',
337
+ activity_group_id: 'activity-1',
338
+ },
339
+ }),
340
+ makeMessage({
341
+ timestamp: 3_000,
342
+ message: 'Found Japan news',
343
+ details: {
344
+ event_class: 'activity',
345
+ tool: 'web_search_serper',
346
+ tool_status: 'completed',
347
+ tool_run_id: 'tool-1',
348
+ activity_group_id: 'activity-1',
349
+ },
350
+ }),
351
+ makeMessage({
352
+ timestamp: 4_000,
353
+ message: 'Thinking...',
354
+ details: {
355
+ event_class: 'activity',
356
+ display_role: 'thinking',
357
+ activity_id: '17',
358
+ activity_group_id: 'activity-1',
359
+ },
360
+ }),
361
+ ],
362
+ false,
363
+ );
364
+
365
+ expect(screen.getAllByRole('button', { name: /Working.*for/ })).toHaveLength(1);
366
+ expect(screen.getByTestId('post-tool-thinking-indicator').textContent).toBe('Thinking...');
367
+ });
368
+
261
369
  it('replaces a completed tool’s progress title with an immediately visible resource deep link', () => {
262
370
  renderSummary(
263
371
  [
@@ -464,6 +572,7 @@ describe('AllMessagesMixed summary view', () => {
464
572
  type: AgentMessageType.REQUEST_INPUT,
465
573
  message: 'Approve Create Document: name News Headlines Today?',
466
574
  details: {
575
+ request_id: 'create_document:name:News Headlines Today',
467
576
  tool_approval: {
468
577
  approval_key: 'create_document:name:News Headlines Today',
469
578
  tool_name: 'create_document',
@@ -499,6 +608,9 @@ describe('AllMessagesMixed summary view', () => {
499
608
  decision: 'allow_once',
500
609
  approval_key: 'create_document:name:News Headlines Today',
501
610
  },
611
+ request_input_response: {
612
+ request_id: 'create_document:name:News Headlines Today',
613
+ },
502
614
  });
503
615
  });
504
616
 
@@ -68,6 +68,7 @@ import {
68
68
  isToolApprovalRequestInput,
69
69
  isToolApprovalRequestInputHidden,
70
70
  type RequestInputMessageWithUx,
71
+ sendRequestInputResponse,
71
72
  } from './requestInputMessages';
72
73
  import {
73
74
  interleaveTurnSummaries,
@@ -92,6 +93,7 @@ import {
92
93
  getWorkstreamId,
93
94
  groupMessagesWithStreaming,
94
95
  isInProgress,
96
+ isToolActivityMessage,
95
97
  isToolPreambleMessage,
96
98
  isUserStoppedMessage,
97
99
  mergeConsecutiveToolGroups,
@@ -753,13 +755,22 @@ function SummaryMessage({
753
755
  variant={uxConfig.variant}
754
756
  multiSelect={uxConfig.multiSelect}
755
757
  onSelect={(optionId) =>
756
- onSendMessage?.(optionId, getToolApprovalResponseMetadata(message, optionId))
758
+ sendRequestInputResponse(
759
+ onSendMessage,
760
+ message,
761
+ optionId,
762
+ getToolApprovalResponseMetadata(message, optionId),
763
+ )
764
+ }
765
+ onMultiSelect={(optionIds) =>
766
+ sendRequestInputResponse(onSendMessage, message, optionIds.join(', '))
757
767
  }
758
- onMultiSelect={(optionIds) => onSendMessage?.(optionIds.join(', '))}
759
768
  allowFreeResponse={!uxConfig.options?.length || !!uxConfig.free_response}
760
769
  placeholder={uxConfig.free_response?.placeholder}
761
770
  submitLabel={uxConfig.free_response?.submit_label}
762
- onSubmit={(value) => onSendMessage?.(value, uxConfig.free_response?.metadata)}
771
+ onSubmit={(value) =>
772
+ sendRequestInputResponse(onSendMessage, message, value, uxConfig.free_response?.metadata)
773
+ }
763
774
  hideBorder
764
775
  compact
765
776
  answered={requestInputAnswered}
@@ -2273,19 +2284,32 @@ function SummaryActivityRow({
2273
2284
  variant={uxConfig.variant}
2274
2285
  multiSelect={uxConfig.multiSelect}
2275
2286
  onSelect={(optionId) =>
2276
- onSendMessage?.(
2287
+ sendRequestInputResponse(
2288
+ onSendMessage,
2289
+ message,
2277
2290
  optionId,
2278
2291
  getToolApprovalResponseMetadata(message, optionId),
2279
2292
  )
2280
2293
  }
2281
- onMultiSelect={(optionIds) => onSendMessage?.(optionIds.join(', '))}
2294
+ onMultiSelect={(optionIds) =>
2295
+ sendRequestInputResponse(
2296
+ onSendMessage,
2297
+ message,
2298
+ optionIds.join(', '),
2299
+ )
2300
+ }
2282
2301
  allowFreeResponse={
2283
2302
  !uxConfig.options?.length || !!uxConfig.free_response
2284
2303
  }
2285
2304
  placeholder={uxConfig.free_response?.placeholder}
2286
2305
  submitLabel={uxConfig.free_response?.submit_label}
2287
2306
  onSubmit={(value) =>
2288
- onSendMessage?.(value, uxConfig.free_response?.metadata)
2307
+ sendRequestInputResponse(
2308
+ onSendMessage,
2309
+ message,
2310
+ value,
2311
+ uxConfig.free_response?.metadata,
2312
+ )
2289
2313
  }
2290
2314
  hideBorder
2291
2315
  compact
@@ -2663,18 +2687,23 @@ function AllMessagesMixedComponent({
2663
2687
  });
2664
2688
  }, []);
2665
2689
 
2690
+ const previousActiveWorkstreamRef = useRef(activeWorkstream);
2691
+ useEffect(() => {
2692
+ if (previousActiveWorkstreamRef.current === activeWorkstream) return;
2693
+ previousActiveWorkstreamRef.current = activeWorkstream;
2694
+ requestAnimationFrame(scrollToTop);
2695
+ }, [activeWorkstream, scrollToTop]);
2696
+
2666
2697
  const handleSelectWorkstream = useCallback(
2667
2698
  (workstreamId: string) => {
2668
2699
  setActiveWorkstream(workstreamId);
2669
- requestAnimationFrame(scrollToTop);
2670
2700
  },
2671
- [scrollToTop, setActiveWorkstream],
2701
+ [setActiveWorkstream],
2672
2702
  );
2673
2703
 
2674
2704
  const handleShowMainAgentChat = useCallback(() => {
2675
2705
  setActiveWorkstream('all');
2676
- requestAnimationFrame(scrollToTop);
2677
- }, [scrollToTop, setActiveWorkstream]);
2706
+ }, [setActiveWorkstream]);
2678
2707
 
2679
2708
  useEffect(() => {
2680
2709
  if (activeWorkstream !== 'all' && !workstreams.has(activeWorkstream)) {
@@ -2945,6 +2974,18 @@ function AllMessagesMixedComponent({
2945
2974
  return !isDisplayCompleted || hasOpenUserTurn(completionDisplayMessages);
2946
2975
  }, [completionDisplayMessages, isDisplayCompleted]);
2947
2976
 
2977
+ const showPostToolThinking = useMemo(() => {
2978
+ if (!isAgentWorking || incompleteStreaming.length > 0) return false;
2979
+
2980
+ const latestSummaryItem = summaryConversationItems[summaryConversationItems.length - 1];
2981
+ const latestMessage = completionDisplayMessages[completionDisplayMessages.length - 1];
2982
+ const latestMessageShowsThinking =
2983
+ latestMessage !== undefined &&
2984
+ (latestMessage.details?.display_role === 'thinking' ||
2985
+ (isToolActivityMessage(latestMessage) && latestMessage.details?.tool_status === 'completed'));
2986
+ return latestSummaryItem?.type === 'work' && latestSummaryItem.isActive && latestMessageShowsThinking;
2987
+ }, [completionDisplayMessages, incompleteStreaming.length, isAgentWorking, summaryConversationItems]);
2988
+
2948
2989
  const showActivityFallback = shouldShowSummaryActivityFallback(
2949
2990
  summaryConversationItems,
2950
2991
  isAgentWorking,
@@ -3661,6 +3702,17 @@ function AllMessagesMixedComponent({
3661
3702
  artifactRunId={artifactRunId}
3662
3703
  />
3663
3704
  ))}
3705
+ {showPostToolThinking && (
3706
+ <div
3707
+ className={cn(
3708
+ 'mx-auto w-full max-w-3xl px-1 text-sm text-muted',
3709
+ workingIndicatorClassName,
3710
+ )}
3711
+ data-testid="post-tool-thinking-indicator"
3712
+ >
3713
+ {t('agent.thinking')}
3714
+ </div>
3715
+ )}
3664
3716
  {/* Activity fallback - shown before any tool/thought message has arrived */}
3665
3717
  {showActivityFallback && !showInitialRequestWaitingCard && (
3666
3718
  <SummaryActivityRow
@@ -1,9 +1,9 @@
1
1
  import { Button, cn } from '@vertesia/ui/core';
2
2
  import { useUITranslation } from '@vertesia/ui/i18n';
3
3
  import { UserSessionContext } from '@vertesia/ui/session';
4
- import { FileTextIcon, ImageIcon, XIcon } from 'lucide-react';
4
+ import { ChevronDown, FileTextIcon, ImageIcon, XIcon } from 'lucide-react';
5
5
  import type React from 'react';
6
- import { useContext, useEffect, useState } from 'react';
6
+ import { useContext, useEffect, useId, useState } from 'react';
7
7
  import { useImageLightbox } from '../ImageLightbox';
8
8
  import { getArtifactCacheKey, useArtifactUrlCache } from '../useArtifactUrlCache';
9
9
 
@@ -26,6 +26,7 @@ const TEXT_EXTENSIONS = new Set([
26
26
  'yaml',
27
27
  'yml',
28
28
  ]);
29
+ const DEFAULT_COLLAPSED_ATTACHMENT_COUNT = 3;
29
30
 
30
31
  export interface AttachmentPreviewItem {
31
32
  id: string;
@@ -374,11 +375,24 @@ export function AttachmentPreviewList({
374
375
  StoreLinkComponent,
375
376
  CollectionLinkComponent,
376
377
  }: AttachmentPreviewListProps) {
378
+ const { t } = useUITranslation();
379
+ const [isExpanded, setIsExpanded] = useState(false);
380
+ const attachmentListId = useId();
381
+ const isCollapsible = variant === 'composer' && items.length > DEFAULT_COLLAPSED_ATTACHMENT_COUNT;
382
+ const visibleItems = isCollapsible && !isExpanded ? items.slice(0, DEFAULT_COLLAPSED_ATTACHMENT_COUNT) : items;
383
+ const hiddenItemCount = items.length - DEFAULT_COLLAPSED_ATTACHMENT_COUNT;
384
+
385
+ useEffect(() => {
386
+ if (items.length <= DEFAULT_COLLAPSED_ATTACHMENT_COUNT) {
387
+ setIsExpanded(false);
388
+ }
389
+ }, [items.length]);
390
+
377
391
  if (items.length === 0) return null;
378
392
 
379
393
  return (
380
- <div className={cn('flex flex-wrap gap-2', align === 'end' && 'justify-end', className)}>
381
- {items.map((item) => (
394
+ <div id={attachmentListId} className={cn('flex flex-wrap gap-2', align === 'end' && 'justify-end', className)}>
395
+ {visibleItems.map((item) => (
382
396
  <AttachmentPreview
383
397
  key={item.id}
384
398
  item={item}
@@ -390,6 +404,24 @@ export function AttachmentPreviewList({
390
404
  CollectionLinkComponent={CollectionLinkComponent}
391
405
  />
392
406
  ))}
407
+ {isCollapsible && (
408
+ <Button
409
+ type="button"
410
+ variant="ghost"
411
+ size="sm"
412
+ aria-controls={attachmentListId}
413
+ aria-expanded={isExpanded}
414
+ onClick={() => setIsExpanded((expanded) => !expanded)}
415
+ className="h-8 shrink-0 gap-1 rounded-xl px-2.5 text-xs text-muted"
416
+ >
417
+ {!isExpanded && <span>+{hiddenItemCount}</span>}
418
+ {isExpanded ? t('agent.showLess') : t('agent.showMore')}
419
+ <ChevronDown
420
+ className={cn('size-3.5 transition-transform', isExpanded && 'rotate-180')}
421
+ aria-hidden="true"
422
+ />
423
+ </Button>
424
+ )}
393
425
  </div>
394
426
  );
395
427
  }
@@ -5,6 +5,7 @@ import { useRouterContext } from '@vertesia/ui/router';
5
5
  import { useUserSession } from '@vertesia/ui/session';
6
6
  import {
7
7
  Bot,
8
+ Bug,
8
9
  ClipboardList,
9
10
  CopyIcon,
10
11
  DownloadCloudIcon,
@@ -13,7 +14,6 @@ import {
13
14
  InfoIcon,
14
15
  MessageSquareText,
15
16
  MoreVertical,
16
- Rewind,
17
17
  Rows3,
18
18
  XIcon,
19
19
  } from 'lucide-react';
@@ -185,7 +185,7 @@ export default function Header({
185
185
  variant === 'compact' && 'size-8 rounded-lg',
186
186
  )}
187
187
  >
188
- <Rewind className={cn('size-4', variant === 'full' && 'me-1.5')} />
188
+ <Bug className={cn('size-4', variant === 'full' && 'me-1.5')} />
189
189
  {variant === 'full' ? (
190
190
  <span className="font-medium text-xs">{t('agent.rewind.label')}</span>
191
191
  ) : (
@@ -201,6 +201,38 @@ describe('MessageInput', () => {
201
201
  expect(screen.getByText('Ready')).not.toBeNull();
202
202
  });
203
203
 
204
+ it('collapses large attachment lists while preserving attachment actions', () => {
205
+ const onRemoveDocument = vi.fn();
206
+ const selectedDocuments = ['Alpha', 'Beta', 'Gamma', 'Delta', 'Epsilon'].map((name, index) => ({
207
+ id: `doc-${index}`,
208
+ name,
209
+ }));
210
+
211
+ renderWithProviders(
212
+ <MessageInput onSend={vi.fn()} selectedDocuments={selectedDocuments} onRemoveDocument={onRemoveDocument} />,
213
+ );
214
+
215
+ expect(screen.getByText('Alpha')).not.toBeNull();
216
+ expect(screen.getByText('Gamma')).not.toBeNull();
217
+ expect(screen.queryByText('Delta')).toBeNull();
218
+ expect(screen.getByText('+2')).not.toBeNull();
219
+
220
+ fireEvent.click(screen.getByRole('button', { name: /show more/i }));
221
+
222
+ expect(screen.getByText('Delta')).not.toBeNull();
223
+ expect(screen.getByText('Epsilon')).not.toBeNull();
224
+ expect(screen.getByRole('button', { name: /show less/i })).not.toBeNull();
225
+
226
+ fireEvent.click(screen.getByRole('button', { name: /Remove Epsilon/i }));
227
+
228
+ expect(onRemoveDocument).toHaveBeenCalledWith('doc-4');
229
+
230
+ fireEvent.click(screen.getByRole('button', { name: /show less/i }));
231
+
232
+ expect(screen.queryByText('Delta')).toBeNull();
233
+ expect(screen.getByText('+2')).not.toBeNull();
234
+ });
235
+
204
236
  it('renders context usage and triggers manual compaction', async () => {
205
237
  const onCompactContext = vi.fn();
206
238
 
@@ -269,4 +301,43 @@ describe('MessageInput', () => {
269
301
  tray.compareDocumentPosition(screen.getByRole('textbox')) & Node.DOCUMENT_POSITION_FOLLOWING,
270
302
  ).toBeTruthy();
271
303
  });
304
+
305
+ it('collapses large workstream lists and navigates from a selected workstream', () => {
306
+ const onSelectWorkstream = vi.fn();
307
+ const activeWorkstreams = ['alpha', 'beta', 'gamma', 'delta', 'epsilon'].map((workstream_id, index) => ({
308
+ workstream_id,
309
+ launch_id: `launch-${index}`,
310
+ elapsed_ms: 0,
311
+ deadline_ms: 0,
312
+ remaining_ms: 0,
313
+ status: 'running' as const,
314
+ }));
315
+
316
+ renderWithProviders(
317
+ <MessageInput
318
+ onSend={vi.fn()}
319
+ isStreaming
320
+ activeWorkstreams={activeWorkstreams}
321
+ onSelectWorkstream={onSelectWorkstream}
322
+ />,
323
+ );
324
+
325
+ expect(screen.getByRole('button', { name: 'Alpha' })).not.toBeNull();
326
+ expect(screen.getByRole('button', { name: 'Gamma' })).not.toBeNull();
327
+ expect(screen.queryByRole('button', { name: 'Delta' })).toBeNull();
328
+ expect(screen.getByText('+2')).not.toBeNull();
329
+ expect(screen.getByText('Show more')).not.toBeNull();
330
+
331
+ fireEvent.click(screen.getByRole('button', { name: /show more/i }));
332
+
333
+ expect(screen.getByRole('button', { name: 'Delta' })).not.toBeNull();
334
+ expect(screen.getByRole('button', { name: 'Epsilon' })).not.toBeNull();
335
+ expect(screen.getByRole('button', { name: /show less/i })).not.toBeNull();
336
+
337
+ fireEvent.click(screen.getByRole('button', { name: 'Delta' }));
338
+
339
+ expect(onSelectWorkstream).toHaveBeenCalledWith('delta');
340
+ expect(screen.queryByRole('button', { name: 'Delta' })).toBeNull();
341
+ expect(screen.getByRole('button', { name: /show more/i })).not.toBeNull();
342
+ });
272
343
  });
@@ -66,6 +66,7 @@ interface MessageInputProps {
66
66
  isCompactingContext?: boolean;
67
67
  activeTaskCount?: number;
68
68
  activeWorkstreams?: WorkstreamInfo[];
69
+ onSelectWorkstream?: (workstreamId: string) => void;
69
70
  placeholder?: string;
70
71
 
71
72
  // File upload props
@@ -133,6 +134,7 @@ export default function MessageInput({
133
134
  isCompactingContext = false,
134
135
  activeTaskCount = 0,
135
136
  activeWorkstreams = [],
137
+ onSelectWorkstream,
136
138
  placeholder,
137
139
  // File upload props
138
140
  onFilesSelected,
@@ -474,7 +476,7 @@ export default function MessageInput({
474
476
  />
475
477
  )}
476
478
 
477
- <ActiveWorkstreamsSummary activeWorkstreams={activeWorkstreams} />
479
+ <ActiveWorkstreamsSummary activeWorkstreams={activeWorkstreams} onSelectWorkstream={onSelectWorkstream} />
478
480
 
479
481
  {/* Input row */}
480
482
  <div className="mx-auto flex max-w-3xl flex-col gap-2 rounded-2xl border border-border/70 bg-mixer-muted/15 p-2.5 shadow-lg shadow-black/5">
@@ -37,7 +37,7 @@ import { createAgentMarkdownAnchor } from './AgentMarkdownAnchor';
37
37
  import { AttachmentPreviewList, parseUserMessageAttachments } from './AttachmentPreview';
38
38
  import { MessageDeliveryStatus } from './MessageDeliveryStatus';
39
39
  import { processContentForMarkdown } from './processContentForMarkdown';
40
- import { getToolApprovalResponseMetadata } from './requestInputMessages';
40
+ import { getToolApprovalResponseMetadata, sendRequestInputResponse } from './requestInputMessages';
41
41
  import { getWorkstreamId } from './utils';
42
42
 
43
43
  /** className overrides for MessageItem — single source of truth for all className overrides. */
@@ -625,13 +625,27 @@ function MessageItemComponent({
625
625
  variant={askUserUx.variant}
626
626
  multiSelect={askUserUx.multiSelect}
627
627
  onSelect={(optionId) =>
628
- onSendMessage?.(optionId, getToolApprovalResponseMetadata(message, optionId))
628
+ sendRequestInputResponse(
629
+ onSendMessage,
630
+ message,
631
+ optionId,
632
+ getToolApprovalResponseMetadata(message, optionId),
633
+ )
634
+ }
635
+ onMultiSelect={(optionIds) =>
636
+ sendRequestInputResponse(onSendMessage, message, optionIds.join(', '))
629
637
  }
630
- onMultiSelect={(optionIds) => onSendMessage?.(optionIds.join(', '))}
631
638
  allowFreeResponse={!askUserUx.options?.length || !!askUserUx.free_response}
632
639
  placeholder={askUserUx.free_response?.placeholder}
633
640
  submitLabel={askUserUx.free_response?.submit_label}
634
- onSubmit={(value) => onSendMessage?.(value, askUserUx.free_response?.metadata)}
641
+ onSubmit={(value) =>
642
+ sendRequestInputResponse(
643
+ onSendMessage,
644
+ message,
645
+ value,
646
+ askUserUx.free_response?.metadata,
647
+ )
648
+ }
635
649
  hideBorder
636
650
  compact
637
651
  answered={requestInputAnswered}