@opengeni/react 7.1.2 → 7.2.0-canary.36237839247001

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/composer.js CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  useChatComposer,
41
41
  useChatComposerController,
42
42
  useVoiceInput
43
- } from "./chunk-4ACTWOOW.js";
43
+ } from "./chunk-VDSZ3AVO.js";
44
44
  import {
45
45
  BillingClassMark,
46
46
  ModelPolicyPicker,
package/dist/index.js CHANGED
@@ -114,7 +114,7 @@ import {
114
114
  useWorkspaceModelCatalog,
115
115
  userMessageLikelyNeedsDisclosure,
116
116
  v4aToGitFileDiff
117
- } from "./chunk-VHEY4CB3.js";
117
+ } from "./chunk-RSFCYKA7.js";
118
118
  import "./chunk-GEN54DU5.js";
119
119
  import "./chunk-GBPCZH3Y.js";
120
120
  import "./chunk-ZYRGEKWS.js";
@@ -142,7 +142,7 @@ import {
142
142
  useSession,
143
143
  useSessionEvents,
144
144
  useTurnQueue
145
- } from "./chunk-VI7HELBI.js";
145
+ } from "./chunk-SJ5HE4ZA.js";
146
146
  import {
147
147
  OpenGeniProvider
148
148
  } from "./chunk-PAJU5KKL.js";
@@ -178,7 +178,7 @@ import {
178
178
  useSlashCommands,
179
179
  useTranscription,
180
180
  useVoiceInput
181
- } from "./chunk-4ACTWOOW.js";
181
+ } from "./chunk-VDSZ3AVO.js";
182
182
  import {
183
183
  BillingClassMark,
184
184
  ModelPolicyPicker,
@@ -8174,7 +8174,7 @@ import {
8174
8174
  } from "lucide-react";
8175
8175
  import { jsx as jsx11, jsxs as jsxs9 } from "react/jsx-runtime";
8176
8176
  var LazyBrowserViewer = lazy2(async () => {
8177
- const { BrowserViewer } = await import("./browser-viewer-TZ6T76E3.js");
8177
+ const { BrowserViewer } = await import("./browser-viewer-K2H3SLIW.js");
8178
8178
  return { default: BrowserViewer };
8179
8179
  });
8180
8180
  var LazyComputerViewer = lazy2(async () => {
@@ -7,7 +7,7 @@ import {
7
7
  useBrowserSession,
8
8
  useBrowserSessions,
9
9
  useSiteAuthConnections
10
- } from "./chunk-CP6IFD2Z.js";
10
+ } from "./chunk-ETA37UZ7.js";
11
11
  import {
12
12
  ComputerViewer,
13
13
  useComputerFrameStream,
@@ -19,16 +19,16 @@ import {
19
19
  setStartupDetails,
20
20
  useStartupDetails,
21
21
  userMessageLikelyNeedsDisclosure
22
- } from "./chunk-VHEY4CB3.js";
22
+ } from "./chunk-RSFCYKA7.js";
23
23
  import "./chunk-GEN54DU5.js";
24
24
  import "./chunk-GBPCZH3Y.js";
25
25
  import "./chunk-ZYRGEKWS.js";
26
26
  import "./chunk-WYWRYUNQ.js";
27
27
  import {
28
28
  createOlderHistoryLoadReceipt
29
- } from "./chunk-VI7HELBI.js";
29
+ } from "./chunk-SJ5HE4ZA.js";
30
30
  import "./chunk-PAJU5KKL.js";
31
- import "./chunk-4ACTWOOW.js";
31
+ import "./chunk-VDSZ3AVO.js";
32
32
  import "./chunk-3LL47F4U.js";
33
33
  import "./chunk-AKOMC3VO.js";
34
34
  import "./chunk-IOLPHOF4.js";
package/dist/session.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  useSession,
30
30
  useSessionEvents,
31
31
  useTurnQueue
32
- } from "./chunk-VI7HELBI.js";
32
+ } from "./chunk-SJ5HE4ZA.js";
33
33
  import {
34
34
  FILE_ONLY_MESSAGE_TEXT,
35
35
  composeSendInput,
@@ -375,6 +375,8 @@ export type AuthNeededItem = {
375
375
  authorizationUrl: string | null;
376
376
  /** Agent-selected catalog recommendation. The host still owns authorization. */
377
377
  capability?: NonNullable<ToolAuthNeededPayload["capability"]> | null | undefined;
378
+ /** Human-reviewed custom integration proposal. Never treated as catalog authority. */
379
+ setupRequest?: ToolAuthNeededPayload["setupRequest"] | null | undefined;
378
380
  occurredAt: string;
379
381
  };
380
382
  export type TurnOutcome = "complete" | "failed" | "cancelled";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@opengeni/react",
3
- "version": "7.1.2",
3
+ "version": "7.2.0-canary.36237839247001",
4
4
  "description": "React hooks and styled components for OpenGeni: live session streaming, chat composer, message timeline, session status, and fleet views — token-themed (CSS variables), dark-first, built on Tailwind v4 + Radix + Motion.",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
@@ -51,8 +51,8 @@
51
51
  "import": "./dist/session-ui.js"
52
52
  },
