@burdenoff/website-sdk 2026.1104.3 → 2026.1104.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -387,9 +387,14 @@ publishes audio, video or data, and it sends the avatar nothing to say.
387
387
  browser's own note of a session today, kept in `localStorage` as
388
388
  `boff.explore.avatarUsedOn`), the box asks ordinary questions, without
389
389
  `surface`, and the answer shows in the tab as text, with "Read the answer and
390
- sources in Chat". Pass `currentPath` to `ExplorePanel` (as
391
- `SiteAssistantDock` does) so a page change calls off a "Start when ready"
392
- at once; without it, the window's path is checked before the create.
390
+ sources in Chat". The text is the answer's words, not its markdown: the form
391
+ the chat's read-aloud says (`answerSpeechText`), so a link is its label, a
392
+ table its rows, and the marks of emphasis, headings and quotes go, and on
393
+ this screen list markers as well (each item keeps its own line); citation
394
+ markers such as `[1]` stay, as in the chat. Pass `currentPath` to
395
+ `ExplorePanel` (as `SiteAssistantDock` does) so a page change calls off a
396
+ "Start when ready" at once; without it, the window's path is checked before
397
+ the create.
393
398
  - **One answer: the first, to a suggested question.** The avatar service
394
399
  accepts a conversation only when it holds exactly two messages: the first user
395
400
  message (a suggested question) and the first assistant answer, completed. The
@@ -421,7 +426,22 @@ publishes audio, video or data, and it sends the avatar nothing to say.
421
426
  back (Safari, iOS), a "Tap to turn on sound" button appears. A refusal (busy,
422
427
  limits, region, language, paused, a question the avatar cannot read) is one
423
428
  short sentence and "Continue in Chat". A network or CORS failure, or a route
424
- that is not there, shows the view as it was before.
429
+ that is not there, shows the view as it was before, until the create request
430
+ has been sent: from then on the server may have made the session, so a create
431
+ that gets no answer (its request failing on the way, its body cut off, or
432
+ nothing within 30 s) says "The live avatar couldn't start." with the way back
433
+ to Chat, never "Live avatar coming soon". The 30 s are a timer raced against
434
+ the request, so the view leaves "Connecting…" by then whatever the network
435
+ does. A create is never sent again by itself (the bot check's token is good
436
+ for one, and a second create could be paid twice), and a 201 that arrives
437
+ after the 30 s is never joined: its session is ended at once with its own
438
+ secret (reason `client_error`, or the visitor's own where they had stopped it
439
+ first). For that, only a request with no answer yet is aborted at 30 s: an
440
+ answer whose status is in by then is read to its end, so a 201 whose body is
441
+ still on its way keeps its secret. A request aborted with no answer at all
442
+ brings nothing back where the browser honours the abort, so a session the
443
+ server made for it is ended by the server's own deadlines (`no_show`). A 401,
444
+ 403, 404 or 405 answer is still the view as it was before.
425
445
  - **Ending.** Stop, closing the panel, leaving the page (`pagehide`), and the
426
446
  live avatar going off under a session (the panel's locale changing to one
427
447
  whose strings have not been reviewed) all end the session with a `keepalive`
@@ -1,7 +1,7 @@
1
1
  'use strict';
2
2
 
3
3
  var chunkWSEQ2I6I_js = require('../chunk-WSEQ2I6I.js');
4
- var chunkO4GS3XPZ_js = require('../chunk-O4GS3XPZ.js');
4
+ var chunkN7E6Z2GF_js = require('../chunk-N7E6Z2GF.js');
5
5
  var chunkC6OJIHNO_js = require('../chunk-C6OJIHNO.js');
6
6
  require('../chunk-ZSTFX7FV.js');
7
7
  var react = require('react');
@@ -182,7 +182,7 @@ async function requestAvailability(target) {
182
182
  timer.done();
183
183
  }
184
184
  }
