@appilots/sdk 0.11.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -83,6 +83,33 @@ interface ScreenActionMetadata {
83
83
  * between sdk and mcp-generator packages.
84
84
  */
85
85
  appilotsInferred?: AppilotsInferredAction;
86
+ /**
87
+ * How long this action's work takes, in milliseconds, as DECLARED by
88
+ * the app. The SDK waits this long for the UI to settle after pressing
89
+ * it, instead of the agnostic default.
90
+ *
91
+ * There is no constant that fits: an app operation runs from ~100ms to
92
+ * minutes, and when the wait is too short the agent photographs a
93
+ * loading screen with no controls on it and plans against nothing. The
94
+ * app is the only party that knows, so it says so:
95
+ *
96
+ * ```tsx
97
+ * registerScreen({
98
+ * name: 'Wizard',
99
+ * actions: [{ id: 'gerar', label: 'Gerar', type: 'submit', asyncBudgetMs: 90_000 }],
100
+ * });
101
+ * ```
102
+ *
103
+ * Omitting it is supported and common: the SDK then falls back on its
104
+ * defaults (6s, or 10s when the generator inferred the handler is
105
+ * async) AND on what it can see — while the screen keeps a loading
106
+ * indicator up, the wait runs on to a safety ceiling. So declaring is
107
+ * how an app says "this one is QUICK, do not wait on my spinner", or
108
+ * gives a number for work that shows no indicator at all. The value is
109
+ * clamped to `MAX_DECLARED_ASYNC_BUDGET_MS`: a declaration is a
110
+ * request, not a licence to hang the session.
111
+ */
112
+ asyncBudgetMs?: number;
86
113
  }
87
114
  /**
88
115
  * Inferred metadata about an action's expected async behavior.
@@ -149,19 +176,34 @@ interface NavigationConfig {
149
176
  * ```
150
177
  */
151
178
  declare function registerScreen(metadata: ScreenMetadata): void;
152
- /** Get metadata for a specific screen */
179
+ /**
180
+ * Get metadata for a specific screen.
181
+ *
182
+ * Exact name first, then the SAME fold the server applies.
183
+ *
184
+ * The asymmetry this closes: the backend routes by
185
+ * `normalizeRouteIdentity`, so `HomeTab`, `HomeScreen` and `Home` are
186
+ * one screen to it, while this lookup was a bare `Map.get` and they were
187
+ * three. An app whose navigator names a tab `HomeTab` and whose
188
+ * `registerScreen` says `Home` — the ordinary React Navigation shape,
189
+ * since the tab and the screen inside it cannot share a name — served
190
+ * the agent a described screen from the map and `undefined` here, so the
191
+ * title, the description and the declared risk of the screen it was
192
+ * standing on all went missing. Nothing reported an error; the metadata
193
+ * was simply absent.
194
+ *
195
+ * Folded matching only resolves an UNAMBIGUOUS hit. Two screens that
196
+ * fold together (`Orders` and `OrdersTab` both registered) are a real
197
+ * ambiguity, and guessing between them would be worse than the miss:
198
+ * the caller gets `undefined` and the app keeps the exact-name contract
199
+ * it already had.
200
+ */
153
201
  declare function getScreenMetadata(name: string): ScreenMetadata | undefined;
154
202
  /** Get all registered screens */
155
203
  declare function getAllScreens(): ScreenMetadata[];
156
204
  /** Clear the screen registry (useful for testing) */
157
205
  declare function clearScreenRegistry(): void;
158
206
 
159
- /**
160
- * Global navigation state — lightweight singleton so any part of the SDK
161
- * can read the current screen without needing a React context or hook.
162
- *
163
- * Updated by AppilotsNavigationContainer and read by useAppilotsChat().
164
- */
165
207
  /**
166
208
  * Tell the SDK which screen the user is on.
167
209
  *
@@ -199,6 +241,21 @@ interface RuntimeNavigationStateSnapshot {
199
241
  routeNames: string[];
200
242
  /** Whether React Navigation reports that goBack() can currently run. */
201
243
  canGoBack?: boolean;
244
+ /**
245
+ * The stack a back press pops through, oldest first, ending on the
246
+ * current screen. Empty when no navigator is reachable.
247
+ *
248
+ * `canGoBack` says a back exists; this says where it goes.
249
+ */
250
+ backStack?: string[];
251
+ /**
252
+ * Screens the user has been on this session, oldest first, capped and
253
+ * with consecutive repeats collapsed.
254
+ *
255
+ * Different from `backStack`: a popped screen leaves the stack and
256
+ * stays here, and a tab switch never touches the stack at all.
257
+ */
258
+ visited?: string[];
202
259
  }
