@runtypelabs/persona 4.23.1 → 4.24.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 (158) hide show
  1. package/dist/animations/glyph-cycle.d.cts +1 -1
  2. package/dist/animations/glyph-cycle.d.ts +1 -1
  3. package/dist/animations/{types-9Vxm-PPz.d.cts → types-Db61aI9J.d.cts} +25 -2
  4. package/dist/animations/{types-9Vxm-PPz.d.ts → types-Db61aI9J.d.ts} +25 -2
  5. package/dist/animations/wipe.d.cts +1 -1
  6. package/dist/animations/wipe.d.ts +1 -1
  7. package/dist/animations-extra.d.cts +25 -2
  8. package/dist/animations-extra.d.ts +25 -2
  9. package/dist/approval-ui-3NMYNXBD.js +1 -0
  10. package/dist/approval-ui.cjs +2 -2
  11. package/dist/approval-ui.d.cts +261 -40
  12. package/dist/approval-ui.d.ts +261 -40
  13. package/dist/approval-ui.js +2 -2
  14. package/dist/{artifacts-ui-F4N6VMKF.js → artifacts-ui-IGB5YKLO.js} +4 -4
  15. package/dist/artifacts-ui.d.cts +256 -28
  16. package/dist/artifacts-ui.d.ts +256 -28
  17. package/dist/{chunk-NIKYKSNZ.js → chunk-7CO4T5KB.js} +1 -1
  18. package/dist/codegen.cjs +11 -11
  19. package/dist/codegen.js +13 -13
  20. package/dist/context-mentions.d.cts +256 -28
  21. package/dist/context-mentions.d.ts +256 -28
  22. package/dist/event-stream-view.d.cts +256 -28
  23. package/dist/event-stream-view.d.ts +256 -28
  24. package/dist/forms-ui.d.cts +291 -30
  25. package/dist/forms-ui.d.ts +291 -30
  26. package/dist/history-view.d.cts +25 -2
  27. package/dist/history-view.d.ts +25 -2
  28. package/dist/index.cjs +70 -69
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +337 -51
  31. package/dist/index.d.ts +337 -51
  32. package/dist/index.global.js +57 -54
  33. package/dist/index.global.js.map +1 -1
  34. package/dist/index.js +70 -69
  35. package/dist/index.js.map +1 -1
  36. package/dist/install.global.js +1 -1
  37. package/dist/install.global.js.map +1 -1
  38. package/dist/launcher.global.js +2 -2
  39. package/dist/launcher.global.js.map +1 -1
  40. package/dist/session-reconnect.d.cts +256 -28
  41. package/dist/session-reconnect.d.ts +256 -28
  42. package/dist/smart-dom-reader.d.cts +256 -28
  43. package/dist/smart-dom-reader.d.ts +256 -28
  44. package/dist/theme-editor-preview.cjs +74 -73
  45. package/dist/theme-editor-preview.d.cts +263 -28
  46. package/dist/theme-editor-preview.d.ts +263 -28
  47. package/dist/theme-editor-preview.js +51 -50
  48. package/dist/theme-editor.cjs +10 -10
  49. package/dist/theme-editor.d.cts +276 -30
  50. package/dist/theme-editor.d.ts +276 -30
  51. package/dist/theme-editor.js +10 -10
  52. package/dist/theme-reference.cjs +1 -1
  53. package/dist/theme-reference.d.cts +1522 -11
  54. package/dist/theme-reference.d.ts +1522 -11
  55. package/dist/theme-reference.js +1 -1
  56. package/dist/widget.css +1 -1
  57. package/package.json +3 -2
  58. package/src/__fixtures__/defaults-parity.v4.json +776 -0
  59. package/src/approval-ui-entry.ts +0 -1
  60. package/src/approval-ui-loader.ts +5 -4
  61. package/src/approval-ui.ts +0 -1
  62. package/src/client-identity-proof.test.ts +193 -0
  63. package/src/client-session-warm.test.ts +216 -0
  64. package/src/client-visitor-history.test.ts +6 -0
  65. package/src/client.tool-pair-replay.test.ts +325 -0
  66. package/src/client.ts +154 -61
  67. package/src/codegen.test.ts +24 -0
  68. package/src/components/activity-row.test.ts +100 -0
  69. package/src/components/activity-row.ts +136 -0
  70. package/src/components/approval-actions.test.ts +8 -5
  71. package/src/components/approval-actions.ts +19 -6
  72. package/src/components/approval-bubble.test.ts +56 -9
  73. package/src/components/approval-bubble.ts +36 -41
  74. package/src/components/composer-builder.ts +5 -2
  75. package/src/components/composer-parts.ts +10 -2
  76. package/src/components/header-builder.test.ts +32 -0
  77. package/src/components/header-builder.ts +7 -4
  78. package/src/components/header-layouts.test.ts +12 -0
  79. package/src/components/header-layouts.ts +3 -2
  80. package/src/components/launcher.ts +11 -2
  81. package/src/components/message-bubble.test.ts +22 -0
  82. package/src/components/message-bubble.ts +7 -3
  83. package/src/components/panel.test.ts +52 -1
  84. package/src/components/panel.ts +47 -8
  85. package/src/components/pill-composer-builder.test.ts +2 -0
  86. package/src/components/pill-composer-builder.ts +0 -1
  87. package/src/components/reasoning-bubble.ts +29 -17
  88. package/src/components/suggestions.test.ts +1 -0
  89. package/src/components/suggestions.ts +0 -16
  90. package/src/components/tool-bubble.ts +131 -114
  91. package/src/components/tool-details.test.ts +87 -0
  92. package/src/components/tool-details.ts +102 -0
  93. package/src/components/welcome.test.ts +8 -0
  94. package/src/components/welcome.ts +4 -2
  95. package/src/defaults-parity.test.ts +158 -0
  96. package/src/defaults.test.ts +69 -0
  97. package/src/defaults.ts +128 -49
  98. package/src/generated/runtype-openapi-contract.ts +28 -13
  99. package/src/index-core.ts +2 -0
  100. package/src/install.test.ts +24 -0
  101. package/src/install.ts +3 -1
  102. package/src/launcher-global.ts +2 -1
  103. package/src/launcher-variants.test.ts +52 -0
  104. package/src/runtime/init-update-reset.test.ts +21 -0
  105. package/src/session.ts +61 -9
  106. package/src/styles/widget-styles.test.ts +19 -0
  107. package/src/styles/widget.css +295 -23
  108. package/src/theme-editor/index.ts +2 -0
  109. package/src/theme-editor/preview-utils.test.ts +8 -0
  110. package/src/theme-editor/preview-utils.ts +7 -3
  111. package/src/theme-editor/sections.test.ts +31 -0
  112. package/src/theme-editor/sections.ts +55 -1
  113. package/src/theme-editor/state-preferences.test.ts +49 -0
  114. package/src/theme-editor/state.ts +73 -15
  115. package/src/theme-reference.test.ts +20 -0
  116. package/src/theme-reference.ts +11 -5
  117. package/src/tool-call-display-defaults.test.ts +33 -1
  118. package/src/types/theme.ts +80 -3
  119. package/src/types.ts +179 -23
  120. package/src/ui.activity-row.test.ts +91 -0
  121. package/src/ui.approval-plugin.test.ts +63 -0
  122. package/src/ui.composer-compact.test.ts +24 -2
  123. package/src/ui.composer-placement.test.ts +23 -0
  124. package/src/ui.detached-panel.test.ts +33 -6
  125. package/src/ui.docked.test.ts +19 -0
  126. package/src/ui.header-update-stability.test.ts +25 -0
  127. package/src/ui.identity-proof.test.ts +58 -0
  128. package/src/ui.launcher-teaser.test.ts +25 -0
  129. package/src/ui.message-width.test.ts +17 -2
  130. package/src/ui.reasoning-display.test.ts +92 -0
  131. package/src/ui.rebuild-composer.test.ts +20 -3
  132. package/src/ui.scroll.test.ts +32 -0
  133. package/src/ui.session-init.test.ts +312 -0
  134. package/src/ui.status-indicator.test.ts +82 -0
  135. package/src/ui.suggest-replies.test.ts +11 -0
  136. package/src/ui.ts +363 -74
  137. package/src/ui.v5-defaults.test.ts +144 -0
  138. package/src/ui.welcome.test.ts +92 -3
  139. package/src/utils/__snapshots__/tokens-parity.test.ts.snap +811 -140
  140. package/src/utils/code-generators.ts +14 -0
  141. package/src/utils/composer-compact.test.ts +0 -2
  142. package/src/utils/composer-compact.ts +2 -4
  143. package/src/utils/composer-placement.ts +2 -3
  144. package/src/utils/config-merge.test.ts +81 -5
  145. package/src/utils/config-merge.ts +13 -3
  146. package/src/utils/defaults-provenance.ts +67 -0
  147. package/src/utils/dock.ts +7 -1
  148. package/src/utils/launcher-variant.ts +16 -0
  149. package/src/utils/panel-config.ts +97 -0
  150. package/src/utils/status-indicator.ts +39 -0
  151. package/src/utils/theme.test.ts +182 -0
  152. package/src/utils/theme.ts +131 -18
  153. package/src/utils/tokens.test.ts +122 -1
  154. package/src/utils/tokens.ts +177 -17
  155. package/src/utils/tool-pair-replay.ts +100 -0
  156. package/src/welcome.test.ts +23 -0
  157. package/src/welcome.ts +16 -9
  158. package/dist/approval-ui-K7R5Q5OC.js +0 -1
