@opengeni/react 0.52.1 → 0.57.0

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.
Files changed (132) hide show
  1. package/README.md +48 -0
  2. package/dist/artifacts.d.ts +1 -1
  3. package/dist/artifacts.js +43 -14
  4. package/dist/artifacts.js.map +1 -1
  5. package/dist/{chunk-ROIRTM37.js → chunk-5UQPNLIO.js} +6 -6
  6. package/dist/{chunk-JR3QFY6B.js → chunk-6KLX2RH5.js} +49 -2
  7. package/dist/chunk-6KLX2RH5.js.map +1 -0
  8. package/dist/{chunk-FWS3KC3Z.js → chunk-B6WPZSTT.js} +104 -104
  9. package/dist/{chunk-FWS3KC3Z.js.map → chunk-B6WPZSTT.js.map} +1 -1
  10. package/dist/{chunk-HDBG7XGO.js → chunk-C5ZPSSS5.js} +50 -1
  11. package/dist/chunk-C5ZPSSS5.js.map +1 -0
  12. package/dist/{chunk-X4S56Y2H.js → chunk-HA46CYQK.js} +135 -11
  13. package/dist/chunk-HA46CYQK.js.map +1 -0
  14. package/dist/{chunk-724BW4TW.js → chunk-IGC6A2ZI.js} +2 -2
  15. package/dist/chunk-LLOLUHVG.js +5937 -0
  16. package/dist/chunk-LLOLUHVG.js.map +1 -0
  17. package/dist/{chunk-WYA65Y3F.js → chunk-MKPIAGVL.js} +13 -3
  18. package/dist/chunk-MKPIAGVL.js.map +1 -0
  19. package/dist/chunk-R3AZOAB5.js +933 -0
  20. package/dist/chunk-R3AZOAB5.js.map +1 -0
  21. package/dist/{chunk-23EJ676W.js → chunk-XBZWOM2K.js} +9 -4
  22. package/dist/chunk-XBZWOM2K.js.map +1 -0
  23. package/dist/{chunk-JAGDUCZQ.js → chunk-Y7OEDUJ5.js} +17 -13
  24. package/dist/chunk-Y7OEDUJ5.js.map +1 -0
  25. package/dist/client.d.ts +6 -4
  26. package/dist/components/artifacts/index.d.ts +1 -0
  27. package/dist/components/artifacts/published-html-artifact-frame.d.ts +14 -0
  28. package/dist/components/browser-input.d.ts +7 -0
  29. package/dist/components/browser-viewer.d.ts +5 -4
  30. package/dist/components/computer-viewer.d.ts +2 -1
  31. package/dist/components/desktop-viewer.d.ts +26 -2
  32. package/dist/components/interaction-intervention-banner.d.ts +11 -0
  33. package/dist/components/sandbox-terminal.d.ts +4 -1
  34. package/dist/components/sandbox-workspace.d.ts +3 -0
  35. package/dist/composer.js +6 -6
  36. package/dist/hooks/use-auth-runs.d.ts +32 -0
  37. package/dist/hooks/use-browser-downloads.d.ts +25 -0
  38. package/dist/hooks/use-browser-identities.d.ts +5 -1
  39. package/dist/hooks/use-browser-session.d.ts +2 -1
  40. package/dist/hooks/use-browser-sessions.d.ts +1 -1
  41. package/dist/hooks/use-channels.d.ts +26 -0
  42. package/dist/hooks/use-computer-frame-stream.d.ts +1 -1
  43. package/dist/hooks/use-computer-session.d.ts +2 -1
  44. package/dist/hooks/use-computer-sessions.d.ts +1 -1
  45. package/dist/hooks/use-desktop-stream.d.ts +4 -0
  46. package/dist/hooks/use-interaction-interventions.d.ts +27 -0
  47. package/dist/hooks/use-interaction-invalidation.d.ts +19 -0
  48. package/dist/hooks/use-network-routes.d.ts +25 -0
  49. package/dist/hooks/use-site-auth-connections.d.ts +25 -0
  50. package/dist/hooks/use-supergrok-accounts.d.ts +26 -0
  51. package/dist/hooks/use-terminal-stream.d.ts +3 -0
  52. package/dist/index.d.ts +4 -0
  53. package/dist/index.js +1220 -1545
  54. package/dist/index.js.map +1 -1
  55. package/dist/interaction.d.ts +13 -3
  56. package/dist/interaction.js +520 -6
  57. package/dist/interaction.js.map +1 -1
  58. package/dist/interactive-workbench-viewers-TOHBXJEE.js +15 -0
  59. package/dist/lib/host-platform.d.ts +5 -0
  60. package/dist/lib/relay-wire.d.ts +20 -1
  61. package/dist/machines.js +3 -3
  62. package/dist/model-policy.d.ts +1 -1
  63. package/dist/model-policy.js +1 -1
  64. package/dist/provider.d.ts +3 -2
  65. package/dist/realtime/realtime-control.d.ts +2 -2
  66. package/dist/realtime.js +36 -9
  67. package/dist/realtime.js.map +1 -1
  68. package/dist/session-context.d.ts +10 -2
  69. package/dist/session-ui.js +3 -3
  70. package/dist/session.js +11 -11
  71. package/dist/timeline/types.d.ts +5 -0
  72. package/package.json +2 -2
  73. package/src/artifacts.ts +3 -0
  74. package/src/client.ts +73 -3
  75. package/src/components/artifacts/index.ts +5 -0
  76. package/src/components/artifacts/published-html-artifact-frame.tsx +38 -0
  77. package/src/components/browser-input.ts +65 -0
  78. package/src/components/browser-viewer.tsx +1287 -177
  79. package/src/components/computer-viewer.tsx +611 -114
  80. package/src/components/desktop-viewer.tsx +114 -1
  81. package/src/components/interaction-intervention-banner.tsx +99 -0
  82. package/src/components/machines/machine-detail.tsx +1 -1
  83. package/src/components/model-policy-picker.tsx +15 -0
  84. package/src/components/sandbox-terminal.tsx +24 -7
  85. package/src/components/sandbox-workspace.tsx +42 -4
  86. package/src/components/workspace-dock.tsx +59 -5
  87. package/src/hooks/use-attached-browsers.ts +13 -11
  88. package/src/hooks/use-auth-runs.ts +278 -0
  89. package/src/hooks/use-browser-downloads.ts +218 -0
  90. package/src/hooks/use-browser-frame-stream.ts +168 -59
  91. package/src/hooks/use-browser-identities.ts +47 -6
  92. package/src/hooks/use-browser-session.ts +94 -27
  93. package/src/hooks/use-browser-sessions.ts +19 -14
  94. package/src/hooks/use-channels.ts +99 -0
  95. package/src/hooks/use-computer-frame-stream.ts +184 -61
  96. package/src/hooks/use-computer-session.ts +22 -2
  97. package/src/hooks/use-computer-sessions.ts +19 -13
  98. package/src/hooks/use-desktop-stream.ts +39 -7
  99. package/src/hooks/use-interaction-interventions.ts +242 -0
  100. package/src/hooks/use-interaction-invalidation.ts +66 -0
  101. package/src/hooks/use-machine-chip.ts +13 -3
  102. package/src/hooks/use-network-routes.ts +214 -0
  103. package/src/hooks/use-site-auth-connections.ts +226 -0
  104. package/src/hooks/use-supergrok-accounts.ts +81 -0
  105. package/src/hooks/use-terminal-stream.ts +288 -55
  106. package/src/index.ts +8 -0
  107. package/src/interaction.ts +30 -0
  108. package/src/lib/host-platform.ts +10 -0
  109. package/src/lib/relay-wire.ts +43 -4
  110. package/src/lib/terminal-capability.ts +1 -1
  111. package/src/model-policy.ts +19 -4
  112. package/src/provider.tsx +133 -1
  113. package/src/realtime/realtime-control.tsx +34 -6
  114. package/src/session-context.ts +66 -1
  115. package/src/timeline/activity-rail.tsx +6 -0
  116. package/src/timeline/projection.ts +58 -0
  117. package/src/timeline/types.ts +2 -0
  118. package/styles/compiled.css +64 -6
  119. package/dist/chunk-23EJ676W.js.map +0 -1
  120. package/dist/chunk-6BBWWOOU.js +0 -82
  121. package/dist/chunk-6BBWWOOU.js.map +0 -1
  122. package/dist/chunk-7I2KB3OK.js +0 -3895
  123. package/dist/chunk-7I2KB3OK.js.map +0 -1
  124. package/dist/chunk-HDBG7XGO.js.map +0 -1
  125. package/dist/chunk-JAGDUCZQ.js.map +0 -1
  126. package/dist/chunk-JR3QFY6B.js.map +0 -1
  127. package/dist/chunk-WYA65Y3F.js.map +0 -1
  128. package/dist/chunk-X4S56Y2H.js.map +0 -1
  129. package/dist/interactive-workbench-viewers-KHNOHWQE.js +0 -13
  130. /package/dist/{chunk-ROIRTM37.js.map → chunk-5UQPNLIO.js.map} +0 -0
  131. /package/dist/{chunk-724BW4TW.js.map → chunk-IGC6A2ZI.js.map} +0 -0
  132. /package/dist/{interactive-workbench-viewers-KHNOHWQE.js.map → interactive-workbench-viewers-TOHBXJEE.js.map} +0 -0
