@appilots/sdk 0.2.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.
@@ -14,10 +14,20 @@ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require
14
14
  throw Error('Dynamic require of "' + x + '" is not supported');
15
15
  });
16
16
 
17
- // src/version.ts
18
- var SDK_VERSION = "0.1.0";
17
+ // ../shared/dist/chunk-43YQ52Q7.mjs
18
+ var SENSITIVE_FIELD_NAME_RE = /(\b|_|-)(password|senha|contrase[ñn]a|mot[\s_-]*de[\s_-]*passe|passwd|pwd|secret|token|api[\s_-]*key|access[\s_-]*key|private[\s_-]*key|cvv|cvc|cvn|cvc2|cid|pin(?:[\s_-]*code)?|otp|one[\s_-]*time[\s_-]*code|2fa|mfa|security[\s_-]*code|c[óo]digo[\s_-]*seguran[çc]a|c[óo]digo[\s_-]*verifica[çc][ãa]o|verification[\s_-]*code)(\b|_|-)/i;
19
+ function isSensitiveFieldName(id, label, placeholder, type) {
20
+ if (type === "password") return true;
21
+ for (const candidate of [id, label, placeholder]) {
22
+ if (typeof candidate === "string" && SENSITIVE_FIELD_NAME_RE.test(candidate)) {
23
+ return true;
24
+ }
25
+ }
26
+ return false;
27
+ }
19
28
 
