@appilots/web-sdk 0.2.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.
- package/README.md +216 -0
- package/dist/AppilotsWebRuntime-DZF26UyU.d.mts +1389 -0
- package/dist/AppilotsWebRuntime-DZF26UyU.d.ts +1389 -0
- package/dist/chunk-T5TUFZ6T.js +10442 -0
- package/dist/chunk-VHRE4RUO.mjs +10382 -0
- package/dist/index.d.mts +1010 -0
- package/dist/index.d.ts +1010 -0
- package/dist/index.js +234 -0
- package/dist/index.mjs +1 -0
- package/dist/react/index.d.mts +84 -0
- package/dist/react/index.d.ts +84 -0
- package/dist/react/index.js +748 -0
- package/dist/react/index.mjs +738 -0
- package/package.json +76 -0
|
@@ -0,0 +1,1389 @@
|
|
|
1
|
+
/** Source facts, not an executable locator or proof of a business outcome. */
|
|
2
|
+
interface ControlEvidence {
|
|
3
|
+
version: 1;
|
|
4
|
+
siteId: string;
|
|
5
|
+
component: string;
|
|
6
|
+
icons: string[];
|
|
7
|
+
handler?: string;
|
|
8
|
+
calls: string[];
|
|
9
|
+
argumentBindings: string[];
|
|
10
|
+
conditions: string[];
|
|
11
|
+
nativeConfirmation?: {
|
|
12
|
+
title?: string;
|
|
13
|
+
destructiveOption: boolean;
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/** Receipt of the app callback actually invoked after user approval; not database proof. */
|
|
17
|
+
interface NativeConfirmationReceipt {
|
|
18
|
+
title: string;
|
|
19
|
+
message?: string;
|
|
20
|
+
buttonLabel?: string;
|
|
21
|
+
handlerCompleted: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Customer conversation/UI locales. Map text and host-app labels may use other languages. */
|
|
25
|
+
declare const SUPPORTED_APPILOTS_LOCALES: readonly ["pt-BR", "en", "es", "fr"];
|
|
26
|
+
type SupportedAppilotsLocale = (typeof SUPPORTED_APPILOTS_LOCALES)[number];
|
|
27
|
+
|
|
28
|
+
type MissionStatus = 'awaiting_approval' | 'running' | 'paused' | 'blocked' | 'completed' | 'cancelled';
|
|
29
|
+
interface MissionScope {
|
|
30
|
+
id: string;
|
|
31
|
+
revision: number;
|
|
32
|
+
operation: 'delete';
|
|
33
|
+
screen: string;
|
|
34
|
+
listId: string;
|
|
35
|
+
items: Array<{
|
|
36
|
+
key: string;
|
|
37
|
+
label: string;
|
|
38
|
+
}>;
|
|
39
|
+
expiresAt: number;
|
|
40
|
+
}
|
|
41
|
+
interface MissionView {
|
|
42
|
+
scope: MissionScope;
|
|
43
|
+
objective: string;
|
|
44
|
+
status: MissionStatus;
|
|
45
|
+
completed: number;
|
|
46
|
+
total: number;
|
|
47
|
+
reason?: string;
|
|
48
|
+
progressText: string;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Theme tokens — the source of truth for any color, font, radius or
|
|
53
|
+
* shadow used inside the SDK's chat UI. Components must read from these
|
|
54
|
+
* tokens (via {@link useAppilotsTheme}) rather than hardcoding values, so
|
|
55
|
+
* a developer who sets a `theme` prop or dashboard config can re-skin
|
|
56
|
+
* the whole chat without forking the SDK.
|
|
57
|
+
*
|
|
58
|
+
* BACKLOG 4.1.
|
|
59
|
+
*
|
|
60
|
+
* Adding a new token:
|
|
61
|
+
* 1. Add a field to `AppilotsThemeTokens` below.
|
|
62
|
+
* 2. Set a value in BOTH `defaultLight` and `defaultDark` (and ideally
|
|
63
|
+
* keep them visually consistent — e.g. dark text on light bg ↔
|
|
64
|
+
* light text on dark bg).
|
|
65
|
+
* 3. Use it in components via `useAppilotsTheme()`.
|
|
66
|
+
* 4. Mirror the field on `themeTokensSchema` in `@appilots/shared` if
|
|
67
|
+
* it should be settable from the dashboard.
|
|
68
|
+
*
|
|
69
|
+
* Removing a token is a breaking change — bump the SDK minor.
|
|
70
|
+
*/
|
|
71
|
+
interface AppilotsThemeTokens {
|
|
72
|
+
/** Hex color values. The 8-digit form (#RRGGBBAA) is allowed. */
|
|
73
|
+
colors: {
|
|
74
|
+
/** Brand color — send button, FAB, header dot, links. */
|
|
75
|
+
primary: string;
|
|
76
|
+
/** Secondary brand accent — used sparingly (e.g. the human-agent/operator icon accent in message rows). */
|
|
77
|
+
secondary: string;
|
|
78
|
+
/** Page/modal background, behind the messages list. */
|
|
79
|
+
background: string;
|
|
80
|
+
/** Surface color for assistant message bubbles, input field, chips. */
|
|
81
|
+
surface: string;
|
|
82
|
+
/** Default text color (high contrast on background). */
|
|
83
|
+
text: string;
|
|
84
|
+
/** Muted text — placeholders, secondary labels, "Powered by". */
|
|
85
|
+
textSecondary: string;
|
|
86
|
+
/** 1px hairlines under header, around chips. */
|
|
87
|
+
border: string;
|
|
88
|
+
/** Success state — completed action checks in the breadcrumb (ActionBreadcrumb `success` tone). */
|
|
89
|
+
success: string;
|
|
90
|
+
/**
|
|
91
|
+
* Warning / caution state — drives the breadcrumb's `pending` tone
|
|
92
|
+
* (queued, not-yet-run actions). The default is a neutral gray, not
|
|
93
|
+
* amber, so it renders pixel-identical to the SDK's pre-4.2 hardcoded
|
|
94
|
+
* palette out of the box; set an amber/yellow here to make queued
|
|
95
|
+
* actions visually louder.
|
|
96
|
+
*/
|
|
97
|
+
warning: string;
|
|
98
|
+
/** Error state — failed action rows (ActionBreadcrumb `failed` tone) and destructive badges/CTAs (confirm card, reject button). */
|
|
99
|
+
error: string;
|
|
100
|
+
/** Text color for chip/button labels (e.g. suggested prompts). */
|
|
101
|
+
buttonText: string;
|
|
102
|
+
/** Text color inside the assistant's message bubble (not the user's — that stays white on `primary`). */
|
|
103
|
+
bubbleText: string;
|
|
104
|
+
/**
|
|
105
|
+
* Text color inside the USER's message bubble (on `primary`). Optional
|
|
106
|
+
* mirror of `bubbleText` for the user side — falls back to white,
|
|
107
|
+
* matching the SDK's pre-4.2 hardcoded value, when unset.
|
|
108
|
+
*/
|
|
109
|
+
userBubbleText: string;
|
|
110
|
+
};
|
|
111
|
+
typography: {
|
|
112
|
+
/** Font family applied to ALL chat text. Falls back to system. */
|
|
113
|
+
fontFamily: string;
|
|
114
|
+
fontSizeXs: number;
|
|
115
|
+
fontSizeSm: number;
|
|
116
|
+
fontSizeBase: number;
|
|
117
|
+
fontSizeLg: number;
|
|
118
|
+
};
|
|
119
|
+
radii: {
|
|
120
|
+
/** Tight corners — chips, small badges. */
|
|
121
|
+
sm: number;
|
|
122
|
+
/** Default — input field, send button, secondary buttons. */
|
|
123
|
+
md: number;
|
|
124
|
+
/** Generous — chat sheet, message bubbles. */
|
|
125
|
+
lg: number;
|
|
126
|
+
};
|
|
127
|
+
/** Optional drop-shadows (RN-shaped, applied via View style). */
|
|
128
|
+
shadows: {
|
|
129
|
+
/** Tells the developer's dark/light intent. Components don't read
|
|
130
|
+
* this directly — they read tokens — but `mode` is forwarded so
|
|
131
|
+
* status bar / image color choices can branch on it. */
|
|
132
|
+
appearance: 'light' | 'dark';
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Deep-partial of the token tree — what a developer hands to the SDK
|
|
137
|
+
* via the `theme` prop or dashboard config. Every field optional;
|
|
138
|
+
* unspecified fields fall through to the default.
|
|
139
|
+
*/
|
|
140
|
+
type PartialThemeTokens = {
|
|
141
|
+
colors?: Partial<AppilotsThemeTokens['colors']>;
|
|
142
|
+
typography?: Partial<AppilotsThemeTokens['typography']>;
|
|
143
|
+
radii?: Partial<AppilotsThemeTokens['radii']>;
|
|
144
|
+
shadows?: Partial<AppilotsThemeTokens['shadows']>;
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* SDK locale code. Canonical source of truth for `AppilotsLocale` — moved
|
|
149
|
+
* here (out of the React Native-coupled `i18n/I18nProvider`) so that
|
|
150
|
+
* platform-agnostic contract types (`RemotePersonalization.defaultLocale`)
|
|
151
|
+
* don't pull in a React/React Native import just for a string union type.
|
|
152
|
+
*
|
|
153
|
+
* `@appilots/sdk`'s `i18n/I18nProvider.tsx` re-exports this type from its
|
|
154
|
+
* original public path (`@appilots/sdk`'s `./i18n` barrel) so nothing
|
|
155
|
+
* downstream of the SDK's public API changes.
|
|
156
|
+
*
|
|
157
|
+
* Adding a locale = adding a bundle file in `@appilots/sdk`'s
|
|
158
|
+
* `i18n/bundles/` AND a branch in `i18n/I18nProvider.tsx`'s `BUNDLES`
|
|
159
|
+
* map AND a variant here. BACKLOG 4.1.
|
|
160
|
+
*/
|
|
161
|
+
type AppilotsLocale = SupportedAppilotsLocale;
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Core types for the Appilots SDK.
|
|
165
|
+
*/
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Payload of `GET /agent/personalization` — what the dev configured in
|
|
169
|
+
* the dashboard under Projects > Personalization. Fetched once on
|
|
170
|
+
* `AppilotsProvider` mount and applied by `AppilotsChat` as a fallback
|
|
171
|
+
* for any prop the dev did NOT set explicitly in code
|
|
172
|
+
* (prop > remote > SDK default).
|
|
173
|
+
*
|
|
174
|
+
* Only JSON-serializable values travel here: URLs/hex/booleans work,
|
|
175
|
+
* React nodes and local `require()` assets can only come via props.
|
|
176
|
+
* `null` means "not configured" — fall through to the next source.
|
|
177
|
+
*/
|
|
178
|
+
interface RemotePersonalization {
|
|
179
|
+
assistantName: string | null;
|
|
180
|
+
/** Avatar URL shown next to assistant turns. */
|
|
181
|
+
assistantAvatar: string | null;
|
|
182
|
+
/** Emoji or image URL for the empty-state badge. */
|
|
183
|
+
emptyStateIcon: string | null;
|
|
184
|
+
welcomeMessage: string | null;
|
|
185
|
+
chatTitle: string | null;
|
|
186
|
+
poweredByVisible: boolean;
|
|
187
|
+
/**
|
|
188
|
+
* Partial token tree merged onto the SDK defaults. `mode` (when set)
|
|
189
|
+
* plays the role of the `themeMode` prop.
|
|
190
|
+
*/
|
|
191
|
+
theme: (PartialThemeTokens & {
|
|
192
|
+
mode?: 'auto' | 'light' | 'dark';
|
|
193
|
+
}) | null;
|
|
194
|
+
defaultLocale: AppilotsLocale | null;
|
|
195
|
+
triggerButtonColor: string | null;
|
|
196
|
+
triggerButtonImageUrl: string | null;
|
|
197
|
+
}
|
|
198
|
+
type MessageRole = 'user' | 'assistant' | 'system' | 'human_agent';
|
|
199
|
+
interface ChatMessage {
|
|
200
|
+
id: string;
|
|
201
|
+
role: MessageRole;
|
|
202
|
+
content: string;
|
|
203
|
+
timestamp: number;
|
|
204
|
+
metadata?: Record<string, unknown>;
|
|
205
|
+
}
|
|
206
|
+
type AgentActionType = 'navigate' | 'form_fill' | 'ui_interaction' | 'scroll_list' | 'confirm' | 'custom';
|
|
207
|
+
interface AgentAction {
|
|
208
|
+
id: string;
|
|
209
|
+
type: AgentActionType;
|
|
210
|
+
payload: NavigationPayload | FormFillPayload | UIInteractionPayload | ScrollListPayload | Record<string, unknown>;
|
|
211
|
+
status: 'pending' | 'executing' | 'completed' | 'failed';
|
|
212
|
+
error?: string;
|
|
213
|
+
/** ID of the assistant message that triggered this action */
|
|
214
|
+
messageId?: string;
|
|
215
|
+
/**
|
|
216
|
+
* BACKLOG 2.2 — When the server flags an action as destructive,
|
|
217
|
+
* the SDK renders any preceding `confirm` action with the danger
|
|
218
|
+
* variant and expects the action to be gated by user approval.
|
|
219
|
+
*/
|
|
220
|
+
destructive?: boolean;
|
|
221
|
+
}
|
|
222
|
+
interface NavigationPayload {
|
|
223
|
+
/** Absent only for `goBack`, which has no destination to name (#398). */
|
|
224
|
+
screenName?: string;
|
|
225
|
+
params?: Record<string, unknown>;
|
|
226
|
+
navigationAction: 'push' | 'navigate' | 'replace' | 'goBack' | 'reset';
|
|
227
|
+
/**
|
|
228
|
+
* Optional path of parent navigator route names for nested screens.
|
|
229
|
+
* E.g. ['HomeTab', 'VehiclesTab'] to reach a screen inside
|
|
230
|
+
* HomeTab → VehiclesTab → screenName.
|
|
231
|
+
*
|
|
232
|
+
* React Navigation requires nested navigation params:
|
|
233
|
+
* nav.navigate('HomeTab', { screen: 'VehiclesTab', params: { screen: 'VehicleCreate' } })
|
|
234
|
+
*
|
|
235
|
+
* The AI model provides this based on the MCP document's navigation graph.
|
|
236
|
+
*/
|
|
237
|
+
path?: string[];
|
|
238
|
+
}
|
|
239
|
+
interface FormFillPayload {
|
|
240
|
+
screenName: string;
|
|
241
|
+
fields: FormFieldAction[];
|
|
242
|
+
/**
|
|
243
|
+
* If true, the handler presses the screen's primary submit button
|
|
244
|
+
* after all fields are filled. The submit button is resolved via
|
|
245
|
+
* `registerScreen({ actions: [{ type: 'submit', ... }] })` first,
|
|
246
|
+
* then via the fiber tree and a label heuristic.
|
|
247
|
+
*
|
|
248
|
+
* Aligned with the `submitAfterFill` field in the LLM tool schema
|
|
249
|
+
* (`agentTools.form_fill` in ai-relay.ts).
|
|
250
|
+
*/
|
|
251
|
+
submitAfterFill?: boolean;
|
|
252
|
+
}
|
|
253
|
+
interface FormFieldAction {
|
|
254
|
+
fieldId: string;
|
|
255
|
+
fieldType: 'text' | 'select' | 'toggle' | 'date' | 'number' | 'custom';
|
|
256
|
+
value: unknown;
|
|
257
|
+
label?: string;
|
|
258
|
+
}
|
|
259
|
+
interface UIInteractionPayload {
|
|
260
|
+
componentId: string;
|
|
261
|
+
/** Canonical server/LLM identifier. `componentId` is kept as SDK alias. */
|
|
262
|
+
targetId?: string;
|
|
263
|
+
action: 'press' | 'longPress' | 'toggle' | 'scroll' | 'swipe' | 'focus' | 'set_value';
|
|
264
|
+
/**
|
|
265
|
+
* Numeric value for `set_value` (sliders / adjustable controls).
|
|
266
|
+
* The handler clamps to the slider's [min, max] and snaps to its step.
|
|
267
|
+
*/
|
|
268
|
+
value?: number;
|
|
269
|
+
params?: Record<string, unknown>;
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* Scroll a list/collection so virtualized off-screen rows mount and
|
|
273
|
+
* show up in the next observation. Mirrors `scrollListPayloadSchema`
|
|
274
|
+
* in @appilots/shared. At least one of the fields should be present;
|
|
275
|
+
* the handler falls back to paging one viewport down when only a
|
|
276
|
+
* listId is given.
|
|
277
|
+
*/
|
|
278
|
+
interface ScrollListPayload {
|
|
279
|
+
/** Runtime list id from the observation's "Lists visible" block. */
|
|
280
|
+
listId?: string;
|
|
281
|
+
/** 1-based index in the list's DATA to scroll to. */
|
|
282
|
+
toIndex?: number;
|
|
283
|
+
/** Coarse paging when no target index is known. */
|
|
284
|
+
direction?: 'up' | 'down';
|
|
285
|
+
}
|
|
286
|
+
interface AgentPermissions {
|
|
287
|
+
canNavigate: boolean;
|
|
288
|
+
canFillForms: boolean;
|
|
289
|
+
canInteractUI: boolean;
|
|
290
|
+
canSubmitForms: boolean;
|
|
291
|
+
allowedScreens?: string[];
|
|
292
|
+
blockedScreens?: string[];
|
|
293
|
+
allowedActions?: AgentActionType[];
|
|
294
|
+
}
|
|
295
|
+
type AppilotsEventType = 'agent:action:start'
|
|
296
|
+
/** Queue dispatch, after approval; distinct from a proposed action. */
|
|
297
|
+
| 'agent:action:executing' | 'agent:action:complete' | 'agent:action:error' | 'agent:message' | 'navigation:change' | 'chat:open' | 'chat:close' | 'chat:cancel' | 'chat:escalation:start' | 'chat:escalation:message' | 'chat:escalation:end' | 'sdk:introspection:unavailable';
|
|
298
|
+
interface AppilotsEvent {
|
|
299
|
+
type: AppilotsEventType;
|
|
300
|
+
timestamp: number;
|
|
301
|
+
data: Record<string, unknown>;
|
|
302
|
+
}
|
|
303
|
+
type AppilotsEventHandler = (event: AppilotsEvent) => void;
|
|
304
|
+
/**
|
|
305
|
+
* Client-side view of an open escalation. `resolved` never appears
|
|
306
|
+
* here — resolution clears the state (after surfacing a system
|
|
307
|
+
* message in the transcript).
|
|
308
|
+
*/
|
|
309
|
+
interface EscalationState {
|
|
310
|
+
id: string;
|
|
311
|
+
status: 'pending' | 'active';
|
|
312
|
+
/** Set once an operator claims the conversation. */
|
|
313
|
+
operatorName?: string;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Optional host storage; only a session handle is stored, never screen contents. */
|
|
317
|
+
interface AppilotsSessionStorage {
|
|
318
|
+
getItem(key: string): Promise<string | null>;
|
|
319
|
+
setItem(key: string, value: string): Promise<void>;
|
|
320
|
+
removeItem(key: string): Promise<void>;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Mirrors `introspectionDiagnosticsSchema` on the wire. Declared here
|
|
324
|
+
* rather than imported so client-core stays free of the server's
|
|
325
|
+
* validator package — which means the two must be kept in step by hand.
|
|
326
|
+
* Adding a reason to one and not the other is a typecheck failure at
|
|
327
|
+
* the SDK, not a silent wire mismatch.
|
|
328
|
+
*/
|
|
329
|
+
interface IntrospectionDiagnosticsInput {
|
|
330
|
+
captured: boolean;
|
|
331
|
+
failureReason?: 'sentinel-missing-internals' | 'never-mounted' | 'render-crashed' | 'names-mangled' | 'walk-recognized-nothing' | null;
|
|
332
|
+
reactVersion?: string | null;
|
|
333
|
+
failureCount?: number;
|
|
334
|
+
}
|
|
335
|
+
interface AppilotsClientOptions {
|
|
336
|
+
sessionStorage?: AppilotsSessionStorage;
|
|
337
|
+
projectId: string;
|
|
338
|
+
apiBaseUrl?: string;
|
|
339
|
+
apiKey?: string;
|
|
340
|
+
debug?: boolean;
|
|
341
|
+
timeout?: number;
|
|
342
|
+
headers?: Record<string, string>;
|
|
343
|
+
/**
|
|
344
|
+
* Version of the MCP doc bundled with this app build. The server
|
|
345
|
+
* compares this against the currently-active MCP doc for the
|
|
346
|
+
* project and emits a `mcp_version_mismatch` telemetry event when
|
|
347
|
+
* they differ — the canonical sign that the dev shipped an app
|
|
348
|
+
* update but forgot to regenerate/upload the MCP.
|
|
349
|
+
*
|
|
350
|
+
* Tip: import this from the MCP file the CLI generates, or read it
|
|
351
|
+
* from your app's package.json so it stays in lockstep with releases.
|
|
352
|
+
*/
|
|
353
|
+
mcpVersion?: string;
|
|
354
|
+
/**
|
|
355
|
+
* Reports whether the platform binding could read the host app's UI
|
|
356
|
+
* tree. Called once per message; return `null` (or omit the option)
|
|
357
|
+
* when there is nothing to report.
|
|
358
|
+
*
|
|
359
|
+
* Only a FAILED reading is put on the wire. When introspection is
|
|
360
|
+
* dead every observation is empty but perfectly well-formed, so the
|
|
361
|
+
* server cannot tell a blind session from a user looking at empty
|
|
362
|
+
* screens — this is the only signal that distinguishes them.
|
|
363
|
+
*/
|
|
364
|
+
introspectionReporter?: () => IntrospectionDiagnosticsInput | null | undefined;
|
|
365
|
+
/**
|
|
366
|
+
* The host app's version (e.g. from package.json or
|
|
367
|
+
* react-native-device-info). Sent as `X-App-Version` on every request
|
|
368
|
+
* so the server can match the compatible MCP document per app build —
|
|
369
|
+
* essential for OTA-updated fleets where multiple app versions coexist.
|
|
370
|
+
*/
|
|
371
|
+
appVersion?: string;
|
|
372
|
+
/**
|
|
373
|
+
* The version of the SDK package driving this client, sent as
|
|
374
|
+
* `X-Appilots-Sdk-Version`. Each platform SDK passes its OWN published
|
|
375
|
+
* version (`@appilots/sdk`, `@appilots/web-sdk`); `@appilots/client-core`
|
|
376
|
+
* is private and never published, so its version means nothing to a
|
|
377
|
+
* customer and is only the fallback for a caller that constructs
|
|
378
|
+
* `AppilotsClient` directly.
|
|
379
|
+
*
|
|
380
|
+
* This is the server's only evidence of which SDKs are in the field,
|
|
381
|
+
* and therefore the only input to "is this field safe to tighten yet?"
|
|
382
|
+
* (issue #312). Leaving it wrong is not cosmetic: it makes the
|
|
383
|
+
* backward-compatibility promise in CLAUDE.md unverifiable.
|
|
384
|
+
*/
|
|
385
|
+
sdkVersion?: string;
|
|
386
|
+
/**
|
|
387
|
+
* Who the app's current user is. `id` becomes the session's
|
|
388
|
+
* externalUserId; `name`/`identifiers` are upserted into the
|
|
389
|
+
* workspace's end-users registry (fire-and-forget identify after the
|
|
390
|
+
* session opens) so support operators see a person, not an anonymous
|
|
391
|
+
* id. Identifiers are dashboard-only — never sent to the LLM.
|
|
392
|
+
*/
|
|
393
|
+
user?: AppilotsUser;
|
|
394
|
+
}
|
|
395
|
+
interface AppilotsUser {
|
|
396
|
+
/** The app's own user id (matches your backend). */
|
|
397
|
+
id: string;
|
|
398
|
+
name?: string;
|
|
399
|
+
/** Free-form lookup keys: { email, phone, cpf, ... }. Max 10. */
|
|
400
|
+
identifiers?: Record<string, string>;
|
|
401
|
+
}
|
|
402
|
+
interface SendMessageResponse {
|
|
403
|
+
mission?: MissionView;
|
|
404
|
+
replyLocale?: SupportedAppilotsLocale;
|
|
405
|
+
sessionId: string;
|
|
406
|
+
message: ChatMessage;
|
|
407
|
+
/** Summary message to show AFTER actions complete */
|
|
408
|
+
summaryMessage?: ChatMessage;
|
|
409
|
+
actions?: AgentAction[];
|
|
410
|
+
usage?: {
|
|
411
|
+
tokensUsed: number;
|
|
412
|
+
modelUsed: string;
|
|
413
|
+
latencyMs: number;
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
interface SendMessageStreamOptions {
|
|
417
|
+
/**
|
|
418
|
+
* Fired per SSE text-delta with the chunk and the accumulated text so
|
|
419
|
+
* far — render `fullText` directly; chunks may split words/markdown.
|
|
420
|
+
*/
|
|
421
|
+
onTextDelta?: (chunk: string, fullText: string) => void;
|
|
422
|
+
/** Fired once when the server announces the session id. */
|
|
423
|
+
onSession?: (sessionId: string) => void;
|
|
424
|
+
onReplyLocale?: (locale: SupportedAppilotsLocale) => void;
|
|
425
|
+
/** Abort mid-generation (stop button). Rejects with name='AbortError'. */
|
|
426
|
+
signal?: AbortSignal;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
* The server refused the request because this workspace (or this end
|
|
430
|
+
* user) is going too fast — HTTP 429, `error.code = 'RATE_LIMITED'`.
|
|
431
|
+
* See `apps/api/src/common/services/relay-rate-limit.ts`.
|
|
432
|
+
*
|
|
433
|
+
* A distinct type for two reasons, both of which the plain `Error` the
|
|
434
|
+
* transport used to throw could not express:
|
|
435
|
+
*
|
|
436
|
+
* - It is a POLICY rejection, not a transport failure. On the streaming
|
|
437
|
+
* path that distinction decides whether the caller retries: a stream
|
|
438
|
+
* that dies before its first event is safely retried against the
|
|
439
|
+
* non-streaming endpoint, but retrying a 429 immediately is exactly
|
|
440
|
+
* the behaviour the limit exists to stop, and it earns a second 429.
|
|
441
|
+
* - It carries `retryAfterSeconds`, so a caller can say WHEN instead of
|
|
442
|
+
* "try again later", and can back off instead of spinning.
|
|
443
|
+
*
|
|
444
|
+
* `retryAfterSeconds` is read from the response envelope, falling back
|
|
445
|
+
* to the `Retry-After` header and then to 60 — a client that waits a
|
|
446
|
+
* minute is never wrong by much, and never zero.
|
|
447
|
+
*/
|
|
448
|
+
declare class RateLimitedError extends Error {
|
|
449
|
+
readonly retryAfterSeconds: number;
|
|
450
|
+
constructor(message: string, retryAfterSeconds: number);
|
|
451
|
+
}
|
|
452
|
+
/** Runtime context sent with every user message. */
|
|
453
|
+
interface SendMessageContext {
|
|
454
|
+
missionProtocol?: 1;
|
|
455
|
+
missionId?: string;
|
|
456
|
+
/** Supported device language, independent of the app/map/visible UI locale. */
|
|
457
|
+
deviceLocale?: SupportedAppilotsLocale;
|
|
458
|
+
/**
|
|
459
|
+
* Which client platform this observation came from — `'react-native'`,
|
|
460
|
+
* `'web'`, `'android'`, `'ios'`, or any other client-defined string.
|
|
461
|
+
* The server treats an absent value as `'react-native'` (see
|
|
462
|
+
* `docs/agent-contract.md`'s "The `platform` field" section), so
|
|
463
|
+
* setting it is optional but forward-compatible: a client that sends
|
|
464
|
+
* it explicitly isn't relying on that default staying `'react-native'`
|
|
465
|
+
* forever.
|
|
466
|
+
*/
|
|
467
|
+
platform?: string;
|
|
468
|
+
currentScreen?: string;
|
|
469
|
+
formState?: Record<string, unknown>;
|
|
470
|
+
/** Design-time screen metadata from registerScreen() or the MCP doc */
|
|
471
|
+
screenMetadata?: Record<string, unknown>;
|
|
472
|
+
/** Runtime React Navigation summary captured by the SDK */
|
|
473
|
+
navigationState?: {
|
|
474
|
+
currentRouteName?: string;
|
|
475
|
+
activePath?: string[];
|
|
476
|
+
rootRouteNames?: string[];
|
|
477
|
+
currentRouteNames?: string[];
|
|
478
|
+
routeNames?: string[];
|
|
479
|
+
canGoBack?: boolean;
|
|
480
|
+
};
|
|
481
|
+
/** Runtime snapshot from captureSnapshot() — what's actually visible */
|
|
482
|
+
snapshot?: Record<string, unknown>;
|
|
483
|
+
/** Legacy registry snapshot — kept as a fallback signal */
|
|
484
|
+
registeredComponents?: Array<{
|
|
485
|
+
id: string;
|
|
486
|
+
kind: 'field' | 'target' | 'toggle';
|
|
487
|
+
screen?: string;
|
|
488
|
+
label?: string;
|
|
489
|
+
}>;
|
|
490
|
+
/** Runtime list registry snapshot — collection metadata even when rows are virtualized. */
|
|
491
|
+
registeredLists?: Array<{
|
|
492
|
+
kind: 'list';
|
|
493
|
+
id: string;
|
|
494
|
+
component: string;
|
|
495
|
+
itemCount?: number;
|
|
496
|
+
label?: string;
|
|
497
|
+
screen?: string;
|
|
498
|
+
refreshing?: boolean;
|
|
499
|
+
empty?: boolean;
|
|
500
|
+
}>;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
declare class AppilotsClient {
|
|
504
|
+
private readonly baseUrl;
|
|
505
|
+
private readonly projectId;
|
|
506
|
+
private readonly headers;
|
|
507
|
+
private readonly timeout;
|
|
508
|
+
private readonly debug;
|
|
509
|
+
private readonly mcpVersion;
|
|
510
|
+
private readonly introspectionReporter;
|
|
511
|
+
private readonly user;
|
|
512
|
+
private sessionId;
|
|
513
|
+
private mission;
|
|
514
|
+
private missionDispatchPaused;
|
|
515
|
+
private readonly sessionStorage;
|
|
516
|
+
private readonly sessionStorageKey;
|
|
517
|
+
private sessionLoad;
|
|
518
|
+
/** Guard so identify fires at most once per client instance. */
|
|
519
|
+
private identified;
|
|
520
|
+
constructor(options: AppilotsClientOptions);
|
|
521
|
+
/**
|
|
522
|
+
* A 404 on an `/agent/*` path is almost never a missing record — those
|
|
523
|
+
* routes are static. It means the request reached SOMETHING that is not
|
|
524
|
+
* this API, or reached it at the wrong mount point, and the server's own
|
|
525
|
+
* message ("Route not found") tells the integrator nothing about which.
|
|
526
|
+
*
|
|
527
|
+
* The symptom is unmistakable once you have seen it: every message
|
|
528
|
+
* fails, including a plain "hello" that needs no tool at all, because
|
|
529
|
+
* nothing ever reaches the agent. Left as-is it reads like the agent is
|
|
530
|
+
* broken.
|
|
531
|
+
*/
|
|
532
|
+
private explainIfMisroutedBaseUrl;
|
|
533
|
+
private log;
|
|
534
|
+
private describeNonJsonResponse;
|
|
535
|
+
private request;
|
|
536
|
+
createSession(userId?: string, deviceInfo?: Record<string, unknown>): Promise<string>;
|
|
537
|
+
/**
|
|
538
|
+
* Upsert the configured user into the end-users registry. Fires once
|
|
539
|
+
* per client, after the first session opens; fire-and-forget — a
|
|
540
|
+
* failed identify must never break the chat.
|
|
541
|
+
*/
|
|
542
|
+
private identifyConfiguredUser;
|
|
543
|
+
destroySession(): Promise<void>;
|
|
544
|
+
getSessionId(): string | null;
|
|
545
|
+
/**
|
|
546
|
+
* Wire fragment for introspection health. Empty on the happy path —
|
|
547
|
+
* a working client sends nothing, so the field costs a byte only
|
|
548
|
+
* when something is actually wrong. Never throws: a broken reporter
|
|
549
|
+
* must not take the message down with it.
|
|
550
|
+
*/
|
|
551
|
+
private introspectionFragment;
|
|
552
|
+
sendMessage(content: string, context?: SendMessageContext): Promise<SendMessageResponse>;
|
|
553
|
+
/**
|
|
554
|
+
* Streaming variant of `sendMessage` — consumes the server's SSE
|
|
555
|
+
* endpoint (`/agent/message/stream`) and fires `onTextDelta` per chunk
|
|
556
|
+
* so the UI can render tokens as they arrive (OKR-008 KR1). Resolves
|
|
557
|
+
* with the same `SendMessageResponse` shape once the `done` event lands.
|
|
558
|
+
*
|
|
559
|
+
* Uses XMLHttpRequest because React Native's fetch does not expose
|
|
560
|
+
* `response.body` for incremental reads; RN's XHR delivers progressive
|
|
561
|
+
* `responseText`, which is the standard SSE transport on RN.
|
|
562
|
+
*
|
|
563
|
+
* Errors:
|
|
564
|
+
* - `StreamTransportError` — transport failed before ANY event; safe
|
|
565
|
+
* for the caller to fall back to the non-streaming endpoint.
|
|
566
|
+
* - `StreamAbortError` (name='AbortError') — options.signal fired;
|
|
567
|
+
* carries `partialContent` so the UI can keep what streamed.
|
|
568
|
+
*/
|
|
569
|
+
sendMessageStream(content: string, context?: SendMessageContext, options?: SendMessageStreamOptions): Promise<SendMessageResponse>;
|
|
570
|
+
continueAgent(results: Array<{
|
|
571
|
+
actionId: string;
|
|
572
|
+
type: string;
|
|
573
|
+
success: boolean;
|
|
574
|
+
error?: string;
|
|
575
|
+
summary?: string;
|
|
576
|
+
/** BACKLOG 3.2 — structured failure diagnose, server uses for recovery */
|
|
577
|
+
diagnose?: {
|
|
578
|
+
category: 'component-not-found' | 'disabled' | 'validation' | 'network-5xx' | 'network-4xx' | 'screen-timeout' | 'ambiguous-target' | 'unknown';
|
|
579
|
+
visibleMessage?: string;
|
|
580
|
+
fieldId?: string;
|
|
581
|
+
targetId?: string;
|
|
582
|
+
screen?: string;
|
|
583
|
+
httpStatus?: number;
|
|
584
|
+
/** Candidate ids when resolution failed due to ambiguity. */
|
|
585
|
+
candidates?: string[];
|
|
586
|
+
recoverable?: boolean;
|
|
587
|
+
requiresUserInput?: boolean;
|
|
588
|
+
};
|
|
589
|
+
/** BACKLOG 3.2 — how many times this fingerprint has been retried (1 = first retry). */
|
|
590
|
+
recoveryAttempt?: number;
|
|
591
|
+
/** BACKLOG 3.2 — set when SDK has hit the per-action retry cap. */
|
|
592
|
+
recoveryExhausted?: boolean;
|
|
593
|
+
/**
|
|
594
|
+
* Set when a handler reported success but the expected visible
|
|
595
|
+
* side-effect did not happen, e.g. navigate returned but route stayed.
|
|
596
|
+
*/
|
|
597
|
+
toolWithoutEffect?: boolean;
|
|
598
|
+
effect?: 'unknown' | 'none' | 'partial' | 'changed' | 'completed';
|
|
599
|
+
userVisibleStatusKey?: string;
|
|
600
|
+
}>, context: Record<string, unknown>, hop: number): Promise<SendMessageResponse & {
|
|
601
|
+
isDone: boolean;
|
|
602
|
+
hop: number;
|
|
603
|
+
}>;
|
|
604
|
+
private acceptMission;
|
|
605
|
+
getMission(): MissionView | null;
|
|
606
|
+
pauseMissionLocally(): void;
|
|
607
|
+
missionApprovalAllowed(action: AgentAction): boolean;
|
|
608
|
+
missionScopeForAction(action: AgentAction): MissionScope | undefined;
|
|
609
|
+
claimMissionAction(action: AgentAction, context: Record<string, unknown>): Promise<{
|
|
610
|
+
allowed: boolean;
|
|
611
|
+
scope: MissionScope;
|
|
612
|
+
expiresAt: number;
|
|
613
|
+
}>;
|
|
614
|
+
controlMission(command: 'observe' | 'pause' | 'resume' | 'cancel', context: Record<string, unknown>): Promise<SendMessageResponse & {
|
|
615
|
+
isDone: boolean;
|
|
616
|
+
hop: number;
|
|
617
|
+
}>;
|
|
618
|
+
private persistSession;
|
|
619
|
+
private loadStoredSession;
|
|
620
|
+
restoreMission(): Promise<MissionView | null>;
|
|
621
|
+
executeAction(actionId: string): Promise<AgentAction>;
|
|
622
|
+
/**
|
|
623
|
+
* Rewrite an action's type when the AI used a semantically-misclassified
|
|
624
|
+
* action type. The most common case: the LLM emits
|
|
625
|
+
* { type: 'ui_interaction', payload: { action: 'select', targetId, value } }
|
|
626
|
+
* to mean "set this field's value", which is really a form_fill. We detect
|
|
627
|
+
* that here and convert it before normalization, so the executor sees the
|
|
628
|
+
* right action shape and the field's setValue() is called instead of a
|
|
629
|
+
* meaningless press on a non-pressable component.
|
|
630
|
+
*/
|
|
631
|
+
private rewriteActionTypeIfNeeded;
|
|
632
|
+
/**
|
|
633
|
+
* Normalize API tool call payloads to match SDK's expected payload types.
|
|
634
|
+
* The AI model may return slightly different field names than what the
|
|
635
|
+
* SDK executor expects, and may use vocabulary (e.g. "select", "scroll_to",
|
|
636
|
+
* "open") that the SDK doesn't natively support — we remap those to the
|
|
637
|
+
* nearest equivalent here so the executor never sees an "unknown action".
|
|
638
|
+
*/
|
|
639
|
+
private normalizePayload;
|
|
640
|
+
/**
|
|
641
|
+
* Legacy per-action status report. The agentic chat loop reports via
|
|
642
|
+
* `continueAgent` (which carries diagnose + a fresh snapshot); this
|
|
643
|
+
* endpoint remains for apps driving actions manually through
|
|
644
|
+
* `useAppilotsActions`. `diagnose` is forwarded when provided so even
|
|
645
|
+
* the legacy path gives the server a failure category.
|
|
646
|
+
*/
|
|
647
|
+
completeAction(actionId: string, success: boolean, error?: string, diagnose?: Record<string, unknown>): Promise<void>;
|
|
648
|
+
/**
|
|
649
|
+
* Fetch actions the server has persisted but that haven't been
|
|
650
|
+
* completed yet. Use after an SSE drop or a hard error to recover
|
|
651
|
+
* the batch without losing the actions the model already decided.
|
|
652
|
+
*
|
|
653
|
+
* Returns an empty array if there are no pending actions or if the
|
|
654
|
+
* call fails — never throws.
|
|
655
|
+
*/
|
|
656
|
+
recoverPendingActions(sessionId?: string): Promise<AgentAction[]>;
|
|
657
|
+
/**
|
|
658
|
+
* Fetch dev-defined and empirically-popular prompts to seed the chat
|
|
659
|
+
* empty-state with one-tap chips. Cheap (cached server-side for 1h) but
|
|
660
|
+
* still a network hit, so callers should debounce around chat-open.
|
|
661
|
+
*
|
|
662
|
+
* @param screen Optional — when provided, response includes screen-scoped
|
|
663
|
+
* popular prompts in addition to globals.
|
|
664
|
+
*/
|
|
665
|
+
getPopularPrompts(screen?: string, limit?: number): Promise<{
|
|
666
|
+
devDefined: string[];
|
|
667
|
+
screenPopular: string[];
|
|
668
|
+
globalPopular: string[];
|
|
669
|
+
computedAt: string;
|
|
670
|
+
}>;
|
|
671
|
+
/**
|
|
672
|
+
* Fetch the project's dashboard-configured personalization (theme,
|
|
673
|
+
* branding, FAB color/image, locale...). Called by `AppilotsProvider`
|
|
674
|
+
* once on mount — cheap (cached server-side for 1h) and applied by
|
|
675
|
+
* `AppilotsChat` as a fallback for props not set explicitly in code.
|
|
676
|
+
*
|
|
677
|
+
* Throws on network/HTTP failure — the provider treats any failure as
|
|
678
|
+
* "no remote config" and stays on props/defaults.
|
|
679
|
+
*/
|
|
680
|
+
getPersonalization(): Promise<RemotePersonalization>;
|
|
681
|
+
/**
|
|
682
|
+
* Ask for a human operator. Idempotent: if the session already has
|
|
683
|
+
* an open escalation the server returns it instead of creating a
|
|
684
|
+
* duplicate. Creates the session first when the chat had none yet
|
|
685
|
+
* (the server sends it back and we adopt it, so the whole
|
|
686
|
+
* conversation stays under one sessionId).
|
|
687
|
+
*/
|
|
688
|
+
requestEscalation(reason: 'user_requested' | 'agent_gave_up', note?: string): Promise<{
|
|
689
|
+
escalationId: string;
|
|
690
|
+
status: 'pending' | 'active';
|
|
691
|
+
}>;
|
|
692
|
+
/**
|
|
693
|
+
* Tell the server the user walked away from the conversation (chat
|
|
694
|
+
* cleared) so the escalation is closed instead of leaving the
|
|
695
|
+
* operator talking to nobody. Fire-and-forget: never throws — the
|
|
696
|
+
* local cleanup must proceed even if the network is gone.
|
|
697
|
+
*/
|
|
698
|
+
abandonEscalation(escalationId: string): Promise<void>;
|
|
699
|
+
/** User message routed to the human operator (no LLM involved). */
|
|
700
|
+
sendEscalationMessage(escalationId: string, content: string): Promise<void>;
|
|
701
|
+
/**
|
|
702
|
+
* Poll fallback / reconnect catch-up: status + operator messages
|
|
703
|
+
* created after `since` (ISO timestamp of the last one we have).
|
|
704
|
+
*/
|
|
705
|
+
getEscalationUpdates(escalationId: string, since?: string): Promise<{
|
|
706
|
+
status: 'pending' | 'active' | 'resolved';
|
|
707
|
+
messages: Array<{
|
|
708
|
+
id: string;
|
|
709
|
+
content: string;
|
|
710
|
+
createdAt: string;
|
|
711
|
+
}>;
|
|
712
|
+
}>;
|
|
713
|
+
/**
|
|
714
|
+
* Open the escalation SSE channel — operator replies + status
|
|
715
|
+
* changes pushed live. Same XHR transport as `sendMessageStream`
|
|
716
|
+
* (RN's fetch can't read a body incrementally).
|
|
717
|
+
*
|
|
718
|
+
* Long-lived by design: no idle watchdog — the server pings every
|
|
719
|
+
* 25s, and a genuinely dead connection surfaces as `onClose`, which
|
|
720
|
+
* the hook answers with a backoff reconnect + `since` catch-up.
|
|
721
|
+
* Returns a close function; closing never fires `onClose`.
|
|
722
|
+
*/
|
|
723
|
+
openEscalationStream(escalationId: string, handlers: {
|
|
724
|
+
since?: string;
|
|
725
|
+
onHumanMessage: (message: {
|
|
726
|
+
id: string;
|
|
727
|
+
content: string;
|
|
728
|
+
createdAt: string;
|
|
729
|
+
}) => void;
|
|
730
|
+
onStatus: (status: 'pending' | 'active' | 'resolved', operatorName?: string) => void;
|
|
731
|
+
/** Transport ended (error or server close) — caller decides on reconnect. */
|
|
732
|
+
onClose: () => void;
|
|
733
|
+
}): () => void;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
type ActionFailureCategory = 'component-not-found' | 'disabled' | 'validation' | 'network-5xx' | 'network-4xx' | 'screen-timeout' | 'ambiguous-target' | 'unknown';
|
|
737
|
+
interface ActionDiagnose {
|
|
738
|
+
category: ActionFailureCategory;
|
|
739
|
+
visibleMessage?: string;
|
|
740
|
+
fieldId?: string;
|
|
741
|
+
targetId?: string;
|
|
742
|
+
screen?: string;
|
|
743
|
+
httpStatus?: number;
|
|
744
|
+
candidates?: string[];
|
|
745
|
+
recoverable?: boolean;
|
|
746
|
+
requiresUserInput?: boolean;
|
|
747
|
+
}
|
|
748
|
+
/** Effect classification an executor can attach to a result. */
|
|
749
|
+
type ActionEffect = 'unknown' | 'none' | 'partial' | 'changed' | 'completed';
|
|
750
|
+
/** Terminal result of executing one action locally. */
|
|
751
|
+
interface ActionExecutionResult {
|
|
752
|
+
success: boolean;
|
|
753
|
+
error?: string;
|
|
754
|
+
diagnose?: ActionDiagnose;
|
|
755
|
+
effect?: ActionEffect;
|
|
756
|
+
userVisibleStatusKey?: string;
|
|
757
|
+
}
|
|
758
|
+
/** One entry of the `results` array POSTed to /agent/continue. */
|
|
759
|
+
interface TurnActionReport {
|
|
760
|
+
nativeConfirmation?: NativeConfirmationReceipt;
|
|
761
|
+
actionId: string;
|
|
762
|
+
type: string;
|
|
763
|
+
success: boolean;
|
|
764
|
+
error?: string;
|
|
765
|
+
summary?: string;
|
|
766
|
+
diagnose?: ActionDiagnose;
|
|
767
|
+
recoveryAttempt?: number;
|
|
768
|
+
recoveryExhausted?: boolean;
|
|
769
|
+
toolWithoutEffect?: boolean;
|
|
770
|
+
effect?: ActionEffect;
|
|
771
|
+
userVisibleStatusKey?: string;
|
|
772
|
+
}
|
|
773
|
+
type ChatLoadingStatusKey = 'thinking' | 'statusAnalyzing' | 'statusWaitingApp' | 'statusAdjusting';
|
|
774
|
+
/**
|
|
775
|
+
* Localized copy for escalation system messages, injected by the chat
|
|
776
|
+
* surface (the machine has no i18n access — same pattern as errorPrefix).
|
|
777
|
+
* When omitted, no system messages are appended but the state machine
|
|
778
|
+
* still works.
|
|
779
|
+
*/
|
|
780
|
+
interface EscalationStrings {
|
|
781
|
+
/** Appended right after the escalation is created. */
|
|
782
|
+
requested: string;
|
|
783
|
+
/** Appended when an operator claims the conversation. */
|
|
784
|
+
connected: string;
|
|
785
|
+
/** Appended when the operator resolves — AI takes over again. */
|
|
786
|
+
resolved: string;
|
|
787
|
+
/** Content of the offer chip message appended when the agent gives up. */
|
|
788
|
+
offer: string;
|
|
789
|
+
}
|
|
790
|
+
interface ChatSessionOptions {
|
|
791
|
+
/** User-facing prefix for client-side failures before the relay can answer. */
|
|
792
|
+
errorPrefix?: string;
|
|
793
|
+
/**
|
|
794
|
+
* Stream assistant replies token-by-token via SSE (default true).
|
|
795
|
+
* When the streaming transport fails before any output, the machine
|
|
796
|
+
* falls back to the non-streaming endpoint transparently.
|
|
797
|
+
*/
|
|
798
|
+
streaming?: boolean;
|
|
799
|
+
/** Localized escalation copy — see EscalationStrings. */
|
|
800
|
+
escalationStrings?: EscalationStrings;
|
|
801
|
+
}
|
|
802
|
+
interface ChatSessionState {
|
|
803
|
+
mission?: MissionView | null;
|
|
804
|
+
/** Current request has dispatched an app action; retained across continuation hops. */
|
|
805
|
+
hasStartedActing: boolean;
|
|
806
|
+
replyLocale: AppilotsLocale | null;
|
|
807
|
+
messages: ChatMessage[];
|
|
808
|
+
isLoading: boolean;
|
|
809
|
+
loadingStatusKey: ChatLoadingStatusKey;
|
|
810
|
+
error: string | null;
|
|
811
|
+
pendingActions: AgentAction[];
|
|
812
|
+
/**
|
|
813
|
+
* Open escalation, or null. While set, `sendMessage` routes to the
|
|
814
|
+
* human channel — the agent session itself is never interrupted.
|
|
815
|
+
*/
|
|
816
|
+
escalation: EscalationState | null;
|
|
817
|
+
}
|
|
818
|
+
/** Everything the chat machine needs from the transport client. */
|
|
819
|
+
interface ChatSessionClient {
|
|
820
|
+
restoreMission?(): Promise<MissionView | null>;
|
|
821
|
+
getMission?(): MissionView | null;
|
|
822
|
+
pauseMissionLocally?(): void;
|
|
823
|
+
controlMission?(command: 'observe' | 'pause' | 'resume' | 'cancel', context: Record<string, unknown>): Promise<SendMessageResponse & {
|
|
824
|
+
isDone: boolean;
|
|
825
|
+
hop: number;
|
|
826
|
+
}>;
|
|
827
|
+
sendMessage(content: string, context?: SendMessageContext): Promise<SendMessageResponse>;
|
|
828
|
+
sendMessageStream(content: string, context?: SendMessageContext, options?: SendMessageStreamOptions): Promise<SendMessageResponse>;
|
|
829
|
+
continueAgent(results: TurnActionReport[], context: Record<string, unknown>, hop: number): Promise<SendMessageResponse & {
|
|
830
|
+
isDone: boolean;
|
|
831
|
+
hop: number;
|
|
832
|
+
}>;
|
|
833
|
+
requestEscalation(reason: 'user_requested' | 'agent_gave_up', note?: string): Promise<{
|
|
834
|
+
escalationId: string;
|
|
835
|
+
status: 'pending' | 'active';
|
|
836
|
+
}>;
|
|
837
|
+
abandonEscalation(escalationId: string): Promise<void>;
|
|
838
|
+
sendEscalationMessage(escalationId: string, content: string): Promise<void>;
|
|
839
|
+
openEscalationStream(escalationId: string, handlers: {
|
|
840
|
+
since?: string;
|
|
841
|
+
onHumanMessage: (message: {
|
|
842
|
+
id: string;
|
|
843
|
+
content: string;
|
|
844
|
+
createdAt: string;
|
|
845
|
+
}) => void;
|
|
846
|
+
onStatus: (status: 'pending' | 'active' | 'resolved', operatorName?: string) => void;
|
|
847
|
+
onClose: () => void;
|
|
848
|
+
}): () => void;
|
|
849
|
+
destroySession(): Promise<void> | void;
|
|
850
|
+
}
|
|
851
|
+
/** Input handed to the platform adapter after a turn's actions finished. */
|
|
852
|
+
interface SettleTurnInput {
|
|
853
|
+
/** Whether any action in this turn was a navigate. */
|
|
854
|
+
hadNavigate: boolean;
|
|
855
|
+
/** Screen observed when the turn started, BEFORE any navigate dispatched. */
|
|
856
|
+
preNavigateScreen: string | null;
|
|
857
|
+
/** Full route signature (name + params) captured pre-navigate. */
|
|
858
|
+
preNavigateSignature: string | null;
|
|
859
|
+
/** The turn's actions, in order. */
|
|
860
|
+
turnActions: AgentAction[];
|
|
861
|
+
/**
|
|
862
|
+
* Terminal execution results reported so far (actionId → outcome).
|
|
863
|
+
*
|
|
864
|
+
* `effect` é o veredito POR AÇÃO, medido com baseline tirado antes do
|
|
865
|
+
* press. Quem consome este input não pode contradizê-lo: a verificação
|
|
866
|
+
* de turno roda depois de tudo executado, então o baseline dela é
|
|
867
|
+
* estruturalmente posterior ao efeito que ela tenta observar.
|
|
868
|
+
*/
|
|
869
|
+
results: Array<{
|
|
870
|
+
actionId: string;
|
|
871
|
+
type: string;
|
|
872
|
+
success: boolean;
|
|
873
|
+
effect?: ActionEffect;
|
|
874
|
+
}>;
|
|
875
|
+
/** componentId of the most recent press-like ui_interaction, if any. */
|
|
876
|
+
pressedComponentId: string | null;
|
|
877
|
+
/** screenName of the most recent navigate action, if any. */
|
|
878
|
+
navigateTargetScreen: string | null;
|
|
879
|
+
}
|
|
880
|
+
interface SettleTurnOutcome {
|
|
881
|
+
/** True when the platform gave up waiting for async work to finish. */
|
|
882
|
+
loadingPending: boolean;
|
|
883
|
+
/** Action ids that "succeeded" but produced no observable effect. */
|
|
884
|
+
toolWithoutEffectIds: Set<string>;
|
|
885
|
+
}
|
|
886
|
+
/**
|
|
887
|
+
* The imperative platform surface the chat machine calls into. React
|
|
888
|
+
* Native implements this with the fiber walker + React Navigation
|
|
889
|
+
* settling; web implements it with a DOM walker + history/router.
|
|
890
|
+
*/
|
|
891
|
+
interface ChatPlatformAdapter {
|
|
892
|
+
/**
|
|
893
|
+
* Build the rich runtime context that travels with every agent turn:
|
|
894
|
+
* platform id, route name, screen metadata, UI snapshot, registries.
|
|
895
|
+
*/
|
|
896
|
+
buildContext(extras?: {
|
|
897
|
+
loadingPending?: boolean;
|
|
898
|
+
/**
|
|
899
|
+
* This is the first observation of a NEW user request, so any
|
|
900
|
+
* cross-observation state the adapter keeps — today the baseline for
|
|
901
|
+
* `snapshot.delta` — starts over. Without it the first hop of a
|
|
902
|
+
* mission would report whatever the person did on their own since
|
|
903
|
+
* the last one as the effect of the agent's previous action.
|
|
904
|
+
*/
|
|
905
|
+
newMission?: boolean;
|
|
906
|
+
}): Record<string, unknown>;
|
|
907
|
+
/** Current route name, or null when unknown. */
|
|
908
|
+
getCurrentScreen(): string | null;
|
|
909
|
+
/** Full route+params signature, or null when unknown. */
|
|
910
|
+
getCurrentScreenSignature(): string | null;
|
|
911
|
+
/**
|
|
912
|
+
* Wait for the UI to settle after a turn's actions executed, and
|
|
913
|
+
* detect actions that reported success but had no observable effect.
|
|
914
|
+
* Runs BEFORE the fresh context is captured.
|
|
915
|
+
*/
|
|
916
|
+
settleTurn(input: SettleTurnInput): Promise<SettleTurnOutcome>;
|
|
917
|
+
}
|
|
918
|
+
interface ChatSessionMachineDeps {
|
|
919
|
+
client: ChatSessionClient;
|
|
920
|
+
adapter: ChatPlatformAdapter;
|
|
921
|
+
emit: (event: AppilotsEvent) => void;
|
|
922
|
+
/** Debug warn sink — defaults to console.warn. */
|
|
923
|
+
warn?: (message: string, ...args: unknown[]) => void;
|
|
924
|
+
}
|
|
925
|
+
/**
|
|
926
|
+
* Loose structural view of one declared screen action's metadata
|
|
927
|
+
* (mirrors the SDK's ScreenActionMetadata without importing it).
|
|
928
|
+
*/
|
|
929
|
+
interface ScreenActionMetadataLike {
|
|
930
|
+
id: string;
|
|
931
|
+
label?: string;
|
|
932
|
+
requiresConfirmation?: boolean;
|
|
933
|
+
[key: string]: unknown;
|
|
934
|
+
}
|
|
935
|
+
/** Everything the action queue machine needs from the transport client. */
|
|
936
|
+
interface ActionQueueClient {
|
|
937
|
+
missionApprovalAllowed?(action: AgentAction): boolean;
|
|
938
|
+
missionScopeForAction?(action: AgentAction): MissionScope | undefined;
|
|
939
|
+
claimMissionAction?(action: AgentAction, context: Record<string, unknown>): Promise<{
|
|
940
|
+
allowed: boolean;
|
|
941
|
+
scope: MissionScope;
|
|
942
|
+
expiresAt: number;
|
|
943
|
+
}>;
|
|
944
|
+
completeAction(actionId: string, success: boolean, error?: string, diagnose?: Record<string, unknown>): Promise<void>;
|
|
945
|
+
}
|
|
946
|
+
/**
|
|
947
|
+
* The imperative platform surface the action queue calls into: actually
|
|
948
|
+
* executing an action against the live UI, plus the navigation probes
|
|
949
|
+
* used to sequence post-navigate actions.
|
|
950
|
+
*/
|
|
951
|
+
interface ActionRunnerAdapter {
|
|
952
|
+
/** Fresh platform evidence for mission scope validation, before and after dispatch claims. */
|
|
953
|
+
buildMissionContext?(): Record<string, unknown>;
|
|
954
|
+
/**
|
|
955
|
+
* Execute one action against the platform UI. `confirmedDestructive`
|
|
956
|
+
* is true when a destructive confirm gating this action was already
|
|
957
|
+
* approved by the user — platforms use it to suppress their own
|
|
958
|
+
* native re-confirmation dialog (RN monkey-patches Alert.alert; web
|
|
959
|
+
* patches window.confirm).
|
|
960
|
+
*/
|
|
961
|
+
execute(action: AgentAction, options: {
|
|
962
|
+
confirmedDestructive: boolean;
|
|
963
|
+
}): Promise<ActionExecutionResult>;
|
|
964
|
+
getCurrentScreen(): string | null;
|
|
965
|
+
getCurrentScreenSignature(): string | null;
|
|
966
|
+
/** Declared action metadata for a screen, if the app registered any. */
|
|
967
|
+
getScreenActionsMetadata(screen: string): Array<ScreenActionMetadataLike | string> | undefined;
|
|
968
|
+
/** Wait for a navigate to land before executing subsequent actions. */
|
|
969
|
+
waitForScreenSettle(options: {
|
|
970
|
+
expectingChange: boolean;
|
|
971
|
+
fromScreen: string | null;
|
|
972
|
+
fromSignature: string | null;
|
|
973
|
+
maxMs: number;
|
|
974
|
+
}): Promise<{
|
|
975
|
+
screen: string | null;
|
|
976
|
+
transitioned: boolean;
|
|
977
|
+
timedOut: boolean;
|
|
978
|
+
waitedMs: number;
|
|
979
|
+
}>;
|
|
980
|
+
}
|
|
981
|
+
interface ActionQueueMachineDeps {
|
|
982
|
+
client: ActionQueueClient;
|
|
983
|
+
adapter: ActionRunnerAdapter;
|
|
984
|
+
emit: (event: AppilotsEvent) => void;
|
|
985
|
+
warn?: (message: string, ...args: unknown[]) => void;
|
|
986
|
+
}
|
|
987
|
+
interface ActionQueueState {
|
|
988
|
+
actions: AgentAction[];
|
|
989
|
+
}
|
|
990
|
+
|
|
991
|
+
/**
|
|
992
|
+
* ChatSessionMachine — the platform-agnostic chat/turn/continuation
|
|
993
|
+
* state machine extracted from the RN `useAppilotsChat` hook.
|
|
994
|
+
*
|
|
995
|
+
* It owns: the message transcript, the pending-action list for the
|
|
996
|
+
* current turn, loading/error state, per-turn result bookkeeping, the
|
|
997
|
+
* recovery-attempt counters, the /agent/continue loop, and the human-
|
|
998
|
+
* escalation channel. Platforms plug in through `ChatPlatformAdapter`
|
|
999
|
+
* (context capture + post-action settling) and forward the shared
|
|
1000
|
+
* event bus into `handleEvent`.
|
|
1001
|
+
*/
|
|
1002
|
+
|
|
1003
|
+
declare class ChatSessionMachine {
|
|
1004
|
+
private readonly deps;
|
|
1005
|
+
private options;
|
|
1006
|
+
private state;
|
|
1007
|
+
private readonly listeners;
|
|
1008
|
+
private messageIdCounter;
|
|
1009
|
+
private generation;
|
|
1010
|
+
private currentTurn;
|
|
1011
|
+
private continuationInFlight;
|
|
1012
|
+
private deviceLocale;
|
|
1013
|
+
private missionSuspended;
|
|
1014
|
+
/**
|
|
1015
|
+
* BACKLOG 3.2 — per-message-thread retry counter, keyed on action
|
|
1016
|
+
* fingerprint. Reset on every fresh `sendMessage`, persists across
|
|
1017
|
+
* /agent/continue hops within the same user turn so two retries of
|
|
1018
|
+
* the same action add up across hops.
|
|
1019
|
+
*/
|
|
1020
|
+
private recoveryCountByFingerprint;
|
|
1021
|
+
/** Abort controller for the in-flight generation (cancelMessage). */
|
|
1022
|
+
private abortController;
|
|
1023
|
+
private escalationStreamClose;
|
|
1024
|
+
private escalationReconnectAttempt;
|
|
1025
|
+
private escalationReconnectTimer;
|
|
1026
|
+
/** createdAt of the newest operator message — reconnect catch-up cursor. */
|
|
1027
|
+
private escalationSince;
|
|
1028
|
+
/** Operator message ids already appended (SSE replay dedupe). */
|
|
1029
|
+
private seenHumanMessageIds;
|
|
1030
|
+
private disposed;
|
|
1031
|
+
constructor(deps: ChatSessionMachineDeps, options?: ChatSessionOptions);
|
|
1032
|
+
getState(): ChatSessionState;
|
|
1033
|
+
subscribeState(listener: () => void): () => void;
|
|
1034
|
+
/** Update mutable options (errorPrefix, streaming, escalation copy). */
|
|
1035
|
+
setOptions(options: ChatSessionOptions): void;
|
|
1036
|
+
/** Tear down timers and the escalation stream. Safe to call twice. */
|
|
1037
|
+
dispose(): void;
|
|
1038
|
+
private setState;
|
|
1039
|
+
private warn;
|
|
1040
|
+
private addAssistantMessage;
|
|
1041
|
+
private addMissionReply;
|
|
1042
|
+
private appendLocalSystemMessage;
|
|
1043
|
+
requestHuman(reason?: 'user_requested' | 'agent_gave_up'): Promise<void>;
|
|
1044
|
+
/**
|
|
1045
|
+
* Live channel: operator replies + status changes. Reopens with
|
|
1046
|
+
* capped exponential backoff (and a `since` catch-up cursor) when
|
|
1047
|
+
* the transport drops, for as long as the escalation stays open.
|
|
1048
|
+
*/
|
|
1049
|
+
private openEscalationStream;
|
|
1050
|
+
private closeEscalationStream;
|
|
1051
|
+
/** Route a user message to the human channel while escalated. */
|
|
1052
|
+
private sendEscalationMessage;
|
|
1053
|
+
private beginTurn;
|
|
1054
|
+
private registerSynthesizedTurnAction;
|
|
1055
|
+
private runContinuation;
|
|
1056
|
+
handleEvent(event: AppilotsEvent): void;
|
|
1057
|
+
sendMessage(content: string): Promise<void>;
|
|
1058
|
+
/** Stop future work, keeping partial text and allowing an already dispatched action to settle. */
|
|
1059
|
+
cancelMessage(): void;
|
|
1060
|
+
restoreMission(): Promise<void>;
|
|
1061
|
+
pauseMission(): Promise<void>;
|
|
1062
|
+
resumeMission(): Promise<void>;
|
|
1063
|
+
cancelMission(): Promise<void>;
|
|
1064
|
+
private controlMission;
|
|
1065
|
+
clearMessages(): void;
|
|
1066
|
+
clearError(): void;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1069
|
+
/**
|
|
1070
|
+
* ActionQueueMachine — the platform-agnostic action queue extracted
|
|
1071
|
+
* from the RN `useAppilotsActions` hook.
|
|
1072
|
+
*
|
|
1073
|
+
* It owns: the action list and its status transitions, sequential
|
|
1074
|
+
* auto-execution, the confirm gate (server-injected and locally
|
|
1075
|
+
* synthesized), backend result reporting, and approve/reject. The
|
|
1076
|
+
* platform supplies actual execution and the navigation probes through
|
|
1077
|
+
* `ActionRunnerAdapter`.
|
|
1078
|
+
*/
|
|
1079
|
+
|
|
1080
|
+
interface ActionQueueOptions {
|
|
1081
|
+
/**
|
|
1082
|
+
* If true, actions are executed automatically without user approval.
|
|
1083
|
+
* Confirm actions always require approval regardless of this setting.
|
|
1084
|
+
* @default false
|
|
1085
|
+
*/
|
|
1086
|
+
autoExecute?: boolean;
|
|
1087
|
+
}
|
|
1088
|
+
declare class ActionQueueMachine {
|
|
1089
|
+
private readonly deps;
|
|
1090
|
+
private options;
|
|
1091
|
+
private state;
|
|
1092
|
+
private readonly listeners;
|
|
1093
|
+
/** Action ids already dispatched — never auto-executed twice. */
|
|
1094
|
+
private readonly processed;
|
|
1095
|
+
/** Guards against overlapping sequential runs. */
|
|
1096
|
+
private draining;
|
|
1097
|
+
private disposed;
|
|
1098
|
+
/** Set when new work arrives while a drain is in flight. */
|
|
1099
|
+
private drainRequested;
|
|
1100
|
+
/** Set while a drain is queued on the microtask queue. */
|
|
1101
|
+
private drainScheduled;
|
|
1102
|
+
constructor(deps: ActionQueueMachineDeps, options?: ActionQueueOptions);
|
|
1103
|
+
dispose(): void;
|
|
1104
|
+
getState(): ActionQueueState;
|
|
1105
|
+
subscribeState(listener: () => void): () => void;
|
|
1106
|
+
setOptions(options: ActionQueueOptions): void;
|
|
1107
|
+
private setActions;
|
|
1108
|
+
private updateActions;
|
|
1109
|
+
private warn;
|
|
1110
|
+
handleEvent(event: AppilotsEvent): void;
|
|
1111
|
+
/**
|
|
1112
|
+
* Kick the sequential executor. Safe to call at any time — it
|
|
1113
|
+
* coalesces while a drain is already running so a burst of arriving
|
|
1114
|
+
* actions produces exactly one in-order pass.
|
|
1115
|
+
*
|
|
1116
|
+
* The drain is deferred to a microtask, and that is load-bearing, not
|
|
1117
|
+
* a nicety. A turn's actions arrive as a synchronous burst of
|
|
1118
|
+
* `agent:action:start` events, and a confirm gating a destructive
|
|
1119
|
+
* action can arrive AFTER the action it protects. Draining
|
|
1120
|
+
* synchronously on the first event would execute that action while
|
|
1121
|
+
* its gate is still in flight — a delete running before the user
|
|
1122
|
+
* confirmed it. Deferring lets the whole burst land first, so the
|
|
1123
|
+
* gate is visible by the time anything executes. (The React hook this
|
|
1124
|
+
* was extracted from got the same guarantee for free, from state
|
|
1125
|
+
* batching before its effect ran.)
|
|
1126
|
+
*/
|
|
1127
|
+
scheduleDrain(): void;
|
|
1128
|
+
private drain;
|
|
1129
|
+
/**
|
|
1130
|
+
* Client-side defense-in-depth confirm injection.
|
|
1131
|
+
*
|
|
1132
|
+
* When the server's permission filter MISSES a destructive action
|
|
1133
|
+
* (e.g. the MCP doc is stale and doesn't have the destructive flag
|
|
1134
|
+
* for that action, but the live screen metadata DOES have
|
|
1135
|
+
* `requiresConfirmation: true`), the action would otherwise execute
|
|
1136
|
+
* immediately with no user confirmation — exactly the "delete
|
|
1137
|
+
* executed before I confirmed" bug.
|
|
1138
|
+
*
|
|
1139
|
+
* Returns `true` when the target must NOT execute this pass (either
|
|
1140
|
+
* a synthetic confirm was injected, or one is already gating it).
|
|
1141
|
+
*/
|
|
1142
|
+
private ensureLocalConfirmIfNeeded;
|
|
1143
|
+
/**
|
|
1144
|
+
* Execute pending actions in order so chained actions (e.g.
|
|
1145
|
+
* form_fill → press button) run in the correct sequence.
|
|
1146
|
+
*/
|
|
1147
|
+
private executeSequentially;
|
|
1148
|
+
/**
|
|
1149
|
+
* A turn is an all-terminal contract: ChatSessionMachine cannot ask the
|
|
1150
|
+
* server for the next step until every action in the current batch has
|
|
1151
|
+
* reported a completion or an error. When an early action fails we must
|
|
1152
|
+
* not execute dependent actions, but leaving them `pending` deadlocks the
|
|
1153
|
+
* whole turn. Mark the untouched suffix as failed/skipped and emit the
|
|
1154
|
+
* same terminal event a real executor would have emitted.
|
|
1155
|
+
*/
|
|
1156
|
+
private skipRemainingBatchActions;
|
|
1157
|
+
approveAction(actionId: string): Promise<boolean>;
|
|
1158
|
+
rejectAction(actionId: string): Promise<void>;
|
|
1159
|
+
get pendingActions(): AgentAction[];
|
|
1160
|
+
get executingActions(): AgentAction[];
|
|
1161
|
+
get completedActions(): AgentAction[];
|
|
1162
|
+
}
|
|
1163
|
+
|
|
1164
|
+
/**
|
|
1165
|
+
* Navigation adapter — the web answer to React Navigation.
|
|
1166
|
+
*
|
|
1167
|
+
* On the web there is no navigator object to interrogate, so the URL is
|
|
1168
|
+
* the source of truth: the path maps to a route name and its segments
|
|
1169
|
+
* to the `activePath` the agent contract expects. An app with a router
|
|
1170
|
+
* (React Router, TanStack Router, ...) can inject its own `navigate()`
|
|
1171
|
+
* and route table so navigation stays client-side and route names match
|
|
1172
|
+
* the MCP document; without one, the adapter falls back to
|
|
1173
|
+
* `history.pushState` and derives names from path segments.
|
|
1174
|
+
*/
|
|
1175
|
+
interface WebNavigationState {
|
|
1176
|
+
currentRouteName?: string;
|
|
1177
|
+
activePath: string[];
|
|
1178
|
+
rootRouteNames: string[];
|
|
1179
|
+
currentRouteNames: string[];
|
|
1180
|
+
routeNames: string[];
|
|
1181
|
+
canGoBack?: boolean;
|
|
1182
|
+
/**
|
|
1183
|
+
* Screens this session has been on, oldest first. No `backStack`
|
|
1184
|
+
* counterpart: the browser exposes `history.length` and nothing about
|
|
1185
|
+
* its entries, so where back goes is unknowable here.
|
|
1186
|
+
*/
|
|
1187
|
+
visited?: string[];
|
|
1188
|
+
}
|
|
1189
|
+
/** A route the app declared, mapping an agent-facing name to a path. */
|
|
1190
|
+
interface WebRoute {
|
|
1191
|
+
/** Route name as it appears in the MCP document (e.g. `VehicleList`). */
|
|
1192
|
+
name: string;
|
|
1193
|
+
/** URL path, optionally with `:param` segments (e.g. `/vehicles/:id`). */
|
|
1194
|
+
path: string;
|
|
1195
|
+
}
|
|
1196
|
+
interface WebNavigationAdapterOptions {
|
|
1197
|
+
/** Declared routes — the same names the MCP document uses. */
|
|
1198
|
+
routes?: WebRoute[];
|
|
1199
|
+
/**
|
|
1200
|
+
* The app's own navigation function. Injected by a router integration
|
|
1201
|
+
* so navigating stays client-side (no full page reload).
|
|
1202
|
+
*/
|
|
1203
|
+
navigate?: (path: string, options?: {
|
|
1204
|
+
replace?: boolean;
|
|
1205
|
+
}) => void;
|
|
1206
|
+
/** Overrides `window` — used by tests and non-browser hosts. */
|
|
1207
|
+
window?: Window;
|
|
1208
|
+
}
|
|
1209
|
+
/**
|
|
1210
|
+
* Match a declared route pattern against a concrete path, returning the
|
|
1211
|
+
* captured params. `/vehicles/:id` vs `/vehicles/42` → `{ id: '42' }`.
|
|
1212
|
+
*/
|
|
1213
|
+
declare function matchRoute(pattern: string, pathname: string): Record<string, string> | undefined;
|
|
1214
|
+
/** Fill `/vehicles/:id` with `{ id: 42 }` → `/vehicles/42`. */
|
|
1215
|
+
declare function buildPath(pattern: string, params?: Record<string, unknown>): string;
|
|
1216
|
+
declare class WebNavigationAdapter {
|
|
1217
|
+
private routes;
|
|
1218
|
+
private navigateFn?;
|
|
1219
|
+
private readonly win?;
|
|
1220
|
+
/** Bumped on every navigation so the settle loop can detect movement. */
|
|
1221
|
+
private listeners;
|
|
1222
|
+
constructor(options?: WebNavigationAdapterOptions);
|
|
1223
|
+
/** Register (or replace) the app's routes and navigate function. */
|
|
1224
|
+
configure(options: Pick<WebNavigationAdapterOptions, 'routes' | 'navigate'>): void;
|
|
1225
|
+
private get location();
|
|
1226
|
+
/**
|
|
1227
|
+
* The declared route matching the current URL, if any.
|
|
1228
|
+
*
|
|
1229
|
+
* When several patterns match, the most specific wins: a route with
|
|
1230
|
+
* fewer `:param` segments beats one with more. Without this,
|
|
1231
|
+
* declaration order decides, and `/vehicles/:id` would swallow
|
|
1232
|
+
* `/vehicles/new` — resolving the create screen to VehicleDetails
|
|
1233
|
+
* with `id: 'new'`.
|
|
1234
|
+
*/
|
|
1235
|
+
private currentRoute;
|
|
1236
|
+
/**
|
|
1237
|
+
* Current route name: the declared route's name when the URL matches
|
|
1238
|
+
* one, else a name derived from the last path segment ('/' → 'Home').
|
|
1239
|
+
*/
|
|
1240
|
+
/**
|
|
1241
|
+
* Screens this session has been on, oldest first.
|
|
1242
|
+
*
|
|
1243
|
+
* Recorded in `getCurrentScreen` rather than on a router event: the
|
|
1244
|
+
* adapter derives the route from the URL on demand and has no state
|
|
1245
|
+
* change to hook, and this method is called on every observation and
|
|
1246
|
+
* throughout settle polling, so a route that is up long enough to act
|
|
1247
|
+
* on is a route this sees.
|
|
1248
|
+
*
|
|
1249
|
+
* There is no web counterpart to `backStack`. The browser exposes
|
|
1250
|
+
* `history.length` and nothing else about its entries, so where back
|
|
1251
|
+
* goes is genuinely unknowable here — and the field stays absent
|
|
1252
|
+
* rather than guessed.
|
|
1253
|
+
*/
|
|
1254
|
+
private visited;
|
|
1255
|
+
/** The visit log, oldest first. */
|
|
1256
|
+
getVisitedScreens(): string[];
|
|
1257
|
+
private recordVisit;
|
|
1258
|
+
getCurrentScreen(): string | null;
|
|
1259
|
+
private resolveCurrentScreen;
|
|
1260
|
+
/**
|
|
1261
|
+
* Route name plus its params — lets the settle loop detect a navigate
|
|
1262
|
+
* to the same route with different params (`/vehicles/41` →
|
|
1263
|
+
* `/vehicles/42`), which a name-only comparison would miss.
|
|
1264
|
+
*/
|
|
1265
|
+
getCurrentScreenSignature(): string | null;
|
|
1266
|
+
/**
|
|
1267
|
+
* The `navigationState` block of the agent context. `activePath` is
|
|
1268
|
+
* the URL's segments mapped to route names — the web equivalent of a
|
|
1269
|
+
* nested navigator's active route chain.
|
|
1270
|
+
*/
|
|
1271
|
+
getNavigationState(): WebNavigationState | null;
|
|
1272
|
+
/** Path for a route name, or undefined when it is not declared. */
|
|
1273
|
+
resolvePath(screenName: string, params?: Record<string, unknown>): string | undefined;
|
|
1274
|
+
/** True when the app declared this route name. */
|
|
1275
|
+
hasRoute(screenName: string): boolean;
|
|
1276
|
+
knownRouteNames(): string[];
|
|
1277
|
+
/**
|
|
1278
|
+
* Navigate to a path. Uses the app's own navigate function when one
|
|
1279
|
+
* was injected (keeping SPA routing intact); otherwise pushes onto
|
|
1280
|
+
* the History API and notifies listeners, so a router listening to
|
|
1281
|
+
* `popstate` still re-renders.
|
|
1282
|
+
*/
|
|
1283
|
+
navigate(path: string, options?: {
|
|
1284
|
+
replace?: boolean;
|
|
1285
|
+
}): void;
|
|
1286
|
+
goBack(): void;
|
|
1287
|
+
canGoBack(): boolean;
|
|
1288
|
+
subscribe(listener: () => void): () => void;
|
|
1289
|
+
private notify;
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
/**
|
|
1293
|
+
* AppilotsWebRuntime — wires the shared session machines to the web.
|
|
1294
|
+
*
|
|
1295
|
+
* This is the whole web client in one object: the transport, the two
|
|
1296
|
+
* client-core state machines, and the two platform adapters that give
|
|
1297
|
+
* them eyes (DOM snapshot) and hands (DOM dispatch). A React binding
|
|
1298
|
+
* sits on top of it, but nothing here needs React — a Vue or vanilla
|
|
1299
|
+
* app can use this class directly.
|
|
1300
|
+
*/
|
|
1301
|
+
|
|
1302
|
+
/** Design-time metadata an app declares for a screen. */
|
|
1303
|
+
interface WebScreenMetadata {
|
|
1304
|
+
name: string;
|
|
1305
|
+
title?: string;
|
|
1306
|
+
description?: string;
|
|
1307
|
+
fields?: Array<Record<string, unknown>>;
|
|
1308
|
+
actions?: Array<ScreenActionMetadataLike | string>;
|
|
1309
|
+
}
|
|
1310
|
+
interface AppilotsWebRuntimeOptions {
|
|
1311
|
+
projectId: string;
|
|
1312
|
+
apiKey?: string;
|
|
1313
|
+
apiBaseUrl?: string;
|
|
1314
|
+
debug?: boolean;
|
|
1315
|
+
appVersion?: string;
|
|
1316
|
+
mcpVersion?: string;
|
|
1317
|
+
user?: {
|
|
1318
|
+
id: string;
|
|
1319
|
+
name?: string;
|
|
1320
|
+
identifiers?: Record<string, string>;
|
|
1321
|
+
};
|
|
1322
|
+
permissions?: Partial<AgentPermissions>;
|
|
1323
|
+
/** Declared routes — names must match the MCP document. */
|
|
1324
|
+
routes?: WebRoute[];
|
|
1325
|
+
/** The app's router navigate function, when it has one. */
|
|
1326
|
+
navigate?: WebNavigationAdapterOptions['navigate'];
|
|
1327
|
+
/** Screen metadata keyed by route name. */
|
|
1328
|
+
screens?: WebScreenMetadata[];
|
|
1329
|
+
/** Subtree to observe and act within. Defaults to the document. */
|
|
1330
|
+
root?: Element;
|
|
1331
|
+
/** Execute actions as they arrive, without per-action approval. */
|
|
1332
|
+
autoExecute?: boolean;
|
|
1333
|
+
/** Injected transport, for tests. */
|
|
1334
|
+
client?: AppilotsClient;
|
|
1335
|
+
chat?: ChatSessionOptions;
|
|
1336
|
+
}
|
|
1337
|
+
declare class AppilotsWebRuntime {
|
|
1338
|
+
readonly client: AppilotsClient;
|
|
1339
|
+
readonly navigation: WebNavigationAdapter;
|
|
1340
|
+
readonly chat: ChatSessionMachine;
|
|
1341
|
+
readonly actions: ActionQueueMachine;
|
|
1342
|
+
/**
|
|
1343
|
+
* Baseline for `snapshot.delta`. Per-runtime rather than per-module so
|
|
1344
|
+
* two runtimes on one page (a preview inside an app) cannot read each
|
|
1345
|
+
* other's previous screen.
|
|
1346
|
+
*/
|
|
1347
|
+
private readonly deltaTracker;
|
|
1348
|
+
private readonly handlers;
|
|
1349
|
+
private readonly screens;
|
|
1350
|
+
private permissions?;
|
|
1351
|
+
private root?;
|
|
1352
|
+
private lifetime;
|
|
1353
|
+
constructor(options: AppilotsWebRuntimeOptions);
|
|
1354
|
+
subscribe(handler: AppilotsEventHandler): () => void;
|
|
1355
|
+
emit(event: AppilotsEvent): void;
|
|
1356
|
+
configureNavigation(options: Pick<WebNavigationAdapterOptions, 'routes' | 'navigate'>): void;
|
|
1357
|
+
registerScreen(metadata: WebScreenMetadata): void;
|
|
1358
|
+
setPermissions(permissions: Partial<AgentPermissions> | undefined): void;
|
|
1359
|
+
setRoot(root: Element | undefined): void;
|
|
1360
|
+
suspend(): void;
|
|
1361
|
+
resume(): void;
|
|
1362
|
+
dispose(): void;
|
|
1363
|
+
/**
|
|
1364
|
+
* The observation sent with every turn. `platform: 'web'` is explicit
|
|
1365
|
+
* on every request (including /agent/continue, which reuses this) so
|
|
1366
|
+
* the server applies its platform-neutral profile rather than the
|
|
1367
|
+
* React Native heuristics.
|
|
1368
|
+
*/
|
|
1369
|
+
buildContext(extras?: {
|
|
1370
|
+
loadingPending?: boolean;
|
|
1371
|
+
newMission?: boolean;
|
|
1372
|
+
}): Record<string, unknown>;
|
|
1373
|
+
private createChatAdapter;
|
|
1374
|
+
private createActionRunner;
|
|
1375
|
+
/**
|
|
1376
|
+
* The declared actions for a screen, expanded with one entry per live
|
|
1377
|
+
* DOM element that points at a declaration via `data-appilots-action`.
|
|
1378
|
+
*
|
|
1379
|
+
* A list renders one delete button per row, each needing its own id to
|
|
1380
|
+
* be targetable, but all of them are the same declared action and must
|
|
1381
|
+
* inherit its `requiresConfirmation`. The confirm matcher is exact by
|
|
1382
|
+
* design (a fuzzy one fired destructive dialogs on plain navigation),
|
|
1383
|
+
* so we expand the declaration to the concrete ids instead of
|
|
1384
|
+
* loosening the match.
|
|
1385
|
+
*/
|
|
1386
|
+
private screenActionsFor;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
export { type AgentAction as A, type ControlEvidence as C, type EscalationState as E, RateLimitedError as R, type ScreenActionMetadataLike as S, WebNavigationAdapter as W, type AppilotsLocale as a, type AgentPermissions as b, type AppilotsEvent as c, type ActionExecutionResult as d, type ActionDiagnose as e, ActionQueueMachine as f, type AgentActionType as g, AppilotsClient as h, type AppilotsEventHandler as i, AppilotsWebRuntime as j, type AppilotsWebRuntimeOptions as k, type ChatMessage as l, ChatSessionMachine as m, type ChatSessionOptions as n, type ChatSessionState as o, type EscalationStrings as p, type WebNavigationAdapterOptions as q, type WebNavigationState as r, type WebRoute as s, type WebScreenMetadata as t, buildPath as u, matchRoute as v };
|