@opengeni/react 0.13.0 → 0.20.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 (113) hide show
  1. package/README.md +129 -23
  2. package/dist/chunk-6BXBGJ3B.js +1956 -0
  3. package/dist/chunk-6BXBGJ3B.js.map +1 -0
  4. package/dist/chunk-6UT5OC3P.js +2133 -0
  5. package/dist/chunk-6UT5OC3P.js.map +1 -0
  6. package/dist/chunk-EBTT3MYN.js +92 -0
  7. package/dist/chunk-EBTT3MYN.js.map +1 -0
  8. package/dist/chunk-HHFA4AFX.js +425 -0
  9. package/dist/chunk-HHFA4AFX.js.map +1 -0
  10. package/dist/chunk-LAZ2A743.js +2393 -0
  11. package/dist/chunk-LAZ2A743.js.map +1 -0
  12. package/dist/chunk-MRSECXRP.js +563 -0
  13. package/dist/chunk-MRSECXRP.js.map +1 -0
  14. package/dist/chunk-TK7G6XLT.js +18 -0
  15. package/dist/chunk-TK7G6XLT.js.map +1 -0
  16. package/dist/composer-DvUaa5ki.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 +642 -1080
  21. package/dist/index.js +7672 -8005
  22. package/dist/index.js.map +1 -1
  23. package/dist/machines.d.ts +412 -3
  24. package/dist/machines.js +25 -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-NQMV2ML3.js +890 -0
  30. package/dist/queue-surface-implementation-NQMV2ML3.js.map +1 -0
  31. package/dist/session-Gd4iVPYw.d.ts +425 -0
  32. package/dist/session-context-DIFFlXKc.d.ts +52 -0
  33. package/dist/session.d.ts +4 -0
  34. package/dist/session.js +50 -0
  35. package/dist/session.js.map +1 -0
  36. package/dist/use-composer-BCfm4mzX.d.ts +112 -0
  37. package/package.json +23 -4
  38. package/src/approvals.ts +5 -3
  39. package/src/client.ts +50 -1
  40. package/src/components/approval-surface.tsx +198 -0
  41. package/src/components/chat-composer.tsx +104 -751
  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 +82 -16
  51. package/src/components/machine-health-pill.tsx +68 -0
  52. package/src/components/machine-metrics.tsx +10 -24
  53. package/src/components/machines/health.ts +146 -0
  54. package/src/components/machines/machine-detail.tsx +220 -0
  55. package/src/components/machines/metric-history-chart.tsx +298 -0
  56. package/src/components/machines/metric-sparkline.tsx +76 -0
  57. package/src/components/machines/series.ts +113 -0
  58. package/src/components/machines-dashboard.tsx +16 -4
  59. package/src/components/message-timeline.tsx +15 -7
  60. package/src/components/model-picker.tsx +12 -3
  61. package/src/components/pierre-diff.tsx +32 -6
  62. package/src/components/queue-surface-implementation.tsx +1041 -0
  63. package/src/components/queue-surface-state.tsx +94 -0
  64. package/src/components/queue-surface.tsx +60 -0
  65. package/src/components/sandbox-files.tsx +230 -21
  66. package/src/components/sandbox-terminal.tsx +8 -2
  67. package/src/components/sandbox-workspace.tsx +489 -135
  68. package/src/components/session-status.tsx +0 -6
  69. package/src/components/workbench-changes.tsx +145 -50
  70. package/src/components/workspace-dock.tsx +329 -68
  71. package/src/composer.ts +60 -0
  72. package/src/hooks/internal.ts +115 -34
  73. package/src/hooks/use-composer.ts +635 -74
  74. package/src/hooks/use-human-input.ts +131 -0
  75. package/src/hooks/use-machine-chip.ts +2 -2
  76. package/src/hooks/use-machines.ts +27 -11
  77. package/src/hooks/use-sandbox-files.ts +310 -62
  78. package/src/hooks/use-sandbox-git.ts +154 -40
  79. package/src/hooks/use-sandbox-terminal.ts +28 -6
  80. package/src/hooks/use-session-capabilities.ts +38 -9
  81. package/src/hooks/use-session-control.ts +43 -15
  82. package/src/hooks/use-session-events.ts +523 -48
  83. package/src/hooks/use-session-lineage.ts +15 -6
  84. package/src/hooks/use-session.ts +10 -2
  85. package/src/hooks/use-slash-commands.ts +38 -38
  86. package/src/hooks/use-transcription.ts +757 -0
  87. package/src/hooks/use-turn-queue.ts +273 -77
  88. package/src/hooks/use-windowed-sections.ts +2 -2
  89. package/src/hooks/use-workspace-capture.ts +146 -40
  90. package/src/hooks/use-workspace-edit.ts +50 -14
  91. package/src/hooks/use-workspace-sessions.ts +3 -0
  92. package/src/human-input.ts +72 -0
  93. package/src/index.ts +88 -12
  94. package/src/lib/noto-sans-arabic-loader.ts +21 -0
  95. package/src/lib/noto-sans-jp-loader.generated.ts +295 -0
  96. package/src/lib/use-portal-token-style.ts +51 -0
  97. package/src/lib/use-unicode-fonts.ts +57 -0
  98. package/src/machines.ts +16 -0
  99. package/src/provider.tsx +163 -41
  100. package/src/session-context.ts +135 -0
  101. package/src/session.ts +79 -0
  102. package/src/timeline/parsers.ts +78 -6
  103. package/src/timeline/projection.ts +36 -6
  104. package/src/timeline/tool-renderers.tsx +41 -0
  105. package/src/timeline/turn-summary.tsx +2 -2
  106. package/src/timeline/types.ts +16 -8
  107. package/styles/index.css +78 -0
  108. package/styles/index.d.ts +2 -0
  109. package/styles/tokens.css +11 -6
  110. package/styles/tokens.d.ts +2 -0
  111. package/dist/chunk-NFYVQWIB.js +0 -1377
  112. package/dist/chunk-NFYVQWIB.js.map +0 -1
  113. package/dist/machines-CnlMb7E-.d.ts +0 -329
