@appilots/sdk 0.3.0 → 0.4.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.
@@ -973,56 +973,6 @@ function fingerprintAction(action, screen) {
973
973
  return `${action.type}${scope}:${JSON.stringify(p)}`;
974
974
  }
975
975
  }
976
- function normalize2(s) {
977
- return s.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]/g, "");
978
- }
979
- function matchScore(needle, hay) {
980
- if (!needle || !hay) return 0;
981
- if (needle === hay) return 100;
982
- if (hay.startsWith(needle) || needle.startsWith(hay)) return 75;
983
- if (hay.endsWith(needle) || needle.endsWith(hay)) return 60;
984
- if (hay.includes(needle) || needle.includes(hay)) return 40;
985
- return 0;
986
- }
987
- function isGenericSelectValue(value) {
988
- if (value === void 0 || value === null || value === "") return true;
989
- const normalized = normalize2(String(value));
990
- if (!normalized) return true;
991
- return /^(any|qualquer|random|aleatorio|aleatoria|auto|automatico|automatica|mock|mockado|mockada|mockados|mockadas|dummy|sample|exemplo|default|padrao|teste|test)$/.test(
992
- normalized
993
- );
994
- }
995
- var LIST_ORDINAL_RE = /^list-\d+-item-\d+$/i;
996
- function normalizeId(value) {
997
- return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
998
- }
999
- function isOptionalCosmeticTargetId(targetId) {
1000
- if (typeof targetId !== "string" || !targetId.trim()) return false;
1001
- const id = normalizeId(targetId);
1002
- return /foto|photo|avatar|icone|icon|emoji|sheetaction|sheet-action|btn-foto/.test(id) || id.includes("photo-sheet");
1003
- }
1004
- function routeLooksLikeFormContext(route) {
1005
- const haystack = normalizeId(route ?? "");
1006
- return /create|edit|form|cadastr|register|novo|vehiclecreate|subscriptioncreate|vehicleedit|subscriptioncreate/.test(
1007
- haystack
1008
- );
1009
- }
1010
- function screenPathParts(screen) {
1011
- if (!screen) return [];
1012
- return screen.split("/").map((part) => part.trim()).filter(Boolean);
1013
- }
1014
- function isMisappliedListOrdinalOnForm(targetId, screen, activePath) {
1015
- if (typeof targetId !== "string" || !LIST_ORDINAL_RE.test(targetId.trim())) {
1016
- return false;
1017
- }
1018
- const routes = [...screenPathParts(screen), ...activePath ?? []];
1019
- if (routes.length === 0) return routeLooksLikeFormContext(screen);
1020
- return routes.some((route) => routeLooksLikeFormContext(route));
1021
- }
1022
- function isOptionalStepAutomationFailure(targetId, screen, activePath) {
1023
- return isOptionalCosmeticTargetId(targetId) || isMisappliedListOrdinalOnForm(targetId, screen, activePath);
1024
- }
1025
- var OPTIONAL_STEP_AUTOMATION_HINT = "optional-step-automation-failed";
1026
976
  var MAX_TRACE_ENTRIES = 200;
1027
977
  var entries = [];
1028
978
  var listeners = /* @__PURE__ */ new Set();
@@ -1080,6 +1030,1223 @@ function subscribeAppilotsDebugTraces(listener) {
1080
1030
  listeners.delete(listener);
1081
1031
  };
1082
1032
  }
