@opengeni/react 7.6.1 → 7.7.0-canary.37111190284001
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/README.md +47 -2
- package/dist/{browser-viewer-5X47I6T2.js → browser-viewer-63TXMINB.js} +6 -5
- package/dist/{chunk-5EXGU7UF.js → chunk-55DDDVKU.js} +3 -3
- package/dist/{chunk-UINDK6C3.js → chunk-7CYYXWHU.js} +5 -8
- package/dist/chunk-7CYYXWHU.js.map +1 -0
- package/dist/{chunk-LZ3KHA55.js → chunk-A2L5N3EI.js} +332 -339
- package/dist/chunk-A2L5N3EI.js.map +1 -0
- package/dist/{chunk-KGENSZKS.js → chunk-AAG4R3QN.js} +4 -3
- package/dist/chunk-AAG4R3QN.js.map +1 -0
- package/dist/{chunk-Z3DISEQN.js → chunk-CWWKTNKK.js} +35 -18
- package/dist/chunk-CWWKTNKK.js.map +1 -0
- package/dist/{chunk-WZ55DVGK.js → chunk-ELCE5SXV.js} +8 -4
- package/dist/chunk-ELCE5SXV.js.map +1 -0
- package/dist/{chunk-YSMMDOOE.js → chunk-GISEVGS6.js} +10 -2
- package/dist/chunk-GISEVGS6.js.map +1 -0
- package/dist/{chunk-BI57CQUV.js → chunk-I7IWRQLN.js} +2 -2
- package/dist/{chunk-XA6R7DJQ.js → chunk-IQUCRAT7.js} +3 -3
- package/dist/{chunk-CHPKSLPN.js → chunk-NBZ64YY7.js} +4 -2
- package/dist/chunk-NBZ64YY7.js.map +1 -0
- package/dist/{chunk-DV2DBNPQ.js → chunk-Q5QBEZIQ.js} +48 -29
- package/dist/chunk-Q5QBEZIQ.js.map +1 -0
- package/dist/{chunk-FGBCYQOT.js → chunk-RDKBXMWX.js} +120 -74
- package/dist/chunk-RDKBXMWX.js.map +1 -0
- package/dist/{chunk-45GE35O7.js → chunk-TKB4SB44.js} +10 -2
- package/dist/chunk-TKB4SB44.js.map +1 -0
- package/dist/{chunk-YCGBREUV.js → chunk-TOAH2BKA.js} +6 -2
- package/dist/{chunk-YCGBREUV.js.map → chunk-TOAH2BKA.js.map} +1 -1
- package/dist/chunk-UGLEYEMP.js +33 -0
- package/dist/chunk-UGLEYEMP.js.map +1 -0
- package/dist/{chunk-UJRYEWZI.js → chunk-UTRTG6LH.js} +48 -8
- package/dist/chunk-UTRTG6LH.js.map +1 -0
- package/dist/{chunk-YRYF2GLG.js → chunk-WYVLAAJA.js} +39 -31
- package/dist/chunk-WYVLAAJA.js.map +1 -0
- package/dist/{chunk-VGIKFIO2.js → chunk-XOAMRDDK.js} +6 -3
- package/dist/chunk-XOAMRDDK.js.map +1 -0
- package/dist/{chunk-6NEZPF53.js → chunk-YN7I3STW.js} +153 -32
- package/dist/chunk-YN7I3STW.js.map +1 -0
- package/dist/{chunk-KZ73RL76.js → chunk-YQEAA6LT.js} +7 -3
- package/dist/chunk-YQEAA6LT.js.map +1 -0
- package/dist/client.d.ts +4 -1
- package/dist/commands/types.d.ts +2 -0
- package/dist/components/approval-surface.d.ts +15 -0
- package/dist/components/session-conversation.d.ts +3 -0
- package/dist/composer.js +8 -7
- package/dist/{computer-viewer-7GF35SNY.js → computer-viewer-X2MBRZY4.js} +5 -4
- package/dist/connect.js +3 -3
- package/dist/hooks/use-browser-session.d.ts +4 -0
- package/dist/hooks/use-composer.d.ts +2 -0
- package/dist/hooks/use-computer-session.d.ts +2 -0
- package/dist/hooks/use-file-attachments.d.ts +2 -0
- package/dist/hooks/use-session-events.d.ts +1 -1
- package/dist/hooks/use-workspace-sessions.d.ts +15 -5
- package/dist/index.d.ts +2 -2
- package/dist/index.js +28 -21
- package/dist/index.js.map +1 -1
- package/dist/interaction.d.ts +1 -1
- package/dist/interaction.js +6 -5
- package/dist/interaction.js.map +1 -1
- package/dist/{latest-question-3BJGKQQ6.js → latest-question-CNEZ5KAN.js} +3 -3
- package/dist/lib/error-message.d.ts +7 -0
- package/dist/lib/format.d.ts +27 -3
- package/dist/lib/interaction-errors.d.ts +3 -0
- package/dist/machines.js +5 -4
- package/dist/{model-policy-picker-menu-PUTA5JOB.js → model-policy-picker-menu-GN2LTB7Z.js} +4 -4
- package/dist/model-policy.js +1 -1
- package/dist/provider.d.ts +5 -1
- package/dist/{queue-surface-implementation-2CAQAIHM.js → queue-surface-implementation-HENWYFWA.js} +3 -2
- package/dist/{queue-surface-implementation-2CAQAIHM.js.map → queue-surface-implementation-HENWYFWA.js.map} +1 -1
- package/dist/realtime.js +2 -2
- package/dist/{sandbox-files-AEJTWGT4.js → sandbox-files-BBC47SLD.js} +4 -3
- package/dist/session-ui.d.ts +1 -1
- package/dist/session-ui.js +13 -12
- package/dist/session-ui.js.map +1 -1
- package/dist/session.js +7 -6
- package/dist/usage.js +2 -1
- package/dist/usage.js.map +1 -1
- package/package.json +3 -3
- package/src/client.ts +5 -1
- package/src/commands/registry.ts +18 -15
- package/src/commands/types.ts +2 -0
- package/src/components/approval-surface.tsx +166 -42
- package/src/components/browser-viewer.tsx +38 -9
- package/src/components/composer.tsx +9 -2
- package/src/components/computer-viewer.tsx +160 -30
- package/src/components/generated-video-player.tsx +4 -2
- package/src/components/human-input-form.tsx +18 -5
- package/src/components/message-timeline.tsx +120 -168
- package/src/components/model-policy-picker.tsx +2 -15
- package/src/components/open-geni-chat.tsx +11 -4
- package/src/components/queue-surface-state.tsx +3 -1
- package/src/components/session-chrome.tsx +5 -2
- package/src/components/session-commands-panel.tsx +3 -1
- package/src/components/session-conversation.tsx +10 -3
- package/src/components/session-list.tsx +7 -5
- package/src/components/session-mcp-capability-card.tsx +11 -1
- package/src/hooks/internal.ts +3 -2
- package/src/hooks/use-browser-session.ts +115 -79
- package/src/hooks/use-composer.ts +70 -31
- package/src/hooks/use-computer-session.ts +57 -6
- package/src/hooks/use-file-attachments.ts +16 -3
- package/src/hooks/use-goal.ts +2 -1
- package/src/hooks/use-session-capabilities.ts +3 -2
- package/src/hooks/use-session-events.ts +15 -8
- package/src/hooks/use-slash-commands.ts +6 -11
- package/src/hooks/use-turn-queue.ts +2 -1
- package/src/hooks/use-workspace-sessions.ts +40 -7
- package/src/index.ts +7 -2
- package/src/interaction.ts +1 -0
- package/src/lib/error-message.ts +37 -0
- package/src/lib/format.ts +73 -9
- package/src/lib/interaction-errors.ts +15 -0
- package/src/model-policy.ts +6 -1
- package/src/provider.tsx +12 -4
- package/src/session-ui.ts +1 -1
- package/src/timeline/projection.ts +12 -1
- package/styles/compiled.css +14 -30
- package/dist/chunk-45GE35O7.js.map +0 -1
- package/dist/chunk-6NEZPF53.js.map +0 -1
- package/dist/chunk-CHPKSLPN.js.map +0 -1
- package/dist/chunk-DV2DBNPQ.js.map +0 -1
- package/dist/chunk-FGBCYQOT.js.map +0 -1
- package/dist/chunk-KGENSZKS.js.map +0 -1
- package/dist/chunk-KZ73RL76.js.map +0 -1
- package/dist/chunk-LZ3KHA55.js.map +0 -1
- package/dist/chunk-UINDK6C3.js.map +0 -1
- package/dist/chunk-UJRYEWZI.js.map +0 -1
- package/dist/chunk-VGIKFIO2.js.map +0 -1
- package/dist/chunk-WZ55DVGK.js.map +0 -1
- package/dist/chunk-YRYF2GLG.js.map +0 -1
- package/dist/chunk-YSMMDOOE.js.map +0 -1
- package/dist/chunk-Z3DISEQN.js.map +0 -1
- package/dist/components/timeline-pill-geometry.d.ts +0 -8
- package/dist/components/timeline-pill-placement.d.ts +0 -24
- package/dist/timeline-pill-geometry-Z23V243M.js +0 -110
- package/dist/timeline-pill-geometry-Z23V243M.js.map +0 -1
- package/src/components/timeline-pill-geometry.ts +0 -169
- package/src/components/timeline-pill-placement.ts +0 -99
- /package/dist/{browser-viewer-5X47I6T2.js.map → browser-viewer-63TXMINB.js.map} +0 -0
- /package/dist/{chunk-5EXGU7UF.js.map → chunk-55DDDVKU.js.map} +0 -0
- /package/dist/{chunk-BI57CQUV.js.map → chunk-I7IWRQLN.js.map} +0 -0
- /package/dist/{chunk-XA6R7DJQ.js.map → chunk-IQUCRAT7.js.map} +0 -0
- /package/dist/{computer-viewer-7GF35SNY.js.map → computer-viewer-X2MBRZY4.js.map} +0 -0
- /package/dist/{latest-question-3BJGKQQ6.js.map → latest-question-CNEZ5KAN.js.map} +0 -0
- /package/dist/{model-policy-picker-menu-PUTA5JOB.js.map → model-policy-picker-menu-GN2LTB7Z.js.map} +0 -0
- /package/dist/{sandbox-files-AEJTWGT4.js.map → sandbox-files-BBC47SLD.js.map} +0 -0
|
@@ -4,6 +4,7 @@ import { useEffect, useRef, useState, type FormEvent } from "react";
|
|
|
4
4
|
import { useWorkspaceSessions } from "../hooks/use-workspace-sessions";
|
|
5
5
|
import { cn } from "../lib/cn";
|
|
6
6
|
import { formatRelativeTime } from "../lib/format";
|
|
7
|
+
import { useErrorMessage } from "../lib/error-message";
|
|
7
8
|
import { useOpenGeni, type ClientOverride } from "../session-context";
|
|
8
9
|
|
|
9
10
|
export type SessionListLabels = {
|
|
@@ -90,7 +91,8 @@ export function SessionList({
|
|
|
90
91
|
}, [refresh, refreshKey]);
|
|
91
92
|
const [editing, setEditing] = useState<{ id: string; title: string } | null>(null);
|
|
92
93
|
const [busy, setBusy] = useState<string | null>(null);
|
|
93
|
-
const [error, setError] = useState<
|
|
94
|
+
const [error, setError] = useState<{ cause: unknown } | null>(null);
|
|
95
|
+
const formatError = useErrorMessage();
|
|
94
96
|
const archiveClient = context.client as unknown as ArchiveClient;
|
|
95
97
|
const canArchive = archive && typeof archiveClient.updateSessionArchive === "function";
|
|
96
98
|
|
|
@@ -101,7 +103,7 @@ export function SessionList({
|
|
|
101
103
|
await action();
|
|
102
104
|
await list.refresh();
|
|
103
105
|
} catch (cause) {
|
|
104
|
-
setError(
|
|
106
|
+
setError({ cause });
|
|
105
107
|
} finally {
|
|
106
108
|
setBusy(null);
|
|
107
109
|
}
|
|
@@ -148,16 +150,16 @@ export function SessionList({
|
|
|
148
150
|
</button>
|
|
149
151
|
) : null}
|
|
150
152
|
</div>
|
|
151
|
-
{error ? (
|
|
153
|
+
{error || list.error ? (
|
|
152
154
|
<p role="alert" className="px-2 text-og-xs text-og-status-failed">
|
|
153
|
-
{error}
|
|
155
|
+
{formatError(error ? error.cause : list.error)}
|
|
154
156
|
</p>
|
|
155
157
|
) : null}
|
|
156
158
|
<ul className="flex min-h-0 flex-1 flex-col gap-0.5 overflow-y-auto px-1 pb-2">
|
|
157
159
|
{list.loading && sessions.length === 0 ? (
|
|
158
160
|
<li className="px-2 py-2 text-og-xs text-og-fg-subtle">{labels.loading}</li>
|
|
159
161
|
) : null}
|
|
160
|
-
{!list.loading && sessions.length === 0 ? (
|
|
162
|
+
{!list.loading && !list.error && sessions.length === 0 ? (
|
|
161
163
|
<li className="px-2 py-2 text-og-xs text-og-fg-subtle">{labels.empty}</li>
|
|
162
164
|
) : null}
|
|
163
165
|
{sessions.map((session) => {
|
|
@@ -127,7 +127,17 @@ function ScopedCard({
|
|
|
127
127
|
]);
|
|
128
128
|
if (!invocation || invocation.signal.aborted || lifetime.current !== invocation || !active())
|
|
129
129
|
return null;
|
|
130
|
-
|
|
130
|
+
let resolved = catalog.items.find((entry) => entry.id === capabilityId);
|
|
131
|
+
// Custom catalog entries may not declare authentication. Discover it from
|
|
132
|
+
// the live catalog's endpoint, never from recommendation/host copy.
|
|
133
|
+
if (resolved?.kind === "mcp" && !resolved.enabled && !resolved.authKind) {
|
|
134
|
+
const endpoint = resolved.mcpUrl ?? resolved.endpointUrl;
|
|
135
|
+
if (endpoint) {
|
|
136
|
+
const inspected = await client.inspectMcpAuthentication(workspaceId, endpoint);
|
|
137
|
+
if (invocation.signal.aborted || lifetime.current !== invocation || !active()) return null;
|
|
138
|
+
if (inspected.kind === "oauth2") resolved = { ...resolved, authKind: "oauth2" };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
131
141
|
if (!resolved || resolved.kind !== "mcp" || resolved.authKind !== "oauth2")
|
|
132
142
|
throw new Error(
|
|
133
143
|
"This recommendation is not an available OAuth MCP integration. Review the current connection catalog.",
|
package/src/hooks/internal.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { SessionEvent } from "@opengeni/sdk";
|
|
2
2
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
3
3
|
import type { EmbeddedSessionEventClientLike } from "../client";
|
|
4
|
+
import { normalizeError } from "../lib/error-message";
|
|
4
5
|
|
|
5
6
|
const HIDDEN_STREAM_GRACE_MS = 2_000;
|
|
6
7
|
|
|
@@ -116,7 +117,7 @@ export function usePolledValue<T>(
|
|
|
116
117
|
activeLoadRef.current === load &&
|
|
117
118
|
!requestAbort.signal.aborted
|
|
118
119
|
) {
|
|
119
|
-
setError(
|
|
120
|
+
setError(normalizeError(cause));
|
|
120
121
|
setLoading(false);
|
|
121
122
|
}
|
|
122
123
|
} finally {
|
|
@@ -258,7 +259,7 @@ export function useMutationRunner(identity: unknown = undefined): MutationState
|
|
|
258
259
|
generation.current === ownedGeneration &&
|
|
259
260
|
Object.is(identityRef.current, ownedIdentity)
|
|
260
261
|
) {
|
|
261
|
-
setMutationError(
|
|
262
|
+
setMutationError(normalizeError(cause));
|
|
262
263
|
}
|
|
263
264
|
return null;
|
|
264
265
|
} finally {
|
|
@@ -30,6 +30,10 @@ export type BrowserFrameInputFence = Pick<
|
|
|
30
30
|
| "frameId"
|
|
31
31
|
>;
|
|
32
32
|
|
|
33
|
+
export type BrowserInputFailure = Pick<BrowserActionReceipt, "operationId" | "error"> & {
|
|
34
|
+
state: Exclude<BrowserActionReceipt["state"], "completed">;
|
|
35
|
+
};
|
|
36
|
+
|
|
33
37
|
export type UseBrowserSessionOptions = EmbeddedBrowserInteractionClientOverride & {
|
|
34
38
|
browserSessionId: string | null;
|
|
35
39
|
enabled?: boolean | undefined;
|
|
@@ -47,6 +51,7 @@ export type UseBrowserSessionResult = {
|
|
|
47
51
|
loading: boolean;
|
|
48
52
|
mutating: boolean;
|
|
49
53
|
error: Error | null;
|
|
54
|
+
inputFailure: BrowserInputFailure | null;
|
|
50
55
|
refresh: () => Promise<void>;
|
|
51
56
|
observeForInput: () => Promise<BrowserObservation>;
|
|
52
57
|
actFromObservation: (
|
|
@@ -96,10 +101,13 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
96
101
|
loading: boolean;
|
|
97
102
|
mutating: boolean;
|
|
98
103
|
error: Error | null;
|
|
104
|
+
inputFailure: BrowserInputFailure | null;
|
|
99
105
|
}>(() => emptyState(browserSessionId, enabled));
|
|
100
106
|
const visible =
|
|
101
107
|
state.browserSessionId === browserSessionId ? state : emptyState(browserSessionId, enabled);
|
|
102
108
|
const refreshBlocked = isNonRetryableInteractionError(visible.error);
|
|
109
|
+
const inputFailureRef = useRef(visible.inputFailure);
|
|
110
|
+
inputFailureRef.current = visible.inputFailure;
|
|
103
111
|
const selectedTargetIdRef = useRef<string | null>(visible.selectedTargetId);
|
|
104
112
|
selectedTargetIdRef.current = visible.selectedTargetId;
|
|
105
113
|
const selectedTargetRef = useRef<BrowserTarget | null>(
|
|
@@ -139,88 +147,104 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
139
147
|
requestRef.current = { id, controller: null };
|
|
140
148
|
}, []);
|
|
141
149
|
|
|
142
|
-
const
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
client.getBrowserSession(workspaceId, browserSessionId, {
|
|
151
|
-
signal: controller.signal,
|
|
152
|
-
}),
|
|
153
|
-
client.listBrowserTargets(workspaceId, browserSessionId, {
|
|
154
|
-
signal: controller.signal,
|
|
155
|
-
}),
|
|
156
|
-
]);
|
|
157
|
-
if (!mountedRef.current || requestRef.current.id !== id) return;
|
|
158
|
-
const selected = chooseTarget(targetResponse.targets, selectedTargetIdRef.current);
|
|
159
|
-
selectedTargetIdRef.current = selected?.id ?? null;
|
|
160
|
-
const previousObservation =
|
|
161
|
-
observationRef.current.browserSessionId === browserSessionId
|
|
162
|
-
? observationRef.current.observation
|
|
163
|
-
: null;
|
|
164
|
-
const retainedObservation =
|
|
165
|
-
selected && previousObservation && sameObservationTarget(previousObservation, selected)
|
|
166
|
-
? previousObservation
|
|
167
|
-
: null;
|
|
168
|
-
observationRef.current = { browserSessionId, observation: retainedObservation };
|
|
169
|
-
setState((current) =>
|
|
170
|
-
current.browserSessionId === browserSessionId
|
|
171
|
-
? {
|
|
172
|
-
...current,
|
|
173
|
-
session,
|
|
174
|
-
targets: targetResponse.targets,
|
|
175
|
-
selectedTargetId: selected?.id ?? null,
|
|
176
|
-
observation: retainedObservation,
|
|
177
|
-
loading: false,
|
|
178
|
-
error: null,
|
|
179
|
-
}
|
|
180
|
-
: current,
|
|
181
|
-
);
|
|
182
|
-
if (!selected || !semanticObservationEnabledRef.current) return;
|
|
150
|
+
const refreshInventory = useCallback(
|
|
151
|
+
async (checkInput = false) => {
|
|
152
|
+
if (!enabled || !browserSessionId) return;
|
|
153
|
+
const checkedInputFailure = inputFailureRef.current;
|
|
154
|
+
const id = requestRef.current.id + 1;
|
|
155
|
+
requestRef.current.controller?.abort();
|
|
156
|
+
const controller = new AbortController();
|
|
157
|
+
requestRef.current = { id, controller };
|
|
183
158
|
try {
|
|
184
|
-
const
|
|
185
|
-
workspaceId,
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
159
|
+
const [session, targetResponse] = await Promise.all([
|
|
160
|
+
client.getBrowserSession(workspaceId, browserSessionId, {
|
|
161
|
+
signal: controller.signal,
|
|
162
|
+
}),
|
|
163
|
+
client.listBrowserTargets(workspaceId, browserSessionId, {
|
|
164
|
+
signal: controller.signal,
|
|
165
|
+
}),
|
|
166
|
+
]);
|
|
190
167
|
if (!mountedRef.current || requestRef.current.id !== id) return;
|
|
191
|
-
|
|
192
|
-
|
|
168
|
+
const selected = chooseTarget(targetResponse.targets, selectedTargetIdRef.current);
|
|
169
|
+
selectedTargetIdRef.current = selected?.id ?? null;
|
|
170
|
+
const previousObservation =
|
|
171
|
+
observationRef.current.browserSessionId === browserSessionId
|
|
172
|
+
? observationRef.current.observation
|
|
173
|
+
: null;
|
|
174
|
+
const retainedObservation =
|
|
175
|
+
selected && previousObservation && sameObservationTarget(previousObservation, selected)
|
|
176
|
+
? previousObservation
|
|
177
|
+
: null;
|
|
178
|
+
observationRef.current = { browserSessionId, observation: retainedObservation };
|
|
193
179
|
setState((current) =>
|
|
194
|
-
current.browserSessionId === browserSessionId
|
|
195
|
-
|
|
196
|
-
|
|
180
|
+
current.browserSessionId === browserSessionId
|
|
181
|
+
? {
|
|
182
|
+
...current,
|
|
183
|
+
session,
|
|
184
|
+
targets: targetResponse.targets,
|
|
185
|
+
selectedTargetId: selected?.id ?? null,
|
|
186
|
+
observation: retainedObservation,
|
|
187
|
+
loading: false,
|
|
188
|
+
error: null,
|
|
189
|
+
// Media and background polling cannot confirm an input outcome.
|
|
190
|
+
// Only an explicit fresh check clears the notice it began with.
|
|
191
|
+
inputFailure:
|
|
192
|
+
checkInput &&
|
|
193
|
+
session.id === browserSessionId &&
|
|
194
|
+
targetResponse.browserSessionId === browserSessionId &&
|
|
195
|
+
current.inputFailure === checkedInputFailure
|
|
196
|
+
? null
|
|
197
|
+
: current.inputFailure,
|
|
198
|
+
}
|
|
197
199
|
: current,
|
|
198
200
|
);
|
|
201
|
+
if (!selected || !semanticObservationEnabledRef.current) return;
|
|
202
|
+
try {
|
|
203
|
+
const observation = await client.observeBrowserTarget(
|
|
204
|
+
workspaceId,
|
|
205
|
+
browserSessionId,
|
|
206
|
+
selected.id,
|
|
207
|
+
{ signal: controller.signal },
|
|
208
|
+
);
|
|
209
|
+
if (!mountedRef.current || requestRef.current.id !== id) return;
|
|
210
|
+
observationRef.current = { browserSessionId, observation };
|
|
211
|
+
lastObservationAtRef.current = Date.now();
|
|
212
|
+
setState((current) =>
|
|
213
|
+
current.browserSessionId === browserSessionId &&
|
|
214
|
+
current.selectedTargetId === observation.target.id
|
|
215
|
+
? { ...current, observation }
|
|
216
|
+
: current,
|
|
217
|
+
);
|
|
218
|
+
} catch (cause) {
|
|
219
|
+
if (controller.signal.aborted || !mountedRef.current || requestRef.current.id !== id)
|
|
220
|
+
return;
|
|
221
|
+
// Target inventory and the media plane remain authoritative for the
|
|
222
|
+
// human-facing browser. A failed semantic snapshot must not blank or
|
|
223
|
+
// disable an otherwise live visual browser.
|
|
224
|
+
void cause;
|
|
225
|
+
}
|
|
199
226
|
} catch (cause) {
|
|
200
227
|
if (controller.signal.aborted || !mountedRef.current || requestRef.current.id !== id)
|
|
201
228
|
return;
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
}
|
|
216
|
-
: current,
|
|
217
|
-
);
|
|
218
|
-
} finally {
|
|
219
|
-
if (requestRef.current.id === id) {
|
|
220
|
-
requestRef.current = { id, controller: null };
|
|
229
|
+
setState((current) =>
|
|
230
|
+
current.browserSessionId === browserSessionId
|
|
231
|
+
? {
|
|
232
|
+
...current,
|
|
233
|
+
loading: false,
|
|
234
|
+
error: cause instanceof Error ? cause : new Error(String(cause)),
|
|
235
|
+
}
|
|
236
|
+
: current,
|
|
237
|
+
);
|
|
238
|
+
} finally {
|
|
239
|
+
if (requestRef.current.id === id) {
|
|
240
|
+
requestRef.current = { id, controller: null };
|
|
241
|
+
}
|
|
221
242
|
}
|
|
222
|
-
}
|
|
223
|
-
|
|
243
|
+
},
|
|
244
|
+
[browserSessionId, client, enabled, workspaceId],
|
|
245
|
+
);
|
|
246
|
+
|
|
247
|
+
const refresh = useCallback(async () => await refreshInventory(true), [refreshInventory]);
|
|
224
248
|
|
|
225
249
|
const refreshSemantic = useCallback(async (): Promise<void> => {
|
|
226
250
|
if (!enabled || !browserSessionId) return;
|
|
@@ -265,12 +289,12 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
265
289
|
? { ...current, loading: true }
|
|
266
290
|
: emptyState(browserSessionId, true),
|
|
267
291
|
);
|
|
268
|
-
void
|
|
292
|
+
void refreshInventory();
|
|
269
293
|
return () => {
|
|
270
294
|
mountedRef.current = false;
|
|
271
295
|
invalidateRefresh();
|
|
272
296
|
};
|
|
273
|
-
}, [browserSessionId, enabled, invalidateRefresh,
|
|
297
|
+
}, [browserSessionId, enabled, invalidateRefresh, refreshInventory]);
|
|
274
298
|
|
|
275
299
|
useEffect(() => {
|
|
276
300
|
const previous = previousSemanticObservationRef.current;
|
|
@@ -306,17 +330,17 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
306
330
|
useEffect(() => {
|
|
307
331
|
if (!enabled || !pageLive || visible.mutating || refreshBlocked) return;
|
|
308
332
|
const timer = setInterval(() => {
|
|
309
|
-
if (!requestRef.current.controller) void
|
|
333
|
+
if (!requestRef.current.controller) void refreshInventory();
|
|
310
334
|
}, pollIntervalMs);
|
|
311
335
|
return () => clearInterval(timer);
|
|
312
|
-
}, [enabled, pageLive, pollIntervalMs,
|
|
336
|
+
}, [enabled, pageLive, pollIntervalMs, refreshInventory, refreshBlocked, visible.mutating]);
|
|
313
337
|
|
|
314
338
|
useEffect(() => {
|
|
315
339
|
if (!enabled || !browserSessionId || !pageLive || refreshBlocked) return;
|
|
316
340
|
let disposed = false;
|
|
317
341
|
const heartbeat = () => {
|
|
318
342
|
void client.heartbeatBrowserSession(workspaceId, browserSessionId).catch(() => {
|
|
319
|
-
if (!disposed) void
|
|
343
|
+
if (!disposed) void refreshInventory();
|
|
320
344
|
});
|
|
321
345
|
};
|
|
322
346
|
const timer = setInterval(heartbeat, 30_000);
|
|
@@ -324,7 +348,7 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
324
348
|
disposed = true;
|
|
325
349
|
clearInterval(timer);
|
|
326
350
|
};
|
|
327
|
-
}, [browserSessionId, client, enabled, pageLive,
|
|
351
|
+
}, [browserSessionId, client, enabled, pageLive, refreshInventory, refreshBlocked, workspaceId]);
|
|
328
352
|
|
|
329
353
|
const runMutation = useCallback(
|
|
330
354
|
async <T>(scopeBrowserSessionId: string, operation: () => Promise<T>): Promise<T> => {
|
|
@@ -532,6 +556,16 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
532
556
|
observationMode,
|
|
533
557
|
action,
|
|
534
558
|
});
|
|
559
|
+
if (receipt.state !== "completed") {
|
|
560
|
+
const inputFailure: BrowserInputFailure = {
|
|
561
|
+
operationId: receipt.operationId,
|
|
562
|
+
state: receipt.state,
|
|
563
|
+
error: receipt.error,
|
|
564
|
+
};
|
|
565
|
+
setState((current) =>
|
|
566
|
+
current.browserSessionId === browserSessionId ? { ...current, inputFailure } : current,
|
|
567
|
+
);
|
|
568
|
+
}
|
|
535
569
|
if (receipt.observation) {
|
|
536
570
|
observationRef.current = {
|
|
537
571
|
browserSessionId,
|
|
@@ -643,6 +677,7 @@ export function useBrowserSession(options: UseBrowserSessionOptions): UseBrowser
|
|
|
643
677
|
loading: visible.loading,
|
|
644
678
|
mutating: visible.mutating,
|
|
645
679
|
error: visible.error,
|
|
680
|
+
inputFailure: visible.inputFailure,
|
|
646
681
|
refresh,
|
|
647
682
|
observeForInput,
|
|
648
683
|
actFromObservation,
|
|
@@ -666,6 +701,7 @@ function emptyState(browserSessionId: string | null, loading: boolean) {
|
|
|
666
701
|
loading,
|
|
667
702
|
mutating: false,
|
|
668
703
|
error: null as Error | null,
|
|
704
|
+
inputFailure: null as BrowserInputFailure | null,
|
|
669
705
|
};
|
|
670
706
|
}
|
|
671
707
|
|
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
type EffectiveControlResumeOption,
|
|
6
6
|
type EffectiveSessionControl,
|
|
7
7
|
type LatencyMode,
|
|
8
|
-
|
|
8
|
+
OpenGeniApiError,
|
|
9
9
|
type ReasoningEffort,
|
|
10
10
|
type ResourceRef,
|
|
11
11
|
type SaveComposerDraftRequest,
|
|
@@ -15,7 +15,15 @@ import {
|
|
|
15
15
|
} from "@opengeni/sdk";
|
|
16
16
|
import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
17
17
|
import { useEmbeddedSession, type EmbeddedSessionClientOverride } from "../session-context";
|
|
18
|
-
import {
|
|
18
|
+
import {
|
|
19
|
+
COMPOSER_MODEL_UNAVAILABLE_MESSAGE,
|
|
20
|
+
ComposerReconciliationRequiredError,
|
|
21
|
+
ComposerStateError,
|
|
22
|
+
ComposerWorkspaceControlUnavailableError,
|
|
23
|
+
composerSubmissionCanRetry,
|
|
24
|
+
composerSubmissionErrorMessage,
|
|
25
|
+
} from "../lib/format";
|
|
26
|
+
import { normalizeError, useErrorMessage } from "../lib/error-message";
|
|
19
27
|
import { useSessionEventTrigger, type SessionEventFeedOptions } from "./internal";
|
|
20
28
|
|
|
21
29
|
export type ComposerPolicy = {
|
|
@@ -98,6 +106,8 @@ export type ComposerOptimisticMessage = {
|
|
|
98
106
|
triggerEventId?: string | null | undefined;
|
|
99
107
|
appliedQueueVersion?: number | null | undefined;
|
|
100
108
|
error?: string | undefined;
|
|
109
|
+
/** Bounded support reference retained independently of display copy across remounts. */
|
|
110
|
+
correlationId?: string | undefined;
|
|
101
111
|
/** False for a definitive refusal that requires changing payment or allowance settings. */
|
|
102
112
|
retryable?: boolean | undefined;
|
|
103
113
|
outcomeUnknown?: boolean | undefined;
|
|
@@ -185,12 +195,19 @@ function restoreOptimisticSendOperations(key: string | null): OptimisticSendOper
|
|
|
185
195
|
operation.state === "sending" || operation.state === "queued"
|
|
186
196
|
? true
|
|
187
197
|
: operation.outcomeUnknown,
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
198
|
+
correlationId: restoredCorrelationId(operation.correlationId),
|
|
199
|
+
error: restoredErrorReference(
|
|
200
|
+
operation.correlationId,
|
|
201
|
+
hasMcpCredentialUpdates && (operation.state === "sending" || operation.outcomeUnknown)
|
|
202
|
+
? composerSubmissionErrorMessage(new ComposerReconciliationRequiredError())
|
|
203
|
+
: operation.state === "sending"
|
|
204
|
+
? "Delivery was interrupted; retry to reconcile this message."
|
|
205
|
+
: operation.outcomeUnknown
|
|
206
|
+
? "The request could not be confirmed. Check its status before retrying."
|
|
207
|
+
: operation.error
|
|
208
|
+
? composerSubmissionErrorMessage(new Error(operation.error))
|
|
209
|
+
: undefined,
|
|
210
|
+
),
|
|
194
211
|
retryable:
|
|
195
212
|
operation.state === "sending" || operation.outcomeUnknown
|
|
196
213
|
? true
|
|
@@ -201,6 +218,20 @@ function restoreOptimisticSendOperations(key: string | null): OptimisticSendOper
|
|
|
201
218
|
}));
|
|
202
219
|
}
|
|
203
220
|
|
|
221
|
+
function restoredCorrelationId(value: unknown): string | undefined {
|
|
222
|
+
return typeof value === "string" && /^[\w.:-]{1,128}$/u.test(value) ? value : undefined;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function restoredErrorReference(
|
|
226
|
+
reference: unknown,
|
|
227
|
+
message: string | undefined,
|
|
228
|
+
): string | undefined {
|
|
229
|
+
const correlationId = restoredCorrelationId(reference);
|
|
230
|
+
return message && correlationId && message !== COMPOSER_MODEL_UNAVAILABLE_MESSAGE
|
|
231
|
+
? `${message} Reference: ${correlationId}.`
|
|
232
|
+
: message;
|
|
233
|
+
}
|
|
234
|
+
|
|
204
235
|
function rememberOptimisticSendOperations(
|
|
205
236
|
key: string | null,
|
|
206
237
|
operations: OptimisticSendOperation[],
|
|
@@ -651,6 +682,7 @@ export function useComposer(
|
|
|
651
682
|
options: UseComposerOptions = {},
|
|
652
683
|
): ComposerControllerState {
|
|
653
684
|
const { client, workspaceId, registerSessionReconciler } = useEmbeddedSession(options);
|
|
685
|
+
const formatError = useErrorMessage();
|
|
654
686
|
const durableDrafts = options.draftPersistence !== "disabled";
|
|
655
687
|
if (!durableDrafts && !options.initialPolicy) {
|
|
656
688
|
throw new Error("useComposer requires initialPolicy when draft persistence is disabled");
|
|
@@ -1071,7 +1103,9 @@ export function useComposer(
|
|
|
1071
1103
|
const problem = asError(cause);
|
|
1072
1104
|
const timedOut = problem.name === "TimeoutError";
|
|
1073
1105
|
setDraftReadError(
|
|
1074
|
-
timedOut
|
|
1106
|
+
timedOut
|
|
1107
|
+
? new ComposerStateError("Draft sync timed out. Retrying…", { cause })
|
|
1108
|
+
: problem,
|
|
1075
1109
|
);
|
|
1076
1110
|
if (
|
|
1077
1111
|
timedOut ||
|
|
@@ -1378,22 +1412,34 @@ export function useComposer(
|
|
|
1378
1412
|
optimisticProcessorBusyRef.current = true;
|
|
1379
1413
|
void (async () => {
|
|
1380
1414
|
let mutationFailureObserved = false;
|
|
1381
|
-
const markFailed = (
|
|
1415
|
+
const markFailed = (cause: unknown, outcomeUnknown: boolean): void => {
|
|
1382
1416
|
if (
|
|
1383
1417
|
targetKeyRef.current !== ownedTargetKey ||
|
|
1384
1418
|
targetGeneration.current !== ownedGeneration
|
|
1385
1419
|
) {
|
|
1386
1420
|
return;
|
|
1387
1421
|
}
|
|
1422
|
+
const problem = asError(cause);
|
|
1388
1423
|
replaceOptimisticSends((current) =>
|
|
1389
1424
|
current.map((candidate) =>
|
|
1390
1425
|
candidate.clientEventId === operation.clientEventId
|
|
1391
1426
|
? {
|
|
1392
1427
|
...candidate,
|
|
1393
1428
|
state: "failed",
|
|
1394
|
-
error:
|
|
1429
|
+
error: formatError(
|
|
1430
|
+
cause,
|
|
1431
|
+
outcomeUnknown &&
|
|
1432
|
+
!isOutcomeUnknownError(problem) &&
|
|
1433
|
+
!(problem instanceof ComposerReconciliationRequiredError)
|
|
1434
|
+
? `The request could not be confirmed. Check its status before retrying.${problem instanceof OpenGeniApiError && problem.correlationId ? ` Reference: ${problem.correlationId}.` : ""}`
|
|
1435
|
+
: composerSubmissionErrorMessage(problem),
|
|
1436
|
+
),
|
|
1395
1437
|
retryable: outcomeUnknown || composerSubmissionCanRetry(problem),
|
|
1396
1438
|
outcomeUnknown,
|
|
1439
|
+
correlationId:
|
|
1440
|
+
problem instanceof OpenGeniApiError
|
|
1441
|
+
? problem.correlationId
|
|
1442
|
+
: candidate.correlationId,
|
|
1397
1443
|
}
|
|
1398
1444
|
: candidate,
|
|
1399
1445
|
),
|
|
@@ -1420,7 +1466,7 @@ export function useComposer(
|
|
|
1420
1466
|
// This read cannot settle the prior mutation in either direction.
|
|
1421
1467
|
// Preserve its exact key and uncertain status until reconciliation
|
|
1422
1468
|
// succeeds or that same mutation is actually replayed.
|
|
1423
|
-
markFailed(
|
|
1469
|
+
markFailed(cause, true);
|
|
1424
1470
|
return;
|
|
1425
1471
|
}
|
|
1426
1472
|
const reconciled = reconcileOptimisticSendFromEvents(operation, events);
|
|
@@ -1444,15 +1490,13 @@ export function useComposer(
|
|
|
1444
1490
|
return;
|
|
1445
1491
|
}
|
|
1446
1492
|
if (!operation.canRetry) {
|
|
1447
|
-
throw new
|
|
1448
|
-
"Opengeni cannot safely retry this uncertain request after remount; reconcile the session before sending again.",
|
|
1449
|
-
);
|
|
1493
|
+
throw new ComposerReconciliationRequiredError();
|
|
1450
1494
|
}
|
|
1451
1495
|
}
|
|
1452
1496
|
let expectedDraftRevision: number | undefined;
|
|
1453
1497
|
if (durableDrafts && operation.draftPayload) {
|
|
1454
1498
|
if (!(await persistPayload(operation.draftPayload))) {
|
|
1455
|
-
throw new
|
|
1499
|
+
throw new ComposerStateError("The message draft could not be saved before delivery.");
|
|
1456
1500
|
}
|
|
1457
1501
|
expectedDraftRevision = draftRef.current?.revision;
|
|
1458
1502
|
}
|
|
@@ -1470,7 +1514,7 @@ export function useComposer(
|
|
|
1470
1514
|
let acceptedEvent: SessionEvent | null = null;
|
|
1471
1515
|
if (durableDrafts) {
|
|
1472
1516
|
if (!operation.draftPayload || expectedDraftRevision === undefined) {
|
|
1473
|
-
throw new
|
|
1517
|
+
throw new ComposerStateError("The durable composer draft is not ready for delivery.");
|
|
1474
1518
|
}
|
|
1475
1519
|
const result = await client
|
|
1476
1520
|
.submitComposerDraft(workspaceId, sessionId, {
|
|
@@ -1568,7 +1612,7 @@ export function useComposer(
|
|
|
1568
1612
|
operation.outcomeUnknown && !mutationFailureObserved
|
|
1569
1613
|
? true
|
|
1570
1614
|
: isOutcomeUnknownError(cause);
|
|
1571
|
-
markFailed(
|
|
1615
|
+
markFailed(cause, outcomeUnknown);
|
|
1572
1616
|
} finally {
|
|
1573
1617
|
optimisticProcessorBusyRef.current = false;
|
|
1574
1618
|
if (targetKeyRef.current === ownedTargetKey) {
|
|
@@ -1580,6 +1624,7 @@ export function useComposer(
|
|
|
1580
1624
|
adoptDraftBase,
|
|
1581
1625
|
client,
|
|
1582
1626
|
durableDrafts,
|
|
1627
|
+
formatError,
|
|
1583
1628
|
markOptimisticAccepted,
|
|
1584
1629
|
loadDraft,
|
|
1585
1630
|
onSent,
|
|
@@ -1748,7 +1793,7 @@ export function useComposer(
|
|
|
1748
1793
|
input.reasoningEffort === undefined ||
|
|
1749
1794
|
input.latencyMode === undefined
|
|
1750
1795
|
) {
|
|
1751
|
-
throw new
|
|
1796
|
+
throw new ComposerStateError("The durable composer draft is not ready for delivery.");
|
|
1752
1797
|
}
|
|
1753
1798
|
const result = await client.submitComposerDraft(workspaceId, sessionId, {
|
|
1754
1799
|
text: input.text,
|
|
@@ -1853,11 +1898,7 @@ export function useComposer(
|
|
|
1853
1898
|
return true;
|
|
1854
1899
|
}
|
|
1855
1900
|
if (!pending.canRetry) {
|
|
1856
|
-
setError(
|
|
1857
|
-
new Error(
|
|
1858
|
-
"Opengeni cannot safely retry this uncertain request after remount; reconcile the session before sending again.",
|
|
1859
|
-
),
|
|
1860
|
-
);
|
|
1901
|
+
setError(new ComposerReconciliationRequiredError());
|
|
1861
1902
|
return false;
|
|
1862
1903
|
}
|
|
1863
1904
|
try {
|
|
@@ -2252,7 +2293,7 @@ export function useComposer(
|
|
|
2252
2293
|
targetKeyRef.current === ownedTargetKey &&
|
|
2253
2294
|
targetGeneration.current === ownedGeneration
|
|
2254
2295
|
) {
|
|
2255
|
-
setError(
|
|
2296
|
+
setError(normalizeError(cause));
|
|
2256
2297
|
}
|
|
2257
2298
|
} finally {
|
|
2258
2299
|
if (
|
|
@@ -2296,7 +2337,7 @@ export function useComposer(
|
|
|
2296
2337
|
targetKeyRef.current === ownedTargetKey &&
|
|
2297
2338
|
targetGeneration.current === ownedGeneration
|
|
2298
2339
|
) {
|
|
2299
|
-
setError(
|
|
2340
|
+
setError(normalizeError(cause));
|
|
2300
2341
|
}
|
|
2301
2342
|
} finally {
|
|
2302
2343
|
if (
|
|
@@ -2323,9 +2364,7 @@ export function useComposer(
|
|
|
2323
2364
|
(blocker) => blocker.kind === "workspace",
|
|
2324
2365
|
);
|
|
2325
2366
|
if (!client.setWorkspaceInferenceState) {
|
|
2326
|
-
throw new
|
|
2327
|
-
"@opengeni/react: workspace-scoped resume requires setWorkspaceInferenceState.",
|
|
2328
|
-
);
|
|
2367
|
+
throw new ComposerWorkspaceControlUnavailableError();
|
|
2329
2368
|
}
|
|
2330
2369
|
const clientEventId = generateClientEventId();
|
|
2331
2370
|
await replayOutcomeUnknown(() =>
|
|
@@ -2455,7 +2494,7 @@ export function useComposer(
|
|
|
2455
2494
|
const addAnnotation = useCallback(
|
|
2456
2495
|
(annotation: DraftTimelineAnnotation) => {
|
|
2457
2496
|
if (annotationsRef.current.length >= 12) {
|
|
2458
|
-
setError(new
|
|
2497
|
+
setError(new ComposerStateError("A message can include at most 12 timeline annotations."));
|
|
2459
2498
|
return;
|
|
2460
2499
|
}
|
|
2461
2500
|
const next = [...annotationsRef.current, annotation];
|
|
@@ -2852,7 +2891,7 @@ async function replayOutcomeUnknown<T>(command: () => Promise<T>): Promise<T> {
|
|
|
2852
2891
|
}
|
|
2853
2892
|
|
|
2854
2893
|
function asError(cause: unknown): Error {
|
|
2855
|
-
return
|
|
2894
|
+
return normalizeError(cause);
|
|
2856
2895
|
}
|
|
2857
2896
|
|
|
2858
2897
|
function promptRoutingFromAcceptedEvent(
|