@opengeni/react 0.15.0 → 0.23.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 (103) hide show
  1. package/README.md +110 -10
  2. package/dist/{chunk-TOJR776I.js → chunk-6XUS5VFM.js} +45 -369
  3. package/dist/chunk-6XUS5VFM.js.map +1 -0
  4. package/dist/chunk-CATATY77.js +2393 -0
  5. package/dist/chunk-CATATY77.js.map +1 -0
  6. package/dist/chunk-I3BJZIG5.js +439 -0
  7. package/dist/chunk-I3BJZIG5.js.map +1 -0
  8. package/dist/chunk-TK7G6XLT.js +18 -0
  9. package/dist/chunk-TK7G6XLT.js.map +1 -0
  10. package/dist/chunk-UAHPKLB6.js +2237 -0
  11. package/dist/chunk-UAHPKLB6.js.map +1 -0
  12. package/dist/chunk-UQS7OQDE.js +591 -0
  13. package/dist/chunk-UQS7OQDE.js.map +1 -0
  14. package/dist/chunk-YDNWMKXO.js +102 -0
  15. package/dist/chunk-YDNWMKXO.js.map +1 -0
  16. package/dist/composer-CVm5uoUI.d.ts +585 -0
  17. package/dist/composer.d.ts +6 -0
  18. package/dist/composer.js +56 -0
  19. package/dist/composer.js.map +1 -0
  20. package/dist/index.d.ts +1288 -1785
  21. package/dist/index.js +7140 -8817
  22. package/dist/index.js.map +1 -1
  23. package/dist/machines.d.ts +412 -3
  24. package/dist/machines.js +3 -1
  25. package/dist/noto-sans-arabic-loader-IA2T4X2A.js +21 -0
  26. package/dist/noto-sans-arabic-loader-IA2T4X2A.js.map +1 -0
  27. package/dist/noto-sans-jp-loader.generated-HFGLYBR3.js +292 -0
  28. package/dist/noto-sans-jp-loader.generated-HFGLYBR3.js.map +1 -0
  29. package/dist/queue-surface-implementation-3UW3MIIR.js +890 -0
  30. package/dist/queue-surface-implementation-3UW3MIIR.js.map +1 -0
  31. package/dist/session-Db00TRwl.d.ts +446 -0
  32. package/dist/session-context-rgrfElMl.d.ts +58 -0
  33. package/dist/session.d.ts +4 -0
  34. package/dist/session.js +54 -0
  35. package/dist/session.js.map +1 -0
  36. package/dist/use-composer-By1wn3MM.d.ts +99 -0
  37. package/package.json +20 -4
  38. package/src/approvals.ts +5 -3
  39. package/src/client.ts +44 -0
  40. package/src/components/approval-surface.tsx +198 -0
  41. package/src/components/chat-composer.tsx +94 -993
  42. package/src/components/code-editor.tsx +104 -26
  43. package/src/components/command-palette.tsx +8 -2
  44. package/src/components/composer-transcription-control.tsx +211 -0
  45. package/src/components/composer.tsx +1536 -0
  46. package/src/components/desktop-viewer.tsx +3 -3
  47. package/src/components/enrollment-consent.tsx +2 -2
  48. package/src/components/file-browser.tsx +367 -29
  49. package/src/components/human-input-form.tsx +417 -0
  50. package/src/components/machine-card.tsx +1 -1
  51. package/src/components/machines-dashboard.tsx +3 -3
  52. package/src/components/message-timeline.tsx +205 -25
  53. package/src/components/model-picker.tsx +12 -3
  54. package/src/components/pierre-diff.tsx +32 -6
  55. package/src/components/queue-draft-policy.ts +18 -0
  56. package/src/components/queue-surface-implementation.tsx +1037 -0
  57. package/src/components/queue-surface-state.tsx +94 -0
  58. package/src/components/queue-surface.tsx +42 -559
  59. package/src/components/sandbox-files.tsx +138 -14
  60. package/src/components/sandbox-terminal.tsx +8 -2
  61. package/src/components/sandbox-workspace.tsx +339 -119
  62. package/src/components/workbench-changes.tsx +84 -33
  63. package/src/components/workspace-dock.tsx +188 -18
  64. package/src/composer.ts +60 -0
  65. package/src/hooks/internal.ts +115 -34
  66. package/src/hooks/use-composer.ts +385 -104
  67. package/src/hooks/use-human-input.ts +131 -0
  68. package/src/hooks/use-machine-chip.ts +2 -2
  69. package/src/hooks/use-machines.ts +27 -11
  70. package/src/hooks/use-sandbox-files.ts +310 -62
  71. package/src/hooks/use-sandbox-git.ts +154 -40
  72. package/src/hooks/use-sandbox-terminal.ts +28 -6
  73. package/src/hooks/use-session-capabilities.ts +38 -9
  74. package/src/hooks/use-session-control.ts +37 -8
  75. package/src/hooks/use-session-events.ts +522 -48
  76. package/src/hooks/use-session-mcp-approval-policy.ts +161 -0
  77. package/src/hooks/use-slash-commands.ts +38 -38
  78. package/src/hooks/use-transcription.ts +757 -0
  79. package/src/hooks/use-turn-queue.ts +134 -66
  80. package/src/hooks/use-windowed-sections.ts +2 -2
  81. package/src/hooks/use-workspace-capture.ts +146 -40
  82. package/src/hooks/use-workspace-edit.ts +50 -14
  83. package/src/hooks/use-workspace-sessions.ts +3 -0
  84. package/src/human-input.ts +72 -0
  85. package/src/index.ts +85 -6
  86. package/src/lib/noto-sans-arabic-loader.ts +21 -0
  87. package/src/lib/noto-sans-jp-loader.generated.ts +295 -0
  88. package/src/lib/use-portal-token-style.ts +51 -0
  89. package/src/lib/use-unicode-fonts.ts +57 -0
  90. package/src/provider.tsx +4 -69
  91. package/src/session-context.ts +160 -0
  92. package/src/session.ts +89 -0
  93. package/src/timeline/parsers.ts +35 -0
  94. package/src/timeline/projection.ts +12 -4
  95. package/src/timeline/tool-renderers.tsx +41 -0
  96. package/src/timeline/turn-summary.tsx +2 -2
  97. package/src/timeline/types.ts +10 -9
  98. package/styles/index.css +56 -0
  99. package/styles/index.d.ts +2 -0
  100. package/styles/tokens.css +11 -6
  101. package/styles/tokens.d.ts +2 -0
  102. package/dist/chunk-TOJR776I.js.map +0 -1
  103. package/dist/machines-BpdwuQcD.d.ts +0 -449