1033
+ var INTERNAL_MARKER_RE = /^\[(?:Permission policy notice|Recovery exhausted|User rejected the proposed action)[^\]]*\]:\s*/i;
1034
+ var TECHNICAL_TRACE_RE = /(^|\n)\s*(?:Results:|[-*]\s*[✓✗]\s+|(?:press|click|tap|navigate|form_fill|ui_interaction|scroll_list)\b|(?:Cliquei|Clicked)\s+em\s+(?:el:|list-\d+-item-\d+|[A-Za-z]+:))/i;
1035
+ var TECHNICAL_SELECTOR_RE = /\b(?:el:[^\s]+|list-\d+-item-\d+|[A-Za-z][\w-]*:(?!\/\/)[^\s]+|(?:testID|test-id|accessibilityId|accessibility-id)[:=_-][^\s]+)/i;
1036
+ function sanitizeVisibleAssistantContent(content) {
1037
+ return content.split("\n").map((line) => line.replace(INTERNAL_MARKER_RE, "").trimEnd()).filter((line) => !INTERNAL_MARKER_RE.test(line)).join("\n").trim();
1038
+ }
1039
+ function humanizeAssistantFallback(content) {
1040
+ let text = sanitizeVisibleAssistantContent(content).replace(/\blist-\d+-item-\d+\b/gi, "o item da lista").replace(/\bel:[^\s".]+/gi, "o elemento").replace(
1041
+ /"\s*(?:press|click|tap|navigate|form_fill|ui_interaction|scroll_list)\s+on\s+[^"]+"/gi,
1042
+ '"a a\xE7\xE3o solicitada"'
1043
+ ).replace(/\bsubscription-cancel-[^\s"]+/gi, "cancelamento de assinatura").trim();
1044
+ if (text.length < 16) return null;
1045
+ const probe = {
1046
+ role: "assistant",
1047
+ content: text
1048
+ };
1049
+ if (isInternalTraceMessage(probe)) return null;
1050
+ return text;
1051
+ }
1052
+ function isInternalTraceMessage(message) {
1053
+ if (message.role !== "assistant") return false;
1054
+ const content = message.content.trim();
1055
+ if (!content) return true;
1056
+ return TECHNICAL_TRACE_RE.test(content) || TECHNICAL_SELECTOR_RE.test(content);
1057
+ }
1058
+ var CREATE_FORM_ROUTE_RE = /vehiclecreate|subscriptioncreate|vehicleedit|subscriptionedit|maintenancecreate|create|cadastr|register|novo|edit/i;
1059
+ var SUBMIT_BUTTON_RE = /cadastrar|registrar|salvar|submit|register|guardar|enviar|confirmar|save/i;
1060
+ var FORM_VALIDATION_TEXT_RE = /(inv[aá]lid|obrigat[oó]ri|required|invalid|j[aá]\s+existe|already\s+exists|duplicad|formato|format|too\s+short|muito\s+curto|n[aã]o\s+(?:foi|pode)|must\s+be|deve\s+ser|selecione|escolha|select\s+(?:the|a|o|an))/i;
1061
+ function normalize2(value) {
1062
+ return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
1063
+ }
1064
+ function routeHintsCreateForm(routeNames) {
1065
+ return routeNames.some((name) => CREATE_FORM_ROUTE_RE.test(normalize2(name)));
1066
+ }
1067
+ function snapshotHasSubmitControl(snap) {
1068
+ return (snap.buttons ?? []).some(
1069
+ (btn) => SUBMIT_BUTTON_RE.test(normalize2(`${btn.label ?? btn.id ?? ""}`))
1070
+ );
1071
+ }
1072
+ function snapshotHasVisibleSubmitButton(snap) {
1073
+ return (snap.buttons ?? []).some(
1074
+ (btn) => !btn.disabled && SUBMIT_BUTTON_RE.test(normalize2(`${btn.label ?? btn.id ?? ""}`))
1075
+ );
1076
+ }
1077
+ function snapshotHasVisibleValidation(snap) {
1078
+ return (snap.texts ?? []).some((text) => typeof text === "string" && FORM_VALIDATION_TEXT_RE.test(text));
1079
+ }
1080
+ function snapshotShowsOpenCreateForm(snap, activePath = []) {
1081
+ if (!snap) return false;
1082
+ const routeNames = [
1083
+ ...typeof snap.route === "string" && snap.route ? [snap.route] : [],
1084
+ ...activePath
1085
+ ];
1086
+ const hasFormInputs = (snap.inputs?.length ?? 0) > 0;
1087
+ const hasValidation = snapshotHasVisibleValidation(snap);
1088
+ const hasFormContext = routeHintsCreateForm(routeNames) || hasFormInputs || hasValidation;
1089
+ if (!hasFormContext) return false;
1090
+ if (snap.loading === true) return true;
1091
+ if (snapshotHasVisibleSubmitButton(snap)) return true;
1092
+ if (snapshotHasSubmitControl(snap) && hasFormInputs) return true;
1093
+ if (hasValidation) return true;
1094
+ return false;
1095
+ }
1096
+ function formFillLackedWriteEffect(action, snap, activePath = []) {
1097
+ if (action.type !== "form_fill") return false;
1098
+ if (!snapshotShowsOpenCreateForm(snap, activePath)) return false;
1099
+ const payload = action.payload;
1100
+ if (payload?.submitAfterFill === true) return true;
1101
+ return Array.isArray(payload?.fields) && payload.fields.length > 0;
1102
+ }
1103
+ function summarizeAction(action) {
1104
+ const p = action.payload;
1105
+ switch (action.type) {
1106
+ case "navigate":
1107
+ return `navigate to ${p?.screenName ?? "?"}`;
1108
+ case "form_fill": {
1109
+ const fields = p?.fields ?? [];
1110
+ if (fields.length === 0) return "form_fill (no fields)";
1111
+ const parts = fields.slice(0, 3).map((f) => `${f.fieldId}=${JSON.stringify(f.value)}`);
1112
+ const extra = fields.length > 3 ? ` +${fields.length - 3} more` : "";
1113
+ return `form_fill ${parts.join(", ")}${extra}`;
1114
+ }
1115
+ case "ui_interaction":
1116
+ return `${p?.action ?? "press"} on ${p?.componentId ?? "?"}`;
1117
+ case "scroll_list": {
1118
+ const target = typeof p?.toIndex === "number" ? `to item #${p.toIndex}` : p?.direction ?? "down";
1119
+ return `scroll_list ${p?.listId ? `"${p.listId}" ` : ""}${target}`;
1120
+ }
1121
+ case "confirm":
1122
+ return `confirm: ${p?.message ?? p?.title ?? ""}`;
1123
+ default:
1124
+ return action.type;
1125
+ }
1126
+ }
1127
+ function extractPressedComponentId(actions) {
1128
+ for (let i = actions.length - 1; i >= 0; i--) {
1129
+ const a = actions[i];
1130
+ if (!a || a.type !== "ui_interaction") continue;
1131
+ const p = a.payload;
1132
+ const interaction = p?.action ?? "press";
1133
+ if (interaction !== "press" && interaction !== "longPress") continue;
1134
+ const id = p?.componentId;
1135
+ if (typeof id === "string" && id.length > 0) return id;
1136
+ }
1137
+ return null;
1138
+ }
1139
+ function extractNavigateTargetScreen(actions) {
1140
+ for (let i = actions.length - 1; i >= 0; i--) {
1141
+ const a = actions[i];
1142
+ if (!a || a.type !== "navigate") continue;
1143
+ const screenName = a.payload?.screenName;
1144
+ if (typeof screenName === "string" && screenName.length > 0) return screenName;
1145
+ }
1146
+ return null;
1147
+ }
1148
+ function idLooselyMatches(candidate, target) {
1149
+ if (!candidate || !target) return false;
1150
+ if (candidate === target) return true;
1151
+ const c = candidate.toLowerCase();
1152
+ const t = target.toLowerCase();
1153
+ if (c === t) return true;
1154
+ return c.endsWith(`-${t}`) || c.endsWith(`_${t}`) || t.endsWith(`-${c}`) || t.endsWith(`_${c}`);
1155
+ }
1156
+ var ESCALATION_RECONNECT_MAX_MS = 3e4;
1157
+ var MAX_RECOVERY_ATTEMPTS_PER_ACTION = 2;
1158
+ var INITIAL_STATE = {
1159
+ messages: [],
1160
+ isLoading: false,
1161
+ loadingStatusKey: "thinking",
1162
+ error: null,
1163
+ pendingActions: [],
1164
+ escalation: null
1165
+ };
1166
+ var ChatSessionMachine = class {
1167
+ deps;
1168
+ options = {};
1169
+ state = INITIAL_STATE;
1170
+ listeners = /* @__PURE__ */ new Set();
1171
+ messageIdCounter = 0;
1172
+ currentTurn = null;
1173
+ continuationInFlight = false;
1174
+ /**
1175
+ * BACKLOG 3.2 — per-message-thread retry counter, keyed on action
1176
+ * fingerprint. Reset on every fresh `sendMessage`, persists across
1177
+ * /agent/continue hops within the same user turn so two retries of
1178
+ * the same action add up across hops.
1179
+ */
1180
+ recoveryCountByFingerprint = /* @__PURE__ */ new Map();
1181
+ /** Abort controller for the in-flight generation (cancelMessage). */
1182
+ abortController = null;
1183
+ // ── Escalation channel bookkeeping (docs/human-escalation.md) ──
1184
+ escalationStreamClose = null;
1185
+ escalationReconnectAttempt = 0;
1186
+ escalationReconnectTimer = null;
1187
+ /** createdAt of the newest operator message — reconnect catch-up cursor. */
1188
+ escalationSince = void 0;
1189
+ /** Operator message ids already appended (SSE replay dedupe). */
1190
+ seenHumanMessageIds = /* @__PURE__ */ new Set();
1191
+ disposed = false;
1192
+ constructor(deps, options = {}) {
1193
+ this.deps = deps;
1194
+ this.options = options;
1195
+ this.subscribeState = this.subscribeState.bind(this);
1196
+ this.getState = this.getState.bind(this);
1197
+ this.handleEvent = this.handleEvent.bind(this);
1198
+ this.sendMessage = this.sendMessage.bind(this);
1199
+ this.cancelMessage = this.cancelMessage.bind(this);
1200
+ this.clearMessages = this.clearMessages.bind(this);
1201
+ this.clearError = this.clearError.bind(this);
1202
+ this.requestHuman = this.requestHuman.bind(this);
1203
+ }
1204
+ // ── State plumbing ───────────────────────────────────────────
1205
+ getState() {
1206
+ return this.state;
1207
+ }
1208
+ subscribeState(listener) {
1209
+ this.listeners.add(listener);
1210
+ return () => {
1211
+ this.listeners.delete(listener);
1212
+ };
1213
+ }
1214
+ /** Update mutable options (errorPrefix, streaming, escalation copy). */
1215
+ setOptions(options) {
1216
+ this.options = options;
1217
+ }
1218
+ /** Tear down timers and the escalation stream. Safe to call twice. */
1219
+ dispose() {
1220
+ this.disposed = true;
1221
+ this.closeEscalationStream();
1222
+ }
1223
+ setState(partial) {
1224
+ this.state = { ...this.state, ...partial };
1225
+ for (const listener of this.listeners) listener();
1226
+ }
1227
+ warn(message, ...args) {
1228
+ (this.deps.warn ?? console.warn)(message, ...args);
1229
+ }
1230
+ // ── Message helpers ──────────────────────────────────────────
1231
+ addAssistantMessage(message) {
1232
+ const visibleContent = message.role === "assistant" ? sanitizeVisibleAssistantContent(message.content) : message.content;
1233
+ const candidate = { ...message, content: visibleContent };
1234
+ if (isInternalTraceMessage(candidate)) {
1235
+ recordAppilotsDebugTrace({
1236
+ type: "agent:trace",
1237
+ timestamp: Date.now(),
1238
+ messageId: candidate.id,
1239
+ summary: "Suppressed internal assistant trace from visible chat",
1240
+ data: { role: candidate.role, content: message.content }
1241
+ });
1242
+ const fallback = humanizeAssistantFallback(message.content);
1243
+ if (fallback) {
1244
+ this.setState({ messages: [...this.state.messages, { ...candidate, content: fallback }] });
1245
+ }
1246
+ return;
1247
+ }
1248
+ this.setState({ messages: [...this.state.messages, candidate] });
1249
+ }
1250
+ appendLocalSystemMessage(content, metadata) {
1251
+ if (!content) return;
1252
+ this.setState({
1253
+ messages: [
1254
+ ...this.state.messages,
1255
+ {
1256
+ id: `system_${++this.messageIdCounter}`,
1257
+ role: "system",
1258
+ content,
1259
+ timestamp: Date.now(),
1260
+ ...metadata ? { metadata } : {}
1261
+ }
1262
+ ]
1263
+ });
1264
+ }
1265
+ // ── Escalation: request, stream, and message routing ─────────
1266
+ async requestHuman(reason = "user_requested") {
1267
+ if (this.state.escalation) return;
1268
+ try {
1269
+ const lastError = this.state.messages.slice().reverse().find((m) => m.role === "assistant" && m.id.startsWith("error_"));
1270
+ const result = await this.deps.client.requestEscalation(reason, lastError?.content);
1271
+ this.setState({ escalation: { id: result.escalationId, status: result.status } });
1272
+ this.escalationReconnectAttempt = 0;
1273
+ this.appendLocalSystemMessage(this.options.escalationStrings?.requested ?? "");
1274
+ this.deps.emit({
1275
+ type: "chat:escalation:start",
1276
+ timestamp: Date.now(),
1277
+ data: { escalationId: result.escalationId, reason }
1278
+ });
1279
+ this.openEscalationStream(result.escalationId);
1280
+ } catch (err) {
1281
+ this.setState({ error: err instanceof Error ? err.message : "Failed to request a human" });
1282
+ }
1283
+ }
1284
+ /**
1285
+ * Live channel: operator replies + status changes. Reopens with
1286
+ * capped exponential backoff (and a `since` catch-up cursor) when
1287
+ * the transport drops, for as long as the escalation stays open.
1288
+ */
1289
+ openEscalationStream(escalationId) {
1290
+ this.closeEscalationStream();
1291
+ if (this.disposed) return;
1292
+ this.escalationStreamClose = this.deps.client.openEscalationStream(escalationId, {
1293
+ since: this.escalationSince,
1294
+ onHumanMessage: (message) => {
1295
+ this.escalationReconnectAttempt = 0;
1296
+ if (this.seenHumanMessageIds.has(message.id)) return;
1297
+ this.seenHumanMessageIds.add(message.id);
1298
+ this.escalationSince = message.createdAt;
1299
+ this.setState({
1300
+ messages: [
1301
+ ...this.state.messages,
1302
+ {
1303
+ id: message.id,
1304
+ role: "human_agent",
1305
+ content: message.content,
1306
+ timestamp: Date.now()
1307
+ }
1308
+ ]
1309
+ });
1310
+ this.deps.emit({
1311
+ type: "chat:escalation:message",
1312
+ timestamp: Date.now(),
1313
+ data: { escalationId, messageId: message.id }
1314
+ });
1315
+ },
1316
+ onStatus: (status, operatorName) => {
1317
+ this.escalationReconnectAttempt = 0;
1318
+ const current = this.state.escalation;
1319
+ if (status === "active") {
1320
+ if (!current || current.id !== escalationId) return;
1321
+ if (current.status === "active") return;
1322
+ this.appendLocalSystemMessage(this.options.escalationStrings?.connected ?? "");
1323
+ this.setState({ escalation: { ...current, status: "active", operatorName } });
1324
+ } else if (status === "resolved") {
1325
+ this.appendLocalSystemMessage(this.options.escalationStrings?.resolved ?? "");
1326
+ this.setState({ escalation: null });
1327
+ this.closeEscalationStream();
1328
+ this.deps.emit({
1329
+ type: "chat:escalation:end",
1330
+ timestamp: Date.now(),
1331
+ data: { escalationId }
1332
+ });
1333
+ }
1334
+ },
1335
+ onClose: () => {
1336
+ const current = this.state.escalation;
1337
+ if (this.disposed || !current || current.id !== escalationId) return;
1338
+ const attempt = ++this.escalationReconnectAttempt;
1339
+ const delay = Math.min(ESCALATION_RECONNECT_MAX_MS, 1e3 * 2 ** Math.min(attempt, 5));
1340
+ this.escalationReconnectTimer = setTimeout(() => {
1341
+ const still = this.state.escalation;
1342
+ if (this.disposed || !still || still.id !== escalationId) return;
1343
+ this.openEscalationStream(escalationId);
1344
+ }, delay);
1345
+ }
1346
+ });
1347
+ }
1348
+ closeEscalationStream() {
1349
+ if (this.escalationReconnectTimer) {
1350
+ clearTimeout(this.escalationReconnectTimer);
1351
+ this.escalationReconnectTimer = null;
1352
+ }
1353
+ if (this.escalationStreamClose) {
1354
+ const close = this.escalationStreamClose;
1355
+ this.escalationStreamClose = null;
1356
+ close();
1357
+ }
1358
+ }
1359
+ /** Route a user message to the human channel while escalated. */
1360
+ async sendEscalationMessage(content) {
1361
+ const current = this.state.escalation;
1362
+ if (!current) return;
1363
+ const userMessage = {
1364
+ id: `local_${++this.messageIdCounter}`,
1365
+ role: "user",
1366
+ content,
1367
+ timestamp: Date.now()
1368
+ };
1369
+ this.setState({ messages: [...this.state.messages, userMessage] });
1370
+ try {
1371
+ await this.deps.client.sendEscalationMessage(current.id, content);
1372
+ } catch (err) {
1373
+ this.setState({ error: err instanceof Error ? err.message : "Failed to send message" });
1374
+ }
1375
+ }
1376
+ // ── Start a turn (or sub-turn): register actions + emit events ──
1377
+ beginTurn(assistantMessageId, actions, hop) {
1378
+ if (!actions || actions.length === 0) {
1379
+ this.currentTurn = null;
1380
+ return;
1381
+ }
1382
+ const stamped = actions.map((a) => ({ ...a, messageId: assistantMessageId }));
1383
+ this.setState({ pendingActions: [...this.state.pendingActions, ...stamped] });
1384
+ const turn = {
1385
+ messageId: assistantMessageId,
1386
+ actionIds: new Set(stamped.map((a) => a.id)),
1387
+ results: /* @__PURE__ */ new Map(),
1388
+ reportedIds: /* @__PURE__ */ new Set(),
1389
+ hop,
1390
+ hadNavigate: stamped.some((a) => a.type === "navigate"),
1391
+ // Snapshot the current screen NOW, before any navigate
1392
+ // dispatches. The platform settle will use this as the
1393
+ // baseline to detect that the navigate completed.
1394
+ preNavigateScreen: this.deps.adapter.getCurrentScreen(),
1395
+ // Full route+params signature — lets settle detect navigate
1396
+ // to the same route name with different params.
1397
+ preNavigateSignature: this.deps.adapter.getCurrentScreenSignature()
1398
+ };
1399
+ const originScreen = this.deps.adapter.getCurrentScreen() ?? void 0;
1400
+ for (const a of stamped) {
1401
+ turn.results.set(a.id, {
1402
+ type: a.type,
1403
+ success: false,
1404
+ summary: summarizeAction(a),
1405
+ fingerprint: fingerprintAction(a, originScreen)
1406
+ });
1407
+ }
1408
+ this.currentTurn = turn;
1409
+ for (const action of stamped) {
1410
+ this.deps.emit({
1411
+ type: "agent:action:start",
1412
+ timestamp: Date.now(),
1413
+ data: { action }
1414
+ });
1415
+ }
1416
+ if (turnHasConfirmActions(stamped)) {
1417
+ this.setState({ isLoading: false, loadingStatusKey: "thinking" });
1418
+ }
1419
+ }
1420
+ registerSynthesizedTurnAction(action) {
1421
+ const turn = this.currentTurn;
1422
+ if (!turn || turn.actionIds.has(action.id)) return;
1423
+ const stamped = { ...action, messageId: turn.messageId };
1424
+ turn.actionIds.add(stamped.id);
1425
+ turn.results.set(stamped.id, {
1426
+ type: stamped.type,
1427
+ success: false,
1428
+ summary: summarizeAction(stamped),
1429
+ fingerprint: fingerprintAction(stamped, this.deps.adapter.getCurrentScreen() ?? void 0)
1430
+ });
1431
+ if (!this.state.pendingActions.some((candidate) => candidate.id === stamped.id)) {
1432
+ this.setState({ pendingActions: [...this.state.pendingActions, stamped] });
1433
+ }
1434
+ if (stamped.type === "confirm") {
1435
+ this.setState({ isLoading: false, loadingStatusKey: "thinking" });
1436
+ }
1437
+ }
1438
+ // ── Continuation: when all current-turn actions finish, ask the
1439
+ // model what to do next, given the outcomes + fresh snapshot.
1440
+ async runContinuation(turn) {
1441
+ if (this.continuationInFlight) return;
1442
+ this.continuationInFlight = true;
1443
+ try {
1444
+ this.setState({ isLoading: true, loadingStatusKey: "statusWaitingApp" });
1445
+ const toolWithoutEffectIds = /* @__PURE__ */ new Set();
1446
+ const turnActions = this.state.pendingActions.filter(
1447
+ (a) => turn.actionIds.has(a.id)
1448
+ );
1449
+ const pressedId = extractPressedComponentId(turnActions);
1450
+ const navigateTargetScreen = extractNavigateTargetScreen(turnActions);
1451
+ const settle = await this.deps.adapter.settleTurn({
1452
+ hadNavigate: turn.hadNavigate,
1453
+ preNavigateScreen: turn.preNavigateScreen,
1454
+ preNavigateSignature: turn.preNavigateSignature,
1455
+ turnActions,
1456
+ results: Array.from(turn.results.entries()).map(([actionId, r]) => ({
1457
+ actionId,
1458
+ type: r.type,
1459
+ success: r.success
1460
+ })),
1461
+ pressedComponentId: pressedId,
1462
+ navigateTargetScreen
1463
+ });
1464
+ const loadingPending = settle.loadingPending;
1465
+ for (const id of settle.toolWithoutEffectIds) toolWithoutEffectIds.add(id);
1466
+ const context = this.deps.adapter.buildContext({ loadingPending });
1467
+ const snap = context.snapshot;
1468
+ const activePath = context.navigationState?.activePath ?? [];
1469
+ for (const action of turnActions) {
1470
+ const result = turn.results.get(action.id);
1471
+ if (result?.success && formFillLackedWriteEffect(action, snap, activePath)) {
1472
+ toolWithoutEffectIds.add(action.id);
1473
+ this.warn(
1474
+ `tool-without-effect: form_fill succeeded but create form is still open (actionId="${action.id}")`
1475
+ );
1476
+ }
1477
+ }
1478
+ const results = Array.from(turn.actionIds).map((actionId) => {
1479
+ const r = turn.results.get(actionId);
1480
+ if (!r) {
1481
+ return {
1482
+ actionId,
1483
+ type: "unknown",
1484
+ success: false,
1485
+ error: "no result reported",
1486
+ summary: actionId,
1487
+ diagnose: void 0,
1488
+ recoveryAttempt: 0,
1489
+ recoveryExhausted: false,
1490
+ toolWithoutEffect: false,
1491
+ effect: "unknown"
1492
+ };
1493
+ }
1494
+ const hasToolWithoutEffect = toolWithoutEffectIds.has(actionId);
1495
+ let recoveryAttempt = 0;
1496
+ let recoveryExhausted = false;
1497
+ if (!r.success || hasToolWithoutEffect) {
1498
+ const previous = this.recoveryCountByFingerprint.get(r.fingerprint) ?? 0;
1499
+ const next = previous + 1;
1500
+ this.recoveryCountByFingerprint.set(r.fingerprint, next);
1501
+ recoveryAttempt = next;
1502
+ recoveryExhausted = next >= MAX_RECOVERY_ATTEMPTS_PER_ACTION;
1503
+ }
1504
+ return {
1505
+ actionId,
1506
+ type: r.type,
1507
+ success: r.success,
1508
+ error: r.error,
1509
+ summary: r.summary,
1510
+ diagnose: r.diagnose,
1511
+ recoveryAttempt,
1512
+ recoveryExhausted,
1513
+ toolWithoutEffect: hasToolWithoutEffect,
1514
+ effect: hasToolWithoutEffect ? "none" : r.effect,
1515
+ userVisibleStatusKey: r.userVisibleStatusKey
1516
+ };
1517
+ });
1518
+ const exhausted = results.filter((r) => r.recoveryExhausted);
1519
+ this.setState({
1520
+ loadingStatusKey: results.some((r) => !r.success || r.toolWithoutEffect) ? "statusAdjusting" : "statusAnalyzing"
1521
+ });
1522
+ console.log(
1523
+ `[Appilots] ChatSessionMachine: running continuation hop=${turn.hop} (${results.filter((r) => r.success).length}/${results.length} succeeded` + (exhausted.length > 0 ? `, ${exhausted.length} recovery-exhausted` : "") + ")"
1524
+ );
1525
+ const response = await this.deps.client.continueAgent(results, context, turn.hop);
1526
+ this.addAssistantMessage(response.message);
1527
+ this.deps.emit({
1528
+ type: "agent:message",
1529
+ timestamp: Date.now(),
1530
+ data: { message: response.message, usage: response.usage }
1531
+ });
1532
+ if (response.isDone || !response.actions || response.actions.length === 0) {
1533
+ this.currentTurn = null;
1534
+ this.setState({ isLoading: false, loadingStatusKey: "thinking" });
1535
+ return;
1536
+ }
1537
+ this.beginTurn(response.message.id, response.actions, response.hop);
1538
+ } catch (err) {
1539
+ const raw = err instanceof Error ? err.message : "Continuation failed";
1540
+ this.warn("ChatSessionMachine: continuation failed \u2014", raw);
1541
+ const recentUserTexts = this.state.messages.filter((m) => m.role === "user").slice(-4).map((m) => m.content);
1542
+ const friendly = buildContinuationFailureMessage(raw, recentUserTexts);
1543
+ this.addAssistantMessage({
1544
+ id: `error_${++this.messageIdCounter}`,
1545
+ role: "assistant",
1546
+ content: friendly,
1547
+ timestamp: Date.now()
1548
+ });
1549
+ if (!this.state.escalation && this.options.escalationStrings?.offer) {
1550
+ this.appendLocalSystemMessage(this.options.escalationStrings.offer, {
1551
+ escalationOffer: true
1552
+ });
1553
+ }
1554
+ if (turn) {
1555
+ this.setState({
1556
+ pendingActions: this.state.pendingActions.map(
1557
+ (a) => turn.actionIds.has(a.id) && (a.status === "pending" || a.status === "executing") ? { ...a, status: "failed", error: friendly } : a
1558
+ )
1559
+ });
1560
+ for (const actionId of turn.actionIds) {
1561
+ if (turn.reportedIds.has(actionId)) continue;
1562
+ this.deps.emit({
1563
+ type: "agent:action:error",
1564
+ timestamp: Date.now(),
1565
+ data: { actionId, error: friendly, abandoned: true }
1566
+ });
1567
+ }
1568
+ }
1569
+ this.currentTurn = null;
1570
+ this.setState({ isLoading: false, loadingStatusKey: "thinking" });
1571
+ } finally {
1572
+ this.continuationInFlight = false;
1573
+ }
1574
+ }
1575
+ // ── Event intake: action lifecycle events drive the loop ─────
1576
+ handleEvent(event) {
1577
+ if (event.type === "agent:action:start") {
1578
+ const action = event.data?.action;
1579
+ if (!action?.id || action.type !== "confirm") return;
1580
+ const injectedBy = action.payload?.injectedBy;
1581
+ if (injectedBy !== "sdk-local") return;
1582
+ this.registerSynthesizedTurnAction(action);
1583
+ return;
1584
+ }
1585
+ if (event.type !== "agent:action:complete" && event.type !== "agent:action:error") return;
1586
+ const turn = this.currentTurn;
1587
+ if (!turn) return;
1588
+ const actionId = event.data?.actionId;
1589
+ if (!actionId || !turn.actionIds.has(actionId)) return;
1590
+ if (turn.reportedIds.has(actionId)) return;
1591
+ const success = event.type === "agent:action:complete";
1592
+ const errorMsg = event.data?.error || void 0;
1593
+ const diagnose = event.data?.diagnose;
1594
+ const effect = event.data?.effect;
1595
+ const userVisibleStatusKey = event.data?.userVisibleStatusKey;
1596
+ const existing = turn.results.get(actionId);
1597
+ if (existing) {
1598
+ existing.success = success;
1599
+ existing.error = errorMsg;
1600
+ existing.diagnose = diagnose;
1601
+ existing.effect = effect;
1602
+ existing.userVisibleStatusKey = userVisibleStatusKey;
1603
+ }
1604
+ turn.reportedIds.add(actionId);
1605
+ if (turn.reportedIds.size >= turn.actionIds.size) {
1606
+ void this.runContinuation(turn);
1607
+ }
1608
+ }
1609
+ // ── Send / cancel / clear ────────────────────────────────────
1610
+ async sendMessage(content) {
1611
+ if (!content.trim() || this.state.isLoading) return;
1612
+ if (this.state.escalation) {
1613
+ await this.sendEscalationMessage(content.trim());
1614
+ return;
1615
+ }
1616
+ const errorPrefix = this.options.errorPrefix ?? "Algo deu errado";
1617
+ const streamingEnabled = this.options.streaming !== false;
1618
+ this.setState({ error: null, isLoading: true, loadingStatusKey: "statusAnalyzing" });
1619
+ this.currentTurn = null;
1620
+ this.recoveryCountByFingerprint = /* @__PURE__ */ new Map();
1621
+ const userMessage = {
1622
+ id: `local_${++this.messageIdCounter}`,
1623
+ role: "user",
1624
+ content: content.trim(),
1625
+ timestamp: Date.now()
1626
+ };
1627
+ this.setState({ messages: [...this.state.messages, userMessage] });
1628
+ const streamingId = `streaming_${++this.messageIdCounter}`;
1629
+ let streamedVisible = false;
1630
+ const removeStreamingPlaceholder = () => {
1631
+ if (!streamedVisible) return;
1632
+ streamedVisible = false;
1633
+ this.setState({ messages: this.state.messages.filter((m) => m.id !== streamingId) });
1634
+ };
1635
+ const abortController = new AbortController();
1636
+ this.abortController = abortController;
1637
+ try {
1638
+ const context = this.deps.adapter.buildContext();
1639
+ console.log(
1640
+ `[Appilots] ChatSessionMachine sendMessage: context \u2014 currentScreen="${context.currentScreen ?? "unknown"}", snapshot={texts:${context.snapshot?.texts?.length ?? 0}, inputs:${context.snapshot?.inputs?.length ?? 0}, buttons:${context.snapshot?.buttons?.length ?? 0}, elements:${context.snapshot?.elements?.length ?? 0}, lists:${context.snapshot?.lists?.length ?? 0}, listItems:${(context.snapshot?.lists ?? []).reduce((acc, l) => acc + (l.items?.length ?? 0), 0)}, choiceGroups:${context.snapshot?.choiceGroups?.length ?? 0}}`
1641
+ );
1642
+ let response;
1643
+ if (streamingEnabled) {
1644
+ try {
1645
+ response = await this.deps.client.sendMessageStream(content.trim(), context, {
1646
+ signal: abortController.signal,
1647
+ onTextDelta: (_chunk, fullText) => {
1648
+ const visible = sanitizeVisibleAssistantContent(fullText);
1649
+ if (!streamedVisible) {
1650
+ streamedVisible = true;
1651
+ this.setState({
1652
+ messages: [
1653
+ ...this.state.messages,
1654
+ { id: streamingId, role: "assistant", content: visible, timestamp: Date.now() }
1655
+ ]
1656
+ });
1657
+ } else {
1658
+ this.setState({
1659
+ messages: this.state.messages.map(
1660
+ (m) => m.id === streamingId ? { ...m, content: visible } : m
1661
+ )
1662
+ });
1663
+ }
1664
+ }
1665
+ });
1666
+ } catch (err) {
1667
+ if (err instanceof StreamTransportError) {
1668
+ console.warn(
1669
+ `[Appilots] streaming unavailable, falling back to JSON: ${err.message}`
1670
+ );
1671
+ response = await this.deps.client.sendMessage(content.trim(), context);
1672
+ } else {
1673
+ throw err;
1674
+ }
1675
+ }
1676
+ } else {
1677
+ response = await this.deps.client.sendMessage(content.trim(), context);
1678
+ }
1679
+ console.log(
1680
+ `[Appilots] ChatSessionMachine sendMessage: response received \u2014 actions=${response.actions?.length ?? 0}, hasSummary=${!!response.summaryMessage}`
1681
+ );
1682
+ removeStreamingPlaceholder();
1683
+ this.addAssistantMessage(response.message);
1684
+ this.beginTurn(response.message.id, response.actions, 1);
1685
+ if (response.summaryMessage) {
1686
+ this.addAssistantMessage(response.summaryMessage);
1687
+ }
1688
+ this.deps.emit({
1689
+ type: "agent:message",
1690
+ timestamp: Date.now(),
1691
+ data: { message: response.message, usage: response.usage }
1692
+ });
1693
+ if (!response.actions || response.actions.length === 0) {
1694
+ this.setState({ isLoading: false, loadingStatusKey: "thinking" });
1695
+ }
1696
+ } catch (err) {
1697
+ if (err instanceof StreamAbortError || err instanceof Error && err.name === "AbortError") {
1698
+ this.setState({ isLoading: false, loadingStatusKey: "thinking" });
1699
+ return;
1700
+ }
1701
+ removeStreamingPlaceholder();
1702
+ const errorMessage = err instanceof Error ? err.message : "Failed to send message";
1703
+ this.setState({ error: errorMessage });
1704
+ this.addAssistantMessage({
1705
+ id: `error_${++this.messageIdCounter}`,
1706
+ role: "assistant",
1707
+ content: `${errorPrefix}: ${errorMessage}`,
1708
+ timestamp: Date.now()
1709
+ });
1710
+ this.setState({ isLoading: false, loadingStatusKey: "thinking" });
1711
+ } finally {
1712
+ if (this.abortController === abortController) {
1713
+ this.abortController = null;
1714
+ }
1715
+ }
1716
+ }
1717
+ /**
1718
+ * Cancels the in-flight generation. Streamed partial text is kept in
1719
+ * the transcript; a no-op when nothing is in flight. Cancellation
1720
+ * only covers the generation phase — once actions start executing
1721
+ * they run to completion.
1722
+ */
1723
+ cancelMessage() {
1724
+ this.abortController?.abort();
1725
+ }
1726
+ clearMessages() {
1727
+ this.setState({
1728
+ messages: [],
1729
+ pendingActions: [],
1730
+ error: null,
1731
+ isLoading: false,
1732
+ loadingStatusKey: "thinking"
1733
+ });
1734
+ this.currentTurn = null;
1735
+ this.continuationInFlight = false;
1736
+ this.recoveryCountByFingerprint = /* @__PURE__ */ new Map();
1737
+ if (this.state.escalation) {
1738
+ void this.deps.client.abandonEscalation(this.state.escalation.id);
1739
+ }
1740
+ this.setState({ escalation: null });
1741
+ this.closeEscalationStream();
1742
+ this.escalationSince = void 0;
1743
+ this.seenHumanMessageIds = /* @__PURE__ */ new Set();
1744
+ this.escalationReconnectAttempt = 0;
1745
+ void this.deps.client.destroySession();
1746
+ }
1747
+ clearError() {
1748
+ this.setState({ error: null });
1749
+ }
1750
+ };
1751
+ function readPayload(action) {
1752
+ return action.payload ?? {};
1753
+ }
1754
+ function isStructuredRisky(value) {
1755
+ return value === true || value === "destructive" || value === "danger" || value === "high";
1756
+ }
1757
+ function actionMetadataRequiresConfirmation(meta) {
1758
+ if (meta.requiresConfirmation === true || meta.destructive === true || meta.effect === "destructive" || meta.riskLevel === "high" || meta.risk === "destructive") {
1759
+ return { required: true, label: typeof meta.label === "string" ? meta.label : void 0, source: "screen-metadata" };
1760
+ }
1761
+ return { required: false };
1762
+ }
1763
+ function payloadRequiresConfirmation(action) {
1764
+ const payload = readPayload(action);
1765
+ if (action.destructive === true || payload.destructive === true || payload.requiresConfirmation === true || isStructuredRisky(payload.effect) || isStructuredRisky(payload.risk) || isStructuredRisky(payload.riskLevel) || isStructuredRisky(payload.actionRisk)) {
1766
+ return {
1767
+ required: true,
1768
+ label: typeof payload.actionDescription === "string" && payload.actionDescription || typeof payload.label === "string" && payload.label || void 0,
1769
+ source: "action-policy"
1770
+ };
1771
+ }
1772
+ const policy = payload._appilotsPolicy;
1773
+ if (policy && typeof policy === "object") {
1774
+ const p = policy;
1775
+ if (p.requiresConfirmation === true || isStructuredRisky(p.effect) || isStructuredRisky(p.risk) || isStructuredRisky(p.riskLevel)) {
1776
+ return {
1777
+ required: true,
1778
+ label: typeof p.summary === "string" ? p.summary : void 0,
1779
+ source: "inline-policy"
1780
+ };
1781
+ }
1782
+ }
1783
+ return { required: false };
1784
+ }
1785
+ function isConfirmGateFor(confirm, target) {
1786
+ if (confirm.type !== "confirm") return false;
1787
+ const payload = readPayload(confirm);
1788
+ const injectedBy = payload.injectedBy;
1789
+ return payload.gatedActionId === target.id && (injectedBy === "permissions" || injectedBy === "sdk-local");
1790
+ }
1791
+ function findGatingConfirm(target, actions) {
1792
+ return actions.find((a) => isConfirmGateFor(a, target));
1793
+ }
1794
+ function toDestructiveConfirmPayload(confirm, policy, fallbackLabel) {
1795
+ const payload = readPayload(confirm);
1796
+ const existingDescription = typeof payload.actionDescription === "string" && payload.actionDescription ? payload.actionDescription : void 0;
1797
+ const labelText = policy.label ?? existingDescription ?? fallbackLabel ?? "esta a\xE7\xE3o";
1798
+ return {
1799
+ ...payload,
1800
+ title: "A\xE7\xE3o destrutiva",
1801
+ message: `Tem certeza que deseja "${labelText}"?`,
1802
+ actionDescription: labelText,
1803
+ severity: "danger",
1804
+ destructive: true,
1805
+ confirmLabel: typeof payload.confirmLabel === "string" && payload.confirmLabel ? payload.confirmLabel : "Sim, executar",
1806
+ cancelLabel: typeof payload.cancelLabel === "string" && payload.cancelLabel ? payload.cancelLabel : "Cancelar"
1807
+ };
1808
+ }
1809
+ function confirmNeedsDestructiveUpgrade(confirm) {
1810
+ const payload = readPayload(confirm);
1811
+ return confirm.destructive !== true || payload.destructive !== true || payload.severity !== "danger";
1812
+ }
1813
+ function actionHasCompletedConfirmGate(action, actions) {
1814
+ return actions.some((candidate) => {
1815
+ if (!isConfirmGateFor(candidate, action)) return false;
1816
+ if (candidate.status !== "completed") return false;
1817
+ const payload = readPayload(candidate);
1818
+ return payload.destructive === true || payload.severity === "danger";
1819
+ });
1820
+ }
1821
+ var ActionQueueMachine = class {
1822
+ deps;
1823
+ options;
1824
+ state = { actions: [] };
1825
+ listeners = /* @__PURE__ */ new Set();
1826
+ /** Action ids already dispatched — never auto-executed twice. */
1827
+ processed = /* @__PURE__ */ new Set();
1828
+ /** Guards against overlapping sequential runs. */
1829
+ draining = false;
1830
+ /** Set when new work arrives while a drain is in flight. */
1831
+ drainRequested = false;
1832
+ /** Set while a drain is queued on the microtask queue. */
1833
+ drainScheduled = false;
1834
+ constructor(deps, options = {}) {
1835
+ this.deps = deps;
1836
+ this.options = options;
1837
+ this.getState = this.getState.bind(this);
1838
+ this.subscribeState = this.subscribeState.bind(this);
1839
+ this.handleEvent = this.handleEvent.bind(this);
1840
+ this.approveAction = this.approveAction.bind(this);
1841
+ this.rejectAction = this.rejectAction.bind(this);
1842
+ }
1843
+ // ── State plumbing ───────────────────────────────────────────
1844
+ getState() {
1845
+ return this.state;
1846
+ }
1847
+ subscribeState(listener) {
1848
+ this.listeners.add(listener);
1849
+ return () => {
1850
+ this.listeners.delete(listener);
1851
+ };
1852
+ }
1853
+ setOptions(options) {
1854
+ this.options = options;
1855
+ }
1856
+ setActions(next) {
1857
+ if (next === this.state.actions) return;
1858
+ this.state = { actions: next };
1859
+ for (const listener of this.listeners) listener();
1860
+ }
1861
+ updateActions(updater) {
1862
+ this.setActions(updater(this.state.actions));
1863
+ }
1864
+ warn(message, ...args) {
1865
+ (this.deps.warn ?? console.warn)(message, ...args);
1866
+ }
1867
+ // ── Event intake: new actions from the agent ─────────────────
1868
+ handleEvent(event) {
1869
+ if (event.type !== "agent:action:start") return;
1870
+ const action = event.data?.action;
1871
+ if (!action?.id) return;
1872
+ console.log(
1873
+ `[Appilots] ActionQueueMachine: received agent:action:start event, action id="${action.id}", type="${action.type}"`
1874
+ );
1875
+ if (this.state.actions.some((a) => a.id === action.id)) return;
1876
+ this.setActions([...this.state.actions, action]);
1877
+ this.scheduleDrain();
1878
+ }
1879
+ /**
1880
+ * Kick the sequential executor. Safe to call at any time — it
1881
+ * coalesces while a drain is already running so a burst of arriving
1882
+ * actions produces exactly one in-order pass.
1883
+ *
1884
+ * The drain is deferred to a microtask, and that is load-bearing, not
1885
+ * a nicety. A turn's actions arrive as a synchronous burst of
1886
+ * `agent:action:start` events, and a confirm gating a destructive
1887
+ * action can arrive AFTER the action it protects. Draining
1888
+ * synchronously on the first event would execute that action while
1889
+ * its gate is still in flight — a delete running before the user
1890
+ * confirmed it. Deferring lets the whole burst land first, so the
1891
+ * gate is visible by the time anything executes. (The React hook this
1892
+ * was extracted from got the same guarantee for free, from state
1893
+ * batching before its effect ran.)
1894
+ */
1895
+ scheduleDrain() {
1896
+ if (!this.options.autoExecute) return;
1897
+ if (this.draining) {
1898
+ this.drainRequested = true;
1899
+ return;
1900
+ }
1901
+ if (this.drainScheduled) return;
1902
+ this.drainScheduled = true;
1903
+ queueMicrotask(() => {
1904
+ this.drainScheduled = false;
1905
+ if (!this.options.autoExecute) return;
1906
+ if (this.draining) {
1907
+ this.drainRequested = true;
1908
+ return;
1909
+ }
1910
+ void this.drain();
1911
+ });
1912
+ }
1913
+ async drain() {
1914
+ this.draining = true;
1915
+ try {
1916
+ do {
1917
+ this.drainRequested = false;
1918
+ await this.executeSequentially();
1919
+ } while (this.drainRequested);
1920
+ } finally {
1921
+ this.draining = false;
1922
+ }
1923
+ }
1924
+ // ── Confirm gating (defense-in-depth) ────────────────────────
1925
+ /**
1926
+ * Client-side defense-in-depth confirm injection.
1927
+ *
1928
+ * When the server's permission filter MISSES a destructive action
1929
+ * (e.g. the MCP doc is stale and doesn't have the destructive flag
1930
+ * for that action, but the live screen metadata DOES have
1931
+ * `requiresConfirmation: true`), the action would otherwise execute
1932
+ * immediately with no user confirmation — exactly the "delete
1933
+ * executed before I confirmed" bug.
1934
+ *
1935
+ * Returns `true` when the target must NOT execute this pass (either
1936
+ * a synthetic confirm was injected, or one is already gating it).
1937
+ */
1938
+ ensureLocalConfirmIfNeeded(target) {
1939
+ if (target.type !== "ui_interaction") return false;
1940
+ const payload = target.payload;
1941
+ const componentId = typeof payload?.componentId === "string" && payload.componentId || typeof payload?.targetId === "string" && payload.targetId || "";
1942
+ let policy = payloadRequiresConfirmation(target);
1943
+ if (!policy.required && componentId) {
1944
+ const screen = this.deps.adapter.getCurrentScreen();
1945
+ const actionsMeta = screen ? this.deps.adapter.getScreenActionsMetadata(screen) : void 0;
1946
+ if (actionsMeta && Array.isArray(actionsMeta)) {
1947
+ const match = actionsMeta.find((a) => {
1948
+ if (typeof a === "string") return false;
1949
+ return metadataMatchesComponentId(a.id, componentId);
1950
+ });
1951
+ if (match && typeof match !== "string") {
1952
+ policy = actionMetadataRequiresConfirmation(match);
1953
+ }
1954
+ }
1955
+ }
1956
+ const existingGate = findGatingConfirm(target, this.state.actions);
1957
+ if (existingGate) {
1958
+ if (policy.required && confirmNeedsDestructiveUpgrade(existingGate)) {
1959
+ this.updateActions(
1960
+ (prev) => prev.map(
1961
+ (a) => a.id === existingGate.id ? {
1962
+ ...a,
1963
+ destructive: true,
1964
+ payload: toDestructiveConfirmPayload(a, policy, componentId || target.id)
1965
+ } : a
1966
+ )
1967
+ );
1968
+ }
1969
+ return false;
1970
+ }
1971
+ if (!policy.required) return false;
1972
+ const confirmId = `confirm-local-${target.id}`;
1973
+ if (this.state.actions.some((a) => a.id === confirmId)) return true;
1974
+ console.log(
1975
+ `[Appilots] ActionQueueMachine: action "${componentId || target.id}" requires confirmation (${policy.source ?? "local-policy"}) \u2014 synthesising local confirm (id="${confirmId}")`
1976
+ );
1977
+ const synth = {
1978
+ id: confirmId,
1979
+ type: "confirm",
1980
+ // Flagged at the top level too, not only inside the payload: this
1981
+ // is the field a chat surface reads to render the danger variant,
1982
+ // and we only synthesize a confirm because the target IS
1983
+ // destructive. The upgrade path above sets it for the same reason.
1984
+ destructive: true,
1985
+ payload: toDestructiveConfirmPayload(
1986
+ {
1987
+ payload: {
1988
+ gatedActionId: target.id,
1989
+ gatedActionType: target.type,
1990
+ injectedBy: "sdk-local"
1991
+ },
1992
+ messageId: target.messageId
1993
+ },
1994
+ policy,
1995
+ componentId || "esta a\xE7\xE3o"
1996
+ ),
1997
+ status: "pending",
1998
+ messageId: target.messageId
1999
+ };
2000
+ this.updateActions((prev) => {
2001
+ const idx = prev.findIndex((a) => a.id === target.id);
2002
+ if (idx < 0) return [...prev, synth];
2003
+ return [...prev.slice(0, idx), synth, ...prev.slice(idx)];
2004
+ });
2005
+ this.deps.emit({
2006
+ type: "agent:action:start",
2007
+ timestamp: Date.now(),
2008
+ data: { action: synth }
2009
+ });
2010
+ return true;
2011
+ }
2012
+ // ── Sequential auto-execution ────────────────────────────────
2013
+ /**
2014
+ * Execute pending actions in order so chained actions (e.g.
2015
+ * form_fill → press button) run in the correct sequence.
2016
+ */
2017
+ async executeSequentially() {
2018
+ const pending = this.state.actions.filter(
2019
+ (a) => a.status === "pending" && !this.processed.has(a.id)
2020
+ );
2021
+ console.log(
2022
+ `[Appilots] ActionQueueMachine: autoExecute check \u2014 ${pending.length} pending action(s) to process`
2023
+ );
2024
+ let lastWasNavigate = false;
2025
+ let preNavScreen = null;
2026
+ let preNavSignature = null;
2027
+ for (const action of pending) {
2028
+ if (action.type === "confirm") {
2029
+ continue;
2030
+ }
2031
+ if (this.ensureLocalConfirmIfNeeded(action)) {
2032
+ continue;
2033
+ }
2034
+ const gate = findGatingConfirm(action, this.state.actions);
2035
+ if (gate) {
2036
+ if (gate.status === "pending" || gate.status === "executing") {
2037
+ console.log(
2038
+ `[Appilots] ActionQueueMachine: action id="${action.id}" is gated by confirm id="${gate.id}" (status=${gate.status}); waiting for user decision`
2039
+ );
2040
+ continue;
2041
+ }
2042
+ if (gate.status === "failed") {
2043
+ console.log(
2044
+ `[Appilots] ActionQueueMachine: confirm id="${gate.id}" was rejected; skipping gated action id="${action.id}"`
2045
+ );
2046
+ this.processed.add(action.id);
2047
+ this.updateActions(
2048
+ (prev) => prev.map(
2049
+ (a) => a.id === action.id ? { ...a, status: "failed", error: "Confirm canceled by user" } : a
2050
+ )
2051
+ );
2052
+ try {
2053
+ await this.deps.client.completeAction(action.id, false, "Confirm canceled by user");
2054
+ } catch {
2055
+ }
2056
+ this.deps.emit({
2057
+ type: "agent:action:error",
2058
+ timestamp: Date.now(),
2059
+ data: {
2060
+ actionId: action.id,
2061
+ actionType: action.type,
2062
+ success: false,
2063
+ error: "Confirm canceled by user"
2064
+ }
2065
+ });
2066
+ continue;
2067
+ }
2068
+ }
2069
+ if (lastWasNavigate) {
2070
+ const settle = await this.deps.adapter.waitForScreenSettle({
2071
+ expectingChange: true,
2072
+ fromScreen: preNavScreen,
2073
+ fromSignature: preNavSignature,
2074
+ maxMs: 3500
2075
+ });
2076
+ console.log(
2077
+ `[Appilots] ActionQueueMachine: post-navigate settle done \u2014 screen="${settle.screen}" transitioned=${settle.transitioned} timedOut=${settle.timedOut} waitedMs=${settle.waitedMs}`
2078
+ );
2079
+ }
2080
+ console.log(
2081
+ `[Appilots] ActionQueueMachine: auto-executing action id="${action.id}" type="${action.type}"`
2082
+ );
2083
+ this.processed.add(action.id);
2084
+ if (action.type === "navigate") {
2085
+ preNavScreen = this.deps.adapter.getCurrentScreen();
2086
+ preNavSignature = this.deps.adapter.getCurrentScreenSignature();
2087
+ }
2088
+ const succeeded = await this.approveAction(action.id);
2089
+ if (!succeeded) {
2090
+ this.warn(
2091
+ `ActionQueueMachine: stopping current action batch after failed action id="${action.id}"`
2092
+ );
2093
+ break;
2094
+ }
2095
+ lastWasNavigate = action.type === "navigate";
2096
+ }
2097
+ }
2098
+ // ── Approve & execute an action ─────────────────────────────
2099
+ async approveAction(actionId) {
2100
+ const action = this.state.actions.find((a) => a.id === actionId);
2101
+ if (!action) return false;
2102
+ console.log(
2103
+ `[Appilots] ActionQueueMachine: action status transition: pending \u2192 executing (id="${actionId}", type="${action.type}")`
2104
+ );
2105
+ this.updateActions(
2106
+ (prev) => prev.map((a) => a.id === actionId ? { ...a, status: "executing" } : a)
2107
+ );
2108
+ try {
2109
+ const confirmedDestructive = actionHasCompletedConfirmGate(action, this.state.actions);
2110
+ const result = await this.deps.adapter.execute(
2111
+ { ...action, status: "executing" },
2112
+ { confirmedDestructive }
2113
+ );
2114
+ if (result.success) {
2115
+ console.log(
2116
+ `[Appilots] ActionQueueMachine: action status transition: executing \u2192 completed (id="${actionId}", type="${action.type}")`
2117
+ );
2118
+ this.updateActions(
2119
+ (prev) => prev.map((a) => a.id === actionId ? { ...a, status: "completed" } : a)
2120
+ );
2121
+ try {
2122
+ await this.deps.client.completeAction(actionId, true);
2123
+ } catch {
2124
+ }
2125
+ this.scheduleDrain();
2126
+ return true;
2127
+ }
2128
+ this.warn(
2129
+ `ActionQueueMachine: action status transition: executing \u2192 failed (id="${actionId}", type="${action.type}", error="${result.error}")`
2130
+ );
2131
+ this.updateActions(
2132
+ (prev) => prev.map(
2133
+ (a) => a.id === actionId ? { ...a, status: "failed", error: result.error } : a
2134
+ )
2135
+ );
2136
+ try {
2137
+ await this.deps.client.completeAction(
2138
+ actionId,
2139
+ false,
2140
+ result.error,
2141
+ result.diagnose
2142
+ );
2143
+ } catch {
2144
+ }
2145
+ return false;
2146
+ } catch (err) {
2147
+ const errorMsg = err instanceof Error ? err.message : "Action failed";
2148
+ this.warn(
2149
+ `ActionQueueMachine: action status transition: executing \u2192 failed (exception) (id="${actionId}", error="${errorMsg}")`
2150
+ );
2151
+ this.updateActions(
2152
+ (prev) => prev.map(
2153
+ (a) => a.id === actionId ? { ...a, status: "failed", error: errorMsg } : a
2154
+ )
2155
+ );
2156
+ try {
2157
+ await this.deps.client.completeAction(actionId, false, errorMsg);
2158
+ } catch {
2159
+ }
2160
+ return false;
2161
+ }
2162
+ }
2163
+ // ── Reject an action ────────────────────────────────────────
2164
+ async rejectAction(actionId) {
2165
+ this.updateActions(
2166
+ (prev) => prev.map(
2167
+ (a) => a.id === actionId ? { ...a, status: "failed", error: "Rejected by user" } : a
2168
+ )
2169
+ );
2170
+ const action = this.state.actions.find((a) => a.id === actionId);
2171
+ this.deps.emit({
2172
+ type: "agent:action:error",
2173
+ timestamp: Date.now(),
2174
+ data: {
2175
+ actionId,
2176
+ actionType: action?.type,
2177
+ success: false,
2178
+ error: "Rejected by user"
2179
+ }
2180
+ });
2181
+ try {
2182
+ await this.deps.client.completeAction(actionId, false, "Rejected by user");
2183
+ } catch {
2184
+ }
2185
+ this.scheduleDrain();
2186
+ }
2187
+ // ── Derived views ───────────────────────────────────────────
2188
+ get pendingActions() {
2189
+ return this.state.actions.filter((a) => a.status === "pending");
2190
+ }
2191
+ get executingActions() {
2192
+ return this.state.actions.filter((a) => a.status === "executing");
2193
+ }
2194
+ get completedActions() {
2195
+ return this.state.actions.filter(
2196
+ (a) => a.status === "completed" || a.status === "failed"
2197
+ );
2198
+ }
2199
+ };
2200
+ function normalize3(s) {
2201
+ return s.toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]/g, "");
2202
+ }
2203
+ function matchScore(needle, hay) {
2204
+ if (!needle || !hay) return 0;
2205
+ if (needle === hay) return 100;
2206
+ if (hay.startsWith(needle) || needle.startsWith(hay)) return 75;
2207
+ if (hay.endsWith(needle) || needle.endsWith(hay)) return 60;
2208
+ if (hay.includes(needle) || needle.includes(hay)) return 40;
2209
+ return 0;
2210
+ }
2211
+ function isGenericSelectValue(value) {
2212
+ if (value === void 0 || value === null || value === "") return true;
2213
+ const normalized = normalize3(String(value));
2214
+ if (!normalized) return true;
2215
+ return /^(any|qualquer|random|aleatorio|aleatoria|auto|automatico|automatica|mock|mockado|mockada|mockados|mockadas|dummy|sample|exemplo|default|padrao|teste|test)$/.test(
2216
+ normalized
2217
+ );
2218
+ }
2219
+ var LIST_ORDINAL_RE = /^list-\d+-item-\d+$/i;
2220
+ function normalizeId(value) {
2221
+ return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase();
2222
+ }
2223
+ function isOptionalCosmeticTargetId(targetId) {
2224
+ if (typeof targetId !== "string" || !targetId.trim()) return false;
2225
+ const id = normalizeId(targetId);
2226
+ return /foto|photo|avatar|icone|icon|emoji|sheetaction|sheet-action|btn-foto/.test(id) || id.includes("photo-sheet");
2227
+ }
2228
+ function routeLooksLikeFormContext(route) {
2229
+ const haystack = normalizeId(route ?? "");
2230
+ return /create|edit|form|cadastr|register|novo|vehiclecreate|subscriptioncreate|vehicleedit|subscriptioncreate/.test(
2231
+ haystack
2232
+ );
2233
+ }
2234
+ function screenPathParts(screen) {
2235
+ if (!screen) return [];
2236
+ return screen.split("/").map((part) => part.trim()).filter(Boolean);
2237
+ }
2238
+ function isMisappliedListOrdinalOnForm(targetId, screen, activePath) {
2239
+ if (typeof targetId !== "string" || !LIST_ORDINAL_RE.test(targetId.trim())) {
2240
+ return false;
2241
+ }
2242
+ const routes = [...screenPathParts(screen), ...activePath ?? []];
2243
+ if (routes.length === 0) return routeLooksLikeFormContext(screen);
2244
+ return routes.some((route) => routeLooksLikeFormContext(route));
2245
+ }
2246
+ function isOptionalStepAutomationFailure(targetId, screen, activePath) {
2247
+ return isOptionalCosmeticTargetId(targetId) || isMisappliedListOrdinalOnForm(targetId, screen, activePath);
2248
+ }
2249
+ var OPTIONAL_STEP_AUTOMATION_HINT = "optional-step-automation-failed";
1083
2250
  function describeAction(action) {
1084
2251
  const state = mapStatus(action.status);
1085
2252
  const label = labelFor(action, state);
@@ -1374,6 +2541,223 @@ function readArray(payload, key) {
1374
2541
  const v = payload[key];
1375
2542
  return Array.isArray(v) ? v : [];
1376
2543
  }
2544
+ function normalizePart(value) {
2545
+ return String(value ?? "").toLowerCase().normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 48);
2546
+ }
2547
+ function hashString(value) {
2548
+ let hash = 2166136261;
2549
+ for (let i = 0; i < value.length; i++) {
2550
+ hash ^= value.charCodeAt(i);
2551
+ hash = Math.imul(hash, 16777619);
2552
+ }
2553
+ return (hash >>> 0).toString(36);
2554
+ }
2555
+ function stableElementId(parts) {
2556
+ const raw = parts.filter((p) => p !== void 0 && String(p).length > 0).join("|");
2557
+ const readable = parts.map(normalizePart).filter(Boolean).join(":").slice(0, 120);
2558
+ return `el:${readable}:${hashString(raw)}`;
2559
+ }
2560
+ function parseStableElementId(id) {
2561
+ if (typeof id !== "string") return null;
2562
+ const trimmed = id.trim();
2563
+ if (!/^el:/i.test(trimmed)) return null;
2564
+ const parts = trimmed.split(":");
2565
+ if (parts.length < 3) return null;
2566
+ const hash = parts[parts.length - 1];
2567
+ const segments = parts.slice(1, -1);
2568
+ if (segments.length === 0) return null;
2569
+ const role = segments[0];
2570
+ const result = { role, segments, hash };
2571
+ if (role === "option" && segments.length >= 4) {
2572
+ result.listId = segments[segments.length - 2];
2573
+ result.itemKey = segments[segments.length - 1];
2574
+ }
2575
+ return result;
2576
+ }
2577
+ function uniqueTexts(texts) {
2578
+ const seen = /* @__PURE__ */ new Set();
2579
+ const out = [];
2580
+ for (const text of texts) {
2581
+ const t = typeof text === "string" ? text.trim() : "";
2582
+ if (!t || seen.has(t)) continue;
2583
+ seen.add(t);
2584
+ out.push(t);
2585
+ }
2586
+ return out;
2587
+ }
2588
+ function labelForListItem(item) {
2589
+ return uniqueTexts([
2590
+ ...item.texts ?? [],
2591
+ ...(item.buttons ?? []).map((button) => button.label ?? button.id)
2592
+ ]).slice(0, 2).join(" \xB7 ") || void 0;
2593
+ }
2594
+ function listItemElement(route, list, item) {
2595
+ const texts = uniqueTexts([
2596
+ ...item.texts ?? [],
2597
+ ...(item.buttons ?? []).map((button) => button.label ?? button.id)
2598
+ ]);
2599
+ const label = labelForListItem(item);
2600
+ const key = item.itemKey ?? item.reactKey ?? label ?? item.syntheticId;
2601
+ const id = stableElementId([
2602
+ "option",
2603
+ route ?? "unknown",
2604
+ list.id ?? list.label ?? `list-${list.index}`,
2605
+ key
2606
+ ]);
2607
+ const inModal = (item.buttons ?? []).some((button) => button.inModal) || (item.inputs ?? []).some((input) => input.inModal) || (item.toggles ?? []).some((toggle) => toggle.inModal);
2608
+ return {
2609
+ id,
2610
+ role: "option",
2611
+ label,
2612
+ texts,
2613
+ actions: ["press"],
2614
+ disabled: false,
2615
+ source: "list",
2616
+ targetId: item.syntheticId,
2617
+ ...inModal ? { inModal: true } : {},
2618
+ listContext: {
2619
+ listIndex: list.index,
2620
+ listId: list.id,
2621
+ listLabel: list.label,
2622
+ itemIndex: item.index,
2623
+ itemKey: item.itemKey,
2624
+ reactKey: item.reactKey,
2625
+ syntheticId: item.syntheticId
2626
+ }
2627
+ };
2628
+ }
2629
+ function buttonElement(route, button, index) {
2630
+ const label = button.label ?? button.id;
2631
+ const id = stableElementId([
2632
+ "button",
2633
+ route ?? "unknown",
2634
+ button.id ?? label ?? `button-${index}`
2635
+ ]);
2636
+ return {
2637
+ id,
2638
+ role: "button",
2639
+ label,
2640
+ texts: uniqueTexts([label]),
2641
+ actions: ["press"],
2642
+ disabled: button.disabled,
2643
+ selected: button.selected,
2644
+ source: "button",
2645
+ targetId: button.id ?? button.label,
2646
+ ...button.inModal ? { inModal: true } : {}
2647
+ };
2648
+ }
2649
+ function inputElement(route, input, index) {
2650
+ const label = input.label ?? input.placeholder ?? input.id;
2651
+ const id = stableElementId([
2652
+ "input",
2653
+ route ?? "unknown",
2654
+ input.id ?? label ?? `input-${index}`
2655
+ ]);
2656
+ return {
2657
+ id,
2658
+ role: "input",
2659
+ label,
2660
+ texts: uniqueTexts([label, input.placeholder]),
2661
+ actions: ["focus", "setValue"],
2662
+ disabled: input.editable === false,
2663
+ source: "input",
2664
+ targetId: input.id ?? input.label ?? input.placeholder,
2665
+ ...input.inModal ? { inModal: true } : {}
2666
+ };
2667
+ }
2668
+ function toggleElement(route, toggle, index) {
2669
+ const label = toggle.label ?? toggle.id;
2670
+ const id = stableElementId([
2671
+ "toggle",
2672
+ route ?? "unknown",
2673
+ toggle.id ?? label ?? `toggle-${index}`
2674
+ ]);
2675
+ return {
2676
+ id,
2677
+ role: "toggle",
2678
+ label,
2679
+ texts: uniqueTexts([label]),
2680
+ actions: ["toggle", "press"],
2681
+ selected: toggle.value,
2682
+ source: "toggle",
2683
+ targetId: toggle.id ?? toggle.label,
2684
+ ...toggle.inModal ? { inModal: true } : {}
2685
+ };
2686
+ }
2687
+ function sliderElement(route, slider, index) {
2688
+ const label = slider.label ?? slider.id;
2689
+ const id = stableElementId([
2690
+ "slider",
2691
+ route ?? "unknown",
2692
+ slider.id ?? label ?? `slider-${index}`
2693
+ ]);
2694
+ return {
2695
+ id,
2696
+ role: "slider",
2697
+ label,
2698
+ texts: uniqueTexts([label]),
2699
+ actions: ["setValue"],
2700
+ disabled: slider.disabled,
2701
+ source: "slider",
2702
+ targetId: slider.id ?? slider.label,
2703
+ ...slider.inModal ? { inModal: true } : {}
2704
+ };
2705
+ }
2706
+ function deriveInteractionElements(snapshot) {
2707
+ const elements = [];
2708
+ const seen = /* @__PURE__ */ new Set();
2709
+ const push = (element) => {
2710
+ if (!element.id || seen.has(element.id)) return;
2711
+ seen.add(element.id);
2712
+ elements.push(element);
2713
+ };
2714
+ for (const list of snapshot.lists ?? []) {
2715
+ for (const item of list.items ?? []) {
2716
+ push(listItemElement(snapshot.route, list, item));
2717
+ }
2718
+ }
2719
+ for (const [index, button] of (snapshot.buttons ?? []).entries()) {
2720
+ push(buttonElement(snapshot.route, button, index));
2721
+ }
2722
+ for (const [index, input] of (snapshot.inputs ?? []).entries()) {
2723
+ push(inputElement(snapshot.route, input, index));
2724
+ }
2725
+ for (const [index, toggle] of (snapshot.toggles ?? []).entries()) {
2726
+ push(toggleElement(snapshot.route, toggle, index));
2727
+ }
2728
+ for (const [index, slider] of (snapshot.sliders ?? []).entries()) {
2729
+ push(sliderElement(snapshot.route, slider, index));
2730
+ }
2731
+ return elements;
2732
+ }
2733
+ function findElementForChoice(elements, option) {
2734
+ if (option.targetId) {
2735
+ const byTarget = elements.find((element) => element.targetId === option.targetId);
2736
+ if (byTarget) return byTarget;
2737
+ }
2738
+ const optionLabel = normalizePart(option.label ?? option.texts?.join(" "));
2739
+ if (!optionLabel) return void 0;
2740
+ return elements.find((element) => {
2741
+ if (element.role !== "option" && element.role !== "button") return false;
2742
+ const label = normalizePart(element.label ?? element.texts.join(" "));
2743
+ return label === optionLabel || label.includes(optionLabel) || optionLabel.includes(label);
2744
+ });
2745
+ }
2746
+ function attachElementIdsToChoiceGroups(choiceGroups, elements) {
2747
+ return choiceGroups.map((group) => ({
2748
+ ...group,
2749
+ options: group.options.map((option) => {
2750
+ const element = findElementForChoice(elements, option);
2751
+ if (!element) return option;
2752
+ const handle = element.listContext?.syntheticId ?? element.id;
2753
+ return {
2754
+ ...option,
2755
+ syntheticId: handle,
2756
+ targetId: handle
2757
+ };
2758
+ })
2759
+ }));
2760
+ }
1377
2761
 
