@mobileai/react-native 0.9.26 → 0.9.28

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +28 -15
  2. package/android/build.gradle +17 -0
  3. package/android/src/main/java/com/mobileai/overlay/FloatingOverlayDialogRootViewGroup.kt +243 -0
  4. package/android/src/main/java/com/mobileai/overlay/FloatingOverlayView.kt +281 -87
  5. package/android/src/newarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +52 -17
  6. package/android/src/oldarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +49 -2
  7. package/bin/generate-map.cjs +556 -126
  8. package/ios/Podfile +63 -0
  9. package/ios/Podfile.lock +2290 -0
  10. package/ios/Podfile.properties.json +4 -0
  11. package/ios/mobileaireactnative/AppDelegate.swift +69 -0
  12. package/ios/mobileaireactnative/Images.xcassets/AppIcon.appiconset/Contents.json +13 -0
  13. package/ios/mobileaireactnative/Images.xcassets/Contents.json +6 -0
  14. package/ios/mobileaireactnative/Images.xcassets/SplashScreenLegacy.imageset/Contents.json +21 -0
  15. package/ios/mobileaireactnative/Images.xcassets/SplashScreenLegacy.imageset/SplashScreenLegacy.png +0 -0
  16. package/ios/mobileaireactnative/Info.plist +55 -0
  17. package/ios/mobileaireactnative/PrivacyInfo.xcprivacy +48 -0
  18. package/ios/mobileaireactnative/SplashScreen.storyboard +47 -0
  19. package/ios/mobileaireactnative/Supporting/Expo.plist +6 -0
  20. package/ios/mobileaireactnative/mobileaireactnative-Bridging-Header.h +3 -0
  21. package/ios/mobileaireactnative.xcodeproj/project.pbxproj +547 -0
  22. package/ios/mobileaireactnative.xcodeproj/xcshareddata/xcschemes/mobileaireactnative.xcscheme +88 -0
  23. package/ios/mobileaireactnative.xcworkspace/contents.xcworkspacedata +10 -0
  24. package/lib/module/components/AIAgent.js +407 -148
  25. package/lib/module/components/AgentChatBar.js +253 -62
  26. package/lib/module/components/FloatingOverlayWrapper.js +68 -32
  27. package/lib/module/config/endpoints.js +22 -1
  28. package/lib/module/core/AgentRuntime.js +192 -24
  29. package/lib/module/core/FiberTreeWalker.js +410 -34
  30. package/lib/module/core/OutcomeVerifier.js +149 -0
  31. package/lib/module/core/systemPrompt.js +126 -44
  32. package/lib/module/providers/GeminiProvider.js +9 -3
  33. package/lib/module/services/MobileAIKnowledgeRetriever.js +1 -1
  34. package/lib/module/services/telemetry/MobileAI.js +1 -1
  35. package/lib/module/services/telemetry/TelemetryService.js +21 -2
  36. package/lib/module/services/telemetry/TouchAutoCapture.js +45 -35
  37. package/lib/module/specs/FloatingOverlayNativeComponent.ts +7 -1
  38. package/lib/module/support/supportPrompt.js +22 -7
  39. package/lib/module/support/supportStyle.js +55 -0
  40. package/lib/module/support/types.js +2 -0
  41. package/lib/module/tools/tapTool.js +77 -6
  42. package/lib/module/tools/typeTool.js +20 -0
  43. package/lib/module/utils/humanizeScreenName.js +49 -0
  44. package/lib/typescript/src/components/AIAgent.d.ts +6 -2
  45. package/lib/typescript/src/components/AgentChatBar.d.ts +15 -1
  46. package/lib/typescript/src/components/FloatingOverlayWrapper.d.ts +22 -10
  47. package/lib/typescript/src/config/endpoints.d.ts +4 -0
  48. package/lib/typescript/src/core/AgentRuntime.d.ts +17 -1
  49. package/lib/typescript/src/core/FiberTreeWalker.d.ts +12 -1
  50. package/lib/typescript/src/core/OutcomeVerifier.d.ts +46 -0
  51. package/lib/typescript/src/core/systemPrompt.d.ts +3 -10
  52. package/lib/typescript/src/core/types.d.ts +37 -1
  53. package/lib/typescript/src/index.d.ts +1 -0
  54. package/lib/typescript/src/services/MobileAIKnowledgeRetriever.d.ts +1 -1
  55. package/lib/typescript/src/services/telemetry/TelemetryService.d.ts +7 -1
  56. package/lib/typescript/src/services/telemetry/types.d.ts +1 -1
  57. package/lib/typescript/src/specs/FloatingOverlayNativeComponent.d.ts +5 -0
  58. package/lib/typescript/src/support/index.d.ts +1 -0
  59. package/lib/typescript/src/support/supportStyle.d.ts +9 -0
  60. package/lib/typescript/src/support/types.d.ts +3 -0
  61. package/lib/typescript/src/tools/tapTool.d.ts +3 -2
  62. package/lib/typescript/src/utils/humanizeScreenName.d.ts +6 -0
  63. package/lib/typescript/test-tree.d.ts +2 -0
  64. package/package.json +5 -2
  65. package/src/specs/FloatingOverlayNativeComponent.ts +7 -1
  66. package/ios/MobileAIFloatingOverlayComponentView.mm +0 -73
  67. package/ios/MobileAIPilotIntents.swift +0 -51
