@fiodos/web-core 0.1.18 → 0.1.19

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.
@@ -57,7 +57,13 @@ async function errorFromResponse(res) {
57
57
  return new AgentApiError('unauthorized', detail, res.status);
58
58
  }
59
59
  if (res.status === 429) {
60
- const quotaSignal = typeof body.remaining === 'number' || (detail ?? '').toLowerCase().includes('quota');
60
+ // The backend marks plan/budget 429s with error:"quota_exceeded" (rate
61
+ // limits carry error:"rate_limited"); the detail/remaining checks cover
62
+ // older backends. Getting this right matters: a quota 429 shown as "too
63
+ // many requests" sends the developer chasing rate limits that are fine.
64
+ const quotaSignal = body.error === 'quota_exceeded' ||
65
+ typeof body.remaining === 'number' ||
66
+ (detail ?? '').toLowerCase().includes('quota');
61
67
  return new AgentApiError(quotaSignal ? 'quota_exceeded' : 'rate_limited', detail, res.status);
62
68
  }
63
69
  if (res.status === 404)
@@ -18,14 +18,20 @@ export interface AgentTimings {
18
18
  }
19
19
  export declare const DEFAULT_AGENT_TIMINGS: AgentTimings;
20
20
  /**
21
- * Autonomous multi-step chaining. OFF by default: with `enabled: false` the orb
22
- * behaves exactly as before (one action per turn). When ON, after a successful
21
+ * Autonomous multi-step chaining. ON by default: after a successful
23
22
  * navigate/execute whose backend `task_status` is "in_progress", the orb takes
24
23
  * the next step on its own — capped at `maxSteps` and guarded by no-progress
25
24
  * detection and sensitive-confirmation pauses (which always require the human).
25
+ *
26
+ * Default ON is a product invariant, not a tuning choice: data actions
27
+ * (search, get-cart…) are INVISIBLE — without the continuation turn the orb
28
+ * runs them and then goes silent ("I'll look for products…" and nothing
29
+ * happens). Every install (embeds included, which pass no config) must get
30
+ * the full act→observe→act loop. Pass `chaining: { enabled: false }` to opt
31
+ * out and return to one-action-per-turn.
26
32
  */
