@runtypelabs/persona 4.21.0 → 4.22.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 (78) hide show
  1. package/dist/{approval-ui-RNNBR5R3.js → approval-ui-K7R5Q5OC.js} +1 -1
  2. package/dist/approval-ui.cjs +2 -2
  3. package/dist/approval-ui.d.cts +73 -2
  4. package/dist/approval-ui.d.ts +73 -2
  5. package/dist/approval-ui.js +2 -2
  6. package/dist/artifacts-ui-F4N6VMKF.js +7 -0
  7. package/dist/artifacts-ui.cjs +1 -1
  8. package/dist/artifacts-ui.d.cts +73 -2
  9. package/dist/artifacts-ui.d.ts +73 -2
  10. package/dist/artifacts-ui.js +1 -1
  11. package/dist/chunk-NQLDCJAP.js +4 -0
  12. package/dist/codegen.cjs +1 -1
  13. package/dist/codegen.js +1 -1
  14. package/dist/context-mentions.d.cts +73 -2
  15. package/dist/context-mentions.d.ts +73 -2
  16. package/dist/event-stream-view-FKWDY3UB.js +1 -0
  17. package/dist/event-stream-view.cjs +1 -1
  18. package/dist/event-stream-view.d.cts +73 -2
  19. package/dist/event-stream-view.d.ts +73 -2
  20. package/dist/event-stream-view.js +1 -1
  21. package/dist/forms-ui.d.cts +73 -2
  22. package/dist/forms-ui.d.ts +73 -2
  23. package/dist/index.cjs +66 -66
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +136 -5
  26. package/dist/index.d.ts +136 -5
  27. package/dist/index.global.js +50 -50
  28. package/dist/index.global.js.map +1 -1
  29. package/dist/index.js +67 -67
  30. package/dist/index.js.map +1 -1
  31. package/dist/launcher.global.js +2 -2
  32. package/dist/launcher.global.js.map +1 -1
  33. package/dist/session-reconnect.d.cts +73 -2
  34. package/dist/session-reconnect.d.ts +73 -2
  35. package/dist/smart-dom-reader.d.cts +73 -2
  36. package/dist/smart-dom-reader.d.ts +73 -2
  37. package/dist/theme-editor-preview.cjs +65 -65
  38. package/dist/theme-editor-preview.d.cts +73 -2
  39. package/dist/theme-editor-preview.d.ts +73 -2
  40. package/dist/theme-editor-preview.js +49 -49
  41. package/dist/theme-editor.cjs +4 -4
  42. package/dist/theme-editor.d.cts +73 -2
  43. package/dist/theme-editor.d.ts +73 -2
  44. package/dist/theme-editor.js +2 -2
  45. package/dist/theme-reference.cjs +1 -1
  46. package/dist/theme-reference.d.cts +65 -1
  47. package/dist/theme-reference.d.ts +65 -1
  48. package/dist/theme-reference.js +1 -1
  49. package/dist/webmcp-polyfill.js +1 -4
  50. package/dist/webmcp-runtime-JI7T4QYF.js +6 -0
  51. package/dist/webmcp-runtime.cjs +2 -2
  52. package/dist/webmcp-runtime.d.cts +34 -2
  53. package/dist/webmcp-runtime.d.ts +34 -2
  54. package/dist/webmcp-runtime.js +2 -2
  55. package/package.json +2 -2
  56. package/src/client-visitor-history.test.ts +131 -0
  57. package/src/components/artifact-preview.ts +5 -0
  58. package/src/components/event-stream-badge-theming.test.ts +159 -0
  59. package/src/components/event-stream-view.ts +50 -20
  60. package/src/generated/runtype-openapi-contract.ts +8 -0
  61. package/src/index-core.ts +2 -0
  62. package/src/runtime/init.registry.test.ts +56 -0
  63. package/src/runtime/init.ts +17 -0
  64. package/src/types/theme.ts +41 -1
  65. package/src/types.ts +38 -1
  66. package/src/ui.reasoning-display.test.ts +136 -2
  67. package/src/ui.ts +69 -21
  68. package/src/utils/__snapshots__/tokens-parity.test.ts.snap +1740 -0
  69. package/src/utils/formatting.ts +22 -0
  70. package/src/utils/theme.ts +16 -0
  71. package/src/utils/tokens.ts +59 -0
  72. package/src/webmcp-bridge.test.ts +132 -23
  73. package/src/webmcp-bridge.ts +26 -2
  74. package/src/webmcp-runtime-entry.ts +71 -11
  75. package/dist/artifacts-ui-J6AKUGTS.js +0 -7
  76. package/dist/chunk-HBE7LFNT.js +0 -4
  77. package/dist/event-stream-view-73NWKWVQ.js +0 -1
  78. package/dist/webmcp-runtime-P3CHZRHF.js +0 -6
