@fiodos/web-core 0.1.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.
Files changed (84) hide show
  1. package/LICENSE +77 -0
  2. package/dist/cjs/adapters/webNavigationAdapter.d.ts +31 -0
  3. package/dist/cjs/adapters/webNavigationAdapter.js +45 -0
  4. package/dist/cjs/adapters/webStorageAdapter.d.ts +13 -0
  5. package/dist/cjs/adapters/webStorageAdapter.js +57 -0
  6. package/dist/cjs/adapters/webVoiceAdapter.d.ts +16 -0
  7. package/dist/cjs/adapters/webVoiceAdapter.js +477 -0
  8. package/dist/cjs/api/backendClient.d.ts +31 -0
  9. package/dist/cjs/api/backendClient.js +182 -0
  10. package/dist/cjs/api/backendTelemetry.d.ts +23 -0
  11. package/dist/cjs/api/backendTelemetry.js +80 -0
  12. package/dist/cjs/api/clientBootstrap.d.ts +44 -0
  13. package/dist/cjs/api/clientBootstrap.js +68 -0
  14. package/dist/cjs/api/errors.d.ts +11 -0
  15. package/dist/cjs/api/errors.js +16 -0
  16. package/dist/cjs/bridge/createBridge.d.ts +13 -0
  17. package/dist/cjs/bridge/createBridge.js +15 -0
  18. package/dist/cjs/config/types.d.ts +63 -0
  19. package/dist/cjs/config/types.js +9 -0
  20. package/dist/cjs/context/screenContextStore.d.ts +26 -0
  21. package/dist/cjs/context/screenContextStore.js +25 -0
  22. package/dist/cjs/controller/AgentController.d.ts +115 -0
  23. package/dist/cjs/controller/AgentController.js +594 -0
  24. package/dist/cjs/core/turnEngine.d.ts +70 -0
  25. package/dist/cjs/core/turnEngine.js +96 -0
  26. package/dist/cjs/dropin/createFiodosAgent.d.ts +60 -0
  27. package/dist/cjs/dropin/createFiodosAgent.js +143 -0
  28. package/dist/cjs/dropin/createFyodosAgent.d.ts +60 -0
  29. package/dist/cjs/dropin/createFyodosAgent.js +143 -0
  30. package/dist/cjs/index.d.ts +45 -0
  31. package/dist/cjs/index.js +67 -0
  32. package/dist/cjs/orb/mountOrb.d.ts +24 -0
  33. package/dist/cjs/orb/mountOrb.js +694 -0
  34. package/dist/cjs/orb/orbView.d.ts +56 -0
  35. package/dist/cjs/orb/orbView.js +426 -0
  36. package/dist/cjs/orb/publishedConfig.d.ts +32 -0
  37. package/dist/cjs/orb/publishedConfig.js +70 -0
  38. package/dist/cjs/package.json +1 -0
  39. package/dist/cjs/speech/speechSession.d.ts +35 -0
  40. package/dist/cjs/speech/speechSession.js +60 -0
  41. package/dist/cjs/ui/messages.d.ts +36 -0
  42. package/dist/cjs/ui/messages.js +41 -0
  43. package/dist/esm/adapters/webNavigationAdapter.d.ts +31 -0
  44. package/dist/esm/adapters/webNavigationAdapter.js +41 -0
  45. package/dist/esm/adapters/webStorageAdapter.d.ts +13 -0
  46. package/dist/esm/adapters/webStorageAdapter.js +54 -0
  47. package/dist/esm/adapters/webVoiceAdapter.d.ts +16 -0
  48. package/dist/esm/adapters/webVoiceAdapter.js +474 -0
  49. package/dist/esm/api/backendClient.d.ts +31 -0
  50. package/dist/esm/api/backendClient.js +178 -0
  51. package/dist/esm/api/backendTelemetry.d.ts +23 -0
  52. package/dist/esm/api/backendTelemetry.js +77 -0
  53. package/dist/esm/api/clientBootstrap.d.ts +44 -0
  54. package/dist/esm/api/clientBootstrap.js +63 -0
  55. package/dist/esm/api/errors.d.ts +11 -0
  56. package/dist/esm/api/errors.js +11 -0
  57. package/dist/esm/bridge/createBridge.d.ts +13 -0
  58. package/dist/esm/bridge/createBridge.js +12 -0
  59. package/dist/esm/config/types.d.ts +63 -0
  60. package/dist/esm/config/types.js +6 -0
  61. package/dist/esm/context/screenContextStore.d.ts +26 -0
  62. package/dist/esm/context/screenContextStore.js +22 -0
  63. package/dist/esm/controller/AgentController.d.ts +115 -0
  64. package/dist/esm/controller/AgentController.js +590 -0
  65. package/dist/esm/core/turnEngine.d.ts +70 -0
  66. package/dist/esm/core/turnEngine.js +92 -0
  67. package/dist/esm/dropin/createFiodosAgent.d.ts +60 -0
  68. package/dist/esm/dropin/createFiodosAgent.js +140 -0
  69. package/dist/esm/dropin/createFyodosAgent.d.ts +60 -0
  70. package/dist/esm/dropin/createFyodosAgent.js +140 -0
  71. package/dist/esm/index.d.ts +45 -0
  72. package/dist/esm/index.js +38 -0
  73. package/dist/esm/orb/mountOrb.d.ts +24 -0
  74. package/dist/esm/orb/mountOrb.js +691 -0
  75. package/dist/esm/orb/orbView.d.ts +56 -0
  76. package/dist/esm/orb/orbView.js +421 -0
  77. package/dist/esm/orb/publishedConfig.d.ts +32 -0
  78. package/dist/esm/orb/publishedConfig.js +67 -0
  79. package/dist/esm/package.json +1 -0
  80. package/dist/esm/speech/speechSession.d.ts +35 -0
  81. package/dist/esm/speech/speechSession.js +57 -0
  82. package/dist/esm/ui/messages.d.ts +36 -0
  83. package/dist/esm/ui/messages.js +38 -0
  84. package/package.json +38 -0
