@fiodos/web-core 0.1.12 → 0.1.15

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.
Files changed (44) hide show
  1. package/dist/cjs/adapters/webCredentialAutofillAdapter.d.ts +16 -0
  2. package/dist/cjs/adapters/webCredentialAutofillAdapter.js +47 -0
  3. package/dist/cjs/api/backendClient.d.ts +10 -3
  4. package/dist/cjs/api/backendClient.js +31 -9
  5. package/dist/cjs/config/types.d.ts +22 -2
  6. package/dist/cjs/controller/AgentController.d.ts +14 -0
  7. package/dist/cjs/controller/AgentController.js +65 -2
  8. package/dist/cjs/dropin/createFiodosAgent.d.ts +27 -1
  9. package/dist/cjs/dropin/createFiodosAgent.js +48 -4
  10. package/dist/cjs/embed/dynamics.d.ts +61 -0
  11. package/dist/cjs/embed/dynamics.js +173 -0
  12. package/dist/cjs/embed/global.d.ts +45 -0
  13. package/dist/cjs/embed/global.js +48 -0
  14. package/dist/cjs/index.d.ts +3 -0
  15. package/dist/cjs/index.js +8 -1
  16. package/dist/cjs/orb/mountOrb.js +172 -28
  17. package/dist/cjs/orb/orbView.js +42 -8
  18. package/dist/cjs/orb/publishedConfig.d.ts +4 -2
  19. package/dist/cjs/orb/publishedConfig.js +56 -7
  20. package/dist/cjs/version.d.ts +1 -1
  21. package/dist/cjs/version.js +1 -1
  22. package/dist/embed/fiodos-embed.js +66 -0
  23. package/dist/esm/adapters/webCredentialAutofillAdapter.d.ts +16 -0
  24. package/dist/esm/adapters/webCredentialAutofillAdapter.js +44 -0
  25. package/dist/esm/api/backendClient.d.ts +10 -3
  26. package/dist/esm/api/backendClient.js +32 -10
  27. package/dist/esm/config/types.d.ts +22 -2
  28. package/dist/esm/controller/AgentController.d.ts +14 -0
  29. package/dist/esm/controller/AgentController.js +65 -2
  30. package/dist/esm/dropin/createFiodosAgent.d.ts +27 -1
  31. package/dist/esm/dropin/createFiodosAgent.js +48 -4
  32. package/dist/esm/embed/dynamics.d.ts +61 -0
  33. package/dist/esm/embed/dynamics.js +168 -0
  34. package/dist/esm/embed/global.d.ts +45 -0
  35. package/dist/esm/embed/global.js +46 -0
  36. package/dist/esm/index.d.ts +3 -0
  37. package/dist/esm/index.js +3 -0
  38. package/dist/esm/orb/mountOrb.js +173 -29
  39. package/dist/esm/orb/orbView.js +43 -9
  40. package/dist/esm/orb/publishedConfig.d.ts +4 -2
  41. package/dist/esm/orb/publishedConfig.js +56 -7
  42. package/dist/esm/version.d.ts +1 -1
  43. package/dist/esm/version.js +1 -1
  44. package/package.json +5 -3
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Web implementation of @fiodos/core's CredentialAutofillAdapter, on top of
3
+ * the Credential Management API (`navigator.credentials.get({ password: true,
4
+ * mediation: 'required' })`). The BROWSER owns the account-picker / user
5
+ * verification UI; this module only receives the outcome:
6
+ *
7
+ * - A `PasswordCredential` → 'success' with the saved username + password.
8
+ * - `null` (user dismissed the picker, or no saved credential and the browser
9
+ * chose not to prompt) → 'dismissed' (graceful fallback, never an error).
10
+ * - No API / SecurityError (insecure context, iframe policy) → 'unavailable'.
11
+ *
12
+ * The credentials never touch storage, logs or the network from here: they go
13
+ * straight into the app's own login handler parameters, one call, then gone.
14
+ */
15
+ import type { CredentialAutofillAdapter } from '@fiodos/core';
16
+ export declare function createWebCredentialAutofillAdapter(): CredentialAutofillAdapter;
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createWebCredentialAutofillAdapter = createWebCredentialAutofillAdapter;
4
+ function credentialsContainer() {
5
+ if (typeof navigator === 'undefined')
6
+ return null;
7
+ const nav = navigator;
8
+ return nav.credentials && typeof nav.credentials.get === 'function' ? nav.credentials : null;
9
+ }
10
+ function passwordCredentialSupported() {
11
+ if (typeof window === 'undefined')
12
+ return false;
13
+ const w = window;
14
+ // The Credential Management password store needs both the constructor and a
15
+ // secure context; feature-detect instead of sniffing the browser.
16
+ return typeof w.PasswordCredential === 'function' && w.isSecureContext !== false;
17
+ }
18
+ function createWebCredentialAutofillAdapter() {
19
+ return {
20
+ isSupported() {
21
+ return credentialsContainer() !== null && passwordCredentialSupported();
22
+ },
23
+ async requestCredentials() {
24
+ const container = credentialsContainer();
25
+ if (!container || !passwordCredentialSupported())
26
+ return { status: 'unavailable' };
27
+ let raw;
28
+ try {
29
+ // mediation 'required': ALWAYS show the browser's account picker — the
30
+ // user explicitly asked to sign in, so a visible, cancellable choice is
31
+ // the honest UX (and the only one that lets them pick among accounts).
32
+ raw = await container.get({ password: true, mediation: 'required' });
33
+ }
34
+ catch {
35
+ // SecurityError (permissions policy / insecure frame), NotSupportedError…
36
+ return { status: 'unavailable' };
37
+ }
38
+ const cred = raw;
39
+ if (!cred)
40
+ return { status: 'dismissed' };
41
+ if (cred.type === 'password' && cred.id && typeof cred.password === 'string' && cred.password) {
42
+ return { status: 'success', username: cred.id, password: cred.password };
43
+ }
44
+ return { status: 'unavailable' };
45
+ },
46
+ };
47
+ }
@@ -6,7 +6,7 @@
6
6
  * way. The only platform dependency is `fetch` + `AbortController` (native in
7
7
  * the browser). No client source code is ever sent — only the manifest payload.