@@ -2,8 +2,8 @@ import { useCallback, useEffect, useRef, useState } from "react";
2
2
  import { useOpenGeni, type ClientOverride } from "../provider";
3
3
 
4
4
  /**
5
- * The wake-on-edit state machine (dossier §3 #5 / §10.4 / §12-C). M3 OWNS this
6
- * logic; M5 renders it. States are explicit and exhaustive:
5
+ * The wake-on-edit state machine. This hook owns the logic and the UI renders
6
+ * its result. States are explicit and exhaustive:
7
7
  *
8
8
  * viewing-cold — cold snapshot open, no local edits (editable, cloud).
9
9
  * buffering — local edits buffered; the box is not warm and no warm is
@@ -80,11 +80,11 @@ function isLive(liveness: string | undefined): boolean {
80
80
  * Cold-editing without a spinner: the editor opens instantly from the capture,
81
81
  * the first keystroke buffers locally AND signals the host to warm the box, and on
82
82
  * warm the buffer flushes IF the live file still matches the captured base — else a
83
- * non-blocking conflict is surfaced and nothing is overwritten (dossier §12-C2).
83
+ * non-blocking conflict is surfaced and nothing is overwritten.
84
84
  *
85
85
  * The base guard compares the live content at flush time against `baseContent`
86
86
  * (the exact bytes the editor loaded). This is equivalent to the sha256 compare
87
- * the dossier describes — M1 leaves `baseHash` null, so the loaded content IS the
87
+ * the design specifies — M1 leaves `baseHash` null, so the loaded content IS the
88
88
  * authoritative base — and avoids async Web Crypto in the render path.
89
89
  */
