@appilots/sdk 0.11.1 → 0.13.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-LLH7QCSX.mjs';
2
- import { useAppilotsChat, useAppilotsActions, shouldShowSuggestedPrompts, useSuggestedPrompts } from './chunk-GSHE45YX.mjs';
3
- export { appilotsSelfCheck, captureSnapshot, executeAction, useAppilots, useAppilotsActions, useAppilotsChat, useAppilotsField, useAppilotsNavigation, useAppilotsSlider, useAppilotsTarget, useAppilotsToggle, useSuggestedPrompts } from './chunk-GSHE45YX.mjs';
4
- import { defaultLightTheme, describeAction, mergeThemeTokens, resolveBaseTheme, useAppilotsContext, AppilotsErrorBoundary, componentRegistry } from './chunk-PQZ64CCE.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-PQZ64CCE.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-L3SY3Q4K.mjs';
2
+ import { subscribeSpotlight, getSpotlightTarget, AppilotsI18nProvider, useAppilotsI18n, useAppilotsChat, useAppilotsActions, setSpotlightMode, shouldShowSuggestedPrompts, useSuggestedPrompts } from './chunk-UYBKXULM.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-UYBKXULM.mjs';
4
+ import { defaultLightTheme, describeAction, isRectOnScreen, mergeThemeTokens, resolveBaseTheme, useAppilotsContext, AppilotsErrorBoundary, subscribeGeometryHost, geometryAvailability, isGeometryResolved, needsWideChatLayout, componentRegistry, appilotsDebugWarn, enhancePlainAssistantContent, looksLikeMarkdown, parseMarkdownBlocks, parseInlineMarkdown, getFiberRoot, shouldSkipSubtree, isModalFiber } from './chunk-HRWBK35Y.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-HRWBK35Y.mjs';
6
+ import * as React8 from 'react';
7
+ import React8__default, { createContext, useRef, useEffect, useCallback, useSyncExternalStore, useMemo, useContext, useState } from 'react';
8
+ import { StyleSheet, Dimensions, Platform, View, Animated, Text, TouchableOpacity, ActivityIndicator, useColorScheme, 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 = {
@@ -114,6 +151,7 @@ function ActionBreadcrumb({
114
151
  fontFamily,
115
152
  palette,
116
153
  confirmStrings,
154
+ locale,
117
155
  requireApprovalFor,
118
156
  onApprove,
119
157
  onReject
@@ -125,7 +163,7 @@ function ActionBreadcrumb({
125
163
  primary: primaryColor,
126
164
  text: textColor
127
165
  };
128
- return /* @__PURE__ */ React7__default.createElement(View, { style: styles.container }, actions.map((action) => /* @__PURE__ */ React7__default.createElement(
166
+ return /* @__PURE__ */ React8__default.createElement(View, { style: styles.container }, actions.map((action) => /* @__PURE__ */ React8__default.createElement(
129
167
  BreadcrumbRow,
130
168
  {
131
169
  key: action.id,
@@ -135,6 +173,7 @@ function ActionBreadcrumb({
135
173
  fontFamily,
136
174
  palette: resolvedPalette,
137
175
  confirmStrings,
176
+ locale,
138
177
  needsApproval: requireApprovalFor?.(action) ?? false,
139
178
  onApprove,
140
179
  onReject
@@ -144,7 +183,7 @@ function ActionBreadcrumb({
144
183
  function BreadcrumbRow(props) {
145
184
  const { action, needsApproval } = props;
146
185
  if (action.type === "confirm" && action.status === "pending" && needsApproval) {
147
- return /* @__PURE__ */ React7__default.createElement(
186
+ return /* @__PURE__ */ React8__default.createElement(
148
187
  ConfirmCard,
149
188
  {
150
189
  action,
@@ -156,7 +195,7 @@ function BreadcrumbRow(props) {
156
195
  }
157
196
  );
158
197
  }
159
- return /* @__PURE__ */ React7__default.createElement(RegularBreadcrumbRow, { ...props });
198
+ return /* @__PURE__ */ React8__default.createElement(RegularBreadcrumbRow, { ...props });
160
199
  }
161
200
  function RegularBreadcrumbRow({
162
201
  action,
@@ -164,11 +203,12 @@ function RegularBreadcrumbRow({
164
203
  textColor,
165
204
  fontFamily,
166
205
  palette,
206
+ locale,
167
207
  needsApproval,
168
208
  onApprove,
169
209
  onReject
170
210
  }) {
171
- const { label, state } = describeAction(action);
211
+ const { label, state } = describeAction(action, locale);
172
212
  const tone = deriveStateTone(palette)[state];
173
213
  const fade = useRef(new Animated.Value(0)).current;
174
214
  const pulse = useRef(new Animated.Value(1)).current;
@@ -191,39 +231,41 @@ function RegularBreadcrumbRow({
191
231
  }).start();
192
232
  }, [state, pulse]);
193
233
  const isDestructive = isDestructiveAction(action);
194
- return /* @__PURE__ */ React7__default.createElement(Animated.View, { style: [styles.row, { opacity: fade }] }, /* @__PURE__ */ React7__default.createElement(
234
+ return /* @__PURE__ */ React8__default.createElement(Animated.View, { style: [styles.row, { opacity: fade }] }, /* @__PURE__ */ React8__default.createElement(
195
235
  Animated.View,
196
236
  {
197
237
  style: { opacity: pulse, flexDirection: "row", alignItems: "flex-start", flex: 1 }
198
238
  },
199
- /* @__PURE__ */ React7__default.createElement(StateIcon, { state, tone, primaryColor }),
200
- /* @__PURE__ */ React7__default.createElement(
239
+ /* @__PURE__ */ React8__default.createElement(StateIcon, { state, tone, primaryColor }),
240
+ /* @__PURE__ */ React8__default.createElement(
201
241
  Text,
202
242
  {
203
243
  style: [styles.label, { color: state === "failed" ? tone.color : textColor, fontFamily }],
204
244
  accessibilityLabel: `${state}: ${label}`
205
245
  },
206
246
  label,
207
- isDestructive && /* @__PURE__ */ React7__default.createElement(Text, { style: [styles.destructiveTag, { color: palette.error }] }, " \u26A0 destrutiva")
247
+ isDestructive && /* @__PURE__ */ React8__default.createElement(Text, { style: [styles.destructiveTag, { color: palette.error }] }, " \u26A0 destrutiva")
208
248
  )
209
- ), needsApproval && action.status === "pending" && onApprove && onReject && /* @__PURE__ */ React7__default.createElement(View, { style: styles.approvalButtons }, /* @__PURE__ */ React7__default.createElement(
249
+ ), needsApproval && action.status === "pending" && onApprove && onReject && /* @__PURE__ */ React8__default.createElement(View, { style: styles.approvalButtons }, /* @__PURE__ */ React8__default.createElement(
210
250
  TouchableOpacity,
211
251
  {
212
252
  onPress: () => onReject(action.id),
213
253
  style: [styles.btn, { backgroundColor: deriveStateTone(palette).failed.iconBg }],
214
254
  activeOpacity: 0.7,
215
- accessibilityLabel: "Rejeitar a\xE7\xE3o"
255
+ accessibilityLabel: "Rejeitar a\xE7\xE3o",
256
+ testID: "assistant-action-reject"
216
257
  },
217
- /* @__PURE__ */ React7__default.createElement(Text, { style: [styles.rejectBtnText, { color: palette.error }] }, "Rejeitar")
218
- ), /* @__PURE__ */ React7__default.createElement(
258
+ /* @__PURE__ */ React8__default.createElement(Text, { style: [styles.rejectBtnText, { color: palette.error }] }, "Rejeitar")
259
+ ), /* @__PURE__ */ React8__default.createElement(
219
260
  TouchableOpacity,
220
261
  {
221
262
  onPress: () => onApprove(action.id),
222
263
  style: [styles.btn, styles.approveBtn, { backgroundColor: primaryColor }],
223
264
  activeOpacity: 0.7,
224
- accessibilityLabel: "Aprovar a\xE7\xE3o"
265
+ accessibilityLabel: "Aprovar a\xE7\xE3o",
266
+ testID: "assistant-action-approve"
225
267
  },
226
- /* @__PURE__ */ React7__default.createElement(Text, { style: styles.approveBtnText }, "Aprovar")
268
+ /* @__PURE__ */ React8__default.createElement(Text, { style: styles.approveBtnText }, "Aprovar")
227
269
  )));
228
270
  }
229
271
  function ConfirmCard({
@@ -248,7 +290,7 @@ function ConfirmCard({
248
290
  cancelBackground,
249
291
  cancelTextColor
250
292
  } = deriveConfirmCardContent(action, palette, confirmStrings);
251
- return /* @__PURE__ */ React7__default.createElement(
293
+ return /* @__PURE__ */ React8__default.createElement(
252
294
  View,
253
295
  {
254
296
  style: [
@@ -257,7 +299,7 @@ function ConfirmCard({
257
299
  ],
258
300
  testID: "assistant-confirm-card"
259
301
  },
260
- /* @__PURE__ */ React7__default.createElement(View, { style: confirmCardStyles.headerRow }, /* @__PURE__ */ React7__default.createElement(Text, { style: [confirmCardStyles.glyph, { color: actionColor }] }, glyph), /* @__PURE__ */ React7__default.createElement(
302
+ /* @__PURE__ */ React8__default.createElement(View, { style: confirmCardStyles.headerRow }, /* @__PURE__ */ React8__default.createElement(Text, { style: [confirmCardStyles.glyph, { color: actionColor }] }, glyph), /* @__PURE__ */ React8__default.createElement(
261
303
  Text,
262
304
  {
263
305
  style: [
@@ -267,18 +309,18 @@ function ConfirmCard({
267
309
  },
268
310
  title
269
311
  )),
270
- /* @__PURE__ */ React7__default.createElement(Text, { style: [confirmCardStyles.message, { color: messageColor, fontFamily }] }, message),
271
- /* @__PURE__ */ React7__default.createElement(View, { style: confirmCardStyles.buttons }, /* @__PURE__ */ React7__default.createElement(
312
+ /* @__PURE__ */ React8__default.createElement(Text, { style: [confirmCardStyles.message, { color: messageColor, fontFamily }] }, message),
313
+ /* @__PURE__ */ React8__default.createElement(View, { style: confirmCardStyles.buttons }, /* @__PURE__ */ React8__default.createElement(
272
314
  TouchableOpacity,
273
315
  {
274
316
  onPress: () => onReject?.(action.id),
275
317
  style: [confirmCardStyles.btn, { backgroundColor: cancelBackground }],
276
318
  activeOpacity: 0.7,
277
- accessibilityLabel: "Cancelar",
319
+ accessibilityLabel: cancelLabel,
278
320
  testID: "assistant-confirm-reject"
279
321
  },
280
- /* @__PURE__ */ React7__default.createElement(Text, { style: [confirmCardStyles.cancelText, { color: cancelTextColor, fontFamily }] }, cancelLabel)
281
- ), /* @__PURE__ */ React7__default.createElement(
322
+ /* @__PURE__ */ React8__default.createElement(Text, { style: [confirmCardStyles.cancelText, { color: cancelTextColor, fontFamily }] }, cancelLabel)
323
+ ), /* @__PURE__ */ React8__default.createElement(
282
324
  TouchableOpacity,
283
325
  {
284
326
  onPress: () => onApprove?.(action.id),
@@ -288,10 +330,10 @@ function ConfirmCard({
288
330
  { backgroundColor: actionColor }
289
331
  ],
290
332
  activeOpacity: 0.7,
291
- accessibilityLabel: "Confirmar",
333
+ accessibilityLabel: confirmLabel,
292
334
  testID: "assistant-confirm-approve"
293
335
  },
294
- /* @__PURE__ */ React7__default.createElement(Text, { style: [confirmCardStyles.confirmText, { fontFamily }] }, confirmLabel)
336
+ /* @__PURE__ */ React8__default.createElement(Text, { style: [confirmCardStyles.confirmText, { fontFamily }] }, confirmLabel)
295
337
  ))
296
338
  );
297
339
  }
@@ -356,7 +398,7 @@ function StateIcon({
356
398
  tone,
357
399
  primaryColor
358
400
  }) {
359
- 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)));
401
+ return /* @__PURE__ */ React8__default.createElement(View, { style: [styles.iconCircle, { backgroundColor: tone.iconBg }] }, state === "running" ? /* @__PURE__ */ React8__default.createElement(ActivityIndicator, { size: "small", color: primaryColor }) : /* @__PURE__ */ React8__default.createElement(Text, { style: [styles.iconGlyph, { color: tone.color }] }, iconGlyphFor(state)));
360
402
  }
361
403
  var styles = StyleSheet.create({
362
404
  container: {
@@ -447,7 +489,7 @@ function resolvePersonalization(props, remote) {
447
489
  };
448
490
  }
449
491
  function ChatIcon({ size = 24, color = "#ffffff" }) {
450
- return /* @__PURE__ */ React7.createElement(Svg, { width: size, height: size, viewBox: "0 0 24 24", fill: "none" }, /* @__PURE__ */ React7.createElement(
492
+ return /* @__PURE__ */ React8.createElement(Svg, { width: size, height: size, viewBox: "0 0 24 24", fill: "none" }, /* @__PURE__ */ React8.createElement(
451
493
  Path,
452
494
  {
453
495
  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",
@@ -455,8 +497,438 @@ function ChatIcon({ size = 24, color = "#ffffff" }) {
455
497
  }
456
498
  ));
457
499
  }
500
+
501
+ // src/components/internalProps.ts
502
+ var APPILOTS_SKIP_PROPS = {
503
+ __appilotsSkip: true,
504
+ __appilotsInternal: true
505
+ };
506
+
507
+ // src/components/AgentPill.tsx
508
+ function toneFor(state, colors) {
509
+ switch (state) {
510
+ case "success":
511
+ return colors.success;
512
+ case "failed":
513
+ return colors.error;
514
+ default:
515
+ return colors.primary;
516
+ }
517
+ }
518
+ function AgentPill({
519
+ label,
520
+ state,
521
+ onExpand,
522
+ onStop,
523
+ position,
524
+ insets,
525
+ colors,
526
+ fontFamily,
527
+ strings
528
+ }) {
529
+ const atTop = position === "top-left" || position === "top-right";
530
+ const enter = useRef(new Animated.Value(0)).current;
531
+ useEffect(() => {
532
+ Animated.spring(enter, {
533
+ toValue: 1,
534
+ useNativeDriver: true,
535
+ tension: 70,
536
+ friction: 12
537
+ }).start();
538
+ }, [enter]);
539
+ const translateY = enter.interpolate({
540
+ inputRange: [0, 1],
541
+ outputRange: [atTop ? -80 : 80, 0]
542
+ });
543
+ const running = state === "running" || state === "pending";
544
+ return /* @__PURE__ */ React8__default.createElement(
545
+ Animated.View,
546
+ {
547
+ ...APPILOTS_SKIP_PROPS,
548
+ pointerEvents: "box-none",
549
+ style: [
550
+ styles2.wrapper,
551
+ atTop ? { top: insets.top + 8 } : { bottom: insets.bottom + 8 },
552
+ { opacity: enter, transform: [{ translateY }] }
553
+ ]
554
+ },
555
+ /* @__PURE__ */ React8__default.createElement(
556
+ TouchableOpacity,
557
+ {
558
+ ...APPILOTS_SKIP_PROPS,
559
+ testID: "appilots-agent-pill",
560
+ accessibilityRole: "button",
561
+ accessibilityLabel: strings.expandA11y,
562
+ activeOpacity: 0.85,
563
+ onPress: onExpand,
564
+ style: [styles2.pill, { backgroundColor: colors.surface, borderColor: colors.border }]
565
+ },
566
+ running ? /* @__PURE__ */ React8__default.createElement(ActivityIndicator, { size: "small", color: toneFor(state, colors) }) : /* @__PURE__ */ React8__default.createElement(View, { style: [styles2.dot, { backgroundColor: toneFor(state, colors) }] }),
567
+ /* @__PURE__ */ React8__default.createElement(
568
+ Text,
569
+ {
570
+ testID: "appilots-agent-pill-label",
571
+ numberOfLines: 1,
572
+ style: [styles2.label, { color: colors.text, fontFamily }]
573
+ },
574
+ label
575
+ ),
576
+ onStop ? /* @__PURE__ */ React8__default.createElement(
577
+ TouchableOpacity,
578
+ {
579
+ ...APPILOTS_SKIP_PROPS,
580
+ testID: "appilots-agent-pill-stop",
581
+ accessibilityRole: "button",
582
+ accessibilityLabel: strings.stopA11y,
583
+ onPress: onStop,
584
+ hitSlop: { top: 12, bottom: 12, left: 12, right: 12 },
585
+ style: [styles2.stop, { borderColor: colors.border }]
586
+ },
587
+ /* @__PURE__ */ React8__default.createElement(View, { style: [styles2.stopGlyph, { backgroundColor: colors.textSecondary }] })
588
+ ) : null
589
+ )
590
+ );
591
+ }
592
+ var styles2 = StyleSheet.create({
593
+ // `box-none` above + `position: absolute` here is what lets touches
594
+ // outside the pill itself reach the app underneath.
595
+ wrapper: {
596
+ position: "absolute",
597
+ left: 12,
598
+ right: 12,
599
+ zIndex: 999,
600
+ alignItems: "center"
601
+ },
602
+ pill: {
603
+ flexDirection: "row",
604
+ alignItems: "center",
605
+ gap: 10,
606
+ maxWidth: "100%",
607
+ paddingLeft: 14,
608
+ paddingRight: 8,
609
+ paddingVertical: 10,
610
+ borderRadius: 24,
611
+ borderWidth: StyleSheet.hairlineWidth,
612
+ shadowColor: "#000",
613
+ shadowOffset: { width: 0, height: 2 },
614
+ shadowOpacity: 0.18,
615
+ shadowRadius: 8,
616
+ elevation: 8
617
+ },
618
+ dot: { width: 8, height: 8, borderRadius: 4 },
619
+ label: { flexShrink: 1, fontSize: 14, fontWeight: "500" },
620
+ stop: {
621
+ width: 26,
622
+ height: 26,
623
+ borderRadius: 13,
624
+ borderWidth: StyleSheet.hairlineWidth,
625
+ alignItems: "center",
626
+ justifyContent: "center"
627
+ },
628
+ stopGlyph: { width: 9, height: 9, borderRadius: 1.5 }
629
+ });
630
+ var PADDING = 6;
631
+ var CAPTION_HEIGHT = 28;
632
+ var CAPTION_GAP = 8;
633
+ function SpotlightOverlay({
634
+ rect,
635
+ seq,
636
+ label,
637
+ color,
638
+ captionBackground,
639
+ captionColor,
640
+ fontFamily,
641
+ window: win
642
+ }) {
643
+ const pulse = useRef(new Animated.Value(0)).current;
644
+ useEffect(() => {
645
+ pulse.setValue(0);
646
+ Animated.timing(pulse, {
647
+ toValue: 1,
648
+ duration: 220,
649
+ useNativeDriver: true
650
+ }).start();
651
+ }, [seq, pulse]);
652
+ if (!isRectOnScreen(rect, win)) return null;
653
+ const frame = {
654
+ left: rect.x - PADDING,
655
+ top: rect.y - PADDING,
656
+ width: rect.width + PADDING * 2,
657
+ height: rect.height + PADDING * 2
658
+ };
659
+ const above = frame.top - CAPTION_GAP - CAPTION_HEIGHT >= 0;
660
+ const captionTop = above ? frame.top - CAPTION_GAP - CAPTION_HEIGHT : frame.top + frame.height + CAPTION_GAP;
661
+ const captionFits = captionTop + CAPTION_HEIGHT <= win.height;
662
+ return /* @__PURE__ */ React8__default.createElement(
663
+ View,
664
+ {
665
+ ...APPILOTS_SKIP_PROPS,
666
+ pointerEvents: "none",
667
+ style: StyleSheet.absoluteFill,
668
+ testID: "appilots-spotlight"
669
+ },
670
+ /* @__PURE__ */ React8__default.createElement(
671
+ Animated.View,
672
+ {
673
+ ...APPILOTS_SKIP_PROPS,
674
+ pointerEvents: "none",
675
+ style: [
676
+ styles3.ring,
677
+ frame,
678
+ { borderColor: color },
679
+ {
680
+ opacity: pulse,
681
+ // Settles inward: the ring arrives slightly large and snaps
682
+ // to the control, which reads as "this one" rather than as a
683
+ // box that was always there.
684
+ transform: [
685
+ {
686
+ scale: pulse.interpolate({ inputRange: [0, 1], outputRange: [1.08, 1] })
687
+ }
688
+ ]
689
+ }
690
+ ]
691
+ }
692
+ ),
693
+ label && captionFits ? /* @__PURE__ */ React8__default.createElement(
694
+ Animated.View,
695
+ {
696
+ ...APPILOTS_SKIP_PROPS,
697
+ pointerEvents: "none",
698
+ style: [
699
+ styles3.caption,
700
+ {
701
+ top: captionTop,
702
+ // Clamped so a control at either edge does not push the
703
+ // caption off screen.
704
+ left: Math.max(8, Math.min(frame.left, win.width - 8)),
705
+ maxWidth: win.width - 16,
706
+ backgroundColor: captionBackground,
707
+ opacity: pulse
708
+ }
709
+ ]
710
+ },
711
+ /* @__PURE__ */ React8__default.createElement(
712
+ Text,
713
+ {
714
+ testID: "appilots-spotlight-label",
715
+ numberOfLines: 1,
716
+ style: [styles3.captionText, { color: captionColor, fontFamily }]
717
+ },
718
+ label
719
+ )
720
+ ) : null
721
+ );
722
+ }
723
+ var styles3 = StyleSheet.create({
724
+ ring: {
725
+ position: "absolute",
726
+ borderWidth: 2.5,
727
+ borderRadius: 10
728
+ },
729
+ caption: {
730
+ position: "absolute",
731
+ height: CAPTION_HEIGHT,
732
+ justifyContent: "center",
733
+ paddingHorizontal: 10,
734
+ borderRadius: 8
735
+ },
736
+ captionText: { fontSize: 13, fontWeight: "500" }
737
+ });
738
+ var NEVER = () => () => {
739
+ };
740
+ var NULL_TARGET = () => null;
741
+ function useSpotlight(enabled) {
742
+ const subscribe = useCallback(
743
+ (onChange) => enabled ? subscribeSpotlight(onChange) : NEVER(),
744
+ [enabled]
745
+ );
746
+ return useSyncExternalStore(subscribe, enabled ? getSpotlightTarget : NULL_TARGET, NULL_TARGET);
747
+ }
748
+
749
+ // src/components/pillStatus.ts
750
+ function resolvePillStatus(actions, busy, strings) {
751
+ for (let i = actions.length - 1; i >= 0; i--) {
752
+ const action = actions[i];
753
+ if (action.status === "executing") return describeAction(action, strings.locale);
754
+ }
755
+ if (!busy) {
756
+ for (let i = actions.length - 1; i >= 0; i--) {
757
+ const action = actions[i];
758
+ if (action.status === "completed" || action.status === "failed") {
759
+ return describeAction(action, strings.locale);
760
+ }
761
+ }
762
+ return { label: strings.done, state: "success" };
763
+ }
764
+ return { label: strings.working, state: "running" };
765
+ }
766
+
767
+ // src/components/speech.ts
768
+ function isSpeechHost(value) {
769
+ if (!value || typeof value !== "object") return false;
770
+ const candidate = value;
771
+ return typeof candidate.start === "function" && typeof candidate.stop === "function" && typeof candidate.cancel === "function";
772
+ }
773
+ function useVoiceInput({
774
+ host,
775
+ locale,
776
+ onTranscript
777
+ }) {
778
+ const [state, setState] = useState("idle");
779
+ const [partial, setPartial] = useState("");
780
+ const mounted = useRef(true);
781
+ useEffect(() => {
782
+ mounted.current = true;
783
+ return () => {
784
+ mounted.current = false;
785
+ };
786
+ }, []);
787
+ const onTranscriptRef = useRef(onTranscript);
788
+ onTranscriptRef.current = onTranscript;
789
+ const stateRef = useRef("idle");
790
+ stateRef.current = state;
791
+ useEffect(() => {
792
+ return () => {
793
+ if (stateRef.current === "listening" || stateRef.current === "starting") {
794
+ void host?.cancel().catch(() => {
795
+ });
796
+ }
797
+ };
798
+ }, [host]);
799
+ const start = useCallback(() => {
800
+ if (!host) return;
801
+ if (state !== "idle" && state !== "error") return;
802
+ setState("starting");
803
+ setPartial("");
804
+ host.start({ locale }).then(() => {
805
+ if (!mounted.current) return;
806
+ setState("listening");
807
+ }).catch((err) => {
808
+ appilotsDebugWarn("useVoiceInput: start failed", err);
809
+ if (!mounted.current) return;
810
+ setState("error");
811
+ });
812
+ }, [host, locale, state]);
813
+ const stop = useCallback(() => {
814
+ if (!host) return;
815
+ if (state !== "listening" && state !== "starting") return;
816
+ setState("stopping");
817
+ host.stop().then((text) => {
818
+ if (!mounted.current) return;
819
+ setState("idle");
820
+ setPartial("");
821
+ const trimmed = text?.trim();
822
+ if (trimmed) onTranscriptRef.current(trimmed);
823
+ }).catch((err) => {
824
+ appilotsDebugWarn("useVoiceInput: stop failed", err);
825
+ if (!mounted.current) return;
826
+ setState("error");
827
+ setPartial("");
828
+ });
829
+ }, [host, state]);
830
+ const cancel = useCallback(() => {
831
+ if (!host) return;
832
+ setPartial("");
833
+ setState("idle");
834
+ void host.cancel().catch((err) => appilotsDebugWarn("useVoiceInput: cancel failed", err));
835
+ }, [host]);
836
+ useEffect(() => {
837
+ if (!host?.onPartial) return;
838
+ if (state !== "listening") return;
839
+ const dispose = host.onPartial((text) => {
840
+ if (mounted.current) setPartial(text);
841
+ });
842
+ return dispose;
843
+ }, [host, state]);
844
+ return { state, partial, start, stop, cancel };
845
+ }
846
+
847
+ // src/components/experience.ts
848
+ var DEFAULT_EXPERIENCE = {
849
+ presence: "sheet",
850
+ spotlight: "off",
851
+ control: "agent",
852
+ entry: ["fab"],
853
+ input: ["text"]
854
+ };
855
+ function uniq(values) {
856
+ return values.filter((value, index) => values.indexOf(value) === index);
857
+ }
858
+ function resolveExperience(requested, caps) {
859
+ const downgrades = [];
860
+ let presence = requested?.presence ?? DEFAULT_EXPERIENCE.presence;
861
+ let spotlight = requested?.spotlight ?? DEFAULT_EXPERIENCE.spotlight;
862
+ const control = requested?.control ?? DEFAULT_EXPERIENCE.control;
863
+ const entry = uniq(requested?.entry ?? DEFAULT_EXPERIENCE.entry);
864
+ let input = uniq(requested?.input ?? DEFAULT_EXPERIENCE.input);
865
+ if (spotlight !== "off" && !caps.geometry) {
866
+ downgrades.push({
867
+ field: "spotlight",
868
+ from: spotlight,
869
+ to: "off",
870
+ reason: 'spotlight needs Fabric window geometry, which this app cannot report. Enable the new React Native architecture, or set spotlight:"off" to silence this.'
871
+ });
872
+ spotlight = "off";
873
+ }
874
+ if (spotlight !== "off" && presence === "sheet") {
875
+ downgrades.push({
876
+ field: "presence",
877
+ from: "sheet",
878
+ to: "pill",
879
+ reason: 'spotlight draws on the app, which the sheet covers. Set presence:"pill" or "hidden" explicitly to silence this.'
880
+ });
881
+ presence = "pill";
882
+ }
883
+ if (control === "shared" && presence === "sheet") {
884
+ downgrades.push({
885
+ field: "presence",
886
+ from: "sheet",
887
+ to: "pill",
888
+ 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.'
889
+ });
890
+ presence = "pill";
891
+ }
892
+ if (input.includes("voice") && !caps.speech) {
893
+ downgrades.push({
894
+ field: "input",
895
+ from: input.join("+"),
896
+ to: input.filter((mode) => mode !== "voice").join("+") || "text",
897
+ reason: 'input:"voice" needs a SpeechHost passed to <AppilotsChat speech={...} />. The SDK ships no speech engine of its own \u2014 see docs/experience.md.'
898
+ });
899
+ input = input.filter((mode) => mode !== "voice");
900
+ }
901
+ if (input.length === 0) input = ["text"];
902
+ return {
903
+ experience: { presence, spotlight, control, entry, input },
904
+ downgrades
905
+ };
906
+ }
907
+ function formatDowngrades(downgrades) {
908
+ return downgrades.map(
909
+ (d) => `[appilots] experience.${d.field}: "${d.from}" -> "${d.to}". ${d.reason}`
910
+ );
911
+ }
912
+
913
+ // src/components/presence.ts
914
+ function resolveSurface(presence, activity) {
915
+ if (presence === "sheet") return activity.open ? "sheet" : "none";
916
+ if (activity.awaitingUser) return "sheet";
917
+ if (activity.busy && (activity.acting || !activity.open)) return "pill";
918
+ if (activity.hostModalOpen && !activity.explicitlyOpened && (activity.open || activity.unread))
919
+ return "pill";
920
+ if (activity.open) return "sheet";
921
+ if (activity.unread) return "pill";
922
+ return "none";
923
+ }
924
+ function shouldReopenOnTurnEnd(presence) {
925
+ return presence === "pill";
926
+ }
927
+ function usesPill(presence) {
928
+ return presence !== "sheet";
929
+ }
458
930
  function HeadsetIcon({ size = 18, color = "#ffffff" }) {
459
- return /* @__PURE__ */ React7.createElement(Svg, { width: size, height: size, viewBox: "0 0 24 24", fill: "none" }, /* @__PURE__ */ React7.createElement(
931
+ return /* @__PURE__ */ React8.createElement(Svg, { width: size, height: size, viewBox: "0 0 24 24", fill: "none" }, /* @__PURE__ */ React8.createElement(
460
932
  Path,
461
933
  {
462
934
  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",
@@ -497,195 +969,6 @@ function buildChatTimeline(messages, actions) {
497
969
  }
498
970
  return timeline;
499
971
  }
500
-
501
- // src/components/markdownContent.ts
502
- var ORDERED_LINE = /^\s*(\d+)\.\s+(.+)$/;
503
- var BULLET_LINE = /^\s*[-*•]\s+(.+)$/;
504
- var HEADING_LINE = /^\s*(#{1,6})\s+(.+)$/;
505
- var FENCE_LINE = /^\s*```\s*([A-Za-z0-9+#_-]*)\s*$/;
506
- var INLINE_PATTERN = /\[([^\]\n]+)\]\((https?:\/\/[^\s)]+)\)|\*\*(.+?)\*\*|__(.+?)__|`([^`]+)`|\*(.+?)\*|_(.+?)_/g;
507
- var MIN_ENHANCE_LENGTH = 80;
508
- var SENTENCE_SPLIT = /[^.!?]+[.!?]+(?=\s+[A-ZÁÉÍÓÚÀÈÌÒÙÂÊÎÔÛÃÕÇÑ¿¡"“]|$)|[^.!?]+$/g;
509
- 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;
510
- var INCLUDE_LIST_SENTENCE = /^(.+?)\s+(?:incluem|inclui|include|includes|incluye|incluyen|such as|for example|par exemple|por exemplo)\s+(.+?)\.?\s*$/i;
511
- function looksLikeMarkdown(content) {
512
- if (!content) return false;
513
- 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);
514
- }
515
- function splitEnumeration(text) {
516
- const trimmed = text.trim().replace(/\.\s*$/, "");
517
- if (!trimmed) return [];
518
- const commaParts = trimmed.split(",").map((part) => part.trim()).filter(Boolean);
519
- if (commaParts.length <= 1) {
520
- const conjParts = trimmed.split(/\s+(?:e|and|y|ou|or)\s+/i).map((part) => part.trim());
521
- return conjParts.filter(Boolean);
522
- }
523
- const last = commaParts[commaParts.length - 1];
524
- const conjMatch = last.match(/^(.+?)\s+(?:e|and|y|ou|or)\s+(.+)$/i);
525
- if (conjMatch) {
526
- commaParts[commaParts.length - 1] = conjMatch[1].trim();
527
- commaParts.push(conjMatch[2].trim());
528
- }
529
- return commaParts.filter(Boolean);
530
- }
531
- function splitSentences(text) {
532
- const matches = text.match(SENTENCE_SPLIT);
533
- if (!matches) return [text.trim()];
534
- return matches.map((part) => part.trim()).filter(Boolean);
535
- }
536
- function formatCapabilitySentence(sentence) {
537
- const match = CAPABILITY_SENTENCE.exec(sentence.trim());
538
- if (!match) return null;
539
- const items = splitEnumeration(match[2] ?? "");
540
- if (items.length < 3) return null;
541
- const intro = `${match[1].trim()}:`;
542
- return `${intro}
543
- ${items.map((item) => `- ${item}`).join("\n")}`;
544
- }
545
- function formatIncludeListSentence(sentence) {
546
- const match = INCLUDE_LIST_SENTENCE.exec(sentence.trim());
547
- if (!match) return null;
548
- const intro = match[1].trim().replace(/[,:]\s*$/, "");
549
- const items = splitEnumeration(match[2] ?? "");
550
- if (items.length < 2) return null;
551
- return `${intro}:
552
- ${items.map((item) => `- ${item}`).join("\n")}`;
553
- }
554
- function formatReadableSentence(sentence) {
555
- return formatCapabilitySentence(sentence) ?? formatIncludeListSentence(sentence) ?? sentence.trim();
556
- }
557
- function enhancePlainAssistantContent(content) {
558
- if (!content?.trim() || looksLikeMarkdown(content)) return content;
559
- const trimmed = content.trim();
560
- if (trimmed.length < MIN_ENHANCE_LENGTH) return content;
561
- const sentences = splitSentences(trimmed);
562
- if (sentences.length === 0) return content;
563
- const blocks = sentences.map(formatReadableSentence);
564
- const enhanced = blocks.join("\n\n");
565
- return enhanced || content;
566
- }
567
- function needsWideChatLayout(content) {
568
- if (!content?.trim()) return false;
569
- const prepared = enhancePlainAssistantContent(content);
570
- return looksLikeMarkdown(prepared) || /\n\n+/.test(prepared);
571
- }
572
- function parseMarkdownBlocks(content) {
573
- const lines = content.replace(/\r\n/g, "\n").split("\n");
574
- const blocks = [];
575
- let paragraph = [];
576
- let bulletItems = [];
577
- let orderedItems = [];
578
- let codeLines = null;
579
- let codeLanguage;
580
- const flushParagraph = () => {
581
- if (paragraph.length === 0) return;
582
- blocks.push({ kind: "paragraph", text: paragraph.join("\n") });
583
- paragraph = [];
584
- };
585
- const flushBullets = () => {
586
- if (bulletItems.length === 0) return;
587
- blocks.push({ kind: "bullet", items: bulletItems });
588
- bulletItems = [];
589
- };
590
- const flushOrdered = () => {
591
- if (orderedItems.length === 0) return;
592
- blocks.push({ kind: "ordered", items: orderedItems });
593
- orderedItems = [];
594
- };
595
- const flushLists = () => {
596
- flushBullets();
597
- flushOrdered();
598
- };
599
- const flushCode = () => {
600
- if (codeLines === null) return;
601
- blocks.push({
602
- kind: "codeblock",
603
- code: codeLines.join("\n"),
604
- ...codeLanguage ? { language: codeLanguage } : {}
605
- });
606
- codeLines = null;
607
- codeLanguage = void 0;
608
- };
609
- for (const line of lines) {
610
- if (codeLines !== null) {
611
- if (/^\s*```\s*$/.test(line)) {
612
- flushCode();
613
- } else {
614
- codeLines.push(line);
615
- }
616
- continue;
617
- }
618
- const fence = FENCE_LINE.exec(line);
619
- if (fence) {
620
- flushParagraph();
621
- flushLists();
622
- codeLines = [];
623
- codeLanguage = fence[1] || void 0;
624
- continue;
625
- }
626
- const trimmed = line.trim();
627
- if (!trimmed) {
628
- flushParagraph();
629
- flushLists();
630
- continue;
631
- }
632
- const heading = HEADING_LINE.exec(line);
633
- if (heading) {
634
- flushParagraph();
635
- flushLists();
636
- const level = Math.min(heading[1].length, 3);
637
- blocks.push({ kind: "heading", level, text: heading[2].trim() });
638
- continue;
639
- }
640
- const ordered = ORDERED_LINE.exec(line);
641
- if (ordered) {
642
- flushParagraph();
643
- flushBullets();
644
- orderedItems.push(ordered[2].trim());
645
- continue;
646
- }
647
- const bullet = BULLET_LINE.exec(line);
648
- if (bullet) {
649
- flushParagraph();
650
- flushOrdered();
651
- bulletItems.push(bullet[1].trim());
652
- continue;
653
- }
654
- flushLists();
655
- paragraph.push(line);
656
- }
657
- flushParagraph();
658
- flushLists();
659
- flushCode();
660
- return blocks.length > 0 ? blocks : [{ kind: "paragraph", text: content }];
661
- }
662
- function parseInlineMarkdown(text) {
663
- if (!text) return [{ kind: "text", value: "" }];
664
- const segments = [];
665
- let lastIndex = 0;
666
- INLINE_PATTERN.lastIndex = 0;
667
- for (let match = INLINE_PATTERN.exec(text); match; match = INLINE_PATTERN.exec(text)) {
668
- if (match.index > lastIndex) {
669
- segments.push({ kind: "text", value: text.slice(lastIndex, match.index) });
670
- }
671
- if (match[1] && match[2]) {
672
- segments.push({ kind: "link", value: match[1].trim(), href: match[2] });
673
- } else if (match[3] || match[4]) {
674
- segments.push({ kind: "bold", value: (match[3] ?? match[4]).trim() });
675
- } else if (match[5]) {
676
- segments.push({ kind: "code", value: match[5] });
677
- } else if (match[6] || match[7]) {
678
- segments.push({ kind: "italic", value: (match[6] ?? match[7]).trim() });
679
- }
680
- lastIndex = INLINE_PATTERN.lastIndex;
681
- }
682
- if (lastIndex < text.length) {
683
- segments.push({ kind: "text", value: text.slice(lastIndex) });
684
- }
685
- return segments.length > 0 ? segments : [{ kind: "text", value: text }];
686
- }
687
-
688
- // src/components/ChatMarkdown.tsx
689
972
  function segmentKey(segment, index) {
690
973
  return `${segment.kind}-${index}-${segment.value.slice(0, 12)}`;
691
974
  }
@@ -695,17 +978,17 @@ function renderInlineSegments(text, theme, baseStyle) {
695
978
  const key = segmentKey(segment, index);
696
979
  switch (segment.kind) {
697
980
  case "bold":
698
- return /* @__PURE__ */ React7__default.createElement(Text, { key, style: [baseStyle, styles2.bold] }, segment.value);
981
+ return /* @__PURE__ */ React8__default.createElement(Text, { key, style: [baseStyle, styles4.bold] }, segment.value);
699
982
  case "italic":
700
- return /* @__PURE__ */ React7__default.createElement(Text, { key, style: [baseStyle, styles2.italic] }, segment.value);
983
+ return /* @__PURE__ */ React8__default.createElement(Text, { key, style: [baseStyle, styles4.italic] }, segment.value);
701
984
  case "code":
702
- return /* @__PURE__ */ React7__default.createElement(
985
+ return /* @__PURE__ */ React8__default.createElement(
703
986
  Text,
704
987
  {
705
988
  key,
706
989
  style: [
707
990
  baseStyle,
708
- styles2.code,
991
+ styles4.code,
709
992
  {
710
993
  backgroundColor: theme.codeBackground ?? (theme.variant === "user" ? "rgba(255,255,255,0.22)" : "rgba(0,0,0,0.06)"),
711
994
  color: theme.codeColor ?? theme.color
@@ -715,14 +998,14 @@ function renderInlineSegments(text, theme, baseStyle) {
715
998
  segment.value
716
999
  );
717
1000
  case "link":
718
- return /* @__PURE__ */ React7__default.createElement(
1001
+ return /* @__PURE__ */ React8__default.createElement(
719
1002
  Text,
720
1003
  {
721
1004
  key,
722
1005
  accessibilityRole: "link",
723
1006
  style: [
724
1007
  baseStyle,
725
- styles2.link,
1008
+ styles4.link,
726
1009
  {
727
1010
  color: theme.linkColor ?? (theme.variant === "user" ? theme.color : "#2563EB")
728
1011
  }
@@ -749,7 +1032,7 @@ function InlineMarkdown({
749
1032
  fontSize: theme.fontSize,
750
1033
  lineHeight: theme.lineHeight
751
1034
  };
752
- return /* @__PURE__ */ React7__default.createElement(Text, { style: baseStyle }, renderInlineSegments(text, theme, baseStyle));
1035
+ return /* @__PURE__ */ React8__default.createElement(Text, { style: baseStyle }, renderInlineSegments(text, theme, baseStyle));
753
1036
  }
754
1037
  function ChatMarkdown({ content, theme, contentWidth }) {
755
1038
  const prepared = enhancePlainAssistantContent(content);
@@ -759,29 +1042,29 @@ function ChatMarkdown({ content, theme, contentWidth }) {
759
1042
  fontSize: theme.fontSize,
760
1043
  lineHeight: theme.lineHeight
761
1044
  };
762
- const rootStyle = contentWidth ? [styles2.root, { width: contentWidth }] : styles2.root;
1045
+ const rootStyle = contentWidth ? [styles4.root, { width: contentWidth }] : styles4.root;
763
1046
  if (!looksLikeMarkdown(prepared)) {
764
1047
  const paragraphs = prepared.split(/\n\n+/).map((part) => part.trim()).filter(Boolean);
765
1048
  if (paragraphs.length > 1) {
766
- 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))));
1049
+ 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))));
767
1050
  }
768
- return /* @__PURE__ */ React7__default.createElement(Text, { style: baseTextStyle }, prepared);
1051
+ return /* @__PURE__ */ React8__default.createElement(Text, { style: baseTextStyle }, prepared);
769
1052
  }
770
1053
  const blocks = parseMarkdownBlocks(prepared);
771
- return /* @__PURE__ */ React7__default.createElement(View, { style: rootStyle }, blocks.map((block, blockIndex) => {
1054
+ return /* @__PURE__ */ React8__default.createElement(View, { style: rootStyle }, blocks.map((block, blockIndex) => {
772
1055
  if (block.kind === "paragraph") {
773
- 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 }));
1056
+ 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 }));
774
1057
  }
775
1058
  if (block.kind === "heading") {
776
1059
  const scale = block.level === 1 ? 1.25 : block.level === 2 ? 1.15 : 1.05;
777
1060
  const headingSize = Math.round((theme.fontSize ?? 15) * scale);
778
- return /* @__PURE__ */ React7__default.createElement(View, { key: `h-${blockIndex}`, style: blockIndex > 0 ? styles2.blockGap : void 0 }, /* @__PURE__ */ React7__default.createElement(
1061
+ return /* @__PURE__ */ React8__default.createElement(View, { key: `h-${blockIndex}`, style: blockIndex > 0 ? styles4.blockGap : void 0 }, /* @__PURE__ */ React8__default.createElement(
779
1062
  Text,
780
1063
  {
781
1064
  accessibilityRole: "header",
782
1065
  style: [
783
1066
  baseTextStyle,
784
- styles2.bold,
1067
+ styles4.bold,
785
1068
  { fontSize: headingSize, lineHeight: Math.round(headingSize * 1.35) }
786
1069
  ]
787
1070
  },
@@ -793,23 +1076,23 @@ function ChatMarkdown({ content, theme, contentWidth }) {
793
1076
  ));
794
1077
  }
795
1078
  if (block.kind === "codeblock") {
796
- return /* @__PURE__ */ React7__default.createElement(
1079
+ return /* @__PURE__ */ React8__default.createElement(
797
1080
  View,
798
1081
  {
799
1082
  key: `code-${blockIndex}`,
800
1083
  style: [
801
- styles2.codeBlock,
802
- blockIndex > 0 ? styles2.blockGap : void 0,
1084
+ styles4.codeBlock,
1085
+ blockIndex > 0 ? styles4.blockGap : void 0,
803
1086
  {
804
1087
  backgroundColor: theme.codeBackground ?? (theme.variant === "user" ? "rgba(255,255,255,0.18)" : "rgba(0,0,0,0.055)")
805
1088
  }
806
1089
  ]
807
1090
  },
808
- /* @__PURE__ */ React7__default.createElement(
1091
+ /* @__PURE__ */ React8__default.createElement(
809
1092
  Text,
810
1093
  {
811
1094
  style: [
812
- styles2.code,
1095
+ styles4.code,
813
1096
  {
814
1097
  color: theme.codeColor ?? theme.color,
815
1098
  fontSize: (theme.fontSize ?? 15) - 1,
@@ -822,28 +1105,28 @@ function ChatMarkdown({ content, theme, contentWidth }) {
822
1105
  );
823
1106
  }
824
1107
  if (block.kind === "bullet") {
825
- 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(
1108
+ 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(
826
1109
  Text,
827
1110
  {
828
1111
  key: `ul-${blockIndex}-${itemIndex}`,
829
- style: [baseTextStyle, itemIndex > 0 ? styles2.listItemGap : void 0]
1112
+ style: [baseTextStyle, itemIndex > 0 ? styles4.listItemGap : void 0]
830
1113
  },
831
1114
  "\u2022 ",
832
1115
  renderInlineSegments(item, theme, baseTextStyle)
833
1116
  )));
834
1117
  }
835
- 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(
1118
+ 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(
836
1119
  Text,
837
1120
  {
838
1121
  key: `ol-${blockIndex}-${itemIndex}`,
839
- style: [baseTextStyle, itemIndex > 0 ? styles2.listItemGap : void 0]
1122
+ style: [baseTextStyle, itemIndex > 0 ? styles4.listItemGap : void 0]
840
1123
  },
841
1124
  `${itemIndex + 1}. `,
842
1125
  renderInlineSegments(item, theme, baseTextStyle)
843
1126
  )));
844
1127
  }));
845
1128
  }
846
- var styles2 = StyleSheet.create({
1129
+ var styles4 = StyleSheet.create({
847
1130
  root: {
848
1131
  flexShrink: 1,
849
1132
  alignSelf: "stretch"
@@ -887,7 +1170,7 @@ function AppilotsThemeProvider({
887
1170
  () => mergeThemeTokens(resolveBaseTheme(mode, systemScheme), theme ?? void 0),
888
1171
  [mode, systemScheme, theme]
889
1172
  );
890
- return /* @__PURE__ */ React7__default.createElement(ThemeContext.Provider, { value: tokens }, children);
1173
+ return /* @__PURE__ */ React8__default.createElement(ThemeContext.Provider, { value: tokens }, children);
891
1174
  }
892
1175
  function useAppilotsTheme() {
893
1176
  return useContext(ThemeContext);
@@ -980,177 +1263,6 @@ var appilotsTheme = {
980
1263
  fromTailwind: appilotsThemeFromTailwind
981
1264
  };
982
1265
 
983
- // src/i18n/bundles/pt-BR.ts
984
- var ptBR = {
985
- chatTitle: "Assistente Appilots",
986
- clearButton: "Limpar",
987
- inputPlaceholder: "Digite uma mensagem...",
988
- emptyTitle: "Como posso ajudar?",
989
- emptySubtitle: "Navego, preencho formul\xE1rios e executo a\xE7\xF5es neste app.",
990
- thinking: "Pensando...",
991
- statusAnalyzing: "Analisando a tela...",
992
- statusWaitingApp: "Aguardando o app responder...",
993
- statusAdjusting: "Ajustando o plano...",
994
- poweredBy: "Powered by Appilots",
995
- approve: "Aprovar",
996
- reject: "Rejeitar",
997
- cancelledByUser: "Cancelado pelo usu\xE1rio",
998
- confirmDestructiveTitle: "A\xE7\xE3o destrutiva",
999
- confirmTitle: "Confirma\xE7\xE3o necess\xE1ria",
1000
- confirmMessage: 'Tem certeza que deseja "{action}"?',
1001
- confirmDestructiveApprove: "Sim, executar",
1002
- confirmApprove: "Confirmar",
1003
- confirmCancel: "Cancelar",
1004
- confirmThisAction: "esta a\xE7\xE3o",
1005
- unknownAction: "A\xE7\xE3o desconhecida",
1006
- somethingWentWrong: "Algo deu errado",
1007
- openChatA11y: "Abrir chat do assistente",
1008
- closeChatA11y: "Fechar chat do assistente",
1009
- stopGeneratingA11y: "Parar gera\xE7\xE3o",
1010
- talkToHumanA11y: "Falar com um atendente humano",
1011
- humanAgentLabel: "Atendente",
1012
- escalationRequested: "Certo! Chamei um atendente humano. Voc\xEA pode continuar usando o app normalmente enquanto isso.",
1013
- escalationConnected: "Um atendente entrou na conversa.",
1014
- escalationResolved: "O atendimento humano foi encerrado. Voltei a te ajudar por aqui.",
1015
- escalationOffer: "Se preferir, posso chamar uma pessoa para ajudar.",
1016
- escalationOfferChip: "Falar com um atendente",
1017
- escalationPendingBanner: "Aguardando um atendente...",
1018
- escalationActiveBanner: "Voc\xEA est\xE1 falando com um atendente"
1019
- };
1020
-
1021
- // src/i18n/bundles/en.ts
1022
- var en = {
1023
- chatTitle: "Appilots Assistant",
1024
- clearButton: "Clear",
1025
- inputPlaceholder: "Type a message...",
1026
- emptyTitle: "How can I help?",
1027
- emptySubtitle: "I navigate, fill forms, and run actions in this app.",
1028
- thinking: "Thinking...",
1029
- statusAnalyzing: "Reading the screen...",
1030
- statusWaitingApp: "Waiting for the app...",
1031
- statusAdjusting: "Adjusting the plan...",
1032
- poweredBy: "Powered by Appilots",
1033
- approve: "Approve",
1034
- reject: "Reject",
1035
- cancelledByUser: "Cancelled by user",
1036
- confirmDestructiveTitle: "Destructive action",
1037
- confirmTitle: "Confirmation needed",
1038
- confirmMessage: 'Are you sure you want to "{action}"?',
1039
- confirmDestructiveApprove: "Yes, do it",
1040
- confirmApprove: "Confirm",
1041
- confirmCancel: "Cancel",
1042
- confirmThisAction: "this action",
1043
- unknownAction: "Unknown action",
1044
- somethingWentWrong: "Something went wrong",
1045
- openChatA11y: "Open assistant chat",
1046
- closeChatA11y: "Close assistant chat",
1047
- stopGeneratingA11y: "Stop generating",
1048
- talkToHumanA11y: "Talk to a human agent",
1049
- humanAgentLabel: "Agent",
1050
- escalationRequested: "Done! I've called a human agent. You can keep using the app normally in the meantime.",
1051
- escalationConnected: "A human agent joined the conversation.",
1052
- escalationResolved: "The human handoff has ended. I'm back to help you here.",
1053
- escalationOffer: "If you prefer, I can bring in a person to help.",
1054
- escalationOfferChip: "Talk to a human",
1055
- escalationPendingBanner: "Waiting for a human agent...",
1056
- escalationActiveBanner: "You're talking to a human agent"
1057
- };
1058
-
1059
- // src/i18n/bundles/es.ts
1060
- var es = {
1061
- chatTitle: "Asistente Appilots",
1062
- clearButton: "Limpiar",
1063
- inputPlaceholder: "Escribe un mensaje...",
1064
- emptyTitle: "\xBFEn qu\xE9 puedo ayudarte?",
1065
- emptySubtitle: "Navego, relleno formularios y ejecuto acciones en esta app.",
1066
- thinking: "Pensando...",
1067
- statusAnalyzing: "Analizando la pantalla...",
1068
- statusWaitingApp: "Esperando a la app...",
1069
- statusAdjusting: "Ajustando el plan...",
1070
- poweredBy: "Hecho con Appilots",
1071
- approve: "Aprobar",
1072
- reject: "Rechazar",
1073
- cancelledByUser: "Cancelado por el usuario",
1074
- confirmDestructiveTitle: "Acci\xF3n destructiva",
1075
- confirmTitle: "Confirmaci\xF3n necesaria",
1076
- confirmMessage: '\xBFSeguro que quieres "{action}"?',
1077
- confirmDestructiveApprove: "S\xED, ejecutar",
1078
- confirmApprove: "Confirmar",
1079
- confirmCancel: "Cancelar",
1080
- confirmThisAction: "esta acci\xF3n",
1081
- unknownAction: "Acci\xF3n desconocida",
1082
- somethingWentWrong: "Algo sali\xF3 mal",
1083
- openChatA11y: "Abrir chat del asistente",
1084
- closeChatA11y: "Cerrar chat del asistente",
1085
- stopGeneratingA11y: "Detener generaci\xF3n",
1086
- talkToHumanA11y: "Hablar con un agente humano",
1087
- humanAgentLabel: "Agente",
1088
- escalationRequested: "\xA1Listo! Llam\xE9 a un agente humano. Mientras tanto puedes seguir usando la app con normalidad.",
1089
- escalationConnected: "Un agente humano se uni\xF3 a la conversaci\xF3n.",
1090
- escalationResolved: "La atenci\xF3n humana termin\xF3. Vuelvo a ayudarte por aqu\xED.",
1091
- escalationOffer: "Si prefieres, puedo llamar a una persona para ayudarte.",
1092
- escalationOfferChip: "Hablar con un agente",
1093
- escalationPendingBanner: "Esperando a un agente...",
1094
- escalationActiveBanner: "Est\xE1s hablando con un agente humano"
1095
- };
1096
- var BUNDLES = {
1097
- "pt-BR": ptBR,
1098
- en,
1099
- es
1100
- };
1101
- var DEFAULT_LOCALE = "en";
1102
- function detectDeviceLocale() {
1103
- try {
1104
- let raw;
1105
- if (Platform.OS === "ios") {
1106
- const settings = NativeModules?.SettingsManager?.settings;
1107
- raw = settings?.AppleLocale || (Array.isArray(settings?.AppleLanguages) ? settings?.AppleLanguages?.[0] : void 0);
1108
- } else if (Platform.OS === "android") {
1109
- raw = NativeModules?.I18nManager?.localeIdentifier;
1110
- }
1111
- if (!raw || typeof raw !== "string") return null;
1112
- const lower = raw.toLowerCase().replace("_", "-");
1113
- if (lower.startsWith("pt")) return "pt-BR";
1114
- if (lower.startsWith("es")) return "es";
1115
- if (lower.startsWith("en")) return "en";
1116
- return null;
1117
- } catch {
1118
- return null;
1119
- }
1120
- }
1121
- function resolveLocale(prop) {
1122
- if (prop && BUNDLES[prop]) return prop;
1123
- const detected = detectDeviceLocale();
1124
- if (detected && BUNDLES[detected]) return detected;
1125
- return DEFAULT_LOCALE;
1126
- }
1127
- var I18nContext = createContext(null);
1128
- function AppilotsI18nProvider({
1129
- locale,
1130
- overrides,
1131
- children
1132
- }) {
1133
- const value = useMemo(() => {
1134
- const resolved = resolveLocale(locale ?? null);
1135
- const merged = overrides ? { ...BUNDLES[resolved], ...overrides } : BUNDLES[resolved];
1136
- return {
1137
- locale: resolved,
1138
- strings: merged,
1139
- t: (key) => merged[key]
1140
- };
1141
- }, [locale, overrides]);
1142
- return /* @__PURE__ */ React7__default.createElement(I18nContext.Provider, { value }, children);
1143
- }
1144
- function useAppilotsI18n() {
1145
- const ctx = useContext(I18nContext);
1146
- if (ctx) return ctx;
1147
- return {
1148
- locale: DEFAULT_LOCALE,
1149
- strings: BUNDLES[DEFAULT_LOCALE],
1150
- t: (key) => BUNDLES[DEFAULT_LOCALE][key]
1151
- };
1152
- }
1153
-
1154
1266
  // src/components/AppilotsChat.tsx
1155
1267
  function translateLegacyTheme(legacy) {
1156
1268
  const out = {};
@@ -1181,31 +1293,35 @@ var { height: SCREEN_HEIGHT, width: SCREEN_WIDTH } = Dimensions.get("window");
1181
1293
  var CHAT_HORIZONTAL_PADDING = 16;
1182
1294
  var BUBBLE_HORIZONTAL_PADDING = 14;
1183
1295
  var ASSISTANT_CONTENT_WIDTH = SCREEN_WIDTH - CHAT_HORIZONTAL_PADDING * 2 - BUBBLE_HORIZONTAL_PADDING * 2;
1184
- var APPILOTS_SKIP_PROPS = {
1185
- __appilotsSkip: true,
1186
- __appilotsInternal: true
1187
- };
1188
1296
  function AppilotsChat(props) {
1189
1297
  const { degraded } = useAppilotsContext();
1190
1298
  if (degraded) return null;
1191
- return /* @__PURE__ */ React7__default.createElement(AppilotsErrorBoundary, { surface: "chat", fallback: null }, /* @__PURE__ */ React7__default.createElement(AppilotsChatSurface, { ...props }));
1299
+ return /* @__PURE__ */ React8__default.createElement(AppilotsErrorBoundary, { surface: "chat", fallback: null }, /* @__PURE__ */ React8__default.createElement(AppilotsChatSurface, { ...props }));
1192
1300
  }
1193
1301
  function AppilotsChatSurface(props) {
1302
+ const [conversationLocale, setConversationLocale] = useState(null);
1194
1303
  const { remotePersonalization } = useAppilotsContext();
1195
1304
  const themeProp = useMemo(() => {
1196
1305
  if (!props.theme) return void 0;
1197
1306
  return isLegacyTheme(props.theme) ? translateLegacyTheme(props.theme) : props.theme;
1198
1307
  }, [props.theme]);
1199
- return /* @__PURE__ */ React7__default.createElement(
1308
+ return /* @__PURE__ */ React8__default.createElement(
1200
1309
  AppilotsThemeProvider,
1201
1310
  {
1202
1311
  theme: resolveTheme(themeProp, remotePersonalization),
1203
1312
  mode: resolveThemeMode(props.themeMode, remotePersonalization)
1204
1313
  },
1205
- /* @__PURE__ */ React7__default.createElement(AppilotsI18nProvider, { locale: resolveLocaleProp(props.locale, remotePersonalization) }, /* @__PURE__ */ React7__default.createElement(AppilotsChatInner, { ...props }))
1314
+ /* @__PURE__ */ React8__default.createElement(
1315
+ AppilotsI18nProvider,
1316
+ {
1317
+ locale: conversationLocale ?? resolveLocaleProp(props.locale, remotePersonalization)
1318
+ },
1319
+ /* @__PURE__ */ React8__default.createElement(AppilotsChatInner, { ...props, onReplyLocaleChange: setConversationLocale })
1320
+ )
1206
1321
  );
1207
1322
  }
1208
1323
  function AppilotsChatInner({
1324
+ onReplyLocaleChange,
1209
1325
  visible: controlledVisible,
1210
1326
  onClose,
1211
1327
  placeholder,
@@ -1221,14 +1337,22 @@ function AppilotsChatInner({
1221
1337
  triggerButton = "default",
1222
1338
  triggerIcon,
1223
1339
  triggerButtonColor: triggerButtonColorProp,
1224
- triggerButtonImage: triggerButtonImageProp
1340
+ triggerButtonImage: triggerButtonImageProp,
1341
+ experience: experienceProp,
1342
+ speech: speechProp
1225
1343
  }) {
1226
1344
  const theme = useAppilotsTheme();
1227
- const { strings, t } = useAppilotsI18n();
1345
+ const { strings, t, locale: resolvedLocale } = useAppilotsI18n();
1228
1346
  const {
1229
1347
  messages,
1348
+ mission,
1349
+ pauseMission,
1350
+ resumeMission,
1351
+ cancelMission,
1352
+ replyLocale,
1230
1353
  isLoading,
1231
1354
  loadingStatusKey,
1355
+ hasStartedActing,
1232
1356
  sendMessage,
1233
1357
  cancelMessage,
1234
1358
  clearMessages,
@@ -1243,6 +1367,9 @@ function AppilotsChatInner({
1243
1367
  offer: t("escalationOffer")
1244
1368
  }
1245
1369
  });
1370
+ useEffect(() => {
1371
+ onReplyLocaleChange(replyLocale);
1372
+ }, [replyLocale, onReplyLocaleChange]);
1246
1373
  const { pendingActions, executingActions, completedActions, approveAction, rejectAction } = useAppilotsActions({ autoExecute });
1247
1374
  const { emit, remotePersonalization } = useAppilotsContext();
1248
1375
  const {
@@ -1268,7 +1395,7 @@ function AppilotsChatInner({
1268
1395
  );
1269
1396
  const allActions = [...pendingActions, ...executingActions, ...completedActions];
1270
1397
  const timeline = buildChatTimeline(messages, allActions);
1271
- const lastAssistantMessageId = React7__default.useMemo(() => {
1398
+ const lastAssistantMessageId = React8__default.useMemo(() => {
1272
1399
  for (let i = messages.length - 1; i >= 0; i--) {
1273
1400
  if (messages[i].role === "assistant") return messages[i].id;
1274
1401
  }
@@ -1279,7 +1406,65 @@ function AppilotsChatInner({
1279
1406
  const [inputText, setInputText] = useState("");
1280
1407
  const flatListRef = useRef(null);
1281
1408
  const slideAnim = useRef(new Animated.Value(SCREEN_HEIGHT)).current;
1282
- const isVisible = mode === "inline" ? true : controlledVisible !== void 0 ? controlledVisible : internalVisible;
1409
+ const wantsSheet = mode === "inline" ? true : controlledVisible !== void 0 ? controlledVisible : internalVisible;
1410
+ const speechHost = isSpeechHost(speechProp) ? speechProp : null;
1411
+ const hasGeometry = useSyncExternalStore(
1412
+ subscribeGeometryHost,
1413
+ () => geometryAvailability() !== "unavailable",
1414
+ () => false
1415
+ );
1416
+ const capabilities = useMemo(
1417
+ () => ({ geometry: hasGeometry, speech: speechHost !== null }),
1418
+ [hasGeometry, speechHost]
1419
+ );
1420
+ const { experience, downgrades } = useMemo(
1421
+ () => resolveExperience(experienceProp, capabilities),
1422
+ [experienceProp, capabilities]
1423
+ );
1424
+ const presence = mode === "inline" ? "sheet" : experience.presence;
1425
+ useEffect(() => {
1426
+ if (!__DEV__ || downgrades.length === 0) return;
1427
+ if (!isGeometryResolved()) return;
1428
+ for (const line of formatDowngrades(downgrades)) {
1429
+ console.warn(line);
1430
+ }
1431
+ }, [downgrades, hasGeometry]);
1432
+ const [unreadResult, setUnreadResult] = useState(false);
1433
+ const [explicitlyOpened, setExplicitlyOpened] = useState(false);
1434
+ const requireApproval = useCallback(
1435
+ (action) => {
1436
+ if (action.status !== "pending") return false;
1437
+ if (action.type === "confirm") return true;
1438
+ return !autoExecute;
1439
+ },
1440
+ [autoExecute]
1441
+ );
1442
+ const awaitingUser = pendingActions.some((action) => requireApproval(action)) || escalation?.status === "pending" || escalation?.status === "active";
1443
+ const busy = isLoading || executingActions.length > 0;
1444
+ const acting = busy && hasStartedActing;
1445
+ const hostModalOpen = useHostModalOpen(
1446
+ usesPill(presence) && (busy || wantsSheet || unreadResult)
1447
+ );
1448
+ const surface = resolveSurface(presence, {
1449
+ busy,
1450
+ acting,
1451
+ awaitingUser,
1452
+ open: wantsSheet,
1453
+ unread: unreadResult,
1454
+ hostModalOpen,
1455
+ explicitlyOpened
1456
+ });
1457
+ const isVisible = mode === "inline" ? true : surface === "sheet";
1458
+ const spotlight = useSpotlight(experience.spotlight !== "off");
1459
+ useEffect(() => {
1460
+ setSpotlightMode(experience.spotlight);
1461
+ return () => setSpotlightMode("off");
1462
+ }, [experience.spotlight]);
1463
+ const { label: pillLabel, state: pillState } = surface === "pill" ? resolvePillStatus(allActions, busy, {
1464
+ working: strings.pillWorking,
1465
+ done: strings.pillDone,
1466
+ locale: resolvedLocale
1467
+ }) : { label: "", state: "pending" };
1283
1468
  const showSuggestedPrompts = shouldShowSuggestedPrompts({
1284
1469
  isVisible,
1285
1470
  messageCount: messages.length,
@@ -1290,14 +1475,38 @@ function AppilotsChatInner({
1290
1475
  limit: 3
1291
1476
  });
1292
1477
  const openChat = useCallback(() => {
1478
+ setExplicitlyOpened(true);
1293
1479
  setInternalVisible(true);
1294
1480
  emit({ type: "chat:open", timestamp: Date.now(), data: {} });
1295
1481
  }, [emit]);
1296
1482
  const closeChat = useCallback(() => {
1483
+ setExplicitlyOpened(false);
1297
1484
  setInternalVisible(false);
1298
1485
  onClose?.();
1299
1486
  emit({ type: "chat:close", timestamp: Date.now(), data: {} });
1300
1487
  }, [onClose, emit]);
1488
+ useEffect(() => {
1489
+ if (acting) setExplicitlyOpened(false);
1490
+ if (presence === "hidden" && acting) setInternalVisible(false);
1491
+ }, [presence, acting]);
1492
+ const wasActingRef = useRef(false);
1493
+ useEffect(() => {
1494
+ if (!usesPill(presence)) {
1495
+ wasActingRef.current = acting;
1496
+ return;
1497
+ }
1498
+ if (acting) {
1499
+ wasActingRef.current = true;
1500
+ return;
1501
+ }
1502
+ if (!wasActingRef.current) return;
1503
+ wasActingRef.current = false;
1504
+ if (shouldReopenOnTurnEnd(presence)) setInternalVisible(true);
1505
+ else setUnreadResult(true);
1506
+ }, [acting, presence]);
1507
+ useEffect(() => {
1508
+ if (surface === "sheet") setUnreadResult(false);
1509
+ }, [surface]);
1301
1510
  useEffect(() => {
1302
1511
  if (mode === "inline") return;
1303
1512
  Animated.spring(slideAnim, {
@@ -1312,6 +1521,12 @@ function AppilotsChatInner({
1312
1521
  setTimeout(() => flatListRef.current?.scrollToEnd({ animated: true }), 100);
1313
1522
  }
1314
1523
  }, [messages.length]);
1524
+ const voiceEnabled = experience.input.includes("voice");
1525
+ const voice = useVoiceInput({
1526
+ host: voiceEnabled ? speechHost : null,
1527
+ locale: resolvedLocale,
1528
+ onTranscript: sendMessage
1529
+ });
1315
1530
  const handleSend = useCallback(() => {
1316
1531
  if (!inputText.trim()) return;
1317
1532
  sendMessage(inputText.trim());
@@ -1321,11 +1536,11 @@ function AppilotsChatInner({
1321
1536
  ({ item }) => {
1322
1537
  if (item.role === "system") {
1323
1538
  const isOffer = item.metadata?.escalationOffer === true;
1324
- return /* @__PURE__ */ React7__default.createElement(View, { style: styles3.systemMessageContainer }, /* @__PURE__ */ React7__default.createElement(
1539
+ return /* @__PURE__ */ React8__default.createElement(View, { style: styles5.systemMessageContainer }, /* @__PURE__ */ React8__default.createElement(
1325
1540
  Text,
1326
1541
  {
1327
1542
  style: [
1328
- styles3.systemMessageText,
1543
+ styles5.systemMessageText,
1329
1544
  {
1330
1545
  color: theme.colors.textSecondary,
1331
1546
  fontFamily: theme.typography.fontFamily,
@@ -1334,13 +1549,13 @@ function AppilotsChatInner({
1334
1549
  ]
1335
1550
  },
1336
1551
  item.content
1337
- ), isOffer && !escalation && /* @__PURE__ */ React7__default.createElement(
1552
+ ), isOffer && !escalation && /* @__PURE__ */ React8__default.createElement(
1338
1553
  TouchableOpacity,
1339
1554
  {
1340
1555
  ...APPILOTS_SKIP_PROPS,
1341
1556
  style: [
1342
- styles3.chip,
1343
- styles3.escalationOfferChip,
1557
+ styles5.chip,
1558
+ styles5.escalationOfferChip,
1344
1559
  {
1345
1560
  backgroundColor: theme.colors.surface,
1346
1561
  borderColor: theme.colors.border,
@@ -1353,12 +1568,12 @@ function AppilotsChatInner({
1353
1568
  accessibilityLabel: strings.talkToHumanA11y,
1354
1569
  testID: "escalation-offer-chip"
1355
1570
  },
1356
- /* @__PURE__ */ React7__default.createElement(HeadsetIcon, { size: 14, color: theme.colors.textSecondary }),
1357
- /* @__PURE__ */ React7__default.createElement(
1571
+ /* @__PURE__ */ React8__default.createElement(HeadsetIcon, { size: 14, color: theme.colors.textSecondary }),
1572
+ /* @__PURE__ */ React8__default.createElement(
1358
1573
  Text,
1359
1574
  {
1360
1575
  style: [
1361
- styles3.chipText,
1576
+ styles5.chipText,
1362
1577
  {
1363
1578
  color: theme.colors.text,
1364
1579
  fontFamily: theme.typography.fontFamily,
@@ -1374,11 +1589,11 @@ function AppilotsChatInner({
1374
1589
  const isHumanAgent = item.role === "human_agent";
1375
1590
  const useWideAssistantLayout = !isUser && needsWideChatLayout(item.content);
1376
1591
  if (isHumanAgent) {
1377
- 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(
1592
+ 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(
1378
1593
  Text,
1379
1594
  {
1380
1595
  style: [
1381
- styles3.humanAgentLabel,
1596
+ styles5.humanAgentLabel,
1382
1597
  {
1383
1598
  color: theme.colors.textSecondary,
1384
1599
  fontFamily: theme.typography.fontFamily
@@ -1386,17 +1601,17 @@ function AppilotsChatInner({
1386
1601
  ]
1387
1602
  },
1388
1603
  escalation?.operatorName ?? strings.humanAgentLabel
1389
- )), /* @__PURE__ */ React7__default.createElement(
1604
+ )), /* @__PURE__ */ React8__default.createElement(
1390
1605
  View,
1391
1606
  {
1392
1607
  accessibilityLabel: item.content,
1393
1608
  style: [
1394
- styles3.messageBubble,
1395
- styles3.assistantBubble,
1609
+ styles5.messageBubble,
1610
+ styles5.assistantBubble,
1396
1611
  { backgroundColor: theme.colors.surface }
1397
1612
  ]
1398
1613
  },
1399
- /* @__PURE__ */ React7__default.createElement(
1614
+ /* @__PURE__ */ React8__default.createElement(
1400
1615
  ChatMarkdown,
1401
1616
  {
1402
1617
  content: item.content,
@@ -1411,21 +1626,21 @@ function AppilotsChatInner({
1411
1626
  )
1412
1627
  ));
1413
1628
  }
1414
- return /* @__PURE__ */ React7__default.createElement(
1629
+ return /* @__PURE__ */ React8__default.createElement(
1415
1630
  View,
1416
1631
  {
1417
1632
  testID: isUser ? void 0 : item.id === lastAssistantMessageId ? "assistant-last-message" : "assistant-message",
1418
1633
  accessibilityLabel: isUser ? void 0 : item.content,
1419
1634
  style: [
1420
- styles3.messageBubble,
1421
- isUser ? [styles3.userBubble, { backgroundColor: theme.colors.primary }] : [
1422
- styles3.assistantBubble,
1423
- useWideAssistantLayout && styles3.assistantBubbleWide,
1635
+ styles5.messageBubble,
1636
+ isUser ? [styles5.userBubble, { backgroundColor: theme.colors.primary }] : [
1637
+ styles5.assistantBubble,
1638
+ useWideAssistantLayout && styles5.assistantBubbleWide,
1424
1639
  { backgroundColor: theme.colors.surface }
1425
1640
  ]
1426
1641
  ]
1427
1642
  },
1428
- /* @__PURE__ */ React7__default.createElement(
1643
+ /* @__PURE__ */ React8__default.createElement(
1429
1644
  ChatMarkdown,
1430
1645
  {
1431
1646
  content: item.content,
@@ -1443,19 +1658,12 @@ function AppilotsChatInner({
1443
1658
  },
1444
1659
  [theme, lastAssistantMessageId, escalation, requestHuman, strings]
1445
1660
  );
1446
- const requireApproval = useCallback(
1447
- (action) => {
1448
- if (action.status !== "pending") return false;
1449
- if (action.type === "confirm") return true;
1450
- return !autoExecute;
1451
- },
1452
- [autoExecute]
1453
- );
1454
1661
  const renderBreadcrumb = useCallback(
1455
- (actions) => /* @__PURE__ */ React7__default.createElement(
1662
+ (actions) => /* @__PURE__ */ React8__default.createElement(
1456
1663
  ActionBreadcrumb,
1457
1664
  {
1458
1665
  actions,
1666
+ locale: resolvedLocale,
1459
1667
  primaryColor: theme.colors.primary,
1460
1668
  textColor: theme.colors.text,
1461
1669
  fontFamily: theme.typography.fontFamily,
@@ -1491,6 +1699,8 @@ function AppilotsChatInner({
1491
1699
  theme.colors.warning,
1492
1700
  theme.colors.error,
1493
1701
  theme.typography.fontFamily,
1702
+ strings,
1703
+ resolvedLocale,
1494
1704
  requireApproval,
1495
1705
  approveAction,
1496
1706
  rejectAction
@@ -1503,17 +1713,17 @@ function AppilotsChatInner({
1503
1713
  const showEmptySubtitle = resolvedEmptySubtitle.length > 0 && !(showSuggestedPrompts && suggestedPrompts.length > 0);
1504
1714
  const renderAvatar = () => {
1505
1715
  if (!assistantAvatar) {
1506
- return /* @__PURE__ */ React7__default.createElement(View, { style: [styles3.headerDot, { backgroundColor: theme.colors.primary }] });
1716
+ return /* @__PURE__ */ React8__default.createElement(View, { style: [styles5.headerDot, { backgroundColor: theme.colors.primary }] });
1507
1717
  }
1508
1718
  if (typeof assistantAvatar === "string") {
1509
- return /* @__PURE__ */ React7__default.createElement(Image, { source: { uri: assistantAvatar }, style: styles3.headerAvatar });
1719
+ return /* @__PURE__ */ React8__default.createElement(Image, { source: { uri: assistantAvatar }, style: styles5.headerAvatar });
1510
1720
  }
1511
1721
  if (typeof assistantAvatar === "number") {
1512
- return /* @__PURE__ */ React7__default.createElement(Image, { source: assistantAvatar, style: styles3.headerAvatar });
1722
+ return /* @__PURE__ */ React8__default.createElement(Image, { source: assistantAvatar, style: styles5.headerAvatar });
1513
1723
  }
1514
- return /* @__PURE__ */ React7__default.createElement(View, { style: styles3.headerAvatarSlot }, assistantAvatar);
1724
+ return /* @__PURE__ */ React8__default.createElement(View, { style: styles5.headerAvatarSlot }, assistantAvatar);
1515
1725
  };
1516
- const showTrigger = mode === "bubble" && !isVisible && controlledVisible === void 0 && triggerButton !== "none";
1726
+ const showTrigger = mode === "bubble" && surface === "none" && experience.entry.includes("fab") && controlledVisible === void 0 && triggerButton !== "none";
1517
1727
  const fabPositionStyle = (() => {
1518
1728
  switch (position) {
1519
1729
  case "bottom-left":
@@ -1527,15 +1737,15 @@ function AppilotsChatInner({
1527
1737
  return { bottom: 24, right: 24 };
1528
1738
  }
1529
1739
  })();
1530
- const chatSurface = /* @__PURE__ */ React7__default.createElement(
1740
+ const chatSurface = /* @__PURE__ */ React8__default.createElement(
1531
1741
  View,
1532
1742
  {
1533
1743
  ...APPILOTS_SKIP_PROPS,
1534
1744
  style: [
1535
- styles3.chatContainer,
1536
- mode === "fullscreen" && styles3.chatContainerFullscreen,
1537
- mode === "sidebar" && styles3.chatContainerSidebar,
1538
- mode === "inline" && styles3.chatContainerInline,
1745
+ styles5.chatContainer,
1746
+ mode === "fullscreen" && styles5.chatContainerFullscreen,
1747
+ mode === "sidebar" && styles5.chatContainerSidebar,
1748
+ mode === "inline" && styles5.chatContainerInline,
1539
1749
  {
1540
1750
  backgroundColor: theme.colors.background,
1541
1751
  borderTopLeftRadius: theme.radii.lg,
@@ -1543,11 +1753,11 @@ function AppilotsChatInner({
1543
1753
  }
1544
1754
  ]
1545
1755
  },
1546
- /* @__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(
1756
+ /* @__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(
1547
1757
  Text,
1548
1758
  {
1549
1759
  style: [
1550
- styles3.headerTitle,
1760
+ styles5.headerTitle,
1551
1761
  {
1552
1762
  color: theme.colors.text,
1553
1763
  fontFamily: theme.typography.fontFamily,
@@ -1556,46 +1766,50 @@ function AppilotsChatInner({
1556
1766
  ]
1557
1767
  },
1558
1768
  resolvedHeaderTitle
1559
- )), /* @__PURE__ */ React7__default.createElement(View, { style: styles3.headerActions }, /* @__PURE__ */ React7__default.createElement(
1769
+ )), /* @__PURE__ */ React8__default.createElement(View, { style: styles5.headerActions }, /* @__PURE__ */ React8__default.createElement(
1560
1770
  TouchableOpacity,
1561
1771
  {
1562
1772
  ...APPILOTS_SKIP_PROPS,
1563
1773
  onPress: () => requestHuman("user_requested"),
1564
- style: styles3.headerButton,
1774
+ style: styles5.headerButton,
1565
1775
  accessibilityRole: "button",
1566
1776
  accessibilityLabel: strings.talkToHumanA11y,
1567
1777
  testID: "escalation-header-button"
1568
1778
  },
1569
- /* @__PURE__ */ React7__default.createElement(
1779
+ /* @__PURE__ */ React8__default.createElement(
1570
1780
  HeadsetIcon,
1571
1781
  {
1572
1782
  size: 16,
1573
1783
  color: escalation ? theme.colors.primary : theme.colors.textSecondary
1574
1784
  }
1575
1785
  )
1576
- ), /* @__PURE__ */ React7__default.createElement(
1786
+ ), /* @__PURE__ */ React8__default.createElement(
1577
1787
  TouchableOpacity,
1578
1788
  {
1579
1789
  ...APPILOTS_SKIP_PROPS,
1580
1790
  onPress: clearMessages,
1581
- style: styles3.headerButton
1791
+ style: styles5.headerButton,
1792
+ accessibilityRole: "button",
1793
+ testID: "assistant-clear"
1582
1794
  },
1583
- /* @__PURE__ */ React7__default.createElement(Text, { style: [styles3.headerButtonText, { color: theme.colors.textSecondary }] }, strings.clearButton)
1584
- ), mode !== "inline" && /* @__PURE__ */ React7__default.createElement(
1795
+ /* @__PURE__ */ React8__default.createElement(Text, { style: [styles5.headerButtonText, { color: theme.colors.textSecondary }] }, strings.clearButton)
1796
+ ), mode !== "inline" && /* @__PURE__ */ React8__default.createElement(
1585
1797
  TouchableOpacity,
1586
1798
  {
1587
1799
  ...APPILOTS_SKIP_PROPS,
1588
1800
  onPress: closeChat,
1589
- style: styles3.closeButton,
1590
- accessibilityLabel: strings.closeChatA11y
1801
+ style: styles5.closeButton,
1802
+ accessibilityRole: "button",
1803
+ accessibilityLabel: strings.closeChatA11y,
1804
+ testID: "assistant-close"
1591
1805
  },
1592
- /* @__PURE__ */ React7__default.createElement(Text, { style: [styles3.closeButtonText, { color: theme.colors.text }] }, "\u2715")
1806
+ /* @__PURE__ */ React8__default.createElement(Text, { style: [styles5.closeButtonText, { color: theme.colors.text }] }, "\u2715")
1593
1807
  ))),
1594
- escalation && /* @__PURE__ */ React7__default.createElement(
1808
+ escalation && /* @__PURE__ */ React8__default.createElement(
1595
1809
  View,
1596
1810
  {
1597
1811
  style: [
1598
- styles3.escalationBanner,
1812
+ styles5.escalationBanner,
1599
1813
  {
1600
1814
  borderBottomColor: theme.colors.border,
1601
1815
  backgroundColor: theme.colors.primary + "0d"
@@ -1603,12 +1817,12 @@ function AppilotsChatInner({
1603
1817
  ],
1604
1818
  testID: "escalation-banner"
1605
1819
  },
1606
- /* @__PURE__ */ React7__default.createElement(HeadsetIcon, { size: 12, color: theme.colors.primary }),
1607
- /* @__PURE__ */ React7__default.createElement(
1820
+ /* @__PURE__ */ React8__default.createElement(HeadsetIcon, { size: 12, color: theme.colors.primary }),
1821
+ /* @__PURE__ */ React8__default.createElement(
1608
1822
  Text,
1609
1823
  {
1610
1824
  style: [
1611
- styles3.escalationBannerText,
1825
+ styles5.escalationBannerText,
1612
1826
  {
1613
1827
  color: theme.colors.textSecondary,
1614
1828
  fontFamily: theme.typography.fontFamily,
@@ -1619,10 +1833,11 @@ function AppilotsChatInner({
1619
1833
  escalation.status === "pending" ? strings.escalationPendingBanner : strings.escalationActiveBanner
1620
1834
  )
1621
1835
  ),
1622
- /* @__PURE__ */ React7__default.createElement(
1836
+ /* @__PURE__ */ React8__default.createElement(
1623
1837
  FlatList,
1624
1838
  {
1625
1839
  ...APPILOTS_SKIP_PROPS,
1840
+ testID: "assistant-transcript",
1626
1841
  ref: flatListRef,
1627
1842
  data: timeline,
1628
1843
  keyExtractor: (item) => item.type === "message" ? item.data.id : item.key,
@@ -1635,34 +1850,34 @@ function AppilotsChatInner({
1635
1850
  }
1636
1851
  return null;
1637
1852
  },
1638
- contentContainerStyle: styles3.messageList,
1639
- ListEmptyComponent: /* @__PURE__ */ React7__default.createElement(View, { style: styles3.emptyState }, emptyStateIcon && isImageSource(emptyStateIcon) ? /* @__PURE__ */ React7__default.createElement(
1853
+ contentContainerStyle: styles5.messageList,
1854
+ ListEmptyComponent: /* @__PURE__ */ React8__default.createElement(View, { style: styles5.emptyState }, emptyStateIcon && isImageSource(emptyStateIcon) ? /* @__PURE__ */ React8__default.createElement(
1640
1855
  Image,
1641
1856
  {
1642
1857
  source: typeof emptyStateIcon === "string" ? { uri: emptyStateIcon } : emptyStateIcon,
1643
- style: styles3.emptyIconImage,
1858
+ style: styles5.emptyIconImage,
1644
1859
  resizeMode: "contain"
1645
1860
  }
1646
- ) : /* @__PURE__ */ React7__default.createElement(
1861
+ ) : /* @__PURE__ */ React8__default.createElement(
1647
1862
  View,
1648
1863
  {
1649
- style: [styles3.emptyIconBadge, { backgroundColor: theme.colors.primary + "14" }]
1864
+ style: [styles5.emptyIconBadge, { backgroundColor: theme.colors.primary + "14" }]
1650
1865
  },
1651
- emptyStateIcon ? /* @__PURE__ */ React7__default.createElement(Text, { style: styles3.emptyIconEmoji }, emptyStateIcon) : /* @__PURE__ */ React7__default.createElement(ChatIcon, { size: 30, color: theme.colors.primary })
1652
- ), /* @__PURE__ */ React7__default.createElement(
1866
+ emptyStateIcon ? /* @__PURE__ */ React8__default.createElement(Text, { style: styles5.emptyIconEmoji }, emptyStateIcon) : /* @__PURE__ */ React8__default.createElement(ChatIcon, { size: 30, color: theme.colors.primary })
1867
+ ), /* @__PURE__ */ React8__default.createElement(
1653
1868
  Text,
1654
1869
  {
1655
1870
  style: [
1656
- styles3.emptyTitle,
1871
+ styles5.emptyTitle,
1657
1872
  { color: theme.colors.text, fontFamily: theme.typography.fontFamily }
1658
1873
  ]
1659
1874
  },
1660
1875
  resolvedEmptyTitle
1661
- ), showEmptySubtitle && /* @__PURE__ */ React7__default.createElement(
1876
+ ), showEmptySubtitle && /* @__PURE__ */ React8__default.createElement(
1662
1877
  Text,
1663
1878
  {
1664
1879
  style: [
1665
- styles3.emptySubtitle,
1880
+ styles5.emptySubtitle,
1666
1881
  {
1667
1882
  color: theme.colors.textSecondary,
1668
1883
  fontFamily: theme.typography.fontFamily
@@ -1671,12 +1886,12 @@ function AppilotsChatInner({
1671
1886
  numberOfLines: 2
1672
1887
  },
1673
1888
  resolvedEmptySubtitle
1674
- ), showSuggestedPrompts && suggestedPrompts.length > 0 && /* @__PURE__ */ React7__default.createElement(View, { style: styles3.chipsContainer }, suggestedPrompts.map((prompt) => /* @__PURE__ */ React7__default.createElement(
1889
+ ), showSuggestedPrompts && suggestedPrompts.length > 0 && /* @__PURE__ */ React8__default.createElement(View, { style: styles5.chipsContainer, testID: "assistant-suggested-prompts" }, suggestedPrompts.map((prompt) => /* @__PURE__ */ React8__default.createElement(
1675
1890
  TouchableOpacity,
1676
1891
  {
1677
1892
  key: `${prompt.source}:${prompt.text}`,
1678
1893
  style: [
1679
- styles3.chip,
1894
+ styles5.chip,
1680
1895
  {
1681
1896
  backgroundColor: theme.colors.surface,
1682
1897
  borderColor: theme.colors.border,
@@ -1687,11 +1902,11 @@ function AppilotsChatInner({
1687
1902
  activeOpacity: 0.7,
1688
1903
  accessibilityLabel: `Suggested prompt: ${prompt.text}`
1689
1904
  },
1690
- /* @__PURE__ */ React7__default.createElement(
1905
+ /* @__PURE__ */ React8__default.createElement(
1691
1906
  Text,
1692
1907
  {
1693
1908
  style: [
1694
- styles3.chipText,
1909
+ styles5.chipText,
1695
1910
  {
1696
1911
  color: theme.colors.buttonText,
1697
1912
  fontFamily: theme.typography.fontFamily,
@@ -1703,20 +1918,20 @@ function AppilotsChatInner({
1703
1918
  prompt.text
1704
1919
  )
1705
1920
  )))),
1706
- ListFooterComponent: isLoading ? /* @__PURE__ */ React7__default.createElement(
1921
+ ListFooterComponent: isLoading ? /* @__PURE__ */ React8__default.createElement(
1707
1922
  View,
1708
1923
  {
1709
1924
  style: [
1710
- styles3.messageBubble,
1711
- styles3.assistantBubble,
1925
+ styles5.messageBubble,
1926
+ styles5.assistantBubble,
1712
1927
  { backgroundColor: theme.colors.surface }
1713
1928
  ]
1714
1929
  },
1715
- /* @__PURE__ */ React7__default.createElement(
1930
+ /* @__PURE__ */ React8__default.createElement(
1716
1931
  Text,
1717
1932
  {
1718
1933
  style: [
1719
- styles3.messageText,
1934
+ styles5.messageText,
1720
1935
  { color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily }
1721
1936
  ]
1722
1937
  },
@@ -1725,22 +1940,48 @@ function AppilotsChatInner({
1725
1940
  ) : null
1726
1941
  }
1727
1942
  ),
1728
- /* @__PURE__ */ React7__default.createElement(
1943
+ 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: { flexDirection: "row", gap: 12 } }, mission.status === "running" ? /* @__PURE__ */ React8__default.createElement(
1944
+ TouchableOpacity,
1945
+ {
1946
+ onPress: () => void pauseMission(),
1947
+ testID: "appilots-mission-pause",
1948
+ accessibilityRole: "button"
1949
+ },
1950
+ /* @__PURE__ */ React8__default.createElement(Text, { style: { color: theme.colors.text } }, strings.missionPause)
1951
+ ) : mission.status !== "awaiting_approval" ? /* @__PURE__ */ React8__default.createElement(
1952
+ TouchableOpacity,
1953
+ {
1954
+ onPress: () => void resumeMission(),
1955
+ disabled: isLoading,
1956
+ testID: "appilots-mission-resume",
1957
+ accessibilityRole: "button"
1958
+ },
1959
+ /* @__PURE__ */ React8__default.createElement(Text, { style: { color: theme.colors.text } }, strings.missionResume)
1960
+ ) : null, /* @__PURE__ */ React8__default.createElement(
1961
+ TouchableOpacity,
1962
+ {
1963
+ onPress: () => void cancelMission(),
1964
+ testID: "appilots-mission-cancel",
1965
+ accessibilityRole: "button"
1966
+ },
1967
+ /* @__PURE__ */ React8__default.createElement(Text, { style: { color: theme.colors.text } }, strings.missionCancel)
1968
+ ))),
1969
+ /* @__PURE__ */ React8__default.createElement(
1729
1970
  View,
1730
1971
  {
1731
1972
  style: [
1732
- styles3.inputContainer,
1973
+ styles5.inputContainer,
1733
1974
  {
1734
1975
  borderTopColor: theme.colors.border,
1735
1976
  paddingBottom: Math.max(insets.bottom, 10)
1736
1977
  }
1737
1978
  ]
1738
1979
  },
1739
- /* @__PURE__ */ React7__default.createElement(
1980
+ /* @__PURE__ */ React8__default.createElement(
1740
1981
  TextInput,
1741
1982
  {
1742
1983
  style: [
1743
- styles3.input,
1984
+ styles5.input,
1744
1985
  {
1745
1986
  backgroundColor: theme.colors.surface,
1746
1987
  color: theme.colors.text,
@@ -1757,16 +1998,35 @@ function AppilotsChatInner({
1757
1998
  multiline: true,
1758
1999
  blurOnSubmit: false,
1759
2000
  scrollEnabled: true,
1760
- editable: !isLoading,
2001
+ editable: !isLoading && (!mission || ["completed", "cancelled"].includes(mission.status)),
1761
2002
  testID: "assistant-input",
1762
2003
  __appilotsInternal: true
1763
2004
  }
1764
2005
  ),
1765
- isLoading ? /* @__PURE__ */ React7__default.createElement(
2006
+ voiceEnabled && !isLoading ? /* @__PURE__ */ React8__default.createElement(
2007
+ TouchableOpacity,
2008
+ {
2009
+ style: [
2010
+ styles5.sendButton,
2011
+ {
2012
+ backgroundColor: voice.state === "listening" || voice.state === "starting" ? theme.colors.error : theme.colors.primary + "40",
2013
+ borderRadius: theme.radii.md
2014
+ }
2015
+ ],
2016
+ onPressIn: voice.start,
2017
+ onPressOut: voice.stop,
2018
+ accessibilityRole: "button",
2019
+ accessibilityLabel: voice.state === "listening" ? strings.micStopA11y : strings.micStartA11y,
2020
+ testID: "appilots-mic-button",
2021
+ activeOpacity: 0.7
2022
+ },
2023
+ /* @__PURE__ */ React8__default.createElement(Text, { style: styles5.sendButtonText }, "\u25CF")
2024
+ ) : null,
2025
+ isLoading ? /* @__PURE__ */ React8__default.createElement(
1766
2026
  TouchableOpacity,
1767
2027
  {
1768
2028
  style: [
1769
- styles3.sendButton,
2029
+ styles5.sendButton,
1770
2030
  {
1771
2031
  backgroundColor: theme.colors.primary,
1772
2032
  borderRadius: theme.radii.md
@@ -1778,31 +2038,31 @@ function AppilotsChatInner({
1778
2038
  testID: "appilots-stop-button",
1779
2039
  activeOpacity: 0.7
1780
2040
  },
1781
- /* @__PURE__ */ React7__default.createElement(Text, { style: styles3.sendButtonText }, "\u25A0")
1782
- ) : /* @__PURE__ */ React7__default.createElement(
2041
+ /* @__PURE__ */ React8__default.createElement(Text, { style: styles5.sendButtonText }, "\u25A0")
2042
+ ) : /* @__PURE__ */ React8__default.createElement(
1783
2043
  TouchableOpacity,
1784
2044
  {
1785
2045
  style: [
1786
- styles3.sendButton,
2046
+ styles5.sendButton,
1787
2047
  {
1788
2048
  backgroundColor: inputText.trim() ? theme.colors.primary : theme.colors.primary + "40",
1789
2049
  borderRadius: theme.radii.md
1790
2050
  }
1791
2051
  ],
1792
2052
  onPress: handleSend,
1793
- disabled: !inputText.trim(),
2053
+ disabled: !inputText.trim() || !!(mission && !["completed", "cancelled"].includes(mission.status)),
1794
2054
  activeOpacity: 0.7,
1795
2055
  accessibilityRole: "button",
1796
2056
  testID: "assistant-send"
1797
2057
  },
1798
- /* @__PURE__ */ React7__default.createElement(Text, { style: styles3.sendButtonText }, "\u2191")
2058
+ /* @__PURE__ */ React8__default.createElement(Text, { style: styles5.sendButtonText }, "\u2191")
1799
2059
  )
1800
2060
  ),
1801
- poweredByVisible && /* @__PURE__ */ React7__default.createElement(View, { style: styles3.poweredByContainer }, /* @__PURE__ */ React7__default.createElement(
2061
+ poweredByVisible && /* @__PURE__ */ React8__default.createElement(View, { style: styles5.poweredByContainer }, /* @__PURE__ */ React8__default.createElement(
1802
2062
  Text,
1803
2063
  {
1804
2064
  style: [
1805
- styles3.poweredByText,
2065
+ styles5.poweredByText,
1806
2066
  {
1807
2067
  color: theme.colors.textSecondary,
1808
2068
  fontFamily: theme.typography.fontFamily,
@@ -1818,8 +2078,8 @@ function AppilotsChatInner({
1818
2078
  }
1819
2079
  const renderTrigger = () => {
1820
2080
  if (triggerButton === "none") return null;
1821
- if (triggerButton !== "default" && React7__default.isValidElement(triggerButton)) {
1822
- return /* @__PURE__ */ React7__default.createElement(View, { ...APPILOTS_SKIP_PROPS, style: [styles3.fabWrapper, fabPositionStyle] }, /* @__PURE__ */ React7__default.createElement(
2081
+ if (triggerButton !== "default" && React8__default.isValidElement(triggerButton)) {
2082
+ return /* @__PURE__ */ React8__default.createElement(View, { ...APPILOTS_SKIP_PROPS, style: [styles5.fabWrapper, fabPositionStyle] }, /* @__PURE__ */ React8__default.createElement(
1823
2083
  TouchableOpacity,
1824
2084
  {
1825
2085
  ...APPILOTS_SKIP_PROPS,
@@ -1832,12 +2092,12 @@ function AppilotsChatInner({
1832
2092
  triggerButton
1833
2093
  ));
1834
2094
  }
1835
- return /* @__PURE__ */ React7__default.createElement(
2095
+ return /* @__PURE__ */ React8__default.createElement(
1836
2096
  TouchableOpacity,
1837
2097
  {
1838
2098
  ...APPILOTS_SKIP_PROPS,
1839
2099
  style: [
1840
- styles3.fab,
2100
+ styles5.fab,
1841
2101
  fabPositionStyle,
1842
2102
  { backgroundColor: triggerButtonColor ?? theme.colors.primary }
1843
2103
  ],
@@ -1847,48 +2107,113 @@ function AppilotsChatInner({
1847
2107
  accessibilityLabel: strings.openChatA11y,
1848
2108
  testID: "assistant-launcher"
1849
2109
  },
1850
- triggerIcon ?? (triggerButtonImage ? /* @__PURE__ */ React7__default.createElement(
2110
+ triggerIcon ?? (triggerButtonImage ? /* @__PURE__ */ React8__default.createElement(
1851
2111
  Image,
1852
2112
  {
1853
2113
  source: typeof triggerButtonImage === "string" ? { uri: triggerButtonImage } : triggerButtonImage,
1854
- style: styles3.fabImage,
2114
+ style: styles5.fabImage,
1855
2115
  resizeMode: "contain"
1856
2116
  }
1857
- ) : /* @__PURE__ */ React7__default.createElement(ChatIcon, { size: 26, color: "#ffffff" }))
2117
+ ) : /* @__PURE__ */ React8__default.createElement(ChatIcon, { size: 26, color: "#ffffff" }))
1858
2118
  );
1859
2119
  };
1860
- return /* @__PURE__ */ React7__default.createElement(React7__default.Fragment, null, showTrigger && renderTrigger(), /* @__PURE__ */ React7__default.createElement(
1861
- Modal,
2120
+ return /* @__PURE__ */ React8__default.createElement(React8__default.Fragment, null, showTrigger && renderTrigger(), spotlight && experience.spotlight !== "off" ? /* @__PURE__ */ React8__default.createElement(
2121
+ SpotlightOverlay,
2122
+ {
2123
+ rect: spotlight.rect,
2124
+ seq: spotlight.seq,
2125
+ label: spotlight.label,
2126
+ color: theme.colors.primary,
2127
+ captionBackground: theme.colors.surface,
2128
+ captionColor: theme.colors.text,
2129
+ fontFamily: theme.typography.fontFamily,
2130
+ window: { width: SCREEN_WIDTH, height: SCREEN_HEIGHT }
2131
+ }
2132
+ ) : null, surface === "pill" && /* @__PURE__ */ React8__default.createElement(
2133
+ AgentPill,
2134
+ {
2135
+ label: mission && !["completed", "cancelled"].includes(mission.status) ? mission.progressText : pillLabel,
2136
+ state: pillState,
2137
+ position,
2138
+ insets,
2139
+ fontFamily: theme.typography.fontFamily,
2140
+ colors: {
2141
+ surface: theme.colors.surface,
2142
+ text: theme.colors.text,
2143
+ textSecondary: theme.colors.textSecondary,
2144
+ primary: theme.colors.primary,
2145
+ border: theme.colors.border,
2146
+ success: theme.colors.success,
2147
+ error: theme.colors.error
2148
+ },
2149
+ strings: { expandA11y: strings.pillExpandA11y, stopA11y: strings.pillStopA11y },
2150
+ onExpand: openChat,
2151
+ onStop: busy ? cancelMessage : void 0
2152
+ }
2153
+ ), /* @__PURE__ */ React8__default.createElement(SheetHost, { useModal: presence === "sheet", visible: isVisible, onRequestClose: closeChat }, /* @__PURE__ */ React8__default.createElement(
2154
+ Animated.View,
1862
2155
  {
1863
2156
  ...APPILOTS_SKIP_PROPS,
1864
- visible: isVisible,
1865
- transparent: true,
1866
- animationType: "none",
1867
- onRequestClose: closeChat
2157
+ pointerEvents: presence === "sheet" ? "auto" : "box-none",
2158
+ style: [
2159
+ mode === "fullscreen" ? styles5.modalContainerFullscreen : styles5.modalContainer,
2160
+ mode === "sidebar" && styles5.modalContainerSidebar,
2161
+ presence !== "sheet" && styles5.sheetContainerNoScrim,
2162
+ // Keeps the sheet out of the status bar / notch. It is padding
2163
+ // and not a margin so the scrim still covers the full screen.
2164
+ // Only reachable now that the sheet can grow to the top edge:
2165
+ // `fullscreen` and `sidebar` always could, and `bubble` does
2166
+ // whenever the keyboard shrinks it — without this, the header
2167
+ // renders under the clock.
2168
+ { paddingTop: insets.top },
2169
+ { transform: [{ translateY: slideAnim }] }
2170
+ ]
1868
2171
  },
1869
- /* @__PURE__ */ React7__default.createElement(
1870
- Animated.View,
2172
+ /* @__PURE__ */ React8__default.createElement(
2173
+ KeyboardAvoidingView,
1871
2174
  {
1872
- ...APPILOTS_SKIP_PROPS,
1873
- style: [
1874
- mode === "fullscreen" ? styles3.modalContainerFullscreen : styles3.modalContainer,
1875
- mode === "sidebar" && styles3.modalContainerSidebar,
1876
- { transform: [{ translateY: slideAnim }] }
1877
- ]
2175
+ style: styles5.keyboardView,
2176
+ behavior: Platform.OS === "ios" ? "padding" : "height",
2177
+ keyboardVerticalOffset: 0
1878
2178
  },
1879
- /* @__PURE__ */ React7__default.createElement(
1880
- KeyboardAvoidingView,
1881
- {
1882
- style: styles3.keyboardView,
1883
- behavior: Platform.OS === "ios" ? "padding" : "height",
1884
- keyboardVerticalOffset: 0
1885
- },
1886
- chatSurface
1887
- )
2179
+ chatSurface
1888
2180
  )
1889
- ));
2181
+ )));
1890
2182
  }
1891
- var styles3 = StyleSheet.create({
2183
+ function SheetHost({
2184
+ useModal,
2185
+ visible,
2186
+ onRequestClose,
2187
+ children
2188
+ }) {
2189
+ if (useModal) {
2190
+ return /* @__PURE__ */ React8__default.createElement(
2191
+ Modal,
2192
+ {
2193
+ ...APPILOTS_SKIP_PROPS,
2194
+ visible,
2195
+ transparent: true,
2196
+ animationType: "none",
2197
+ onRequestClose
2198
+ },
2199
+ children
2200
+ );
2201
+ }
2202
+ if (!visible) return null;
2203
+ return /* @__PURE__ */ React8__default.createElement(
2204
+ View,
2205
+ {
2206
+ ...APPILOTS_SKIP_PROPS,
2207
+ pointerEvents: "box-none",
2208
+ style: [StyleSheet.absoluteFill, styles5.sheetOverlay]
2209
+ },
2210
+ children
2211
+ );
2212
+ }
2213
+ var styles5 = StyleSheet.create({
2214
+ // An explicitly opened chat must not mount underneath a sibling portal.
2215
+ // Automatic expansion is separately deferred while the host modal is open.
2216
+ sheetOverlay: { zIndex: 1e3, elevation: 7 },
1892
2217
  fab: {
1893
2218
  position: "absolute",
1894
2219
  width: 56,
@@ -1931,12 +2256,31 @@ var styles3 = StyleSheet.create({
1931
2256
  flexDirection: "row",
1932
2257
  justifyContent: "flex-end"
1933
2258
  },
2259
+ // Non-modal presences drop the scrim entirely: dimming the app is
2260
+ // the opposite of the point, and a painted full-screen surface is
2261
+ // touch-opaque no matter what pointerEvents the parent asks for.
2262
+ sheetContainerNoScrim: {
2263
+ backgroundColor: "transparent"
2264
+ },
1934
2265
  keyboardView: {
1935
2266
  flex: 1,
1936
2267
  justifyContent: "flex-end"
1937
2268
  },
2269
+ // The sheet has to be able to SHRINK, so its size is a maximum and not
2270
+ // a height. `keyboardView` is a KeyboardAvoidingView on `padding`
2271
+ // (`height` on Android): when the keyboard opens, the space left for
2272
+ // this child drops by the keyboard's height. A fixed `height` ignores
2273
+ // that — the sheet keeps its full size, the parent's
2274
+ // `justifyContent: 'flex-end'` pins it to the bottom, and it overflows
2275
+ // off the TOP, carrying the header away with it. Measured on a
2276
+ // third-party app at 0.75 × 812pt against a ~336pt keyboard: focusing
2277
+ // the input put the entire header off screen, close button included,
2278
+ // and nothing could dismiss the chat. `flex: 1` + `maxHeight` keeps the
2279
+ // same size with the keyboard down and shrinks instead of overflowing
2280
+ // with it up.
1938
2281
  chatContainer: {
1939
- height: SCREEN_HEIGHT * 0.75,
2282
+ flex: 1,
2283
+ maxHeight: SCREEN_HEIGHT * 0.75,
1940
2284
  shadowColor: "#000",
1941
2285
  shadowOffset: { width: 0, height: -3 },
1942
2286
  shadowOpacity: 0.1,
@@ -1944,15 +2288,19 @@ var styles3 = StyleSheet.create({
1944
2288
  elevation: 10
1945
2289
  },
1946
2290
  chatContainerFullscreen: {
1947
- height: SCREEN_HEIGHT
2291
+ maxHeight: SCREEN_HEIGHT
1948
2292
  },
1949
2293
  chatContainerSidebar: {
1950
- height: SCREEN_HEIGHT,
2294
+ maxHeight: SCREEN_HEIGHT,
1951
2295
  width: SCREEN_WIDTH > 600 ? SCREEN_WIDTH * 0.4 : SCREEN_WIDTH
1952
2296
  },
2297
+ // Inline renders in place with no modal and no KeyboardAvoidingView, so
2298
+ // it fills its host instead — the 75% cap above would shrink it to
2299
+ // three quarters of whatever the integrator sized the slot.
1953
2300
  chatContainerInline: {
1954
2301
  height: "100%",
1955
2302
  width: "100%",
2303
+ maxHeight: "100%",
1956
2304
  flex: 1
1957
2305
  },
1958
2306
  header: {
@@ -2178,6 +2526,11 @@ function ConfirmDialog(_props = {}) {
2178
2526
  return null;
2179
2527
  }
2180
2528
 
2529
+ // src/auto/appilotsListTotal.ts
2530
+ function appilotsListTotal(total) {
2531
+ return { appilotsTotalItemCount: total };
2532
+ }
2533
+
2181
2534
  // src/hoc/canHoldRef.ts
2182
2535
  var FORWARD_REF = /* @__PURE__ */ Symbol.for("react.forward_ref");
2183
2536
  var MEMO = /* @__PURE__ */ Symbol.for("react.memo");
@@ -2204,12 +2557,19 @@ function canHoldRef(type) {
2204
2557
  }
2205
2558
  }
2206
2559
 
2560
+ // src/hoc/withDeclaredIdentity.ts
2561
+ function withDeclaredIdentity(props, declaredId, enabled) {
2562
+ if (!enabled || !declaredId) return props;
2563
+ if (props.testID != null) return props;
2564
+ return { ...props, testID: declaredId };
2565
+ }
2566
+
2207
2567
  // src/hoc/createAppilotsInput.tsx
2208
2568
  function normalizeId(label) {
2209
2569
  return label.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9\s]/g, "").trim().replace(/\s+(.)/g, (_, c) => c.toUpperCase());
2210
2570
  }
2211
2571
  function createAppilotsInput(WrappedComponent) {
2212
- const AppilotsInput = React7__default.forwardRef((props, ref) => {
2572
+ const AppilotsInput = React8__default.forwardRef((props, ref) => {
2213
2573
  const {
2214
2574
  appilotsId,
2215
2575
  appilotsScreen,
@@ -2264,10 +2624,14 @@ function createAppilotsInput(WrappedComponent) {
2264
2624
  },
2265
2625
  [ref]
2266
2626
  );
2267
- return /* @__PURE__ */ React7__default.createElement(
2627
+ return /* @__PURE__ */ React8__default.createElement(
2268
2628
  WrappedComponent,
2269
2629
  {
2270
- ...{ label, value, onChangeText, ...rest },
2630
+ ...withDeclaredIdentity(
2631
+ { label, value, onChangeText, ...rest },
2632
+ fieldId,
2633
+ appilotsEnabled
2634
+ ),
2271
2635
  ...canHoldRef(WrappedComponent) ? { ref: mergedRef } : {}
2272
2636
  }
2273
2637
  );
@@ -2279,7 +2643,7 @@ function normalizeId2(label) {
2279
2643
  return label.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9\s]/g, "").trim().replace(/\s+(.)/g, (_, c) => c.toUpperCase());
2280
2644
  }
2281
2645
  function createAppilotsButton(WrappedComponent) {
2282
- const AppilotsButton = React7__default.forwardRef((props, ref) => {
2646
+ const AppilotsButton = React8__default.forwardRef((props, ref) => {
2283
2647
  const {
2284
2648
  appilotsId,
2285
2649
  appilotsScreen,
@@ -2305,10 +2669,10 @@ function createAppilotsButton(WrappedComponent) {
2305
2669
  componentRegistry.unregister(targetId);
2306
2670
  };
2307
2671
  }, [targetId, appilotsEnabled, press, title, appilotsScreen]);
2308
- return /* @__PURE__ */ React7__default.createElement(
2672
+ return /* @__PURE__ */ React8__default.createElement(
2309
2673
  WrappedComponent,
2310
2674
  {
2311
- ...{ title, onPress, ...rest },
2675
+ ...withDeclaredIdentity({ title, onPress, ...rest }, targetId, appilotsEnabled),
2312
2676
  ...canHoldRef(WrappedComponent) ? { ref } : {}
2313
2677
  }
2314
2678
  );
@@ -2320,7 +2684,7 @@ function normalizeId3(label) {
2320
2684
  return label.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9\s]/g, "").trim().replace(/\s+(.)/g, (_, c) => c.toUpperCase());
2321
2685
  }
2322
2686
  function createAppilotsSwitch(WrappedComponent) {
2323
- const AppilotsSwitch = React7__default.forwardRef((props, ref) => {
2687
+ const AppilotsSwitch = React8__default.forwardRef((props, ref) => {
2324
2688
  const {
2325
2689
  appilotsId,
2326
2690
  appilotsScreen,
@@ -2351,10 +2715,14 @@ function createAppilotsSwitch(WrappedComponent) {
2351
2715
  componentRegistry.unregister(toggleId);
2352
2716
  };
2353
2717
  }, [toggleId, appilotsEnabled, getValue, setValue, label, appilotsScreen]);
2354
- return /* @__PURE__ */ React7__default.createElement(
2718
+ return /* @__PURE__ */ React8__default.createElement(
2355
2719
  WrappedComponent,
2356
2720
  {
2357
- ...{ label, value, onValueChange, ...rest },
2721
+ ...withDeclaredIdentity(
2722
+ { label, value, onValueChange, ...rest },
2723
+ toggleId,
2724
+ appilotsEnabled
2725
+ ),
2358
2726
  ...canHoldRef(WrappedComponent) ? { ref } : {}
2359
2727
  }
2360
2728
  );
@@ -2363,4 +2731,4 @@ function createAppilotsSwitch(WrappedComponent) {
2363
2731
  return AppilotsSwitch;
2364
2732
  }
2365
2733
 
2366
- export { ActionBreadcrumb, AppilotsChat, AppilotsI18nProvider, AppilotsThemeProvider, ChatIcon, ConfirmDialog, HeadsetIcon, appilotsTheme, appilotsThemeFromTailwind, createAppilotsButton, createAppilotsInput, createAppilotsSwitch, detectDeviceLocale, resolveLocale, useAppilotsI18n, useAppilotsTheme };
2734
+ 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 };