@appilots/sdk 0.11.0 → 0.11.3

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.
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- var chunk55Y2AQE7_js = require('./chunk-55Y2AQE7.js');
3
+ var chunkSUMIABJK_js = require('./chunk-SUMIABJK.js');
4
4
  var React = require('react');
5
5
 
6
6
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
@@ -15,17 +15,17 @@ function getActiveRouteName(state) {
15
15
  return route.name;
16
16
  }
17
17
  function AppilotsNavigationContainer({ children }) {
18
- const { emit } = chunk55Y2AQE7_js.useAppilotsContext();
18
+ const { emit } = chunkSUMIABJK_js.useAppilotsContext();
19
19
  const internalRef = React.useRef(null);
20
20
  const handleRef = React.useCallback(
21
21
  (instance) => {
22
22
  internalRef.current = instance;
23
- chunk55Y2AQE7_js.setNavigationRef(instance);
23
+ chunkSUMIABJK_js.setNavigationRef(instance);
24
24
  const childRef = children.ref;
25
25
  if (typeof childRef === "function") childRef(instance);
26
26
  else if (childRef && typeof childRef === "object") childRef.current = instance;
27
27
  if (instance) {
28
- chunk55Y2AQE7_js.appilotsDebugLog("NavigationContainer ref captured");
28
+ chunkSUMIABJK_js.appilotsDebugLog("NavigationContainer ref captured");
29
29
  }
30
30
  },
31
31
  [children]
@@ -34,8 +34,8 @@ function AppilotsNavigationContainer({ children }) {
34
34
  (state) => {
35
35
  const routeName = getActiveRouteName(state);
36
36
  if (routeName) {
37
- chunk55Y2AQE7_js.appilotsDebugLog(`Screen changed \u2192 "${routeName}"`);
38
- chunk55Y2AQE7_js.setCurrentScreen(routeName);
37
+ chunkSUMIABJK_js.appilotsDebugLog(`Screen changed \u2192 "${routeName}"`);
38
+ chunkSUMIABJK_js.setCurrentScreen(routeName);
39
39
  emit({
40
40
  type: "navigation:change",
41
41
  timestamp: Date.now(),
@@ -50,8 +50,8 @@ function AppilotsNavigationContainer({ children }) {
50
50
  const timer = setTimeout(() => {
51
51
  const route = internalRef.current?.getCurrentRoute?.();
52
52
  if (route?.name) {
53
- chunk55Y2AQE7_js.appilotsDebugLog(`Initial screen \u2192 "${route.name}"`);
54
- chunk55Y2AQE7_js.setCurrentScreen(route.name);
53
+ chunkSUMIABJK_js.appilotsDebugLog(`Initial screen \u2192 "${route.name}"`);
54
+ chunkSUMIABJK_js.setCurrentScreen(route.name);
55
55
  emit({
56
56
  type: "navigation:change",
57
57
  timestamp: Date.now(),
@@ -1,4 +1,4 @@
1
- import { useAppilotsContext, setNavigationRef, appilotsDebugLog, setCurrentScreen } from './chunk-G6KI7KVH.mjs';
1
+ import { useAppilotsContext, setNavigationRef, appilotsDebugLog, setCurrentScreen } from './chunk-ROXXCDIG.mjs';
2
2
  import React, { useRef, useCallback, useEffect } from 'react';
3
3
 
4
4
  function getActiveRouteName(state) {
@@ -155,7 +155,7 @@ new RegExp(
155
155
  );
156
156
  var CREATE_EDIT_ROUTE_RE = new RegExp(`(${build(CREATE_EDIT_ROUTE)})`, "i");
157
157
 
158
- // ../shared/dist/chunk-43YQ52Q7.mjs
158
+ // ../shared/dist/chunk-SWQOZWHP.mjs
159
159
  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;
160
160
  function isSensitiveFieldName(id, label, placeholder, type) {
161
161
  if (type === "password") return true;
@@ -1364,6 +1364,7 @@ function snapshotHasVisibleSubmitButton(snap) {
1364
1364
  );
1365
1365
  }
1366
1366
  function snapshotHasVisibleValidation(snap) {
1367
+ if ((snap.inputs ?? []).some((input) => input.invalid === true)) return true;
1367
1368
  return (snap.texts ?? []).some(
1368
1369
  (text) => typeof text === "string" && FORM_VALIDATION_TEXT_RE.test(text)
1369
1370
  );
@@ -1509,6 +1510,8 @@ var ChatSessionMachine = class {
1509
1510
  /** Tear down timers and the escalation stream. Safe to call twice. */
1510
1511
  dispose() {
1511
1512
  this.disposed = true;
1513
+ this.abortController?.abort();
1514
+ this.currentTurn = null;
1512
1515
  this.closeEscalationStream();
1513
1516
  }
1514
1517
  setState(partial) {
@@ -1555,7 +1558,7 @@ var ChatSessionMachine = class {
1555
1558
  }
1556
1559
  // ── Escalation: request, stream, and message routing ─────────
1557
1560
  async requestHuman(reason = "user_requested") {
1558
- if (this.state.escalation) return;
1561
+ if (this.disposed || this.state.escalation) return;
1559
1562
  try {
1560
1563
  const lastError = this.state.messages.slice().reverse().find((m) => m.role === "assistant" && m.id.startsWith("error_"));
1561
1564
  const result = await this.deps.client.requestEscalation(reason, lastError?.content);
@@ -1729,7 +1732,7 @@ var ChatSessionMachine = class {
1729
1732
  // ── Continuation: when all current-turn actions finish, ask the
1730
1733
  // model what to do next, given the outcomes + fresh snapshot.
1731
1734
  async runContinuation(turn) {
1732
- if (this.continuationInFlight) return;
1735
+ if (this.disposed || this.continuationInFlight) return;
1733
1736
  this.continuationInFlight = true;
1734
1737
  try {
1735
1738
  this.setState({ isLoading: true, loadingStatusKey: "statusWaitingApp" });
@@ -1752,6 +1755,7 @@ var ChatSessionMachine = class {
1752
1755
  pressedComponentId: pressedId,
1753
1756
  navigateTargetScreen
1754
1757
  });
1758
+ if (this.disposed || this.currentTurn !== turn) return;
1755
1759
  const loadingPending = settle.loadingPending;
1756
1760
  for (const id of settle.toolWithoutEffectIds) toolWithoutEffectIds.add(id);
1757
1761
  const context = this.deps.adapter.buildContext({ loadingPending });
@@ -1817,6 +1821,7 @@ var ChatSessionMachine = class {
1817
1821
  `ChatSessionMachine: running continuation hop=${turn.hop} (${results.filter((r) => r.success).length}/${results.length} succeeded` + (exhausted.length > 0 ? `, ${exhausted.length} recovery-exhausted` : "") + ")"
1818
1822
  );
1819
1823
  const response = await this.deps.client.continueAgent(results, context, turn.hop);
1824
+ if (this.disposed) return;
1820
1825
  this.addAssistantMessage(response.message);
1821
1826
  this.deps.emit({
1822
1827
  type: "agent:message",
@@ -1904,7 +1909,7 @@ var ChatSessionMachine = class {
1904
1909
  }
1905
1910
  // ── Send / cancel / clear ────────────────────────────────────
1906
1911
  async sendMessage(content) {
1907
- if (!content.trim() || this.state.isLoading) return;
1912
+ if (this.disposed || !content.trim() || this.state.isLoading) return;
1908
1913
  if (this.state.escalation) {
1909
1914
  await this.sendEscalationMessage(content.trim());
1910
1915
  return;
@@ -1931,7 +1936,7 @@ var ChatSessionMachine = class {
1931
1936
  const abortController = new AbortController();
1932
1937
  this.abortController = abortController;
1933
1938
  try {
1934
- const context = this.deps.adapter.buildContext();
1939
+ const context = this.deps.adapter.buildContext({ newMission: true });
1935
1940
  appilotsDebugLog(
1936
1941
  `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}}`
1937
1942
  );
@@ -2132,6 +2137,7 @@ var ActionQueueMachine = class {
2132
2137
  processed = /* @__PURE__ */ new Set();
2133
2138
  /** Guards against overlapping sequential runs. */
2134
2139
  draining = false;
2140
+ disposed = false;
2135
2141
  /** Set when new work arrives while a drain is in flight. */
2136
2142
  drainRequested = false;
2137
2143
  /** Set while a drain is queued on the microtask queue. */
@@ -2146,6 +2152,12 @@ var ActionQueueMachine = class {
2146
2152
  this.rejectAction = this.rejectAction.bind(this);
2147
2153
  }
2148
2154
  // ── State plumbing ───────────────────────────────────────────
2155
+ dispose() {
2156
+ this.disposed = true;
2157
+ this.drainRequested = false;
2158
+ this.setActions([]);
2159
+ this.listeners.clear();
2160
+ }
2149
2161
  getState() {
2150
2162
  return this.state;
2151
2163
  }
@@ -2171,7 +2183,7 @@ var ActionQueueMachine = class {
2171
2183
  }
2172
2184
  // ── Event intake: new actions from the agent ─────────────────
2173
2185
  handleEvent(event) {
2174
- if (event.type !== "agent:action:start") return;
2186
+ if (this.disposed || event.type !== "agent:action:start") return;
2175
2187
  const action = event.data?.action;
2176
2188
  if (!action?.id) return;
2177
2189
  appilotsDebugLog(
@@ -2198,7 +2210,7 @@ var ActionQueueMachine = class {
2198
2210
  * batching before its effect ran.)
2199
2211
  */
2200
2212
  scheduleDrain() {
2201
- if (!this.options.autoExecute) return;
2213
+ if (this.disposed || !this.options.autoExecute) return;
2202
2214
  if (this.draining) {
2203
2215
  this.drainRequested = true;
2204
2216
  return;
@@ -2207,7 +2219,7 @@ var ActionQueueMachine = class {
2207
2219
  this.drainScheduled = true;
2208
2220
  queueMicrotask(() => {
2209
2221
  this.drainScheduled = false;
2210
- if (!this.options.autoExecute) return;
2222
+ if (this.disposed || !this.options.autoExecute) return;
2211
2223
  if (this.draining) {
2212
2224
  this.drainRequested = true;
2213
2225
  return;
@@ -2330,6 +2342,7 @@ var ActionQueueMachine = class {
2330
2342
  let preNavScreen = null;
2331
2343
  let preNavSignature = null;
2332
2344
  for (const [index, action] of pending.entries()) {
2345
+ if (this.disposed) return;
2333
2346
  if (action.type === "confirm") {
2334
2347
  continue;
2335
2348
  }
@@ -2449,6 +2462,7 @@ var ActionQueueMachine = class {
2449
2462
  }
2450
2463
  // ── Approve & execute an action ─────────────────────────────
2451
2464
  async approveAction(actionId) {
2465
+ if (this.disposed) return false;
2452
2466
  const action = this.state.actions.find((a) => a.id === actionId);
2453
2467
  if (!action) return false;
2454
2468
  appilotsDebugLog(
@@ -3264,6 +3278,91 @@ function clampSnapshotToWireLimits(snapshot) {
3264
3278
  if (cut.dropped) snapshot.truncated = true;
3265
3279
  return snapshot;
3266
3280
  }
3281
+ var MAX_DELTA_FIELD_IDS = 12;
3282
+ function isFilled(input) {
3283
+ if (typeof input.filled === "boolean") return input.filled;
3284
+ return typeof input.value === "string" && input.value.length > 0;
3285
+ }
3286
+ function fingerprintSnapshot(snap) {
3287
+ const texts = /* @__PURE__ */ new Set();
3288
+ for (const text of snap.texts ?? []) {
3289
+ if (typeof text !== "string") continue;
3290
+ const trimmed = text.trim();
3291
+ if (trimmed) texts.add(trimmed);
3292
+ }
3293
+ let visibleRows = 0;
3294
+ let totalRows = 0;
3295
+ for (const list of snap.lists ?? []) {
3296
+ visibleRows += list.items?.length ?? 0;
3297
+ if (typeof list.itemCount === "number") totalRows += list.itemCount;
3298
+ }
3299
+ const filledDeclaredFields = /* @__PURE__ */ new Set();
3300
+ let anyInvalid = false;
3301
+ for (const input of snap.inputs ?? []) {
3302
+ if (input.invalid === true) anyInvalid = true;
3303
+ if (input.provenance !== "declared") continue;
3304
+ if (!input.id) continue;
3305
+ if (isFilled(input)) filledDeclaredFields.add(input.id);
3306
+ }
3307
+ return {
3308
+ route: snap.route ?? null,
3309
+ modal: snap.modalOpen === true || !!snap.nativeDialog,
3310
+ loading: snap.loading === true,
3311
+ texts,
3312
+ visibleRows,
3313
+ totalRows,
3314
+ filledDeclaredFields,
3315
+ anyInvalid
3316
+ };
3317
+ }
3318
+ function countMissing(from, inSet) {
3319
+ let n = 0;
3320
+ for (const value of from) if (!inSet.has(value)) n += 1;
3321
+ return n;
3322
+ }
3323
+ function namesMissing(from, inSet) {
3324
+ const out = [];
3325
+ for (const value of from) if (!inSet.has(value)) out.push(value);
3326
+ out.sort();
3327
+ return out.slice(0, MAX_DELTA_FIELD_IDS);
3328
+ }
3329
+ function diffFingerprints(prev, next) {
3330
+ const delta = {};
3331
+ if (prev.route !== next.route) delta.routeChanged = true;
3332
+ if (!prev.modal && next.modal) delta.modalOpened = true;
3333
+ if (prev.modal && !next.modal) delta.modalClosed = true;
3334
+ if (!prev.loading && next.loading) delta.loadingStarted = true;
3335
+ if (prev.loading && !next.loading) delta.loadingFinished = true;
3336
+ const added = countMissing(next.texts, prev.texts);
3337
+ const removed = countMissing(prev.texts, next.texts);
3338
+ if (added > 0) delta.textsAdded = added;
3339
+ if (removed > 0) delta.textsRemoved = removed;
3340
+ const visibleRowsDelta = next.visibleRows - prev.visibleRows;
3341
+ if (visibleRowsDelta !== 0) delta.visibleRowsDelta = visibleRowsDelta;
3342
+ const totalRowsDelta = next.totalRows - prev.totalRows;
3343
+ if (totalRowsDelta !== 0) delta.totalRowsDelta = totalRowsDelta;
3344
+ const newlyFilled = namesMissing(next.filledDeclaredFields, prev.filledDeclaredFields);
3345
+ if (newlyFilled.length > 0) delta.fieldsNewlyFilled = newlyFilled;
3346
+ const cleared = namesMissing(prev.filledDeclaredFields, next.filledDeclaredFields);
3347
+ if (cleared.length > 0) delta.fieldsCleared = cleared;
3348
+ if (!prev.anyInvalid && next.anyInvalid) delta.invalidAppeared = true;
3349
+ if (Object.keys(delta).length === 0) delta.unchanged = true;
3350
+ return delta;
3351
+ }
3352
+ function createSnapshotDeltaTracker() {
3353
+ let previous;
3354
+ return {
3355
+ observe(snap) {
3356
+ const next = fingerprintSnapshot(snap);
3357
+ const delta = previous ? diffFingerprints(previous, next) : void 0;
3358
+ previous = next;
3359
+ return delta;
3360
+ },
3361
+ reset() {
3362
+ previous = void 0;
3363
+ }
3364
+ };
3365
+ }
3267
3366
 
3268
3367
  // src/registry/ComponentRegistry.ts
3269
3368
  var ComponentRegistryImpl = class {
@@ -4465,6 +4564,37 @@ function rectForNode(node) {
4465
4564
  return _rectByNode.get(node);
4466
4565
  }
4467
4566
 
4567
+ // src/introspection/focusState.ts
4568
+ var _host2 = null;
4569
+ function setFocusHost(host) {
4570
+ _host2 = host;
4571
+ }
4572
+ var HOST_SEARCH_DEPTH2 = 6;
4573
+ function isFiberFocused(fiber) {
4574
+ const host = _host2;
4575
+ if (!host || !fiber) return void 0;
4576
+ let focused = null;
4577
+ try {
4578
+ focused = host.focusedInstance();
4579
+ } catch {
4580
+ return void 0;
4581
+ }
4582
+ if (!focused) return false;
4583
+ const stack = [{ node: fiber, depth: 0 }];
4584
+ while (stack.length > 0) {
4585
+ const { node, depth } = stack.pop();
4586
+ const current = node;
4587
+ if (!current) continue;
4588
+ if (current.stateNode && current.stateNode === focused) return true;
4589
+ if (depth < HOST_SEARCH_DEPTH2 && current.child) {
4590
+ stack.push({ node: current.child, depth: depth + 1 });
4591
+ const sibling = current.child.sibling;
4592
+ if (sibling) stack.push({ node: sibling, depth: depth + 1 });
4593
+ }
4594
+ }
4595
+ return false;
4596
+ }
4597
+
4468
4598
  // src/introspection/walkFiber.ts
4469
4599
  function getTypeName(type) {
4470
4600
  if (!type) return null;
@@ -4533,6 +4663,71 @@ function isAppilotsSensitiveMarked(props) {
4533
4663
  if (!props) return false;
4534
4664
  return props.__appilotsSensitive === true || props["data-appilots-sensitive"] === true || props.appilotsSensitive === true;
4535
4665
  }
4666
+ function booleanish(value) {
4667
+ if (typeof value === "boolean") return value;
4668
+ if (typeof value === "string") {
4669
+ const normalized = value.trim().toLowerCase();
4670
+ if (normalized === "true") return true;
4671
+ if (normalized === "false") return false;
4672
+ }
4673
+ return void 0;
4674
+ }
4675
+ function firstBooleanish(props, keys) {
4676
+ for (const key of keys) {
4677
+ const resolved = booleanish(props?.[key]);
4678
+ if (resolved !== void 0) return resolved;
4679
+ }
4680
+ return void 0;
4681
+ }
4682
+ function readRequiredProp(props) {
4683
+ const direct = firstBooleanish(props, [
4684
+ "required",
4685
+ "aria-required",
4686
+ "isRequired",
4687
+ "accessibilityRequired"
4688
+ ]);
4689
+ if (direct !== void 0) return direct;
4690
+ return booleanish(props?.accessibilityState?.required);
4691
+ }
4692
+ function readInvalidProp(props) {
4693
+ const direct = firstBooleanish(props, ["invalid", "aria-invalid", "isInvalid", "hasError"]);
4694
+ if (direct !== void 0) return direct;
4695
+ const state = booleanish(props?.accessibilityState?.invalid);
4696
+ if (state !== void 0) return state;
4697
+ for (const key of ["error", "errorMessage", "errorText"]) {
4698
+ const value = props?.[key];
4699
+ const asBoolean = booleanish(value);
4700
+ if (asBoolean !== void 0) return asBoolean;
4701
+ if (typeof value === "string") return value.trim().length > 0;
4702
+ }
4703
+ return void 0;
4704
+ }
4705
+ var WRAPPER_PROP_SEARCH_DEPTH = 3;
4706
+ var WRAPPER_SUBTREE_BUDGET = 64;
4707
+ function wrapsExactlyOneInput(fiber) {
4708
+ let seen = 0;
4709
+ let visited = 0;
4710
+ const stack = [fiber?.child];
4711
+ while (stack.length > 0) {
4712
+ if ((visited += 1) > WRAPPER_SUBTREE_BUDGET) return false;
4713
+ const node = stack.pop();
4714
+ if (!node) continue;
4715
+ if (matchesName(node, "TextInput") && (seen += 1) > 1) return false;
4716
+ if (node.child) stack.push(node.child);
4717
+ if (node.sibling) stack.push(node.sibling);
4718
+ }
4719
+ return seen === 1;
4720
+ }
4721
+ function readWrapperProps(fiber) {
4722
+ let node = fiber?.return;
4723
+ for (let depth = 0; node && depth < WRAPPER_PROP_SEARCH_DEPTH; depth += 1, node = node.return) {
4724
+ const props = node.memoizedProps;
4725
+ if (!props) continue;
4726
+ if (readInvalidProp(props) === void 0 && readRequiredProp(props) === void 0) continue;
4727
+ return wrapsExactlyOneInput(node) ? props : void 0;
4728
+ }
4729
+ return void 0;
4730
+ }
4536
4731
  function stringProp(props, name) {
4537
4732
  const value = props?.[name];
4538
4733
  return typeof value === "string" && value.length > 0 ? value : void 0;
@@ -4588,6 +4783,8 @@ function isSwitchLike(fiber, props) {
4588
4783
  }
4589
4784
  function switchValue(props) {
4590
4785
  if (typeof props?.value === "boolean") return props.value;
4786
+ const aria = props?.["aria-checked"];
4787
+ if (typeof aria === "boolean") return aria;
4591
4788
  const checked = props?.accessibilityState?.checked;
4592
4789
  return checked === true;
4593
4790
  }
@@ -4981,6 +5178,11 @@ function walkSubtree(root, detectLists, startInModal = false) {
4981
5178
  const isSecure = !!props.secureTextEntry || isAppilotsSensitiveMarked(props);
4982
5179
  const rawValue = typeof props.value === "string" ? props.value : void 0;
4983
5180
  const safeValue = isSecure ? rawValue && rawValue.length > 0 ? "<hidden>" : void 0 : rawValue;
5181
+ const filled = typeof rawValue === "string" ? rawValue.length > 0 : void 0;
5182
+ const wrapper = readWrapperProps(fiber);
5183
+ const required = readRequiredProp(props) ?? (wrapper ? readRequiredProp(wrapper) : void 0);
5184
+ const invalid = readInvalidProp(props) ?? (wrapper ? readInvalidProp(wrapper) : void 0);
5185
+ const focused = isFiberFocused(fiber);
4984
5186
  const identity = resolveIdentity(props, inferLabel(props));
4985
5187
  const geom = readGeometry(fiber);
4986
5188
  result.inputs.push(
@@ -4994,6 +5196,13 @@ function walkSubtree(root, detectLists, startInModal = false) {
4994
5196
  editable: props.editable !== false,
4995
5197
  secure: isSecure,
4996
5198
  type: inferInputType(props),
5199
+ // Each one omitted rather than sent as `false` when the app
5200
+ // says nothing: absent is UNKNOWN on all four, and the wire
5201
+ // has to keep that apart from a declared negative.
5202
+ ...required !== void 0 ? { required } : {},
5203
+ ...invalid !== void 0 ? { invalid } : {},
5204
+ ...filled !== void 0 ? { filled } : {},
5205
+ ...focused !== void 0 ? { focused } : {},
4997
5206
  // A field whose return key submits (#398). On a search box or
4998
5207
  // a one-field login this IS the submit, and without the flag
4999
5208
  // the screen looks like a form with no way to send it.
@@ -5468,9 +5677,21 @@ function routesBelongToSameFeature(a, b) {
5468
5677
  // src/navigation/navigationState.ts
5469
5678
  var _currentScreen = null;
5470
5679
  var _navigationRef = null;
5680
+ var MAX_VISITED_SCREENS = 8;
5681
+ var _visited = [];
5682
+ function recordScreenVisit(screen) {
5683
+ if (!screen) return;
5684
+ if (_visited[_visited.length - 1] === screen) return;
5685
+ _visited.push(screen);
5686
+ if (_visited.length > MAX_VISITED_SCREENS) _visited.shift();
5687
+ }
5688
+ function getVisitedScreens() {
5689
+ return [..._visited];
5690
+ }
5471
5691
  function setCurrentScreen(screen) {
5472
5692
  appilotsDebugLog(`setCurrentScreen: "${_currentScreen}" \u2192 "${screen}"`);
5473
5693
  _currentScreen = screen;
5694
+ recordScreenVisit(screen);
5474
5695
  }
5475
5696
  function getCurrentScreen() {
5476
5697
  const activePath = getActiveRouteNames();
@@ -5484,6 +5705,7 @@ function getCurrentScreen() {
5484
5705
  }
5485
5706
  _currentScreen = deepest;
5486
5707
  }
5708
+ recordScreenVisit(deepest);
5487
5709
  if (deepest) return deepest;
5488
5710
  }
5489
5711
  if (_navigationRef) {
@@ -5495,6 +5717,7 @@ function getCurrentScreen() {
5495
5717
  );
5496
5718
  _currentScreen = liveScreen;
5497
5719
  }
5720
+ recordScreenVisit(liveScreen);
5498
5721
  return liveScreen;
5499
5722
  }
5500
5723
  }
@@ -5554,6 +5777,13 @@ function collectRouteNamesFromState(state, out = /* @__PURE__ */ new Set()) {
5554
5777
  }
5555
5778
  return out;
5556
5779
  }
5780
+ function backStackOf(state) {
5781
+ const deepest = getDeepestNavigatorState(state);
5782
+ const routes = deepest?.routes;
5783
+ if (!Array.isArray(routes)) return [];
5784
+ const index = typeof deepest.index === "number" ? deepest.index : routes.length - 1;
5785
+ return routes.slice(0, index + 1).map((route) => route?.name).filter((name) => typeof name === "string" && name.length > 0);
5786
+ }
5557
5787
  function getDeepestNavigatorState(state) {
5558
5788
  if (!state) return void 0;
5559
5789
  const route = state.routes?.[state.index ?? 0];
@@ -5568,7 +5798,8 @@ function getNavigationStateSnapshot() {
5568
5798
  activePath: current,
5569
5799
  rootRouteNames: current,
5570
5800
  currentRouteNames: current,
5571
- routeNames: current
5801
+ routeNames: current,
5802
+ ...getVisitedScreens().length > 0 ? { visited: getVisitedScreens() } : {}
5572
5803
  } : null;
5573
5804
  }
5574
5805
  try {
@@ -5581,20 +5812,27 @@ function getNavigationStateSnapshot() {
5581
5812
  rootRouteNames: current,
5582
5813
  currentRouteNames: current,
5583
5814
  routeNames: current,
5584
- canGoBack: typeof _navigationRef.canGoBack === "function" ? _navigationRef.canGoBack() : void 0
5815
+ canGoBack: typeof _navigationRef.canGoBack === "function" ? _navigationRef.canGoBack() : void 0,
5816
+ ...getVisitedScreens().length > 0 ? { visited: getVisitedScreens() } : {}
5585
5817
  } : null;
5586
5818
  }
5587
5819
  const activePath = getActiveRouteNamesFromState(state);
5588
5820
  const currentRouteName = activePath[activePath.length - 1] ?? _currentScreen ?? void 0;
5589
5821
  const deepestState = getDeepestNavigatorState(state);
5590
5822
  const routeNames = Array.from(collectRouteNamesFromState(state));
5823
+ const backStack = backStackOf(state);
5591
5824
  return {
5592
5825
  currentRouteName,
5593
5826
  activePath,
5594
5827
  rootRouteNames: routeNamesAtState(state),
5595
5828
  currentRouteNames: routeNamesAtState(deepestState),
5596
5829
  routeNames,
5597
- canGoBack: typeof _navigationRef.canGoBack === "function" ? _navigationRef.canGoBack() : void 0
5830
+ canGoBack: typeof _navigationRef.canGoBack === "function" ? _navigationRef.canGoBack() : void 0,
5831
+ // Both omitted when empty rather than sent as `[]`: an empty array
5832
+ // reads as "we looked and there is nowhere to go back to", and on
5833
+ // an app with no reachable navigator we did not look.
5834
+ ...backStack.length > 0 ? { backStack } : {},
5835
+ ...getVisitedScreens().length > 0 ? { visited: getVisitedScreens() } : {}
5598
5836
  };
5599
5837
  } catch {
5600
5838
  const current = _currentScreen ? [_currentScreen] : [];
@@ -5603,7 +5841,8 @@ function getNavigationStateSnapshot() {
5603
5841
  activePath: current,
5604
5842
  rootRouteNames: current,
5605
5843
  currentRouteNames: current,
5606
- routeNames: current
5844
+ routeNames: current,
5845
+ ...getVisitedScreens().length > 0 ? { visited: getVisitedScreens() } : {}
5607
5846
  } : null;
5608
5847
  }
5609
5848
  }
@@ -5757,13 +5996,37 @@ async function waitForLoadingSettle(opts = {}) {
5757
5996
  const stableTarget = opts.stableReads ?? 2;
5758
5997
  const pressedComponentId = opts.pressedComponentId ?? null;
5759
5998
  const fromScreen = opts.fromScreen ?? null;
5999
+ const busyMaxMs = Math.max(maxMs, opts.busyMaxMs ?? 0);
5760
6000
  const startMs = Date.now();
5761
- const remaining = () => maxMs - (Date.now() - startMs);
5762
6001
  let probe = probeLoadingState(pressedComponentId, getCurrentScreen());
5763
6002
  let probeCount = 1;
5764
6003
  let stableCount = 1;
5765
6004
  let lastFingerprint = probe.fingerprint;
5766
6005
  let interval = initialPollMs;
6006
+ let sawBusy = false;
6007
+ let busySpent = false;
6008
+ let extendedPastBudget = false;
6009
+ let graceDeadlineMs;
6010
+ const noteEvidence = () => {
6011
+ const elapsedMs = Date.now() - startMs;
6012
+ if (probe.loading) {
6013
+ if (busySpent) return;
6014
+ sawBusy = true;
6015
+ if (elapsedMs > maxMs) extendedPastBudget = true;
6016
+ return;
6017
+ }
6018
+ if (!sawBusy || busySpent) return;
6019
+ if (extendedPastBudget) {
6020
+ graceDeadlineMs = Math.min(busyMaxMs, elapsedMs + (stableTarget + 1) * pollMaxMs);
6021
+ }
6022
+ busySpent = true;
6023
+ };
6024
+ const remaining = () => {
6025
+ const elapsedMs = Date.now() - startMs;
6026
+ if (probe.loading && !busySpent) return busyMaxMs - elapsedMs;
6027
+ return Math.max(maxMs, graceDeadlineMs ?? 0) - elapsedMs;
6028
+ };
6029
+ noteEvidence();
5767
6030
  const looksImmediatelyClean = !probe.loading && !probe.pressedDisabled && (!pressedComponentId || probe.pressedFound);
5768
6031
  if (looksImmediatelyClean) {
5769
6032
  await sleepMs(Math.min(80, Math.max(0, remaining())));
@@ -5780,6 +6043,7 @@ async function waitForLoadingSettle(opts = {}) {
5780
6043
  };
5781
6044
  }
5782
6045
  probe = recheck;
6046
+ noteEvidence();
5783
6047
  lastFingerprint = recheck.fingerprint;
5784
6048
  stableCount = 1;
5785
6049
  }
@@ -5800,6 +6064,7 @@ async function waitForLoadingSettle(opts = {}) {
5800
6064
  }
5801
6065
  probe = probeLoadingState(pressedComponentId, currentScreen);
5802
6066
  probeCount++;
6067
+ noteEvidence();
5803
6068
  const quiescent = !probe.loading && !probe.pressedDisabled && probe.fingerprint === lastFingerprint;
5804
6069
  if (quiescent) {
5805
6070
  stableCount++;
@@ -5831,7 +6096,7 @@ async function waitForLoadingSettle(opts = {}) {
5831
6096
  }
5832
6097
 
5833
6098
  // src/version.ts
5834
- var SDK_VERSION2 = "0.11.0";
6099
+ var SDK_VERSION2 = "0.11.3";
5835
6100
  var FiberSentinel = class extends React2.Component {
5836
6101
  componentDidMount() {
5837
6102
  const fiber = this._reactInternals ?? this._reactInternalFiber ?? null;
@@ -5975,6 +6240,21 @@ function probeNameMangling(components) {
5975
6240
  return _lastProbe;
5976
6241
  }
5977
6242
 
6243
+ // src/platform/textInputFocus.ts
6244
+ function createTextInputFocusHost(textInput) {
6245
+ const read = textInput?.State?.currentlyFocusedInput;
6246
+ if (typeof read !== "function") return null;
6247
+ return {
6248
+ focusedInstance() {
6249
+ try {
6250
+ return textInput.State.currentlyFocusedInput() ?? null;
6251
+ } catch {
6252
+ return null;
6253
+ }
6254
+ }
6255
+ };
6256
+ }
6257
+
5978
6258
  // src/platform/fabricGeometry.ts
5979
6259
  function readRect(instance) {
5980
6260
  const candidate = instance;
@@ -6105,6 +6385,10 @@ function AppilotsProviderInner({
6105
6385
  setGeometryHost(createFabricGeometryHost(Dimensions));
6106
6386
  return () => setGeometryHost(null);
6107
6387
  }, []);
6388
+ useEffect(() => {
6389
+ setFocusHost(createTextInputFocusHost(TextInput));
6390
+ return () => setFocusHost(null);
6391
+ }, []);
6108
6392
  const trackNativeDialogs = config.trackNativeDialogs !== false;
6109
6393
  useEffect(() => {
6110
6394
  if (!trackNativeDialogs) return;
@@ -6204,4 +6488,4 @@ function clearScreenRegistry() {
6204
6488
  screenRegistry.clear();
6205
6489
  }
6206
6490
 
6207
- export { ActionQueueMachine, AppilotsClient, AppilotsErrorBoundary, AppilotsProvider, AppilotsRegistryProvider, ChatSessionMachine, FORM_VALIDATION_TEXT_RE, FORM_WRITE_SUCCESS_TEXT_RE, OPTIONAL_STEP_AUTOMATION_HINT, RateLimitedError, SDK_VERSION2 as SDK_VERSION, TOUCHABLE_NAMES, _patchJsxRuntimes, actionPressTargetId, appilotsDebugLog, appilotsDebugWarn, attachElementIdsToChoiceGroups, clampSnapshotToWireLimits, clearAppilotsDebugTraces, clearScreenRegistry, componentRegistry, createComponentRegistry, createElementRegistry, createListRegistry, defaultDarkTheme, defaultLightTheme, deriveInteractionElements, describeAction, describeValue, elementRegistry, emptyDetectorCounts, enableAppilotsAutoTracking, findListItems, foldForCompare, geometryAvailability, geometryStats, getActiveRouteNames, getAllScreens, getAppilotsDebugTraces, getAutoTrackingState, getCurrentScreen, getCurrentScreenSignature, getDefaultRegistry, getFiberRoot, getGlobalConfig, getIntrospectionDiagnostics, getLastAnsweredNativeDialog, getLastWalkDiagnostics, getNameManglingProbe, getNavigationRef, getNavigationStateSnapshot, getOpenNativeDialog, getScreenMetadata, getTypeName, humanizeError, idLooselyMatches, initAppilots, isAlwaysListContainer, isAutoTrackingEnabled, isGenericSelectValue, isHeuristicListContainer, isListItemPressTarget, isModalFiber, isOptionalStepAutomationFailure, isReferenceable, isSwitchLike, listRegistry, looksDestructiveActionLabel, matchScore, mergeThemeTokens, normalize3, parseStableElementId, probeLoadingState, recordAppilotsDebugTrace, recordIntrospectionFailure, rectForNode, registerScreen, resolveBaseTheme, routesBelongToSameFeature, screenDepartedBaseline, setCurrentScreen, setNavigationRef, shouldSkipSubtree, snapshotShowsOpenCreateForm, subscribeAppilotsDebugTraces, switchValue, toWireElement, totalDetectorHits, useAppilotsContext, useResolvedRegistry, waitForLoadingSettle, waitForScreenSettle, walkFiber };
6491
+ export { ActionQueueMachine, AppilotsClient, AppilotsErrorBoundary, AppilotsProvider, AppilotsRegistryProvider, ChatSessionMachine, FORM_VALIDATION_TEXT_RE, FORM_WRITE_SUCCESS_TEXT_RE, OPTIONAL_STEP_AUTOMATION_HINT, RateLimitedError, SDK_VERSION2 as SDK_VERSION, TOUCHABLE_NAMES, _patchJsxRuntimes, actionPressTargetId, appilotsDebugLog, appilotsDebugWarn, attachElementIdsToChoiceGroups, clampSnapshotToWireLimits, clearAppilotsDebugTraces, clearScreenRegistry, componentRegistry, createComponentRegistry, createElementRegistry, createListRegistry, createSnapshotDeltaTracker, defaultDarkTheme, defaultLightTheme, deriveInteractionElements, describeAction, describeValue, elementRegistry, emptyDetectorCounts, enableAppilotsAutoTracking, findListItems, foldForCompare, geometryAvailability, geometryStats, getActiveRouteNames, getAllScreens, getAppilotsDebugTraces, getAutoTrackingState, getCurrentScreen, getCurrentScreenSignature, getDefaultRegistry, getFiberRoot, getGlobalConfig, getIntrospectionDiagnostics, getLastAnsweredNativeDialog, getLastWalkDiagnostics, getNameManglingProbe, getNavigationRef, getNavigationStateSnapshot, getOpenNativeDialog, getScreenMetadata, getTypeName, humanizeError, idLooselyMatches, initAppilots, isAlwaysListContainer, isAutoTrackingEnabled, isGenericSelectValue, isHeuristicListContainer, isListItemPressTarget, isModalFiber, isOptionalStepAutomationFailure, isReferenceable, isSwitchLike, listRegistry, looksDestructiveActionLabel, matchScore, mergeThemeTokens, normalize3, parseStableElementId, probeLoadingState, recordAppilotsDebugTrace, recordIntrospectionFailure, rectForNode, registerScreen, resolveBaseTheme, routesBelongToSameFeature, screenDepartedBaseline, setCurrentScreen, setNavigationRef, shouldSkipSubtree, snapshotShowsOpenCreateForm, subscribeAppilotsDebugTraces, switchValue, toWireElement, totalDetectorHits, useAppilotsContext, useResolvedRegistry, waitForLoadingSettle, waitForScreenSettle, walkFiber };