@@ -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
@@ -1,18 +1,36 @@
1
+ // oxlint-disable-next-line typescript/triple-slash-reference -- package consumers must load the ambient type for this optional untyped peer without emitting a runtime import.
2
+ /// <reference path="./types/external.d.ts" />
3
+
1
4
  // @opengeni/react — hooks + styled components on @opengeni/sdk.
2
5
  //
3
6
  // Import the styles once in your Tailwind entry CSS:
4
7
  // @import "@opengeni/react/styles.css";
5
8
  // @source "../node_modules/@opengeni/react/src";
6
9
 
7
- export type { SessionClientLike } from "./client";
8
- export { OpenGeniProvider, useOpenGeni, useOpenGeniClient } from "./provider";
9
- export type { ClientOverride, OpenGeniContextValue, OpenGeniProviderProps } from "./provider";
10
+ export type {
11
+ EmbeddedHumanInputSessionClientLike,
12
+ EmbeddedSessionClientLike,
13
+ SessionClientLike,
14
+ } from "./client";
15
+ export { OpenGeniProvider } from "./provider";
16
+ export type { OpenGeniProviderProps } from "./provider";
17
+ export { useOpenGeni, useOpenGeniClient } from "./session-context";
18
+ export type { ClientOverride, OpenGeniContextValue } from "./session-context";
10
19
 
11
20
  // Hooks
12
21
  export { useSession, isTitleEvent } from "./hooks/use-session";
13
22
  export type { UseSessionOptions, UseSessionResult } from "./hooks/use-session";