203
260
  /**
204
261
  * Snapshot the runtime navigation tree in a small, serializable shape.
@@ -83,6 +83,33 @@ interface ScreenActionMetadata {
83
83
  * between sdk and mcp-generator packages.
84
84
  */
85
85
  appilotsInferred?: AppilotsInferredAction;
86
+ /**
87
+ * How long this action's work takes, in milliseconds, as DECLARED by
88
+ * the app. The SDK waits this long for the UI to settle after pressing
89
+ * it, instead of the agnostic default.
90
+ *
91
+ * There is no constant that fits: an app operation runs from ~100ms to
92
+ * minutes, and when the wait is too short the agent photographs a
93
+ * loading screen with no controls on it and plans against nothing. The
94
+ * app is the only party that knows, so it says so:
95
+ *
96
+ * ```tsx
97
+ * registerScreen({
98
+ * name: 'Wizard',
99
+ * actions: [{ id: 'gerar', label: 'Gerar', type: 'submit', asyncBudgetMs: 90_000 }],
100
+ * });
101
+ * ```
102
+ *
103
+ * Omitting it is supported and common: the SDK then falls back on its
104
+ * defaults (6s, or 10s when the generator inferred the handler is
105
+ * async) AND on what it can see — while the screen keeps a loading
106
+ * indicator up, the wait runs on to a safety ceiling. So declaring is
107
+ * how an app says "this one is QUICK, do not wait on my spinner", or
108
+ * gives a number for work that shows no indicator at all. The value is
109
+ * clamped to `MAX_DECLARED_ASYNC_BUDGET_MS`: a declaration is a
110
+ * request, not a licence to hang the session.
111
+ */
112
+ asyncBudgetMs?: number;
86
113
  }
87
114
  /**
88
115
  * Inferred metadata about an action's expected async behavior.
@@ -149,19 +176,34 @@ interface NavigationConfig {
149
176
  * ```
150
177
  */
151
178
  declare function registerScreen(metadata: ScreenMetadata): void;
152
- /** Get metadata for a specific screen */
179
+ /**
180
+ * Get metadata for a specific screen.
181
+ *
182
+ * Exact name first, then the SAME fold the server applies.
183
+ *
184
+ * The asymmetry this closes: the backend routes by
185
+ * `normalizeRouteIdentity`, so `HomeTab`, `HomeScreen` and `Home` are
186
+ * one screen to it, while this lookup was a bare `Map.get` and they were
187
+ * three. An app whose navigator names a tab `HomeTab` and whose
188
+ * `registerScreen` says `Home` — the ordinary React Navigation shape,
189
+ * since the tab and the screen inside it cannot share a name — served
190
+ * the agent a described screen from the map and `undefined` here, so the
191
+ * title, the description and the declared risk of the screen it was
192
+ * standing on all went missing. Nothing reported an error; the metadata
193
+ * was simply absent.
194
+ *
195
+ * Folded matching only resolves an UNAMBIGUOUS hit. Two screens that
196
+ * fold together (`Orders` and `OrdersTab` both registered) are a real
197
+ * ambiguity, and guessing between them would be worse than the miss:
198
+ * the caller gets `undefined` and the app keeps the exact-name contract
199
+ * it already had.
200
+ */
153
201
  declare function getScreenMetadata(name: string): ScreenMetadata | undefined;
154
202
  /** Get all registered screens */
155
203
  declare function getAllScreens(): ScreenMetadata[];
156
204
  /** Clear the screen registry (useful for testing) */
157
205
  declare function clearScreenRegistry(): void;
158
206
 
159
- /**
160
- * Global navigation state — lightweight singleton so any part of the SDK
161
- * can read the current screen without needing a React context or hook.
162
- *
163
- * Updated by AppilotsNavigationContainer and read by useAppilotsChat().
164
- */
165
207
  /**
166
208
  * Tell the SDK which screen the user is on.
167
209
  *
@@ -199,6 +241,21 @@ interface RuntimeNavigationStateSnapshot {
199
241
  routeNames: string[];
200
242
  /** Whether React Navigation reports that goBack() can currently run. */
201
243
  canGoBack?: boolean;
