@agentforge-io/chat-sdk 2.5.0 → 2.5.1

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 (2) hide show
  1. package/dist/react.js +58 -48
  2. package/package.json +1 -1
package/dist/react.js CHANGED
@@ -563,28 +563,34 @@ function ChatWidget(props) {
563
563
  }
564
564
  visibleIdxs.push(i);
565
565
  }
566
- // Drop the empty intermediaries: every assistant bubble
567
- // that has no content AND whose chain ends at a later
568
- // bubble is hidden. The chain head will collect their
569
- // identities and surface them as an avatar group.
570
- const lastInChain = new Map(); // head -> last idx
571
- for (const i of visibleIdxs) {
572
- if (messages[i].role === 'assistant') {
573
- lastInChain.set(chainHeadFor[i], i);
574
- }
566
+ // Find the index of the next assistant message after each
567
+ // position. Used to decide whether an empty bubble has been
568
+ // superseded by a later speaker (in which case the visitor
569
+ // should not still see it typing).
570
+ const nextAssistantIdx = new Array(messages.length).fill(-1);
571
+ for (let k = messages.length - 2; k >= 0; k--) {
572
+ const nextI = k + 1;
573
+ nextAssistantIdx[k] =
574
+ messages[nextI].role === 'assistant'
575
+ ? nextI
576
+ : nextAssistantIdx[nextI];
575
577
  }
576
578
  const finalIdxs = visibleIdxs.filter((i) => {
577
579
  const m = messages[i];
578
580
  if (m.role !== 'assistant')
579
581
  return true;
580
- const head = chainHeadFor[i];
581
- const last = lastInChain.get(head);
582
- // Hide intermediates: assistant bubbles that are part of
583
- // a chain whose LAST element is a later bubble AND that
584
- // are themselves empty. The last bubble in the chain
585
- // always renders; non-empty intermediates also render so
586
- // we never lose visible text.
587
- if (last !== undefined && i !== last && !m.content)
582
+ // Any empty assistant bubble that has been overtaken by
583
+ // a LATER assistant bubble is an intermediate — hide it.
584
+ // Covers two cases at once:
585
+ // 1. The classic "chain head/intermediate" path: bubble
586
+ // i is empty and there's a final bubble after it.
587
+ // 2. The "live delegation" path: while a chain streams,
588
+ // the previous member sits at content='' isStreaming=
589
+ // true (the SDK only flips isStreaming when the
590
+ // bubble produced text). A naive filter would keep
591
+ // showing its typing dots; we drop it as soon as
592
+ // a fresher speaker exists.
593
+ if (!m.content && nextAssistantIdx[i] !== -1)
588
594
  return false;
589
595
  return true;
590
596
  });
@@ -612,37 +618,41 @@ function ChatWidget(props) {
612
618
  let chainParticipants;
613
619
  if (isAssistant && showAvatar) {
614
620
  const head = chainHeadFor[i];
615
- const last = lastInChain.get(head);
616
- if (last === i && head !== i) {
617
- // Collect all distinct speakers in [head…i] in
618
- // chronological order, then surface them with the
619
- // active one (the last) at the front.
620
- const seen = new Set();
621
- const ordered = [];
622
- for (let j = head; j <= i; j++) {
623
- const mj = messages[j];
624
- if (mj.role !== 'assistant')
625
- continue;
626
- const key = mj.actingAgentId ?? null;
627
- if (seen.has(key))
628
- continue;
629
- seen.add(key);
630
- const mem = mj.actingAgentId
631
- ? membersById.get(mj.actingAgentId)
632
- : undefined;
633
- ordered.push({
634
- agentId: mj.actingAgentId ?? agent?.slug,
635
- name: mem?.name ?? personaName ?? agent?.name,
636
- theme: mem
637
- ? { ...theme, avatarUrl: mem.avatarUrl }
638
- : theme,
639
- });
640
- }
641
- if (ordered.length > 1) {
642
- // Move the LAST (active speaker) to the front.
643
- const active = ordered.pop();
644
- chainParticipants = [active, ...ordered.reverse()];
645
- }
621
+ // Collect all distinct speakers in [head…i] in
622
+ // chronological order. The bubble that's rendering now
623
+ // (i) is the most recent speaker so it becomes the
624
+ // "active" one at the front of the stack; everything
625
+ // earlier in the chain trails behind in reverse-
626
+ // chronological order. Works the same whether the
627
+ // chain has finished or is still streaming — visitor
628
+ // sees one bubble with a live-updating header.
629
+ const seen = new Set();
630
+ const ordered = [];
631
+ for (let j = head; j <= i; j++) {
632
+ const mj = messages[j];
633
+ if (mj.role !== 'assistant')
634
+ continue;
635
+ const key = mj.actingAgentId ?? null;
636
+ if (seen.has(key))
637
+ continue;
638
+ seen.add(key);
639
+ const mem = mj.actingAgentId
640
+ ? membersById.get(mj.actingAgentId)
641
+ : undefined;
642
+ ordered.push({
643
+ agentId: mj.actingAgentId ?? agent?.slug,
644
+ name: mem?.name ?? personaName ?? agent?.name,
645
+ theme: mem
646
+ ? { ...theme, avatarUrl: mem.avatarUrl }
647
+ : theme,
648
+ });
649
+ }
650
+ if (ordered.length > 1) {
651
+ // The visible bubble's speaker (i) sits at the END of
652
+ // `ordered`. Move them to the front so the active
653
+ // avatar leads the stack.
654
+ const active = ordered.pop();
655
+ chainParticipants = [active, ...ordered.reverse()];
646
656
  }
647
657
  }
648
658
  return ((0, jsx_runtime_1.jsx)(MessageBubble, { message: m, session: session, readOnly: readOnlyApprovals, onDecision: onApprovalDecision, bare: bare, showAvatar: showAvatar, avatarTheme: bubbleAvatarTheme, avatarName: bubbleAvatarName, avatarAgentId: bubbleAgentId, speakerLabel: member?.name, chainParticipants: chainParticipants, onContinue: () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentforge-io/chat-sdk",
3
- "version": "2.5.0",
3
+ "version": "2.5.1",
4
4
  "description": "Framework-free chat session SDK for AgentForge public chat tokens. Headless — no DOM. Drop into any frontend (React, Vue, Svelte, vanilla) and listen for events.",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",