90
90
  export function useWorkspaceEdit(
@@ -96,31 +96,48 @@ export function useWorkspaceEdit(
96
96
  const baseContent = options.baseContent ?? null;
97
97
  const live = isLive(options.liveness);
98
98
  const offline = options.offline === true;
99
+ const identityKey = `${workspaceId}\u0000${sessionId ?? ""}\u0000${path ?? ""}`;
99
100
 
100
101
  const [buffer, setBuffer] = useState<string | null>(null);
101
102
  const [wantsWarm, setWantsWarm] = useState(false);
102
103
  const [flush, setFlush] = useState<"idle" | "flushing" | "flushed" | "conflict">("idle");
103
104
  const [conflict, setConflict] = useState<WorkspaceEditConflict | null>(null);
104
105
  const [error, setError] = useState<Error | null>(null);
106
+ const [stateIdentity, setStateIdentity] = useState(identityKey);
105
107
 
106
108
  const onWarmRequested = options.onWarmRequested;
107
109
  const bufferRef = useRef<string | null>(null);
108
110
  bufferRef.current = buffer;
109
111
  // Guards a single in-flight flush against re-entry (liveness can re-render).
110
112
  const flushingRef = useRef(false);
113
+ const identityGenerationRef = useRef(0);
114
+ const flushAttemptRef = useRef(0);
115
+ const readAbortRef = useRef<AbortController | null>(null);
111
116
 
112
117
  // Reset the machine when the edited file (or session) changes.
113
118
  useEffect(() => {
119
+ identityGenerationRef.current += 1;
120
+ flushAttemptRef.current += 1;
121
+ readAbortRef.current?.abort();
122
+ readAbortRef.current = null;
123
+ setStateIdentity(identityKey);
114
124
  setBuffer(null);
115
125
  setWantsWarm(false);
116
126
  setFlush("idle");
117
127
  setConflict(null);
118
128
  setError(null);
119
129
  flushingRef.current = false;
120
- }, [sessionId, path]);
130
+ return () => {
131
+ identityGenerationRef.current += 1;
132
+ flushAttemptRef.current += 1;
133
+ readAbortRef.current?.abort();
134
+ readAbortRef.current = null;
135
+ };
136
+ }, [identityKey]);
121
137
 
122
138
  const edit = useCallback(
123
139
  (content: string) => {
140
+ if (stateIdentity !== identityKey) return;
124
141
  if (offline) return; // read-only — no buffering, no wake.
125
142
  setBuffer((prev) => (prev === content ? prev : content));
126
143
  // Any new edit invalidates a prior flushed/conflict resolution.
@@ -134,15 +151,18 @@ export function useWorkspaceEdit(
134
151
  });
135
152
  }
136
153
  },
137
- [offline, live, onWarmRequested],
154
+ [offline, live, onWarmRequested, stateIdentity, identityKey],
138
155
  );
139
156
 