@@ -0,0 +1,92 @@
1
+ /**
2
+ * Framework-free turn decision engine.
3
+ *
4
+ * Concentrates the SECURITY-CRITICAL decision logic of a turn so it can be
5
+ * unit-tested without any framework, and so every web binding (Vue, Svelte,
6
+ * Angular) and React Native share the exact same rules. It does NOT
7
+ * reimplement the engine: it delegates execution, prechecks and confirmation
8
+ * classification to @fiodos/core. It only sequences them.
9
+ *
10
+ * Invariants enforced here (identical to @fiodos/react-native):
11
+ * 1. The source of truth for "needs confirmation" is the MANIFEST
12
+ * (`requireConfirmation`), never the LLM's flag.
13
+ * 2. A sensitive action is NEVER executed by `decideAction`; it returns a
14
+ * PendingConfirmation and waits for an explicit confirmation.
15
+ * 3. Voice confirmation honours the manifest mode: 'disabled' can never be
16
+ * confirmed by voice (tap only); 'strict' requires the exact phrase; a
17
+ * loose "yes" does not confirm a strict action. Negation always wins.
18
+ */
19
+ import { classifyConfirmationReply, classifyStrictConfirmationReply, formatMessage, } from '@fiodos/core';
20
+ function findManifestAction(manifest, intent) {
21
+ if (!intent)
22
+ return undefined;
23
+ return manifest.actions.find((a) => a.intent === intent);
24
+ }
25
+ /**
26
+ * Decides what to do with a backend-proposed action. Sensitive actions are
27
+ * never executed here — they return `needs-confirmation`.
28
+ */
29
+ export async function decideAction(params) {
30
+ const { action, manifest, executor, context, messages, userMessage } = params;
31
+ if (!action || action.type === 'none') {
32
+ return { kind: 'noop' };
33
+ }
34
+ if (action.type === 'navigate') {
35
+ const result = await executor.execute(action);
36
+ return { kind: 'executed', result };
37
+ }
38
+ if (action.type === 'execute') {
39
+ const manifestAction = findManifestAction(manifest, action.intent);
40
+ const needsConfirmation = manifestAction?.requireConfirmation === true;
41
+ if (!needsConfirmation) {
42
+ const result = await executor.execute(action, context);
43
+ return { kind: 'executed', result };
44
+ }
45
+ // Precheck (context + idempotency) BEFORE asking: applying an already-applied
46
+ // state answers directly without bothering the user with a confirmation.
47
+ const pre = await executor.precheck(action, context);
48
+ if (pre.kind === 'done') {
49
+ return { kind: 'precheck-resolved', result: pre.result };
50
+ }
51
+ const voiceMode = manifestAction?.voiceConfirmation ?? 'standard';
52
+ const strictPhrase = voiceMode === 'strict'
53
+ ? (manifestAction?.confirmationPhrase ??
54
+ formatMessage(messages.strictConfirmationPhraseTemplate, {
55
+ label: (manifestAction?.label ?? action.intent ?? '').toLowerCase(),
56
+ }))
57
+ : null;
58
+ const pending = {
59
+ action,
60
+ context,
61
+ question: manifestAction?.confirmationMessageTemplate ??
62
+ action.confirmationMessage ??
63
+ messages.confirmationFallbackQuestion,
64
+ voiceMode,
65
+ strictPhrase,
66
+ remainingConfirmations: manifestAction?.doubleConfirmation === true ? 2 : 1,
67
+ userMessage,
68
+ intent: action.intent ?? '',
69
+ };
70
+ return { kind: 'needs-confirmation', pending };
71
+ }
72
+ return { kind: 'noop' };
73
+ }
74
+ /**
75
+ * Classifies a spoken reply against a pending confirmation, honouring the
76
+ * manifest's voice mode. Returns 'ambiguous' when voice cannot resolve it
77
+ * (disabled mode, silent turn, or unclear speech) — the caller must then rely
78
+ * on the tactile modal. NEVER returns 'affirmative' for a disabled/silent
79
+ * confirmation.
80
+ */
81
+ export function classifyPendingReply(text, pending, lexicon, opts = {}) {
82
+ // Safety net: a stray transcript can NEVER resolve a tap-only confirmation.
83
+ if (pending.voiceMode === 'disabled' || opts.silent)
84
+ return 'ambiguous';
85
+ const trimmed = text.trim();
86
+ if (trimmed.length < 2)
87
+ return 'ambiguous';
88
+ if (pending.voiceMode === 'strict' && pending.strictPhrase != null) {
89
+ return classifyStrictConfirmationReply(trimmed, pending.strictPhrase, lexicon);
90
+ }
91
+ return classifyConfirmationReply(trimmed, lexicon);
92
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Vanilla drop-in — assembles the default browser adapters, builds an
3
+ * AgentController and (optionally) mounts the DOM orb in one call. Frameworks
4
+ * use the AgentController + their own components; this is the zero-framework
5
+ * path (and the reference the Vue/Svelte/Angular drop-ins follow).
6
+ *
7
+ * PARITY WITH @fiodos/react's <FiodosAgent/>: when `manifest` is omitted, the
8
+ * agent self-bootstraps — it fetches the published manifest by `apiKey`
9
+ * (GET /v1/client/manifest), assembles default browser adapters, mounts the orb
10
+ * and posts the `orb-seen` heartbeat. So a one-line drop-in needs only an
11
+ * apiKey, exactly like React. Passing a manifest keeps the fully-synchronous,
12
+ * manual path.
13
+ */
14
+ import { type ActionRegistries, type AppManifest } from '@fiodos/core';
15
+ import { AgentController } from '../controller/AgentController.js';
16
+ import { type MountOrbOptions, type MountedOrb } from '../orb/mountOrb.js';
17
+ import type { FiodosWebAgentConfig } from '../config/types.js';
18
+ export interface CreateFiodosAgentOptions {
19
+ /** Backend base URL. Defaults to the public Fiodos backend. */
20
+ baseUrl?: string;
21
+ /** Client API key (x-api-key). Required when `manifest` is omitted (auto-fetch). */
22
+ apiKey?: string;
23
+ /**
24
+ * The validated app manifest. OPTIONAL: omit it to self-fetch the published
25
+ * manifest by apiKey (the React-parity drop-in path).
26
+ */
27
+ manifest?: AppManifest;
28
+ /** Action handlers / context validators (defaults to empty registries). */
29
+ registries?: ActionRegistries;
30
+ /** Agent reply locale (e.g. 'es', 'en'). Defaults to the browser language. */
31
+ locale?: string;
32
+ /** STT locale (e.g. 'es-ES', 'en-US'). Defaults to the browser language. */
33
+ sttLocale?: string;
34
+ /** Host router navigate delegate. Defaults to location.assign. */
35
+ navigate?: (route: string) => void;
36
+ /** Host auth check for actions requiring auth. */
37
+ isAuthenticated?: () => boolean;
38
+ /** Voice id forwarded to the backend TTS. */
39
+ ttsVoice?: string | (() => string | undefined);
40
+ /** End-user id for rate limiting / telemetry correlation. */
41
+ getUserId?: () => string | null;
42
+ /** Mount the built-in DOM orb. Default: true. Pass false to render your own. */
43
+ mount?: boolean;
44
+ orb?: MountOrbOptions;
45
+ /** Escape hatch: extra config merged into the controller config. */
46
+ configOverrides?: Partial<FiodosWebAgentConfig>;
47
+ /** Called once the manifest loaded and the orb mounted (auto-fetch path). */
48
+ onReady?: (controller: AgentController) => void;
49
+ /** Called when self-bootstrap fails (network / rejected key / no manifest). */
50
+ onError?: (error: Error) => void;
51
+ }
52
+ export interface FiodosAgentHandle {
53
+ /** Null until the async self-bootstrap resolves (auto-fetch path). */
54
+ controller: AgentController | null;
55
+ orb: MountedOrb | null;
56
+ /** Resolves once the controller is ready (immediately on the manual path). */
57
+ ready: Promise<AgentController | null>;
58
+ destroy(): void;
59
+ }
60
+ export declare function createFiodosAgent(options: CreateFiodosAgentOptions): FiodosAgentHandle;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Vanilla drop-in — assembles the default browser adapters, builds an
3
+ * AgentController and (optionally) mounts the DOM orb in one call. Frameworks
4
+ * use the AgentController + their own components; this is the zero-framework
5
+ * path (and the reference the Vue/Svelte/Angular drop-ins follow).
6
+ *
7
+ * PARITY WITH @fiodos/react's <FiodosAgent/>: when `manifest` is omitted, the
8
+ * agent self-bootstraps — it fetches the published manifest by `apiKey`
9
+ * (GET /v1/client/manifest), assembles default browser adapters, mounts the orb
10
+ * and posts the `orb-seen` heartbeat. So a one-line drop-in needs only an
11
+ * apiKey, exactly like React. Passing a manifest keeps the fully-synchronous,
12
+ * manual path.
13
+ */
14
+ import { validateManifest } from '@fiodos/core';
15
+ import { createFiodosBackendClient } from '../api/backendClient.js';
16
+ import { createFiodosTelemetry } from '../api/backendTelemetry.js';
17
+ import { createWebNavigationAdapter } from '../adapters/webNavigationAdapter.js';
18
+ import { createWebStorageAdapter } from '../adapters/webStorageAdapter.js';
19
+ import { createWebVoiceAdapter } from '../adapters/webVoiceAdapter.js';
20
+ import { AgentController } from '../controller/AgentController.js';
21
+ import { mountOrb } from '../orb/mountOrb.js';
22
+ import { DEFAULT_WEB_API_URL, fetchClientManifest, sendOrbSeen } from '../api/clientBootstrap.js';
23
+ function defaultNavigate(route) {
24
+ if (typeof window !== 'undefined' && window.location)
25
+ window.location.assign(route);
26
+ }
27
+ function browserLocale() {
28
+ const lang = typeof navigator !== 'undefined' && navigator.language ? navigator.language : 'en-US';
29
+ return { locale: lang.split('-')[0] || 'en', sttLocale: lang };
30
+ }
31
+ /** Build the controller + adapters from a known manifest (shared by both paths). */
32
+ function assemble(options, manifest, baseUrl) {
33
+ const fallback = browserLocale();
34
+ const locale = options.locale ?? fallback.locale;
35
+ const sttLocale = options.sttLocale ?? options.locale ?? fallback.sttLocale;
36
+ const backend = createFiodosBackendClient({
37
+ baseUrl,
38
+ apiKey: options.apiKey,
39
+ getUserId: options.getUserId,
40
+ });
41
+ const telemetry = createFiodosTelemetry({
42
+ baseUrl,
43
+ apiKey: options.apiKey,
44
+ getUserId: options.getUserId,
45
+ });
46
+ const config = {
47
+ manifest,
48
+ locale,
49
+ sttLocale,
50
+ navigation: createWebNavigationAdapter({ navigate: options.navigate ?? defaultNavigate }),
51
+ registries: options.registries ?? { handlers: {}, idempotencyCheckers: {} },
52
+ backend,
53
+ voice: createWebVoiceAdapter(),
54
+ storage: createWebStorageAdapter(),
55
+ telemetry,
56
+ isAuthenticated: options.isAuthenticated,
57
+ ttsVoice: options.ttsVoice,
58
+ ...options.configOverrides,
59
+ };
60
+ const controller = new AgentController(config);
61
+ controller.warmUp();
62
+ return controller;
63
+ }
64
+ export function createFiodosAgent(options) {
65
+ const baseUrl = (options.baseUrl ?? DEFAULT_WEB_API_URL).replace(/\/$/, '');
66
+ const shouldMount = options.mount !== false;
67
+ // ── Manual path: manifest provided → fully synchronous (back-compat). ───────
68
+ if (options.manifest) {
69
+ const controller = assemble(options, options.manifest, baseUrl);
70
+ const orb = shouldMount ? mountOrb(controller, options.orb) : null;
71
+ if (shouldMount && options.apiKey) {
72
+ void sendOrbSeen({ baseUrl, apiKey: options.apiKey });
73
+ }
74
+ options.onReady?.(controller);
75
+ const handle = {
76
+ controller,
77
+ orb,
78
+ ready: Promise.resolve(controller),
79
+ destroy() {
80
+ orb?.unmount();
81
+ controller.dispose();
82
+ },
83
+ };
84
+ return handle;
85
+ }
86
+ // ── Auto path (React parity): fetch manifest by apiKey, then assemble. ──────
87
+ const handle = {
88
+ controller: null,
89
+ orb: null,
90
+ ready: Promise.resolve(null),
91
+ destroy() {
92
+ cancelled = true;
93
+ handle.orb?.unmount();
94
+ handle.controller?.dispose();
95
+ },
96
+ };
97
+ let cancelled = false;
98
+ handle.ready = (async () => {
99
+ try {
100
+ if (!options.apiKey) {
101
+ throw new Error('[fyodos] createFiodosAgent requires either a `manifest` or an `apiKey` ' +
102
+ '(to self-fetch the published manifest).');
103
+ }
104
+ const result = await fetchClientManifest({ baseUrl, apiKey: options.apiKey });
105
+ if (cancelled)
106
+ return null;
107
+ if (result.status === 'no-manifest' || !result.manifest) {
108
+ // eslint-disable-next-line no-console
109
+ console.warn('[fyodos] The orb is idle: this project has no published manifest yet. ' +
110
+ 'Run the analysis from your Fiodos dashboard, then reload.');
111
+ return null;
112
+ }
113
+ // Fail loudly here (not deep inside the controller) so onError is precise.
114
+ const check = validateManifest(result.manifest);
115
+ if (!check.valid) {
116
+ throw new Error(`[fyodos] Invalid manifest for "${result.manifest.appId}":\n- ${check.errors.join('\n- ')}`);
117
+ }
118
+ const controller = assemble(options, result.manifest, baseUrl);
119
+ if (cancelled) {
120
+ controller.dispose();
121
+ return null;
122
+ }
123
+ handle.controller = controller;
124
+ handle.orb = shouldMount ? mountOrb(controller, options.orb) : null;
125
+ if (shouldMount && options.apiKey) {
126
+ void sendOrbSeen({ baseUrl, apiKey: options.apiKey });
127
+ }
128
+ options.onReady?.(controller);
129
+ return controller;
130
+ }
131
+ catch (e) {
132
+ const error = e instanceof Error ? e : new Error(String(e));
133
+ // eslint-disable-next-line no-console
134
+ console.error(error.message);
135
+ options.onError?.(error);
136
+ return null;
137
+ }
138
+ })();
139
+ return handle;
140
+ }
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Vanilla drop-in — assembles the default browser adapters, builds an
3
+ * AgentController and (optionally) mounts the DOM orb in one call. Frameworks
4
+ * use the AgentController + their own components; this is the zero-framework
5
+ * path (and the reference the Vue/Svelte/Angular drop-ins follow).
6
+ *
7
+ * PARITY WITH @fyodos/react's <FyodosAgent/>: when `manifest` is omitted, the
8
+ * agent self-bootstraps — it fetches the published manifest by `apiKey`
9
+ * (GET /v1/client/manifest), assembles default browser adapters, mounts the orb
10
+ * and posts the `orb-seen` heartbeat. So a one-line drop-in needs only an
11
+ * apiKey, exactly like React. Passing a manifest keeps the fully-synchronous,
12
+ * manual path.
13
+ */
14
+ import { type ActionRegistries, type AppManifest } from '@fyodos/core';
15
+ import { AgentController } from '../controller/AgentController.js';
16
+ import { type MountOrbOptions, type MountedOrb } from '../orb/mountOrb.js';
17
+ import type { FyodosWebAgentConfig } from '../config/types.js';
18
+ export interface CreateFyodosAgentOptions {
19
+ /** Backend base URL. Defaults to the public Fyodos backend. */
20
+ baseUrl?: string;
21
+ /** Client API key (x-api-key). Required when `manifest` is omitted (auto-fetch). */
22
+ apiKey?: string;
23
+ /**
24
+ * The validated app manifest. OPTIONAL: omit it to self-fetch the published
25
+ * manifest by apiKey (the React-parity drop-in path).
26
+ */
27
+ manifest?: AppManifest;
28
+ /** Action handlers / context validators (defaults to empty registries). */
29
+ registries?: ActionRegistries;
30
+ /** Agent reply locale (e.g. 'es', 'en'). Defaults to the browser language. */
31
+ locale?: string;
32
+ /** STT locale (e.g. 'es-ES', 'en-US'). Defaults to the browser language. */
33
+ sttLocale?: string;
34
+ /** Host router navigate delegate. Defaults to location.assign. */
35
+ navigate?: (route: string) => void;
36
+ /** Host auth check for actions requiring auth. */
37
+ isAuthenticated?: () => boolean;
38
+ /** Voice id forwarded to the backend TTS. */
39
+ ttsVoice?: string | (() => string | undefined);
40
+ /** End-user id for rate limiting / telemetry correlation. */
41
+ getUserId?: () => string | null;
42
+ /** Mount the built-in DOM orb. Default: true. Pass false to render your own. */
43
+ mount?: boolean;
44
+ orb?: MountOrbOptions;
45
+ /** Escape hatch: extra config merged into the controller config. */
46
+ configOverrides?: Partial<FyodosWebAgentConfig>;
47
+ /** Called once the manifest loaded and the orb mounted (auto-fetch path). */
48
+ onReady?: (controller: AgentController) => void;
49
+ /** Called when self-bootstrap fails (network / rejected key / no manifest). */
50
+ onError?: (error: Error) => void;
51
+ }
52
+ export interface FyodosAgentHandle {
53
+ /** Null until the async self-bootstrap resolves (auto-fetch path). */
54
+ controller: AgentController | null;
55
+ orb: MountedOrb | null;
56
+ /** Resolves once the controller is ready (immediately on the manual path). */
57
+ ready: Promise<AgentController | null>;
58
+ destroy(): void;
59
+ }
60
+ export declare function createFyodosAgent(options: CreateFyodosAgentOptions): FyodosAgentHandle;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Vanilla drop-in — assembles the default browser adapters, builds an
3
+ * AgentController and (optionally) mounts the DOM orb in one call. Frameworks
4
+ * use the AgentController + their own components; this is the zero-framework
5
+ * path (and the reference the Vue/Svelte/Angular drop-ins follow).
6
+ *
7
+ * PARITY WITH @fyodos/react's <FyodosAgent/>: when `manifest` is omitted, the
8
+ * agent self-bootstraps — it fetches the published manifest by `apiKey`
9
+ * (GET /v1/client/manifest), assembles default browser adapters, mounts the orb
10
+ * and posts the `orb-seen` heartbeat. So a one-line drop-in needs only an
11
+ * apiKey, exactly like React. Passing a manifest keeps the fully-synchronous,
12
+ * manual path.
13
+ */
14
+ import { validateManifest } from '@fyodos/core';
15
+ import { createFyodosBackendClient } from '../api/backendClient.js';
16
+ import { createFyodosTelemetry } from '../api/backendTelemetry.js';
17
+ import { createWebNavigationAdapter } from '../adapters/webNavigationAdapter.js';
18
+ import { createWebStorageAdapter } from '../adapters/webStorageAdapter.js';
19
+ import { createWebVoiceAdapter } from '../adapters/webVoiceAdapter.js';
20
+ import { AgentController } from '../controller/AgentController.js';
21
+ import { mountOrb } from '../orb/mountOrb.js';
22
+ import { DEFAULT_WEB_API_URL, fetchClientManifest, sendOrbSeen } from '../api/clientBootstrap.js';
23
+ function defaultNavigate(route) {
24
+ if (typeof window !== 'undefined' && window.location)
25
+ window.location.assign(route);
26
+ }
27
+ function browserLocale() {
28
+ const lang = typeof navigator !== 'undefined' && navigator.language ? navigator.language : 'en-US';
29
+ return { locale: lang.split('-')[0] || 'en', sttLocale: lang };
30
+ }
31
+ /** Build the controller + adapters from a known manifest (shared by both paths). */
32
+ function assemble(options, manifest, baseUrl) {
33
+ const fallback = browserLocale();
34
+ const locale = options.locale ?? fallback.locale;
35
+ const sttLocale = options.sttLocale ?? options.locale ?? fallback.sttLocale;
36
+ const backend = createFyodosBackendClient({
37
+ baseUrl,
38
+ apiKey: options.apiKey,
39
+ getUserId: options.getUserId,
40
+ });
41
+ const telemetry = createFyodosTelemetry({
42
+ baseUrl,
43
+ apiKey: options.apiKey,
44
+ getUserId: options.getUserId,
45
+ });
46
+ const config = {
47
+ manifest,
48
+ locale,
49
+ sttLocale,
50
+ navigation: createWebNavigationAdapter({ navigate: options.navigate ?? defaultNavigate }),
51
+ registries: options.registries ?? { handlers: {}, idempotencyCheckers: {} },
52
+ backend,
53
+ voice: createWebVoiceAdapter(),
54
+ storage: createWebStorageAdapter(),
55
+ telemetry,
56
+ isAuthenticated: options.isAuthenticated,
57
+ ttsVoice: options.ttsVoice,
58
+ ...options.configOverrides,
59
+ };
60
+ const controller = new AgentController(config);
61
+ controller.warmUp();
62
+ return controller;
63
+ }
64
+ export function createFyodosAgent(options) {
65
+ const baseUrl = (options.baseUrl ?? DEFAULT_WEB_API_URL).replace(/\/$/, '');
66
+ const shouldMount = options.mount !== false;
67
+ // ── Manual path: manifest provided → fully synchronous (back-compat). ───────
68
+ if (options.manifest) {
69
+ const controller = assemble(options, options.manifest, baseUrl);
70
+ const orb = shouldMount ? mountOrb(controller, options.orb) : null;
71
+ if (shouldMount && options.apiKey) {
72
+ void sendOrbSeen({ baseUrl, apiKey: options.apiKey });
73
+ }
74
+ options.onReady?.(controller);
75
+ const handle = {
76
+ controller,
77
+ orb,
78
+ ready: Promise.resolve(controller),
79
+ destroy() {
80
+ orb?.unmount();
81
+ controller.dispose();
82
+ },
83
+ };
84
+ return handle;
85
+ }
86
+ // ── Auto path (React parity): fetch manifest by apiKey, then assemble. ──────
87
+ const handle = {
88
+ controller: null,
89
+ orb: null,
90
+ ready: Promise.resolve(null),
91
+ destroy() {
92
+ cancelled = true;
93
+ handle.orb?.unmount();
94
+ handle.controller?.dispose();
95
+ },
96
+ };
97
+ let cancelled = false;
98
+ handle.ready = (async () => {
99
+ try {
100
+ if (!options.apiKey) {
101
+ throw new Error('[fyodos] createFyodosAgent requires either a `manifest` or an `apiKey` ' +
102
+ '(to self-fetch the published manifest).');
103
+ }
104
+ const result = await fetchClientManifest({ baseUrl, apiKey: options.apiKey });
105
+ if (cancelled)
106
+ return null;
107
+ if (result.status === 'no-manifest' || !result.manifest) {
108
+ // eslint-disable-next-line no-console
109
+ console.warn('[fyodos] The orb is idle: this project has no published manifest yet. ' +
110
+ 'Run the analysis from your Fyodos dashboard, then reload.');
111
+ return null;
112
+ }
113
+ // Fail loudly here (not deep inside the controller) so onError is precise.
114
+ const check = validateManifest(result.manifest);
115
+ if (!check.valid) {
116
+ throw new Error(`[fyodos] Invalid manifest for "${result.manifest.appId}":\n- ${check.errors.join('\n- ')}`);
117
+ }
118
+ const controller = assemble(options, result.manifest, baseUrl);
119
+ if (cancelled) {
120
+ controller.dispose();
121
+ return null;
122
+ }
123
+ handle.controller = controller;
124
+ handle.orb = shouldMount ? mountOrb(controller, options.orb) : null;
125
+ if (shouldMount && options.apiKey) {
126
+ void sendOrbSeen({ baseUrl, apiKey: options.apiKey });
127
+ }
128
+ options.onReady?.(controller);
129
+ return controller;
130
+ }
131
+ catch (e) {
132
+ const error = e instanceof Error ? e : new Error(String(e));
133
+ // eslint-disable-next-line no-console
134
+ console.error(error.message);
135
+ options.onError?.(error);
136
+ return null;
137
+ }
138
+ })();
139
+ return handle;
140
+ }
@@ -0,0 +1,45 @@
1
+ /**
2
+ * @fiodos/web-core — framework-agnostic browser layer for the Fiodos agent.
3
+ *
4
+ * Contains the vanilla orchestrator (AgentController), the browser adapters,
5
+ * the HTTP client, the decision engine and a vanilla DOM orb — all over
6
+ * @fiodos/core. @fiodos/vue, @fiodos/svelte and @fiodos/angular are thin
7
+ * reactive wrappers around THIS package, so they share identical agent
8
+ * behaviour and security. No framework imports live here.
9
+ */
10
+ export { AgentController } from './controller/AgentController.js';
11
+ export type { AgentState, AgentExchange } from './controller/AgentController.js';
12
+ export { createFiodosAgent } from './dropin/createFiodosAgent.js';
13
+ export type { CreateFiodosAgentOptions, FiodosAgentHandle } from './dropin/createFiodosAgent.js';
14
+ export { DEFAULT_WEB_API_URL, fetchClientManifest, sendOrbSeen, } from './api/clientBootstrap.js';
15
+ export type { ManifestBootstrapResult, ManifestBootstrapStatus, } from './api/clientBootstrap.js';
16
+ export { mountOrb } from './orb/mountOrb.js';
17
+ export type { MountOrbOptions, MountedOrb, OrbCorner } from './orb/mountOrb.js';
18
+ export { createOrbVisual, buildKeyboardChip, DEFAULT_ORB_APPEARANCE } from './orb/orbView.js';
19
+ export type { OrbAppearance, OrbVisual, OrbVisualState, ChipTheme } from './orb/orbView.js';
20
+ export { watchPublishedConfig } from './orb/publishedConfig.js';
21
+ export type { ResolvedOrbConfig, WatchPublishedConfigOptions } from './orb/publishedConfig.js';
22
+ export { createWebStorageAdapter } from './adapters/webStorageAdapter.js';
23
+ export type { WebStorageAdapterOptions } from './adapters/webStorageAdapter.js';
24
+ export { createWebNavigationAdapter, BACK_ROUTE, } from './adapters/webNavigationAdapter.js';
25
+ export type { WebNavigationAdapter, WebNavigationAdapterOptions, } from './adapters/webNavigationAdapter.js';
26
+ export { createWebVoiceAdapter } from './adapters/webVoiceAdapter.js';
27
+ export type { WebVoiceAdapterOptions } from './adapters/webVoiceAdapter.js';
28
+ export { createFiodosBackendClient, buildManifestPayload } from './api/backendClient.js';
29
+ export type { FiodosBackendClientOptions } from './api/backendClient.js';
30
+ export { createFiodosTelemetry } from './api/backendTelemetry.js';
31
+ export type { FiodosTelemetryOptions } from './api/backendTelemetry.js';
32
+ export { AgentApiError, isAgentApiError } from './api/errors.js';
33
+ export type { AgentApiErrorCode } from './api/errors.js';
34
+ export { createBridge } from './bridge/createBridge.js';
35
+ export type { Bridge } from './bridge/createBridge.js';
36
+ export { createScreenContextStore } from './context/screenContextStore.js';
37
+ export type { ScreenContextStore, AgentScreenSnapshot } from './context/screenContextStore.js';
38
+ export { createSpeechSession } from './speech/speechSession.js';
39
+ export type { SpeechSession, SpeechSessionOptions, SpeechSessionState, SpeechSessionSnapshot, } from './speech/speechSession.js';
40
+ export { DEFAULT_AGENT_TIMINGS } from './config/types.js';
41
+ export type { FiodosWebAgentConfig, AgentPhase, AgentTimings, TurnGateVerdict, } from './config/types.js';
42
+ export { resolveUiMessages } from './ui/messages.js';
43
+ export type { WebUiMessages, ResolveUiMessagesOptions } from './ui/messages.js';
44
+ export { decideAction, classifyPendingReply } from './core/turnEngine.js';
45
+ export type { ActionDecision, PendingConfirmation, VoiceConfirmationMode, DecideActionParams, } from './core/turnEngine.js';
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @fiodos/web-core — framework-agnostic browser layer for the Fiodos agent.
3
+ *
4
+ * Contains the vanilla orchestrator (AgentController), the browser adapters,
5
+ * the HTTP client, the decision engine and a vanilla DOM orb — all over
6
+ * @fiodos/core. @fiodos/vue, @fiodos/svelte and @fiodos/angular are thin
7
+ * reactive wrappers around THIS package, so they share identical agent
8
+ * behaviour and security. No framework imports live here.
9
+ */
10
+ // ── Orchestrator ──────────────────────────────────────────────────────────────
11
+ export { AgentController } from './controller/AgentController.js';
12
+ // ── Drop-in (zero-wiring) ─────────────────────────────────────────────────────
13
+ export { createFiodosAgent } from './dropin/createFiodosAgent.js';
14
+ // ── Client self-bootstrap (manifest auto-fetch + orb-seen heartbeat) ──────────
15
+ export { DEFAULT_WEB_API_URL, fetchClientManifest, sendOrbSeen, } from './api/clientBootstrap.js';
16
+ // ── Vanilla DOM orb ───────────────────────────────────────────────────────────
17
+ export { mountOrb } from './orb/mountOrb.js';
18
+ export { createOrbVisual, buildKeyboardChip, DEFAULT_ORB_APPEARANCE } from './orb/orbView.js';
19
+ export { watchPublishedConfig } from './orb/publishedConfig.js';
20
+ // ── Adapters (browser implementations of @fiodos/core contracts) ──────────────
21
+ export { createWebStorageAdapter } from './adapters/webStorageAdapter.js';
22
+ export { createWebNavigationAdapter, BACK_ROUTE, } from './adapters/webNavigationAdapter.js';
23
+ export { createWebVoiceAdapter } from './adapters/webVoiceAdapter.js';
24
+ // ── Backend + telemetry (same wire contract as every platform) ────────────────
25
+ export { createFiodosBackendClient, buildManifestPayload } from './api/backendClient.js';
26
+ export { createFiodosTelemetry } from './api/backendTelemetry.js';
27
+ export { AgentApiError, isAgentApiError } from './api/errors.js';
28
+ // ── Bridges ───────────────────────────────────────────────────────────────────
29
+ export { createBridge } from './bridge/createBridge.js';
30
+ // ── Screen context ────────────────────────────────────────────────────────────
31
+ export { createScreenContextStore } from './context/screenContextStore.js';
32
+ // ── Speech session ──────────────────────────────────────────────────────────────
33
+ export { createSpeechSession } from './speech/speechSession.js';
34
+ // ── Config / i18n ─────────────────────────────────────────────────────────────
35
+ export { DEFAULT_AGENT_TIMINGS } from './config/types.js';
36
+ export { resolveUiMessages } from './ui/messages.js';
37
+ // ── Decision engine (shared, framework-free; advanced use / testing) ──────────
38
+ export { decideAction, classifyPendingReply } from './core/turnEngine.js';
@@ -0,0 +1,24 @@
1
+ import type { AgentController } from '../controller/AgentController.js';
2
+ export type OrbCorner = 'bottom-right' | 'bottom-left' | 'top-right' | 'top-left';
3
+ export interface MountOrbOptions {
4
+ /** Where to mount. Defaults to document.body. */
5
+ container?: HTMLElement;
6
+ /** Screen corner for the floating orb (fallback when no published position). */
7
+ corner?: OrbCorner;
8
+ /** Override the orb diameter in px (otherwise the published/default size). */
9
+ size?: number;
10
+ /** Override the accent (fill/border) color (otherwise published/default). */
11
+ accent?: string;
12
+ /** Show the text-input affordance. Default: true. */
13
+ enableTextInput?: boolean;
14
+ /** Base z-index for the orb + modals. Default: 2147483000. */
15
+ zIndex?: number;
16
+ /** Poll interval (ms) for live dashboard config. Default 12000 (0 = off). */
17
+ appearancePollMs?: number;
18
+ /** Honour the dashboard's published screen position. Default: true. */
19
+ usePublishedPosition?: boolean;
20
+ }
21
+ export interface MountedOrb {
22
+ unmount(): void;
23
+ }
24
+ export declare function mountOrb(controller: AgentController, options?: MountOrbOptions): MountedOrb;