@opengeni/react 7.1.0 → 7.1.2-canary.36079201993001
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/dist/artifacts-document.js +2 -2
- package/dist/artifacts-presentation.js +2 -2
- package/dist/artifacts-spreadsheet.js +2 -2
- package/dist/artifacts.js +4 -4
- package/dist/{browser-viewer-MN7MOYQT.js → browser-viewer-K2H3SLIW.js} +2 -2
- package/dist/{chunk-4ACTWOOW.js → chunk-6N3AZUOV.js} +31 -14
- package/dist/chunk-6N3AZUOV.js.map +1 -0
- package/dist/{chunk-KCGXPP5V.js → chunk-6P7T4RPE.js} +7 -3
- package/dist/chunk-6P7T4RPE.js.map +1 -0
- package/dist/{chunk-SHRCATV6.js → chunk-7J4DUR3E.js} +12 -5
- package/dist/chunk-7J4DUR3E.js.map +1 -0
- package/dist/{chunk-ZC2DK5SC.js → chunk-AWIX4UAC.js} +88 -7
- package/dist/chunk-AWIX4UAC.js.map +1 -0
- package/dist/{chunk-UQS7BGNQ.js → chunk-ETA37UZ7.js} +82 -11
- package/dist/chunk-ETA37UZ7.js.map +1 -0
- package/dist/{chunk-VR4Z73EU.js → chunk-G2SCXNXW.js} +9 -2
- package/dist/chunk-G2SCXNXW.js.map +1 -0
- package/dist/{chunk-5UONOQ3Y.js → chunk-K3MQZO7R.js} +150 -67
- package/dist/chunk-K3MQZO7R.js.map +1 -0
- package/dist/{chunk-OWHPKAOV.js → chunk-L6MYP3QU.js} +4 -2
- package/dist/{chunk-OWHPKAOV.js.map → chunk-L6MYP3QU.js.map} +1 -1
- package/dist/{chunk-VI7HELBI.js → chunk-SJ5HE4ZA.js} +21 -2
- package/dist/chunk-SJ5HE4ZA.js.map +1 -0
- package/dist/{chunk-XMXRI5JK.js → chunk-ZZ4744OZ.js} +128 -71
- package/dist/chunk-ZZ4744OZ.js.map +1 -0
- package/dist/components/artifacts/artifact-surface.d.ts +3 -1
- package/dist/components/artifacts/document-editor.d.ts +4 -2
- package/dist/components/artifacts/editable-document.d.ts +1 -1
- package/dist/components/artifacts/editable-presentation.d.ts +1 -1
- package/dist/components/artifacts/editable-spreadsheet.d.ts +2 -1
- package/dist/components/artifacts/presentation-editor.d.ts +4 -2
- package/dist/components/browser-viewer.d.ts +4 -1
- package/dist/components/markdown.d.ts +2 -0
- package/dist/components/session-capability-frame.d.ts +5 -1
- package/dist/composer.js +1 -1
- package/dist/connect.js +2 -2
- package/dist/hooks/use-browser-session.d.ts +3 -0
- package/dist/index.js +7 -6
- package/dist/index.js.map +1 -1
- package/dist/interaction.js +1 -1
- package/dist/session-ui.js +4 -4
- package/dist/session.js +1 -1
- package/dist/timeline/types.d.ts +2 -0
- package/package.json +3 -3
- package/src/components/artifacts/artifact-surface.tsx +39 -19
- package/src/components/artifacts/document-editor.tsx +77 -9
- package/src/components/artifacts/editable-document.tsx +3 -0
- package/src/components/artifacts/editable-presentation.tsx +3 -0
- package/src/components/artifacts/editable-spreadsheet.tsx +3 -0
- package/src/components/artifacts/presentation-editor.tsx +6 -0
- package/src/components/browser-viewer.tsx +11 -5
- package/src/components/composer-transcription-control.tsx +4 -2
- package/src/components/composer.tsx +20 -9
- package/src/components/markdown.tsx +9 -2
- package/src/components/sandbox-workspace.tsx +1 -0
- package/src/components/session-capability-frame.tsx +11 -3
- package/src/components/session-mcp-capability-card.tsx +174 -70
- package/src/hooks/use-browser-frame-stream.ts +3 -1
- package/src/hooks/use-browser-session.ts +107 -3
- package/src/hooks/use-voice-input.ts +12 -1
- package/src/timeline/compaction-copy.ts +2 -0
- package/src/timeline/parsers.ts +2 -2
- package/src/timeline/projection.ts +31 -5
- package/src/timeline/tool-renderers.tsx +105 -16
- package/src/timeline/turn-summary.tsx +10 -2
- package/src/timeline/types.ts +2 -0
- package/styles/compiled.css +6 -0
- package/styles/session-capability.css +2 -0
- package/dist/chunk-4ACTWOOW.js.map +0 -1
- package/dist/chunk-5UONOQ3Y.js.map +0 -1
- package/dist/chunk-KCGXPP5V.js.map +0 -1
- package/dist/chunk-SHRCATV6.js.map +0 -1
- package/dist/chunk-UQS7BGNQ.js.map +0 -1
- package/dist/chunk-VI7HELBI.js.map +0 -1
- package/dist/chunk-VR4Z73EU.js.map +0 -1
- package/dist/chunk-XMXRI5JK.js.map +0 -1
- package/dist/chunk-ZC2DK5SC.js.map +0 -1
- /package/dist/{browser-viewer-MN7MOYQT.js.map → browser-viewer-K2H3SLIW.js.map} +0 -0
|
@@ -53,6 +53,7 @@ const InteractiveContext = createContext<{
|
|
|
53
53
|
streaming?: boolean | undefined;
|
|
54
54
|
render?: (block: MarkdownInteractiveBlock) => ReactNode;
|
|
55
55
|
renderImage?: (image: { src: string; alt: string }) => ReactNode;
|
|
56
|
+
suppressImages?: boolean;
|
|
56
57
|
}>({ source: "" });
|
|
57
58
|
|
|
58
59
|
export type MarkdownProps = {
|
|
@@ -61,6 +62,8 @@ export type MarkdownProps = {
|
|
|
61
62
|
/** Reveals a bounded source excerpt; closing Find retains it until explicit restore. */
|
|
62
63
|
searchTarget?: TimelineSearchTarget | null | undefined;
|
|
63
64
|
renderImage?: ((image: { src: string; alt: string }) => ReactNode) | undefined;
|
|
65
|
+
/** Show alt text without loading image URLs (e.g. in search previews). */
|
|
66
|
+
suppressImages?: boolean | undefined;
|
|
64
67
|
/** Host opt-in for assistant-authored interactive fences. */
|
|
65
68
|
renderInteractiveBlock?: ((block: MarkdownInteractiveBlock) => ReactNode) | undefined;
|
|
66
69
|
children: string;
|
|
@@ -399,7 +402,9 @@ export function retainedImageId(src: string): string | null {
|
|
|
399
402
|
);
|
|
400
403
|
}
|
|
401
404
|
function MarkdownImage({ src, alt }: ComponentPropsWithoutRef<"img">) {
|
|
402
|
-
const { renderImage } = useContext(InteractiveContext);
|
|
405
|
+
const { renderImage, suppressImages } = useContext(InteractiveContext);
|
|
406
|
+
if (suppressImages)
|
|
407
|
+
return <span data-og-image-placeholder="">{alt || "Image"} (preview unavailable)</span>;
|
|
403
408
|
if (!src) return <span>{alt ?? "Image unavailable"}</span>;
|
|
404
409
|
if (retainedImageId(src))
|
|
405
410
|
return renderImage ? (
|
|
@@ -592,6 +597,7 @@ function MarkdownImpl({
|
|
|
592
597
|
onSandboxFile,
|
|
593
598
|
renderInteractiveBlock,
|
|
594
599
|
renderImage,
|
|
600
|
+
suppressImages,
|
|
595
601
|
}: MarkdownProps) {
|
|
596
602
|
// Tip-ink engine for THIS body: created on the first streaming render, kept
|
|
597
603
|
// through a short linger after the stream ends (so the last age window can
|
|
@@ -692,8 +698,9 @@ function MarkdownImpl({
|
|
|
692
698
|
streaming,
|
|
693
699
|
...(renderInteractiveBlock ? { render: renderInteractiveBlock } : {}),
|
|
694
700
|
...(renderImage ? { renderImage } : {}),
|
|
701
|
+
...(suppressImages ? { suppressImages } : {}),
|
|
695
702
|
}),
|
|
696
|
-
[children, streaming, renderInteractiveBlock, renderImage],
|
|
703
|
+
[children, streaming, renderInteractiveBlock, renderImage, suppressImages],
|
|
697
704
|
);
|
|
698
705
|
|
|
699
706
|
// `min-w-0` lets the prose shrink inside flex parents (message bubbles) so
|
|
@@ -843,6 +843,7 @@ export function useSandboxWorkspaceTabs(
|
|
|
843
843
|
// start BrowserViewer—the lazy panel is still unmounted until the
|
|
844
844
|
// user/agent first visits it.
|
|
845
845
|
enabled={workspaceVisible}
|
|
846
|
+
active={resolvedActiveTab === WORKBENCH_TAB_BROWSER}
|
|
846
847
|
onNotify={onNotify}
|
|
847
848
|
initialBrowserSessionId={initialBrowserSessionId}
|
|
848
849
|
onBrowserSessionIdChange={onBrowserSessionIdChange}
|
|
@@ -12,7 +12,11 @@ export type SessionCapabilityFrameProps = {
|
|
|
12
12
|
skill: boolean;
|
|
13
13
|
expanded: boolean;
|
|
14
14
|
complete: boolean;
|
|
15
|
+
/** Completion copy must reflect the provider's actual scope of access. */
|
|
16
|
+
completeLabel?: string;
|
|
15
17
|
actionLabel: string;
|
|
18
|
+
/** Direct navigation actions should not announce a dialog on the card. */
|
|
19
|
+
opensDialog?: boolean;
|
|
16
20
|
note: string;
|
|
17
21
|
onOpen(): void;
|
|
18
22
|
onClose(): void;
|
|
@@ -48,7 +52,9 @@ export function SessionCapabilityFrame({
|
|
|
48
52
|
skill,
|
|
49
53
|
expanded,
|
|
50
54
|
complete,
|
|
55
|
+
completeLabel,
|
|
51
56
|
actionLabel,
|
|
57
|
+
opensDialog = true,
|
|
52
58
|
note,
|
|
53
59
|
onOpen,
|
|
54
60
|
onClose,
|
|
@@ -90,7 +96,8 @@ export function SessionCapabilityFrame({
|
|
|
90
96
|
</div>
|
|
91
97
|
{complete ? (
|
|
92
98
|
<p role="status" className="og-session-capability-copy">
|
|
93
|
-
{
|
|
99
|
+
{completeLabel ??
|
|
100
|
+
(skill ? "Installed · Workspace" : "Connected · Available in this conversation")}
|
|
94
101
|
</p>
|
|
95
102
|
) : (
|
|
96
103
|
<>
|
|
@@ -104,8 +111,9 @@ export function SessionCapabilityFrame({
|
|
|
104
111
|
ref={buttonRef}
|
|
105
112
|
type="button"
|
|
106
113
|
onClick={onOpen}
|
|
107
|
-
|
|
108
|
-
aria-
|
|
114
|
+
disabled={busy}
|
|
115
|
+
aria-haspopup={opensDialog ? "dialog" : undefined}
|
|
116
|
+
aria-expanded={opensDialog ? expanded : undefined}
|
|
109
117
|
>
|
|
110
118
|
{actionLabel}
|
|
111
119
|
</button>
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
authorizeConnectAttempt,
|
|
3
|
+
ConnectPopupClosedError,
|
|
3
4
|
ConnectController,
|
|
4
5
|
createBrowserConnectNavigation,
|
|
5
6
|
reserveBrowserConnectNavigation,
|
|
@@ -77,25 +78,32 @@ function ScopedCard({
|
|
|
77
78
|
const [complete, setComplete] = useState(false);
|
|
78
79
|
const [connected, setConnected] = useState(false);
|
|
79
80
|
const [waiting, setWaiting] = useState(false);
|
|
81
|
+
const [reconciling, setReconciling] = useState(false);
|
|
80
82
|
const [busy, setBusy] = useState(false);
|
|
81
83
|
const [error, setError] = useState<string | null>(null);
|
|
84
|
+
const [notice, setNotice] = useState<string | null>(null);
|
|
85
|
+
const [retryFresh, setRetryFresh] = useState(false);
|
|
82
86
|
const [ownership, setOwnership] = useState<ConnectOwnership>("workspace");
|
|
83
87
|
const lifetime = useRef<AbortController | null>(null);
|
|
84
|
-
const
|
|
88
|
+
const authorization = useRef<AbortController | null>(null);
|
|
89
|
+
const operation = useRef<AbortController | null>(null);
|
|
90
|
+
const reservedPopup = useRef<(() => void) | null>(null);
|
|
85
91
|
const startKey = useRef(crypto.randomUUID());
|
|
86
92
|
const advanceKey = useRef(crypto.randomUUID());
|
|
93
|
+
const startInput = useRef<string | null>(null);
|
|
87
94
|
const storageKey = sessionId
|
|
88
95
|
? `opengeni:session-connect:${workspaceId}:${sessionId}:${capabilityId}`
|
|
89
96
|
: `opengeni:workspace-connect:${workspaceId}:${capabilityId}`;
|
|
90
97
|
const current = () => !!lifetime.current && !lifetime.current.signal.aborted;
|
|
91
98
|
|
|
92
|
-
async function load() {
|
|
99
|
+
async function load(active: () => boolean = current) {
|
|
93
100
|
const invocation = lifetime.current;
|
|
94
101
|
const [catalog, session] = await Promise.all([
|
|
95
102
|
client.listCapabilities(workspaceId),
|
|
96
103
|
sessionId ? client.getSession(workspaceId, sessionId) : Promise.resolve(null),
|
|
97
104
|
]);
|
|
98
|
-
if (!invocation || invocation.signal.aborted || lifetime.current !== invocation
|
|
105
|
+
if (!invocation || invocation.signal.aborted || lifetime.current !== invocation || !active())
|
|
106
|
+
return null;
|
|
99
107
|
const resolved = catalog.items.find((entry) => entry.id === capabilityId);
|
|
100
108
|
if (!resolved || resolved.kind !== "mcp" || resolved.authKind !== "oauth2")
|
|
101
109
|
throw new Error(
|
|
@@ -112,11 +120,11 @@ function ScopedCard({
|
|
|
112
120
|
resolved.connectionRef.subjectScope === "subject"
|
|
113
121
|
? await client.listOwnConnectionAccounts(workspaceId)
|
|
114
122
|
: await client.listConnections(workspaceId);
|
|
115
|
-
if (invocation.signal.aborted || lifetime.current !== invocation) return null;
|
|
123
|
+
if (invocation.signal.aborted || lifetime.current !== invocation || !active()) return null;
|
|
116
124
|
const matches = matchingActiveMcpConnections(resolved, connections);
|
|
117
125
|
accountReady = matches.length === 1;
|
|
118
126
|
}
|
|
119
|
-
if (invocation.signal.aborted || lifetime.current !== invocation) return null;
|
|
127
|
+
if (invocation.signal.aborted || lifetime.current !== invocation || !active()) return null;
|
|
120
128
|
const selected =
|
|
121
129
|
session?.toolPolicy.mode === "workspace_default"
|
|
122
130
|
? session.effectiveToolPolicy?.selectedIds
|
|
@@ -185,23 +193,32 @@ function ScopedCard({
|
|
|
185
193
|
};
|
|
186
194
|
}, [client, item?.logoAssetPath]);
|
|
187
195
|
|
|
188
|
-
async function run(action: () => Promise<void>) {
|
|
196
|
+
async function run(action: (active: () => boolean) => Promise<void>) {
|
|
189
197
|
if (operation.current || !current()) return;
|
|
190
|
-
|
|
198
|
+
const pending = new AbortController();
|
|
199
|
+
operation.current = pending;
|
|
200
|
+
const active = () => current() && operation.current === pending && !pending.signal.aborted;
|
|
191
201
|
setBusy(true);
|
|
192
202
|
setError(null);
|
|
203
|
+
setNotice(null);
|
|
204
|
+
setRetryFresh(false);
|
|
193
205
|
try {
|
|
194
|
-
await action();
|
|
206
|
+
await action(active);
|
|
195
207
|
} catch (failure) {
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
208
|
+
if (active()) {
|
|
209
|
+
if (failure instanceof ConnectPopupClosedError) setNotice(failure.message);
|
|
210
|
+
else
|
|
211
|
+
setError(
|
|
212
|
+
failure instanceof Error
|
|
213
|
+
? failure.message
|
|
214
|
+
: "Connection setup could not finish. Try again.",
|
|
215
|
+
);
|
|
216
|
+
}
|
|
202
217
|
} finally {
|
|
203
|
-
operation.current
|
|
204
|
-
|
|
218
|
+
if (operation.current === pending) {
|
|
219
|
+
operation.current = null;
|
|
220
|
+
if (current()) setBusy(false);
|
|
221
|
+
}
|
|
205
222
|
}
|
|
206
223
|
}
|
|
207
224
|
|
|
@@ -215,15 +232,15 @@ function ScopedCard({
|
|
|
215
232
|
}
|
|
216
233
|
}
|
|
217
234
|
|
|
218
|
-
async function reconcile(attempt: ConnectAttempt) {
|
|
219
|
-
if (!
|
|
235
|
+
async function reconcile(attempt: ConnectAttempt, active: () => boolean = current) {
|
|
236
|
+
if (!active()) return;
|
|
220
237
|
if (attempt.state !== "complete" || !attempt.account || !attempt.credentialsCommitted) return;
|
|
221
|
-
const resolved = await load();
|
|
222
|
-
if (!resolved || !
|
|
238
|
+
const resolved = await load(active);
|
|
239
|
+
if (!resolved || !active()) return;
|
|
223
240
|
const connection = (await client.listConnections(workspaceId)).find(
|
|
224
241
|
(entry) => entry.id === attempt.account!.id,
|
|
225
242
|
);
|
|
226
|
-
if (!
|
|
243
|
+
if (!active()) return;
|
|
227
244
|
if (
|
|
228
245
|
!connection ||
|
|
229
246
|
connection.status !== "active" ||
|
|
@@ -255,11 +272,12 @@ function ScopedCard({
|
|
|
255
272
|
connectionId: connection.id,
|
|
256
273
|
},
|
|
257
274
|
});
|
|
258
|
-
if (!
|
|
259
|
-
const enabled = await load();
|
|
260
|
-
if (enabled &&
|
|
261
|
-
await finishConnection(enabled);
|
|
275
|
+
if (!active()) return;
|
|
276
|
+
const enabled = await load(active);
|
|
277
|
+
if (enabled && active()) {
|
|
278
|
+
await finishConnection(enabled, active);
|
|
262
279
|
}
|
|
280
|
+
if (!active()) return;
|
|
263
281
|
try {
|
|
264
282
|
sessionStorage.removeItem(storageKey);
|
|
265
283
|
} catch {
|
|
@@ -269,14 +287,14 @@ function ScopedCard({
|
|
|
269
287
|
|
|
270
288
|
async function open() {
|
|
271
289
|
setExpanded(true);
|
|
272
|
-
await run(async () => {
|
|
273
|
-
await load();
|
|
274
|
-
if (!
|
|
275
|
-
await recoverSavedAttempt();
|
|
290
|
+
await run(async (active) => {
|
|
291
|
+
await load(active);
|
|
292
|
+
if (!active()) return;
|
|
293
|
+
await recoverSavedAttempt(active);
|
|
276
294
|
});
|
|
277
295
|
}
|
|
278
296
|
|
|
279
|
-
async function recoverSavedAttempt() {
|
|
297
|
+
async function recoverSavedAttempt(active: () => boolean = current) {
|
|
280
298
|
let saved: string | null = null;
|
|
281
299
|
try {
|
|
282
300
|
saved = sessionStorage.getItem(storageKey);
|
|
@@ -285,20 +303,31 @@ function ScopedCard({
|
|
|
285
303
|
}
|
|
286
304
|
if (saved) {
|
|
287
305
|
const attempt = await controller.recover(saved);
|
|
288
|
-
|
|
306
|
+
if (!active()) return;
|
|
307
|
+
if (["failed", "expired", "cancelled"].includes(attempt.state)) setRetryFresh(true);
|
|
308
|
+
if (attempt.state === "complete") setReconciling(true);
|
|
309
|
+
try {
|
|
310
|
+
await reconcile(attempt, active);
|
|
311
|
+
} finally {
|
|
312
|
+
if (active()) setReconciling(false);
|
|
313
|
+
}
|
|
289
314
|
}
|
|
290
315
|
}
|
|
291
316
|
|
|
292
|
-
async function begin() {
|
|
293
|
-
await run(async () => {
|
|
294
|
-
if (
|
|
317
|
+
async function begin(fresh = false) {
|
|
318
|
+
await run(async (active) => {
|
|
319
|
+
if (
|
|
320
|
+
fresh ||
|
|
321
|
+
(view.attempt && ["failed", "expired", "cancelled"].includes(view.attempt.state))
|
|
322
|
+
) {
|
|
295
323
|
startKey.current = crypto.randomUUID();
|
|
296
324
|
advanceKey.current = crypto.randomUUID();
|
|
297
325
|
}
|
|
298
326
|
const reserved = reserveBrowserConnectNavigation(window);
|
|
327
|
+
reservedPopup.current = reserved.close;
|
|
299
328
|
try {
|
|
300
|
-
const resolved = await load();
|
|
301
|
-
if (!resolved || !
|
|
329
|
+
const resolved = await load(active);
|
|
330
|
+
if (!resolved || !active()) return;
|
|
302
331
|
if (
|
|
303
332
|
resolved.connectionRef &&
|
|
304
333
|
ownership !==
|
|
@@ -322,13 +351,22 @@ function ScopedCard({
|
|
|
322
351
|
entry.kind === "oauth2" &&
|
|
323
352
|
entry.metadata.mcpUrl === mcpUrl,
|
|
324
353
|
);
|
|
325
|
-
if (!
|
|
354
|
+
if (!active()) return;
|
|
326
355
|
if (connections.length > 1)
|
|
327
356
|
throw new Error(
|
|
328
357
|
"More than one personal account matches. Choose the account in connection settings before reconnecting.",
|
|
329
358
|
);
|
|
330
359
|
reconnectAccountId = connections[0]?.id;
|
|
331
360
|
}
|
|
361
|
+
// A closed request can still have reached the server. Keep its key for
|
|
362
|
+
// an exact replay, but never reuse it when ownership or another input
|
|
363
|
+
// changes while the user reopens setup.
|
|
364
|
+
const input = JSON.stringify({ ownership, returnUrl, reconnectAccountId, mcpUrl });
|
|
365
|
+
if (startInput.current !== null && startInput.current !== input) {
|
|
366
|
+
startKey.current = crypto.randomUUID();
|
|
367
|
+
advanceKey.current = crypto.randomUUID();
|
|
368
|
+
}
|
|
369
|
+
startInput.current = input;
|
|
332
370
|
let attempt = await controller.begin({
|
|
333
371
|
providerId: "mcp-oauth",
|
|
334
372
|
ownership,
|
|
@@ -336,49 +374,79 @@ function ScopedCard({
|
|
|
336
374
|
idempotencyKey: startKey.current,
|
|
337
375
|
...(reconnectAccountId ? { reconnectAccountId } : {}),
|
|
338
376
|
});
|
|
339
|
-
if (!
|
|
377
|
+
if (!active()) return;
|
|
340
378
|
remember(attempt.id);
|
|
341
379
|
if (attempt.state === "credential_input")
|
|
342
380
|
attempt = await controller.advance(
|
|
343
381
|
{ type: "credentials", values: { mcpUrl } },
|
|
344
382
|
advanceKey.current,
|
|
345
383
|
);
|
|
384
|
+
if (!active()) return;
|
|
346
385
|
if (attempt.nextAction.type === "authorize")
|
|
347
|
-
await performAuthorization(attempt, reserved.navigation);
|
|
348
|
-
else await reconcile(attempt);
|
|
386
|
+
await performAuthorization(attempt, reserved.navigation, active);
|
|
387
|
+
else await reconcile(attempt, active);
|
|
349
388
|
} finally {
|
|
389
|
+
if (reservedPopup.current === reserved.close) reservedPopup.current = null;
|
|
350
390
|
reserved.close();
|
|
351
391
|
}
|
|
352
392
|
});
|
|
353
393
|
}
|
|
354
394
|
|
|
355
|
-
async function performAuthorization(
|
|
395
|
+
async function performAuthorization(
|
|
396
|
+
attempt: ConnectAttempt,
|
|
397
|
+
navigation: ConnectNavigation,
|
|
398
|
+
active: () => boolean,
|
|
399
|
+
) {
|
|
400
|
+
if (!active()) return;
|
|
401
|
+
const pending = new AbortController();
|
|
402
|
+
authorization.current = pending;
|
|
403
|
+
const abortOnUnmount = () => pending.abort(lifetime.current?.signal.reason);
|
|
404
|
+
lifetime.current!.signal.addEventListener("abort", abortOnUnmount, { once: true });
|
|
356
405
|
setWaiting(true);
|
|
357
406
|
try {
|
|
358
407
|
const result = await authorizeConnectAttempt(controller.transport, attempt, navigation, {
|
|
359
408
|
mode: "popup",
|
|
360
|
-
signal:
|
|
409
|
+
signal: pending.signal,
|
|
361
410
|
});
|
|
362
|
-
if (!
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
411
|
+
if (!active() || !result) return;
|
|
412
|
+
setWaiting(false);
|
|
413
|
+
setReconciling(true);
|
|
414
|
+
await reconcile(result, active);
|
|
415
|
+
if (!active()) return;
|
|
416
|
+
if (result.state === "cancelled") {
|
|
417
|
+
setNotice("Sign-in was cancelled. You can try connecting again.");
|
|
418
|
+
setRetryFresh(true);
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
if (result.state !== "complete") {
|
|
422
|
+
if (["failed", "expired"].includes(result.state)) setRetryFresh(true);
|
|
366
423
|
throw new Error("Sign-in did not finish. You can try connecting again.");
|
|
424
|
+
}
|
|
367
425
|
} finally {
|
|
368
|
-
|
|
426
|
+
lifetime.current?.signal.removeEventListener("abort", abortOnUnmount);
|
|
427
|
+
if (authorization.current === pending) authorization.current = null;
|
|
428
|
+
if (active()) {
|
|
429
|
+
setReconciling(false);
|
|
430
|
+
setWaiting(false);
|
|
431
|
+
}
|
|
369
432
|
}
|
|
370
433
|
}
|
|
371
434
|
|
|
372
435
|
function authorize(attempt: ConnectAttempt) {
|
|
373
436
|
// run invokes action synchronously, retaining the browser click gesture.
|
|
374
|
-
return run(() =>
|
|
437
|
+
return run((active) =>
|
|
438
|
+
performAuthorization(attempt, createBrowserConnectNavigation(window), active),
|
|
439
|
+
);
|
|
375
440
|
}
|
|
376
441
|
|
|
377
|
-
async function finishConnection(
|
|
442
|
+
async function finishConnection(
|
|
443
|
+
capability: CapabilityCatalogItem,
|
|
444
|
+
active: () => boolean = current,
|
|
445
|
+
) {
|
|
378
446
|
if (!sessionId) {
|
|
379
447
|
// Connection management does not select session tools.
|
|
380
448
|
await onConfigured?.();
|
|
381
|
-
if (!
|
|
449
|
+
if (!active()) return;
|
|
382
450
|
setComplete(true);
|
|
383
451
|
setExpanded(false);
|
|
384
452
|
onClose?.();
|
|
@@ -388,31 +456,47 @@ function ScopedCard({
|
|
|
388
456
|
const selected = (await client.listConnections(workspaceId)).find(
|
|
389
457
|
(entry) => entry.id === capability.connectionRef?.connectionId,
|
|
390
458
|
);
|
|
391
|
-
if (!
|
|
459
|
+
if (!active()) return;
|
|
392
460
|
if (!selected || selected.status !== "active")
|
|
393
461
|
throw new Error("This account needs reconnection before it can be used here.");
|
|
394
462
|
}
|
|
395
463
|
if (capability.connectionRef?.subjectScope === "subject") {
|
|
396
464
|
const accounts = await client.listOwnConnectionAccounts(workspaceId);
|
|
397
|
-
if (!
|
|
465
|
+
if (!active()) return;
|
|
398
466
|
if (matchingActiveMcpConnections(capability, accounts).length === 0)
|
|
399
467
|
throw new Error("Your account needs reconnection before it can be used here.");
|
|
400
468
|
}
|
|
401
|
-
if (!
|
|
402
|
-
await attachSessionCapability(client, workspaceId, sessionId, capability,
|
|
403
|
-
if (!
|
|
469
|
+
if (!active()) return;
|
|
470
|
+
await attachSessionCapability(client, workspaceId, sessionId, capability, active);
|
|
471
|
+
if (!active()) return;
|
|
404
472
|
await onConfigured?.();
|
|
405
|
-
if (!
|
|
473
|
+
if (!active()) return;
|
|
406
474
|
setComplete(true);
|
|
407
475
|
setExpanded(false);
|
|
408
476
|
}
|
|
409
477
|
|
|
410
478
|
async function useHere() {
|
|
411
|
-
await run(async () => {
|
|
412
|
-
if (item) await finishConnection(item);
|
|
479
|
+
await run(async (active) => {
|
|
480
|
+
if (item) await finishConnection(item, active);
|
|
413
481
|
});
|
|
414
482
|
}
|
|
415
483
|
|
|
484
|
+
function close() {
|
|
485
|
+
const pending = operation.current;
|
|
486
|
+
if (pending) {
|
|
487
|
+
operation.current = null;
|
|
488
|
+
pending.abort(new ConnectPopupClosedError());
|
|
489
|
+
authorization.current?.abort(new ConnectPopupClosedError());
|
|
490
|
+
authorization.current = null;
|
|
491
|
+
reservedPopup.current?.();
|
|
492
|
+
setBusy(false);
|
|
493
|
+
setWaiting(false);
|
|
494
|
+
setReconciling(false);
|
|
495
|
+
}
|
|
496
|
+
setExpanded(false);
|
|
497
|
+
onClose?.();
|
|
498
|
+
}
|
|
499
|
+
|
|
416
500
|
return (
|
|
417
501
|
<SessionCapabilityFrame
|
|
418
502
|
name={item?.name ?? name}
|
|
@@ -436,15 +520,16 @@ function ScopedCard({
|
|
|
436
520
|
: "Review access before signing in. You'll return here after authorization."
|
|
437
521
|
}
|
|
438
522
|
onOpen={() => void open()}
|
|
439
|
-
onClose={
|
|
440
|
-
setExpanded(false);
|
|
441
|
-
onClose?.();
|
|
442
|
-
}}
|
|
443
|
-
busy={busy || view.busy}
|
|
523
|
+
onClose={close}
|
|
444
524
|
dialogOnly={dialogOnly}
|
|
445
525
|
>
|
|
446
526
|
<div className="og-session-capability-setup">
|
|
447
527
|
{error ? <p role="alert">{error}</p> : null}
|
|
528
|
+
{notice ? (
|
|
529
|
+
<p role="status" className="og-session-capability-notice">
|
|
530
|
+
{notice}
|
|
531
|
+
</p>
|
|
532
|
+
) : null}
|
|
448
533
|
{!item ? (
|
|
449
534
|
<>
|
|
450
535
|
<p role="status">
|
|
@@ -458,11 +543,24 @@ function ScopedCard({
|
|
|
458
543
|
<>
|
|
459
544
|
<p>{item.description || rationale}</p>
|
|
460
545
|
{busy ? (
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
546
|
+
<>
|
|
547
|
+
<p role="status" className="og-session-capability-progress">
|
|
548
|
+
{waiting
|
|
549
|
+
? `Finish signing in with ${item.name} in the opened window. This will close automatically when you’re connected.`
|
|
550
|
+
: reconciling
|
|
551
|
+
? "Finishing your connection…"
|
|
552
|
+
: "Preparing your connection…"}
|
|
553
|
+
</p>
|
|
554
|
+
{waiting ? (
|
|
555
|
+
<button
|
|
556
|
+
type="button"
|
|
557
|
+
className="og-session-capability-stop"
|
|
558
|
+
onClick={() => authorization.current?.abort(new ConnectPopupClosedError())}
|
|
559
|
+
>
|
|
560
|
+
Stop waiting
|
|
561
|
+
</button>
|
|
562
|
+
) : null}
|
|
563
|
+
</>
|
|
466
564
|
) : !connected ? (
|
|
467
565
|
<>
|
|
468
566
|
{!item.connectionRef ? (
|
|
@@ -498,11 +596,17 @@ function ScopedCard({
|
|
|
498
596
|
<button
|
|
499
597
|
className="og-session-capability-primary"
|
|
500
598
|
onClick={() => {
|
|
501
|
-
if (
|
|
599
|
+
if (notice || retryFresh) void begin(true);
|
|
600
|
+
else if (
|
|
601
|
+
view.attempt?.nextAction.type === "authorize" &&
|
|
602
|
+
view.attempt.ownership === ownership
|
|
603
|
+
)
|
|
604
|
+
void authorize(view.attempt);
|
|
502
605
|
else void begin();
|
|
503
606
|
}}
|
|
504
607
|
>
|
|
505
|
-
{
|
|
608
|
+
{retryFresh ||
|
|
609
|
+
((error || notice) && view.attempt?.nextAction.type === "authorize")
|
|
506
610
|
? "Try signing in again"
|
|
507
611
|
: `Continue to ${item.name}`}
|
|
508
612
|
</button>
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
STREAM_KIND_BROWSER,
|
|
20
20
|
STREAM_ROLE_CLIENT,
|
|
21
21
|
} from "../lib/relay-wire";
|
|
22
|
+
import { usePageLiveActivity } from "./internal";
|
|
22
23
|
|
|
23
24
|
export type BrowserFrameConnectionState =
|
|
24
25
|
| "idle"
|
|
@@ -73,7 +74,8 @@ export function useBrowserFrameStream(
|
|
|
73
74
|
options: UseBrowserFrameStreamOptions,
|
|
74
75
|
): UseBrowserFrameStreamResult {
|
|
75
76
|
const { client, workspaceId } = useEmbeddedBrowserInteraction(options);
|
|
76
|
-
const
|
|
77
|
+
const pageLive = usePageLiveActivity();
|
|
78
|
+
const enabled = (options.enabled ?? true) && pageLive;
|
|
77
79
|
const [nonce, setNonce] = useState(0);
|
|
78
80
|
const [result, setResult] = useState<Omit<UseBrowserFrameStreamResult, "reconnect">>({
|
|
79
81
|
state: "idle",
|