27
33
  export interface AgentChaining {
28
- /** Master switch. Default false (retrocompatible, one action per turn). */
34
+ /** Master switch. Default true (the agent completes multi-step tasks). */
29
35
  enabled: boolean;
30
36
  /** Hard cap on steps per user request (safety). Default 6. */
31
37
  maxSteps: number;
@@ -5,7 +5,7 @@ export const DEFAULT_AGENT_TIMINGS = {
5
5
  minTranscriptChars: 2,
6
6
  };
7
7
  export const DEFAULT_AGENT_CHAINING = {
8
- enabled: false,
8
+ enabled: true,
9
9
  maxSteps: 6,
10
10
  dwellMs: 450,
11
11
  };
@@ -104,6 +104,12 @@ export declare class AgentController {
104
104
  private pendingConsentIntent;
105
105
  /** Parked chain state (set when a chain pauses on a sensitive action). */
106
106
  private chainState;
107
+ /**
108
+ * Intent whose showcase escort was DEFERRED mid-chain (suppressEscort):
109
+ * flushed when the chain ends so the user is walked to where the last
110
+ * action's effect is visible — never in the middle of a running chain.
111
+ */
112
+ private deferredEscortIntent;
107
113
  /** REAL live activity for the panel (replaces the old canned walk-through). */
108
114
  private liveActivity;
109
115
  private chainActive;
@@ -221,6 +227,13 @@ export declare class AgentController {
221
227
  * maxSteps, with no-progress detection and sensitive-confirmation pauses.
222
228
  */
223
229
  private runChainLoop;
230
+ /**
231
+ * Runs the showcase escort that was deferred while the chain was executing
232
+ * (see suppressEscort in applyOutcome). Skipped while a confirmation is
233
+ * pending: the chain may resume after the user answers, and the confirmed
234
+ * action escorts on its own.
235
+ */
236
+ private flushDeferredEscort;
224
237
  private handleTranscript;
225
238
  private onTranscript;
226
239
  private onSpeechError;
@@ -60,6 +60,12 @@ export class AgentController {
60
60
  this.pendingConsentIntent = null;
61
61
  /** Parked chain state (set when a chain pauses on a sensitive action). */
62
62
  this.chainState = null;
63
+ /**
64
+ * Intent whose showcase escort was DEFERRED mid-chain (suppressEscort):
65
+ * flushed when the chain ends so the user is walked to where the last
66
+ * action's effect is visible — never in the middle of a running chain.
67
+ */
68
+ this.deferredEscortIntent = null;
63
69
  /** REAL live activity for the panel (replaces the old canned walk-through). */
64
70
  this.liveActivity = null;
65
71
  this.chainActive = false;
@@ -539,6 +545,7 @@ export class AgentController {
539
545
  // Declining a sensitive action stops any chain it was part of, cleanly.
540
546
  const chainState = this.chainState;
541
547
  this.chainState = null;
548
+ this.deferredEscortIntent = null;
542
549
  if (chainState) {
543
550
  this.telemetry.logEvent({
544
551
  eventType: 'agent_chain_aborted',
@@ -658,6 +665,10 @@ export class AgentController {
658
665
  let outAudio = turn.audioBase64;
659
666
  if (outReply)
660
667
  this.serverTtsUnavailable = !outAudio;
668
+ // Mid-chain: hold the showcase escort. On multi-page hosts (Shopify
669
+ // embeds) the escort is a full page load that would kill the chain
670
+ // before its continuation turn; the chain escorts once when it ends.
671
+ const escortDeferred = this.chaining.enabled && turn.taskStatus === 'in_progress';
661
672
  const decision = await decideAction({
662
673
  action: turn.action,
663
674
  manifest: this.config.manifest,
@@ -665,6 +676,7 @@ export class AgentController {
665
676
  context: snapshotContext,
666
677
  messages: this.messages,
667
678
  userMessage,
679
+ suppressEscort: escortDeferred,
668
680
  });
669
681
  let canContinue = false;
670
682
  let lastStep;
@@ -735,6 +747,15 @@ export class AgentController {
735
747
  (turn.action.type === 'navigate' || turn.action.type === 'execute') &&
736
748
  r.success &&
737
749
  !r.recoverable) {
750
+ // Deferred-escort bookkeeping: remember the intent whose escort was
751
+ // held (flushed at chain end); a navigate — or an execute whose escort
752
+ // already ran inside the executor — supersedes any earlier deferral.
753
+ if (turn.action.type === 'execute') {
754
+ this.deferredEscortIntent = escortDeferred ? (turn.action.intent ?? null) : null;
755
+ }
756
+ else {
757
+ this.deferredEscortIntent = null;
758
+ }
738
759
  const intent = turn.action.intent ?? '';
739
760
  // Feed the step's RESULT DATA back to the next chain turn (bounded):
740
761
  // it is how the model reads real technical values (variant ids, line
@@ -938,8 +959,23 @@ export class AgentController {
938
959
  }
939
960
  finally {
940
961
  this.setChainActive(false);
962
+ this.flushDeferredEscort();
941
963
  }
942
964
  }
965
+ /**
966
+ * Runs the showcase escort that was deferred while the chain was executing
967
+ * (see suppressEscort in applyOutcome). Skipped while a confirmation is
968
+ * pending: the chain may resume after the user answers, and the confirmed
969
+ * action escorts on its own.
970
+ */
971
+ flushDeferredEscort() {
972
+ if (this.pending != null)
973
+ return;
974
+ const intent = this.deferredEscortIntent;
975
+ this.deferredEscortIntent = null;
976
+ if (intent)
977
+ this.executor.escortForIntent(intent);
978
+ }
943
979
  // ── Main turn ────────────────────────────────────────────────────────────────
944
980
  async handleTranscript(rawText) {
945
981
  const text = rawText.trim();
@@ -1223,6 +1259,7 @@ export class AgentController {
1223
1259
  this.config.voice.stopPlayback();
1224
1260
  this.pending = null;
1225
1261
  this.chainState = null;
1262
+ this.deferredEscortIntent = null;
1226
1263
  this.confirmationMessage = null;
1227
1264
  this.confirmationVoiceHint = null;
1228
1265
  this.liveActivity = null;
@@ -52,6 +52,12 @@ export interface DecideActionParams {
52
52
  context: AgentScreenContext;
53
53
  messages: AgentMessages;
54
54
  userMessage: string;
55
+ /**
56
+ * Skip the post-action showcase escort (mid-chain steps): on multi-page
57
+ * hosts the escort is a full page load that would kill the running chain.
58
+ * The orchestrator escorts once at the END of the chain.
59
+ */
60
+ suppressEscort?: boolean;
55
61
  }
56
62
  /**
57
63
  * Decides what to do with a backend-proposed action. Sensitive actions are
@@ -27,7 +27,7 @@ function findManifestAction(manifest, intent) {
27
27
  * never executed here — they return `needs-confirmation`.
28
28
  */
29
29
  export async function decideAction(params) {
30
- const { action, manifest, executor, context, messages, userMessage } = params;
30
+ const { action, manifest, executor, context, messages, userMessage, suppressEscort } = params;
31
31
  if (!action || action.type === 'none') {
32
32
  return { kind: 'noop' };
33
33
  }
@@ -39,7 +39,7 @@ export async function decideAction(params) {
39
39
  const manifestAction = findManifestAction(manifest, action.intent);
40
40
  const needsConfirmation = manifestAction?.requireConfirmation === true;
41
41
  if (!needsConfirmation) {
42
- const result = await executor.execute(action, context);
42
+ const result = await executor.execute(action, context, { suppressEscort });
43
43
  return { kind: 'executed', result };
44
44
  }
45
45
  // Precheck (context + idempotency) BEFORE asking: applying an already-applied
@@ -27,7 +27,7 @@ import { createScreenContextStore } from '../context/screenContextStore.js';
27
27
  import { connectDynamicsFormContext, dynamicsFormSnapshot, normalizeDynamicsId } from './dynamics.js';
28
28
  import { buildApiActionRegistries, validateManifest } from '@fiodos/core';
29
29
  declare const api: {
30
- readonly version: "0.1.18";
30
+ readonly version: "0.1.19";
31
31
  readonly createFiodosAgent: typeof createFiodosAgent;
32
32
  readonly mountOrb: typeof mountOrb;
33
33
  readonly AgentController: typeof AgentController;
@@ -25,12 +25,29 @@ export function createBubbleDictation(locale) {
25
25
  let listening = false;
26
26
  let baseText = '';
27
27
  const stop = () => {
28
- recognition?.stop();
28
+ const rec = recognition;
29
+ if (!rec)
30
+ return;
31
+ // DISCARD-ON-STOP: recognition engines fire one last consolidated
32
+ // `onresult` AFTER stop() is requested. If the user hits send while still
33
+ // dictating, that late result would rewrite the input we just cleared and
34
+ // the message would reappear (duplicate-send bug). Once a stop is
35
+ // requested, nothing may ever write to the input again.
36
+ rec.onresult = null;
37
+ recognition = null;
38
+ listening = false;
39
+ try {
40
+ if (typeof rec.abort === 'function')
41
+ rec.abort();
42
+ else
43
+ rec.stop();
44
+ }
45
+ catch {
46
+ /* already stopped */
47
+ }
29
48
  };
30
49
  const dispose = () => {
31
50
  stop();
32
- recognition = null;
33
- listening = false;
34
51
  };
35
52
  return {
36
53
  isSupported: () => Ctor != null,
@@ -68,11 +85,18 @@ export function createBubbleDictation(locale) {
68
85
  : dictated;
69
86
  setValue(combined);
70
87
  };
88
+ // Identity-guarded teardown: a session stopped via stop() may fire its
89
+ // onend AFTER the user already started a NEW dictation — it must never
90
+ // tear down the new session.
71
91
  rec.onend = () => {
92
+ if (recognition !== rec)
93
+ return;
72
94
  listening = false;
73
95
  recognition = null;
74
96
  };
75
97
  rec.onerror = () => {
98
+ if (recognition !== rec)
99
+ return;
76
100
  listening = false;
77
101
  recognition = null;
78
102
  };
@@ -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.18";
8
+ export declare const SDK_VERSION = "0.1.19";
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.18';
8
+ export const SDK_VERSION = '0.1.19';
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.18",
3
+ "version": "0.1.19",
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": {