@@ -1,6 +1,10 @@
1
1
  import type { ClientModel, ReasoningEffort, WorkspaceModelCatalogModel } from "@opengeni/sdk";
2
2
 
3
- export type PickerBillingClass = "opengeni_credits" | "codex_subscription" | "byok";
3
+ export type PickerBillingClass =
4
+ | "opengeni_credits"
5
+ | "codex_subscription"
6
+ | "supergrok_subscription"
7
+ | "byok";
4
8
 
5
9
  export type PickerModelRow<TCatalog extends ClientModel = WorkspaceModelCatalogModel> = {
6
10
  id: string;
@@ -21,12 +25,14 @@ export type LatencyModeId = "standard" | "priority" | "fast";
21
25
  const BILLING_CLASS_ORDER: PickerBillingClass[] = [
22
26
  "opengeni_credits",
23
27
  "codex_subscription",
28
+ "supergrok_subscription",
24
29
  "byok",
25
30
  ];
26
31
 
27
32
  const BILLING_CLASS_LABELS: Record<PickerBillingClass, string> = {
28
33
  opengeni_credits: "OpenGeni",
29
34
  codex_subscription: "Codex",
35
+ supergrok_subscription: "SuperGrok",
30
36
  byok: "Your Gateway",
31
37
  };
32
38
 
@@ -41,6 +47,9 @@ const AVAILABILITY_REASON_LABELS: Record<string, string> = {
41
47
  };
42
48
 
43
49
  export function billingClassForModel(model: ClientModel): PickerBillingClass {
50
+ if (model.source === "supergrok") {
51
+ return "supergrok_subscription";
52
+ }
44
53
  if (model.source === "codex") {
45
54
  return "codex_subscription";
46
55
  }
@@ -48,7 +57,9 @@ export function billingClassForModel(model: ClientModel): PickerBillingClass {
48
57
  return "byok";
49
58
  }
50
59
  if (model.credentialSource?.kind === "connected_subscription") {
51
- return "codex_subscription";
60
+ return model.credentialSource.provider === "xai"
61
+ ? "supergrok_subscription"
62
+ : "codex_subscription";
52
63
  }
53
64
  if (model.credentialSource?.kind === "workspace_connection") {
54
65
  return "byok";
@@ -135,7 +146,9 @@ export function payerSummaryForModel(model: ClientModel): string {
135
146
  return "OpenGeni credits · automatic managed route";
136
147
  }
137
148
  if (billing.upstreamPayer === "connected_subscription") {
138
- return "Codex subscription · external billing";
149
+ return model.source === "supergrok"
150
+ ? "SuperGrok subscription · external billing"
151
+ : "Codex subscription · external billing";
139
152
  }
140
153
  if (billing.upstreamPayer === "workspace") {
141
154
  return "Billed to your AI Gateway";
@@ -149,7 +162,9 @@ export function advancedSourceSummary(model: ClientModel): string | null {
149
162
  return null;
150
163
  }
151
164
  if (source.kind === "connected_subscription") {
152
- return "Connected Codex subscription";
165
+ return source.provider === "xai"
166
+ ? "Connected SuperGrok subscription"
167
+ : "Connected Codex subscription";
153
168
  }
154
169
  if (source.kind === "workspace_connection") {
155
170
  return "Workspace AI Gateway";
package/src/provider.tsx CHANGED
@@ -3,6 +3,7 @@ import {
3
3
  OPENGENI_API_CONTRACT_REVISION,
4
4
  type StreamConnectionState,
5
5
  type WorkspaceControlEvent,
6
+ type WorkspaceInteractionRevisionEvent,
6
7
  } from "@opengeni/sdk";
7
8
  import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react";
8
9
  import type { SessionClientLike } from "./client";
@@ -17,6 +18,7 @@ export type OpenGeniProviderProps = {
17
18
  client: SessionClientLike;
18
19
  workspaceId: string;
19
20
  onWorkspaceControlEvent?: ((event: WorkspaceControlEvent) => void) | undefined;
21
+ onWorkspaceInteractionEvent?: ((event: WorkspaceInteractionRevisionEvent) => void) | undefined;
20
22
  children?: ReactNode;
21
23
  };
22
24
 
@@ -28,6 +30,7 @@ export function OpenGeniProvider({
28
30
  client,
29
31
  workspaceId,
30
32
  onWorkspaceControlEvent,
33
+ onWorkspaceInteractionEvent,
31
34
  children,
32
35
  }: OpenGeniProviderProps) {
33
36
  const [workspaceControlEvent, setWorkspaceControlEvent] = useState<WorkspaceControlEvent | null>(
@@ -36,15 +39,25 @@ export function OpenGeniProvider({
36
39
  const [workspaceControlConnectionState, setWorkspaceControlConnectionState] = useState<
37
40
  StreamConnectionState | "idle" | "error"
38
41
  >("idle");
42
+ const [workspaceInteractionEvent, setWorkspaceInteractionEvent] =
43
+ useState<WorkspaceInteractionRevisionEvent | null>(null);
44
+ const [workspaceInteractionConnectionState, setWorkspaceInteractionConnectionState] = useState<
45
+ StreamConnectionState | "idle" | "error"
46
+ >("idle");
39
47
  const [contractMismatch, setContractMismatch] = useState<OpenGeniApiContractMismatchError | null>(
40
48
  null,
41
49
  );
42
50
  const callbackRef = useRef(onWorkspaceControlEvent);
51
+ const interactionCallbackRef = useRef(onWorkspaceInteractionEvent);
43
52
  const reconcilersRef = useRef(new Map<string, Map<string, () => Promise<void>>>());
44
53
  const reconcileInFlightRef = useRef(new Map<string, Promise<void>>());
45
54
  const workspaceControlSequencesRef = useRef(new Map<string, number>());
55
+ const workspaceInteractionSequencesRef = useRef(new Map<string, number>());
46
56
  const pageLive = usePageLiveActivity();
57
+ const supportsWorkspaceLiveStream =
58
+ "streamWorkspaceLiveEvents" in client && typeof client.streamWorkspaceLiveEvents === "function";
47
59
  callbackRef.current = onWorkspaceControlEvent;
60
+ interactionCallbackRef.current = onWorkspaceInteractionEvent;
48
61
 
49
62
  const verifyApiContract = useCallback(async (): Promise<void> => {
50
63
  try {
@@ -105,6 +118,78 @@ export function OpenGeniProvider({
105
118
  );
106
119
 
107
120
  useEffect(() => {
121
+ if (!supportsWorkspaceLiveStream) return;
122
+ const openLiveStream = client.streamWorkspaceLiveEvents!;
123
+ setWorkspaceControlEvent(null);
124
+ setWorkspaceInteractionEvent(null);
125
+ if (!pageLive) {
126
+ setWorkspaceControlConnectionState("idle");
127
+ setWorkspaceInteractionConnectionState("idle");
128
+ return;
129
+ }
130
+ const controller = new AbortController();
131
+ const setConnectionState = (state: StreamConnectionState) => {
132
+ setWorkspaceControlConnectionState(state);
133
+ setWorkspaceInteractionConnectionState(state);
134
+ };
135
+ setConnectionState("connecting");
136
+ void (async () => {
137
+ try {
138
+ await verifyApiContract();
139
+ const workspace = await client.getWorkspace(workspaceId);
140
+ const controlAfter = Math.max(
141
+ workspaceControlSequencesRef.current.get(workspaceId) ?? 0,
142
+ workspace.inferenceControl.revision,
143
+ );
144
+ const interactionAfter = workspaceInteractionSequencesRef.current.get(workspaceId) ?? 0;
145
+ workspaceControlSequencesRef.current.set(workspaceId, controlAfter);
146
+ const stream = openLiveStream.call(client, workspaceId, {
147
+ controlAfter,
148
+ interactionAfter,
149
+ signal: controller.signal,
150
+ onStateChange: setConnectionState,
151
+ });
152
+ for await (const event of stream) {
153
+ if (controller.signal.aborted) return;
154
+ if (event.type === "workspace.control.changed") {
155
+ workspaceControlSequencesRef.current.set(
156
+ workspaceId,
157
+ Math.max(workspaceControlSequencesRef.current.get(workspaceId) ?? 0, event.sequence),
158
+ );
159
+ setWorkspaceControlEvent((current) =>
160
+ !current || event.sequence > current.sequence ? event : current,
161
+ );
162
+ callbackRef.current?.(event);
163
+ } else {
164
+ workspaceInteractionSequencesRef.current.set(
165
+ workspaceId,
166
+ Math.max(
167
+ workspaceInteractionSequencesRef.current.get(workspaceId) ?? 0,
168
+ event.sequence,
169
+ ),
170
+ );
171
+ setWorkspaceInteractionEvent((current) =>
172
+ !current || event.sequence > current.sequence ? event : current,
173
+ );
174
+ interactionCallbackRef.current?.(event);
175
+ }
176
+ }
177
+ } catch (error) {
178
+ if (error instanceof OpenGeniApiContractMismatchError) {
179
+ setContractMismatch(error);
180
+ reloadForContractMismatchOnce(error);
181
+ }
182
+ if (!controller.signal.aborted) {
183
+ setWorkspaceControlConnectionState("error");
184
+ setWorkspaceInteractionConnectionState("error");
185
+ }
186
+ }
187
+ })();
188
+ return () => controller.abort();
189
+ }, [client, pageLive, supportsWorkspaceLiveStream, verifyApiContract, workspaceId]);
190
+
191
+ useEffect(() => {
192
+ if (supportsWorkspaceLiveStream) return;
108
193
  if (!pageLive) {
109
194
  setWorkspaceControlConnectionState("idle");
110
195
  return;
@@ -145,7 +230,50 @@ export function OpenGeniProvider({
145
230
  }
146
231
  })();
147
232
  return () => controller.abort();
148
- }, [client, pageLive, verifyApiContract, workspaceId]);
233
+ }, [client, pageLive, supportsWorkspaceLiveStream, verifyApiContract, workspaceId]);
234
+
235
+ useEffect(() => {
236
+ if (supportsWorkspaceLiveStream) return;
237
+ setWorkspaceInteractionEvent(null);
238
+ if (!pageLive) {
239
+ setWorkspaceInteractionConnectionState("idle");
240
+ return;
241
+ }
242
+ const controller = new AbortController();
243
+ setWorkspaceInteractionConnectionState("connecting");
244
+ void (async () => {
245
+ try {
246
+ await verifyApiContract();
247
+ const resumeAfter = workspaceInteractionSequencesRef.current.get(workspaceId) ?? 0;
248
+ const stream = client.streamWorkspaceInteractionRevisions(workspaceId, {
249
+ after: resumeAfter,
250
+ signal: controller.signal,
251
+ onStateChange: setWorkspaceInteractionConnectionState,
252
+ });
253
+ for await (const event of stream) {
254
+ if (controller.signal.aborted) return;
255
+ workspaceInteractionSequencesRef.current.set(
256
+ workspaceId,
257
+ Math.max(
258
+ workspaceInteractionSequencesRef.current.get(workspaceId) ?? 0,
259
+ event.sequence,
260
+ ),
261
+ );
262
+ setWorkspaceInteractionEvent((current) =>
263
+ !current || event.sequence > current.sequence ? event : current,
264
+ );
265
+ interactionCallbackRef.current?.(event);
266
+ }
267
+ } catch (error) {
268
+ if (error instanceof OpenGeniApiContractMismatchError) {
269
+ setContractMismatch(error);
270
+ reloadForContractMismatchOnce(error);
271
+ }
272
+ if (!controller.signal.aborted) setWorkspaceInteractionConnectionState("error");
273
+ }
274
+ })();
275
+ return () => controller.abort();
276
+ }, [client, pageLive, supportsWorkspaceLiveStream, verifyApiContract, workspaceId]);
149
277
 
150
278
  const value = useMemo(
151
279
  () => ({
@@ -153,6 +281,8 @@ export function OpenGeniProvider({
153
281
  workspaceId,
154
282
  workspaceControlEvent,
155
283
  workspaceControlConnectionState,
284
+ workspaceInteractionEvent,
285
+ workspaceInteractionConnectionState,
156
286
  registerSessionReconciler,
157
287
  reconcileSession,
158
288
  }),
@@ -161,6 +291,8 @@ export function OpenGeniProvider({
161
291
  workspaceId,
162
292
  workspaceControlEvent,
163
293
  workspaceControlConnectionState,
294
+ workspaceInteractionEvent,
295
+ workspaceInteractionConnectionState,
164
296
  registerSessionReconciler,
165
297
  reconcileSession,
166
298
  ],
@@ -57,7 +57,7 @@ import {
57
57
  export type RealtimeModelOption = {
58
58
  id: SessionRealtimeModel;
59
59
  label: string;
60
- provider: "OpenGeni" | "Connected Codex" | "Your Gateway";
60
+ provider: "OpenGeni" | "Connected Codex" | "Connected SuperGrok" | "Your Gateway";
61
61
  description: string;
62
62
  available: boolean;
63
63
  unavailableReason: string | null;
@@ -74,7 +74,12 @@ const CODEX_LIVE_MODEL: RealtimeModelOption = {
74
74
  recommended: false,
75
75
  };
76
76
 
77
- const REALTIME_MODEL_PROVIDERS = ["OpenGeni", "Connected Codex", "Your Gateway"] as const;
77
+ const REALTIME_MODEL_PROVIDERS = [
78
+ "OpenGeni",
79
+ "Connected Codex",
80
+ "Connected SuperGrok",
81
+ "Your Gateway",
82
+ ] as const;
78
83
  type RealtimeModelProvider = (typeof REALTIME_MODEL_PROVIDERS)[number];
79
84
 
80
85
  const REALTIME_PROVIDER_META: Record<
@@ -86,6 +91,10 @@ const REALTIME_PROVIDER_META: Record<
86
91
  billingClass: "codex_subscription",
87
92
  hint: "ChatGPT / Codex plan",
88
93
  },
94
+ "Connected SuperGrok": {
95
+ billingClass: "supergrok_subscription",
96
+ hint: "SuperGrok / xAI plan",
97
+ },
89
98
  "Your Gateway": { billingClass: "byok", hint: "Billed to your AI Gateway" },
90
99
  };
91
100
  const REALTIME_MODEL_STORAGE_PREFIX = "opengeni:realtime-model";
@@ -141,7 +150,11 @@ export function useSessionRealtime(options: {
141
150
  const controllerRef = useRef<SessionRealtimeController | null>(null);
142
151
  const controllerModelRef = useRef<SessionRealtimeModel | null>(null);
143
152
  const [snapshot, setSnapshot] = useState<SessionRealtimeControllerSnapshot>(() => ({
144
- status: hasStoredSessionRealtimeOwnerProof({ workspaceId, sessionId: options.sessionId, model })
153
+ status: hasStoredSessionRealtimeOwnerProof({
154
+ workspaceId,
155
+ sessionId: options.sessionId,
156
+ model,
157
+ })
145
158
  ? "recovering"
146
159
  : "idle",
147
160
  realtimeId: null,
@@ -684,7 +697,13 @@ export function RealtimeVoiceControl(props: {
684
697
  opacity: 1,
685
698
  transition: reduceMotion
686
699
  ? { duration: 0 }
687
- : { delay: 0.32, type: "spring", stiffness: 320, damping: 30, mass: 0.8 },
700
+ : {
701
+ delay: 0.32,
702
+ type: "spring",
703
+ stiffness: 320,
704
+ damping: 30,
705
+ mass: 0.8,
706
+ },
688
707
  }}
689
708
  {...(reduceMotion
690
709
  ? {}
@@ -1160,7 +1179,11 @@ function statusContent(
1160
1179
  };
1161
1180
  }
1162
1181
  if (!canStart && admissionBlocker) {
1163
- return { phase: "unavailable", label: "Voice unavailable", detail: admissionBlocker };
1182
+ return {
1183
+ phase: "unavailable",
1184
+ label: "Voice unavailable",
1185
+ detail: admissionBlocker,
1186
+ };
1164
1187
  }
1165
1188
  return {
1166
1189
  phase: "idle",
@@ -1268,7 +1291,11 @@ function RealtimeStatusDot(props: { phase: RealtimeVisualPhase; reduceMotion: bo
1268
1291
  <motion.span
1269
1292
  className="absolute inset-0 rounded-full bg-og-status-running"
1270
1293
  animate={{ opacity: [0.45, 0], scale: [1, 2.2] }}
1271
- transition={{ duration: 1.4, ease: "easeOut", repeat: Number.POSITIVE_INFINITY }}
1294
+ transition={{
1295
+ duration: 1.4,
1296
+ ease: "easeOut",
1297
+ repeat: Number.POSITIVE_INFINITY,
1298
+ }}
1272
1299
  />
1273
1300
  ) : null}
1274
1301
  <span
@@ -1343,6 +1370,7 @@ function isRealtimeModel(value: string | null): value is SessionRealtimeModel {
1343
1370
  value === "opengeni-gateway/openai/gpt-realtime-2.1" ||
1344
1371
  value === "opengeni-gateway/openai/gpt-realtime-mini" ||
1345
1372
  value === "opengeni-gateway/xai/grok-voice-think-fast-2.0" ||
1373
+ value === "supergrok/grok-voice-think-fast-2.0" ||
1346
1374
  value === "workspace-gateway/openai/gpt-realtime-2.1" ||
1347
1375
  value === "workspace-gateway/openai/gpt-realtime-mini" ||
1348
1376
  value === "workspace-gateway/xai/grok-voice-think-fast-2.0"
@@ -1,4 +1,8 @@
1
- import type { StreamConnectionState, WorkspaceControlEvent } from "@opengeni/sdk";
1
+ import type {
2
+ StreamConnectionState,
3
+ WorkspaceControlEvent,
4
+ WorkspaceInteractionRevisionEvent,
5
+ } from "@opengeni/sdk";
2
6
  import { createContext, useContext } from "react";
3
7
  import type {
4
8
  EmbeddedFileAttachmentClientLike,
@@ -7,6 +11,7 @@ import type {
7
11
  EmbeddedBrowserInteractionClientLike,
8
12
  EmbeddedComputerInteractionClientLike,
9
13
  EmbeddedInteractionClientLike,
14
+ EmbeddedInterventionClientLike,
10
15
  EmbeddedRealtimeSessionClientLike,
11
16
  EmbeddedSessionClientLike,
12
17
  EmbeddedSessionLineageClientLike,
@@ -20,6 +25,8 @@ export type OpenGeniContextValue = {
20
25
  workspaceId: string;
21
26
  workspaceControlEvent: WorkspaceControlEvent | null;
22
27
  workspaceControlConnectionState: StreamConnectionState | "idle" | "error";
28
+ workspaceInteractionEvent: WorkspaceInteractionRevisionEvent | null;
29
+ workspaceInteractionConnectionState: StreamConnectionState | "idle" | "error";
23
30
  registerSessionReconciler: (
24
31
  sessionId: string,
25
32
  key: string,
@@ -84,6 +91,11 @@ export type EmbeddedInteractionClientOverride = {
84
91
  workspaceId?: string | undefined;
85
92
  };
86
93
 
94
+ export type EmbeddedInterventionClientOverride = {
95
+ client?: EmbeddedInterventionClientLike | undefined;
96
+ workspaceId?: string | undefined;
97
+ };
98
+
87
99
  export type EmbeddedBrowserInteractionClientOverride = {
88
100
  client?: EmbeddedBrowserInteractionClientLike | undefined;
89
101
  workspaceId?: string | undefined;
@@ -132,6 +144,8 @@ function useEmbeddedClientRefinement<TClient extends object>(
132
144
  workspaceId,
133
145
  workspaceControlEvent: context?.workspaceControlEvent ?? null,
134
146
  workspaceControlConnectionState: context?.workspaceControlConnectionState ?? "idle",
147
+ workspaceInteractionEvent: context?.workspaceInteractionEvent ?? null,
148
+ workspaceInteractionConnectionState: context?.workspaceInteractionConnectionState ?? "idle",
135
149
  registerSessionReconciler: context?.registerSessionReconciler ?? NOOP_REGISTER_RECONCILER,
136
150
  reconcileSession: context?.reconcileSession ?? NOOP_RECONCILE_SESSION,
137
151
  };
@@ -158,20 +172,46 @@ export function useOpenGeni(override: ClientOverride = {}): OpenGeniContextValue
158
172
  workspaceId,
159
173
  workspaceControlEvent: context?.workspaceControlEvent ?? null,
160
174
  workspaceControlConnectionState: context?.workspaceControlConnectionState ?? "idle",
175
+ workspaceInteractionEvent: context?.workspaceInteractionEvent ?? null,
176
+ workspaceInteractionConnectionState: context?.workspaceInteractionConnectionState ?? "idle",
161
177
  registerSessionReconciler: context?.registerSessionReconciler ?? NOOP_REGISTER_RECONCILER,
162
178
  reconcileSession: context?.reconcileSession ?? NOOP_RECONCILE_SESSION,
163
179
  };
164
180
  }
165
181
 
166
182
  const BROWSER_INTERACTION_METHODS = [
183
+ "streamWorkspaceInteractionRevisions",
184
+ "listNetworkRoutes",
185
+ "getNetworkRoute",
186
+ "createNetworkRoute",
187
+ "updateNetworkRoute",
188
+ "listSiteAuthConnections",
189
+ "getSiteAuthConnection",
190
+ "createSiteAuthConnection",
191
+ "updateSiteAuthConnection",
192
+ "listAuthRuns",
193
+ "getAuthRun",
194
+ "startBrowserAuthRun",
195
+ "reportBrowserAuthRun",
196
+ "protectedBrowserAuthFill",
197
+ "verifyBrowserAuthRun",
198
+ "listInteractionInterventions",
199
+ "getInteractionIntervention",
200
+ "createInteractionIntervention",
201
+ "resolveInteractionIntervention",
167
202
  "listAttachedBrowsers",
168
203
  "getAttachedBrowser",
169
204
  "listBrowserIdentities",
170
205
  "getBrowserIdentity",
171
206
  "createBrowserIdentity",
207
+ "updateBrowserIdentity",
172
208
  "listBrowserRevisions",
173
209
  "listBrowserSessions",
174
210
  "getBrowserSession",
211
+ "readBrowserClipboard",
212
+ "listBrowserDownloads",
213
+ "getBrowserDownload",
214
+ "saveBrowserDownload",
175
215
  "createBrowserSession",
176
216
  "listBrowserTargets",
177
217
  "openBrowserTarget",
@@ -190,8 +230,14 @@ const BROWSER_INTERACTION_METHODS = [
190
230
  ] as const;
191
231
 
192
232
  const COMPUTER_INTERACTION_METHODS = [
233
+ "streamWorkspaceInteractionRevisions",
234
+ "listInteractionInterventions",
235
+ "getInteractionIntervention",
236
+ "createInteractionIntervention",
237
+ "resolveInteractionIntervention",
193
238
  "listComputerSessions",
194
239
  "getComputerSession",
240
+ "readComputerClipboard",
195
241
  "createComputerSession",
196
242
  "listComputerTargets",
197
243
  "observeComputerTarget",
@@ -202,6 +248,25 @@ const COMPUTER_INTERACTION_METHODS = [
202
248
  "endComputerSession",
203
249
  ] as const;
204
250
 
251
+ const INTERVENTION_METHODS = [
252
+ "streamWorkspaceInteractionRevisions",
253
+ "listInteractionInterventions",
254
+ "getInteractionIntervention",
255
+ "createInteractionIntervention",
256
+ "resolveInteractionIntervention",
257
+ ] as const;
258
+
259
+ /** Resolve only the workspace-wide Browser/Computer intervention surface. */
260
+ export function useEmbeddedInterventions(
261
+ override: EmbeddedInterventionClientOverride = {},
262
+ ): EmbeddedClientContextValue<EmbeddedInterventionClientLike> {
263
+ return useEmbeddedClientRefinement(
264
+ override,
265
+ INTERVENTION_METHODS,
266
+ "interaction intervention hooks",
267
+ );
268
+ }
269
+
205
270
  /** Resolve only BrowserSession methods for a standalone Browser embed. */
206
271
  export function useEmbeddedBrowserInteraction(
207
272
  override: EmbeddedBrowserInteractionClientOverride = {},
@@ -417,6 +417,12 @@ function WorkerRow({
417
417
  {truncate(item.prompt, 140)}
418
418
  </p>
419
419
  ) : null}
420
+ {failed && item.failure ? (
421
+ <div className="mt-1 min-w-0 text-og-sm text-og-status-failed">
422
+ <p className="font-og-mono text-og-xs">{item.failure.code}</p>
423
+ <p className="mt-0.5 break-words">{item.failure.message}</p>
424
+ </div>
425
+ ) : null}
420
426
  </div>
421
427
  {failed ? (
422
428
  <span className="inline-flex shrink-0 self-center items-center gap-1.5 font-og-mono text-og-xs leading-none text-og-status-failed">
@@ -49,6 +49,8 @@ export { toolDisplayName, mcpToolLeaf, toolMatchesLeaf } from "./tool-display-na
49
49
  /** Tool leaves on the first-party OpenGeni MCP server that operate on sessions. */
50
50
  const WORKER_SPAWN_TOOL = "session_create";
51
51
  const WORKER_MESSAGE_TOOL = "session_send_message";
52
+ const WORKER_FAILURE_CODE_MAX_LENGTH = 128;
53
+ const WORKER_FAILURE_MESSAGE_MAX_UTF8_BYTES = 1_024;
52
54
 
53
55
  /**
54
56
  * Tools whose durable side-effect events already own the timeline (MemoryRow).
@@ -359,6 +361,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
359
361
  action: toolMatchesLeaf(name, WORKER_SPAWN_TOOL) ? "spawn" : "message",
360
362
  prompt: workerPrompt(args),
361
363
  workerSessionId: extractSessionRef(args),
364
+ failure: null,
362
365
  status: "running",
363
366
  occurredAt: event.occurredAt,
364
367
  });
@@ -417,6 +420,7 @@ export function buildTimeline(events: SessionEvent[]): TimelineItem[] {
417
420
  // isError result) settles to "failed" too, so WorkerRow surfaces it.
418
421
  target.status = isErrorOutput(payload) ? "failed" : "complete";
419
422
  target.workerSessionId = target.workerSessionId ?? extractSessionRef(payload.output);
423
+ target.failure = target.status === "failed" ? workerFailure(payload.output) : null;
420
424
  break;
421
425
  }
422
426
  // An output carrying an explicit error flag (or an MCP isError result)
@@ -1300,6 +1304,8 @@ function isTurnBoundary(group: TimelineGroup | undefined): boolean {
1300
1304
  (group?.kind === "item" &&
1301
1305
  (group.item.kind === "user-message" ||
1302
1306
  group.item.kind === "context-compaction" ||
1307
+ (group.item.kind === "machine-input-batch" &&
1308
+ group.item.members.some((member) => member.kind === "media_generation_result")) ||
1303
1309
  (group.item.kind === "notice" && group.item.tone === "input")))
1304
1310
  );
1305
1311
  }
@@ -1795,6 +1801,58 @@ function workerPrompt(args: unknown): string | null {
1795
1801
  return null;
1796
1802
  }
1797
1803
 
1804
+ function boundedWorkerFailureMessage(value: string): string | null {
1805
+ const normalized = value.replace(/[\u0000-\u001f\u007f]+/g, " ").trim();
1806
+ if (!normalized) return null;
1807
+ const encoded = new TextEncoder().encode(normalized);
1808
+ if (encoded.byteLength <= WORKER_FAILURE_MESSAGE_MAX_UTF8_BYTES) return normalized;
1809
+ let end = WORKER_FAILURE_MESSAGE_MAX_UTF8_BYTES;
1810
+ while (end > 0 && (encoded[end]! & 0xc0) === 0x80) end -= 1;
1811
+ return new TextDecoder().decode(encoded.slice(0, end)).trim();
1812
+ }
1813
+
1814
+ function boundedWorkerFailure(value: unknown): WorkerItem["failure"] {
1815
+ const record = asRecord(value);
1816
+ const code = typeof record.code === "string" ? record.code.trim() : "";
1817
+ const message =
1818
+ typeof record.message === "string" ? boundedWorkerFailureMessage(record.message) : null;
1819
+ if (!/^[A-Za-z0-9._:-]{1,128}$/.test(code) || !message) return null;
1820
+ return { code: code.slice(0, WORKER_FAILURE_CODE_MAX_LENGTH), message };
1821
+ }
1822
+
1823
+ /**
1824
+ * Extract the API's structured orchestration failure from raw objects, JSON
1825
+ * text, or retained MCP CallToolResult wrappers. Legacy flat strings remain
1826
+ * unprojected rather than being mistaken for the trusted diagnostic contract.
1827
+ */
1828
+ function workerFailure(value: unknown, depth = 0): WorkerItem["failure"] {
1829
+ if (depth > 6 || value === null || value === undefined) return null;
1830
+ if (typeof value === "string") {
1831
+ const parsed = tryParseJson(value);
1832
+ return parsed === value ? null : workerFailure(parsed, depth + 1);
1833
+ }
1834
+ if (Array.isArray(value)) {
1835
+ for (const entry of value) {
1836
+ const failure = workerFailure(entry, depth + 1);
1837
+ if (failure) return failure;
1838
+ }
1839
+ return null;
1840
+ }
1841
+ if (typeof value !== "object") return null;
1842
+ const record = value as Record<string, unknown>;
1843
+ const direct = boundedWorkerFailure(record.error);
1844
+ if (direct) return direct;
1845
+ for (const key of ["structuredContent", "content", "result", "output"] as const) {
1846
+ if (!(key in record)) continue;
1847
+ const failure = workerFailure(record[key], depth + 1);
1848
+ if (failure) return failure;
1849
+ }
1850
+ if (record.type === "text" && typeof record.text === "string") {
1851
+ return workerFailure(record.text, depth + 1);
1852
+ }
1853
+ return null;
1854
+ }
1855
+
1798
1856
  /**
1799
1857
  * Find a session id in orchestration tool arguments or output. Handles raw
1800
1858
  * objects, JSON strings, and MCP tool results (`{ content: [{ type: "text",
@@ -127,6 +127,8 @@ export type WorkerItem = {
127
127
  prompt: string | null;
128
128
  /** The target/spawned worker session id, when parseable from args/output. */
129
129
  workerSessionId: string | null;
130
+ /** Bounded structured failure retained from session_create/session_send_message. */
131
+ failure: { code: string; message: string } | null;
130
132
  status: "running" | "complete" | "failed" | "cancelled";
131
133
  occurredAt: string;
132
134
  };