@fiodos/web-core 0.1.2 → 0.1.3

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.
@@ -0,0 +1,2 @@
1
+ /** User-visible chat error — shown IN the text bubble, not only in the console. */
2
+ export declare function formatAgentTurnError(err: unknown, locale: string): string;
@@ -0,0 +1,45 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatAgentTurnError = formatAgentTurnError;
4
+ const errors_1 = require("./errors");
5
+ /** User-visible chat error — shown IN the text bubble, not only in the console. */
6
+ function formatAgentTurnError(err, locale) {
7
+ const es = locale.startsWith('es');
8
+ if (err instanceof errors_1.AgentApiError) {
9
+ switch (err.code) {
10
+ case 'timeout':
11
+ return es
12
+ ? 'La respuesta tardó demasiado. Comprueba tu conexión e inténtalo de nuevo.'
13
+ : 'The response took too long. Check your connection and try again.';
14
+ case 'network':
15
+ return es
16
+ ? 'No pude conectar con el servidor de Fiodos. ¿Está el backend en marcha y la URL correcta?'
17
+ : 'Could not reach the Fiodos server. Is the backend running and the URL correct?';
18
+ case 'unauthorized':
19
+ return es
20
+ ? 'La API key fue rechazada. Comprueba que coincide con la del proyecto en el dashboard.'
21
+ : 'The API key was rejected. Check it matches the project key in your dashboard.';
22
+ case 'quota_exceeded':
23
+ return es
24
+ ? 'Has alcanzado el límite de uso de IA de tu plan. Mejóralo en el dashboard para seguir.'
25
+ : 'You reached your plan\u2019s AI usage limit. Upgrade in the dashboard to continue.';
26
+ case 'rate_limited':
27
+ return es
28
+ ? 'Demasiadas peticiones seguidas. Espera un momento e inténtalo de nuevo.'
29
+ : 'Too many requests. Wait a moment and try again.';
30
+ case 'server_error':
31
+ return es
32
+ ? 'El servidor no pudo responder. Revisa que la IA esté configurada en el backend.'
33
+ : 'The server could not respond. Check that the AI is configured on the backend.';
34
+ case 'cancelled':
35
+ return es ? 'Cancelado.' : 'Cancelled.';
36
+ default:
37
+ break;
38
+ }
39
+ if (err.message && err.message !== err.code)
40
+ return err.message;
41
+ }
42
+ if (err instanceof Error && err.message)
43
+ return err.message;
44
+ return es ? 'No pude responder. Inténtalo de nuevo.' : 'I could not reply. Please try again.';
45
+ }
@@ -58,6 +58,8 @@ export declare class AgentController {
58
58
  private enabled;
59
59
  private phase;
60
60
  private lastExchange;
61
+ /** User text for the turn currently in flight (for error/timeout display). */
62
+ private pendingTurnText;
61
63
  private confirmationMessage;
62
64
  private confirmationVoiceHint;
63
65
  private consentModalVisible;
@@ -20,6 +20,7 @@ exports.AgentController = void 0;
20
20
  */
21
21
  const core_1 = require("@fiodos/core");
22
22
  const errors_1 = require("../api/errors");
23
+ const formatTurnError_1 = require("../api/formatTurnError");
23
24
  const backendClient_1 = require("../api/backendClient");
24
25
  const screenContextStore_1 = require("../context/screenContextStore");
25
26
  const speechSession_1 = require("../speech/speechSession");
@@ -34,6 +35,8 @@ class AgentController {
34
35
  this.enabled = true;
35
36
  this.phase = 'idle';
36
37
  this.lastExchange = null;
38
+ /** User text for the turn currently in flight (for error/timeout display). */
39
+ this.pendingTurnText = null;
37
40
  this.confirmationMessage = null;
38
41
  this.confirmationVoiceHint = null;
39
42
  this.consentModalVisible = false;
@@ -323,11 +326,21 @@ class AgentController {
323
326
  return;
324
327
  }
325
328
  this.setPhase('thinking');
329
+ this.pendingTurnText = text;
330
+ this.lastExchange = { userText: text, replyText: '' };
331
+ this.emit();
326
332
  this.clearWatchdog();
327
333
  this.watchdog = setTimeout(() => {
328
334
  if (this.phase === 'thinking') {
329
335
  this.telemetry.logEvent({ eventType: 'agent_watchdog_reset', sessionId: this.sessionId });
336
+ const userText = this.pendingTurnText ?? text;
337
+ const replyText = this.config.locale.startsWith('es')
338
+ ? 'La respuesta tardó demasiado. Inténtalo de nuevo.'
339
+ : 'The response took too long. Please try again.';
340
+ this.lastExchange = { userText, replyText };
341
+ this.pendingTurnText = null;
330
342
  this.setPhase('idle');
343
+ this.emit();
331
344
  }
332
345
  }, this.timings.thinkingWatchdogMs);
333
346
  const currentRoute = this.config.navigation.getCurrentRoute();
@@ -435,23 +448,36 @@ class AgentController {
435
448
  if (e instanceof errors_1.AgentApiError && e.code === 'cancelled') {
436
449
  if (this.phase === 'thinking')
437
450
  this.setPhase('idle');
451
+ this.pendingTurnText = null;
438
452
  return;
439
453
  }
440
454
  if (e instanceof errors_1.AgentApiError && e.code === 'quota_exceeded') {
441
455
  this.config.onQuotaExceeded?.();
442
456
  }
457
+ const errMsg = (0, formatTurnError_1.formatAgentTurnError)(e, this.config.locale);
458
+ this.lastExchange = { userText: text, replyText: errMsg };
459
+ this.pendingTurnText = null;
443
460
  if (this.phase === 'thinking')
444
461
  this.setPhase('idle');
445
- this.notifyError(reply || (e instanceof Error ? e.message : 'error'));
462
+ this.emit();
463
+ this.notifyError(errMsg);
446
464
  return;
447
465
  }
448
466
  this.clearWatchdog();
449
467
  this.abort = null;
468
+ this.pendingTurnText = null;
450
469
  if (reply) {
451
470
  this.lastExchange = { userText: text, replyText: reply };
452
471
  this.emit();
453
472
  (0, core_1.recordConversationExchange)(this.conversation, text, reply);
454
473
  }
474
+ else {
475
+ const fallback = this.config.locale.startsWith('es')
476
+ ? 'No recibí respuesta del servidor. Inténtalo de nuevo.'
477
+ : 'No reply from the server. Please try again.';
478
+ this.lastExchange = { userText: text, replyText: fallback };
479
+ this.emit();
480
+ }
455
481
  const hasPending = this.pending != null;
456
482
  await this.speakReply(reply, audioBase64);
457
483
  if (hasPending) {
@@ -661,6 +661,10 @@ function mountOrb(controller, options = {}) {
661
661
  const stopWatch = (0, publishedConfig_1.watchPublishedConfig)(controller, {
662
662
  pollMs: options.appearancePollMs,
663
663
  onChange: (config) => {
664
+ // Dashboard master switch: hide the whole orb (button + chip + bubble live
665
+ // inside `root`) when explicitly turned off, show it otherwise. No host
666
+ // code change — it flips on the next poll and reappears when re-enabled.
667
+ root.style.display = config && config.enabled === false ? 'none' : '';
664
668
  if (config) {
665
669
  publishedAppearance = config.appearance;
666
670
  chipTheme = config.keyboardChip;
@@ -17,6 +17,12 @@ export interface ResolvedOrbConfig {
17
17
  keyboardChip: ChipTheme | null;
18
18
  modalTheme: PublishedModalTheme | undefined;
19
19
  screenPosition: OrbScreenPosition | undefined;
20
+ /**
21
+ * Dashboard master visibility switch (`orbEnabled`). False ONLY when the
22
+ * developer explicitly turned the orb off. The orb is hidden when false and
23
+ * shown otherwise (including when nothing is published / on error).
24
+ */
25
+ enabled: boolean;
20
26
  }
21
27
  export interface WatchPublishedConfigOptions {
22
28
  /** Poll interval (ms) to pick up dashboard changes in real time. Default 12000. */
@@ -14,7 +14,7 @@ exports.watchPublishedConfig = watchPublishedConfig;
14
14
  */
15
15
  const core_1 = require("@fiodos/core");
16
16
  const orbView_1 = require("./orbView");
17
- function toResolved(props) {
17
+ function toResolved(props, enabled) {
18
18
  const theme = props.theme;
19
19
  const appearance = {
20
20
  accentColor: theme.accentColor ?? orbView_1.DEFAULT_ORB_APPEARANCE.accentColor,
@@ -33,6 +33,7 @@ function toResolved(props) {
33
33
  keyboardChip: theme.keyboardChip ?? null,
34
34
  modalTheme: props.modalTheme,
35
35
  screenPosition: props.screenPosition,
36
+ enabled,
36
37
  };
37
38
  }
38
39
  /**
@@ -52,7 +53,9 @@ function watchPublishedConfig(controller, options) {
52
53
  const res = await fetcher.call(controller.config.backend);
53
54
  if (cancelled)
54
55
  return;
55
- options.onChange(res.published ? toResolved((0, core_1.mapPublishedConfigToOrbProps)(res.published)) : null);
56
+ options.onChange(res.published
57
+ ? toResolved((0, core_1.mapPublishedConfigToOrbProps)(res.published), res.published.orbEnabled !== false)
58
+ : null);
56
59
  }
57
60
  catch {
58
61
  if (!cancelled)
@@ -5,5 +5,5 @@
5
5
  * Auto-generated by scripts/sync-sdk-version.mjs from package.json. Do not edit
6
6
  * by hand — change package.json `version` and re-run the sync/release script.
7
7
  */
8
- export declare const SDK_VERSION = "0.1.2";
8
+ export declare const SDK_VERSION = "0.1.3";
9
9
  export declare const SDK_PACKAGE = "@fiodos/web-core";
@@ -8,5 +8,5 @@ exports.SDK_PACKAGE = exports.SDK_VERSION = void 0;
8
8
  * Auto-generated by scripts/sync-sdk-version.mjs from package.json. Do not edit
9
9
  * by hand — change package.json `version` and re-run the sync/release script.
10
10
  */
11
- exports.SDK_VERSION = '0.1.2';
11
+ exports.SDK_VERSION = '0.1.3';
12
12
  exports.SDK_PACKAGE = '@fiodos/web-core';
@@ -0,0 +1,2 @@
1
+ /** User-visible chat error — shown IN the text bubble, not only in the console. */
2
+ export declare function formatAgentTurnError(err: unknown, locale: string): string;
@@ -0,0 +1,42 @@
1
+ import { AgentApiError } from './errors.js';
2
+ /** User-visible chat error — shown IN the text bubble, not only in the console. */
3
+ export function formatAgentTurnError(err, locale) {
4
+ const es = locale.startsWith('es');
5
+ if (err instanceof AgentApiError) {
6
+ switch (err.code) {
7
+ case 'timeout':
8
+ return es
9
+ ? 'La respuesta tardó demasiado. Comprueba tu conexión e inténtalo de nuevo.'
10
+ : 'The response took too long. Check your connection and try again.';
11
+ case 'network':
12
+ return es
13
+ ? 'No pude conectar con el servidor de Fiodos. ¿Está el backend en marcha y la URL correcta?'
14
+ : 'Could not reach the Fiodos server. Is the backend running and the URL correct?';
15
+ case 'unauthorized':
16
+ return es
17
+ ? 'La API key fue rechazada. Comprueba que coincide con la del proyecto en el dashboard.'
18
+ : 'The API key was rejected. Check it matches the project key in your dashboard.';
19
+ case 'quota_exceeded':
20
+ return es
21
+ ? 'Has alcanzado el límite de uso de IA de tu plan. Mejóralo en el dashboard para seguir.'
22
+ : 'You reached your plan\u2019s AI usage limit. Upgrade in the dashboard to continue.';
23
+ case 'rate_limited':
24
+ return es
25
+ ? 'Demasiadas peticiones seguidas. Espera un momento e inténtalo de nuevo.'
26
+ : 'Too many requests. Wait a moment and try again.';
27
+ case 'server_error':
28
+ return es
29
+ ? 'El servidor no pudo responder. Revisa que la IA esté configurada en el backend.'
30
+ : 'The server could not respond. Check that the AI is configured on the backend.';
31
+ case 'cancelled':
32
+ return es ? 'Cancelado.' : 'Cancelled.';
33
+ default:
34
+ break;
35
+ }
36
+ if (err.message && err.message !== err.code)
37
+ return err.message;
38
+ }
39
+ if (err instanceof Error && err.message)
40
+ return err.message;
41
+ return es ? 'No pude responder. Inténtalo de nuevo.' : 'I could not reply. Please try again.';
42
+ }
@@ -58,6 +58,8 @@ export declare class AgentController {
58
58
  private enabled;
59
59
  private phase;
60
60
  private lastExchange;
61
+ /** User text for the turn currently in flight (for error/timeout display). */
62
+ private pendingTurnText;
61
63
  private confirmationMessage;
62
64
  private confirmationVoiceHint;
63
65
  private consentModalVisible;
@@ -17,6 +17,7 @@
17
17
  */
18
18
  import { combineContexts, createActionExecutor, createConsentManager, createSafeTelemetry, createSanitizer, createConversationSession, formatMessage, prepareConversationForTurn, recordConversationExchange, resolveConfirmationLexicon, resolveMessages, validateManifest, } from '@fiodos/core';
19
19
  import { AgentApiError } from '../api/errors.js';
20
+ import { formatAgentTurnError } from '../api/formatTurnError.js';
20
21
  import { buildManifestPayload } from '../api/backendClient.js';
21
22
  import { createScreenContextStore, } from '../context/screenContextStore.js';
22
23
  import { createSpeechSession } from '../speech/speechSession.js';
@@ -31,6 +32,8 @@ export class AgentController {
31
32
  this.enabled = true;
32
33
  this.phase = 'idle';
33
34
  this.lastExchange = null;
35
+ /** User text for the turn currently in flight (for error/timeout display). */
36
+ this.pendingTurnText = null;
34
37
  this.confirmationMessage = null;
35
38
  this.confirmationVoiceHint = null;
36
39
  this.consentModalVisible = false;
@@ -320,11 +323,21 @@ export class AgentController {
320
323
  return;
321
324
  }
322
325
  this.setPhase('thinking');
326
+ this.pendingTurnText = text;
327
+ this.lastExchange = { userText: text, replyText: '' };
328
+ this.emit();
323
329
  this.clearWatchdog();
324
330
  this.watchdog = setTimeout(() => {
325
331
  if (this.phase === 'thinking') {
326
332
  this.telemetry.logEvent({ eventType: 'agent_watchdog_reset', sessionId: this.sessionId });
333
+ const userText = this.pendingTurnText ?? text;
334
+ const replyText = this.config.locale.startsWith('es')
335
+ ? 'La respuesta tardó demasiado. Inténtalo de nuevo.'
336
+ : 'The response took too long. Please try again.';
337
+ this.lastExchange = { userText, replyText };
338
+ this.pendingTurnText = null;
327
339
  this.setPhase('idle');
340
+ this.emit();
328
341
  }
329
342
  }, this.timings.thinkingWatchdogMs);
330
343
  const currentRoute = this.config.navigation.getCurrentRoute();
@@ -432,23 +445,36 @@ export class AgentController {
432
445
  if (e instanceof AgentApiError && e.code === 'cancelled') {
433
446
  if (this.phase === 'thinking')
434
447
  this.setPhase('idle');
448
+ this.pendingTurnText = null;
435
449
  return;
436
450
  }
437
451
  if (e instanceof AgentApiError && e.code === 'quota_exceeded') {
438
452
  this.config.onQuotaExceeded?.();
439
453
  }
454
+ const errMsg = formatAgentTurnError(e, this.config.locale);
455
+ this.lastExchange = { userText: text, replyText: errMsg };
456
+ this.pendingTurnText = null;
440
457
  if (this.phase === 'thinking')
441
458
  this.setPhase('idle');
442
- this.notifyError(reply || (e instanceof Error ? e.message : 'error'));
459
+ this.emit();
460
+ this.notifyError(errMsg);
443
461
  return;
444
462
  }
445
463
  this.clearWatchdog();
446
464
  this.abort = null;
465
+ this.pendingTurnText = null;
447
466
  if (reply) {
448
467
  this.lastExchange = { userText: text, replyText: reply };
449
468
  this.emit();
450
469
  recordConversationExchange(this.conversation, text, reply);
451
470
  }
471
+ else {
472
+ const fallback = this.config.locale.startsWith('es')
473
+ ? 'No recibí respuesta del servidor. Inténtalo de nuevo.'
474
+ : 'No reply from the server. Please try again.';
475
+ this.lastExchange = { userText: text, replyText: fallback };
476
+ this.emit();
477
+ }
452
478
  const hasPending = this.pending != null;
453
479
  await this.speakReply(reply, audioBase64);
454
480
  if (hasPending) {
@@ -658,6 +658,10 @@ export function mountOrb(controller, options = {}) {
658
658
  const stopWatch = watchPublishedConfig(controller, {
659
659
  pollMs: options.appearancePollMs,
660
660
  onChange: (config) => {
661
+ // Dashboard master switch: hide the whole orb (button + chip + bubble live
662
+ // inside `root`) when explicitly turned off, show it otherwise. No host
663
+ // code change — it flips on the next poll and reappears when re-enabled.
664
+ root.style.display = config && config.enabled === false ? 'none' : '';
661
665
  if (config) {
662
666
  publishedAppearance = config.appearance;
663
667
  chipTheme = config.keyboardChip;
@@ -17,6 +17,12 @@ export interface ResolvedOrbConfig {
17
17
  keyboardChip: ChipTheme | null;
18
18
  modalTheme: PublishedModalTheme | undefined;
19
19
  screenPosition: OrbScreenPosition | undefined;
20
+ /**
21
+ * Dashboard master visibility switch (`orbEnabled`). False ONLY when the
22
+ * developer explicitly turned the orb off. The orb is hidden when false and
23
+ * shown otherwise (including when nothing is published / on error).
24
+ */
25
+ enabled: boolean;
20
26
  }
21
27
  export interface WatchPublishedConfigOptions {
22
28
  /** Poll interval (ms) to pick up dashboard changes in real time. Default 12000. */
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import { mapPublishedConfigToOrbProps, } from '@fiodos/core';
13
13
  import { DEFAULT_ORB_APPEARANCE } from './orbView.js';
14
- function toResolved(props) {
14
+ function toResolved(props, enabled) {
15
15
  const theme = props.theme;
16
16
  const appearance = {
17
17
  accentColor: theme.accentColor ?? DEFAULT_ORB_APPEARANCE.accentColor,
@@ -30,6 +30,7 @@ function toResolved(props) {
30
30
  keyboardChip: theme.keyboardChip ?? null,
31
31
  modalTheme: props.modalTheme,
32
32
  screenPosition: props.screenPosition,
33
+ enabled,
33
34
  };
34
35
  }
35
36
  /**
@@ -49,7 +50,9 @@ export function watchPublishedConfig(controller, options) {
49
50
  const res = await fetcher.call(controller.config.backend);
50
51
  if (cancelled)
51
52
  return;
52
- options.onChange(res.published ? toResolved(mapPublishedConfigToOrbProps(res.published)) : null);
53
+ options.onChange(res.published
54
+ ? toResolved(mapPublishedConfigToOrbProps(res.published), res.published.orbEnabled !== false)
55
+ : null);
53
56
  }
54
57
  catch {
55
58
  if (!cancelled)
@@ -5,5 +5,5 @@
5
5
  * Auto-generated by scripts/sync-sdk-version.mjs from package.json. Do not edit
6
6
  * by hand — change package.json `version` and re-run the sync/release script.
7
7
  */
8
- export declare const SDK_VERSION = "0.1.2";
8
+ export declare const SDK_VERSION = "0.1.3";
9
9
  export declare const SDK_PACKAGE = "@fiodos/web-core";
@@ -5,5 +5,5 @@
5
5
  * Auto-generated by scripts/sync-sdk-version.mjs from package.json. Do not edit
6
6
  * by hand — change package.json `version` and re-run the sync/release script.
7
7
  */
8
- export const SDK_VERSION = '0.1.2';
8
+ export const SDK_VERSION = '0.1.3';
9
9
  export const SDK_PACKAGE = '@fiodos/web-core';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fiodos/web-core",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Framework-agnostic browser layer for the Fiodos agent: a vanilla AgentController (orchestrator), DOM adapters (navigation/voice/storage), HTTP client and decision engine over @fiodos/core. Shared base for @fiodos/vue, @fiodos/svelte and @fiodos/angular (no framework imports).",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "publishConfig": {