@@ -0,0 +1,159 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import {
3
+ DEFAULT_BADGE_COLORS,
4
+ getBadgeBorderColor,
5
+ getBadgeColor,
6
+ } from "./event-stream-view";
7
+ import { createDarkTheme, createLightTheme, themeToCssVariables } from "../utils/theme";
8
+
9
+ /**
10
+ * Contrast lock for the event-stream badge chips. The chip paints inline
11
+ * `var(--persona-event-badge-*)` styles, so the pairs the theme pipeline emits
12
+ * ARE the rendered colors inside any themed root; asserting on the emitted
13
+ * variables is equivalent to a rendered-computed-style check without a
14
+ * browser. Both schemes must hold every family at >= 4.5:1 (WCAG AA for the
15
+ * 11px badge text).
16
+ */
17
+
18
+ const FAMILIES = [
19
+ "flow",
20
+ "step",
21
+ "reasoning",
22
+ "tool",
23
+ "agent",
24
+ "error",
25
+ "default",
26
+ ] as const;
27
+
28
+ const HEX6 = /^#[0-9a-fA-F]{6}$/;
29
+
30
+ function channelLuminance(byte: number): number {
31
+ const c = byte / 255;
32
+ return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
33
+ }
34
+
35
+ function relativeLuminance(hex: string): number {
36
+ const r = channelLuminance(parseInt(hex.slice(1, 3), 16));
37
+ const g = channelLuminance(parseInt(hex.slice(3, 5), 16));
38
+ const b = channelLuminance(parseInt(hex.slice(5, 7), 16));
39
+ return 0.2126 * r + 0.7152 * g + 0.0722 * b;
40
+ }
41
+
42
+ function contrastRatio(a: string, b: string): number {
43
+ const la = relativeLuminance(a);
44
+ const lb = relativeLuminance(b);
45
+ const [hi, lo] = la >= lb ? [la, lb] : [lb, la];
46
+ return (hi + 0.05) / (lo + 0.05);
47
+ }
48
+
49
+ const SCHEMES = {
50
+ light: () => themeToCssVariables(createLightTheme()),
51
+ dark: () => themeToCssVariables(createDarkTheme()),
52
+ } as const;
53
+
54
+ describe("event-stream badge theming", () => {
55
+ for (const [scheme, buildVars] of Object.entries(SCHEMES)) {
56
+ it(`${scheme} scheme emits a >=4.5:1 pair for every badge family`, () => {
57
+ const vars = buildVars();
58
+ for (const family of FAMILIES) {
59
+ const bg = vars[`--persona-event-badge-${family}-bg`];
60
+ const fg = vars[`--persona-event-badge-${family}-fg`];
61
+ expect(bg, `${family} bg`).toMatch(HEX6);
62
+ expect(fg, `${family} fg`).toMatch(HEX6);
63
+ const ratio = contrastRatio(bg, fg);
64
+ expect(
65
+ ratio,
66
+ `${scheme} ${family}: ${fg} on ${bg} is ${ratio.toFixed(2)}:1`
67
+ ).toBeGreaterThanOrEqual(4.5);
68
+ }
69
+ });
70
+ }
71
+
72
+ it("light pairs keep the pre-token rendered colors", () => {
73
+ // The 4.x chips resolved these exact values inside a themed root; the
74
+ // token pipeline must reproduce them so light mode is unchanged.
75
+ const vars = SCHEMES.light();
76
+ expect(vars["--persona-event-badge-flow-bg"]).toBe("#dcfce7");
77
+ expect(vars["--persona-event-badge-flow-fg"]).toBe("#15803d");
78
+ expect(vars["--persona-event-badge-tool-bg"]).toBe("#f3e8ff");
79
+ expect(vars["--persona-event-badge-tool-fg"]).toBe("#6b21a8");
80
+ expect(vars["--persona-event-badge-agent-bg"]).toBe("#ccfbf1");
81
+ expect(vars["--persona-event-badge-agent-fg"]).toBe("#115e59");
82
+ expect(vars["--persona-event-badge-default-bg"]).toBe("#f3f4f6");
83
+ expect(vars["--persona-event-badge-default-fg"]).toBe("#4b5563");
84
+ });
85
+
86
+ it("dark pairs differ from light for every family", () => {
87
+ const light = SCHEMES.light();
88
+ const dark = SCHEMES.dark();
89
+ for (const family of FAMILIES) {
90
+ expect(
91
+ dark[`--persona-event-badge-${family}-bg`],
92
+ `${family} bg`
93
+ ).not.toBe(light[`--persona-event-badge-${family}-bg`]);
94
+ }
95
+ });
96
+
97
+ it("every default badge value references a variable both schemes emit", () => {
98
+ const light = SCHEMES.light();
99
+ const dark = SCHEMES.dark();
100
+ const pairs = [...Object.values(DEFAULT_BADGE_COLORS), getBadgeColor("unmatched_type")];
101
+ for (const pair of pairs) {
102
+ for (const value of [pair.bg, pair.text]) {
103
+ const match = /^var\((--persona-event-badge-[a-z]+-(?:bg|fg)), #[0-9a-f]{6}\)$/.exec(
104
+ value
105
+ );
106
+ expect(match, value).not.toBeNull();
107
+ const varName = match![1];
108
+ expect(light[varName], `${varName} (light)`).toBeDefined();
109
+ expect(dark[varName], `${varName} (dark)`).toBeDefined();
110
+ }
111
+ }
112
+ });
113
+
114
+ it("routes event types to families: prefixes, reasoning, and execution_error", () => {
115
+ expect(getBadgeColor("flow_start")).toBe(DEFAULT_BADGE_COLORS.flow_);
116
+ expect(getBadgeColor("tool_input_delta")).toBe(DEFAULT_BADGE_COLORS.tool_);
117
+ expect(getBadgeColor("agent_turn_start")).toBe(DEFAULT_BADGE_COLORS.agent_);
118
+ // reasoning_* must hit the reasoning family (the old `reason_` prefix
119
+ // never matched `reasoning_start` and dropped it to the default pair).
120
+ expect(getBadgeColor("reasoning_start")).toBe(DEFAULT_BADGE_COLORS.reasoning_);
121
+ expect(getBadgeColor("error")).toBe(DEFAULT_BADGE_COLORS.error);
122
+ expect(getBadgeColor("execution_error")).toBe(DEFAULT_BADGE_COLORS.error);
123
+ });
124
+
125
+ it("custom badgeColors: exact match beats prefix match", () => {
126
+ const exact = { bg: "#111111", text: "#eeeeee" };
127
+ const prefix = { bg: "#222222", text: "#dddddd" };
128
+ const custom = { tool_: prefix, ["tool_start"]: exact };
129
+ expect(getBadgeColor("tool_start", custom)).toBe(exact);
130
+ expect(getBadgeColor("tool_complete", custom)).toBe(prefix);
131
+ });
132
+
133
+ it("custom badgeColors: any custom match beats any default match", () => {
134
+ const custom = { execution_: { bg: "#222222", text: "#dddddd" } };
135
+ // A host prefix must cover its whole family: the built-in exact
136
+ // `execution_error` entry sits in the lower default tier.
137
+ expect(getBadgeColor("execution_error", custom)).toBe(custom.execution_);
138
+ expect(getBadgeColor("execution_start", custom)).toBe(custom.execution_);
139
+ // A narrower custom prefix wins over the default family prefix (the old
140
+ // merged-map lookup iterated the default `tool_` key first).
141
+ const narrowPair = { bg: "#333333", text: "#cccccc" };
142
+ expect(getBadgeColor("tool_input_delta", { ["tool_input_"]: narrowPair })).toBe(narrowPair);
143
+ // Unmatched custom maps still fall through to the default families.
144
+ expect(getBadgeColor("flow_start", custom)).toBe(DEFAULT_BADGE_COLORS.flow_);
145
+ });
146
+
147
+ it("border composition is valid CSS for hex and non-hex text colors", () => {
148
+ // Bare 6-digit hex keeps the historical alpha suffix.
149
+ expect(getBadgeBorderColor("#6b21a8")).toBe("#6b21a850");
150
+ // var() refs (every default) and other CSS colors must compose via
151
+ // color-mix; a bare suffix would produce an invalid, dropped declaration.
152
+ expect(getBadgeBorderColor("var(--persona-event-badge-tool-fg, #6b21a8)")).toBe(
153
+ "color-mix(in srgb, var(--persona-event-badge-tool-fg, #6b21a8) 31%, transparent)"
154
+ );
155
+ expect(getBadgeBorderColor("rgb(107, 33, 168)")).toBe(
156
+ "color-mix(in srgb, rgb(107, 33, 168) 31%, transparent)"
157
+ );
158
+ });
159
+ });
@@ -32,17 +32,25 @@ function applyCustomClasses(el: HTMLElement, classes?: string): void {
32
32
  // Constants
33
33
  // ============================================================================
34
34
 
35
- const DEFAULT_BADGE_COLORS: Record<string, EventStreamBadgeColor> = {
36
- flow_: { bg: "var(--persona-palette-colors-success-100, #dcfce7)", text: "var(--persona-palette-colors-success-700, #166534)" },
37
- step_: { bg: "var(--persona-palette-colors-primary-100, #f5f5f5)", text: "var(--persona-palette-colors-primary-700, #0a0a0a)" },
38
- reason_: { bg: "var(--persona-palette-colors-warning-100, #ffedd5)", text: "var(--persona-palette-colors-warning-700, #9a3412)" },
39
- tool_: { bg: "var(--persona-palette-colors-purple-100, #f3e8ff)", text: "var(--persona-palette-colors-purple-700, #6b21a8)" },
40
- agent_: { bg: "var(--persona-palette-colors-teal-100, #ccfbf1)", text: "var(--persona-palette-colors-teal-700, #115e59)" },
41
- error: { bg: "var(--persona-palette-colors-error-100, #fecaca)", text: "var(--persona-palette-colors-error-700, #991b1b)" },
35
+ // Badge families resolve through the theme pipeline: the aliases below are
36
+ // materialized into every theme (light pairs in DEFAULT_COMPONENTS, dark pairs
37
+ // in the built-in dark layer of createDarkTheme), so the chip follows the
38
+ // active color scheme and re-resolves live on scheme toggles. The literal
39
+ // fallbacks are the light pairs, for direct mounts outside a themed root.
40
+ export const DEFAULT_BADGE_COLORS: Record<string, EventStreamBadgeColor> = {
41
+ flow_: { bg: "var(--persona-event-badge-flow-bg, #dcfce7)", text: "var(--persona-event-badge-flow-fg, #15803d)" },
42
+ step_: { bg: "var(--persona-event-badge-step-bg, #f5f5f5)", text: "var(--persona-event-badge-step-fg, #0a0a0a)" },
43
+ reasoning_: { bg: "var(--persona-event-badge-reasoning-bg, #fef9c3)", text: "var(--persona-event-badge-reasoning-fg, #a16207)" },
44
+ tool_: { bg: "var(--persona-event-badge-tool-bg, #f3e8ff)", text: "var(--persona-event-badge-tool-fg, #6b21a8)" },
45
+ agent_: { bg: "var(--persona-event-badge-agent-bg, #ccfbf1)", text: "var(--persona-event-badge-agent-fg, #115e59)" },
46
+ error: { bg: "var(--persona-event-badge-error-bg, #fee2e2)", text: "var(--persona-event-badge-error-fg, #b91c1c)" },
42
47
  };
48
+ // The unified error event type is `error`; `execution_error` carries the same
49
+ // severity and would otherwise fall through to the default pair.
50
+ DEFAULT_BADGE_COLORS.execution_error = DEFAULT_BADGE_COLORS.error;
43
51
  const DEFAULT_BADGE_COLOR: EventStreamBadgeColor = {
44
- bg: "var(--persona-palette-colors-gray-100, #f3f4f6)",
45
- text: "var(--persona-palette-colors-gray-600, #4b5563)",
52
+ bg: "var(--persona-event-badge-default-bg, #f3f4f6)",
53
+ text: "var(--persona-event-badge-default-fg, #4b5563)",
46
54
  };
47
55
 
48
56
  const DEFAULT_DESCRIPTION_FIELDS = [
@@ -62,20 +70,42 @@ const UPDATE_THROTTLE_MS = 100;
62
70
  // Helper Functions
63
71
  // ============================================================================
64
72
 
65
- function getBadgeColor(
73
+ function matchBadgeColor(
66
74
  eventType: string,
67
- customColors?: Record<string, EventStreamBadgeColor>
68
- ): EventStreamBadgeColor {
69
- const allColors = { ...DEFAULT_BADGE_COLORS, ...customColors };
70
- // Exact match first
71
- if (allColors[eventType]) return allColors[eventType];
72
- // Prefix match (keys ending with "_")
73
- for (const prefix of Object.keys(allColors)) {
75
+ colors: Record<string, EventStreamBadgeColor>
76
+ ): EventStreamBadgeColor | undefined {
77
+ // Exact match first, then prefix match (keys ending with "_").
78
+ if (colors[eventType]) return colors[eventType];
79
+ for (const prefix of Object.keys(colors)) {
74
80
  if (prefix.endsWith("_") && eventType.startsWith(prefix)) {
75
- return allColors[prefix];
81
+ return colors[prefix];
76
82
  }
77
83
  }
78
- return DEFAULT_BADGE_COLOR;
84
+ return undefined;
85
+ }
86
+
87
+ export function getBadgeColor(
88
+ eventType: string,
89
+ customColors?: Record<string, EventStreamBadgeColor>
90
+ ): EventStreamBadgeColor {
91
+ // Host colors are a strictly higher tier than the defaults: a host prefix
92
+ // (e.g. `execution_`) must not be shadowed by the built-in exact
93
+ // `execution_error` entry, so the maps are never merged flat.
94
+ return (
95
+ (customColors && matchBadgeColor(eventType, customColors)) ??
96
+ matchBadgeColor(eventType, DEFAULT_BADGE_COLORS) ??
97
+ DEFAULT_BADGE_COLOR
98
+ );
99
+ }
100
+
101
+ // The chip border is its text color at ~31% alpha. The hex-suffix trick only
102
+ // composes on a bare 6-digit hex; every other value (var() refs like the
103
+ // defaults above, rgb(), named colors) needs color-mix or the declaration is
104
+ // invalid and silently dropped.
105
+ export function getBadgeBorderColor(text: string): string {
106
+ return /^#[0-9a-fA-F]{6}$/.test(text)
107
+ ? `${text}50`
108
+ : `color-mix(in srgb, ${text} 31%, transparent)`;
79
109
  }
80
110
 
81
111
  function formatRelativeTimestamp(ms: number, firstEventMs: number): string {
@@ -326,7 +356,7 @@ function renderEventRow(
326
356
  );
327
357
  badge.style.backgroundColor = badgeColor.bg;
328
358
  badge.style.color = badgeColor.text;
329
- badge.style.borderColor = badgeColor.text + "50";
359
+ badge.style.borderColor = getBadgeBorderColor(badgeColor.text);
330
360
  badge.textContent = event.type;
331
361
 
332
362
  // 5. Description (extracted from payload)
@@ -432,12 +432,14 @@ export type RuntypeStopReasonKind = NonNullable<
432
432
  >;
433
433
 
434
434
  export type RuntypeClientInitRequest = {
435
+ durableRecovery?: boolean;
435
436
  flowId?: string;
436
437
  identityProof?: string;
437
438
  token: string;
438
439
  visitorHistory?: boolean;
439
440
  visitorToken?: string;
440
441
  } | {
442
+ durableRecovery?: boolean;
441
443
  flowId?: string;
442
444
  identityProof?: string;
443
445
  sessionId: string;
@@ -446,6 +448,7 @@ export type RuntypeClientInitRequest = {
446
448
  visitorToken?: string;
447
449
  } | {
448
450
  conversationId: string;
451
+ durableRecovery?: boolean;
449
452
  flowId?: string;
450
453
  identityProof?: string;
451
454
  token: string;
@@ -453,6 +456,7 @@ export type RuntypeClientInitRequest = {
453
456
  visitorToken: string;
454
457
  } | {
455
458
  conversationId: string;
459
+ durableRecovery?: boolean;
456
460
  flowId?: string;
457
461
  identityProof: string;
458
462
  token: string;
@@ -471,6 +475,9 @@ export type RuntypeClientInitResponse = {
471
475
  };
472
476
  conversationId: string;
473
477
  conversationRevision: string;
478
+ durableRecovery?: {
479
+ enabled: boolean;
480
+ };
474
481
  expiresAt: string;
475
482
  flow?: {
476
483
  description?: string | null;
@@ -538,6 +545,7 @@ export type RuntypeClientChatRequest = {
538
545
  export type RuntypeClientChatStreamEvent = RuntypeExecutionStreamEvent;
539
546
 
540
547
  export type RuntypeClientResumeRequest = {
548
+ after?: string;
541
549
  assistantMessageId?: string;
542
550
  clientTools?: Array<{
543
551
  description: string;
package/src/index-core.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  import {
2
2
  initAgentWidget as initAgentWidgetFn,
3
+ getAgentWidgetHandles as getAgentWidgetHandlesFn,
3
4
  type AgentWidgetInitHandle
4
5
  } from "./runtime/init";
5
6
 
@@ -326,6 +327,7 @@ export {
326
327
  } from "./suggest-replies-tool";
327
328
 
328
329
  export { initAgentWidgetFn as initAgentWidget };
330
+ export { getAgentWidgetHandlesFn as getAgentWidgetHandles };
329
331
  export {
330
332
  createWidgetHostLayout,
331
333
  type WidgetHostLayout,
@@ -0,0 +1,56 @@
1
+ // @vitest-environment jsdom
2
+
3
+ /**
4
+ * `getAgentWidgetHandles()` registry: every `initAgentWidget()` mount is
5
+ * listed (oldest first) until its `destroy()`, so companions created after
6
+ * `persona:chat-ready` fired can still adopt a mounted widget.
7
+ */
8
+
9
+ import { afterEach, describe, expect, it } from "vitest";
10
+
11
+ import { getAgentWidgetHandles, initAgentWidget } from "./init";
12
+
13
+ const mountHost = (id: string): HTMLElement => {
14
+ const host = document.createElement("div");
15
+ host.id = id;
16
+ document.body.appendChild(host);
17
+ return host;
18
+ };
19
+
20
+ const CONFIG = {
21
+ apiUrl: "https://api.example.com/chat",
22
+ launcher: { enabled: false },
23
+ persistState: false,
24
+ };
25
+
26
+ describe("getAgentWidgetHandles", () => {
27
+ afterEach(() => {
28
+ // Sweep any instances a failing assertion left behind.
29
+ for (const handle of getAgentWidgetHandles()) handle.destroy();
30
+ document.body.innerHTML = "";
31
+ });
32
+
33
+ it("lists mounted handles oldest-first and drops them on destroy", () => {
34
+ expect(getAgentWidgetHandles()).toEqual([]);
35
+
36
+ const first = initAgentWidget({ target: mountHost("reg-a"), config: CONFIG });
37
+ const second = initAgentWidget({ target: mountHost("reg-b"), config: CONFIG });
38
+
39
+ expect(getAgentWidgetHandles()).toEqual([first, second]);
40
+ expect(getAgentWidgetHandles().at(-1)).toBe(second);
41
+
42
+ second.destroy();
43
+ expect(getAgentWidgetHandles()).toEqual([first]);
44
+
45
+ first.destroy();
46
+ expect(getAgentWidgetHandles()).toEqual([]);
47
+ });
48
+
49
+ it("returns a copy, not the live registry", () => {
50
+ const handle = initAgentWidget({ target: mountHost("reg-c"), config: CONFIG });
51
+ const snapshot = getAgentWidgetHandles();
52
+ snapshot.length = 0;
53
+ expect(getAgentWidgetHandles()).toEqual([handle]);
54
+ handle.destroy();
55
+ });
56
+ });
@@ -46,6 +46,19 @@ const mountStyles = (root: ShadowRoot | HTMLElement, ownerDocument: Document) =>
46
46
 
47
47
  export type AgentWidgetInitHandle = AgentWidgetController & { host: HTMLElement };
48
48
 
49
+ // Live handles for every mounted initAgentWidget() instance, oldest first.
50
+ // `persona:chat-ready` is a one-shot broadcast and `windowKey` is opt-in, so
51
+ // code that runs after a widget mounted previously had no way to reach it.
52
+ // This registry closes that gap: late companions (selection toolbars, host
53
+ // integrations) can adopt the most recent mount via
54
+ // `getAgentWidgetHandles().at(-1)`. Handles deregister on destroy().
55
+ const activeHandles: AgentWidgetInitHandle[] = [];
56
+
57
+ /** Handles of all currently mounted widgets, oldest first. */
58
+ export const getAgentWidgetHandles = (): AgentWidgetInitHandle[] => [
59
+ ...activeHandles,
60
+ ];
61
+
49
62
  export const initAgentWidget = (
50
63
  options: AgentWidgetInitOptions
51
64
  ): AgentWidgetInitHandle => {
@@ -154,6 +167,8 @@ export const initAgentWidget = (
154
167
  destroy() {
155
168
  destroyCurrentController();
156
169
  hostLayout.destroy();
170
+ const index = activeHandles.indexOf(handle);
171
+ if (index !== -1) activeHandles.splice(index, 1);
157
172
  if (options.windowKey && typeof window !== "undefined") {
158
173
  delete (window as any)[options.windowKey];
159
174
  }
@@ -175,6 +190,8 @@ export const initAgentWidget = (
175
190
  }
176
191
  }) as AgentWidgetInitHandle;
177
192
 
193
+ activeHandles.push(handle);
194
+
178
195
  if (options.windowKey && typeof window !== 'undefined') {
179
196
  (window as any)[options.windowKey] = handle;
180
197
  }
@@ -26,7 +26,11 @@ export interface ColorPalette {
26
26
  warning: ColorShade;
27
27
  error: ColorShade;
28
28
  info: ColorShade;
29
- [key: string]: ColorShade;
29
+ /** Event-stream tool badge family. */
30
+ purple?: ColorShade;
31
+ /** Event-stream agent badge family. */
32
+ teal?: ColorShade;
33
+ [key: string]: ColorShade | undefined;
30
34
  }
31
35
 
32
36
  export interface SpacingScale {
@@ -628,6 +632,40 @@ export interface ReasoningBubbleTokens {
628
632
  shadow: string;
629
633
  }
630
634
 
635
+ /** One event-stream badge chip: fill + text pair. The chip border derives
636
+ * from `foreground` at ~31% alpha. */
637
+ export interface EventStreamBadgePairTokens {
638
+ background: TokenReference<'color'>;
639
+ foreground: TokenReference<'color'>;
640
+ }
641
+
642
+ /**
643
+ * Event-stream inspector badge chips (`features.eventStream`), one pair per
644
+ * unified event family. Materialized into every theme and emitted as
645
+ * `--persona-event-badge-<family>-{bg,fg}`, with scheme-appropriate defaults:
646
+ * light themes pair a 100-tone fill with a 700-tone text, the built-in dark
647
+ * layer flips to a 900-tone fill with a 300-tone text. `badge.default` colors
648
+ * event types no family matches.
649
+ */
650
+ export interface EventStreamTokens {
651
+ badge: {
652
+ /** `flow_*` events. */
653
+ flow: EventStreamBadgePairTokens;
654
+ /** `step_*` events. */
655
+ step: EventStreamBadgePairTokens;
656
+ /** `reasoning_*` events. */
657
+ reasoning: EventStreamBadgePairTokens;
658
+ /** `tool_*` events. */
659
+ tool: EventStreamBadgePairTokens;
660
+ /** `agent_*` events. */
661
+ agent: EventStreamBadgePairTokens;
662
+ /** `error` / `execution_error` events. */
663
+ error: EventStreamBadgePairTokens;
664
+ /** Any event type no other family matches. */
665
+ default: EventStreamBadgePairTokens;
666
+ };
667
+ }
668
+
631
669
  /** Scrollbar appearance shared by every scroller in the widget. */
632
670
  export interface ScrollbarTokens {
633
671
  /** Thumb color. @default semantic.colors.border */
@@ -994,6 +1032,8 @@ export interface ComponentTokens {
994
1032
  attachment: AttachmentTokens;
995
1033
  toolBubble: ToolBubbleTokens;
996
1034
  reasoningBubble: ReasoningBubbleTokens;
1035
+ /** Event-stream inspector badge chips. */
1036
+ eventStream?: EventStreamTokens;
997
1037
  composer: ComposerChromeTokens;
998
1038
  /** Shared motion timing for composer chrome animation. */
999
1039
  motion?: MotionTokens;
package/src/types.ts CHANGED
@@ -826,6 +826,14 @@ export type ClientToolDefinition = {
826
826
  readOnlyHint?: boolean;
827
827
  untrustedContentHint?: boolean;
828
828
  };
829
+ /**
830
+ * Hoisted copy of `annotations.untrustedContentHint`. This is the field the
831
+ * server actually acts on (`InlineClientToolSchema`): it spotlights the
832
+ * tool's output before the model reads it, so instructions embedded in
833
+ * page-provided content (reviews, comments, emails) are not treated as
834
+ * instructions. `annotations` itself is trace/dashboard metadata only.
835
+ */
836
+ untrustedContentHint?: boolean;
829
837
  };
830
838
 
831
839
  /**
@@ -2237,6 +2245,16 @@ export type AgentWidgetToolCallDisplayFeature = {
2237
2245
  * @default "stack"
2238
2246
  */
2239
2247
  groupedMode?: "stack" | "summary";
2248
+ /**
2249
+ * What happens to a tool call row once it completes.
2250
+ *
2251
+ * - `"kept"` (default): the finished row stays in the transcript.
2252
+ * - `"removed"`: no row is rendered at all, so the transcript gap closes with
2253
+ * it. The tool call stays on the message and in `getMessages()`.
2254
+ *
2255
+ * @default "kept"
2256
+ */
2257
+ completedVisibility?: AgentWidgetToolCallCompletedVisibility;
2240
2258
  /**
2241
2259
  * When false, tool call bubbles show only the collapsed summary with no
2242
2260
  * expand/collapse toggle. Users see tool awareness without full details.
@@ -2255,6 +2273,9 @@ export type AgentWidgetToolCallDisplayFeature = {
2255
2273
  loadingAnimation?: AgentWidgetToolCallLoadingAnimation;
2256
2274
  };
2257
2275
 
2276
+ /** See `features.toolCallDisplay.completedVisibility`. */
2277
+ export type AgentWidgetToolCallCompletedVisibility = "kept" | "removed";
2278
+
2258
2279
  export type AgentWidgetReasoningDisplayFeature = {
2259
2280
  /**
2260
2281
  * When true, active collapsed reasoning rows can render a lightweight preview block.
@@ -2300,14 +2321,30 @@ export type AgentWidgetReasoningDisplayFeature = {
2300
2321
  * - `"kept"` (default): the finished row stays in the transcript.
2301
2322
  * - `"removed"`: no row is rendered at all, so the transcript gap closes with
2302
2323
  * it. The reasoning text stays on the message and in `getMessages()`.
2324
+ * - `"removed-when-short"`: behaves as `"removed"` when the completed trace
2325
+ * is short (see `shortThinkThreshold`), and as `"kept"` otherwise.
2303
2326
  *
2304
2327
  * @default "kept"
2305
2328
  */
2306
2329
  completedVisibility?: AgentWidgetReasoningCompletedVisibility;
2330
+ /**
2331
+ * Thresholds used by `completedVisibility: "removed-when-short"` to decide
2332
+ * whether a completed trace counts as short. Duration is checked first when
2333
+ * the reasoning reports one; the character count is a fallback for traces
2334
+ * with no reported duration.
2335
+ * @default { durationMs: 15000, chars: 1200 }
2336
+ */
2337
+ shortThinkThreshold?: {
2338
+ durationMs?: number;
2339
+ chars?: number;
2340
+ };
2307
2341
  };
2308
2342
 
2309
2343
  /** See `features.reasoningDisplay.completedVisibility`. */
2310
- export type AgentWidgetReasoningCompletedVisibility = "kept" | "removed";
2344
+ export type AgentWidgetReasoningCompletedVisibility =
2345
+ | "kept"
2346
+ | "removed"
2347
+ | "removed-when-short";
2311
2348
 
2312
2349
  /**
2313
2350
  * Reveal animation applied to assistant message text while it is streaming.