8
8
  */
9
- import { type AgentBackendClient, type AppManifest } from '@fiodos/core';
9
+ import { type ActionRegistries, type AgentBackendClient, type AppManifest } from '@fiodos/core';
10
10
  export interface FiodosBackendClientOptions {
11
11
  baseUrl: string;
12
12
  /** Client API key sent as x-api-key. */
@@ -19,8 +19,15 @@ export interface FiodosBackendClientOptions {
19
19
  ttsTimeoutMs?: number;
20
20
  }
21
21
  export declare function createFiodosBackendClient(options: FiodosBackendClientOptions): AgentBackendClient;
22
- /** Serializes the manifest payloads for a turn request (used by AgentController). */
23
- export declare function buildManifestPayload(manifest: AppManifest): {
22
+ /**
23
+ * Serializes the manifest payloads for a turn request (used by AgentController).
24
+ *
25
+ * When the live registries are provided, actions with no registered handler
26
+ * are dropped from the payload (runtime "wired or nonexistent"): the LLM never
27
+ * hears about an action this mounted app cannot execute, so it can never
28
+ * promise it. Fail-open without registries.
29
+ */
30
+ export declare function buildManifestPayload(manifest: AppManifest, registries?: ActionRegistries | null): {
24
31
  appFlow?: string | undefined;
25
32
  appType?: string | undefined;
26
33
  manifestVersion: string;
@@ -119,6 +119,20 @@ function createFiodosBackendClient(options) {
119
119
  if (request.sessionId?.trim()) {
120
120
  body.session_id = request.sessionId.trim();
121
121
  }
122
+ // Device capability flags (retrocompatible: old backends ignore them).
123
+ if (request.platformCapabilities?.credentialAutofill) {
124
+ body.platform_capabilities = { credential_autofill: true };
125
+ }
126
+ // Live session verdict for audience filtering (retrocompatible: absent
127
+ // when the host wired no session source; old backends ignore it).
128
+ if (request.sessionContext) {
129
+ body.session_context = {
130
+ authenticated: request.sessionContext.authenticated,
131
+ ...(request.sessionContext.capabilities
132
+ ? { capabilities: request.sessionContext.capabilities }
133
+ : {}),
134
+ };
135
+ }
122
136
  // Autonomous-chain continuation metadata (backward compatible: absent on a
123
137
  // normal turn; an old backend simply ignores these fields).
124
138
  if (request.isContinuation) {
@@ -188,15 +202,23 @@ function createFiodosBackendClient(options) {
188
202
  },
189
203
  };
190
204
  }
191
- /** Serializes the manifest payloads for a turn request (used by AgentController). */
192
- function buildManifestPayload(manifest) {
205
+ /**
206
+ * Serializes the manifest payloads for a turn request (used by AgentController).
207
+ *
208
+ * When the live registries are provided, actions with no registered handler
209
+ * are dropped from the payload (runtime "wired or nonexistent"): the LLM never
210
+ * hears about an action this mounted app cannot execute, so it can never
211
+ * promise it. Fail-open without registries.
212
+ */
213
+ function buildManifestPayload(manifest, registries) {
214
+ const effective = (0, core_1.withExecutableActions)(manifest, registries);
193
215
  return {
194
- manifestVersion: manifest.version,
195
- manifestRoutes: (0, core_1.manifestRoutesForBackend)(manifest),
196
- manifestActions: (0, core_1.manifestActionsForBackend)(manifest),
197
- appName: manifest.appName,
198
- appDescription: manifest.appDescription,
199
- ...(manifest.appType ? { appType: manifest.appType } : {}),
200
- ...(manifest.appFlow ? { appFlow: manifest.appFlow } : {}),
216
+ manifestVersion: effective.version,
217
+ manifestRoutes: (0, core_1.manifestRoutesForBackend)(effective),
218
+ manifestActions: (0, core_1.manifestActionsForBackend)(effective),
219
+ appName: effective.appName,
220
+ appDescription: effective.appDescription,
221
+ ...(effective.appType ? { appType: effective.appType } : {}),
222
+ ...(effective.appFlow ? { appFlow: effective.appFlow } : {}),
201
223
  };
202
224
  }
@@ -3,7 +3,7 @@
3
3
  * Mirrors @fiodos/react's FiodosWebAgentConfig (same adapters, same lifecycle).
4
4
  * Shared by every framework binding through the AgentController.
5
5
  */
6
- import type { ActionRegistries, AgentBackendClient, AgentMessages, AppManifest, ConfirmationLexicon, MessageCatalog, NavigationAdapter, Sanitizer, StorageAdapter, TelemetryAdapter, VoiceAdapter } from '@fiodos/core';
6
+ import type { ActionRegistries, AgentBackendClient, AgentMessages, AppManifest, ConfirmationLexicon, CredentialAutofillAdapter, MessageCatalog, NavigationAdapter, Sanitizer, StorageAdapter, TelemetryAdapter, VoiceAdapter } from '@fiodos/core';
7
7
  import type { ResolveUiMessagesOptions, WebUiMessages } from '../ui/messages';
8
8
  export type AgentPhase = 'idle' | 'listening' | 'thinking' | 'speaking' | 'confirming';
9
9
  export interface AgentTimings {
@@ -44,6 +44,13 @@ export interface FiodosWebAgentConfig {
44
44
  manifest: AppManifest;
45
45
  /** Agent locale (reply language, message catalogs). MANDATORY. */
46
46
  locale: string;
47
+ /**
48
+ * LIVE locale resolver. When set, the orb re-reads it periodically so the
49
+ * thought bubble follows the app's ACTIVE language (host language switcher,
50
+ * `<html lang>`, i18n runtime) without remounting. Falls back to `locale`
51
+ * whenever it returns nothing.
52
+ */
53
+ getLocale?: () => string | null | undefined;
47
54
  /** Speech recognition locale, e.g. 'en-US', 'es-ES'. MANDATORY. */
48
55
  sttLocale: string;
49
56
  /** Device-TTS fallback locale. Defaults to sttLocale. */
@@ -55,7 +62,20 @@ export interface FiodosWebAgentConfig {
55
62
  storage: StorageAdapter;
56
63
  telemetry?: TelemetryAdapter;
57
64
  /** Host auth check, required by manifest actions declaring requiresAuth. */
58
- isAuthenticated?: () => boolean;
65
+ isAuthenticated?: () => boolean | null | undefined;
66
+ /**
67
+ * Fine-grained session capability flags from the wired session bridge
68
+ * (audience map: e.g. { has_payment_method: true }). Sent with each turn so
69
+ * the backend shows this user only THEIR face of the orb. Boolean flags
70
+ * only — never user data. null/undefined = unknown (nothing is filtered).
71
+ */
72
+ getSessionCapabilities?: () => Record<string, boolean> | null | undefined;
73
+ /**
74
+ * Platform saved-credential service powering the built-in SMART SIGN-IN
75
+ * flow (browser password manager). Defaults to the web Credential
76
+ * Management adapter; pass `null` to disable the feature entirely.
77
+ */
78
+ credentialAutofill?: CredentialAutofillAdapter | null;
59
79
  /**
60
80
  * Host end-user identity. Conversation memory is namespaced per identity
61
81
  * (hashed LOCALLY, never sent by this module): logout/account switches swap
@@ -74,6 +74,7 @@ export declare class AgentController {
74
74
  private readonly telemetry;
75
75
  private readonly consent;
76
76
  private readonly executor;
77
+ private readonly credentialAutofill;
77
78
  private readonly timings;
78
79
  private readonly chaining;
79
80
  private readonly ttsLocale;
@@ -191,6 +192,19 @@ export declare class AgentController {
191
192
  confirmPendingAction(): Promise<void>;
192
193
  cancelPendingAction(): void;
193
194
  private handleConfirmationTranscript;
195
+ /**
196
+ * Device capability flags advertised on each turn (never user data). Fresh
197
+ * per turn: cheap, and honest if the environment changes mid-session.
198
+ */
199
+ private platformCapabilities;
200
+ /**
201
+ * Live session verdict advertised on each turn (audience map — never user
202
+ * data): whether the user is signed in plus the app's boolean capability
203
+ * flags. Fresh per turn so login/logout mid-session is honest. Undefined
204
+ * when the host wired neither source (older installs — backend filters
205
+ * nothing, exactly today's behavior).
206
+ */
207
+ private sessionContext;
194
208
  private routeLabel;
195
209
  private actionLabel;
196
210
  /**
@@ -19,6 +19,7 @@ exports.AgentController = void 0;
19
19
  * - The agent only ever executes actions declared in the manifest.
20
20
  */
21
21
  const core_1 = require("@fiodos/core");
22
+ const webCredentialAutofillAdapter_1 = require("../adapters/webCredentialAutofillAdapter");
22
23
  const errors_1 = require("../api/errors");
23
24
  const formatTurnError_1 = require("../api/formatTurnError");
24
25
  const backendClient_1 = require("../api/backendClient");
@@ -94,12 +95,19 @@ class AgentController {
94
95
  appId: config.manifest.appId,
95
96
  version: config.consentVersion,
96
97
  });
98
+ // SMART SIGN-IN ships by default on web (browser password manager);
99
+ // hosts can override the adapter or pass null to opt out entirely.
100
+ this.credentialAutofill =
101
+ config.credentialAutofill === undefined
102
+ ? (0, webCredentialAutofillAdapter_1.createWebCredentialAutofillAdapter)()
103
+ : config.credentialAutofill;
97
104
  this.executor = (0, core_1.createActionExecutor)({
98
105
  manifest: config.manifest,
99
106
  registries: config.registries,
100
107
  navigation: config.navigation,
101
108
  messages: this.messages,
102
109
  isAuthenticated: config.isAuthenticated,
110
+ credentialAutofill: this.credentialAutofill,
103
111
  });
104
112
  this.timings = { ...types_1.DEFAULT_AGENT_TIMINGS, ...config.timings };
105
113
  // Chaining OFF by default; clamp to the same safe bounds as react/react-native.
@@ -584,6 +592,57 @@ class AgentController {
584
592
  this.beginConfirmationVoiceWindow();
585
593
  }
586
594
  }
595
+ /**
596
+ * Device capability flags advertised on each turn (never user data). Fresh
597
+ * per turn: cheap, and honest if the environment changes mid-session.
598
+ */
599
+ platformCapabilities() {
600
+ let autofill = false;
601
+ try {
602
+ autofill = this.credentialAutofill?.isSupported() === true;
603
+ }
604
+ catch {
605
+ autofill = false;
606
+ }
607
+ return autofill ? { credentialAutofill: true } : undefined;
608
+ }
609
+ /**
610
+ * Live session verdict advertised on each turn (audience map — never user
611
+ * data): whether the user is signed in plus the app's boolean capability
612
+ * flags. Fresh per turn so login/logout mid-session is honest. Undefined
613
+ * when the host wired neither source (older installs — backend filters
614
+ * nothing, exactly today's behavior).
615
+ */
616
+ sessionContext() {
617
+ const { isAuthenticated, getSessionCapabilities } = this.config;
618
+ if (!isAuthenticated && !getSessionCapabilities)
619
+ return undefined;
620
+ let authenticated = null;
621
+ try {
622
+ const v = isAuthenticated?.();
623
+ authenticated = v == null ? null : Boolean(v);
624
+ }
625
+ catch {
626
+ authenticated = null;
627
+ }
628
+ let capabilities;
629
+ try {
630
+ const raw = getSessionCapabilities?.();
631
+ if (raw && typeof raw === 'object') {
632
+ const clean = {};
633
+ for (const [k, v] of Object.entries(raw)) {
634
+ if (typeof v === 'boolean')
635
+ clean[k] = v;
636
+ }
637
+ if (Object.keys(clean).length > 0)
638
+ capabilities = clean;
639
+ }
640
+ }
641
+ catch {
642
+ capabilities = undefined;
643
+ }
644
+ return { authenticated, ...(capabilities ? { capabilities } : {}) };
645
+ }
587
646
  routeLabel(intent) {
588
647
  return this.config.manifest.routes.find((r) => r.intent === intent)?.label;
589
648
  }
@@ -769,7 +828,9 @@ class AgentController {
769
828
  isContinuation: true,
770
829
  chainStep: step,
771
830
  lastStep,
772
- ...(0, backendClient_1.buildManifestPayload)(this.config.manifest),
831
+ platformCapabilities: this.platformCapabilities(),
832
+ sessionContext: this.sessionContext(),
833
+ ...(0, backendClient_1.buildManifestPayload)(this.config.manifest, this.config.registries),
773
834
  }, { signal: controller.signal });
774
835
  oc = await this.applyOutcome(turn, routeNow, snap?.context ?? {}, state.userMessage);
775
836
  }
@@ -926,7 +987,9 @@ class AgentController {
926
987
  conversationHistory: conv.history,
927
988
  conversationSummary: conv.summary ?? undefined,
928
989
  sessionId: this.sessionId,
929
- ...(0, backendClient_1.buildManifestPayload)(this.config.manifest),
990
+ platformCapabilities: this.platformCapabilities(),
991
+ sessionContext: this.sessionContext(),
992
+ ...(0, backendClient_1.buildManifestPayload)(this.config.manifest, this.config.registries),
930
993
  }, { signal: controller.signal });
931
994
  outcome = await this.applyOutcome(turn, currentRoute, snapshot?.context ?? {}, text);
932
995
  }
@@ -13,6 +13,7 @@
13
13
  */
14
14
  import { type ActionRegistries, type AppManifest } from '@fiodos/core';
15
15
  import { AgentController } from '../controller/AgentController';
16
+ import type { AgentScreenSnapshot } from '../context/screenContextStore';
16
17
  import { type MountOrbOptions, type MountedOrb } from '../orb/mountOrb';
17
18
  import type { FiodosWebAgentConfig } from '../config/types';
18
19
  export interface CreateFiodosAgentOptions {
@@ -37,8 +38,22 @@ export interface CreateFiodosAgentOptions {
37
38
  sttLocale?: string;
38
39
  /** Host router navigate delegate. Defaults to location.assign. */
39
40
  navigate?: (route: string) => void;
41
+ /**
42
+ * Current route/screen resolver for the agent's screen awareness. Defaults
43
+ * to location.pathname + search. Apps whose screens are CLIENT STATE (no URL
44
+ * change) pass the generated `fyodosGetCurrentRoute` here so the orb knows
45
+ * which screen the user is on (see screen-truth wiring).
46
+ */
47
+ getCurrentRoute?: () => string | null;
40
48
  /** Host auth check for actions requiring auth. */
41
- isAuthenticated?: () => boolean;
49
+ isAuthenticated?: () => boolean | null | undefined;
50
+ /**
51
+ * Fine-grained session capability flags (audience map, e.g.
52
+ * { has_payment_method: true }) from the generated session wiring. Boolean
53
+ * flags only; sent with each turn so the orb shows this user only THEIR
54
+ * available actions.
55
+ */
56
+ getSessionCapabilities?: () => Record<string, boolean> | null | undefined;
42
57
  /** Voice id forwarded to the backend TTS. */
43
58
  ttsVoice?: string | (() => string | undefined);
44
59
  /** End-user id for rate limiting / telemetry correlation. */
@@ -64,6 +79,17 @@ export interface FiodosAgentHandle {
64
79
  * Call it from the app's logout for a hard wipe; no-op until ready.
65
80
  */
66
81
  resetConversation(): void;
82
+ /**
83
+ * Publishes WHAT THE USER IS SEEING so 'execute' actions can target it —
84
+ * the embed-host equivalent of the React `useAgentScreenContext` hook.
85
+ * Hosts without our SDK on screens (Dynamics web resources, WinCC CWCs)
86
+ * call this whenever their form/record/view changes. On the auto-fetch
87
+ * path, calls made before the controller is ready are kept and applied on
88
+ * ready, so hosts don't need to await `ready` themselves.
89
+ */
90
+ setScreenContext(snapshot: AgentScreenSnapshot): void;
91
+ /** Clears a previously published snapshot (e.g. the record was closed). */
92
+ clearScreenContext(): void;
67
93
  destroy(): void;
68
94
  }
69
95
  export declare function createFiodosAgent(options: CreateFiodosAgentOptions): FiodosAgentHandle;
@@ -32,6 +32,11 @@ function browserLocale() {
32
32
  const lang = typeof navigator !== 'undefined' && navigator.language ? navigator.language : 'en-US';
33
33
  return { locale: lang.split('-')[0] || 'en', sttLocale: lang };
34
34
  }
35
+ function documentLocale() {
36
+ return typeof document !== 'undefined'
37
+ ? document.documentElement.lang?.split(/[-_]/)[0]?.trim() || ''
38
+ : '';
39
+ }
35
40
  function resolveAgentLocale(options) {
36
41
  const fromApp = options.getLocale?.()?.trim();
37
42
  if (fromApp)
@@ -41,12 +46,16 @@ function resolveAgentLocale(options) {
41
46
  if (options.localeDetection === 'browser')
42
47
  return browserLocale().locale;
43
48
  if (options.localeDetection === 'document') {
44
- const docLang = typeof document !== 'undefined'
45
- ? document.documentElement.lang?.split(/[-_]/)[0]?.trim()
46
- : '';
49
+ const docLang = documentLocale();
47
50
  if (docLang)
48
51
  return docLang;
49
52
  }
53
+ // Default (nothing configured): follow the HOST PAGE's language when the
54
+ // page declares one (<html lang>), so the orb bubble speaks the language the
55
+ // end user is actually browsing in. English only as the last resort.
56
+ const docLang = documentLocale();
57
+ if (docLang)
58
+ return docLang;
50
59
  return 'en';
51
60
  }
52
61
  /** Build the controller + adapters from a known manifest (shared by both paths). */
@@ -67,14 +76,22 @@ function assemble(options, manifest, baseUrl) {
67
76
  const config = {
68
77
  manifest,
69
78
  locale,
79
+ // Live resolver: re-runs the same resolution chain (app getLocale → static
80
+ // locale → detection) on every read, so the bubble follows the host app's
81
+ // language switcher / <html lang> without remounting the agent.
82
+ getLocale: () => resolveAgentLocale(options),
70
83
  sttLocale,
71
- navigation: (0, webNavigationAdapter_1.createWebNavigationAdapter)({ navigate: options.navigate ?? defaultNavigate }),
84
+ navigation: (0, webNavigationAdapter_1.createWebNavigationAdapter)({
85
+ navigate: options.navigate ?? defaultNavigate,
86
+ getCurrentRoute: options.getCurrentRoute,
87
+ }),
72
88
  registries: options.registries ?? { handlers: {}, idempotencyCheckers: {} },
73
89
  backend,
74
90
  voice: (0, webVoiceAdapter_1.createWebVoiceAdapter)(),
75
91
  storage: (0, webStorageAdapter_1.createWebStorageAdapter)(),
76
92
  telemetry,
77
93
  isAuthenticated: options.isAuthenticated,
94
+ getSessionCapabilities: options.getSessionCapabilities,
78
95
  getUserId: options.getUserId,
79
96
  ttsVoice: options.ttsVoice,
80
97
  ...options.configOverrides,
@@ -86,6 +103,9 @@ function assemble(options, manifest, baseUrl) {
86
103
  function createFiodosAgent(options) {
87
104
  const baseUrl = (options.baseUrl ?? clientBootstrap_1.DEFAULT_WEB_API_URL).replace(/\/$/, '');
88
105
  const shouldMount = options.mount !== false;
106
+ // One stable owner id per handle: the host is "one screen" from the store's
107
+ // point of view, so consecutive setScreenContext calls replace each other.
108
+ const contextOwner = Symbol('fiodos-embed-host');
89
109
  // ── Manual path: manifest provided → fully synchronous (back-compat). ───────
90
110
  if (options.manifest) {
91
111
  const controller = assemble(options, options.manifest, baseUrl);
@@ -101,6 +121,12 @@ function createFiodosAgent(options) {
101
121
  resetConversation() {
102
122
  controller.resetConversation();
103
123
  },
124
+ setScreenContext(snapshot) {
125
+ controller.screenStore.set(contextOwner, snapshot);
126
+ },
127
+ clearScreenContext() {
128
+ controller.screenStore.clear(contextOwner);
129
+ },
104
130
  destroy() {
105
131
  orb?.unmount();
106
132
  controller.dispose();
@@ -109,6 +135,9 @@ function createFiodosAgent(options) {
109
135
  return handle;
110
136
  }
111
137
  // ── Auto path (React parity): fetch manifest by apiKey, then assemble. ──────
138
+ // Context published before the controller resolves is buffered (last one
139
+ // wins) and applied on ready — hosts fire form events without awaiting us.
140
+ let pendingSnapshot;
112
141
  const handle = {
113
142
  controller: null,
114
143
  orb: null,
@@ -116,6 +145,18 @@ function createFiodosAgent(options) {
116
145
  resetConversation() {
117
146
  handle.controller?.resetConversation();
118
147
  },
148
+ setScreenContext(snapshot) {
149
+ if (handle.controller)
150
+ handle.controller.screenStore.set(contextOwner, snapshot);
151
+ else
152
+ pendingSnapshot = snapshot;
153
+ },
154
+ clearScreenContext() {
155
+ if (handle.controller)
156
+ handle.controller.screenStore.clear(contextOwner);
157
+ else
158
+ pendingSnapshot = null;
159
+ },
119
160
  destroy() {
120
161
  cancelled = true;
121
162
  handle.orb?.unmount();
@@ -151,6 +192,9 @@ function createFiodosAgent(options) {
151
192
  return null;
152
193
  }
153
194
  handle.controller = controller;
195
+ if (pendingSnapshot)
196
+ controller.screenStore.set(contextOwner, pendingSnapshot);
197
+ pendingSnapshot = undefined;
154
198
  handle.orb = shouldMount ? (0, mountOrb_1.mountOrb)(controller, options.orb) : null;
155
199
  if (shouldMount && options.apiKey) {
156
200
  void (0, clientBootstrap_1.sendOrbSeen)({ baseUrl, apiKey: options.apiKey });
@@ -0,0 +1,61 @@
1
+ import type { AgentScreenSnapshot } from '../context/screenContextStore';
2
+ interface XrmAttributeLike {
3
+ getName(): string;
4
+ getValue(): unknown;
5
+ /** Present on optionset/lookup attributes; preferred over raw values. */
6
+ getText?(): string | string[] | null;
7
+ }
8
+ interface XrmEntityLike {
9
+ getEntityName(): string;
10
+ /** Record GUID, usually brace-wrapped: "{1D2C...}". Empty on create forms. */
11
+ getId(): string;
12
+ getPrimaryAttributeValue?(): string | null;
13
+ attributes?: {
14
+ forEach(cb: (attribute: XrmAttributeLike) => void): void;
15
+ };
16
+ /** Model-driven apps expose save events here (Xrm ≥ 9.x). */
17
+ addOnPostSave?(handler: () => void): void;
18
+ removeOnPostSave?(handler: () => void): void;
19
+ }
20
+ export interface DynamicsFormContextLike {
21
+ data: {
22
+ entity: XrmEntityLike;
23
+ };
24
+ ui?: {
25
+ getFormType?(): number;
26
+ };
27
+ }
28
+ /** The subset of FiodosAgentHandle the bridge needs (keeps this module cheap to test). */
29
+ export interface ScreenContextSink {
30
+ setScreenContext(snapshot: AgentScreenSnapshot): void;
31
+ clearScreenContext(): void;
32
+ }
33
+ export interface DynamicsSnapshotOptions {
34
+ /**
35
+ * Logical names of the attributes to expose to the agent. When omitted, the
36
+ * first `maxAttributes` non-empty scalar attributes are included.
37
+ */
38
+ attributes?: string[];
39
+ /** Cap when no allowlist is given. Default 12. */
40
+ maxAttributes?: number;
41
+ }
42
+ /** "{1D2C…}" → "1d2c…" (Web API URLs want bare lowercase GUIDs). */
43
+ export declare function normalizeDynamicsId(rawId: string): string;
44
+ /**
45
+ * Builds the AgentScreenSnapshot for the current form state, or null when
46
+ * there is nothing referenceable yet (create form: no record id).
47
+ */
48
+ export declare function dynamicsFormSnapshot(formContext: DynamicsFormContextLike, options?: DynamicsSnapshotOptions): AgentScreenSnapshot | null;
49
+ export interface DynamicsContextBridge {
50
+ /** Re-reads the form and republishes the snapshot (wire to OnChange/OnSave). */
51
+ refresh(): void;
52
+ /** Stops publishing and clears the agent's screen context. */
53
+ disconnect(): void;
54
+ }
55
+ /**
56
+ * Publishes the form snapshot now, republishes after every save (when the
57
+ * form exposes OnPostSave), and clears the context on disconnect. Call it from
58
+ * the form's OnLoad handler.
59
+ */
60
+ export declare function connectDynamicsFormContext(agent: ScreenContextSink, formContext: DynamicsFormContextLike, options?: DynamicsSnapshotOptions): DynamicsContextBridge;
61
+ export {};