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