@mobileai/react-native 0.9.25 → 0.9.27
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 +10 -10
- package/bin/generate-map.cjs +511 -120
- package/lib/module/components/AIAgent.js +25 -3
- package/lib/module/components/AgentChatBar.js +3 -3
- package/lib/module/config/endpoints.js +1 -1
- package/lib/module/core/AgentRuntime.js +89 -23
- package/lib/module/core/FiberTreeWalker.js +312 -34
- package/lib/module/core/systemPrompt.js +30 -19
- package/lib/module/services/MobileAIKnowledgeRetriever.js +1 -1
- package/lib/module/services/telemetry/MobileAI.js +1 -1
- package/lib/module/tools/tapTool.js +77 -6
- package/lib/typescript/src/core/AgentRuntime.d.ts +8 -1
- package/lib/typescript/src/core/types.d.ts +2 -1
- package/lib/typescript/src/services/MobileAIKnowledgeRetriever.d.ts +1 -1
- package/lib/typescript/src/tools/tapTool.d.ts +3 -2
- package/lib/typescript/test-tree.d.ts +2 -0
- package/package.json +1 -1
|
@@ -5,8 +5,9 @@
|
|
|
5
5
|
*
|
|
6
6
|
* Strategies (in priority order):
|
|
7
7
|
* 1. Switch → onValueChange (toggle)
|
|
8
|
-
* 2.
|
|
9
|
-
* 3.
|
|
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:
|
|
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
|
|
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
|
|
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
|
}
|
|
@@ -27,9 +27,16 @@ export declare class AgentRuntime {
|
|
|
27
27
|
private lastSuppressedError;
|
|
28
28
|
private graceTimer;
|
|
29
29
|
private originalReportErrorsAsExceptions;
|
|
30
|
-
private
|
|
30
|
+
private appActionApprovalScope;
|
|
31
|
+
private appActionApprovalSource;
|
|
31
32
|
private static readonly APP_ACTION_TOOLS;
|
|
32
33
|
getConfig(): AgentConfig;
|
|
34
|
+
private resetAppActionApproval;
|
|
35
|
+
private grantWorkflowApproval;
|
|
36
|
+
private hasWorkflowApproval;
|
|
37
|
+
private debugLogChunked;
|
|
38
|
+
private formatInteractiveForDebug;
|
|
39
|
+
private debugScreenSnapshot;
|
|
33
40
|
constructor(provider: AIProvider, config: AgentConfig, rootRef: any, navRef: any);
|
|
34
41
|
private registerBuiltInTools;
|
|
35
42
|
/**
|
|
@@ -11,7 +11,7 @@ export type AgentMode = 'text' | 'voice' | 'human';
|
|
|
11
11
|
*/
|
|
12
12
|
export type InteractionMode = 'copilot' | 'autopilot';
|
|
13
13
|
export type AIProviderName = 'gemini' | 'openai';
|
|
14
|
-
export type ElementType = 'pressable' | 'text-input' | 'switch' | 'scrollable' | 'slider' | 'picker' | 'date-picker';
|
|
14
|
+
export type ElementType = 'pressable' | 'text-input' | 'switch' | 'radio' | 'scrollable' | 'slider' | 'picker' | 'date-picker';
|
|
15
15
|
export interface InteractiveElement {
|
|
16
16
|
/** Unique index assigned during tree walk */
|
|
17
17
|
index: number;
|
|
@@ -33,6 +33,7 @@ export interface InteractiveElement {
|
|
|
33
33
|
* - Picker: onValueChange, items, selectedValue
|
|
34
34
|
* - DatePicker: onChange, onDateChange, mode
|
|
35
35
|
* - Switch: onValueChange, value
|
|
36
|
+
* - Radio: onPress, onValueChange/onChange, value, checked
|
|
36
37
|
*/
|
|
37
38
|
props: Record<string, any>;
|
|
38
39
|
/**
|
|
@@ -3,8 +3,9 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Strategies (in priority order):
|
|
5
5
|
* 1. Switch → onValueChange (toggle)
|
|
6
|
-
* 2.
|
|
7
|
-
* 3.
|
|
6
|
+
* 2. Radio → onPress / onValueChange / parent radio-group handler
|
|
7
|
+
* 3. Direct onPress on element
|
|
8
|
+
* 4. Bubble up fiber tree to find parent onPress (max 5 levels)
|
|
8
9
|
*
|
|
9
10
|
* Includes Maestro-style tap verification:
|
|
10
11
|
* - Captures element count + screen name before tap
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mobileai/react-native",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.27",
|
|
4
4
|
"description": "Build autonomous AI agents for React Native and Expo apps. Provides AI-native UI traversal, tool calling, and structured reasoning.",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|