@appilots/sdk 0.11.3 → 0.14.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,11 +1,11 @@
1
- export { AppilotsNavigationContainer } from './chunk-JKLE5SLN.mjs';
2
- import { useAppilotsChat, useAppilotsActions, shouldShowSuggestedPrompts, useSuggestedPrompts } from './chunk-2ZDELL54.mjs';
3
- export { appilotsSelfCheck, captureSnapshot, executeAction, useAppilots, useAppilotsActions, useAppilotsChat, useAppilotsField, useAppilotsNavigation, useAppilotsSlider, useAppilotsTarget, useAppilotsToggle, useSuggestedPrompts } from './chunk-2ZDELL54.mjs';
4
- import { defaultLightTheme, describeAction, mergeThemeTokens, resolveBaseTheme, useAppilotsContext, AppilotsErrorBoundary, componentRegistry } from './chunk-ROXXCDIG.mjs';
5
- export { AppilotsClient, AppilotsProvider, AppilotsRegistryProvider, RateLimitedError, SDK_VERSION, _patchJsxRuntimes, clearAppilotsDebugTraces, componentRegistry, createComponentRegistry, createElementRegistry, createListRegistry, defaultDarkTheme, defaultLightTheme, describeAction, elementRegistry, enableAppilotsAutoTracking, getAppilotsDebugTraces, getAutoTrackingState, getGlobalConfig, getIntrospectionDiagnostics, humanizeError, initAppilots, isAutoTrackingEnabled, listRegistry, mergeThemeTokens, recordAppilotsDebugTrace, registerScreen, setCurrentScreen, subscribeAppilotsDebugTraces, useAppilotsContext, useResolvedRegistry } from './chunk-ROXXCDIG.mjs';
6
- import * as React7 from 'react';
7
- import React7__default, { createContext, useRef, useEffect, useMemo, useContext, useState, useCallback } from 'react';
8
- import { StyleSheet, Dimensions, Platform, View, Animated, Text, TouchableOpacity, ActivityIndicator, useColorScheme, NativeModules, FlatList, Image, TextInput, Modal, KeyboardAvoidingView, Linking } from 'react-native';
1
+ export { AppilotsNavigationContainer } from './chunk-N66DUP54.mjs';
2
+ import { subscribeSpotlight, getSpotlightTarget, AppilotsI18nProvider, useAppilotsI18n, useAppilotsChat, useAppilotsActions, setSpotlightMode, shouldShowSuggestedPrompts, useSuggestedPrompts, subscribeSpotlightAnnouncement } from './chunk-6JWU3NBT.mjs';
3
+ export { AppilotsI18nProvider, REVEAL_MARGIN, SPOTLIGHT_TTL_MS, appilotsSelfCheck, captureSnapshot, detectDeviceLocale, executeAction, getSpotlightMode, getSpotlightTarget, offsetToReveal, resolveLocale, revealTarget, setSpotlightMode, setSpotlightTarget, subscribeSpotlight, useAppilots, useAppilotsActions, useAppilotsChat, useAppilotsField, useAppilotsI18n, useAppilotsNavigation, useAppilotsSlider, useAppilotsTarget, useAppilotsToggle, useSuggestedPrompts } from './chunk-6JWU3NBT.mjs';
4
+ import { defaultLightTheme, describeAction, confirmExpiresAt, isRectOnScreen, mergeThemeTokens, resolveBaseTheme, useAppilotsContext, AppilotsErrorBoundary, isSpeechHost, subscribeGeometryHost, geometryAvailability, isGeometryResolved, needsWideChatLayout, componentRegistry, startStillWorkingTimer, appilotsDebugWarn, enhancePlainAssistantContent, looksLikeMarkdown, parseMarkdownBlocks, parseInlineMarkdown, getFiberRoot, shouldSkipSubtree, isModalFiber } from './chunk-AKAMWKO6.mjs';
5
+ export { AppilotsClient, AppilotsProvider, AppilotsRegistryProvider, RateLimitedError, SDK_VERSION, _patchJsxRuntimes, clearAppilotsDebugTraces, componentRegistry, createComponentRegistry, createElementRegistry, createListRegistry, defaultDarkTheme, defaultLightTheme, describeAction, elementRegistry, enableAppilotsAutoTracking, getAppilotsDebugTraces, getAutoTrackingState, getGlobalConfig, getIntrospectionDiagnostics, humanizeError, initAppilots, isAutoTrackingEnabled, isSpeechHost, listRegistry, mergeThemeTokens, recordAppilotsDebugTrace, registerScreen, setCurrentScreen, subscribeAppilotsDebugTraces, useAppilotsContext, useResolvedRegistry } from './chunk-AKAMWKO6.mjs';
6
+ import * as React8 from 'react';
7
+ import React8__default, { createContext, useRef, useEffect, useState, useCallback, useSyncExternalStore, useMemo, useContext } from 'react';
8
+ import { StyleSheet, Dimensions, Platform, View, Animated, Text, TouchableOpacity, AccessibilityInfo, ActivityIndicator, useColorScheme, useWindowDimensions, FlatList, Image, TextInput, KeyboardAvoidingView, Modal, Linking } from 'react-native';
9
9
  import { SafeAreaInsetsContext } from 'react-native-safe-area-context';
10
10
  import Svg, { Path } from 'react-native-svg';
11
11
 
@@ -22,6 +22,43 @@ function useSafeAreaInsetsOrDefault() {
22
22
  }
23
23
  return NO_INSETS;
24
24
  }
25
+ function probeHostModalOpen() {
26
+ const stack = [getFiberRoot()];
27
+ const seen = /* @__PURE__ */ new Set();
28
+ while (stack.length) {
29
+ const node = stack.pop();
30
+ if (!node || seen.has(node)) continue;
31
+ seen.add(node);
32
+ if (node.sibling) stack.push(node.sibling);
33
+ if (shouldSkipSubtree(node)) continue;
34
+ if (isModalFiber(node) && node.child) return true;
35
+ if (node.child) stack.push(node.child);
36
+ }
37
+ return false;
38
+ }
39
+ function useHostModalOpen(enabled) {
40
+ const [open, setOpen] = useState(false);
41
+ useEffect(() => {
42
+ if (!enabled) {
43
+ setOpen(false);
44
+ return;
45
+ }
46
+ let closedSince;
47
+ const sample = () => {
48
+ if (probeHostModalOpen()) {
49
+ closedSince = void 0;
50
+ setOpen(true);
51
+ } else {
52
+ closedSince ??= Date.now();
53
+ if (Date.now() - closedSince >= 300) setOpen(false);
54
+ }
55
+ };
56
+ sample();
57
+ const timer = setInterval(sample, 150);
58
+ return () => clearInterval(timer);
59
+ }, [enabled]);
60
+ return enabled && open;
61
+ }
25
62
 
26
63
  // src/components/breadcrumbPresentation.ts