244
+ /**
245
+ * The stack a back press pops through, oldest first, ending on the
246
+ * current screen. Empty when no navigator is reachable.
247
+ *
248
+ * `canGoBack` says a back exists; this says where it goes.
249
+ */
250
+ backStack?: string[];
251
+ /**
252
+ * Screens the user has been on this session, oldest first, capped and
253
+ * with consecutive repeats collapsed.
254
+ *
255
+ * Different from `backStack`: a popped screen leaves the stack and
256
+ * stays here, and a tab switch never touches the stack at all.
257
+ */
258
+ visited?: string[];
202
259
  }
203
260
  /**
204
261
  * Snapshot the runtime navigation tree in a small, serializable shape.
@@ -1,51 +1,51 @@
1
1
  'use strict';
2
2
 
3
- var chunkDRXUBLGY_js = require('../chunk-DRXUBLGY.js');
4
- var chunkKZ2BLMS4_js = require('../chunk-KZ2BLMS4.js');
3
+ var chunkALJGKORS_js = require('../chunk-ALJGKORS.js');
4
+ var chunkUZ2D4BVX_js = require('../chunk-UZ2D4BVX.js');
5
5
 
6
6
 
7
7
 
8
8
  Object.defineProperty(exports, "AppilotsNavigationContainer", {
9
9
  enumerable: true,
10
- get: function () { return chunkDRXUBLGY_js.AppilotsNavigationContainer; }
10
+ get: function () { return chunkALJGKORS_js.AppilotsNavigationContainer; }
11
11
  });
12
12
  Object.defineProperty(exports, "clearScreenRegistry", {
13
13
  enumerable: true,
14
- get: function () { return chunkKZ2BLMS4_js.clearScreenRegistry; }
14
+ get: function () { return chunkUZ2D4BVX_js.clearScreenRegistry; }
15
15
  });
16
16
  Object.defineProperty(exports, "getActiveRouteNames", {
17
17
  enumerable: true,
18
- get: function () { return chunkKZ2BLMS4_js.getActiveRouteNames; }
18
+ get: function () { return chunkUZ2D4BVX_js.getActiveRouteNames; }
19
19
  });
20
20
  Object.defineProperty(exports, "getAllScreens", {
21
21
  enumerable: true,
22
- get: function () { return chunkKZ2BLMS4_js.getAllScreens; }
22
+ get: function () { return chunkUZ2D4BVX_js.getAllScreens; }
23
23
  });
24
24
  Object.defineProperty(exports, "getCurrentScreen", {
25
25
  enumerable: true,
26
- get: function () { return chunkKZ2BLMS4_js.getCurrentScreen; }
26
+ get: function () { return chunkUZ2D4BVX_js.getCurrentScreen; }
27
27
  });
28
28
  Object.defineProperty(exports, "getNavigationRef", {
29
29
  enumerable: true,
30
- get: function () { return chunkKZ2BLMS4_js.getNavigationRef; }
30
+ get: function () { return chunkUZ2D4BVX_js.getNavigationRef; }
31
31
  });
32
32
  Object.defineProperty(exports, "getNavigationStateSnapshot", {
33
33
  enumerable: true,
34
- get: function () { return chunkKZ2BLMS4_js.getNavigationStateSnapshot; }
34
+ get: function () { return chunkUZ2D4BVX_js.getNavigationStateSnapshot; }
35
35
  });
36
36
  Object.defineProperty(exports, "getScreenMetadata", {
37
37
  enumerable: true,
38
- get: function () { return chunkKZ2BLMS4_js.getScreenMetadata; }
38
+ get: function () { return chunkUZ2D4BVX_js.getScreenMetadata; }
39
39
  });
40
40
  Object.defineProperty(exports, "registerScreen", {
41
41
  enumerable: true,
42
- get: function () { return chunkKZ2BLMS4_js.registerScreen; }
42
+ get: function () { return chunkUZ2D4BVX_js.registerScreen; }
43
43
  });
44
44
  Object.defineProperty(exports, "setCurrentScreen", {
45
45
  enumerable: true,
46
- get: function () { return chunkKZ2BLMS4_js.setCurrentScreen; }
46
+ get: function () { return chunkUZ2D4BVX_js.setCurrentScreen; }
47
47
  });
48
48
  Object.defineProperty(exports, "setNavigationRef", {
49
49
  enumerable: true,
50
- get: function () { return chunkKZ2BLMS4_js.setNavigationRef; }
50
+ get: function () { return chunkUZ2D4BVX_js.setNavigationRef; }
51
51
  });
@@ -1,2 +1,2 @@
1
- export { AppilotsNavigationContainer } from '../chunk-LLH7QCSX.mjs';
2
- export { clearScreenRegistry, getActiveRouteNames, getAllScreens, getCurrentScreen, getNavigationRef, getNavigationStateSnapshot, getScreenMetadata, registerScreen, setCurrentScreen, setNavigationRef } from '../chunk-PQZ64CCE.mjs';
1
+ export { AppilotsNavigationContainer } from '../chunk-L3SY3Q4K.mjs';
2
+ export { clearScreenRegistry, getActiveRouteNames, getAllScreens, getCurrentScreen, getNavigationRef, getNavigationStateSnapshot, getScreenMetadata, registerScreen, setCurrentScreen, setNavigationRef } from '../chunk-HRWBK35Y.mjs';
package/metro.d.ts CHANGED
@@ -13,17 +13,15 @@
13
13
  export interface MetroConfigLike {
14
14
  projectRoot?: string;
15
15
  resolver?: {
16
- resolveRequest?: (
17
- context: unknown,
18
- moduleName: string,
19
- platform: string | null,
20
- ) => unknown;
16
+ resolveRequest?: (context: unknown, moduleName: string, platform: string | null) => unknown;
21
17
  [option: string]: unknown;
22
18
  };
23
19
  [option: string]: unknown;
24
20
  }
25
21
 
26
22
  export interface WithAppilotsOptions {
23
+ /** CLI output directory containing control-evidence.json. Defaults to .appilots. */
24
+ outputDir?: string;
27
25
  /**
28
26
  * Path to the `.appilotsrc` file.
29
27
  *
package/metro.js CHANGED
@@ -65,13 +65,16 @@ function missingConfigError(configPath) {
65
65
  );
66
66
  }
67
67
 
68
- function createShimSource(configJSON) {
68
+ function createShimSource(configJSON, sessionStorageAvailable) {
69
69
  return (
70
70
  '// Auto-generated by @appilots/sdk/metro — do not edit\n' +
71
71
  'var _g = typeof globalThis !== "undefined" ? globalThis : global;\n' +
72
72
  'var _config = ' +
73
73
  configJSON +
74
74
  ';\n' +
75
+ (sessionStorageAvailable
76
+ ? 'try { var _storage = require("@react-native-async-storage/async-storage");\n_config.sessionStorage = _storage.default || _storage; } catch (_storageError) {}\n'
77
+ : '') +
75
78
  '_g.__APPILOTS_RC__ = _config;\n' +
76
79
  '\n' +
77
80
  '// Call initAppilots immediately so auto-tracking patches React.createElement\n' +
@@ -99,9 +102,9 @@ function createShimSource(configJSON) {
99
102
  );
100
103
  }
101
104
 
102
- function writeShim(configJSON, shimPath) {
105
+ function writeShim(configJSON, shimPath, sessionStorageAvailable) {
103
106
  fs.mkdirSync(path.dirname(shimPath), { recursive: true });
104
- fs.writeFileSync(shimPath, createShimSource(configJSON));
107
+ fs.writeFileSync(shimPath, createShimSource(configJSON, sessionStorageAvailable));
105
108
  }
106
109
 
107
110
  function withAppilots(metroConfig, options = {}) {
@@ -136,12 +139,47 @@ function withAppilots(metroConfig, options = {}) {
136
139
  // A malformed .appilotsrc throws from JSON.parse and is NOT downgraded to
137
140
  // a warning either: the shim would be stale or absent, and the failure
138
141
  // would resurface later as an unresolvable module.
139
- writeShim(parseConfigJSON(source), shimPath);
142
+ // Reuse an existing host dependency; never add a native dependency to the app.
143
+ let sessionStorageAvailable = false;
144
+ if (JSON.parse(source).sessionPersistence !== false) {
145
+ try {
146
+ require.resolve('@react-native-async-storage/async-storage', { paths: [projectRoot] });
147
+ sessionStorageAvailable = true;
148
+ } catch {
149
+ /* Hosts without storage keep in-memory sessions. */
150
+ }
151
+ }
152
+ writeShim(parseConfigJSON(source), shimPath, sessionStorageAvailable);
140
153
  lastConfigSource = source;
