@opengeni/react-native 1.4.5-canary.37615713321001

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/LICENSE +190 -0
  2. package/README.md +74 -0
  3. package/dist/adapters.d.ts +53 -0
  4. package/dist/adapters.js +3 -0
  5. package/dist/adapters.js.map +1 -0
  6. package/dist/attachments.d.ts +75 -0
  7. package/dist/chunk-3VSZAQ7G.js +391 -0
  8. package/dist/chunk-3VSZAQ7G.js.map +1 -0
  9. package/dist/chunk-5ZVK7WKA.js +88 -0
  10. package/dist/chunk-5ZVK7WKA.js.map +1 -0
  11. package/dist/chunk-EEDIMY3L.js +240 -0
  12. package/dist/chunk-EEDIMY3L.js.map +1 -0
  13. package/dist/chunk-M23SDI4R.js +83 -0
  14. package/dist/chunk-M23SDI4R.js.map +1 -0
  15. package/dist/chunk-PHJBSAR3.js +2006 -0
  16. package/dist/chunk-PHJBSAR3.js.map +1 -0
  17. package/dist/environment.d.ts +19 -0
  18. package/dist/expo.d.ts +25 -0
  19. package/dist/expo.js +267 -0
  20. package/dist/expo.js.map +1 -0
  21. package/dist/index.d.ts +13 -0
  22. package/dist/index.js +3345 -0
  23. package/dist/index.js.map +1 -0
  24. package/dist/presentation.d.ts +262 -0
  25. package/dist/read-acknowledgement.d.ts +39 -0
  26. package/dist/realtime/call-events.d.ts +11 -0
  27. package/dist/realtime/call-view.d.ts +26 -0
  28. package/dist/realtime/expo-call.d.ts +11 -0
  29. package/dist/realtime/native-call.d.ts +104 -0
  30. package/dist/realtime/native-realtime.d.ts +35 -0
  31. package/dist/realtime/use-native-session-realtime.d.ts +25 -0
  32. package/dist/session-view.d.ts +87 -0
  33. package/dist/timeline/activity.d.ts +34 -0
  34. package/dist/timeline/composer.d.ts +86 -0
  35. package/dist/timeline/controls.d.ts +27 -0
  36. package/dist/timeline/decisions.d.ts +29 -0
  37. package/dist/timeline/feedback.d.ts +26 -0
  38. package/dist/timeline/icon-nodes.generated.d.ts +738 -0
  39. package/dist/timeline/icon.d.ts +11 -0
  40. package/dist/timeline/index.d.ts +32 -0
  41. package/dist/timeline/index.js +5419 -0
  42. package/dist/timeline/index.js.map +1 -0
  43. package/dist/timeline/markdown.d.ts +11 -0
  44. package/dist/timeline/markdown.js +415 -0
  45. package/dist/timeline/markdown.js.map +1 -0
  46. package/dist/timeline/message-attachments.d.ts +13 -0
  47. package/dist/timeline/message-timeline.d.ts +36 -0
  48. package/dist/timeline/model-picker.d.ts +17 -0
  49. package/dist/timeline/primitives.d.ts +85 -0
  50. package/dist/timeline/queue-dock.d.ts +19 -0
  51. package/dist/timeline/session-actions.d.ts +34 -0
  52. package/dist/timeline/session-list.d.ts +50 -0
  53. package/dist/timeline/session-screen.d.ts +35 -0
  54. package/dist/timeline/session-signals.d.ts +37 -0
  55. package/dist/timeline/sheet.d.ts +14 -0
  56. package/dist/timeline/theme.d.ts +58 -0
  57. package/dist/timeline/thinking-orb.d.ts +13 -0
  58. package/dist/timeline/turn.d.ts +80 -0
  59. package/dist/ui/attention.d.ts +14 -0
  60. package/dist/ui/cards.d.ts +21 -0
  61. package/dist/ui/composer.d.ts +21 -0
  62. package/dist/ui/format.d.ts +13 -0
  63. package/dist/ui/icons.d.ts +9 -0
  64. package/dist/ui/index.d.ts +12 -0
  65. package/dist/ui/index.js +1757 -0
  66. package/dist/ui/index.js.map +1 -0
  67. package/dist/ui/steps-sheet.d.ts +10 -0
  68. package/dist/ui/theme.d.ts +63 -0
  69. package/dist/ui/timeline.d.ts +48 -0
  70. package/dist/ui/web-tokens.gen.d.ts +90 -0
  71. package/dist/use-native-session.d.ts +40 -0
  72. package/dist/view-model.d.ts +52 -0
  73. package/dist/voice-input.d.ts +102 -0
  74. package/dist/webrtc.d.ts +9 -0
  75. package/dist/webrtc.js +19 -0
  76. package/dist/webrtc.js.map +1 -0
  77. package/expo-module.config.json +7 -0
  78. package/ios/OpenGeniCall.podspec +22 -0
  79. package/ios/OpenGeniCallAppDelegateSubscriber.swift +29 -0
  80. package/ios/OpenGeniCallCenter.swift +189 -0
  81. package/ios/OpenGeniCallModule.swift +64 -0
  82. package/package.json +93 -0
  83. package/src/adapters.ts +150 -0
  84. package/src/attachments.ts +396 -0
  85. package/src/environment.tsx +214 -0
  86. package/src/expo.ts +295 -0
  87. package/src/index.ts +98 -0
  88. package/src/presentation.ts +285 -0
  89. package/src/read-acknowledgement.ts +72 -0
  90. package/src/realtime/call-events.ts +41 -0
  91. package/src/realtime/call-view.tsx +220 -0
  92. package/src/realtime/expo-call.ts +49 -0
  93. package/src/realtime/native-call.ts +266 -0
  94. package/src/realtime/native-realtime.ts +115 -0
  95. package/src/realtime/use-native-session-realtime.ts +29 -0
  96. package/src/session-view.tsx +2251 -0
  97. package/src/timeline/activity.tsx +736 -0
  98. package/src/timeline/composer.tsx +686 -0
  99. package/src/timeline/controls.tsx +151 -0
  100. package/src/timeline/decisions.tsx +651 -0
  101. package/src/timeline/feedback.tsx +251 -0
  102. package/src/timeline/icon-nodes.generated.ts +1830 -0
  103. package/src/timeline/icon.tsx +44 -0
  104. package/src/timeline/index.ts +67 -0
  105. package/src/timeline/markdown.tsx +510 -0
  106. package/src/timeline/message-attachments.tsx +155 -0
  107. package/src/timeline/message-timeline.tsx +1042 -0
  108. package/src/timeline/model-picker.tsx +450 -0
  109. package/src/timeline/primitives.tsx +561 -0
  110. package/src/timeline/queue-dock.tsx +331 -0
  111. package/src/timeline/session-actions.tsx +379 -0
  112. package/src/timeline/session-list.tsx +338 -0
  113. package/src/timeline/session-screen.tsx +488 -0
  114. package/src/timeline/session-signals.tsx +312 -0
  115. package/src/timeline/sheet.tsx +153 -0
  116. package/src/timeline/theme.tsx +128 -0
  117. package/src/timeline/thinking-orb.tsx +93 -0
  118. package/src/timeline/turn.tsx +569 -0
  119. package/src/ui/attention.tsx +220 -0
  120. package/src/ui/cards.tsx +267 -0
  121. package/src/ui/composer.tsx +296 -0
  122. package/src/ui/format.ts +86 -0
  123. package/src/ui/icons.tsx +131 -0
  124. package/src/ui/index.ts +18 -0
  125. package/src/ui/steps-sheet.tsx +71 -0
  126. package/src/ui/theme.ts +261 -0
  127. package/src/ui/timeline.tsx +837 -0
  128. package/src/ui/web-tokens.gen.ts +94 -0
  129. package/src/use-native-session.ts +219 -0
  130. package/src/view-model.ts +485 -0
  131. package/src/voice-input.ts +382 -0
  132. package/src/webrtc.ts +30 -0