140
157
  const doFlush = useCallback(
141
158
  async (force: boolean) => {
159
+ if (stateIdentity !== identityKey) return;
142
160
  if (!sessionId || !path) return;
143
161
  const content = bufferRef.current;
144
162
  if (content === null) return;
145
163
  if (flushingRef.current) return;
164
+ const identityGeneration = identityGenerationRef.current;
165
+ const flushAttempt = (flushAttemptRef.current += 1);
146
166
  flushingRef.current = true;
147
167
  setFlush("flushing");
148
168
  setError(null);
@@ -151,7 +171,17 @@ export function useWorkspaceEdit(
151
171
  // Base guard: re-read the live file and compare to the captured base. A
152
172
  // divergence means the box changed under us — surface a conflict, write
153
173
  // NOTHING (C2).
154
- const liveRead = await client.fsRead(workspaceId, sessionId, { path });
174
+ const readAbort = new AbortController();
175
+ readAbortRef.current?.abort();
176
+ readAbortRef.current = readAbort;
177
+ const liveRead = await client.fsRead(
178
+ workspaceId,
179
+ sessionId,
180
+ { path },
181
+ { signal: readAbort.signal },
182
+ );
183
+ if (readAbortRef.current === readAbort) readAbortRef.current = null;
184
+ if (identityGenerationRef.current !== identityGeneration) return;
155
185
  const liveContent = liveRead.content;
156
186
  if (baseContent !== null && liveContent !== baseContent) {
157
187
  setConflict({ path, base: baseContent, live: liveContent });
@@ -160,18 +190,23 @@ export function useWorkspaceEdit(
160
190
  }
161
191
  }
162
192
  await client.fsWrite(workspaceId, sessionId, { path, content, overwrite: true });
193
+ if (identityGenerationRef.current !== identityGeneration) return;
163
194
  setConflict(null);
164
195
  setFlush("flushed");
165
196
  setWantsWarm(false);
166
197
  } catch (cause) {
198
+ if (identityGenerationRef.current !== identityGeneration) return;
167
199
  setError(cause instanceof Error ? cause : new Error(String(cause)));
168
200
  // Leave the buffer intact so the user can retry; fall back to buffering.
169
201
  setFlush("idle");
170
202
  } finally {
171
- flushingRef.current = false;
203
+ if (flushAttemptRef.current === flushAttempt) {
204
+ flushingRef.current = false;
205
+ readAbortRef.current = null;
206
+ }
172
207
  }
173
208
  },
174
- [client, workspaceId, sessionId, path, baseContent],
209
+ [client, workspaceId, sessionId, path, baseContent, stateIdentity, identityKey],
175
210
  );
176
211
 
177
212
  // Auto-flush once the box is warm and a buffer is pending (the wake completed).
@@ -217,15 +252,16 @@ export function useWorkspaceEdit(
217
252
  state = "buffering";
218
253
  }
219
254
 
255
+ const identityMatches = stateIdentity === identityKey;
220
256
  return {
221
- state,
257
+ state: identityMatches ? state : offline ? "readonly-offline" : "viewing-cold",
222
258
  readOnly: offline,
223
- buffer,
224
- wantsWarm,
259
+ buffer: identityMatches ? buffer : null,
260
+ wantsWarm: identityMatches && wantsWarm,
225
261
  edit,
226
- conflict,
262
+ conflict: identityMatches ? conflict : null,
227
263
  overwrite,
228
264
  discard,
229
- error,
265
+ error: identityMatches ? error : null,
230
266
  };
231
267
  }
