@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.
- package/dist/{approval-ui-RNNBR5R3.js → approval-ui-K7R5Q5OC.js} +1 -1
- package/dist/approval-ui.cjs +2 -2
- package/dist/approval-ui.d.cts +73 -2
- package/dist/approval-ui.d.ts +73 -2
- package/dist/approval-ui.js +2 -2
- package/dist/artifacts-ui-F4N6VMKF.js +7 -0
- package/dist/artifacts-ui.cjs +1 -1
- package/dist/artifacts-ui.d.cts +73 -2
- package/dist/artifacts-ui.d.ts +73 -2
- package/dist/artifacts-ui.js +1 -1
- package/dist/chunk-NQLDCJAP.js +4 -0
- package/dist/codegen.cjs +1 -1
- package/dist/codegen.js +1 -1
- package/dist/context-mentions.d.cts +73 -2
- package/dist/context-mentions.d.ts +73 -2
- package/dist/event-stream-view-FKWDY3UB.js +1 -0
- package/dist/event-stream-view.cjs +1 -1
- package/dist/event-stream-view.d.cts +73 -2
- package/dist/event-stream-view.d.ts +73 -2
- package/dist/event-stream-view.js +1 -1
- package/dist/forms-ui.d.cts +73 -2
- package/dist/forms-ui.d.ts +73 -2
- package/dist/index.cjs +66 -66
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +136 -5
- package/dist/index.d.ts +136 -5
- package/dist/index.global.js +50 -50
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +67 -67
- package/dist/index.js.map +1 -1
- package/dist/launcher.global.js +2 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/session-reconnect.d.cts +73 -2
- package/dist/session-reconnect.d.ts +73 -2
- package/dist/smart-dom-reader.d.cts +73 -2
- package/dist/smart-dom-reader.d.ts +73 -2
- package/dist/theme-editor-preview.cjs +65 -65
- package/dist/theme-editor-preview.d.cts +73 -2
- package/dist/theme-editor-preview.d.ts +73 -2
- package/dist/theme-editor-preview.js +49 -49
- package/dist/theme-editor.cjs +4 -4
- package/dist/theme-editor.d.cts +73 -2
- package/dist/theme-editor.d.ts +73 -2
- package/dist/theme-editor.js +2 -2
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +65 -1
- package/dist/theme-reference.d.ts +65 -1
- package/dist/theme-reference.js +1 -1
- package/dist/webmcp-polyfill.js +1 -4
- package/dist/webmcp-runtime-JI7T4QYF.js +6 -0
- package/dist/webmcp-runtime.cjs +2 -2
- package/dist/webmcp-runtime.d.cts +34 -2
- package/dist/webmcp-runtime.d.ts +34 -2
- package/dist/webmcp-runtime.js +2 -2
- package/package.json +2 -2
- package/src/client-visitor-history.test.ts +131 -0
- package/src/components/artifact-preview.ts +5 -0
- package/src/components/event-stream-badge-theming.test.ts +159 -0
- package/src/components/event-stream-view.ts +50 -20
- package/src/generated/runtype-openapi-contract.ts +8 -0
- package/src/index-core.ts +2 -0
- package/src/runtime/init.registry.test.ts +56 -0
- package/src/runtime/init.ts +17 -0
- package/src/types/theme.ts +41 -1
- package/src/types.ts +38 -1
- package/src/ui.reasoning-display.test.ts +136 -2
- package/src/ui.ts +69 -21
- package/src/utils/__snapshots__/tokens-parity.test.ts.snap +1740 -0
- package/src/utils/formatting.ts +22 -0
- package/src/utils/theme.ts +16 -0
- package/src/utils/tokens.ts +59 -0
- package/src/webmcp-bridge.test.ts +132 -23
- package/src/webmcp-bridge.ts +26 -2
- package/src/webmcp-runtime-entry.ts +71 -11
- package/dist/artifacts-ui-J6AKUGTS.js +0 -7
- package/dist/chunk-HBE7LFNT.js +0 -4
- package/dist/event-stream-view-73NWKWVQ.js +0 -1
- 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
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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-
|
|
45
|
-
text: "var(--persona-
|
|
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
|
|
73
|
+
function matchBadgeColor(
|
|
66
74
|
eventType: string,
|
|
67
|
-
|
|
68
|
-
): EventStreamBadgeColor {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
|
81
|
+
return colors[prefix];
|
|
76
82
|
}
|
|
77
83
|
}
|
|
78
|
-
return
|
|
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
|
|
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
|
+
});
|
package/src/runtime/init.ts
CHANGED
|
@@ -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
|
}
|
package/src/types/theme.ts
CHANGED
|
@@ -26,7 +26,11 @@ export interface ColorPalette {
|
|
|
26
26
|
warning: ColorShade;
|
|
27
27
|
error: ColorShade;
|
|
28
28
|
info: ColorShade;
|
|
29
|
-
|
|
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 =
|
|
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.
|