53
53
  "./accounts": {
54
- "types": "./dist/accounts.tsx",
55
- "import": "./dist/accounts.tsx"
54
+ "types": "./dist/accounts.d.ts",
55
+ "import": "./dist/accounts.js"
56
56
  },
57
57
  "./machines": {
58
58
  "types": "./dist/machines.d.ts",
@@ -133,8 +133,8 @@
133
133
  "@dnd-kit/utilities": "3.2.2",
134
134
  "@fontsource-variable/noto-sans-arabic": "5.2.10",
135
135
  "@fontsource-variable/noto-sans-jp": "5.2.10",
136
- "@opengeni/connect": "^0.3.0",
137
- "@opengeni/sdk": "^7.1.1",
136
+ "@opengeni/connect": "^0.3.0-canary.36237839247001",
137
+ "@opengeni/sdk": "^7.2.0-canary.36237839247001",
138
138
  "clsx": "^2.1.1",
139
139
  "dequal": "2.0.3",
140
140
  "lucide-react": "^1.8.0",
@@ -342,12 +342,8 @@ export function BrowserViewer({
342
342
  setResumeAttempt((visible) =>
343
343
  visible?.sessionId === selectedRegistrySession.id ? null : visible,
344
344
  );
345
- return;
346
- }
347
- if (selectedRegistrySession.lifecycle === "suspended") {
348
- void wakeBrowser(selectedRegistrySession);
349
345
  }
350
- }, [selectedRegistrySession, wakeBrowser]);
346
+ }, [selectedRegistrySession]);
351
347
 