27
64
  var DEFAULT_BREADCRUMB_PALETTE = {
@@ -79,13 +116,15 @@ var PT_BR_CONFIRM_FALLBACK = {
79
116
  confirmDestructiveApprove: "Sim, executar",
80
117
  confirmApprove: "Confirmar",
81
118
  confirmCancel: "Cancelar",
82
- confirmThisAction: "esta a\xE7\xE3o"
119
+ confirmThisAction: "esta a\xE7\xE3o",
120
+ confirmExpired: "Esta confirma\xE7\xE3o expirou. Pe\xE7a de novo ao assistente para continuar."
83
121
  };
84
122
  function deriveConfirmCardContent(action, palette = DEFAULT_BREADCRUMB_PALETTE, strings) {
85
123
  const payload = action.payload ?? {};
86
124
  const isDestructive = isDestructiveAction(action);
87
125
  const copy = { ...PT_BR_CONFIRM_FALLBACK, ...strings ?? {} };
88
- const actionName = typeof payload.actionDescription === "string" && payload.actionDescription || copy.confirmThisAction;
126
+ const described = typeof payload.actionDescription === "string" ? payload.actionDescription : "";
127
+ const actionName = described || copy.confirmThisAction;
89
128
  const message = typeof payload.message === "string" && payload.message || copy.confirmMessage.replace("{action}", actionName);
90
129
  const title = typeof payload.title === "string" && payload.title || (isDestructive ? copy.confirmDestructiveTitle : copy.confirmTitle);
91
130
  const confirmLabel = typeof payload.confirmLabel === "string" && payload.confirmLabel || (isDestructive ? copy.confirmDestructiveApprove : copy.confirmApprove);
@@ -96,6 +135,10 @@ function deriveConfirmCardContent(action, palette = DEFAULT_BREADCRUMB_PALETTE,
96
135
  message,
97
136
  confirmLabel,
98
137
  cancelLabel,
138
+ // A bare "Confirm" heard out of context says nothing about what it does.
139
+ confirmA11yLabel: described ? `${confirmLabel}: ${described}` : confirmLabel,
140
+ cancelA11yLabel: described ? `${cancelLabel}: ${described}` : cancelLabel,
141
+ expiredMessage: copy.confirmExpired ?? PT_BR_CONFIRM_FALLBACK.confirmExpired,
99
142
  glyph: isDestructive ? "\u26A0" : "?",
100
143
  actionColor: isDestructive ? palette.error : palette.primary,
101
144
  cardBackground: palette.surface,
@@ -105,6 +148,79 @@ function deriveConfirmCardContent(action, palette = DEFAULT_BREADCRUMB_PALETTE,
105
148
  cancelTextColor: palette.textSecondary
106
149
  };
107
150
  }
151
+ var TARGET_ANNOUNCEMENT = {
152
+ "pt-BR": {
153
+ press: "Tocando em {label}",
154
+ toggle: "Alternando {label}",
155
+ focus: "Selecionando {label}",
156
+ fill: "Preenchendo {label}"
157
+ },
158
+ en: {
159
+ press: "Tapping {label}",
160
+ toggle: "Toggling {label}",
161
+ focus: "Selecting {label}",
162
+ fill: "Filling in {label}"
163
+ },
164
+ es: {
165
+ press: "Tocando {label}",
166
+ toggle: "Cambiando {label}",
167
+ focus: "Seleccionando {label}",
168
+ fill: "Rellenando {label}"
169
+ },
170
+ fr: {
171
+ press: "Appui sur {label}",
172
+ toggle: "Bascule de {label}",
173
+ focus: "S\xE9lection de {label}",
174
+ fill: "Saisie de {label}"
175
+ }
176
+ };
177
+ function targetAnnouncement(verb, label, locale = "pt-BR") {
178
+ return (TARGET_ANNOUNCEMENT[locale] ?? TARGET_ANNOUNCEMENT["pt-BR"])[verb].replace(
179
+ "{label}",
180
+ label
181
+ );
182
+ }
183
+ function executorNamesTarget(action) {
184
+ if (action.type === "form_fill") return true;
185
+ if (action.type !== "ui_interaction") return false;
186
+ const interaction = action.payload?.action;
187
+ return !["scroll", "scroll_to"].includes(String(interaction ?? "press"));
188
+ }
189
+ function actionAnnouncement(action, locale, named) {
190
+ const { state, label } = describeAction(action, locale);
191
+ if (state === "failed") return label;
192
+ const executorNames = executorNamesTarget(action);
193
+ if (state === "running") return executorNames ? null : label;
194
+ if (state === "success") return executorNames && !named ? label : null;
195
+ return null;
196
+ }
197
+ var reduceMotion = false;
198
+ var listeners = /* @__PURE__ */ new Set();
199
+ var subscription;
200
+ function set(value) {
201
+ if (value === reduceMotion) return;
202
+ reduceMotion = value;
203
+ for (const listener of listeners) listener();
204
+ }
205
+ function subscribe(onChange) {
206
+ listeners.add(onChange);
207
+ if (!subscription) {
208
+ subscription = AccessibilityInfo.addEventListener("reduceMotionChanged", set);
209
+ AccessibilityInfo.isReduceMotionEnabled().then(set, () => {
210
+ });
211
+ }
212
+ return () => {
213
+ listeners.delete(onChange);
214
+ if (listeners.size === 0) {
215
+ subscription?.remove();
216
+ subscription = void 0;
217
+ }
218
+ };
219
+ }
220
+ var get = () => reduceMotion;
221
+ function useReduceMotion() {
222
+ return useSyncExternalStore(subscribe, get, get);
223
+ }
108
224
 
109
225
  // src/components/ActionBreadcrumb.tsx
110
226
  function ActionBreadcrumb({
@@ -114,9 +230,11 @@ function ActionBreadcrumb({
114
230
  fontFamily,
115
231
  palette,
116
232
  confirmStrings,
233
+ locale,
117
234
  requireApprovalFor,
118
235
  onApprove,
119
- onReject
236
+ onReject,
237
+ onConfirmCardGone
120
238
  }) {
121
239
  if (actions.length === 0) return null;
122
240
  const resolvedPalette = {
@@ -125,7 +243,7 @@ function ActionBreadcrumb({
125
243
  primary: primaryColor,
126
244
  text: textColor
127
245
  };
128
- return /* @__PURE__ */ React7__default.createElement(View, { style: styles.container }, actions.map((action) => /* @__PURE__ */ React7__default.createElement(
246
+ return /* @__PURE__ */ React8__default.createElement(View, { style: styles.container }, actions.map((action) => /* @__PURE__ */ React8__default.createElement(
129
247
  BreadcrumbRow,
130
248
  {
131
249
  key: action.id,
@@ -135,16 +253,18 @@ function ActionBreadcrumb({
135
253
  fontFamily,
136
254
  palette: resolvedPalette,
137
255
  confirmStrings,
256
+ locale,
138
257
  needsApproval: requireApprovalFor?.(action) ?? false,
139
258
  onApprove,
140
- onReject
259
+ onReject,
260
+ onConfirmCardGone
141
261
  }
142
262
  )));
143
263
  }
144
264
  function BreadcrumbRow(props) {
145
265
  const { action, needsApproval } = props;
146
266
  if (action.type === "confirm" && action.status === "pending" && needsApproval) {
147
- return /* @__PURE__ */ React7__default.createElement(
267
+ return /* @__PURE__ */ React8__default.createElement(
148
268
  ConfirmCard,
149
269
  {
150
270
  action,
@@ -152,11 +272,12 @@ function BreadcrumbRow(props) {
152
272
  fontFamily: props.fontFamily,
153
273
  confirmStrings: props.confirmStrings,
154
274
  onApprove: props.onApprove,
155
- onReject: props.onReject
275
+ onReject: props.onReject,
276
+ onGone: props.onConfirmCardGone
156
277
  }
157
278
  );
158
279
  }
159
- return /* @__PURE__ */ React7__default.createElement(RegularBreadcrumbRow, { ...props });
280
+ return /* @__PURE__ */ React8__default.createElement(RegularBreadcrumbRow, { ...props });
160
281
  }
161
282
  function RegularBreadcrumbRow({
162
283
  action,
@@ -164,49 +285,49 @@ function RegularBreadcrumbRow({
164
285
  textColor,
165
286
  fontFamily,
166
287
  palette,
288
+ locale,
167
289
  needsApproval,
168
290
  onApprove,
169
291
  onReject
170
292
  }) {
171
- const { label, state } = describeAction(action);
293
+ const { label, state } = describeAction(action, locale);
172
294
  const tone = deriveStateTone(palette)[state];
173
295
  const fade = useRef(new Animated.Value(0)).current;
174
296
  const pulse = useRef(new Animated.Value(1)).current;
175
297
  const lastState = useRef(state);
298
+ const reduceMotion2 = useReduceMotion();
176
299
  useEffect(() => {
300
+ if (reduceMotion2) {
301
+ fade.setValue(1);
302
+ return;
303
+ }
177
304
  Animated.timing(fade, {
178
305
  toValue: 1,
179
306
  duration: 180,
180
307
  useNativeDriver: true
181
308
  }).start();
182
- }, [fade]);
309
+ }, [fade, reduceMotion2]);
183
310
  useEffect(() => {
184
311
  if (lastState.current === state) return;
185
312
  lastState.current = state;
313
+ if (reduceMotion2) return;
186
314
  pulse.setValue(0.4);
187
315
  Animated.timing(pulse, {
188
316
  toValue: 1,
189
317
  duration: 220,
190
318
  useNativeDriver: true
191
319
  }).start();
192
- }, [state, pulse]);
320
+ }, [state, pulse, reduceMotion2]);
193
321
  const isDestructive = isDestructiveAction(action);
194
- return /* @__PURE__ */ React7__default.createElement(Animated.View, { style: [styles.row, { opacity: fade }] }, /* @__PURE__ */ React7__default.createElement(
195
- Animated.View,
322
+ return /* @__PURE__ */ React8__default.createElement(Animated.View, { style: [styles.row, { opacity: fade }] }, /* @__PURE__ */ React8__default.createElement(Animated.View, { style: [styles.labelGroup, { opacity: pulse }] }, /* @__PURE__ */ React8__default.createElement(StateIcon, { state, tone, primaryColor }), /* @__PURE__ */ React8__default.createElement(
323
+ Text,
196
324
  {
197
- style: { opacity: pulse, flexDirection: "row", alignItems: "flex-start", flex: 1 }
325
+ style: [styles.label, { color: state === "failed" ? tone.color : textColor, fontFamily }],
326
+ accessibilityLabel: `${state}: ${label}`
198
327
  },
199
- /* @__PURE__ */ React7__default.createElement(StateIcon, { state, tone, primaryColor }),
200
- /* @__PURE__ */ React7__default.createElement(
201
- Text,
202
- {
203
- style: [styles.label, { color: state === "failed" ? tone.color : textColor, fontFamily }],
204
- accessibilityLabel: `${state}: ${label}`
205
- },
206
- label,
207
- isDestructive && /* @__PURE__ */ React7__default.createElement(Text, { style: [styles.destructiveTag, { color: palette.error }] }, " \u26A0 destrutiva")
208
- )
209
- ), needsApproval && action.status === "pending" && onApprove && onReject && /* @__PURE__ */ React7__default.createElement(View, { style: styles.approvalButtons }, /* @__PURE__ */ React7__default.createElement(
328
+ label,
329
+ isDestructive && /* @__PURE__ */ React8__default.createElement(Text, { style: [styles.destructiveTag, { color: palette.error }] }, " \u26A0 destrutiva")
330
+ )), needsApproval && action.status === "pending" && onApprove && onReject && /* @__PURE__ */ React8__default.createElement(View, { style: styles.approvalButtons }, /* @__PURE__ */ React8__default.createElement(
210
331
  TouchableOpacity,
211
332
  {
212
333
  onPress: () => onReject(action.id),
@@ -215,8 +336,8 @@ function RegularBreadcrumbRow({
215
336
  accessibilityLabel: "Rejeitar a\xE7\xE3o",
216
337
  testID: "assistant-action-reject"
217
338
  },
218
- /* @__PURE__ */ React7__default.createElement(Text, { style: [styles.rejectBtnText, { color: palette.error }] }, "Rejeitar")
219
- ), /* @__PURE__ */ React7__default.createElement(
339
+ /* @__PURE__ */ React8__default.createElement(Text, { style: [styles.rejectBtnText, { color: palette.error }] }, "Rejeitar")
340
+ ), /* @__PURE__ */ React8__default.createElement(
220
341
  TouchableOpacity,
221
342
  {
222
343
  onPress: () => onApprove(action.id),
@@ -225,16 +346,18 @@ function RegularBreadcrumbRow({
225
346
  accessibilityLabel: "Aprovar a\xE7\xE3o",
226
347
  testID: "assistant-action-approve"
227
348
  },
228
- /* @__PURE__ */ React7__default.createElement(Text, { style: styles.approveBtnText }, "Aprovar")
349
+ /* @__PURE__ */ React8__default.createElement(Text, { style: styles.approveBtnText }, "Aprovar")
229
350
  )));
230
351
  }
352
+ var MIN_TOUCH_TARGET = 44;
231
353
  function ConfirmCard({
232
354
  action,
233
355
  palette,
234
356
  fontFamily,
235
357
  confirmStrings,
236
358
  onApprove,
237
- onReject
359
+ onReject,
360
+ onGone
238
361
  }) {
239
362
  const {
240
363
  isDestructive,
@@ -242,6 +365,9 @@ function ConfirmCard({
242
365
  message,
243
366
  confirmLabel,
244
367
  cancelLabel,
368
+ confirmA11yLabel,
369
+ cancelA11yLabel,
370
+ expiredMessage,
245
371
  glyph,
246
372
  actionColor,
247
373
  cardBackground,
@@ -250,50 +376,91 @@ function ConfirmCard({
250
376
  cancelBackground,
251
377
  cancelTextColor
252
378
  } = deriveConfirmCardContent(action, palette, confirmStrings);
253
- return /* @__PURE__ */ React7__default.createElement(
379
+ const summaryRef = useRef(null);
380
+ const expiresAt = confirmExpiresAt(action);
381
+ const [expired, setExpired] = useState(() => expiresAt !== void 0 && expiresAt <= Date.now());
382
+ const onGoneRef = useRef(onGone);
383
+ onGoneRef.current = onGone;
384
+ useEffect(() => {
385
+ if (summaryRef.current) AccessibilityInfo.sendAccessibilityEvent(summaryRef.current, "focus");
386
+ return () => onGoneRef.current?.();
387
+ }, []);
388
+ useEffect(() => {
389
+ if (expiresAt === void 0 || expired) return;
390
+ const timer = setTimeout(() => setExpired(true), expiresAt - Date.now());
391
+ return () => clearTimeout(timer);
392
+ }, [expiresAt, expired]);
393
+ useEffect(() => {
394
+ if (expired) AccessibilityInfo.announceForAccessibility(expiredMessage);
395
+ }, [expired, expiredMessage]);
396
+ return /* @__PURE__ */ React8__default.createElement(
254
397
  View,
255
398
  {
256
399
  style: [
257
400
  confirmCardStyles.card,
258
401
  { backgroundColor: cardBackground, borderColor: cardBorderColor }
259
402
  ],
260
- testID: "assistant-confirm-card"
403
+ testID: "assistant-confirm-card",
404
+ onAccessibilityEscape: () => onReject?.(action.id)
261
405
  },
262
- /* @__PURE__ */ React7__default.createElement(View, { style: confirmCardStyles.headerRow }, /* @__PURE__ */ React7__default.createElement(Text, { style: [confirmCardStyles.glyph, { color: actionColor }] }, glyph), /* @__PURE__ */ React7__default.createElement(
406
+ /* @__PURE__ */ React8__default.createElement(
407
+ View,
408
+ {
409
+ ref: summaryRef,
410
+ accessible: true,
411
+ accessibilityLabel: `${title}. ${message}`,
412
+ accessibilityLiveRegion: "polite"
413
+ },
414
+ /* @__PURE__ */ React8__default.createElement(View, { style: confirmCardStyles.headerRow }, /* @__PURE__ */ React8__default.createElement(Text, { style: [confirmCardStyles.glyph, { color: actionColor }] }, glyph), /* @__PURE__ */ React8__default.createElement(
415
+ Text,
416
+ {
417
+ style: [
418
+ confirmCardStyles.title,
419
+ { color: isDestructive ? palette.error : palette.text, fontFamily }
420
+ ]
421
+ },
422
+ title
423
+ )),
424
+ /* @__PURE__ */ React8__default.createElement(Text, { style: [confirmCardStyles.message, { color: messageColor, fontFamily }] }, message)
425
+ ),
426
+ expired && /* @__PURE__ */ React8__default.createElement(
263
427
  Text,
264
428
  {
265
- style: [
266
- confirmCardStyles.title,
267
- { color: isDestructive ? palette.error : palette.text, fontFamily }
268
- ]
429
+ style: [confirmCardStyles.expired, { color: palette.error, fontFamily }],
430
+ accessibilityLiveRegion: "assertive",
431
+ testID: "assistant-confirm-expired"
269
432
  },
270
- title
271
- )),
272
- /* @__PURE__ */ React7__default.createElement(Text, { style: [confirmCardStyles.message, { color: messageColor, fontFamily }] }, message),
273
- /* @__PURE__ */ React7__default.createElement(View, { style: confirmCardStyles.buttons }, /* @__PURE__ */ React7__default.createElement(
433
+ expiredMessage
434
+ ),
435
+ /* @__PURE__ */ React8__default.createElement(View, { style: confirmCardStyles.buttons }, /* @__PURE__ */ React8__default.createElement(
274
436
  TouchableOpacity,
275
437
  {
276
438
  onPress: () => onReject?.(action.id),
277
439
  style: [confirmCardStyles.btn, { backgroundColor: cancelBackground }],
278
440
  activeOpacity: 0.7,
279
- accessibilityLabel: cancelLabel,
441
+ accessibilityRole: "button",
442
+ accessibilityLabel: cancelA11yLabel,
280
443
  testID: "assistant-confirm-reject"
281
444
  },
282
- /* @__PURE__ */ React7__default.createElement(Text, { style: [confirmCardStyles.cancelText, { color: cancelTextColor, fontFamily }] }, cancelLabel)
283
- ), /* @__PURE__ */ React7__default.createElement(
445
+ /* @__PURE__ */ React8__default.createElement(Text, { style: [confirmCardStyles.cancelText, { color: cancelTextColor, fontFamily }] }, cancelLabel)
446
+ ), /* @__PURE__ */ React8__default.createElement(
284
447
  TouchableOpacity,
285
448
  {
286
449
  onPress: () => onApprove?.(action.id),
450
+ disabled: expired,
287
451
  style: [
288
452
  confirmCardStyles.btn,
289
453
  confirmCardStyles.confirmBtn,
290
- { backgroundColor: actionColor }
454
+ { backgroundColor: actionColor },
455
+ expired && confirmCardStyles.disabledBtn
291
456
  ],
292
457
  activeOpacity: 0.7,
293
- accessibilityLabel: confirmLabel,
458
+ accessibilityRole: "button",
459
+ accessibilityState: { disabled: expired },
460
+ accessibilityLabel: confirmA11yLabel,
294
461
  testID: "assistant-confirm-approve"
295
462
  },
296
- /* @__PURE__ */ React7__default.createElement(Text, { style: [confirmCardStyles.confirmText, { fontFamily }] }, confirmLabel)
463
+ /* @__PURE__ */ React8__default.createElement(Text, { style: [confirmCardStyles.confirmText, { fontFamily }] }, confirmLabel)
297
464
  ))
298
465
  );
299
466
  }
@@ -327,18 +494,33 @@ var confirmCardStyles = StyleSheet.create({
327
494
  // color set inline — theme-driven (textSecondary).
328
495
  marginBottom: 12
329
496
  },
497
+ // Wraps: on one line the two buttons ran off the LEFT edge at iOS AX5
498
+ // and Cancel was unreachable — on the one card that gates something
499
+ // irreversible.
330
500
  buttons: {
331
501
  flexDirection: "row",
502
+ flexWrap: "wrap",
332
503
  gap: 8,
333
504
  justifyContent: "flex-end"
334
505
  },
335
506
  btn: {
507
+ minHeight: MIN_TOUCH_TARGET,
508
+ maxWidth: "100%",
509
+ justifyContent: "center",
336
510
  paddingHorizontal: 14,
337
511
  paddingVertical: 8,
338
512
  borderRadius: 10,
339
513
  minWidth: 96,
340
514
  alignItems: "center"
341
515
  },
516
+ disabledBtn: {
517
+ opacity: 0.4
518
+ },
519
+ expired: {
520
+ fontSize: 13,
521
+ fontWeight: "600",
522
+ marginBottom: 12
523
+ },
342
524
  cancelText: {
343
525
  fontSize: 13,
344
526
  fontWeight: "600"
@@ -358,7 +540,11 @@ function StateIcon({
358
540
  tone,
359
541
  primaryColor
360
542
  }) {
361
- return /* @__PURE__ */ React7__default.createElement(View, { style: [styles.iconCircle, { backgroundColor: tone.iconBg }] }, state === "running" ? /* @__PURE__ */ React7__default.createElement(ActivityIndicator, { size: "small", color: primaryColor }) : /* @__PURE__ */ React7__default.createElement(Text, { style: [styles.iconGlyph, { color: tone.color }] }, iconGlyphFor(state)));
543
+ return /* @__PURE__ */ React8__default.createElement(View, { style: [styles.iconCircle, { backgroundColor: tone.iconBg }] }, state === "running" ? /* @__PURE__ */ React8__default.createElement(ActivityIndicator, { size: "small", color: primaryColor }) : (
544
+ // Decorative, inside a fixed circle: capped so it cannot clip. The
545
+ // state is also in the row's accessibilityLabel.
546
+ /* @__PURE__ */ React8__default.createElement(Text, { maxFontSizeMultiplier: 1.3, style: [styles.iconGlyph, { color: tone.color }] }, iconGlyphFor(state))
547
+ ));
362
548
  }
363
549
  var styles = StyleSheet.create({
364
550
  container: {
@@ -366,12 +552,26 @@ var styles = StyleSheet.create({
366
552
  marginBottom: 8,
367
553
  paddingVertical: 4
368
554
  },
555
+ // Wraps so Approve/Reject drop below the label at large text sizes. On
556
+ // one line they squeezed the label to zero width (iOS AX5): the user was
557
+ // asked to approve an action they could not read.
369
558
  row: {
370
559
  flexDirection: "row",
560
+ flexWrap: "wrap",
561
+ gap: 8,
371
562
  alignItems: "center",
372
563
  paddingVertical: 6,
373
564
  paddingHorizontal: 4
374
565
  },
566
+ labelGroup: {
567
+ flexDirection: "row",
568
+ alignItems: "flex-start",
569
+ flexGrow: 1,
570
+ flexShrink: 1,
571
+ flexBasis: 0,
572
+ // What forces the wrap: the label keeps at least half the row.
573
+ minWidth: "50%"
574
+ },
375
575
  iconCircle: {
376
576
  width: 22,
377
577
  height: 22,
@@ -399,9 +599,11 @@ var styles = StyleSheet.create({
399
599
  approvalButtons: {
400
600
  flexDirection: "row",
401
601
  gap: 6,
402
- marginLeft: 8
602
+ marginLeft: "auto"
403
603
  },
404
604
  btn: {
605
+ minHeight: MIN_TOUCH_TARGET,
606
+ justifyContent: "center",
405
607
  paddingHorizontal: 10,
406
608
  paddingVertical: 5,
407
609
  borderRadius: 8
@@ -449,7 +651,7 @@ function resolvePersonalization(props, remote) {
449
651
  };
450
652
  }
451
653
  function ChatIcon({ size = 24, color = "#ffffff" }) {
452
- return /* @__PURE__ */ React7.createElement(Svg, { width: size, height: size, viewBox: "0 0 24 24", fill: "none" }, /* @__PURE__ */ React7.createElement(
654
+ return /* @__PURE__ */ React8.createElement(Svg, { width: size, height: size, viewBox: "0 0 24 24", fill: "none" }, /* @__PURE__ */ React8.createElement(
453
655
  Path,
454
656
  {
455
657
  d: "M5 3.5h14a3 3 0 0 1 3 3v9a3 3 0 0 1-3 3h-7.4l-4.6 4v-4H5a3 3 0 0 1-3-3v-9a3 3 0 0 1 3-3z",
@@ -457,8 +659,524 @@ function ChatIcon({ size = 24, color = "#ffffff" }) {
457
659
  }
458
660
  ));
459
661
  }
662
+
663
+ // src/components/internalProps.ts
664
+ var APPILOTS_SKIP_PROPS = {
665
+ __appilotsSkip: true,
666
+ __appilotsInternal: true
667
+ };
668
+
669
+ // src/components/AgentPill.tsx
670
+ var PILL_MAX_FONT_SCALE = 1.5;
671
+ function toneFor(state, colors) {
672
+ switch (state) {
673
+ case "success":
674
+ return colors.success;
675
+ case "failed":
676
+ return colors.error;
677
+ default:
678
+ return colors.primary;
679
+ }
680
+ }
681
+ function AgentPill({
682
+ label,
683
+ state,
684
+ onExpand,
685
+ expandRef,
686
+ onStop,
687
+ position,
688
+ insets,
689
+ colors,
690
+ fontFamily,
691
+ strings
692
+ }) {
693
+ const atTop = position === "top-left" || position === "top-right";
694
+ const reduceMotion2 = useReduceMotion();
695
+ const enter = useRef(new Animated.Value(0)).current;
696
+ useEffect(() => {
697
+ if (reduceMotion2) {
698
+ enter.setValue(1);
699
+ return;
700
+ }
701
+ Animated.spring(enter, {
702
+ toValue: 1,
703
+ useNativeDriver: true,
704
+ tension: 70,
705
+ friction: 12
706
+ }).start();
707
+ }, [enter, reduceMotion2]);
708
+ const translateY = enter.interpolate({
709
+ inputRange: [0, 1],
710
+ outputRange: [atTop ? -80 : 80, 0]
711
+ });
712
+ const running = state === "running" || state === "pending";
713
+ return /* @__PURE__ */ React8__default.createElement(
714
+ Animated.View,
715
+ {
716
+ ...APPILOTS_SKIP_PROPS,
717
+ pointerEvents: "box-none",
718
+ style: [
719
+ styles2.wrapper,
720
+ atTop ? { top: insets.top + 8 } : { bottom: insets.bottom + 8 },
721
+ { opacity: enter, transform: [{ translateY }] }
722
+ ]
723
+ },
724
+ /* @__PURE__ */ React8__default.createElement(
725
+ TouchableOpacity,
726
+ {
727
+ ...APPILOTS_SKIP_PROPS,
728
+ ref: expandRef,
729
+ testID: "appilots-agent-pill",
730
+ accessibilityRole: "button",
731
+ accessibilityLabel: strings.expandA11y,
732
+ activeOpacity: 0.85,
733
+ onPress: onExpand,
734
+ style: [styles2.pill, { backgroundColor: colors.surface, borderColor: colors.border }]
735
+ },
736
+ running ? /* @__PURE__ */ React8__default.createElement(ActivityIndicator, { size: "small", color: toneFor(state, colors) }) : /* @__PURE__ */ React8__default.createElement(View, { style: [styles2.dot, { backgroundColor: toneFor(state, colors) }] }),
737
+ /* @__PURE__ */ React8__default.createElement(
738
+ Text,
739
+ {
740
+ testID: "appilots-agent-pill-label",
741
+ numberOfLines: 2,
742
+ maxFontSizeMultiplier: PILL_MAX_FONT_SCALE,
743
+ style: [styles2.label, { color: colors.text, fontFamily }]
744
+ },
745
+ label
746
+ ),
747
+ onStop ? /* @__PURE__ */ React8__default.createElement(
748
+ TouchableOpacity,
749
+ {
750
+ ...APPILOTS_SKIP_PROPS,
751
+ testID: "appilots-agent-pill-stop",
752
+ accessibilityRole: "button",
753
+ accessibilityLabel: strings.stopA11y,
754
+ onPress: onStop,
755
+ hitSlop: { top: 12, bottom: 12, left: 12, right: 12 },
756
+ style: [styles2.stop, { borderColor: colors.border }]
757
+ },
758
+ /* @__PURE__ */ React8__default.createElement(View, { style: [styles2.stopGlyph, { backgroundColor: colors.textSecondary }] })
759
+ ) : null
760
+ )
761
+ );
762
+ }
763
+ var styles2 = StyleSheet.create({
764
+ // `box-none` above + `position: absolute` here is what lets touches
765
+ // outside the pill itself reach the app underneath.
766
+ wrapper: {
767
+ position: "absolute",
768
+ left: 12,
769
+ right: 12,
770
+ zIndex: 999,
771
+ alignItems: "center"
772
+ },
773
+ pill: {
774
+ flexDirection: "row",
775
+ alignItems: "center",
776
+ gap: 10,
777
+ maxWidth: "100%",
778
+ paddingLeft: 14,
779
+ paddingRight: 8,
780
+ paddingVertical: 10,
781
+ borderRadius: 24,
782
+ borderWidth: StyleSheet.hairlineWidth,
783
+ shadowColor: "#000",
784
+ shadowOffset: { width: 0, height: 2 },
785
+ shadowOpacity: 0.18,
786
+ shadowRadius: 8,
787
+ elevation: 8
788
+ },
789
+ dot: { width: 8, height: 8, borderRadius: 4 },
790
+ label: { flexShrink: 1, fontSize: 14, fontWeight: "500" },
791
+ stop: {
792
+ width: 26,
793
+ height: 26,
794
+ borderRadius: 13,
795
+ borderWidth: StyleSheet.hairlineWidth,
796
+ alignItems: "center",
797
+ justifyContent: "center"
798
+ },
799
+ stopGlyph: { width: 9, height: 9, borderRadius: 1.5 }
800
+ });
801
+ var PADDING = 6;
802
+ var CAPTION_HEIGHT = 28;
803
+ var CAPTION_GAP = 8;
804
+ function SpotlightOverlay({
805
+ rect,
806
+ seq,
807
+ label,
808
+ color,
809
+ captionBackground,
810
+ captionColor,
811
+ fontFamily,
812
+ window: win
813
+ }) {
814
+ const reduceMotion2 = useReduceMotion();
815
+ const pulse = useRef(new Animated.Value(reduceMotion2 ? 1 : 0)).current;
816
+ useEffect(() => {
817
+ if (reduceMotion2) {
818
+ pulse.stopAnimation();
819
+ pulse.setValue(1);
820
+ return;
821
+ }
822
+ pulse.setValue(0);
823
+ Animated.timing(pulse, {
824
+ toValue: 1,
825
+ duration: 220,
826
+ useNativeDriver: true
827
+ }).start();
828
+ }, [seq, pulse, reduceMotion2]);
829
+ if (!isRectOnScreen(rect, win)) return null;
830
+ const frame = {
831
+ left: rect.x - PADDING,
832
+ top: rect.y - PADDING,
833
+ width: rect.width + PADDING * 2,
834
+ height: rect.height + PADDING * 2
835
+ };
836
+ const above = frame.top - CAPTION_GAP - CAPTION_HEIGHT >= 0;
837
+ const captionTop = above ? frame.top - CAPTION_GAP - CAPTION_HEIGHT : frame.top + frame.height + CAPTION_GAP;
838
+ const captionFits = captionTop + CAPTION_HEIGHT <= win.height;
839
+ return /* @__PURE__ */ React8__default.createElement(
840
+ View,
841
+ {
842
+ ...APPILOTS_SKIP_PROPS,
843
+ pointerEvents: "none",
844
+ style: StyleSheet.absoluteFill,
845
+ testID: "appilots-spotlight"
846
+ },
847
+ /* @__PURE__ */ React8__default.createElement(
848
+ Animated.View,
849
+ {
850
+ ...APPILOTS_SKIP_PROPS,
851
+ pointerEvents: "none",
852
+ style: [
853
+ styles3.ring,
854
+ frame,
855
+ { borderColor: color },
856
+ {
857
+ opacity: pulse,
858
+ // Settles inward: the ring arrives slightly large and snaps
859
+ // to the control, which reads as "this one" rather than as a
860
+ // box that was always there.
861
+ transform: [
862
+ {
863
+ scale: pulse.interpolate({ inputRange: [0, 1], outputRange: [1.08, 1] })
864
+ }
865
+ ]
866
+ }
867
+ ]
868
+ }
869
+ ),
870
+ label && captionFits ? /* @__PURE__ */ React8__default.createElement(
871
+ Animated.View,
872
+ {
873
+ ...APPILOTS_SKIP_PROPS,
874
+ pointerEvents: "none",
875
+ style: [
876
+ styles3.caption,
877
+ {
878
+ top: captionTop,
879
+ // Clamped so a control at either edge does not push the
880
+ // caption off screen.
881
+ left: Math.max(8, Math.min(frame.left, win.width - 8)),
882
+ maxWidth: win.width - 16,
883
+ backgroundColor: captionBackground,
884
+ opacity: pulse
885
+ }
886
+ ]
887
+ },
888
+ /* @__PURE__ */ React8__default.createElement(
889
+ Text,
890
+ {
891
+ testID: "appilots-spotlight-label",
892
+ numberOfLines: 1,
893
+ style: [styles3.captionText, { color: captionColor, fontFamily }]
894
+ },
895
+ label
896
+ )
897
+ ) : null
898
+ );
899
+ }
900
+ var styles3 = StyleSheet.create({
901
+ ring: {
902
+ position: "absolute",
903
+ borderWidth: 2.5,
904
+ borderRadius: 10
905
+ },
906
+ caption: {
907
+ position: "absolute",
908
+ height: CAPTION_HEIGHT,
909
+ justifyContent: "center",
910
+ paddingHorizontal: 10,
911
+ borderRadius: 8
912
+ },
913
+ captionText: { fontSize: 13, fontWeight: "500" }
914
+ });
915
+ var NEVER = () => () => {
916
+ };
917
+ var NULL_TARGET = () => null;
918
+ function useSpotlight(enabled) {
919
+ const subscribe2 = useCallback(
920
+ (onChange) => enabled ? subscribeSpotlight(onChange) : NEVER(),
921
+ [enabled]
922
+ );
923
+ return useSyncExternalStore(subscribe2, enabled ? getSpotlightTarget : NULL_TARGET, NULL_TARGET);
924
+ }
925
+
926
+ // src/components/pillStatus.ts
927
+ function resolvePillStatus(actions, busy, strings) {
928
+ for (let i = actions.length - 1; i >= 0; i--) {
929
+ const action = actions[i];
930
+ if (action.status === "executing") return describeAction(action, strings.locale);
931
+ }
932
+ if (!busy) {
933
+ for (let i = actions.length - 1; i >= 0; i--) {
934
+ const action = actions[i];
935
+ if (action.status === "completed" || action.status === "failed") {
936
+ return describeAction(action, strings.locale);
937
+ }
938
+ }
939
+ return { label: strings.done, state: "success" };
940
+ }
941
+ return { label: strings.working, state: "running" };
942
+ }
943
+ var STREAMING_PREFIX = "streaming_";
944
+ function announce(message) {
945
+ if (Platform.OS === "ios" && AccessibilityInfo.announceForAccessibilityWithOptions) {
946
+ AccessibilityInfo.announceForAccessibilityWithOptions(message, { queue: true });
947
+ } else {
948
+ AccessibilityInfo.announceForAccessibility(message);
949
+ }
950
+ }
951
+ var speakable = (content) => content.replace(/[*`#]+/g, "").trim();
952
+ function useChatAnnouncements({
953
+ messages,
954
+ actions,
955
+ awaitingApproval,
956
+ acting,
957
+ locale,
958
+ strings
959
+ }) {
960
+ const seenMessages = useRef(null);
961
+ if (!seenMessages.current) seenMessages.current = new Set(messages.map((m) => m.id));
962
+ const seenActions = useRef(null);
963
+ if (!seenActions.current) seenActions.current = new Map(actions.map((a) => [a.id, a.status]));
964
+ const prev = useRef({ awaitingApproval, acting });
965
+ const namedSinceSettle = useRef(false);
966
+ const localeRef = useRef(locale);
967
+ localeRef.current = locale;
968
+ useEffect(
969
+ () => subscribeSpotlightAnnouncement((label, verb) => {
970
+ namedSinceSettle.current = true;
971
+ announce(targetAnnouncement(verb, label, localeRef.current));
972
+ }),
973
+ []
974
+ );
975
+ useEffect(() => {
976
+ const out = [];
977
+ for (const message of messages) {
978
+ if (message.role === "user" || message.id.startsWith(STREAMING_PREFIX)) continue;
979
+ if (seenMessages.current.has(message.id)) continue;
980
+ seenMessages.current.add(message.id);
981
+ const text = speakable(message.content);
982
+ if (text) out.push(text);
983
+ }
984
+ for (const action of actions) {
985
+ if (action.type === "confirm") continue;
986
+ if (seenActions.current.get(action.id) === action.status) continue;
987
+ seenActions.current.set(action.id, action.status);
988
+ const line = actionAnnouncement(action, locale, namedSinceSettle.current);
989
+ if (action.status === "completed" || action.status === "failed") {
990
+ namedSinceSettle.current = false;
991
+ }
992
+ if (line) out.push(line);
993
+ }
994
+ if (awaitingApproval && !prev.current.awaitingApproval) out.push(strings.a11yAwaitingApproval);
995
+ if (prev.current.acting && !acting) out.push(strings.a11yTurnDone);
996
+ prev.current = { awaitingApproval, acting };
997
+ for (const line of out) announce(line);
998
+ }, [messages, actions, awaitingApproval, acting, locale, strings]);
999
+ }
1000
+ function useStillWorking(active, template) {
1001
+ const [seconds, setSeconds] = useState(0);
1002
+ useEffect(() => {
1003
+ setSeconds(0);
1004
+ if (!active) return;
1005
+ return startStillWorkingTimer((elapsed) => {
1006
+ setSeconds(elapsed);
1007
+ AccessibilityInfo.announceForAccessibility(template.replace("{seconds}", String(elapsed)));
1008
+ });
1009
+ }, [active, template]);
1010
+ return seconds;
1011
+ }
1012
+ function useVoiceInput({
1013
+ host,
1014
+ locale,
1015
+ onTranscript
1016
+ }) {
1017
+ const [state, setState] = useState("idle");
1018
+ const [partial, setPartial] = useState("");
1019
+ const mounted = useRef(true);
1020
+ useEffect(() => {
1021
+ mounted.current = true;
1022
+ return () => {
1023
+ mounted.current = false;
1024
+ };
1025
+ }, []);
1026
+ const onTranscriptRef = useRef(onTranscript);
1027
+ onTranscriptRef.current = onTranscript;
1028
+ const stateRef = useRef("idle");
1029
+ stateRef.current = state;
1030
+ useEffect(() => {
1031
+ return () => {
1032
+ if (stateRef.current === "listening" || stateRef.current === "starting") {
1033
+ void host?.cancel().catch(() => {
1034
+ });
1035
+ }
1036
+ };
1037
+ }, [host]);
1038
+ const start = useCallback(() => {
1039
+ if (!host) return;
1040
+ if (state !== "idle" && state !== "error") return;
1041
+ setState("starting");
1042
+ setPartial("");
1043
+ host.start({ locale }).then(() => {
1044
+ if (!mounted.current) return;
1045
+ setState("listening");
1046
+ }).catch((err) => {
1047
+ appilotsDebugWarn("useVoiceInput: start failed", err);
1048
+ if (!mounted.current) return;
1049
+ setState("error");
1050
+ });
1051
+ }, [host, locale, state]);
1052
+ const stop = useCallback(() => {
1053
+ if (!host) return;
1054
+ if (state !== "listening" && state !== "starting") return;
1055
+ setState("stopping");
1056
+ host.stop().then((text) => {
1057
+ if (!mounted.current) return;
1058
+ setState("idle");
1059
+ setPartial("");
1060
+ const trimmed = text?.trim();
1061
+ if (trimmed) onTranscriptRef.current(trimmed);
1062
+ }).catch((err) => {
1063
+ appilotsDebugWarn("useVoiceInput: stop failed", err);
1064
+ if (!mounted.current) return;
1065
+ setState("error");
1066
+ setPartial("");
1067
+ });
1068
+ }, [host, state]);
1069
+ const cancel = useCallback(() => {
1070
+ if (!host) return;
1071
+ setPartial("");
1072
+ setState("idle");
1073
+ void host.cancel().catch((err) => appilotsDebugWarn("useVoiceInput: cancel failed", err));
1074
+ }, [host]);
1075
+ useEffect(() => {
1076
+ if (!host?.onPartial) return;
1077
+ if (state !== "listening") return;
1078
+ const dispose = host.onPartial((text) => {
1079
+ if (mounted.current) setPartial(text);
1080
+ });
1081
+ return dispose;
1082
+ }, [host, state]);
1083
+ useEffect(() => {
1084
+ if (!host?.onEnd) return;
1085
+ if (state !== "listening") return;
1086
+ return host.onEnd((error) => {
1087
+ if (!mounted.current) return;
1088
+ setPartial("");
1089
+ setState(error ? "error" : "idle");
1090
+ });
1091
+ }, [host, state]);
1092
+ return { state, partial, start, stop, cancel };
1093
+ }
1094
+
1095
+ // src/components/experience.ts
1096
+ var DEFAULT_EXPERIENCE = {
1097
+ presence: "sheet",
1098
+ spotlight: "off",
1099
+ control: "agent",
1100
+ entry: ["fab"],
1101
+ input: ["text"]
1102
+ };
1103
+ function uniq(values) {
1104
+ return values.filter((value, index) => values.indexOf(value) === index);
1105
+ }
1106
+ function resolveExperience(requested, caps) {
1107
+ const downgrades = [];
1108
+ let presence = requested?.presence ?? DEFAULT_EXPERIENCE.presence;
1109
+ let spotlight = requested?.spotlight ?? DEFAULT_EXPERIENCE.spotlight;
1110
+ const control = requested?.control ?? DEFAULT_EXPERIENCE.control;
1111
+ const entry = uniq(requested?.entry ?? DEFAULT_EXPERIENCE.entry);
1112
+ let input = uniq(requested?.input ?? DEFAULT_EXPERIENCE.input);
1113
+ if (spotlight !== "off" && !caps.geometry) {
1114
+ downgrades.push({
1115
+ field: "spotlight",
1116
+ from: spotlight,
1117
+ to: "off",
1118
+ reason: 'spotlight needs Fabric window geometry, which this app cannot report. Enable the new React Native architecture, or set spotlight:"off" to silence this.'
1119
+ });
1120
+ spotlight = "off";
1121
+ }
1122
+ if (spotlight !== "off" && presence === "sheet") {
1123
+ downgrades.push({
1124
+ field: "presence",
1125
+ from: "sheet",
1126
+ to: "pill",
1127
+ reason: 'spotlight draws on the app, which the sheet covers. Set presence:"pill" or "hidden" explicitly to silence this.'
1128
+ });
1129
+ presence = "pill";
1130
+ }
1131
+ if (control === "shared" && presence === "sheet") {
1132
+ downgrades.push({
1133
+ field: "presence",
1134
+ from: "sheet",
1135
+ to: "pill",
1136
+ reason: 'control:"shared" needs the app to be touchable, and the sheet is a native Modal that swallows every touch. Set presence:"pill" or "hidden" to silence this.'
1137
+ });
1138
+ presence = "pill";
1139
+ }
1140
+ if (input.includes("voice") && !caps.speech) {
1141
+ downgrades.push({
1142
+ field: "input",
1143
+ from: input.join("+"),
1144
+ to: input.filter((mode) => mode !== "voice").join("+") || "text",
1145
+ reason: 'input:"voice" needs a SpeechHost passed to <AppilotsChat speech={...} />. The SDK ships no speech engine of its own \u2014 see https://docs.appilots.com/docs/guides/voice-input.'
1146
+ });
1147
+ input = input.filter((mode) => mode !== "voice");
1148
+ }
1149
+ if (input.length === 0) input = ["text"];
1150
+ return {
1151
+ experience: { presence, spotlight, control, entry, input },
1152
+ downgrades
1153
+ };
1154
+ }
1155
+ function formatDowngrades(downgrades) {
1156
+ return downgrades.map(
1157
+ (d) => `[appilots] experience.${d.field}: "${d.from}" -> "${d.to}". ${d.reason}`
1158
+ );
1159
+ }
1160
+
1161
+ // src/components/presence.ts
1162
+ function resolveSurface(presence, activity) {
1163
+ if (presence === "sheet") return activity.open ? "sheet" : "none";
1164
+ if (activity.awaitingUser) return "sheet";
1165
+ if (activity.busy && (activity.acting || !activity.open)) return "pill";
1166
+ if (activity.hostModalOpen && !activity.explicitlyOpened && (activity.open || activity.unread))
1167
+ return "pill";
1168
+ if (activity.open) return "sheet";
1169
+ if (activity.unread) return "pill";
1170
+ return "none";
1171
+ }
1172
+ function shouldReopenOnTurnEnd(presence) {
1173
+ return presence === "pill";
1174
+ }
1175
+ function usesPill(presence) {
1176
+ return presence !== "sheet";
1177
+ }
460
1178
  function HeadsetIcon({ size = 18, color = "#ffffff" }) {
461
- return /* @__PURE__ */ React7.createElement(Svg, { width: size, height: size, viewBox: "0 0 24 24", fill: "none" }, /* @__PURE__ */ React7.createElement(
1179
+ return /* @__PURE__ */ React8.createElement(Svg, { width: size, height: size, viewBox: "0 0 24 24", fill: "none" }, /* @__PURE__ */ React8.createElement(
462
1180
  Path,
463
1181
  {
464
1182
  d: "M12 3a8 8 0 0 0-8 8v5.5A2.5 2.5 0 0 0 6.5 19H8a1 1 0 0 0 1-1v-5a1 1 0 0 0-1-1H6v-1a6 6 0 0 1 12 0v1h-2a1 1 0 0 0-1 1v5a1 1 0 0 0 1 1h1.5a2.5 2.5 0 0 0 2.5-2.5V11a8 8 0 0 0-8-8z",
@@ -499,195 +1217,6 @@ function buildChatTimeline(messages, actions) {
499
1217
  }
500
1218
  return timeline;
501
1219
  }
502
-
503
- // src/components/markdownContent.ts
504
- var ORDERED_LINE = /^\s*(\d+)\.\s+(.+)$/;
505
- var BULLET_LINE = /^\s*[-*•]\s+(.+)$/;
506
- var HEADING_LINE = /^\s*(#{1,6})\s+(.+)$/;
507
- var FENCE_LINE = /^\s*```\s*([A-Za-z0-9+#_-]*)\s*$/;
508
- var INLINE_PATTERN = /\[([^\]\n]+)\]\((https?:\/\/[^\s)]+)\)|\*\*(.+?)\*\*|__(.+?)__|`([^`]+)`|\*(.+?)\*|_(.+?)_/g;
509
- var MIN_ENHANCE_LENGTH = 80;
510
- var SENTENCE_SPLIT = /[^.!?]+[.!?]+(?=\s+[A-ZÁÉÍÓÚÀÈÌÒÙÂÊÎÔÛÃÕÇÑ¿¡"“]|$)|[^.!?]+$/g;
511
- var CAPABILITY_SENTENCE = /^((?:No aplicativo,?|In (?:this|the) app,?|En la aplicaci[oó]n,?|Dans l['’]application,?)?(?:\s+(?:voc[eê]|voce|you|tu|usted|puedes|puede|peux))?\s*(?:pode(?:m|r)?|can|could|puedes|puede|peux|peut))\s+(.+?)\.?\s*$/i;
512
- var INCLUDE_LIST_SENTENCE = /^(.+?)\s+(?:incluem|inclui|include|includes|incluye|incluyen|such as|for example|par exemple|por exemplo)\s+(.+?)\.?\s*$/i;
513
- function looksLikeMarkdown(content) {
514
- if (!content) return false;
515
- return /\*\*.+?\*\*/.test(content) || /__.+?__/.test(content) || /`[^`\n]+`/.test(content) || /^\s*[-*•]\s+/m.test(content) || /^\s*\d+\.\s+/m.test(content) || /^\s*#{1,6}\s+/m.test(content) || /^\s*```/m.test(content) || /\[[^\]\n]+\]\(https?:\/\/[^\s)]+\)/.test(content);
516
- }
517
- function splitEnumeration(text) {
518
- const trimmed = text.trim().replace(/\.\s*$/, "");
519
- if (!trimmed) return [];
520
- const commaParts = trimmed.split(",").map((part) => part.trim()).filter(Boolean);
521
- if (commaParts.length <= 1) {
522
- const conjParts = trimmed.split(/\s+(?:e|and|y|ou|or)\s+/i).map((part) => part.trim());
523
- return conjParts.filter(Boolean);
524
- }
525
- const last = commaParts[commaParts.length - 1];
526
- const conjMatch = last.match(/^(.+?)\s+(?:e|and|y|ou|or)\s+(.+)$/i);
527
- if (conjMatch) {
528
- commaParts[commaParts.length - 1] = conjMatch[1].trim();
529
- commaParts.push(conjMatch[2].trim());
530
- }
531
- return commaParts.filter(Boolean);
532
- }
533
- function splitSentences(text) {
534
- const matches = text.match(SENTENCE_SPLIT);
535
- if (!matches) return [text.trim()];
536
- return matches.map((part) => part.trim()).filter(Boolean);
537
- }
538
- function formatCapabilitySentence(sentence) {
539
- const match = CAPABILITY_SENTENCE.exec(sentence.trim());
540
- if (!match) return null;
541
- const items = splitEnumeration(match[2] ?? "");
542
- if (items.length < 3) return null;
543
- const intro = `${match[1].trim()}:`;
544
- return `${intro}
545
- ${items.map((item) => `- ${item}`).join("\n")}`;
546
- }
547
- function formatIncludeListSentence(sentence) {
548
- const match = INCLUDE_LIST_SENTENCE.exec(sentence.trim());
549
- if (!match) return null;
550
- const intro = match[1].trim().replace(/[,:]\s*$/, "");
551
- const items = splitEnumeration(match[2] ?? "");
552
- if (items.length < 2) return null;
553
- return `${intro}:
554
- ${items.map((item) => `- ${item}`).join("\n")}`;
555
- }
556
- function formatReadableSentence(sentence) {
557
- return formatCapabilitySentence(sentence) ?? formatIncludeListSentence(sentence) ?? sentence.trim();
558
- }
559
- function enhancePlainAssistantContent(content) {
560
- if (!content?.trim() || looksLikeMarkdown(content)) return content;
561
- const trimmed = content.trim();
562
- if (trimmed.length < MIN_ENHANCE_LENGTH) return content;
563
- const sentences = splitSentences(trimmed);
564
- if (sentences.length === 0) return content;
565
- const blocks = sentences.map(formatReadableSentence);
566
- const enhanced = blocks.join("\n\n");
567
- return enhanced || content;
568
- }
569
- function needsWideChatLayout(content) {
570
- if (!content?.trim()) return false;
571
- const prepared = enhancePlainAssistantContent(content);
572
- return looksLikeMarkdown(prepared) || /\n\n+/.test(prepared);
573
- }
574
- function parseMarkdownBlocks(content) {
575
- const lines = content.replace(/\r\n/g, "\n").split("\n");
576
- const blocks = [];
577
- let paragraph = [];
578
- let bulletItems = [];
579
- let orderedItems = [];
580
- let codeLines = null;
581
- let codeLanguage;
582
- const flushParagraph = () => {
583
- if (paragraph.length === 0) return;
584
- blocks.push({ kind: "paragraph", text: paragraph.join("\n") });
585
- paragraph = [];
586
- };
587
- const flushBullets = () => {
588
- if (bulletItems.length === 0) return;
589
- blocks.push({ kind: "bullet", items: bulletItems });
590
- bulletItems = [];
591
- };
592
- const flushOrdered = () => {
593
- if (orderedItems.length === 0) return;
594
- blocks.push({ kind: "ordered", items: orderedItems });
595
- orderedItems = [];
596
- };
597
- const flushLists = () => {
598
- flushBullets();
599
- flushOrdered();
600
- };
601
- const flushCode = () => {
602
- if (codeLines === null) return;
603
- blocks.push({
604
- kind: "codeblock",
605
- code: codeLines.join("\n"),
606
- ...codeLanguage ? { language: codeLanguage } : {}
607
- });
608
- codeLines = null;
609
- codeLanguage = void 0;
610
- };
611
- for (const line of lines) {
612
- if (codeLines !== null) {
613
- if (/^\s*```\s*$/.test(line)) {
614
- flushCode();
615
- } else {
616
- codeLines.push(line);
617
- }
618
- continue;
619
- }
620
- const fence = FENCE_LINE.exec(line);
621
- if (fence) {
622
- flushParagraph();
623
- flushLists();
624
- codeLines = [];
625
- codeLanguage = fence[1] || void 0;
626
- continue;
627
- }
628
- const trimmed = line.trim();
629
- if (!trimmed) {
630
- flushParagraph();
631
- flushLists();
632
- continue;
633
- }
634
- const heading = HEADING_LINE.exec(line);
635
- if (heading) {
636
- flushParagraph();
637
- flushLists();
638
- const level = Math.min(heading[1].length, 3);
639
- blocks.push({ kind: "heading", level, text: heading[2].trim() });
640
- continue;
641
- }
642
- const ordered = ORDERED_LINE.exec(line);
643
- if (ordered) {
644
- flushParagraph();
645
- flushBullets();
646
- orderedItems.push(ordered[2].trim());
647
- continue;
648
- }
649
- const bullet = BULLET_LINE.exec(line);
650
- if (bullet) {
651
- flushParagraph();
652
- flushOrdered();
653
- bulletItems.push(bullet[1].trim());
654
- continue;
655
- }
656
- flushLists();
657
- paragraph.push(line);
658
- }
659
- flushParagraph();
660
- flushLists();
661
- flushCode();
662
- return blocks.length > 0 ? blocks : [{ kind: "paragraph", text: content }];
663
- }
664
- function parseInlineMarkdown(text) {
665
- if (!text) return [{ kind: "text", value: "" }];
666
- const segments = [];
667
- let lastIndex = 0;
668
- INLINE_PATTERN.lastIndex = 0;
669
- for (let match = INLINE_PATTERN.exec(text); match; match = INLINE_PATTERN.exec(text)) {
670
- if (match.index > lastIndex) {
671
- segments.push({ kind: "text", value: text.slice(lastIndex, match.index) });
672
- }
673
- if (match[1] && match[2]) {
674
- segments.push({ kind: "link", value: match[1].trim(), href: match[2] });
675
- } else if (match[3] || match[4]) {
676
- segments.push({ kind: "bold", value: (match[3] ?? match[4]).trim() });
677
- } else if (match[5]) {
678
- segments.push({ kind: "code", value: match[5] });
679
- } else if (match[6] || match[7]) {
680
- segments.push({ kind: "italic", value: (match[6] ?? match[7]).trim() });
681
- }
682
- lastIndex = INLINE_PATTERN.lastIndex;
683
- }
684
- if (lastIndex < text.length) {
685
- segments.push({ kind: "text", value: text.slice(lastIndex) });
686
- }
687
- return segments.length > 0 ? segments : [{ kind: "text", value: text }];
688
- }
689
-
690
- // src/components/ChatMarkdown.tsx
691
1220
  function segmentKey(segment, index) {
692
1221
  return `${segment.kind}-${index}-${segment.value.slice(0, 12)}`;
693
1222
  }
@@ -697,17 +1226,17 @@ function renderInlineSegments(text, theme, baseStyle) {
697
1226
  const key = segmentKey(segment, index);
698
1227
  switch (segment.kind) {
699
1228
  case "bold":
700
- return /* @__PURE__ */ React7__default.createElement(Text, { key, style: [baseStyle, styles2.bold] }, segment.value);
1229
+ return /* @__PURE__ */ React8__default.createElement(Text, { key, style: [baseStyle, styles4.bold] }, segment.value);
701
1230
  case "italic":
702
- return /* @__PURE__ */ React7__default.createElement(Text, { key, style: [baseStyle, styles2.italic] }, segment.value);
1231
+ return /* @__PURE__ */ React8__default.createElement(Text, { key, style: [baseStyle, styles4.italic] }, segment.value);
703
1232
  case "code":
704
- return /* @__PURE__ */ React7__default.createElement(
1233
+ return /* @__PURE__ */ React8__default.createElement(
705
1234
  Text,
706
1235
  {
707
1236
  key,
708
1237
  style: [
709
1238
  baseStyle,
710
- styles2.code,
1239
+ styles4.code,
711
1240
  {
712
1241
  backgroundColor: theme.codeBackground ?? (theme.variant === "user" ? "rgba(255,255,255,0.22)" : "rgba(0,0,0,0.06)"),
713
1242
  color: theme.codeColor ?? theme.color
@@ -717,14 +1246,14 @@ function renderInlineSegments(text, theme, baseStyle) {
717
1246
  segment.value
718
1247
  );
719
1248
  case "link":
720
- return /* @__PURE__ */ React7__default.createElement(
1249
+ return /* @__PURE__ */ React8__default.createElement(
721
1250
  Text,
722
1251
  {
723
1252
  key,
724
1253
  accessibilityRole: "link",
725
1254
  style: [
726
1255
  baseStyle,
727
- styles2.link,
1256
+ styles4.link,
728
1257
  {
729
1258
  color: theme.linkColor ?? (theme.variant === "user" ? theme.color : "#2563EB")
730
1259
  }
@@ -751,7 +1280,7 @@ function InlineMarkdown({
751
1280
  fontSize: theme.fontSize,
752
1281
  lineHeight: theme.lineHeight
753
1282
  };
754
- return /* @__PURE__ */ React7__default.createElement(Text, { style: baseStyle }, renderInlineSegments(text, theme, baseStyle));
1283
+ return /* @__PURE__ */ React8__default.createElement(Text, { style: baseStyle }, renderInlineSegments(text, theme, baseStyle));
755
1284
  }
756
1285
  function ChatMarkdown({ content, theme, contentWidth }) {
757
1286
  const prepared = enhancePlainAssistantContent(content);
@@ -761,29 +1290,29 @@ function ChatMarkdown({ content, theme, contentWidth }) {
761
1290
  fontSize: theme.fontSize,
762
1291
  lineHeight: theme.lineHeight
763
1292
  };
764
- const rootStyle = contentWidth ? [styles2.root, { width: contentWidth }] : styles2.root;
1293
+ const rootStyle = contentWidth ? [styles4.root, { width: contentWidth }] : styles4.root;
765
1294
  if (!looksLikeMarkdown(prepared)) {
766
1295
  const paragraphs = prepared.split(/\n\n+/).map((part) => part.trim()).filter(Boolean);
767
1296
  if (paragraphs.length > 1) {
768
- return /* @__PURE__ */ React7__default.createElement(View, { style: rootStyle }, paragraphs.map((paragraph, index) => /* @__PURE__ */ React7__default.createElement(View, { key: `plain-p-${index}`, style: index > 0 ? styles2.blockGap : void 0 }, /* @__PURE__ */ React7__default.createElement(Text, { style: baseTextStyle }, paragraph))));
1297
+ return /* @__PURE__ */ React8__default.createElement(View, { style: rootStyle }, paragraphs.map((paragraph, index) => /* @__PURE__ */ React8__default.createElement(View, { key: `plain-p-${index}`, style: index > 0 ? styles4.blockGap : void 0 }, /* @__PURE__ */ React8__default.createElement(Text, { style: baseTextStyle }, paragraph))));
769
1298
  }
770
- return /* @__PURE__ */ React7__default.createElement(Text, { style: baseTextStyle }, prepared);
1299
+ return /* @__PURE__ */ React8__default.createElement(Text, { style: baseTextStyle }, prepared);
771
1300
  }
772
1301
  const blocks = parseMarkdownBlocks(prepared);
773
- return /* @__PURE__ */ React7__default.createElement(View, { style: rootStyle }, blocks.map((block, blockIndex) => {
1302
+ return /* @__PURE__ */ React8__default.createElement(View, { style: rootStyle }, blocks.map((block, blockIndex) => {
774
1303
  if (block.kind === "paragraph") {
775
- return /* @__PURE__ */ React7__default.createElement(View, { key: `p-${blockIndex}`, style: blockIndex > 0 ? styles2.blockGap : void 0 }, /* @__PURE__ */ React7__default.createElement(InlineMarkdown, { text: block.text, theme }));
1304
+ return /* @__PURE__ */ React8__default.createElement(View, { key: `p-${blockIndex}`, style: blockIndex > 0 ? styles4.blockGap : void 0 }, /* @__PURE__ */ React8__default.createElement(InlineMarkdown, { text: block.text, theme }));
776
1305
  }
777
1306
  if (block.kind === "heading") {
778
1307
  const scale = block.level === 1 ? 1.25 : block.level === 2 ? 1.15 : 1.05;
779
1308
  const headingSize = Math.round((theme.fontSize ?? 15) * scale);
780
- return /* @__PURE__ */ React7__default.createElement(View, { key: `h-${blockIndex}`, style: blockIndex > 0 ? styles2.blockGap : void 0 }, /* @__PURE__ */ React7__default.createElement(
1309
+ return /* @__PURE__ */ React8__default.createElement(View, { key: `h-${blockIndex}`, style: blockIndex > 0 ? styles4.blockGap : void 0 }, /* @__PURE__ */ React8__default.createElement(
781
1310
  Text,
782
1311
  {
783
1312
  accessibilityRole: "header",
784
1313
  style: [
785
1314
  baseTextStyle,
786
- styles2.bold,
1315
+ styles4.bold,
787
1316
  { fontSize: headingSize, lineHeight: Math.round(headingSize * 1.35) }
788
1317
  ]
789
1318
  },
@@ -795,23 +1324,23 @@ function ChatMarkdown({ content, theme, contentWidth }) {
795
1324
  ));
796
1325
  }
797
1326
  if (block.kind === "codeblock") {
798
- return /* @__PURE__ */ React7__default.createElement(
1327
+ return /* @__PURE__ */ React8__default.createElement(
799
1328
  View,
800
1329
  {
801
1330
  key: `code-${blockIndex}`,
802
1331
  style: [
803
- styles2.codeBlock,
804
- blockIndex > 0 ? styles2.blockGap : void 0,
1332
+ styles4.codeBlock,
1333
+ blockIndex > 0 ? styles4.blockGap : void 0,
805
1334
  {
806
1335
  backgroundColor: theme.codeBackground ?? (theme.variant === "user" ? "rgba(255,255,255,0.18)" : "rgba(0,0,0,0.055)")
807
1336
  }
808
1337
  ]
809
1338
  },
810
- /* @__PURE__ */ React7__default.createElement(
1339
+ /* @__PURE__ */ React8__default.createElement(
811
1340
  Text,
812
1341
  {
813
1342
  style: [
814
- styles2.code,
1343
+ styles4.code,
815
1344
  {
816
1345
  color: theme.codeColor ?? theme.color,
817
1346
  fontSize: (theme.fontSize ?? 15) - 1,
@@ -824,28 +1353,28 @@ function ChatMarkdown({ content, theme, contentWidth }) {
824
1353
  );
825
1354
  }
826
1355
  if (block.kind === "bullet") {
827
- return /* @__PURE__ */ React7__default.createElement(View, { key: `ul-${blockIndex}`, style: blockIndex > 0 ? styles2.blockGap : void 0 }, block.items.map((item, itemIndex) => /* @__PURE__ */ React7__default.createElement(
1356
+ return /* @__PURE__ */ React8__default.createElement(View, { key: `ul-${blockIndex}`, style: blockIndex > 0 ? styles4.blockGap : void 0 }, block.items.map((item, itemIndex) => /* @__PURE__ */ React8__default.createElement(
828
1357
  Text,
829
1358
  {
830
1359
  key: `ul-${blockIndex}-${itemIndex}`,
831
- style: [baseTextStyle, itemIndex > 0 ? styles2.listItemGap : void 0]
1360
+ style: [baseTextStyle, itemIndex > 0 ? styles4.listItemGap : void 0]
832
1361
  },
833
1362
  "\u2022 ",
834
1363
  renderInlineSegments(item, theme, baseTextStyle)
835
1364
  )));
836
1365
  }
837
- return /* @__PURE__ */ React7__default.createElement(View, { key: `ol-${blockIndex}`, style: blockIndex > 0 ? styles2.blockGap : void 0 }, block.items.map((item, itemIndex) => /* @__PURE__ */ React7__default.createElement(
1366
+ return /* @__PURE__ */ React8__default.createElement(View, { key: `ol-${blockIndex}`, style: blockIndex > 0 ? styles4.blockGap : void 0 }, block.items.map((item, itemIndex) => /* @__PURE__ */ React8__default.createElement(
838
1367
  Text,
839
1368
  {
840
1369
  key: `ol-${blockIndex}-${itemIndex}`,
841
- style: [baseTextStyle, itemIndex > 0 ? styles2.listItemGap : void 0]
1370
+ style: [baseTextStyle, itemIndex > 0 ? styles4.listItemGap : void 0]
842
1371
  },
843
1372
  `${itemIndex + 1}. `,
844
1373
  renderInlineSegments(item, theme, baseTextStyle)
845
1374
  )));
846
1375
  }));
847
1376
  }
848
- var styles2 = StyleSheet.create({
1377
+ var styles4 = StyleSheet.create({
849
1378
  root: {
850
1379
  flexShrink: 1,
851
1380
  alignSelf: "stretch"
@@ -889,7 +1418,7 @@ function AppilotsThemeProvider({
889
1418
  () => mergeThemeTokens(resolveBaseTheme(mode, systemScheme), theme ?? void 0),
890
1419
  [mode, systemScheme, theme]
891
1420
  );
892
- return /* @__PURE__ */ React7__default.createElement(ThemeContext.Provider, { value: tokens }, children);
1421
+ return /* @__PURE__ */ React8__default.createElement(ThemeContext.Provider, { value: tokens }, children);
893
1422
  }
894
1423
  function useAppilotsTheme() {
895
1424
  return useContext(ThemeContext);
@@ -982,177 +1511,6 @@ var appilotsTheme = {
982
1511
  fromTailwind: appilotsThemeFromTailwind
983
1512
  };
984
1513
 
985
- // src/i18n/bundles/pt-BR.ts
986
- var ptBR = {
987
- chatTitle: "Assistente Appilots",
988
- clearButton: "Limpar",
989
- inputPlaceholder: "Digite uma mensagem...",
990
- emptyTitle: "Como posso ajudar?",
991
- emptySubtitle: "Navego, preencho formul\xE1rios e executo a\xE7\xF5es neste app.",
992
- thinking: "Pensando...",
993
- statusAnalyzing: "Analisando a tela...",
994
- statusWaitingApp: "Aguardando o app responder...",
995
- statusAdjusting: "Ajustando o plano...",
996
- poweredBy: "Powered by Appilots",
997
- approve: "Aprovar",
998
- reject: "Rejeitar",
999
- cancelledByUser: "Cancelado pelo usu\xE1rio",
1000
- confirmDestructiveTitle: "A\xE7\xE3o destrutiva",
1001
- confirmTitle: "Confirma\xE7\xE3o necess\xE1ria",
1002
- confirmMessage: 'Tem certeza que deseja "{action}"?',
1003
- confirmDestructiveApprove: "Sim, executar",
1004
- confirmApprove: "Confirmar",
1005
- confirmCancel: "Cancelar",
1006
- confirmThisAction: "esta a\xE7\xE3o",
1007
- unknownAction: "A\xE7\xE3o desconhecida",
1008
- somethingWentWrong: "Algo deu errado",
1009
- openChatA11y: "Abrir chat do assistente",
1010
- closeChatA11y: "Fechar chat do assistente",
1011
- stopGeneratingA11y: "Parar gera\xE7\xE3o",
1012
- talkToHumanA11y: "Falar com um atendente humano",
1013
- humanAgentLabel: "Atendente",
1014
- escalationRequested: "Certo! Chamei um atendente humano. Voc\xEA pode continuar usando o app normalmente enquanto isso.",
1015
- escalationConnected: "Um atendente entrou na conversa.",
1016
- escalationResolved: "O atendimento humano foi encerrado. Voltei a te ajudar por aqui.",
1017
- escalationOffer: "Se preferir, posso chamar uma pessoa para ajudar.",
1018
- escalationOfferChip: "Falar com um atendente",
1019
- escalationPendingBanner: "Aguardando um atendente...",
1020
- escalationActiveBanner: "Voc\xEA est\xE1 falando com um atendente"
1021
- };
1022
-
1023
- // src/i18n/bundles/en.ts
1024
- var en = {
1025
- chatTitle: "Appilots Assistant",
1026
- clearButton: "Clear",
1027
- inputPlaceholder: "Type a message...",
1028
- emptyTitle: "How can I help?",
1029
- emptySubtitle: "I navigate, fill forms, and run actions in this app.",
1030
- thinking: "Thinking...",
1031
- statusAnalyzing: "Reading the screen...",
1032
- statusWaitingApp: "Waiting for the app...",
1033
- statusAdjusting: "Adjusting the plan...",
1034
- poweredBy: "Powered by Appilots",
1035
- approve: "Approve",
1036
- reject: "Reject",
1037
- cancelledByUser: "Cancelled by user",
1038
- confirmDestructiveTitle: "Destructive action",
1039
- confirmTitle: "Confirmation needed",
1040
- confirmMessage: 'Are you sure you want to "{action}"?',
1041
- confirmDestructiveApprove: "Yes, do it",
1042
- confirmApprove: "Confirm",
1043
- confirmCancel: "Cancel",
1044
- confirmThisAction: "this action",
1045
- unknownAction: "Unknown action",
1046
- somethingWentWrong: "Something went wrong",
1047
- openChatA11y: "Open assistant chat",
1048
- closeChatA11y: "Close assistant chat",
1049
- stopGeneratingA11y: "Stop generating",
1050
- talkToHumanA11y: "Talk to a human agent",
1051
- humanAgentLabel: "Agent",
1052
- escalationRequested: "Done! I've called a human agent. You can keep using the app normally in the meantime.",
1053
- escalationConnected: "A human agent joined the conversation.",
1054
- escalationResolved: "The human handoff has ended. I'm back to help you here.",
1055
- escalationOffer: "If you prefer, I can bring in a person to help.",
1056
- escalationOfferChip: "Talk to a human",
1057
- escalationPendingBanner: "Waiting for a human agent...",
1058
- escalationActiveBanner: "You're talking to a human agent"
1059
- };
1060
-
1061
- // src/i18n/bundles/es.ts
1062
- var es = {
1063
- chatTitle: "Asistente Appilots",
1064
- clearButton: "Limpiar",
1065
- inputPlaceholder: "Escribe un mensaje...",
1066
- emptyTitle: "\xBFEn qu\xE9 puedo ayudarte?",
1067
- emptySubtitle: "Navego, relleno formularios y ejecuto acciones en esta app.",
1068
- thinking: "Pensando...",
1069
- statusAnalyzing: "Analizando la pantalla...",
1070
- statusWaitingApp: "Esperando a la app...",
1071
- statusAdjusting: "Ajustando el plan...",
1072
- poweredBy: "Hecho con Appilots",
1073
- approve: "Aprobar",
1074
- reject: "Rechazar",
1075
- cancelledByUser: "Cancelado por el usuario",
1076
- confirmDestructiveTitle: "Acci\xF3n destructiva",
1077
- confirmTitle: "Confirmaci\xF3n necesaria",
1078
- confirmMessage: '\xBFSeguro que quieres "{action}"?',
1079
- confirmDestructiveApprove: "S\xED, ejecutar",
1080
- confirmApprove: "Confirmar",
1081
- confirmCancel: "Cancelar",
1082
- confirmThisAction: "esta acci\xF3n",
1083
- unknownAction: "Acci\xF3n desconocida",
1084
- somethingWentWrong: "Algo sali\xF3 mal",
1085
- openChatA11y: "Abrir chat del asistente",
1086
- closeChatA11y: "Cerrar chat del asistente",
1087
- stopGeneratingA11y: "Detener generaci\xF3n",
1088
- talkToHumanA11y: "Hablar con un agente humano",
1089
- humanAgentLabel: "Agente",
1090
- escalationRequested: "\xA1Listo! Llam\xE9 a un agente humano. Mientras tanto puedes seguir usando la app con normalidad.",
1091
- escalationConnected: "Un agente humano se uni\xF3 a la conversaci\xF3n.",
1092
- escalationResolved: "La atenci\xF3n humana termin\xF3. Vuelvo a ayudarte por aqu\xED.",
1093
- escalationOffer: "Si prefieres, puedo llamar a una persona para ayudarte.",
1094
- escalationOfferChip: "Hablar con un agente",
1095
- escalationPendingBanner: "Esperando a un agente...",
1096
- escalationActiveBanner: "Est\xE1s hablando con un agente humano"
1097
- };
1098
- var BUNDLES = {
1099
- "pt-BR": ptBR,
1100
- en,
1101
- es
1102
- };
1103
- var DEFAULT_LOCALE = "en";
1104
- function detectDeviceLocale() {
1105
- try {
1106
- let raw;
1107
- if (Platform.OS === "ios") {
1108
- const settings = NativeModules?.SettingsManager?.settings;
1109
- raw = settings?.AppleLocale || (Array.isArray(settings?.AppleLanguages) ? settings?.AppleLanguages?.[0] : void 0);
1110
- } else if (Platform.OS === "android") {
1111
- raw = NativeModules?.I18nManager?.localeIdentifier;
1112
- }
1113
- if (!raw || typeof raw !== "string") return null;
1114
- const lower = raw.toLowerCase().replace("_", "-");
1115
- if (lower.startsWith("pt")) return "pt-BR";
1116
- if (lower.startsWith("es")) return "es";
1117
- if (lower.startsWith("en")) return "en";
1118
- return null;
1119
- } catch {
1120
- return null;
1121
- }
1122
- }
1123
- function resolveLocale(prop) {
1124
- if (prop && BUNDLES[prop]) return prop;
1125
- const detected = detectDeviceLocale();
1126
- if (detected && BUNDLES[detected]) return detected;
1127
- return DEFAULT_LOCALE;
1128
- }
1129
- var I18nContext = createContext(null);
1130
- function AppilotsI18nProvider({
1131
- locale,
1132
- overrides,
1133
- children
1134
- }) {
1135
- const value = useMemo(() => {
1136
- const resolved = resolveLocale(locale ?? null);
1137
- const merged = overrides ? { ...BUNDLES[resolved], ...overrides } : BUNDLES[resolved];
1138
- return {
1139
- locale: resolved,
1140
- strings: merged,
1141
- t: (key) => merged[key]
1142
- };
1143
- }, [locale, overrides]);
1144
- return /* @__PURE__ */ React7__default.createElement(I18nContext.Provider, { value }, children);
1145
- }
1146
- function useAppilotsI18n() {
1147
- const ctx = useContext(I18nContext);
1148
- if (ctx) return ctx;
1149
- return {
1150
- locale: DEFAULT_LOCALE,
1151
- strings: BUNDLES[DEFAULT_LOCALE],
1152
- t: (key) => BUNDLES[DEFAULT_LOCALE][key]
1153
- };
1154
- }
1155
-
1156
1514
  // src/components/AppilotsChat.tsx
1157
1515
  function translateLegacyTheme(legacy) {
1158
1516
  const out = {};
@@ -1180,34 +1538,42 @@ function isImageSource(value) {
1180
1538
  return typeof value === "number" || /^https?:\/\//i.test(value.trim());
1181
1539
  }
1182
1540
  var { height: SCREEN_HEIGHT, width: SCREEN_WIDTH } = Dimensions.get("window");
1541
+ var A11Y_FOCUS_DELAY_MS = 500;
1183
1542
  var CHAT_HORIZONTAL_PADDING = 16;
1184
1543
  var BUBBLE_HORIZONTAL_PADDING = 14;
1185
1544
  var ASSISTANT_CONTENT_WIDTH = SCREEN_WIDTH - CHAT_HORIZONTAL_PADDING * 2 - BUBBLE_HORIZONTAL_PADDING * 2;
1186
- var APPILOTS_SKIP_PROPS = {
1187
- __appilotsSkip: true,
1188
- __appilotsInternal: true
1189
- };
1545
+ var MIN_TOUCH_TARGET2 = 44;
1546
+ var LARGE_TEXT_FONT_SCALE = 1.6;
1547
+ var INPUT_MAX_HEIGHT = 120;
1190
1548
  function AppilotsChat(props) {
1191
1549
  const { degraded } = useAppilotsContext();
1192
1550
  if (degraded) return null;
1193
- return /* @__PURE__ */ React7__default.createElement(AppilotsErrorBoundary, { surface: "chat", fallback: null }, /* @__PURE__ */ React7__default.createElement(AppilotsChatSurface, { ...props }));
1551
+ return /* @__PURE__ */ React8__default.createElement(AppilotsErrorBoundary, { surface: "chat", fallback: null }, /* @__PURE__ */ React8__default.createElement(AppilotsChatSurface, { ...props }));
1194
1552
  }
1195
1553
  function AppilotsChatSurface(props) {
1554
+ const [conversationLocale, setConversationLocale] = useState(null);
1196
1555
  const { remotePersonalization } = useAppilotsContext();
1197
1556
  const themeProp = useMemo(() => {
1198
1557
  if (!props.theme) return void 0;
1199
1558
  return isLegacyTheme(props.theme) ? translateLegacyTheme(props.theme) : props.theme;
1200
1559
  }, [props.theme]);
1201
- return /* @__PURE__ */ React7__default.createElement(
1560
+ return /* @__PURE__ */ React8__default.createElement(
1202
1561
  AppilotsThemeProvider,
1203
1562
  {
1204
1563
  theme: resolveTheme(themeProp, remotePersonalization),
1205
1564
  mode: resolveThemeMode(props.themeMode, remotePersonalization)
1206
1565
  },
1207
- /* @__PURE__ */ React7__default.createElement(AppilotsI18nProvider, { locale: resolveLocaleProp(props.locale, remotePersonalization) }, /* @__PURE__ */ React7__default.createElement(AppilotsChatInner, { ...props }))
1566
+ /* @__PURE__ */ React8__default.createElement(
1567
+ AppilotsI18nProvider,
1568
+ {
1569
+ locale: conversationLocale ?? resolveLocaleProp(props.locale, remotePersonalization)
1570
+ },
1571
+ /* @__PURE__ */ React8__default.createElement(AppilotsChatInner, { ...props, onReplyLocaleChange: setConversationLocale })
1572
+ )
1208
1573
  );
1209
1574
  }
1210
1575
  function AppilotsChatInner({
1576
+ onReplyLocaleChange,
1211
1577
  visible: controlledVisible,
1212
1578
  onClose,
1213
1579
  placeholder,
@@ -1223,14 +1589,22 @@ function AppilotsChatInner({
1223
1589
  triggerButton = "default",
1224
1590
  triggerIcon,
1225
1591
  triggerButtonColor: triggerButtonColorProp,
1226
- triggerButtonImage: triggerButtonImageProp
1592
+ triggerButtonImage: triggerButtonImageProp,
1593
+ experience: experienceProp,
1594
+ speech: speechProp
1227
1595
  }) {
1228
1596
  const theme = useAppilotsTheme();
1229
- const { strings, t } = useAppilotsI18n();
1597
+ const { strings, t, locale: resolvedLocale } = useAppilotsI18n();
1230
1598
  const {
1231
1599
  messages,
1600
+ mission,
1601
+ pauseMission,
1602
+ resumeMission,
1603
+ cancelMission,
1604
+ replyLocale,
1232
1605
  isLoading,
1233
1606
  loadingStatusKey,
1607
+ hasStartedActing,
1234
1608
  sendMessage,
1235
1609
  cancelMessage,
1236
1610
  clearMessages,
@@ -1245,6 +1619,9 @@ function AppilotsChatInner({
1245
1619
  offer: t("escalationOffer")
1246
1620
  }
1247
1621
  });
1622
+ useEffect(() => {
1623
+ onReplyLocaleChange(replyLocale);
1624
+ }, [replyLocale, onReplyLocaleChange]);
1248
1625
  const { pendingActions, executingActions, completedActions, approveAction, rejectAction } = useAppilotsActions({ autoExecute });
1249
1626
  const { emit, remotePersonalization } = useAppilotsContext();
1250
1627
  const {
@@ -1270,18 +1647,87 @@ function AppilotsChatInner({
1270
1647
  );
1271
1648
  const allActions = [...pendingActions, ...executingActions, ...completedActions];
1272
1649
  const timeline = buildChatTimeline(messages, allActions);
1273
- const lastAssistantMessageId = React7__default.useMemo(() => {
1650
+ const lastAssistantMessageId = React8__default.useMemo(() => {
1274
1651
  for (let i = messages.length - 1; i >= 0; i--) {
1275
1652
  if (messages[i].role === "assistant") return messages[i].id;
1276
1653
  }
1277
1654
  return void 0;
1278
1655
  }, [messages]);
1279
1656
  const insets = useSafeAreaInsetsOrDefault();
1657
+ const { fontScale } = useWindowDimensions();
1280
1658
  const [internalVisible, setInternalVisible] = useState(mode === "inline");
1281
1659
  const [inputText, setInputText] = useState("");
1282
1660
  const flatListRef = useRef(null);
1283
1661
  const slideAnim = useRef(new Animated.Value(SCREEN_HEIGHT)).current;
1284
- const isVisible = mode === "inline" ? true : controlledVisible !== void 0 ? controlledVisible : internalVisible;
1662
+ const wantsSheet = mode === "inline" ? true : controlledVisible !== void 0 ? controlledVisible : internalVisible;
1663
+ const speechHost = isSpeechHost(speechProp) ? speechProp : null;
1664
+ const hasGeometry = useSyncExternalStore(
1665
+ subscribeGeometryHost,
1666
+ () => geometryAvailability() !== "unavailable",
1667
+ () => false
1668
+ );
1669
+ const capabilities = useMemo(
1670
+ () => ({ geometry: hasGeometry, speech: speechHost !== null }),
1671
+ [hasGeometry, speechHost]
1672
+ );
1673
+ const { experience, downgrades } = useMemo(
1674
+ () => resolveExperience(experienceProp, capabilities),
1675
+ [experienceProp, capabilities]
1676
+ );
1677
+ const presence = mode === "inline" ? "sheet" : experience.presence;
1678
+ useEffect(() => {
1679
+ if (!__DEV__ || downgrades.length === 0) return;
1680
+ if (!isGeometryResolved()) return;
1681
+ for (const line of formatDowngrades(downgrades)) {
1682
+ console.warn(line);
1683
+ }
1684
+ }, [downgrades, hasGeometry]);
1685
+ const [unreadResult, setUnreadResult] = useState(false);
1686
+ const [explicitlyOpened, setExplicitlyOpened] = useState(false);
1687
+ const requireApproval = useCallback(
1688
+ (action) => {
1689
+ if (action.status !== "pending") return false;
1690
+ if (action.type === "confirm") return true;
1691
+ return !autoExecute;
1692
+ },
1693
+ [autoExecute]
1694
+ );
1695
+ const awaitingApproval = pendingActions.some((action) => requireApproval(action));
1696
+ const awaitingUser = awaitingApproval || escalation?.status === "pending" || escalation?.status === "active";
1697
+ const busy = isLoading || executingActions.length > 0;
1698
+ const stillWorkingSeconds = useStillWorking(busy && !awaitingUser, strings.stillWorking);
1699
+ const acting = busy && hasStartedActing;
1700
+ useChatAnnouncements({
1701
+ messages,
1702
+ actions: allActions,
1703
+ awaitingApproval,
1704
+ acting,
1705
+ locale: resolvedLocale,
1706
+ strings
1707
+ });
1708
+ const hostModalOpen = useHostModalOpen(
1709
+ usesPill(presence) && (busy || wantsSheet || unreadResult)
1710
+ );
1711
+ const surface = resolveSurface(presence, {
1712
+ busy,
1713
+ acting,
1714
+ awaitingUser,
1715
+ open: wantsSheet,
1716
+ unread: unreadResult,
1717
+ hostModalOpen,
1718
+ explicitlyOpened
1719
+ });
1720
+ const isVisible = mode === "inline" ? true : surface === "sheet";
1721
+ const spotlight = useSpotlight(experience.spotlight !== "off");
1722
+ useEffect(() => {
1723
+ setSpotlightMode(experience.spotlight);
1724
+ return () => setSpotlightMode("off");
1725
+ }, [experience.spotlight]);
1726
+ const { label: pillLabel, state: pillState } = surface === "pill" ? resolvePillStatus(allActions, busy, {
1727
+ working: strings.pillWorking,
1728
+ done: strings.pillDone,
1729
+ locale: resolvedLocale
1730
+ }) : { label: "", state: "pending" };
1285
1731
  const showSuggestedPrompts = shouldShowSuggestedPrompts({
1286
1732
  isVisible,
1287
1733
  messageCount: messages.length,
@@ -1292,14 +1738,38 @@ function AppilotsChatInner({
1292
1738
  limit: 3
1293
1739
  });
1294
1740
  const openChat = useCallback(() => {
1741
+ setExplicitlyOpened(true);
1295
1742
  setInternalVisible(true);
1296
1743
  emit({ type: "chat:open", timestamp: Date.now(), data: {} });
1297
1744
  }, [emit]);
1298
1745
  const closeChat = useCallback(() => {
1746
+ setExplicitlyOpened(false);
1299
1747
  setInternalVisible(false);
1300
1748
  onClose?.();
1301
1749
  emit({ type: "chat:close", timestamp: Date.now(), data: {} });
1302
1750
  }, [onClose, emit]);
1751
+ useEffect(() => {
1752
+ if (acting) setExplicitlyOpened(false);
1753
+ if (presence === "hidden" && acting) setInternalVisible(false);
1754
+ }, [presence, acting]);
1755
+ const wasActingRef = useRef(false);
1756
+ useEffect(() => {
1757
+ if (!usesPill(presence)) {
1758
+ wasActingRef.current = acting;
1759
+ return;
1760
+ }
1761
+ if (acting) {
1762
+ wasActingRef.current = true;
1763
+ return;
1764
+ }
1765
+ if (!wasActingRef.current) return;
1766
+ wasActingRef.current = false;
1767
+ if (shouldReopenOnTurnEnd(presence)) setInternalVisible(true);
1768
+ else setUnreadResult(true);
1769
+ }, [acting, presence]);
1770
+ useEffect(() => {
1771
+ if (surface === "sheet") setUnreadResult(false);
1772
+ }, [surface]);
1303
1773
  useEffect(() => {
1304
1774
  if (mode === "inline") return;
1305
1775
  Animated.spring(slideAnim, {
@@ -1309,11 +1779,30 @@ function AppilotsChatInner({
1309
1779
  friction: 11
1310
1780
  }).start();
1311
1781
  }, [isVisible, slideAnim, mode]);
1782
+ const titleRef = useRef(null);
1783
+ const launcherRef = useRef(null);
1784
+ const pillRef = useRef(null);
1785
+ const wasVisibleRef = useRef(isVisible);
1786
+ useEffect(() => {
1787
+ if (mode === "inline" || wasVisibleRef.current === isVisible) return;
1788
+ wasVisibleRef.current = isVisible;
1789
+ const timer = setTimeout(() => {
1790
+ const target = isVisible ? titleRef.current : pillRef.current ?? launcherRef.current;
1791
+ if (target) AccessibilityInfo.sendAccessibilityEvent(target, "focus");
1792
+ }, A11Y_FOCUS_DELAY_MS);
1793
+ return () => clearTimeout(timer);
1794
+ }, [isVisible, mode]);
1312
1795
  useEffect(() => {
1313
1796
  if (messages.length > 0) {
1314
1797
  setTimeout(() => flatListRef.current?.scrollToEnd({ animated: true }), 100);
1315
1798
  }
1316
1799
  }, [messages.length]);
1800
+ const voiceEnabled = experience.input.includes("voice");
1801
+ const voice = useVoiceInput({
1802
+ host: voiceEnabled ? speechHost : null,
1803
+ locale: resolvedLocale,
1804
+ onTranscript: sendMessage
1805
+ });
1317
1806
  const handleSend = useCallback(() => {
1318
1807
  if (!inputText.trim()) return;
1319
1808
  sendMessage(inputText.trim());
@@ -1323,11 +1812,11 @@ function AppilotsChatInner({
1323
1812
  ({ item }) => {
1324
1813
  if (item.role === "system") {
1325
1814
  const isOffer = item.metadata?.escalationOffer === true;
1326
- return /* @__PURE__ */ React7__default.createElement(View, { style: styles3.systemMessageContainer }, /* @__PURE__ */ React7__default.createElement(
1815
+ return /* @__PURE__ */ React8__default.createElement(View, { style: styles5.systemMessageContainer }, /* @__PURE__ */ React8__default.createElement(
1327
1816
  Text,
1328
1817
  {
1329
1818
  style: [
1330
- styles3.systemMessageText,
1819
+ styles5.systemMessageText,
1331
1820
  {
1332
1821
  color: theme.colors.textSecondary,
1333
1822
  fontFamily: theme.typography.fontFamily,
@@ -1336,13 +1825,13 @@ function AppilotsChatInner({
1336
1825
  ]
1337
1826
  },
1338
1827
  item.content
1339
- ), isOffer && !escalation && /* @__PURE__ */ React7__default.createElement(
1828
+ ), isOffer && !escalation && /* @__PURE__ */ React8__default.createElement(
1340
1829
  TouchableOpacity,
1341
1830
  {
1342
1831
  ...APPILOTS_SKIP_PROPS,
1343
1832
  style: [
1344
- styles3.chip,
1345
- styles3.escalationOfferChip,
1833
+ styles5.chip,
1834
+ styles5.escalationOfferChip,
1346
1835
  {
1347
1836
  backgroundColor: theme.colors.surface,
1348
1837
  borderColor: theme.colors.border,
@@ -1355,12 +1844,12 @@ function AppilotsChatInner({
1355
1844
  accessibilityLabel: strings.talkToHumanA11y,
1356
1845
  testID: "escalation-offer-chip"
1357
1846
  },
1358
- /* @__PURE__ */ React7__default.createElement(HeadsetIcon, { size: 14, color: theme.colors.textSecondary }),
1359
- /* @__PURE__ */ React7__default.createElement(
1847
+ /* @__PURE__ */ React8__default.createElement(HeadsetIcon, { size: 14, color: theme.colors.textSecondary }),
1848
+ /* @__PURE__ */ React8__default.createElement(
1360
1849
  Text,
1361
1850
  {
1362
1851
  style: [
1363
- styles3.chipText,
1852
+ styles5.chipText,
1364
1853
  {
1365
1854
  color: theme.colors.text,
1366
1855
  fontFamily: theme.typography.fontFamily,
@@ -1376,11 +1865,11 @@ function AppilotsChatInner({
1376
1865
  const isHumanAgent = item.role === "human_agent";
1377
1866
  const useWideAssistantLayout = !isUser && needsWideChatLayout(item.content);
1378
1867
  if (isHumanAgent) {
1379
- return /* @__PURE__ */ React7__default.createElement(View, { style: styles3.humanAgentContainer, testID: "human-agent-message" }, /* @__PURE__ */ React7__default.createElement(View, { style: styles3.humanAgentLabelRow }, /* @__PURE__ */ React7__default.createElement(HeadsetIcon, { size: 11, color: theme.colors.secondary }), /* @__PURE__ */ React7__default.createElement(
1868
+ return /* @__PURE__ */ React8__default.createElement(View, { style: styles5.humanAgentContainer, testID: "human-agent-message" }, /* @__PURE__ */ React8__default.createElement(View, { style: styles5.humanAgentLabelRow }, /* @__PURE__ */ React8__default.createElement(HeadsetIcon, { size: 11, color: theme.colors.secondary }), /* @__PURE__ */ React8__default.createElement(
1380
1869
  Text,
1381
1870
  {
1382
1871
  style: [
1383
- styles3.humanAgentLabel,
1872
+ styles5.humanAgentLabel,
1384
1873
  {
1385
1874
  color: theme.colors.textSecondary,
1386
1875
  fontFamily: theme.typography.fontFamily
@@ -1388,17 +1877,17 @@ function AppilotsChatInner({
1388
1877
  ]
1389
1878
  },
1390
1879
  escalation?.operatorName ?? strings.humanAgentLabel
1391
- )), /* @__PURE__ */ React7__default.createElement(
1880
+ )), /* @__PURE__ */ React8__default.createElement(
1392
1881
  View,
1393
1882
  {
1394
1883
  accessibilityLabel: item.content,
1395
1884
  style: [
1396
- styles3.messageBubble,
1397
- styles3.assistantBubble,
1885
+ styles5.messageBubble,
1886
+ styles5.assistantBubble,
1398
1887
  { backgroundColor: theme.colors.surface }
1399
1888
  ]
1400
1889
  },
1401
- /* @__PURE__ */ React7__default.createElement(
1890
+ /* @__PURE__ */ React8__default.createElement(
1402
1891
  ChatMarkdown,
1403
1892
  {
1404
1893
  content: item.content,
@@ -1413,21 +1902,21 @@ function AppilotsChatInner({
1413
1902
  )
1414
1903
  ));
1415
1904
  }
1416
- return /* @__PURE__ */ React7__default.createElement(
1905
+ return /* @__PURE__ */ React8__default.createElement(
1417
1906
  View,
1418
1907
  {
1419
1908
  testID: isUser ? void 0 : item.id === lastAssistantMessageId ? "assistant-last-message" : "assistant-message",
1420
1909
  accessibilityLabel: isUser ? void 0 : item.content,
1421
1910
  style: [
1422
- styles3.messageBubble,
1423
- isUser ? [styles3.userBubble, { backgroundColor: theme.colors.primary }] : [
1424
- styles3.assistantBubble,
1425
- useWideAssistantLayout && styles3.assistantBubbleWide,
1911
+ styles5.messageBubble,
1912
+ isUser ? [styles5.userBubble, { backgroundColor: theme.colors.primary }] : [
1913
+ styles5.assistantBubble,
1914
+ useWideAssistantLayout && styles5.assistantBubbleWide,
1426
1915
  { backgroundColor: theme.colors.surface }
1427
1916
  ]
1428
1917
  ]
1429
1918
  },
1430
- /* @__PURE__ */ React7__default.createElement(
1919
+ /* @__PURE__ */ React8__default.createElement(
1431
1920
  ChatMarkdown,
1432
1921
  {
1433
1922
  content: item.content,
@@ -1445,19 +1934,16 @@ function AppilotsChatInner({
1445
1934
  },
1446
1935
  [theme, lastAssistantMessageId, escalation, requestHuman, strings]
1447
1936
  );
1448
- const requireApproval = useCallback(
1449
- (action) => {
1450
- if (action.status !== "pending") return false;
1451
- if (action.type === "confirm") return true;
1452
- return !autoExecute;
1453
- },
1454
- [autoExecute]
1455
- );
1937
+ const composerRef = useRef(null);
1938
+ const returnFocusToComposer = useCallback(() => {
1939
+ if (composerRef.current) AccessibilityInfo.sendAccessibilityEvent(composerRef.current, "focus");
1940
+ }, []);
1456
1941
  const renderBreadcrumb = useCallback(
1457
- (actions) => /* @__PURE__ */ React7__default.createElement(
1942
+ (actions) => /* @__PURE__ */ React8__default.createElement(
1458
1943
  ActionBreadcrumb,
1459
1944
  {
1460
1945
  actions,
1946
+ locale: resolvedLocale,
1461
1947
  primaryColor: theme.colors.primary,
1462
1948
  textColor: theme.colors.text,
1463
1949
  fontFamily: theme.typography.fontFamily,
@@ -1468,7 +1954,8 @@ function AppilotsChatInner({
1468
1954
  confirmDestructiveApprove: strings.confirmDestructiveApprove,
1469
1955
  confirmApprove: strings.confirmApprove,
1470
1956
  confirmCancel: strings.confirmCancel,
1471
- confirmThisAction: strings.confirmThisAction
1957
+ confirmThisAction: strings.confirmThisAction,
1958
+ confirmExpired: strings.confirmExpired
1472
1959
  },
1473
1960
  palette: {
1474
1961
  textSecondary: theme.colors.textSecondary,
@@ -1480,7 +1967,8 @@ function AppilotsChatInner({
1480
1967
  },
1481
1968
  requireApprovalFor: requireApproval,
1482
1969
  onApprove: approveAction,
1483
- onReject: rejectAction
1970
+ onReject: rejectAction,
1971
+ onConfirmCardGone: returnFocusToComposer
1484
1972
  }
1485
1973
  ),
1486
1974
  [
@@ -1493,9 +1981,12 @@ function AppilotsChatInner({
1493
1981
  theme.colors.warning,
1494
1982
  theme.colors.error,
1495
1983
  theme.typography.fontFamily,
1984
+ strings,
1985
+ resolvedLocale,
1496
1986
  requireApproval,
1497
1987
  approveAction,
1498
- rejectAction
1988
+ rejectAction,
1989
+ returnFocusToComposer
1499
1990
  ]
1500
1991
  );
1501
1992
  const resolvedHeaderTitle = resolvedTitle ?? t("chatTitle");
@@ -1505,17 +1996,17 @@ function AppilotsChatInner({
1505
1996
  const showEmptySubtitle = resolvedEmptySubtitle.length > 0 && !(showSuggestedPrompts && suggestedPrompts.length > 0);
1506
1997
  const renderAvatar = () => {
1507
1998
  if (!assistantAvatar) {
1508
- return /* @__PURE__ */ React7__default.createElement(View, { style: [styles3.headerDot, { backgroundColor: theme.colors.primary }] });
1999
+ return /* @__PURE__ */ React8__default.createElement(View, { style: [styles5.headerDot, { backgroundColor: theme.colors.primary }] });
1509
2000
  }
1510
2001
  if (typeof assistantAvatar === "string") {
1511
- return /* @__PURE__ */ React7__default.createElement(Image, { source: { uri: assistantAvatar }, style: styles3.headerAvatar });
2002
+ return /* @__PURE__ */ React8__default.createElement(Image, { source: { uri: assistantAvatar }, style: styles5.headerAvatar });
1512
2003
  }
1513
2004
  if (typeof assistantAvatar === "number") {
1514
- return /* @__PURE__ */ React7__default.createElement(Image, { source: assistantAvatar, style: styles3.headerAvatar });
2005
+ return /* @__PURE__ */ React8__default.createElement(Image, { source: assistantAvatar, style: styles5.headerAvatar });
1515
2006
  }
1516
- return /* @__PURE__ */ React7__default.createElement(View, { style: styles3.headerAvatarSlot }, assistantAvatar);
2007
+ return /* @__PURE__ */ React8__default.createElement(View, { style: styles5.headerAvatarSlot }, assistantAvatar);
1517
2008
  };
1518
- const showTrigger = mode === "bubble" && !isVisible && controlledVisible === void 0 && triggerButton !== "none";
2009
+ const showTrigger = mode === "bubble" && surface === "none" && experience.entry.includes("fab") && controlledVisible === void 0 && triggerButton !== "none";
1519
2010
  const fabPositionStyle = (() => {
1520
2011
  switch (position) {
1521
2012
  case "bottom-left":
@@ -1529,15 +2020,16 @@ function AppilotsChatInner({
1529
2020
  return { bottom: 24, right: 24 };
1530
2021
  }
1531
2022
  })();
1532
- const chatSurface = /* @__PURE__ */ React7__default.createElement(
2023
+ const chatSurface = /* @__PURE__ */ React8__default.createElement(
1533
2024
  View,
1534
2025
  {
1535
2026
  ...APPILOTS_SKIP_PROPS,
1536
2027
  style: [
1537
- styles3.chatContainer,
1538
- mode === "fullscreen" && styles3.chatContainerFullscreen,
1539
- mode === "sidebar" && styles3.chatContainerSidebar,
1540
- mode === "inline" && styles3.chatContainerInline,
2028
+ styles5.chatContainer,
2029
+ mode === "fullscreen" && styles5.chatContainerFullscreen,
2030
+ mode === "sidebar" && styles5.chatContainerSidebar,
2031
+ mode === "inline" && styles5.chatContainerInline,
2032
+ mode === "bubble" && fontScale >= LARGE_TEXT_FONT_SCALE && styles5.chatContainerFullscreen,
1541
2033
  {
1542
2034
  backgroundColor: theme.colors.background,
1543
2035
  borderTopLeftRadius: theme.radii.lg,
@@ -1545,11 +2037,13 @@ function AppilotsChatInner({
1545
2037
  }
1546
2038
  ]
1547
2039
  },
1548
- /* @__PURE__ */ React7__default.createElement(View, { style: [styles3.header, { borderBottomColor: theme.colors.border }] }, /* @__PURE__ */ React7__default.createElement(View, { style: styles3.headerLeft }, renderAvatar(), /* @__PURE__ */ React7__default.createElement(
2040
+ /* @__PURE__ */ React8__default.createElement(View, { style: [styles5.header, { borderBottomColor: theme.colors.border }] }, /* @__PURE__ */ React8__default.createElement(View, { style: styles5.headerLeft }, renderAvatar(), /* @__PURE__ */ React8__default.createElement(
1549
2041
  Text,
1550
2042
  {
2043
+ ref: titleRef,
2044
+ accessibilityRole: "header",
1551
2045
  style: [
1552
- styles3.headerTitle,
2046
+ styles5.headerTitle,
1553
2047
  {
1554
2048
  color: theme.colors.text,
1555
2049
  fontFamily: theme.typography.fontFamily,
@@ -1558,50 +2052,50 @@ function AppilotsChatInner({
1558
2052
  ]
1559
2053
  },
1560
2054
  resolvedHeaderTitle
1561
- )), /* @__PURE__ */ React7__default.createElement(View, { style: styles3.headerActions }, /* @__PURE__ */ React7__default.createElement(
2055
+ )), /* @__PURE__ */ React8__default.createElement(View, { style: styles5.headerActions }, /* @__PURE__ */ React8__default.createElement(
1562
2056
  TouchableOpacity,
1563
2057
  {
1564
2058
  ...APPILOTS_SKIP_PROPS,
1565
2059
  onPress: () => requestHuman("user_requested"),
1566
- style: styles3.headerButton,
2060
+ style: styles5.headerButton,
1567
2061
  accessibilityRole: "button",
1568
2062
  accessibilityLabel: strings.talkToHumanA11y,
1569
2063
  testID: "escalation-header-button"
1570
2064
  },
1571
- /* @__PURE__ */ React7__default.createElement(
2065
+ /* @__PURE__ */ React8__default.createElement(
1572
2066
  HeadsetIcon,
1573
2067
  {
1574
2068
  size: 16,
1575
2069
  color: escalation ? theme.colors.primary : theme.colors.textSecondary
1576
2070
  }
1577
2071
  )
1578
- ), /* @__PURE__ */ React7__default.createElement(
2072
+ ), /* @__PURE__ */ React8__default.createElement(
1579
2073
  TouchableOpacity,
1580
2074
  {
1581
2075
  ...APPILOTS_SKIP_PROPS,
1582
2076
  onPress: clearMessages,
1583
- style: styles3.headerButton,
2077
+ style: styles5.headerButton,
1584
2078
  accessibilityRole: "button",
1585
2079
  testID: "assistant-clear"
1586
2080
  },
1587
- /* @__PURE__ */ React7__default.createElement(Text, { style: [styles3.headerButtonText, { color: theme.colors.textSecondary }] }, strings.clearButton)
1588
- ), mode !== "inline" && /* @__PURE__ */ React7__default.createElement(
2081
+ /* @__PURE__ */ React8__default.createElement(Text, { style: [styles5.headerButtonText, { color: theme.colors.textSecondary }] }, strings.clearButton)
2082
+ ), mode !== "inline" && /* @__PURE__ */ React8__default.createElement(
1589
2083
  TouchableOpacity,
1590
2084
  {
1591
2085
  ...APPILOTS_SKIP_PROPS,
1592
2086
  onPress: closeChat,
1593
- style: styles3.closeButton,
2087
+ style: styles5.closeButton,
1594
2088
  accessibilityRole: "button",
1595
2089
  accessibilityLabel: strings.closeChatA11y,
1596
2090
  testID: "assistant-close"
1597
2091
  },
1598
- /* @__PURE__ */ React7__default.createElement(Text, { style: [styles3.closeButtonText, { color: theme.colors.text }] }, "\u2715")
2092
+ /* @__PURE__ */ React8__default.createElement(Text, { style: [styles5.closeButtonText, { color: theme.colors.text }] }, "\u2715")
1599
2093
  ))),
1600
- escalation && /* @__PURE__ */ React7__default.createElement(
2094
+ escalation && /* @__PURE__ */ React8__default.createElement(
1601
2095
  View,
1602
2096
  {
1603
2097
  style: [
1604
- styles3.escalationBanner,
2098
+ styles5.escalationBanner,
1605
2099
  {
1606
2100
  borderBottomColor: theme.colors.border,
1607
2101
  backgroundColor: theme.colors.primary + "0d"
@@ -1609,12 +2103,12 @@ function AppilotsChatInner({
1609
2103
  ],
1610
2104
  testID: "escalation-banner"
1611
2105
  },
1612
- /* @__PURE__ */ React7__default.createElement(HeadsetIcon, { size: 12, color: theme.colors.primary }),
1613
- /* @__PURE__ */ React7__default.createElement(
2106
+ /* @__PURE__ */ React8__default.createElement(HeadsetIcon, { size: 12, color: theme.colors.primary }),
2107
+ /* @__PURE__ */ React8__default.createElement(
1614
2108
  Text,
1615
2109
  {
1616
2110
  style: [
1617
- styles3.escalationBannerText,
2111
+ styles5.escalationBannerText,
1618
2112
  {
1619
2113
  color: theme.colors.textSecondary,
1620
2114
  fontFamily: theme.typography.fontFamily,
@@ -1625,10 +2119,11 @@ function AppilotsChatInner({
1625
2119
  escalation.status === "pending" ? strings.escalationPendingBanner : strings.escalationActiveBanner
1626
2120
  )
1627
2121
  ),
1628
- /* @__PURE__ */ React7__default.createElement(
2122
+ /* @__PURE__ */ React8__default.createElement(
1629
2123
  FlatList,
1630
2124
  {
1631
2125
  ...APPILOTS_SKIP_PROPS,
2126
+ testID: "assistant-transcript",
1632
2127
  ref: flatListRef,
1633
2128
  data: timeline,
1634
2129
  keyExtractor: (item) => item.type === "message" ? item.data.id : item.key,
@@ -1641,48 +2136,47 @@ function AppilotsChatInner({
1641
2136
  }
1642
2137
  return null;
1643
2138
  },
1644
- contentContainerStyle: styles3.messageList,
1645
- ListEmptyComponent: /* @__PURE__ */ React7__default.createElement(View, { style: styles3.emptyState }, emptyStateIcon && isImageSource(emptyStateIcon) ? /* @__PURE__ */ React7__default.createElement(
2139
+ contentContainerStyle: styles5.messageList,
2140
+ ListEmptyComponent: /* @__PURE__ */ React8__default.createElement(View, { style: styles5.emptyState }, emptyStateIcon && isImageSource(emptyStateIcon) ? /* @__PURE__ */ React8__default.createElement(
1646
2141
  Image,
1647
2142
  {
1648
2143
  source: typeof emptyStateIcon === "string" ? { uri: emptyStateIcon } : emptyStateIcon,
1649
- style: styles3.emptyIconImage,
2144
+ style: styles5.emptyIconImage,
1650
2145
  resizeMode: "contain"
1651
2146
  }
1652
- ) : /* @__PURE__ */ React7__default.createElement(
2147
+ ) : /* @__PURE__ */ React8__default.createElement(
1653
2148
  View,
1654
2149
  {
1655
- style: [styles3.emptyIconBadge, { backgroundColor: theme.colors.primary + "14" }]
2150
+ style: [styles5.emptyIconBadge, { backgroundColor: theme.colors.primary + "14" }]
1656
2151
  },
1657
- emptyStateIcon ? /* @__PURE__ */ React7__default.createElement(Text, { style: styles3.emptyIconEmoji }, emptyStateIcon) : /* @__PURE__ */ React7__default.createElement(ChatIcon, { size: 30, color: theme.colors.primary })
1658
- ), /* @__PURE__ */ React7__default.createElement(
2152
+ emptyStateIcon ? /* @__PURE__ */ React8__default.createElement(Text, { style: styles5.emptyIconEmoji }, emptyStateIcon) : /* @__PURE__ */ React8__default.createElement(ChatIcon, { size: 30, color: theme.colors.primary })
2153
+ ), /* @__PURE__ */ React8__default.createElement(
1659
2154
  Text,
1660
2155
  {
1661
2156
  style: [
1662
- styles3.emptyTitle,
2157
+ styles5.emptyTitle,
1663
2158
  { color: theme.colors.text, fontFamily: theme.typography.fontFamily }
1664
2159
  ]
1665
2160
  },
1666
2161
  resolvedEmptyTitle
1667
- ), showEmptySubtitle && /* @__PURE__ */ React7__default.createElement(
2162
+ ), showEmptySubtitle && /* @__PURE__ */ React8__default.createElement(
1668
2163
  Text,
1669
2164
  {
1670
2165
  style: [
1671
- styles3.emptySubtitle,
2166
+ styles5.emptySubtitle,
1672
2167
  {
1673
2168
  color: theme.colors.textSecondary,
1674
2169
  fontFamily: theme.typography.fontFamily
1675
2170
  }
1676
- ],
1677
- numberOfLines: 2
2171
+ ]
1678
2172
  },
1679
2173
  resolvedEmptySubtitle
1680
- ), showSuggestedPrompts && suggestedPrompts.length > 0 && /* @__PURE__ */ React7__default.createElement(View, { style: styles3.chipsContainer, testID: "assistant-suggested-prompts" }, suggestedPrompts.map((prompt) => /* @__PURE__ */ React7__default.createElement(
2174
+ ), showSuggestedPrompts && suggestedPrompts.length > 0 && /* @__PURE__ */ React8__default.createElement(View, { style: styles5.chipsContainer, testID: "assistant-suggested-prompts" }, suggestedPrompts.map((prompt) => /* @__PURE__ */ React8__default.createElement(
1681
2175
  TouchableOpacity,
1682
2176
  {
1683
2177
  key: `${prompt.source}:${prompt.text}`,
1684
2178
  style: [
1685
- styles3.chip,
2179
+ styles5.chip,
1686
2180
  {
1687
2181
  backgroundColor: theme.colors.surface,
1688
2182
  borderColor: theme.colors.border,
@@ -1693,67 +2187,101 @@ function AppilotsChatInner({
1693
2187
  activeOpacity: 0.7,
1694
2188
  accessibilityLabel: `Suggested prompt: ${prompt.text}`
1695
2189
  },
1696
- /* @__PURE__ */ React7__default.createElement(
2190
+ /* @__PURE__ */ React8__default.createElement(
1697
2191
  Text,
1698
2192
  {
1699
2193
  style: [
1700
- styles3.chipText,
2194
+ styles5.chipText,
1701
2195
  {
1702
2196
  color: theme.colors.buttonText,
1703
2197
  fontFamily: theme.typography.fontFamily,
1704
2198
  fontSize: theme.typography.fontSizeSm
1705
2199
  }
1706
- ],
1707
- numberOfLines: 1
2200
+ ]
1708
2201
  },
1709
2202
  prompt.text
1710
2203
  )
1711
2204
  )))),
1712
- ListFooterComponent: isLoading ? /* @__PURE__ */ React7__default.createElement(
2205
+ ListFooterComponent: isLoading ? /* @__PURE__ */ React8__default.createElement(
1713
2206
  View,
1714
2207
  {
1715
2208
  style: [
1716
- styles3.messageBubble,
1717
- styles3.assistantBubble,
2209
+ styles5.messageBubble,
2210
+ styles5.assistantBubble,
1718
2211
  { backgroundColor: theme.colors.surface }
1719
2212
  ]
1720
2213
  },
1721
- /* @__PURE__ */ React7__default.createElement(
2214
+ /* @__PURE__ */ React8__default.createElement(
1722
2215
  Text,
1723
2216
  {
1724
2217
  style: [
1725
- styles3.messageText,
2218
+ styles5.messageText,
1726
2219
  { color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily }
1727
2220
  ]
1728
2221
  },
1729
- strings[loadingStatusKey] ?? strings.thinking
2222
+ stillWorkingSeconds ? strings.stillWorking.replace("{seconds}", String(stillWorkingSeconds)) : strings[loadingStatusKey] ?? strings.thinking
1730
2223
  )
1731
2224
  ) : null
1732
2225
  }
1733
2226
  ),
1734
- /* @__PURE__ */ React7__default.createElement(
2227
+ mission && !["completed", "cancelled"].includes(mission.status) && /* @__PURE__ */ React8__default.createElement(View, { style: { padding: 12, gap: 8 }, testID: "appilots-mission-controls" }, /* @__PURE__ */ React8__default.createElement(Text, { style: { color: theme.colors.text }, testID: "appilots-mission-progress" }, mission.progressText, mission.status === "paused" ? ` \xB7 ${strings.missionPaused}` : ""), /* @__PURE__ */ React8__default.createElement(View, { style: styles5.missionButtons }, mission.status === "running" ? /* @__PURE__ */ React8__default.createElement(
2228
+ TouchableOpacity,
2229
+ {
2230
+ onPress: () => void pauseMission(),
2231
+ testID: "appilots-mission-pause",
2232
+ style: styles5.missionButton,
2233
+ accessibilityRole: "button"
2234
+ },
2235
+ /* @__PURE__ */ React8__default.createElement(Text, { style: { color: theme.colors.text } }, strings.missionPause)
2236
+ ) : mission.status !== "awaiting_approval" ? /* @__PURE__ */ React8__default.createElement(
2237
+ TouchableOpacity,
2238
+ {
2239
+ onPress: () => void resumeMission(),
2240
+ disabled: isLoading,
2241
+ testID: "appilots-mission-resume",
2242
+ style: styles5.missionButton,
2243
+ accessibilityRole: "button"
2244
+ },
2245
+ /* @__PURE__ */ React8__default.createElement(Text, { style: { color: theme.colors.text } }, strings.missionResume)
2246
+ ) : null, /* @__PURE__ */ React8__default.createElement(
2247
+ TouchableOpacity,
2248
+ {
2249
+ onPress: () => void cancelMission(),
2250
+ testID: "appilots-mission-cancel",
2251
+ style: styles5.missionButton,
2252
+ accessibilityRole: "button"
2253
+ },
2254
+ /* @__PURE__ */ React8__default.createElement(Text, { style: { color: theme.colors.text } }, strings.missionCancel)
2255
+ ))),
2256
+ /* @__PURE__ */ React8__default.createElement(
1735
2257
  View,
1736
2258
  {
1737
2259
  style: [
1738
- styles3.inputContainer,
2260
+ styles5.inputContainer,
1739
2261
  {
1740
2262
  borderTopColor: theme.colors.border,
1741
2263
  paddingBottom: Math.max(insets.bottom, 10)
1742
2264
  }
1743
2265
  ]
1744
2266
  },
1745
- /* @__PURE__ */ React7__default.createElement(
2267
+ /* @__PURE__ */ React8__default.createElement(
1746
2268
  TextInput,
1747
2269
  {
2270
+ ref: composerRef,
1748
2271
  style: [
1749
- styles3.input,
2272
+ styles5.input,
1750
2273
  {
1751
2274
  backgroundColor: theme.colors.surface,
1752
2275
  color: theme.colors.text,
1753
2276
  borderRadius: theme.radii.md,
1754
2277
  fontFamily: theme.typography.fontFamily,
1755
2278
  fontSize: theme.typography.fontSizeBase,
1756
- lineHeight: theme.typography.fontSizeBase * 1.35
2279
+ lineHeight: theme.typography.fontSizeBase * 1.35,
2280
+ // The 120pt cap is ~5 lines at default size and barely one at
2281
+ // iOS AX5, where even the placeholder was cut. It grows with
2282
+ // the text, but never past a quarter of the screen, so the
2283
+ // keyboard still leaves room for the transcript.
2284
+ maxHeight: Math.min(INPUT_MAX_HEIGHT * fontScale, SCREEN_HEIGHT * 0.25)
1757
2285
  }
1758
2286
  ],
1759
2287
  placeholder: resolvedPlaceholder,
@@ -1763,16 +2291,35 @@ function AppilotsChatInner({
1763
2291
  multiline: true,
1764
2292
  blurOnSubmit: false,
1765
2293
  scrollEnabled: true,
1766
- editable: !isLoading,
2294
+ editable: !isLoading && (!mission || ["completed", "cancelled"].includes(mission.status)),
1767
2295
  testID: "assistant-input",
1768
2296
  __appilotsInternal: true
1769
2297
  }
1770
2298
  ),
1771
- isLoading ? /* @__PURE__ */ React7__default.createElement(
2299
+ voiceEnabled && !isLoading ? /* @__PURE__ */ React8__default.createElement(
1772
2300
  TouchableOpacity,
1773
2301
  {
1774
2302
  style: [
1775
- styles3.sendButton,
2303
+ styles5.sendButton,
2304
+ {
2305
+ backgroundColor: voice.state === "listening" || voice.state === "starting" ? theme.colors.error : theme.colors.primary + "40",
2306
+ borderRadius: theme.radii.md
2307
+ }
2308
+ ],
2309
+ onPressIn: voice.start,
2310
+ onPressOut: voice.stop,
2311
+ accessibilityRole: "button",
2312
+ accessibilityLabel: voice.state === "listening" ? strings.micStopA11y : strings.micStartA11y,
2313
+ testID: "appilots-mic-button",
2314
+ activeOpacity: 0.7
2315
+ },
2316
+ /* @__PURE__ */ React8__default.createElement(Text, { style: styles5.sendButtonText }, "\u25CF")
2317
+ ) : null,
2318
+ isLoading ? /* @__PURE__ */ React8__default.createElement(
2319
+ TouchableOpacity,
2320
+ {
2321
+ style: [
2322
+ styles5.sendButton,
1776
2323
  {
1777
2324
  backgroundColor: theme.colors.primary,
1778
2325
  borderRadius: theme.radii.md
@@ -1784,31 +2331,31 @@ function AppilotsChatInner({
1784
2331
  testID: "appilots-stop-button",
1785
2332
  activeOpacity: 0.7
1786
2333
  },
1787
- /* @__PURE__ */ React7__default.createElement(Text, { style: styles3.sendButtonText }, "\u25A0")
1788
- ) : /* @__PURE__ */ React7__default.createElement(
2334
+ /* @__PURE__ */ React8__default.createElement(Text, { style: styles5.sendButtonText }, "\u25A0")
2335
+ ) : /* @__PURE__ */ React8__default.createElement(
1789
2336
  TouchableOpacity,
1790
2337
  {
1791
2338
  style: [
1792
- styles3.sendButton,
2339
+ styles5.sendButton,
1793
2340
  {
1794
2341
  backgroundColor: inputText.trim() ? theme.colors.primary : theme.colors.primary + "40",
1795
2342
  borderRadius: theme.radii.md
1796
2343
  }
1797
2344
  ],
1798
2345
  onPress: handleSend,
1799
- disabled: !inputText.trim(),
2346
+ disabled: !inputText.trim() || !!(mission && !["completed", "cancelled"].includes(mission.status)),
1800
2347
  activeOpacity: 0.7,
1801
2348
  accessibilityRole: "button",
1802
2349
  testID: "assistant-send"
1803
2350
  },
1804
- /* @__PURE__ */ React7__default.createElement(Text, { style: styles3.sendButtonText }, "\u2191")
2351
+ /* @__PURE__ */ React8__default.createElement(Text, { style: styles5.sendButtonText }, "\u2191")
1805
2352
  )
1806
2353
  ),
1807
- poweredByVisible && /* @__PURE__ */ React7__default.createElement(View, { style: styles3.poweredByContainer }, /* @__PURE__ */ React7__default.createElement(
2354
+ poweredByVisible && /* @__PURE__ */ React8__default.createElement(View, { style: styles5.poweredByContainer }, /* @__PURE__ */ React8__default.createElement(
1808
2355
  Text,
1809
2356
  {
1810
2357
  style: [
1811
- styles3.poweredByText,
2358
+ styles5.poweredByText,
1812
2359
  {
1813
2360
  color: theme.colors.textSecondary,
1814
2361
  fontFamily: theme.typography.fontFamily,
@@ -1824,11 +2371,12 @@ function AppilotsChatInner({
1824
2371
  }
1825
2372
  const renderTrigger = () => {
1826
2373
  if (triggerButton === "none") return null;
1827
- if (triggerButton !== "default" && React7__default.isValidElement(triggerButton)) {
1828
- return /* @__PURE__ */ React7__default.createElement(View, { ...APPILOTS_SKIP_PROPS, style: [styles3.fabWrapper, fabPositionStyle] }, /* @__PURE__ */ React7__default.createElement(
2374
+ if (triggerButton !== "default" && React8__default.isValidElement(triggerButton)) {
2375
+ return /* @__PURE__ */ React8__default.createElement(View, { ...APPILOTS_SKIP_PROPS, style: [styles5.fabWrapper, fabPositionStyle] }, /* @__PURE__ */ React8__default.createElement(
1829
2376
  TouchableOpacity,
1830
2377
  {
1831
2378
  ...APPILOTS_SKIP_PROPS,
2379
+ ref: launcherRef,
1832
2380
  onPress: openChat,
1833
2381
  activeOpacity: 0.8,
1834
2382
  accessibilityRole: "button",
@@ -1838,12 +2386,13 @@ function AppilotsChatInner({
1838
2386
  triggerButton
1839
2387
  ));
1840
2388
  }
1841
- return /* @__PURE__ */ React7__default.createElement(
2389
+ return /* @__PURE__ */ React8__default.createElement(
1842
2390
  TouchableOpacity,
1843
2391
  {
1844
2392
  ...APPILOTS_SKIP_PROPS,
2393
+ ref: launcherRef,
1845
2394
  style: [
1846
- styles3.fab,
2395
+ styles5.fab,
1847
2396
  fabPositionStyle,
1848
2397
  { backgroundColor: triggerButtonColor ?? theme.colors.primary }
1849
2398
  ],
@@ -1853,55 +2402,116 @@ function AppilotsChatInner({
1853
2402
  accessibilityLabel: strings.openChatA11y,
1854
2403
  testID: "assistant-launcher"
1855
2404
  },
1856
- triggerIcon ?? (triggerButtonImage ? /* @__PURE__ */ React7__default.createElement(
2405
+ triggerIcon ?? (triggerButtonImage ? /* @__PURE__ */ React8__default.createElement(
1857
2406
  Image,
1858
2407
  {
1859
2408
  source: typeof triggerButtonImage === "string" ? { uri: triggerButtonImage } : triggerButtonImage,
1860
- style: styles3.fabImage,
2409
+ style: styles5.fabImage,
1861
2410
  resizeMode: "contain"
1862
2411
  }
1863
- ) : /* @__PURE__ */ React7__default.createElement(ChatIcon, { size: 26, color: "#ffffff" }))
2412
+ ) : /* @__PURE__ */ React8__default.createElement(ChatIcon, { size: 26, color: "#ffffff" }))
1864
2413
  );
1865
2414
  };
1866
- return /* @__PURE__ */ React7__default.createElement(React7__default.Fragment, null, showTrigger && renderTrigger(), /* @__PURE__ */ React7__default.createElement(
1867
- Modal,
2415
+ return /* @__PURE__ */ React8__default.createElement(React8__default.Fragment, null, showTrigger && renderTrigger(), spotlight && experience.spotlight !== "off" ? /* @__PURE__ */ React8__default.createElement(
2416
+ SpotlightOverlay,
2417
+ {
2418
+ rect: spotlight.rect,
2419
+ seq: spotlight.seq,
2420
+ label: spotlight.label,
2421
+ color: theme.colors.primary,
2422
+ captionBackground: theme.colors.surface,
2423
+ captionColor: theme.colors.text,
2424
+ fontFamily: theme.typography.fontFamily,
2425
+ window: { width: SCREEN_WIDTH, height: SCREEN_HEIGHT }
2426
+ }
2427
+ ) : null, surface === "pill" && /* @__PURE__ */ React8__default.createElement(
2428
+ AgentPill,
2429
+ {
2430
+ label: mission && !["completed", "cancelled"].includes(mission.status) ? mission.progressText : pillLabel,
2431
+ state: pillState,
2432
+ position,
2433
+ insets,
2434
+ fontFamily: theme.typography.fontFamily,
2435
+ colors: {
2436
+ surface: theme.colors.surface,
2437
+ text: theme.colors.text,
2438
+ textSecondary: theme.colors.textSecondary,
2439
+ primary: theme.colors.primary,
2440
+ border: theme.colors.border,
2441
+ success: theme.colors.success,
2442
+ error: theme.colors.error
2443
+ },
2444
+ strings: { expandA11y: strings.pillExpandA11y, stopA11y: strings.pillStopA11y },
2445
+ onExpand: openChat,
2446
+ expandRef: pillRef,
2447
+ onStop: busy ? cancelMessage : void 0
2448
+ }
2449
+ ), /* @__PURE__ */ React8__default.createElement(SheetHost, { useModal: presence === "sheet", visible: isVisible, onRequestClose: closeChat }, /* @__PURE__ */ React8__default.createElement(
2450
+ Animated.View,
1868
2451
  {
1869
2452
  ...APPILOTS_SKIP_PROPS,
1870
- visible: isVisible,
1871
- transparent: true,
1872
- animationType: "none",
1873
- onRequestClose: closeChat
2453
+ pointerEvents: presence === "sheet" ? "auto" : "box-none",
2454
+ onAccessibilityEscape: closeChat,
2455
+ style: [
2456
+ mode === "fullscreen" ? styles5.modalContainerFullscreen : styles5.modalContainer,
2457
+ mode === "sidebar" && styles5.modalContainerSidebar,
2458
+ presence !== "sheet" && styles5.sheetContainerNoScrim,
2459
+ // Keeps the sheet out of the status bar / notch. It is padding
2460
+ // and not a margin so the scrim still covers the full screen.
2461
+ // Only reachable now that the sheet can grow to the top edge:
2462
+ // `fullscreen` and `sidebar` always could, and `bubble` does
2463
+ // whenever the keyboard shrinks it — without this, the header
2464
+ // renders under the clock.
2465
+ { paddingTop: insets.top },
2466
+ { transform: [{ translateY: slideAnim }] }
2467
+ ]
1874
2468
  },
1875
- /* @__PURE__ */ React7__default.createElement(
1876
- Animated.View,
2469
+ /* @__PURE__ */ React8__default.createElement(
2470
+ KeyboardAvoidingView,
1877
2471
  {
1878
- ...APPILOTS_SKIP_PROPS,
1879
- style: [
1880
- mode === "fullscreen" ? styles3.modalContainerFullscreen : styles3.modalContainer,
1881
- mode === "sidebar" && styles3.modalContainerSidebar,
1882
- // Keeps the sheet out of the status bar / notch. It is padding
1883
- // and not a margin so the scrim still covers the full screen.
1884
- // Only reachable now that the sheet can grow to the top edge:
1885
- // `fullscreen` and `sidebar` always could, and `bubble` does
1886
- // whenever the keyboard shrinks it — without this, the header
1887
- // renders under the clock.
1888
- { paddingTop: insets.top },
1889
- { transform: [{ translateY: slideAnim }] }
1890
- ]
2472
+ style: styles5.keyboardView,
2473
+ behavior: Platform.OS === "ios" ? "padding" : "height",
2474
+ keyboardVerticalOffset: 0
1891
2475
  },
1892
- /* @__PURE__ */ React7__default.createElement(
1893
- KeyboardAvoidingView,
1894
- {
1895
- style: styles3.keyboardView,
1896
- behavior: Platform.OS === "ios" ? "padding" : "height",
1897
- keyboardVerticalOffset: 0
1898
- },
1899
- chatSurface
1900
- )
2476
+ chatSurface
1901
2477
  )
1902
- ));
2478
+ )));
1903
2479
  }
1904
- var styles3 = StyleSheet.create({
2480
+ function SheetHost({
2481
+ useModal,
2482
+ visible,
2483
+ onRequestClose,
2484
+ children
2485
+ }) {
2486
+ if (useModal) {
2487
+ return /* @__PURE__ */ React8__default.createElement(
2488
+ Modal,
2489
+ {
2490
+ ...APPILOTS_SKIP_PROPS,
2491
+ visible,
2492
+ transparent: true,
2493
+ animationType: "none",
2494
+ onRequestClose
2495
+ },
2496
+ children
2497
+ );
2498
+ }
2499
+ if (!visible) return null;
2500
+ return /* @__PURE__ */ React8__default.createElement(
2501
+ View,
2502
+ {
2503
+ ...APPILOTS_SKIP_PROPS,
2504
+ pointerEvents: "box-none",
2505
+ accessibilityViewIsModal: true,
2506
+ style: [StyleSheet.absoluteFill, styles5.sheetOverlay]
2507
+ },
2508
+ children
2509
+ );
2510
+ }
2511
+ var styles5 = StyleSheet.create({
2512
+ // An explicitly opened chat must not mount underneath a sibling portal.
2513
+ // Automatic expansion is separately deferred while the host modal is open.
2514
+ sheetOverlay: { zIndex: 1e3, elevation: 7 },
1905
2515
  fab: {
1906
2516
  position: "absolute",
1907
2517
  width: 56,
@@ -1944,6 +2554,12 @@ var styles3 = StyleSheet.create({
1944
2554
  flexDirection: "row",
1945
2555
  justifyContent: "flex-end"
1946
2556
  },
2557
+ // Non-modal presences drop the scrim entirely: dimming the app is
2558
+ // the opposite of the point, and a painted full-screen surface is
2559
+ // touch-opaque no matter what pointerEvents the parent asks for.
2560
+ sheetContainerNoScrim: {
2561
+ backgroundColor: "transparent"
2562
+ },
1947
2563
  keyboardView: {
1948
2564
  flex: 1,
1949
2565
  justifyContent: "flex-end"
@@ -1985,8 +2601,13 @@ var styles3 = StyleSheet.create({
1985
2601
  maxHeight: "100%",
1986
2602
  flex: 1
1987
2603
  },
2604
+ // Wraps instead of squeezing: at large text sizes the title alone is
2605
+ // wider than the row, and without the wrap it pushed Clear and ✕ off the
2606
+ // right edge — the chat could not be closed (measured at iOS AX5).
1988
2607
  header: {
1989
2608
  flexDirection: "row",
2609
+ flexWrap: "wrap",
2610
+ rowGap: 4,
1990
2611
  alignItems: "center",
1991
2612
  justifyContent: "space-between",
1992
2613
  paddingHorizontal: 16,
@@ -1996,7 +2617,8 @@ var styles3 = StyleSheet.create({
1996
2617
  headerLeft: {
1997
2618
  flexDirection: "row",
1998
2619
  alignItems: "center",
1999
- gap: 8
2620
+ gap: 8,
2621
+ flexShrink: 1
2000
2622
  },
2001
2623
  headerDot: {
2002
2624
  width: 8,
@@ -2015,24 +2637,30 @@ var styles3 = StyleSheet.create({
2015
2637
  justifyContent: "center"
2016
2638
  },
2017
2639
  headerTitle: {
2018
- fontWeight: "600"
2640
+ fontWeight: "600",
2641
+ flexShrink: 1
2019
2642
  },
2020
2643
  headerActions: {
2021
2644
  flexDirection: "row",
2022
2645
  alignItems: "center",
2023
- gap: 12
2646
+ gap: 4,
2647
+ // Stays right-aligned when the header wraps it onto its own line.
2648
+ marginLeft: "auto"
2024
2649
  },
2025
2650
  headerButton: {
2651
+ minWidth: MIN_TOUCH_TARGET2,
2652
+ minHeight: MIN_TOUCH_TARGET2,
2026
2653
  paddingHorizontal: 8,
2027
- paddingVertical: 4
2654
+ alignItems: "center",
2655
+ justifyContent: "center"
2028
2656
  },
2029
2657
  headerButtonText: {
2030
2658
  fontSize: 13
2031
2659
  },
2032
2660
  closeButton: {
2033
- width: 28,
2034
- height: 28,
2035
- borderRadius: 14,
2661
+ minWidth: MIN_TOUCH_TARGET2,
2662
+ minHeight: MIN_TOUCH_TARGET2,
2663
+ borderRadius: MIN_TOUCH_TARGET2 / 2,
2036
2664
  alignItems: "center",
2037
2665
  justifyContent: "center"
2038
2666
  },
@@ -2114,6 +2742,8 @@ var styles3 = StyleSheet.create({
2114
2742
  maxWidth: 320
2115
2743
  },
2116
2744
  chip: {
2745
+ minHeight: MIN_TOUCH_TARGET2,
2746
+ justifyContent: "center",
2117
2747
  paddingHorizontal: 16,
2118
2748
  paddingVertical: 10,
2119
2749
  borderWidth: 1,
@@ -2176,16 +2806,16 @@ var styles3 = StyleSheet.create({
2176
2806
  },
2177
2807
  input: {
2178
2808
  flex: 1,
2179
- minHeight: 38,
2180
- maxHeight: 120,
2809
+ minHeight: MIN_TOUCH_TARGET2,
2810
+ maxHeight: INPUT_MAX_HEIGHT,
2181
2811
  paddingHorizontal: 16,
2182
2812
  paddingTop: Platform.OS === "ios" ? 9 : 8,
2183
2813
  paddingBottom: Platform.OS === "ios" ? 9 : 8,
2184
2814
  textAlignVertical: "top"
2185
2815
  },
2186
2816
  sendButton: {
2187
- width: 38,
2188
- height: 38,
2817
+ minWidth: MIN_TOUCH_TARGET2,
2818
+ minHeight: MIN_TOUCH_TARGET2,
2189
2819
  alignItems: "center",
2190
2820
  justifyContent: "center"
2191
2821
  },
@@ -2194,6 +2824,15 @@ var styles3 = StyleSheet.create({
2194
2824
  fontSize: 18,
2195
2825
  fontWeight: "700"
2196
2826
  },
2827
+ missionButtons: {
2828
+ flexDirection: "row",
2829
+ flexWrap: "wrap",
2830
+ columnGap: 12
2831
+ },
2832
+ missionButton: {
2833
+ minHeight: MIN_TOUCH_TARGET2,
2834
+ justifyContent: "center"
2835
+ },
2197
2836
  poweredByContainer: {
2198
2837
  alignItems: "center",
2199
2838
  paddingVertical: 4
@@ -2208,6 +2847,11 @@ function ConfirmDialog(_props = {}) {
2208
2847
  return null;
2209
2848
  }
2210
2849
 
2850
+ // src/auto/appilotsListTotal.ts
2851
+ function appilotsListTotal(total) {
2852
+ return { appilotsTotalItemCount: total };
2853
+ }
2854
+
2211
2855
  // src/hoc/canHoldRef.ts
2212
2856
  var FORWARD_REF = /* @__PURE__ */ Symbol.for("react.forward_ref");
2213
2857
  var MEMO = /* @__PURE__ */ Symbol.for("react.memo");
@@ -2234,12 +2878,19 @@ function canHoldRef(type) {
2234
2878
  }
2235
2879
  }
2236
2880
 
2881
+ // src/hoc/withDeclaredIdentity.ts
2882
+ function withDeclaredIdentity(props, declaredId, enabled) {
2883
+ if (!enabled || !declaredId) return props;
2884
+ if (props.testID != null) return props;
2885
+ return { ...props, testID: declaredId };
2886
+ }
2887
+
2237
2888
  // src/hoc/createAppilotsInput.tsx
2238
2889
  function normalizeId(label) {
2239
2890
  return label.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9\s]/g, "").trim().replace(/\s+(.)/g, (_, c) => c.toUpperCase());
2240
2891
  }
2241
2892
  function createAppilotsInput(WrappedComponent) {
2242
- const AppilotsInput = React7__default.forwardRef((props, ref) => {
2893
+ const AppilotsInput = React8__default.forwardRef((props, ref) => {
2243
2894
  const {
2244
2895
  appilotsId,
2245
2896
  appilotsScreen,
@@ -2294,10 +2945,14 @@ function createAppilotsInput(WrappedComponent) {
2294
2945
  },
2295
2946
  [ref]
2296
2947
  );
2297
- return /* @__PURE__ */ React7__default.createElement(
2948
+ return /* @__PURE__ */ React8__default.createElement(
2298
2949
  WrappedComponent,
2299
2950
  {
2300
- ...{ label, value, onChangeText, ...rest },
2951
+ ...withDeclaredIdentity(
2952
+ { label, value, onChangeText, ...rest },
2953
+ fieldId,
2954
+ appilotsEnabled
2955
+ ),
2301
2956
  ...canHoldRef(WrappedComponent) ? { ref: mergedRef } : {}
2302
2957
  }
2303
2958
  );
@@ -2309,7 +2964,7 @@ function normalizeId2(label) {
2309
2964
  return label.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9\s]/g, "").trim().replace(/\s+(.)/g, (_, c) => c.toUpperCase());
2310
2965
  }
2311
2966
  function createAppilotsButton(WrappedComponent) {
2312
- const AppilotsButton = React7__default.forwardRef((props, ref) => {
2967
+ const AppilotsButton = React8__default.forwardRef((props, ref) => {
2313
2968
  const {
2314
2969
  appilotsId,
2315
2970
  appilotsScreen,
@@ -2335,10 +2990,10 @@ function createAppilotsButton(WrappedComponent) {
2335
2990
  componentRegistry.unregister(targetId);
2336
2991
  };
2337
2992
  }, [targetId, appilotsEnabled, press, title, appilotsScreen]);
2338
- return /* @__PURE__ */ React7__default.createElement(
2993
+ return /* @__PURE__ */ React8__default.createElement(
2339
2994
  WrappedComponent,
2340
2995
  {
2341
- ...{ title, onPress, ...rest },
2996
+ ...withDeclaredIdentity({ title, onPress, ...rest }, targetId, appilotsEnabled),
2342
2997
  ...canHoldRef(WrappedComponent) ? { ref } : {}
2343
2998
  }
2344
2999
  );
@@ -2350,7 +3005,7 @@ function normalizeId3(label) {
2350
3005
  return label.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9\s]/g, "").trim().replace(/\s+(.)/g, (_, c) => c.toUpperCase());
2351
3006
  }
2352
3007
  function createAppilotsSwitch(WrappedComponent) {
2353
- const AppilotsSwitch = React7__default.forwardRef((props, ref) => {
3008
+ const AppilotsSwitch = React8__default.forwardRef((props, ref) => {
2354
3009
  const {
2355
3010
  appilotsId,
2356
3011
  appilotsScreen,
@@ -2381,10 +3036,14 @@ function createAppilotsSwitch(WrappedComponent) {
2381
3036
  componentRegistry.unregister(toggleId);
2382
3037
  };
2383
3038
  }, [toggleId, appilotsEnabled, getValue, setValue, label, appilotsScreen]);
2384
- return /* @__PURE__ */ React7__default.createElement(
3039
+ return /* @__PURE__ */ React8__default.createElement(
2385
3040
  WrappedComponent,
2386
3041
  {
2387
- ...{ label, value, onValueChange, ...rest },
3042
+ ...withDeclaredIdentity(
3043
+ { label, value, onValueChange, ...rest },
3044
+ toggleId,
3045
+ appilotsEnabled
3046
+ ),
2388
3047
  ...canHoldRef(WrappedComponent) ? { ref } : {}
2389
3048
  }
2390
3049
  );
@@ -2393,4 +3052,4 @@ function createAppilotsSwitch(WrappedComponent) {
2393
3052
  return AppilotsSwitch;
2394
3053
  }
2395
3054
 
2396
- export { ActionBreadcrumb, AppilotsChat, AppilotsI18nProvider, AppilotsThemeProvider, ChatIcon, ConfirmDialog, HeadsetIcon, appilotsTheme, appilotsThemeFromTailwind, createAppilotsButton, createAppilotsInput, createAppilotsSwitch, detectDeviceLocale, resolveLocale, useAppilotsI18n, useAppilotsTheme };
3055
+ export { ActionBreadcrumb, AgentPill, AppilotsChat, AppilotsThemeProvider, ChatIcon, ConfirmDialog, DEFAULT_EXPERIENCE, HeadsetIcon, SpotlightOverlay, appilotsListTotal, appilotsTheme, appilotsThemeFromTailwind, createAppilotsButton, createAppilotsInput, createAppilotsSwitch, formatDowngrades, resolveExperience, resolvePillStatus, resolveSurface, shouldReopenOnTurnEnd, useAppilotsTheme, useSpotlight, usesPill };