package/dist/index.js ADDED
@@ -0,0 +1,3345 @@
1
+ import { sha256HexToArrayBuffer } from './chunk-5ZVK7WKA.js';
2
+ export { createHydratedPersistenceAdapter, sha256HexToArrayBuffer } from './chunk-5ZVK7WKA.js';
3
+ export { NativeTranscriptionError, appendDictation, isRetryableTranscriptionError, useNativeVoiceInput } from './chunk-EEDIMY3L.js';
4
+ import { ThinkingOrb } from './chunk-M23SDI4R.js';
5
+ import { useNativeTimelineTheme, Icon, fontStyle } from './chunk-PHJBSAR3.js';
6
+ import { createContext, useState, useEffect, useLayoutEffect, useMemo, useContext, useRef, useCallback } from 'react';
7
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
8
+ import { useSessionEvents, useSession, useTurnQueue, useComposer, useSessionControl, useHumanInputRequests, useGoal, useSessionLineage, projectPendingApprovals, groupTimeline, useSessionMcpApprovalPolicy } from '@opengeni/react/session';
9
+ import { sessionAttentionReadThroughSequence } from '@opengeni/react/session-attention-model';
10
+ import { StyleSheet, Platform, KeyboardAvoidingView, View, FlatList, ActivityIndicator, Text, ScrollView, Pressable, Linking, TextInput } from 'react-native';
11
+ import { createSessionRealtimeController, sessionRealtimeTransportKind } from '@opengeni/sdk/realtime';
12
+ import { useSessionRealtime } from '@opengeni/react/session-realtime';
13
+
14
+ function createEventTarget() {
15
+ const listeners = /* @__PURE__ */ new Map();
16
+ return {
17
+ addEventListener: (type, listener) => {
18
+ const existing = listeners.get(type) ?? /* @__PURE__ */ new Set();
19
+ existing.add(listener);
20
+ listeners.set(type, existing);
21
+ },
22
+ removeEventListener: (type, listener) => {
23
+ listeners.get(type)?.delete(listener);
24
+ },
25
+ dispatch: (type) => {
26
+ for (const listener of listeners.get(type) ?? []) listener();
27
+ }
28
+ };
29
+ }
30
+ function installOpenGeniReactNativeEnvironment(adapters) {
31
+ const globals = globalThis;
32
+ const windowTarget = createEventTarget();
33
+ const documentTarget = createEventTarget();
34
+ const existingWindow = Reflect.get(globals, "window");
35
+ const existingSessionStorage = Reflect.get(globals, "sessionStorage");
36
+ const existingDocument = Reflect.get(globals, "document");
37
+ const existingCrypto = Reflect.get(globals, "crypto");
38
+ const nativeWindow = createDelegatingObject(isObject(existingWindow) ? existingWindow : globals);
39
+ let lifecycleState = adapters.lifecycle.currentState();
40
+ const sessionStorage = {
41
+ getItem: (key) => adapters.persistence.getItem(key),
42
+ setItem: (key, value) => adapters.persistence.setItem(key, value),
43
+ removeItem: (key) => adapters.persistence.removeItem(key),
44
+ clear: () => adapters.persistence.clear(),
45
+ key: () => null,
46
+ get length() {
47
+ return 0;
48
+ }
49
+ };
50
+ const nativeCrypto = createDelegatingObject(isObject(existingCrypto) ? existingCrypto : null);
51
+ const subtleValue = isObject(existingCrypto) ? Reflect.get(existingCrypto, "subtle") : void 0;
52
+ const nativeSubtle = createDelegatingObject(isObject(subtleValue) ? subtleValue : null);
53
+ Reflect.set(nativeCrypto, "randomUUID", () => adapters.crypto.randomUUID());
54
+ Reflect.set(
55
+ nativeSubtle,
56
+ "digest",
57
+ async (algorithm, data) => {
58
+ const name = typeof algorithm === "string" ? algorithm : algorithm.name;
59
+ if (name.toUpperCase() !== "SHA-256") {
60
+ throw new TypeError(`Unsupported native digest algorithm: ${name}`);
61
+ }
62
+ const bytes = data instanceof ArrayBuffer ? new Uint8Array(data) : new Uint8Array(data.buffer, data.byteOffset, data.byteLength);
63
+ return sha256HexToArrayBuffer(await adapters.crypto.sha256Hex(bytes));
64
+ }
65
+ );
66
+ Reflect.set(nativeCrypto, "subtle", nativeSubtle);
67
+ Reflect.set(globals, "crypto", nativeCrypto);
68
+ Reflect.set(nativeWindow, "sessionStorage", sessionStorage);
69
+ if (typeof Reflect.get(nativeWindow, "setTimeout") !== "function") {
70
+ Reflect.set(nativeWindow, "setTimeout", globalThis.setTimeout.bind(globalThis));
71
+ }
72
+ if (typeof Reflect.get(nativeWindow, "clearTimeout") !== "function") {
73
+ Reflect.set(nativeWindow, "clearTimeout", globalThis.clearTimeout.bind(globalThis));
74
+ }
75
+ if (typeof Reflect.get(nativeWindow, "addEventListener") !== "function") {
76
+ Reflect.set(
77
+ nativeWindow,
78
+ "addEventListener",
79
+ (type, listener) => windowTarget.addEventListener(type, listener)
80
+ );
81
+ }
82
+ if (typeof Reflect.get(nativeWindow, "removeEventListener") !== "function") {
83
+ Reflect.set(
84
+ nativeWindow,
85
+ "removeEventListener",
86
+ (type, listener) => windowTarget.removeEventListener(type, listener)
87
+ );
88
+ }
89
+ Reflect.set(globals, "window", nativeWindow);
90
+ Reflect.set(globals, "sessionStorage", sessionStorage);
91
+ const nativeDocument = {
92
+ get visibilityState() {
93
+ return lifecycleState === "active" ? "visible" : "hidden";
94
+ },
95
+ addEventListener: (type, listener) => documentTarget.addEventListener(type, listener),
96
+ removeEventListener: (type, listener) => documentTarget.removeEventListener(type, listener)
97
+ };
98
+ Reflect.set(globals, "document", nativeDocument);
99
+ const unsubscribe = adapters.lifecycle.subscribe((next) => {
100
+ const wasActive = lifecycleState === "active";
101
+ lifecycleState = next;
102
+ documentTarget.dispatch("visibilitychange");
103
+ if (!wasActive && next === "active") windowTarget.dispatch("pageshow");
104
+ });
105
+ return {
106
+ cleanup: () => {
107
+ unsubscribe();
108
+ restoreGlobal(globals, "window", nativeWindow, existingWindow);
109
+ restoreGlobal(globals, "sessionStorage", sessionStorage, existingSessionStorage);
110
+ restoreGlobal(globals, "document", nativeDocument, existingDocument);
111
+ restoreGlobal(globals, "crypto", nativeCrypto, existingCrypto);
112
+ }
113
+ };
114
+ }
115
+ function restoreGlobal(target, key, installedValue, previousValue) {
116
+ if (Reflect.get(target, key) !== installedValue) return;
117
+ if (previousValue === void 0) Reflect.deleteProperty(target, key);
118
+ else Reflect.set(target, key, previousValue);
119
+ }
120
+ function isObject(value) {
121
+ return typeof value === "object" && value !== null || typeof value === "function";
122
+ }
123
+ function createDelegatingObject(prototype) {
124
+ const value = {};
125
+ Object.setPrototypeOf(value, prototype);
126
+ return value;
127
+ }
128
+ var NativeEnvironmentContext = createContext(null);
129
+ function OpenGeniReactNativeProvider({
130
+ adapters,
131
+ children,
132
+ loadingFallback = null
133
+ }) {
134
+ const [hydrated, setHydrated] = useState(false);
135
+ const [active, setActive] = useState(adapters.lifecycle.currentState() === "active");
136
+ useEffect(() => {
137
+ let mounted = true;
138
+ setHydrated(false);
139
+ void adapters.persistence.hydrate().then(() => {
140
+ if (!mounted) return;
141
+ setHydrated(true);
142
+ });
143
+ const unsubscribe = adapters.lifecycle.subscribe((state) => setActive(state === "active"));
144
+ return () => {
145
+ mounted = false;
146
+ unsubscribe();
147
+ };
148
+ }, [adapters]);
149
+ useLayoutEffect(() => {
150
+ const installed = installOpenGeniReactNativeEnvironment(adapters);
151
+ return () => {
152
+ globalThis.setTimeout(() => installed.cleanup(), 0);
153
+ };
154
+ }, [adapters]);
155
+ const value = useMemo(() => ({ adapters, active }), [active, adapters]);
156
+ if (!hydrated) return loadingFallback;
157
+ return /* @__PURE__ */ jsx(NativeEnvironmentContext.Provider, { value, children });
158
+ }
159
+ function useOpenGeniReactNativeEnvironment() {
160
+ const value = useContext(NativeEnvironmentContext);
161
+ if (!value) {
162
+ throw new Error("OpenGeni native hooks require OpenGeniReactNativeProvider");
163
+ }
164
+ return value;
165
+ }
166
+ var NATIVE_ATTACHMENT_PREPARATION_TIMEOUT_MS = 3e4;
167
+ function sameNativeAttachmentScope(left, right) {
168
+ return left.client === right.client && left.workspaceId === right.workspaceId && left.sessionId === right.sessionId;
169
+ }
170
+ function projectNativeAttachmentScopeState(scopeMatches, attachments, error) {
171
+ return scopeMatches ? { attachments, error } : { attachments: [], error: null };
172
+ }
173
+ async function runNativeAttachmentPicker(input) {
174
+ try {
175
+ const files = await input.pick();
176
+ if (input.currentGeneration() !== input.generation) return "stale";
177
+ await input.addFiles(files, input.generation);
178
+ return input.currentGeneration() === input.generation ? "added" : "stale";
179
+ } catch (cause) {
180
+ if (input.currentGeneration() !== input.generation) return "stale";
181
+ input.onError(cause instanceof Error ? cause : new Error(String(cause)));
182
+ return "failed";
183
+ }
184
+ }
185
+ async function addNativeFilesForGeneration(input) {
186
+ if (input.currentGeneration() !== input.generation) return;
187
+ const unique = input.files.filter((file) => !input.hasSource(file.id));
188
+ if (unique.length === 0) return;
189
+ for (const file of unique) input.rememberSource(file);
190
+ input.appendAttachments(unique);
191
+ await unique.reduce(async (previous, file) => {
192
+ await previous;
193
+ if (input.currentGeneration() !== input.generation) return;
194
+ await input.upload(file, input.generation);
195
+ }, Promise.resolve());
196
+ }
197
+ async function prepareNativeAttachmentUpload(input) {
198
+ const timeoutMs = input.timeoutMs ?? NATIVE_ATTACHMENT_PREPARATION_TIMEOUT_MS;
199
+ const bytes = await withTimeout(
200
+ input.files.readBytes(input.source),
201
+ timeoutMs,
202
+ "File preparation timed out. Retry the upload."
203
+ );
204
+ const sha256 = await withTimeout(
205
+ input.crypto.sha256Hex(bytes),
206
+ timeoutMs,
207
+ "File verification timed out. Retry the upload."
208
+ );
209
+ const uploadBytes = Uint8Array.from(bytes);
210
+ const data = uploadBytes.buffer;
211
+ return { bytes, data, sha256 };
212
+ }
213
+ async function withTimeout(operation, timeoutMs, message) {
214
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
215
+ throw new Error("Native attachment timeout must be a positive number");
216
+ }
217
+ let timeout;
218
+ const timedOut = new Promise((_resolve, reject) => {
219
+ timeout = setTimeout(() => reject(new Error(message)), timeoutMs);
220
+ });
221
+ try {
222
+ return await Promise.race([operation, timedOut]);
223
+ } finally {
224
+ if (timeout !== void 0) clearTimeout(timeout);
225
+ }
226
+ }
227
+ function useNativeFileAttachments(input) {
228
+ const [attachments, setAttachments] = useState([]);
229
+ const [error, setError] = useState(null);
230
+ const sources = useRef(/* @__PURE__ */ new Map());
231
+ const attempts = useRef(/* @__PURE__ */ new Map());
232
+ const scopeGeneration = useRef(0);
233
+ const currentScope = {
234
+ client: input.client,
235
+ workspaceId: input.workspaceId,
236
+ sessionId: input.sessionId
237
+ };
238
+ const previousScope = useRef(currentScope);
239
+ const scopeMatches = sameNativeAttachmentScope(previousScope.current, currentScope);
240
+ useLayoutEffect(() => {
241
+ const nextScope = {
242
+ client: input.client,
243
+ workspaceId: input.workspaceId,
244
+ sessionId: input.sessionId
245
+ };
246
+ if (sameNativeAttachmentScope(previousScope.current, nextScope)) return;
247
+ previousScope.current = nextScope;
248
+ scopeGeneration.current += 1;
249
+ sources.current.clear();
250
+ attempts.current.clear();
251
+ setAttachments([]);
252
+ setError(null);
253
+ }, [input.client, input.sessionId, input.workspaceId]);
254
+ useEffect(
255
+ () => () => {
256
+ scopeGeneration.current += 1;
257
+ sources.current.clear();
258
+ attempts.current.clear();
259
+ },
260
+ []
261
+ );
262
+ const upload = useCallback(
263
+ async (source, generation = scopeGeneration.current) => {
264
+ if (scopeGeneration.current !== generation) return;
265
+ const attempt = (attempts.current.get(source.id) ?? 0) + 1;
266
+ attempts.current.set(source.id, attempt);
267
+ setAttachments(
268
+ (current) => current.map(
269
+ (entry) => entry.id === source.id ? { ...entry, status: "preparing", error: void 0 } : entry
270
+ )
271
+ );
272
+ try {
273
+ const { data, sha256 } = await prepareNativeAttachmentUpload({
274
+ source,
275
+ files: input.files,
276
+ crypto: input.crypto
277
+ });
278
+ if (scopeGeneration.current !== generation || attempts.current.get(source.id) !== attempt || !sources.current.has(source.id)) {
279
+ return;
280
+ }
281
+ setAttachments(
282
+ (current) => current.map(
283
+ (entry) => entry.id === source.id ? { ...entry, status: "uploading" } : entry
284
+ )
285
+ );
286
+ const file = await input.client.uploadFile(input.workspaceId, {
287
+ filename: source.name,
288
+ contentType: source.contentType,
289
+ data,
290
+ sha256
291
+ });
292
+ if (scopeGeneration.current !== generation || attempts.current.get(source.id) !== attempt || !sources.current.has(source.id)) {
293
+ return;
294
+ }
295
+ sources.current.delete(source.id);
296
+ attempts.current.delete(source.id);
297
+ setAttachments(
298
+ (current) => current.map(
299
+ (entry) => entry.id === source.id ? { ...entry, status: "ready", file, error: void 0 } : entry
300
+ )
301
+ );
302
+ } catch (cause) {
303
+ if (scopeGeneration.current !== generation || attempts.current.get(source.id) !== attempt || !sources.current.has(source.id)) {
304
+ return;
305
+ }
306
+ setAttachments(
307
+ (current) => current.map(
308
+ (entry) => entry.id === source.id ? {
309
+ ...entry,
310
+ status: "failed",
311
+ error: cause instanceof Error ? cause.message : String(cause)
312
+ } : entry
313
+ )
314
+ );
315
+ }
316
+ },
317
+ [input.client, input.crypto, input.files, input.workspaceId]
318
+ );
319
+ const addFilesForGeneration = useCallback(
320
+ async (files, generation) => {
321
+ await addNativeFilesForGeneration({
322
+ files,
323
+ generation,
324
+ currentGeneration: () => scopeGeneration.current,
325
+ hasSource: (id) => sources.current.has(id),
326
+ rememberSource: (file) => sources.current.set(file.id, file),
327
+ appendAttachments: (unique) => setAttachments((current) => [
328
+ ...current,
329
+ ...unique.map(
330
+ (file) => ({
331
+ id: file.id,
332
+ name: file.name,
333
+ contentType: file.contentType,
334
+ sizeBytes: file.sizeBytes,
335
+ kind: file.kind,
336
+ ...file.previewUri ? { previewUri: file.previewUri } : {},
337
+ status: "preparing"
338
+ })
339
+ )
340
+ ]),
341
+ upload
342
+ });
343
+ },
344
+ [upload]
345
+ );
346
+ const addFiles = useCallback(
347
+ async (files) => await addFilesForGeneration(files, scopeGeneration.current),
348
+ [addFilesForGeneration]
349
+ );
350
+ const pickFiles = useCallback(
351
+ async (pick) => {
352
+ const generation = scopeGeneration.current;
353
+ setError(null);
354
+ await runNativeAttachmentPicker({
355
+ generation,
356
+ currentGeneration: () => scopeGeneration.current,
357
+ pick,
358
+ addFiles: addFilesForGeneration,
359
+ onError: setError
360
+ });
361
+ },
362
+ [addFilesForGeneration]
363
+ );
364
+ const pickDocuments = useCallback(
365
+ async () => await pickFiles(() => input.files.pickDocuments()),
366
+ [input.files, pickFiles]
367
+ );
368
+ const pickImages = useCallback(
369
+ async () => await pickFiles(() => input.files.pickImages()),
370
+ [input.files, pickFiles]
371
+ );
372
+ const retry = useCallback(
373
+ async (id) => {
374
+ const source = sources.current.get(id);
375
+ if (source) await upload(source);
376
+ },
377
+ [upload]
378
+ );
379
+ const remove = useCallback((id) => {
380
+ sources.current.delete(id);
381
+ attempts.current.delete(id);
382
+ setAttachments((current) => current.filter((entry) => entry.id !== id));
383
+ }, []);
384
+ const clear = useCallback(() => {
385
+ sources.current.clear();
386
+ attempts.current.clear();
387
+ setAttachments([]);
388
+ setError(null);
389
+ }, []);
390
+ const clearError = useCallback(() => setError(null), []);
391
+ const restoreReady = useCallback((files) => {
392
+ const restored = [...files];
393
+ setAttachments((current) => {
394
+ const kept = current.filter((entry) => entry.status !== "ready");
395
+ return [
396
+ ...restored.map(
397
+ (file) => ({
398
+ id: `restored:${file.id}`,
399
+ name: file.filename,
400
+ contentType: file.contentType,
401
+ sizeBytes: file.sizeBytes,
402
+ kind: file.contentType.startsWith("image/") ? "image" : "document",
403
+ status: "ready",
404
+ file
405
+ })
406
+ ),
407
+ ...kept
408
+ ];
409
+ });
410
+ }, []);
411
+ const visibleState = projectNativeAttachmentScopeState(scopeMatches, attachments, error);
412
+ const visibleAttachments = visibleState.attachments;
413
+ const visibleError = visibleState.error;
414
+ const readyResources = useMemo(
415
+ () => visibleAttachments.flatMap(
416
+ (attachment) => attachment.status === "ready" && attachment.file ? [{ kind: "file", fileId: attachment.file.id }] : []
417
+ ),
418
+ [visibleAttachments]
419
+ );
420
+ return {
421
+ attachments: visibleAttachments,
422
+ readyResources,
423
+ uploading: visibleAttachments.some(
424
+ (attachment) => attachment.status === "preparing" || attachment.status === "uploading"
425
+ ),
426
+ hasUnresolved: visibleAttachments.some((attachment) => attachment.status !== "ready"),
427
+ error: visibleError,
428
+ pickDocuments,
429
+ pickImages,
430
+ addFiles,
431
+ retry,
432
+ remove,
433
+ clear,
434
+ clearError,
435
+ restoreReady
436
+ };
437
+ }
438
+ function readAcknowledgementFrontier(session, attentionThrough) {
439
+ const lastSequence = session?.lastSequence ?? 0;
440
+ return {
441
+ readThrough: Math.max(lastSequence, attentionThrough),
442
+ unread: Boolean(session?.unread) || attentionThrough > lastSequence
443
+ };
444
+ }
445
+ function useNativeReadAcknowledgement(input) {
446
+ const { client, workspaceId, sessionId, session, events, active } = input;
447
+ const attentionThrough = useMemo(() => sessionAttentionReadThroughSequence(events), [events]);
448
+ const { readThrough, unread } = readAcknowledgementFrontier(session, attentionThrough);
449
+ const acknowledged = useRef(null);
450
+ useEffect(() => {
451
+ if (!active || !session || !unread || readThrough <= 0) return;
452
+ if (!client.updateSessionAttention) return;
453
+ const key = `${workspaceId}:${sessionId}:${readThrough}`;
454
+ const previous = acknowledged.current;
455
+ if (previous?.key === key) return;
456
+ acknowledged.current = { key, retried: false };
457
+ const send = () => client.updateSessionAttention(workspaceId, sessionId, {
458
+ unread: false,
459
+ acknowledgedThroughSequence: readThrough
460
+ });
461
+ send().catch(() => {
462
+ const current = acknowledged.current;
463
+ if (current?.key !== key || current.retried) return;
464
+ current.retried = true;
465
+ void send().catch(() => void 0);
466
+ });
467
+ }, [active, client, readThrough, session, sessionId, unread, workspaceId]);
468
+ }
469
+
470
+ // src/use-native-session.ts
471
+ function sessionRunActive(status) {
472
+ return status === "queued" || status === "running" || status === "recovering" || status === "waiting_capacity" || status === "requires_action";
473
+ }
474
+ function useOpenGeniNativeSession(input) {
475
+ const environment = useOpenGeniReactNativeEnvironment();
476
+ const events = useSessionEvents(input.sessionId, {
477
+ client: input.client,
478
+ workspaceId: input.workspaceId,
479
+ enabled: environment.active
480
+ });
481
+ const session = useSession(input.sessionId, {
482
+ client: input.client,
483
+ workspaceId: input.workspaceId,
484
+ enabled: environment.active,
485
+ events: events.events
486
+ });
487
+ const queue = useTurnQueue(input.sessionId, {
488
+ client: input.client,
489
+ workspaceId: input.workspaceId,
490
+ enabled: environment.active,
491
+ events: events.events
492
+ });
493
+ const attachments = useNativeFileAttachments({
494
+ client: input.client,
495
+ workspaceId: input.workspaceId,
496
+ sessionId: input.sessionId,
497
+ files: environment.adapters.files,
498
+ crypto: environment.adapters.crypto
499
+ });
500
+ const composer = useComposer(input.sessionId, {
501
+ client: input.client,
502
+ workspaceId: input.workspaceId,
503
+ events: events.events,
504
+ effectiveControl: queue.effectiveControl,
505
+ // As on web: while a turn runs or prompts wait, a Send shows in the queue
506
+ // at once instead of flashing as a chat bubble before it is queued.
507
+ sendDestination: () => (session.session?.activeTurnId ?? null) !== null || queue.queue.length > 0 ? "queue" : "chat",
508
+ sendExtras: () => ({ resources: attachments.readyResources }),
509
+ sendBlocked: () => attachments.hasUnresolved,
510
+ onSent: () => attachments.clear()
511
+ });
512
+ const control = useSessionControl(input.sessionId, {
513
+ client: input.client,
514
+ workspaceId: input.workspaceId
515
+ });
516
+ const humanInput = useHumanInputRequests(input.sessionId, {
517
+ client: input.client,
518
+ workspaceId: input.workspaceId,
519
+ enabled: environment.active,
520
+ events: events.events
521
+ });
522
+ const goal = useGoal(input.sessionId, {
523
+ client: input.client,
524
+ workspaceId: input.workspaceId,
525
+ enabled: environment.active,
526
+ events: events.events
527
+ });
528
+ const lineage = useSessionLineage(input.sessionId, {
529
+ client: input.client,
530
+ workspaceId: input.workspaceId,
531
+ enabled: environment.active,
532
+ events: events.events,
533
+ pollIntervalMs: 3e4
534
+ });
535
+ const approvals = useMemo(() => projectPendingApprovals(events.events), [events.events]);
536
+ const timelineGroups = useMemo(() => groupTimeline(events.timeline), [events.timeline]);
537
+ useNativeReadAcknowledgement({
538
+ client: input.client,
539
+ workspaceId: input.workspaceId,
540
+ sessionId: input.sessionId,
541
+ session: session.session ?? null,
542
+ events: events.events,
543
+ active: environment.active
544
+ });
545
+ const mcpApprovalPolicy = useSessionMcpApprovalPolicy(
546
+ input.sessionId,
547
+ input.mcpApprovalPolicyServerId,
548
+ {
549
+ client: input.client,
550
+ workspaceId: input.workspaceId,
551
+ events: events.events,
552
+ enabled: environment.active && Boolean(input.mcpApprovalPolicyServerId)
553
+ }
554
+ );
555
+ const refresh = useCallback(async () => {
556
+ await Promise.all([
557
+ session.refresh(),
558
+ queue.refresh(),
559
+ humanInput.refresh(),
560
+ events.jumpToLatest(),
561
+ mcpApprovalPolicy.refresh()
562
+ ]);
563
+ }, [events, humanInput, mcpApprovalPolicy, queue, session]);
564
+ return {
565
+ sessionId: input.sessionId,
566
+ workspaceId: input.workspaceId,
567
+ session,
568
+ events: events.events,
569
+ timeline: events.timeline,
570
+ timelineGroups,
571
+ connectionState: events.connectionState,
572
+ sessionStatus: events.sessionStatus ?? session.session?.status ?? null,
573
+ initialLoading: events.initialLoading || session.loading,
574
+ hasOlder: events.hasOlder,
575
+ loadingOlder: events.loadingOlder,
576
+ loadOlder: events.loadOlder,
577
+ queue,
578
+ composer,
579
+ attachments,
580
+ approvals,
581
+ control,
582
+ humanInput,
583
+ mcpApprovalPolicy,
584
+ goal,
585
+ lineage,
586
+ active: environment.active,
587
+ runActive: sessionRunActive(events.sessionStatus ?? session.session?.status ?? null),
588
+ error: events.error ?? session.error ?? queue.error ?? queue.mutationError ?? composer.error ?? composer.draftConflict ?? control.error ?? humanInput.error ?? humanInput.mutationError ?? mcpApprovalPolicy.error,
589
+ refresh
590
+ };
591
+ }
592
+
593
+ // src/presentation.ts
594
+ var DEFAULT_OPENGENI_NATIVE_LABELS = {
595
+ knowledgeOutcome: {
596
+ published: "Knowledge published",
597
+ pending: "Knowledge awaiting review",
598
+ rejected: "Knowledge rejected",
599
+ archived: "Knowledge archived",
600
+ failed: "Knowledge could not be saved"
601
+ },
602
+ emptyTitle: "Start a conversation",
603
+ emptyDescription: "Ask the agent to help with work in this session.",
604
+ loadEarlier: "Load earlier activity",
605
+ loadingEarlier: "Loading earlier activity\u2026",
606
+ reconnecting: "Reconnecting\u2026",
607
+ connecting: "Connecting\u2026",
608
+ offlinePaused: "Live updates pause while the app is in the background.",
609
+ error: "Something went wrong. Try again.",
610
+ retry: "Retry",
611
+ dismiss: "Dismiss",
612
+ queueTitle: "Queued prompts",
613
+ queueEmpty: "No queued prompts",
614
+ edit: "Edit",
615
+ runNext: "Run next",
616
+ remove: "Remove",
617
+ moveEarlier: "Move earlier",
618
+ moveLater: "Move later",
619
+ approvalsTitle: "Approval required",
620
+ approve: "Approve",
621
+ deny: "Deny",
622
+ questionsTitle: "Your input is needed",
623
+ humanInputAsked: "Agent asked",
624
+ humanInputOutcome: {
625
+ answered: "You answered",
626
+ skipped: "Skipped",
627
+ expired: "Expired",
628
+ cancelled: "Cancelled"
629
+ },
630
+ submitAnswers: "Submit answers",
631
+ skip: "Skip",
632
+ other: "Other",
633
+ required: "Required",
634
+ attachDocument: "Attach document",
635
+ attachImage: "Attach image",
636
+ preparingAttachment: "Preparing\u2026",
637
+ uploading: "Uploading\u2026",
638
+ uploadFailed: "Upload failed",
639
+ attachmentError: "Could not open the file picker.",
640
+ removeAttachment: "Remove attachment",
641
+ retryUpload: "Retry upload",
642
+ restoredAttachment: "Attached file",
643
+ composerPlaceholder: "Message the agent\u2026",
644
+ send: "Send",
645
+ steer: "Steer",
646
+ pause: "Pause",
647
+ resume: "Resume",
648
+ savingDraft: "Saving draft\u2026",
649
+ draftConflict: "This draft changed elsewhere.",
650
+ keepMine: "Keep mine",
651
+ useRemote: "Use saved draft",
652
+ thinking: "Thinking",
653
+ thought: "Thought",
654
+ reasoning: "Reasoning",
655
+ tool: "Tool",
656
+ worker: "Worker",
657
+ user: "You",
658
+ assistant: "Assistant",
659
+ sandbox: "Sandbox",
660
+ goal: "Goal",
661
+ goalAction: {
662
+ set: "Goal set",
663
+ updated: "Goal updated",
664
+ completed: "Goal completed",
665
+ paused: "Goal paused",
666
+ resumed: "Goal resumed",
667
+ cleared: "Goal cleared",
668
+ held: "Goal held",
669
+ continuation: "Continuing toward the goal"
670
+ },
671
+ contextUpdated: "Context updated",
672
+ contextCompactionPhase: {
673
+ started: "Compacting conversation history",
674
+ compacted: "Conversation history compacted",
675
+ skipped: "Conversation history compaction skipped"
676
+ },
677
+ machineUpdates: "Machine updates: {value}",
678
+ connectionRequired: "Connection required for {provider}",
679
+ memory: "Memory",
680
+ memoryAction: {
681
+ saved: "Saved to memory",
682
+ corrected: "Updated memory",
683
+ updated: "Updated memory in place",
684
+ archived: "Archived memory"
685
+ },
686
+ fleetDecision: "Fleet decision",
687
+ fleetOutcome: {
688
+ selected: "Selected {candidate}",
689
+ waiting: "Waiting for capacity",
690
+ none: "No candidate selected"
691
+ },
692
+ fleetReason: {
693
+ leaseReused: "Reused the fenced lease",
694
+ pin: "Kept the session pin",
695
+ rotation: "Rotated for capacity",
696
+ active: "Used the active subscription",
697
+ allCapped: "All observed subscriptions were capped",
698
+ none: "No production candidate was selected"
699
+ },
700
+ turn: "Turn",
701
+ activity: "Activity",
702
+ statusQueued: "Queued",
703
+ statusRunning: "Running",
704
+ statusComplete: "Complete",
705
+ statusRecovering: "Recovering",
706
+ statusWaitingCapacity: "Waiting for capacity",
707
+ statusIdle: "Idle",
708
+ statusRequiresAction: "Waiting on you",
709
+ statusFailed: "Failed",
710
+ statusCancelled: "Cancelled",
711
+ relativeNow: "now",
712
+ relativeSeconds: "{value}s",
713
+ relativeMinutes: "{value}m",
714
+ relativeHours: "{value}h",
715
+ relativeDays: "{value}d"
716
+ };
717
+ var DEFAULT_OPENGENI_NATIVE_THEME = {
718
+ dark: false,
719
+ colors: {
720
+ background: "#f7f7f5",
721
+ surface: "#ffffff",
722
+ elevated: "#f0f0ed",
723
+ border: "#dfdfda",
724
+ text: "#1b1b19",
725
+ secondaryText: "#5c5c57",
726
+ mutedText: "#83837b",
727
+ accent: "#2563eb",
728
+ accentForeground: "#ffffff",
729
+ userBubble: "#1f2937",
730
+ userBubbleText: "#ffffff",
731
+ danger: "#b42318",
732
+ dangerSurface: "#fef3f2",
733
+ warning: "#b54708",
734
+ warningSurface: "#fffaeb",
735
+ success: "#067647"
736
+ }
737
+ };
738
+
739
+ // src/view-model.ts
740
+ function nativeKnowledgeOutcomeLabel(item, labels) {
741
+ return (labels.knowledgeOutcome ?? DEFAULT_OPENGENI_NATIVE_LABELS.knowledgeOutcome)[item.outcome];
742
+ }
743
+ function nativeSessionStatusTone(status) {
744
+ switch (status) {
745
+ case "queued":
746
+ case "running":
747
+ case "recovering":
748
+ case "waiting_capacity":
749
+ return "working";
750
+ case "requires_action":
751
+ return "attention";
752
+ case "failed":
753
+ return "failed";
754
+ case "cancelled":
755
+ return "complete";
756
+ default:
757
+ return "neutral";
758
+ }
759
+ }
760
+ function boundedJson(value, maximum = 1200) {
761
+ let serialized;
762
+ try {
763
+ serialized = JSON.stringify(value, null, 2) ?? "";
764
+ } catch {
765
+ serialized = String(value);
766
+ }
767
+ return serialized.length <= maximum ? serialized : `${serialized.slice(0, maximum)}\u2026`;
768
+ }
769
+ function plainTextContent(value) {
770
+ return value.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1").replace(/\[([^\]]+)\]\([^)]*\)/g, "$1").replace(/^\s{0,3}(?:#{1,6}|>|[-+])\s+/gm, "").replace(/\*\*|__|~~|`/g, "").replace(/(^|\s)[*_](?=\S)/g, "$1").replace(/(?<=\S)[*_](?=\s|$|[.,!?;:])/g, "").trim();
771
+ }
772
+ function normalizedPreview(value, maximum = 140) {
773
+ const normalized = plainTextContent(value).replace(/\s+/g, " ").trim();
774
+ if (!normalized) return "";
775
+ return normalized.length <= maximum ? normalized : `${normalized.slice(0, maximum - 1)}\u2026`;
776
+ }
777
+ function recordPreview(value) {
778
+ for (const key of ["summary", "message", "title", "name", "status", "text", "query", "cmd"]) {
779
+ const candidate = Reflect.get(value, key);
780
+ if (typeof candidate === "string") {
781
+ const preview = normalizedPreview(candidate);
782
+ if (preview) return preview;
783
+ }
784
+ }
785
+ const keys = Object.keys(value);
786
+ return keys.length > 0 ? keys.slice(0, 4).join(", ") : "";
787
+ }
788
+ function inspectableValuePreview(value) {
789
+ if (typeof value === "string") return normalizedPreview(value);
790
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
791
+ if (Array.isArray(value)) return value.length === 1 ? "1 item" : `${value.length} items`;
792
+ if (value && typeof value === "object") return recordPreview(value);
793
+ return "";
794
+ }
795
+ function nativeToolDisplayName(name) {
796
+ const boundary = name.indexOf("__");
797
+ const leaf = boundary >= 0 ? name.slice(boundary + 2) : name;
798
+ const phrase = leaf.replace(/[_-]+/g, " ").trim();
799
+ return phrase ? phrase.charAt(0).toLocaleUpperCase() + phrase.slice(1) : name;
800
+ }
801
+ function activityStatus(item, labels) {
802
+ const fallbackStatus = nativeTimelineStatusLabel(item.status, labels);
803
+ return {
804
+ status: item.status === "complete" ? null : fallbackStatus,
805
+ fallbackStatus,
806
+ expandedByDefault: item.status === "failed",
807
+ tone: item.status === "running" ? "running" : item.status === "failed" ? "failed" : "neutral"
808
+ };
809
+ }
810
+ function nativeActivityPresentation(item, labels) {
811
+ switch (item.kind) {
812
+ case "agent-message":
813
+ return {
814
+ title: labels.assistant,
815
+ preview: normalizedPreview(item.text),
816
+ status: item.streaming ? labels.statusRunning : null,
817
+ detail: plainTextContent(item.text) || null,
818
+ expandedByDefault: false,
819
+ tone: item.streaming ? "running" : "neutral"
820
+ };
821
+ case "reasoning":
822
+ return {
823
+ title: item.streaming ? labels.thinking : labels.thought,
824
+ preview: normalizedPreview(item.text),
825
+ status: item.streaming ? labels.statusRunning : null,
826
+ detail: plainTextContent(item.text) || null,
827
+ expandedByDefault: false,
828
+ tone: item.streaming ? "running" : "neutral"
829
+ };
830
+ case "tool-call": {
831
+ const outputPreview = inspectableValuePreview(item.output);
832
+ const argumentPreview = inspectableValuePreview(item.arguments);
833
+ const status = activityStatus(item, labels);
834
+ return {
835
+ title: nativeToolDisplayName(item.name),
836
+ preview: outputPreview || argumentPreview || status.fallbackStatus,
837
+ detail: boundedJson({
838
+ input: item.arguments,
839
+ ...item.output !== void 0 ? { result: item.output } : {},
840
+ ...item.truncation ? { truncation: item.truncation } : {}
841
+ }),
842
+ status: status.status,
843
+ expandedByDefault: status.expandedByDefault,
844
+ tone: status.tone
845
+ };
846
+ }
847
+ case "worker": {
848
+ const status = activityStatus(item, labels);
849
+ return {
850
+ title: labels.worker,
851
+ preview: normalizedPreview(item.prompt ?? item.failure?.message ?? "") || status.fallbackStatus,
852
+ detail: item.failure ? boundedJson(item.failure) : item.prompt?.trim() || item.workerSessionId || null,
853
+ status: status.status,
854
+ expandedByDefault: status.expandedByDefault,
855
+ tone: status.tone
856
+ };
857
+ }
858
+ case "sandbox": {
859
+ const status = activityStatus(item, labels);
860
+ return {
861
+ title: nativeToolDisplayName(item.name || labels.sandbox),
862
+ preview: inspectableValuePreview(item.output) || inspectableValuePreview(item.command) || status.fallbackStatus,
863
+ detail: boundedJson({
864
+ ...item.command ? { command: item.command } : {},
865
+ ...item.output ? { output: item.output } : {},
866
+ ...item.origin ? { origin: item.origin } : {}
867
+ }),
868
+ status: status.status,
869
+ expandedByDefault: status.expandedByDefault,
870
+ tone: status.tone
871
+ };
872
+ }
873
+ case "startup-phase": {
874
+ const status = activityStatus(item, labels);
875
+ return {
876
+ title: nativeToolDisplayName(item.phase),
877
+ preview: item.outcome ? nativeToolDisplayName(item.outcome) : status.fallbackStatus,
878
+ detail: item.durationMs == null ? null : `${Math.max(0, Math.round(item.durationMs / 100) / 10)}s`,
879
+ status: status.status,
880
+ expandedByDefault: status.expandedByDefault,
881
+ tone: status.tone
882
+ };
883
+ }
884
+ case "knowledge":
885
+ return {
886
+ title: nativeKnowledgeOutcomeLabel(item, labels),
887
+ preview: item.filename ?? "",
888
+ status: null,
889
+ detail: item.filename ?? null,
890
+ expandedByDefault: false,
891
+ tone: item.status === "failed" ? "failed" : "neutral"
892
+ };
893
+ case "memory":
894
+ return {
895
+ title: nativeMemoryActionLabel(item, labels),
896
+ preview: normalizedPreview(item.replacementPreview ?? item.preview),
897
+ status: null,
898
+ detail: item.replacementPreview ? boundedJson({ before: item.preview, after: item.replacementPreview }) : item.preview,
899
+ expandedByDefault: false,
900
+ tone: "neutral"
901
+ };
902
+ case "fleet-decision": {
903
+ const summary = nativeFleetDecisionSummary(item, labels);
904
+ return {
905
+ title: labels.fleetDecision,
906
+ preview: summary,
907
+ status: null,
908
+ detail: boundedJson({
909
+ outcome: item.actualOutcome,
910
+ reason: item.actualReason,
911
+ admission: item.admissionOutcome,
912
+ candidates: item.candidateCount
913
+ }),
914
+ expandedByDefault: false,
915
+ tone: item.actualOutcome === "waiting" ? "running" : "neutral"
916
+ };
917
+ }
918
+ }
919
+ }
920
+ function formatLabel(template, values) {
921
+ return Object.entries(values).reduce(
922
+ (formatted, [key, value]) => formatted.split(`{${key}}`).join(String(value)),
923
+ template
924
+ );
925
+ }
926
+ function formatNativeRelativeTime(iso, labels, now = /* @__PURE__ */ new Date()) {
927
+ const then = new Date(iso).getTime();
928
+ if (Number.isNaN(then)) return "";
929
+ const seconds = Math.max(0, Math.floor((now.getTime() - then) / 1e3));
930
+ if (seconds < 10) return labels.relativeNow;
931
+ if (seconds < 60) return formatLabel(labels.relativeSeconds, { value: seconds });
932
+ const minutes = Math.floor(seconds / 60);
933
+ if (minutes < 60) return formatLabel(labels.relativeMinutes, { value: minutes });
934
+ const hours = Math.floor(minutes / 60);
935
+ if (hours < 24) return formatLabel(labels.relativeHours, { value: hours });
936
+ const days = Math.floor(hours / 24);
937
+ if (days < 14) return formatLabel(labels.relativeDays, { value: days });
938
+ return new Date(iso).toLocaleDateString();
939
+ }
940
+ function nativeTimelineStatusLabel(status, labels) {
941
+ switch (status) {
942
+ case "running":
943
+ return labels.statusRunning;
944
+ case "complete":
945
+ return labels.statusComplete;
946
+ case "failed":
947
+ return labels.statusFailed;
948
+ case "cancelled":
949
+ return labels.statusCancelled;
950
+ }
951
+ }
952
+ function nativeGoalActionLabel(action, labels) {
953
+ return labels.goalAction[action];
954
+ }
955
+ function nativeHumanInputRequestPreview(requests) {
956
+ return requests.flatMap(
957
+ (request) => request.questions.map((question) => question.label?.trim() || question.prompt.trim())
958
+ ).filter((label) => label.length > 0).join(" \xB7 ");
959
+ }
960
+ function nativeHumanInputSummary(item, labels) {
961
+ const questions = item.questions.map((question) => question.label ? `${question.label}: ${question.prompt}` : question.prompt).join("\n");
962
+ const outcome = labels.humanInputOutcome[item.response.outcome];
963
+ const answers = item.answers.map(
964
+ (answer) => `${answer.label}: ${answer.values.join(", ") || labels.humanInputOutcome.answered}`
965
+ ).join("\n");
966
+ return [questions, answers ? `${outcome}: ${answers}` : outcome].filter(Boolean).join("\n");
967
+ }
968
+ function nativeContextCompactionPhaseLabel(phase, labels) {
969
+ return labels.contextCompactionPhase[phase];
970
+ }
971
+ function nativeMemoryActionLabel(item, labels) {
972
+ if (item.variant === "saved") return labels.memoryAction.saved;
973
+ if (item.replacementPreview) return labels.memoryAction.corrected;
974
+ return item.action === "updated" ? labels.memoryAction.updated : labels.memoryAction.archived;
975
+ }
976
+ function nativeFleetDecisionSummary(item, labels) {
977
+ const outcome = item.actualOutcome === "selected" && item.actualCandidateKey ? formatLabel(labels.fleetOutcome.selected, { candidate: item.actualCandidateKey }) : item.actualOutcome === "waiting" ? labels.fleetOutcome.waiting : labels.fleetOutcome.none;
978
+ const reason = (() => {
979
+ switch (item.actualReason) {
980
+ case "lease_reused":
981
+ return labels.fleetReason.leaseReused;
982
+ case "pin":
983
+ return labels.fleetReason.pin;
984
+ case "rotation":
985
+ return labels.fleetReason.rotation;
986
+ case "active":
987
+ return labels.fleetReason.active;
988
+ case "all_capped":
989
+ return labels.fleetReason.allCapped;
990
+ case "none":
991
+ return labels.fleetReason.none;
992
+ }
993
+ })();
994
+ return `${outcome} \xB7 ${reason}`;
995
+ }
996
+ function timelineAccessibilityLabel(item, labels) {
997
+ switch (item.kind) {
998
+ case "user-message":
999
+ return `${labels.user}: ${item.text}`;
1000
+ case "human-input":
1001
+ return `${labels.humanInputAsked}: ${nativeHumanInputSummary(item, labels)}`;
1002
+ case "agent-message":
1003
+ return `${labels.assistant}: ${item.text}`;
1004
+ case "reasoning":
1005
+ return `${labels.reasoning}: ${item.text}`;
1006
+ case "tool-call":
1007
+ return `${labels.tool} ${item.name}, ${nativeTimelineStatusLabel(item.status, labels)}`;
1008
+ case "worker":
1009
+ return `${labels.worker}, ${nativeTimelineStatusLabel(item.status, labels)}${item.prompt ? `: ${item.prompt}` : ""}`;
1010
+ case "sandbox":
1011
+ return `${labels.sandbox} ${item.name}, ${nativeTimelineStatusLabel(item.status, labels)}`;
1012
+ case "startup-phase":
1013
+ return `${labels.activity}, ${nativeTimelineStatusLabel(item.status, labels)}`;
1014
+ case "session-status":
1015
+ return nativeSessionStatusLabel(item.status, labels);
1016
+ case "goal":
1017
+ return `${nativeGoalActionLabel(item.action, labels)}${item.text ? `: ${item.text}` : ""}`;
1018
+ case "notice":
1019
+ return item.text;
1020
+ case "context-compaction":
1021
+ return nativeContextCompactionPhaseLabel(item.phase, labels);
1022
+ case "machine-input-batch":
1023
+ return formatLabel(labels.machineUpdates, { value: item.members.length });
1024
+ case "auth-needed":
1025
+ return formatLabel(labels.connectionRequired, { provider: item.providerDomain });
1026
+ case "knowledge":
1027
+ return `${nativeKnowledgeOutcomeLabel(item, labels)}${item.filename ? `: ${item.filename}` : ""}`;
1028
+ case "memory":
1029
+ return `${nativeMemoryActionLabel(item, labels)}: ${item.replacementPreview ?? item.preview}`;
1030
+ case "fleet-decision":
1031
+ return `${labels.fleetDecision}: ${nativeFleetDecisionSummary(item, labels)}`;
1032
+ case "turn-end":
1033
+ return `${labels.turn}, ${nativeTimelineStatusLabel(item.outcome, labels)}`;
1034
+ case "worker-completion":
1035
+ return item.text;
1036
+ }
1037
+ }
1038
+ function nativeSessionStatusLabel(status, labels) {
1039
+ switch (status) {
1040
+ case "queued":
1041
+ return labels.statusQueued;
1042
+ case "running":
1043
+ return labels.statusRunning;
1044
+ case "recovering":
1045
+ return labels.statusRecovering;
1046
+ case "waiting_capacity":
1047
+ return labels.statusWaitingCapacity;
1048
+ case "idle":
1049
+ return labels.statusIdle;
1050
+ case "requires_action":
1051
+ return labels.statusRequiresAction;
1052
+ case "failed":
1053
+ return labels.statusFailed;
1054
+ case "cancelled":
1055
+ return labels.statusCancelled;
1056
+ }
1057
+ }
1058
+ function validateHumanInputAnswers(questions, drafts) {
1059
+ const answers = [];
1060
+ let valid = true;
1061
+ for (const question of questions) {
1062
+ const draft = drafts[question.id] ?? emptyHumanInputDraft();
1063
+ const values = question.kind === "text" ? draft.values.filter((value) => value.trim().length > 0) : draft.values;
1064
+ const other = draft.otherSelected ? draft.other.trim() : "";
1065
+ const supplied = values.length + (other.length > 0 ? 1 : 0);
1066
+ if (question.kind !== "text" && draft.otherSelected && other.length === 0) valid = false;
1067
+ if (question.required && supplied === 0) valid = false;
1068
+ if (question.kind !== "text") {
1069
+ const minimum = question.validation?.minSelections;
1070
+ const maximum = question.kind === "single_select" ? 1 : question.validation?.maxSelections;
1071
+ if (minimum != null && supplied < minimum) valid = false;
1072
+ if (maximum != null && supplied > maximum) valid = false;
1073
+ }
1074
+ if (supplied > 0) {
1075
+ answers.push({
1076
+ questionId: question.id,
1077
+ values,
1078
+ ...other ? { other } : {}
1079
+ });
1080
+ }
1081
+ }
1082
+ return { answers, valid };
1083
+ }
1084
+ function emptyHumanInputDraft() {
1085
+ return { values: [], other: "", otherSelected: false };
1086
+ }
1087
+ var NO_COMPOSER_INSETS = { bottom: 0, left: 0, right: 0 };
1088
+ var NO_RESTORED_RESOURCES = [];
1089
+ function OpenGeniNativeSessionView({
1090
+ controller,
1091
+ theme = DEFAULT_OPENGENI_NATIVE_THEME,
1092
+ labels: labelOverrides,
1093
+ header,
1094
+ composerAccessory,
1095
+ composerSafeAreaInsets = NO_COMPOSER_INSETS,
1096
+ renderComposer,
1097
+ renderComposerActions,
1098
+ renderComposerSurface,
1099
+ renderTool,
1100
+ renderMessage,
1101
+ summarizeTurn,
1102
+ formatError,
1103
+ keyboardVerticalOffset = 0,
1104
+ testID
1105
+ }) {
1106
+ const labels = useMemo(
1107
+ () => ({ ...DEFAULT_OPENGENI_NATIVE_LABELS, ...labelOverrides }),
1108
+ [labelOverrides]
1109
+ );
1110
+ const listRef = useRef(null);
1111
+ const followTail = useRef(true);
1112
+ const humanInputRequests = controller.humanInput.requests;
1113
+ const humanInputRequestKey = humanInputRequests.map((request) => request.id).join("|");
1114
+ const [humanInputOpen, setHumanInputOpen] = useState(true);
1115
+ const humanInputExpanded = humanInputRequests.length > 0 && humanInputOpen;
1116
+ const auxiliarySurfacesVisible = controller.queue.queue.length > 0 || controller.queue.effectiveControl?.state === "paused" && controller.queue.effectiveControl.resumeOptions.length > 1;
1117
+ useEffect(() => {
1118
+ if (humanInputRequestKey) setHumanInputOpen(true);
1119
+ }, [humanInputRequestKey]);
1120
+ const onScroll = useCallback((event) => {
1121
+ const { contentOffset, contentSize, layoutMeasurement } = event.nativeEvent;
1122
+ followTail.current = contentSize.height - (contentOffset.y + layoutMeasurement.height) < 96;
1123
+ }, []);
1124
+ return /* @__PURE__ */ jsxs(
1125
+ KeyboardAvoidingView,
1126
+ {
1127
+ behavior: Platform.OS === "ios" ? "padding" : void 0,
1128
+ keyboardVerticalOffset,
1129
+ style: [styles.root, { backgroundColor: theme.colors.background }],
1130
+ testID,
1131
+ children: [
1132
+ header,
1133
+ /* @__PURE__ */ jsx(View, { style: styles.body, children: /* @__PURE__ */ jsxs(View, { style: styles.timelineColumn, children: [
1134
+ /* @__PURE__ */ jsxs(View, { style: styles.timelineStage, children: [
1135
+ /* @__PURE__ */ jsx(
1136
+ ConnectionBanner,
1137
+ {
1138
+ controller,
1139
+ formatError,
1140
+ labels,
1141
+ theme
1142
+ }
1143
+ ),
1144
+ /* @__PURE__ */ jsx(View, { style: humanInputExpanded ? styles.timelineHidden : styles.timeline, children: /* @__PURE__ */ jsx(
1145
+ FlatList,
1146
+ {
1147
+ contentContainerStyle: [
1148
+ styles.timelineContent,
1149
+ renderComposer ? styles.timelineContentNativeComposer : null,
1150
+ controller.timelineGroups.length === 0 && styles.emptyTimeline
1151
+ ],
1152
+ data: controller.timelineGroups,
1153
+ keyboardDismissMode: "interactive",
1154
+ keyboardShouldPersistTaps: "handled",
1155
+ keyExtractor: timelineGroupKey,
1156
+ ListHeaderComponent: controller.hasOlder ? /* @__PURE__ */ jsx(
1157
+ ActionButton,
1158
+ {
1159
+ label: controller.loadingOlder ? labels.loadingEarlier : labels.loadEarlier,
1160
+ onPress: () => void controller.loadOlder(),
1161
+ theme,
1162
+ disabled: controller.loadingOlder,
1163
+ variant: "quiet"
1164
+ }
1165
+ ) : void 0,
1166
+ ListEmptyComponent: controller.initialLoading ? /* @__PURE__ */ jsx(ActivityIndicator, { color: theme.colors.accent }) : /* @__PURE__ */ jsxs(View, { accessible: true, accessibilityRole: "summary", style: styles.emptyState, children: [
1167
+ /* @__PURE__ */ jsx(Text, { style: [styles.emptyTitle, { color: theme.colors.text }], children: labels.emptyTitle }),
1168
+ /* @__PURE__ */ jsx(
1169
+ Text,
1170
+ {
1171
+ style: [styles.emptyDescription, { color: theme.colors.secondaryText }],
1172
+ children: labels.emptyDescription
1173
+ }
1174
+ )
1175
+ ] }),
1176
+ onContentSizeChange: () => {
1177
+ if (followTail.current) listRef.current?.scrollToEnd({ animated: true });
1178
+ },
1179
+ onScroll,
1180
+ ref: listRef,
1181
+ renderItem: ({ item }) => /* @__PURE__ */ jsx(
1182
+ TimelineGroupRow,
1183
+ {
1184
+ group: item,
1185
+ labels,
1186
+ renderMessage,
1187
+ renderTool,
1188
+ summarizeTurn,
1189
+ theme
1190
+ }
1191
+ ),
1192
+ scrollEventThrottle: 32,
1193
+ style: styles.timeline
1194
+ }
1195
+ ) }),
1196
+ humanInputRequests.length > 0 ? /* @__PURE__ */ jsx(
1197
+ HumanInputWorkspace,
1198
+ {
1199
+ expanded: humanInputOpen,
1200
+ labels,
1201
+ onRespond: (requestId, response) => void controller.humanInput.respond(requestId, response),
1202
+ onToggle: () => setHumanInputOpen((current) => !current),
1203
+ requests: humanInputRequests,
1204
+ respondingRequestId: controller.humanInput.respondingRequestId,
1205
+ theme
1206
+ }
1207
+ ) : null,
1208
+ controller.approvals.length > 0 ? /* @__PURE__ */ jsx(View, { pointerEvents: "box-none", style: styles.approvalOverlay, children: /* @__PURE__ */ jsx(ApprovalSurface, { controller, labels, theme }) }) : null
1209
+ ] }),
1210
+ auxiliarySurfacesVisible ? /* @__PURE__ */ jsx(
1211
+ ScrollView,
1212
+ {
1213
+ contentContainerStyle: styles.sessionSurfacesContent,
1214
+ keyboardShouldPersistTaps: "handled",
1215
+ style: styles.sessionSurfaces,
1216
+ children: /* @__PURE__ */ jsx(SessionSurfaces, { controller, labels, theme })
1217
+ }
1218
+ ) : null,
1219
+ /* @__PURE__ */ jsxs(
1220
+ View,
1221
+ {
1222
+ style: [
1223
+ styles.composerDock,
1224
+ {
1225
+ backgroundColor: theme.colors.background,
1226
+ borderTopColor: theme.colors.border,
1227
+ borderTopWidth: StyleSheet.hairlineWidth,
1228
+ paddingBottom: Math.max(8, composerSafeAreaInsets.bottom),
1229
+ paddingLeft: Math.max(12, composerSafeAreaInsets.left + 12),
1230
+ paddingRight: Math.max(12, composerSafeAreaInsets.right + 12)
1231
+ }
1232
+ ],
1233
+ children: [
1234
+ /* @__PURE__ */ jsx(
1235
+ OpenGeniNativeAttachmentStrip,
1236
+ {
1237
+ attachments: controller.attachments,
1238
+ labels,
1239
+ onRemoveRestoredResource: controller.composer.removeRestoredResource,
1240
+ restoredResources: controller.composer.restoredResources,
1241
+ theme
1242
+ }
1243
+ ),
1244
+ /* @__PURE__ */ jsx(
1245
+ Composer,
1246
+ {
1247
+ accessory: composerAccessory,
1248
+ controller,
1249
+ labels,
1250
+ renderActions: renderComposerActions,
1251
+ renderComposer,
1252
+ renderSurface: renderComposerSurface,
1253
+ theme
1254
+ }
1255
+ )
1256
+ ]
1257
+ }
1258
+ )
1259
+ ] }) })
1260
+ ]
1261
+ }
1262
+ );
1263
+ }
1264
+ function timelineGroupKey(group) {
1265
+ return group.kind === "item" ? `item-${group.item.id}` : group.id;
1266
+ }
1267
+ function TimelineGroupRow({
1268
+ group,
1269
+ labels,
1270
+ renderMessage,
1271
+ renderTool,
1272
+ summarizeTurn,
1273
+ theme
1274
+ }) {
1275
+ switch (group.kind) {
1276
+ case "item":
1277
+ return /* @__PURE__ */ jsx(
1278
+ TimelineRow,
1279
+ {
1280
+ item: group.item,
1281
+ labels,
1282
+ renderMessage,
1283
+ renderTool,
1284
+ theme
1285
+ }
1286
+ );
1287
+ case "activity":
1288
+ return /* @__PURE__ */ jsx(
1289
+ ActivityRail,
1290
+ {
1291
+ items: group.items,
1292
+ labels,
1293
+ renderMessage,
1294
+ renderTool,
1295
+ theme
1296
+ }
1297
+ );
1298
+ case "turn":
1299
+ return /* @__PURE__ */ jsx(
1300
+ SettledTurn,
1301
+ {
1302
+ group,
1303
+ labels,
1304
+ renderMessage,
1305
+ renderTool,
1306
+ summarizeTurn,
1307
+ theme
1308
+ }
1309
+ );
1310
+ }
1311
+ }
1312
+ function SettledTurn({
1313
+ group,
1314
+ labels,
1315
+ renderMessage,
1316
+ renderTool,
1317
+ summarizeTurn,
1318
+ theme
1319
+ }) {
1320
+ const [open, setOpen] = useState(group.outcome === "failed");
1321
+ const items = useMemo(() => collectTurnActivityItems(group.groups), [group.groups]);
1322
+ const durationMs = turnDurationMs(group.startedAt, group.endedAt);
1323
+ const summary = summarizeTurn?.({
1324
+ items,
1325
+ toolCalls: items.filter((item) => item.kind === "tool-call"),
1326
+ outcome: group.outcome,
1327
+ failureText: group.failureText,
1328
+ durationMs,
1329
+ settled: items.every((item) => !activityItemRunning(item)),
1330
+ contextCompactionCount: group.contextCompactionCount ?? 0
1331
+ }) ?? {
1332
+ content: `${labels.activity} \xB7 ${items.length}`,
1333
+ accessibilityLabel: `${labels.activity}, ${items.length}`
1334
+ };
1335
+ return /* @__PURE__ */ jsxs(View, { style: styles.turnGroup, children: [
1336
+ /* @__PURE__ */ jsxs(
1337
+ Pressable,
1338
+ {
1339
+ accessibilityLabel: summary.accessibilityLabel ?? summary.content,
1340
+ accessibilityRole: "button",
1341
+ accessibilityState: { expanded: open },
1342
+ onPress: () => setOpen((current) => !current),
1343
+ style: ({ pressed }) => [
1344
+ styles.turnSummary,
1345
+ {
1346
+ backgroundColor: group.outcome === "failed" ? theme.colors.dangerSurface : theme.colors.elevated,
1347
+ borderColor: group.outcome === "failed" ? theme.colors.danger : theme.colors.border,
1348
+ opacity: pressed ? 0.72 : 1
1349
+ }
1350
+ ],
1351
+ children: [
1352
+ /* @__PURE__ */ jsx(Text, { style: [styles.turnChevron, { color: theme.colors.mutedText }], children: open ? "\u2304" : "\u203A" }),
1353
+ /* @__PURE__ */ jsx(
1354
+ Text,
1355
+ {
1356
+ accessibilityElementsHidden: true,
1357
+ importantForAccessibility: "no",
1358
+ style: [
1359
+ styles.turnOutcome,
1360
+ {
1361
+ color: group.outcome === "failed" ? theme.colors.danger : group.outcome === "complete" ? theme.colors.success : theme.colors.mutedText
1362
+ }
1363
+ ],
1364
+ children: group.outcome === "complete" ? "\u2713" : group.outcome === "failed" ? "!" : "\u2013"
1365
+ }
1366
+ ),
1367
+ /* @__PURE__ */ jsx(
1368
+ Text,
1369
+ {
1370
+ numberOfLines: 1,
1371
+ style: [
1372
+ styles.turnSummaryText,
1373
+ {
1374
+ color: group.outcome === "failed" ? theme.colors.danger : theme.colors.secondaryText
1375
+ }
1376
+ ],
1377
+ children: summary.content
1378
+ }
1379
+ ),
1380
+ summary.duration ? /* @__PURE__ */ jsx(Text, { style: [styles.turnDuration, { color: theme.colors.mutedText }], children: summary.duration }) : null
1381
+ ]
1382
+ }
1383
+ ),
1384
+ open ? /* @__PURE__ */ jsxs(View, { style: [styles.turnRail, { borderLeftColor: theme.colors.border }], children: [
1385
+ group.groups.map((child) => /* @__PURE__ */ jsx(
1386
+ TimelineGroupRow,
1387
+ {
1388
+ group: child,
1389
+ labels,
1390
+ renderMessage,
1391
+ renderTool,
1392
+ summarizeTurn,
1393
+ theme
1394
+ },
1395
+ timelineGroupKey(child)
1396
+ )),
1397
+ group.failureText ? /* @__PURE__ */ jsx(Text, { style: [styles.secondaryText, { color: theme.colors.danger }], children: group.failureText }) : null
1398
+ ] }) : null
1399
+ ] });
1400
+ }
1401
+ function collectTurnActivityItems(groups) {
1402
+ return groups.flatMap((group) => {
1403
+ switch (group.kind) {
1404
+ case "activity":
1405
+ return group.items;
1406
+ case "turn":
1407
+ return collectTurnActivityItems(group.groups);
1408
+ case "item":
1409
+ return isActivityItem(group.item) ? [group.item] : [];
1410
+ }
1411
+ });
1412
+ }
1413
+ function isActivityItem(item) {
1414
+ return item.kind === "agent-message" && item.phase === "commentary" || item.kind === "reasoning" || item.kind === "tool-call" || item.kind === "worker" || item.kind === "sandbox" || item.kind === "memory" || item.kind === "fleet-decision";
1415
+ }
1416
+ function activityItemRunning(item) {
1417
+ return (item.kind === "reasoning" || item.kind === "agent-message") && item.streaming || (item.kind === "tool-call" || item.kind === "worker" || item.kind === "sandbox") && item.status === "running";
1418
+ }
1419
+ function turnDurationMs(startedAt, endedAt) {
1420
+ const start = Date.parse(startedAt);
1421
+ const end = Date.parse(endedAt);
1422
+ return Number.isFinite(start) && Number.isFinite(end) && end >= start ? end - start : void 0;
1423
+ }
1424
+ function ActivityRail({
1425
+ items,
1426
+ labels,
1427
+ renderMessage,
1428
+ renderTool,
1429
+ theme
1430
+ }) {
1431
+ return /* @__PURE__ */ jsx(View, { style: [styles.activityRail, { borderLeftColor: theme.colors.border }], children: items.map((item) => /* @__PURE__ */ jsx(
1432
+ TimelineRow,
1433
+ {
1434
+ compactActivity: true,
1435
+ item,
1436
+ labels,
1437
+ renderMessage,
1438
+ renderTool,
1439
+ theme
1440
+ },
1441
+ item.id
1442
+ )) });
1443
+ }
1444
+ function ConnectionBanner({
1445
+ controller,
1446
+ formatError,
1447
+ labels,
1448
+ theme
1449
+ }) {
1450
+ const message = !controller.active ? labels.offlinePaused : controller.connectionState === "reconnecting" ? labels.reconnecting : controller.connectionState === "connecting" ? labels.connecting : null;
1451
+ const error = controller.error;
1452
+ if (!message && !error) return null;
1453
+ const displayMessage = error ? formatError?.(error) ?? labels.error : message;
1454
+ return /* @__PURE__ */ jsxs(
1455
+ View,
1456
+ {
1457
+ accessibilityLiveRegion: "polite",
1458
+ style: [
1459
+ styles.banner,
1460
+ {
1461
+ backgroundColor: error ? theme.colors.dangerSurface : theme.colors.elevated
1462
+ }
1463
+ ],
1464
+ children: [
1465
+ /* @__PURE__ */ jsx(
1466
+ Text,
1467
+ {
1468
+ numberOfLines: 3,
1469
+ style: [
1470
+ styles.bannerText,
1471
+ { color: error ? theme.colors.danger : theme.colors.secondaryText }
1472
+ ],
1473
+ children: displayMessage
1474
+ }
1475
+ ),
1476
+ error ? /* @__PURE__ */ jsx(
1477
+ ActionButton,
1478
+ {
1479
+ label: labels.retry,
1480
+ onPress: () => void controller.refresh(),
1481
+ theme,
1482
+ variant: "quiet"
1483
+ }
1484
+ ) : null
1485
+ ]
1486
+ }
1487
+ );
1488
+ }
1489
+ function TimelineRow({
1490
+ compactActivity = false,
1491
+ item,
1492
+ labels,
1493
+ renderMessage,
1494
+ renderTool,
1495
+ theme
1496
+ }) {
1497
+ if (item.kind === "tool-call" && renderTool) {
1498
+ const rendered = renderTool(item, { compact: compactActivity, labels, theme });
1499
+ if (rendered !== null && rendered !== void 0) return /* @__PURE__ */ jsx(Fragment, { children: rendered });
1500
+ }
1501
+ if (item.kind === "user-message" || item.kind === "agent-message") {
1502
+ const user = item.kind === "user-message";
1503
+ const role = user ? "user" : "assistant";
1504
+ return /* @__PURE__ */ jsx(
1505
+ View,
1506
+ {
1507
+ accessible: true,
1508
+ accessibilityLabel: timelineAccessibilityLabel(item, labels),
1509
+ style: [styles.messageRow, user ? styles.userMessageRow : styles.agentMessageRow],
1510
+ children: /* @__PURE__ */ jsxs(
1511
+ View,
1512
+ {
1513
+ style: [
1514
+ styles.messageBubble,
1515
+ user ? styles.userMessageBubble : styles.agentMessage,
1516
+ {
1517
+ backgroundColor: user ? theme.colors.userBubble : "transparent",
1518
+ borderColor: user ? theme.colors.border : "transparent"
1519
+ }
1520
+ ],
1521
+ children: [
1522
+ renderMessage ? renderMessage(item.text, role, { theme }) : /* @__PURE__ */ jsx(
1523
+ Text,
1524
+ {
1525
+ selectable: true,
1526
+ style: [
1527
+ styles.messageText,
1528
+ { color: user ? theme.colors.userBubbleText : theme.colors.text }
1529
+ ],
1530
+ children: item.text
1531
+ }
1532
+ ),
1533
+ item.kind === "agent-message" && item.streaming ? /* @__PURE__ */ jsx(ActivityIndicator, { color: theme.colors.accent, size: "small" }) : null
1534
+ ]
1535
+ }
1536
+ )
1537
+ }
1538
+ );
1539
+ }
1540
+ if (item.kind === "reasoning") {
1541
+ if (compactActivity) {
1542
+ return /* @__PURE__ */ jsx(
1543
+ ReasoningDisclosure,
1544
+ {
1545
+ item,
1546
+ labels,
1547
+ renderMessage,
1548
+ theme
1549
+ }
1550
+ );
1551
+ }
1552
+ return /* @__PURE__ */ jsx(TimelineCard, { item, labels, theme, title: labels.reasoning, children: renderMessage ? renderMessage(item.text, "reasoning", { theme }) : /* @__PURE__ */ jsx(Text, { selectable: true, style: [styles.secondaryText, { color: theme.colors.secondaryText }], children: item.text }) });
1553
+ }
1554
+ if (item.kind === "notice") {
1555
+ return /* @__PURE__ */ jsx(NoticeRow, { item, labels, theme });
1556
+ }
1557
+ if (item.kind === "session-status") {
1558
+ return /* @__PURE__ */ jsx(SessionStatusRow, { item, labels, theme });
1559
+ }
1560
+ if (item.kind === "tool-call") {
1561
+ return /* @__PURE__ */ jsx(
1562
+ NativeActivityDisclosure,
1563
+ {
1564
+ compact: compactActivity,
1565
+ item,
1566
+ labels,
1567
+ theme
1568
+ }
1569
+ );
1570
+ }
1571
+ if (isActivityItem(item)) {
1572
+ return /* @__PURE__ */ jsx(
1573
+ NativeActivityDisclosure,
1574
+ {
1575
+ compact: compactActivity,
1576
+ item,
1577
+ labels,
1578
+ theme
1579
+ }
1580
+ );
1581
+ }
1582
+ const title = timelineTitle(item, labels);
1583
+ const detail = timelineSummary(item, labels);
1584
+ if (compactActivity) {
1585
+ return /* @__PURE__ */ jsxs(
1586
+ View,
1587
+ {
1588
+ accessible: true,
1589
+ accessibilityLabel: timelineAccessibilityLabel(item, labels),
1590
+ style: styles.step,
1591
+ children: [
1592
+ /* @__PURE__ */ jsx(Text, { style: [styles.stepLabel, { color: theme.colors.mutedText }], children: title }),
1593
+ detail ? /* @__PURE__ */ jsx(Text, { selectable: true, style: [styles.secondaryText, { color: theme.colors.secondaryText }], children: detail }) : null
1594
+ ]
1595
+ }
1596
+ );
1597
+ }
1598
+ return /* @__PURE__ */ jsx(TimelineCard, { item, labels, theme, title, children: detail ? /* @__PURE__ */ jsx(Text, { selectable: true, style: [styles.secondaryText, { color: theme.colors.secondaryText }], children: detail }) : null });
1599
+ }
1600
+ function NativeActivityDisclosure({
1601
+ compact,
1602
+ item,
1603
+ labels,
1604
+ theme
1605
+ }) {
1606
+ const presentation = nativeActivityPresentation(item, labels);
1607
+ const [open, setOpen] = useState(presentation.expandedByDefault);
1608
+ const expandable = Boolean(presentation.detail);
1609
+ const foreground = presentation.tone === "failed" ? theme.colors.danger : presentation.tone === "running" ? theme.colors.accent : theme.colors.mutedText;
1610
+ const technical = item.kind === "tool-call" || item.kind === "sandbox";
1611
+ return /* @__PURE__ */ jsxs(
1612
+ View,
1613
+ {
1614
+ style: [
1615
+ styles.activityDisclosure,
1616
+ !compact && styles.activityDisclosureCard,
1617
+ !compact && {
1618
+ backgroundColor: theme.colors.surface,
1619
+ borderColor: presentation.tone === "failed" ? theme.colors.danger : theme.colors.border
1620
+ }
1621
+ ],
1622
+ children: [
1623
+ /* @__PURE__ */ jsxs(
1624
+ Pressable,
1625
+ {
1626
+ accessible: true,
1627
+ accessibilityLabel: timelineAccessibilityLabel(item, labels),
1628
+ accessibilityRole: expandable ? "button" : void 0,
1629
+ accessibilityState: expandable ? { expanded: open } : void 0,
1630
+ disabled: !expandable,
1631
+ onPress: () => setOpen((current) => !current),
1632
+ style: ({ pressed }) => [
1633
+ styles.activityDisclosureHeader,
1634
+ { opacity: pressed && expandable ? 0.68 : 1 }
1635
+ ],
1636
+ children: [
1637
+ /* @__PURE__ */ jsx(Text, { style: [styles.activityDisclosureChevron, { color: theme.colors.mutedText }], children: expandable ? open ? "\u2304" : "\u203A" : "\xB7" }),
1638
+ /* @__PURE__ */ jsx(View, { style: [styles.activityStatusDot, { backgroundColor: foreground }] }),
1639
+ /* @__PURE__ */ jsx(
1640
+ Text,
1641
+ {
1642
+ numberOfLines: 1,
1643
+ style: [
1644
+ styles.activityDisclosureTitle,
1645
+ { color: presentation.tone === "failed" ? theme.colors.danger : theme.colors.text }
1646
+ ],
1647
+ children: presentation.title
1648
+ }
1649
+ ),
1650
+ !open && presentation.preview ? /* @__PURE__ */ jsx(
1651
+ Text,
1652
+ {
1653
+ numberOfLines: 1,
1654
+ style: [styles.activityDisclosurePreview, { color: theme.colors.mutedText }],
1655
+ children: presentation.preview
1656
+ }
1657
+ ) : null,
1658
+ presentation.status ? /* @__PURE__ */ jsx(Text, { numberOfLines: 1, style: [styles.activityDisclosureStatus, { color: foreground }], children: presentation.status }) : null
1659
+ ]
1660
+ }
1661
+ ),
1662
+ open && presentation.detail ? /* @__PURE__ */ jsx(View, { style: styles.activityDisclosureBody, children: /* @__PURE__ */ jsx(
1663
+ Text,
1664
+ {
1665
+ selectable: true,
1666
+ style: [
1667
+ technical ? styles.codeText : styles.secondaryText,
1668
+ { color: theme.colors.secondaryText }
1669
+ ],
1670
+ children: presentation.detail
1671
+ }
1672
+ ) }) : null
1673
+ ]
1674
+ }
1675
+ );
1676
+ }
1677
+ function timelineTitle(item, labels) {
1678
+ switch (item.kind) {
1679
+ case "worker":
1680
+ case "worker-completion":
1681
+ return labels.worker;
1682
+ case "sandbox":
1683
+ return item.name;
1684
+ case "startup-phase":
1685
+ return labels.activity;
1686
+ case "session-status":
1687
+ return nativeSessionStatusLabel(item.status, labels);
1688
+ case "human-input":
1689
+ return labels.humanInputAsked;
1690
+ case "goal":
1691
+ return nativeGoalActionLabel(item.action, labels);
1692
+ case "context-compaction":
1693
+ return nativeContextCompactionPhaseLabel(item.phase, labels);
1694
+ case "memory":
1695
+ return nativeMemoryActionLabel(item, labels);
1696
+ case "knowledge":
1697
+ return nativeKnowledgeOutcomeLabel(item, labels);
1698
+ case "fleet-decision":
1699
+ return labels.fleetDecision;
1700
+ case "turn-end":
1701
+ return labels.turn;
1702
+ case "notice":
1703
+ case "machine-input-batch":
1704
+ case "auth-needed":
1705
+ return labels.activity;
1706
+ }
1707
+ }
1708
+ function ReasoningDisclosure({
1709
+ item,
1710
+ labels,
1711
+ renderMessage,
1712
+ theme
1713
+ }) {
1714
+ const [open, setOpen] = useState(false);
1715
+ const presentation = nativeActivityPresentation(item, labels);
1716
+ const { preview, title } = presentation;
1717
+ return /* @__PURE__ */ jsxs(View, { style: styles.disclosure, children: [
1718
+ /* @__PURE__ */ jsxs(
1719
+ Pressable,
1720
+ {
1721
+ accessibilityLabel: `${title}: ${preview}`,
1722
+ accessibilityRole: "button",
1723
+ accessibilityState: { expanded: open },
1724
+ onPress: () => setOpen((current) => !current),
1725
+ style: ({ pressed }) => [
1726
+ styles.disclosureHeader,
1727
+ { backgroundColor: pressed ? theme.colors.elevated : "transparent" }
1728
+ ],
1729
+ children: [
1730
+ /* @__PURE__ */ jsx(Text, { style: [styles.disclosureChevron, { color: theme.colors.mutedText }], children: open ? "\u2304" : "\u203A" }),
1731
+ /* @__PURE__ */ jsx(Text, { style: [styles.disclosureIcon, { color: theme.colors.mutedText }], children: "\u25CC" }),
1732
+ /* @__PURE__ */ jsx(
1733
+ Text,
1734
+ {
1735
+ numberOfLines: 1,
1736
+ style: [
1737
+ styles.disclosureTitle,
1738
+ {
1739
+ color: theme.colors.mutedText,
1740
+ fontStyle: item.streaming ? "normal" : "italic",
1741
+ fontWeight: item.streaming ? "600" : "400"
1742
+ }
1743
+ ],
1744
+ children: title
1745
+ }
1746
+ ),
1747
+ !open ? /* @__PURE__ */ jsx(
1748
+ Text,
1749
+ {
1750
+ numberOfLines: 1,
1751
+ style: [styles.disclosurePreview, { color: theme.colors.mutedText }],
1752
+ children: preview
1753
+ }
1754
+ ) : null
1755
+ ]
1756
+ }
1757
+ ),
1758
+ open ? /* @__PURE__ */ jsx(View, { style: styles.disclosureBody, children: renderMessage ? renderMessage(item.text, "reasoning", { theme }) : /* @__PURE__ */ jsx(Text, { selectable: true, style: [styles.secondaryText, { color: theme.colors.secondaryText }], children: presentation.detail }) }) : null
1759
+ ] });
1760
+ }
1761
+ function NoticeRow({
1762
+ item,
1763
+ labels,
1764
+ theme
1765
+ }) {
1766
+ const [detailsOpen, setDetailsOpen] = useState(false);
1767
+ const action = item.action;
1768
+ const failed = item.tone === "failed";
1769
+ const waiting = item.tone === "waiting" || item.tone === "input";
1770
+ const foreground = failed ? theme.colors.danger : waiting ? theme.colors.warning : theme.colors.secondaryText;
1771
+ const background = failed ? theme.colors.dangerSurface : waiting ? theme.colors.warningSurface : theme.colors.elevated;
1772
+ return /* @__PURE__ */ jsxs(
1773
+ View,
1774
+ {
1775
+ accessibilityLiveRegion: "polite",
1776
+ accessible: true,
1777
+ accessibilityLabel: timelineAccessibilityLabel(item, labels),
1778
+ style: [styles.notice, { backgroundColor: background, borderColor: foreground }],
1779
+ children: [
1780
+ /* @__PURE__ */ jsx(Text, { style: [styles.noticeIcon, { color: foreground }], children: "!" }),
1781
+ /* @__PURE__ */ jsxs(View, { style: styles.noticeContent, children: [
1782
+ /* @__PURE__ */ jsx(Text, { selectable: true, style: [styles.secondaryText, { color: foreground }], children: item.text }),
1783
+ item.details ? /* @__PURE__ */ jsxs(View, { style: styles.noticeDetails, children: [
1784
+ /* @__PURE__ */ jsx(
1785
+ Pressable,
1786
+ {
1787
+ accessibilityRole: "button",
1788
+ accessibilityState: { expanded: detailsOpen },
1789
+ onPress: () => setDetailsOpen((current) => !current),
1790
+ children: /* @__PURE__ */ jsxs(Text, { style: [styles.noticeAction, { color: foreground }], children: [
1791
+ detailsOpen ? "\u2304" : "\u203A",
1792
+ " ",
1793
+ item.details.label
1794
+ ] })
1795
+ }
1796
+ ),
1797
+ detailsOpen ? /* @__PURE__ */ jsx(Text, { selectable: true, style: [styles.codeText, { color: foreground }], children: boundedJson(item.details.value) }) : null
1798
+ ] }) : null
1799
+ ] }),
1800
+ action ? /* @__PURE__ */ jsx(
1801
+ Pressable,
1802
+ {
1803
+ accessibilityRole: "link",
1804
+ onPress: () => void Linking.openURL(action.url),
1805
+ style: styles.noticeLink,
1806
+ children: /* @__PURE__ */ jsx(Text, { style: [styles.noticeAction, { color: foreground }], children: action.label })
1807
+ }
1808
+ ) : null
1809
+ ]
1810
+ }
1811
+ );
1812
+ }
1813
+ function SessionStatusRow({
1814
+ item,
1815
+ labels,
1816
+ theme
1817
+ }) {
1818
+ const label = nativeSessionStatusLabel(item.status, labels).toLocaleLowerCase();
1819
+ const dotColor = sessionStatusColor(item.status, theme);
1820
+ const relativeTime = formatNativeRelativeTime(item.occurredAt, labels);
1821
+ return /* @__PURE__ */ jsxs(
1822
+ View,
1823
+ {
1824
+ accessibilityRole: "text",
1825
+ accessible: true,
1826
+ accessibilityLabel: `${label}${relativeTime ? `, ${relativeTime}` : ""}`,
1827
+ style: styles.sessionStatus,
1828
+ children: [
1829
+ /* @__PURE__ */ jsx(View, { style: [styles.sessionStatusRule, { backgroundColor: theme.colors.border }] }),
1830
+ /* @__PURE__ */ jsxs(View, { style: styles.sessionStatusLabel, children: [
1831
+ /* @__PURE__ */ jsx(View, { style: [styles.sessionStatusDot, { backgroundColor: dotColor }] }),
1832
+ /* @__PURE__ */ jsxs(Text, { style: [styles.sessionStatusText, { color: theme.colors.mutedText }], children: [
1833
+ label,
1834
+ relativeTime ? ` \xB7 ${relativeTime}` : ""
1835
+ ] })
1836
+ ] }),
1837
+ /* @__PURE__ */ jsx(View, { style: [styles.sessionStatusRule, { backgroundColor: theme.colors.border }] })
1838
+ ]
1839
+ }
1840
+ );
1841
+ }
1842
+ function sessionStatusColor(status, theme) {
1843
+ switch (status) {
1844
+ case "failed":
1845
+ return theme.colors.danger;
1846
+ case "requires_action":
1847
+ case "waiting_capacity":
1848
+ return theme.colors.warning;
1849
+ case "idle":
1850
+ return theme.colors.success;
1851
+ case "running":
1852
+ case "recovering":
1853
+ return theme.colors.accent;
1854
+ default:
1855
+ return theme.colors.mutedText;
1856
+ }
1857
+ }
1858
+ function TimelineCard({
1859
+ children,
1860
+ item,
1861
+ labels,
1862
+ theme,
1863
+ title
1864
+ }) {
1865
+ return /* @__PURE__ */ jsxs(
1866
+ View,
1867
+ {
1868
+ accessible: true,
1869
+ accessibilityLabel: timelineAccessibilityLabel(item, labels),
1870
+ style: [
1871
+ styles.timelineCard,
1872
+ { backgroundColor: theme.colors.surface, borderColor: theme.colors.border }
1873
+ ],
1874
+ children: [
1875
+ /* @__PURE__ */ jsx(Text, { style: [styles.cardTitle, { color: theme.colors.text }], children: title }),
1876
+ children
1877
+ ]
1878
+ }
1879
+ );
1880
+ }
1881
+ function timelineSummary(item, labels) {
1882
+ switch (item.kind) {
1883
+ case "worker":
1884
+ return item.prompt ?? nativeTimelineStatusLabel(item.status, labels);
1885
+ case "worker-completion":
1886
+ return item.text;
1887
+ case "sandbox":
1888
+ return item.output || item.command || nativeTimelineStatusLabel(item.status, labels);
1889
+ case "startup-phase":
1890
+ return nativeTimelineStatusLabel(item.status, labels);
1891
+ case "session-status":
1892
+ return nativeSessionStatusLabel(item.status, labels);
1893
+ case "human-input":
1894
+ return nativeHumanInputSummary(item, labels);
1895
+ case "goal":
1896
+ return item.text ?? "";
1897
+ case "notice":
1898
+ return item.text;
1899
+ case "context-compaction":
1900
+ return "";
1901
+ case "machine-input-batch":
1902
+ return item.members.map((member) => member.summary).join("\n");
1903
+ case "auth-needed":
1904
+ return item.providerDomain;
1905
+ case "memory":
1906
+ return item.replacementPreview ?? item.preview;
1907
+ case "knowledge":
1908
+ return item.filename ?? "";
1909
+ case "fleet-decision":
1910
+ return nativeFleetDecisionSummary(item, labels);
1911
+ case "turn-end":
1912
+ return item.failureText ?? nativeTimelineStatusLabel(item.outcome, labels);
1913
+ }
1914
+ }
1915
+ function ApprovalSurface({
1916
+ controller,
1917
+ labels,
1918
+ theme
1919
+ }) {
1920
+ return /* @__PURE__ */ jsxs(
1921
+ View,
1922
+ {
1923
+ pointerEvents: "auto",
1924
+ style: [
1925
+ styles.approvalSurface,
1926
+ { backgroundColor: theme.colors.warningSurface, borderColor: theme.colors.warning }
1927
+ ],
1928
+ children: [
1929
+ /* @__PURE__ */ jsx(Text, { style: [styles.actionTitle, { color: theme.colors.text }], children: labels.approvalsTitle }),
1930
+ controller.approvals.map((approval) => /* @__PURE__ */ jsxs(View, { style: styles.approvalItem, children: [
1931
+ /* @__PURE__ */ jsx(Text, { style: [styles.cardTitle, { color: theme.colors.text }], children: approval.name }),
1932
+ /* @__PURE__ */ jsx(
1933
+ Text,
1934
+ {
1935
+ numberOfLines: 3,
1936
+ selectable: true,
1937
+ style: [styles.codeText, { color: theme.colors.secondaryText }],
1938
+ children: boundedJson(approval.arguments)
1939
+ }
1940
+ ),
1941
+ /* @__PURE__ */ jsxs(View, { style: styles.buttonRow, children: [
1942
+ /* @__PURE__ */ jsx(
1943
+ ActionButton,
1944
+ {
1945
+ label: labels.deny,
1946
+ onPress: () => void controller.control.reject(approval.id),
1947
+ theme,
1948
+ variant: "danger",
1949
+ disabled: controller.control.responding
1950
+ }
1951
+ ),
1952
+ /* @__PURE__ */ jsx(
1953
+ ActionButton,
1954
+ {
1955
+ label: labels.approve,
1956
+ onPress: () => void controller.control.approve(approval.id),
1957
+ theme,
1958
+ disabled: controller.control.responding
1959
+ }
1960
+ )
1961
+ ] })
1962
+ ] }, approval.id))
1963
+ ]
1964
+ }
1965
+ );
1966
+ }
1967
+ function SessionSurfaces({
1968
+ controller,
1969
+ labels,
1970
+ theme
1971
+ }) {
1972
+ return /* @__PURE__ */ jsxs(View, { style: styles.actionSurfaces, children: [
1973
+ controller.queue.effectiveControl?.state === "paused" && controller.queue.effectiveControl.resumeOptions.length > 1 ? /* @__PURE__ */ jsxs(View, { style: styles.actionGroup, children: [
1974
+ /* @__PURE__ */ jsx(Text, { style: [styles.actionTitle, { color: theme.colors.text }], children: labels.resume }),
1975
+ controller.queue.effectiveControl.resumeOptions.filter((option) => option.scope !== "selected").map((option) => /* @__PURE__ */ jsxs(
1976
+ View,
1977
+ {
1978
+ style: [
1979
+ styles.actionCard,
1980
+ {
1981
+ backgroundColor: theme.colors.warningSurface,
1982
+ borderColor: theme.colors.warning
1983
+ }
1984
+ ],
1985
+ children: [
1986
+ /* @__PURE__ */ jsx(Text, { style: [styles.secondaryText, { color: theme.colors.text }], children: option.impactCopy }),
1987
+ /* @__PURE__ */ jsx(
1988
+ ActionButton,
1989
+ {
1990
+ label: labels.resume,
1991
+ onPress: () => void controller.composer.resumeScope(option),
1992
+ theme,
1993
+ disabled: controller.composer.resuming
1994
+ }
1995
+ )
1996
+ ]
1997
+ },
1998
+ `${option.scope}:${option.targetId ?? "current"}`
1999
+ ))
2000
+ ] }) : null,
2001
+ controller.queue.queue.length > 0 ? /* @__PURE__ */ jsx(QueueSurface, { controller, labels, theme }) : null
2002
+ ] });
2003
+ }
2004
+ function QueueSurface({
2005
+ controller,
2006
+ labels,
2007
+ theme
2008
+ }) {
2009
+ const [open, setOpen] = useState(false);
2010
+ const edit = async (turn) => {
2011
+ if (controller.composer.hasDraftContent()) return;
2012
+ const draft = await controller.queue.editTurn(turn.id, {
2013
+ expectedDraftRevision: controller.composer.draftRevision,
2014
+ replaceDraft: true
2015
+ });
2016
+ if (draft) controller.composer.applyDraft(draft);
2017
+ };
2018
+ const move = (index, direction) => {
2019
+ const turn = controller.queue.queue[index];
2020
+ if (!turn) return;
2021
+ const beforeTurnId = direction < 0 ? controller.queue.queue[index - 1]?.id ?? null : controller.queue.queue[index + 2]?.id ?? null;
2022
+ void controller.queue.moveTurn(turn.id, beforeTurnId);
2023
+ };
2024
+ return /* @__PURE__ */ jsxs(
2025
+ View,
2026
+ {
2027
+ style: [
2028
+ styles.queueSurface,
2029
+ { backgroundColor: theme.colors.surface, borderColor: theme.colors.border }
2030
+ ],
2031
+ children: [
2032
+ /* @__PURE__ */ jsxs(
2033
+ Pressable,
2034
+ {
2035
+ accessibilityLabel: `${labels.queueTitle}, ${controller.queue.queue.length}`,
2036
+ accessibilityRole: "button",
2037
+ accessibilityState: { expanded: open },
2038
+ onPress: () => setOpen((value) => !value),
2039
+ style: ({ pressed }) => [styles.queueDisclosure, { opacity: pressed ? 0.72 : 1 }],
2040
+ children: [
2041
+ /* @__PURE__ */ jsx(Text, { style: [styles.queueChevron, { color: theme.colors.mutedText }], children: open ? "\u2304" : "\u203A" }),
2042
+ /* @__PURE__ */ jsxs(Text, { numberOfLines: 1, style: [styles.queueTitle, { color: theme.colors.text }], children: [
2043
+ controller.queue.queue.length,
2044
+ " ",
2045
+ labels.queueTitle
2046
+ ] }),
2047
+ !open ? /* @__PURE__ */ jsx(Text, { numberOfLines: 1, style: [styles.queuePreview, { color: theme.colors.mutedText }], children: controller.queue.queue[0]?.prompt }) : null
2048
+ ]
2049
+ }
2050
+ ),
2051
+ open ? controller.queue.queue.map((turn, index) => /* @__PURE__ */ jsxs(View, { style: [styles.queueRow, { borderTopColor: theme.colors.border }], children: [
2052
+ /* @__PURE__ */ jsx(Text, { numberOfLines: 3, style: [styles.queuePrompt, { color: theme.colors.text }], children: turn.prompt }),
2053
+ /* @__PURE__ */ jsxs(View, { style: styles.queueActions, children: [
2054
+ index > 0 ? /* @__PURE__ */ jsx(
2055
+ ActionButton,
2056
+ {
2057
+ compact: true,
2058
+ label: labels.moveEarlier,
2059
+ onPress: () => move(index, -1),
2060
+ theme,
2061
+ variant: "quiet",
2062
+ disabled: controller.queue.mutating
2063
+ }
2064
+ ) : null,
2065
+ index < controller.queue.queue.length - 1 ? /* @__PURE__ */ jsx(
2066
+ ActionButton,
2067
+ {
2068
+ compact: true,
2069
+ label: labels.moveLater,
2070
+ onPress: () => move(index, 1),
2071
+ theme,
2072
+ variant: "quiet",
2073
+ disabled: controller.queue.mutating
2074
+ }
2075
+ ) : null,
2076
+ /* @__PURE__ */ jsx(
2077
+ ActionButton,
2078
+ {
2079
+ compact: true,
2080
+ label: labels.edit,
2081
+ onPress: () => void edit(turn),
2082
+ theme,
2083
+ variant: "quiet",
2084
+ disabled: controller.composer.hasDraftContent() || controller.queue.mutating
2085
+ }
2086
+ ),
2087
+ /* @__PURE__ */ jsx(
2088
+ ActionButton,
2089
+ {
2090
+ compact: true,
2091
+ label: labels.runNext,
2092
+ onPress: () => void controller.queue.steerTurn(turn.id),
2093
+ theme,
2094
+ variant: "quiet",
2095
+ disabled: controller.queue.mutating
2096
+ }
2097
+ ),
2098
+ /* @__PURE__ */ jsx(
2099
+ ActionButton,
2100
+ {
2101
+ compact: true,
2102
+ label: labels.remove,
2103
+ onPress: () => void controller.queue.removeTurn(turn.id),
2104
+ theme,
2105
+ variant: "danger",
2106
+ disabled: controller.queue.mutating
2107
+ }
2108
+ )
2109
+ ] })
2110
+ ] }, turn.id)) : null
2111
+ ]
2112
+ }
2113
+ );
2114
+ }
2115
+ function HumanInputWorkspace({
2116
+ expanded,
2117
+ labels,
2118
+ onRespond,
2119
+ onToggle,
2120
+ requests,
2121
+ respondingRequestId,
2122
+ theme
2123
+ }) {
2124
+ const preview = nativeHumanInputRequestPreview(requests);
2125
+ const identityLabel = preview ? `${labels.questionsTitle}: ${preview}` : labels.questionsTitle;
2126
+ return /* @__PURE__ */ jsxs(
2127
+ View,
2128
+ {
2129
+ style: expanded ? styles.humanInputWorkspaceExpanded : styles.humanInputWorkspaceCollapsed,
2130
+ testID: "opengeni-human-input-workspace",
2131
+ children: [
2132
+ /* @__PURE__ */ jsxs(
2133
+ Pressable,
2134
+ {
2135
+ accessibilityLabel: identityLabel,
2136
+ accessibilityRole: "button",
2137
+ accessibilityState: { expanded },
2138
+ onPress: onToggle,
2139
+ style: ({ pressed }) => [
2140
+ styles.humanInputDisclosure,
2141
+ { backgroundColor: pressed ? theme.colors.elevated : "transparent" }
2142
+ ],
2143
+ children: [
2144
+ /* @__PURE__ */ jsx(Text, { style: [styles.disclosureChevron, { color: theme.colors.mutedText }], children: expanded ? "\u2304" : "\u203A" }),
2145
+ /* @__PURE__ */ jsx(
2146
+ Text,
2147
+ {
2148
+ numberOfLines: 1,
2149
+ style: [styles.humanInputDisclosureTitle, { color: theme.colors.text }],
2150
+ children: labels.questionsTitle
2151
+ }
2152
+ ),
2153
+ !expanded && preview ? /* @__PURE__ */ jsx(
2154
+ Text,
2155
+ {
2156
+ numberOfLines: 1,
2157
+ style: [styles.disclosurePreview, { color: theme.colors.mutedText }],
2158
+ children: preview
2159
+ }
2160
+ ) : null
2161
+ ]
2162
+ }
2163
+ ),
2164
+ expanded ? /* @__PURE__ */ jsx(
2165
+ ScrollView,
2166
+ {
2167
+ contentContainerStyle: styles.humanInputWorkspaceBody,
2168
+ keyboardShouldPersistTaps: "handled",
2169
+ style: styles.humanInputWorkspaceScroll,
2170
+ children: requests.map((request) => /* @__PURE__ */ jsx(
2171
+ HumanInputCard,
2172
+ {
2173
+ labels,
2174
+ onRespond: (response) => onRespond(request.id, response),
2175
+ request,
2176
+ responding: respondingRequestId === request.id,
2177
+ theme
2178
+ },
2179
+ request.id
2180
+ ))
2181
+ }
2182
+ ) : null
2183
+ ]
2184
+ }
2185
+ );
2186
+ }
2187
+ function HumanInputCard({
2188
+ labels,
2189
+ onRespond,
2190
+ request,
2191
+ responding,
2192
+ theme
2193
+ }) {
2194
+ const [drafts, setDrafts] = useState({});
2195
+ const projection = validateHumanInputAnswers(request.questions, drafts);
2196
+ const update = (questionId, updater) => {
2197
+ setDrafts((current) => ({
2198
+ ...current,
2199
+ [questionId]: updater(current[questionId] ?? emptyHumanInputDraft())
2200
+ }));
2201
+ };
2202
+ return /* @__PURE__ */ jsx(View, { style: styles.actionGroup, children: /* @__PURE__ */ jsxs(
2203
+ View,
2204
+ {
2205
+ style: [
2206
+ styles.actionCard,
2207
+ { backgroundColor: theme.colors.surface, borderColor: theme.colors.border }
2208
+ ],
2209
+ children: [
2210
+ request.questions.map((question) => /* @__PURE__ */ jsx(
2211
+ QuestionField,
2212
+ {
2213
+ draft: drafts[question.id],
2214
+ labels,
2215
+ onChange: (next) => update(question.id, () => next),
2216
+ question,
2217
+ theme
2218
+ },
2219
+ question.id
2220
+ )),
2221
+ /* @__PURE__ */ jsxs(View, { style: styles.buttonRow, children: [
2222
+ request.allowSkip ? /* @__PURE__ */ jsx(
2223
+ ActionButton,
2224
+ {
2225
+ label: labels.skip,
2226
+ onPress: () => onRespond({ outcome: "skipped" }),
2227
+ theme,
2228
+ variant: "quiet",
2229
+ disabled: responding
2230
+ }
2231
+ ) : null,
2232
+ /* @__PURE__ */ jsx(
2233
+ ActionButton,
2234
+ {
2235
+ label: labels.submitAnswers,
2236
+ onPress: () => onRespond({ outcome: "answered", answers: projection.answers }),
2237
+ theme,
2238
+ disabled: !projection.valid || responding
2239
+ }
2240
+ )
2241
+ ] })
2242
+ ]
2243
+ }
2244
+ ) });
2245
+ }
2246
+ function QuestionField({
2247
+ draft,
2248
+ labels,
2249
+ onChange,
2250
+ question,
2251
+ theme
2252
+ }) {
2253
+ const current = draft ?? emptyHumanInputDraft();
2254
+ const values = current.values;
2255
+ const toggle = (id) => {
2256
+ const next = question.kind === "single_select" ? [id] : values.includes(id) ? values.filter((value) => value !== id) : [...values, id];
2257
+ onChange({
2258
+ ...current,
2259
+ values: next,
2260
+ ...question.kind === "single_select" ? { otherSelected: false } : {}
2261
+ });
2262
+ };
2263
+ return /* @__PURE__ */ jsxs(View, { style: styles.question, children: [
2264
+ /* @__PURE__ */ jsxs(Text, { style: [styles.cardTitle, { color: theme.colors.text }], children: [
2265
+ question.label ?? question.prompt,
2266
+ question.required ? ` \xB7 ${labels.required}` : ""
2267
+ ] }),
2268
+ question.label ? /* @__PURE__ */ jsx(Text, { style: [styles.secondaryText, { color: theme.colors.secondaryText }], children: question.prompt }) : null,
2269
+ question.helpText ? /* @__PURE__ */ jsx(Text, { style: [styles.helpText, { color: theme.colors.mutedText }], children: question.helpText }) : null,
2270
+ question.kind === "text" ? /* @__PURE__ */ jsx(
2271
+ TextInput,
2272
+ {
2273
+ accessibilityLabel: question.label ?? question.prompt,
2274
+ multiline: true,
2275
+ onChangeText: (text) => onChange({ ...current, values: text ? [text] : [] }),
2276
+ style: [
2277
+ styles.questionInput,
2278
+ {
2279
+ backgroundColor: theme.colors.background,
2280
+ borderColor: theme.colors.border,
2281
+ color: theme.colors.text
2282
+ }
2283
+ ],
2284
+ value: values[0] ?? ""
2285
+ }
2286
+ ) : /* @__PURE__ */ jsx(View, { style: styles.optionList, children: question.options.map((option) => {
2287
+ const selected = values.includes(option.id);
2288
+ return /* @__PURE__ */ jsxs(
2289
+ Pressable,
2290
+ {
2291
+ accessibilityRole: question.kind === "single_select" ? "radio" : "checkbox",
2292
+ accessibilityState: { checked: selected },
2293
+ onPress: () => toggle(option.id),
2294
+ style: [
2295
+ styles.option,
2296
+ {
2297
+ backgroundColor: selected ? theme.colors.elevated : theme.colors.background,
2298
+ borderColor: selected ? theme.colors.accent : theme.colors.border
2299
+ }
2300
+ ],
2301
+ children: [
2302
+ /* @__PURE__ */ jsx(Text, { style: [styles.optionLabel, { color: theme.colors.text }], children: option.label }),
2303
+ option.description ? /* @__PURE__ */ jsx(Text, { style: [styles.helpText, { color: theme.colors.secondaryText }], children: option.description }) : null
2304
+ ]
2305
+ },
2306
+ option.id
2307
+ );
2308
+ }) }),
2309
+ question.kind !== "text" && question.allowOther ? /* @__PURE__ */ jsxs(
2310
+ View,
2311
+ {
2312
+ style: [
2313
+ styles.option,
2314
+ {
2315
+ backgroundColor: current.otherSelected ? theme.colors.elevated : theme.colors.background,
2316
+ borderColor: current.otherSelected ? theme.colors.accent : theme.colors.border
2317
+ }
2318
+ ],
2319
+ children: [
2320
+ /* @__PURE__ */ jsx(
2321
+ Pressable,
2322
+ {
2323
+ accessibilityRole: question.kind === "single_select" ? "radio" : "checkbox",
2324
+ accessibilityState: { checked: current.otherSelected },
2325
+ onPress: () => onChange({
2326
+ ...current,
2327
+ otherSelected: !current.otherSelected,
2328
+ ...question.kind === "single_select" && !current.otherSelected ? { values: [] } : {}
2329
+ }),
2330
+ style: styles.otherOptionToggle,
2331
+ children: /* @__PURE__ */ jsx(Text, { style: [styles.optionLabel, { color: theme.colors.text }], children: labels.other })
2332
+ }
2333
+ ),
2334
+ /* @__PURE__ */ jsx(
2335
+ TextInput,
2336
+ {
2337
+ accessibilityLabel: labels.other,
2338
+ editable: current.otherSelected,
2339
+ onChangeText: (other) => onChange({ ...current, other }),
2340
+ placeholder: labels.other,
2341
+ placeholderTextColor: theme.colors.mutedText,
2342
+ style: [
2343
+ styles.otherInput,
2344
+ {
2345
+ backgroundColor: theme.colors.background,
2346
+ borderColor: theme.colors.border,
2347
+ color: theme.colors.text,
2348
+ opacity: current.otherSelected ? 1 : 0.5
2349
+ }
2350
+ ],
2351
+ value: current.other
2352
+ }
2353
+ )
2354
+ ]
2355
+ }
2356
+ ) : null
2357
+ ] });
2358
+ }
2359
+ function OpenGeniNativeAttachmentStrip({
2360
+ attachments,
2361
+ labels,
2362
+ onRemoveRestoredResource,
2363
+ restoredResources = NO_RESTORED_RESOURCES,
2364
+ theme
2365
+ }) {
2366
+ const error = attachments.error;
2367
+ if (attachments.attachments.length === 0 && restoredResources.length === 0 && !error) return null;
2368
+ return /* @__PURE__ */ jsxs(
2369
+ ScrollView,
2370
+ {
2371
+ contentContainerStyle: styles.attachmentStrip,
2372
+ horizontal: true,
2373
+ keyboardShouldPersistTaps: "handled",
2374
+ showsHorizontalScrollIndicator: false,
2375
+ children: [
2376
+ error ? /* @__PURE__ */ jsxs(
2377
+ View,
2378
+ {
2379
+ style: [
2380
+ styles.attachmentChip,
2381
+ { backgroundColor: theme.colors.dangerSurface, borderColor: theme.colors.danger }
2382
+ ],
2383
+ children: [
2384
+ /* @__PURE__ */ jsx(Text, { style: [styles.attachmentName, { color: theme.colors.danger }], children: labels.attachmentError }),
2385
+ /* @__PURE__ */ jsx(
2386
+ ActionButton,
2387
+ {
2388
+ label: labels.dismiss,
2389
+ onPress: () => attachments.clearError(),
2390
+ theme,
2391
+ variant: "quiet"
2392
+ }
2393
+ )
2394
+ ]
2395
+ }
2396
+ ) : null,
2397
+ attachments.attachments.map((attachment) => /* @__PURE__ */ jsxs(
2398
+ View,
2399
+ {
2400
+ style: [
2401
+ styles.attachmentChip,
2402
+ { backgroundColor: theme.colors.elevated, borderColor: theme.colors.border }
2403
+ ],
2404
+ children: [
2405
+ /* @__PURE__ */ jsx(Text, { numberOfLines: 1, style: [styles.attachmentName, { color: theme.colors.text }], children: attachment.name }),
2406
+ /* @__PURE__ */ jsx(
2407
+ Text,
2408
+ {
2409
+ style: [
2410
+ styles.helpText,
2411
+ {
2412
+ color: attachment.status === "failed" ? theme.colors.danger : theme.colors.mutedText
2413
+ }
2414
+ ],
2415
+ children: attachment.status === "preparing" ? labels.preparingAttachment : attachment.status === "uploading" ? labels.uploading : attachment.status === "failed" ? labels.uploadFailed : formatBytes(attachment.sizeBytes)
2416
+ }
2417
+ ),
2418
+ /* @__PURE__ */ jsxs(View, { style: styles.buttonRow, children: [
2419
+ attachment.status === "failed" ? /* @__PURE__ */ jsx(
2420
+ ActionButton,
2421
+ {
2422
+ label: labels.retryUpload,
2423
+ onPress: () => void attachments.retry(attachment.id),
2424
+ theme,
2425
+ variant: "quiet"
2426
+ }
2427
+ ) : null,
2428
+ /* @__PURE__ */ jsx(
2429
+ ActionButton,
2430
+ {
2431
+ label: labels.removeAttachment,
2432
+ onPress: () => attachments.remove(attachment.id),
2433
+ theme,
2434
+ variant: "quiet"
2435
+ }
2436
+ )
2437
+ ] })
2438
+ ]
2439
+ },
2440
+ attachment.id
2441
+ )),
2442
+ restoredResources.map((_resource, index) => /* @__PURE__ */ jsxs(
2443
+ View,
2444
+ {
2445
+ style: [
2446
+ styles.attachmentChip,
2447
+ { backgroundColor: theme.colors.elevated, borderColor: theme.colors.border }
2448
+ ],
2449
+ children: [
2450
+ /* @__PURE__ */ jsx(Text, { style: [styles.attachmentName, { color: theme.colors.text }], children: labels.restoredAttachment }),
2451
+ /* @__PURE__ */ jsx(
2452
+ ActionButton,
2453
+ {
2454
+ label: labels.removeAttachment,
2455
+ onPress: () => onRemoveRestoredResource?.(index),
2456
+ theme,
2457
+ variant: "quiet",
2458
+ disabled: !onRemoveRestoredResource
2459
+ }
2460
+ )
2461
+ ]
2462
+ },
2463
+ `restored-${index}`
2464
+ ))
2465
+ ]
2466
+ }
2467
+ );
2468
+ }
2469
+ function Composer({
2470
+ accessory,
2471
+ controller,
2472
+ labels,
2473
+ renderActions,
2474
+ renderComposer,
2475
+ renderSurface,
2476
+ theme
2477
+ }) {
2478
+ const paused = controller.queue.effectiveControl?.state === "paused";
2479
+ const busy = controller.composer.sending || controller.composer.pausing || controller.composer.resuming;
2480
+ const actionContext = {
2481
+ accessory,
2482
+ labels,
2483
+ theme,
2484
+ busy,
2485
+ paused,
2486
+ runActive: controller.runActive,
2487
+ canSubmit: controller.composer.canSend && !busy,
2488
+ draftSaving: controller.composer.draftSaving,
2489
+ submitLabel: labels.send,
2490
+ onAttachDocument: () => void controller.attachments.pickDocuments(),
2491
+ onAttachImage: () => void controller.attachments.pickImages(),
2492
+ onTogglePause: () => void (paused ? controller.composer.resume() : controller.composer.pause()),
2493
+ onSubmit: () => void controller.composer.send()
2494
+ };
2495
+ const composerContext = {
2496
+ ...actionContext,
2497
+ value: controller.composer.value,
2498
+ onChangeText: controller.composer.setValue,
2499
+ placeholder: labels.composerPlaceholder
2500
+ };
2501
+ const content = /* @__PURE__ */ jsxs(Fragment, { children: [
2502
+ controller.composer.draftConflict ? /* @__PURE__ */ jsxs(View, { style: [styles.draftConflict, { backgroundColor: theme.colors.warningSurface }], children: [
2503
+ /* @__PURE__ */ jsx(Text, { style: [styles.secondaryText, { color: theme.colors.warning }], children: labels.draftConflict }),
2504
+ /* @__PURE__ */ jsxs(View, { style: styles.buttonRow, children: [
2505
+ /* @__PURE__ */ jsx(
2506
+ ActionButton,
2507
+ {
2508
+ label: labels.useRemote,
2509
+ onPress: () => void controller.composer.resolveDraftConflict("use_remote"),
2510
+ theme,
2511
+ variant: "quiet"
2512
+ }
2513
+ ),
2514
+ /* @__PURE__ */ jsx(
2515
+ ActionButton,
2516
+ {
2517
+ label: labels.keepMine,
2518
+ onPress: () => void controller.composer.resolveDraftConflict("keep_mine"),
2519
+ theme,
2520
+ variant: "quiet"
2521
+ }
2522
+ )
2523
+ ] })
2524
+ ] }) : null,
2525
+ renderComposer ? renderComposer(composerContext) : /* @__PURE__ */ jsxs(View, { style: renderActions ? styles.composerRow : styles.composerStack, children: [
2526
+ /* @__PURE__ */ jsx(
2527
+ TextInput,
2528
+ {
2529
+ accessibilityLabel: labels.composerPlaceholder,
2530
+ blurOnSubmit: false,
2531
+ multiline: true,
2532
+ onChangeText: controller.composer.setValue,
2533
+ placeholder: labels.composerPlaceholder,
2534
+ placeholderTextColor: theme.colors.mutedText,
2535
+ style: [styles.composerInput, { color: theme.colors.text }],
2536
+ value: controller.composer.value
2537
+ }
2538
+ ),
2539
+ renderActions ? renderActions(actionContext) : /* @__PURE__ */ jsx(DefaultComposerActions, { ...actionContext })
2540
+ ] })
2541
+ ] });
2542
+ if (renderComposer) {
2543
+ return /* @__PURE__ */ jsx(Fragment, { children: content });
2544
+ }
2545
+ if (renderSurface) {
2546
+ return renderSurface({ children: content, style: styles.composerContainer });
2547
+ }
2548
+ return /* @__PURE__ */ jsx(
2549
+ View,
2550
+ {
2551
+ style: [
2552
+ styles.composerContainer,
2553
+ { backgroundColor: theme.colors.surface, borderColor: theme.colors.border }
2554
+ ],
2555
+ children: content
2556
+ }
2557
+ );
2558
+ }
2559
+ function DefaultComposerActions(context) {
2560
+ return /* @__PURE__ */ jsxs(View, { style: styles.composerToolbar, children: [
2561
+ /* @__PURE__ */ jsxs(View, { style: styles.buttonWrap, children: [
2562
+ /* @__PURE__ */ jsx(
2563
+ ActionButton,
2564
+ {
2565
+ accessibilityLabel: context.labels.attachDocument,
2566
+ label: "\uFF0B",
2567
+ onPress: () => context.onAttachDocument(),
2568
+ theme: context.theme,
2569
+ variant: "quiet",
2570
+ disabled: context.busy
2571
+ }
2572
+ ),
2573
+ /* @__PURE__ */ jsx(
2574
+ ActionButton,
2575
+ {
2576
+ accessibilityLabel: context.labels.attachImage,
2577
+ label: "\u25A7",
2578
+ onPress: () => context.onAttachImage(),
2579
+ theme: context.theme,
2580
+ variant: "quiet",
2581
+ disabled: context.busy
2582
+ }
2583
+ ),
2584
+ context.accessory
2585
+ ] }),
2586
+ /* @__PURE__ */ jsxs(View, { style: styles.buttonWrap, children: [
2587
+ /* @__PURE__ */ jsx(
2588
+ ActionButton,
2589
+ {
2590
+ label: context.paused ? context.labels.resume : context.labels.pause,
2591
+ onPress: () => context.onTogglePause(),
2592
+ theme: context.theme,
2593
+ variant: "quiet",
2594
+ disabled: context.busy
2595
+ }
2596
+ ),
2597
+ /* @__PURE__ */ jsx(
2598
+ ActionButton,
2599
+ {
2600
+ label: context.submitLabel,
2601
+ onPress: () => context.onSubmit(),
2602
+ theme: context.theme,
2603
+ disabled: !context.canSubmit
2604
+ }
2605
+ )
2606
+ ] })
2607
+ ] });
2608
+ }
2609
+ function ActionButton({
2610
+ accessibilityLabel,
2611
+ compact = false,
2612
+ disabled = false,
2613
+ label,
2614
+ onPress,
2615
+ theme,
2616
+ variant = "primary"
2617
+ }) {
2618
+ const colors = variant === "primary" ? {
2619
+ background: theme.colors.accent,
2620
+ foreground: theme.colors.accentForeground,
2621
+ border: theme.colors.accent
2622
+ } : variant === "danger" ? {
2623
+ background: "transparent",
2624
+ foreground: theme.colors.danger,
2625
+ border: theme.colors.danger
2626
+ } : {
2627
+ background: "transparent",
2628
+ foreground: theme.colors.secondaryText,
2629
+ border: theme.colors.border
2630
+ };
2631
+ return /* @__PURE__ */ jsx(
2632
+ Pressable,
2633
+ {
2634
+ accessibilityLabel: accessibilityLabel ?? label,
2635
+ accessibilityRole: "button",
2636
+ accessibilityState: { disabled },
2637
+ disabled,
2638
+ onPress,
2639
+ style: ({ pressed }) => [
2640
+ styles.button,
2641
+ compact && styles.compactButton,
2642
+ {
2643
+ backgroundColor: colors.background,
2644
+ borderColor: colors.border,
2645
+ opacity: disabled ? 0.42 : pressed ? 0.72 : 1
2646
+ }
2647
+ ],
2648
+ children: /* @__PURE__ */ jsx(
2649
+ Text,
2650
+ {
2651
+ style: [
2652
+ styles.buttonText,
2653
+ compact && styles.compactButtonText,
2654
+ { color: colors.foreground }
2655
+ ],
2656
+ children: label
2657
+ }
2658
+ )
2659
+ }
2660
+ );
2661
+ }
2662
+ function formatBytes(value) {
2663
+ if (value === null) return "";
2664
+ if (value < 1024) return `${value} B`;
2665
+ if (value < 1024 * 1024) return `${Math.round(value / 1024)} KB`;
2666
+ return `${(value / (1024 * 1024)).toFixed(1)} MB`;
2667
+ }
2668
+ var styles = StyleSheet.create({
2669
+ root: { flex: 1 },
2670
+ body: { flex: 1 },
2671
+ timelineColumn: { flex: 1, minWidth: 0 },
2672
+ timelineStage: { flex: 1, minHeight: 0 },
2673
+ timeline: { flex: 1 },
2674
+ timelineHidden: { flex: 0, height: 0, overflow: "hidden", opacity: 0 },
2675
+ timelineContent: {
2676
+ width: "100%",
2677
+ maxWidth: 820,
2678
+ alignSelf: "center",
2679
+ gap: 12,
2680
+ padding: 16,
2681
+ paddingBottom: 132
2682
+ },
2683
+ timelineContentNativeComposer: {
2684
+ paddingBottom: 16
2685
+ },
2686
+ emptyTimeline: { flexGrow: 1, justifyContent: "center" },
2687
+ emptyState: { alignItems: "center", gap: 8, paddingHorizontal: 32 },
2688
+ emptyTitle: { fontSize: 22, fontWeight: "700", textAlign: "center" },
2689
+ emptyDescription: { fontSize: 15, lineHeight: 22, textAlign: "center" },
2690
+ banner: {
2691
+ minHeight: 44,
2692
+ paddingHorizontal: 16,
2693
+ paddingVertical: 10,
2694
+ flexDirection: "row",
2695
+ alignItems: "center",
2696
+ gap: 12
2697
+ },
2698
+ bannerText: { flex: 1, fontSize: 13, lineHeight: 18 },
2699
+ messageRow: { width: "100%" },
2700
+ userMessageRow: { alignItems: "flex-end" },
2701
+ agentMessageRow: { alignItems: "flex-start" },
2702
+ messageBubble: {
2703
+ gap: 8
2704
+ },
2705
+ userMessageBubble: {
2706
+ maxWidth: "84%",
2707
+ borderRadius: 16,
2708
+ borderWidth: StyleSheet.hairlineWidth,
2709
+ paddingHorizontal: 14,
2710
+ paddingVertical: 11
2711
+ },
2712
+ agentMessage: {
2713
+ width: "100%",
2714
+ paddingHorizontal: 4,
2715
+ paddingVertical: 5
2716
+ },
2717
+ messageText: { fontSize: 16, lineHeight: 23 },
2718
+ timelineCard: { borderRadius: 14, borderWidth: StyleSheet.hairlineWidth, padding: 12, gap: 7 },
2719
+ activityRail: { borderLeftWidth: 2, gap: 12, paddingLeft: 12 },
2720
+ step: { gap: 4 },
2721
+ stepLabel: { fontSize: 12, fontWeight: "600", lineHeight: 16 },
2722
+ disclosure: { gap: 4 },
2723
+ disclosureHeader: {
2724
+ minHeight: 44,
2725
+ borderRadius: 8,
2726
+ flexDirection: "row",
2727
+ alignItems: "center",
2728
+ gap: 7,
2729
+ paddingHorizontal: 6,
2730
+ paddingVertical: 7
2731
+ },
2732
+ disclosureChevron: { width: 14, fontSize: 18, lineHeight: 20, textAlign: "center" },
2733
+ disclosureIcon: { width: 15, fontSize: 16, lineHeight: 18, textAlign: "center" },
2734
+ disclosureTitle: { flexShrink: 0, fontSize: 14, lineHeight: 19 },
2735
+ disclosurePreview: { minWidth: 0, flex: 1, fontSize: 13, lineHeight: 18 },
2736
+ disclosureBody: { marginLeft: 42, paddingBottom: 6 },
2737
+ activityDisclosure: { gap: 4 },
2738
+ activityDisclosureCard: {
2739
+ borderRadius: 12,
2740
+ borderWidth: StyleSheet.hairlineWidth,
2741
+ paddingHorizontal: 8,
2742
+ paddingVertical: 4
2743
+ },
2744
+ activityDisclosureHeader: {
2745
+ minHeight: 44,
2746
+ flexDirection: "row",
2747
+ alignItems: "center",
2748
+ gap: 7,
2749
+ paddingVertical: 6
2750
+ },
2751
+ activityDisclosureChevron: { width: 14, fontSize: 18, lineHeight: 20, textAlign: "center" },
2752
+ activityStatusDot: { width: 6, height: 6, borderRadius: 999 },
2753
+ activityDisclosureTitle: { minWidth: 0, flexShrink: 1, fontSize: 13, fontWeight: "600" },
2754
+ activityDisclosurePreview: { minWidth: 0, flex: 1, fontSize: 13, lineHeight: 18 },
2755
+ activityDisclosureStatus: { flexShrink: 0, fontSize: 11, fontWeight: "600", lineHeight: 15 },
2756
+ activityDisclosureBody: { marginLeft: 27, paddingBottom: 7, paddingRight: 6 },
2757
+ notice: {
2758
+ borderRadius: 10,
2759
+ borderWidth: StyleSheet.hairlineWidth,
2760
+ flexDirection: "row",
2761
+ alignItems: "flex-start",
2762
+ gap: 10,
2763
+ paddingHorizontal: 13,
2764
+ paddingVertical: 11
2765
+ },
2766
+ noticeIcon: { width: 16, fontSize: 17, fontWeight: "800", lineHeight: 20, textAlign: "center" },
2767
+ noticeContent: { minWidth: 0, flex: 1, gap: 7 },
2768
+ noticeDetails: { gap: 6 },
2769
+ noticeAction: { fontSize: 12, fontWeight: "700", lineHeight: 17 },
2770
+ noticeLink: { minHeight: 44, justifyContent: "center", paddingHorizontal: 2 },
2771
+ sessionStatus: { flexDirection: "row", alignItems: "center", gap: 10 },
2772
+ sessionStatusRule: { height: StyleSheet.hairlineWidth, flex: 1 },
2773
+ sessionStatusLabel: { flexDirection: "row", alignItems: "center", gap: 6 },
2774
+ sessionStatusDot: { width: 5, height: 5, borderRadius: 999 },
2775
+ sessionStatusText: { fontSize: 11, lineHeight: 15 },
2776
+ turnGroup: { gap: 8 },
2777
+ turnSummary: {
2778
+ minHeight: 48,
2779
+ borderRadius: 999,
2780
+ borderWidth: StyleSheet.hairlineWidth,
2781
+ flexDirection: "row",
2782
+ alignItems: "center",
2783
+ gap: 7,
2784
+ paddingHorizontal: 12
2785
+ },
2786
+ turnSummaryText: { flex: 1, fontSize: 13, fontWeight: "600" },
2787
+ turnChevron: { width: 13, fontSize: 19, fontWeight: "500", textAlign: "center" },
2788
+ turnOutcome: { width: 14, fontSize: 13, fontWeight: "800", textAlign: "center" },
2789
+ turnDuration: { fontSize: 12, fontVariant: ["tabular-nums"] },
2790
+ turnRail: { borderLeftWidth: 2, gap: 12, marginLeft: 8, paddingLeft: 12 },
2791
+ cardTitle: { fontSize: 14, fontWeight: "700", lineHeight: 19 },
2792
+ secondaryText: { fontSize: 14, lineHeight: 20 },
2793
+ helpText: { fontSize: 12, lineHeight: 17 },
2794
+ codeText: {
2795
+ fontFamily: Platform.select({ ios: "Menlo", android: "monospace" }),
2796
+ fontSize: 12,
2797
+ lineHeight: 17
2798
+ },
2799
+ approvalOverlay: {
2800
+ position: "absolute",
2801
+ left: 0,
2802
+ right: 0,
2803
+ bottom: 12,
2804
+ alignItems: "center",
2805
+ paddingHorizontal: 16
2806
+ },
2807
+ approvalSurface: {
2808
+ width: "100%",
2809
+ maxWidth: 760,
2810
+ borderRadius: 16,
2811
+ borderWidth: 1,
2812
+ padding: 12,
2813
+ gap: 10,
2814
+ elevation: 8,
2815
+ shadowColor: "#000000",
2816
+ shadowOffset: { width: 0, height: 10 },
2817
+ shadowOpacity: 0.16,
2818
+ shadowRadius: 18
2819
+ },
2820
+ approvalItem: { gap: 8 },
2821
+ sessionSurfaces: { maxHeight: 300 },
2822
+ sessionSurfacesContent: { paddingVertical: 4 },
2823
+ humanInputWorkspaceCollapsed: { flexShrink: 0 },
2824
+ humanInputWorkspaceExpanded: { flex: 1, minHeight: 0 },
2825
+ humanInputDisclosure: {
2826
+ minHeight: 44,
2827
+ flexDirection: "row",
2828
+ alignItems: "center",
2829
+ gap: 7,
2830
+ paddingHorizontal: 12,
2831
+ paddingVertical: 7
2832
+ },
2833
+ humanInputDisclosureTitle: { flexShrink: 0, fontSize: 14, fontWeight: "600", lineHeight: 19 },
2834
+ humanInputWorkspaceScroll: { flex: 1, minHeight: 0 },
2835
+ humanInputWorkspaceBody: { gap: 14, paddingHorizontal: 12, paddingBottom: 16 },
2836
+ actionSurfaces: { gap: 14, paddingHorizontal: 12, paddingVertical: 8 },
2837
+ actionGroup: { gap: 8 },
2838
+ actionTitle: { fontSize: 15, fontWeight: "700" },
2839
+ actionCard: { borderRadius: 14, borderWidth: 1, padding: 12, gap: 10 },
2840
+ queueSurface: {
2841
+ overflow: "hidden",
2842
+ borderRadius: 12,
2843
+ borderWidth: StyleSheet.hairlineWidth
2844
+ },
2845
+ queueDisclosure: {
2846
+ minHeight: 44,
2847
+ flexDirection: "row",
2848
+ alignItems: "center",
2849
+ gap: 8,
2850
+ paddingHorizontal: 12,
2851
+ paddingVertical: 8
2852
+ },
2853
+ queueChevron: { width: 12, fontSize: 18, lineHeight: 20, textAlign: "center" },
2854
+ queueTitle: { flexShrink: 0, fontSize: 13, fontWeight: "600" },
2855
+ queuePreview: { minWidth: 0, flex: 1, fontSize: 13 },
2856
+ queueRow: { borderTopWidth: StyleSheet.hairlineWidth, padding: 12, gap: 9 },
2857
+ queuePrompt: { fontSize: 14, lineHeight: 19 },
2858
+ queueActions: { flexDirection: "row", alignItems: "center", flexWrap: "wrap", gap: 6 },
2859
+ buttonRow: { flexDirection: "row", alignItems: "center", justifyContent: "flex-end", gap: 8 },
2860
+ buttonWrap: { flexDirection: "row", alignItems: "center", flexWrap: "wrap", gap: 6 },
2861
+ button: {
2862
+ minHeight: 48,
2863
+ minWidth: 48,
2864
+ borderRadius: 11,
2865
+ borderWidth: 1,
2866
+ alignItems: "center",
2867
+ justifyContent: "center",
2868
+ paddingHorizontal: 12,
2869
+ paddingVertical: 8
2870
+ },
2871
+ buttonText: { fontSize: 13, fontWeight: "700" },
2872
+ compactButton: {
2873
+ minHeight: 36,
2874
+ minWidth: 0,
2875
+ borderRadius: 9,
2876
+ paddingHorizontal: 10,
2877
+ paddingVertical: 6
2878
+ },
2879
+ compactButtonText: { fontSize: 12 },
2880
+ question: { gap: 8 },
2881
+ questionInput: {
2882
+ minHeight: 48,
2883
+ maxHeight: 144,
2884
+ borderWidth: 1,
2885
+ borderRadius: 11,
2886
+ paddingHorizontal: 12,
2887
+ paddingVertical: 10,
2888
+ fontSize: 15,
2889
+ textAlignVertical: "top"
2890
+ },
2891
+ optionList: { gap: 7 },
2892
+ option: {
2893
+ minHeight: 48,
2894
+ borderWidth: 1,
2895
+ borderRadius: 11,
2896
+ justifyContent: "center",
2897
+ paddingHorizontal: 12,
2898
+ paddingVertical: 9
2899
+ },
2900
+ optionLabel: { fontSize: 14, fontWeight: "600" },
2901
+ otherOptionToggle: { minHeight: 48, justifyContent: "center" },
2902
+ otherInput: {
2903
+ minHeight: 48,
2904
+ borderWidth: 1,
2905
+ borderRadius: 9,
2906
+ marginTop: 7,
2907
+ paddingHorizontal: 10,
2908
+ paddingVertical: 8,
2909
+ fontSize: 14
2910
+ },
2911
+ attachmentStrip: { gap: 8, paddingHorizontal: 12, paddingVertical: 6 },
2912
+ attachmentChip: {
2913
+ width: 180,
2914
+ minHeight: 64,
2915
+ borderWidth: 1,
2916
+ borderRadius: 12,
2917
+ padding: 9,
2918
+ gap: 4
2919
+ },
2920
+ attachmentName: { fontSize: 13, fontWeight: "600" },
2921
+ composerDock: { paddingHorizontal: 12, paddingTop: 5, paddingBottom: 8, gap: 5 },
2922
+ composerContainer: {
2923
+ width: "100%",
2924
+ maxWidth: 820,
2925
+ alignSelf: "center",
2926
+ borderRadius: 28,
2927
+ borderWidth: 1,
2928
+ padding: 5,
2929
+ gap: 4
2930
+ },
2931
+ composerRow: { minHeight: 40, flexDirection: "row", alignItems: "flex-end" },
2932
+ composerStack: { gap: 8 },
2933
+ composerInput: {
2934
+ minWidth: 0,
2935
+ minHeight: 40,
2936
+ maxHeight: 120,
2937
+ flex: 1,
2938
+ paddingHorizontal: 11,
2939
+ paddingVertical: 9,
2940
+ fontSize: 16,
2941
+ lineHeight: 22,
2942
+ textAlignVertical: "top"
2943
+ },
2944
+ composerToolbar: {
2945
+ flexDirection: "row",
2946
+ alignItems: "center",
2947
+ justifyContent: "space-between",
2948
+ gap: 8
2949
+ },
2950
+ draftConflict: { borderRadius: 10, padding: 8, gap: 6 }
2951
+ });
2952
+ function createMemoryRealtimeOwnerStorage() {
2953
+ const values = /* @__PURE__ */ new Map();
2954
+ return {
2955
+ getItem: (key) => values.get(key) ?? null,
2956
+ setItem: (key, value) => void values.set(key, String(value)),
2957
+ removeItem: (key) => void values.delete(key)
2958
+ };
2959
+ }
2960
+ function audioTracks(stream) {
2961
+ const candidate = stream;
2962
+ if (!candidate) return [];
2963
+ if (typeof candidate.getAudioTracks === "function") return candidate.getAudioTracks();
2964
+ if (typeof candidate.getTracks === "function")
2965
+ return candidate.getTracks().filter((track) => track.kind === "audio");
2966
+ return [];
2967
+ }
2968
+ function createNativeRemoteAudio() {
2969
+ let source = null;
2970
+ let muted = false;
2971
+ let paused = true;
2972
+ const apply = () => {
2973
+ const enabled = !muted && !paused;
2974
+ for (const track of audioTracks(source)) track.enabled = enabled;
2975
+ };
2976
+ const element = {
2977
+ autoplay: true,
2978
+ get muted() {
2979
+ return muted;
2980
+ },
2981
+ set muted(value) {
2982
+ muted = Boolean(value);
2983
+ apply();
2984
+ },
2985
+ get srcObject() {
2986
+ return source;
2987
+ },
2988
+ set srcObject(value) {
2989
+ source = value ?? null;
2990
+ if (source === null) paused = true;
2991
+ apply();
2992
+ },
2993
+ get paused() {
2994
+ return paused;
2995
+ },
2996
+ async play() {
2997
+ paused = false;
2998
+ apply();
2999
+ },
3000
+ pause() {
3001
+ paused = true;
3002
+ apply();
3003
+ }
3004
+ };
3005
+ return element;
3006
+ }
3007
+ function createNativeRealtimeControllerFactory(webrtc, options = {}) {
3008
+ const storage = options.storage ?? createMemoryRealtimeOwnerStorage();
3009
+ return (controllerOptions) => createSessionRealtimeController({
3010
+ ...controllerOptions,
3011
+ storage: controllerOptions.storage ?? storage,
3012
+ remoteAudio: controllerOptions.remoteAudio ?? createNativeRemoteAudio(),
3013
+ createPeerConnection: () => webrtc.createPeerConnection(),
3014
+ getUserMedia: (constraints) => webrtc.getUserMedia(constraints)
3015
+ });
3016
+ }
3017
+ function useNativeSessionRealtime(options) {
3018
+ const { webrtc, storage, ...realtimeOptions } = options;
3019
+ const controllerFactory = useMemo(
3020
+ () => createNativeRealtimeControllerFactory(webrtc, storage ? { storage } : {}),
3021
+ [webrtc, storage]
3022
+ );
3023
+ return useSessionRealtime({ ...realtimeOptions, controllerFactory });
3024
+ }
3025
+ function nativeRealtimeModelSupported(model) {
3026
+ const transport = sessionRealtimeTransportKind(model);
3027
+ return transport === "codex" || transport === "azure-live";
3028
+ }
3029
+ function randomCallId() {
3030
+ const crypto = globalThis.crypto;
3031
+ if (crypto?.randomUUID) return crypto.randomUUID();
3032
+ return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (char) => {
3033
+ const value = Math.floor(Math.random() * 16);
3034
+ return (char === "x" ? value : value & 3 | 8).toString(16);
3035
+ });
3036
+ }
3037
+ function useNativeRealtimeCall(options) {
3038
+ const { realtime, call } = options;
3039
+ const [callId, setCallId] = useState(null);
3040
+ const [ending, setEnding] = useState(false);
3041
+ const [speaker, setSpeakerState] = useState(false);
3042
+ const [route, setRoute] = useState(null);
3043
+ const [error, setError] = useState(null);
3044
+ const [systemCall, setSystemCall] = useState(false);
3045
+ const callIdRef = useRef(null);
3046
+ const systemCallRef = useRef(null);
3047
+ const connectedRef = useRef(null);
3048
+ const realtimeRef = useRef(realtime);
3049
+ realtimeRef.current = realtime;
3050
+ const status = realtime.snapshot.status;
3051
+ const muted = realtime.snapshot.inputMuted;
3052
+ const finish = useCallback(
3053
+ async (reason) => {
3054
+ const id = callIdRef.current;
3055
+ if (!id) return;
3056
+ const reported = systemCallRef.current === id;
3057
+ callIdRef.current = null;
3058
+ systemCallRef.current = null;
3059
+ connectedRef.current = null;
3060
+ setCallId(null);
3061
+ setEnding(false);
3062
+ setSpeakerState(false);
3063
+ setSystemCall(false);
3064
+ if (reported) await call?.endCall(id, reason).catch(() => void 0);
3065
+ },
3066
+ [call]
3067
+ );
3068
+ useEffect(() => {
3069
+ if (!call) return;
3070
+ return call.subscribe((event) => {
3071
+ if (event.type === "routeChanged") {
3072
+ setRoute(event.route);
3073
+ setSpeakerState(event.route === "speaker");
3074
+ return;
3075
+ }
3076
+ if (event.type === "startRequested" || event.type === "audioSessionActivated" || event.type === "audioSessionDeactivated")
3077
+ return;
3078
+ if (event.callId !== callIdRef.current) return;
3079
+ if (event.type === "ended") {
3080
+ callIdRef.current = null;
3081
+ systemCallRef.current = null;
3082
+ connectedRef.current = null;
3083
+ setCallId(null);
3084
+ setEnding(false);
3085
+ setSystemCall(false);
3086
+ void realtimeRef.current.stop().catch(() => void 0);
3087
+ } else if (event.type === "muteChanged") {
3088
+ realtimeRef.current.setInputMuted(event.muted);
3089
+ }
3090
+ });
3091
+ }, [call]);
3092
+ useEffect(() => {
3093
+ const id = callIdRef.current;
3094
+ if (id && status === "active" && connectedRef.current !== id) {
3095
+ connectedRef.current = id;
3096
+ if (systemCallRef.current === id) call?.reportConnected(id);
3097
+ }
3098
+ }, [call, status, callId]);
3099
+ useEffect(() => {
3100
+ if (!callIdRef.current) return;
3101
+ if (status === "error" || status === "lost_owner") {
3102
+ setError(realtime.snapshot.error ?? "Voice disconnected.");
3103
+ void finish("failed");
3104
+ } else if (status === "idle" && connectedRef.current === callIdRef.current) {
3105
+ void finish("ended");
3106
+ }
3107
+ }, [finish, realtime.snapshot.error, status]);
3108
+ const start = useCallback(async () => {
3109
+ if (callIdRef.current) return;
3110
+ const current = realtimeRef.current;
3111
+ if (!current.canStart) {
3112
+ throw new Error(current.admissionBlocker ?? "Voice is not ready yet.");
3113
+ }
3114
+ const id = randomCallId();
3115
+ callIdRef.current = id;
3116
+ setCallId(id);
3117
+ setError(null);
3118
+ if (call) {
3119
+ try {
3120
+ await call.startCall({ callId: id, title: options.title, target: options.target ?? null });
3121
+ if (callIdRef.current === id) {
3122
+ systemCallRef.current = id;
3123
+ setSystemCall(true);
3124
+ }
3125
+ } catch {
3126
+ }
3127
+ }
3128
+ if (callIdRef.current !== id) return;
3129
+ try {
3130
+ await current.start();
3131
+ } catch (cause) {
3132
+ setError(cause instanceof Error ? cause.message : "Could not start the call.");
3133
+ await finish("failed");
3134
+ throw cause;
3135
+ }
3136
+ }, [call, finish, options.target, options.title]);
3137
+ const end = useCallback(async () => {
3138
+ if (!callIdRef.current) return;
3139
+ setEnding(true);
3140
+ await realtimeRef.current.stop().catch(() => void 0);
3141
+ await finish("ended");
3142
+ }, [finish]);
3143
+ const setMuted = useCallback(
3144
+ (next) => {
3145
+ realtimeRef.current.setInputMuted(next);
3146
+ const id = callIdRef.current;
3147
+ if (id && systemCallRef.current === id) void call?.setMuted(id, next).catch(() => void 0);
3148
+ },
3149
+ [call]
3150
+ );
3151
+ const setSpeaker = useCallback(
3152
+ (on) => {
3153
+ setSpeakerState(on);
3154
+ void call?.setSpeaker(on).catch(() => setSpeakerState(!on));
3155
+ },
3156
+ [call]
3157
+ );
3158
+ const phase = !callId ? "idle" : ending || status === "stopping" ? "ending" : status === "active" ? "active" : status === "recovering" && connectedRef.current === callId ? "reconnecting" : "connecting";
3159
+ return {
3160
+ phase,
3161
+ muted,
3162
+ speaker,
3163
+ route,
3164
+ error,
3165
+ systemCall,
3166
+ canStart: !callId && realtime.canStart,
3167
+ start,
3168
+ end,
3169
+ setMuted,
3170
+ setSpeaker
3171
+ };
3172
+ }
3173
+ var DEFAULT_OPENGENI_NATIVE_CALL_LABELS = {
3174
+ connecting: "Connecting\u2026",
3175
+ live: "Listening",
3176
+ reconnecting: "Reconnecting\u2026",
3177
+ ending: "Ending\u2026",
3178
+ muted: "Muted",
3179
+ mute: "Mute",
3180
+ unmute: "Unmute",
3181
+ speaker: "Speaker",
3182
+ end: "End",
3183
+ minimize: "Back to chat"
3184
+ };
3185
+ function formatDuration(seconds) {
3186
+ const minutes = Math.floor(seconds / 60);
3187
+ const rest = seconds % 60;
3188
+ return `${minutes}:${String(rest).padStart(2, "0")}`;
3189
+ }
3190
+ function useCallDuration(active) {
3191
+ const [since, setSince] = useState(null);
3192
+ const [now, setNow] = useState(() => Date.now());
3193
+ useEffect(() => {
3194
+ if (!active) return setSince(null);
3195
+ setSince((current) => current ?? Date.now());
3196
+ const timer = setInterval(() => setNow(Date.now()), 1e3);
3197
+ return () => clearInterval(timer);
3198
+ }, [active]);
3199
+ return since === null ? null : Math.max(0, Math.floor((now - since) / 1e3));
3200
+ }
3201
+ function CallButton({
3202
+ icon,
3203
+ label,
3204
+ onPress,
3205
+ selected = false,
3206
+ destructive = false,
3207
+ disabled = false
3208
+ }) {
3209
+ const theme = useNativeTimelineTheme();
3210
+ const { colors } = theme;
3211
+ const hangUp = theme.scheme === "dark" ? "#FF453A" : "#FF3B30";
3212
+ const background = destructive ? hangUp : selected ? colors.fg : colors["surface-2"];
3213
+ const foreground = destructive ? "#FFFFFF" : selected ? colors.bg : colors.fg;
3214
+ return /* @__PURE__ */ jsxs(View, { style: styles2.buttonColumn, children: [
3215
+ /* @__PURE__ */ jsx(
3216
+ Pressable,
3217
+ {
3218
+ accessibilityRole: "button",
3219
+ accessibilityLabel: label,
3220
+ accessibilityState: { selected, disabled },
3221
+ disabled,
3222
+ onPress,
3223
+ style: ({ pressed }) => [
3224
+ styles2.button,
3225
+ { backgroundColor: background, opacity: disabled ? 0.4 : pressed ? 0.75 : 1 }
3226
+ ],
3227
+ children: /* @__PURE__ */ jsx(Icon, { name: icon, size: 26, color: foreground })
3228
+ }
3229
+ ),
3230
+ /* @__PURE__ */ jsx(Text, { style: [styles2.buttonLabel, fontStyle(theme, 500), { color: colors["fg-muted"] }], children: label })
3231
+ ] });
3232
+ }
3233
+ function OpenGeniNativeCallView({
3234
+ call,
3235
+ title,
3236
+ subtitle,
3237
+ onMinimize,
3238
+ labels: labelOverrides
3239
+ }) {
3240
+ const theme = useNativeTimelineTheme();
3241
+ const { colors } = theme;
3242
+ const labels = { ...DEFAULT_OPENGENI_NATIVE_CALL_LABELS, ...labelOverrides };
3243
+ const duration = useCallDuration(call.phase === "active");
3244
+ const status = call.phase === "connecting" || call.phase === "idle" ? labels.connecting : call.phase === "reconnecting" ? labels.reconnecting : call.phase === "ending" ? labels.ending : call.muted ? labels.muted : labels.live;
3245
+ const orbState = call.phase === "active" ? call.muted ? "breathing" : "listening" : "connecting";
3246
+ const live = call.phase !== "idle" && call.phase !== "ending";
3247
+ return /* @__PURE__ */ jsxs(View, { style: [styles2.screen, { backgroundColor: colors.canvas }], testID: "opengeni-call", children: [
3248
+ /* @__PURE__ */ jsx(View, { style: styles2.header, children: onMinimize ? /* @__PURE__ */ jsx(
3249
+ Pressable,
3250
+ {
3251
+ accessibilityRole: "button",
3252
+ accessibilityLabel: labels.minimize,
3253
+ hitSlop: 12,
3254
+ onPress: onMinimize,
3255
+ style: ({ pressed }) => [styles2.minimize, { opacity: pressed ? 0.6 : 1 }],
3256
+ children: /* @__PURE__ */ jsx(Icon, { name: "chevron-down", size: 24, color: colors["fg-muted"] })
3257
+ }
3258
+ ) : null }),
3259
+ /* @__PURE__ */ jsxs(View, { style: styles2.center, children: [
3260
+ /* @__PURE__ */ jsx(ThinkingOrb, { state: orbState, size: 64, displaySize: 176, speed: 0.7 }),
3261
+ /* @__PURE__ */ jsx(
3262
+ Text,
3263
+ {
3264
+ style: [styles2.title, fontStyle(theme, 600), { color: colors.fg }],
3265
+ numberOfLines: 2,
3266
+ accessibilityRole: "header",
3267
+ children: title
3268
+ }
3269
+ ),
3270
+ subtitle ? /* @__PURE__ */ jsx(
3271
+ Text,
3272
+ {
3273
+ style: [styles2.subtitle, fontStyle(theme, 400), { color: colors["fg-muted"] }],
3274
+ numberOfLines: 1,
3275
+ children: subtitle
3276
+ }
3277
+ ) : null,
3278
+ /* @__PURE__ */ jsx(
3279
+ Text,
3280
+ {
3281
+ style: [styles2.status, fontStyle(theme, 500), { color: colors["fg-muted"] }],
3282
+ accessibilityLiveRegion: "polite",
3283
+ children: duration !== null && !call.muted ? `${status} \xB7 ${formatDuration(duration)}` : status
3284
+ }
3285
+ ),
3286
+ call.error ? /* @__PURE__ */ jsx(Text, { style: [styles2.error, fontStyle(theme, 400), { color: colors.danger }], children: call.error }) : null
3287
+ ] }),
3288
+ /* @__PURE__ */ jsxs(View, { style: styles2.controls, children: [
3289
+ /* @__PURE__ */ jsx(
3290
+ CallButton,
3291
+ {
3292
+ icon: call.muted ? "mic-off" : "mic",
3293
+ label: call.muted ? labels.unmute : labels.mute,
3294
+ selected: call.muted,
3295
+ disabled: !live,
3296
+ onPress: () => call.setMuted(!call.muted)
3297
+ }
3298
+ ),
3299
+ /* @__PURE__ */ jsx(
3300
+ CallButton,
3301
+ {
3302
+ icon: "volume-2",
3303
+ label: labels.speaker,
3304
+ selected: call.speaker,
3305
+ disabled: !live,
3306
+ onPress: () => call.setSpeaker(!call.speaker)
3307
+ }
3308
+ ),
3309
+ /* @__PURE__ */ jsx(
3310
+ CallButton,
3311
+ {
3312
+ icon: "phone-off",
3313
+ label: labels.end,
3314
+ destructive: true,
3315
+ disabled: call.phase === "idle" || call.phase === "ending",
3316
+ onPress: () => void call.end()
3317
+ }
3318
+ )
3319
+ ] })
3320
+ ] });
3321
+ }
3322
+ var styles2 = StyleSheet.create({
3323
+ screen: { flex: 1, paddingHorizontal: 24, paddingBottom: 32 },
3324
+ header: { height: 52, flexDirection: "row", alignItems: "center" },
3325
+ minimize: { width: 44, height: 44, alignItems: "center", justifyContent: "center" },
3326
+ center: { flex: 1, alignItems: "center", justifyContent: "center", gap: 10 },
3327
+ title: { fontSize: 26, lineHeight: 32, textAlign: "center", marginTop: 28 },
3328
+ subtitle: { fontSize: 15, lineHeight: 20, textAlign: "center" },
3329
+ status: { fontSize: 17, lineHeight: 22, marginTop: 4, fontVariant: ["tabular-nums"] },
3330
+ error: { fontSize: 15, lineHeight: 20, textAlign: "center", marginTop: 8 },
3331
+ controls: { flexDirection: "row", justifyContent: "space-evenly", alignItems: "flex-start" },
3332
+ buttonColumn: { alignItems: "center", gap: 8, width: 88 },
3333
+ button: {
3334
+ width: 72,
3335
+ height: 72,
3336
+ borderRadius: 36,
3337
+ alignItems: "center",
3338
+ justifyContent: "center"
3339
+ },
3340
+ buttonLabel: { fontSize: 13, lineHeight: 16 }
3341
+ });
3342
+
3343
+ export { DEFAULT_OPENGENI_NATIVE_CALL_LABELS, DEFAULT_OPENGENI_NATIVE_LABELS, DEFAULT_OPENGENI_NATIVE_THEME, NATIVE_ATTACHMENT_PREPARATION_TIMEOUT_MS, OpenGeniNativeAttachmentStrip, OpenGeniNativeCallView, OpenGeniNativeSessionView, OpenGeniReactNativeProvider, boundedJson, createMemoryRealtimeOwnerStorage, createNativeRealtimeControllerFactory, createNativeRemoteAudio, formatNativeRelativeTime, installOpenGeniReactNativeEnvironment, nativeHumanInputRequestPreview, nativeRealtimeModelSupported, nativeSessionStatusTone, prepareNativeAttachmentUpload, timelineAccessibilityLabel, useNativeFileAttachments, useNativeRealtimeCall, useNativeSessionRealtime, useOpenGeniNativeSession, useOpenGeniReactNativeEnvironment, validateHumanInputAnswers };
3344
+ //# sourceMappingURL=index.js.map
3345
+ //# sourceMappingURL=index.js.map