@appilots/sdk 0.11.3 → 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-JKLE5SLN.mjs';
2
- import { useAppilotsChat, useAppilotsActions, shouldShowSuggestedPrompts, useSuggestedPrompts } from './chunk-2ZDELL54.mjs';
3
- export { appilotsSelfCheck, captureSnapshot, executeAction, useAppilots, useAppilotsActions, useAppilotsChat, useAppilotsField, useAppilotsNavigation, useAppilotsSlider, useAppilotsTarget, useAppilotsToggle, useSuggestedPrompts } from './chunk-2ZDELL54.mjs';
4
- import { defaultLightTheme, describeAction, mergeThemeTokens, resolveBaseTheme, useAppilotsContext, AppilotsErrorBoundary, componentRegistry } from './chunk-ROXXCDIG.mjs';
5
- export { AppilotsClient, AppilotsProvider, AppilotsRegistryProvider, RateLimitedError, SDK_VERSION, _patchJsxRuntimes, clearAppilotsDebugTraces, componentRegistry, createComponentRegistry, createElementRegistry, createListRegistry, defaultDarkTheme, defaultLightTheme, describeAction, elementRegistry, enableAppilotsAutoTracking, getAppilotsDebugTraces, getAutoTrackingState, getGlobalConfig, getIntrospectionDiagnostics, humanizeError, initAppilots, isAutoTrackingEnabled, listRegistry, mergeThemeTokens, recordAppilotsDebugTrace, registerScreen, setCurrentScreen, subscribeAppilotsDebugTraces, useAppilotsContext, useResolvedRegistry } from './chunk-ROXXCDIG.mjs';
6
- import * as React7 from 'react';
7
- import React7__default, { createContext, useRef, useEffect, useMemo, useContext, useState, useCallback } from 'react';
8
- import { StyleSheet, Dimensions, Platform, View, Animated, Text, TouchableOpacity, ActivityIndicator, useColorScheme, NativeModules, FlatList, Image, TextInput, Modal, KeyboardAvoidingView, Linking } from 'react-native';
1
+ export { AppilotsNavigationContainer } from './chunk-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,22 +231,22 @@ 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),
@@ -215,8 +255,8 @@ function RegularBreadcrumbRow({
215
255
  accessibilityLabel: "Rejeitar a\xE7\xE3o",
216
256
  testID: "assistant-action-reject"
217
257
  },
218
- /* @__PURE__ */ React7__default.createElement(Text, { style: [styles.rejectBtnText, { color: palette.error }] }, "Rejeitar")
219
- ), /* @__PURE__ */ React7__default.createElement(
258
+ /* @__PURE__ */ React8__default.createElement(Text, { style: [styles.rejectBtnText, { color: palette.error }] }, "Rejeitar")
259
+ ), /* @__PURE__ */ React8__default.createElement(
220
260
  TouchableOpacity,
221
261
  {
222
262
  onPress: () => onApprove(action.id),
@@ -225,7 +265,7 @@ function RegularBreadcrumbRow({
225
265
  accessibilityLabel: "Aprovar a\xE7\xE3o",
226
266
  testID: "assistant-action-approve"
227
267
  },
228
- /* @__PURE__ */ React7__default.createElement(Text, { style: styles.approveBtnText }, "Aprovar")
268
+ /* @__PURE__ */ React8__default.createElement(Text, { style: styles.approveBtnText }, "Aprovar")
229
269
  )));
230
270
  }
