@appilots/sdk 0.11.3 → 0.13.0
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/control-transformer.js +69 -0
- package/dist/.build-meta.json +3 -3
- package/dist/{chunk-UFMTRGML.js → chunk-AJDHZRBN.js} +1097 -460
- package/dist/{chunk-5UVMNKPT.js → chunk-ALJGKORS.js} +8 -8
- package/dist/{chunk-ROXXCDIG.mjs → chunk-HRWBK35Y.mjs} +5738 -540
- package/dist/{chunk-JKLE5SLN.mjs → chunk-L3SY3Q4K.mjs} +1 -1
- package/dist/{chunk-2ZDELL54.mjs → chunk-UYBKXULM.mjs} +815 -191
- package/dist/{chunk-SUMIABJK.js → chunk-UZ2D4BVX.js} +5756 -538
- package/dist/hooks/index.d.mts +1 -1
- package/dist/hooks/index.d.ts +1 -1
- package/dist/hooks/index.js +11 -11
- package/dist/hooks/index.mjs +2 -2
- package/dist/{index-BR-33WMN.d.mts → index-CI4jmSoP.d.mts} +136 -660
- package/dist/{index-BR-33WMN.d.ts → index-CI4jmSoP.d.ts} +136 -660
- package/dist/index.d.mts +1447 -76
- package/dist/index.d.ts +1447 -76
- package/dist/index.js +1069 -672
- package/dist/index.mjs +902 -564
- package/dist/navigation/index.d.mts +22 -1
- package/dist/navigation/index.d.ts +22 -1
- package/dist/navigation/index.js +13 -13
- package/dist/navigation/index.mjs +2 -2
- package/metro.d.ts +3 -5
- package/metro.js +46 -5
- package/package.json +16 -2
|
@@ -1,6 +1,261 @@
|
|
|
1
|
-
import { createSnapshotDeltaTracker, getFiberRoot, appilotsDebugWarn, recordIntrospectionFailure, getCurrentScreen, walkFiber, getActiveRouteNames, clampSnapshotToWireLimits, deriveInteractionElements, rectForNode, toWireElement, attachElementIdsToChoiceGroups, elementRegistry, appilotsDebugLog, getLastWalkDiagnostics, SDK_VERSION, geometryStats, geometryAvailability, getNameManglingProbe, emptyDetectorCounts, getAutoTrackingState, getIntrospectionDiagnostics, getDefaultRegistry, useAppilotsContext, ChatSessionMachine, getCurrentScreenSignature, ActionQueueMachine, getScreenMetadata, setCurrentScreen, useResolvedRegistry, totalDetectorHits,
|
|
2
|
-
import React, { useMemo, useRef, useSyncExternalStore, useEffect, useCallback, useState } from 'react';
|
|
3
|
-
import { Alert } from 'react-native';
|
|
1
|
+
import { createSnapshotDeltaTracker, normalizeAppilotsLocale, getFiberRoot, appilotsDebugWarn, recordIntrospectionFailure, getCurrentScreen, walkFiber, getActiveRouteNames, clampSnapshotToWireLimits, deriveInteractionElements, rectForNode, toWireElement, attachElementIdsToChoiceGroups, elementRegistry, appilotsDebugLog, getLastWalkDiagnostics, SDK_VERSION, geometryStats, geometryAvailability, getNameManglingProbe, emptyDetectorCounts, getAutoTrackingState, getIntrospectionDiagnostics, getGeometryHost, isRectOnScreen, listRegistry, resolveAgentPermissions, getDefaultRegistry, useAppilotsContext, ChatSessionMachine, getCurrentScreenSignature, ActionQueueMachine, getScreenMetadata, setCurrentScreen, useResolvedRegistry, totalDetectorHits, getOpenNativeDialog, componentRegistry, permissionDeniedMessage, probeLoadingState, getLastAnsweredNativeDialog, isListItemPressTarget, waitForLoadingSettle, describeValue, waitForScreenSettle, screenDepartedBaseline, routesBelongToSameFeature, actionPressTargetId, getNavigationStateSnapshot, getNavigationRef, isOptionalStepAutomationFailure, OPTIONAL_STEP_AUTOMATION_HINT, isSourceControlId, resolveTarget, isSwitchLike, sourceControlTarget, rectOfFiber, idLooselyMatches, normalize3, matchScore, FORM_VALIDATION_TEXT_RE, parseStableElementId, shouldSkipSubtree, isAlwaysListContainer, isHeuristicListContainer, findListItems, getTypeName, foldForCompare, isModalFiber, sourceControlId, endTextInputEditing, switchValue, getAllScreens, snapshotShowsOpenCreateForm, isReferenceable, looksDestructiveActionLabel, isGenericSelectValue, SUBMIT_CONTROL_RE, isVisibleValidationText, FORM_WRITE_SUCCESS_TEXT_RE, TOUCHABLE_NAMES } from './chunk-HRWBK35Y.mjs';
|
|
2
|
+
import React, { createContext, useMemo, useContext, useRef, useSyncExternalStore, useEffect, useCallback, useState } from 'react';
|
|
3
|
+
import { Platform, NativeModules, AppState, Alert } from 'react-native';
|
|
4
|
+
|
|
5
|
+
// src/i18n/bundles/pt-BR.ts
|
|
6
|
+
var ptBR = {
|
|
7
|
+
chatTitle: "Assistente Appilots",
|
|
8
|
+
clearButton: "Limpar",
|
|
9
|
+
inputPlaceholder: "Digite uma mensagem...",
|
|
10
|
+
emptyTitle: "Como posso ajudar?",
|
|
11
|
+
emptySubtitle: "Navego, preencho formul\xE1rios e executo a\xE7\xF5es neste app.",
|
|
12
|
+
thinking: "Pensando...",
|
|
13
|
+
statusAnalyzing: "Analisando a tela...",
|
|
14
|
+
statusWaitingApp: "Aguardando o app responder...",
|
|
15
|
+
statusAdjusting: "Ajustando o plano...",
|
|
16
|
+
poweredBy: "Powered by Appilots",
|
|
17
|
+
approve: "Aprovar",
|
|
18
|
+
reject: "Rejeitar",
|
|
19
|
+
cancelledByUser: "Cancelado pelo usu\xE1rio",
|
|
20
|
+
confirmDestructiveTitle: "A\xE7\xE3o destrutiva",
|
|
21
|
+
confirmTitle: "Confirma\xE7\xE3o necess\xE1ria",
|
|
22
|
+
confirmMessage: 'Tem certeza que deseja "{action}"?',
|
|
23
|
+
confirmDestructiveApprove: "Sim, executar",
|
|
24
|
+
confirmApprove: "Confirmar",
|
|
25
|
+
confirmCancel: "Cancelar",
|
|
26
|
+
confirmThisAction: "esta a\xE7\xE3o",
|
|
27
|
+
unknownAction: "A\xE7\xE3o desconhecida",
|
|
28
|
+
somethingWentWrong: "Algo deu errado",
|
|
29
|
+
openChatA11y: "Abrir chat do assistente",
|
|
30
|
+
closeChatA11y: "Fechar chat do assistente",
|
|
31
|
+
stopGeneratingA11y: "Parar gera\xE7\xE3o",
|
|
32
|
+
talkToHumanA11y: "Falar com um atendente humano",
|
|
33
|
+
humanAgentLabel: "Atendente",
|
|
34
|
+
escalationRequested: "Certo! Chamei um atendente humano. Voc\xEA pode continuar usando o app normalmente enquanto isso.",
|
|
35
|
+
escalationConnected: "Um atendente entrou na conversa.",
|
|
36
|
+
escalationResolved: "O atendimento humano foi encerrado. Voltei a te ajudar por aqui.",
|
|
37
|
+
escalationOffer: "Se preferir, posso chamar uma pessoa para ajudar.",
|
|
38
|
+
escalationOfferChip: "Falar com um atendente",
|
|
39
|
+
escalationPendingBanner: "Aguardando um atendente...",
|
|
40
|
+
escalationActiveBanner: "Voc\xEA est\xE1 falando com um atendente",
|
|
41
|
+
pillWorking: "Trabalhando...",
|
|
42
|
+
pillExpandA11y: "Abrir a conversa do assistente",
|
|
43
|
+
pillStopA11y: "Parar o assistente",
|
|
44
|
+
missionPause: "Pausar",
|
|
45
|
+
missionResume: "Retomar tarefa",
|
|
46
|
+
missionCancel: "Cancelar tarefa",
|
|
47
|
+
missionPaused: "Pausada",
|
|
48
|
+
pillDone: "Pronto \u2014 toque para ver",
|
|
49
|
+
micStartA11y: "Segurar para falar",
|
|
50
|
+
micStopA11y: "Soltar para enviar",
|
|
51
|
+
micListening: "Ouvindo..."
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// src/i18n/bundles/en.ts
|
|
55
|
+
var en = {
|
|
56
|
+
chatTitle: "Appilots Assistant",
|
|
57
|
+
clearButton: "Clear",
|
|
58
|
+
inputPlaceholder: "Type a message...",
|
|
59
|
+
emptyTitle: "How can I help?",
|
|
60
|
+
emptySubtitle: "I navigate, fill forms, and run actions in this app.",
|
|
61
|
+
thinking: "Thinking...",
|
|
62
|
+
statusAnalyzing: "Reading the screen...",
|
|
63
|
+
statusWaitingApp: "Waiting for the app...",
|
|
64
|
+
statusAdjusting: "Adjusting the plan...",
|
|
65
|
+
poweredBy: "Powered by Appilots",
|
|
66
|
+
approve: "Approve",
|
|
67
|
+
reject: "Reject",
|
|
68
|
+
cancelledByUser: "Cancelled by user",
|
|
69
|
+
confirmDestructiveTitle: "Destructive action",
|
|
70
|
+
confirmTitle: "Confirmation needed",
|
|
71
|
+
confirmMessage: 'Are you sure you want to "{action}"?',
|
|
72
|
+
confirmDestructiveApprove: "Yes, do it",
|
|
73
|
+
confirmApprove: "Confirm",
|
|
74
|
+
confirmCancel: "Cancel",
|
|
75
|
+
confirmThisAction: "this action",
|
|
76
|
+
unknownAction: "Unknown action",
|
|
77
|
+
somethingWentWrong: "Something went wrong",
|
|
78
|
+
openChatA11y: "Open assistant chat",
|
|
79
|
+
closeChatA11y: "Close assistant chat",
|
|
80
|
+
stopGeneratingA11y: "Stop generating",
|
|
81
|
+
talkToHumanA11y: "Talk to a human agent",
|
|
82
|
+
humanAgentLabel: "Agent",
|
|
83
|
+
escalationRequested: "Done! I've called a human agent. You can keep using the app normally in the meantime.",
|
|
84
|
+
escalationConnected: "A human agent joined the conversation.",
|
|
85
|
+
escalationResolved: "The human handoff has ended. I'm back to help you here.",
|
|
86
|
+
escalationOffer: "If you prefer, I can bring in a person to help.",
|
|
87
|
+
escalationOfferChip: "Talk to a human",
|
|
88
|
+
escalationPendingBanner: "Waiting for a human agent...",
|
|
89
|
+
escalationActiveBanner: "You're talking to a human agent",
|
|
90
|
+
pillWorking: "Working...",
|
|
91
|
+
pillExpandA11y: "Open the assistant conversation",
|
|
92
|
+
pillStopA11y: "Stop the assistant",
|
|
93
|
+
missionPause: "Pause",
|
|
94
|
+
missionResume: "Resume task",
|
|
95
|
+
missionCancel: "Cancel task",
|
|
96
|
+
missionPaused: "Paused",
|
|
97
|
+
pillDone: "Done \u2014 tap to read",
|
|
98
|
+
micStartA11y: "Hold to talk",
|
|
99
|
+
micStopA11y: "Release to send",
|
|
100
|
+
micListening: "Listening..."
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
// src/i18n/bundles/es.ts
|
|
104
|
+
var es = {
|
|
105
|
+
chatTitle: "Asistente Appilots",
|
|
106
|
+
clearButton: "Limpiar",
|
|
107
|
+
inputPlaceholder: "Escribe un mensaje...",
|
|
108
|
+
emptyTitle: "\xBFEn qu\xE9 puedo ayudarte?",
|
|
109
|
+
emptySubtitle: "Navego, relleno formularios y ejecuto acciones en esta app.",
|
|
110
|
+
thinking: "Pensando...",
|
|
111
|
+
statusAnalyzing: "Analizando la pantalla...",
|
|
112
|
+
statusWaitingApp: "Esperando a la app...",
|
|
113
|
+
statusAdjusting: "Ajustando el plan...",
|
|
114
|
+
poweredBy: "Hecho con Appilots",
|
|
115
|
+
approve: "Aprobar",
|
|
116
|
+
reject: "Rechazar",
|
|
117
|
+
cancelledByUser: "Cancelado por el usuario",
|
|
118
|
+
confirmDestructiveTitle: "Acci\xF3n destructiva",
|
|
119
|
+
confirmTitle: "Confirmaci\xF3n necesaria",
|
|
120
|
+
confirmMessage: '\xBFSeguro que quieres "{action}"?',
|
|
121
|
+
confirmDestructiveApprove: "S\xED, ejecutar",
|
|
122
|
+
confirmApprove: "Confirmar",
|
|
123
|
+
confirmCancel: "Cancelar",
|
|
124
|
+
confirmThisAction: "esta acci\xF3n",
|
|
125
|
+
unknownAction: "Acci\xF3n desconocida",
|
|
126
|
+
somethingWentWrong: "Algo sali\xF3 mal",
|
|
127
|
+
openChatA11y: "Abrir chat del asistente",
|
|
128
|
+
closeChatA11y: "Cerrar chat del asistente",
|
|
129
|
+
stopGeneratingA11y: "Detener generaci\xF3n",
|
|
130
|
+
talkToHumanA11y: "Hablar con un agente humano",
|
|
131
|
+
humanAgentLabel: "Agente",
|
|
132
|
+
escalationRequested: "\xA1Listo! Llam\xE9 a un agente humano. Mientras tanto puedes seguir usando la app con normalidad.",
|
|
133
|
+
escalationConnected: "Un agente humano se uni\xF3 a la conversaci\xF3n.",
|
|
134
|
+
escalationResolved: "La atenci\xF3n humana termin\xF3. Vuelvo a ayudarte por aqu\xED.",
|
|
135
|
+
escalationOffer: "Si prefieres, puedo llamar a una persona para ayudarte.",
|
|
136
|
+
escalationOfferChip: "Hablar con un agente",
|
|
137
|
+
escalationPendingBanner: "Esperando a un agente...",
|
|
138
|
+
escalationActiveBanner: "Est\xE1s hablando con un agente humano",
|
|
139
|
+
pillWorking: "Trabajando...",
|
|
140
|
+
pillExpandA11y: "Abrir la conversaci\xF3n del asistente",
|
|
141
|
+
pillStopA11y: "Detener el asistente",
|
|
142
|
+
missionPause: "Pausar",
|
|
143
|
+
missionResume: "Reanudar tarea",
|
|
144
|
+
missionCancel: "Cancelar tarea",
|
|
145
|
+
missionPaused: "Pausada",
|
|
146
|
+
pillDone: "Listo \u2014 toca para ver",
|
|
147
|
+
micStartA11y: "Mantener para hablar",
|
|
148
|
+
micStopA11y: "Soltar para enviar",
|
|
149
|
+
micListening: "Escuchando..."
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// src/i18n/bundles/fr.ts
|
|
153
|
+
var fr = {
|
|
154
|
+
chatTitle: "Assistant Appilots",
|
|
155
|
+
clearButton: "Effacer",
|
|
156
|
+
inputPlaceholder: "\xC9crivez un message\u2026",
|
|
157
|
+
emptyTitle: "Comment puis-je vous aider ?",
|
|
158
|
+
emptySubtitle: "Je navigue, remplis les formulaires et effectue des actions dans cette application.",
|
|
159
|
+
thinking: "R\xE9flexion en cours\u2026",
|
|
160
|
+
statusAnalyzing: "Lecture de l\u2019\xE9cran\u2026",
|
|
161
|
+
statusWaitingApp: "En attente de l\u2019application\u2026",
|
|
162
|
+
statusAdjusting: "Ajustement du plan\u2026",
|
|
163
|
+
poweredBy: "Propuls\xE9 par Appilots",
|
|
164
|
+
approve: "Approuver",
|
|
165
|
+
reject: "Refuser",
|
|
166
|
+
cancelledByUser: "Annul\xE9 par l\u2019utilisateur",
|
|
167
|
+
confirmDestructiveTitle: "Action destructive",
|
|
168
|
+
confirmTitle: "Confirmation requise",
|
|
169
|
+
confirmMessage: "Voulez-vous vraiment \xAB {action} \xBB ?",
|
|
170
|
+
confirmDestructiveApprove: "Oui, ex\xE9cuter",
|
|
171
|
+
confirmApprove: "Confirmer",
|
|
172
|
+
confirmCancel: "Annuler",
|
|
173
|
+
confirmThisAction: "cette action",
|
|
174
|
+
unknownAction: "Action inconnue",
|
|
175
|
+
somethingWentWrong: "Une erreur est survenue",
|
|
176
|
+
openChatA11y: "Ouvrir la conversation avec l\u2019assistant",
|
|
177
|
+
closeChatA11y: "Fermer la conversation",
|
|
178
|
+
stopGeneratingA11y: "Arr\xEAter la g\xE9n\xE9ration",
|
|
179
|
+
talkToHumanA11y: "Parler \xE0 un conseiller",
|
|
180
|
+
humanAgentLabel: "Conseiller",
|
|
181
|
+
escalationRequested: "J\u2019ai contact\xE9 un conseiller. Vous pouvez continuer \xE0 utiliser l\u2019application en attendant.",
|
|
182
|
+
escalationConnected: "Un conseiller a rejoint la conversation.",
|
|
183
|
+
escalationResolved: "L\u2019intervention du conseiller est termin\xE9e. Je peux \xE0 nouveau vous aider.",
|
|
184
|
+
escalationOffer: "Si vous pr\xE9f\xE9rez, je peux faire appel \xE0 un conseiller.",
|
|
185
|
+
escalationOfferChip: "Parler \xE0 un conseiller",
|
|
186
|
+
escalationPendingBanner: "En attente d\u2019un conseiller\u2026",
|
|
187
|
+
escalationActiveBanner: "Vous discutez avec un conseiller",
|
|
188
|
+
pillWorking: "En cours\u2026",
|
|
189
|
+
pillExpandA11y: "Ouvrir la conversation avec l\u2019assistant",
|
|
190
|
+
pillStopA11y: "Arr\xEAter l\u2019assistant",
|
|
191
|
+
missionPause: "Mettre en pause",
|
|
192
|
+
missionResume: "Reprendre la t\xE2che",
|
|
193
|
+
missionCancel: "Annuler la t\xE2che",
|
|
194
|
+
missionPaused: "En pause",
|
|
195
|
+
pillDone: "Termin\xE9 \u2014 touchez pour lire",
|
|
196
|
+
micStartA11y: "Maintenez pour parler",
|
|
197
|
+
micStopA11y: "Rel\xE2chez pour envoyer",
|
|
198
|
+
micListening: "\xC9coute en cours\u2026"
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
// src/i18n/I18nProvider.tsx
|
|
202
|
+
var BUNDLES = {
|
|
203
|
+
"pt-BR": ptBR,
|
|
204
|
+
en,
|
|
205
|
+
es,
|
|
206
|
+
fr
|
|
207
|
+
};
|
|
208
|
+
var DEFAULT_LOCALE = "en";
|
|
209
|
+
function detectDeviceLocale() {
|
|
210
|
+
try {
|
|
211
|
+
let raw;
|
|
212
|
+
if (Platform.OS === "ios") {
|
|
213
|
+
const manager = NativeModules?.SettingsManager;
|
|
214
|
+
const settings = manager?.getConstants?.()?.settings ?? manager?.settings;
|
|
215
|
+
const languages = Array.isArray(settings?.AppleLanguages) ? settings.AppleLanguages : [];
|
|
216
|
+
for (const language of languages) {
|
|
217
|
+
const locale = normalizeAppilotsLocale(language);
|
|
218
|
+
if (locale) return locale;
|
|
219
|
+
}
|
|
220
|
+
raw = settings?.AppleLocale;
|
|
221
|
+
} else if (Platform.OS === "android") {
|
|
222
|
+
const manager = NativeModules?.I18nManager;
|
|
223
|
+
raw = manager?.getConstants?.()?.localeIdentifier ?? manager?.localeIdentifier;
|
|
224
|
+
}
|
|
225
|
+
if (!raw || typeof raw !== "string") return null;
|
|
226
|
+
return normalizeAppilotsLocale(raw) ?? null;
|
|
227
|
+
} catch {
|
|
228
|
+
return null;
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
function resolveLocale(prop) {
|
|
232
|
+
if (prop && BUNDLES[prop]) return prop;
|
|
233
|
+
const detected = detectDeviceLocale();
|
|
234
|
+
if (detected && BUNDLES[detected]) return detected;
|
|
235
|
+
return DEFAULT_LOCALE;
|
|
236
|
+
}
|
|
237
|
+
var I18nContext = createContext(null);
|
|
238
|
+
function AppilotsI18nProvider({ locale, overrides, children }) {
|
|
239
|
+
const value = useMemo(() => {
|
|
240
|
+
const resolved = resolveLocale(locale ?? null);
|
|
241
|
+
const merged = overrides ? { ...BUNDLES[resolved], ...overrides } : BUNDLES[resolved];
|
|
242
|
+
return {
|
|
243
|
+
locale: resolved,
|
|
244
|
+
strings: merged,
|
|
245
|
+
t: (key) => merged[key]
|
|
246
|
+
};
|
|
247
|
+
}, [locale, overrides]);
|
|
248
|
+
return /* @__PURE__ */ React.createElement(I18nContext.Provider, { value }, children);
|
|
249
|
+
}
|
|
250
|
+
function useAppilotsI18n() {
|
|
251
|
+
const ctx = useContext(I18nContext);
|
|
252
|
+
if (ctx) return ctx;
|
|
253
|
+
return {
|
|
254
|
+
locale: DEFAULT_LOCALE,
|
|
255
|
+
strings: BUNDLES[DEFAULT_LOCALE],
|
|
256
|
+
t: (key) => BUNDLES[DEFAULT_LOCALE][key]
|
|
257
|
+
};
|
|
258
|
+
}
|
|
4
259
|
|
|
5
260
|
// src/introspection/captureSnapshot.ts
|
|
6
261
|
function normalizeSliderKey(value) {
|
|
@@ -74,20 +329,35 @@ function mergeRegisteredLists(snapshot) {
|
|
|
74
329
|
}
|
|
75
330
|
for (const entry of registered) {
|
|
76
331
|
if (entry.kind === "scroll") continue;
|
|
332
|
+
const existing = byId.get(entry.id);
|
|
333
|
+
if (!existing && !(entry.screen && entry.screen === snapshot.route)) continue;
|
|
77
334
|
const metrics = entry.getScrollMetrics?.();
|
|
78
335
|
const scrollFacts = metrics ? {
|
|
79
336
|
scrollOffsetY: Math.max(0, Math.round(metrics.offset)),
|
|
80
337
|
canScrollUp: metrics.offset > 8,
|
|
81
338
|
canScrollDown: metrics.offset + metrics.visibleLength < metrics.contentLength - 8
|
|
82
339
|
} : void 0;
|
|
83
|
-
const existing = byId.get(entry.id);
|
|
84
340
|
if (existing) {
|
|
85
341
|
existing.itemCount = existing.itemCount ?? entry.itemCount;
|
|
342
|
+
existing.totalItemCount = existing.totalItemCount ?? entry.totalItemCount;
|
|
86
343
|
existing.refreshing = existing.refreshing ?? entry.refreshing;
|
|
87
344
|
existing.empty = existing.empty ?? entry.empty;
|
|
88
345
|
existing.label = existing.label ?? entry.label;
|
|
89
346
|
existing.source = existing.source ?? "auto-tracked";
|
|
90
347
|
existing.visibleItemCount = existing.items.length;
|
|
348
|
+
const viewport = entry.measureViewport?.();
|
|
349
|
+
if (viewport && viewport.width > 0 && viewport.height > 0) {
|
|
350
|
+
let measured = 0;
|
|
351
|
+
let inViewport = 0;
|
|
352
|
+
for (const item of existing.items) {
|
|
353
|
+
const rect = rectForNode(item);
|
|
354
|
+
if (!rect) continue;
|
|
355
|
+
measured++;
|
|
356
|
+
item.onScreen = rect.width > 0 && rect.height > 0 && rect.x < viewport.x + viewport.width && rect.x + rect.width > viewport.x && rect.y < viewport.y + viewport.height && rect.y + rect.height > viewport.y;
|
|
357
|
+
if (item.onScreen) inViewport++;
|
|
358
|
+
}
|
|
359
|
+
if (measured === existing.items.length) existing.viewportItemCount = inViewport;
|
|
360
|
+
}
|
|
91
361
|
if (entry.dataPreview && entry.dataPreview.length > 0) {
|
|
92
362
|
existing.dataPreview = entry.dataPreview;
|
|
93
363
|
}
|
|
@@ -104,6 +374,7 @@ function mergeRegisteredLists(snapshot) {
|
|
|
104
374
|
containerType: entry.component,
|
|
105
375
|
source: "registry",
|
|
106
376
|
itemCount: entry.itemCount,
|
|
377
|
+
totalItemCount: entry.totalItemCount,
|
|
107
378
|
visibleItemCount: 0,
|
|
108
379
|
refreshing: entry.refreshing,
|
|
109
380
|
empty: entry.empty,
|
|
@@ -169,6 +440,43 @@ function recordBlindWalk() {
|
|
|
169
440
|
if (totalDetectorHits(walk.detectors) > 0) return;
|
|
170
441
|
recordIntrospectionFailure("walk-recognized-nothing", null);
|
|
171
442
|
}
|
|
443
|
+
function mergeRegisteredFieldValues(snapshot) {
|
|
444
|
+
const present = new Set(snapshot.inputs.map((i) => normalizeFieldKey(i.id)));
|
|
445
|
+
const onScreenIds = [
|
|
446
|
+
...snapshot.buttons.map((b) => (b.id ?? "").toLowerCase()),
|
|
447
|
+
...snapshot.toggles.map((t) => (t.id ?? "").toLowerCase())
|
|
448
|
+
].filter(Boolean);
|
|
449
|
+
const onScreenLabels = new Set(
|
|
450
|
+
[...snapshot.buttons.map((b) => b.label), ...snapshot.texts].filter((l) => !!l).map((l) => l.toLowerCase())
|
|
451
|
+
);
|
|
452
|
+
for (const { id, kind, screen, label } of componentRegistry.snapshot()) {
|
|
453
|
+
if (kind !== "field") continue;
|
|
454
|
+
const key = normalizeFieldKey(id);
|
|
455
|
+
if (!key || present.has(key)) continue;
|
|
456
|
+
const entry = componentRegistry.getField(id);
|
|
457
|
+
if (!entry) continue;
|
|
458
|
+
const fieldType = entry.fieldType ?? "";
|
|
459
|
+
if (fieldType === "text" || fieldType === "") continue;
|
|
460
|
+
const declaresRoute = typeof screen === "string" && screen === snapshot.route;
|
|
461
|
+
if (screen && !declaresRoute && !getActiveRouteNames().includes(screen)) continue;
|
|
462
|
+
const idEvidence = onScreenIds.some((candidate) => candidate.split(/[^a-z0-9]+/).includes(key));
|
|
463
|
+
const labelEvidence = !!entry.label && onScreenLabels.has(entry.label.toLowerCase());
|
|
464
|
+
if (!declaresRoute && !idEvidence && !labelEvidence) continue;
|
|
465
|
+
let value;
|
|
466
|
+
try {
|
|
467
|
+
value = String(entry.getValue() ?? "");
|
|
468
|
+
} catch {
|
|
469
|
+
continue;
|
|
470
|
+
}
|
|
471
|
+
snapshot.inputs.push({
|
|
472
|
+
id,
|
|
473
|
+
label: entry.label ?? label,
|
|
474
|
+
value,
|
|
475
|
+
type: fieldType
|
|
476
|
+
});
|
|
477
|
+
present.add(key);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
172
480
|
function captureSnapshot() {
|
|
173
481
|
const root = getFiberRoot();
|
|
174
482
|
if (!root) {
|
|
@@ -201,6 +509,7 @@ function captureSnapshot() {
|
|
|
201
509
|
mergeNativeDialog(snapshot);
|
|
202
510
|
reconcileSliders(snapshot);
|
|
203
511
|
canonicalizeRegisteredFields(snapshot);
|
|
512
|
+
mergeRegisteredFieldValues(snapshot);
|
|
204
513
|
clampSnapshotToWireLimits(snapshot);
|
|
205
514
|
const entries = deriveInteractionElements(snapshot, { rectOf: rectForNode });
|
|
206
515
|
const rectById = new Map(
|
|
@@ -303,6 +612,11 @@ function flipSwitchFiber(fiber, next) {
|
|
|
303
612
|
return { ok: true, via: handler.via };
|
|
304
613
|
}
|
|
305
614
|
|
|
615
|
+
// src/introspection/positionalIdentity.ts
|
|
616
|
+
function isPositionalControlId(value) {
|
|
617
|
+
return /^(?:list-\d+-item-\d+-)?(?:btn|input|slider|toggle)-\d+$/i.test(value.trim());
|
|
618
|
+
}
|
|
619
|
+
|
|
306
620
|
// src/introspection/findInteractive.ts
|
|
307
621
|
function getTypeName2(type) {
|
|
308
622
|
if (!type) return null;
|
|
@@ -390,6 +704,11 @@ function shouldSkipSubtree2(fiber) {
|
|
|
390
704
|
var normalize = foldForCompare;
|
|
391
705
|
function scoreMatchDetail(fiber, queryNorm, queryRaw) {
|
|
392
706
|
const props = fiber.memoizedProps ?? {};
|
|
707
|
+
if (isSourceControlId(queryRaw)) {
|
|
708
|
+
const matches = sourceControlId(fiber) === queryRaw;
|
|
709
|
+
const disabled = props.disabled === true || props.accessibilityState?.disabled === true;
|
|
710
|
+
return { score: matches ? disabled ? 60 : 120 : 0, named: matches, disabled };
|
|
711
|
+
}
|
|
393
712
|
const candidates = [];
|
|
394
713
|
if (typeof props.testID === "string") candidates.push({ val: props.testID, weight: 100 });
|
|
395
714
|
if (typeof props.accessibilityLabel === "string")
|
|
@@ -408,11 +727,11 @@ function scoreMatchDetail(fiber, queryNorm, queryRaw) {
|
|
|
408
727
|
if (!vNorm) continue;
|
|
409
728
|
if (val === queryRaw) best = Math.max(best, weight + 20);
|
|
410
729
|
else if (vNorm === queryNorm) best = Math.max(best, weight + 10);
|
|
411
|
-
else if (queryNorm && (vNorm.includes(queryNorm) || queryNorm.includes(vNorm)))
|
|
730
|
+
else if (!isPositionalControlId(queryRaw) && queryNorm && (vNorm.includes(queryNorm) || queryNorm.includes(vNorm)))
|
|
412
731
|
best = Math.max(best, weight - 20);
|
|
413
732
|
}
|
|
414
733
|
const named = best >= IDENTITY_MATCH_SCORE;
|
|
415
|
-
const disabledProp = props.disabled === true;
|
|
734
|
+
const disabledProp = props.disabled === true || props.editable === false || props.readOnly === true;
|
|
416
735
|
const disabledA11y = typeof props.accessibilityState === "object" && props.accessibilityState !== null && props.accessibilityState.disabled === true;
|
|
417
736
|
if (best > 0 && disabledProp) {
|
|
418
737
|
best = Math.max(1, best - 60);
|
|
@@ -422,12 +741,18 @@ function scoreMatchDetail(fiber, queryNorm, queryRaw) {
|
|
|
422
741
|
}
|
|
423
742
|
return { score: best, named, disabled: disabledProp || disabledA11y };
|
|
424
743
|
}
|
|
744
|
+
var inputIdentities = /* @__PURE__ */ new WeakMap();
|
|
425
745
|
function wrapTextInput(fiber) {
|
|
746
|
+
const identity = inputIdentities.get(fiber) ?? (fiber.alternate && inputIdentities.get(fiber.alternate)) ?? {};
|
|
747
|
+
inputIdentities.set(fiber, identity);
|
|
748
|
+
if (fiber.alternate) inputIdentities.set(fiber.alternate, identity);
|
|
426
749
|
const getProps = () => fiber.memoizedProps ?? {};
|
|
427
750
|
return {
|
|
428
751
|
kind: "field",
|
|
752
|
+
controlIdentity: identity,
|
|
429
753
|
label: getProps().accessibilityLabel ?? getProps().label ?? getProps().placeholder ?? getProps().testID,
|
|
430
754
|
getValue: () => getProps().value,
|
|
755
|
+
finishEditing: () => endTextInputEditing(getProps()),
|
|
431
756
|
setValue: (value) => {
|
|
432
757
|
const { onChangeText, onChange } = getProps();
|
|
433
758
|
if (typeof onChangeText === "function") {
|
|
@@ -508,10 +833,10 @@ function describeCandidate(fiber, kind) {
|
|
|
508
833
|
const props = fiber?.memoizedProps ?? {};
|
|
509
834
|
return props.testID ?? props.accessibilityLabel ?? props.placeholder ?? (kind === "target" ? findChildText(fiber) : void 0);
|
|
510
835
|
}
|
|
511
|
-
function isDescendantOf(fiber, ancestor) {
|
|
836
|
+
function isDescendantOf(fiber, ancestor, parents) {
|
|
512
837
|
if (!fiber || !ancestor) return false;
|
|
513
|
-
let node = fiber
|
|
514
|
-
for (let depth = 0; node && depth < 200; depth++, node = node
|
|
838
|
+
let node = parents.get(fiber);
|
|
839
|
+
for (let depth = 0; node && depth < 200; depth++, node = parents.get(node)) {
|
|
515
840
|
if (node === ancestor) return true;
|
|
516
841
|
}
|
|
517
842
|
return false;
|
|
@@ -532,13 +857,15 @@ function findInteractiveByIdentifier(query, options = {}) {
|
|
|
532
857
|
let bestDisabled = false;
|
|
533
858
|
let tied = [];
|
|
534
859
|
const visited = /* @__PURE__ */ new WeakSet();
|
|
860
|
+
const parents = /* @__PURE__ */ new WeakMap();
|
|
535
861
|
const stack = [];
|
|
536
|
-
if (root.child) stack.push(root.child);
|
|
862
|
+
if (root.child) stack.push({ fiber: root.child, parent: root });
|
|
537
863
|
while (stack.length > 0) {
|
|
538
|
-
const fiber = stack.pop();
|
|
864
|
+
const { fiber, parent } = stack.pop();
|
|
539
865
|
if (!fiber || visited.has(fiber)) continue;
|
|
540
866
|
visited.add(fiber);
|
|
541
|
-
|
|
867
|
+
parents.set(fiber, parent);
|
|
868
|
+
if (fiber.sibling) stack.push({ fiber: fiber.sibling, parent });
|
|
542
869
|
if (shouldSkipSubtree2(fiber)) continue;
|
|
543
870
|
const props = fiber.memoizedProps ?? {};
|
|
544
871
|
let kind = null;
|
|
@@ -552,7 +879,7 @@ function findInteractiveByIdentifier(query, options = {}) {
|
|
|
552
879
|
if (score > bestScore) tied = [{ fiber, kind }];
|
|
553
880
|
else if (score === bestScore) tied.push({ fiber, kind });
|
|
554
881
|
}
|
|
555
|
-
const beatsBest = score > bestScore || score > 0 && score === bestScore && isDescendantOf(fiber, bestFiber);
|
|
882
|
+
const beatsBest = score > bestScore || score > 0 && score === bestScore && isDescendantOf(fiber, bestFiber, parents);
|
|
556
883
|
if (beatsBest) {
|
|
557
884
|
bestScore = score;
|
|
558
885
|
bestFiber = fiber;
|
|
@@ -561,11 +888,15 @@ function findInteractiveByIdentifier(query, options = {}) {
|
|
|
561
888
|
bestId = describeCandidate(fiber, kind);
|
|
562
889
|
if (kind === "field") bestHandle = wrapTextInput(fiber);
|
|
563
890
|
else if (kind === "toggle") bestHandle = wrapSwitch(fiber);
|
|
564
|
-
else
|
|
891
|
+
else if (isSourceControlId(queryRaw)) {
|
|
892
|
+
const bound = sourceControlTarget(fiber);
|
|
893
|
+
if (!bound || bound.disabled) bestHandle = null;
|
|
894
|
+
else bestHandle = wrapTouchable(bound.fiber, findChildText(fiber));
|
|
895
|
+
} else bestHandle = wrapTouchable(fiber, findChildText(fiber));
|
|
565
896
|
}
|
|
566
897
|
}
|
|
567
898
|
if (kind === "toggle") continue;
|
|
568
|
-
if (fiber.child) stack.push(fiber.child);
|
|
899
|
+
if (fiber.child) stack.push({ fiber: fiber.child, parent: fiber });
|
|
569
900
|
}
|
|
570
901
|
if (bestHandle && bestScore < (options.minScore ?? 0)) {
|
|
571
902
|
appilotsDebugLog(
|
|
@@ -576,12 +907,14 @@ function findInteractiveByIdentifier(query, options = {}) {
|
|
|
576
907
|
if (bestHandle) {
|
|
577
908
|
const independent = tied.filter(
|
|
578
909
|
(candidate) => !tied.some(
|
|
579
|
-
(other) => other.fiber !== candidate.fiber && isDescendantOf(other.fiber, candidate.fiber)
|
|
910
|
+
(other) => other.fiber !== candidate.fiber && isDescendantOf(other.fiber, candidate.fiber, parents)
|
|
580
911
|
)
|
|
581
912
|
);
|
|
582
913
|
bestHandle.matchCount = independent.length;
|
|
583
914
|
bestHandle.named = bestNamed;
|
|
584
915
|
bestHandle.disabled = bestDisabled;
|
|
916
|
+
const measured = bestFiber;
|
|
917
|
+
bestHandle.measure = () => rectOfFiber(measured);
|
|
585
918
|
const candidates = independent.map(({ fiber, kind }) => describeCandidate(fiber, kind)).filter((id) => typeof id === "string" && id.length > 0).slice(0, 8);
|
|
586
919
|
if (candidates.length > 0) bestHandle.matchCandidates = candidates;
|
|
587
920
|
appilotsDebugLog(
|
|
@@ -593,6 +926,119 @@ function findInteractiveByIdentifier(query, options = {}) {
|
|
|
593
926
|
return bestHandle;
|
|
594
927
|
}
|
|
595
928
|
|
|
929
|
+
// src/introspection/spotlightTarget.ts
|
|
930
|
+
var listeners = /* @__PURE__ */ new Set();
|
|
931
|
+
var current = null;
|
|
932
|
+
var seq = 0;
|
|
933
|
+
var expiry = null;
|
|
934
|
+
var SPOTLIGHT_TTL_MS = 1200;
|
|
935
|
+
var mode = "off";
|
|
936
|
+
function setSpotlightMode(next) {
|
|
937
|
+
mode = next;
|
|
938
|
+
}
|
|
939
|
+
function getSpotlightMode() {
|
|
940
|
+
return mode;
|
|
941
|
+
}
|
|
942
|
+
function emit() {
|
|
943
|
+
for (const listener of listeners) listener(current);
|
|
944
|
+
}
|
|
945
|
+
function setSpotlightTarget(rect, label) {
|
|
946
|
+
if (expiry) {
|
|
947
|
+
clearTimeout(expiry);
|
|
948
|
+
expiry = null;
|
|
949
|
+
}
|
|
950
|
+
if (!rect) {
|
|
951
|
+
appilotsDebugLog(
|
|
952
|
+
`setSpotlightTarget: cleared${label ? ` (was about to mark "${label}")` : ""}`
|
|
953
|
+
);
|
|
954
|
+
if (current === null) return;
|
|
955
|
+
current = null;
|
|
956
|
+
emit();
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
current = { rect, label, seq: ++seq };
|
|
960
|
+
appilotsDebugLog(
|
|
961
|
+
`setSpotlightTarget: seq=${current.seq} label="${label ?? ""}" rect=${Math.round(rect.x)},${Math.round(rect.y)} ${Math.round(rect.width)}x${Math.round(rect.height)}`
|
|
962
|
+
);
|
|
963
|
+
emit();
|
|
964
|
+
expiry = setTimeout(() => {
|
|
965
|
+
expiry = null;
|
|
966
|
+
current = null;
|
|
967
|
+
emit();
|
|
968
|
+
}, SPOTLIGHT_TTL_MS);
|
|
969
|
+
}
|
|
970
|
+
function getSpotlightTarget() {
|
|
971
|
+
return current;
|
|
972
|
+
}
|
|
973
|
+
function subscribeSpotlight(listener) {
|
|
974
|
+
listeners.add(listener);
|
|
975
|
+
return () => {
|
|
976
|
+
listeners.delete(listener);
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
// src/introspection/scrollIntoView.ts
|
|
981
|
+
var REVEAL_MARGIN = 24;
|
|
982
|
+
function offsetToReveal({ rect, viewport, metrics }) {
|
|
983
|
+
const maxOffset = Math.max(0, metrics.contentLength - metrics.visibleLength);
|
|
984
|
+
const viewTop = viewport.top + REVEAL_MARGIN;
|
|
985
|
+
const viewBottom = viewport.top + viewport.height - REVEAL_MARGIN;
|
|
986
|
+
const tallerThanViewport = rect.height > viewport.height - REVEAL_MARGIN * 2;
|
|
987
|
+
let delta;
|
|
988
|
+
if (tallerThanViewport) {
|
|
989
|
+
if (rect.y >= viewTop && rect.y <= viewBottom) return null;
|
|
990
|
+
delta = rect.y - viewTop;
|
|
991
|
+
} else if (rect.y < viewTop) {
|
|
992
|
+
delta = rect.y - viewTop;
|
|
993
|
+
} else if (rect.y + rect.height > viewBottom) {
|
|
994
|
+
delta = rect.y + rect.height - viewBottom;
|
|
995
|
+
} else {
|
|
996
|
+
return null;
|
|
997
|
+
}
|
|
998
|
+
const target = clamp(metrics.offset + delta, 0, maxOffset);
|
|
999
|
+
if (Math.abs(target - metrics.offset) < 1) return null;
|
|
1000
|
+
return target;
|
|
1001
|
+
}
|
|
1002
|
+
function clamp(value, min, max) {
|
|
1003
|
+
return Math.min(max, Math.max(min, value));
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
// src/introspection/revealTarget.ts
|
|
1007
|
+
function revealTarget(rect) {
|
|
1008
|
+
const host = getGeometryHost();
|
|
1009
|
+
if (!host) return { rect, scrolled: false };
|
|
1010
|
+
if (isRectOnScreen(rect, host.window)) return { rect, scrolled: false };
|
|
1011
|
+
const scrollable = resolveScrollable();
|
|
1012
|
+
if (!scrollable) return { rect, scrolled: false };
|
|
1013
|
+
const metrics = scrollable.getScrollMetrics?.();
|
|
1014
|
+
if (!metrics) return { rect, scrolled: false };
|
|
1015
|
+
const box = scrollable.measureViewport?.();
|
|
1016
|
+
const viewport = box ? { top: box.y, height: box.height } : { top: 0, height: host.window.height };
|
|
1017
|
+
const target = offsetToReveal({ rect, viewport, metrics });
|
|
1018
|
+
if (target === null) return { rect, scrolled: false };
|
|
1019
|
+
const moved = scrollable.scrollToOffset?.(target) ?? false;
|
|
1020
|
+
if (!moved) {
|
|
1021
|
+
appilotsDebugLog(`revealTarget: "${scrollable.id}" refused scrollToOffset(${target})`);
|
|
1022
|
+
return { rect, scrolled: false };
|
|
1023
|
+
}
|
|
1024
|
+
const delta = target - metrics.offset;
|
|
1025
|
+
appilotsDebugLog(
|
|
1026
|
+
`revealTarget: scrolled "${scrollable.id}" by ${Math.round(delta)}px (${Math.round(metrics.offset)} \u2192 ${Math.round(target)})`
|
|
1027
|
+
);
|
|
1028
|
+
return { rect: { ...rect, y: rect.y - delta }, scrolled: true };
|
|
1029
|
+
}
|
|
1030
|
+
function resolveScrollable() {
|
|
1031
|
+
const screen = getCurrentScreen();
|
|
1032
|
+
const candidates = listRegistry.snapshot().filter((entry) => entry.scrollToOffset && entry.getScrollMetrics).filter((entry) => !screen || !entry.screen || entry.screen === screen);
|
|
1033
|
+
if (candidates.length === 1) return candidates[0];
|
|
1034
|
+
if (candidates.length > 1) {
|
|
1035
|
+
appilotsDebugLog(
|
|
1036
|
+
`revealTarget: ${candidates.length} scrollables on "${screen ?? "?"}" \u2014 cannot tell which holds the target, so not scrolling.`
|
|
1037
|
+
);
|
|
1038
|
+
}
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
|
|
596
1042
|
// src/executor/handlers/navigateHandler.ts
|
|
597
1043
|
var _reportedMissingNavRef = false;
|
|
598
1044
|
function reportMissingNavigationRef() {
|
|
@@ -635,7 +1081,18 @@ function screenExistsAnywhere(state, screenName) {
|
|
|
635
1081
|
return false;
|
|
636
1082
|
}
|
|
637
1083
|
function safeNavigate(nav, screenName, params, path) {
|
|
1084
|
+
const state = nav.getState?.();
|
|
1085
|
+
const rootState = nav.getRootState?.() ?? state;
|
|
638
1086
|
if (path && path.length > 0) {
|
|
1087
|
+
if (Array.isArray(rootState?.routeNames) && !screenExistsAnywhere(rootState, path[0]) && !state?.routeNames?.includes(path[0])) {
|
|
1088
|
+
if (state?.routeNames?.includes(screenName)) {
|
|
1089
|
+
nav.navigate(screenName, params);
|
|
1090
|
+
return;
|
|
1091
|
+
}
|
|
1092
|
+
throw new Error(
|
|
1093
|
+
`Route "${path[0]}" is not registered in any navigator. Cannot resolve the supplied path to "${screenName}".`
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
639
1096
|
const { root, nestedParams } = buildNestedParams(path, screenName, params);
|
|
640
1097
|
appilotsDebugLog(
|
|
641
1098
|
`safeNavigate: using path-based navigation \u2014 root="${root}", nested=${JSON.stringify(nestedParams)}`
|
|
@@ -643,12 +1100,10 @@ function safeNavigate(nav, screenName, params, path) {
|
|
|
643
1100
|
nav.navigate(root, nestedParams);
|
|
644
1101
|
return;
|
|
645
1102
|
}
|
|
646
|
-
const state = nav.getState?.();
|
|
647
1103
|
if (state?.routeNames?.includes(screenName)) {
|
|
648
1104
|
nav.navigate(screenName, params);
|
|
649
1105
|
return;
|
|
650
1106
|
}
|
|
651
|
-
const rootState = nav.getRootState?.() ?? state;
|
|
652
1107
|
if (!screenExistsAnywhere(rootState, screenName)) {
|
|
653
1108
|
throw new Error(
|
|
654
1109
|
`Screen "${screenName}" is not registered in any navigator. The screen may have been removed, may live inside a flow that is not currently mounted (e.g. Auth stack after login), or the MCP doc is stale.`
|
|
@@ -670,8 +1125,8 @@ function navigateHandler(payload, context) {
|
|
|
670
1125
|
if (!permissions.canNavigate) {
|
|
671
1126
|
return {
|
|
672
1127
|
success: false,
|
|
673
|
-
error: "Navigation
|
|
674
|
-
diagnose: { category: "unknown", screen: payload.screenName }
|
|
1128
|
+
error: permissionDeniedMessage("Navigation", "canNavigate"),
|
|
1129
|
+
diagnose: { category: "unknown", screen: payload.screenName, recoverable: false }
|
|
675
1130
|
};
|
|
676
1131
|
}
|
|
677
1132
|
const isGoBack = payload.navigationAction === "goBack";
|
|
@@ -1028,8 +1483,10 @@ function pressResolvedListItem(list, itemFiber, listIndex, itemIndex) {
|
|
|
1028
1483
|
(candidate) => candidate !== touchable && containsFiber(touchable, candidate)
|
|
1029
1484
|
);
|
|
1030
1485
|
if (primaryWrapsNestedTargets) {
|
|
1486
|
+
const outer = describeTouchable(touchable) ?? "?";
|
|
1487
|
+
const nested = safeIdentifiableTouchables.filter((candidate) => candidate !== touchable && containsFiber(touchable, candidate)).map((candidate) => describeTouchable(candidate) ?? "?").slice(0, 6);
|
|
1031
1488
|
appilotsDebugLog(
|
|
1032
|
-
`listItemDispatch: item "list-${listIndex}-item-${itemIndex}" has nested pressables; pressing outer row target`
|
|
1489
|
+
`listItemDispatch: item "list-${listIndex}-item-${itemIndex}" has nested pressables; pressing outer row target [outer=${outer}] [nested=${nested.join(", ")}] [outerHasOnPress=${typeof touchable?.memoizedProps?.onPress === "function"}]`
|
|
1033
1490
|
);
|
|
1034
1491
|
} else {
|
|
1035
1492
|
const candidates = safeIdentifiableTouchables.map(describeTouchable).filter((d) => !!d).slice(0, 8);
|
|
@@ -1096,10 +1553,12 @@ function scoreItemCandidate(item, zeroBasedIndex, locator) {
|
|
|
1096
1553
|
if (locator.itemKey) {
|
|
1097
1554
|
if (itemKey === locator.itemKey) score += 1e3;
|
|
1098
1555
|
else if (itemKey && normalize2(itemKey) === normalize2(locator.itemKey)) score += 950;
|
|
1556
|
+
else return 0;
|
|
1099
1557
|
}
|
|
1100
1558
|
if (locator.reactKey) {
|
|
1101
1559
|
if (reactKey === locator.reactKey) score += 900;
|
|
1102
1560
|
else if (reactKey && normalize2(reactKey) === normalize2(locator.reactKey)) score += 850;
|
|
1561
|
+
else if (!locator.itemKey) return 0;
|
|
1103
1562
|
}
|
|
1104
1563
|
if (locator.itemIndex && zeroBasedIndex === locator.itemIndex - 1) score += 750;
|
|
1105
1564
|
if (locator.itemIndex && markedIndex !== void 0 && (markedIndex === locator.itemIndex || markedIndex === locator.itemIndex - 1)) {
|
|
@@ -1169,7 +1628,7 @@ function resolveListItemByIdentity(locator) {
|
|
|
1169
1628
|
scoredCandidates.sort((a, b) => b.score - a.score);
|
|
1170
1629
|
const best = scoredCandidates[0];
|
|
1171
1630
|
const tiedCandidates = scoredCandidates.filter((c) => c.score === best.score);
|
|
1172
|
-
if (tiedCandidates.length > 1 && best.score < 750) {
|
|
1631
|
+
if (tiedCandidates.length > 1 && (best.score < 750 || Boolean(effective.itemKey || effective.reactKey))) {
|
|
1173
1632
|
const candidates = tiedCandidates.map((c) => describeItemCandidate(c.listIndex, c.itemIndex - 1, c.item)).slice(0, 8);
|
|
1174
1633
|
return {
|
|
1175
1634
|
ok: false,
|
|
@@ -1237,11 +1696,11 @@ function toggleListItemByIdentity(locator, options) {
|
|
|
1237
1696
|
}
|
|
1238
1697
|
const target = switches[0];
|
|
1239
1698
|
const props = target?.memoizedProps ?? {};
|
|
1240
|
-
const
|
|
1241
|
-
if (options?.expected !== void 0 &&
|
|
1242
|
-
return { ok: true, containerType: list.containerType, value:
|
|
1699
|
+
const current2 = switchValue(props);
|
|
1700
|
+
if (options?.expected !== void 0 && current2 === options.expected) {
|
|
1701
|
+
return { ok: true, containerType: list.containerType, value: current2, noop: true };
|
|
1243
1702
|
}
|
|
1244
|
-
const flipped = flipSwitchFiber(target, !
|
|
1703
|
+
const flipped = flipSwitchFiber(target, !current2);
|
|
1245
1704
|
if (!flipped.ok) {
|
|
1246
1705
|
if (flipped.reason === "no-handler") {
|
|
1247
1706
|
return {
|
|
@@ -1258,7 +1717,7 @@ function toggleListItemByIdentity(locator, options) {
|
|
|
1258
1717
|
containerType: list.containerType
|
|
1259
1718
|
};
|
|
1260
1719
|
}
|
|
1261
|
-
return { ok: true, containerType: list.containerType, value: !
|
|
1720
|
+
return { ok: true, containerType: list.containerType, value: !current2 };
|
|
1262
1721
|
}
|
|
1263
1722
|
function pressListItemByIdentity(locator) {
|
|
1264
1723
|
const resolved = resolveListItemByIdentity(locator);
|
|
@@ -1453,6 +1912,12 @@ function dispatchFiberTarget(targetId, action) {
|
|
|
1453
1912
|
diagnose: screenDiagnose(targetId)
|
|
1454
1913
|
};
|
|
1455
1914
|
}
|
|
1915
|
+
const measured = handle.measure?.() ?? null;
|
|
1916
|
+
if (measured && getSpotlightMode() === "guided") {
|
|
1917
|
+
setSpotlightTarget(revealTarget(measured).rect, handle.label);
|
|
1918
|
+
} else {
|
|
1919
|
+
setSpotlightTarget(measured, handle.label);
|
|
1920
|
+
}
|
|
1456
1921
|
try {
|
|
1457
1922
|
if (isPressLike(action) && handle.kind === "target" && handle.press) {
|
|
1458
1923
|
handle.press();
|
|
@@ -1503,6 +1968,13 @@ function dispatchLegacyTarget(targetId, registry, action) {
|
|
|
1503
1968
|
};
|
|
1504
1969
|
}
|
|
1505
1970
|
function dispatchElement(element, registry, action) {
|
|
1971
|
+
if (element.dispatchable === false) {
|
|
1972
|
+
return {
|
|
1973
|
+
success: false,
|
|
1974
|
+
error: `Element "${element.id}" is observation-only and has no exact live control binding.`,
|
|
1975
|
+
diagnose: { ...screenDiagnose(element.id, "ambiguous-target"), recoverable: false }
|
|
1976
|
+
};
|
|
1977
|
+
}
|
|
1506
1978
|
if (element.disabled === true) {
|
|
1507
1979
|
return {
|
|
1508
1980
|
success: false,
|
|
@@ -1521,6 +1993,13 @@ function dispatchElement(element, registry, action) {
|
|
|
1521
1993
|
texts: element.texts
|
|
1522
1994
|
});
|
|
1523
1995
|
if (pressed.ok) return { success: true };
|
|
1996
|
+
if (!pressed.refusal && (element.listContext.itemKey || element.listContext.reactKey)) {
|
|
1997
|
+
return {
|
|
1998
|
+
success: false,
|
|
1999
|
+
error: pressed.error ?? `The observed list entity "${element.label ?? element.id}" could not be resolved. Reobserve; do not substitute its old position.`,
|
|
2000
|
+
diagnose: { ...screenDiagnose(element.id, "ambiguous-target"), recoverable: true }
|
|
2001
|
+
};
|
|
2002
|
+
}
|
|
1524
2003
|
if (pressed.refusal) {
|
|
1525
2004
|
return {
|
|
1526
2005
|
success: false,
|
|
@@ -1838,12 +2317,18 @@ function normalizeText(value) {
|
|
|
1838
2317
|
function containsWord(haystack, needle) {
|
|
1839
2318
|
return ` ${haystack} `.includes(` ${needle} `);
|
|
1840
2319
|
}
|
|
1841
|
-
function readVisibleFormValidation(snap) {
|
|
2320
|
+
function readVisibleFormValidation(snap, beforeTexts) {
|
|
1842
2321
|
if (!snap) return void 0;
|
|
2322
|
+
const invalid = snap.inputs?.find((input) => input.invalid === true);
|
|
2323
|
+
const hasSubmitControl = snap.buttons?.some(
|
|
2324
|
+
(button) => SUBMIT_CONTROL_RE.test(button.label ?? button.id ?? "")
|
|
2325
|
+
);
|
|
1843
2326
|
for (const text of snap.texts ?? []) {
|
|
1844
|
-
if (!text?.trim() || !
|
|
2327
|
+
if (!text?.trim() || !isVisibleValidationText(text, { invalid: !!invalid, hasSubmitControl, beforeTexts }))
|
|
2328
|
+
continue;
|
|
1845
2329
|
return { fieldId: inferValidationFieldId(text, snap.inputs), message: text };
|
|
1846
2330
|
}
|
|
2331
|
+
if (invalid) return { fieldId: invalid.id, message: "The app marks this field as invalid." };
|
|
1847
2332
|
return void 0;
|
|
1848
2333
|
}
|
|
1849
2334
|
function readVisibleFormSuccess(snap) {
|
|
@@ -1856,15 +2341,18 @@ function submitAsyncStillPending(submitTargetId) {
|
|
|
1856
2341
|
if (submitTargetId && probe.pressedFound && probe.pressedDisabled) return true;
|
|
1857
2342
|
return false;
|
|
1858
2343
|
}
|
|
1859
|
-
function evaluatePostSubmitSnapshot(snap, activePath) {
|
|
1860
|
-
const validation = readVisibleFormValidation(snap);
|
|
2344
|
+
function evaluatePostSubmitSnapshot(snap, activePath, beforeTexts) {
|
|
2345
|
+
const validation = readVisibleFormValidation(snap, beforeTexts);
|
|
1861
2346
|
if (validation) {
|
|
1862
2347
|
return { status: "failure", fieldId: validation.fieldId, message: validation.message };
|
|
1863
2348
|
}
|
|
1864
2349
|
const successText = readVisibleFormSuccess(snap);
|
|
1865
2350
|
if (successText) return { status: "success", message: successText };
|
|
1866
2351
|
if (!snapshotShowsOpenCreateForm(snap, activePath)) {
|
|
1867
|
-
return {
|
|
2352
|
+
return {
|
|
2353
|
+
status: "unverified",
|
|
2354
|
+
message: "The form closed without a visible success receipt. Inspect the resulting record before claiming the goal is complete."
|
|
2355
|
+
};
|
|
1868
2356
|
}
|
|
1869
2357
|
return void 0;
|
|
1870
2358
|
}
|
|
@@ -1880,7 +2368,7 @@ async function waitForPostSubmitOutcome(options) {
|
|
|
1880
2368
|
}
|
|
1881
2369
|
const snap2 = captureSnapshot();
|
|
1882
2370
|
const activePath2 = getActiveRouteNames();
|
|
1883
|
-
const outcome2 = evaluatePostSubmitSnapshot(snap2, activePath2);
|
|
2371
|
+
const outcome2 = evaluatePostSubmitSnapshot(snap2, activePath2, options?.beforeTexts);
|
|
1884
2372
|
if (outcome2) return outcome2;
|
|
1885
2373
|
await sleep(pollMs);
|
|
1886
2374
|
}
|
|
@@ -1892,7 +2380,7 @@ async function waitForPostSubmitOutcome(options) {
|
|
|
1892
2380
|
}
|
|
1893
2381
|
const snap = captureSnapshot();
|
|
1894
2382
|
const activePath = getActiveRouteNames();
|
|
1895
|
-
const outcome = evaluatePostSubmitSnapshot(snap, activePath);
|
|
2383
|
+
const outcome = evaluatePostSubmitSnapshot(snap, activePath, options?.beforeTexts);
|
|
1896
2384
|
if (outcome) return outcome;
|
|
1897
2385
|
return {
|
|
1898
2386
|
status: "failure",
|
|
@@ -1900,91 +2388,6 @@ async function waitForPostSubmitOutcome(options) {
|
|
|
1900
2388
|
};
|
|
1901
2389
|
}
|
|
1902
2390
|
|
|
1903
|
-
// ../shared/dist/chunk-ERITSWKE.mjs
|
|
1904
|
-
var DEFAULT_FUZZY_FLOOR = 4;
|
|
1905
|
-
function normalizeId(value) {
|
|
1906
|
-
return String(value ?? "").normalize("NFD").replace(/[̀-ͯ]/g, "").toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1907
|
-
}
|
|
1908
|
-
var KIND_PREFIX_WORDS = "button|switch|toggle|input|field|link|btn|slider";
|
|
1909
|
-
var KEBAB_SNAKE_PREFIX_RE = new RegExp(`^(?:${KIND_PREFIX_WORDS})[-_]`, "i");
|
|
1910
|
-
var CAMEL_PREFIX_RE = new RegExp(`^(?:${KIND_PREFIX_WORDS})(?=[A-Z])`, "");
|
|
1911
|
-
function stripKindPrefix(id) {
|
|
1912
|
-
const raw = String(id ?? "");
|
|
1913
|
-
if (KEBAB_SNAKE_PREFIX_RE.test(raw)) return raw.replace(KEBAB_SNAKE_PREFIX_RE, "");
|
|
1914
|
-
if (CAMEL_PREFIX_RE.test(raw)) return raw.replace(CAMEL_PREFIX_RE, "");
|
|
1915
|
-
return raw;
|
|
1916
|
-
}
|
|
1917
|
-
function actionAllowsKind(action, kind) {
|
|
1918
|
-
if (!action || !kind) return true;
|
|
1919
|
-
switch (action) {
|
|
1920
|
-
case "press":
|
|
1921
|
-
case "longPress":
|
|
1922
|
-
return kind !== "field" && kind !== "input" && kind !== "slider";
|
|
1923
|
-
case "set_value":
|
|
1924
|
-
return kind === "slider" || kind === "field" || kind === "input";
|
|
1925
|
-
case "focus":
|
|
1926
|
-
return kind === "field" || kind === "input";
|
|
1927
|
-
case "toggle":
|
|
1928
|
-
return kind === "toggle";
|
|
1929
|
-
default:
|
|
1930
|
-
return true;
|
|
1931
|
-
}
|
|
1932
|
-
}
|
|
1933
|
-
function distinctIds(candidates) {
|
|
1934
|
-
return [...new Set(candidates.map((c) => c.id))];
|
|
1935
|
-
}
|
|
1936
|
-
function resolveTarget(requested, candidates, opts = {}) {
|
|
1937
|
-
const floor = opts.fuzzyFloor ?? DEFAULT_FUZZY_FLOOR;
|
|
1938
|
-
const nReq = normalizeId(requested);
|
|
1939
|
-
if (!nReq || candidates.length === 0) {
|
|
1940
|
-
return { status: "unverified", reason: "not-found" };
|
|
1941
|
-
}
|
|
1942
|
-
for (const c of candidates) {
|
|
1943
|
-
if (normalizeId(c.id) === nReq) {
|
|
1944
|
-
return { status: "matched", id: c.id, tier: "exact" };
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
const labelExact = distinctIds(
|
|
1948
|
-
candidates.filter((c) => c.label && normalizeId(c.label) === nReq)
|
|
1949
|
-
);
|
|
1950
|
-
if (labelExact.length === 1) return { status: "matched", id: labelExact[0], tier: "exact" };
|
|
1951
|
-
if (labelExact.length > 1) return { status: "unverified", reason: "ambiguous" };
|
|
1952
|
-
const nReqStripped = normalizeId(stripKindPrefix(requested));
|
|
1953
|
-
for (const c of candidates) {
|
|
1954
|
-
const nId = normalizeId(c.id);
|
|
1955
|
-
const nIdStripped = normalizeId(stripKindPrefix(c.id));
|
|
1956
|
-
if (nReqStripped && (nReqStripped === nId || nReqStripped === nIdStripped) || nIdStripped && nIdStripped === nReq) {
|
|
1957
|
-
return { status: "matched", id: c.id, tier: "prefix" };
|
|
1958
|
-
}
|
|
1959
|
-
}
|
|
1960
|
-
if (opts.destructive) {
|
|
1961
|
-
return { status: "unverified", reason: "destructive-inexact" };
|
|
1962
|
-
}
|
|
1963
|
-
if (nReq.length < floor) {
|
|
1964
|
-
return { status: "unverified", reason: "not-found" };
|
|
1965
|
-
}
|
|
1966
|
-
const fuzzy = candidates.filter((c) => {
|
|
1967
|
-
for (const s of [c.id, c.label]) {
|
|
1968
|
-
if (!s) continue;
|
|
1969
|
-
const ns = normalizeId(s);
|
|
1970
|
-
if (ns.length >= floor && (ns.includes(nReq) || nReq.includes(ns))) return true;
|
|
1971
|
-
}
|
|
1972
|
-
return false;
|
|
1973
|
-
});
|
|
1974
|
-
if (fuzzy.length === 0) {
|
|
1975
|
-
return { status: "unverified", reason: "not-found" };
|
|
1976
|
-
}
|
|
1977
|
-
const typed = fuzzy.filter((c) => actionAllowsKind(opts.action, c.kind));
|
|
1978
|
-
if (typed.length === 0) {
|
|
1979
|
-
return { status: "unverified", reason: "type-mismatch" };
|
|
1980
|
-
}
|
|
1981
|
-
const ids = distinctIds(typed);
|
|
1982
|
-
if (ids.length > 1) {
|
|
1983
|
-
return { status: "unverified", reason: "ambiguous" };
|
|
1984
|
-
}
|
|
1985
|
-
return { status: "matched", id: ids[0], tier: "fuzzy" };
|
|
1986
|
-
}
|
|
1987
|
-
|
|
1988
2391
|
// src/executor/handlers/uiInteractionHandler.ts
|
|
1989
2392
|
function findSnapshotButton(targetId) {
|
|
1990
2393
|
try {
|
|
@@ -2057,6 +2460,7 @@ function stripComponentPrefix(componentId) {
|
|
|
2057
2460
|
return null;
|
|
2058
2461
|
}
|
|
2059
2462
|
function resolveComponentId(componentId, registry, action) {
|
|
2463
|
+
if (isSourceControlId(componentId)) return componentId;
|
|
2060
2464
|
if (registry.getTarget(componentId) || registry.getToggle(componentId) || registry.getField(componentId) || registry.getSlider(componentId)) {
|
|
2061
2465
|
return componentId;
|
|
2062
2466
|
}
|
|
@@ -2070,6 +2474,7 @@ function resolveComponentId(componentId, registry, action) {
|
|
|
2070
2474
|
return comp.id;
|
|
2071
2475
|
}
|
|
2072
2476
|
}
|
|
2477
|
+
if (isPositionalControlId(componentId)) return componentId;
|
|
2073
2478
|
const currentScreen = getCurrentScreen();
|
|
2074
2479
|
if (currentScreen) {
|
|
2075
2480
|
const screenMeta = getScreenMetadata(currentScreen);
|
|
@@ -2315,11 +2720,14 @@ function uiInteractionHandler(payload, context) {
|
|
|
2315
2720
|
if (!permissions.canInteractUI) {
|
|
2316
2721
|
return {
|
|
2317
2722
|
success: false,
|
|
2318
|
-
error: "UI interaction
|
|
2319
|
-
diagnose:
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2723
|
+
error: permissionDeniedMessage("UI interaction", "canInteractUI"),
|
|
2724
|
+
diagnose: {
|
|
2725
|
+
...buildUiDiagnose(
|
|
2726
|
+
payload.componentId ?? payload.targetId ?? "",
|
|
2727
|
+
"unknown"
|
|
2728
|
+
),
|
|
2729
|
+
recoverable: false
|
|
2730
|
+
}
|
|
2323
2731
|
};
|
|
2324
2732
|
}
|
|
2325
2733
|
const { action } = payload;
|
|
@@ -2549,6 +2957,28 @@ function uiInteractionHandler(payload, context) {
|
|
|
2549
2957
|
};
|
|
2550
2958
|
}
|
|
2551
2959
|
|
|
2960
|
+
// src/executor/completeTextEdit.ts
|
|
2961
|
+
var sleep2 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
2962
|
+
async function completeTextEdit(fieldId, expected, registered, origin, resolveRegistered) {
|
|
2963
|
+
await sleep2(80);
|
|
2964
|
+
const current2 = findInteractiveByIdentifier(fieldId, { kind: "field" });
|
|
2965
|
+
if (origin?.controlIdentity && current2?.controlIdentity !== origin.controlIdentity) return false;
|
|
2966
|
+
if (registered && resolveRegistered && resolveRegistered() !== registered) return false;
|
|
2967
|
+
if (registered?.finishEditing) registered.finishEditing();
|
|
2968
|
+
else if (current2 && (current2.matchCount ?? 1) === 1 && !current2.disabled)
|
|
2969
|
+
current2.finishEditing?.();
|
|
2970
|
+
const started = Date.now();
|
|
2971
|
+
do {
|
|
2972
|
+
await sleep2(80);
|
|
2973
|
+
const live = findInteractiveByIdentifier(fieldId, { kind: "field" });
|
|
2974
|
+
if (origin?.controlIdentity && live?.controlIdentity !== origin.controlIdentity) return false;
|
|
2975
|
+
if (registered && resolveRegistered && resolveRegistered() !== registered) return false;
|
|
2976
|
+
const value = registered ? registered.getValue() : live?.getValue?.();
|
|
2977
|
+
if (value !== void 0 && value !== null && String(value) === String(expected)) return true;
|
|
2978
|
+
} while (Date.now() - started < 480);
|
|
2979
|
+
return false;
|
|
2980
|
+
}
|
|
2981
|
+
|
|
2552
2982
|
// src/executor/formSubmitResolve.ts
|
|
2553
2983
|
var SUBMIT_LABEL_HINT_RE = /\b(submit|save|send|continue|next|sign\s*in|log\s*in|register|salvar|enviar|entrar|cadastrar|continuar|proximo|próximo|avancar|avançar|confirmar|guardar|registrarse|iniciar\s+sesi[óo]n|enregistrer|envoyer|valider|soumettre|confirmer|se\s+connecter|s['e]\s*inscrire)\b/i;
|
|
2554
2984
|
var AGENT_CHROME_TARGET_RE = /\b(fechar|close|dismiss|cancelar|cancel|limpar|clear|chat\s+do\s+assistente|assistant\s+chat)\b/i;
|
|
@@ -2695,8 +3125,8 @@ function selectValueApplied(fieldId, requestedValue, currentValue, registry) {
|
|
|
2695
3125
|
function getMetadataField(fieldId, registry) {
|
|
2696
3126
|
const screens = /* @__PURE__ */ new Set();
|
|
2697
3127
|
for (const name of getActiveRouteNames()) screens.add(name);
|
|
2698
|
-
const
|
|
2699
|
-
if (
|
|
3128
|
+
const current2 = getCurrentScreen();
|
|
3129
|
+
if (current2) screens.add(current2);
|
|
2700
3130
|
if (registry) {
|
|
2701
3131
|
for (const comp of registry.snapshot()) {
|
|
2702
3132
|
if (comp.kind === "field" && comp.screen) screens.add(comp.screen);
|
|
@@ -2737,7 +3167,7 @@ async function tryOpenSelectModal(fieldId, registry) {
|
|
|
2737
3167
|
if (!target) continue;
|
|
2738
3168
|
try {
|
|
2739
3169
|
target.press();
|
|
2740
|
-
await
|
|
3170
|
+
await sleep3(180);
|
|
2741
3171
|
return true;
|
|
2742
3172
|
} catch {
|
|
2743
3173
|
}
|
|
@@ -3122,7 +3552,7 @@ function findSlider(fieldId, registry) {
|
|
|
3122
3552
|
}
|
|
3123
3553
|
return void 0;
|
|
3124
3554
|
}
|
|
3125
|
-
var
|
|
3555
|
+
var sleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
3126
3556
|
async function formFillHandler(payload, context) {
|
|
3127
3557
|
const { permissions } = context;
|
|
3128
3558
|
const registry = context.registry ?? componentRegistry;
|
|
@@ -3131,8 +3561,8 @@ async function formFillHandler(payload, context) {
|
|
|
3131
3561
|
if (!permissions.canFillForms) {
|
|
3132
3562
|
return {
|
|
3133
3563
|
success: false,
|
|
3134
|
-
error: "Form filling
|
|
3135
|
-
diagnose: { category: "unknown", screen: screenPath }
|
|
3564
|
+
error: permissionDeniedMessage("Form filling", "canFillForms"),
|
|
3565
|
+
diagnose: { category: "unknown", screen: screenPath, recoverable: false }
|
|
3136
3566
|
};
|
|
3137
3567
|
}
|
|
3138
3568
|
if (!payload.fields || payload.fields.length === 0) {
|
|
@@ -3189,14 +3619,51 @@ async function formFillHandler(payload, context) {
|
|
|
3189
3619
|
const fieldEntry = findField(fieldId, registry);
|
|
3190
3620
|
if (fieldEntry) {
|
|
3191
3621
|
try {
|
|
3622
|
+
const nativeField = findInteractiveByIdentifier(fieldId, { kind: "field" });
|
|
3623
|
+
if (nativeField?.disabled || (nativeField?.matchCount ?? 1) > 1) {
|
|
3624
|
+
fieldResults.push({
|
|
3625
|
+
fieldId,
|
|
3626
|
+
success: false,
|
|
3627
|
+
error: nativeField?.disabled ? "The field is disabled" : "The field identifier matches multiple controls",
|
|
3628
|
+
diagnose: {
|
|
3629
|
+
category: nativeField?.disabled ? "disabled" : "ambiguous-target",
|
|
3630
|
+
fieldId,
|
|
3631
|
+
screen: screenPath
|
|
3632
|
+
}
|
|
3633
|
+
});
|
|
3634
|
+
allSuccess = false;
|
|
3635
|
+
continue;
|
|
3636
|
+
}
|
|
3192
3637
|
appilotsDebugLog(
|
|
3193
3638
|
`formFillHandler: found field "${fieldId}" via registry, calling setValue(<${String(value).length} chars>)`
|
|
3194
3639
|
);
|
|
3195
3640
|
const beforeValue = effectiveFieldType === "date" ? fieldEntry.getValue?.() ?? "" : void 0;
|
|
3196
3641
|
fieldEntry.focus?.();
|
|
3197
3642
|
fieldEntry.setValue(String(value));
|
|
3643
|
+
if (effectiveFieldType === "text" || effectiveFieldType === "number") {
|
|
3644
|
+
if (!await completeTextEdit(
|
|
3645
|
+
fieldId,
|
|
3646
|
+
value,
|
|
3647
|
+
fieldEntry,
|
|
3648
|
+
nativeField,
|
|
3649
|
+
() => findField(fieldId, registry)
|
|
3650
|
+
)) {
|
|
3651
|
+
fieldResults.push({
|
|
3652
|
+
fieldId,
|
|
3653
|
+
success: false,
|
|
3654
|
+
error: "The field did not retain the requested value after editing ended. Inspect its current value before retrying.",
|
|
3655
|
+
diagnose: { category: "validation", fieldId, screen: screenPath }
|
|
3656
|
+
});
|
|
3657
|
+
allSuccess = false;
|
|
3658
|
+
continue;
|
|
3659
|
+
}
|
|
3660
|
+
}
|
|
3198
3661
|
if (effectiveFieldType === "date") {
|
|
3199
|
-
|
|
3662
|
+
let afterValue = fieldEntry.getValue?.() ?? "";
|
|
3663
|
+
if (afterValue === beforeValue) {
|
|
3664
|
+
await sleep3(80);
|
|
3665
|
+
afterValue = fieldEntry.getValue?.() ?? "";
|
|
3666
|
+
}
|
|
3200
3667
|
if (afterValue === beforeValue) {
|
|
3201
3668
|
fieldResults.push({
|
|
3202
3669
|
fieldId,
|
|
@@ -3216,7 +3683,7 @@ async function formFillHandler(payload, context) {
|
|
|
3216
3683
|
if (effectiveFieldType === "select") {
|
|
3217
3684
|
let after = fieldEntry.getValue?.() ?? "";
|
|
3218
3685
|
if (!selectValueApplied(fieldId, value, after, registry)) {
|
|
3219
|
-
await
|
|
3686
|
+
await sleep3(80);
|
|
3220
3687
|
after = fieldEntry.getValue?.() ?? "";
|
|
3221
3688
|
}
|
|
3222
3689
|
if (!selectValueApplied(fieldId, value, after, registry)) {
|
|
@@ -3337,7 +3804,7 @@ async function formFillHandler(payload, context) {
|
|
|
3337
3804
|
if (!selectResult.success && selectedChoiceThisBatch) {
|
|
3338
3805
|
const nextResult = pressContinuationButton(registry);
|
|
3339
3806
|
if (nextResult.success) {
|
|
3340
|
-
await
|
|
3807
|
+
await sleep3(180);
|
|
3341
3808
|
selectResult = await selectVisibleChoice(fieldId, value, registry, {
|
|
3342
3809
|
allowFirstVisibleWhenNoCue: true
|
|
3343
3810
|
});
|
|
@@ -3348,7 +3815,7 @@ async function formFillHandler(payload, context) {
|
|
|
3348
3815
|
appilotsDebugLog(
|
|
3349
3816
|
`formFillHandler: selected visible option for "${fieldId}"` + (selectResult.selectedLabel ? ` \u2192 "${selectResult.selectedLabel}"` : "")
|
|
3350
3817
|
);
|
|
3351
|
-
await
|
|
3818
|
+
await sleep3(120);
|
|
3352
3819
|
fieldResults.push({ fieldId, success: true });
|
|
3353
3820
|
continue;
|
|
3354
3821
|
}
|
|
@@ -3360,8 +3827,34 @@ async function formFillHandler(payload, context) {
|
|
|
3360
3827
|
});
|
|
3361
3828
|
if (handle?.setValue) {
|
|
3362
3829
|
try {
|
|
3830
|
+
if (handle.disabled || (handle.matchCount ?? 1) > 1) {
|
|
3831
|
+
fieldResults.push({
|
|
3832
|
+
fieldId,
|
|
3833
|
+
success: false,
|
|
3834
|
+
error: handle.disabled ? "The field is disabled" : "The field identifier matches multiple controls",
|
|
3835
|
+
diagnose: {
|
|
3836
|
+
category: handle.disabled ? "disabled" : "ambiguous-target",
|
|
3837
|
+
fieldId,
|
|
3838
|
+
screen: screenPath,
|
|
3839
|
+
candidates: handle.matchCandidates
|
|
3840
|
+
}
|
|
3841
|
+
});
|
|
3842
|
+
allSuccess = false;
|
|
3843
|
+
continue;
|
|
3844
|
+
}
|
|
3845
|
+
setSpotlightTarget(handle.measure?.() ?? null, handle.label);
|
|
3363
3846
|
handle.focus?.();
|
|
3364
3847
|
handle.setValue(value);
|
|
3848
|
+
if (handle.kind === "field" && !await completeTextEdit(fieldId, value, void 0, handle)) {
|
|
3849
|
+
fieldResults.push({
|
|
3850
|
+
fieldId,
|
|
3851
|
+
success: false,
|
|
3852
|
+
error: "The field did not retain the requested value after editing ended. Inspect its current value before retrying.",
|
|
3853
|
+
diagnose: { category: "validation", fieldId, screen: screenPath }
|
|
3854
|
+
});
|
|
3855
|
+
allSuccess = false;
|
|
3856
|
+
continue;
|
|
3857
|
+
}
|
|
3365
3858
|
appilotsDebugLog(
|
|
3366
3859
|
`formFillHandler: fiber fallback succeeded for "${fieldId}" \u2192 label="${handle.label}"`
|
|
3367
3860
|
);
|
|
@@ -3413,10 +3906,11 @@ async function formFillHandler(payload, context) {
|
|
|
3413
3906
|
"formFillHandler: submitAfterFill=true requested but some fields failed; NOT submitting" + (failedIds.length > 0 ? ` (failed: ${failedIds.join(", ")})` : "")
|
|
3414
3907
|
);
|
|
3415
3908
|
} else {
|
|
3416
|
-
await
|
|
3909
|
+
await sleep3(120);
|
|
3417
3910
|
if (!permissions.canSubmitForms) {
|
|
3418
3911
|
return { success: false, error: "Form submission is not permitted", fieldResults };
|
|
3419
3912
|
}
|
|
3913
|
+
const beforeSubmitTexts = captureSnapshot().texts;
|
|
3420
3914
|
const submitResult = pressSubmitButton(
|
|
3421
3915
|
{
|
|
3422
3916
|
screen,
|
|
@@ -3436,7 +3930,8 @@ async function formFillHandler(payload, context) {
|
|
|
3436
3930
|
};
|
|
3437
3931
|
}
|
|
3438
3932
|
const postSubmit = await waitForPostSubmitOutcome({
|
|
3439
|
-
submitTargetId: submitResult.pressedTargetId
|
|
3933
|
+
submitTargetId: submitResult.pressedTargetId,
|
|
3934
|
+
beforeTexts: beforeSubmitTexts
|
|
3440
3935
|
});
|
|
3441
3936
|
if (postSubmit.status === "pending") {
|
|
3442
3937
|
appilotsDebugWarn(`formFillHandler: submit still pending \u2014 ${postSubmit.message}`);
|
|
@@ -3463,6 +3958,9 @@ async function formFillHandler(payload, context) {
|
|
|
3463
3958
|
}
|
|
3464
3959
|
};
|
|
3465
3960
|
}
|
|
3961
|
+
if (postSubmit.status === "unverified") {
|
|
3962
|
+
return { success: true, fieldResults, effect: "unknown" };
|
|
3963
|
+
}
|
|
3466
3964
|
appilotsDebugLog("formFillHandler: submitAfterFill \u2014 submit completed");
|
|
3467
3965
|
}
|
|
3468
3966
|
}
|
|
@@ -3600,7 +4098,21 @@ function aggregateFieldDiagnoses(results, screen) {
|
|
|
3600
4098
|
var FALLBACK_PAGE_PX = 600;
|
|
3601
4099
|
var PAGE_FRACTION = 0.85;
|
|
3602
4100
|
var POST_SCROLL_MOUNT_MS = 400;
|
|
3603
|
-
var
|
|
4101
|
+
var PAGINATION_MAX_WAIT_MS = 4e3;
|
|
4102
|
+
var PAGINATION_POLL_MS = 150;
|
|
4103
|
+
var sleep4 = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
4104
|
+
async function settleAfterScroll() {
|
|
4105
|
+
await sleep4(POST_SCROLL_MOUNT_MS);
|
|
4106
|
+
if (!probeLoadingState(void 0, getCurrentScreen()).loading) return;
|
|
4107
|
+
const deadline = Date.now() + PAGINATION_MAX_WAIT_MS;
|
|
4108
|
+
while (Date.now() < deadline) {
|
|
4109
|
+
await sleep4(PAGINATION_POLL_MS);
|
|
4110
|
+
if (!probeLoadingState(void 0, getCurrentScreen()).loading) {
|
|
4111
|
+
await sleep4(POST_SCROLL_MOUNT_MS);
|
|
4112
|
+
return;
|
|
4113
|
+
}
|
|
4114
|
+
}
|
|
4115
|
+
}
|
|
3604
4116
|
function normalizeId2(value) {
|
|
3605
4117
|
return String(value ?? "").toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "");
|
|
3606
4118
|
}
|
|
@@ -3636,13 +4148,26 @@ function pageOffset(metrics, direction) {
|
|
|
3636
4148
|
}
|
|
3637
4149
|
return next;
|
|
3638
4150
|
}
|
|
4151
|
+
async function finishScroll(entry, before) {
|
|
4152
|
+
await settleAfterScroll();
|
|
4153
|
+
const current2 = listRegistry.get(entry.id) ?? entry;
|
|
4154
|
+
const after = current2.getScrollMetrics?.();
|
|
4155
|
+
const grew = typeof entry.itemCount === "number" && typeof current2.itemCount === "number" && current2.itemCount > entry.itemCount;
|
|
4156
|
+
const moved = grew || !!(before && after && (Math.abs(after.offset - before.offset) > 2 || after.contentLength > before.contentLength + 2));
|
|
4157
|
+
const outcome = moved ? "moved" : before && after ? "no-progress" : "unverified";
|
|
4158
|
+
current2.exploration?.recordScroll(outcome);
|
|
4159
|
+
return {
|
|
4160
|
+
success: true,
|
|
4161
|
+
effect: moved ? "changed" : outcome === "no-progress" ? "none" : "unknown"
|
|
4162
|
+
};
|
|
4163
|
+
}
|
|
3639
4164
|
async function scrollListHandler(payload, context) {
|
|
3640
4165
|
const { permissions } = context;
|
|
3641
4166
|
if (!permissions.canInteractUI) {
|
|
3642
4167
|
return {
|
|
3643
4168
|
success: false,
|
|
3644
|
-
error: "UI interaction
|
|
3645
|
-
diagnose: buildDiagnose("unknown", payload.listId)
|
|
4169
|
+
error: permissionDeniedMessage("UI interaction", "canInteractUI"),
|
|
4170
|
+
diagnose: { ...buildDiagnose("unknown", payload.listId), recoverable: false }
|
|
3646
4171
|
};
|
|
3647
4172
|
}
|
|
3648
4173
|
const toIndex = typeof payload.toIndex === "number" ? payload.toIndex : void 0;
|
|
@@ -3670,6 +4195,15 @@ async function scrollListHandler(payload, context) {
|
|
|
3670
4195
|
diagnose: buildDiagnose("component-not-found", payload.listId, candidates)
|
|
3671
4196
|
};
|
|
3672
4197
|
}
|
|
4198
|
+
const exploration = entry.exploration?.snapshot();
|
|
4199
|
+
if (exploration && (exploration.remainingScrollSteps === 0 || exploration.consecutiveNoProgress >= 2)) {
|
|
4200
|
+
return {
|
|
4201
|
+
success: false,
|
|
4202
|
+
effect: "none",
|
|
4203
|
+
error: "List exploration stopped: scroll budget exhausted or repeated no progress. Use the current loaded count/coverage and report any remaining uncertainty; do not repeat this scroll.",
|
|
4204
|
+
diagnose: { ...buildDiagnose("validation", entry.id), recoverable: false }
|
|
4205
|
+
};
|
|
4206
|
+
}
|
|
3673
4207
|
if (toIndex !== void 0) {
|
|
3674
4208
|
if (entry.kind === "scroll") {
|
|
3675
4209
|
return {
|
|
@@ -3685,17 +4219,16 @@ async function scrollListHandler(payload, context) {
|
|
|
3685
4219
|
diagnose: buildDiagnose("validation", entry.id)
|
|
3686
4220
|
};
|
|
3687
4221
|
}
|
|
4222
|
+
const before = entry.getScrollMetrics?.();
|
|
3688
4223
|
const zeroBased = toIndex - 1;
|
|
3689
4224
|
if (entry.scrollToIndex?.(zeroBased)) {
|
|
3690
|
-
|
|
3691
|
-
return { success: true };
|
|
4225
|
+
return finishScroll(entry, before);
|
|
3692
4226
|
}
|
|
3693
4227
|
const metrics2 = entry.getScrollMetrics?.();
|
|
3694
4228
|
if (metrics2 && metrics2.contentLength > 0 && typeof entry.itemCount === "number" && entry.itemCount > 0) {
|
|
3695
4229
|
const estimated = zeroBased / entry.itemCount * metrics2.contentLength;
|
|
3696
4230
|
if (entry.scrollToOffset?.(Math.max(0, estimated))) {
|
|
3697
|
-
|
|
3698
|
-
return { success: true };
|
|
4231
|
+
return finishScroll(entry, before);
|
|
3699
4232
|
}
|
|
3700
4233
|
}
|
|
3701
4234
|
return {
|
|
@@ -3705,10 +4238,21 @@ async function scrollListHandler(payload, context) {
|
|
|
3705
4238
|
};
|
|
3706
4239
|
}
|
|
3707
4240
|
const metrics = entry.getScrollMetrics?.();
|
|
4241
|
+
if (!metrics && exploration?.lastScroll === "unverified") {
|
|
4242
|
+
return {
|
|
4243
|
+
success: false,
|
|
4244
|
+
effect: "unknown",
|
|
4245
|
+
error: "Scroll offset is still unavailable after one probe. Cannot safely page again; do not infer the end or repeat the same absolute offset.",
|
|
4246
|
+
diagnose: { ...buildDiagnose("validation", entry.id), recoverable: false }
|
|
4247
|
+
};
|
|
4248
|
+
}
|
|
3708
4249
|
const target = pageOffset(metrics, direction);
|
|
4250
|
+
if (metrics && Math.abs(target - metrics.offset) <= 2) {
|
|
4251
|
+
entry.exploration?.recordScroll("boundary");
|
|
4252
|
+
return { success: true, effect: "none" };
|
|
4253
|
+
}
|
|
3709
4254
|
if (entry.scrollToOffset?.(target)) {
|
|
3710
|
-
|
|
3711
|
-
return { success: true };
|
|
4255
|
+
return finishScroll(entry, metrics);
|
|
3712
4256
|
}
|
|
3713
4257
|
return {
|
|
3714
4258
|
success: false,
|
|
@@ -3792,7 +4336,7 @@ function hashFieldValue(value) {
|
|
|
3792
4336
|
}
|
|
3793
4337
|
return (hash >>> 0).toString(36);
|
|
3794
4338
|
}
|
|
3795
|
-
var DISCRETE_FIELD_TYPES = /* @__PURE__ */ new Set(["select", "date"]);
|
|
4339
|
+
var DISCRETE_FIELD_TYPES = /* @__PURE__ */ new Set(["select", "date", "number"]);
|
|
3796
4340
|
function discreteFieldSignature() {
|
|
3797
4341
|
const sigs = [];
|
|
3798
4342
|
for (const { id, kind } of componentRegistry.snapshot()) {
|
|
@@ -3806,12 +4350,23 @@ function discreteFieldSignature() {
|
|
|
3806
4350
|
}
|
|
3807
4351
|
return sigs.sort().join(",");
|
|
3808
4352
|
}
|
|
3809
|
-
|
|
4353
|
+
function capturePreInteractionBaseline(payload) {
|
|
3810
4354
|
const targetId = targetIdFromPayload(payload);
|
|
3811
|
-
const
|
|
3812
|
-
const
|
|
3813
|
-
|
|
3814
|
-
|
|
4355
|
+
const screen = getCurrentScreen();
|
|
4356
|
+
const probe = probeLoadingState(targetId || null, screen);
|
|
4357
|
+
return {
|
|
4358
|
+
fingerprint: probe.fingerprint,
|
|
4359
|
+
fields: discreteFieldSignature(),
|
|
4360
|
+
screen,
|
|
4361
|
+
readable: probe.fingerprint !== ""
|
|
4362
|
+
};
|
|
4363
|
+
}
|
|
4364
|
+
async function waitForPostInteractionSettle(payload, preBaseline) {
|
|
4365
|
+
const targetId = targetIdFromPayload(payload);
|
|
4366
|
+
const baseline = preBaseline ?? capturePreInteractionBaseline(payload);
|
|
4367
|
+
const fromScreen = baseline.screen;
|
|
4368
|
+
const baselineFields = baseline.fields;
|
|
4369
|
+
const readable = baseline.readable;
|
|
3815
4370
|
await sleepMs(100);
|
|
3816
4371
|
const afterPress = probeLoadingState(targetId || null, getCurrentScreen());
|
|
3817
4372
|
const modalOrLoading = afterPress.modalOpen || afterPress.loading || afterPress.pressedDisabled;
|
|
@@ -3843,12 +4398,6 @@ async function waitForPostInteractionSettle(payload) {
|
|
|
3843
4398
|
}
|
|
3844
4399
|
|
|
3845
4400
|
// src/executor/ActionExecutor.ts
|
|
3846
|
-
var DEFAULT_PERMISSIONS = {
|
|
3847
|
-
canNavigate: true,
|
|
3848
|
-
canFillForms: true,
|
|
3849
|
-
canInteractUI: true,
|
|
3850
|
-
canSubmitForms: true
|
|
3851
|
-
};
|
|
3852
4401
|
function validateActionPayload(action) {
|
|
3853
4402
|
const invalid = (message, ids) => ({
|
|
3854
4403
|
success: false,
|
|
@@ -3919,7 +4468,7 @@ async function executeAction(action, context) {
|
|
|
3919
4468
|
appilotsDebugLog(
|
|
3920
4469
|
`executeAction called: type="${action.type}", id="${action.id}", payload=${JSON.stringify(action.payload)}`
|
|
3921
4470
|
);
|
|
3922
|
-
const permissions = context.permissions
|
|
4471
|
+
const permissions = resolveAgentPermissions(context.permissions);
|
|
3923
4472
|
context.emit({
|
|
3924
4473
|
type: "agent:action:start",
|
|
3925
4474
|
timestamp: Date.now(),
|
|
@@ -3977,12 +4526,13 @@ async function executeAction(action, context) {
|
|
|
3977
4526
|
break;
|
|
3978
4527
|
case "ui_interaction": {
|
|
3979
4528
|
const payload = action.payload;
|
|
4529
|
+
const baseline = capturePreInteractionBaseline(payload);
|
|
3980
4530
|
result = uiInteractionHandler(payload, {
|
|
3981
4531
|
permissions,
|
|
3982
4532
|
registry: context.registry ?? getDefaultRegistry()
|
|
3983
4533
|
});
|
|
3984
4534
|
if (result.success) {
|
|
3985
|
-
const settle = await waitForPostInteractionSettle(payload);
|
|
4535
|
+
const settle = await waitForPostInteractionSettle(payload, baseline);
|
|
3986
4536
|
result.effect = result.effect ?? effectFor(payload, settle.effect);
|
|
3987
4537
|
}
|
|
3988
4538
|
break;
|
|
@@ -4070,18 +4620,28 @@ var _patchedAlert = (title, message, buttons, options) => {
|
|
|
4070
4620
|
return suppression.originalAlert(title, message, buttons, options);
|
|
4071
4621
|
}
|
|
4072
4622
|
suppression.consumed = true;
|
|
4623
|
+
const report = (handlerCompleted) => suppression.onHandled?.({
|
|
4624
|
+
title: title.slice(0, 300),
|
|
4625
|
+
message: message?.slice(0, 1e3),
|
|
4626
|
+
buttonLabel: button.text?.slice(0, 160),
|
|
4627
|
+
handlerCompleted
|
|
4628
|
+
});
|
|
4073
4629
|
appilotsDebugLog("Suppressing native Alert during already-confirmed destructive agent action");
|
|
4074
4630
|
try {
|
|
4075
|
-
suppression.pending = Promise.resolve(button.onPress()).then(() =>
|
|
4631
|
+
suppression.pending = Promise.resolve(button.onPress()).then(() => report(true)).catch((err) => {
|
|
4632
|
+
reportHandlerFailure(err);
|
|
4633
|
+
report(false);
|
|
4634
|
+
});
|
|
4076
4635
|
} catch (err) {
|
|
4077
4636
|
reportHandlerFailure(err);
|
|
4078
4637
|
suppression.pending = null;
|
|
4638
|
+
report(false);
|
|
4079
4639
|
}
|
|
4080
4640
|
};
|
|
4081
4641
|
function reportHandlerFailure(err) {
|
|
4082
4642
|
console.error("[Appilots] The app\u2019s Alert handler threw while being auto-confirmed", err);
|
|
4083
4643
|
}
|
|
4084
|
-
async function runWithConfirmedDestructiveContext(enabled, host, fn) {
|
|
4644
|
+
async function runWithConfirmedDestructiveContext(enabled, host, fn, onHandled) {
|
|
4085
4645
|
if (!enabled) return fn();
|
|
4086
4646
|
if (_active) {
|
|
4087
4647
|
_active.depth += 1;
|
|
@@ -4102,6 +4662,7 @@ async function runWithConfirmedDestructiveContext(enabled, host, fn) {
|
|
|
4102
4662
|
depth: 1,
|
|
4103
4663
|
consumed: false,
|
|
4104
4664
|
pending: null,
|
|
4665
|
+
onHandled,
|
|
4105
4666
|
timer: null
|
|
4106
4667
|
};
|
|
4107
4668
|
_active = suppression;
|
|
@@ -4135,9 +4696,14 @@ function resolveScreenMetadata() {
|
|
|
4135
4696
|
return screen ? getScreenMetadata(screen) : void 0;
|
|
4136
4697
|
}
|
|
4137
4698
|
function buildAgentContext(extras = {}) {
|
|
4138
|
-
if (extras.newMission === true)
|
|
4699
|
+
if (extras.newMission === true) {
|
|
4700
|
+
deltaTracker.reset();
|
|
4701
|
+
for (const entry of listRegistry.snapshot()) entry.exploration?.reset();
|
|
4702
|
+
}
|
|
4139
4703
|
const context = {};
|
|
4140
4704
|
context.platform = "react-native";
|
|
4705
|
+
context.missionProtocol = 1;
|
|
4706
|
+
context.deviceLocale = detectDeviceLocale() ?? "en";
|
|
4141
4707
|
const screen = getCurrentScreen();
|
|
4142
4708
|
if (screen) context.currentScreen = screen;
|
|
4143
4709
|
const navigationState = getNavigationStateSnapshot();
|
|
@@ -4146,6 +4712,12 @@ function buildAgentContext(extras = {}) {
|
|
|
4146
4712
|
if (screenMeta) context.screenMetadata = screenMeta;
|
|
4147
4713
|
try {
|
|
4148
4714
|
const snapshot = captureSnapshot();
|
|
4715
|
+
for (const list of snapshot.lists) {
|
|
4716
|
+
const memory = list.id ? listRegistry.get(list.id)?.exploration : void 0;
|
|
4717
|
+
if (!memory) continue;
|
|
4718
|
+
memory.observe(list.items);
|
|
4719
|
+
list.exploration = memory.snapshot();
|
|
4720
|
+
}
|
|
4149
4721
|
const delta = deltaTracker.observe(snapshot);
|
|
4150
4722
|
if (delta) snapshot.delta = delta;
|
|
4151
4723
|
context.snapshot = snapshot;
|
|
@@ -4157,9 +4729,20 @@ function buildAgentContext(extras = {}) {
|
|
|
4157
4729
|
if (filtered.length > 0) context.registeredComponents = filtered;
|
|
4158
4730
|
const registeredLists = listRegistry.snapshot();
|
|
4159
4731
|
if (registeredLists.length > 0) {
|
|
4160
|
-
|
|
4161
|
-
|
|
4732
|
+
const snapshot = context.snapshot;
|
|
4733
|
+
const activeIds = new Set(
|
|
4734
|
+
[...snapshot?.lists ?? [], ...snapshot?.scrollables ?? []].map((l) => l.id)
|
|
4162
4735
|
);
|
|
4736
|
+
context.registeredLists = registeredLists.filter((entry) => activeIds.has(entry.id)).map(({ id, kind, component, itemCount, totalItemCount, label, refreshing, empty }) => ({
|
|
4737
|
+
id,
|
|
4738
|
+
kind,
|
|
4739
|
+
component,
|
|
4740
|
+
itemCount,
|
|
4741
|
+
totalItemCount,
|
|
4742
|
+
label,
|
|
4743
|
+
refreshing,
|
|
4744
|
+
empty
|
|
4745
|
+
}));
|
|
4163
4746
|
}
|
|
4164
4747
|
if (extras.loadingPending === true) context.loadingPending = true;
|
|
4165
4748
|
return context;
|
|
@@ -4169,6 +4752,7 @@ async function settleTurn(input) {
|
|
|
4169
4752
|
let loadingPending = false;
|
|
4170
4753
|
const { turnActions, pressedComponentId: pressedId, navigateTargetScreen } = input;
|
|
4171
4754
|
const successById = new Map(input.results.map((r) => [r.actionId, r.success]));
|
|
4755
|
+
const effectById = new Map(input.results.map((r) => [r.actionId, r.effect]));
|
|
4172
4756
|
const typeById = new Map(input.results.map((r) => [r.actionId, r.type]));
|
|
4173
4757
|
const pressedAction = turnActions.find(
|
|
4174
4758
|
(a) => a.type === "ui_interaction" && a.payload?.componentId === pressedId
|
|
@@ -4244,6 +4828,7 @@ async function settleTurn(input) {
|
|
|
4244
4828
|
);
|
|
4245
4829
|
if (!postPressSettle.transitioned && baselineFingerprint === afterFingerprint) {
|
|
4246
4830
|
for (const action of listPressActions) {
|
|
4831
|
+
if (effectById.get(action.id) === "changed") continue;
|
|
4247
4832
|
if (successById.get(action.id)) {
|
|
4248
4833
|
toolWithoutEffectIds.add(action.id);
|
|
4249
4834
|
}
|
|
@@ -4261,18 +4846,40 @@ var reactNativeChatAdapter = {
|
|
|
4261
4846
|
};
|
|
4262
4847
|
function createReactNativeActionRunner(getOptions) {
|
|
4263
4848
|
return {
|
|
4849
|
+
buildMissionContext: () => reactNativeChatAdapter.buildContext(),
|
|
4264
4850
|
async execute(action, { confirmedDestructive }) {
|
|
4265
|
-
const { permissions, emit, navigationRef, suppressNativeConfirm } = getOptions();
|
|
4851
|
+
const { permissions, emit: emit2, navigationRef, suppressNativeConfirm } = getOptions();
|
|
4266
4852
|
const navRef = navigationRef?.current ? navigationRef : { current: getNavigationRef() };
|
|
4267
|
-
|
|
4853
|
+
let receipt;
|
|
4854
|
+
let terminal;
|
|
4855
|
+
const result = await runWithConfirmedDestructiveContext(
|
|
4268
4856
|
confirmedDestructive && suppressNativeConfirm !== false,
|
|
4269
4857
|
Alert,
|
|
4270
4858
|
() => executeAction(action, {
|
|
4271
4859
|
navigationRef: navRef,
|
|
4272
4860
|
permissions,
|
|
4273
|
-
emit
|
|
4274
|
-
|
|
4861
|
+
emit: (event) => {
|
|
4862
|
+
if (confirmedDestructive && (event.type === "agent:action:complete" || event.type === "agent:action:error"))
|
|
4863
|
+
terminal = event;
|
|
4864
|
+
else emit2(event);
|
|
4865
|
+
}
|
|
4866
|
+
}),
|
|
4867
|
+
(handled) => {
|
|
4868
|
+
receipt = handled;
|
|
4869
|
+
}
|
|
4275
4870
|
);
|
|
4871
|
+
const failed = receipt?.handlerCompleted === false;
|
|
4872
|
+
if (terminal)
|
|
4873
|
+
emit2({
|
|
4874
|
+
...terminal,
|
|
4875
|
+
type: failed ? "agent:action:error" : terminal.type,
|
|
4876
|
+
data: {
|
|
4877
|
+
...terminal.data,
|
|
4878
|
+
...receipt ? { nativeConfirmation: receipt } : {},
|
|
4879
|
+
...failed ? { success: false, error: "Native confirmation callback failed" } : {}
|
|
4880
|
+
}
|
|
4881
|
+
});
|
|
4882
|
+
return failed ? { ...result, success: false, error: "Native confirmation callback failed" } : result;
|
|
4276
4883
|
},
|
|
4277
4884
|
getCurrentScreen,
|
|
4278
4885
|
getCurrentScreenSignature,
|
|
@@ -4285,16 +4892,16 @@ function createReactNativeActionRunner(getOptions) {
|
|
|
4285
4892
|
|
|
4286
4893
|
// src/hooks/useAppilotsChat.ts
|
|
4287
4894
|
function useAppilotsChat(options = {}) {
|
|
4288
|
-
const { client, emit, subscribe } = useAppilotsContext();
|
|
4895
|
+
const { client, emit: emit2, subscribe } = useAppilotsContext();
|
|
4289
4896
|
const machine = useMemo(
|
|
4290
4897
|
() => new ChatSessionMachine(
|
|
4291
|
-
{ client, adapter: reactNativeChatAdapter, emit, warn: appilotsDebugWarn },
|
|
4898
|
+
{ client, adapter: reactNativeChatAdapter, emit: emit2, warn: appilotsDebugWarn },
|
|
4292
4899
|
options
|
|
4293
4900
|
),
|
|
4294
4901
|
// A new machine per client/provider instance — options are pushed in
|
|
4295
4902
|
// below so changing copy never resets the transcript.
|
|
4296
4903
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
4297
|
-
[client,
|
|
4904
|
+
[client, emit2]
|
|
4298
4905
|
);
|
|
4299
4906
|
const optionsRef = useRef(options);
|
|
4300
4907
|
optionsRef.current = options;
|
|
@@ -4302,6 +4909,14 @@ function useAppilotsChat(options = {}) {
|
|
|
4302
4909
|
const state = useSyncExternalStore(machine.subscribeState, machine.getState, machine.getState);
|
|
4303
4910
|
useEffect(() => subscribe(machine.handleEvent), [subscribe, machine]);
|
|
4304
4911
|
useEffect(() => () => machine.dispose(), [machine]);
|
|
4912
|
+
useEffect(() => {
|
|
4913
|
+
void machine.restoreMission();
|
|
4914
|
+
const subscription = AppState?.addEventListener?.("change", (next) => {
|
|
4915
|
+
if (next !== "active" && machine.getState().mission?.status === "running")
|
|
4916
|
+
void machine.pauseMission();
|
|
4917
|
+
});
|
|
4918
|
+
return () => subscription?.remove();
|
|
4919
|
+
}, [machine]);
|
|
4305
4920
|
const requestHuman = useCallback(
|
|
4306
4921
|
(reason) => machine.requestHuman(reason),
|
|
4307
4922
|
[machine]
|
|
@@ -4311,7 +4926,13 @@ function useAppilotsChat(options = {}) {
|
|
|
4311
4926
|
const clearMessages = useCallback(() => machine.clearMessages(), [machine]);
|
|
4312
4927
|
const clearError = useCallback(() => machine.clearError(), [machine]);
|
|
4313
4928
|
return {
|
|
4929
|
+
mission: state.mission,
|
|
4930
|
+
pauseMission: () => machine.pauseMission(),
|
|
4931
|
+
resumeMission: () => machine.resumeMission(),
|
|
4932
|
+
cancelMission: () => machine.cancelMission(),
|
|
4933
|
+
hasStartedActing: state.hasStartedActing,
|
|
4314
4934
|
messages: state.messages,
|
|
4935
|
+
replyLocale: state.replyLocale,
|
|
4315
4936
|
isLoading: state.isLoading,
|
|
4316
4937
|
loadingStatusKey: state.loadingStatusKey,
|
|
4317
4938
|
error: state.error,
|
|
@@ -4324,24 +4945,18 @@ function useAppilotsChat(options = {}) {
|
|
|
4324
4945
|
clearError
|
|
4325
4946
|
};
|
|
4326
4947
|
}
|
|
4327
|
-
var DEFAULT_PERMISSIONS2 = {
|
|
4328
|
-
canNavigate: true,
|
|
4329
|
-
canFillForms: true,
|
|
4330
|
-
canInteractUI: true,
|
|
4331
|
-
canSubmitForms: true
|
|
4332
|
-
};
|
|
4333
4948
|
function useAppilotsActions(options = {}) {
|
|
4334
|
-
const { client, subscribe, config, emit } = useAppilotsContext();
|
|
4949
|
+
const { client, subscribe, config, emit: emit2 } = useAppilotsContext();
|
|
4335
4950
|
const { navigationRef, autoExecute = false } = options;
|
|
4336
4951
|
const runnerOptionsRef = useRef({
|
|
4337
|
-
permissions: config.permissions
|
|
4338
|
-
emit,
|
|
4952
|
+
permissions: resolveAgentPermissions(config.permissions),
|
|
4953
|
+
emit: emit2,
|
|
4339
4954
|
navigationRef,
|
|
4340
4955
|
suppressNativeConfirm: config.suppressNativeConfirm
|
|
4341
4956
|
});
|
|
4342
4957
|
runnerOptionsRef.current = {
|
|
4343
|
-
permissions: config.permissions
|
|
4344
|
-
emit,
|
|
4958
|
+
permissions: resolveAgentPermissions(config.permissions),
|
|
4959
|
+
emit: emit2,
|
|
4345
4960
|
navigationRef,
|
|
4346
4961
|
suppressNativeConfirm: config.suppressNativeConfirm
|
|
4347
4962
|
};
|
|
@@ -4350,13 +4965,13 @@ function useAppilotsActions(options = {}) {
|
|
|
4350
4965
|
{
|
|
4351
4966
|
client,
|
|
4352
4967
|
adapter: createReactNativeActionRunner(() => runnerOptionsRef.current),
|
|
4353
|
-
emit,
|
|
4968
|
+
emit: emit2,
|
|
4354
4969
|
warn: appilotsDebugWarn
|
|
4355
4970
|
},
|
|
4356
4971
|
{ autoExecute }
|
|
4357
4972
|
),
|
|
4358
4973
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
4359
|
-
[client,
|
|
4974
|
+
[client, emit2]
|
|
4360
4975
|
);
|
|
4361
4976
|
machine.setOptions({ autoExecute });
|
|
4362
4977
|
const state = useSyncExternalStore(machine.subscribeState, machine.getState, machine.getState);
|
|
@@ -4446,9 +5061,9 @@ function useSuggestedPrompts(options) {
|
|
|
4446
5061
|
if (!enabled) return;
|
|
4447
5062
|
let lastScreen = getCurrentScreen();
|
|
4448
5063
|
const interval = setInterval(() => {
|
|
4449
|
-
const
|
|
4450
|
-
if (
|
|
4451
|
-
lastScreen =
|
|
5064
|
+
const current2 = getCurrentScreen();
|
|
5065
|
+
if (current2 !== lastScreen) {
|
|
5066
|
+
lastScreen = current2;
|
|
4452
5067
|
setRefreshTick((t) => t + 1);
|
|
4453
5068
|
}
|
|
4454
5069
|
}, 800);
|
|
@@ -4461,7 +5076,7 @@ function useSuggestedPrompts(options) {
|
|
|
4461
5076
|
};
|
|
4462
5077
|
}
|
|
4463
5078
|
function useAppilotsNavigation() {
|
|
4464
|
-
const { emit } = useAppilotsContext();
|
|
5079
|
+
const { emit: emit2 } = useAppilotsContext();
|
|
4465
5080
|
const [currentScreen, setCurrentScreenState] = useState(null);
|
|
4466
5081
|
const [navigationHistory, setNavigationHistory] = useState([]);
|
|
4467
5082
|
const navigationRef = useRef(null);
|
|
@@ -4470,13 +5085,13 @@ function useAppilotsNavigation() {
|
|
|
4470
5085
|
setCurrentScreenState(screenName);
|
|
4471
5086
|
setCurrentScreen(screenName);
|
|
4472
5087
|
setNavigationHistory((prev) => [...prev, screenName]);
|
|
4473
|
-
|
|
5088
|
+
emit2({
|
|
4474
5089
|
type: "navigation:change",
|
|
4475
5090
|
timestamp: Date.now(),
|
|
4476
5091
|
data: { screenName }
|
|
4477
5092
|
});
|
|
4478
5093
|
},
|
|
4479
|
-
[
|
|
5094
|
+
[emit2]
|
|
4480
5095
|
);
|
|
4481
5096
|
useEffect(() => {
|
|
4482
5097
|
const nav = navigationRef.current;
|
|
@@ -4526,6 +5141,11 @@ function useAppilots() {
|
|
|
4526
5141
|
rejectAction: actions.rejectAction
|
|
4527
5142
|
};
|
|
4528
5143
|
}
|
|
5144
|
+
function useAppilotsElementProps(id) {
|
|
5145
|
+
return useMemo(() => ({ testID: id }), [id]);
|
|
5146
|
+
}
|
|
5147
|
+
|
|
5148
|
+
// src/hooks/useAppilotsField.ts
|
|
4529
5149
|
function useAppilotsField(id, options) {
|
|
4530
5150
|
const { value, onChangeText, ref, fieldType, label, screen } = options;
|
|
4531
5151
|
const registry = useResolvedRegistry();
|
|
@@ -4553,6 +5173,7 @@ function useAppilotsField(id, options) {
|
|
|
4553
5173
|
registry.unregister(id);
|
|
4554
5174
|
};
|
|
4555
5175
|
}, [id, registry, getValue, setValue, focus, ref, fieldType, label, screen]);
|
|
5176
|
+
return useAppilotsElementProps(id);
|
|
4556
5177
|
}
|
|
4557
5178
|
function useAppilotsTarget(id, options) {
|
|
4558
5179
|
const { onPress, onLongPress, onScrollTo, label, screen } = options;
|
|
@@ -4580,6 +5201,7 @@ function useAppilotsTarget(id, options) {
|
|
|
4580
5201
|
registry.unregister(id);
|
|
4581
5202
|
};
|
|
4582
5203
|
}, [id, registry, press, longPress, scrollTo, onLongPress, onScrollTo, label, screen]);
|
|
5204
|
+
return useAppilotsElementProps(id);
|
|
4583
5205
|
}
|
|
4584
5206
|
function useAppilotsToggle(id, options) {
|
|
4585
5207
|
const { value, onValueChange, label, screen } = options;
|
|
@@ -4603,6 +5225,7 @@ function useAppilotsToggle(id, options) {
|
|
|
4603
5225
|
registry.unregister(id);
|
|
4604
5226
|
};
|
|
4605
5227
|
}, [id, registry, getValue, setValue, label, screen]);
|
|
5228
|
+
return useAppilotsElementProps(id);
|
|
4606
5229
|
}
|
|
4607
5230
|
function useAppilotsSlider(id, options) {
|
|
4608
5231
|
const { value, onValueChange, min, max, step, label, screen } = options;
|
|
@@ -4629,6 +5252,7 @@ function useAppilotsSlider(id, options) {
|
|
|
4629
5252
|
registry.unregister(id);
|
|
4630
5253
|
};
|
|
4631
5254
|
}, [id, registry, getValue, setValue, min, max, step, label, screen]);
|
|
5255
|
+
return useAppilotsElementProps(id);
|
|
4632
5256
|
}
|
|
4633
5257
|
|
|
4634
|
-
export { appilotsSelfCheck, captureSnapshot, executeAction, shouldShowSuggestedPrompts, useAppilots, useAppilotsActions, useAppilotsChat, useAppilotsField, useAppilotsNavigation, useAppilotsSlider, useAppilotsTarget, useAppilotsToggle, useSuggestedPrompts };
|
|
5258
|
+
export { AppilotsI18nProvider, REVEAL_MARGIN, SPOTLIGHT_TTL_MS, appilotsSelfCheck, captureSnapshot, detectDeviceLocale, executeAction, getSpotlightMode, getSpotlightTarget, offsetToReveal, resolveLocale, revealTarget, setSpotlightMode, setSpotlightTarget, shouldShowSuggestedPrompts, subscribeSpotlight, useAppilots, useAppilotsActions, useAppilotsChat, useAppilotsField, useAppilotsI18n, useAppilotsNavigation, useAppilotsSlider, useAppilotsTarget, useAppilotsToggle, useSuggestedPrompts };
|