@mobileai/react-native 0.9.12 → 0.9.13
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 +480 -61
- package/lib/module/components/AIAgent.js +61 -3
- package/lib/module/components/AIAgent.js.map +1 -1
- package/lib/module/components/AgentChatBar.js +12 -2
- package/lib/module/components/AgentChatBar.js.map +1 -1
- package/lib/module/components/DiscoveryTooltip.js +128 -0
- package/lib/module/components/DiscoveryTooltip.js.map +1 -0
- package/lib/module/core/AgentRuntime.js +77 -1
- package/lib/module/core/AgentRuntime.js.map +1 -1
- package/lib/module/core/FiberTreeWalker.js +5 -1
- package/lib/module/core/FiberTreeWalker.js.map +1 -1
- package/lib/module/core/systemPrompt.js +41 -3
- package/lib/module/core/systemPrompt.js.map +1 -1
- package/lib/module/index.js.map +1 -1
- package/lib/typescript/src/components/AIAgent.d.ts +18 -2
- package/lib/typescript/src/components/AIAgent.d.ts.map +1 -1
- package/lib/typescript/src/components/AgentChatBar.d.ts +5 -1
- package/lib/typescript/src/components/AgentChatBar.d.ts.map +1 -1
- package/lib/typescript/src/components/DiscoveryTooltip.d.ts +15 -0
- package/lib/typescript/src/components/DiscoveryTooltip.d.ts.map +1 -0
- package/lib/typescript/src/core/AgentRuntime.d.ts +7 -0
- package/lib/typescript/src/core/AgentRuntime.d.ts.map +1 -1
- package/lib/typescript/src/core/FiberTreeWalker.d.ts.map +1 -1
- package/lib/typescript/src/core/systemPrompt.d.ts +1 -1
- package/lib/typescript/src/core/systemPrompt.d.ts.map +1 -1
- package/lib/typescript/src/core/types.d.ts +19 -0
- package/lib/typescript/src/core/types.d.ts.map +1 -1
- package/lib/typescript/src/index.d.ts +1 -1
- package/lib/typescript/src/index.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/AIAgent.tsx +75 -1
- package/src/components/AgentChatBar.tsx +19 -1
- package/src/components/DiscoveryTooltip.tsx +148 -0
- package/src/core/AgentRuntime.ts +87 -1
- package/src/core/FiberTreeWalker.ts +5 -0
- package/src/core/systemPrompt.ts +41 -3
- package/src/core/types.ts +21 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* DiscoveryTooltip — One-time tooltip shown above the FAB on first use.
|
|
3
|
+
*
|
|
4
|
+
* Tells users the AI can navigate the app and do things for them.
|
|
5
|
+
* Shows once, then persists dismissal via AsyncStorage.
|
|
6
|
+
* Bilingual: EN/AR.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { useEffect, useRef } from 'react';
|
|
10
|
+
import {
|
|
11
|
+
View,
|
|
12
|
+
Text,
|
|
13
|
+
Pressable,
|
|
14
|
+
StyleSheet,
|
|
15
|
+
Animated,
|
|
16
|
+
} from 'react-native';
|
|
17
|
+
|
|
18
|
+
interface DiscoveryTooltipProps {
|
|
19
|
+
language: 'en' | 'ar';
|
|
20
|
+
primaryColor?: string;
|
|
21
|
+
onDismiss: () => void;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const LABELS = {
|
|
25
|
+
en: '✨ I can help you navigate the app and do things for you!',
|
|
26
|
+
ar: '✨ أقدر أساعدك تتنقل في التطبيق وأعمل حاجات بدالك!',
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const AUTO_DISMISS_MS = 6000;
|
|
30
|
+
|
|
31
|
+
export function DiscoveryTooltip({
|
|
32
|
+
language,
|
|
33
|
+
primaryColor,
|
|
34
|
+
onDismiss,
|
|
35
|
+
}: DiscoveryTooltipProps) {
|
|
36
|
+
const scaleAnim = useRef(new Animated.Value(0)).current;
|
|
37
|
+
const opacityAnim = useRef(new Animated.Value(0)).current;
|
|
38
|
+
|
|
39
|
+
useEffect(() => {
|
|
40
|
+
// Spring-in entry
|
|
41
|
+
Animated.parallel([
|
|
42
|
+
Animated.spring(scaleAnim, {
|
|
43
|
+
toValue: 1,
|
|
44
|
+
friction: 6,
|
|
45
|
+
tension: 80,
|
|
46
|
+
useNativeDriver: true,
|
|
47
|
+
}),
|
|
48
|
+
Animated.timing(opacityAnim, {
|
|
49
|
+
toValue: 1,
|
|
50
|
+
duration: 200,
|
|
51
|
+
useNativeDriver: true,
|
|
52
|
+
}),
|
|
53
|
+
]).start();
|
|
54
|
+
|
|
55
|
+
// Auto-dismiss after timeout
|
|
56
|
+
const timer = setTimeout(() => {
|
|
57
|
+
dismissWithAnimation();
|
|
58
|
+
}, AUTO_DISMISS_MS);
|
|
59
|
+
|
|
60
|
+
return () => clearTimeout(timer);
|
|
61
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
62
|
+
}, []);
|
|
63
|
+
|
|
64
|
+
const dismissWithAnimation = () => {
|
|
65
|
+
Animated.parallel([
|
|
66
|
+
Animated.timing(scaleAnim, {
|
|
67
|
+
toValue: 0,
|
|
68
|
+
duration: 200,
|
|
69
|
+
useNativeDriver: true,
|
|
70
|
+
}),
|
|
71
|
+
Animated.timing(opacityAnim, {
|
|
72
|
+
toValue: 0,
|
|
73
|
+
duration: 200,
|
|
74
|
+
useNativeDriver: true,
|
|
75
|
+
}),
|
|
76
|
+
]).start(() => onDismiss());
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const isArabic = language === 'ar';
|
|
80
|
+
const bgColor = primaryColor || '#1a1a2e';
|
|
81
|
+
|
|
82
|
+
return (
|
|
83
|
+
<Animated.View
|
|
84
|
+
style={[
|
|
85
|
+
styles.container,
|
|
86
|
+
{
|
|
87
|
+
backgroundColor: bgColor,
|
|
88
|
+
transform: [{ scale: scaleAnim }],
|
|
89
|
+
opacity: opacityAnim,
|
|
90
|
+
},
|
|
91
|
+
]}
|
|
92
|
+
>
|
|
93
|
+
<Pressable onPress={dismissWithAnimation} style={styles.contentArea}>
|
|
94
|
+
<Text style={[styles.text, isArabic && styles.textRTL]}>
|
|
95
|
+
{LABELS[language]}
|
|
96
|
+
</Text>
|
|
97
|
+
</Pressable>
|
|
98
|
+
|
|
99
|
+
{/* Triangle pointer toward FAB */}
|
|
100
|
+
<View style={[styles.pointer, { borderTopColor: bgColor }]} />
|
|
101
|
+
</Animated.View>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const styles = StyleSheet.create({
|
|
106
|
+
container: {
|
|
107
|
+
position: 'absolute',
|
|
108
|
+
bottom: 70,
|
|
109
|
+
right: -4,
|
|
110
|
+
minWidth: 200,
|
|
111
|
+
maxWidth: 260,
|
|
112
|
+
borderRadius: 16,
|
|
113
|
+
paddingHorizontal: 14,
|
|
114
|
+
paddingVertical: 10,
|
|
115
|
+
shadowColor: '#000',
|
|
116
|
+
shadowOffset: { width: 0, height: 4 },
|
|
117
|
+
shadowOpacity: 0.3,
|
|
118
|
+
shadowRadius: 8,
|
|
119
|
+
elevation: 6,
|
|
120
|
+
},
|
|
121
|
+
contentArea: {
|
|
122
|
+
flexDirection: 'row',
|
|
123
|
+
alignItems: 'center',
|
|
124
|
+
},
|
|
125
|
+
text: {
|
|
126
|
+
color: '#ffffff',
|
|
127
|
+
fontSize: 13,
|
|
128
|
+
lineHeight: 19,
|
|
129
|
+
fontWeight: '500',
|
|
130
|
+
},
|
|
131
|
+
textRTL: {
|
|
132
|
+
textAlign: 'right',
|
|
133
|
+
writingDirection: 'rtl',
|
|
134
|
+
},
|
|
135
|
+
pointer: {
|
|
136
|
+
position: 'absolute',
|
|
137
|
+
bottom: -8,
|
|
138
|
+
right: 22,
|
|
139
|
+
width: 0,
|
|
140
|
+
height: 0,
|
|
141
|
+
borderLeftWidth: 8,
|
|
142
|
+
borderRightWidth: 8,
|
|
143
|
+
borderTopWidth: 8,
|
|
144
|
+
borderLeftColor: 'transparent',
|
|
145
|
+
borderRightColor: 'transparent',
|
|
146
|
+
borderTopColor: '#1a1a2e',
|
|
147
|
+
},
|
|
148
|
+
});
|
package/src/core/AgentRuntime.ts
CHANGED
|
@@ -786,6 +786,15 @@ ${screen.elementsText}
|
|
|
786
786
|
return validationError;
|
|
787
787
|
}
|
|
788
788
|
|
|
789
|
+
// ── Copilot aiConfirm gate ──────────────────────────────────
|
|
790
|
+
// In copilot mode, elements marked with aiConfirm={true} require
|
|
791
|
+
// user confirmation before execution. This is the code-level safety net
|
|
792
|
+
// complementing the prompt-level copilot instructions.
|
|
793
|
+
if (this.config.interactionMode !== 'autopilot') {
|
|
794
|
+
const confirmResult = await this.checkCopilotConfirmation(toolName, args);
|
|
795
|
+
if (confirmResult) return confirmResult;
|
|
796
|
+
}
|
|
797
|
+
|
|
789
798
|
const result = await tool.execute(args);
|
|
790
799
|
|
|
791
800
|
// Settle window for async side-effects (useEffect, native callbacks)
|
|
@@ -838,6 +847,74 @@ ${screen.elementsText}
|
|
|
838
847
|
return null;
|
|
839
848
|
}
|
|
840
849
|
|
|
850
|
+
// ─── Copilot Confirmation ─────────────────────────────────────
|
|
851
|
+
|
|
852
|
+
/** Write tools that can mutate state — only these are checked for aiConfirm */
|
|
853
|
+
private static readonly WRITE_TOOLS = new Set([
|
|
854
|
+
'tap', 'type', 'long_press', 'adjust_slider', 'select_picker', 'set_date',
|
|
855
|
+
]);
|
|
856
|
+
|
|
857
|
+
/**
|
|
858
|
+
* Check if a tool call targets an aiConfirm element and request user confirmation.
|
|
859
|
+
* Returns null if the action should proceed, or an error string if rejected.
|
|
860
|
+
*/
|
|
861
|
+
private async checkCopilotConfirmation(
|
|
862
|
+
toolName: string,
|
|
863
|
+
args: Record<string, any>,
|
|
864
|
+
): Promise<string | null> {
|
|
865
|
+
// Only gate write tools
|
|
866
|
+
if (!AgentRuntime.WRITE_TOOLS.has(toolName)) return null;
|
|
867
|
+
|
|
868
|
+
// Look up the target element by index
|
|
869
|
+
const index = args.index;
|
|
870
|
+
if (typeof index !== 'number') return null;
|
|
871
|
+
|
|
872
|
+
const screen = this.lastDehydratedRoot as import('./types').DehydratedScreen | null;
|
|
873
|
+
if (!screen?.elements) return null;
|
|
874
|
+
|
|
875
|
+
const element = screen.elements.find(e => e.index === index);
|
|
876
|
+
if (!element?.requiresConfirmation) return null;
|
|
877
|
+
|
|
878
|
+
// Element has aiConfirm — request user confirmation
|
|
879
|
+
const label = element.label || `[${element.type}]`;
|
|
880
|
+
const description = this.getToolStatusLabel(toolName, args);
|
|
881
|
+
const question = `I'm about to ${description} on "${label}". Should I proceed?`;
|
|
882
|
+
|
|
883
|
+
logger.info('AgentRuntime', `🛡️ Copilot: aiConfirm gate triggered for "${toolName}" on "${label}"`);
|
|
884
|
+
|
|
885
|
+
// Use onAskUser if available (integrated into chat UI), otherwise Alert.alert
|
|
886
|
+
if (this.config.onAskUser) {
|
|
887
|
+
const response = await this.config.onAskUser(question);
|
|
888
|
+
const approved = /^(yes|ok|sure|go|proceed|confirm|y)/i.test(response.trim());
|
|
889
|
+
if (!approved) {
|
|
890
|
+
logger.info('AgentRuntime', `🛑 User rejected "${toolName}" on "${label}"`);
|
|
891
|
+
return `❌ User rejected "${toolName}" action on "${label}". Ask what they would like to do instead.`;
|
|
892
|
+
}
|
|
893
|
+
return null;
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
// Fallback: React Native Alert
|
|
897
|
+
const { Alert } = require('react-native');
|
|
898
|
+
const approved = await new Promise<boolean>(resolve => {
|
|
899
|
+
Alert.alert(
|
|
900
|
+
'Confirm Action',
|
|
901
|
+
question,
|
|
902
|
+
[
|
|
903
|
+
{ text: 'Cancel', style: 'cancel', onPress: () => resolve(false) },
|
|
904
|
+
{ text: 'Continue', onPress: () => resolve(true) },
|
|
905
|
+
],
|
|
906
|
+
{ cancelable: false },
|
|
907
|
+
);
|
|
908
|
+
});
|
|
909
|
+
|
|
910
|
+
if (!approved) {
|
|
911
|
+
logger.info('AgentRuntime', `🛑 User rejected "${toolName}" on "${label}"`);
|
|
912
|
+
return `❌ User rejected "${toolName}" action on "${label}". Ask what they would like to do instead.`;
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
|
|
841
918
|
// ─── Walk Config (passes security settings to FiberTreeWalker) ─
|
|
842
919
|
|
|
843
920
|
private getWalkConfig(): WalkConfig {
|
|
@@ -1190,7 +1267,8 @@ ${screen.elementsText}
|
|
|
1190
1267
|
// 5. Send to AI provider
|
|
1191
1268
|
this.config.onStatusUpdate?.('Analyzing screen...');
|
|
1192
1269
|
const hasKnowledge = !!this.knowledgeService;
|
|
1193
|
-
const
|
|
1270
|
+
const isCopilot = this.config.interactionMode !== 'autopilot';
|
|
1271
|
+
const systemPrompt = buildSystemPrompt('en', hasKnowledge, isCopilot);
|
|
1194
1272
|
const tools = this.buildToolsForProvider();
|
|
1195
1273
|
|
|
1196
1274
|
logger.info('AgentRuntime', `Sending to AI with ${tools.length} tools...`);
|
|
@@ -1339,6 +1417,14 @@ ${screen.elementsText}
|
|
|
1339
1417
|
steps: this.history,
|
|
1340
1418
|
tokenUsage: sessionUsage,
|
|
1341
1419
|
};
|
|
1420
|
+
|
|
1421
|
+
// Dev warning: remind developers to add aiConfirm for extra safety
|
|
1422
|
+
if (__DEV__ && this.config.interactionMode !== 'autopilot') {
|
|
1423
|
+
logger.info('AgentRuntime',
|
|
1424
|
+
'ℹ️ Copilot mode active. Tip: Add aiConfirm={true} to critical buttons (e.g. "Place Order", "Delete") for extra safety.'
|
|
1425
|
+
);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1342
1428
|
await this.config.onAfterTask?.(result);
|
|
1343
1429
|
return result;
|
|
1344
1430
|
} catch (error: any) {
|
|
@@ -617,6 +617,7 @@ export function walkFiberTree(rootRef: any, config?: WalkConfig): WalkResult {
|
|
|
617
617
|
zoneId: currentZoneId,
|
|
618
618
|
fiberNode: node,
|
|
619
619
|
props: { ...props },
|
|
620
|
+
requiresConfirmation: props.aiConfirm === true,
|
|
620
621
|
});
|
|
621
622
|
|
|
622
623
|
// Build output tag with state attributes
|
|
@@ -627,6 +628,10 @@ export function walkFiberTree(rootRef: any, config?: WalkConfig): WalkResult {
|
|
|
627
628
|
if (currentZoneId) attrStr += ` zoneId="${currentZoneId}"`;
|
|
628
629
|
}
|
|
629
630
|
|
|
631
|
+
if (props.aiConfirm === true) {
|
|
632
|
+
attrStr += ' aiConfirm';
|
|
633
|
+
}
|
|
634
|
+
|
|
630
635
|
const textContent = label || '';
|
|
631
636
|
const elementOutput = `${indent}[${currentIndex}]<${resolvedType}${attrStr}>${textContent} />${childrenText.trim() ? '\n' + childrenText : ''}\n`;
|
|
632
637
|
currentIndex++;
|
package/src/core/systemPrompt.ts
CHANGED
|
@@ -89,9 +89,43 @@ const SHARED_CAPABILITY = `- It is ok to fail the task. User would rather you re
|
|
|
89
89
|
- The app can have bugs. If something is not working as expected, report it to the user.
|
|
90
90
|
- Trying too hard can be harmful. If stuck, report partial progress rather than repeating failed actions.`;
|
|
91
91
|
|
|
92
|
+
/**
|
|
93
|
+
* Copilot mode rules — AI pauses once before final irreversible commit.
|
|
94
|
+
* Injected when interactionMode is 'copilot' (the default).
|
|
95
|
+
*/
|
|
96
|
+
const COPILOT_RULES = `<copilot_mode>
|
|
97
|
+
You are in COPILOT mode. This means:
|
|
98
|
+
|
|
99
|
+
Execute ALL intermediate actions SILENTLY — no confirmation needed:
|
|
100
|
+
- Navigating between screens, tabs, menus
|
|
101
|
+
- Scrolling to find content
|
|
102
|
+
- Typing into form fields
|
|
103
|
+
- Selecting options, filters, categories
|
|
104
|
+
- Adding items to cart (user can remove later)
|
|
105
|
+
- Opening/closing dialogs or details
|
|
106
|
+
- Toggling form controls while filling a form
|
|
107
|
+
|
|
108
|
+
PAUSE only when you reach the FINAL action that IRREVERSIBLY commits
|
|
109
|
+
the user's change. Use ask_user to summarize what you have done so far
|
|
110
|
+
and ask permission BEFORE tapping the commit button. Examples of commit actions:
|
|
111
|
+
- Placing an order / completing a purchase
|
|
112
|
+
- Submitting a form that sends data
|
|
113
|
+
- Deleting something (account, item, message)
|
|
114
|
+
- Confirming a payment or transaction
|
|
115
|
+
- Sending a message or email
|
|
116
|
+
- Saving account/profile changes
|
|
117
|
+
|
|
118
|
+
Elements marked with aiConfirm in the element tree are developer-flagged
|
|
119
|
+
as requiring confirmation. Treat them as commit actions regardless of context.
|
|
120
|
+
|
|
121
|
+
Call ask_user EXACTLY ONCE per task — at the final commit moment, not at
|
|
122
|
+
every step. If the task has no irreversible commit (e.g., "show me my orders",
|
|
123
|
+
"find the cheapest item"), complete the task without pausing.
|
|
124
|
+
</copilot_mode>`;
|
|
125
|
+
|
|
92
126
|
// ─── Text Agent Prompt ──────────────────────────────────────────────────────
|
|
93
127
|
|
|
94
|
-
export function buildSystemPrompt(language: string, hasKnowledge = false): string {
|
|
128
|
+
export function buildSystemPrompt(language: string, hasKnowledge = false, isCopilot = true): string {
|
|
95
129
|
const isArabic = language === 'ar';
|
|
96
130
|
|
|
97
131
|
return `${CONFIDENTIALITY("I'm your app assistant — I can help you navigate and use this app. What would you like to do?")}
|
|
@@ -170,6 +204,8 @@ ${NAVIGATION_RULE}
|
|
|
170
204
|
${UI_SIMPLIFICATION_RULE}
|
|
171
205
|
</rules>
|
|
172
206
|
|
|
207
|
+
${isCopilot ? COPILOT_RULES : ''}
|
|
208
|
+
|
|
173
209
|
<task_completion_rules>
|
|
174
210
|
You must call the done action in one of these cases:
|
|
175
211
|
- When you have fully completed the USER REQUEST.
|
|
@@ -188,8 +224,10 @@ The done action is your opportunity to communicate findings and provide a cohere
|
|
|
188
224
|
- Use the text field to answer questions, summarize what you found, or explain what you did.
|
|
189
225
|
- You are ONLY ALLOWED to call done as a single action. Do not call it together with other actions.
|
|
190
226
|
|
|
191
|
-
The ask_user action should ONLY be used when
|
|
192
|
-
-
|
|
227
|
+
The ask_user action should ONLY be used when:
|
|
228
|
+
- The user gave an action request but you lack specific information to execute it (e.g., user says "order a pizza" but there are multiple options and you don't know which one).
|
|
229
|
+
- You are in copilot mode and about to perform an irreversible commit action (see copilot_mode rules above).
|
|
230
|
+
- Do NOT use ask_user for routine confirmations the user already gave. If they said "place my order", proceed to the commit step and confirm there.
|
|
193
231
|
- NEVER ask for the same confirmation twice. If the user already answered, proceed with their answer.
|
|
194
232
|
- For destructive/purchase actions (place order, delete, pay), tap the button exactly ONCE. Do not repeat the same action — the user could be charged multiple times.
|
|
195
233
|
</task_completion_rules>
|
package/src/core/types.ts
CHANGED
|
@@ -6,6 +6,15 @@
|
|
|
6
6
|
|
|
7
7
|
export type AgentMode = 'text' | 'voice' | 'human';
|
|
8
8
|
|
|
9
|
+
/**
|
|
10
|
+
* Controls how the agent handles irreversible actions.
|
|
11
|
+
* 'copilot' (default): AI pauses before final commit actions (place order, delete, submit).
|
|
12
|
+
* The prompt instructs the AI to ask_user before the final irreversible step.
|
|
13
|
+
* Elements with aiConfirm={true} also trigger a code-level confirmation gate.
|
|
14
|
+
* 'autopilot': Full autonomy — all actions execute without confirmation.
|
|
15
|
+
*/
|
|
16
|
+
export type InteractionMode = 'copilot' | 'autopilot';
|
|
17
|
+
|
|
9
18
|
// ─── Provider Names ──────────────────────────────────────────
|
|
10
19
|
|
|
11
20
|
export type AIProviderName = 'gemini' | 'openai';
|
|
@@ -38,6 +47,11 @@ export interface InteractiveElement {
|
|
|
38
47
|
* - Switch: onValueChange, value
|
|
39
48
|
*/
|
|
40
49
|
props: Record<string, any>;
|
|
50
|
+
/**
|
|
51
|
+
* If true, AI interaction with this element requires user confirmation (copilot safety net).
|
|
52
|
+
* Set automatically by the FiberTreeWalker when the element has aiConfirm={true} prop.
|
|
53
|
+
*/
|
|
54
|
+
requiresConfirmation?: boolean;
|
|
41
55
|
}
|
|
42
56
|
|
|
43
57
|
// ─── Dehydrated Screen State ──────────────────────────────────
|
|
@@ -119,6 +133,13 @@ export interface AgentConfig {
|
|
|
119
133
|
/** Maximum steps per task */
|
|
120
134
|
maxSteps?: number;
|
|
121
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Controls how the agent handles irreversible actions.
|
|
138
|
+
* 'copilot' (default): AI pauses before final commit actions.
|
|
139
|
+
* 'autopilot': Full autonomy, no pauses.
|
|
140
|
+
*/
|
|
141
|
+
interactionMode?: InteractionMode;
|
|
142
|
+
|
|
122
143
|
/**
|
|
123
144
|
* MCP server mode — controls whether external agents can discover and invoke actions.
|
|
124
145
|
* 'auto' (default): enabled in __DEV__, disabled in production
|