@puredesktop/puredesktop-ui-bridge 2.1.7 → 2.1.9
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/package.json +89 -5
- package/src/agents/mcpServerPresets.test.ts +105 -0
- package/src/agents/mcpServerPresets.ts +380 -0
- package/src/agents/runtime/index.ts +7 -0
- package/src/agents/runtime/mapAgentSnapshot.ts +11 -8
- package/src/agents/runtime/pendingAsks.test.ts +117 -0
- package/src/agents/runtime/pendingAsks.ts +110 -0
- package/src/bridge/agentScopeDefaults.ts +65 -0
- package/src/bridge/agentTypes.ts +57 -3
- package/src/bridge/agents.d.mts +6 -0
- package/src/bridge/agents.mjs +10 -0
- package/src/bridge/agents.ts +4 -0
- package/src/bridge/collectionImagePaste.ts +19 -1
- package/src/bridge/context.mjs +32 -0
- package/src/bridge/credentials.d.mts +62 -0
- package/src/bridge/credentials.mjs +53 -0
- package/src/bridge/documents.d.mts +76 -0
- package/src/bridge/documents.mjs +40 -0
- package/src/bridge/events.d.mts +1 -0
- package/src/bridge/events.mjs +1 -0
- package/src/bridge/googleAuth.ts +7 -152
- package/src/bridge/mcpClient.d.mts +74 -0
- package/src/bridge/mcpClient.mjs +79 -0
- package/src/bridge/methods.d.mts +45 -0
- package/src/bridge/methods.mjs +46 -0
- package/src/bridge/notifications.d.mts +12 -0
- package/src/bridge/notifications.mjs +36 -0
- package/src/bridge/operations.d.mts +44 -0
- package/src/bridge/operations.mjs +40 -0
- package/src/bridge/pureRender/base.css +44 -0
- package/src/bridge/pureRender/document.ts +29 -2
- package/src/bridge/pureRender/index.ts +3 -0
- package/src/bridge/pureRender/normalize.ts +167 -0
- package/src/bridge/pureRender/sanitizeExportBody.test.ts +51 -0
- package/src/bridge/pureRender/sanitizeExportBody.ts +61 -0
- package/src/bridge/pureRender/styles.ts +125 -5
- package/src/bridge/pureRender/theme.test.ts +259 -0
- package/src/bridge/pureRender/theme.ts +19 -1
- package/src/bridge/pureRender/types.ts +33 -0
- package/src/bridge/pureRender/visualLayoutInspection.test.ts +26 -0
- package/src/bridge/pureRender/visualLayoutInspection.ts +75 -0
- package/src/bridge/react/useDocumentHotkeys.ts +41 -0
- package/src/bridge/react/useDocumentLifecycle.tsx +359 -0
- package/src/bridge/react/usePlatformAgentSessionDrive.test.tsx +29 -10
- package/src/bridge/react/usePlatformAgentSessionDrive.tsx +10 -81
- package/src/bridge/react/usePlatformAppSettings.test.tsx +105 -0
- package/src/bridge/react/usePlatformAppSettings.tsx +104 -0
- package/src/bridge/react/usePlatformJsonStore.test.tsx +152 -0
- package/src/bridge/react/usePlatformJsonStore.tsx +149 -0
- package/src/bridge/react/usePlatformOperations.tsx +75 -0
- package/src/bridge/storage.d.mts +23 -10
- package/src/bridge/storage.mjs +6 -3
- package/src/bridge/storage.test.ts +6 -6
- package/src/bridge/types.ts +7 -0
- package/src/components/agents/AgentComposer.tsx +10 -6
- package/src/components/agents/AgentDrawerPanel.test.tsx +2 -205
- package/src/components/agents/AgentDrawerPanel.tsx +23 -62
- package/src/components/agents/AgentMessageBubble.tsx +59 -33
- package/src/components/agents/AgentMessageList.tsx +4 -4
- package/src/components/agents/AgentQuestionPromptList.tsx +34 -212
- package/src/components/agents/AgentToolApprovalActions.tsx +13 -11
- package/src/components/agents/AgentToolCallCard.tsx +20 -20
- package/src/components/agents/AgentToolPendingList.tsx +6 -6
- package/src/components/agents/AgentToolPendingReview.tsx +6 -6
- package/src/components/agents/ContextPicker.tsx +239 -0
- package/src/components/agents/QuestionRequestList.tsx +278 -0
- package/src/components/agents/agentPanelStyles.ts +8 -31
- package/src/components/agents/agentTypes.ts +25 -41
- package/src/components/agents/index.ts +4 -0
- package/src/components/assets/asset-library/sidebar/AssetLibraryList.tsx +10 -4
- package/src/components/chrome/WorkspaceTabStrip.tsx +22 -4
- package/src/components/chrome/workspaceTabTypes.ts +2 -0
- package/src/components/common/chat/ChatBox.tsx +9 -9
- package/src/components/common/chat/ChatModelMenu.tsx +34 -4
- package/src/components/common/chat/ChatThread.tsx +23 -7
- package/src/components/common/connections/ProviderConnection.test.tsx +208 -0
- package/src/components/common/connections/ProviderConnection.tsx +277 -0
- package/src/components/common/connections/index.ts +5 -0
- package/src/components/common/containers/AppFrame.test.tsx +4 -4
- package/src/components/common/containers/AppFrame.tsx +26 -10
- package/src/components/common/containers/AppHeader.tsx +37 -15
- package/src/components/common/documents/DocumentHeaderActions.tsx +206 -0
- package/src/components/common/documents/DocumentSwitcher.tsx +1181 -0
- package/src/components/common/documents/index.ts +8 -0
- package/src/components/common/dropdown/MenuDropdownItem.tsx +14 -2
- package/src/components/common/dropdown/MenuPortal.tsx +3 -1
- package/src/components/common/dropdown/menuViewportPosition.test.ts +20 -10
- package/src/components/common/dropdown/menuViewportPosition.ts +35 -27
- package/src/components/common/overlays/Modal.tsx +13 -2
- package/src/components/common/research/EvidenceDossier.tsx +1232 -150
- package/src/components/common/research/index.ts +2 -0
- package/src/components/editor/EditorLinkPromptDialog.tsx +1 -1
- package/src/components/print-design/PrintDesignPanel.test.tsx +430 -40
- package/src/components/print-design/PrintDesignPanel.tsx +1770 -207
- package/src/components/print-design/index.ts +2 -0
- package/src/components/print-design/previewContent.test.ts +32 -0
- package/src/components/print-design/previewContent.ts +31 -0
- package/src/context/buildContextDocument.test.ts +240 -0
- package/src/context/buildContextDocument.ts +309 -0
- package/src/context/contextAccess.ts +66 -0
- package/src/context/contextConfig.ts +72 -0
- package/src/context/contextDocument.test.ts +155 -0
- package/src/context/contextDocument.ts +300 -0
- package/src/context/contextSections.test.ts +88 -0
- package/src/context/contextSections.ts +84 -0
- package/src/context/htmlToContextMarkdown.test.ts +134 -0
- package/src/context/htmlToContextMarkdown.ts +369 -0
- package/src/ics/generateIcs.test.ts +153 -0
- package/src/ics/generateIcs.ts +197 -0
- package/src/index.ts +3 -0
- package/src/net/httpRetry.test.ts +117 -0
- package/src/net/httpRetry.ts +111 -0
- package/src/theme/appAccents.test.ts +36 -35
- package/src/theme/appAccents.ts +93 -104
- package/src/theme/appIdentityCss.mjs +178 -229
- package/src/theme/appIdentityCss.ts +222 -231
- package/src/theme/themes/dark.ts +3 -1
- package/src/theme/themes/light.ts +3 -1
- package/src/bridge/googleProviderConfig.ts +0 -73
|
@@ -12,7 +12,6 @@ import {
|
|
|
12
12
|
import { AgentDrawerPanel } from './AgentDrawerPanel.js'
|
|
13
13
|
import {
|
|
14
14
|
AgentLivePhase,
|
|
15
|
-
AgentUiRunState,
|
|
16
15
|
type AgentDrawerPanelProps,
|
|
17
16
|
} from './agentTypes.js'
|
|
18
17
|
|
|
@@ -27,12 +26,7 @@ function baseProps(
|
|
|
27
26
|
messages: [],
|
|
28
27
|
assistantLabel: 'Assistant',
|
|
29
28
|
pendingToolCalls: [],
|
|
30
|
-
runState: AgentUiRunState.AwaitingUser,
|
|
31
|
-
busy: false,
|
|
32
29
|
error: null,
|
|
33
|
-
composerValue: 'draft',
|
|
34
|
-
onComposerChange: vi.fn(),
|
|
35
|
-
onSend: vi.fn(),
|
|
36
30
|
onApproveTools: vi.fn(),
|
|
37
31
|
onRejectTools: vi.fn(),
|
|
38
32
|
...overrides,
|
|
@@ -64,32 +58,10 @@ describe('AgentDrawerPanel composer and steering behavior', () => {
|
|
|
64
58
|
})
|
|
65
59
|
}
|
|
66
60
|
|
|
67
|
-
it('
|
|
68
|
-
render(baseProps({ busy: true, onStop: vi.fn() }))
|
|
69
|
-
|
|
70
|
-
const textarea = host.querySelector('textarea')
|
|
71
|
-
const stopButton = host.querySelector('button[aria-label="Stop"]')
|
|
72
|
-
const sendButton = host.querySelector('button[aria-label="Send"]')
|
|
73
|
-
|
|
74
|
-
expect(textarea).toBeInstanceOf(HTMLTextAreaElement)
|
|
75
|
-
expect((textarea as HTMLTextAreaElement).disabled).toBe(false)
|
|
76
|
-
expect(stopButton).toBeInstanceOf(HTMLButtonElement)
|
|
77
|
-
expect((stopButton as HTMLButtonElement).disabled).toBe(false)
|
|
78
|
-
expect(sendButton).toBeNull()
|
|
79
|
-
})
|
|
80
|
-
|
|
81
|
-
it('keeps the composer textarea editable when the run state is ready', () => {
|
|
82
|
-
render(baseProps({ runState: AgentUiRunState.Ready }))
|
|
83
|
-
|
|
84
|
-
const textarea = host.querySelector('textarea')
|
|
85
|
-
|
|
86
|
-
expect(textarea).toBeInstanceOf(HTMLTextAreaElement)
|
|
87
|
-
expect((textarea as HTMLTextAreaElement).disabled).toBe(false)
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
it('renders manual approval controls and composer together', () => {
|
|
61
|
+
it('renders manual approval controls above the composer slot', () => {
|
|
91
62
|
render(
|
|
92
63
|
baseProps({
|
|
64
|
+
composerSlot: <textarea aria-label="Composer" />,
|
|
93
65
|
pendingToolCalls: [
|
|
94
66
|
{
|
|
95
67
|
id: 'call-1',
|
|
@@ -195,179 +167,4 @@ describe('AgentDrawerPanel composer and steering behavior', () => {
|
|
|
195
167
|
expect(host.textContent).toContain('Prepared Summarize')
|
|
196
168
|
})
|
|
197
169
|
|
|
198
|
-
it('renders persisted follow-up controls above the composer surface', () => {
|
|
199
|
-
const onRemoveFollowUp = vi.fn()
|
|
200
|
-
const onSendFollowUpNow = vi.fn()
|
|
201
|
-
render(
|
|
202
|
-
baseProps({
|
|
203
|
-
busy: true,
|
|
204
|
-
followUps: [
|
|
205
|
-
{
|
|
206
|
-
id: 'followup-1',
|
|
207
|
-
message: 'Look at the second chapter',
|
|
208
|
-
position: 0,
|
|
209
|
-
},
|
|
210
|
-
],
|
|
211
|
-
onRemoveFollowUp,
|
|
212
|
-
onSendFollowUpNow,
|
|
213
|
-
}),
|
|
214
|
-
)
|
|
215
|
-
|
|
216
|
-
const sendNow = host.querySelector(
|
|
217
|
-
'button[aria-label="Send follow-up now"]',
|
|
218
|
-
)
|
|
219
|
-
const remove = host.querySelector('button[aria-label="Remove follow-up"]')
|
|
220
|
-
const textarea = host.querySelector('textarea')
|
|
221
|
-
const composerSurface = textarea?.parentElement
|
|
222
|
-
|
|
223
|
-
expect(host.textContent).not.toContain('Queued follow-ups')
|
|
224
|
-
expect(host.textContent).toContain('Look at the second chapter')
|
|
225
|
-
expect(composerSurface?.textContent).not.toContain('Queued follow-ups')
|
|
226
|
-
expect(composerSurface?.textContent).not.toContain(
|
|
227
|
-
'Look at the second chapter',
|
|
228
|
-
)
|
|
229
|
-
expect(sendNow).toBeInstanceOf(HTMLButtonElement)
|
|
230
|
-
expect(remove).toBeInstanceOf(HTMLButtonElement)
|
|
231
|
-
|
|
232
|
-
act(() => {
|
|
233
|
-
sendNow?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
234
|
-
remove?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
235
|
-
})
|
|
236
|
-
|
|
237
|
-
expect(onSendFollowUpNow).toHaveBeenCalledWith('followup-1')
|
|
238
|
-
expect(onRemoveFollowUp).toHaveBeenCalledWith('followup-1')
|
|
239
|
-
})
|
|
240
|
-
|
|
241
|
-
it('keeps follow-up play available when the drawer is not busy', () => {
|
|
242
|
-
const onSendFollowUpNow = vi.fn()
|
|
243
|
-
render(
|
|
244
|
-
baseProps({
|
|
245
|
-
busy: false,
|
|
246
|
-
followUps: [
|
|
247
|
-
{
|
|
248
|
-
id: 'followup-1',
|
|
249
|
-
message: 'Run this next',
|
|
250
|
-
position: 0,
|
|
251
|
-
},
|
|
252
|
-
],
|
|
253
|
-
onSendFollowUpNow,
|
|
254
|
-
}),
|
|
255
|
-
)
|
|
256
|
-
|
|
257
|
-
const sendNow = host.querySelector(
|
|
258
|
-
'button[aria-label="Send follow-up now"]',
|
|
259
|
-
)
|
|
260
|
-
|
|
261
|
-
expect(sendNow).toBeInstanceOf(HTMLButtonElement)
|
|
262
|
-
expect((sendNow as HTMLButtonElement).disabled).toBe(false)
|
|
263
|
-
|
|
264
|
-
act(() => {
|
|
265
|
-
sendNow?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
266
|
-
})
|
|
267
|
-
|
|
268
|
-
expect(onSendFollowUpNow).toHaveBeenCalledWith('followup-1')
|
|
269
|
-
})
|
|
270
|
-
|
|
271
|
-
it('renders question prompts in the composer without approval controls', async () => {
|
|
272
|
-
const onAnswerQuestionTool = vi.fn()
|
|
273
|
-
const onDeclineQuestionTool = vi.fn()
|
|
274
|
-
render(
|
|
275
|
-
baseProps({
|
|
276
|
-
busy: true,
|
|
277
|
-
pendingQuestionToolCalls: [
|
|
278
|
-
{
|
|
279
|
-
id: 'question-1',
|
|
280
|
-
name: 'harness.questions',
|
|
281
|
-
arguments: {
|
|
282
|
-
question: 'Which source should I use?',
|
|
283
|
-
mode: 'single_choice',
|
|
284
|
-
options: [
|
|
285
|
-
{ id: 'notes', label: 'Notes' },
|
|
286
|
-
{ id: 'files', label: 'Files' },
|
|
287
|
-
],
|
|
288
|
-
},
|
|
289
|
-
},
|
|
290
|
-
],
|
|
291
|
-
onAnswerQuestionTool,
|
|
292
|
-
onDeclineQuestionTool,
|
|
293
|
-
}),
|
|
294
|
-
)
|
|
295
|
-
|
|
296
|
-
const buttons = Array.from(host.querySelectorAll('button'))
|
|
297
|
-
const notesButton = buttons.find(button => button.textContent === 'Notes')
|
|
298
|
-
const answerButton = buttons.find(button => button.textContent === 'Answer')
|
|
299
|
-
const approveButton = buttons.find(
|
|
300
|
-
button => button.textContent === 'Approve',
|
|
301
|
-
)
|
|
302
|
-
|
|
303
|
-
expect(host.textContent).toContain('Which source should I use?')
|
|
304
|
-
expect(approveButton).toBeUndefined()
|
|
305
|
-
expect(notesButton).toBeInstanceOf(HTMLButtonElement)
|
|
306
|
-
expect(notesButton?.disabled).toBe(false)
|
|
307
|
-
expect(answerButton).toBeInstanceOf(HTMLButtonElement)
|
|
308
|
-
expect(answerButton?.disabled).toBe(true)
|
|
309
|
-
|
|
310
|
-
act(() => {
|
|
311
|
-
notesButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
312
|
-
})
|
|
313
|
-
|
|
314
|
-
expect(answerButton?.disabled).toBe(false)
|
|
315
|
-
|
|
316
|
-
await act(async () => {
|
|
317
|
-
answerButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
318
|
-
await Promise.resolve()
|
|
319
|
-
})
|
|
320
|
-
|
|
321
|
-
expect(onAnswerQuestionTool).toHaveBeenCalledWith('question-1', ['notes'])
|
|
322
|
-
|
|
323
|
-
const declineInput = host.querySelector(
|
|
324
|
-
'input[aria-label="Decline reason"]',
|
|
325
|
-
) as HTMLInputElement | null
|
|
326
|
-
const declineButton = buttons.find(
|
|
327
|
-
button => button.textContent === 'Decline',
|
|
328
|
-
)
|
|
329
|
-
const setter = Object.getOwnPropertyDescriptor(
|
|
330
|
-
HTMLInputElement.prototype,
|
|
331
|
-
'value',
|
|
332
|
-
)?.set
|
|
333
|
-
|
|
334
|
-
await act(async () => {
|
|
335
|
-
setter?.call(declineInput, 'Need more context')
|
|
336
|
-
declineInput?.dispatchEvent(new InputEvent('input', { bubbles: true }))
|
|
337
|
-
})
|
|
338
|
-
await act(async () => {
|
|
339
|
-
declineButton?.dispatchEvent(new MouseEvent('click', { bubbles: true }))
|
|
340
|
-
await Promise.resolve()
|
|
341
|
-
})
|
|
342
|
-
|
|
343
|
-
expect(onDeclineQuestionTool).toHaveBeenCalledWith(
|
|
344
|
-
'question-1',
|
|
345
|
-
'Need more context',
|
|
346
|
-
)
|
|
347
|
-
})
|
|
348
|
-
|
|
349
|
-
it('collapses question prompts with preserved exiting content', () => {
|
|
350
|
-
const question = {
|
|
351
|
-
id: 'question-1',
|
|
352
|
-
name: 'harness.questions',
|
|
353
|
-
arguments: {
|
|
354
|
-
question: 'Which source should I use?',
|
|
355
|
-
mode: 'single_choice',
|
|
356
|
-
options: [{ id: 'notes', label: 'Notes' }],
|
|
357
|
-
},
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
render(baseProps({ pendingQuestionToolCalls: [question] }))
|
|
361
|
-
|
|
362
|
-
const openPresence = host.querySelector('[data-animation-direction="y"]')
|
|
363
|
-
expect(openPresence).toBeInstanceOf(HTMLElement)
|
|
364
|
-
expect(host.textContent).toContain('Which source should I use?')
|
|
365
|
-
|
|
366
|
-
render(baseProps({ pendingQuestionToolCalls: [] }))
|
|
367
|
-
|
|
368
|
-
const closingPresence = host.querySelector('[data-animation-direction="y"]')
|
|
369
|
-
expect(closingPresence).toBeInstanceOf(HTMLElement)
|
|
370
|
-
expect(closingPresence?.getAttribute('data-open')).toBe('false')
|
|
371
|
-
expect(host.textContent).toContain('Which source should I use?')
|
|
372
|
-
})
|
|
373
170
|
})
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { InlineBanner } from '../common/feedback/InlineBanner.js'
|
|
2
|
-
import { AgentComposer } from './AgentComposer.js'
|
|
3
2
|
import { AgentMessageList } from './AgentMessageList.js'
|
|
4
3
|
import { AgentToolPendingList } from './AgentToolPendingList.js'
|
|
5
4
|
import type { AgentDrawerPanelProps } from './agentTypes.js'
|
|
@@ -8,49 +7,31 @@ import {
|
|
|
8
7
|
StyledAgentDrawer,
|
|
9
8
|
StyledAgentFooter,
|
|
10
9
|
StyledAgentThreadHost,
|
|
11
|
-
StyledComposerFooterSpacer,
|
|
12
|
-
StyledComposerReserveFooter,
|
|
13
10
|
} from './agentPanelStyles.js'
|
|
14
11
|
|
|
15
12
|
export function AgentDrawerPanel({
|
|
16
13
|
messages,
|
|
17
14
|
assistantLabel,
|
|
18
15
|
pendingToolCalls,
|
|
19
|
-
pendingQuestionToolCalls = [],
|
|
20
|
-
busy,
|
|
21
16
|
error,
|
|
22
|
-
|
|
23
|
-
composerDisabled = false,
|
|
24
|
-
composerInputDisabled = false,
|
|
25
|
-
composerSendDisabled = false,
|
|
26
|
-
composerLeading,
|
|
27
|
-
followUps = [],
|
|
17
|
+
composerSlot = null,
|
|
28
18
|
activityHint = null,
|
|
29
|
-
onComposerChange,
|
|
30
|
-
onSend,
|
|
31
|
-
onStop,
|
|
32
|
-
onRemoveFollowUp,
|
|
33
|
-
onSendFollowUpNow,
|
|
34
19
|
onApproveTools,
|
|
35
20
|
onRejectTools,
|
|
36
21
|
onApproveOne,
|
|
37
22
|
onRejectOne,
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
onDeclineQuestionTool,
|
|
23
|
+
approveAlwaysScopeLabel,
|
|
24
|
+
onApproveAlwaysForScope,
|
|
41
25
|
storageHint = null,
|
|
42
|
-
|
|
43
|
-
|
|
26
|
+
storageHintAction = null,
|
|
27
|
+
onStorageHintDismiss,
|
|
44
28
|
pureAssistantTabActive = false,
|
|
45
|
-
drawerOpen = true,
|
|
46
|
-
composerPortaled = false,
|
|
47
29
|
threadHeader = null,
|
|
30
|
+
bottomPanel = null,
|
|
48
31
|
}: AgentDrawerPanelProps): React.ReactElement {
|
|
49
32
|
const awaitingApproval = pendingToolCalls.length > 0
|
|
50
|
-
|
|
33
|
+
void pureAssistantTabActive
|
|
51
34
|
|
|
52
|
-
const showFooterComposer = !composerPortaled
|
|
53
|
-
const reserveComposerFooter = composerPortaled && drawerOpen
|
|
54
35
|
const footerWork = (
|
|
55
36
|
<>
|
|
56
37
|
{awaitingApproval ? (
|
|
@@ -61,62 +42,42 @@ export function AgentDrawerPanel({
|
|
|
61
42
|
onReject={onRejectTools}
|
|
62
43
|
onApproveOne={onApproveOne}
|
|
63
44
|
onRejectOne={onRejectOne}
|
|
64
|
-
|
|
45
|
+
approveAlwaysScopeLabel={approveAlwaysScopeLabel}
|
|
46
|
+
onApproveAlwaysForScope={onApproveAlwaysForScope}
|
|
65
47
|
/>
|
|
66
48
|
) : null}
|
|
67
49
|
</>
|
|
68
50
|
)
|
|
69
|
-
const hasFooterWork = awaitingApproval
|
|
70
51
|
|
|
71
52
|
return (
|
|
72
|
-
<StyledAgentDrawer
|
|
73
|
-
data-pure-assistant-tab-active={pureAssistantTabActive || undefined}
|
|
74
|
-
>
|
|
53
|
+
<StyledAgentDrawer>
|
|
75
54
|
<StyledAgentBody>
|
|
76
55
|
{storageHint ? (
|
|
77
|
-
<InlineBanner
|
|
56
|
+
<InlineBanner
|
|
57
|
+
variant="warning"
|
|
58
|
+
actions={storageHintAction}
|
|
59
|
+
onDismiss={onStorageHintDismiss}
|
|
60
|
+
>
|
|
61
|
+
{storageHint}
|
|
62
|
+
</InlineBanner>
|
|
78
63
|
) : null}
|
|
79
64
|
{activityHint ? (
|
|
80
65
|
<InlineBanner variant="warning">{activityHint}</InlineBanner>
|
|
81
66
|
) : null}
|
|
82
67
|
{error ? <InlineBanner variant="danger">{error}</InlineBanner> : null}
|
|
68
|
+
{threadHeader}
|
|
83
69
|
<StyledAgentThreadHost>
|
|
84
70
|
<AgentMessageList
|
|
85
71
|
messages={messages}
|
|
86
72
|
assistantLabel={assistantLabel}
|
|
87
|
-
threadHeader={threadHeader}
|
|
88
73
|
/>
|
|
89
74
|
</StyledAgentThreadHost>
|
|
90
75
|
</StyledAgentBody>
|
|
91
|
-
{
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
busy={busy}
|
|
97
|
-
disabled={composerBlocked}
|
|
98
|
-
inputDisabled={composerInputDisabled}
|
|
99
|
-
sendDisabled={composerSendDisabled}
|
|
100
|
-
leading={composerLeading}
|
|
101
|
-
followUps={followUps}
|
|
102
|
-
questionToolCalls={pendingQuestionToolCalls}
|
|
103
|
-
usage={usage}
|
|
104
|
-
liveTurn={liveTurn}
|
|
105
|
-
onChange={onComposerChange}
|
|
106
|
-
onSend={onSend}
|
|
107
|
-
onStop={onStop}
|
|
108
|
-
onRemoveFollowUp={onRemoveFollowUp}
|
|
109
|
-
onSendFollowUpNow={onSendFollowUpNow}
|
|
110
|
-
onAnswerQuestionTool={onAnswerQuestionTool}
|
|
111
|
-
onDeclineQuestionTool={onDeclineQuestionTool}
|
|
112
|
-
/>
|
|
113
|
-
</StyledAgentFooter>
|
|
114
|
-
) : reserveComposerFooter || hasFooterWork ? (
|
|
115
|
-
<StyledComposerReserveFooter aria-hidden={!hasFooterWork}>
|
|
116
|
-
{footerWork}
|
|
117
|
-
{reserveComposerFooter ? <StyledComposerFooterSpacer /> : null}
|
|
118
|
-
</StyledComposerReserveFooter>
|
|
119
|
-
) : null}
|
|
76
|
+
{bottomPanel}
|
|
77
|
+
<StyledAgentFooter>
|
|
78
|
+
{footerWork}
|
|
79
|
+
{composerSlot}
|
|
80
|
+
</StyledAgentFooter>
|
|
120
81
|
</StyledAgentDrawer>
|
|
121
82
|
)
|
|
122
83
|
}
|
|
@@ -8,16 +8,17 @@ import {
|
|
|
8
8
|
} from '../common/chat/index.js'
|
|
9
9
|
import { AgentThinkingBlock } from './AgentThinkingBlock.js'
|
|
10
10
|
import {
|
|
11
|
-
agentToolResultDetails,
|
|
12
|
-
formatAgentToolLabel,
|
|
13
|
-
formatAgentToolRowSummary,
|
|
14
|
-
} from './agentToolDisplay.js'
|
|
15
|
-
import {
|
|
16
|
-
AgentLivePhase,
|
|
17
|
-
type AgentDelegatedActivity,
|
|
18
|
-
type
|
|
19
|
-
type
|
|
20
|
-
|
|
11
|
+
agentToolResultDetails,
|
|
12
|
+
formatAgentToolLabel,
|
|
13
|
+
formatAgentToolRowSummary,
|
|
14
|
+
} from './agentToolDisplay.js'
|
|
15
|
+
import {
|
|
16
|
+
AgentLivePhase,
|
|
17
|
+
type AgentDelegatedActivity,
|
|
18
|
+
type AgentToolActivity,
|
|
19
|
+
type AgentUiMessage,
|
|
20
|
+
type AgentUiToolCall,
|
|
21
|
+
} from './agentTypes.js'
|
|
21
22
|
|
|
22
23
|
export interface AgentMessageBubbleProps {
|
|
23
24
|
message: AgentUiMessage
|
|
@@ -57,17 +58,17 @@ export function AgentMessageBubble({
|
|
|
57
58
|
) : null
|
|
58
59
|
}
|
|
59
60
|
/>
|
|
60
|
-
{message.toolCalls?.map(call => (
|
|
61
|
-
<ChatToolRow
|
|
62
|
-
key={call.id}
|
|
63
|
-
icon={
|
|
64
|
-
call
|
|
65
|
-
<RunningIcon size={11} aria-hidden />
|
|
66
|
-
) : (
|
|
67
|
-
<Wrench size={11} />
|
|
61
|
+
{message.toolCalls?.map(call => (
|
|
62
|
+
<ChatToolRow
|
|
63
|
+
key={call.id}
|
|
64
|
+
icon={
|
|
65
|
+
toolCallIsIncomplete(call) || call.delegatedActivity ? (
|
|
66
|
+
<RunningIcon size={11} aria-hidden />
|
|
67
|
+
) : (
|
|
68
|
+
<Wrench size={11} />
|
|
68
69
|
)
|
|
69
70
|
}
|
|
70
|
-
title={
|
|
71
|
+
title={formatToolRowTitle(call)}
|
|
71
72
|
summary={formatToolRowSummary(call)}
|
|
72
73
|
details={formatToolRowDetails(call)}
|
|
73
74
|
/>
|
|
@@ -97,21 +98,46 @@ export function AgentMessageBubble({
|
|
|
97
98
|
}
|
|
98
99
|
}
|
|
99
100
|
|
|
100
|
-
function formatToolRowSummary(call: AgentUiToolCall): string {
|
|
101
|
-
if (call.
|
|
102
|
-
return
|
|
103
|
-
}
|
|
101
|
+
function formatToolRowSummary(call: AgentUiToolCall): string {
|
|
102
|
+
if (call.serviceActivity) {
|
|
103
|
+
return toolActivitySummary(call.serviceActivity)
|
|
104
|
+
}
|
|
105
|
+
if (call.delegatedActivity) {
|
|
106
|
+
return delegatedActivitySummary(call.delegatedActivity)
|
|
107
|
+
}
|
|
104
108
|
return formatAgentToolRowSummary(call)
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
function
|
|
108
|
-
if (call.
|
|
109
|
-
return
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function formatToolRowTitle(call: AgentUiToolCall): string {
|
|
112
|
+
if (call.name === 'harness.request_app_development_iteration') {
|
|
113
|
+
return call.arguments.mode === 'ask'
|
|
114
|
+
? 'Asking app builder'
|
|
115
|
+
: 'Requesting app changes'
|
|
116
|
+
}
|
|
117
|
+
return formatAgentToolLabel(call.name)
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function formatToolRowDetails(call: AgentUiToolCall): string[] | undefined {
|
|
121
|
+
if (call.serviceActivity) {
|
|
122
|
+
return call.serviceActivity.detail ? [call.serviceActivity.detail] : undefined
|
|
123
|
+
}
|
|
124
|
+
if (call.delegatedActivity) {
|
|
125
|
+
return delegatedActivityDetails(call.delegatedActivity)
|
|
126
|
+
}
|
|
127
|
+
return call.result ? agentToolResultDetails(call.result) : undefined
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function toolCallIsIncomplete(call: AgentUiToolCall): boolean {
|
|
131
|
+
return call.status !== 'done' && call.result === undefined
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function toolActivitySummary(activity: AgentToolActivity): string {
|
|
135
|
+
return activity.detail
|
|
136
|
+
? `${activity.title}: ${activity.detail}`
|
|
137
|
+
: activity.title
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function delegatedActivityStatus(activity: AgentDelegatedActivity): string {
|
|
115
141
|
switch (activity.liveTurn.phase) {
|
|
116
142
|
case AgentLivePhase.Thinking:
|
|
117
143
|
return 'starting'
|
|
@@ -14,20 +14,19 @@ export interface AgentMessageListProps {
|
|
|
14
14
|
messages: AgentUiMessage[]
|
|
15
15
|
assistantLabel?: string
|
|
16
16
|
emptyMessage?: string
|
|
17
|
-
|
|
17
|
+
threadFooter?: React.ReactNode
|
|
18
18
|
}
|
|
19
19
|
|
|
20
20
|
export const AgentMessageList = memo(function AgentMessageList({
|
|
21
21
|
messages,
|
|
22
22
|
assistantLabel = 'assistant',
|
|
23
23
|
emptyMessage = 'Ask the agent about your workspace. Tool calls wait for your approval.',
|
|
24
|
-
|
|
24
|
+
threadFooter = null,
|
|
25
25
|
}: AgentMessageListProps): React.ReactElement {
|
|
26
26
|
const scrollKey = messages.map(message => message.id).join('|')
|
|
27
27
|
|
|
28
28
|
return (
|
|
29
|
-
<StyledAgentScroll
|
|
30
|
-
{threadHeader}
|
|
29
|
+
<StyledAgentScroll>
|
|
31
30
|
<ChatThread
|
|
32
31
|
scrollKey={scrollKey}
|
|
33
32
|
emptyMessage={
|
|
@@ -35,6 +34,7 @@ export const AgentMessageList = memo(function AgentMessageList({
|
|
|
35
34
|
<ChatThreadEmpty>{emptyMessage}</ChatThreadEmpty>
|
|
36
35
|
) : null
|
|
37
36
|
}
|
|
37
|
+
footer={threadFooter}
|
|
38
38
|
>
|
|
39
39
|
{messages.map(message => (
|
|
40
40
|
<Fragment key={message.id}>
|