20
- // src/services/AppilotsClient.ts
29
+ // ../client-core/dist/index.mjs
30
+ var SDK_VERSION = "0.1.0";
21
31
  var StreamTransportError = class extends Error {
22
32
  constructor(message) {
23
33
  super(message);
@@ -744,47 +754,2016 @@ var AppilotsClient = class {
744
754
  typeof event.operatorName === "string" ? event.operatorName : void 0
745
755
  );
746
756
  }
747
- }
748
- };
749
- const consumeBuffer = () => {
750
- const text = xhr.responseText ?? "";
751
- const boundary = text.lastIndexOf("\n\n");
752
- if (boundary <= parsedIndex) return;
753
- const slice = text.slice(parsedIndex, boundary);
754
- parsedIndex = boundary;
755
- for (const frame of slice.split("\n\n")) {
756
- for (const line of frame.split("\n")) {
757
- if (!line.startsWith("data: ")) continue;
757
+ }
758
+ };
759
+ const consumeBuffer = () => {
760
+ const text = xhr.responseText ?? "";
761
+ const boundary = text.lastIndexOf("\n\n");
762
+ if (boundary <= parsedIndex) return;
763
+ const slice = text.slice(parsedIndex, boundary);
764
+ parsedIndex = boundary;
765
+ for (const frame of slice.split("\n\n")) {
766
+ for (const line of frame.split("\n")) {
767
+ if (!line.startsWith("data: ")) continue;
768
+ try {
769
+ handleEvent(JSON.parse(line.slice(6)));
770
+ } catch {
771
+ this.log("Skipping unparseable escalation SSE line:", line.slice(0, 120));
772
+ }
773
+ }
774
+ }
775
+ };
776
+ xhr.open("GET", url);
777
+ for (const [key, value] of Object.entries(this.headers)) {
778
+ xhr.setRequestHeader(key, value);
779
+ }
780
+ xhr.setRequestHeader("Accept", "text/event-stream");
781
+ xhr.onprogress = consumeBuffer;
782
+ xhr.onload = () => {
783
+ consumeBuffer();
784
+ end();
785
+ };
786
+ xhr.onerror = end;
787
+ xhr.onabort = end;
788
+ xhr.send();
789
+ return () => {
790
+ closedByCaller = true;
791
+ try {
792
+ xhr.abort();
793
+ } catch {
794
+ }
795
+ };
796
+ }
797
+ };
798
+ var defaultLightTheme = {
799
+ colors: {
800
+ primary: "#6366f1",
801
+ secondary: "#8b5cf6",
802
+ background: "#ffffff",
803
+ surface: "#f3f4f6",
804
+ text: "#1f2937",
805
+ textSecondary: "#6b7280",
806
+ border: "#e5e7eb",
807
+ // NOTE (BACKLOG 4.2): success/warning/error used to have ZERO visual
808
+ // consumers — nothing read them, so ActionBreadcrumb's tones and the
809
+ // confirm card's destructive accents were hardcoded separately with
810
+ // their OWN hex values (#10B981/#EF4444/#9CA3AF-ish grays). Since no
811
+ // other UI in the SDK reads these three tokens, we picked these
812
+ // defaults to exactly match those pre-existing hardcoded values —
813
+ // wiring the tokens up to ActionBreadcrumb (see breadcrumbPresentation.ts)
814
+ // is now a no-op for anyone on the bundled default theme, while a
815
+ // custom theme's success/warning/error now actually do something.
816
+ success: "#10b981",
817
+ warning: "#9ca3af",
818
+ error: "#ef4444",
819
+ // Same value as `text` — split out so an app can diverge them, but
820
+ // any app that doesn't set these renders pixel-identical to before.
821
+ buttonText: "#1f2937",
822
+ bubbleText: "#1f2937",
823
+ userBubbleText: "#ffffff"
824
+ },
825
+ typography: {
826
+ fontFamily: "System",
827
+ fontSizeXs: 11,
828
+ fontSizeSm: 13,
829
+ fontSizeBase: 15,
830
+ fontSizeLg: 18
831
+ },
832
+ radii: {
833
+ sm: 8,
834
+ md: 12,
835
+ lg: 16
836
+ },
837
+ shadows: {
838
+ appearance: "light"
839
+ }
840
+ };
841
+ var defaultDarkTheme = {
842
+ colors: {
843
+ primary: "#818cf8",
844
+ secondary: "#a78bfa",
845
+ background: "#0f172a",
846
+ surface: "#1e293b",
847
+ text: "#f1f5f9",
848
+ textSecondary: "#94a3b8",
849
+ border: "#334155",
850
+ // Kept IDENTICAL to the light theme's success/warning/error (not the
851
+ // usual "lighter for dark bg" adjustment other tokens get) — the
852
+ // breadcrumb's tones were a fixed, theme-agnostic hardcoded palette
853
+ // before this token existed, rendering the SAME hex in light and dark
854
+ // mode. Diverging them here would change dark-mode breadcrumbs even
855
+ // though nothing in the token tree asked for that.
856
+ success: "#10b981",
857
+ warning: "#9ca3af",
858
+ error: "#ef4444",
859
+ buttonText: "#f1f5f9",
860
+ bubbleText: "#f1f5f9",
861
+ userBubbleText: "#ffffff"
862
+ },
863
+ typography: { ...defaultLightTheme.typography },
864
+ radii: { ...defaultLightTheme.radii },
865
+ shadows: { appearance: "dark" }
866
+ };
867
+ function mergeThemeTokens(base, partial) {
868
+ if (!partial) return base;
869
+ return {
870
+ colors: { ...base.colors, ...partial.colors ?? {} },
871
+ typography: { ...base.typography, ...partial.typography ?? {} },
872
+ radii: { ...base.radii, ...partial.radii ?? {} },
873
+ shadows: { ...base.shadows, ...partial.shadows ?? {} }
874
+ };
875
+ }
876
+ function resolveBaseTheme(mode, systemScheme) {
877
+ if (mode === "dark") return defaultDarkTheme;
878
+ if (mode === "light") return defaultLightTheme;
879
+ return systemScheme === "dark" ? defaultDarkTheme : defaultLightTheme;
880
+ }
881
+ var LIST_ITEM_TARGET_RE = /^list-\d+-item-\d+$/i;
882
+ var DESTRUCTIVE_LABEL_RE = /\b(cancelar?\w*|cancel(?:ar|led|ation|amento)?|delete\w*|exclu\w*|delet\w*|remov\w*|remove|apag\w*|destroy\w*|unsubscribe\w*)\b/i;
883
+ function isListItemPressTarget(value) {
884
+ if (typeof value !== "string" || !value.trim()) return false;
885
+ return LIST_ITEM_TARGET_RE.test(value.trim());
886
+ }
887
+ function looksDestructiveActionLabel(value) {
888
+ if (typeof value !== "string" || !value.trim()) return false;
889
+ return DESTRUCTIVE_LABEL_RE.test(value.trim());
890
+ }
891
+ function actionPressTargetId(action) {
892
+ if (action.type !== "ui_interaction") return null;
893
+ const payload = action.payload;
894
+ const componentId = payload?.componentId;
895
+ if (typeof componentId === "string" && componentId.trim()) return componentId.trim();
896
+ const targetId = payload?.targetId;
897
+ if (typeof targetId === "string" && targetId.trim()) return targetId.trim();
898
+ return null;
899
+ }
900
+ function turnHasConfirmActions(actions) {
901
+ return actions.some((action) => action.type === "confirm");
902
+ }
903
+ function normalize(value) {
904
+ return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").replace(/[‘’]/g, "'").toLowerCase();
905
+ }
906
+ function detectLocaleFromUserTexts(texts) {
907
+ for (const raw of texts) {
908
+ const normalized = normalize(raw);
909
+ if (/[ãõáéíóúâêôç]/i.test(raw) || /\b(voce|voces|pra|assinatura|cancela|canela|segunda|certo|quais|tenho|pode|posso)\b/.test(
910
+ normalized
911
+ )) {
912
+ return "pt";
913
+ }
914
+ if (/[áéíóúñ¿¡]/i.test(raw) || /\b(cancela|segunda|suscripcion|puedo|cuales|hola)\b/.test(normalized)) {
915
+ return "es";
916
+ }
917
+ if (/[àâçéèêëîïôûùüÿœ]/i.test(raw) || /\b(annuler|deuxieme|abonnement|bonjour)\b/.test(normalized)) {
918
+ return "fr";
919
+ }
920
+ }
921
+ return "en";
922
+ }
923
+ var copy = {
924
+ pt: {
925
+ rateLimit: "O provedor de IA atingiu o limite de uso durante a automa\xE7\xE3o. Aguarde um momento e pe\xE7a para continuar, ou aumente o limite de tokens por minuto.",
926
+ invalidInput: "N\xE3o consegui continuar a automa\xE7\xE3o por um problema interno de sincroniza\xE7\xE3o. Pode tentar de novo ou fazer essa parte manualmente?",
927
+ generic: (detail) => `Perdi o fio da automa\xE7\xE3o (${detail}). Pode continuar manualmente ou me pedir de novo?`
928
+ },
929
+ es: {
930
+ rateLimit: "El proveedor de IA alcanz\xF3 su l\xEDmite de uso durante la automatizaci\xF3n. Espera un momento y p\xEDdeme continuar, o aumenta el l\xEDmite de tokens por minuto.",
931
+ invalidInput: "No pude continuar la automatizaci\xF3n por un problema interno de sincronizaci\xF3n. \xBFPuedes intentarlo de nuevo o hacer esta parte manualmente?",
932
+ generic: (detail) => `Perd\xED el hilo de la automatizaci\xF3n (${detail}). \xBFPuedes continuar manualmente o ped\xEDrmelo de nuevo?`
933
+ },
934
+ fr: {
935
+ rateLimit: "Le fournisseur d'IA a atteint sa limite d'utilisation pendant l'automatisation. Attendez un instant et demandez-moi de continuer, ou augmentez la limite de tokens par minute.",
936
+ invalidInput: "Je n'ai pas pu poursuivre l'automatisation \xE0 cause d'un probl\xE8me interne de synchronisation. Pouvez-vous r\xE9essayer ou faire cette \xE9tape manuellement ?",
937
+ generic: (detail) => `J'ai perdu le fil de l'automatisation (${detail}). Vous pouvez continuer manuellement ou me redemander ?`
938
+ },
939
+ en: {
940
+ rateLimit: "Your AI provider hit its rate limit during automation. Wait a moment and ask me to continue, or raise your TPM limit.",
941
+ invalidInput: "I could not continue the automation because of an internal sync issue. You can try again or take over manually.",
942
+ generic: (detail) => `I lost my footing during automation (${detail}). You can take over from here.`
943
+ }
944
+ };
945
+ function buildContinuationFailureMessage(rawError, recentUserTexts) {
946
+ const locale = detectLocaleFromUserTexts(recentUserTexts);
947
+ const strings = copy[locale] ?? copy.en;
948
+ if (/rate limit|tokens per min|tpm/i.test(rawError)) {
949
+ return strings.rateLimit;
950
+ }
951
+ if (/invalid input|validation_error|422/i.test(rawError)) {
952
+ return strings.invalidInput;
953
+ }
954
+ return strings.generic(rawError);
955
+ }
956
+ function metadataMatchesComponentId(metaId, componentId) {
957
+ const norm = (s) => s.toLowerCase().replace(/^el:(button|link|input|toggle|listitem)[:_-]/, "").replace(/^(btn-|btn_|button-|button_|link-|link_)/, "").trim();
958
+ const meta = norm(metaId);
959
+ const comp = norm(componentId);
960
+ if (!meta || !comp) return false;
961
+ return meta === comp;
962
+ }
963
+ function fingerprintAction(action, screen) {
964
+ const p = action.payload ?? {};
965
+ const scope = `@${screen ?? "?"}`;
966
+ switch (action.type) {
967
+ case "navigate":
968
+ return `navigate${scope}:${p.screenName ?? "?"}`;
969
+ case "form_fill": {
970
+ const fields = p.fields ?? [];
971
+ const ids = fields.map((f) => String(f?.fieldId ?? "?")).sort();
972
+ return `form_fill${scope}:${ids.join(",")}`;
973
+ }
974
+ case "ui_interaction":
975
+ return `ui_interaction${scope}:${p.action ?? "?"}:${p.componentId ?? p.targetId ?? "?"}`;
976
+ case "confirm":
977
+ return `confirm${scope}:${p.message ?? p.title ?? "?"}`;
978
+ default:
979
+ return `${action.type}${scope}:${JSON.stringify(p)}`;
980
+ }
981
+ }
982
+ var MAX_TRACE_ENTRIES = 200;
983
+ var entries = [];
984
+ var listeners = /* @__PURE__ */ new Set();
985
+ var traceCounter = 0;
986
+ function notify() {
987
+ const snapshot = [...entries];
988
+ listeners.forEach((listener) => listener(snapshot));
989
+ }
990
+ function append(entry) {
991
+ entries.push(entry);
992
+ if (entries.length > MAX_TRACE_ENTRIES) {
993
+ entries.splice(0, entries.length - MAX_TRACE_ENTRIES);
994
+ }
995
+ notify();
996
+ }
997
+ function recordAppilotsDebugTrace(entry) {
998
+ append({
999
+ ...entry,
1000
+ id: entry.id ?? `trace_${++traceCounter}`
1001
+ });
1002
+ }
1003
+ function actionFromEvent(event) {
1004
+ const action = event.data?.action;
1005
+ if (action && typeof action === "object" && typeof action.id === "string") {
1006
+ return action;
1007
+ }
1008
+ return void 0;
1009
+ }
1010
+ function recordAppilotsEventTrace(event) {
1011
+ if (!event.type.startsWith("agent:action:")) return;
1012
+ const action = actionFromEvent(event);
1013
+ const actionId = action?.id || (typeof event.data?.actionId === "string" ? event.data.actionId : void 0);
1014
+ const actionType = action?.type || (typeof event.data?.actionType === "string" ? event.data.actionType : void 0);
1015
+ recordAppilotsDebugTrace({
1016
+ timestamp: event.timestamp,
1017
+ type: event.type,
1018
+ messageId: action?.messageId,
1019
+ actionId,
1020
+ actionType,
1021
+ status: event.type === "agent:action:start" ? "started" : event.type === "agent:action:complete" ? "completed" : "error",
1022
+ data: event.data
1023
+ });
1024
+ }
1025
+ function getAppilotsDebugTraces() {
1026
+ return [...entries];
1027
+ }
1028
+ function clearAppilotsDebugTraces() {
1029
+ entries.splice(0, entries.length);
1030
+ notify();
1031
+ }
1032
+ function subscribeAppilotsDebugTraces(listener) {
1033
+ listeners.add(listener);
1034
+ listener([...entries]);
1035
+ return () => {
1036
+ listeners.delete(listener);
1037
+ };
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
+ );
758
2058
  try {
759
- handleEvent(JSON.parse(line.slice(6)));
2059
+ await this.deps.client.completeAction(action.id, false, "Confirm canceled by user");
760
2060
  } catch {
761
- this.log("Skipping unparseable escalation SSE line:", line.slice(0, 120));
762
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;
763
2073
  }