185
- async function requestCreate(target) {
185
+ function requestCreate(target) {
186
186
  const { body } = target;
187
187
  const payload = JSON.stringify({
188
188
  site: body.site,
@@ -192,27 +192,45 @@ async function requestCreate(target) {
192
192
  consentVersion: body.consentVersion,
193
193
  manifestVersion: body.manifestVersion
194
194
  });
195
- const timer = deadline(CREATE_TIMEOUT_MS);
196
- try {
197
- const response = await fetch(`${target.base}/sessions`, {
198
- method: "POST",
199
- credentials: "omit",
200
- cache: "no-store",
201
- headers: {
202
- ...target.headers,
203
- "Content-Type": "application/json",
204
- "Idempotency-Key": target.idempotencyKey,
205
- ...target.botToken === void 0 ? {} : { "x-web-avatar-bot-token": target.botToken }
206
- },
207
- body: payload,
208
- signal: timer.signal
195
+ const controller = new AbortController();
196
+ let answered = false;
197
+ const answer = (async () => {
198
+ try {
199
+ const response = await fetch(`${target.base}/sessions`, {
200
+ method: "POST",
201
+ credentials: "omit",
202
+ cache: "no-store",
203
+ headers: {
204
+ ...target.headers,
205
+ "Content-Type": "application/json",
206
+ "Idempotency-Key": target.idempotencyKey,
207
+ ...target.botToken === void 0 ? {} : { "x-web-avatar-bot-token": target.botToken }
208
+ },
209
+ body: payload,
210
+ signal: controller.signal
211
+ });
212
+ answered = true;
213
+ return readCreate(response.status, await readJson(response));
214
+ } catch {
215
+ return FAILED;
216
+ }
217
+ })();
218
+ return new Promise((resolve) => {
219
+ let late = false;
220
+ const timer = setTimeout(() => {
221
+ late = true;
222
+ if (!answered) controller.abort();
223
+ resolve(FAILED);
224
+ }, CREATE_TIMEOUT_MS);
225
+ void answer.then((result) => {
226
+ if (late) {
227
+ target.onLate?.(result);
228
+ return;
229
+ }
230
+ clearTimeout(timer);
231
+ resolve(result);
209
232
  });
210
- return readCreate(response.status, await readJson(response));
211
- } catch {
212
- return INERT;
213
- } finally {
214
- timer.done();
215
- }
233
+ });
216
234
  }
217
235
  function sendEnd(target) {
218
236
  try {
@@ -1361,21 +1379,21 @@ function AvatarLiveView({
1361
1379
  [go]
1362
1380
  );
1363
1381
  const settle = react.useCallback(
1364
- (result, run, context) => {
1382
+ (result, givenUp, context) => {
1365
1383
  const created = result.kind === "created" ? {
1366
1384
  sessionId: result.session.sessionId,
1367
1385
  secret: result.session.endSecret
1368
1386
  } : result.kind === "unusable" ? { sessionId: result.sessionId, secret: result.endSecret } : null;
1369
1387
  const claimed = created !== null || result.kind === "refused" && result.reason === "answer_claimed";
1370
1388
  if (claimed) claimedRef.current.add(context.messageId);
1371
- if (run.cancelled !== null) {
1389
+ if (givenUp !== null) {
1372
1390
  if (created !== null) {
1373
1391
  sendEnd({
1374
1392
  base: context.base,
1375
1393
  headers: context.headers,
1376
1394
  sessionId: created.sessionId,
1377
1395
  secret: created.secret,
1378
- reason: run.cancelled
1396
+ reason: givenUp
1379
1397
  });
1380
1398
  }
1381
1399
  const shown2 = phaseRef.current;
@@ -1484,21 +1502,21 @@ function AvatarLiveView({
1484
1502
  return;
1485
1503
  }
1486
1504
  if (turnstile !== null) go({ name: "creating" });
1505
+ const context = { module: loaded, offer, messageId, base: base2, headers: headers2 };
1487
1506
  const result = await requestCreate({
1488
1507
  base: base2,
1489
1508
  headers: headers2,
1490
1509
  idempotencyKey,
1491
1510
  botToken,
1492
- body: body2
1511
+ body: body2,
1512
+ // An answer past the deadline, after the view has said the live avatar couldn't start: a session it made is
1513
+ // ended at once with its own secret, and never joined. The reason is the visitor's own where they gave the
1514
+ // start up first, else `client_error`. It reads `run` and never writes it, so the deadline's `failed` below is
1515
+ // shown whichever of the two is handled first.
1516
+ onLate: (late) => settle(late, run.cancelled ?? "client_error", context)
1493
1517
  });
1494
1518
  if (startRef.current === run) startRef.current = null;
1495
- settle(result, run, {
1496
- module: loaded,
1497
- offer,
1498
- messageId,
1499
- base: base2,
1500
- headers: headers2
1501
- });
1519
+ settle(result, run.cancelled, context);
1502
1520
  })();
1503
1521
  },
1504
1522
  [exchangeNow, go, reoffer, settle]
@@ -2097,7 +2115,7 @@ function AvatarLiveView({
2097
2115
  className: "w-full max-w-sm rounded-lg border border-border p-4 text-start",
2098
2116
  children: [
2099
2117
  /* @__PURE__ */ jsxRuntime.jsx("h2", { id: consentTitleId, className: "text-base font-semibold", children: copy.title }),
2100
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm", children: productName === null || copy.introProduct === null ? copy.intro : chunkO4GS3XPZ_js.fill(copy.introProduct, { product: productName }) }),
2118
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm", children: productName === null || copy.introProduct === null ? copy.intro : chunkN7E6Z2GF_js.fill(copy.introProduct, { product: productName }) }),
2101
2119
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: copy.processing }),
2102
2120
  /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-2 text-sm text-muted-foreground", children: copy.storage }),
2103
2121
  /* @__PURE__ */ jsxRuntime.jsxs(
@@ -2277,14 +2295,21 @@ function AvatarLiveView({
2277
2295
  children: lastQuestion.content
2278
2296
  }
2279
2297
  ),
2280
- lastAnswer === void 0 ? unanswered === "waiting" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: activity ?? S.gettingAnswer }) : unanswered === "failed" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: S.answerFailed }) : null : lastAnswer.status === "PENDING" || lastAnswer.status === "RUNNING" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: activity ?? S.gettingAnswer }) : lastAnswer.status === "FAILED" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: S.answerFailed }) : /* @__PURE__ */ jsxRuntime.jsx(
2281
- "p",
2282
- {
2283
- "data-boff-explore": "avatar-live-text-answer",
2284
- dir: "auto",
2285
- className: "line-clamp-6 w-full max-w-sm whitespace-pre-wrap break-words text-start text-sm",
2286
- children: lastAnswer.content
2287
- }
2298
+ lastAnswer === void 0 ? unanswered === "waiting" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: activity ?? S.gettingAnswer }) : unanswered === "failed" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: S.answerFailed }) : null : lastAnswer.status === "PENDING" || lastAnswer.status === "RUNNING" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: activity ?? S.gettingAnswer }) : lastAnswer.status === "FAILED" ? /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: S.answerFailed }) : (
2299
+ // The answer's words, not its markdown: `content` is written for the chat's renderer, and shown as it is
2300
+ // here a link reads `[label](https://…)`. In its spoken form, as the chat's read-aloud says it: a link is its
2301
+ // label (the address is in Chat, one tap away), a table its rows, and the markers of emphasis, headings,
2302
+ // quotes and list items go (`answerDisplayText`). The words are the ones the chat shows, citation markers
2303
+ // included, which the sources in Chat answer to.
2304
+ /* @__PURE__ */ jsxRuntime.jsx(
2305
+ "p",
2306
+ {
2307
+ "data-boff-explore": "avatar-live-text-answer",
2308
+ dir: "auto",
2309
+ className: "line-clamp-6 w-full max-w-sm whitespace-pre-wrap break-words text-start text-sm",
2310
+ children: chunkN7E6Z2GF_js.answerDisplayText(lastAnswer.content)
2311
+ }
2312
+ )
2288
2313
  )
2289
2314
  ] });
2290
2315
  const ended = phase.name === "ended";