appilot 0.0.1 → 0.1.1
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 +101 -7
- package/dist/dom/sensitiveFields.d.ts +33 -0
- package/dist/dom/sensitiveFields.js +72 -0
- package/dist/dom/surfaces.d.ts +81 -0
- package/dist/dom/surfaces.js +91 -0
- package/dist/dom/uniqueSelector.d.ts +23 -0
- package/dist/dom/uniqueSelector.js +104 -0
- package/dist/domResponder.d.ts +38 -0
- package/dist/domResponder.js +355 -0
- package/dist/focusedSessions/pageSdk.d.ts +107 -0
- package/dist/focusedSessions/pageSdk.js +238 -0
- package/dist/httpFetch.d.ts +52 -0
- package/dist/httpFetch.js +137 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +24 -0
- package/dist/runtime.d.ts +16 -0
- package/dist/runtime.js +21 -0
- package/dist/sessions/types.d.ts +140 -0
- package/dist/sessions/types.js +12 -0
- package/dist/webmcp/registry.d.ts +75 -0
- package/dist/webmcp/registry.js +152 -0
- package/dist/webmcp/sdk.d.ts +38 -0
- package/dist/webmcp/sdk.js +81 -0
- package/dist/widget/boot.d.ts +91 -0
- package/dist/widget/boot.js +313 -0
- package/package.json +51 -16
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-page client-action registry: the WebMCP-aligned tool surface a host page
|
|
3
|
+
* declares at runtime so the Appilot agent can call its actions.
|
|
4
|
+
*
|
|
5
|
+
* WHY a window-anchored global (not a module singleton): the registry must be
|
|
6
|
+
* reachable from code that does NOT share this module instance.
|
|
7
|
+
* - Widget: the embedding page and the widget bundle are separate bundles in
|
|
8
|
+
* the same realm; a module-level singleton in one is invisible to the other.
|
|
9
|
+
* - Extension: the content-script runs in an isolated world; the page's tools
|
|
10
|
+
* live in the main world. A main-world bridge collects/invokes via this
|
|
11
|
+
* global. See packages/apps/extension/.../mainWorldWebmcp.ts.
|
|
12
|
+
* Anchoring on `window.__APPILOT_WEBMCP__` (mirroring `navigator.modelContext`)
|
|
13
|
+
* gives every party one stable handle. This file is the only place that reads
|
|
14
|
+
* or writes that global.
|
|
15
|
+
*
|
|
16
|
+
* Standard alignment: we bind to the stable WebMCP descriptor
|
|
17
|
+
* (name/description/inputSchema/execute → {content:[{type,text}]}) and treat the
|
|
18
|
+
* namespace (`navigator.modelContext` vs `document.modelContext`) as detect-only.
|
|
19
|
+
* See docs/content-model/tools-client-actions.md and webmcp-alignment.md.
|
|
20
|
+
*/
|
|
21
|
+
export interface ClientToolContent {
|
|
22
|
+
type: 'text';
|
|
23
|
+
text: string;
|
|
24
|
+
}
|
|
25
|
+
export interface ClientToolResult {
|
|
26
|
+
content?: ClientToolContent[];
|
|
27
|
+
/** Loose escape hatch: handlers that return raw JSON are normalized too. */
|
|
28
|
+
[k: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
export interface ClientToolDefinition {
|
|
31
|
+
name: string;
|
|
32
|
+
description: string;
|
|
33
|
+
/** JSON Schema for the args. Mirrors WebMCP `inputSchema`. */
|
|
34
|
+
inputSchema?: Record<string, unknown>;
|
|
35
|
+
/** Mirrors WebMCP `annotations.readOnlyHint`. Absent ⇒ treated as mutating. */
|
|
36
|
+
annotations?: {
|
|
37
|
+
readOnlyHint?: boolean;
|
|
38
|
+
};
|
|
39
|
+
execute: (input: Record<string, unknown>) => Promise<ClientToolResult | unknown> | ClientToolResult | unknown;
|
|
40
|
+
}
|
|
41
|
+
/** The agent-facing descriptor (no handler) shipped to the backend per turn. */
|
|
42
|
+
export interface SerializedClientTool {
|
|
43
|
+
name: string;
|
|
44
|
+
description: string;
|
|
45
|
+
input_schema?: Record<string, unknown>;
|
|
46
|
+
read_only: boolean;
|
|
47
|
+
title?: string;
|
|
48
|
+
}
|
|
49
|
+
export interface ClientToolHandle {
|
|
50
|
+
unregister(): void;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Register a client tool/action. Idempotent per name (re-registering replaces).
|
|
54
|
+
* Returns a handle whose `unregister()` removes it (use on route change).
|
|
55
|
+
*
|
|
56
|
+
* Secure-context only (HTTPS / localhost / *.lvh.me), matching the standard.
|
|
57
|
+
* On a non-secure context the call is a no-op and returns an inert handle.
|
|
58
|
+
*/
|
|
59
|
+
export declare function registerClientTool(def: ClientToolDefinition): ClientToolHandle;
|
|
60
|
+
/** Remove a registered tool by name. */
|
|
61
|
+
export declare function unregisterClientTool(name: string): void;
|
|
62
|
+
/** Subscribe to registry changes (WebMCP `toolchange` analogue). */
|
|
63
|
+
export declare function onClientToolsChange(listener: () => void): () => void;
|
|
64
|
+
/** Serialize the current registry to the agent-facing descriptors (no handlers). */
|
|
65
|
+
export declare function serializeClientTools(): SerializedClientTool[];
|
|
66
|
+
/**
|
|
67
|
+
* Invoke a registered handler by name and normalize the result to a plain
|
|
68
|
+
* value the agent loop can serialize. Returns a structured error object when
|
|
69
|
+
* the action is unknown or throws (never rejects).
|
|
70
|
+
*/
|
|
71
|
+
export declare function invokeClientTool(name: string, args: Record<string, unknown>): Promise<unknown>;
|
|
72
|
+
/** Collapse a WebMCP `{content:[{type,text}]}` result or raw JSON to a value. */
|
|
73
|
+
export declare function normalizeResult(raw: unknown): unknown;
|
|
74
|
+
/** Test/inspection helper: clear the global registry. */
|
|
75
|
+
export declare function __resetClientToolRegistry(): void;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-page client-action registry: the WebMCP-aligned tool surface a host page
|
|
3
|
+
* declares at runtime so the Appilot agent can call its actions.
|
|
4
|
+
*
|
|
5
|
+
* WHY a window-anchored global (not a module singleton): the registry must be
|
|
6
|
+
* reachable from code that does NOT share this module instance.
|
|
7
|
+
* - Widget: the embedding page and the widget bundle are separate bundles in
|
|
8
|
+
* the same realm; a module-level singleton in one is invisible to the other.
|
|
9
|
+
* - Extension: the content-script runs in an isolated world; the page's tools
|
|
10
|
+
* live in the main world. A main-world bridge collects/invokes via this
|
|
11
|
+
* global. See packages/apps/extension/.../mainWorldWebmcp.ts.
|
|
12
|
+
* Anchoring on `window.__APPILOT_WEBMCP__` (mirroring `navigator.modelContext`)
|
|
13
|
+
* gives every party one stable handle. This file is the only place that reads
|
|
14
|
+
* or writes that global.
|
|
15
|
+
*
|
|
16
|
+
* Standard alignment: we bind to the stable WebMCP descriptor
|
|
17
|
+
* (name/description/inputSchema/execute → {content:[{type,text}]}) and treat the
|
|
18
|
+
* namespace (`navigator.modelContext` vs `document.modelContext`) as detect-only.
|
|
19
|
+
* See docs/content-model/tools-client-actions.md and webmcp-alignment.md.
|
|
20
|
+
*/
|
|
21
|
+
const GLOBAL_KEY = '__APPILOT_WEBMCP__';
|
|
22
|
+
function getWindow() {
|
|
23
|
+
return typeof window !== 'undefined' ? window : undefined;
|
|
24
|
+
}
|
|
25
|
+
function getRegistry() {
|
|
26
|
+
const w = getWindow();
|
|
27
|
+
if (!w)
|
|
28
|
+
return undefined;
|
|
29
|
+
const holder = w;
|
|
30
|
+
if (!holder[GLOBAL_KEY]) {
|
|
31
|
+
holder[GLOBAL_KEY] = { version: 1, tools: new Map(), listeners: new Set() };
|
|
32
|
+
}
|
|
33
|
+
return holder[GLOBAL_KEY];
|
|
34
|
+
}
|
|
35
|
+
function isSecureContextOk() {
|
|
36
|
+
const w = getWindow();
|
|
37
|
+
if (!w)
|
|
38
|
+
return false;
|
|
39
|
+
// Mirror WebMCP's [SecureContext]. Allow localhost/127.* for dev + playground.
|
|
40
|
+
if (w.isSecureContext)
|
|
41
|
+
return true;
|
|
42
|
+
const host = w.location?.hostname ?? '';
|
|
43
|
+
return host === 'localhost' || host === '127.0.0.1' || host.endsWith('.localhost') || host.endsWith('.lvh.me');
|
|
44
|
+
}
|
|
45
|
+
function notify(reg) {
|
|
46
|
+
for (const fn of reg.listeners) {
|
|
47
|
+
try {
|
|
48
|
+
fn();
|
|
49
|
+
}
|
|
50
|
+
catch { /* listener errors are non-fatal */ }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Register a client tool/action. Idempotent per name (re-registering replaces).
|
|
55
|
+
* Returns a handle whose `unregister()` removes it (use on route change).
|
|
56
|
+
*
|
|
57
|
+
* Secure-context only (HTTPS / localhost / *.lvh.me), matching the standard.
|
|
58
|
+
* On a non-secure context the call is a no-op and returns an inert handle.
|
|
59
|
+
*/
|
|
60
|
+
export function registerClientTool(def) {
|
|
61
|
+
const reg = getRegistry();
|
|
62
|
+
if (!reg || !isSecureContextOk()) {
|
|
63
|
+
return { unregister() { } };
|
|
64
|
+
}
|
|
65
|
+
if (!def || typeof def.name !== 'string' || typeof def.execute !== 'function') {
|
|
66
|
+
throw new Error('registerClientTool: { name, execute } are required');
|
|
67
|
+
}
|
|
68
|
+
reg.tools.set(def.name, def);
|
|
69
|
+
notify(reg);
|
|
70
|
+
let active = true;
|
|
71
|
+
return {
|
|
72
|
+
unregister() {
|
|
73
|
+
if (!active)
|
|
74
|
+
return;
|
|
75
|
+
active = false;
|
|
76
|
+
if (reg.tools.get(def.name) === def) {
|
|
77
|
+
reg.tools.delete(def.name);
|
|
78
|
+
notify(reg);
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** Remove a registered tool by name. */
|
|
84
|
+
export function unregisterClientTool(name) {
|
|
85
|
+
const reg = getRegistry();
|
|
86
|
+
if (!reg)
|
|
87
|
+
return;
|
|
88
|
+
if (reg.tools.delete(name))
|
|
89
|
+
notify(reg);
|
|
90
|
+
}
|
|
91
|
+
/** Subscribe to registry changes (WebMCP `toolchange` analogue). */
|
|
92
|
+
export function onClientToolsChange(listener) {
|
|
93
|
+
const reg = getRegistry();
|
|
94
|
+
if (!reg)
|
|
95
|
+
return () => { };
|
|
96
|
+
reg.listeners.add(listener);
|
|
97
|
+
return () => { reg.listeners.delete(listener); };
|
|
98
|
+
}
|
|
99
|
+
/** Serialize the current registry to the agent-facing descriptors (no handlers). */
|
|
100
|
+
export function serializeClientTools() {
|
|
101
|
+
const reg = getRegistry();
|
|
102
|
+
if (!reg)
|
|
103
|
+
return [];
|
|
104
|
+
const out = [];
|
|
105
|
+
for (const def of reg.tools.values()) {
|
|
106
|
+
out.push({
|
|
107
|
+
name: def.name,
|
|
108
|
+
description: def.description ?? '',
|
|
109
|
+
input_schema: def.inputSchema,
|
|
110
|
+
read_only: def.annotations?.readOnlyHint === true,
|
|
111
|
+
title: def.name,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
return out;
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Invoke a registered handler by name and normalize the result to a plain
|
|
118
|
+
* value the agent loop can serialize. Returns a structured error object when
|
|
119
|
+
* the action is unknown or throws (never rejects).
|
|
120
|
+
*/
|
|
121
|
+
export async function invokeClientTool(name, args) {
|
|
122
|
+
const reg = getRegistry();
|
|
123
|
+
const def = reg?.tools.get(name);
|
|
124
|
+
if (!def) {
|
|
125
|
+
return { ok: false, error: 'ACTION_NOT_REGISTERED', detail: `no client action registered as "${name}"` };
|
|
126
|
+
}
|
|
127
|
+
try {
|
|
128
|
+
const raw = await def.execute(args ?? {});
|
|
129
|
+
return { ok: true, data: normalizeResult(raw) };
|
|
130
|
+
}
|
|
131
|
+
catch (err) {
|
|
132
|
+
return { ok: false, error: 'ACTION_FAILED', detail: err instanceof Error ? err.message : String(err) };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
/** Collapse a WebMCP `{content:[{type,text}]}` result or raw JSON to a value. */
|
|
136
|
+
export function normalizeResult(raw) {
|
|
137
|
+
if (raw && typeof raw === 'object' && Array.isArray(raw.content)) {
|
|
138
|
+
const parts = raw.content;
|
|
139
|
+
const text = parts.filter(p => p && p.type === 'text').map(p => p.text).join('\n').trim();
|
|
140
|
+
if (text)
|
|
141
|
+
return text;
|
|
142
|
+
}
|
|
143
|
+
return raw;
|
|
144
|
+
}
|
|
145
|
+
/** Test/inspection helper: clear the global registry. */
|
|
146
|
+
export function __resetClientToolRegistry() {
|
|
147
|
+
const reg = getRegistry();
|
|
148
|
+
if (reg) {
|
|
149
|
+
reg.tools.clear();
|
|
150
|
+
reg.listeners.clear();
|
|
151
|
+
}
|
|
152
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `appilot` client actions: the WebMCP-aligned surface a host app calls to
|
|
3
|
+
* expose its actions to the Appilot agent.
|
|
4
|
+
*
|
|
5
|
+
* Two postures, one call site:
|
|
6
|
+
* - POLYFILL (provider): for the 99% of apps with no native WebMCP, our
|
|
7
|
+
* in-page registry IS the tool surface (see registry.ts).
|
|
8
|
+
* - CONSUMER (native): when the browser exposes `navigator.modelContext` /
|
|
9
|
+
* `document.modelContext`, we also delegate registration to it (so the page
|
|
10
|
+
* is a real WebMCP page for any agent), and the collector prefers native
|
|
11
|
+
* tools. Namespace is detect-only because the spec is in flux; we bind to
|
|
12
|
+
* the stable descriptor (name/description/inputSchema/execute).
|
|
13
|
+
*
|
|
14
|
+
* Import from `appilot`. See docs/content-model/tools-client-actions.md.
|
|
15
|
+
*/
|
|
16
|
+
import { type ClientToolDefinition, type ClientToolHandle, type SerializedClientTool } from './registry.js';
|
|
17
|
+
/** True when the browser natively implements a WebMCP `modelContext`. */
|
|
18
|
+
export declare function hasNativeWebMcp(): boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Register a tool with Appilot, WebMCP-style. The descriptor is the stable
|
|
21
|
+
* WebMCP shape: `{ name, description, inputSchema?, annotations?, execute }`.
|
|
22
|
+
*
|
|
23
|
+
* If the browser exposes a native `modelContext`, we ALSO register there so a
|
|
24
|
+
* native browser agent sees the same tool: "re-emission, not rewrite". We
|
|
25
|
+
* always mirror into Appilot's registry so collection does not depend on the
|
|
26
|
+
* still-unspecified native `getTools()`/`executeTool()` discovery API.
|
|
27
|
+
*/
|
|
28
|
+
export declare function registerTool(def: ClientToolDefinition): ClientToolHandle;
|
|
29
|
+
/**
|
|
30
|
+
* The per-turn collector the extension/widget call to gather the descriptors
|
|
31
|
+
* to send to the backend. Returns Appilot-registry tools today; when only
|
|
32
|
+
* native tools exist (a page that used `navigator.modelContext` directly), the
|
|
33
|
+
* extension's main-world bridge supplies them instead. Kept here so both
|
|
34
|
+
* surfaces share one serialization shape.
|
|
35
|
+
*/
|
|
36
|
+
export declare function collectClientTools(): SerializedClientTool[];
|
|
37
|
+
export { registerClientTool, unregisterClientTool, onClientToolsChange } from './registry.js';
|
|
38
|
+
export type { ClientToolDefinition, ClientToolHandle, SerializedClientTool } from './registry.js';
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `appilot` client actions: the WebMCP-aligned surface a host app calls to
|
|
3
|
+
* expose its actions to the Appilot agent.
|
|
4
|
+
*
|
|
5
|
+
* Two postures, one call site:
|
|
6
|
+
* - POLYFILL (provider): for the 99% of apps with no native WebMCP, our
|
|
7
|
+
* in-page registry IS the tool surface (see registry.ts).
|
|
8
|
+
* - CONSUMER (native): when the browser exposes `navigator.modelContext` /
|
|
9
|
+
* `document.modelContext`, we also delegate registration to it (so the page
|
|
10
|
+
* is a real WebMCP page for any agent), and the collector prefers native
|
|
11
|
+
* tools. Namespace is detect-only because the spec is in flux; we bind to
|
|
12
|
+
* the stable descriptor (name/description/inputSchema/execute).
|
|
13
|
+
*
|
|
14
|
+
* Import from `appilot`. See docs/content-model/tools-client-actions.md.
|
|
15
|
+
*/
|
|
16
|
+
import { registerClientTool, serializeClientTools, } from './registry.js';
|
|
17
|
+
function nativeModelContext() {
|
|
18
|
+
if (typeof navigator !== 'undefined' && navigator.modelContext) {
|
|
19
|
+
return navigator.modelContext;
|
|
20
|
+
}
|
|
21
|
+
if (typeof document !== 'undefined' && document.modelContext) {
|
|
22
|
+
return document.modelContext;
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
/** True when the browser natively implements a WebMCP `modelContext`. */
|
|
27
|
+
export function hasNativeWebMcp() {
|
|
28
|
+
return nativeModelContext() !== undefined;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Register a tool with Appilot, WebMCP-style. The descriptor is the stable
|
|
32
|
+
* WebMCP shape: `{ name, description, inputSchema?, annotations?, execute }`.
|
|
33
|
+
*
|
|
34
|
+
* If the browser exposes a native `modelContext`, we ALSO register there so a
|
|
35
|
+
* native browser agent sees the same tool: "re-emission, not rewrite". We
|
|
36
|
+
* always mirror into Appilot's registry so collection does not depend on the
|
|
37
|
+
* still-unspecified native `getTools()`/`executeTool()` discovery API.
|
|
38
|
+
*/
|
|
39
|
+
export function registerTool(def) {
|
|
40
|
+
const appilotHandle = registerClientTool(def);
|
|
41
|
+
let nativeAbort;
|
|
42
|
+
const native = nativeModelContext();
|
|
43
|
+
if (native?.registerTool) {
|
|
44
|
+
try {
|
|
45
|
+
// Both the spec-draft (returns Promise + AbortController option) and
|
|
46
|
+
// the Chrome-shipped surface accept this descriptor shape.
|
|
47
|
+
const controller = typeof AbortController !== 'undefined' ? new AbortController() : undefined;
|
|
48
|
+
native.registerTool({
|
|
49
|
+
name: def.name,
|
|
50
|
+
description: def.description,
|
|
51
|
+
inputSchema: def.inputSchema,
|
|
52
|
+
annotations: def.annotations,
|
|
53
|
+
execute: def.execute,
|
|
54
|
+
}, controller ? { signal: controller.signal } : undefined);
|
|
55
|
+
nativeAbort = controller;
|
|
56
|
+
}
|
|
57
|
+
catch {
|
|
58
|
+
// Native registration is best-effort; Appilot's registry still works.
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
unregister() {
|
|
63
|
+
appilotHandle.unregister();
|
|
64
|
+
try {
|
|
65
|
+
nativeAbort?.abort?.();
|
|
66
|
+
}
|
|
67
|
+
catch { /* ignore */ }
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* The per-turn collector the extension/widget call to gather the descriptors
|
|
73
|
+
* to send to the backend. Returns Appilot-registry tools today; when only
|
|
74
|
+
* native tools exist (a page that used `navigator.modelContext` directly), the
|
|
75
|
+
* extension's main-world bridge supplies them instead. Kept here so both
|
|
76
|
+
* surfaces share one serialization shape.
|
|
77
|
+
*/
|
|
78
|
+
export function collectClientTools() {
|
|
79
|
+
return serializeClientTools();
|
|
80
|
+
}
|
|
81
|
+
export { registerClientTool, unregisterClientTool, onClientToolsChange } from './registry.js';
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Appilot widget boot: load the embeddable assistant into a host app with an
|
|
3
|
+
* identified user token, and keep it authenticated for the life of the page.
|
|
4
|
+
*
|
|
5
|
+
* The host app owns identity: it exposes a backend RELAY endpoint that mints an
|
|
6
|
+
* identified widget token (so the widget key never sees the end user's
|
|
7
|
+
* credentials), and this boot loader POSTs to it, then loads the widget bundle
|
|
8
|
+
* with the returned token.
|
|
9
|
+
*
|
|
10
|
+
* Design guarantees:
|
|
11
|
+
* - Boot does not latch on failure: a failed relay or bundle load returns
|
|
12
|
+
* availability to 'unavailable' and a later bootAppilotWidget() call retries
|
|
13
|
+
* from scratch (one hiccup never permanently disables the assistant).
|
|
14
|
+
* - The widget token (TTL ~3600s) is re-relayed at ~80% of its TTL and handed
|
|
15
|
+
* to the live instance through `window.Appilot.setUserToken`, so a long-lived
|
|
16
|
+
* tab never starts 401ing on a healthy host session. The refreshed token is
|
|
17
|
+
* NOT written back to the DOM (see "The token and the DOM" below).
|
|
18
|
+
* - The widget can request an out-of-band refresh by dispatching
|
|
19
|
+
* 'appilot:identity-refresh-requested' on window (it does so when its
|
|
20
|
+
* federated identity is rejected); the loader re-relays immediately.
|
|
21
|
+
* - When the host supplies `getBearer` and it returns null (the host session
|
|
22
|
+
* is gone), the scheduled refresh does NOT call the relay: relaying without
|
|
23
|
+
* a bearer would mint whatever fallback identity the endpoint assigns,
|
|
24
|
+
* silently swapping the signed-in user for a default persona. The loader
|
|
25
|
+
* retries until the host session is back.
|
|
26
|
+
* - The relay call carries a bearer supplied by the host (`getBearer`) when one
|
|
27
|
+
* exists, so a logged-in user federates as themselves; without it the relay
|
|
28
|
+
* falls back to whatever identity the endpoint assigns.
|
|
29
|
+
* - Availability is observable (getWidgetAvailability / subscribeWidgetAvailability)
|
|
30
|
+
* so pages can render an honest "assistant unavailable" state. Pairs with
|
|
31
|
+
* React useSyncExternalStore(subscribeWidgetAvailability, getWidgetAvailability).
|
|
32
|
+
*
|
|
33
|
+
* ## The token and the DOM
|
|
34
|
+
*
|
|
35
|
+
* `data-user-token` is a bearer token for the end user. Anything that can read
|
|
36
|
+
* the host page's DOM can read it and act as that person against the Appilot
|
|
37
|
+
* backend, so it must not live there.
|
|
38
|
+
*
|
|
39
|
+
* Two changes follow. The refresh no longer writes the rotated token back to
|
|
40
|
+
* the tag: `window.Appilot.setUserToken` already feeds the live instance, and a
|
|
41
|
+
* later re-boot re-relays a fresh token anyway, so the DOM write bought a
|
|
42
|
+
* hypothetical reboot path at the price of a token sitting in the page for the
|
|
43
|
+
* life of the tab, being renewed there every ~48 minutes.
|
|
44
|
+
*
|
|
45
|
+
* The boot token still passes through the attribute, because that is the only
|
|
46
|
+
* channel the shipped widget bundle reads its configuration from, and it is
|
|
47
|
+
* removed as soon as the bundle has consumed it (`releaseBootToken`). That
|
|
48
|
+
* shrinks the exposure from the lifetime of the page to the interval between
|
|
49
|
+
* appending the script and the bundle parsing its config. Closing it entirely
|
|
50
|
+
* needs the widget to accept a non-DOM handoff; until then, treat the boot
|
|
51
|
+
* token as short-lived and keep the relay's TTL short.
|
|
52
|
+
*/
|
|
53
|
+
export type WidgetAvailability = 'unknown' | 'booting' | 'ready' | 'unavailable';
|
|
54
|
+
export interface AppilotWidgetBootOptions {
|
|
55
|
+
/** Widget bundle URL. Missing = no assistant. */
|
|
56
|
+
widgetScriptUrl: string | undefined;
|
|
57
|
+
/** Publishable widget key. */
|
|
58
|
+
widgetKey?: string;
|
|
59
|
+
/** Appilot backend base URL. */
|
|
60
|
+
appilotApiUrl?: string;
|
|
61
|
+
/** Identity relay endpoint on the host app's own backend. */
|
|
62
|
+
tokenEndpoint?: string;
|
|
63
|
+
/** JSON body for the relay, e.g. { persona: 'learner' }. Omitted = relay default. */
|
|
64
|
+
tokenBody?: Record<string, unknown>;
|
|
65
|
+
/**
|
|
66
|
+
* Supplies the host session bearer attached to the relay call so the relay
|
|
67
|
+
* can federate the LOGGED-IN user instead of a default identity. Return null
|
|
68
|
+
* when no user session exists. Host-specific; keeps this loader app-agnostic.
|
|
69
|
+
*/
|
|
70
|
+
getBearer?: () => string | null | undefined;
|
|
71
|
+
/** How the app names its assistant in console diagnostics ('assistant', 'Appilot'). */
|
|
72
|
+
surfaceLabel?: string;
|
|
73
|
+
/**
|
|
74
|
+
* Header brand text and assistant label inside the panel (`data-brand-name`).
|
|
75
|
+
* Omitted, the widget falls back to the organization name from `/widget/init`,
|
|
76
|
+
* which white-labels the panel with the HOST's brand. An app that wants the
|
|
77
|
+
* assistant to carry its own name passes it here.
|
|
78
|
+
*/
|
|
79
|
+
brandName?: string;
|
|
80
|
+
/** Maximum time for token relay + bundle readiness. Defaults to 10 seconds. */
|
|
81
|
+
timeoutMs?: number;
|
|
82
|
+
position?: 'bottom-right' | 'bottom-left';
|
|
83
|
+
theme?: string;
|
|
84
|
+
language?: string;
|
|
85
|
+
}
|
|
86
|
+
export declare function getWidgetAvailability(): WidgetAvailability;
|
|
87
|
+
/** Subscribe to availability changes. Returns the unsubscribe function. */
|
|
88
|
+
export declare function subscribeWidgetAvailability(listener: (state: WidgetAvailability) => void): () => void;
|
|
89
|
+
export declare function bootAppilotWidget(options: AppilotWidgetBootOptions): Promise<void>;
|
|
90
|
+
/** Test-only: reset module state between tests. Never call from app code. */
|
|
91
|
+
export declare function __resetAppilotWidgetForTests(): void;
|