764
2074
  }
765
- };
766
- xhr.open("GET", url);
767
- for (const [key, value] of Object.entries(this.headers)) {
768
- xhr.setRequestHeader(key, value);
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";
769
2102
  }
770
- xhr.setRequestHeader("Accept", "text/event-stream");
771
- xhr.onprogress = consumeBuffer;
772
- xhr.onload = () => {
773
- consumeBuffer();
774
- end();
775
- };
776
- xhr.onerror = end;
777
- xhr.onabort = end;
778
- xhr.send();
779
- return () => {
780
- closedByCaller = true;
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
+ );
781
2142
  try {
782
- xhr.abort();
2143
+ await this.deps.client.completeAction(
2144
+ actionId,
2145
+ false,
2146
+ result.error,
2147
+ result.diagnose
2148
+ );
783
2149
  } catch {
784
2150
  }
785
- };
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
+ );
786
2204
  }
787
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";
2256
+ function describeAction(action) {
2257
+ const state = mapStatus(action.status);
2258
+ const label = labelFor(action, state);
2259
+ return { label, state };
2260
+ }
2261
+ function mapStatus(status) {
2262
+ switch (status) {
2263
+ case "pending":
2264
+ return "pending";
2265
+ case "executing":
2266
+ return "running";
2267
+ case "completed":
2268
+ return "success";
2269
+ case "failed":
2270
+ return "failed";
2271
+ default:
2272
+ return "pending";
2273
+ }
2274
+ }
2275
+ function labelFor(action, state) {
2276
+ if (state === "failed") {
2277
+ return failureLabel(action);
2278
+ }
2279
+ switch (action.type) {
2280
+ case "navigate":
2281
+ return navigateLabel(action, state);
2282
+ case "form_fill":
2283
+ return formFillLabel(action, state);
2284
+ case "ui_interaction":
2285
+ return uiInteractionLabel(action, state);
2286
+ case "scroll_list":
2287
+ return scrollListLabel(action, state);
2288
+ case "confirm":
2289
+ return confirmLabel(state);
2290
+ default:
2291
+ return genericLabel(action.type, state);
2292
+ }
2293
+ }
2294
+ function navigateLabel(action, state) {
2295
+ const screen = readString(action.payload, "screenName") || "a outra tela";
2296
+ switch (state) {
2297
+ case "pending":
2298
+ return `Vou navegar para ${screen}`;
2299
+ case "running":
2300
+ return `Navegando para ${screen}...`;
2301
+ case "success":
2302
+ return `Naveguei para ${screen}`;
2303
+ default:
2304
+ return `Navega\xE7\xE3o para ${screen}`;
2305
+ }
2306
+ }
2307
+ function formFillLabel(action, state) {
2308
+ const fields = readArray(action.payload, "fields");
2309
+ const summary = summariseFields(fields);
2310
+ switch (state) {
2311
+ case "pending":
2312
+ return summary ? `Vou preencher ${summary}` : "Vou preencher o formul\xE1rio";
2313
+ case "running":
2314
+ return summary ? `Preenchendo ${summary}...` : "Preenchendo formul\xE1rio...";
2315
+ case "success":
2316
+ return summary ? `Preenchi ${summary}` : "Preenchi o formul\xE1rio";
2317
+ default:
2318
+ return "Preenchimento do formul\xE1rio";
2319
+ }
2320
+ }
2321
+ function uiInteractionLabel(action, state) {
2322
+ const verbBase = uiVerb(readString(action.payload, "action"));
2323
+ const target = humanActionTarget(action.payload);
2324
+ const where = target ? ` em ${target}` : "";
2325
+ switch (state) {
2326
+ case "pending":
2327
+ return `Vou ${verbBase.infinitive}${where}`;
2328
+ case "running":
2329
+ return `${verbBase.gerund}${where}...`;
2330
+ case "success":
2331
+ return `${verbBase.past}${where}`;
2332
+ default:
2333
+ return `Intera\xE7\xE3o${where}`;
2334
+ }
2335
+ }
2336
+ function scrollListLabel(action, state) {
2337
+ const payload = action.payload;
2338
+ const toIndex = typeof payload?.toIndex === "number" ? payload.toIndex : void 0;
2339
+ const direction = payload?.direction === "up" ? "para cima" : "para baixo";
2340
+ const target = toIndex !== void 0 ? `at\xE9 o item ${toIndex}` : direction;
2341
+ switch (state) {
2342
+ case "pending":
2343
+ return `Vou rolar a lista ${target}`;
2344
+ case "running":
2345
+ return `Rolando a lista ${target}...`;
2346
+ case "success":
2347
+ return `Rolei a lista ${target}`;
2348
+ default:
2349
+ return "Rolagem da lista";
2350
+ }
2351
+ }
2352
+ function confirmLabel(state) {
2353
+ switch (state) {
2354
+ case "pending":
2355
+ case "running":
2356
+ return "Aguardando confirma\xE7\xE3o";
2357
+ case "success":
2358
+ return "Voc\xEA confirmou";
2359
+ case "failed":
2360
+ return "Confirma\xE7\xE3o cancelada";
2361
+ default:
2362
+ return "Confirma\xE7\xE3o";
2363
+ }
2364
+ }
2365
+ function genericLabel(type, state) {
2366
+ switch (state) {
2367
+ case "pending":
2368
+ return `Vou executar ${type}`;
2369
+ case "running":
2370
+ return `Executando ${type}...`;
2371
+ case "success":
2372
+ return `Executei ${type}`;
2373
+ default:
2374
+ return type;
2375
+ }
2376
+ }
2377
+ function failureLabel(action) {
2378
+ const cause = humanizeError(action.error);
2379
+ const head = failureHead(action);
2380
+ return cause ? `${head} (${cause})` : head;
2381
+ }
2382
+ function failureHead(action) {
2383
+ switch (action.type) {
2384
+ case "navigate": {
2385
+ const screen = readString(action.payload, "screenName") || "a tela";
2386
+ return `N\xE3o consegui navegar para ${screen}`;
2387
+ }
2388
+ case "form_fill":
2389
+ return "N\xE3o consegui preencher o formul\xE1rio";
2390
+ case "ui_interaction": {
2391
+ const verb = uiVerb(readString(action.payload, "action"));
2392
+ const target = humanComponentId(
2393
+ readString(action.payload, "componentId") || readString(action.payload, "targetId")
2394
+ );
2395
+ return target ? `N\xE3o consegui ${verb.infinitive} em ${target}` : `N\xE3o consegui ${verb.infinitive}`;
2396
+ }
2397
+ case "scroll_list":
2398
+ return "N\xE3o consegui rolar a lista";
2399
+ case "confirm":
2400
+ return "Voc\xEA cancelou";
2401
+ default:
2402
+ return `N\xE3o consegui executar ${action.type}`;
2403
+ }
2404
+ }
2405
+ var TECHNICAL_PATTERNS = [
2406
+ /\bat\s+\S+\s*\(/,
2407
+ // stack trace frame
2408
+ /TypeError|ReferenceError|SyntaxError|RangeError/i,
2409
+ /HTTP\s*[45]\d{2}/i,
2410
+ /^\s*\{[\s\S]*\}\s*$/,
2411
+ // JSON dump
2412
+ /undefined is not a function/i,
2413
+ /Cannot read propert/i
2414
+ ];
2415
+ var FRIENDLY_REWRITES = [
2416
+ [/rejected by user/i, "cancelado pelo usu\xE1rio"],
2417
+ [/component '([^']+)' not found/i, "componente $1 n\xE3o encontrado"],
2418
+ [/field '([^']+)' not found/i, "campo $1 n\xE3o encontrado"],
2419
+ [/screen '([^']+)' not registered/i, "tela $1 n\xE3o registrada"],
2420
+ [/permission denied/i, "sem permiss\xE3o"],
2421
+ [/timeout|timed out/i, "tempo esgotado"],
2422
+ [/network/i, "falha de rede"]
2423
+ ];
2424
+ var MAX_REASON_LEN = 80;
2425
+ function humanizeError(raw, _action) {
2426
+ if (!raw) return void 0;
2427
+ const trimmed = String(raw).trim();
2428
+ if (!trimmed) return void 0;
2429
+ for (const [pattern, replacement] of FRIENDLY_REWRITES) {
2430
+ if (pattern.test(trimmed)) {
2431
+ return finalize(trimmed.replace(pattern, replacement));
2432
+ }
2433
+ }
2434
+ if (TECHNICAL_PATTERNS.some((p) => p.test(trimmed))) {
2435
+ return "algo deu errado";
2436
+ }
2437
+ if (trimmed.length > MAX_REASON_LEN) {
2438
+ return "algo deu errado";
2439
+ }
2440
+ return finalize(trimmed);
2441
+ }
2442
+ function finalize(text) {
2443
+ let out = text.trim();
2444
+ if (out.length > MAX_REASON_LEN) out = out.slice(0, MAX_REASON_LEN - 1).trimEnd() + "\u2026";
2445
+ if (out.length > 0) out = out[0].toLowerCase() + out.slice(1);
2446
+ return out;
2447
+ }
2448
+ function uiVerb(raw) {
2449
+ switch ((raw ?? "press").toLowerCase()) {
2450
+ case "press":
2451
+ case "tap":
2452
+ case "click":
2453
+ return { infinitive: "clicar", gerund: "Clicando", past: "Cliquei" };
2454
+ case "longpress":
2455
+ case "long_press":
2456
+ return { infinitive: "pressionar", gerund: "Pressionando", past: "Pressionei" };
2457
+ case "scroll":
2458
+ case "scroll_to":
2459
+ return { infinitive: "rolar", gerund: "Rolando", past: "Rolei" };
2460
+ case "focus":
2461
+ return { infinitive: "focar", gerund: "Focando", past: "Foquei" };
2462
+ default:
2463
+ return { infinitive: "interagir", gerund: "Interagindo", past: "Interagi" };
2464
+ }
2465
+ }
2466
+ function summariseFields(fields) {
2467
+ if (!Array.isArray(fields) || fields.length === 0) return void 0;
2468
+ const parts = [];
2469
+ const visible = fields.slice(0, 3);
2470
+ for (const f of visible) {
2471
+ if (!f || typeof f !== "object") continue;
2472
+ const obj = f;
2473
+ const name = humanFieldName(obj.label, obj.fieldId);
2474
+ if (!name) continue;
2475
+ const fieldIdStr = typeof obj.fieldId === "string" ? obj.fieldId : void 0;
2476
+ const labelStr = typeof obj.label === "string" ? obj.label : void 0;
2477
+ const fieldTypeStr = typeof obj.fieldType === "string" ? obj.fieldType : void 0;
2478
+ const sensitive = isSensitiveFieldName(fieldIdStr, labelStr, void 0, fieldTypeStr);
2479
+ if (sensitive) {
2480
+ parts.push(`${name}=\u2022\u2022\u2022\u2022`);
2481
+ continue;
2482
+ }
2483
+ const value = formatFieldValue(obj.value);
2484
+ parts.push(value !== void 0 ? `${name}=${value}` : name);
2485
+ }
2486
+ if (parts.length === 0) return void 0;
2487
+ const extra = fields.length - parts.length;
2488
+ return extra > 0 ? `${parts.join(", ")} +${extra}` : parts.join(", ");
2489
+ }
2490
+ function formatFieldValue(value) {
2491
+ if (value === void 0 || value === null || value === "") return void 0;
2492
+ if (typeof value === "string") {
2493
+ const v = value.length > 18 ? `${value.slice(0, 17)}\u2026` : value;
2494
+ return `"${v}"`;
2495
+ }
2496
+ if (typeof value === "number" || typeof value === "boolean") return String(value);
2497
+ return void 0;
2498
+ }
2499
+ function humanActionTarget(payload) {
2500
+ if (!payload || typeof payload !== "object") return "";
2501
+ const record = payload;
2502
+ const friendly = readFriendlyString(record, "label") || readFriendlyString(record, "actionDescription") || readFriendlyString(record, "title") || readFriendlyString(record, "name");
2503
+ if (friendly) return friendly;
2504
+ return humanComponentId(
2505
+ readString(payload, "componentId") || readString(payload, "targetId")
2506
+ );
2507
+ }
2508
+ function humanFieldName(label, fieldId) {
2509
+ if (typeof label === "string" && label.trim()) return label.trim();
2510
+ if (typeof fieldId !== "string" || isTechnicalIdentifier(fieldId)) return void 0;
2511
+ return humanComponentId(fieldId) || void 0;
2512
+ }
2513
+ function readFriendlyString(record, key) {
2514
+ const value = record[key];
2515
+ if (typeof value !== "string") return "";
2516
+ const trimmed = value.trim();
2517
+ if (!trimmed || isTechnicalIdentifier(trimmed)) return "";
2518
+ return trimmed;
2519
+ }
2520
+ function humanComponentId(id) {
2521
+ if (!id) return "";
2522
+ if (isTechnicalIdentifier(id)) return "";
2523
+ const cleaned = id.replace(/^(btn-|btn_|button-|component-)/i, "").replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/[-_]+/g, " ").trim();
2524
+ if (!cleaned) return "";
2525
+ return cleaned[0].toUpperCase() + cleaned.slice(1);
2526
+ }
2527
+ function isTechnicalIdentifier(value) {
2528
+ const raw = value.trim();
2529
+ if (!raw) return false;
2530
+ if (/^el:/i.test(raw)) return true;
2531
+ if (/^list-\d+-item-\d+$/i.test(raw)) return true;
2532
+ if (/(testid|test-id|accessibilityid|accessibility-id|automationid|automation-id)/i.test(raw)) {
2533
+ return true;
2534
+ }
2535
+ if (/^[a-z]+:[^ ]+:[^ ]+/i.test(raw)) return true;
2536
+ if (/^[A-Za-z][\w-]*:[^\s]+$/i.test(raw)) return true;
2537
+ if (/^[a-z]+(?:[-_][a-z]+)*[-_][a-z0-9]{5,}$/i.test(raw)) return true;
2538
+ return false;
2539
+ }
2540
+ function readString(payload, key) {
2541
+ if (!payload || typeof payload !== "object") return "";
2542
+ const v = payload[key];
2543
+ return typeof v === "string" ? v : "";
2544
+ }
2545
+ function readArray(payload, key) {
2546
+ if (!payload || typeof payload !== "object") return [];
2547
+ const v = payload[key];
2548
+ return Array.isArray(v) ? v : [];
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
+ }
788
2767
 