14
- export { useSessionEvents } from "./hooks/use-session-events";
23
+ export {
24
+ SESSION_EVENT_BROWSER_MAX_BYTES,
25
+ SESSION_EVENT_BROWSER_MAX_COUNT,
26
+ SESSION_EVENT_BROWSER_PENDING_MAX_BYTES,
27
+ SESSION_EVENT_BROWSER_PENDING_MAX_COUNT,
28
+ SESSION_EVENT_BROWSER_SINGLE_EVENT_MAX_BYTES,
29
+ boundBrowserSessionEventWindow,
30
+ useSessionEvents,
31
+ } from "./hooks/use-session-events";
15
32
  export type {
33
+ BrowserSessionEventWindow,
16
34
  SessionEventsConnectionState,
17
35
  UseSessionEventsOptions,
18
36
  UseSessionEventsResult,
@@ -20,15 +38,24 @@ export type {
20
38
  export {
21
39
  useComposer,
22
40
  composeSendInput,
41
+ shouldSteerOnKey,
23
42
  shouldSubmitOnKey,
24
43
  FILE_ONLY_MESSAGE_TEXT,
25
44
  } from "./hooks/use-composer";
45
+ export type { ComposerSendExtras, ComposerState, UseComposerOptions } from "./hooks/use-composer";
46
+ export {
47
+ INITIAL_TRANSCRIPTION_CONTROL_STATE,
48
+ appendFinalTranscript,
49
+ transitionTranscriptionControl,
50
+ useTranscription,
51
+ } from "./hooks/use-transcription";
26
52
  export type {
27
- ComposerMode,
28
- ComposerSendExtras,
29
- ComposerState,
30
- UseComposerOptions,
31
- } from "./hooks/use-composer";
53
+ TranscriptionControlAction,
54
+ TranscriptionControlState,
55
+ TranscriptionControlTransition,
56
+ UseTranscriptionOptions,
57
+ UseTranscriptionResult,
58
+ } from "./hooks/use-transcription";
32
59
  export { useFileAttachments } from "./hooks/use-file-attachments";
33
60
  export type {
34
61
  FileAttachment,
@@ -36,7 +63,14 @@ export type {
36
63
  UseFileAttachmentsResult,
37
64
  } from "./hooks/use-file-attachments";
38
65
  export { useTurnQueue, isTurnQueueEvent } from "./hooks/use-turn-queue";
39
- export type { UseTurnQueueOptions, UseTurnQueueResult } from "./hooks/use-turn-queue";
66
+ export type {
67
+ QueueMutationKind,
68
+ UseTurnQueueOptions,
69
+ UseTurnQueueResult,
70
+ } from "./hooks/use-turn-queue";
71
+ export { QueueSurface } from "./components/queue-surface";
72
+ export type { QueueSurfaceProps } from "./components/queue-surface";
73
+ export { OPEN_WORKSTREAM_CONTROL_EVENT } from "./components/chat-composer";
40
74
  export { useGoal, isGoalEvent } from "./hooks/use-goal";
41
75
  export type { UseGoalOptions, UseGoalResult } from "./hooks/use-goal";
42
76
  export { useSessionControl } from "./hooks/use-session-control";
@@ -117,15 +151,25 @@ export type {
117
151
  UseSandboxTerminalOptions,
118
152
  UseSandboxTerminalResult,
119
153
  } from "./hooks/use-sandbox-terminal";
120
- export { useSandboxFiles } from "./hooks/use-sandbox-files";
154
+ export {
155
+ CapturedFileUnavailableError,
156
+ FileWriteConflictError,
157
+ useSandboxFiles,
158
+ } from "./hooks/use-sandbox-files";
121
159
  export type {
160
+ CapturedFileUnavailableReason,
122
161
  FileTreeNode,
123
162
  FileTreeStatus,
163
+ SandboxWriteFileOptions,
124
164
  UseSandboxFilesOptions,
125
165
  UseSandboxFilesResult,
126
166
  } from "./hooks/use-sandbox-files";
127
167
  export { useSandboxGit } from "./hooks/use-sandbox-git";
128
- export type { UseSandboxGitOptions, UseSandboxGitResult } from "./hooks/use-sandbox-git";
168
+ export type {
169
+ SandboxGitFileDiff,
170
+ UseSandboxGitOptions,
171
+ UseSandboxGitResult,
172
+ } from "./hooks/use-sandbox-git";
129
173
 
130
174
  // Workbench v2 turn-end capture (the cold-paint data layer)
131
175
  export { useWorkspaceCapture } from "./hooks/use-workspace-capture";
@@ -151,6 +195,27 @@ export type {
151
195
  // Pending-approvals projection
152
196
  export { approvalsFromRequiresAction, projectPendingApprovals } from "./approvals";
153
197
  export type { PendingApproval } from "./approvals";
198
+ export { ApprovalSurface, defaultApprovalSurfaceMessages } from "./components/approval-surface";
199
+ export type { ApprovalSurfaceMessages, ApprovalSurfaceProps } from "./components/approval-surface";
200
+
201
+ // Structured human input: event projection, authoritative hook, and styled form.
202
+ export { humanInputRequestFromEvent, projectPendingHumanInputRequests } from "./human-input";
203
+ export type { PendingHumanInputRequest } from "./human-input";
204
+ export { isHumanInputEvent, useHumanInputRequests } from "./hooks/use-human-input";
205
+ export type {
206
+ UseHumanInputRequestsOptions,
207
+ UseHumanInputRequestsResult,
208
+ } from "./hooks/use-human-input";
209
+ export {
210
+ HumanInputForm,
211
+ answersFromDrafts,
212
+ defaultHumanInputFormMessages,
213
+ } from "./components/human-input-form";
214
+ export type {
215
+ HumanInputAnswerDraft,
216
+ HumanInputFormMessages,
217
+ HumanInputFormProps,
218
+ } from "./components/human-input-form";
154
219
 
155
220
  // Timeline projection
156
221
  export {
@@ -269,6 +334,13 @@ export type { CommandPaletteProps } from "./components/command-palette";
269
334
  // Components
270
335
  export { ChatComposer } from "./components/chat-composer";
271
336
  export type { ChatComposerProps } from "./components/chat-composer";
337
+ export { ComposerTranscriptionControl } from "./components/composer-transcription-control";
338
+ export type {
339
+ ComposerTranscriptionControlProps,
340
+ ComposerTranscriptionMessages,
341
+ } from "./components/composer-transcription-control";
342
+ export { defaultChatComposerMessages } from "./components/composer";
343
+ export type { ChatComposerMessages } from "./components/composer";
272
344
  export { ModelPicker } from "./components/model-picker";
273
345
  export type { ModelPickerProps } from "./components/model-picker";
274
346
  export { MessageTimeline, TimelineRow } from "./components/message-timeline";
@@ -312,8 +384,12 @@ export {
312
384
  initialWorkspaceTab,
313
385
  WORKBENCH_TAB_CHANGES,
314
386
  WORKBENCH_TAB_FILES,
387
+ WORKBENCH_TAB_TERMINAL,
388
+ WORKBENCH_TAB_DESKTOP,
389
+ WORKBENCH_SURFACES,
315
390
  } from "./components/sandbox-workspace";
316
391
  export type {
392
+ SandboxWorkspaceSurface,
317
393
  SandboxWorkspaceProps,
318
394
  UseSandboxWorkspaceTabsOptions,
319
395
  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
+ }