141
154
  };
142
155
 
143
156
  ensureShimFresh();
144
157
 
158
+ // The existing Metro integration consumes the CLI's revision-bound evidence.
159
+ // Client JSX files remain unchanged; chain the original transformer.
160
+ const upstream = metroConfig.transformer?.babelTransformerPath;
161
+ let transformerPath;
162
+ if (upstream) {
163
+ transformerPath = path.join(shimDir, 'control-transformer.js');
164
+ const evidencePath = path.resolve(
165
+ projectRoot,
166
+ options.outputDir || '.appilots',
167
+ 'control-evidence.json',
168
+ );
169
+ fs.writeFileSync(
170
+ transformerPath,
171
+ 'module.exports = require(' +
172
+ JSON.stringify(path.join(__dirname, 'control-transformer.js')) +
173
+ ').createControlTransformer(' +
174
+ JSON.stringify(upstream) +
175
+ ',' +
176
+ JSON.stringify(evidencePath) +
177
+ ',' +
178
+ JSON.stringify(projectRoot) +
179
+ ');\n',
180
+ );
181
+ }
182
+
145
183
  // Resolve `@appilots/auto-init` to the shim
146
184
  const existingResolveRequest = metroConfig.resolver?.resolveRequest;
147
185
 
@@ -160,7 +198,10 @@ function withAppilots(metroConfig, options = {}) {
160
198
  return context.resolveRequest(context, moduleName, platform);
161
199
  },