789
2768
  // src/registry/ComponentRegistry.ts
790
2769
  var ComponentRegistryImpl = class {
@@ -1018,7 +2997,7 @@ function getDefaultRegistry() {
1018
2997
  }
1019
2998
 
1020
2999
  // src/auto/idExtraction.ts
1021
- function normalizeId(raw) {
3000
+ function normalizeId2(raw) {
1022
3001
  return raw.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/^(input-|btn-|toggle-|switch-)/, "").replace(/[^a-z0-9\s-_]/g, "").trim().replace(/[-_\s]+(.)/g, (_, c) => c.toUpperCase()).replace(/[-_\s]+$/, "");
1023
3002
  }
1024
3003
  function deriveSemanticInputId(props) {
@@ -1066,7 +3045,7 @@ function sectionListFlatIndex(sections, section, localIndex) {
1066
3045
  function extractId(props) {
1067
3046
  const raw = props?.testID ?? props?.nativeID ?? props?.id ?? props?.accessibilityLabel ?? props?.name ?? props?.placeholder ?? (typeof props?.autoComplete === "string" && props.autoComplete !== "off" ? props.autoComplete : void 0);
1068
3047
  if (raw && typeof raw === "string") {
1069
- const normalized = normalizeId(raw);
3048
+ const normalized = normalizeId2(raw);
1070
3049
  if (normalized && normalized.length > 0) {
1071
3050
  console.log(`[Appilots] extractId: raw="${raw}" \u2192 normalized="${normalized}"`);
1072
3051
  return normalized;
@@ -1217,7 +3196,7 @@ function componentDisplayName(type) {
1217
3196
  function extractListId(props, componentName) {
1218
3197
  const raw = props?.appilotsListId ?? props?.testID ?? props?.nativeID ?? props?.id ?? props?.accessibilityLabel ?? props?.name;
1219
3198
  if (typeof raw === "string" && raw.trim().length > 0) {
1220
- const normalized = normalizeId(raw);
3199
+ const normalized = normalizeId2(raw);
1221
3200
  if (normalized) return normalized;
1222
3201
  }
1223
3202
  _generatedListIdCounter += 1;
@@ -1647,65 +3626,6 @@ function getGlobalConfig() {
1647
3626
  return _globalConfig;
1648
3627
  }
1649
3628
 
1650
- // src/debug/actionTraceStore.ts
1651
- var MAX_TRACE_ENTRIES = 200;
1652
- var entries = [];
1653
- var listeners = /* @__PURE__ */ new Set();
1654
- var traceCounter = 0;
1655
- function notify() {
1656
- const snapshot = [...entries];
1657
- listeners.forEach((listener) => listener(snapshot));
1658
- }
1659
- function append(entry) {
1660
- entries.push(entry);
1661
- if (entries.length > MAX_TRACE_ENTRIES) {
1662
- entries.splice(0, entries.length - MAX_TRACE_ENTRIES);
1663
- }
1664
- notify();
1665
- }
1666
- function recordAppilotsDebugTrace(entry) {
1667
- append({
1668
- ...entry,
1669
- id: entry.id ?? `trace_${++traceCounter}`
1670
- });
1671
- }
1672
- function actionFromEvent(event) {
1673
- const action = event.data?.action;
1674
- if (action && typeof action === "object" && typeof action.id === "string") {
1675
- return action;
1676
- }
1677
- return void 0;
1678
- }
1679
- function recordAppilotsEventTrace(event) {
1680
- if (!event.type.startsWith("agent:action:")) return;
1681
- const action = actionFromEvent(event);
1682
- const actionId = action?.id || (typeof event.data?.actionId === "string" ? event.data.actionId : void 0);
1683
- const actionType = action?.type || (typeof event.data?.actionType === "string" ? event.data.actionType : void 0);
1684
- recordAppilotsDebugTrace({
1685
- timestamp: event.timestamp,
1686
- type: event.type,
1687
- messageId: action?.messageId,
1688
- actionId,
1689
- actionType,
1690
- status: event.type === "agent:action:start" ? "started" : event.type === "agent:action:complete" ? "completed" : "error",
1691
- data: event.data
1692
- });
1693
- }
1694
- function getAppilotsDebugTraces() {
1695
- return [...entries];
1696
- }
1697
- function clearAppilotsDebugTraces() {
1698
- entries.splice(0, entries.length);
1699
- notify();
1700
- }
1701
- function subscribeAppilotsDebugTraces(listener) {
1702
- listeners.add(listener);
1703
- listener([...entries]);
1704
- return () => {
1705
- listeners.delete(listener);
1706
- };
1707
- }
1708
-
1709
3629
  // src/introspection/fiberRoot.ts
1710
3630
  var _fiberRoot = null;
1711
3631
  function setFiberRoot(fiber) {
@@ -2351,20 +4271,27 @@ function clearScreenRegistry() {
2351
4271
  screenRegistry.clear();
2352
4272
  }
2353
4273
 
4274
+ exports.ActionQueueMachine = ActionQueueMachine;
2354
4275
  exports.AppilotsClient = AppilotsClient;
2355
4276
  exports.AppilotsProvider = AppilotsProvider;
2356
4277
  exports.AppilotsRegistryProvider = AppilotsRegistryProvider;
4278
+ exports.ChatSessionMachine = ChatSessionMachine;
4279
+ exports.OPTIONAL_STEP_AUTOMATION_HINT = OPTIONAL_STEP_AUTOMATION_HINT;
2357
4280
  exports.SDK_VERSION = SDK_VERSION;
2358
- exports.StreamAbortError = StreamAbortError;
2359
- exports.StreamTransportError = StreamTransportError;
2360
4281
  exports._patchJsxRuntimes = _patchJsxRuntimes;
4282
+ exports.actionPressTargetId = actionPressTargetId;
2361
4283
  exports.appilotsDebugWarn = appilotsDebugWarn;
4284
+ exports.attachElementIdsToChoiceGroups = attachElementIdsToChoiceGroups;
2362
4285
  exports.clearAppilotsDebugTraces = clearAppilotsDebugTraces;
2363
4286
  exports.clearScreenRegistry = clearScreenRegistry;
2364
4287
  exports.componentRegistry = componentRegistry;
2365
4288
  exports.createComponentRegistry = createComponentRegistry;
2366
4289
  exports.createElementRegistry = createElementRegistry;
2367
4290
  exports.createListRegistry = createListRegistry;
4291
+ exports.defaultDarkTheme = defaultDarkTheme;
4292
+ exports.defaultLightTheme = defaultLightTheme;
4293
+ exports.deriveInteractionElements = deriveInteractionElements;
4294
+ exports.describeAction = describeAction;
2368
4295
  exports.elementRegistry = elementRegistry;
2369
4296
  exports.enableAppilotsAutoTracking = enableAppilotsAutoTracking;
2370
4297
  exports.getActiveRouteNames = getActiveRouteNames;
@@ -2378,16 +4305,28 @@ exports.getGlobalConfig = getGlobalConfig;
2378
4305
  exports.getNavigationRef = getNavigationRef;
2379
4306
  exports.getNavigationStateSnapshot = getNavigationStateSnapshot;
2380
4307
  exports.getScreenMetadata = getScreenMetadata;
4308
+ exports.humanizeError = humanizeError;
4309
+ exports.idLooselyMatches = idLooselyMatches;
2381
4310
  exports.initAppilots = initAppilots;
2382
4311
  exports.isAutoTrackingEnabled = isAutoTrackingEnabled;
4312
+ exports.isGenericSelectValue = isGenericSelectValue;
4313
+ exports.isListItemPressTarget = isListItemPressTarget;
4314
+ exports.isOptionalStepAutomationFailure = isOptionalStepAutomationFailure;
2383
4315
  exports.listRegistry = listRegistry;
4316
+ exports.looksDestructiveActionLabel = looksDestructiveActionLabel;
4317
+ exports.matchScore = matchScore;
4318
+ exports.mergeThemeTokens = mergeThemeTokens;
4319
+ exports.normalize3 = normalize3;
4320
+ exports.parseStableElementId = parseStableElementId;
2384
4321
  exports.probeLoadingState = probeLoadingState;
2385
4322
  exports.recordAppilotsDebugTrace = recordAppilotsDebugTrace;
2386
4323
  exports.registerScreen = registerScreen;
4324
+ exports.resolveBaseTheme = resolveBaseTheme;
2387
4325
  exports.routesBelongToSameFeature = routesBelongToSameFeature;
2388
4326
  exports.screenDepartedBaseline = screenDepartedBaseline;
2389
4327
  exports.setCurrentScreen = setCurrentScreen;
2390
4328
  exports.setNavigationRef = setNavigationRef;
4329
+ exports.snapshotShowsOpenCreateForm = snapshotShowsOpenCreateForm;
2391
4330
  exports.subscribeAppilotsDebugTraces = subscribeAppilotsDebugTraces;
2392
4331
  exports.useAppilotsContext = useAppilotsContext;
2393
4332
  exports.useResolvedRegistry = useResolvedRegistry;