@@ -78,6 +78,7 @@ export type RuntypeExecutionStreamEvent = ({
78
78
  turnIndex?: number;
79
79
  type: "turn_start";
80
80
  }) | ({
81
+ attributes?: Record<string, string | number | boolean>;
81
82
  completedAt?: string;
82
83
  content?: string;
83
84
  cost?: number;
@@ -136,7 +137,8 @@ export type RuntypeExecutionStreamEvent = ({
136
137
  };
137
138
  turnId?: string;
138
139
  type: "model_call_start";
139
- } | {
140
+ } | ({
141
+ attributes?: Record<string, string | number | boolean>;
140
142
  completedAt?: string;
141
143
  cost?: number;
142
144
  durationMs?: number;
@@ -160,10 +162,11 @@ export type RuntypeExecutionStreamEvent = ({
160
162
  cacheWrite?: number;
161
163
  input: number;
162
164
  output: number;
165
+ reasoning?: number;
163
166
  };
164
167
  turnId?: string;
165
168
  type: "model_call_complete";
166
- } | {
169
+ }) | {
167
170
  executionId: string;
168
171
  id: string;
169
172
  index?: number;
@@ -558,38 +561,50 @@ export type RuntypeStopReasonKind = NonNullable<
558
561
  RuntypeTurnCompleteEvent["stopReason"] | RuntypeStepCompleteEvent["stopReason"]
559
562
  >;
560
563
 
561
- export type RuntypeClientInitRequest = {
564
+ export type RuntypeClientInitRequest = ({
562
565
  durableRecovery?: boolean;
563
566
  flowId?: string;
564
- identityProof?: string;
567
+ identityProof?: string | {
568
+ provider: string;
569
+ token: string;
570
+ };
565
571
  token: string;
566
572
  visitorHistory?: boolean;
567
573
  visitorToken?: string;
568
- } | {
574
+ }) | ({
569
575
  durableRecovery?: boolean;
570
576
  flowId?: string;
571
- identityProof?: string;
577
+ identityProof?: string | {
578
+ provider: string;
579
+ token: string;
580
+ };
572
581
  sessionId: string;
573
582
  token: string;
574
583
  visitorHistory?: boolean;
575
584
  visitorToken?: string;
576
- } | {
585
+ }) | ({
577
586
  conversationId: string;
578
587
  durableRecovery?: boolean;
579
588
  flowId?: string;
580
- identityProof?: string;
589
+ identityProof?: string | {
590
+ provider: string;
591
+ token: string;
592
+ };
581
593
  token: string;
582
594
  visitorHistory?: boolean;
583
595
  visitorToken: string;
584
- } | {
596
+ }) | ({
585
597
  conversationId: string;
586
598
  durableRecovery?: boolean;
587
599
  flowId?: string;
588
- identityProof: string;
600
+ identityProof: string | {
601
+ provider: string;
602
+ token: string;
603
+ };
589
604
  token: string;
590
605
  visitorHistory?: boolean;
591
606
  visitorToken?: string;
592
- };
607
+ });
593
608
 
594
609
  export type RuntypeClientInitResponse = {
595
610
  app?: {
@@ -604,7 +619,7 @@ export type RuntypeClientInitResponse = {
604
619
  conversationRevision: string;
605
620
  durableRecovery?: {
606
621
  enabled: boolean;
607
- join?: boolean;
622
+ steer?: boolean;
608
623
  };
609
624
  expiresAt: string;
610
625
  flow?: {
@@ -666,7 +681,7 @@ export type RuntypeClientChatRequest = {
666
681
  }>;
667
682
  metadata?: Record<string, unknown>;
668
683
  sessionId: string;
669
- submitMode?: "normal" | "interrupt" | "join";
684
+ submitMode?: "normal" | "interrupt" | "steer";
670
685
  turnId?: string;
671
686
  };
672
687
 
package/src/index-core.ts CHANGED
@@ -9,6 +9,8 @@ export type {
9
9
  AgentWidgetStorageAdapter,
10
10
  AgentWidgetStoredState,
11
11
  AgentWidgetConfigPatch,
12
+ AgentWidgetSessionInitTrigger,
13
+ AgentWidgetSessionInitHook,
12
14
  TargetResolver,
13
15
  ResolvedTarget,
14
16
  AgentWidgetFeatureFlags,
@@ -226,6 +226,30 @@ describe("install.ts: deferred launcher path", () => {
226
226
 
227
227
  expect(initAgentWidget).toHaveBeenCalledTimes(1);
228
228
  });
229
+
230
+ it.each([false, true])("passes future.v5Defaults=%s through the deferred launcher handoff", async (v5Defaults) => {
231
+ markCssLoaded();
232
+ provideFullBundle();
233
+ provideLauncherBundle();
234
+
235
+ const variant = v5Defaults ? "pill" : "circle";
236
+ await install({
237
+ config: { apiUrl: "/api", launcher: { variant } },
238
+ future: { v5Defaults },
239
+ });
240
+ await flush();
241
+
242
+ expect(launcherMount).toHaveBeenCalledWith(expect.objectContaining({
243
+ config: expect.objectContaining({ future: { v5Defaults }, launcher: expect.objectContaining({ variant }) }),
244
+ }));
245
+
246
+ capturedOnOpen!();
247
+ await flush();
248
+
249
+ expect(initAgentWidget).toHaveBeenCalledWith(expect.objectContaining({
250
+ config: expect.objectContaining({ future: { v5Defaults }, launcher: expect.objectContaining({ variant }) }),
251
+ }));
252
+ });
229
253
  });
230
254
 
231
255
  describe("install.ts: eager path", () => {
package/src/install.ts CHANGED
@@ -19,6 +19,8 @@ interface SiteAgentInstallConfig {
19
19
  jsUrl?: string;
20
20
  target?: string | HTMLElement;
21
21
  config?: any;
22
+ /** Opt in to the staged v5 defaults when using top-level installer options. */
23
+ future?: { v5Defaults?: boolean };
22
24
  autoInit?: boolean;
23
25
  // Client token mode options (can also be set via data attributes)
24
26
  clientToken?: string;
@@ -447,6 +449,7 @@ declare global {
447
449
  if (config.clientToken && !widgetConfig.clientToken) widgetConfig.clientToken = config.clientToken;
448
450
  if (config.flowId && !widgetConfig.flowId) widgetConfig.flowId = config.flowId;
449
451
  if (config.agentId && !widgetConfig.agentId) widgetConfig.agentId = config.agentId;
452
+ if (config.future && !widgetConfig.future) widgetConfig.future = config.future;
450
453
 
451
454
  const hasApiConfig = !!(widgetConfig.apiUrl || widgetConfig.clientToken);
452
455
  return { target, widgetConfig, hasApiConfig };
@@ -631,4 +634,3 @@ declare global {
631
634
  // This prevents Next.js/Nuxt/etc. from removing dynamically added CSS
632
635
  waitForHydration(install);
633
636
  })();
634
-
@@ -22,7 +22,7 @@
22
22
  */
23
23
  import { createLauncherSurface } from "./components/launcher";
24
24
  import { applyThemeVariables } from "./utils/theme";
25
- import { DEFAULT_LAUNCHER_CONFIG } from "./defaults";
25
+ import { DEFAULT_LAUNCHER_CONFIG, DEFAULT_LAUNCHER_V5 } from "./defaults";
26
26
  import { setIconsExtraLoader } from "./icons-extra-loader";
27
27
  import type { AgentWidgetConfig } from "./types";
28
28
 
@@ -94,6 +94,7 @@ const mergeCriticalLauncherConfig = (
94
94
  ...config,
95
95
  launcher: {
96
96
  ...DEFAULT_LAUNCHER_CONFIG,
97
+ ...(config?.future?.v5Defaults === true ? DEFAULT_LAUNCHER_V5 : {}),
97
98
  ...config?.launcher,
98
99
  dock: {
99
100
  ...DEFAULT_LAUNCHER_CONFIG.dock,
@@ -0,0 +1,52 @@
1
+ // @vitest-environment jsdom
2
+ import { afterEach, describe, expect, it, vi } from "vitest";
3
+ import { createAgentExperience } from "./ui";
4
+ import { mount as mountCritical } from "./launcher-global";
5
+ import { getActiveTheme, themeToCssVariables } from "./utils/theme";
6
+ import type { AgentWidgetConfig } from "./types";
7
+
8
+ afterEach(() => { document.body.replaceChildren(); vi.restoreAllMocks(); });
9
+
10
+ describe.each([false, true])("launcher variants (v5=%s)", (v5Defaults) => {
11
+ it.each([undefined, "pill", "circle"] as const)("matches deferred and full rendering for %s", (variant) => {
12
+ window.scrollTo = vi.fn();
13
+ const config: AgentWidgetConfig = {
14
+ apiUrl: "/dispatch", persistState: false, future: { v5Defaults },
15
+ launcher: { ...(variant ? { variant } : {}), title: "Open support", teaser: { text: "Need help?" } },
16
+ };
17
+ const host = document.body.appendChild(document.createElement("div"));
18
+ const target = document.body.appendChild(document.createElement("div"));
19
+ const full = createAgentExperience(host, config);
20
+ const critical = mountCritical({ target, config, onOpen: vi.fn() });
21
+ const button = host.querySelector<HTMLButtonElement>(".persona-launcher-surface > button")!;
22
+ try {
23
+ const circle = variant ? variant === "circle" : v5Defaults;
24
+ expect(button.dataset.personaLauncherVariant).toBe(circle ? "circle" : undefined);
25
+ expect(button.innerHTML).toBe(critical.element.innerHTML);
26
+ expect(button.getAttribute("style")).toBe(critical.element.getAttribute("style"));
27
+ expect(host.style.getPropertyValue("--persona-launcher-bg"))
28
+ .toBe(critical.root.style.getPropertyValue("--persona-launcher-bg"));
29
+ if (circle) expect(button.getAttribute("aria-label")).toBe("Open support");
30
+ full.update({ launcher: { variant: circle ? "pill" : "circle" } });
31
+ critical.update({ ...config, launcher: { ...config.launcher, variant: circle ? "pill" : "circle" } });
32
+ expect(button.dataset.personaLauncherVariant).toBe(circle ? undefined : "circle");
33
+ expect(button.innerHTML).toBe(critical.element.innerHTML);
34
+ } finally { full.destroy(); critical.destroy(); }
35
+ });
36
+
37
+ it.each(["light", "dark"] as const)("resolves circle geometry and explicit tokens in %s", colorScheme => {
38
+ const config: AgentWidgetConfig = { future: { v5Defaults }, colorScheme, launcher: { variant: "circle" } };
39
+ const css = themeToCssVariables(getActiveTheme(config));
40
+ expect(css["--persona-components-launcher-size"]).toBe("48px");
41
+ expect(css["--persona-components-launcher-offset"]).toBe("20px");
42
+ expect(css["--persona-launcher-shadow"]).toBe("0 8px 24px rgba(0,0,0,.16)");
43
+ expect(css["--persona-launcher-bg"]).not.toBe(css["--persona-launcher-fg"]);
44
+ const custom = themeToCssVariables(getActiveTheme({ ...config, theme: { components: { launcher: {
45
+ size: "56px", iconSize: "20px", offset: "32px", background: "#123456", foreground: "#ffffff", shadow: "none",
46
+ } } } }));
47
+ expect(custom["--persona-components-launcher-size"]).toBe("56px");
48
+ expect(custom["--persona-components-launcher-offset"]).toBe("32px");
49
+ expect(custom["--persona-launcher-bg"]).toBe("#123456");
50
+ expect(custom["--persona-launcher-shadow"]).toBe("none");
51
+ });
52
+ });
@@ -78,4 +78,25 @@ describe("handle.update explicit-undefined reset reaches the controller", () =>
78
78
 
79
79
  handle.destroy();
80
80
  });
81
+
82
+ it("updates the root defaults-version attribute", () => {
83
+ window.scrollTo = vi.fn();
84
+ document.body.innerHTML = `<div id="target"></div>`;
85
+
86
+ const handle = initAgentWidget({
87
+ target: "#target",
88
+ config: {
89
+ apiUrl: "https://api.example.com/chat",
90
+ launcher: { enabled: false },
91
+ future: { v5Defaults: false },
92
+ },
93
+ });
94
+ const root = () => document.querySelector<HTMLElement>("[data-persona-root]")!;
95
+
96
+ expect(root().getAttribute("data-persona-defaults")).toBe("v4");
97
+ handle.update({ future: { v5Defaults: true } });
98
+ expect(root().getAttribute("data-persona-defaults")).toBe("v5");
99
+
100
+ handle.destroy();
101
+ });
81
102
  });
package/src/session.ts CHANGED
@@ -144,6 +144,7 @@ const CONNECTION_CONFIG_KEYS = [
144
144
  "getStoredSessionId",
145
145
  "setStoredSessionId",
146
146
  "getIdentityProof",
147
+ "identityProvider",
147
148
  "getStoredConversationId",
148
149
  "setStoredConversationId",
149
150
  "clearStoredConversationId",
@@ -1033,12 +1034,29 @@ export class AgentWidgetSession {
1033
1034
  }
1034
1035
  }
1035
1036
 
1037
+ /**
1038
+ * Early, fire-and-forget client-token init (`sessionInit` trigger or
1039
+ * `controller.initSession()`). Failures stay silent here: no `onError`, no
1040
+ * UI. The send re-runs `initSession()` and reports them as it always has.
1041
+ */
1042
+ public warmClientSession(): void {
1043
+ // The visitor may send before this resolves; the server welcome still
1044
+ // belongs ahead of that first turn when the transcript started empty.
1045
+ const pristine = !this.messages.length;
1046
+ void this.client.warmSession().then((session) => {
1047
+ // A start-new or credential re-init may have replaced it meanwhile.
1048
+ if (session && this.client.getClientSession() === session) {
1049
+ this.setClientSession(session, pristine);
1050
+ }
1051
+ });
1052
+ }
1053
+
1036
1054
  /**
1037
1055
  * Set the client session after initialization
1038
1056
  */
1039
- public setClientSession(session: ClientSession): void {
1057
+ public setClientSession(session: ClientSession, pristine?: boolean): void {
1040
1058
  this.clientSession = session;
1041
- this.injectWelcomeMessage(session);
1059
+ this.injectWelcomeMessage(session, pristine);
1042
1060
  }
1043
1061
 
1044
1062
  /**
@@ -1046,15 +1064,20 @@ export class AgentWidgetSession {
1046
1064
  * reopening a stored conversation is not a new thread, and the hydrated
1047
1065
  * transcript would otherwise race a welcome bubble.
1048
1066
  */
1049
- private injectWelcomeMessage(session: ClientSession): void {
1067
+ private injectWelcomeMessage(
1068
+ session: ClientSession,
1069
+ pristine = !this.messages.length
1070
+ ): void {
1050
1071
  if (this.suppressWelcomeInjection) return;
1051
- if (!session.config.welcomeMessage || this.messages.length !== 0) return;
1072
+ if (!session.config.welcomeMessage || !pristine) return;
1073
+ // A late welcome (the first send raced the init) sorts ahead of that turn.
1074
+ const first = this.messages[0];
1052
1075
  const welcomeMessage: AgentWidgetMessage = {
1053
1076
  id: `welcome-${Date.now()}`,
1054
1077
  role: "assistant",
1055
1078
  content: session.config.welcomeMessage,
1056
- createdAt: new Date().toISOString(),
1057
- sequence: this.nextSequence()
1079
+ createdAt: first?.createdAt ?? new Date().toISOString(),
1080
+ sequence: first ? (first.sequence ?? 0) - 1 : this.nextSequence()
1058
1081
  };
1059
1082
  this.appendMessage(welcomeMessage);
1060
1083
  }
@@ -3816,16 +3839,34 @@ export class AgentWidgetSession {
3816
3839
  // re-emits don't re-execute the page tool, then complete each bubble.
3817
3840
  // Do this only after /resume HTTP success; if /resume fails, the server
3818
3841
  // may still be paused and the retry path must not show a final result.
3842
+ const batch = ready[0]!.dedupeKey;
3819
3843
  for (const r of ready) {
3820
3844
  this.webMcpResolvedKeys.add(r.dedupeKey);
3845
+ const toolName = r.toolMessage.toolCall?.name;
3846
+ const toolCallId = r.toolMessage.agentMetadata?.webMcpToolCallId;
3821
3847
  this.markWebMcpToolComplete(
3822
3848
  r.toolMessage,
3823
3849
  r.output,
3824
3850
  r.startedAt,
3825
3851
  r.completedAt,
3826
- r.toolMessage.toolCall?.name === SUGGEST_REPLIES_TOOL_NAME
3827
- ? { suggestRepliesResolved: true }
3828
- : undefined,
3852
+ {
3853
+ ...(toolName === SUGGEST_REPLIES_TOOL_NAME
3854
+ ? { suggestRepliesResolved: true }
3855
+ : {}),
3856
+ // Only a provider call id can be replayed: a legacy name-keyed
3857
+ // resume has nothing the model's transcript can reference.
3858
+ ...(toolName && toolCallId
3859
+ ? {
3860
+ clientToolAnswer: {
3861
+ toolCallId,
3862
+ toolName,
3863
+ args: r.toolMessage.toolCall?.args,
3864
+ result: r.output,
3865
+ batch,
3866
+ },
3867
+ }
3868
+ : {}),
3869
+ },
3829
3870
  );
3830
3871
  }
3831
3872
  if (response.body) {
@@ -4928,6 +4969,17 @@ export class AgentWidgetSession {
4928
4969
  awaitingLocalTool: false,
4929
4970
  };
4930
4971
  }
4972
+ // The accepted client-tool answer is what later proxy/agent turns
4973
+ // replay; a post-resume `tool_complete` re-emit carries a fresh
4974
+ // metadata skeleton and must not drop it.
4975
+ const clientToolAnswer = existing.agentMetadata?.clientToolAnswer;
4976
+ if (
4977
+ clientToolAnswer &&
4978
+ merged.agentMetadata &&
4979
+ !merged.agentMetadata.clientToolAnswer
4980
+ ) {
4981
+ merged.agentMetadata = { ...merged.agentMetadata, clientToolAnswer };
4982
+ }
4931
4983
  // Approval equivalent: `approval_complete` carries only the
4932
4984
  // resolution (approvalId, decision, resolvedBy): the runtime does not
4933
4985
  // re-send toolName/description/toolType/reason/parameters, so client.ts
@@ -189,6 +189,25 @@ describe("suggestion interaction styles", () => {
189
189
  });
190
190
  });
191
191
 
192
+ describe("collapsible widget chrome styles", () => {
193
+ it("uses prefixed aliases while retaining each legacy alias and literal fallback", () => {
194
+ const expectedDeclarations: Array<[string, number]> = [
195
+ ["var(--persona-cw-border, var(--cw-border, #e5e7eb))", 2],
196
+ ["var(--persona-cw-container, var(--cw-container, #f9fafb))", 1],
197
+ ["var(--persona-cw-surface, var(--cw-surface, #ffffff))", 2],
198
+ ["var(--persona-cw-border, var(--cw-border, #f1f5f9))", 1],
199
+ ["var(--persona-cw-container, var(--cw-container, #f8fafc))", 1],
200
+ ["var(--persona-cw-border, var(--cw-border, #d1d5db))", 1],
201
+ ["var(--persona-cw-surface, var(--cw-surface, #fff))", 2],
202
+ ];
203
+
204
+ for (const [declaration, count] of expectedDeclarations) {
205
+ expect(widgetCss).toContain(declaration);
206
+ expect(widgetCss.split(declaration).length - 1).toBe(count);
207
+ }
208
+ });
209
+ });
210
+
192
211
  describe("composer spacing styles", () => {
193
212
  it("themes the composer form padding and gap with utility-matching fallbacks", () => {
194
213
  const selector =