231
271
  function ConfirmCard({
@@ -250,7 +290,7 @@ function ConfirmCard({
250
290
  cancelBackground,
251
291
  cancelTextColor
252
292
  } = deriveConfirmCardContent(action, palette, confirmStrings);
253
- return /* @__PURE__ */ React7__default.createElement(
293
+ return /* @__PURE__ */ React8__default.createElement(
254
294
  View,
255
295
  {
256
296
  style: [
@@ -259,7 +299,7 @@ function ConfirmCard({
259
299
  ],
260
300
  testID: "assistant-confirm-card"
261
301
  },
262
- /* @__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(
263
303
  Text,
264
304
  {
265
305
  style: [
@@ -269,8 +309,8 @@ function ConfirmCard({
269
309
  },
270
310
  title
271
311
  )),
272
- /* @__PURE__ */ React7__default.createElement(Text, { style: [confirmCardStyles.message, { color: messageColor, fontFamily }] }, message),
273
- /* @__PURE__ */ React7__default.createElement(View, { style: confirmCardStyles.buttons }, /* @__PURE__ */ React7__default.createElement(
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(
274
314
  TouchableOpacity,
275
315
  {
276
316
  onPress: () => onReject?.(action.id),
@@ -279,8 +319,8 @@ function ConfirmCard({
279
319
  accessibilityLabel: cancelLabel,
280
320
  testID: "assistant-confirm-reject"
281
321
  },
282
- /* @__PURE__ */ React7__default.createElement(Text, { style: [confirmCardStyles.cancelText, { color: cancelTextColor, fontFamily }] }, cancelLabel)
283
- ), /* @__PURE__ */ React7__default.createElement(
322
+ /* @__PURE__ */ React8__default.createElement(Text, { style: [confirmCardStyles.cancelText, { color: cancelTextColor, fontFamily }] }, cancelLabel)
323
+ ), /* @__PURE__ */ React8__default.createElement(
284
324
  TouchableOpacity,
285
325
  {
286
326
  onPress: () => onApprove?.(action.id),
@@ -293,7 +333,7 @@ function ConfirmCard({
293
333
  accessibilityLabel: confirmLabel,
294
334
  testID: "assistant-confirm-approve"
295
335
  },
296
- /* @__PURE__ */ React7__default.createElement(Text, { style: [confirmCardStyles.confirmText, { fontFamily }] }, confirmLabel)
336
+ /* @__PURE__ */ React8__default.createElement(Text, { style: [confirmCardStyles.confirmText, { fontFamily }] }, confirmLabel)
297
337
  ))
298
338
  );
299
339
  }
@@ -358,7 +398,7 @@ function StateIcon({
358
398
  tone,
359
399
  primaryColor
360
400
  }) {
361
- return /* @__PURE__ */ React7__default.createElement(View, { style: [styles.iconCircle, { backgroundColor: tone.iconBg }] }, state === "running" ? /* @__PURE__ */ React7__default.createElement(ActivityIndicator, { size: "small", color: primaryColor }) : /* @__PURE__ */ React7__default.createElement(Text, { style: [styles.iconGlyph, { color: tone.color }] }, iconGlyphFor(state)));
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)));
362
402
  }
363
403
  var styles = StyleSheet.create({
364
404
  container: {
@@ -449,7 +489,7 @@ function resolvePersonalization(props, remote) {
449
489
  };
450
490
  }
451
491
  function ChatIcon({ size = 24, color = "#ffffff" }) {
452
- return /* @__PURE__ */ React7.createElement(Svg, { width: size, height: size, viewBox: "0 0 24 24", fill: "none" }, /* @__PURE__ */ React7.createElement(
492
+ return /* @__PURE__ */ React8.createElement(Svg, { width: size, height: size, viewBox: "0 0 24 24", fill: "none" }, /* @__PURE__ */ React8.createElement(
453
493
  Path,
454
494
  {
455
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",
@@ -457,8 +497,438 @@ function ChatIcon({ size = 24, color = "#ffffff" }) {
457
497
  }
458
498
  ));
459
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
+ }
460
930
  function HeadsetIcon({ size = 18, color = "#ffffff" }) {
461
- return /* @__PURE__ */ React7.createElement(Svg, { width: size, height: size, viewBox: "0 0 24 24", fill: "none" }, /* @__PURE__ */ React7.createElement(
931
+ return /* @__PURE__ */ React8.createElement(Svg, { width: size, height: size, viewBox: "0 0 24 24", fill: "none" }, /* @__PURE__ */ React8.createElement(
462
932
  Path,
463
933
  {
464
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",
@@ -499,195 +969,6 @@ function buildChatTimeline(messages, actions) {
499
969
  }
500
970
  return timeline;
501
971
  }
502
-
503
- // src/components/markdownContent.ts
504
- var ORDERED_LINE = /^\s*(\d+)\.\s+(.+)$/;
505
- var BULLET_LINE = /^\s*[-*•]\s+(.+)$/;
506
- var HEADING_LINE = /^\s*(#{1,6})\s+(.+)$/;
507
- var FENCE_LINE = /^\s*```\s*([A-Za-z0-9+#_-]*)\s*$/;
508
- var INLINE_PATTERN = /\[([^\]\n]+)\]\((https?:\/\/[^\s)]+)\)|\*\*(.+?)\*\*|__(.+?)__|`([^`]+)`|\*(.+?)\*|_(.+?)_/g;
509
- var MIN_ENHANCE_LENGTH = 80;
510
- var SENTENCE_SPLIT = /[^.!?]+[.!?]+(?=\s+[A-ZÁÉÍÓÚÀÈÌÒÙÂÊÎÔÛÃÕÇÑ¿¡"“]|$)|[^.!?]+$/g;
511
- var CAPABILITY_SENTENCE = /^((?:No aplicativo,?|In (?:this|the) app,?|En la aplicaci[oó]n,?|Dans l['’]application,?)?(?:\s+(?:voc[eê]|voce|you|tu|usted|puedes|puede|peux))?\s*(?:pode(?:m|r)?|can|could|puedes|puede|peux|peut))\s+(.+?)\.?\s*$/i;
512
- var INCLUDE_LIST_SENTENCE = /^(.+?)\s+(?:incluem|inclui|include|includes|incluye|incluyen|such as|for example|par exemple|por exemplo)\s+(.+?)\.?\s*$/i;
513
- function looksLikeMarkdown(content) {
514
- if (!content) return false;
515
- return /\*\*.+?\*\*/.test(content) || /__.+?__/.test(content) || /`[^`\n]+`/.test(content) || /^\s*[-*•]\s+/m.test(content) || /^\s*\d+\.\s+/m.test(content) || /^\s*#{1,6}\s+/m.test(content) || /^\s*```/m.test(content) || /\[[^\]\n]+\]\(https?:\/\/[^\s)]+\)/.test(content);
516
- }
517
- function splitEnumeration(text) {
518
- const trimmed = text.trim().replace(/\.\s*$/, "");
519
- if (!trimmed) return [];
520
- const commaParts = trimmed.split(",").map((part) => part.trim()).filter(Boolean);
521
- if (commaParts.length <= 1) {
522
- const conjParts = trimmed.split(/\s+(?:e|and|y|ou|or)\s+/i).map((part) => part.trim());
523
- return conjParts.filter(Boolean);
524
- }
525
- const last = commaParts[commaParts.length - 1];
526
- const conjMatch = last.match(/^(.+?)\s+(?:e|and|y|ou|or)\s+(.+)$/i);
527
- if (conjMatch) {
528
- commaParts[commaParts.length - 1] = conjMatch[1].trim();
529
- commaParts.push(conjMatch[2].trim());
530
- }
531
- return commaParts.filter(Boolean);
532
- }
533
- function splitSentences(text) {
534
- const matches = text.match(SENTENCE_SPLIT);
535
- if (!matches) return [text.trim()];
536
- return matches.map((part) => part.trim()).filter(Boolean);
537
- }
538
- function formatCapabilitySentence(sentence) {
539
- const match = CAPABILITY_SENTENCE.exec(sentence.trim());
540
- if (!match) return null;
541
- const items = splitEnumeration(match[2] ?? "");
542
- if (items.length < 3) return null;
543
- const intro = `${match[1].trim()}:`;
544
- return `${intro}
545
- ${items.map((item) => `- ${item}`).join("\n")}`;
546
- }
547
- function formatIncludeListSentence(sentence) {
548
- const match = INCLUDE_LIST_SENTENCE.exec(sentence.trim());
549
- if (!match) return null;
550
- const intro = match[1].trim().replace(/[,:]\s*$/, "");
551
- const items = splitEnumeration(match[2] ?? "");
552
- if (items.length < 2) return null;
553
- return `${intro}:
554
- ${items.map((item) => `- ${item}`).join("\n")}`;
555
- }
556
- function formatReadableSentence(sentence) {
557
- return formatCapabilitySentence(sentence) ?? formatIncludeListSentence(sentence) ?? sentence.trim();
558
- }
559
- function enhancePlainAssistantContent(content) {
560
- if (!content?.trim() || looksLikeMarkdown(content)) return content;
561
- const trimmed = content.trim();
562
- if (trimmed.length < MIN_ENHANCE_LENGTH) return content;
563
- const sentences = splitSentences(trimmed);
564
- if (sentences.length === 0) return content;
565
- const blocks = sentences.map(formatReadableSentence);
566
- const enhanced = blocks.join("\n\n");
567
- return enhanced || content;
568
- }
569
- function needsWideChatLayout(content) {
570
- if (!content?.trim()) return false;
571
- const prepared = enhancePlainAssistantContent(content);
572
- return looksLikeMarkdown(prepared) || /\n\n+/.test(prepared);
573
- }
574
- function parseMarkdownBlocks(content) {
575
- const lines = content.replace(/\r\n/g, "\n").split("\n");
576
- const blocks = [];
577
- let paragraph = [];
578
- let bulletItems = [];
579
- let orderedItems = [];
580
- let codeLines = null;
581
- let codeLanguage;
582
- const flushParagraph = () => {
583
- if (paragraph.length === 0) return;
584
- blocks.push({ kind: "paragraph", text: paragraph.join("\n") });
585
- paragraph = [];
586
- };
587
- const flushBullets = () => {
588
- if (bulletItems.length === 0) return;
589
- blocks.push({ kind: "bullet", items: bulletItems });
590
- bulletItems = [];
591
- };
592
- const flushOrdered = () => {
593
- if (orderedItems.length === 0) return;
594
- blocks.push({ kind: "ordered", items: orderedItems });
595
- orderedItems = [];
596
- };
597
- const flushLists = () => {
598
- flushBullets();
599
- flushOrdered();
600
- };
601
- const flushCode = () => {
602
- if (codeLines === null) return;
603
- blocks.push({
604
- kind: "codeblock",
605
- code: codeLines.join("\n"),
606
- ...codeLanguage ? { language: codeLanguage } : {}
607
- });
608
- codeLines = null;
609
- codeLanguage = void 0;
610
- };
611
- for (const line of lines) {
612
- if (codeLines !== null) {
613
- if (/^\s*```\s*$/.test(line)) {
614
- flushCode();
615
- } else {
616
- codeLines.push(line);
617
- }
618
- continue;
619
- }
620
- const fence = FENCE_LINE.exec(line);
621
- if (fence) {
622
- flushParagraph();
623
- flushLists();
624
- codeLines = [];
625
- codeLanguage = fence[1] || void 0;
626
- continue;
627
- }
628
- const trimmed = line.trim();
629
- if (!trimmed) {
630
- flushParagraph();
631
- flushLists();
632
- continue;
633
- }
634
- const heading = HEADING_LINE.exec(line);
635
- if (heading) {
636
- flushParagraph();
637
- flushLists();
638
- const level = Math.min(heading[1].length, 3);
639
- blocks.push({ kind: "heading", level, text: heading[2].trim() });
640
- continue;
641
- }
642
- const ordered = ORDERED_LINE.exec(line);
643
- if (ordered) {
644
- flushParagraph();
645
- flushBullets();
646
- orderedItems.push(ordered[2].trim());
647
- continue;
648
- }
649
- const bullet = BULLET_LINE.exec(line);
650
- if (bullet) {
651
- flushParagraph();
652
- flushOrdered();
653
- bulletItems.push(bullet[1].trim());
654
- continue;
655
- }
656
- flushLists();
657
- paragraph.push(line);
658
- }
659
- flushParagraph();
660
- flushLists();
661
- flushCode();
662
- return blocks.length > 0 ? blocks : [{ kind: "paragraph", text: content }];
663
- }
664
- function parseInlineMarkdown(text) {
665
- if (!text) return [{ kind: "text", value: "" }];
666
- const segments = [];
667
- let lastIndex = 0;
668
- INLINE_PATTERN.lastIndex = 0;
669
- for (let match = INLINE_PATTERN.exec(text); match; match = INLINE_PATTERN.exec(text)) {
670
- if (match.index > lastIndex) {
671
- segments.push({ kind: "text", value: text.slice(lastIndex, match.index) });
672
- }
673
- if (match[1] && match[2]) {
674
- segments.push({ kind: "link", value: match[1].trim(), href: match[2] });
675
- } else if (match[3] || match[4]) {
676
- segments.push({ kind: "bold", value: (match[3] ?? match[4]).trim() });
677
- } else if (match[5]) {
678
- segments.push({ kind: "code", value: match[5] });
679
- } else if (match[6] || match[7]) {
680
- segments.push({ kind: "italic", value: (match[6] ?? match[7]).trim() });
681
- }
682
- lastIndex = INLINE_PATTERN.lastIndex;
683
- }
684
- if (lastIndex < text.length) {
685
- segments.push({ kind: "text", value: text.slice(lastIndex) });
686
- }
687
- return segments.length > 0 ? segments : [{ kind: "text", value: text }];
688
- }
689
-
690
- // src/components/ChatMarkdown.tsx
691
972
  function segmentKey(segment, index) {
692
973
  return `${segment.kind}-${index}-${segment.value.slice(0, 12)}`;
693
974
  }
@@ -697,17 +978,17 @@ function renderInlineSegments(text, theme, baseStyle) {
697
978
  const key = segmentKey(segment, index);
698
979
  switch (segment.kind) {
699
980
  case "bold":
700
- 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);
701
982
  case "italic":
702
- 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);
703
984
  case "code":
704
- return /* @__PURE__ */ React7__default.createElement(
985
+ return /* @__PURE__ */ React8__default.createElement(
705
986
  Text,
706
987
  {
707
988
  key,
708
989
  style: [
709
990
  baseStyle,
710
- styles2.code,
991
+ styles4.code,
711
992
  {
712
993
  backgroundColor: theme.codeBackground ?? (theme.variant === "user" ? "rgba(255,255,255,0.22)" : "rgba(0,0,0,0.06)"),
713
994
  color: theme.codeColor ?? theme.color
@@ -717,14 +998,14 @@ function renderInlineSegments(text, theme, baseStyle) {
717
998
  segment.value
718
999
  );
719
1000
  case "link":
720
- return /* @__PURE__ */ React7__default.createElement(
1001
+ return /* @__PURE__ */ React8__default.createElement(
721
1002
  Text,
722
1003
  {
723
1004
  key,
724
1005
  accessibilityRole: "link",
725
1006
  style: [
726
1007
  baseStyle,
727
- styles2.link,
1008
+ styles4.link,
728
1009
  {
729
1010
  color: theme.linkColor ?? (theme.variant === "user" ? theme.color : "#2563EB")
730
1011
  }
@@ -751,7 +1032,7 @@ function InlineMarkdown({
751
1032
  fontSize: theme.fontSize,
752
1033
  lineHeight: theme.lineHeight
753
1034
  };
754
- 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));
755
1036
  }
756
1037
  function ChatMarkdown({ content, theme, contentWidth }) {
757
1038
  const prepared = enhancePlainAssistantContent(content);
@@ -761,29 +1042,29 @@ function ChatMarkdown({ content, theme, contentWidth }) {
761
1042
  fontSize: theme.fontSize,
762
1043
  lineHeight: theme.lineHeight
763
1044
  };
764
- const rootStyle = contentWidth ? [styles2.root, { width: contentWidth }] : styles2.root;
1045
+ const rootStyle = contentWidth ? [styles4.root, { width: contentWidth }] : styles4.root;
765
1046
  if (!looksLikeMarkdown(prepared)) {
766
1047
  const paragraphs = prepared.split(/\n\n+/).map((part) => part.trim()).filter(Boolean);
767
1048
  if (paragraphs.length > 1) {
768
- return /* @__PURE__ */ React7__default.createElement(View, { style: rootStyle }, paragraphs.map((paragraph, index) => /* @__PURE__ */ React7__default.createElement(View, { key: `plain-p-${index}`, style: index > 0 ? styles2.blockGap : void 0 }, /* @__PURE__ */ React7__default.createElement(Text, { style: baseTextStyle }, paragraph))));
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))));
769
1050
  }
770
- return /* @__PURE__ */ React7__default.createElement(Text, { style: baseTextStyle }, prepared);
1051
+ return /* @__PURE__ */ React8__default.createElement(Text, { style: baseTextStyle }, prepared);
771
1052
  }
772
1053
  const blocks = parseMarkdownBlocks(prepared);
773
- 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) => {
774
1055
  if (block.kind === "paragraph") {
775
- return /* @__PURE__ */ React7__default.createElement(View, { key: `p-${blockIndex}`, style: blockIndex > 0 ? styles2.blockGap : void 0 }, /* @__PURE__ */ React7__default.createElement(InlineMarkdown, { text: block.text, theme }));
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 }));
776
1057
  }
777
1058
  if (block.kind === "heading") {
778
1059
  const scale = block.level === 1 ? 1.25 : block.level === 2 ? 1.15 : 1.05;
779
1060
  const headingSize = Math.round((theme.fontSize ?? 15) * scale);
780
- return /* @__PURE__ */ React7__default.createElement(View, { key: `h-${blockIndex}`, style: blockIndex > 0 ? styles2.blockGap : void 0 }, /* @__PURE__ */ React7__default.createElement(
1061
+ return /* @__PURE__ */ React8__default.createElement(View, { key: `h-${blockIndex}`, style: blockIndex > 0 ? styles4.blockGap : void 0 }, /* @__PURE__ */ React8__default.createElement(
781
1062
  Text,
782
1063
  {
783
1064
  accessibilityRole: "header",
784
1065
  style: [
785
1066
  baseTextStyle,
786
- styles2.bold,
1067
+ styles4.bold,
787
1068
  { fontSize: headingSize, lineHeight: Math.round(headingSize * 1.35) }
788
1069
  ]
789
1070
  },
@@ -795,23 +1076,23 @@ function ChatMarkdown({ content, theme, contentWidth }) {
795
1076
  ));
796
1077
  }
797
1078
  if (block.kind === "codeblock") {
798
- return /* @__PURE__ */ React7__default.createElement(
1079
+ return /* @__PURE__ */ React8__default.createElement(
799
1080
  View,
800
1081
  {
801
1082
  key: `code-${blockIndex}`,
802
1083
  style: [
803
- styles2.codeBlock,
804
- blockIndex > 0 ? styles2.blockGap : void 0,
1084
+ styles4.codeBlock,
1085
+ blockIndex > 0 ? styles4.blockGap : void 0,
805
1086
  {
806
1087
  backgroundColor: theme.codeBackground ?? (theme.variant === "user" ? "rgba(255,255,255,0.18)" : "rgba(0,0,0,0.055)")
807
1088
  }
808
1089
  ]
809
1090
  },
810
- /* @__PURE__ */ React7__default.createElement(
1091
+ /* @__PURE__ */ React8__default.createElement(
811
1092
  Text,
812
1093
  {
813
1094
  style: [
814
- styles2.code,
1095
+ styles4.code,
815
1096
  {
816
1097
  color: theme.codeColor ?? theme.color,
817
1098
  fontSize: (theme.fontSize ?? 15) - 1,
@@ -824,28 +1105,28 @@ function ChatMarkdown({ content, theme, contentWidth }) {
824
1105
  );
825
1106
  }
826
1107
  if (block.kind === "bullet") {
827
- return /* @__PURE__ */ React7__default.createElement(View, { key: `ul-${blockIndex}`, style: blockIndex > 0 ? styles2.blockGap : void 0 }, block.items.map((item, itemIndex) => /* @__PURE__ */ React7__default.createElement(
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(
828
1109
  Text,
829
1110
  {
830
1111
  key: `ul-${blockIndex}-${itemIndex}`,
831
- style: [baseTextStyle, itemIndex > 0 ? styles2.listItemGap : void 0]
1112
+ style: [baseTextStyle, itemIndex > 0 ? styles4.listItemGap : void 0]
832
1113
  },
833
1114
  "\u2022 ",
834
1115
  renderInlineSegments(item, theme, baseTextStyle)
835
1116
  )));
836
1117
  }
837
- return /* @__PURE__ */ React7__default.createElement(View, { key: `ol-${blockIndex}`, style: blockIndex > 0 ? styles2.blockGap : void 0 }, block.items.map((item, itemIndex) => /* @__PURE__ */ React7__default.createElement(
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(
838
1119
  Text,
839
1120
  {
840
1121
  key: `ol-${blockIndex}-${itemIndex}`,
841
- style: [baseTextStyle, itemIndex > 0 ? styles2.listItemGap : void 0]
1122
+ style: [baseTextStyle, itemIndex > 0 ? styles4.listItemGap : void 0]
842
1123
  },
843
1124
  `${itemIndex + 1}. `,
844
1125
  renderInlineSegments(item, theme, baseTextStyle)
845
1126
  )));
846
1127
  }));
847
1128
  }
848
- var styles2 = StyleSheet.create({
1129
+ var styles4 = StyleSheet.create({
849
1130
  root: {
850
1131
  flexShrink: 1,
851
1132
  alignSelf: "stretch"
@@ -889,7 +1170,7 @@ function AppilotsThemeProvider({
889
1170
  () => mergeThemeTokens(resolveBaseTheme(mode, systemScheme), theme ?? void 0),
890
1171
  [mode, systemScheme, theme]
891
1172
  );
892
- return /* @__PURE__ */ React7__default.createElement(ThemeContext.Provider, { value: tokens }, children);
1173
+ return /* @__PURE__ */ React8__default.createElement(ThemeContext.Provider, { value: tokens }, children);
893
1174
  }
894
1175
  function useAppilotsTheme() {
895
1176
  return useContext(ThemeContext);
@@ -982,177 +1263,6 @@ var appilotsTheme = {
982
1263
  fromTailwind: appilotsThemeFromTailwind
983
1264
  };
984
1265
 
985
- // src/i18n/bundles/pt-BR.ts
986
- var ptBR = {
987
- chatTitle: "Assistente Appilots",
988
- clearButton: "Limpar",
989
- inputPlaceholder: "Digite uma mensagem...",
990
- emptyTitle: "Como posso ajudar?",
991
- emptySubtitle: "Navego, preencho formul\xE1rios e executo a\xE7\xF5es neste app.",
992
- thinking: "Pensando...",
993
- statusAnalyzing: "Analisando a tela...",
994
- statusWaitingApp: "Aguardando o app responder...",
995
- statusAdjusting: "Ajustando o plano...",
996
- poweredBy: "Powered by Appilots",
997
- approve: "Aprovar",
998
- reject: "Rejeitar",
999
- cancelledByUser: "Cancelado pelo usu\xE1rio",
1000
- confirmDestructiveTitle: "A\xE7\xE3o destrutiva",
1001
- confirmTitle: "Confirma\xE7\xE3o necess\xE1ria",
1002
- confirmMessage: 'Tem certeza que deseja "{action}"?',
1003
- confirmDestructiveApprove: "Sim, executar",
1004
- confirmApprove: "Confirmar",
1005
- confirmCancel: "Cancelar",
1006
- confirmThisAction: "esta a\xE7\xE3o",
1007
- unknownAction: "A\xE7\xE3o desconhecida",
1008
- somethingWentWrong: "Algo deu errado",
1009
- openChatA11y: "Abrir chat do assistente",
1010
- closeChatA11y: "Fechar chat do assistente",
1011
- stopGeneratingA11y: "Parar gera\xE7\xE3o",
1012
- talkToHumanA11y: "Falar com um atendente humano",
1013
- humanAgentLabel: "Atendente",
1014
- escalationRequested: "Certo! Chamei um atendente humano. Voc\xEA pode continuar usando o app normalmente enquanto isso.",
1015
- escalationConnected: "Um atendente entrou na conversa.",
1016
- escalationResolved: "O atendimento humano foi encerrado. Voltei a te ajudar por aqui.",
1017
- escalationOffer: "Se preferir, posso chamar uma pessoa para ajudar.",
1018
- escalationOfferChip: "Falar com um atendente",
1019
- escalationPendingBanner: "Aguardando um atendente...",
1020
- escalationActiveBanner: "Voc\xEA est\xE1 falando com um atendente"
1021
- };
1022
-
1023
- // src/i18n/bundles/en.ts
1024
- var en = {
1025
- chatTitle: "Appilots Assistant",
1026
- clearButton: "Clear",
1027
- inputPlaceholder: "Type a message...",
1028
- emptyTitle: "How can I help?",
1029
- emptySubtitle: "I navigate, fill forms, and run actions in this app.",
1030
- thinking: "Thinking...",
1031
- statusAnalyzing: "Reading the screen...",
1032
- statusWaitingApp: "Waiting for the app...",
1033
- statusAdjusting: "Adjusting the plan...",
1034
- poweredBy: "Powered by Appilots",
1035
- approve: "Approve",
1036
- reject: "Reject",
1037
- cancelledByUser: "Cancelled by user",
1038
- confirmDestructiveTitle: "Destructive action",
1039
- confirmTitle: "Confirmation needed",
1040
- confirmMessage: 'Are you sure you want to "{action}"?',
1041
- confirmDestructiveApprove: "Yes, do it",
1042
- confirmApprove: "Confirm",
1043
- confirmCancel: "Cancel",
1044
- confirmThisAction: "this action",
1045
- unknownAction: "Unknown action",
1046
- somethingWentWrong: "Something went wrong",
1047
- openChatA11y: "Open assistant chat",
1048
- closeChatA11y: "Close assistant chat",
1049
- stopGeneratingA11y: "Stop generating",
1050
- talkToHumanA11y: "Talk to a human agent",
1051
- humanAgentLabel: "Agent",
1052
- escalationRequested: "Done! I've called a human agent. You can keep using the app normally in the meantime.",
1053
- escalationConnected: "A human agent joined the conversation.",
1054
- escalationResolved: "The human handoff has ended. I'm back to help you here.",
1055
- escalationOffer: "If you prefer, I can bring in a person to help.",
1056
- escalationOfferChip: "Talk to a human",
1057
- escalationPendingBanner: "Waiting for a human agent...",
1058
- escalationActiveBanner: "You're talking to a human agent"
1059
- };
1060
-
1061
- // src/i18n/bundles/es.ts
1062
- var es = {
1063
- chatTitle: "Asistente Appilots",
1064
- clearButton: "Limpiar",
1065
- inputPlaceholder: "Escribe un mensaje...",
1066
- emptyTitle: "\xBFEn qu\xE9 puedo ayudarte?",
1067
- emptySubtitle: "Navego, relleno formularios y ejecuto acciones en esta app.",
1068
- thinking: "Pensando...",
1069
- statusAnalyzing: "Analizando la pantalla...",
1070
- statusWaitingApp: "Esperando a la app...",
1071
- statusAdjusting: "Ajustando el plan...",
1072
- poweredBy: "Hecho con Appilots",
1073
- approve: "Aprobar",
1074
- reject: "Rechazar",
1075
- cancelledByUser: "Cancelado por el usuario",
1076
- confirmDestructiveTitle: "Acci\xF3n destructiva",
1077
- confirmTitle: "Confirmaci\xF3n necesaria",
1078
- confirmMessage: '\xBFSeguro que quieres "{action}"?',
1079
- confirmDestructiveApprove: "S\xED, ejecutar",
1080
- confirmApprove: "Confirmar",
1081
- confirmCancel: "Cancelar",
1082
- confirmThisAction: "esta acci\xF3n",
1083
- unknownAction: "Acci\xF3n desconocida",
1084
- somethingWentWrong: "Algo sali\xF3 mal",
1085
- openChatA11y: "Abrir chat del asistente",
1086
- closeChatA11y: "Cerrar chat del asistente",
1087
- stopGeneratingA11y: "Detener generaci\xF3n",
1088
- talkToHumanA11y: "Hablar con un agente humano",
1089
- humanAgentLabel: "Agente",
1090
- escalationRequested: "\xA1Listo! Llam\xE9 a un agente humano. Mientras tanto puedes seguir usando la app con normalidad.",
1091
- escalationConnected: "Un agente humano se uni\xF3 a la conversaci\xF3n.",
1092
- escalationResolved: "La atenci\xF3n humana termin\xF3. Vuelvo a ayudarte por aqu\xED.",
1093
- escalationOffer: "Si prefieres, puedo llamar a una persona para ayudarte.",
1094
- escalationOfferChip: "Hablar con un agente",
1095
- escalationPendingBanner: "Esperando a un agente...",
1096
- escalationActiveBanner: "Est\xE1s hablando con un agente humano"
1097
- };
1098
- var BUNDLES = {
1099
- "pt-BR": ptBR,
1100
- en,
1101
- es
1102
- };
1103
- var DEFAULT_LOCALE = "en";
1104
- function detectDeviceLocale() {
1105
- try {
1106
- let raw;
1107
- if (Platform.OS === "ios") {
1108
- const settings = NativeModules?.SettingsManager?.settings;
1109
- raw = settings?.AppleLocale || (Array.isArray(settings?.AppleLanguages) ? settings?.AppleLanguages?.[0] : void 0);
1110
- } else if (Platform.OS === "android") {
1111
- raw = NativeModules?.I18nManager?.localeIdentifier;
1112
- }
1113
- if (!raw || typeof raw !== "string") return null;
1114
- const lower = raw.toLowerCase().replace("_", "-");
1115
- if (lower.startsWith("pt")) return "pt-BR";
1116
- if (lower.startsWith("es")) return "es";
1117
- if (lower.startsWith("en")) return "en";
1118
- return null;
1119
- } catch {
1120
- return null;
1121
- }
1122
- }
1123
- function resolveLocale(prop) {
1124
- if (prop && BUNDLES[prop]) return prop;
1125
- const detected = detectDeviceLocale();
1126
- if (detected && BUNDLES[detected]) return detected;
1127
- return DEFAULT_LOCALE;
1128
- }
1129
- var I18nContext = createContext(null);
1130
- function AppilotsI18nProvider({
1131
- locale,
1132
- overrides,
1133
- children
1134
- }) {
1135
- const value = useMemo(() => {
1136
- const resolved = resolveLocale(locale ?? null);
1137
- const merged = overrides ? { ...BUNDLES[resolved], ...overrides } : BUNDLES[resolved];
1138
- return {
1139
- locale: resolved,
1140
- strings: merged,
1141
- t: (key) => merged[key]
1142
- };
1143
- }, [locale, overrides]);
1144
- return /* @__PURE__ */ React7__default.createElement(I18nContext.Provider, { value }, children);
1145
- }
1146
- function useAppilotsI18n() {
1147
- const ctx = useContext(I18nContext);
1148
- if (ctx) return ctx;
1149
- return {
1150
- locale: DEFAULT_LOCALE,
1151
- strings: BUNDLES[DEFAULT_LOCALE],
1152
- t: (key) => BUNDLES[DEFAULT_LOCALE][key]
1153
- };
1154
- }
1155
-
1156
1266
  // src/components/AppilotsChat.tsx
1157
1267
  function translateLegacyTheme(legacy) {
1158
1268
  const out = {};
@@ -1183,31 +1293,35 @@ var { height: SCREEN_HEIGHT, width: SCREEN_WIDTH } = Dimensions.get("window");
1183
1293
  var CHAT_HORIZONTAL_PADDING = 16;
1184
1294
  var BUBBLE_HORIZONTAL_PADDING = 14;
1185
1295
  var ASSISTANT_CONTENT_WIDTH = SCREEN_WIDTH - CHAT_HORIZONTAL_PADDING * 2 - BUBBLE_HORIZONTAL_PADDING * 2;
1186
- var APPILOTS_SKIP_PROPS = {
1187
- __appilotsSkip: true,
1188
- __appilotsInternal: true
1189
- };
1190
1296
  function AppilotsChat(props) {
1191
1297
  const { degraded } = useAppilotsContext();
1192
1298
  if (degraded) return null;
1193
- 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 }));
1194
1300
  }
1195
1301
  function AppilotsChatSurface(props) {
1302
+ const [conversationLocale, setConversationLocale] = useState(null);
1196
1303
  const { remotePersonalization } = useAppilotsContext();
1197
1304
  const themeProp = useMemo(() => {
1198
1305
  if (!props.theme) return void 0;
1199
1306
  return isLegacyTheme(props.theme) ? translateLegacyTheme(props.theme) : props.theme;
1200
1307
  }, [props.theme]);
1201
- return /* @__PURE__ */ React7__default.createElement(
1308
+ return /* @__PURE__ */ React8__default.createElement(
1202
1309
  AppilotsThemeProvider,
1203
1310
  {
1204
1311
  theme: resolveTheme(themeProp, remotePersonalization),
1205
1312
  mode: resolveThemeMode(props.themeMode, remotePersonalization)
1206
1313
  },
1207
- /* @__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
+ )
1208
1321
  );
1209
1322
  }
1210
1323
  function AppilotsChatInner({
1324
+ onReplyLocaleChange,
1211
1325
  visible: controlledVisible,
1212
1326
  onClose,
1213
1327
  placeholder,
@@ -1223,14 +1337,22 @@ function AppilotsChatInner({
1223
1337
  triggerButton = "default",
1224
1338
  triggerIcon,
1225
1339
  triggerButtonColor: triggerButtonColorProp,
1226
- triggerButtonImage: triggerButtonImageProp
1340
+ triggerButtonImage: triggerButtonImageProp,
1341
+ experience: experienceProp,
1342
+ speech: speechProp
1227
1343
  }) {
1228
1344
  const theme = useAppilotsTheme();
1229
- const { strings, t } = useAppilotsI18n();
1345
+ const { strings, t, locale: resolvedLocale } = useAppilotsI18n();
1230
1346
  const {
1231
1347
  messages,
1348
+ mission,
1349
+ pauseMission,
1350
+ resumeMission,
1351
+ cancelMission,
1352
+ replyLocale,
1232
1353
  isLoading,
1233
1354
  loadingStatusKey,
1355
+ hasStartedActing,
1234
1356
  sendMessage,
1235
1357
  cancelMessage,
1236
1358
  clearMessages,
@@ -1245,6 +1367,9 @@ function AppilotsChatInner({
1245
1367
  offer: t("escalationOffer")
1246
1368
  }
1247
1369
  });
1370
+ useEffect(() => {
1371
+ onReplyLocaleChange(replyLocale);
1372
+ }, [replyLocale, onReplyLocaleChange]);
1248
1373
  const { pendingActions, executingActions, completedActions, approveAction, rejectAction } = useAppilotsActions({ autoExecute });
1249
1374
  const { emit, remotePersonalization } = useAppilotsContext();
1250
1375
  const {
@@ -1270,7 +1395,7 @@ function AppilotsChatInner({
1270
1395
  );
1271
1396
  const allActions = [...pendingActions, ...executingActions, ...completedActions];
1272
1397
  const timeline = buildChatTimeline(messages, allActions);
1273
- const lastAssistantMessageId = React7__default.useMemo(() => {
1398
+ const lastAssistantMessageId = React8__default.useMemo(() => {
1274
1399
  for (let i = messages.length - 1; i >= 0; i--) {
1275
1400
  if (messages[i].role === "assistant") return messages[i].id;
1276
1401
  }
@@ -1281,7 +1406,65 @@ function AppilotsChatInner({
1281
1406
  const [inputText, setInputText] = useState("");
1282
1407
  const flatListRef = useRef(null);
1283
1408
  const slideAnim = useRef(new Animated.Value(SCREEN_HEIGHT)).current;
1284
- 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" };
1285
1468
  const showSuggestedPrompts = shouldShowSuggestedPrompts({
1286
1469
  isVisible,
1287
1470
  messageCount: messages.length,
@@ -1292,14 +1475,38 @@ function AppilotsChatInner({
1292
1475
  limit: 3
1293
1476
  });
1294
1477
  const openChat = useCallback(() => {
1478
+ setExplicitlyOpened(true);
1295
1479
  setInternalVisible(true);
1296
1480
  emit({ type: "chat:open", timestamp: Date.now(), data: {} });
1297
1481
  }, [emit]);
1298
1482
  const closeChat = useCallback(() => {
1483
+ setExplicitlyOpened(false);
1299
1484
  setInternalVisible(false);
1300
1485
  onClose?.();
1301
1486
  emit({ type: "chat:close", timestamp: Date.now(), data: {} });
1302
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]);
1303
1510
  useEffect(() => {
1304
1511
  if (mode === "inline") return;
1305
1512
  Animated.spring(slideAnim, {
@@ -1314,6 +1521,12 @@ function AppilotsChatInner({
1314
1521
  setTimeout(() => flatListRef.current?.scrollToEnd({ animated: true }), 100);
1315
1522
  }
1316
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
+ });
1317
1530
  const handleSend = useCallback(() => {
1318
1531
  if (!inputText.trim()) return;
1319
1532
  sendMessage(inputText.trim());
@@ -1323,11 +1536,11 @@ function AppilotsChatInner({
1323
1536
  ({ item }) => {
1324
1537
  if (item.role === "system") {
1325
1538
  const isOffer = item.metadata?.escalationOffer === true;
1326
- 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(
1327
1540
  Text,
1328
1541
  {
1329
1542
  style: [
1330
- styles3.systemMessageText,
1543
+ styles5.systemMessageText,
1331
1544
  {
1332
1545
  color: theme.colors.textSecondary,
1333
1546
  fontFamily: theme.typography.fontFamily,
@@ -1336,13 +1549,13 @@ function AppilotsChatInner({
1336
1549
  ]
1337
1550
  },
1338
1551
  item.content
1339
- ), isOffer && !escalation && /* @__PURE__ */ React7__default.createElement(
1552
+ ), isOffer && !escalation && /* @__PURE__ */ React8__default.createElement(
1340
1553
  TouchableOpacity,
1341
1554
  {
1342
1555
  ...APPILOTS_SKIP_PROPS,
1343
1556
  style: [
1344
- styles3.chip,
1345
- styles3.escalationOfferChip,
1557
+ styles5.chip,
1558
+ styles5.escalationOfferChip,
1346
1559
  {
1347
1560
  backgroundColor: theme.colors.surface,
1348
1561
  borderColor: theme.colors.border,
@@ -1355,12 +1568,12 @@ function AppilotsChatInner({
1355
1568
  accessibilityLabel: strings.talkToHumanA11y,
1356
1569
  testID: "escalation-offer-chip"
1357
1570
  },
1358
- /* @__PURE__ */ React7__default.createElement(HeadsetIcon, { size: 14, color: theme.colors.textSecondary }),
1359
- /* @__PURE__ */ React7__default.createElement(
1571
+ /* @__PURE__ */ React8__default.createElement(HeadsetIcon, { size: 14, color: theme.colors.textSecondary }),
1572
+ /* @__PURE__ */ React8__default.createElement(
1360
1573
  Text,
1361
1574
  {
1362
1575
  style: [
1363
- styles3.chipText,
1576
+ styles5.chipText,
1364
1577
  {
1365
1578
  color: theme.colors.text,
1366
1579
  fontFamily: theme.typography.fontFamily,
@@ -1376,11 +1589,11 @@ function AppilotsChatInner({
1376
1589
  const isHumanAgent = item.role === "human_agent";
1377
1590
  const useWideAssistantLayout = !isUser && needsWideChatLayout(item.content);
1378
1591
  if (isHumanAgent) {
1379
- return /* @__PURE__ */ React7__default.createElement(View, { style: styles3.humanAgentContainer, testID: "human-agent-message" }, /* @__PURE__ */ React7__default.createElement(View, { style: styles3.humanAgentLabelRow }, /* @__PURE__ */ React7__default.createElement(HeadsetIcon, { size: 11, color: theme.colors.secondary }), /* @__PURE__ */ React7__default.createElement(
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(
1380
1593
  Text,
1381
1594
  {
1382
1595
  style: [
1383
- styles3.humanAgentLabel,
1596
+ styles5.humanAgentLabel,
1384
1597
  {
1385
1598
  color: theme.colors.textSecondary,
1386
1599
  fontFamily: theme.typography.fontFamily
@@ -1388,17 +1601,17 @@ function AppilotsChatInner({
1388
1601
  ]
1389
1602
  },
1390
1603
  escalation?.operatorName ?? strings.humanAgentLabel
1391
- )), /* @__PURE__ */ React7__default.createElement(
1604
+ )), /* @__PURE__ */ React8__default.createElement(
1392
1605
  View,
1393
1606
  {
1394
1607
  accessibilityLabel: item.content,
1395
1608
  style: [
1396
- styles3.messageBubble,
1397
- styles3.assistantBubble,
1609
+ styles5.messageBubble,
1610
+ styles5.assistantBubble,
1398
1611
  { backgroundColor: theme.colors.surface }
1399
1612
  ]
1400
1613
  },
1401
- /* @__PURE__ */ React7__default.createElement(
1614
+ /* @__PURE__ */ React8__default.createElement(
1402
1615
  ChatMarkdown,
1403
1616
  {
1404
1617
  content: item.content,
@@ -1413,21 +1626,21 @@ function AppilotsChatInner({
1413
1626
  )
1414
1627
  ));
1415
1628
  }
1416
- return /* @__PURE__ */ React7__default.createElement(
1629
+ return /* @__PURE__ */ React8__default.createElement(
1417
1630
  View,
1418
1631
  {
1419
1632
  testID: isUser ? void 0 : item.id === lastAssistantMessageId ? "assistant-last-message" : "assistant-message",
1420
1633
  accessibilityLabel: isUser ? void 0 : item.content,
1421
1634
  style: [
1422
- styles3.messageBubble,
1423
- isUser ? [styles3.userBubble, { backgroundColor: theme.colors.primary }] : [
1424
- styles3.assistantBubble,
1425
- useWideAssistantLayout && styles3.assistantBubbleWide,
1635
+ styles5.messageBubble,
1636
+ isUser ? [styles5.userBubble, { backgroundColor: theme.colors.primary }] : [
1637
+ styles5.assistantBubble,
1638
+ useWideAssistantLayout && styles5.assistantBubbleWide,
1426
1639
  { backgroundColor: theme.colors.surface }
1427
1640
  ]
1428
1641
  ]
1429
1642
  },
1430
- /* @__PURE__ */ React7__default.createElement(
1643
+ /* @__PURE__ */ React8__default.createElement(
1431
1644
  ChatMarkdown,
1432
1645
  {
1433
1646
  content: item.content,
@@ -1445,19 +1658,12 @@ function AppilotsChatInner({
1445
1658
  },
1446
1659
  [theme, lastAssistantMessageId, escalation, requestHuman, strings]
1447
1660
  );
1448
- const requireApproval = useCallback(
1449
- (action) => {
1450
- if (action.status !== "pending") return false;
1451
- if (action.type === "confirm") return true;
1452
- return !autoExecute;
1453
- },
1454
- [autoExecute]
1455
- );
1456
1661
  const renderBreadcrumb = useCallback(
1457
- (actions) => /* @__PURE__ */ React7__default.createElement(
1662
+ (actions) => /* @__PURE__ */ React8__default.createElement(
1458
1663
  ActionBreadcrumb,
1459
1664
  {
1460
1665
  actions,
1666
+ locale: resolvedLocale,
1461
1667
  primaryColor: theme.colors.primary,
1462
1668
  textColor: theme.colors.text,
1463
1669
  fontFamily: theme.typography.fontFamily,
@@ -1493,6 +1699,8 @@ function AppilotsChatInner({
1493
1699
  theme.colors.warning,
1494
1700
  theme.colors.error,
1495
1701
  theme.typography.fontFamily,
1702
+ strings,
1703
+ resolvedLocale,
1496
1704
  requireApproval,
1497
1705
  approveAction,
1498
1706
  rejectAction
@@ -1505,17 +1713,17 @@ function AppilotsChatInner({
1505
1713
  const showEmptySubtitle = resolvedEmptySubtitle.length > 0 && !(showSuggestedPrompts && suggestedPrompts.length > 0);
1506
1714
  const renderAvatar = () => {
1507
1715
  if (!assistantAvatar) {
1508
- 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 }] });
1509
1717
  }
1510
1718
  if (typeof assistantAvatar === "string") {
1511
- 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 });
1512
1720
  }
1513
1721
  if (typeof assistantAvatar === "number") {
1514
- return /* @__PURE__ */ React7__default.createElement(Image, { source: assistantAvatar, style: styles3.headerAvatar });
1722
+ return /* @__PURE__ */ React8__default.createElement(Image, { source: assistantAvatar, style: styles5.headerAvatar });
1515
1723
  }
1516
- return /* @__PURE__ */ React7__default.createElement(View, { style: styles3.headerAvatarSlot }, assistantAvatar);
1724
+ return /* @__PURE__ */ React8__default.createElement(View, { style: styles5.headerAvatarSlot }, assistantAvatar);
1517
1725
  };
1518
- 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";
1519
1727
  const fabPositionStyle = (() => {
1520
1728
  switch (position) {
1521
1729
  case "bottom-left":
@@ -1529,15 +1737,15 @@ function AppilotsChatInner({
1529
1737
  return { bottom: 24, right: 24 };
1530
1738
  }
1531
1739
  })();
1532
- const chatSurface = /* @__PURE__ */ React7__default.createElement(
1740
+ const chatSurface = /* @__PURE__ */ React8__default.createElement(
1533
1741
  View,
1534
1742
  {
1535
1743
  ...APPILOTS_SKIP_PROPS,
1536
1744
  style: [
1537
- styles3.chatContainer,
1538
- mode === "fullscreen" && styles3.chatContainerFullscreen,
1539
- mode === "sidebar" && styles3.chatContainerSidebar,
1540
- mode === "inline" && styles3.chatContainerInline,
1745
+ styles5.chatContainer,
1746
+ mode === "fullscreen" && styles5.chatContainerFullscreen,
1747
+ mode === "sidebar" && styles5.chatContainerSidebar,
1748
+ mode === "inline" && styles5.chatContainerInline,
1541
1749
  {
1542
1750
  backgroundColor: theme.colors.background,
1543
1751
  borderTopLeftRadius: theme.radii.lg,
@@ -1545,11 +1753,11 @@ function AppilotsChatInner({
1545
1753
  }
1546
1754
  ]
1547
1755
  },
1548
- /* @__PURE__ */ React7__default.createElement(View, { style: [styles3.header, { borderBottomColor: theme.colors.border }] }, /* @__PURE__ */ React7__default.createElement(View, { style: styles3.headerLeft }, renderAvatar(), /* @__PURE__ */ React7__default.createElement(
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(
1549
1757
  Text,
1550
1758
  {
1551
1759
  style: [
1552
- styles3.headerTitle,
1760
+ styles5.headerTitle,
1553
1761
  {
1554
1762
  color: theme.colors.text,
1555
1763
  fontFamily: theme.typography.fontFamily,
@@ -1558,50 +1766,50 @@ function AppilotsChatInner({
1558
1766
  ]
1559
1767
  },
1560
1768
  resolvedHeaderTitle
1561
- )), /* @__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(
1562
1770
  TouchableOpacity,
1563
1771
  {
1564
1772
  ...APPILOTS_SKIP_PROPS,
1565
1773
  onPress: () => requestHuman("user_requested"),
1566
- style: styles3.headerButton,
1774
+ style: styles5.headerButton,
1567
1775
  accessibilityRole: "button",
1568
1776
  accessibilityLabel: strings.talkToHumanA11y,
1569
1777
  testID: "escalation-header-button"
1570
1778
  },
1571
- /* @__PURE__ */ React7__default.createElement(
1779
+ /* @__PURE__ */ React8__default.createElement(
1572
1780
  HeadsetIcon,
1573
1781
  {
1574
1782
  size: 16,
1575
1783
  color: escalation ? theme.colors.primary : theme.colors.textSecondary
1576
1784
  }
1577
1785
  )
1578
- ), /* @__PURE__ */ React7__default.createElement(
1786
+ ), /* @__PURE__ */ React8__default.createElement(
1579
1787
  TouchableOpacity,
1580
1788
  {
1581
1789
  ...APPILOTS_SKIP_PROPS,
1582
1790
  onPress: clearMessages,
1583
- style: styles3.headerButton,
1791
+ style: styles5.headerButton,
1584
1792
  accessibilityRole: "button",
1585
1793
  testID: "assistant-clear"
1586
1794
  },
1587
- /* @__PURE__ */ React7__default.createElement(Text, { style: [styles3.headerButtonText, { color: theme.colors.textSecondary }] }, strings.clearButton)
1588
- ), 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(
1589
1797
  TouchableOpacity,
1590
1798
  {
1591
1799
  ...APPILOTS_SKIP_PROPS,
1592
1800
  onPress: closeChat,
1593
- style: styles3.closeButton,
1801
+ style: styles5.closeButton,
1594
1802
  accessibilityRole: "button",
1595
1803
  accessibilityLabel: strings.closeChatA11y,
1596
1804
  testID: "assistant-close"
1597
1805
  },
1598
- /* @__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")
1599
1807
  ))),
1600
- escalation && /* @__PURE__ */ React7__default.createElement(
1808
+ escalation && /* @__PURE__ */ React8__default.createElement(
1601
1809
  View,
1602
1810
  {
1603
1811
  style: [
1604
- styles3.escalationBanner,
1812
+ styles5.escalationBanner,
1605
1813
  {
1606
1814
  borderBottomColor: theme.colors.border,
1607
1815
  backgroundColor: theme.colors.primary + "0d"
@@ -1609,12 +1817,12 @@ function AppilotsChatInner({
1609
1817
  ],
1610
1818
  testID: "escalation-banner"
1611
1819
  },
1612
- /* @__PURE__ */ React7__default.createElement(HeadsetIcon, { size: 12, color: theme.colors.primary }),
1613
- /* @__PURE__ */ React7__default.createElement(
1820
+ /* @__PURE__ */ React8__default.createElement(HeadsetIcon, { size: 12, color: theme.colors.primary }),
1821
+ /* @__PURE__ */ React8__default.createElement(
1614
1822
  Text,
1615
1823
  {
1616
1824
  style: [
1617
- styles3.escalationBannerText,
1825
+ styles5.escalationBannerText,
1618
1826
  {
1619
1827
  color: theme.colors.textSecondary,
1620
1828
  fontFamily: theme.typography.fontFamily,
@@ -1625,10 +1833,11 @@ function AppilotsChatInner({
1625
1833
  escalation.status === "pending" ? strings.escalationPendingBanner : strings.escalationActiveBanner
1626
1834
  )
1627
1835
  ),
1628
- /* @__PURE__ */ React7__default.createElement(
1836
+ /* @__PURE__ */ React8__default.createElement(
1629
1837
  FlatList,
1630
1838
  {
1631
1839
  ...APPILOTS_SKIP_PROPS,
1840
+ testID: "assistant-transcript",
1632
1841
  ref: flatListRef,
1633
1842
  data: timeline,
1634
1843
  keyExtractor: (item) => item.type === "message" ? item.data.id : item.key,
@@ -1641,34 +1850,34 @@ function AppilotsChatInner({
1641
1850
  }
1642
1851
  return null;
1643
1852
  },
1644
- contentContainerStyle: styles3.messageList,
1645
- 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(
1646
1855
  Image,
1647
1856
  {
1648
1857
  source: typeof emptyStateIcon === "string" ? { uri: emptyStateIcon } : emptyStateIcon,
1649
- style: styles3.emptyIconImage,
1858
+ style: styles5.emptyIconImage,
1650
1859
  resizeMode: "contain"
1651
1860
  }
1652
- ) : /* @__PURE__ */ React7__default.createElement(
1861
+ ) : /* @__PURE__ */ React8__default.createElement(
1653
1862
  View,
1654
1863
  {
1655
- style: [styles3.emptyIconBadge, { backgroundColor: theme.colors.primary + "14" }]
1864
+ style: [styles5.emptyIconBadge, { backgroundColor: theme.colors.primary + "14" }]
1656
1865
  },
1657
- emptyStateIcon ? /* @__PURE__ */ React7__default.createElement(Text, { style: styles3.emptyIconEmoji }, emptyStateIcon) : /* @__PURE__ */ React7__default.createElement(ChatIcon, { size: 30, color: theme.colors.primary })
1658
- ), /* @__PURE__ */ React7__default.createElement(
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(
1659
1868
  Text,
1660
1869
  {
1661
1870
  style: [
1662
- styles3.emptyTitle,
1871
+ styles5.emptyTitle,
1663
1872
  { color: theme.colors.text, fontFamily: theme.typography.fontFamily }
1664
1873
  ]
1665
1874
  },
1666
1875
  resolvedEmptyTitle
1667
- ), showEmptySubtitle && /* @__PURE__ */ React7__default.createElement(
1876
+ ), showEmptySubtitle && /* @__PURE__ */ React8__default.createElement(
1668
1877
  Text,
1669
1878
  {
1670
1879
  style: [
1671
- styles3.emptySubtitle,
1880
+ styles5.emptySubtitle,
1672
1881
  {
1673
1882
  color: theme.colors.textSecondary,
1674
1883
  fontFamily: theme.typography.fontFamily
@@ -1677,12 +1886,12 @@ function AppilotsChatInner({
1677
1886
  numberOfLines: 2
1678
1887
  },
1679
1888
  resolvedEmptySubtitle
1680
- ), showSuggestedPrompts && suggestedPrompts.length > 0 && /* @__PURE__ */ React7__default.createElement(View, { style: styles3.chipsContainer, testID: "assistant-suggested-prompts" }, suggestedPrompts.map((prompt) => /* @__PURE__ */ React7__default.createElement(
1889
+ ), showSuggestedPrompts && suggestedPrompts.length > 0 && /* @__PURE__ */ React8__default.createElement(View, { style: styles5.chipsContainer, testID: "assistant-suggested-prompts" }, suggestedPrompts.map((prompt) => /* @__PURE__ */ React8__default.createElement(
1681
1890
  TouchableOpacity,
1682
1891
  {
1683
1892
  key: `${prompt.source}:${prompt.text}`,
1684
1893
  style: [
1685
- styles3.chip,
1894
+ styles5.chip,
1686
1895
  {
1687
1896
  backgroundColor: theme.colors.surface,
1688
1897
  borderColor: theme.colors.border,
@@ -1693,11 +1902,11 @@ function AppilotsChatInner({
1693
1902
  activeOpacity: 0.7,
1694
1903
  accessibilityLabel: `Suggested prompt: ${prompt.text}`
1695
1904
  },
1696
- /* @__PURE__ */ React7__default.createElement(
1905
+ /* @__PURE__ */ React8__default.createElement(
1697
1906
  Text,
1698
1907
  {
1699
1908
  style: [
1700
- styles3.chipText,
1909
+ styles5.chipText,
1701
1910
  {
1702
1911
  color: theme.colors.buttonText,
1703
1912
  fontFamily: theme.typography.fontFamily,
@@ -1709,20 +1918,20 @@ function AppilotsChatInner({
1709
1918
  prompt.text
1710
1919
  )
1711
1920
  )))),
1712
- ListFooterComponent: isLoading ? /* @__PURE__ */ React7__default.createElement(
1921
+ ListFooterComponent: isLoading ? /* @__PURE__ */ React8__default.createElement(
1713
1922
  View,
1714
1923
  {
1715
1924
  style: [
1716
- styles3.messageBubble,
1717
- styles3.assistantBubble,
1925
+ styles5.messageBubble,
1926
+ styles5.assistantBubble,
1718
1927
  { backgroundColor: theme.colors.surface }
1719
1928
  ]
1720
1929
  },
1721
- /* @__PURE__ */ React7__default.createElement(
1930
+ /* @__PURE__ */ React8__default.createElement(
1722
1931
  Text,
1723
1932
  {
1724
1933
  style: [
1725
- styles3.messageText,
1934
+ styles5.messageText,
1726
1935
  { color: theme.colors.textSecondary, fontFamily: theme.typography.fontFamily }
1727
1936
  ]
1728
1937
  },
@@ -1731,22 +1940,48 @@ function AppilotsChatInner({
1731
1940
  ) : null
1732
1941
  }
1733
1942
  ),
1734
- /* @__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(
1735
1970
  View,
1736
1971
  {
1737
1972
  style: [
1738
- styles3.inputContainer,
1973
+ styles5.inputContainer,
1739
1974
  {
1740
1975
  borderTopColor: theme.colors.border,
1741
1976
  paddingBottom: Math.max(insets.bottom, 10)
1742
1977
  }
1743
1978
  ]
1744
1979
  },
1745
- /* @__PURE__ */ React7__default.createElement(
1980
+ /* @__PURE__ */ React8__default.createElement(
1746
1981
  TextInput,
1747
1982
  {
1748
1983
  style: [
1749
- styles3.input,
1984
+ styles5.input,
1750
1985
  {
1751
1986
  backgroundColor: theme.colors.surface,
1752
1987
  color: theme.colors.text,
@@ -1763,16 +1998,35 @@ function AppilotsChatInner({
1763
1998
  multiline: true,
1764
1999
  blurOnSubmit: false,
1765
2000
  scrollEnabled: true,
1766
- editable: !isLoading,
2001
+ editable: !isLoading && (!mission || ["completed", "cancelled"].includes(mission.status)),
1767
2002
  testID: "assistant-input",
1768
2003
  __appilotsInternal: true
1769
2004
  }
1770
2005
  ),
1771
- 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(
1772
2026
  TouchableOpacity,
1773
2027
  {
1774
2028
  style: [
1775
- styles3.sendButton,
2029
+ styles5.sendButton,
1776
2030
  {
1777
2031
  backgroundColor: theme.colors.primary,
1778
2032
  borderRadius: theme.radii.md
@@ -1784,31 +2038,31 @@ function AppilotsChatInner({
1784
2038
  testID: "appilots-stop-button",
1785
2039
  activeOpacity: 0.7
1786
2040
  },
1787
- /* @__PURE__ */ React7__default.createElement(Text, { style: styles3.sendButtonText }, "\u25A0")
1788
- ) : /* @__PURE__ */ React7__default.createElement(
2041
+ /* @__PURE__ */ React8__default.createElement(Text, { style: styles5.sendButtonText }, "\u25A0")
2042
+ ) : /* @__PURE__ */ React8__default.createElement(
1789
2043
  TouchableOpacity,
1790
2044
  {
1791
2045
  style: [
1792
- styles3.sendButton,
2046
+ styles5.sendButton,
1793
2047
  {
1794
2048
  backgroundColor: inputText.trim() ? theme.colors.primary : theme.colors.primary + "40",
1795
2049
  borderRadius: theme.radii.md
1796
2050
  }
1797
2051
  ],
1798
2052
  onPress: handleSend,
1799
- disabled: !inputText.trim(),
2053
+ disabled: !inputText.trim() || !!(mission && !["completed", "cancelled"].includes(mission.status)),
1800
2054
  activeOpacity: 0.7,
1801
2055
  accessibilityRole: "button",
1802
2056
  testID: "assistant-send"
1803
2057
  },
1804
- /* @__PURE__ */ React7__default.createElement(Text, { style: styles3.sendButtonText }, "\u2191")
2058
+ /* @__PURE__ */ React8__default.createElement(Text, { style: styles5.sendButtonText }, "\u2191")
1805
2059
  )
1806
2060
  ),
1807
- 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(
1808
2062
  Text,
1809
2063
  {
1810
2064
  style: [
1811
- styles3.poweredByText,
2065
+ styles5.poweredByText,
1812
2066
  {
1813
2067
  color: theme.colors.textSecondary,
1814
2068
  fontFamily: theme.typography.fontFamily,
@@ -1824,8 +2078,8 @@ function AppilotsChatInner({
1824
2078
  }
1825
2079
  const renderTrigger = () => {
1826
2080
  if (triggerButton === "none") return null;
1827
- if (triggerButton !== "default" && React7__default.isValidElement(triggerButton)) {
1828
- return /* @__PURE__ */ React7__default.createElement(View, { ...APPILOTS_SKIP_PROPS, style: [styles3.fabWrapper, fabPositionStyle] }, /* @__PURE__ */ React7__default.createElement(
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(
1829
2083
  TouchableOpacity,
1830
2084
  {
1831
2085
  ...APPILOTS_SKIP_PROPS,
@@ -1838,12 +2092,12 @@ function AppilotsChatInner({
1838
2092
  triggerButton
1839
2093
  ));
1840
2094
  }
1841
- return /* @__PURE__ */ React7__default.createElement(
2095
+ return /* @__PURE__ */ React8__default.createElement(
1842
2096
  TouchableOpacity,
1843
2097
  {
1844
2098
  ...APPILOTS_SKIP_PROPS,
1845
2099
  style: [
1846
- styles3.fab,
2100
+ styles5.fab,
1847
2101
  fabPositionStyle,
1848
2102
  { backgroundColor: triggerButtonColor ?? theme.colors.primary }
1849
2103
  ],
@@ -1853,55 +2107,113 @@ function AppilotsChatInner({
1853
2107
  accessibilityLabel: strings.openChatA11y,
1854
2108
  testID: "assistant-launcher"
1855
2109
  },
1856
- triggerIcon ?? (triggerButtonImage ? /* @__PURE__ */ React7__default.createElement(
2110
+ triggerIcon ?? (triggerButtonImage ? /* @__PURE__ */ React8__default.createElement(
1857
2111
  Image,
1858
2112
  {
1859
2113
  source: typeof triggerButtonImage === "string" ? { uri: triggerButtonImage } : triggerButtonImage,
1860
- style: styles3.fabImage,
2114
+ style: styles5.fabImage,
1861
2115
  resizeMode: "contain"
1862
2116
  }
1863
- ) : /* @__PURE__ */ React7__default.createElement(ChatIcon, { size: 26, color: "#ffffff" }))
2117
+ ) : /* @__PURE__ */ React8__default.createElement(ChatIcon, { size: 26, color: "#ffffff" }))
1864
2118
  );
1865
2119
  };
1866
- return /* @__PURE__ */ React7__default.createElement(React7__default.Fragment, null, showTrigger && renderTrigger(), /* @__PURE__ */ React7__default.createElement(
1867
- 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,
1868
2155
  {
1869
2156
  ...APPILOTS_SKIP_PROPS,
1870
- visible: isVisible,
1871
- transparent: true,
1872
- animationType: "none",
1873
- 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
+ ]
1874
2171
  },
1875
- /* @__PURE__ */ React7__default.createElement(
1876
- Animated.View,
2172
+ /* @__PURE__ */ React8__default.createElement(
2173
+ KeyboardAvoidingView,
1877
2174
  {
1878
- ...APPILOTS_SKIP_PROPS,
1879
- style: [
1880
- mode === "fullscreen" ? styles3.modalContainerFullscreen : styles3.modalContainer,
1881
- mode === "sidebar" && styles3.modalContainerSidebar,
1882
- // Keeps the sheet out of the status bar / notch. It is padding
1883
- // and not a margin so the scrim still covers the full screen.
1884
- // Only reachable now that the sheet can grow to the top edge:
1885
- // `fullscreen` and `sidebar` always could, and `bubble` does
1886
- // whenever the keyboard shrinks it — without this, the header
1887
- // renders under the clock.
1888
- { paddingTop: insets.top },
1889
- { transform: [{ translateY: slideAnim }] }
1890
- ]
2175
+ style: styles5.keyboardView,
2176
+ behavior: Platform.OS === "ios" ? "padding" : "height",
2177
+ keyboardVerticalOffset: 0
1891
2178
  },
1892
- /* @__PURE__ */ React7__default.createElement(
1893
- KeyboardAvoidingView,
1894
- {
1895
- style: styles3.keyboardView,
1896
- behavior: Platform.OS === "ios" ? "padding" : "height",
1897
- keyboardVerticalOffset: 0
1898
- },
1899
- chatSurface
1900
- )
2179
+ chatSurface
1901
2180
  )
1902
- ));
2181
+ )));
1903
2182
  }
1904
- 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 },
1905
2217
  fab: {
1906
2218
  position: "absolute",
1907
2219
  width: 56,
@@ -1944,6 +2256,12 @@ var styles3 = StyleSheet.create({
1944
2256
  flexDirection: "row",
1945
2257
  justifyContent: "flex-end"
1946
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
+ },
1947
2265
  keyboardView: {
1948
2266
  flex: 1,
1949
2267
  justifyContent: "flex-end"
@@ -2208,6 +2526,11 @@ function ConfirmDialog(_props = {}) {
2208
2526
  return null;
2209
2527
  }
2210
2528
 
2529
+ // src/auto/appilotsListTotal.ts
2530
+ function appilotsListTotal(total) {
2531
+ return { appilotsTotalItemCount: total };
2532
+ }
2533
+
2211
2534
  // src/hoc/canHoldRef.ts
2212
2535
  var FORWARD_REF = /* @__PURE__ */ Symbol.for("react.forward_ref");
2213
2536
  var MEMO = /* @__PURE__ */ Symbol.for("react.memo");
@@ -2234,12 +2557,19 @@ function canHoldRef(type) {
2234
2557
  }
2235
2558
  }
2236
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
+
2237
2567
  // src/hoc/createAppilotsInput.tsx
2238
2568
  function normalizeId(label) {
2239
2569
  return label.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9\s]/g, "").trim().replace(/\s+(.)/g, (_, c) => c.toUpperCase());
2240
2570
  }
2241
2571
  function createAppilotsInput(WrappedComponent) {
2242
- const AppilotsInput = React7__default.forwardRef((props, ref) => {
2572
+ const AppilotsInput = React8__default.forwardRef((props, ref) => {
2243
2573
  const {
2244
2574
  appilotsId,
2245
2575
  appilotsScreen,
@@ -2294,10 +2624,14 @@ function createAppilotsInput(WrappedComponent) {
2294
2624
  },
2295
2625
  [ref]
2296
2626
  );
2297
- return /* @__PURE__ */ React7__default.createElement(
2627
+ return /* @__PURE__ */ React8__default.createElement(
2298
2628
  WrappedComponent,
2299
2629
  {
2300
- ...{ label, value, onChangeText, ...rest },
2630
+ ...withDeclaredIdentity(
2631
+ { label, value, onChangeText, ...rest },
2632
+ fieldId,
2633
+ appilotsEnabled
2634
+ ),
2301
2635
  ...canHoldRef(WrappedComponent) ? { ref: mergedRef } : {}
2302
2636
  }
2303
2637
  );
@@ -2309,7 +2643,7 @@ function normalizeId2(label) {
2309
2643
  return label.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9\s]/g, "").trim().replace(/\s+(.)/g, (_, c) => c.toUpperCase());
2310
2644
  }
2311
2645
  function createAppilotsButton(WrappedComponent) {
2312
- const AppilotsButton = React7__default.forwardRef((props, ref) => {
2646
+ const AppilotsButton = React8__default.forwardRef((props, ref) => {
2313
2647
  const {
2314
2648
  appilotsId,
2315
2649
  appilotsScreen,
@@ -2335,10 +2669,10 @@ function createAppilotsButton(WrappedComponent) {
2335
2669
  componentRegistry.unregister(targetId);
2336
2670
  };
2337
2671
  }, [targetId, appilotsEnabled, press, title, appilotsScreen]);
2338
- return /* @__PURE__ */ React7__default.createElement(
2672
+ return /* @__PURE__ */ React8__default.createElement(
2339
2673
  WrappedComponent,
2340
2674
  {
2341
- ...{ title, onPress, ...rest },
2675
+ ...withDeclaredIdentity({ title, onPress, ...rest }, targetId, appilotsEnabled),
2342
2676
  ...canHoldRef(WrappedComponent) ? { ref } : {}
2343
2677
  }
2344
2678
  );
@@ -2350,7 +2684,7 @@ function normalizeId3(label) {
2350
2684
  return label.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9\s]/g, "").trim().replace(/\s+(.)/g, (_, c) => c.toUpperCase());
2351
2685
  }
2352
2686
  function createAppilotsSwitch(WrappedComponent) {
2353
- const AppilotsSwitch = React7__default.forwardRef((props, ref) => {
2687
+ const AppilotsSwitch = React8__default.forwardRef((props, ref) => {
2354
2688
  const {
2355
2689
  appilotsId,
2356
2690
  appilotsScreen,
@@ -2381,10 +2715,14 @@ function createAppilotsSwitch(WrappedComponent) {
2381
2715
  componentRegistry.unregister(toggleId);
2382
2716
  };
2383
2717
  }, [toggleId, appilotsEnabled, getValue, setValue, label, appilotsScreen]);
2384
- return /* @__PURE__ */ React7__default.createElement(
2718
+ return /* @__PURE__ */ React8__default.createElement(
2385
2719
  WrappedComponent,
2386
2720
  {
2387
- ...{ label, value, onValueChange, ...rest },
2721
+ ...withDeclaredIdentity(
2722
+ { label, value, onValueChange, ...rest },
2723
+ toggleId,
2724
+ appilotsEnabled
2725
+ ),
2388
2726
  ...canHoldRef(WrappedComponent) ? { ref } : {}
2389
2727
  }
2390
2728
  );
@@ -2393,4 +2731,4 @@ function createAppilotsSwitch(WrappedComponent) {
2393
2731
  return AppilotsSwitch;
2394
2732
  }
2395
2733
 
2396
- 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 };