162
200
  },
163
- transformer: withPreservedNames(metroConfig.transformer),
201
+ transformer: {
202
+ ...withPreservedNames(metroConfig.transformer),
203
+ ...(transformerPath ? { babelTransformerPath: transformerPath } : {}),
204
+ },
164
205
  };
165
206
  }
166
207
 
package/package.json CHANGED
@@ -1,12 +1,16 @@
1
1
  {
2
2
  "name": "@appilots/sdk",
3
- "version": "0.11.1",
3
+ "version": "0.13.0",
4
4
  "description": "AI-powered chat component and navigation SDK for React Native apps",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
8
8
  "exports": {
9
9
  ".": {
10
+ "react-native": {
11
+ "types": "./dist/index.d.ts",
12
+ "default": "./dist/index.js"
13
+ },
10
14
  "import": {
11
15
  "types": "./dist/index.d.mts",
12
16
  "default": "./dist/index.mjs"
@@ -17,6 +21,10 @@
17
21
  }
18
22
  },
19
23
  "./navigation": {
24
+ "react-native": {
25
+ "types": "./dist/navigation/index.d.ts",
26
+ "default": "./dist/navigation/index.js"
27
+ },
20
28
  "import": {
21
29
  "types": "./dist/navigation/index.d.mts",
22
30
  "default": "./dist/navigation/index.mjs"
@@ -27,6 +35,10 @@
27
35
  }
28
36
  },
29
37
  "./hooks": {
38
+ "react-native": {
39
+ "types": "./dist/hooks/index.d.ts",
40
+ "default": "./dist/hooks/index.js"
41
+ },
30
42
  "import": {
31
43
  "types": "./dist/hooks/index.d.mts",
32
44
  "default": "./dist/hooks/index.mjs"
@@ -44,6 +56,7 @@
44
56
  "files": [
45
57
  "dist",
46
58
  "metro.js",
59
+ "control-transformer.js",
47
60
  "metro.d.ts",
48
61
  "navigation",
49
62
  "hooks",
@@ -105,16 +118,17 @@
105
118
  "@types/react": "^18.3.0",
106
119
  "@types/react-native": "^0.73.0",
107
120
  "@types/react-test-renderer": "^18.3.0",
121
+ "@vitest/coverage-v8": "^2.1.0",
108
122
  "react": "^18.3.0",
109
123
  "react-19": "npm:react@^19.0.0",
110
124
  "react-native": "^0.76.0",
125
+ "react-native-paper": "5.14.5",
111
126
  "react-native-safe-area-context": "^5.0.0",
112
127
  "react-native-svg": "15.8.0",
113
128
  "react-test-renderer": "^18.3.1",
114
129
  "react-test-renderer-19": "npm:react-test-renderer@^19.0.0",
115
130
  "tsup": "^8.3.0",
116
131
  "typescript": "^5.6.0",
117
- "@vitest/coverage-v8": "^2.1.0",
118
132
  "vitest": "^2.1.0"
119
133
  }
120
134
  }