@puredesktop/puredesktop-ui-bridge 2.1.8 → 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 +35 -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 +36 -2
- 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/credentials.d.mts +62 -0
- package/src/bridge/credentials.mjs +53 -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 +22 -0
- package/src/bridge/methods.mjs +23 -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/react/usePlatformAgentSessionDrive.test.tsx +29 -10
- package/src/bridge/react/usePlatformAgentSessionDrive.tsx +10 -81
- package/src/bridge/react/usePlatformAppSettings.test.tsx +105 -105
- package/src/bridge/react/usePlatformAppSettings.tsx +104 -104
- package/src/bridge/react/usePlatformJsonStore.test.tsx +152 -152
- package/src/bridge/react/usePlatformJsonStore.tsx +149 -149
- package/src/bridge/react/usePlatformOperations.tsx +75 -0
- package/src/bridge/storage.d.mts +17 -4
- package/src/bridge/storage.mjs +6 -3
- package/src/components/agents/AgentComposer.tsx +10 -6
- package/src/components/agents/AgentDrawerPanel.test.tsx +2 -205
- package/src/components/agents/AgentDrawerPanel.tsx +23 -64
- package/src/components/agents/AgentMessageBubble.tsx +59 -33
- package/src/components/agents/AgentMessageList.tsx +4 -9
- package/src/components/agents/AgentQuestionPromptList.tsx +34 -212
- package/src/components/agents/AgentToolApprovalActions.tsx +13 -11
- package/src/components/agents/AgentToolPendingList.tsx +6 -6
- package/src/components/agents/AgentToolPendingReview.tsx +6 -6
- package/src/components/agents/QuestionRequestList.tsx +278 -0
- package/src/components/agents/agentPanelStyles.ts +9 -33
- package/src/components/agents/agentTypes.ts +25 -43
- 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 +4 -3
- 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.tsx +26 -19
- package/src/components/common/containers/AppHeader.tsx +20 -20
- package/src/components/common/documents/DocumentSwitcher.tsx +63 -58
- 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/editor/EditorLinkPromptDialog.tsx +1 -1
- 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.ts +6 -0
- package/src/theme/appIdentityCss.mjs +11 -0
- package/src/theme/appIdentityCss.ts +11 -0
- package/src/theme/themes/dark.ts +3 -1
- package/src/theme/themes/light.ts +3 -1
- package/src/bridge/googleProviderConfig.ts +0 -73
package/src/bridge/storage.d.mts
CHANGED
|
@@ -8,17 +8,30 @@ export interface PlatformStorageJsonRequest {
|
|
|
8
8
|
export interface PlatformStorageJsonReadResult {
|
|
9
9
|
path: string
|
|
10
10
|
value: unknown | null
|
|
11
|
+
/** Content hash for optimistic concurrency (null when the file is absent). */
|
|
12
|
+
version: string | null
|
|
11
13
|
}
|
|
12
14
|
|
|
13
15
|
export interface PlatformStorageJsonWriteRequest
|
|
14
16
|
extends PlatformStorageJsonRequest {
|
|
15
17
|
value: unknown
|
|
18
|
+
/**
|
|
19
|
+
* The version this write was derived from (null = file must not exist).
|
|
20
|
+
* Omit for an unconditional write. On mismatch nothing is written and the
|
|
21
|
+
* result carries conflict: true — re-read, re-apply, retry.
|
|
22
|
+
*/
|
|
23
|
+
ifMatch?: string | null
|
|
16
24
|
}
|
|
17
25
|
|
|
18
|
-
export
|
|
19
|
-
path: string
|
|
20
|
-
|
|
21
|
-
|
|
26
|
+
export type PlatformStorageJsonWriteResult =
|
|
27
|
+
| { path: string; ok: true; version: string }
|
|
28
|
+
| {
|
|
29
|
+
path: string
|
|
30
|
+
ok: false
|
|
31
|
+
conflict: true
|
|
32
|
+
value: unknown | null
|
|
33
|
+
version: string | null
|
|
34
|
+
}
|
|
22
35
|
|
|
23
36
|
export declare function readPlatformStorageJson(
|
|
24
37
|
request: PlatformStorageJsonRequest,
|
package/src/bridge/storage.mjs
CHANGED
|
@@ -3,15 +3,18 @@ import { PLATFORM_BRIDGE_METHODS } from './methods.mjs'
|
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* @param {{ appSlug: string, fileName: string }} request
|
|
6
|
-
* @returns {Promise<{ path: string, value: unknown | null }>}
|
|
6
|
+
* @returns {Promise<{ path: string, value: unknown | null, version: string | null }>}
|
|
7
7
|
*/
|
|
8
8
|
export async function readPlatformStorageJson(request) {
|
|
9
9
|
return bridge.call(PLATFORM_BRIDGE_METHODS.STORAGE_READ_JSON, [request])
|
|
10
10
|
}
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Optional `ifMatch` (version from readPlatformStorageJson) makes the write
|
|
14
|
+
* conditional: on conflict nothing is written and the result has
|
|
15
|
+
* `conflict: true` with the current value — re-read, re-apply, retry.
|
|
16
|
+
* @param {{ appSlug: string, fileName: string, value: unknown, ifMatch?: string | null }} request
|
|
17
|
+
* @returns {Promise<{ path: string, ok: boolean, version: string | null, conflict?: true, value?: unknown | null }>}
|
|
15
18
|
*/
|
|
16
19
|
export async function writePlatformStorageJson(request) {
|
|
17
20
|
return bridge.call(PLATFORM_BRIDGE_METHODS.STORAGE_WRITE_JSON, [request])
|
|
@@ -20,6 +20,7 @@ export interface AgentComposerProps {
|
|
|
20
20
|
leading?: React.ReactNode
|
|
21
21
|
followUps?: AgentUiFollowUp[]
|
|
22
22
|
questionToolCalls?: AgentUiToolCall[]
|
|
23
|
+
questionPromptList?: React.ReactNode
|
|
23
24
|
usage?: AgentUiRunUsage | null
|
|
24
25
|
liveTurn?: AgentLiveTurn | null
|
|
25
26
|
onChange: (value: string) => void
|
|
@@ -76,6 +77,7 @@ export function AgentComposer({
|
|
|
76
77
|
leading,
|
|
77
78
|
followUps = [],
|
|
78
79
|
questionToolCalls = [],
|
|
80
|
+
questionPromptList = null,
|
|
79
81
|
usage = null,
|
|
80
82
|
liveTurn = null,
|
|
81
83
|
onChange,
|
|
@@ -89,7 +91,7 @@ export function AgentComposer({
|
|
|
89
91
|
const showUsage =
|
|
90
92
|
liveTurn?.contextCompaction === 'summarizing' ||
|
|
91
93
|
Boolean(usage && (usage.totalTokens > 0 || usage.contextFillTokens))
|
|
92
|
-
const hasQuestions = questionToolCalls.length > 0
|
|
94
|
+
const hasQuestions = questionPromptList !== null || questionToolCalls.length > 0
|
|
93
95
|
|
|
94
96
|
return (
|
|
95
97
|
<StyledRoot>
|
|
@@ -105,11 +107,13 @@ export function AgentComposer({
|
|
|
105
107
|
preserveChildrenOnExit
|
|
106
108
|
aria-hidden={!hasQuestions}
|
|
107
109
|
>
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
110
|
+
{questionPromptList ?? (
|
|
111
|
+
<AgentQuestionPromptList
|
|
112
|
+
toolCalls={questionToolCalls}
|
|
113
|
+
onAnswer={onAnswerQuestionTool}
|
|
114
|
+
onDecline={onDeclineQuestionTool}
|
|
115
|
+
/>
|
|
116
|
+
)}
|
|
113
117
|
</StyledQuestionPresence>
|
|
114
118
|
<StyledComposerSurfaceAnchor>
|
|
115
119
|
{showUsage ? (
|
|
@@ -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,50 +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,
|
|
48
|
-
|
|
30
|
+
bottomPanel = null,
|
|
49
31
|
}: AgentDrawerPanelProps): React.ReactElement {
|
|
50
32
|
const awaitingApproval = pendingToolCalls.length > 0
|
|
51
|
-
|
|
33
|
+
void pureAssistantTabActive
|
|
52
34
|
|
|
53
|
-
const showFooterComposer = !composerPortaled
|
|
54
|
-
const reserveComposerFooter = composerPortaled && drawerOpen
|
|
55
35
|
const footerWork = (
|
|
56
36
|
<>
|
|
57
37
|
{awaitingApproval ? (
|
|
@@ -62,63 +42,42 @@ export function AgentDrawerPanel({
|
|
|
62
42
|
onReject={onRejectTools}
|
|
63
43
|
onApproveOne={onApproveOne}
|
|
64
44
|
onRejectOne={onRejectOne}
|
|
65
|
-
|
|
45
|
+
approveAlwaysScopeLabel={approveAlwaysScopeLabel}
|
|
46
|
+
onApproveAlwaysForScope={onApproveAlwaysForScope}
|
|
66
47
|
/>
|
|
67
48
|
) : null}
|
|
68
49
|
</>
|
|
69
50
|
)
|
|
70
|
-
const hasFooterWork = awaitingApproval
|
|
71
51
|
|
|
72
52
|
return (
|
|
73
|
-
<StyledAgentDrawer
|
|
74
|
-
data-pure-assistant-tab-active={pureAssistantTabActive || undefined}
|
|
75
|
-
>
|
|
53
|
+
<StyledAgentDrawer>
|
|
76
54
|
<StyledAgentBody>
|
|
77
55
|
{storageHint ? (
|
|
78
|
-
<InlineBanner
|
|
56
|
+
<InlineBanner
|
|
57
|
+
variant="warning"
|
|
58
|
+
actions={storageHintAction}
|
|
59
|
+
onDismiss={onStorageHintDismiss}
|
|
60
|
+
>
|
|
61
|
+
{storageHint}
|
|
62
|
+
</InlineBanner>
|
|
79
63
|
) : null}
|
|
80
64
|
{activityHint ? (
|
|
81
65
|
<InlineBanner variant="warning">{activityHint}</InlineBanner>
|
|
82
66
|
) : null}
|
|
83
67
|
{error ? <InlineBanner variant="danger">{error}</InlineBanner> : null}
|
|
68
|
+
{threadHeader}
|
|
84
69
|
<StyledAgentThreadHost>
|
|
85
70
|
<AgentMessageList
|
|
86
71
|
messages={messages}
|
|
87
72
|
assistantLabel={assistantLabel}
|
|
88
|
-
threadHeader={threadHeader}
|
|
89
|
-
threadHeaderHeight={threadHeaderHeight}
|
|
90
73
|
/>
|
|
91
74
|
</StyledAgentThreadHost>
|
|
92
75
|
</StyledAgentBody>
|
|
93
|
-
{
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
busy={busy}
|
|
99
|
-
disabled={composerBlocked}
|
|
100
|
-
inputDisabled={composerInputDisabled}
|
|
101
|
-
sendDisabled={composerSendDisabled}
|
|
102
|
-
leading={composerLeading}
|
|
103
|
-
followUps={followUps}
|
|
104
|
-
questionToolCalls={pendingQuestionToolCalls}
|
|
105
|
-
usage={usage}
|
|
106
|
-
liveTurn={liveTurn}
|
|
107
|
-
onChange={onComposerChange}
|
|
108
|
-
onSend={onSend}
|
|
109
|
-
onStop={onStop}
|
|
110
|
-
onRemoveFollowUp={onRemoveFollowUp}
|
|
111
|
-
onSendFollowUpNow={onSendFollowUpNow}
|
|
112
|
-
onAnswerQuestionTool={onAnswerQuestionTool}
|
|
113
|
-
onDeclineQuestionTool={onDeclineQuestionTool}
|
|
114
|
-
/>
|
|
115
|
-
</StyledAgentFooter>
|
|
116
|
-
) : reserveComposerFooter || hasFooterWork ? (
|
|
117
|
-
<StyledComposerReserveFooter aria-hidden={!hasFooterWork}>
|
|
118
|
-
{footerWork}
|
|
119
|
-
{reserveComposerFooter ? <StyledComposerFooterSpacer /> : null}
|
|
120
|
-
</StyledComposerReserveFooter>
|
|
121
|
-
) : null}
|
|
76
|
+
{bottomPanel}
|
|
77
|
+
<StyledAgentFooter>
|
|
78
|
+
{footerWork}
|
|
79
|
+
{composerSlot}
|
|
80
|
+
</StyledAgentFooter>
|
|
122
81
|
</StyledAgentDrawer>
|
|
123
82
|
)
|
|
124
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,25 +14,19 @@ export interface AgentMessageListProps {
|
|
|
14
14
|
messages: AgentUiMessage[]
|
|
15
15
|
assistantLabel?: string
|
|
16
16
|
emptyMessage?: string
|
|
17
|
-
|
|
18
|
-
threadHeaderHeight?: number
|
|
17
|
+
threadFooter?: React.ReactNode
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
export const AgentMessageList = memo(function AgentMessageList({
|
|
22
21
|
messages,
|
|
23
22
|
assistantLabel = 'assistant',
|
|
24
23
|
emptyMessage = 'Ask the agent about your workspace. Tool calls wait for your approval.',
|
|
25
|
-
|
|
26
|
-
threadHeaderHeight = 34,
|
|
24
|
+
threadFooter = null,
|
|
27
25
|
}: AgentMessageListProps): React.ReactElement {
|
|
28
26
|
const scrollKey = messages.map(message => message.id).join('|')
|
|
29
27
|
|
|
30
28
|
return (
|
|
31
|
-
<StyledAgentScroll
|
|
32
|
-
data-thread-header={threadHeader ? '' : undefined}
|
|
33
|
-
$threadHeaderHeight={threadHeaderHeight}
|
|
34
|
-
>
|
|
35
|
-
{threadHeader}
|
|
29
|
+
<StyledAgentScroll>
|
|
36
30
|
<ChatThread
|
|
37
31
|
scrollKey={scrollKey}
|
|
38
32
|
emptyMessage={
|
|
@@ -40,6 +34,7 @@ export const AgentMessageList = memo(function AgentMessageList({
|
|
|
40
34
|
<ChatThreadEmpty>{emptyMessage}</ChatThreadEmpty>
|
|
41
35
|
) : null
|
|
42
36
|
}
|
|
37
|
+
footer={threadFooter}
|
|
43
38
|
>
|
|
44
39
|
{messages.map(message => (
|
|
45
40
|
<Fragment key={message.id}>
|