352
348
  const browser = useBrowserSession({
353
349
  ...override,
@@ -248,6 +248,7 @@ export function ComposerTranscriptionControl({
248
248
  >
249
249
  <button
250
250
  type="button"
251
+ disabled={composer.disabled && !retrying}
251
252
  onClick={() =>
252
253
  savedTranscript
253
254
  ? void transcription.insertSavedTranscript()
@@ -265,7 +266,7 @@ export function ComposerTranscriptionControl({
265
266
  className={cn(
266
267
  "inline-flex size-7 shrink-0 items-center justify-center rounded-og-sm",
267
268
  "bg-og-fg text-og-bg transition-colors duration-150 motion-reduce:transition-none",
268
- "hover:bg-og-fg-muted pointer-coarse:size-11",
269
+ "hover:bg-og-fg-muted disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:size-11",
269
270
  )}
270
271
  >
271
272
  {savedTranscript ? (
@@ -280,12 +281,13 @@ export function ComposerTranscriptionControl({
280
281
  <Tip tip={messages.discardRecovered}>
281
282
  <button
282
283
  type="button"
284
+ disabled={composer.disabled}
283
285
  onClick={() => void transcription.discard()}
284
286
  aria-label={messages.discardRecovered}
285
287
  className={cn(
286
288
  "inline-flex size-7 shrink-0 items-center justify-center rounded-og-sm",
287
289
  "text-og-fg-muted transition-colors duration-150 motion-reduce:transition-none",
288
- "hover:bg-og-surface-3 hover:text-og-status-failed pointer-coarse:size-11",
290
+ "hover:bg-og-surface-3 hover:text-og-status-failed disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:size-11",
289
291
  )}
290
292
  >
291
293
  <Trash2Icon className="size-3.5" />
@@ -457,9 +457,9 @@ export function useChatComposerController({
457
457
  (event: DragEvent<HTMLDivElement>) => {
458
458
  if (!attachments || !dragCarriesFiles(event)) return;
459
459
  event.preventDefault();
460
- setDragging(true);
460
+ if (!disabled) setDragging(true);
461
461
  },
462
- [attachments],
462
+ [attachments, disabled],
463
463
  );
464
464
  const handleDragLeave = useCallback(
465
465
  (event: DragEvent<HTMLDivElement>) => {
@@ -474,11 +474,16 @@ export function useChatComposerController({
474
474
  if (!attachments || !dragCarriesFiles(event)) return;
475
475
  event.preventDefault();
476
476
  setDragging(false);
477
- if (event.dataTransfer.files.length > 0) attachments.addFiles(event.dataTransfer.files);
477
+ if (!disabled && event.dataTransfer.files.length > 0)
478
+ attachments.addFiles(event.dataTransfer.files);
478
479
  },
479
- [attachments],
480
+ [attachments, disabled],
480
481
  );
481
482
 
483
+ useEffect(() => {
484
+ if (disabled) setDragging(false);
485
+ }, [disabled]);
486
+
482
487
  const [notice, setNotice] = useState<Notice | null>(null);
483
488
  const [helpOpen, setHelpOpen] = useState(false);
484
489
  const [confirmState, setConfirmState] = useState<ConfirmState>(null);
@@ -624,17 +629,18 @@ export function useChatComposerController({
624
629
  );
625
630
  const handlePaste = useCallback(
626
631
  (event: ClipboardEvent<HTMLTextAreaElement>) => {
632
+ if (disabled) return;
627
633
  onPaste?.(event);
628
634
  attachments?.addFromPaste(event);
629
635
  },
630
- [attachments, onPaste],
636
+ [attachments, disabled, onPaste],
631
637
  );
632
638
  const handleFileChange = useCallback(
633
639
  (event: ChangeEvent<HTMLInputElement>) => {
634
- if (event.target.files) attachments?.addFiles(event.target.files);
640
+ if (!disabled && event.target.files) attachments?.addFiles(event.target.files);
635
641
  event.target.value = "";
636
642
  },
637
- [attachments],
643
+ [attachments, disabled],
638
644
  );
639
645
 
640
646
  const helpCommands = useMemo(
@@ -975,6 +981,7 @@ export function Attachments() {
975
981
  <AttachmentChips
976
982
  attachments={controller.attachments.attachments}
977
983
  messages={controller.messages}
984
+ disabled={controller.disabled}
978
985
  onRemove={controller.attachments.remove}
979
986
  onRetry={controller.attachments.retry}
980
987
  onRetainPreview={controller.attachments.retainPreview}
@@ -996,6 +1003,26 @@ type OwnedInputProps =
996
1003
  | "aria-keyshortcuts";
997
1004
  export type ComposerInputProps = Omit<ComponentPropsWithoutRef<"textarea">, OwnedInputProps>;
998
1005
 
1006
+ const EDITING_FOCUS_SELECTOR =
1007
+ 'input, textarea, select, [contenteditable="true"], [role="textbox"]';
1008
+ const POPUP_FOCUS_SELECTOR =
1009
+ '[role="menu"], [role="menubar"], [role="listbox"], [role="dialog"], [role="alertdialog"]';
1010
+
1011
+ /**
1012
+ * Initial focus is a convenience, not authority to move focus the person has
1013
+ * placed elsewhere. It yields to a field they started editing and to an open
1014
+ * menu, listbox, or dialog that does not contain the composer: moving focus
1015
+ * behind a non-modal popup dismisses it, and a modal surface owns focus until
1016
+ * it closes.
1017
+ */
1018
+ function focusBelongsElsewhere(textarea: HTMLTextAreaElement): boolean {
1019
+ const active = textarea.ownerDocument.activeElement;
1020
+ if (!active || active === textarea) return false;
1021
+ if (active.closest(EDITING_FOCUS_SELECTOR)) return true;
1022
+ const popup = active.closest(POPUP_FOCUS_SELECTOR);
1023
+ return popup !== null && !popup.contains(textarea);
1024
+ }
1025
+
999
1026
  export const Input = forwardRef<HTMLTextAreaElement, ComposerInputProps>(function ComposerInput(
1000
1027
  { rows = 1, placeholder, className, "aria-label": ariaLabel, autoFocus = false, ...props },
1001
1028
  forwardedRef,
@@ -1005,22 +1032,17 @@ export const Input = forwardRef<HTMLTextAreaElement, ComposerInputProps>(functio
1005
1032
  const paletteOpen =
1006
1033
  controller.paletteEnabled && controller.paletteMounted && controller.palette.open;
1007
1034
 
1008
- // Native autoFocus loses when the textarea mounts disabled (create-session draft
1009
- // hydrate). Retry once the controller becomes interactive.
1035
+ // Focus once the controller is interactive. The textarea can mount disabled
1036
+ // (create-session draft hydrate), and the route can finish loading after the
1037
+ // person has already moved on, so this deliberately does not use the native
1038
+ // autoFocus attribute: that would take focus at mount without the guard.
1010
1039
  useEffect(() => {
1011
1040
  if (!autoFocus || controller.disabled || autoFocusedRef.current) return;
1012
1041
  const frame = window.requestAnimationFrame(() => {
1013
1042
  const textarea = controller.textareaRef.current;
1014
1043
  if (!textarea || textarea.disabled) return;
1015
1044
  autoFocusedRef.current = true;
1016
- // Draft hydration may finish after the user has started editing another
1017
- // field. Initial focus is a convenience, not authority to take their caret.
1018
- const active = textarea.ownerDocument.activeElement;
1019
- if (
1020
- active !== textarea &&
1021
- active?.closest('input, textarea, select, [contenteditable="true"], [role="textbox"]')
1022
- )
1023
- return;
1045
+ if (focusBelongsElsewhere(textarea)) return;
1024
1046
  textarea.focus();
1025
1047
  });
1026
1048
  return () => window.cancelAnimationFrame(frame);
@@ -1041,7 +1063,6 @@ export const Input = forwardRef<HTMLTextAreaElement, ComposerInputProps>(functio
1041
1063
  : (placeholder ?? controller.messages.messagePlaceholder)
1042
1064
  }
1043
1065
  disabled={controller.disabled}
1044
- autoFocus={autoFocus && !controller.disabled}
1045
1066
  aria-label={ariaLabel ?? controller.messages.inputLabel}
1046
1067
  aria-keyshortcuts="Enter Meta+Enter Control+Enter Shift+Enter"
1047
1068
  aria-autocomplete={
@@ -1679,6 +1700,7 @@ function ConfirmBar({
1679
1700
  function AttachmentChips({
1680
1701
  attachments,
1681
1702
  messages,
1703
+ disabled,
1682
1704
  onRemove,
1683
1705
  onRetry,
1684
1706
  onRetainPreview,
@@ -1686,6 +1708,7 @@ function AttachmentChips({
1686
1708
  }: {
1687
1709
  attachments: UseFileAttachmentsResult["attachments"];
1688
1710
  messages: ChatComposerMessages;
1711
+ disabled: boolean;
1689
1712
  onRemove: (id: string) => void;
1690
1713
  onRetry?: ((id: string) => void) | undefined;
1691
1714
  onRetainPreview: UseFileAttachmentsResult["retainPreview"];
@@ -1837,7 +1860,8 @@ function AttachmentChips({
1837
1860
  <button
1838
1861
  type="button"
1839
1862
  onClick={() => onRetry(attachment.id)}
1840
- className="shrink-0 rounded-og-xs p-1 text-og-fg-muted hover:bg-og-surface-1 hover:text-og-fg pointer-coarse:size-10"
1863
+ disabled={disabled}
1864
+ className="shrink-0 rounded-og-xs p-1 text-og-fg-muted hover:bg-og-surface-1 hover:text-og-fg disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:size-10"
1841
1865
  aria-label={messages.retryAttachment(attachment.name)}
1842
1866
  >
1843
1867
  <RotateCwIcon className="size-3.5" />
@@ -1847,7 +1871,8 @@ function AttachmentChips({
1847
1871
  <button
1848
1872
  type="button"
1849
1873
  onClick={() => onRemove(attachment.id)}
1850
- className="shrink-0 rounded-og-xs p-1 text-og-fg-muted hover:bg-og-surface-1 hover:text-og-fg pointer-coarse:size-10"
1874
+ disabled={disabled}
1875
+ className="shrink-0 rounded-og-xs p-1 text-og-fg-muted hover:bg-og-surface-1 hover:text-og-fg disabled:cursor-not-allowed disabled:opacity-50 pointer-coarse:size-10"
1851
1876
  aria-label={messages.removeAttachment(attachment.name)}
1852
1877
  >
1853
1878
  <XIcon className="size-3.5" />
@@ -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 enabled = options.enabled ?? true;
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",
@@ -163,6 +163,7 @@ export function useVoiceInput({
163
163
  const automaticRetryAttemptRef = useRef(0);
164
164
  const ownerHeartbeatTimerRef = useRef<ReturnType<typeof setInterval> | null>(null);
165
165
  const controllerRef = useRef<AbortController | null>(null);
166
+ const disabledRef = useRef(disabled);
166
167
  const valueRef = useRef(value);
167
168
  const storeRef = useRef<VoiceRecordingStore | null>(null);
168
169
  const storePromiseRef = useRef<Promise<VoiceRecordingStore> | null>(null);
@@ -176,6 +177,7 @@ export function useVoiceInput({
176
177
  const statusRef = useRef(status);
177
178
  valueRef.current = value;
178
179
  statusRef.current = status;
180
+ disabledRef.current = disabled;
179
181
  workspaceIdRef.current = workspaceId;
180
182
 
181
183
  const ensureOwnerId = useCallback(async (): Promise<string> => {
@@ -468,6 +470,14 @@ export function useVoiceInput({
468
470
  return;
469
471
  }
470
472
 
473
+ // Session creation can disable the composer while a recording is
474
+ // transcribing. Keep the durable transcript for explicit insertion
475
+ // later; never append text after Send captured its visible snapshot.
476
+ if (disabledRef.current) {
477
+ setStatus("transcript-ready");
478
+ return;
479
+ }
480
+
471
481
  const next = appendFinalTranscript(valueRef.current, response.text);
472
482
  if (next !== valueRef.current) {
473
483
  valueRef.current = next;
@@ -944,6 +954,7 @@ export function useVoiceInput({
944
954
  const insertSavedTranscript = useCallback(async (): Promise<void> => {
945
955
  const manifest = manifestRef.current;
946
956
  if (
957
+ disabledRef.current ||
947
958
  !manifest ||
948
959
  manifest.workspaceId !== workspaceId ||
949
960
  manifest.finalizationState !== "transcript-ready" ||
@@ -955,7 +966,7 @@ export function useVoiceInput({
955
966
  controllerRef.current?.abort();
956
967
  controllerRef.current = null;
957
968
  const store = await ensureStore();
958
- if (generation !== generationRef.current) return;
969
+ if (generation !== generationRef.current || disabledRef.current) return;
959
970
  const next = appendFinalTranscript(valueRef.current, manifest.transcriptText);
960
971
  if (next !== valueRef.current) {
961
972
  valueRef.current = next;
@@ -15,6 +15,8 @@ export function compactionSkipSubtitle(
15
15
  return "No active history to compact";
16
16
  case "replacement_not_smaller":
17
17
  return "Checkpoint would not reduce memory size";
18
+ case "replacement_exceeds_model_budget":
19
+ return "Checkpoint exceeds this model's context budget. Chat history is unchanged.";
18
20
  case "replacement_unchanged":
19
21
  return "Checkpoint made no progress";
20
22
  case "summarization_failed":
@@ -1016,6 +1016,7 @@ export function buildTimeline(
1016
1016
  case "tool.auth_needed":
1017
1017
  case "credential.auth_needed": {
1018
1018
  const capability = capabilityAuthorizationRequest(payload.capability);
1019
+ const setupRequest = customMcpSetupRequest(payload.setupRequest);
1019
1020
  if (
1020
1021
  event.type === "tool.auth_needed" &&
1021
1022
  !stringValue(payload.toolName) &&
@@ -1045,11 +1046,12 @@ export function buildTimeline(
1045
1046
  payload.connectionSubjectScope === "workspace"
1046
1047
  ? payload.connectionSubjectScope
1047
1048
  : null,
1048
- source: capability
1049
- ? "capability"
1050
- : event.type === "tool.auth_needed"
1051
- ? "tool"
1052
- : "credential",
1049
+ source:
1050
+ capability || setupRequest
1051
+ ? "capability"
1052
+ : event.type === "tool.auth_needed"
1053
+ ? "tool"
1054
+ : "credential",
1053
1055
  providerDomain: stringValue(payload.providerDomain),
1054
1056
  connectionId: typeof payload.connectionId === "string" ? payload.connectionId : null,
1055
1057
  authoritySource: payload.authoritySource === "host" ? "host" : null,
@@ -1064,6 +1066,7 @@ export function buildTimeline(
1064
1066
  authorizationUrl:
1065
1067
  typeof payload.authorizationUrl === "string" ? payload.authorizationUrl : null,
1066
1068
  capability,
1069
+ setupRequest,
1067
1070
  occurredAt: event.occurredAt,
1068
1071
  });
1069
1072
  break;
@@ -2623,6 +2626,29 @@ function capabilityAuthorizationRequest(
2623
2626
  };
2624
2627
  }
2625
2628
 
2629
+ function customMcpSetupRequest(value: unknown): NonNullable<AuthNeededItem["setupRequest"]> | null {
2630
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
2631
+ const input = value as Record<string, unknown>;
2632
+ if (
2633
+ input.kind !== "mcp" ||
2634
+ typeof input.name !== "string" ||
2635
+ typeof input.rationale !== "string" ||
2636
+ typeof input.endpointUrl !== "string"
2637
+ )
2638
+ return null;
2639
+ try {
2640
+ if (new URL(input.endpointUrl).protocol !== "https:") return null;
2641
+ } catch {
2642
+ return null;
2643
+ }
2644
+ return {
2645
+ kind: "mcp",
2646
+ name: input.name,
2647
+ endpointUrl: input.endpointUrl,
2648
+ rationale: input.rationale,
2649
+ };
2650
+ }
2651
+
2626
2652
  function stringList(value: unknown): string[] {
2627
2653
  return Array.isArray(value)
2628
2654
  ? value.filter((entry): entry is string => typeof entry === "string" && entry.trim().length > 0)
@@ -475,6 +475,8 @@ export type AuthNeededItem = {
475
475
  authorizationUrl: string | null;
476
476
  /** Agent-selected catalog recommendation. The host still owns authorization. */
477
477
  capability?: NonNullable<ToolAuthNeededPayload["capability"]> | null | undefined;
478
+ /** Human-reviewed custom integration proposal. Never treated as catalog authority. */
479
+ setupRequest?: ToolAuthNeededPayload["setupRequest"] | null | undefined;
478
480
  occurredAt: string;
479
481
  };
480
482