@@ -21,6 +21,8 @@ export type UseWorkspaceSessionsResult = {
21
21
  sessions: Session[];
22
22
  /** The complete personal pinned section, also present in `sessions`. */
23
23
  pinned: Session[];
24
+ /** True when the server omitted older pins from its bounded pinned section. */
25
+ pinnedTruncated: boolean;
24
26
  nextCursor: string | null;
25
27
  loading: boolean;
26
28
  error: Error | null;
@@ -65,6 +67,7 @@ export function useWorkspaceSessions(
65
67
  // itself continues to paginate only `ordinary` rows via nextCursor.
66
68
  sessions: [...pinned, ...ordinary],
67
69
  pinned,
70
+ pinnedTruncated: page?.pinnedTruncated ?? false,
68
71
  nextCursor: page?.nextCursor ?? null,
69
72
  loading: state.loading,
70
73
  error: state.error,
@@ -0,0 +1,72 @@
1
+ import type { HumanInputQuestion, SessionEvent } from "@opengeni/sdk";
2
+
3
+ /** Minimal actionable request shape available from the durable event log. */
4
+ export type PendingHumanInputRequest = {
5
+ id: string;
6
+ turnId: string | null;
7
+ questions: HumanInputQuestion[];
8
+ allowSkip: boolean;
9
+ expiresAt: string | null;
10
+ };
11
+
12
+ /** Parse one generic `session.humanInput.requested` event defensively. */
13
+ export function humanInputRequestFromEvent(
14
+ event: Pick<SessionEvent, "type" | "payload" | "turnId">,
15
+ ): PendingHumanInputRequest | null {
16
+ if (event.type !== "session.humanInput.requested" || !isRecord(event.payload)) return null;
17
+ const request = event.payload.request;
18
+ if (!isRecord(request) || typeof request.id !== "string" || !Array.isArray(request.questions)) {
19
+ return null;
20
+ }
21
+ return {
22
+ id: request.id,
23
+ turnId: event.turnId ?? null,
24
+ questions: request.questions as HumanInputQuestion[],
25
+ allowSkip: request.allowSkip === true,
26
+ expiresAt: typeof request.expiresAt === "string" ? request.expiresAt : null,
27
+ };
28
+ }
29
+
30
+ /**
31
+ * Fold a durable event log into the structured requests that are actionable
32
+ * now. Responses remove one request; a terminal owning turn clears any
33
+ * unresolved requests that died with it. Replaying history cannot resurrect a
34
+ * previously answered card.
35
+ */
36
+ export function projectPendingHumanInputRequests(
37
+ events: SessionEvent[],
38
+ ): PendingHumanInputRequest[] {
39
+ const pending = new Map<string, PendingHumanInputRequest>();
40
+ for (const event of events) {
41
+ const requested = humanInputRequestFromEvent(event);
42
+ if (requested) {
43
+ pending.set(requested.id, requested);
44
+ continue;
45
+ }
46
+ if (event.type === "user.humanInputResponse" && isRecord(event.payload)) {
47
+ if (typeof event.payload.requestId === "string") pending.delete(event.payload.requestId);
48
+ continue;
49
+ }
50
+ if (
51
+ event.type === "turn.completed" ||
52
+ event.type === "turn.failed" ||
53
+ event.type === "turn.cancelled"
54
+ ) {
55
+ for (const [id, request] of pending) {
56
+ if (
57
+ request.turnId === null ||
58
+ event.turnId === null ||
59
+ event.turnId === undefined ||
60
+ request.turnId === event.turnId
61
+ ) {
62
+ pending.delete(id);
63
+ }
64
+ }
65
+ }
66
+ }
67
+ return [...pending.values()];
68
+ }
69
+
70
+ function isRecord(value: unknown): value is Record<string, unknown> {
71
+ return Boolean(value) && typeof value === "object" && !Array.isArray(value);
72
+ }
package/src/index.ts CHANGED
@@ -7,15 +7,31 @@
7
7
  // @import "@opengeni/react/styles.css";
8
8
  // @source "../node_modules/@opengeni/react/src";
9
9
 
10
- export type { SessionClientLike } from "./client";
11
- export { OpenGeniProvider, useOpenGeni, useOpenGeniClient } from "./provider";
12
- export type { ClientOverride, OpenGeniContextValue, OpenGeniProviderProps } from "./provider";
10
+ export type {
11
+ EmbeddedHumanInputSessionClientLike,
12
+ EmbeddedSessionMcpApprovalPolicyClientLike,
13
+ EmbeddedSessionClientLike,
14
+ SessionClientLike,
15
+ } from "./client";
16
+ export { OpenGeniProvider } from "./provider";
17
+ export type { OpenGeniProviderProps } from "./provider";
18
+ export { useOpenGeni, useOpenGeniClient } from "./session-context";
19
+ export type { ClientOverride, OpenGeniContextValue } from "./session-context";
13
20
 
14
21
  // Hooks
15
22
  export { useSession, isTitleEvent } from "./hooks/use-session";
16
23
  export type { UseSessionOptions, UseSessionResult } from "./hooks/use-session";
17
- export { useSessionEvents } from "./hooks/use-session-events";
24
+ export {
25
+ SESSION_EVENT_BROWSER_MAX_BYTES,
26
+ SESSION_EVENT_BROWSER_MAX_COUNT,
27
+ SESSION_EVENT_BROWSER_PENDING_MAX_BYTES,
28
+ SESSION_EVENT_BROWSER_PENDING_MAX_COUNT,
29
+ SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES,
30
+ boundBrowserSessionEventWindow,
31
+ useSessionEvents,
32
+ } from "./hooks/use-session-events";
18
33
  export type {
34
+ BrowserSessionEventWindow,
19
35
  SessionEventsConnectionState,
20
36
  UseSessionEventsOptions,
21
37
  UseSessionEventsResult,
@@ -28,6 +44,19 @@ export {
28
44
  FILE_ONLY_MESSAGE_TEXT,
29
45
  } from "./hooks/use-composer";
30
46
  export type { ComposerSendExtras, ComposerState, UseComposerOptions } from "./hooks/use-composer";
47
+ export {
48
+ INITIAL_TRANSCRIPTION_CONTROL_STATE,
49
+ appendFinalTranscript,
50
+ transitionTranscriptionControl,
51
+ useTranscription,
52
+ } from "./hooks/use-transcription";
53
+ export type {
54
+ TranscriptionControlAction,
55
+ TranscriptionControlState,
56
+ TranscriptionControlTransition,
57
+ UseTranscriptionOptions,
58
+ UseTranscriptionResult,
59
+ } from "./hooks/use-transcription";
31
60
  export { useFileAttachments } from "./hooks/use-file-attachments";
32
61
  export type {
33
62
  FileAttachment,
@@ -50,6 +79,14 @@ export type {
50
79
  UseSessionControlOptions,
51
80
  UseSessionControlResult,
52
81
  } from "./hooks/use-session-control";
82
+ export {
83
+ isSessionMcpApprovalPolicyEvent,
84
+ useSessionMcpApprovalPolicy,
85
+ } from "./hooks/use-session-mcp-approval-policy";
86
+ export type {
87
+ UseSessionMcpApprovalPolicyOptions,
88
+ UseSessionMcpApprovalPolicyResult,
89
+ } from "./hooks/use-session-mcp-approval-policy";
53
90
  export { useScheduledTasks } from "./hooks/use-scheduled-tasks";
54
91
  export type {
55
92
  UseScheduledTasksOptions,
@@ -123,15 +160,25 @@ export type {
123
160
  UseSandboxTerminalOptions,
124
161
  UseSandboxTerminalResult,
125
162
  } from "./hooks/use-sandbox-terminal";
126
- export { useSandboxFiles } from "./hooks/use-sandbox-files";
163
+ export {
164
+ CapturedFileUnavailableError,
165
+ FileWriteConflictError,
166
+ useSandboxFiles,
167
+ } from "./hooks/use-sandbox-files";
127
168
  export type {
169
+ CapturedFileUnavailableReason,
128
170
  FileTreeNode,
129
171
  FileTreeStatus,
172
+ SandboxWriteFileOptions,
130
173
  UseSandboxFilesOptions,
131
174
  UseSandboxFilesResult,
132
175
  } from "./hooks/use-sandbox-files";
133
176
  export { useSandboxGit } from "./hooks/use-sandbox-git";
134
- export type { UseSandboxGitOptions, UseSandboxGitResult } from "./hooks/use-sandbox-git";
177
+ export type {
178
+ SandboxGitFileDiff,
179
+ UseSandboxGitOptions,
180
+ UseSandboxGitResult,
181
+ } from "./hooks/use-sandbox-git";
135
182
 
136
183
  // Workbench v2 turn-end capture (the cold-paint data layer)
137
184
  export { useWorkspaceCapture } from "./hooks/use-workspace-capture";
@@ -157,6 +204,27 @@ export type {
157
204
  // Pending-approvals projection
158
205
  export { approvalsFromRequiresAction, projectPendingApprovals } from "./approvals";
159
206
  export type { PendingApproval } from "./approvals";
207
+ export { ApprovalSurface, defaultApprovalSurfaceMessages } from "./components/approval-surface";
208
+ export type { ApprovalSurfaceMessages, ApprovalSurfaceProps } from "./components/approval-surface";
209
+
210
+ // Structured human input: event projection, authoritative hook, and styled form.
211
+ export { humanInputRequestFromEvent, projectPendingHumanInputRequests } from "./human-input";
212
+ export type { PendingHumanInputRequest } from "./human-input";
213
+ export { isHumanInputEvent, useHumanInputRequests } from "./hooks/use-human-input";
214
+ export type {
215
+ UseHumanInputRequestsOptions,
216
+ UseHumanInputRequestsResult,
217
+ } from "./hooks/use-human-input";
218
+ export {
219
+ HumanInputForm,
220
+ answersFromDrafts,
221
+ defaultHumanInputFormMessages,
222
+ } from "./components/human-input-form";
223
+ export type {
224
+ HumanInputAnswerDraft,
225
+ HumanInputFormMessages,
226
+ HumanInputFormProps,
227
+ } from "./components/human-input-form";
160
228
 
161
229
  // Timeline projection
162
230
  export {
@@ -275,6 +343,13 @@ export type { CommandPaletteProps } from "./components/command-palette";
275
343
  // Components
276
344
  export { ChatComposer } from "./components/chat-composer";
277
345
  export type { ChatComposerProps } from "./components/chat-composer";
346
+ export { ComposerTranscriptionControl } from "./components/composer-transcription-control";
347
+ export type {
348
+ ComposerTranscriptionControlProps,
349
+ ComposerTranscriptionMessages,
350
+ } from "./components/composer-transcription-control";
351
+ export { defaultChatComposerMessages } from "./components/composer";
352
+ export type { ChatComposerMessages } from "./components/composer";
278
353
  export { ModelPicker } from "./components/model-picker";
279
354
  export type { ModelPickerProps } from "./components/model-picker";
280
355
  export { MessageTimeline, TimelineRow } from "./components/message-timeline";
@@ -318,8 +393,12 @@ export {
318
393
  initialWorkspaceTab,
319
394
  WORKBENCH_TAB_CHANGES,
320
395
  WORKBENCH_TAB_FILES,
396
+ WORKBENCH_TAB_TERMINAL,
397
+ WORKBENCH_TAB_DESKTOP,
398
+ WORKBENCH_SURFACES,
321
399
  } from "./components/sandbox-workspace";
322
400
  export type {
401
+ SandboxWorkspaceSurface,
323
402
  SandboxWorkspaceProps,
324
403
  UseSandboxWorkspaceTabsOptions,
325
404
  UseSandboxWorkspaceTabsResult,
@@ -0,0 +1,21 @@
1
+ import fontUrl from "@fontsource-variable/noto-sans-arabic/files/noto-sans-arabic-arabic-wght-normal.woff2";
2
+ import unicodeRanges from "@fontsource-variable/noto-sans-arabic/unicode.json";
3
+
4
+ let loading: Promise<void> | undefined;
5
+
6
+ export function loadNotoSansArabic(): Promise<void> {
7
+ loading ??= new FontFace(
8
+ "Noto Sans Arabic Variable",
9
+ `url("${fontUrl}") format("woff2-variations")`,
10
+ { style: "normal", weight: "100 900", unicodeRange: unicodeRanges.arabic },
11
+ )
12
+ .load()
13
+ .then((font) => {
14
+ document.fonts.add(font);
15
+ })
16
+ .catch((error: unknown) => {
17
+ loading = undefined;
18
+ throw error;
19
+ });
20
+ return loading;
21
+ }