@@ -61,7 +61,9 @@ export function checkRageClick(label, telemetry) {
61
61
  const matching = recentTaps.filter(t => t.label === label && t.screen === currentScreen && now - t.ts < RAGE_WINDOW_MS);
62
62
  if (matching.length >= RAGE_THRESHOLD) {
63
63
  telemetry.track('rage_click', {
64
+ canonical_type: 'rage_click_detected',
64
65
  label,
66
+ element_label: label,
65
67
  count: matching.length,
66
68
  screen: currentScreen
67
69
  });
@@ -77,55 +79,63 @@ export function checkRageClick(label, telemetry) {
77
79
  * @returns A descriptive label string for the tapped element
78
80
  */
79
81
  export function extractTouchLabel(event) {
80
- // Try accessible properties first (most reliable)
81
82
  const target = event?.nativeEvent?.target;
82
83
  if (!target) return 'Unknown Element';
83
-
84
- // React Native internal: _targetInst (synthetic event Fiber ref)
85
- // We can walk the Fiber tree from the target to find text
86
84
  try {
87
- // Strategy 1: Fiber from the SyntheticEvent (works in dev and production RN >= 0.60)
88
- // Strategy 2: Walk up the Fiber tree from the touched element via DevTools hook
89
85
  let fiber = event?._targetInst || getFiberFromNativeTag(target);
90
86
  if (fiber) {
91
- // Walk up looking for text content or accessibility labels
92
87
  let current = fiber;
93
88
  let depth = 0;
94
- const MAX_DEPTH = 10;
89
+ const MAX_DEPTH = 12;
90
+ let bestLabel = null;
91
+ let detectedRole = null;
95
92
  while (current && depth < MAX_DEPTH) {
96
- // Check for accessibilityLabel
97
- if (current.memoizedProps?.accessibilityLabel) {
98
- return current.memoizedProps.accessibilityLabel;
99
- }
100
-
101
- // Check for testID
102
- if (current.memoizedProps?.testID) {
103
- return current.memoizedProps.testID;
93
+ // 1. Detect Component Type Context
94
+ if (!detectedRole) {
95
+ if (current.memoizedProps?.accessibilityRole) {
96
+ detectedRole = current.memoizedProps.accessibilityRole;
97
+ } else if (current.memoizedProps?.onValueChange && typeof current.memoizedProps?.value === 'boolean') {
98
+ detectedRole = 'Toggle/Switch';
99
+ } else if (current.memoizedProps?.onChangeText) {
100
+ detectedRole = 'TextInput';
101
+ } else if (current.memoizedProps?.onPress) {
102
+ detectedRole = 'Button';
103
+ } else if (current.type?.name || current.type?.displayName) {
104
+ const name = current.type.name || current.type.displayName;
105
+ if (typeof name === 'string' && name.length > 2 && !name.toLowerCase().includes('wrapper') && !name.startsWith('RCT')) {
106
+ detectedRole = name;
107
+ }
108
+ }
104
109
  }
105
110
 
106
- // Check for title (Button component)
107
- if (current.memoizedProps?.title) {
108
- return current.memoizedProps.title;
109
- }
110
-
111
- // Check for placeholder (TextInput)
112
- if (current.memoizedProps?.placeholder) {
113
- return `Input: ${current.memoizedProps.placeholder}`;
114
- }
115
-
116
- // Check if this is a Text node with children string
117
- if (typeof current.memoizedProps?.children === 'string' && current.memoizedProps.children.trim()) {
118
- return current.memoizedProps.children.trim();
119
- }
120
-
121
- // Check for nested text in children array
122
- if (Array.isArray(current.memoizedProps?.children)) {
123
- const textChild = findTextInChildren(current.memoizedProps.children);
124
- if (textChild) return textChild;
111
+ // 2. Detect String Label Output
112
+ if (!bestLabel) {
113
+ if (current.memoizedProps?.accessibilityLabel) {
114
+ bestLabel = current.memoizedProps.accessibilityLabel;
115
+ } else if (current.memoizedProps?.testID) {
116
+ bestLabel = current.memoizedProps.testID;
117
+ } else if (current.memoizedProps?.title) {
118
+ bestLabel = current.memoizedProps.title;
119
+ } else if (current.memoizedProps?.placeholder) {
120
+ bestLabel = current.memoizedProps.placeholder;
121
+ } else if (typeof current.memoizedProps?.children === 'string' && current.memoizedProps.children.trim()) {
122
+ bestLabel = current.memoizedProps.children.trim();
123
+ } else if (Array.isArray(current.memoizedProps?.children)) {
124
+ bestLabel = findTextInChildren(current.memoizedProps.children);
125
+ } else if (current.memoizedProps?.children && typeof current.memoizedProps.children === 'object') {
126
+ bestLabel = findTextInChildren([current.memoizedProps.children]);
127
+ }
125
128
  }
126
129
  current = current.return;
127
130
  depth++;
128
131
  }
132
+ if (bestLabel) {
133
+ if (detectedRole && detectedRole.toLowerCase() !== 'text' && detectedRole.toLowerCase() !== 'view') {
134
+ const formattedRole = detectedRole.charAt(0).toUpperCase() + detectedRole.slice(1);
135
+ return `[${formattedRole}] ${bestLabel}`;
136
+ }
137
+ return bestLabel;
138
+ }
129
139
  }
130
140
  } catch {
131
141
  // Fiber access failed — fall back gracefully
@@ -11,9 +11,15 @@
11
11
  */
12
12
 
13
13
  import type { ViewProps } from 'react-native';
14
+ import type { Int32 } from 'react-native/Libraries/Types/CodegenTypes';
14
15
  import codegenNativeComponent from 'react-native/Libraries/Utilities/codegenNativeComponent';
15
16
 
16
- export interface NativeProps extends ViewProps {}
17
+ export interface NativeProps extends ViewProps {
18
+ windowX?: Int32;
19
+ windowY?: Int32;
20
+ windowWidth?: Int32;
21
+ windowHeight?: Int32;
22
+ }
17
23
 
18
24
  // Codegen reads this export to generate the native component interfaces.
19
25
  export default codegenNativeComponent<NativeProps>('MobileAIFloatingOverlay');
@@ -6,12 +6,16 @@
6
6
  * Uses POSITIVE framing (what TO DO) instead of negative rules (per user's prompt engineering rules).
7
7
  */
8
8
 
9
+ import { buildSupportStylePrompt, resolveSupportStyle } from "./supportStyle.js";
10
+
9
11
  /**
10
12
  * Build the support mode system prompt addition.
11
13
  * This gets appended to the main system prompt when support mode is active.
12
14
  */
13
15
  export function buildSupportPrompt(config) {
14
16
  const parts = [];
17
+ const supportStyle = config.persona?.preset ?? 'warm-concise';
18
+ const stylePreset = resolveSupportStyle(supportStyle);
15
19
 
16
20
  // Core support persona
17
21
  parts.push(`
@@ -20,18 +24,26 @@ export function buildSupportPrompt(config) {
20
24
  You are a helpful customer support assistant representing the company. Your primary goal is to RESOLVE the user's issue through empathetic conversation. App navigation is a tool you USE when needed, not the first thing you propose.
21
25
 
22
26
  ### Identity & Context
23
- Adopt the persona of a dedicated human customer support team member. Speak on behalf of the company as an organization with human operational timelines.
27
+ Speak like a calm, caring human teammate. Sound emotionally safe, patient, and kind in every reply.
28
+
29
+ Treat any question about "you" or "when you will reply" as referring to the company's real support process, but explain that in warm, human language rather than corporate language. Reassure the user naturally, and make them feel cared for while you work on the issue.
24
30
 
25
- Base all discussions regarding processing, reviews, resolutions, and response expectations on standard operational business timelines. Treat the conversational context holistically—assume any user questions about "you" or "when you will reply" refer to the company's human support staff processing their real-world request. Express empathy naturally, and assure the user that the operational team is handling their ticket promptly.
31
+ ### Gentle Customer Care
32
+ - Lead with warmth before action.
33
+ - Make the user feel supported, not processed.
34
+ - Even when you need details or need to say no, keep your wording soft and respectful.
35
+ - Avoid cold, legalistic, commanding, or overly procedural phrasing.
36
+ - If something failed, acknowledge the frustration first, then guide the user gently toward the next step.
26
37
 
27
38
  ### Support Resolution Protocol (HEARD)
28
39
  Follow this sequence. Exhaust each level before moving to the next:
29
40
 
30
- 1. HEAR: Listen actively. Paraphrase the problem back to confirm you understand. Ask specific
31
- clarifying questions (which order? when? what happened exactly?).
41
+ 1. HEAR: Listen actively. Paraphrase the problem back to confirm you understand. Ask gentle,
42
+ specific clarifying questions (for example: which order, when it happened, and what went wrong).
32
43
 
33
44
  2. EMPATHIZE: Acknowledge the user's feelings with sincerity. Use their name if available.
34
- Say "I understand how frustrating this must be" not "I see you have an issue."
45
+ Use a genuine, varied phrase for example: "I hear you", "That makes total sense", "I'm sorry about that".
46
+ Avoid scripted lines like "I understand how frustrating this must be" — they sound hollow.
35
47
  Take responsibility where appropriate.
36
48
 
37
49
  3. ANSWER: Search the knowledge base (query_knowledge) for relevant policies, FAQs, and procedures.
@@ -49,6 +61,7 @@ Follow this sequence. Exhaust each level before moving to the next:
49
61
  5. DIAGNOSE: After resolution, briefly identify the root cause if visible
50
62
  (e.g. "It looks like the delivery partner marked it as delivered prematurely").
51
63
  Ask the user if the issue is fully resolved before calling done().`);
64
+ parts.push(buildSupportStylePrompt(supportStyle));
52
65
  parts.push(`
53
66
  ### Consent and Liability Guard
54
67
  - Treat money movement, subscription cancellation, deletion, final submission, and account/security changes as high-risk actions.
@@ -68,7 +81,7 @@ Follow this sequence. Exhaust each level before moving to the next:
68
81
  ### Progress Communication
69
82
  When executing a multi-step resolution, you must communicate your progress to keep the user informed.
70
83
  - Do NOT execute more than 2 tools in silence.
71
- - Use the 'ask_user' tool to say phrases like "I am checking your account details now..." or "Just a moment while I pull up that information."
84
+ - Use the 'ask_user' tool to say phrases like "Let me check that for you now." or "Just a moment while I pull that up."
72
85
  - Never leave the user waiting in silence during complex operations.`);
73
86
 
74
87
  // Agent Persona
@@ -80,9 +93,11 @@ When executing a multi-step resolution, you must communicate your progress to ke
80
93
  } = config.persona;
81
94
  let personaStr = `\n### AI Persona & Tone\n`;
82
95
  if (agentName) personaStr += `- Your name is ${agentName}. Introduce yourself if appropriate.\n`;
83
- if (tone) personaStr += `- Maintain a ${tone} tone throughout the conversation.\n`;
96
+ personaStr += `- Maintain a ${tone || stylePreset.tone} tone throughout the conversation.\n`;
84
97
  if (signOff) personaStr += `- When resolving an issue, sign off with: "${signOff}".\n`;
85
98
  parts.push(personaStr);
99
+ } else {
100
+ parts.push(`\n### AI Persona & Tone\n- Maintain a ${stylePreset.tone} tone throughout the conversation.\n`);
86
101
  }
87
102
 
88
103
  // Custom system context from the consumer
@@ -0,0 +1,55 @@
1
+ "use strict";
2
+
3
+ const PRESETS = {
4
+ /**
5
+ * Default — calm, human, to the point.
6
+ * Sounds like a competent teammate who genuinely cares, not a scripted bot.
7
+ */
8
+ 'warm-concise': {
9
+ tone: 'calm, warm, and direct',
10
+ prompt: `
11
+ ### Support Style: Warm Concise
12
+ - Sound like a calm human who is good at their job and genuinely wants to help.
13
+ - Acknowledge the situation once — then focus on solving it. Do not repeat the same empathy phrase twice in a conversation.
14
+ - Keep each message short and scannable (1-3 sentences). Users are on mobile.
15
+ - Use natural language: say "Got it" not "I understand your concern"; say "Let me check" not "I will certainly look into that for you".
16
+ - When something went wrong, own it simply and move on: "That shouldn't have happened — let me fix it."
17
+ - Sound confident and in control. Users trust agents who know what to do.
18
+ - Vary your acknowledgment phrases naturally: "I hear you", "Got it", "That makes sense", "Let's sort this out", "On it" — never repeat the same one twice in a row.`
19
+ },
20
+ /**
21
+ * WOW Service — warm, proactive, surprise-and-delight energy.
22
+ * Best for consumer apps where the brand identity is warm and friendly.
23
+ */
24
+ 'wow-service': {
25
+ tone: 'warm, proactive, and genuinely service-first',
26
+ prompt: `
27
+ ### Support Style: WOW Service
28
+ - Deliver genuinely memorable service — through helpfulness and warmth, not adjectives.
29
+ - Be human and real: a little personality is good, but stay grounded. Never be performatively cheerful.
30
+ - Own mistakes fast and recover without excuses: "That's on us — here's what I'll do."
31
+ - Look for small ways to go above and beyond after resolving the core issue.
32
+ - Never joke through a serious problem. Match the user's energy — if they're frustrated, be calm and direct, not upbeat.`
33
+ },
34
+ /**
35
+ * Neutral Professional — composed, clear, efficient.
36
+ * Best for fintech, healthcare, or enterprise apps.
37
+ */
38
+ 'neutral-professional': {
39
+ tone: 'clear, composed, and respectfully direct',
40
+ prompt: `
41
+ ### Support Style: Neutral Professional
42
+ - Prioritize clarity and efficiency above all. Every sentence should move the conversation forward.
43
+ - Stay warm but understated — acknowledge the issue, then get straight to the solution.
44
+ - Use plain, jargon-free language. Short sentences. No exclamation marks.
45
+ - Be transparent about what you know, what you're checking, and what the next step is.
46
+ - Avoid casual phrases or emotional flourish — composed professionalism builds trust here.`
47
+ }
48
+ };
49
+ export function resolveSupportStyle(style) {
50
+ return PRESETS[style ?? 'warm-concise'];
51
+ }
52
+ export function buildSupportStylePrompt(style) {
53
+ return resolveSupportStyle(style).prompt;
54
+ }
55
+ //# sourceMappingURL=supportStyle.js.map
@@ -1,2 +1,4 @@
1
1
  "use strict";
2
+
3
+ export {};
2
4
  //# sourceMappingURL=types.js.map
@@ -5,8 +5,9 @@
5
5
  *
6
6
  * Strategies (in priority order):
7
7
  * 1. Switch → onValueChange (toggle)
8
- * 2. Direct onPress on element
9
- * 3. Bubble up fiber tree to find parent onPress (max 5 levels)
8
+ * 2. Radio onPress / onValueChange / parent radio-group handler
9
+ * 3. Direct onPress on element
10
+ * 4. Bubble up fiber tree to find parent onPress (max 5 levels)
10
11
  *
11
12
  * Includes Maestro-style tap verification:
12
13
  * - Captures element count + screen name before tap
@@ -16,10 +17,43 @@
16
17
  import { walkFiberTree } from "../core/FiberTreeWalker.js";
17
18
  import { getParent, getProps } from "../core/FiberAdapter.js";
18
19
  import { dismissAlert } from "../core/NativeAlertInterceptor.js";
20
+ function isScalarSelectionValue(value) {
21
+ return typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean';
22
+ }
23
+ function getRadioSelectionPayload(props) {
24
+ return isScalarSelectionValue(props.value) ? props.value : true;
25
+ }
26
+ function getRadioSelectionHandler(props) {
27
+ if (typeof props.onValueChange === 'function') {
28
+ return {
29
+ channel: 'onValueChange',
30
+ handler: props.onValueChange
31
+ };
32
+ }
33
+ if (typeof props.onCheckedChange === 'function') {
34
+ return {
35
+ channel: 'onCheckedChange',
36
+ handler: props.onCheckedChange
37
+ };
38
+ }
39
+ if (typeof props.onChange === 'function') {
40
+ return {
41
+ channel: 'onChange',
42
+ handler: props.onChange
43
+ };
44
+ }
45
+ if (typeof props.onSelect === 'function') {
46
+ return {
47
+ channel: 'onSelect',
48
+ handler: props.onSelect
49
+ };
50
+ }
51
+ return null;
52
+ }
19
53
  export function createTapTool(context) {
20
54
  return {
21
55
  name: 'tap',
22
- description: 'Tap an interactive element by its index. Works universally on buttons, switches, and custom components.',
56
+ description: 'Tap an interactive element by its index. Works universally on buttons, radios, switches, and custom components.',
23
57
  parameters: {
24
58
  index: {
25
59
  type: 'number',
@@ -57,7 +91,31 @@ export function createTapTool(context) {
57
91
  }
58
92
  }
59
93
 
60
- // Strategy 2: Direct onPress
94
+ // Strategy 2: Radio → own selection handler
95
+ if (element.type === 'radio') {
96
+ const radioPayload = getRadioSelectionPayload(element.props);
97
+ const ownSelectionHandler = getRadioSelectionHandler(element.props);
98
+ if (element.props.onPress) {
99
+ try {
100
+ element.props.onPress();
101
+ await new Promise(resolve => setTimeout(resolve, 500));
102
+ return `✅ Selected [${args.index}] "${element.label}"`;
103
+ } catch (error) {
104
+ return `❌ Error selecting [${args.index}]: ${error.message}`;
105
+ }
106
+ }
107
+ if (ownSelectionHandler) {
108
+ try {
109
+ ownSelectionHandler.handler(radioPayload);
110
+ await new Promise(resolve => setTimeout(resolve, 500));
111
+ return `✅ Selected [${args.index}] "${element.label}"`;
112
+ } catch (error) {
113
+ return `❌ Error selecting [${args.index}]: ${error.message}`;
114
+ }
115
+ }
116
+ }
117
+
118
+ // Strategy 3: Direct onPress
61
119
  if (element.props.onPress) {
62
120
  try {
63
121
  element.props.onPress();
@@ -81,9 +139,10 @@ export function createTapTool(context) {
81
139
  }
82
140
  }
83
141
 
84
- // Strategy 3: Bubble up fiber tree (like RNTL's findEventHandler)
142
+ // Strategy 4: Bubble up fiber tree (like RNTL's findEventHandler)
85
143
  let fiber = getParent(element.fiberNode);
86
144
  let bubbleDepth = 0;
145
+ const radioPayload = element.type === 'radio' ? getRadioSelectionPayload(element.props) : undefined;
87
146
  while (fiber && bubbleDepth < 5) {
88
147
  const parentProps = getProps(fiber);
89
148
  if (parentProps.onPress && typeof parentProps.onPress === 'function') {
@@ -95,10 +154,22 @@ export function createTapTool(context) {
95
154
  return `❌ Error tapping parent of [${args.index}]: ${error.message}`;
96
155
  }
97
156
  }
157
+ if (element.type === 'radio') {
158
+ const parentSelectionHandler = getRadioSelectionHandler(parentProps);
159
+ if (parentSelectionHandler) {
160
+ try {
161
+ parentSelectionHandler.handler(radioPayload);
162
+ await new Promise(resolve => setTimeout(resolve, 500));
163
+ return `✅ Selected [${args.index}] "${element.label}" via parent group`;
164
+ } catch (error) {
165
+ return `❌ Error selecting [${args.index}] via parent group: ${error.message}`;
166
+ }
167
+ }
168
+ }
98
169
  fiber = getParent(fiber);
99
170
  bubbleDepth++;
100
171
  }
101
- return `❌ Element [${args.index}] "${element.label}" has no tap handler (no onPress or onValueChange found).`;
172
+ return `❌ Element [${args.index}] "${element.label}" has no tap handler (no onPress, onValueChange, or radio selection handler found).`;
102
173
  }
103
174
  };
104
175
  }
@@ -85,6 +85,21 @@ export function createTypeTool(context) {
85
85
  }
86
86
  }
87
87
 
88
+ // ── Strategy 1b: inner onChangeText (for wrapped 3rd-party inputs) ────
89
+ if (fiberNode) {
90
+ const innerTextInputFiber = findFiberNode(fiberNode, n => typeof getProps(n)?.onChangeText === 'function', 15);
91
+ if (innerTextInputFiber) {
92
+ const innerProps = getProps(innerTextInputFiber);
93
+ try {
94
+ innerProps.onChangeText(args.text);
95
+ await new Promise(resolve => setTimeout(resolve, 300));
96
+ return `✅ Typed "${args.text}" into wrapped component [${args.index}] "${label}"`;
97
+ } catch (err) {
98
+ console.warn(`[Agent] Inner onChangeText failed for element ${args.index}:`, err);
99
+ }
100
+ }
101
+ }
102
+
88
103
  // ── Strategy 2: uncontrolled — find native onChange + setNativeProps ──
89
104
  // For TextInputs with defaultValue only.
90
105
  // We need BOTH:
@@ -106,10 +121,12 @@ export function createTypeTool(context) {
106
121
  if (onChange || nativeInstance) {
107
122
  try {
108
123
  // Step 1: Update visual text in native view
124
+ let visualUpdated = false;
109
125
  if (nativeInstance && typeof nativeInstance.setNativeProps === 'function') {
110
126
  nativeInstance.setNativeProps({
111
127
  text: args.text
112
128
  });
129
+ visualUpdated = true;
113
130
  }
114
131
 
115
132
  // Step 2: Notify React's internal onChange so lastNativeText stays in sync
@@ -122,6 +139,9 @@ export function createTypeTool(context) {
122
139
  }
123
140
  });
124
141
  }
142
+ if (!visualUpdated) {
143
+ return `❌ Type failed: Cannot locate native host node to explicitly inject visual text into uncontrolled element [${args.index}].`;
144
+ }
125
145
  await new Promise(resolve => setTimeout(resolve, 300));
126
146
  return `✅ Typed "${args.text}" into [${args.index}] "${label}"`;
127
147
  } catch (err) {
@@ -0,0 +1,49 @@
1
+ "use strict";
2
+
3
+ /**
4
+ * Transforms raw navigation route names into human-readable labels.
5
+ * Designed to handle both Expo Router (file-based) and React Navigation conventions.
6
+ */
7
+ export function humanizeScreenName(route) {
8
+ if (!route) return '';
9
+ let name = route;
10
+
11
+ // 1. Strip Expo Router groups: e.g., "(tabs)/index" -> "index"
12
+ // Keep replacing in case of nested groups like "(app)/(tabs)/home"
13
+ name = name.replace(/\([^)]+\)\//g, '');
14
+
15
+ // 2. Skip internal layout and catch-all routes
16
+ if (name.includes('_layout') || name.includes('[...')) {
17
+ return '';
18
+ }
19
+
20
+ // 3. Handle nested indexes: "settings/index" -> "settings"
21
+ if (name.endsWith('/index')) {
22
+ name = name.replace(/\/index$/, '');
23
+ }
24
+
25
+ // 4. Special case root index
26
+ if (name === 'index') {
27
+ return 'Home';
28
+ }
29
+
30
+ // 5. Strip dynamic brackets: "[id]" -> "id"
31
+ name = name.replace(/\[([^\]]+)\]/g, '$1');
32
+
33
+ // Strip leading/trailing slashes just in case
34
+ name = name.replace(/^\/|\/$/g, '');
35
+
36
+ // 6. Split on kebab-case, snake_case, slash, and camelCase boundaries
37
+ // e.g., "product-details" -> "product details"
38
+ // e.g., "order_history" -> "order history"
39
+ // e.g., "UserProfile" -> "User Profile"
40
+ // e.g., "settings/profile" -> "settings profile"
41
+ name = name.replace(/[-_/]/g, ' ')
42
+ // Insert a space before all caps (but not at the start) to separate camelCase/PascalCase
43
+ .replace(/([a-z])([A-Z])/g, '$1 $2');
44
+
45
+ // 7. Title-case each word and clean extra spaces
46
+ name = name.split(/\s+/).filter(Boolean).map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ');
47
+ return name;
48
+ }
49
+ //# sourceMappingURL=humanizeScreenName.js.map
@@ -9,7 +9,7 @@
9
9
  */
10
10
  import React from 'react';
11
11
  import type { AIConsentConfig } from './AIConsentDialog';
12
- import type { ExecutionResult, ToolDefinition, AgentStep, TokenUsage, KnowledgeBaseConfig, ChatBarTheme, AIProviderName, ScreenMap, ProactiveHelpConfig, InteractionMode, CustomerSuccessConfig, OnboardingConfig } from '../core/types';
12
+ import type { ExecutionResult, ToolDefinition, AgentStep, TokenUsage, KnowledgeBaseConfig, ChatBarTheme, AIProviderName, ScreenMap, ProactiveHelpConfig, InteractionMode, CustomerSuccessConfig, OnboardingConfig, VerifierConfig, SupportStyle } from '../core/types';
13
13
  interface AIAgentProps {
14
14
  /**
15
15
  * API key (for local prototyping only).
@@ -41,6 +41,10 @@ interface AIAgentProps {
41
41
  voiceProxyHeaders?: Record<string, string>;
42
42
  /** LLM model name (provider-specific) */
43
43
  model?: string;
44
+ /** Support personality preset. Default: 'warm-concise'. */
45
+ supportStyle?: SupportStyle;
46
+ /** Optional outcome verifier configuration for critical actions. */
47
+ verifier?: VerifierConfig;
44
48
  /** Navigation container ref (from useNavigationContainerRef) */
45
49
  navRef?: any;
46
50
  /** Max agent steps per request */
@@ -216,6 +220,6 @@ interface AIAgentProps {
216
220
  */
217
221
  consent?: AIConsentConfig;
218
222
  }
219
- export declare function AIAgent({ apiKey, proxyUrl, proxyHeaders, voiceProxyUrl, voiceProxyHeaders, provider: providerName, model, navRef, maxSteps, showChatBar, children, onResult, interactiveBlacklist, interactiveWhitelist, onBeforeStep, onAfterStep, onBeforeTask, onAfterTask, transformScreenContent, customTools, instructions, stepDelay, mcpServerUrl, router, pathname, enableVoice, onTokenUsage, debug, knowledgeBase, knowledgeMaxTokens, enableUIControl, accentColor, theme, screenMap, useScreenMap, maxTokenBudget, maxCostUSD, analyticsKey, analyticsProxyUrl, analyticsProxyHeaders, proactiveHelp, userContext, pushToken, pushTokenType, interactionMode, showDiscoveryTooltip: showDiscoveryTooltipProp, discoveryTooltipMessage, customerSuccess, onboarding, consent, }: AIAgentProps): import("react/jsx-runtime").JSX.Element;
223
+ export declare function AIAgent({ apiKey, proxyUrl, proxyHeaders, voiceProxyUrl, voiceProxyHeaders, provider: providerName, model, supportStyle, verifier, navRef, maxSteps, showChatBar, children, onResult, interactiveBlacklist, interactiveWhitelist, onBeforeStep, onAfterStep, onBeforeTask, onAfterTask, transformScreenContent, customTools, instructions, stepDelay, mcpServerUrl, router, pathname, enableVoice, onTokenUsage, debug, knowledgeBase, knowledgeMaxTokens, enableUIControl, accentColor, theme, screenMap, useScreenMap, maxTokenBudget, maxCostUSD, analyticsKey, analyticsProxyUrl, analyticsProxyHeaders, proactiveHelp, userContext, pushToken, pushTokenType, interactionMode, showDiscoveryTooltip: showDiscoveryTooltipProp, discoveryTooltipMessage, customerSuccess, onboarding, consent, }: AIAgentProps): import("react/jsx-runtime").JSX.Element;
220
224
  export {};
221
225
  //# sourceMappingURL=AIAgent.d.ts.map
@@ -7,6 +7,7 @@ import type { ExecutionResult, AgentMode, ChatBarTheme, AIMessage, ConversationS
7
7
  import type { SupportTicket } from '../support/types';
8
8
  interface AgentChatBarProps {
9
9
  onSend: (message: string) => void;
10
+ onCancel?: () => void;
10
11
  isThinking: boolean;
11
12
  statusText?: string;
12
13
  lastResult: ExecutionResult | null;
@@ -67,7 +68,20 @@ interface AgentChatBarProps {
67
68
  onNewConversation?: () => void;
68
69
  pendingApprovalQuestion?: string | null;
69
70
  onPendingApprovalAction?: (action: 'approve' | 'reject') => void;
71
+ renderMode?: 'default' | 'android-native-window';
72
+ onWindowMetricsChange?: (metrics: {
73
+ x: number;
74
+ y: number;
75
+ width: number;
76
+ height: number;
77
+ }) => void;
78
+ windowMetrics?: {
79
+ x: number;
80
+ y: number;
81
+ width: number;
82
+ height: number;
83
+ } | null;
70
84
  }
71
- export declare function AgentChatBar({ onSend, isThinking, statusText, lastResult, language, availableModes, mode, onModeChange, onMicToggle, onSpeakerToggle, isMicActive, isSpeakerMuted, isAISpeaking, isVoiceConnected, onStopSession, theme, tickets, selectedTicketId, onTicketSelect, autoExpandTrigger, unreadCounts, totalUnread, showDiscoveryTooltip, discoveryTooltipMessage, onTooltipDismiss, chatMessages, conversations, isLoadingHistory, onConversationSelect, onNewConversation, pendingApprovalQuestion, onPendingApprovalAction, }: AgentChatBarProps): import("react/jsx-runtime").JSX.Element;
85
+ export declare function AgentChatBar({ onSend, onCancel, isThinking, statusText, lastResult, language, availableModes, mode, onModeChange, onMicToggle, onSpeakerToggle, isMicActive, isSpeakerMuted, isAISpeaking, isVoiceConnected, onStopSession, theme, tickets, selectedTicketId, onTicketSelect, autoExpandTrigger, unreadCounts, totalUnread, showDiscoveryTooltip, discoveryTooltipMessage, onTooltipDismiss, chatMessages, conversations, isLoadingHistory, onConversationSelect, onNewConversation, pendingApprovalQuestion, onPendingApprovalAction, renderMode, onWindowMetricsChange, windowMetrics, }: AgentChatBarProps): import("react/jsx-runtime").JSX.Element;
72
86
  export {};
73
87
  //# sourceMappingURL=AgentChatBar.d.ts.map
@@ -9,13 +9,10 @@
9
9
  * Renders ABOVE all native Modals, system alerts, and navigation chrome.
10
10
  * 2. Falls back to plain View if react-native-screens is not installed.
11
11
  *
12
- * Android (both Old and New Architecture):
13
- * 1. Native `MobileAIFloatingOverlay` ViewManager (bundled in this library).
14
- * Creates a Dialog window with TYPE_APPLICATION_PANEL (z=1000),
15
- * above normal app Dialog windows (TYPE_APPLICATION, z=2).
16
- * No SYSTEM_ALERT_WINDOW permission needed — scoped to app's own window.
17
- * 2. Falls back to plain View if the app hasn't been rebuilt after install
18
- * (graceful degradation with DEV warning).
12
+ * Android:
13
+ * 1. Uses a native panel dialog window when explicit bounds are provided.
14
+ * This keeps the floating agent compact and above native modal surfaces.
15
+ * 2. Falls back to a plain View otherwise.
19
16
  *
20
17
  * Usage:
21
18
  * <FloatingOverlayWrapper fallbackStyle={styles.floatingLayer}>
@@ -28,24 +25,39 @@
28
25
  * Note: FullWindowOverlay on iOS does NOT officially accept style props in its TS definition,
29
26
  * but passing StyleSheet.absoluteFill is often necessary to prevent dimensions collapsing conditionally.
30
27
  */
28
+ import React from 'react';
31
29
  /**
32
30
  * True when a native elevated overlay is available on the current platform.
33
31
  * Used by AIConsentDialog to decide whether to render as View vs Modal.
34
32
  *
35
33
  * iOS + react-native-screens installed → true
36
- * Android + native rebuild done → true
37
34
  * Everything else (fallback) → false
38
35
  */
39
36
  export declare const isNativeOverlayActive: boolean;
40
37
  interface FloatingOverlayWrapperProps {
41
38
  children: React.ReactNode;
39
+ androidWindowMetrics?: {
40
+ x: number;
41
+ y: number;
42
+ width: number;
43
+ height: number;
44
+ } | null;
45
+ onAndroidWindowDragEnd?: (metrics: {
46
+ x: number;
47
+ y: number;
48
+ width: number;
49
+ height: number;
50
+ }) => void;
42
51
  /**
43
52
  * Style applied to the View wrapper when no native overlay is available.
44
53
  * Ignored on iOS (FullWindowOverlay creates its own UIWindow) and
45
- * Android (native module creates its own Dialog window).
54
+ * Android (native module creates its own panel dialog window).
46
55
  */
47
56
  fallbackStyle?: any;
48
57
  }
49
- export declare function FloatingOverlayWrapper({ children, fallbackStyle, }: FloatingOverlayWrapperProps): React.ReactElement;
58
+ export interface FloatingOverlayWrapperHandle {
59
+ setAndroidWindowMetrics: (metrics: NonNullable<FloatingOverlayWrapperProps['androidWindowMetrics']>) => void;
60
+ }
61
+ export declare const FloatingOverlayWrapper: React.ForwardRefExoticComponent<FloatingOverlayWrapperProps & React.RefAttributes<FloatingOverlayWrapperHandle>>;
50
62
  export {};
51
63
  //# sourceMappingURL=FloatingOverlayWrapper.d.ts.map
@@ -8,6 +8,10 @@
8
8
  * to route telemetry through your own backend without touching this file.
9
9
  */
10
10
  export declare const ENDPOINTS: {
11
+ /** Hosted MobileAI text proxy — used by default when analyticsKey is set */
12
+ readonly hostedTextProxy: `${string}/api/v1/hosted-proxy/text`;
13
+ /** Hosted MobileAI voice proxy — used by default when analyticsKey is set */
14
+ readonly hostedVoiceProxy: `${string}/ws/hosted-proxy/voice`;
11
15
  /** Telemetry event ingest — receives batched SDK events */
12
16
  readonly telemetryIngest: `${string}/api/v1/events`;
13
17
  /** Feature flag sync — fetches remote flags for this analyticsKey */
@@ -23,14 +23,30 @@ export declare class AgentRuntime {
23
23
  private uiControlOverride?;
24
24
  private lastDehydratedRoot;
25
25
  private currentTraceId;
26
+ private currentUserGoal;
27
+ private verifierProvider;
28
+ private outcomeVerifier;
29
+ private pendingCriticalVerification;
26
30
  private originalErrorHandler;
27
31
  private lastSuppressedError;
28
32
  private graceTimer;
29
33
  private originalReportErrorsAsExceptions;
30
- private appActionApproved;
34
+ private appActionApprovalScope;
35
+ private appActionApprovalSource;
31
36
  private static readonly APP_ACTION_TOOLS;
32
37
  getConfig(): AgentConfig;
38
+ private resetAppActionApproval;
39
+ private grantWorkflowApproval;
40
+ private hasWorkflowApproval;
41
+ private debugLogChunked;
42
+ private formatInteractiveForDebug;
43
+ private debugScreenSnapshot;
33
44
  constructor(provider: AIProvider, config: AgentConfig, rootRef: any, navRef: any);
45
+ private getVerifier;
46
+ private createCurrentVerificationSnapshot;
47
+ private updateCriticalVerification;
48
+ private maybeStartCriticalVerification;
49
+ private shouldBlockSuccessCompletion;
34
50
  private registerBuiltInTools;
35
51
  /**
36
52
  * Register only knowledge-assistant tools (no UI control).