@mobileai/react-native 0.9.27 → 0.9.29
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/README.md +28 -16
- package/android/build.gradle +17 -0
- package/android/src/main/java/com/mobileai/overlay/FloatingOverlayDialogRootViewGroup.kt +243 -0
- package/android/src/main/java/com/mobileai/overlay/FloatingOverlayView.kt +281 -87
- package/android/src/newarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +52 -17
- package/android/src/oldarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +49 -2
- package/bin/generate-map.cjs +45 -6
- package/ios/MobileAIFloatingOverlayComponentView.h +8 -0
- package/ios/MobileAIFloatingOverlayComponentView.mm +12 -41
- package/ios/Podfile +63 -0
- package/ios/Podfile.lock +2290 -0
- package/ios/Podfile.properties.json +4 -0
- package/ios/mobileaireactnative/AppDelegate.swift +69 -0
- package/ios/mobileaireactnative/Images.xcassets/AppIcon.appiconset/Contents.json +13 -0
- package/ios/mobileaireactnative/Images.xcassets/Contents.json +6 -0
- package/ios/mobileaireactnative/Images.xcassets/SplashScreenLegacy.imageset/Contents.json +21 -0
- package/ios/mobileaireactnative/Images.xcassets/SplashScreenLegacy.imageset/SplashScreenLegacy.png +0 -0
- package/ios/mobileaireactnative/Info.plist +55 -0
- package/ios/mobileaireactnative/PrivacyInfo.xcprivacy +48 -0
- package/ios/mobileaireactnative/SplashScreen.storyboard +47 -0
- package/ios/mobileaireactnative/Supporting/Expo.plist +6 -0
- package/ios/mobileaireactnative/mobileaireactnative-Bridging-Header.h +3 -0
- package/ios/mobileaireactnative.xcodeproj/project.pbxproj +547 -0
- package/ios/mobileaireactnative.xcodeproj/xcshareddata/xcschemes/mobileaireactnative.xcscheme +88 -0
- package/ios/mobileaireactnative.xcworkspace/contents.xcworkspacedata +10 -0
- package/lib/module/components/AIAgent.js +501 -191
- package/lib/module/components/AgentChatBar.js +250 -59
- package/lib/module/components/FloatingOverlayWrapper.js +68 -32
- package/lib/module/config/endpoints.js +22 -1
- package/lib/module/core/AgentRuntime.js +110 -8
- package/lib/module/core/FiberTreeWalker.js +211 -10
- package/lib/module/core/OutcomeVerifier.js +149 -0
- package/lib/module/core/systemPrompt.js +96 -25
- package/lib/module/providers/GeminiProvider.js +9 -3
- package/lib/module/services/telemetry/TelemetryService.js +21 -2
- package/lib/module/services/telemetry/TouchAutoCapture.js +235 -38
- package/lib/module/services/telemetry/analyticsLabeling.js +187 -0
- package/lib/module/specs/FloatingOverlayNativeComponent.ts +7 -1
- package/lib/module/support/supportPrompt.js +22 -7
- package/lib/module/support/supportStyle.js +55 -0
- package/lib/module/support/types.js +2 -0
- package/lib/module/tools/typeTool.js +20 -0
- package/lib/module/utils/humanizeScreenName.js +49 -0
- package/lib/typescript/src/components/AIAgent.d.ts +6 -2
- package/lib/typescript/src/components/AgentChatBar.d.ts +15 -1
- package/lib/typescript/src/components/FloatingOverlayWrapper.d.ts +22 -10
- package/lib/typescript/src/config/endpoints.d.ts +4 -0
- package/lib/typescript/src/core/AgentRuntime.d.ts +12 -3
- package/lib/typescript/src/core/FiberTreeWalker.d.ts +12 -1
- package/lib/typescript/src/core/OutcomeVerifier.d.ts +46 -0
- package/lib/typescript/src/core/systemPrompt.d.ts +3 -10
- package/lib/typescript/src/core/types.d.ts +63 -0
- package/lib/typescript/src/index.d.ts +1 -0
- package/lib/typescript/src/services/telemetry/TelemetryService.d.ts +7 -1
- package/lib/typescript/src/services/telemetry/TouchAutoCapture.d.ts +6 -1
- package/lib/typescript/src/services/telemetry/analyticsLabeling.d.ts +20 -0
- package/lib/typescript/src/services/telemetry/types.d.ts +1 -1
- package/lib/typescript/src/specs/FloatingOverlayNativeComponent.d.ts +5 -0
- package/lib/typescript/src/support/index.d.ts +1 -0
- package/lib/typescript/src/support/supportStyle.d.ts +9 -0
- package/lib/typescript/src/support/types.d.ts +3 -0
- package/lib/typescript/src/utils/humanizeScreenName.d.ts +6 -0
- package/package.json +10 -10
- package/src/specs/FloatingOverlayNativeComponent.ts +7 -1
- package/ios/MobileAIPilotIntents.swift +0 -51
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const GENERIC_LABELS = new Set(['button', 'buttons', 'component', 'components', 'container', 'containers', 'content', 'cta', 'item', 'items', 'label', 'labels', 'root', 'row', 'rows', 'screen', 'screens', 'text', 'texts', 'title', 'titles', 'unknown', 'value', 'values', 'view', 'views', 'wrapper', 'wrappers']);
|
|
4
|
+
const GENERIC_IDENTIFIER_TOKENS = new Set(['btn', 'button', 'card', 'cell', 'component', 'container', 'content', 'cta', 'icon', 'input', 'item', 'label', 'node', 'pressable', 'root', 'row', 'screen', 'target', 'text', 'tile', 'toggle', 'view', 'wrapper']);
|
|
5
|
+
const INTERNAL_NAME_PATTERNS = [/^RCT[A-Z]/, /^React/, /^TextImpl/i, /^Android/i, /^UI[A-Z]/, /^RN[A-Z]/, /^Virtualized/i, /^ScrollResponder$/i, /^Animated(Component|.*Wrapper)?$/i, /^Touchable[A-Z]/, /^Pressable$/i, /^View$/i, /^Text$/i, /^Modal$/i, /Legacy/i, /Wrapper/i, /Context$/i, /Provider$/i];
|
|
6
|
+
function normalizeWhitespace(value) {
|
|
7
|
+
if (!value) return '';
|
|
8
|
+
return String(value).replace(/\s+/g, ' ').trim();
|
|
9
|
+
}
|
|
10
|
+
function toTitleCase(value) {
|
|
11
|
+
return value.split(/\s+/).filter(Boolean).map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(' ');
|
|
12
|
+
}
|
|
13
|
+
function humanizeIdentifier(value) {
|
|
14
|
+
const humanized = value.replace(/^icon:/i, '').replace(/[_./-]+/g, ' ').replace(/([a-z])([A-Z])/g, '$1 $2').replace(/\b\d+\b/g, ' ').replace(/\s+/g, ' ').trim();
|
|
15
|
+
return toTitleCase(humanized);
|
|
16
|
+
}
|
|
17
|
+
function stripDecorators(value) {
|
|
18
|
+
return value.replace(/^\[[^\]]+\]\s*/g, '').replace(/^["'`]+|["'`]+$/g, '').trim();
|
|
19
|
+
}
|
|
20
|
+
function looksInternal(value) {
|
|
21
|
+
return INTERNAL_NAME_PATTERNS.some(pattern => pattern.test(value));
|
|
22
|
+
}
|
|
23
|
+
function isLowSignalValue(value) {
|
|
24
|
+
const lowered = value.toLowerCase();
|
|
25
|
+
return GENERIC_LABELS.has(lowered);
|
|
26
|
+
}
|
|
27
|
+
function sanitizeLabelValue(rawValue, source) {
|
|
28
|
+
let normalized = normalizeWhitespace(rawValue);
|
|
29
|
+
if (!normalized) return null;
|
|
30
|
+
normalized = stripDecorators(normalized);
|
|
31
|
+
if (!normalized) return null;
|
|
32
|
+
if (source === 'test-id' || source === 'icon' || source === 'context') {
|
|
33
|
+
normalized = humanizeIdentifier(normalized);
|
|
34
|
+
}
|
|
35
|
+
if (!normalized) return null;
|
|
36
|
+
if (normalized.length > 80) return null;
|
|
37
|
+
if (looksInternal(normalized)) return null;
|
|
38
|
+
const words = normalized.split(/\s+/).filter(Boolean);
|
|
39
|
+
if (words.length === 0) return null;
|
|
40
|
+
if (words.length === 1) {
|
|
41
|
+
const token = words[0].toLowerCase();
|
|
42
|
+
if (GENERIC_IDENTIFIER_TOKENS.has(token) || GENERIC_LABELS.has(token)) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (isLowSignalValue(normalized)) return null;
|
|
47
|
+
return normalized;
|
|
48
|
+
}
|
|
49
|
+
function scoreAnalyticsLabel(label, source, isInteractiveContext = false) {
|
|
50
|
+
let score = 0;
|
|
51
|
+
const words = label.split(/\s+/).filter(Boolean);
|
|
52
|
+
if (source === 'accessibility') score += 95;
|
|
53
|
+
if (source === 'deep-text') score += 78;
|
|
54
|
+
if (source === 'sibling-text') score += 64;
|
|
55
|
+
if (source === 'title') score += 56;
|
|
56
|
+
if (source === 'placeholder') score += 42;
|
|
57
|
+
if (source === 'context') score += 12;
|
|
58
|
+
if (source === 'icon') score -= 8;
|
|
59
|
+
if (source === 'test-id') score -= 14;
|
|
60
|
+
if (isInteractiveContext) score += 18;
|
|
61
|
+
if (words.length >= 2 && words.length <= 6) score += 20;else if (words.length === 1) score += 4;
|
|
62
|
+
if (label.length >= 4 && label.length <= 36) score += 18;else if (label.length > 56) score -= 20;
|
|
63
|
+
if (/^[A-Z]/.test(label)) score += 8;
|
|
64
|
+
if (/[A-Za-z]/.test(label) && !/[_./]/.test(label)) score += 10;
|
|
65
|
+
return score;
|
|
66
|
+
}
|
|
67
|
+
export function getFallbackAnalyticsLabel(elementKind) {
|
|
68
|
+
switch (elementKind) {
|
|
69
|
+
case 'button':
|
|
70
|
+
return 'Primary action';
|
|
71
|
+
case 'text_input':
|
|
72
|
+
return 'Text input';
|
|
73
|
+
case 'toggle':
|
|
74
|
+
return 'Toggle';
|
|
75
|
+
case 'picker':
|
|
76
|
+
return 'Picker';
|
|
77
|
+
case 'slider':
|
|
78
|
+
return 'Slider';
|
|
79
|
+
case 'link':
|
|
80
|
+
return 'Link';
|
|
81
|
+
case 'tab':
|
|
82
|
+
return 'Tab';
|
|
83
|
+
case 'list_item':
|
|
84
|
+
return 'List item';
|
|
85
|
+
case 'image':
|
|
86
|
+
return 'Image';
|
|
87
|
+
case 'icon':
|
|
88
|
+
return 'Icon';
|
|
89
|
+
case 'text':
|
|
90
|
+
return 'Text';
|
|
91
|
+
case 'card':
|
|
92
|
+
return 'Card';
|
|
93
|
+
case 'modal':
|
|
94
|
+
return 'Modal';
|
|
95
|
+
case 'sheet':
|
|
96
|
+
return 'Bottom sheet';
|
|
97
|
+
case 'scroll_area':
|
|
98
|
+
return 'Scrollable area';
|
|
99
|
+
default:
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
export function getAnalyticsElementKind(elementType) {
|
|
104
|
+
switch (elementType) {
|
|
105
|
+
case 'pressable':
|
|
106
|
+
case 'radio':
|
|
107
|
+
case 'button':
|
|
108
|
+
case 'checkbox':
|
|
109
|
+
return 'button';
|
|
110
|
+
case 'link':
|
|
111
|
+
return 'link';
|
|
112
|
+
case 'tab':
|
|
113
|
+
case 'tabbar':
|
|
114
|
+
return 'tab';
|
|
115
|
+
case 'listitem':
|
|
116
|
+
case 'list-item':
|
|
117
|
+
case 'menuitem':
|
|
118
|
+
return 'list_item';
|
|
119
|
+
case 'text-input':
|
|
120
|
+
case 'text_input':
|
|
121
|
+
case 'textinput':
|
|
122
|
+
return 'text_input';
|
|
123
|
+
case 'switch':
|
|
124
|
+
case 'toggle':
|
|
125
|
+
return 'toggle';
|
|
126
|
+
case 'slider':
|
|
127
|
+
return 'slider';
|
|
128
|
+
case 'picker':
|
|
129
|
+
return 'picker';
|
|
130
|
+
case 'date-picker':
|
|
131
|
+
case 'select':
|
|
132
|
+
case 'dropdown':
|
|
133
|
+
return 'picker';
|
|
134
|
+
case 'image':
|
|
135
|
+
case 'imagebutton':
|
|
136
|
+
return 'image';
|
|
137
|
+
case 'icon':
|
|
138
|
+
return 'icon';
|
|
139
|
+
case 'text':
|
|
140
|
+
case 'label':
|
|
141
|
+
case 'header':
|
|
142
|
+
return 'text';
|
|
143
|
+
case 'card':
|
|
144
|
+
return 'card';
|
|
145
|
+
case 'modal':
|
|
146
|
+
return 'modal';
|
|
147
|
+
case 'sheet':
|
|
148
|
+
case 'bottomsheet':
|
|
149
|
+
return 'sheet';
|
|
150
|
+
case 'scrollable':
|
|
151
|
+
case 'scrollview':
|
|
152
|
+
case 'flatlist':
|
|
153
|
+
case 'sectionlist':
|
|
154
|
+
case 'adjustable':
|
|
155
|
+
return 'scroll_area';
|
|
156
|
+
default:
|
|
157
|
+
return 'unknown';
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
export function chooseBestAnalyticsTarget(candidates, elementKind) {
|
|
161
|
+
let best;
|
|
162
|
+
for (const candidate of candidates) {
|
|
163
|
+
const label = sanitizeLabelValue(candidate.text, candidate.source);
|
|
164
|
+
if (!label) continue;
|
|
165
|
+
const score = scoreAnalyticsLabel(label, candidate.source, candidate.isInteractiveContext === true);
|
|
166
|
+
if (!best || score > best.score) {
|
|
167
|
+
best = {
|
|
168
|
+
label,
|
|
169
|
+
score
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (best) {
|
|
174
|
+
const labelConfidence = best.score >= 100 ? 'high' : 'low';
|
|
175
|
+
return {
|
|
176
|
+
label: best.label,
|
|
177
|
+
elementKind,
|
|
178
|
+
labelConfidence
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
return {
|
|
182
|
+
label: getFallbackAnalyticsLabel(elementKind),
|
|
183
|
+
elementKind,
|
|
184
|
+
labelConfidence: 'low'
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
//# sourceMappingURL=analyticsLabeling.js.map
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
31
|
-
clarifying questions (which order
|
|
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
|
-
|
|
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 "
|
|
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
|
-
|
|
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
|
|
@@ -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
|
|
13
|
-
* 1.
|
|
14
|
-
*
|
|
15
|
-
*
|
|
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
|
|
54
|
+
* Android (native module creates its own panel dialog window).
|
|
46
55
|
*/
|
|
47
56
|
fallbackStyle?: any;
|
|
48
57
|
}
|
|
49
|
-
export
|
|
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,6 +23,10 @@ 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;
|
|
@@ -38,6 +42,11 @@ export declare class AgentRuntime {
|
|
|
38
42
|
private formatInteractiveForDebug;
|
|
39
43
|
private debugScreenSnapshot;
|
|
40
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;
|
|
41
50
|
private registerBuiltInTools;
|
|
42
51
|
/**
|
|
43
52
|
* Register only knowledge-assistant tools (no UI control).
|
|
@@ -73,9 +82,9 @@ export declare class AgentRuntime {
|
|
|
73
82
|
/** Maps a tool call to a user-friendly status label for the loading overlay. */
|
|
74
83
|
private getToolStatusLabel;
|
|
75
84
|
/**
|
|
76
|
-
* Captures the
|
|
77
|
-
* Uses react-native-view-shot as
|
|
78
|
-
* Returns null
|
|
85
|
+
* Captures the root component as a base64 JPEG for vision tools.
|
|
86
|
+
* Uses react-native-view-shot as a required peer dependency.
|
|
87
|
+
* Returns null only when capture is temporarily unavailable.
|
|
79
88
|
*/
|
|
80
89
|
private captureScreenshot;
|
|
81
90
|
/**
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* interactive elements by their type and props (onPress, onChangeText, etc.).
|
|
7
7
|
*
|
|
8
8
|
*/
|
|
9
|
-
import type { InteractiveElement } from './types';
|
|
9
|
+
import type { InteractiveElement, WireframeSnapshot } from './types';
|
|
10
10
|
export interface WalkConfig {
|
|
11
11
|
/** React refs of elements to exclude */
|
|
12
12
|
interactiveBlacklist?: React.RefObject<any>[];
|
|
@@ -64,4 +64,15 @@ export interface ScrollableContainer {
|
|
|
64
64
|
* For ScrollView: the stateNode IS the native scroll view directly.
|
|
65
65
|
*/
|
|
66
66
|
export declare function findScrollableContainers(rootRef: any, screenName?: string): ScrollableContainer[];
|
|
67
|
+
/**
|
|
68
|
+
* Capture a privacy-safe wireframe of the current screen.
|
|
69
|
+
*
|
|
70
|
+
* Performance guarantees:
|
|
71
|
+
* - Capped at WIREFRAME_MAX_ELEMENTS (50) — enough for wireframe context
|
|
72
|
+
* - Measures in batches of WIREFRAME_BATCH_SIZE (10), yielding a frame
|
|
73
|
+
* between batches so the bridge stays free for user interactions
|
|
74
|
+
* - The caller (AIAgent) defers this via InteractionManager so it
|
|
75
|
+
* never competes with screen transitions or gestures
|
|
76
|
+
*/
|
|
77
|
+
export declare function captureWireframe(rootRef: React.RefObject<any>, config?: WalkConfig): Promise<WireframeSnapshot | null>;
|
|
67
78
|
//# sourceMappingURL=FiberTreeWalker.d.ts.map
|