@mobileai/react-native 0.9.17 â 0.9.19
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/LICENSE +28 -20
- package/MobileAIFloatingOverlay.podspec +25 -0
- package/android/build.gradle +61 -0
- package/android/src/main/AndroidManifest.xml +3 -0
- package/android/src/main/java/com/mobileai/overlay/FloatingOverlayView.kt +151 -0
- package/android/src/main/java/com/mobileai/overlay/MobileAIOverlayPackage.kt +23 -0
- package/android/src/newarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +45 -0
- package/android/src/oldarch/com/mobileai/overlay/FloatingOverlayViewManager.kt +29 -0
- package/ios/MobileAIFloatingOverlayComponentView.mm +73 -0
- package/lib/module/components/AIAgent.js +902 -136
- package/lib/module/components/AIConsentDialog.js +439 -0
- package/lib/module/components/AgentChatBar.js +828 -134
- package/lib/module/components/AgentOverlay.js +2 -1
- package/lib/module/components/DiscoveryTooltip.js +21 -9
- package/lib/module/components/FloatingOverlayWrapper.js +108 -0
- package/lib/module/components/Icons.js +123 -0
- package/lib/module/config/endpoints.js +12 -2
- package/lib/module/core/AgentRuntime.js +373 -27
- package/lib/module/core/FiberAdapter.js +56 -0
- package/lib/module/core/FiberTreeWalker.js +186 -80
- package/lib/module/core/IdleDetector.js +19 -0
- package/lib/module/core/NativeAlertInterceptor.js +191 -0
- package/lib/module/core/systemPrompt.js +203 -45
- package/lib/module/index.js +3 -0
- package/lib/module/providers/GeminiProvider.js +72 -56
- package/lib/module/providers/ProviderFactory.js +6 -2
- package/lib/module/services/AudioInputService.js +3 -12
- package/lib/module/services/AudioOutputService.js +1 -13
- package/lib/module/services/ConversationService.js +166 -0
- package/lib/module/services/MobileAIKnowledgeRetriever.js +41 -0
- package/lib/module/services/VoiceService.js +29 -8
- package/lib/module/services/telemetry/MobileAI.js +44 -0
- package/lib/module/services/telemetry/TelemetryService.js +13 -1
- package/lib/module/services/telemetry/TouchAutoCapture.js +44 -18
- package/lib/module/specs/FloatingOverlayNativeComponent.ts +19 -0
- package/lib/module/support/CSATSurvey.js +95 -12
- package/lib/module/support/EscalationSocket.js +70 -1
- package/lib/module/support/ReportedIssueEventSource.js +148 -0
- package/lib/module/support/escalateTool.js +4 -2
- package/lib/module/support/index.js +1 -0
- package/lib/module/support/reportIssueTool.js +127 -0
- package/lib/module/support/supportPrompt.js +77 -9
- package/lib/module/tools/guideTool.js +2 -1
- package/lib/module/tools/longPressTool.js +4 -3
- package/lib/module/tools/pickerTool.js +6 -4
- package/lib/module/tools/tapTool.js +12 -3
- package/lib/module/tools/typeTool.js +19 -10
- package/lib/module/utils/logger.js +175 -6
- package/lib/typescript/react-native.config.d.ts +11 -0
- package/lib/typescript/src/components/AIAgent.d.ts +28 -2
- package/lib/typescript/src/components/AIConsentDialog.d.ts +153 -0
- package/lib/typescript/src/components/AgentChatBar.d.ts +15 -2
- package/lib/typescript/src/components/DiscoveryTooltip.d.ts +3 -1
- package/lib/typescript/src/components/FloatingOverlayWrapper.d.ts +51 -0
- package/lib/typescript/src/components/Icons.d.ts +8 -0
- package/lib/typescript/src/config/endpoints.d.ts +5 -3
- package/lib/typescript/src/core/AgentRuntime.d.ts +4 -0
- package/lib/typescript/src/core/FiberAdapter.d.ts +25 -0
- package/lib/typescript/src/core/FiberTreeWalker.d.ts +2 -0
- package/lib/typescript/src/core/IdleDetector.d.ts +11 -0
- package/lib/typescript/src/core/NativeAlertInterceptor.d.ts +55 -0
- package/lib/typescript/src/core/types.d.ts +106 -1
- package/lib/typescript/src/index.d.ts +9 -4
- package/lib/typescript/src/providers/GeminiProvider.d.ts +6 -5
- package/lib/typescript/src/services/ConversationService.d.ts +55 -0
- package/lib/typescript/src/services/MobileAIKnowledgeRetriever.d.ts +9 -0
- package/lib/typescript/src/services/telemetry/MobileAI.d.ts +7 -0
- package/lib/typescript/src/services/telemetry/TelemetryService.d.ts +1 -1
- package/lib/typescript/src/services/telemetry/TouchAutoCapture.d.ts +9 -6
- package/lib/typescript/src/services/telemetry/types.d.ts +3 -1
- package/lib/typescript/src/specs/FloatingOverlayNativeComponent.d.ts +17 -0
- package/lib/typescript/src/support/EscalationSocket.d.ts +17 -0
- package/lib/typescript/src/support/ReportedIssueEventSource.d.ts +24 -0
- package/lib/typescript/src/support/escalateTool.d.ts +5 -0
- package/lib/typescript/src/support/index.d.ts +2 -1
- package/lib/typescript/src/support/reportIssueTool.d.ts +20 -0
- package/lib/typescript/src/support/types.d.ts +56 -1
- package/lib/typescript/src/utils/logger.d.ts +15 -0
- package/package.json +20 -9
- package/react-native.config.js +12 -0
- package/lib/module/__cli_tmp__.js.map +0 -1
- package/lib/module/components/AIAgent.js.map +0 -1
- package/lib/module/components/AIZone.js.map +0 -1
- package/lib/module/components/AgentChatBar.js.map +0 -1
- package/lib/module/components/AgentErrorBoundary.js.map +0 -1
- package/lib/module/components/AgentOverlay.js.map +0 -1
- package/lib/module/components/DiscoveryTooltip.js.map +0 -1
- package/lib/module/components/HighlightOverlay.js.map +0 -1
- package/lib/module/components/Icons.js.map +0 -1
- package/lib/module/components/ProactiveHint.js.map +0 -1
- package/lib/module/components/cards/InfoCard.js.map +0 -1
- package/lib/module/components/cards/ReviewSummary.js.map +0 -1
- package/lib/module/config/endpoints.js.map +0 -1
- package/lib/module/core/ActionRegistry.js.map +0 -1
- package/lib/module/core/AgentRuntime.js.map +0 -1
- package/lib/module/core/FiberTreeWalker.js.map +0 -1
- package/lib/module/core/IdleDetector.js.map +0 -1
- package/lib/module/core/MCPBridge.js.map +0 -1
- package/lib/module/core/ScreenDehydrator.js.map +0 -1
- package/lib/module/core/ZoneRegistry.js.map +0 -1
- package/lib/module/core/systemPrompt.js.map +0 -1
- package/lib/module/core/types.js.map +0 -1
- package/lib/module/hooks/useAction.js.map +0 -1
- package/lib/module/index.js.map +0 -1
- package/lib/module/plugin/withAppIntents.js.map +0 -1
- package/lib/module/providers/GeminiProvider.js.map +0 -1
- package/lib/module/providers/OpenAIProvider.js.map +0 -1
- package/lib/module/providers/ProviderFactory.js.map +0 -1
- package/lib/module/services/AudioInputService.js.map +0 -1
- package/lib/module/services/AudioOutputService.js.map +0 -1
- package/lib/module/services/KnowledgeBaseService.js.map +0 -1
- package/lib/module/services/VoiceService.js.map +0 -1
- package/lib/module/services/flags/FlagService.js.map +0 -1
- package/lib/module/services/telemetry/MobileAI.js.map +0 -1
- package/lib/module/services/telemetry/PiiScrubber.js.map +0 -1
- package/lib/module/services/telemetry/TelemetryService.js.map +0 -1
- package/lib/module/services/telemetry/TouchAutoCapture.js.map +0 -1
- package/lib/module/services/telemetry/device.js.map +0 -1
- package/lib/module/services/telemetry/deviceMetadata.js.map +0 -1
- package/lib/module/services/telemetry/index.js.map +0 -1
- package/lib/module/services/telemetry/types.js.map +0 -1
- package/lib/module/support/CSATSurvey.js.map +0 -1
- package/lib/module/support/EscalationEventSource.js.map +0 -1
- package/lib/module/support/EscalationSocket.js.map +0 -1
- package/lib/module/support/SupportChatModal.js.map +0 -1
- package/lib/module/support/SupportGreeting.js.map +0 -1
- package/lib/module/support/TicketStore.js.map +0 -1
- package/lib/module/support/escalateTool.js.map +0 -1
- package/lib/module/support/index.js.map +0 -1
- package/lib/module/support/supportPrompt.js.map +0 -1
- package/lib/module/support/types.js.map +0 -1
- package/lib/module/tools/datePickerTool.js.map +0 -1
- package/lib/module/tools/guideTool.js.map +0 -1
- package/lib/module/tools/index.js.map +0 -1
- package/lib/module/tools/keyboardTool.js.map +0 -1
- package/lib/module/tools/longPressTool.js.map +0 -1
- package/lib/module/tools/pickerTool.js.map +0 -1
- package/lib/module/tools/restoreTool.js.map +0 -1
- package/lib/module/tools/scrollTool.js.map +0 -1
- package/lib/module/tools/simplifyTool.js.map +0 -1
- package/lib/module/tools/sliderTool.js.map +0 -1
- package/lib/module/tools/tapTool.js.map +0 -1
- package/lib/module/tools/typeTool.js.map +0 -1
- package/lib/module/tools/types.js.map +0 -1
- package/lib/module/types/jsx.d.js.map +0 -1
- package/lib/module/utils/audioUtils.js.map +0 -1
- package/lib/module/utils/logger.js.map +0 -1
- package/lib/typescript/babel.config.d.ts.map +0 -1
- package/lib/typescript/bin/generate-map.d.cts.map +0 -1
- package/lib/typescript/eslint.config.d.mts.map +0 -1
- package/lib/typescript/generate-map.d.ts.map +0 -1
- package/lib/typescript/src/__cli_tmp__.d.ts.map +0 -1
- package/lib/typescript/src/components/AIAgent.d.ts.map +0 -1
- package/lib/typescript/src/components/AIZone.d.ts.map +0 -1
- package/lib/typescript/src/components/AgentChatBar.d.ts.map +0 -1
- package/lib/typescript/src/components/AgentErrorBoundary.d.ts.map +0 -1
- package/lib/typescript/src/components/AgentOverlay.d.ts.map +0 -1
- package/lib/typescript/src/components/DiscoveryTooltip.d.ts.map +0 -1
- package/lib/typescript/src/components/HighlightOverlay.d.ts.map +0 -1
- package/lib/typescript/src/components/Icons.d.ts.map +0 -1
- package/lib/typescript/src/components/ProactiveHint.d.ts.map +0 -1
- package/lib/typescript/src/components/cards/InfoCard.d.ts.map +0 -1
- package/lib/typescript/src/components/cards/ReviewSummary.d.ts.map +0 -1
- package/lib/typescript/src/config/endpoints.d.ts.map +0 -1
- package/lib/typescript/src/core/ActionRegistry.d.ts.map +0 -1
- package/lib/typescript/src/core/AgentRuntime.d.ts.map +0 -1
- package/lib/typescript/src/core/FiberTreeWalker.d.ts.map +0 -1
- package/lib/typescript/src/core/IdleDetector.d.ts.map +0 -1
- package/lib/typescript/src/core/MCPBridge.d.ts.map +0 -1
- package/lib/typescript/src/core/ScreenDehydrator.d.ts.map +0 -1
- package/lib/typescript/src/core/ZoneRegistry.d.ts.map +0 -1
- package/lib/typescript/src/core/systemPrompt.d.ts.map +0 -1
- package/lib/typescript/src/core/types.d.ts.map +0 -1
- package/lib/typescript/src/hooks/useAction.d.ts.map +0 -1
- package/lib/typescript/src/index.d.ts.map +0 -1
- package/lib/typescript/src/plugin/withAppIntents.d.ts.map +0 -1
- package/lib/typescript/src/providers/GeminiProvider.d.ts.map +0 -1
- package/lib/typescript/src/providers/OpenAIProvider.d.ts.map +0 -1
- package/lib/typescript/src/providers/ProviderFactory.d.ts.map +0 -1
- package/lib/typescript/src/services/AudioInputService.d.ts.map +0 -1
- package/lib/typescript/src/services/AudioOutputService.d.ts.map +0 -1
- package/lib/typescript/src/services/KnowledgeBaseService.d.ts.map +0 -1
- package/lib/typescript/src/services/VoiceService.d.ts.map +0 -1
- package/lib/typescript/src/services/flags/FlagService.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/MobileAI.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/PiiScrubber.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/TelemetryService.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/TouchAutoCapture.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/device.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/deviceMetadata.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/index.d.ts.map +0 -1
- package/lib/typescript/src/services/telemetry/types.d.ts.map +0 -1
- package/lib/typescript/src/support/CSATSurvey.d.ts.map +0 -1
- package/lib/typescript/src/support/EscalationEventSource.d.ts.map +0 -1
- package/lib/typescript/src/support/EscalationSocket.d.ts.map +0 -1
- package/lib/typescript/src/support/SupportChatModal.d.ts.map +0 -1
- package/lib/typescript/src/support/SupportGreeting.d.ts.map +0 -1
- package/lib/typescript/src/support/TicketStore.d.ts.map +0 -1
- package/lib/typescript/src/support/escalateTool.d.ts.map +0 -1
- package/lib/typescript/src/support/index.d.ts.map +0 -1
- package/lib/typescript/src/support/supportPrompt.d.ts.map +0 -1
- package/lib/typescript/src/support/types.d.ts.map +0 -1
- package/lib/typescript/src/tools/datePickerTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/guideTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/index.d.ts.map +0 -1
- package/lib/typescript/src/tools/keyboardTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/longPressTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/pickerTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/restoreTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/scrollTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/simplifyTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/sliderTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/tapTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/typeTool.d.ts.map +0 -1
- package/lib/typescript/src/tools/types.d.ts.map +0 -1
- package/lib/typescript/src/utils/audioUtils.d.ts.map +0 -1
- package/lib/typescript/src/utils/logger.d.ts.map +0 -1
- package/src/__cli_tmp__.tsx +0 -9
- package/src/cli/analyzers/chain-analyzer.ts +0 -183
- package/src/cli/extractors/ai-extractor.ts +0 -6
- package/src/cli/extractors/ast-extractor.ts +0 -551
- package/src/cli/generate-intents.ts +0 -140
- package/src/cli/generate-map.ts +0 -121
- package/src/cli/generate-swift.ts +0 -116
- package/src/cli/scanners/expo-scanner.ts +0 -203
- package/src/cli/scanners/rn-scanner.ts +0 -445
- package/src/components/AIAgent.tsx +0 -1716
- package/src/components/AIZone.tsx +0 -147
- package/src/components/AgentChatBar.tsx +0 -1143
- package/src/components/AgentErrorBoundary.tsx +0 -78
- package/src/components/AgentOverlay.tsx +0 -73
- package/src/components/DiscoveryTooltip.tsx +0 -148
- package/src/components/HighlightOverlay.tsx +0 -136
- package/src/components/Icons.tsx +0 -253
- package/src/components/ProactiveHint.tsx +0 -145
- package/src/components/cards/InfoCard.tsx +0 -58
- package/src/components/cards/ReviewSummary.tsx +0 -76
- package/src/config/endpoints.ts +0 -22
- package/src/core/ActionRegistry.ts +0 -105
- package/src/core/AgentRuntime.ts +0 -1471
- package/src/core/FiberTreeWalker.ts +0 -930
- package/src/core/IdleDetector.ts +0 -72
- package/src/core/MCPBridge.ts +0 -163
- package/src/core/ScreenDehydrator.ts +0 -53
- package/src/core/ZoneRegistry.ts +0 -44
- package/src/core/systemPrompt.ts +0 -431
- package/src/core/types.ts +0 -521
- package/src/hooks/useAction.ts +0 -182
- package/src/index.ts +0 -83
- package/src/plugin/withAppIntents.ts +0 -98
- package/src/providers/GeminiProvider.ts +0 -357
- package/src/providers/OpenAIProvider.ts +0 -379
- package/src/providers/ProviderFactory.ts +0 -36
- package/src/services/AudioInputService.ts +0 -226
- package/src/services/AudioOutputService.ts +0 -236
- package/src/services/KnowledgeBaseService.ts +0 -156
- package/src/services/VoiceService.ts +0 -451
- package/src/services/flags/FlagService.ts +0 -137
- package/src/services/telemetry/MobileAI.ts +0 -66
- package/src/services/telemetry/PiiScrubber.ts +0 -17
- package/src/services/telemetry/TelemetryService.ts +0 -323
- package/src/services/telemetry/TouchAutoCapture.ts +0 -165
- package/src/services/telemetry/device.ts +0 -93
- package/src/services/telemetry/deviceMetadata.ts +0 -13
- package/src/services/telemetry/index.ts +0 -13
- package/src/services/telemetry/types.ts +0 -75
- package/src/support/CSATSurvey.tsx +0 -304
- package/src/support/EscalationEventSource.ts +0 -190
- package/src/support/EscalationSocket.ts +0 -152
- package/src/support/SupportChatModal.tsx +0 -563
- package/src/support/SupportGreeting.tsx +0 -161
- package/src/support/TicketStore.ts +0 -100
- package/src/support/escalateTool.ts +0 -174
- package/src/support/index.ts +0 -29
- package/src/support/supportPrompt.ts +0 -55
- package/src/support/types.ts +0 -155
- package/src/tools/datePickerTool.ts +0 -60
- package/src/tools/guideTool.ts +0 -76
- package/src/tools/index.ts +0 -20
- package/src/tools/keyboardTool.ts +0 -30
- package/src/tools/longPressTool.ts +0 -61
- package/src/tools/pickerTool.ts +0 -115
- package/src/tools/restoreTool.ts +0 -33
- package/src/tools/scrollTool.ts +0 -156
- package/src/tools/simplifyTool.ts +0 -33
- package/src/tools/sliderTool.ts +0 -65
- package/src/tools/tapTool.ts +0 -93
- package/src/tools/typeTool.ts +0 -113
- package/src/tools/types.ts +0 -58
- package/src/types/jsx.d.ts +0 -20
- package/src/utils/audioUtils.ts +0 -54
- package/src/utils/logger.ts +0 -38
|
@@ -16,9 +16,18 @@ import { walkFiberTree } from "./FiberTreeWalker.js";
|
|
|
16
16
|
import { dehydrateScreen } from "./ScreenDehydrator.js";
|
|
17
17
|
import { buildSystemPrompt, buildKnowledgeOnlyPrompt } from "./systemPrompt.js";
|
|
18
18
|
import { KnowledgeBaseService } from "../services/KnowledgeBaseService.js";
|
|
19
|
+
import { installAlertInterceptor, uninstallAlertInterceptor } from "./NativeAlertInterceptor.js";
|
|
19
20
|
import { createTapTool, createLongPressTool, createTypeTool, createScrollTool, createSliderTool, createPickerTool, createDatePickerTool, createKeyboardTool, createGuideTool, createSimplifyTool, createRestoreTool } from "../tools/index.js";
|
|
20
21
|
import { actionRegistry } from "./ActionRegistry.js";
|
|
21
22
|
const DEFAULT_MAX_STEPS = 25;
|
|
23
|
+
function generateTraceId() {
|
|
24
|
+
return `trace_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
25
|
+
}
|
|
26
|
+
const APPROVAL_GRANTED_TOKEN = '__APPROVAL_GRANTED__';
|
|
27
|
+
const APPROVAL_REJECTED_TOKEN = '__APPROVAL_REJECTED__';
|
|
28
|
+
const APPROVAL_ALREADY_DONE_TOKEN = '__APPROVAL_ALREADY_DONE__';
|
|
29
|
+
const USER_ALREADY_COMPLETED_MESSAGE = 'â
It looks like you already completed that step yourself. Great â let me know if you want help with anything else.';
|
|
30
|
+
const ACTION_NOT_APPROVED_MESSAGE = "Okay â I won't do that. If you'd like, I can help with something else instead.";
|
|
22
31
|
|
|
23
32
|
// âââ Agent Runtime âââââââââââââââââââââââââââââââââââââââââââââ
|
|
24
33
|
|
|
@@ -30,6 +39,7 @@ export class AgentRuntime {
|
|
|
30
39
|
lastAskUserQuestion = null;
|
|
31
40
|
knowledgeService = null;
|
|
32
41
|
lastDehydratedRoot = null;
|
|
42
|
+
currentTraceId = null;
|
|
33
43
|
|
|
34
44
|
// âââ Task-scoped error suppression ââââââââââââââââââââââââââ
|
|
35
45
|
// Installed once at execute() start, removed after grace period.
|
|
@@ -39,6 +49,14 @@ export class AgentRuntime {
|
|
|
39
49
|
lastSuppressedError = null;
|
|
40
50
|
graceTimer = null;
|
|
41
51
|
originalReportErrorsAsExceptions = undefined;
|
|
52
|
+
|
|
53
|
+
// âââ App-action approval gate ââââââââââââââââââââââââââââââââ
|
|
54
|
+
// Tracks whether the support consent flow (ask_user + request_app_action=true)
|
|
55
|
+
// has been issued and whether the user has explicitly approved it via button tap.
|
|
56
|
+
// Only UI-altering tools are gated; informational tools (done, query_knowledge) are not.
|
|
57
|
+
appActionApproved = false; // true only after __APPROVAL_GRANTED__ received
|
|
58
|
+
// Tools that physically alter the app â must be gated by appAction approval
|
|
59
|
+
static APP_ACTION_TOOLS = new Set(['tap', 'type', 'scroll', 'navigate', 'long_press', 'slider', 'picker', 'date_picker', 'keyboard']);
|
|
42
60
|
getConfig() {
|
|
43
61
|
return this.config;
|
|
44
62
|
}
|
|
@@ -182,6 +200,11 @@ export class AgentRuntime {
|
|
|
182
200
|
description: 'Response message to the user',
|
|
183
201
|
required: true
|
|
184
202
|
},
|
|
203
|
+
message: {
|
|
204
|
+
type: 'string',
|
|
205
|
+
description: 'Alternative to text parameter',
|
|
206
|
+
required: false
|
|
207
|
+
},
|
|
185
208
|
success: {
|
|
186
209
|
type: 'boolean',
|
|
187
210
|
description: 'Whether the task was completed successfully',
|
|
@@ -189,7 +212,12 @@ export class AgentRuntime {
|
|
|
189
212
|
}
|
|
190
213
|
},
|
|
191
214
|
execute: async args => {
|
|
192
|
-
|
|
215
|
+
let cleanText = args.text || args.message || '';
|
|
216
|
+
if (typeof cleanText === 'string') {
|
|
217
|
+
// Strip bracketed indices safely avoiding regex stack overflows on large strings
|
|
218
|
+
cleanText = cleanText.replace(/\[\d+\]/g, '').replace(/ +/g, ' ').trim();
|
|
219
|
+
}
|
|
220
|
+
return cleanText;
|
|
193
221
|
}
|
|
194
222
|
});
|
|
195
223
|
|
|
@@ -214,23 +242,58 @@ export class AgentRuntime {
|
|
|
214
242
|
// ask_user â ask for clarification
|
|
215
243
|
this.tools.set('ask_user', {
|
|
216
244
|
name: 'ask_user',
|
|
217
|
-
description: '
|
|
245
|
+
description: 'Communicate with the user. Use this to ask questions, request permission for app actions, OR answer a question the user asked.',
|
|
218
246
|
parameters: {
|
|
219
247
|
question: {
|
|
220
248
|
type: 'string',
|
|
221
|
-
description: '
|
|
249
|
+
description: 'The message or question to say to the user',
|
|
250
|
+
required: true
|
|
251
|
+
},
|
|
252
|
+
request_app_action: {
|
|
253
|
+
type: 'boolean',
|
|
254
|
+
description: 'Set to true when requesting permission to take an action in the app (navigate, tap, investigate). Shows explicit approval buttons to the user.',
|
|
222
255
|
required: true
|
|
223
256
|
}
|
|
224
257
|
},
|
|
225
258
|
execute: async args => {
|
|
259
|
+
// Strip any leaked bracketed indices like [41] safely
|
|
260
|
+
let cleanQuestion = args.question || '';
|
|
261
|
+
if (typeof cleanQuestion === 'string') {
|
|
262
|
+
cleanQuestion = cleanQuestion.replace(/\[\d+\]/g, '').replace(/ +/g, ' ').trim();
|
|
263
|
+
}
|
|
264
|
+
const kind = args.request_app_action ? 'approval' : 'freeform';
|
|
265
|
+
|
|
266
|
+
// Mark that the support approval flow has been initiated
|
|
267
|
+
if (args.request_app_action) {
|
|
268
|
+
this.appActionApproved = false; // reset until user taps Allow
|
|
269
|
+
logger.info('AgentRuntime', 'đ App action gate: approval requested, UI tools now BLOCKED until granted');
|
|
270
|
+
}
|
|
271
|
+
logger.info('AgentRuntime', `â ask_user emitted (kind=${kind}): "${cleanQuestion}"`);
|
|
226
272
|
if (this.config.onAskUser) {
|
|
227
273
|
// Block until user responds, then continue the loop
|
|
228
274
|
this.config.onStatusUpdate?.('Waiting for your answer...');
|
|
229
|
-
|
|
275
|
+
logger.info('AgentRuntime', `â¸ī¸ Waiting for user response via onAskUser callback (kind=${kind})`);
|
|
276
|
+
const answer = await this.config.onAskUser({
|
|
277
|
+
question: cleanQuestion,
|
|
278
|
+
kind
|
|
279
|
+
});
|
|
280
|
+
logger.info('AgentRuntime', `â
ask_user resolved with: "${String(answer)}"`);
|
|
281
|
+
|
|
282
|
+
// Resolve approval gate based on button response
|
|
283
|
+
if (answer === '__APPROVAL_GRANTED__') {
|
|
284
|
+
this.appActionApproved = true;
|
|
285
|
+
logger.info('AgentRuntime', 'â
App action gate: APPROVED â UI tools unblocked');
|
|
286
|
+
} else if (answer === '__APPROVAL_REJECTED__') {
|
|
287
|
+
this.appActionApproved = false;
|
|
288
|
+
logger.info('AgentRuntime', 'đĢ App action gate: REJECTED â UI tools remain blocked');
|
|
289
|
+
}
|
|
290
|
+
// Any other text answer (conversational interruption) leaves appActionApproved as-is
|
|
291
|
+
|
|
230
292
|
return `User answered: ${answer}`;
|
|
231
293
|
}
|
|
232
294
|
// Legacy fallback: break the loop (context will be lost)
|
|
233
|
-
|
|
295
|
+
logger.warn('AgentRuntime', 'â ī¸ ask_user has no onAskUser callback; returning legacy fallback');
|
|
296
|
+
return `â ${cleanQuestion}`;
|
|
234
297
|
}
|
|
235
298
|
});
|
|
236
299
|
|
|
@@ -712,7 +775,7 @@ ${screen.elementsText}
|
|
|
712
775
|
* The global ErrorUtils handler is task-scoped (installed in execute()),
|
|
713
776
|
* so this method only needs to CHECK for errors, not install/remove.
|
|
714
777
|
*/
|
|
715
|
-
async executeToolSafely(tool, args, toolName) {
|
|
778
|
+
async executeToolSafely(tool, args, toolName, stepIndex) {
|
|
716
779
|
// Clear any previous suppressed error before this tool
|
|
717
780
|
this.lastSuppressedError = null;
|
|
718
781
|
|
|
@@ -720,10 +783,20 @@ ${screen.elementsText}
|
|
|
720
783
|
// This prevents AI-driven taps from being tracked as user_interaction events.
|
|
721
784
|
this.config.onToolExecute?.(true);
|
|
722
785
|
try {
|
|
786
|
+
this.emitTrace('tool_execution_started', {
|
|
787
|
+
tool: toolName,
|
|
788
|
+
args
|
|
789
|
+
}, stepIndex);
|
|
790
|
+
|
|
723
791
|
// ââ Argument Validation (Pattern from Detox/Appium: typeof checks before native dispatch) ââ
|
|
724
792
|
const validationError = this.validateToolArgs(args, toolName);
|
|
725
793
|
if (validationError) {
|
|
726
794
|
logger.warn('AgentRuntime', `đĄī¸ Arg validation rejected "${toolName}": ${validationError}`);
|
|
795
|
+
this.emitTrace('tool_validation_rejected', {
|
|
796
|
+
tool: toolName,
|
|
797
|
+
args,
|
|
798
|
+
validationError
|
|
799
|
+
}, stepIndex);
|
|
727
800
|
return validationError;
|
|
728
801
|
}
|
|
729
802
|
|
|
@@ -732,8 +805,29 @@ ${screen.elementsText}
|
|
|
732
805
|
// user confirmation before execution. This is the code-level safety net
|
|
733
806
|
// complementing the prompt-level copilot instructions.
|
|
734
807
|
if (this.config.interactionMode !== 'autopilot') {
|
|
735
|
-
|
|
808
|
+
logger.info('AgentRuntime', `đĄī¸ Checking copilot confirmation for ${toolName}(${JSON.stringify(args)})`);
|
|
809
|
+
const confirmResult = await this.checkCopilotConfirmation(toolName, args, stepIndex);
|
|
736
810
|
if (confirmResult) return confirmResult;
|
|
811
|
+
} else {
|
|
812
|
+
logger.info('AgentRuntime', `đ interactionMode=autopilot, skipping copilot confirmation for "${toolName}"`);
|
|
813
|
+
this.emitTrace('confirmation_skipped_autopilot', {
|
|
814
|
+
tool: toolName,
|
|
815
|
+
args
|
|
816
|
+
}, stepIndex);
|
|
817
|
+
}
|
|
818
|
+
|
|
819
|
+
// ââ App-action approval gate ââââââââââââââââââââââââââââââââââââââââ
|
|
820
|
+
// Mandate explicit ask_user approval for all UI-altering tools ONLY if we are in
|
|
821
|
+
// copilot mode AND the host app has provided an onAskUser callback.
|
|
822
|
+
// If the model tries to use a UI tool without explicitly getting approval, we block it.
|
|
823
|
+
if (this.config.interactionMode !== 'autopilot' && this.config.onAskUser && AgentRuntime.APP_ACTION_TOOLS.has(toolName) && !this.appActionApproved) {
|
|
824
|
+
const blockedMsg = `đĢ APP ACTION BLOCKED: You are attempting to use "${toolName}" but have not yet received explicit user approval. You MUST first call ask_user(request_app_action=true) and wait for the user to explicitly tap 'Allow' before executing ANY UI actions (including navigate, tap, scroll, etc).`;
|
|
825
|
+
logger.warn('AgentRuntime', blockedMsg);
|
|
826
|
+
this.emitTrace('app_action_gate_blocked', {
|
|
827
|
+
tool: toolName,
|
|
828
|
+
args
|
|
829
|
+
}, stepIndex);
|
|
830
|
+
return blockedMsg;
|
|
737
831
|
}
|
|
738
832
|
const result = await tool.execute(args);
|
|
739
833
|
|
|
@@ -744,11 +838,27 @@ ${screen.elementsText}
|
|
|
744
838
|
if (suppressedError) {
|
|
745
839
|
logger.warn('AgentRuntime', `đĄī¸ Tool "${toolName}" caused async error (suppressed): ${suppressedError.message}`);
|
|
746
840
|
this.lastSuppressedError = null;
|
|
841
|
+
this.emitTrace('tool_async_error_suppressed', {
|
|
842
|
+
tool: toolName,
|
|
843
|
+
args,
|
|
844
|
+
result,
|
|
845
|
+
error: suppressedError.message
|
|
846
|
+
}, stepIndex);
|
|
747
847
|
return `${result} (â ī¸ a background error was safely caught: ${suppressedError.message})`;
|
|
748
848
|
}
|
|
849
|
+
this.emitTrace('tool_execution_finished', {
|
|
850
|
+
tool: toolName,
|
|
851
|
+
args,
|
|
852
|
+
result
|
|
853
|
+
}, stepIndex);
|
|
749
854
|
return result;
|
|
750
855
|
} catch (error) {
|
|
751
856
|
logger.error('AgentRuntime', `Tool "${toolName}" threw: ${error.message}`);
|
|
857
|
+
this.emitTrace('tool_execution_failed', {
|
|
858
|
+
tool: toolName,
|
|
859
|
+
args,
|
|
860
|
+
error: error?.message ?? String(error)
|
|
861
|
+
}, stepIndex);
|
|
752
862
|
return `â Tool "${toolName}" failed: ${error.message}`;
|
|
753
863
|
} finally {
|
|
754
864
|
// Always restore the flag â even on error or validation rejection
|
|
@@ -784,6 +894,18 @@ ${screen.elementsText}
|
|
|
784
894
|
}
|
|
785
895
|
return null;
|
|
786
896
|
}
|
|
897
|
+
emitTrace(stage, data = {}, stepIndex) {
|
|
898
|
+
if (!this.currentTraceId || !this.config.onTrace) return;
|
|
899
|
+
const event = {
|
|
900
|
+
traceId: this.currentTraceId,
|
|
901
|
+
stage,
|
|
902
|
+
timestamp: new Date().toISOString(),
|
|
903
|
+
stepIndex,
|
|
904
|
+
screenName: this.getCurrentScreenName(),
|
|
905
|
+
data
|
|
906
|
+
};
|
|
907
|
+
this.config.onTrace(event);
|
|
908
|
+
}
|
|
787
909
|
|
|
788
910
|
// âââ Copilot Confirmation âââââââââââââââââââââââââââââââââââââ
|
|
789
911
|
|
|
@@ -794,39 +916,145 @@ ${screen.elementsText}
|
|
|
794
916
|
* Check if a tool call targets an aiConfirm element and request user confirmation.
|
|
795
917
|
* Returns null if the action should proceed, or an error string if rejected.
|
|
796
918
|
*/
|
|
797
|
-
async checkCopilotConfirmation(toolName, args) {
|
|
919
|
+
async checkCopilotConfirmation(toolName, args, stepIndex) {
|
|
798
920
|
// Only gate write tools
|
|
799
|
-
if (!AgentRuntime.WRITE_TOOLS.has(toolName))
|
|
921
|
+
if (!AgentRuntime.WRITE_TOOLS.has(toolName)) {
|
|
922
|
+
logger.info('AgentRuntime', `đĄī¸ No confirmation needed for "${toolName}" because it is not a write tool`);
|
|
923
|
+
this.emitTrace('confirmation_not_needed', {
|
|
924
|
+
tool: toolName,
|
|
925
|
+
reason: 'not_write_tool',
|
|
926
|
+
args
|
|
927
|
+
}, stepIndex);
|
|
928
|
+
return null;
|
|
929
|
+
}
|
|
800
930
|
|
|
801
931
|
// Look up the target element by index
|
|
802
932
|
const index = args.index;
|
|
803
|
-
if (typeof index !== 'number')
|
|
933
|
+
if (typeof index !== 'number') {
|
|
934
|
+
logger.info('AgentRuntime', `đĄī¸ No confirmation needed for "${toolName}" because no element index was provided`);
|
|
935
|
+
this.emitTrace('confirmation_not_needed', {
|
|
936
|
+
tool: toolName,
|
|
937
|
+
reason: 'missing_index',
|
|
938
|
+
args
|
|
939
|
+
}, stepIndex);
|
|
940
|
+
return null;
|
|
941
|
+
}
|
|
804
942
|
const screen = this.lastDehydratedRoot;
|
|
805
|
-
if (!screen?.elements)
|
|
943
|
+
if (!screen?.elements) {
|
|
944
|
+
logger.warn('AgentRuntime', `đĄī¸ Could not evaluate confirmation for "${toolName}" because no dehydrated screen was available`);
|
|
945
|
+
this.emitTrace('confirmation_not_evaluated', {
|
|
946
|
+
tool: toolName,
|
|
947
|
+
reason: 'missing_dehydrated_screen',
|
|
948
|
+
args
|
|
949
|
+
}, stepIndex);
|
|
950
|
+
return null;
|
|
951
|
+
}
|
|
806
952
|
const element = screen.elements.find(e => e.index === index);
|
|
807
|
-
if (!element
|
|
953
|
+
if (!element) {
|
|
954
|
+
logger.warn('AgentRuntime', `đĄī¸ Could not find element index ${index} for "${toolName}" on screen "${screen.screenName}"`);
|
|
955
|
+
this.emitTrace('confirmation_not_evaluated', {
|
|
956
|
+
tool: toolName,
|
|
957
|
+
reason: 'element_not_found',
|
|
958
|
+
args,
|
|
959
|
+
index
|
|
960
|
+
}, stepIndex);
|
|
961
|
+
return null;
|
|
962
|
+
}
|
|
963
|
+
logger.info('AgentRuntime', `đĄī¸ Copilot gate inspect: tool="${toolName}" index=${index} label="${element.label}" type="${element.type}" aiConfirm=${element.requiresConfirmation === true}`);
|
|
964
|
+
if (!element.requiresConfirmation) {
|
|
965
|
+
logger.info('AgentRuntime', `đĄī¸ No confirmation needed for "${toolName}" on "${element.label}" because aiConfirm is not set`);
|
|
966
|
+
this.emitTrace('confirmation_not_needed', {
|
|
967
|
+
tool: toolName,
|
|
968
|
+
reason: 'aiConfirm_not_set',
|
|
969
|
+
args,
|
|
970
|
+
elementLabel: element.label,
|
|
971
|
+
elementType: element.type,
|
|
972
|
+
index
|
|
973
|
+
}, stepIndex);
|
|
974
|
+
return null;
|
|
975
|
+
}
|
|
808
976
|
|
|
809
977
|
// Element has aiConfirm â request user confirmation
|
|
810
978
|
const label = element.label || `[${element.type}]`;
|
|
811
979
|
const description = this.getToolStatusLabel(toolName, args);
|
|
812
|
-
const question = `I
|
|
980
|
+
const question = `I can do this in the app for you by tapping and typing where needed, and ${description} on "${label}". If you'd rather do it yourself, I can guide you step by step instead.`;
|
|
813
981
|
logger.info('AgentRuntime', `đĄī¸ Copilot: aiConfirm gate triggered for "${toolName}" on "${label}"`);
|
|
982
|
+
this.emitTrace('confirmation_required', {
|
|
983
|
+
tool: toolName,
|
|
984
|
+
args,
|
|
985
|
+
elementLabel: label,
|
|
986
|
+
elementType: element.type,
|
|
987
|
+
index,
|
|
988
|
+
question
|
|
989
|
+
}, stepIndex);
|
|
814
990
|
|
|
815
991
|
// Use onAskUser if available (integrated into chat UI), otherwise Alert.alert
|
|
816
992
|
if (this.config.onAskUser) {
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
993
|
+
logger.info('AgentRuntime', `đĄī¸ Requesting explicit confirmation via ask_user for "${label}"`);
|
|
994
|
+
this.emitTrace('confirmation_prompted', {
|
|
995
|
+
tool: toolName,
|
|
996
|
+
elementLabel: label,
|
|
997
|
+
elementType: element.type,
|
|
998
|
+
question,
|
|
999
|
+
channel: 'ask_user'
|
|
1000
|
+
}, stepIndex);
|
|
1001
|
+
const response = await this.config.onAskUser({
|
|
1002
|
+
question,
|
|
1003
|
+
kind: 'approval'
|
|
1004
|
+
});
|
|
1005
|
+
logger.info('AgentRuntime', `đĄī¸ Confirmation response for "${label}": "${String(response)}"`);
|
|
1006
|
+
this.emitTrace('confirmation_response_received', {
|
|
1007
|
+
tool: toolName,
|
|
1008
|
+
elementLabel: label,
|
|
1009
|
+
response: String(response)
|
|
1010
|
+
}, stepIndex);
|
|
1011
|
+
if (response === APPROVAL_ALREADY_DONE_TOKEN) {
|
|
1012
|
+
this.emitTrace('confirmation_already_done', {
|
|
1013
|
+
tool: toolName,
|
|
1014
|
+
elementLabel: label
|
|
1015
|
+
}, stepIndex);
|
|
1016
|
+
return APPROVAL_ALREADY_DONE_TOKEN;
|
|
1017
|
+
}
|
|
1018
|
+
if (response === APPROVAL_GRANTED_TOKEN) {
|
|
1019
|
+
logger.info('AgentRuntime', `â
User approved "${toolName}" on "${label}"`);
|
|
1020
|
+
this.emitTrace('confirmation_approved', {
|
|
1021
|
+
tool: toolName,
|
|
1022
|
+
elementLabel: label,
|
|
1023
|
+
response: 'explicit_button'
|
|
1024
|
+
}, stepIndex);
|
|
1025
|
+
return null;
|
|
1026
|
+
}
|
|
1027
|
+
if (response === APPROVAL_REJECTED_TOKEN) {
|
|
820
1028
|
logger.info('AgentRuntime', `đ User rejected "${toolName}" on "${label}"`);
|
|
821
|
-
|
|
1029
|
+
this.emitTrace('confirmation_rejected', {
|
|
1030
|
+
tool: toolName,
|
|
1031
|
+
elementLabel: label,
|
|
1032
|
+
response: 'explicit_button'
|
|
1033
|
+
}, stepIndex);
|
|
1034
|
+
return ACTION_NOT_APPROVED_MESSAGE;
|
|
822
1035
|
}
|
|
823
|
-
|
|
1036
|
+
|
|
1037
|
+
// If it's conversational input (e.g. "Why?"), pause the action and pass the user's question back to the LLM so it can answer it!
|
|
1038
|
+
this.emitTrace('confirmation_interrupted', {
|
|
1039
|
+
tool: toolName,
|
|
1040
|
+
elementLabel: label,
|
|
1041
|
+
response: String(response)
|
|
1042
|
+
}, stepIndex);
|
|
1043
|
+
return `Action paused because the user interrupted with this message: "${response}". Please answer the user by fully explaining your logic.`;
|
|
824
1044
|
}
|
|
825
1045
|
|
|
826
1046
|
// Fallback: React Native Alert
|
|
827
1047
|
const {
|
|
828
1048
|
Alert
|
|
829
1049
|
} = require('react-native');
|
|
1050
|
+
logger.info('AgentRuntime', `đĄī¸ Requesting explicit confirmation via native Alert for "${label}"`);
|
|
1051
|
+
this.emitTrace('confirmation_prompted', {
|
|
1052
|
+
tool: toolName,
|
|
1053
|
+
elementLabel: label,
|
|
1054
|
+
elementType: element.type,
|
|
1055
|
+
question,
|
|
1056
|
+
channel: 'native_alert'
|
|
1057
|
+
}, stepIndex);
|
|
830
1058
|
const approved = await new Promise(resolve => {
|
|
831
1059
|
Alert.alert('Confirm Action', question, [{
|
|
832
1060
|
text: 'Cancel',
|
|
@@ -841,8 +1069,19 @@ ${screen.elementsText}
|
|
|
841
1069
|
});
|
|
842
1070
|
if (!approved) {
|
|
843
1071
|
logger.info('AgentRuntime', `đ User rejected "${toolName}" on "${label}"`);
|
|
844
|
-
|
|
1072
|
+
this.emitTrace('confirmation_rejected', {
|
|
1073
|
+
tool: toolName,
|
|
1074
|
+
elementLabel: label,
|
|
1075
|
+
response: 'cancel'
|
|
1076
|
+
}, stepIndex);
|
|
1077
|
+
return ACTION_NOT_APPROVED_MESSAGE;
|
|
845
1078
|
}
|
|
1079
|
+
logger.info('AgentRuntime', `â
User approved "${toolName}" on "${label}" via native Alert`);
|
|
1080
|
+
this.emitTrace('confirmation_approved', {
|
|
1081
|
+
tool: toolName,
|
|
1082
|
+
elementLabel: label,
|
|
1083
|
+
response: 'continue'
|
|
1084
|
+
}, stepIndex);
|
|
846
1085
|
return null;
|
|
847
1086
|
}
|
|
848
1087
|
|
|
@@ -852,7 +1091,8 @@ ${screen.elementsText}
|
|
|
852
1091
|
return {
|
|
853
1092
|
interactiveBlacklist: this.config.interactiveBlacklist,
|
|
854
1093
|
interactiveWhitelist: this.config.interactiveWhitelist,
|
|
855
|
-
screenName: this.getCurrentScreenName()
|
|
1094
|
+
screenName: this.getCurrentScreenName(),
|
|
1095
|
+
interceptNativeAlerts: this.config.interceptNativeAlerts
|
|
856
1096
|
};
|
|
857
1097
|
}
|
|
858
1098
|
|
|
@@ -1020,8 +1260,11 @@ ${screen.elementsText}
|
|
|
1020
1260
|
this.isRunning = true;
|
|
1021
1261
|
this.isCancelRequested = false;
|
|
1022
1262
|
this.history = [];
|
|
1263
|
+
this.currentTraceId = generateTraceId();
|
|
1023
1264
|
this.observations = [];
|
|
1024
1265
|
this.lastScreenName = '';
|
|
1266
|
+
// Reset app-action approval gate for each new task
|
|
1267
|
+
this.appActionApproved = false;
|
|
1025
1268
|
const maxSteps = this.config.maxSteps || DEFAULT_MAX_STEPS;
|
|
1026
1269
|
const stepDelay = this.config.stepDelay ?? 300;
|
|
1027
1270
|
|
|
@@ -1044,7 +1287,19 @@ ${screen.elementsText}
|
|
|
1044
1287
|
// Lifecycle: onBeforeTask
|
|
1045
1288
|
await this.config.onBeforeTask?.();
|
|
1046
1289
|
try {
|
|
1047
|
-
|
|
1290
|
+
this.emitTrace('task_started', {
|
|
1291
|
+
message: userMessage,
|
|
1292
|
+
contextualMessage,
|
|
1293
|
+
maxSteps,
|
|
1294
|
+
interactionMode: this.config.interactionMode || 'copilot',
|
|
1295
|
+
enableUIControl: this.config.enableUIControl !== false,
|
|
1296
|
+
chatHistoryLength: chatHistory?.length ?? 0
|
|
1297
|
+
});
|
|
1298
|
+
|
|
1299
|
+
// ââ Start interceptors & error suppression ââââââââââââââââââ
|
|
1300
|
+
if (this.config.interceptNativeAlerts) {
|
|
1301
|
+
installAlertInterceptor();
|
|
1302
|
+
}
|
|
1048
1303
|
this._startErrorSuppression();
|
|
1049
1304
|
|
|
1050
1305
|
// âââ Knowledge-only fast path âââââââââââââââââââââââââââââââââ
|
|
@@ -1120,6 +1375,9 @@ ${screen.elementsText}
|
|
|
1120
1375
|
// ââ Cancel check ââ
|
|
1121
1376
|
if (this.isCancelRequested) {
|
|
1122
1377
|
logger.info('AgentRuntime', `Task cancelled by user at step ${step + 1}`);
|
|
1378
|
+
this.emitTrace('task_cancelled', {
|
|
1379
|
+
reason: 'user_cancelled'
|
|
1380
|
+
}, step);
|
|
1123
1381
|
const cancelResult = {
|
|
1124
1382
|
success: false,
|
|
1125
1383
|
message: 'Task was cancelled.',
|
|
@@ -1130,6 +1388,11 @@ ${screen.elementsText}
|
|
|
1130
1388
|
return cancelResult;
|
|
1131
1389
|
}
|
|
1132
1390
|
logger.info('AgentRuntime', `===== Step ${step + 1}/${maxSteps} =====`);
|
|
1391
|
+
this.emitTrace('step_started', {
|
|
1392
|
+
maxSteps,
|
|
1393
|
+
historyLength: this.history.length
|
|
1394
|
+
}, step);
|
|
1395
|
+
logger.info('AgentRuntime', `âī¸ Effective mode: interactionMode=${this.config.interactionMode || 'copilot(default)'} | onAskUser=${!!this.config.onAskUser} | enableUIControl=${this.config.enableUIControl !== false}`);
|
|
1133
1396
|
|
|
1134
1397
|
// Lifecycle: onBeforeStep
|
|
1135
1398
|
await this.config.onBeforeStep?.(step);
|
|
@@ -1141,8 +1404,12 @@ ${screen.elementsText}
|
|
|
1141
1404
|
|
|
1142
1405
|
// Store root for tooling access (e.g., GuideTool measuring)
|
|
1143
1406
|
this.lastDehydratedRoot = screen;
|
|
1407
|
+
this.emitTrace('screen_dehydrated', {
|
|
1408
|
+
screenName: screen.screenName,
|
|
1409
|
+
elementCount: screen.elements.length,
|
|
1410
|
+
elementsTextLength: screen.elementsText.length
|
|
1411
|
+
}, step);
|
|
1144
1412
|
logger.info('AgentRuntime', `Screen: ${screen.screenName}`);
|
|
1145
|
-
logger.debug('AgentRuntime', `Dehydrated:\n${screen.elementsText}`);
|
|
1146
1413
|
|
|
1147
1414
|
// 2. Apply transformScreenContent
|
|
1148
1415
|
let screenContent = screen.elementsText;
|
|
@@ -1160,7 +1427,7 @@ ${screen.elementsText}
|
|
|
1160
1427
|
const screenshot = await this.captureScreenshot();
|
|
1161
1428
|
|
|
1162
1429
|
// 5. Send to AI provider
|
|
1163
|
-
this.config.onStatusUpdate?.('
|
|
1430
|
+
this.config.onStatusUpdate?.('Thinking...');
|
|
1164
1431
|
const hasKnowledge = !!this.knowledgeService;
|
|
1165
1432
|
const isCopilot = this.config.interactionMode !== 'autopilot';
|
|
1166
1433
|
const systemPrompt = buildSystemPrompt('en', hasKnowledge, isCopilot);
|
|
@@ -1169,6 +1436,19 @@ ${screen.elementsText}
|
|
|
1169
1436
|
logger.debug('AgentRuntime', 'System prompt length:', systemPrompt.length);
|
|
1170
1437
|
logger.debug('AgentRuntime', 'User context message:', contextMessage.substring(0, 300));
|
|
1171
1438
|
const response = await this.provider.generateContent(systemPrompt, contextMessage, tools, this.history, screenshot);
|
|
1439
|
+
this.emitTrace('provider_response', {
|
|
1440
|
+
text: response.text,
|
|
1441
|
+
toolCalls: response.toolCalls,
|
|
1442
|
+
tokenUsage: response.tokenUsage
|
|
1443
|
+
}, step);
|
|
1444
|
+
logger.info('AgentRuntime', `đ¤ Provider response: textLength=${response.text?.length || 0} toolCalls=${response.toolCalls?.length || 0}`);
|
|
1445
|
+
if (response.toolCalls?.length) {
|
|
1446
|
+
response.toolCalls.forEach((toolCall, idx) => {
|
|
1447
|
+
logger.info('AgentRuntime', `đ¤ Tool call[${idx}]: ${toolCall.name}(${JSON.stringify(toolCall.args)})`);
|
|
1448
|
+
});
|
|
1449
|
+
} else if (response.text) {
|
|
1450
|
+
logger.info('AgentRuntime', `đ¤ Provider text response: ${response.text}`);
|
|
1451
|
+
}
|
|
1172
1452
|
|
|
1173
1453
|
// Accumulate token usage
|
|
1174
1454
|
if (response.tokenUsage) {
|
|
@@ -1182,6 +1462,11 @@ ${screen.elementsText}
|
|
|
1182
1462
|
// ââ Budget Guards ââââââââââââââââââââââââââââââââââââââ
|
|
1183
1463
|
if (this.config.maxTokenBudget && sessionUsage.totalTokens >= this.config.maxTokenBudget) {
|
|
1184
1464
|
logger.warn('AgentRuntime', `Token budget exceeded: ${sessionUsage.totalTokens} >= ${this.config.maxTokenBudget}`);
|
|
1465
|
+
this.emitTrace('task_stopped_budget', {
|
|
1466
|
+
budgetType: 'tokens',
|
|
1467
|
+
used: sessionUsage.totalTokens,
|
|
1468
|
+
limit: this.config.maxTokenBudget
|
|
1469
|
+
}, step);
|
|
1185
1470
|
const budgetResult = {
|
|
1186
1471
|
success: false,
|
|
1187
1472
|
message: `Task stopped: token budget exceeded (used ${sessionUsage.totalTokens.toLocaleString()} of ${this.config.maxTokenBudget.toLocaleString()} tokens)`,
|
|
@@ -1193,6 +1478,11 @@ ${screen.elementsText}
|
|
|
1193
1478
|
}
|
|
1194
1479
|
if (this.config.maxCostUSD && sessionUsage.estimatedCostUSD >= this.config.maxCostUSD) {
|
|
1195
1480
|
logger.warn('AgentRuntime', `Cost budget exceeded: $${sessionUsage.estimatedCostUSD.toFixed(4)} >= $${this.config.maxCostUSD}`);
|
|
1481
|
+
this.emitTrace('task_stopped_budget', {
|
|
1482
|
+
budgetType: 'cost_usd',
|
|
1483
|
+
used: sessionUsage.estimatedCostUSD,
|
|
1484
|
+
limit: this.config.maxCostUSD
|
|
1485
|
+
}, step);
|
|
1196
1486
|
const budgetResult = {
|
|
1197
1487
|
success: false,
|
|
1198
1488
|
message: `Task stopped: cost budget exceeded ($${sessionUsage.estimatedCostUSD.toFixed(4)} of $${this.config.maxCostUSD.toFixed(2)} max)`,
|
|
@@ -1206,6 +1496,9 @@ ${screen.elementsText}
|
|
|
1206
1496
|
// 6. Process tool calls
|
|
1207
1497
|
if (!response.toolCalls || response.toolCalls.length === 0) {
|
|
1208
1498
|
logger.warn('AgentRuntime', 'No tool calls in response. Text:', response.text);
|
|
1499
|
+
this.emitTrace('task_completed_without_tool', {
|
|
1500
|
+
responseText: response.text
|
|
1501
|
+
}, step);
|
|
1209
1502
|
const result = {
|
|
1210
1503
|
success: true,
|
|
1211
1504
|
message: response.text || 'Task completed.',
|
|
@@ -1232,6 +1525,14 @@ ${screen.elementsText}
|
|
|
1232
1525
|
logger.warn('AgentRuntime', `AI returned ${response.toolCalls.length} tool calls, executing only the first one.`);
|
|
1233
1526
|
}
|
|
1234
1527
|
logger.info('AgentRuntime', `Tool: ${toolCall.name}(${JSON.stringify(toolCall.args)})`);
|
|
1528
|
+
this.emitTrace('tool_selected', {
|
|
1529
|
+
tool: toolCall.name,
|
|
1530
|
+
args: toolCall.args,
|
|
1531
|
+
reasoning
|
|
1532
|
+
}, step);
|
|
1533
|
+
if (toolCall.name !== 'ask_user' && this.config.interactionMode !== 'autopilot') {
|
|
1534
|
+
logger.info('AgentRuntime', `đĄī¸ Tool "${toolCall.name}" chosen without prompt-level pause; relying on model plan-following and aiConfirm safeguards if present`);
|
|
1535
|
+
}
|
|
1235
1536
|
|
|
1236
1537
|
// Dynamic status update based on tool being executed + Reasoning
|
|
1237
1538
|
const statusLabel = this.getToolStatusLabel(toolCall.name, toolCall.args);
|
|
@@ -1243,11 +1544,30 @@ ${screen.elementsText}
|
|
|
1243
1544
|
const tool = this.tools.get(toolCall.name) || this.buildToolsForProvider().find(t => t.name === toolCall.name);
|
|
1244
1545
|
let output;
|
|
1245
1546
|
if (tool) {
|
|
1246
|
-
output = await this.executeToolSafely(tool, toolCall.args, toolCall.name);
|
|
1547
|
+
output = await this.executeToolSafely(tool, toolCall.args, toolCall.name, step);
|
|
1247
1548
|
} else {
|
|
1549
|
+
this.emitTrace('tool_unknown', {
|
|
1550
|
+
tool: toolCall.name,
|
|
1551
|
+
args: toolCall.args
|
|
1552
|
+
}, step);
|
|
1248
1553
|
output = `â Unknown tool: ${toolCall.name}`;
|
|
1249
1554
|
}
|
|
1250
1555
|
logger.info('AgentRuntime', `Result: ${output}`);
|
|
1556
|
+
this.emitTrace('tool_result', {
|
|
1557
|
+
tool: toolCall.name,
|
|
1558
|
+
args: toolCall.args,
|
|
1559
|
+
output
|
|
1560
|
+
}, step);
|
|
1561
|
+
if (output === APPROVAL_ALREADY_DONE_TOKEN) {
|
|
1562
|
+
const result = {
|
|
1563
|
+
success: true,
|
|
1564
|
+
message: USER_ALREADY_COMPLETED_MESSAGE,
|
|
1565
|
+
steps: this.history,
|
|
1566
|
+
tokenUsage: sessionUsage
|
|
1567
|
+
};
|
|
1568
|
+
await this.config.onAfterTask?.(result);
|
|
1569
|
+
return result;
|
|
1570
|
+
}
|
|
1251
1571
|
|
|
1252
1572
|
// Record step with structured reasoning
|
|
1253
1573
|
const agentStep = {
|
|
@@ -1268,24 +1588,37 @@ ${screen.elementsText}
|
|
|
1268
1588
|
if (toolCall.name === 'done') {
|
|
1269
1589
|
const result = {
|
|
1270
1590
|
success: toolCall.args.success !== false,
|
|
1271
|
-
message: toolCall.args.text || output,
|
|
1591
|
+
message: toolCall.args.text || toolCall.args.message || output || reasoning.plan || (toolCall.args.success === false ? 'Action stopped.' : 'Action completed.'),
|
|
1272
1592
|
steps: this.history,
|
|
1273
1593
|
tokenUsage: sessionUsage
|
|
1274
1594
|
};
|
|
1275
1595
|
logger.info('AgentRuntime', `Task completed: ${result.message}`);
|
|
1596
|
+
this.emitTrace('task_completed', {
|
|
1597
|
+
success: result.success,
|
|
1598
|
+
message: result.message,
|
|
1599
|
+
steps: this.history.length,
|
|
1600
|
+
tokenUsage: sessionUsage
|
|
1601
|
+
}, step);
|
|
1276
1602
|
await this.config.onAfterTask?.(result);
|
|
1277
1603
|
return result;
|
|
1278
1604
|
}
|
|
1279
1605
|
|
|
1280
1606
|
// Check if asking user (legacy path â only breaks loop when onAskUser is NOT set)
|
|
1281
1607
|
if (toolCall.name === 'ask_user' && !this.config.onAskUser) {
|
|
1282
|
-
|
|
1608
|
+
let rawQuestion = toolCall.args.question || output || '';
|
|
1609
|
+
if (typeof rawQuestion === 'string') {
|
|
1610
|
+
rawQuestion = rawQuestion.replace(/\[\d+\]/g, '').replace(/ +/g, ' ').trim();
|
|
1611
|
+
}
|
|
1612
|
+
this.lastAskUserQuestion = rawQuestion;
|
|
1283
1613
|
const result = {
|
|
1284
1614
|
success: true,
|
|
1285
|
-
message:
|
|
1615
|
+
message: this.lastAskUserQuestion || '',
|
|
1286
1616
|
steps: this.history,
|
|
1287
1617
|
tokenUsage: sessionUsage
|
|
1288
1618
|
};
|
|
1619
|
+
this.emitTrace('task_paused_for_user', {
|
|
1620
|
+
question: this.lastAskUserQuestion || ''
|
|
1621
|
+
}, step);
|
|
1289
1622
|
await this.config.onAfterTask?.(result);
|
|
1290
1623
|
return result;
|
|
1291
1624
|
}
|
|
@@ -1306,10 +1639,19 @@ ${screen.elementsText}
|
|
|
1306
1639
|
if (__DEV__ && this.config.interactionMode !== 'autopilot') {
|
|
1307
1640
|
logger.info('AgentRuntime', 'âšī¸ Copilot mode active. Tip: Add aiConfirm={true} to critical buttons (e.g. "Place Order", "Delete") for extra safety.');
|
|
1308
1641
|
}
|
|
1642
|
+
this.emitTrace('task_failed_max_steps', {
|
|
1643
|
+
success: false,
|
|
1644
|
+
steps: this.history.length,
|
|
1645
|
+
message: result.message
|
|
1646
|
+
});
|
|
1309
1647
|
await this.config.onAfterTask?.(result);
|
|
1310
1648
|
return result;
|
|
1311
1649
|
} catch (error) {
|
|
1312
1650
|
logger.error('AgentRuntime', 'Execution error:', error);
|
|
1651
|
+
this.emitTrace('task_failed_error', {
|
|
1652
|
+
error: error?.message ?? String(error),
|
|
1653
|
+
steps: this.history.length
|
|
1654
|
+
});
|
|
1313
1655
|
const result = {
|
|
1314
1656
|
success: false,
|
|
1315
1657
|
message: `Error: ${error.message}`,
|
|
@@ -1320,6 +1662,10 @@ ${screen.elementsText}
|
|
|
1320
1662
|
return result;
|
|
1321
1663
|
} finally {
|
|
1322
1664
|
this.isRunning = false;
|
|
1665
|
+
this.currentTraceId = null;
|
|
1666
|
+
if (this.config.interceptNativeAlerts) {
|
|
1667
|
+
uninstallAlertInterceptor();
|
|
1668
|
+
}
|
|
1323
1669
|
// ââ Grace period: keep error suppression for delayed side-effects ââ
|
|
1324
1670
|
// useEffect callbacks, PagerView onPageSelected, scrollToIndex, etc.
|
|
1325
1671
|
// can fire AFTER execute() returns. Keep suppression active for 10s.
|