1378
2762
  // src/registry/ComponentRegistry.ts
1379
2763
  var ComponentRegistryImpl = class {
@@ -2881,4 +4265,4 @@ function clearScreenRegistry() {
2881
4265
  screenRegistry.clear();
2882
4266
  }
2883
4267
 
2884
- export { AppilotsClient, AppilotsProvider, AppilotsRegistryProvider, OPTIONAL_STEP_AUTOMATION_HINT, SDK_VERSION, StreamAbortError, StreamTransportError, _patchJsxRuntimes, actionPressTargetId, appilotsDebugWarn, buildContinuationFailureMessage, clearAppilotsDebugTraces, clearScreenRegistry, componentRegistry, createComponentRegistry, createElementRegistry, createListRegistry, defaultDarkTheme, defaultLightTheme, describeAction, elementRegistry, enableAppilotsAutoTracking, fingerprintAction, getActiveRouteNames, getAllScreens, getAppilotsDebugTraces, getCurrentScreen, getCurrentScreenSignature, getDefaultRegistry, getFiberRoot, getGlobalConfig, getNavigationRef, getNavigationStateSnapshot, getScreenMetadata, humanizeError, initAppilots, isAutoTrackingEnabled, isGenericSelectValue, isListItemPressTarget, isOptionalStepAutomationFailure, listRegistry, looksDestructiveActionLabel, matchScore, mergeThemeTokens, metadataMatchesComponentId, normalize2, probeLoadingState, recordAppilotsDebugTrace, registerScreen, resolveBaseTheme, routesBelongToSameFeature, screenDepartedBaseline, setCurrentScreen, setNavigationRef, subscribeAppilotsDebugTraces, turnHasConfirmActions, useAppilotsContext, useResolvedRegistry, waitForLoadingSettle, waitForScreenSettle };
4268
+ export { ActionQueueMachine, AppilotsClient, AppilotsProvider, AppilotsRegistryProvider, ChatSessionMachine, OPTIONAL_STEP_AUTOMATION_HINT, SDK_VERSION, _patchJsxRuntimes, actionPressTargetId, appilotsDebugWarn, attachElementIdsToChoiceGroups, clearAppilotsDebugTraces, clearScreenRegistry, componentRegistry, createComponentRegistry, createElementRegistry, createListRegistry, defaultDarkTheme, defaultLightTheme, deriveInteractionElements, describeAction, elementRegistry, enableAppilotsAutoTracking, getActiveRouteNames, getAllScreens, getAppilotsDebugTraces, getCurrentScreen, getCurrentScreenSignature, getDefaultRegistry, getFiberRoot, getGlobalConfig, getNavigationRef, getNavigationStateSnapshot, getScreenMetadata, humanizeError, idLooselyMatches, initAppilots, isAutoTrackingEnabled, isGenericSelectValue, isListItemPressTarget, isOptionalStepAutomationFailure, listRegistry, looksDestructiveActionLabel, matchScore, mergeThemeTokens, normalize3, parseStableElementId, probeLoadingState, recordAppilotsDebugTrace, registerScreen, resolveBaseTheme, routesBelongToSameFeature, screenDepartedBaseline, setCurrentScreen, setNavigationRef, snapshotShowsOpenCreateForm, subscribeAppilotsDebugTraces, useAppilotsContext, useResolvedRegistry, waitForLoadingSettle, waitForScreenSettle };