@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
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react'
|
|
2
|
+
import { styled } from 'styled-components'
|
|
3
|
+
import { Button } from '../common/buttons/Button.js'
|
|
4
|
+
import { FlexCol, FlexRow } from '../common/containers/flex.js'
|
|
5
|
+
import { Text } from '../common/typography/Text.js'
|
|
6
|
+
|
|
7
|
+
export interface QuestionOptionView {
|
|
8
|
+
value: string
|
|
9
|
+
label: string
|
|
10
|
+
description?: string
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface QuestionView {
|
|
14
|
+
id: string
|
|
15
|
+
question: string
|
|
16
|
+
multiple?: boolean
|
|
17
|
+
custom?: boolean
|
|
18
|
+
options: QuestionOptionView[]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface QuestionRequestView {
|
|
22
|
+
id: string
|
|
23
|
+
questions: QuestionView[]
|
|
24
|
+
rejectRequiresReason?: boolean
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface QuestionRequestListProps {
|
|
28
|
+
requests: QuestionRequestView[]
|
|
29
|
+
onAnswer?: (requestId: string, answers: string[][]) => void | Promise<void>
|
|
30
|
+
onReject?: (requestId: string, reason?: string) => void | Promise<void>
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const StyledList = styled(FlexCol)`
|
|
34
|
+
gap: 6px;
|
|
35
|
+
`
|
|
36
|
+
|
|
37
|
+
const StyledPrompt = styled(FlexCol)`
|
|
38
|
+
gap: var(--platform-spacing-sm);
|
|
39
|
+
padding: 10px;
|
|
40
|
+
border: 1px solid var(--platform-colors-border);
|
|
41
|
+
border-radius: var(--platform-radius-sm);
|
|
42
|
+
background: var(--platform-colors-bg);
|
|
43
|
+
`
|
|
44
|
+
|
|
45
|
+
const StyledQuestion = styled(Text)`
|
|
46
|
+
line-height: 1.35;
|
|
47
|
+
`
|
|
48
|
+
|
|
49
|
+
const StyledOptionGrid = styled.div`
|
|
50
|
+
display: grid;
|
|
51
|
+
gap: 6px;
|
|
52
|
+
`
|
|
53
|
+
|
|
54
|
+
const StyledOptionButton = styled.button<{ $selected: boolean }>`
|
|
55
|
+
display: grid;
|
|
56
|
+
gap: 2px;
|
|
57
|
+
width: 100%;
|
|
58
|
+
padding: 8px 10px;
|
|
59
|
+
border: 1px solid
|
|
60
|
+
${({ $selected }) =>
|
|
61
|
+
$selected
|
|
62
|
+
? 'var(--platform-colors-accent)'
|
|
63
|
+
: 'var(--platform-colors-border)'};
|
|
64
|
+
border-radius: var(--platform-radius-sm);
|
|
65
|
+
background: ${({ $selected }) =>
|
|
66
|
+
$selected
|
|
67
|
+
? 'color-mix(in srgb, var(--platform-colors-accent) 10%, var(--platform-colors-bg))'
|
|
68
|
+
: 'var(--platform-colors-surface)'};
|
|
69
|
+
color: var(--platform-colors-text);
|
|
70
|
+
text-align: left;
|
|
71
|
+
cursor: pointer;
|
|
72
|
+
transition: background var(--platform-transition-fast),
|
|
73
|
+
border-color var(--platform-transition-fast);
|
|
74
|
+
|
|
75
|
+
&:hover:not(:disabled) {
|
|
76
|
+
background: var(--platform-colors-surface-hover);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
&:focus {
|
|
80
|
+
outline: none;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
&:focus-visible {
|
|
84
|
+
outline: 1px solid var(--platform-colors-border-strong);
|
|
85
|
+
outline-offset: 2px;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
&:disabled {
|
|
89
|
+
opacity: 0.55;
|
|
90
|
+
cursor: not-allowed;
|
|
91
|
+
}
|
|
92
|
+
`
|
|
93
|
+
|
|
94
|
+
const StyledInput = styled.input`
|
|
95
|
+
min-width: 0;
|
|
96
|
+
flex: 1 1 auto;
|
|
97
|
+
height: 28px;
|
|
98
|
+
padding: 0 9px;
|
|
99
|
+
border: 1px solid var(--platform-colors-border);
|
|
100
|
+
border-radius: var(--platform-radius-sm);
|
|
101
|
+
background: var(--platform-colors-surface);
|
|
102
|
+
color: var(--platform-colors-text);
|
|
103
|
+
font: inherit;
|
|
104
|
+
font-size: var(--platform-typography-font-size-sm);
|
|
105
|
+
|
|
106
|
+
&:focus {
|
|
107
|
+
outline: none;
|
|
108
|
+
border-color: var(--platform-colors-accent);
|
|
109
|
+
}
|
|
110
|
+
`
|
|
111
|
+
|
|
112
|
+
function QuestionRequest({
|
|
113
|
+
request,
|
|
114
|
+
onAnswer,
|
|
115
|
+
onReject,
|
|
116
|
+
}: {
|
|
117
|
+
request: QuestionRequestView
|
|
118
|
+
onAnswer?: QuestionRequestListProps['onAnswer']
|
|
119
|
+
onReject?: QuestionRequestListProps['onReject']
|
|
120
|
+
}): React.ReactElement {
|
|
121
|
+
const [answers, setAnswers] = useState<string[][]>([])
|
|
122
|
+
const [rejectReason, setRejectReason] = useState('')
|
|
123
|
+
const [submitting, setSubmitting] = useState(false)
|
|
124
|
+
|
|
125
|
+
useEffect(() => {
|
|
126
|
+
setAnswers(request.questions.map(() => []))
|
|
127
|
+
setRejectReason('')
|
|
128
|
+
setSubmitting(false)
|
|
129
|
+
}, [request.id, request.questions.length])
|
|
130
|
+
|
|
131
|
+
const setQuestionAnswer = (index: number, value: string): void => {
|
|
132
|
+
const question = request.questions[index]
|
|
133
|
+
setAnswers(current => {
|
|
134
|
+
const next = [...current]
|
|
135
|
+
const selected = next[index] ?? []
|
|
136
|
+
next[index] = question.multiple
|
|
137
|
+
? selected.includes(value)
|
|
138
|
+
? selected.filter(item => item !== value)
|
|
139
|
+
: [...selected, value]
|
|
140
|
+
: [value]
|
|
141
|
+
return next
|
|
142
|
+
})
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const submitAnswer = async (): Promise<void> => {
|
|
146
|
+
if (!onAnswer || submitting || !answers.every(answer => answer.length)) {
|
|
147
|
+
return
|
|
148
|
+
}
|
|
149
|
+
setSubmitting(true)
|
|
150
|
+
try {
|
|
151
|
+
await onAnswer(request.id, answers)
|
|
152
|
+
} finally {
|
|
153
|
+
setSubmitting(false)
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const submitReject = async (): Promise<void> => {
|
|
158
|
+
if (!onReject || submitting) return
|
|
159
|
+
const reason = rejectReason.trim()
|
|
160
|
+
if (request.rejectRequiresReason && !reason) return
|
|
161
|
+
setSubmitting(true)
|
|
162
|
+
try {
|
|
163
|
+
await onReject(request.id, reason || undefined)
|
|
164
|
+
} finally {
|
|
165
|
+
setSubmitting(false)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const canAnswer =
|
|
170
|
+
Boolean(onAnswer) && answers.length > 0 && answers.every(answer => answer.length)
|
|
171
|
+
const canReject =
|
|
172
|
+
Boolean(onReject) &&
|
|
173
|
+
(!request.rejectRequiresReason || rejectReason.trim().length > 0)
|
|
174
|
+
|
|
175
|
+
return (
|
|
176
|
+
<StyledPrompt>
|
|
177
|
+
{request.questions.map((question, index) => (
|
|
178
|
+
<FlexCol key={question.id} $gap="sm">
|
|
179
|
+
<StyledQuestion size="sm" weight="medium">
|
|
180
|
+
{question.question}
|
|
181
|
+
</StyledQuestion>
|
|
182
|
+
<StyledOptionGrid>
|
|
183
|
+
{question.options.map(option => {
|
|
184
|
+
const selected = answers[index]?.includes(option.value) ?? false
|
|
185
|
+
return (
|
|
186
|
+
<StyledOptionButton
|
|
187
|
+
key={option.value}
|
|
188
|
+
type="button"
|
|
189
|
+
disabled={submitting}
|
|
190
|
+
$selected={selected}
|
|
191
|
+
aria-pressed={selected}
|
|
192
|
+
onClick={() => setQuestionAnswer(index, option.value)}
|
|
193
|
+
>
|
|
194
|
+
<Text size="sm" weight="medium">
|
|
195
|
+
{option.label}
|
|
196
|
+
</Text>
|
|
197
|
+
{option.description ? (
|
|
198
|
+
<Text size="sm" tone="secondary">
|
|
199
|
+
{option.description}
|
|
200
|
+
</Text>
|
|
201
|
+
) : null}
|
|
202
|
+
</StyledOptionButton>
|
|
203
|
+
)
|
|
204
|
+
})}
|
|
205
|
+
</StyledOptionGrid>
|
|
206
|
+
{question.custom ? (
|
|
207
|
+
<StyledInput
|
|
208
|
+
value={answers[index]?.[0] ?? ''}
|
|
209
|
+
disabled={submitting}
|
|
210
|
+
placeholder="Custom answer"
|
|
211
|
+
aria-label="Custom answer"
|
|
212
|
+
onChange={event => {
|
|
213
|
+
const value = event.target.value
|
|
214
|
+
setAnswers(current => {
|
|
215
|
+
const next = [...current]
|
|
216
|
+
next[index] = value.trim() ? [value] : []
|
|
217
|
+
return next
|
|
218
|
+
})
|
|
219
|
+
}}
|
|
220
|
+
/>
|
|
221
|
+
) : null}
|
|
222
|
+
</FlexCol>
|
|
223
|
+
))}
|
|
224
|
+
<FlexRow $align="center" $justify="between" $gap="sm">
|
|
225
|
+
{request.rejectRequiresReason ? (
|
|
226
|
+
<StyledInput
|
|
227
|
+
value={rejectReason}
|
|
228
|
+
disabled={submitting}
|
|
229
|
+
placeholder="Decline reason"
|
|
230
|
+
aria-label="Decline reason"
|
|
231
|
+
onChange={event => setRejectReason(event.target.value)}
|
|
232
|
+
/>
|
|
233
|
+
) : null}
|
|
234
|
+
{onReject ? (
|
|
235
|
+
<Button
|
|
236
|
+
size="sm"
|
|
237
|
+
variant="subtle"
|
|
238
|
+
disabled={!canReject || submitting}
|
|
239
|
+
loading={submitting}
|
|
240
|
+
onClick={() => void submitReject()}
|
|
241
|
+
>
|
|
242
|
+
Decline
|
|
243
|
+
</Button>
|
|
244
|
+
) : null}
|
|
245
|
+
<Button
|
|
246
|
+
size="sm"
|
|
247
|
+
variant="primary"
|
|
248
|
+
disabled={!canAnswer || submitting}
|
|
249
|
+
loading={submitting}
|
|
250
|
+
onClick={() => void submitAnswer()}
|
|
251
|
+
>
|
|
252
|
+
Answer
|
|
253
|
+
</Button>
|
|
254
|
+
</FlexRow>
|
|
255
|
+
</StyledPrompt>
|
|
256
|
+
)
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function QuestionRequestList({
|
|
260
|
+
requests,
|
|
261
|
+
onAnswer,
|
|
262
|
+
onReject,
|
|
263
|
+
}: QuestionRequestListProps): React.ReactElement | null {
|
|
264
|
+
if (requests.length === 0) return null
|
|
265
|
+
|
|
266
|
+
return (
|
|
267
|
+
<StyledList aria-label="Questions from agent">
|
|
268
|
+
{requests.map(request => (
|
|
269
|
+
<QuestionRequest
|
|
270
|
+
key={request.id}
|
|
271
|
+
request={request}
|
|
272
|
+
onAnswer={onAnswer}
|
|
273
|
+
onReject={onReject}
|
|
274
|
+
/>
|
|
275
|
+
))}
|
|
276
|
+
</StyledList>
|
|
277
|
+
)
|
|
278
|
+
}
|
|
@@ -4,7 +4,10 @@ import { FlexCol, FlexRow } from '../common/containers/flex.js'
|
|
|
4
4
|
export const StyledAgentDrawer = styled(FlexCol)`
|
|
5
5
|
flex: 1 1 auto;
|
|
6
6
|
min-height: 0;
|
|
7
|
+
min-width: 0;
|
|
7
8
|
width: 100%;
|
|
9
|
+
box-sizing: border-box;
|
|
10
|
+
overflow: hidden;
|
|
8
11
|
background: var(--platform-colors-app-viewport);
|
|
9
12
|
margin: 0;
|
|
10
13
|
`
|
|
@@ -24,15 +27,14 @@ export const StyledAgentThreadHost = styled.div`
|
|
|
24
27
|
`
|
|
25
28
|
|
|
26
29
|
export const StyledAgentScroll = styled.div`
|
|
27
|
-
|
|
30
|
+
flex: 1 1 auto;
|
|
28
31
|
min-height: 0;
|
|
32
|
+
min-width: 0;
|
|
33
|
+
width: 100%;
|
|
34
|
+
box-sizing: border-box;
|
|
35
|
+
overflow: hidden;
|
|
29
36
|
display: flex;
|
|
30
37
|
flex-direction: column;
|
|
31
|
-
--platform-agent-thread-header-height: 0px;
|
|
32
|
-
|
|
33
|
-
&[data-thread-header] {
|
|
34
|
-
--platform-agent-thread-header-height: 34px;
|
|
35
|
-
}
|
|
36
38
|
`
|
|
37
39
|
|
|
38
40
|
export const StyledAgentFooter = styled.div`
|
|
@@ -44,31 +46,6 @@ export const StyledAgentFooter = styled.div`
|
|
|
44
46
|
background: var(--platform-colors-surface);
|
|
45
47
|
`
|
|
46
48
|
|
|
47
|
-
/** Delay footer reserve collapse so the portaled chatbox can lead the motion. */
|
|
48
|
-
export const StyledComposerReserveFooter = styled(StyledAgentFooter)`
|
|
49
|
-
overflow: hidden;
|
|
50
|
-
gap: var(--platform-spacing-sm);
|
|
51
|
-
padding-block: 8px 12px;
|
|
52
|
-
border-top: 1px solid var(--platform-colors-border);
|
|
53
|
-
transition: padding-block 0.5s linear, gap 0.5s linear,
|
|
54
|
-
border-color 0.5s linear;
|
|
55
|
-
|
|
56
|
-
[data-pure-assistant-tab-active='true'] & {
|
|
57
|
-
gap: 0;
|
|
58
|
-
padding-block: 0;
|
|
59
|
-
border-top-color: transparent;
|
|
60
|
-
}
|
|
61
|
-
`
|
|
62
|
-
|
|
63
|
-
export const StyledComposerFooterSpacer = styled.div`
|
|
64
|
-
min-height: 112px;
|
|
65
|
-
transition: min-height 0.5s linear;
|
|
66
|
-
|
|
67
|
-
[data-pure-assistant-tab-active='true'] & {
|
|
68
|
-
min-height: 0;
|
|
69
|
-
}
|
|
70
|
-
`
|
|
71
|
-
|
|
72
49
|
export const StyledToolCard = styled.div`
|
|
73
50
|
display: flex;
|
|
74
51
|
flex-direction: column;
|
|
@@ -12,14 +12,22 @@ export type AgentUiRunState =
|
|
|
12
12
|
|
|
13
13
|
export type AgentUiMessageRole = 'user' | 'assistant' | 'tool'
|
|
14
14
|
|
|
15
|
-
export interface AgentUiToolCall {
|
|
16
|
-
id: string
|
|
17
|
-
name: string
|
|
18
|
-
arguments: Record<string, unknown>
|
|
19
|
-
result?: string
|
|
20
|
-
status?: 'running' | 'done'
|
|
21
|
-
delegatedActivity?: AgentDelegatedActivity
|
|
22
|
-
|
|
15
|
+
export interface AgentUiToolCall {
|
|
16
|
+
id: string
|
|
17
|
+
name: string
|
|
18
|
+
arguments: Record<string, unknown>
|
|
19
|
+
result?: string
|
|
20
|
+
status?: 'running' | 'done'
|
|
21
|
+
delegatedActivity?: AgentDelegatedActivity
|
|
22
|
+
serviceActivity?: AgentToolActivity
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AgentToolActivity {
|
|
26
|
+
detail?: string
|
|
27
|
+
id: string
|
|
28
|
+
status: 'done' | 'failed' | 'info' | 'running'
|
|
29
|
+
title: string
|
|
30
|
+
}
|
|
23
31
|
|
|
24
32
|
export const AgentLivePhase = {
|
|
25
33
|
Idle: 'idle',
|
|
@@ -92,48 +100,24 @@ export interface AgentUiRunUsage {
|
|
|
92
100
|
export interface AgentDrawerPanelProps {
|
|
93
101
|
messages: AgentUiMessage[]
|
|
94
102
|
assistantLabel?: string
|
|
95
|
-
liveTurn?: AgentLiveTurn | null
|
|
96
103
|
pendingToolCalls: AgentUiToolCall[]
|
|
97
|
-
pendingQuestionToolCalls?: AgentUiToolCall[]
|
|
98
|
-
runState: AgentUiRunState | null
|
|
99
|
-
busy: boolean
|
|
100
104
|
error: string | null
|
|
101
|
-
|
|
102
|
-
composerDisabled?: boolean
|
|
103
|
-
composerInputDisabled?: boolean
|
|
104
|
-
composerSendDisabled?: boolean
|
|
105
|
-
composerLeading?: React.ReactNode
|
|
106
|
-
followUps?: AgentUiFollowUp[]
|
|
105
|
+
composerSlot?: React.ReactNode
|
|
107
106
|
activityHint?: string | null
|
|
108
|
-
onComposerChange: (value: string) => void
|
|
109
|
-
onSend: () => void
|
|
110
|
-
onStop?: () => void
|
|
111
|
-
onRemoveFollowUp?: (followUpId: string) => void
|
|
112
|
-
onSendFollowUpNow?: (followUpId: string) => void
|
|
113
107
|
onApproveTools: () => void
|
|
114
108
|
onRejectTools: () => void
|
|
115
109
|
onApproveOne?: (toolCallId: string) => void
|
|
116
110
|
onRejectOne?: (toolCallId: string) => void
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
) => void
|
|
121
|
-
onAnswerQuestionTool?: (
|
|
122
|
-
toolCallId: string,
|
|
123
|
-
selectedOptionIds: readonly string[],
|
|
124
|
-
) => void | Promise<void>
|
|
125
|
-
onDeclineQuestionTool?: (
|
|
126
|
-
toolCallId: string,
|
|
127
|
-
declineReason: string,
|
|
128
|
-
) => void | Promise<void>
|
|
111
|
+
/** Label for the standing-approval option ("Always approve for Writer"). */
|
|
112
|
+
approveAlwaysScopeLabel?: string
|
|
113
|
+
onApproveAlwaysForScope?: (qualifiedName: string, toolCallId: string) => void
|
|
129
114
|
storageHint?: string | null
|
|
130
|
-
|
|
115
|
+
storageHintAction?: React.ReactNode
|
|
116
|
+
onStorageHintDismiss?: () => void
|
|
131
117
|
/** Pure Assistant workspace tab is active — shell sets this; UI leaves layout to the host. */
|
|
132
118
|
pureAssistantTabActive?: boolean
|
|
133
|
-
/**
|
|
134
|
-
drawerOpen?: boolean
|
|
135
|
-
/** Composer is portaled by the shell — drawer keeps a footer spacer only. */
|
|
136
|
-
composerPortaled?: boolean
|
|
137
|
-
/** Shell thread chrome (scope, session actions) — anchored to the message column. */
|
|
119
|
+
/** Shell thread chrome rendered above the scrollable message column. */
|
|
138
120
|
threadHeader?: React.ReactNode
|
|
121
|
+
/** Shell-owned content rendered between the message thread and interaction footer. */
|
|
122
|
+
bottomPanel?: React.ReactNode
|
|
139
123
|
}
|
|
@@ -3,6 +3,10 @@ export { AgentMessageList, formatAssistantLabel } from './AgentMessageList.js'
|
|
|
3
3
|
export { AgentMessageBubble } from './AgentMessageBubble.js'
|
|
4
4
|
export { AgentToolPendingList } from './AgentToolPendingList.js'
|
|
5
5
|
export { AgentComposer } from './AgentComposer.js'
|
|
6
|
+
export {
|
|
7
|
+
QuestionRequestList,
|
|
8
|
+
type QuestionRequestView,
|
|
9
|
+
} from './QuestionRequestList.js'
|
|
6
10
|
export {
|
|
7
11
|
AgentContextUsageBar,
|
|
8
12
|
formatAgentTokenCount,
|
|
@@ -12,20 +12,26 @@ const StyledList = styled.div`
|
|
|
12
12
|
flex: 1;
|
|
13
13
|
min-height: 0;
|
|
14
14
|
overflow: auto;
|
|
15
|
-
padding:
|
|
15
|
+
/* No top padding: it's inside the scrollport, so a sticky header pinned at
|
|
16
|
+
top:0 would leave/scroll a gap above it (the "jumps down / white space"). */
|
|
17
|
+
padding: 0 24px 24px;
|
|
16
18
|
`
|
|
17
19
|
|
|
18
20
|
const StyledSectionHeading = styled.div`
|
|
19
|
-
|
|
21
|
+
/* Full-bleed opaque bar: negative inline margin + matching padding makes the
|
|
22
|
+
background span the whole sidebar so rows scroll fully under it (they used
|
|
23
|
+
to overrun/bleed through the label); text stays aligned with the rows. */
|
|
24
|
+
margin: 0 -24px;
|
|
25
|
+
padding: var(--platform-spacing-sm) 24px var(--platform-spacing-xs);
|
|
20
26
|
font-size: var(--platform-typography-font-size-xs);
|
|
21
27
|
font-weight: var(--platform-typography-font-weight-medium);
|
|
22
28
|
letter-spacing: 0.04em;
|
|
23
29
|
text-transform: uppercase;
|
|
24
30
|
color: var(--platform-colors-text-secondary);
|
|
25
|
-
background:
|
|
31
|
+
background: var(--platform-colors-surface);
|
|
26
32
|
position: sticky;
|
|
27
33
|
top: 0;
|
|
28
|
-
z-index:
|
|
34
|
+
z-index: 2;
|
|
29
35
|
`
|
|
30
36
|
|
|
31
37
|
const StyledEmptyList = styled.div`
|
|
@@ -28,7 +28,7 @@ const StyledScroll = styled.div`
|
|
|
28
28
|
flex: 1;
|
|
29
29
|
min-width: 0;
|
|
30
30
|
gap: 2px;
|
|
31
|
-
padding: 5px
|
|
31
|
+
padding: 5px 10px 0 12px;
|
|
32
32
|
overflow-x: auto;
|
|
33
33
|
overflow-y: hidden;
|
|
34
34
|
scrollbar-width: thin;
|
|
@@ -46,7 +46,7 @@ const StyledTab = styled.button<{
|
|
|
46
46
|
height: 31px;
|
|
47
47
|
max-width: 260px;
|
|
48
48
|
margin-bottom: -1px;
|
|
49
|
-
padding: 0 8px 0
|
|
49
|
+
padding: 0 8px 0 12px;
|
|
50
50
|
border: 1px solid transparent;
|
|
51
51
|
border-bottom-color: transparent;
|
|
52
52
|
border-radius: 11px 11px 0 0;
|
|
@@ -120,7 +120,8 @@ const StyledLabel = styled.span`
|
|
|
120
120
|
|
|
121
121
|
const StyledTabWordmark = styled(PureAppWordmark)`
|
|
122
122
|
min-width: 0;
|
|
123
|
-
|
|
123
|
+
padding-left: 1px;
|
|
124
|
+
overflow: visible;
|
|
124
125
|
|
|
125
126
|
> span:last-child {
|
|
126
127
|
overflow: hidden;
|
|
@@ -169,6 +170,20 @@ const StyledDocName = styled.span`
|
|
|
169
170
|
color: var(--pure-chrome-tertiary, #8a857c);
|
|
170
171
|
`
|
|
171
172
|
|
|
173
|
+
const StyledDevBadge = styled.span`
|
|
174
|
+
display: inline-flex;
|
|
175
|
+
align-items: center;
|
|
176
|
+
justify-content: center;
|
|
177
|
+
flex-shrink: 0;
|
|
178
|
+
font-size: 9px;
|
|
179
|
+
font-weight: 600;
|
|
180
|
+
line-height: 1;
|
|
181
|
+
letter-spacing: 0.04em;
|
|
182
|
+
text-transform: uppercase;
|
|
183
|
+
color: var(--pure-chrome-tertiary, #8a857c);
|
|
184
|
+
opacity: 0.72;
|
|
185
|
+
`
|
|
186
|
+
|
|
172
187
|
const StyledClose = styled(X)`
|
|
173
188
|
cursor: pointer;
|
|
174
189
|
opacity: 0.7;
|
|
@@ -226,7 +241,7 @@ export function WorkspaceTabStrip({
|
|
|
226
241
|
<StyledScroll role="tablist" aria-label={ariaLabel}>
|
|
227
242
|
{tabs.map((tab, index) => {
|
|
228
243
|
const active = tab.id === activeTabId
|
|
229
|
-
const closable =
|
|
244
|
+
const closable = onClose
|
|
230
245
|
return (
|
|
231
246
|
<StyledTab
|
|
232
247
|
key={tab.id}
|
|
@@ -306,6 +321,9 @@ export function WorkspaceTabStrip({
|
|
|
306
321
|
tab.label
|
|
307
322
|
)}
|
|
308
323
|
</StyledLabel>
|
|
324
|
+
{tab.underDevelopment ? (
|
|
325
|
+
<StyledDevBadge title="Under development">dev</StyledDevBadge>
|
|
326
|
+
) : null}
|
|
309
327
|
{closable ? (
|
|
310
328
|
<StyledClose
|
|
311
329
|
type="button"
|
|
@@ -4,10 +4,10 @@ import { Send, Square } from 'lucide-react'
|
|
|
4
4
|
import { FlexCol } from '../containers/flex.js'
|
|
5
5
|
import { FlexRow } from '../containers/flex.js'
|
|
6
6
|
|
|
7
|
-
export interface ChatBoxProps {
|
|
8
|
-
value: string
|
|
9
|
-
onChange: (value: string) => void
|
|
10
|
-
onSend: () => void
|
|
7
|
+
export interface ChatBoxProps {
|
|
8
|
+
value: string
|
|
9
|
+
onChange: (value: string) => void
|
|
10
|
+
onSend: () => void
|
|
11
11
|
onStop?: () => void
|
|
12
12
|
sending?: boolean
|
|
13
13
|
disabled?: boolean
|
|
@@ -22,10 +22,10 @@ export interface ChatBoxProps {
|
|
|
22
22
|
|
|
23
23
|
export const ChatBox = forwardRef<HTMLTextAreaElement, ChatBoxProps>(
|
|
24
24
|
function ChatBox(
|
|
25
|
-
{
|
|
26
|
-
value,
|
|
27
|
-
onChange,
|
|
28
|
-
onSend,
|
|
25
|
+
{
|
|
26
|
+
value,
|
|
27
|
+
onChange,
|
|
28
|
+
onSend,
|
|
29
29
|
onStop,
|
|
30
30
|
sending = false,
|
|
31
31
|
disabled = false,
|
|
@@ -54,7 +54,7 @@ export const ChatBox = forwardRef<HTMLTextAreaElement, ChatBoxProps>(
|
|
|
54
54
|
const showStop = sending && onStop
|
|
55
55
|
|
|
56
56
|
return (
|
|
57
|
-
<Root $gap="xs">
|
|
57
|
+
<Root $gap="xs">
|
|
58
58
|
<EditorWrapper>
|
|
59
59
|
{topContent ?? null}
|
|
60
60
|
<Textarea
|
|
@@ -2,6 +2,7 @@ import { ChevronDown } from 'lucide-react'
|
|
|
2
2
|
import { Fragment, useMemo } from 'react'
|
|
3
3
|
import { styled } from 'styled-components'
|
|
4
4
|
import { PlatformIcon } from '../../chrome/PlatformIcon.js'
|
|
5
|
+
import { CredentialProviderIcon } from '../../credentials/credentialProviderIcon.js'
|
|
5
6
|
import { SelectMenuControl } from '../dropdown/SelectMenuControl.js'
|
|
6
7
|
import type { SelectMenuPlacement } from '../dropdown/SelectMenu.js'
|
|
7
8
|
import { useSelectMenuAnchor } from '../dropdown/useSelectMenuAnchor.js'
|
|
@@ -11,6 +12,7 @@ export interface ChatModelMenuOption {
|
|
|
11
12
|
label: string
|
|
12
13
|
groupLabel?: string
|
|
13
14
|
meta?: string
|
|
15
|
+
provider?: string
|
|
14
16
|
disabled?: boolean
|
|
15
17
|
contextWindowTokens?: number
|
|
16
18
|
supportsImageInput?: boolean
|
|
@@ -175,6 +177,12 @@ export function ChatModelMenu({
|
|
|
175
177
|
title={triggerLabel ?? current?.label ?? ariaLabel}
|
|
176
178
|
onClick={toggle}
|
|
177
179
|
>
|
|
180
|
+
{current?.provider ? (
|
|
181
|
+
<CredentialProviderIcon
|
|
182
|
+
credentialId={`${current.provider}.apiKey`}
|
|
183
|
+
label={current.provider}
|
|
184
|
+
/>
|
|
185
|
+
) : null}
|
|
178
186
|
<StyledLabel>{triggerLabel ?? current?.label ?? 'Model'}</StyledLabel>
|
|
179
187
|
<PlatformIcon icon={ChevronDown} size={12} strokeWidth={1.75} />
|
|
180
188
|
</StyledTrigger>
|
|
@@ -234,10 +242,20 @@ function ModelMenuOptions({
|
|
|
234
242
|
onClose()
|
|
235
243
|
}}
|
|
236
244
|
>
|
|
237
|
-
<
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
245
|
+
<StyledOptionContent>
|
|
246
|
+
{option.provider ? (
|
|
247
|
+
<CredentialProviderIcon
|
|
248
|
+
credentialId={`${option.provider}.apiKey`}
|
|
249
|
+
label={option.provider}
|
|
250
|
+
/>
|
|
251
|
+
) : null}
|
|
252
|
+
<StyledOptionText>
|
|
253
|
+
<StyledOptionLabel>{option.label}</StyledOptionLabel>
|
|
254
|
+
{option.meta ? (
|
|
255
|
+
<StyledOptionMeta>{option.meta}</StyledOptionMeta>
|
|
256
|
+
) : null}
|
|
257
|
+
</StyledOptionText>
|
|
258
|
+
</StyledOptionContent>
|
|
241
259
|
</StyledOption>
|
|
242
260
|
</Fragment>
|
|
243
261
|
)
|
|
@@ -269,6 +287,18 @@ const StyledOption = styled.button<{ $selected?: boolean }>`
|
|
|
269
287
|
}
|
|
270
288
|
`
|
|
271
289
|
|
|
290
|
+
const StyledOptionContent = styled.span`
|
|
291
|
+
display: flex;
|
|
292
|
+
align-items: flex-start;
|
|
293
|
+
gap: 7px;
|
|
294
|
+
`
|
|
295
|
+
|
|
296
|
+
const StyledOptionText = styled.span`
|
|
297
|
+
display: flex;
|
|
298
|
+
flex-direction: column;
|
|
299
|
+
gap: 2px;
|
|
300
|
+
`
|
|
301
|
+
|
|
272
302
|
const StyledOptionLabel = styled.span`
|
|
273
303
|
font-size: var(--platform-typography-font-size-sm);
|
|
274
304
|
line-height: 1.25;
|