@runtypelabs/persona 4.13.0 → 4.15.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/README.md +4 -4
- package/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/{types-DveIaNx6.d.cts → types-BpWGm7_6.d.cts} +17 -0
- package/dist/animations/{types-DveIaNx6.d.ts → types-BpWGm7_6.d.ts} +17 -0
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/chunk-QJ4LV2KW.js +1 -0
- package/dist/codegen.cjs +12 -11
- package/dist/codegen.js +14 -13
- package/dist/{context-mentions-ONG7A7M6.js → context-mentions-BTSTIBFC.js} +1 -1
- package/dist/context-mentions.cjs +11 -8
- package/dist/context-mentions.d.cts +339 -22
- package/dist/context-mentions.d.ts +339 -22
- package/dist/context-mentions.js +11 -8
- package/dist/index.cjs +68 -67
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +483 -48
- package/dist/index.d.ts +483 -48
- package/dist/index.global.js +61 -58
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +70 -69
- package/dist/index.js.map +1 -1
- package/dist/launcher.global.js +5 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/smart-dom-reader.d.cts +339 -22
- package/dist/smart-dom-reader.d.ts +339 -22
- package/dist/theme-editor-preview.cjs +67 -67
- package/dist/theme-editor-preview.d.cts +355 -23
- package/dist/theme-editor-preview.d.ts +355 -23
- package/dist/theme-editor-preview.js +75 -75
- package/dist/theme-editor.cjs +10 -10
- package/dist/theme-editor.d.cts +376 -24
- package/dist/theme-editor.d.ts +376 -24
- package/dist/theme-editor.js +10 -10
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +38 -0
- package/dist/theme-reference.d.ts +38 -0
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +1 -1
- package/package.json +1 -1
- package/src/artifacts-session.test.ts +106 -0
- package/src/ask-user-question-tool.ts +10 -7
- package/src/client.test.ts +2 -2
- package/src/client.ts +4 -1
- package/src/components/artifact-card.ts +13 -2
- package/src/components/artifact-inline.ts +11 -2
- package/src/components/artifact-preview.ts +1 -1
- package/src/components/composer-builder.test.ts +2 -0
- package/src/components/composer-builder.ts +1 -1
- package/src/components/composer-parts.test.ts +105 -4
- package/src/components/composer-parts.ts +54 -16
- package/src/components/message-bubble.test.ts +7 -1
- package/src/components/message-bubble.ts +11 -8
- package/src/components/messages.ts +5 -2
- package/src/components/panel.ts +31 -5
- package/src/components/pill-composer-builder.test.ts +18 -0
- package/src/components/suggestions.test.ts +233 -0
- package/src/components/suggestions.ts +463 -81
- package/src/components/widget-view.ts +4 -0
- package/src/defaults.ts +5 -3
- package/src/generated/runtype-openapi-contract.ts +17 -0
- package/src/index-core.ts +52 -2
- package/src/launcher-global.ts +1 -1
- package/src/plugins/types.ts +71 -3
- package/src/presets.ts +3 -3
- package/src/session.ts +55 -12
- package/src/styles/widget-styles.test.ts +162 -0
- package/src/styles/widget.css +440 -1
- package/src/suggest-replies-tool.test.ts +233 -2
- package/src/suggest-replies-tool.ts +115 -39
- package/src/theme-editor/index.ts +5 -0
- package/src/theme-editor/presets.test.ts +43 -0
- package/src/theme-editor/presets.ts +101 -87
- package/src/theme-editor/preview-utils.test.ts +34 -0
- package/src/theme-editor/preview-utils.ts +77 -2
- package/src/theme-editor/sections.test.ts +72 -7
- package/src/theme-editor/sections.ts +113 -13
- package/src/theme-editor/state-preferences.test.ts +15 -0
- package/src/theme-editor/state.ts +46 -0
- package/src/theme-editor/types.ts +15 -1
- package/src/theme-editor/webmcp/tools.test.ts +13 -0
- package/src/theme-editor/webmcp/tools.ts +5 -0
- package/src/theme-reference.ts +9 -2
- package/src/types/theme.ts +32 -0
- package/src/types.ts +291 -25
- package/src/ui.artifact-pane-gating.test.ts +5 -5
- package/src/ui.config-preferences.test.ts +166 -0
- package/src/ui.detached-panel.test.ts +1 -1
- package/src/ui.suggest-replies.test.ts +1066 -1
- package/src/ui.ts +311 -45
- package/src/ui.typing-indicator.test.ts +135 -0
- package/src/utils/artifact-display.test.ts +180 -16
- package/src/utils/artifact-display.ts +155 -14
- package/src/utils/artifact-file.test.ts +1 -1
- package/src/utils/code-generators.test.ts +40 -0
- package/src/utils/code-generators.ts +22 -0
- package/src/utils/config-merge.test.ts +84 -0
- package/src/utils/config-merge.ts +6 -1
- package/src/utils/feature-preferences.test.ts +326 -0
- package/src/utils/feature-preferences.ts +498 -0
- package/src/utils/icons.test.ts +26 -0
- package/src/utils/icons.ts +8 -0
- package/src/utils/theme.test.ts +171 -0
- package/src/utils/tokens.ts +71 -7
- package/dist/chunk-MMUPR2JW.js +0 -1
package/src/index-core.ts
CHANGED
|
@@ -5,6 +5,8 @@ import {
|
|
|
5
5
|
|
|
6
6
|
export type {
|
|
7
7
|
AgentWidgetConfig,
|
|
8
|
+
AgentWidgetStorageAdapter,
|
|
9
|
+
AgentWidgetStoredState,
|
|
8
10
|
AgentWidgetConfigPatch,
|
|
9
11
|
TargetResolver,
|
|
10
12
|
ResolvedTarget,
|
|
@@ -12,12 +14,19 @@ export type {
|
|
|
12
14
|
AgentWidgetArtifactsFeature,
|
|
13
15
|
AgentWidgetArtifactsLayoutConfig,
|
|
14
16
|
PersonaArtifactDisplayMode,
|
|
17
|
+
PersonaArtifactDisplayModeInput,
|
|
18
|
+
PersonaArtifactDisplayRules,
|
|
19
|
+
PersonaArtifactFilesDisplayRules,
|
|
15
20
|
PersonaArtifactKind,
|
|
21
|
+
PersonaArtifactPresentation,
|
|
16
22
|
PersonaArtifactRecord,
|
|
17
23
|
PersonaArtifactActionContext,
|
|
18
24
|
PersonaArtifactStatusLabelContext,
|
|
19
25
|
PersonaArtifactCustomAction,
|
|
20
26
|
PersonaArtifactManualUpsert,
|
|
27
|
+
WidgetPreferenceCapabilities,
|
|
28
|
+
WidgetArtifactLayoutPreference,
|
|
29
|
+
WidgetPreferenceSlice,
|
|
21
30
|
ArtifactConfigPayload,
|
|
22
31
|
AgentWidgetInitOptions,
|
|
23
32
|
AgentWidgetMessage,
|
|
@@ -131,9 +140,45 @@ export type {
|
|
|
131
140
|
AskUserQuestionPrompt,
|
|
132
141
|
AskUserQuestionOption,
|
|
133
142
|
AgentWidgetAskUserQuestionFeature,
|
|
134
|
-
AgentWidgetAskUserQuestionStyles
|
|
143
|
+
AgentWidgetAskUserQuestionStyles,
|
|
144
|
+
AgentWidgetSuggestion,
|
|
145
|
+
AgentWidgetSuggestionChipsConfig,
|
|
146
|
+
AgentWidgetResolvedSuggestion,
|
|
147
|
+
AgentWidgetSuggestionBehavior,
|
|
148
|
+
AgentWidgetSuggestionVariant,
|
|
149
|
+
AgentWidgetSuggestionSurface,
|
|
150
|
+
AgentWidgetSuggestionSource,
|
|
151
|
+
AgentWidgetSuggestionSurfaceConfig,
|
|
152
|
+
AgentWidgetStarterSuggestionsConfig,
|
|
153
|
+
AgentWidgetFollowUpSuggestionsConfig,
|
|
154
|
+
AgentWidgetSuggestionsConfig
|
|
135
155
|
} from "./types";
|
|
136
156
|
|
|
157
|
+
export {
|
|
158
|
+
applyFeaturePreferences,
|
|
159
|
+
parseWidgetPreferenceSlice,
|
|
160
|
+
resolveConfigPreferences,
|
|
161
|
+
normalizeMediaType,
|
|
162
|
+
} from "./utils/feature-preferences";
|
|
163
|
+
|
|
164
|
+
export type {
|
|
165
|
+
WidgetPreferenceParseIssueCode,
|
|
166
|
+
WidgetPreferenceParseIssue,
|
|
167
|
+
WidgetPreferenceParseResult,
|
|
168
|
+
} from "./utils/feature-preferences";
|
|
169
|
+
|
|
170
|
+
export {
|
|
171
|
+
canonicalArtifactDisplayMode,
|
|
172
|
+
resolveArtifactDisplay,
|
|
173
|
+
resolveArtifactDisplayMode,
|
|
174
|
+
} from "./utils/artifact-display";
|
|
175
|
+
|
|
176
|
+
export type {
|
|
177
|
+
PersonaArtifactDisplayDescriptor,
|
|
178
|
+
PersonaArtifactDisplayMatch,
|
|
179
|
+
PersonaArtifactDisplayResolution,
|
|
180
|
+
} from "./utils/artifact-display";
|
|
181
|
+
|
|
137
182
|
export type {
|
|
138
183
|
RuntypeExecutionStreamEvent,
|
|
139
184
|
RuntypeStreamEventOf,
|
|
@@ -292,7 +337,12 @@ export { VERSION } from "./version";
|
|
|
292
337
|
export type { AgentWidgetInitHandle };
|
|
293
338
|
|
|
294
339
|
// Plugin system exports
|
|
295
|
-
export type {
|
|
340
|
+
export type {
|
|
341
|
+
AgentWidgetPlugin,
|
|
342
|
+
AgentWidgetTransformSuggestionsContext,
|
|
343
|
+
AgentWidgetRenderSuggestionContext,
|
|
344
|
+
AgentWidgetSuggestionSelectContext
|
|
345
|
+
} from "./plugins/types";
|
|
296
346
|
export { pluginRegistry } from "./plugins/registry";
|
|
297
347
|
|
|
298
348
|
// Stream animation plugin API: lets consumers register custom animations
|
package/src/launcher-global.ts
CHANGED
|
@@ -76,7 +76,7 @@ const mergeCriticalLauncherConfig = (
|
|
|
76
76
|
* Mirrors the full widget's DOM exactly (`runtime/init.ts` + `ui.ts`): a
|
|
77
77
|
* `[data-persona-root]` wrapper carries the theme CSS variables and the launcher
|
|
78
78
|
* button is its child. Keeping the theme vars on the wrapper (not the button)
|
|
79
|
-
* leaves the button's own inline style
|
|
79
|
+
* leaves the button's own inline style matching the full widget's, so
|
|
80
80
|
* the eventual mount-then-remove handoff is invisible.
|
|
81
81
|
*/
|
|
82
82
|
export const mount = (
|
package/src/plugins/types.ts
CHANGED
|
@@ -8,9 +8,51 @@ import {
|
|
|
8
8
|
EventStreamViewRenderContext,
|
|
9
9
|
EventStreamRowRenderContext,
|
|
10
10
|
EventStreamToolbarRenderContext,
|
|
11
|
-
EventStreamPayloadRenderContext
|
|
11
|
+
EventStreamPayloadRenderContext,
|
|
12
|
+
AgentWidgetSuggestion,
|
|
13
|
+
AgentWidgetResolvedSuggestion,
|
|
14
|
+
AgentWidgetSuggestionSource,
|
|
15
|
+
AgentWidgetSuggestionSurface,
|
|
16
|
+
AgentWidgetSuggestionVariant
|
|
12
17
|
} from "../types";
|
|
13
18
|
|
|
19
|
+
export type AgentWidgetTransformSuggestionsContext = {
|
|
20
|
+
/**
|
|
21
|
+
* Fresh copy of the suggestions entering this plugin, already normalized:
|
|
22
|
+
* shorthand strings expanded and the effective send/fill behavior resolved.
|
|
23
|
+
*/
|
|
24
|
+
suggestions: AgentWidgetResolvedSuggestion[];
|
|
25
|
+
surface: AgentWidgetSuggestionSurface;
|
|
26
|
+
source: AgentWidgetSuggestionSource;
|
|
27
|
+
config: AgentWidgetConfig;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type AgentWidgetRenderSuggestionContext = {
|
|
31
|
+
/** Fully normalized suggestion, including the effective send/fill behavior. */
|
|
32
|
+
suggestion: AgentWidgetResolvedSuggestion;
|
|
33
|
+
index: number;
|
|
34
|
+
surface: AgentWidgetSuggestionSurface;
|
|
35
|
+
source: AgentWidgetSuggestionSource;
|
|
36
|
+
variant: AgentWidgetSuggestionVariant;
|
|
37
|
+
streaming: boolean;
|
|
38
|
+
config: AgentWidgetConfig;
|
|
39
|
+
/** Build Persona's standard chip, card, or list item. */
|
|
40
|
+
defaultRenderer: () => HTMLElement;
|
|
41
|
+
/**
|
|
42
|
+
* Run the normal selection pipeline: DOM event, plugin selection hooks, then
|
|
43
|
+
* send or fill. Custom renderers should call this instead of sending directly.
|
|
44
|
+
*/
|
|
45
|
+
select: () => void;
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
export type AgentWidgetSuggestionSelectContext = {
|
|
49
|
+
suggestion: AgentWidgetResolvedSuggestion;
|
|
50
|
+
surface: AgentWidgetSuggestionSurface;
|
|
51
|
+
source: AgentWidgetSuggestionSource;
|
|
52
|
+
variant: AgentWidgetSuggestionVariant;
|
|
53
|
+
config: AgentWidgetConfig;
|
|
54
|
+
};
|
|
55
|
+
|
|
14
56
|
/**
|
|
15
57
|
* Plugin interface for customizing widget components
|
|
16
58
|
*/
|
|
@@ -25,6 +67,34 @@ export interface AgentWidgetPlugin {
|
|
|
25
67
|
*/
|
|
26
68
|
priority?: number;
|
|
27
69
|
|
|
70
|
+
/**
|
|
71
|
+
* Transform, filter, enrich, or reorder suggestions. Hooks receive normalized
|
|
72
|
+
* items and compose in plugin priority order; the return value may use the
|
|
73
|
+
* loose shape (string shorthand included) and is re-normalized before the next
|
|
74
|
+
* hook. A per-item `behavior` set here survives. `maxItems` caps the list
|
|
75
|
+
* after the full chain. Return an empty array to hide the surface.
|
|
76
|
+
*/
|
|
77
|
+
transformSuggestions?: (
|
|
78
|
+
context: AgentWidgetTransformSuggestionsContext
|
|
79
|
+
) => AgentWidgetSuggestion[];
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Render one starter or follow-up suggestion. Return null to use Persona's
|
|
83
|
+
* default chip/card/list renderer. Call `select()` from custom interaction
|
|
84
|
+
* controls to preserve events, selection hooks, and send/fill behavior.
|
|
85
|
+
*/
|
|
86
|
+
renderSuggestion?: (
|
|
87
|
+
context: AgentWidgetRenderSuggestionContext
|
|
88
|
+
) => HTMLElement | null;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Observe or intercept a suggestion selection. Return false to cancel the
|
|
92
|
+
* built-in send/fill action. Hooks run in plugin priority order.
|
|
93
|
+
*/
|
|
94
|
+
onSuggestionSelect?: (
|
|
95
|
+
context: AgentWidgetSuggestionSelectContext
|
|
96
|
+
) => void | boolean;
|
|
97
|
+
|
|
28
98
|
/**
|
|
29
99
|
* Custom renderer for message bubbles
|
|
30
100
|
* Return null to use default renderer
|
|
@@ -298,5 +368,3 @@ export interface AgentWidgetPlugin {
|
|
|
298
368
|
|
|
299
369
|
|
|
300
370
|
|
|
301
|
-
|
|
302
|
-
|
package/src/presets.ts
CHANGED
|
@@ -75,9 +75,9 @@ export const PRESET_SHOP: WidgetPreset = {
|
|
|
75
75
|
sendButtonLabel: "Send",
|
|
76
76
|
},
|
|
77
77
|
suggestionChips: [
|
|
78
|
-
"
|
|
79
|
-
"
|
|
80
|
-
"
|
|
78
|
+
"Show me what you can help with",
|
|
79
|
+
"Walk me through getting started",
|
|
80
|
+
"Answer a question about this page",
|
|
81
81
|
],
|
|
82
82
|
},
|
|
83
83
|
};
|
package/src/session.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { AgentWidgetClient, type SSEEventCallback } from "./client";
|
|
|
2
2
|
import { isWebMcpToolName } from "./webmcp-bridge";
|
|
3
3
|
import {
|
|
4
4
|
SUGGEST_REPLIES_TOOL_NAME,
|
|
5
|
+
resolveFollowUpsFeature,
|
|
5
6
|
suggestRepliesToolResult,
|
|
6
7
|
} from "./suggest-replies-tool";
|
|
7
8
|
import {
|
|
@@ -77,10 +78,11 @@ export type AgentWidgetSessionStatus =
|
|
|
77
78
|
* request. When NONE of these change, `updateConfig` can refresh the live
|
|
78
79
|
* client in place (preserving the WebMCP bridge and any in-flight stream/resume)
|
|
79
80
|
* instead of swapping in a fresh client and tearing down WebMCP state. Fields
|
|
80
|
-
* outside this list (theme, copy, layout,
|
|
81
|
-
* postprocessMessage, feature display toggles, …) are
|
|
82
|
-
* apply mid-turn: which is what a self-styling widget
|
|
83
|
-
* theme tool re-renders the widget while the agent's
|
|
81
|
+
* outside this list (theme, copy, layout, suggestions, suggestionChips,
|
|
82
|
+
* iterationDisplay, postprocessMessage, feature display toggles, …) are
|
|
83
|
+
* display-only and safe to apply mid-turn: which is what a self-styling widget
|
|
84
|
+
* needs when a `webmcp:*` theme tool re-renders the widget while the agent's
|
|
85
|
+
* turn is still streaming.
|
|
84
86
|
*
|
|
85
87
|
* Compared by identity (`!==`): primitives by value, functions/objects by
|
|
86
88
|
* reference. A consumer that rebuilds these objects on every render simply
|
|
@@ -858,7 +860,11 @@ export class AgentWidgetSession {
|
|
|
858
860
|
}
|
|
859
861
|
|
|
860
862
|
public updateConfig(next: AgentWidgetConfig) {
|
|
863
|
+
const previousArtifactDisplay = this.config.features?.artifacts?.display;
|
|
861
864
|
const merged = { ...this.config, ...next };
|
|
865
|
+
const artifactDisplayChanged =
|
|
866
|
+
JSON.stringify(previousArtifactDisplay) !==
|
|
867
|
+
JSON.stringify(merged.features?.artifacts?.display);
|
|
862
868
|
|
|
863
869
|
// Connection/request-shaping change (apiUrl, clientToken, webmcp, headers,
|
|
864
870
|
// parser, agent, …) → full client rebuild. UI-only change (theme, copy,
|
|
@@ -870,6 +876,7 @@ export class AgentWidgetSession {
|
|
|
870
876
|
if (!connectionConfigChanged(this.config, merged)) {
|
|
871
877
|
this.config = merged;
|
|
872
878
|
this.client.updateConfig(merged);
|
|
879
|
+
if (artifactDisplayChanged) this.refreshArtifactReferenceBlocks();
|
|
873
880
|
return;
|
|
874
881
|
}
|
|
875
882
|
|
|
@@ -886,6 +893,7 @@ export class AgentWidgetSession {
|
|
|
886
893
|
const prevSSECallback = this.client.getSSEEventCallback();
|
|
887
894
|
this.config = merged;
|
|
888
895
|
this.client = new AgentWidgetClient(this.config);
|
|
896
|
+
if (artifactDisplayChanged) this.refreshArtifactReferenceBlocks();
|
|
889
897
|
this.wireDefaultWebMcpConfirm();
|
|
890
898
|
if (prevSSECallback) {
|
|
891
899
|
this.client.setSSEEventCallback(prevSSECallback);
|
|
@@ -2735,7 +2743,8 @@ export class AgentWidgetSession {
|
|
|
2735
2743
|
title: manual.title,
|
|
2736
2744
|
status: "complete",
|
|
2737
2745
|
markdown: manual.content,
|
|
2738
|
-
...(manual.file ? { file: manual.file } : {})
|
|
2746
|
+
...(manual.file ? { file: manual.file } : {}),
|
|
2747
|
+
...(manual.presentation ? { presentation: manual.presentation } : {})
|
|
2739
2748
|
}
|
|
2740
2749
|
: {
|
|
2741
2750
|
id,
|
|
@@ -2743,7 +2752,8 @@ export class AgentWidgetSession {
|
|
|
2743
2752
|
title: manual.title,
|
|
2744
2753
|
status: "complete",
|
|
2745
2754
|
component: manual.component,
|
|
2746
|
-
props: manual.props ?? {}
|
|
2755
|
+
props: manual.props ?? {},
|
|
2756
|
+
...(manual.presentation ? { presentation: manual.presentation } : {})
|
|
2747
2757
|
};
|
|
2748
2758
|
this.artifacts.set(id, rec);
|
|
2749
2759
|
this.selectedArtifactId = id;
|
|
@@ -2757,7 +2767,7 @@ export class AgentWidgetSession {
|
|
|
2757
2767
|
/**
|
|
2758
2768
|
* Injects (or refreshes) the in-thread artifact block for a programmatically
|
|
2759
2769
|
* upserted artifact, matching the streamed UX: the resolved display mode
|
|
2760
|
-
* picks the component ("
|
|
2770
|
+
* picks the component ("collapsed"/"panel" → reference card, "inline" → inline
|
|
2761
2771
|
* preview).
|
|
2762
2772
|
*
|
|
2763
2773
|
* Unlike the streamed path (which embeds content on `artifact_complete`),
|
|
@@ -2774,7 +2784,7 @@ export class AgentWidgetSession {
|
|
|
2774
2784
|
const refId = `artifact-ref-${rec.id}`;
|
|
2775
2785
|
const displayMode = resolveArtifactDisplayMode(
|
|
2776
2786
|
this.config.features?.artifacts,
|
|
2777
|
-
rec
|
|
2787
|
+
rec
|
|
2778
2788
|
);
|
|
2779
2789
|
const rawContent = buildArtifactRefRawContent(displayMode, {
|
|
2780
2790
|
artifactId: rec.id,
|
|
@@ -2782,6 +2792,7 @@ export class AgentWidgetSession {
|
|
|
2782
2792
|
artifactType: rec.artifactType,
|
|
2783
2793
|
status: "complete",
|
|
2784
2794
|
...(rec.file ? { file: rec.file } : {}),
|
|
2795
|
+
...(rec.presentation ? { presentation: rec.presentation } : {}),
|
|
2785
2796
|
...(rec.component ? { component: rec.component } : {}),
|
|
2786
2797
|
...(rec.props ? { componentProps: rec.props } : {}),
|
|
2787
2798
|
...(rec.markdown !== undefined ? { markdown: rec.markdown } : {})
|
|
@@ -2803,6 +2814,38 @@ export class AgentWidgetSession {
|
|
|
2803
2814
|
});
|
|
2804
2815
|
}
|
|
2805
2816
|
|
|
2817
|
+
/**
|
|
2818
|
+
* Re-materialize existing transcript artifact blocks after a live display
|
|
2819
|
+
* preference update. One messages callback keeps conversion atomic.
|
|
2820
|
+
*/
|
|
2821
|
+
private refreshArtifactReferenceBlocks(): void {
|
|
2822
|
+
let changed = false;
|
|
2823
|
+
for (const rec of this.artifacts.values()) {
|
|
2824
|
+
const message = this.messages.find((candidate) => candidate.id === `artifact-ref-${rec.id}`);
|
|
2825
|
+
if (!message) continue;
|
|
2826
|
+
const displayMode = resolveArtifactDisplayMode(
|
|
2827
|
+
this.config.features?.artifacts,
|
|
2828
|
+
rec
|
|
2829
|
+
);
|
|
2830
|
+
const rawContent = buildArtifactRefRawContent(displayMode, {
|
|
2831
|
+
artifactId: rec.id,
|
|
2832
|
+
title: rec.title,
|
|
2833
|
+
artifactType: rec.artifactType,
|
|
2834
|
+
status: rec.status,
|
|
2835
|
+
...(rec.file ? { file: rec.file } : {}),
|
|
2836
|
+
...(rec.presentation ? { presentation: rec.presentation } : {}),
|
|
2837
|
+
...(rec.component ? { component: rec.component } : {}),
|
|
2838
|
+
...(rec.props ? { componentProps: rec.props } : {}),
|
|
2839
|
+
...(rec.markdown !== undefined ? { markdown: rec.markdown } : {}),
|
|
2840
|
+
});
|
|
2841
|
+
if (message.rawContent === rawContent) continue;
|
|
2842
|
+
message.rawContent = rawContent;
|
|
2843
|
+
message.streaming = rec.status === "streaming";
|
|
2844
|
+
changed = true;
|
|
2845
|
+
}
|
|
2846
|
+
if (changed) this.callbacks.onMessagesChanged([...this.messages]);
|
|
2847
|
+
}
|
|
2848
|
+
|
|
2806
2849
|
private clearArtifactState(): void {
|
|
2807
2850
|
if (this.artifacts.size === 0 && this.selectedArtifactId === null) return;
|
|
2808
2851
|
this.artifacts.clear();
|
|
@@ -2942,15 +2985,15 @@ export class AgentWidgetSession {
|
|
|
2942
2985
|
// indefinitely: `resolveWebMcpToolCall` translates the missing-bridge
|
|
2943
2986
|
// case into an isError result that resumes the flow cleanly.
|
|
2944
2987
|
// `suggest_replies`, by contrast, is gated on its feature flag: when
|
|
2945
|
-
// `
|
|
2946
|
-
//
|
|
2947
|
-
//
|
|
2988
|
+
// the resolved `enabled` is false the widget neither renders chips nor
|
|
2989
|
+
// resumes: the same parked-execution posture as a server-declared
|
|
2990
|
+
// ask_user_question with its sheet disabled.
|
|
2948
2991
|
const tc = event.message.toolCall;
|
|
2949
2992
|
const autoResolvable =
|
|
2950
2993
|
!!tc?.name &&
|
|
2951
2994
|
(isWebMcpToolName(tc.name) ||
|
|
2952
2995
|
(tc.name === SUGGEST_REPLIES_TOOL_NAME &&
|
|
2953
|
-
this.config.
|
|
2996
|
+
resolveFollowUpsFeature(this.config).enabled));
|
|
2954
2997
|
if (
|
|
2955
2998
|
event.message.agentMetadata?.awaitingLocalTool === true &&
|
|
2956
2999
|
autoResolvable
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
import { describe, expect, it } from "vitest";
|
|
4
|
+
|
|
5
|
+
const widgetCss = readFileSync(
|
|
6
|
+
new URL("./widget.css", import.meta.url),
|
|
7
|
+
"utf8",
|
|
8
|
+
);
|
|
9
|
+
|
|
10
|
+
describe("suggestion interaction styles", () => {
|
|
11
|
+
it("keeps suggestion surfaces from collapsing inside constrained transcripts", () => {
|
|
12
|
+
const selector = ".persona-suggestions {";
|
|
13
|
+
const start = widgetCss.indexOf(selector);
|
|
14
|
+
const end = widgetCss.indexOf("\n}", start);
|
|
15
|
+
const surfaceRule = widgetCss.slice(start, end);
|
|
16
|
+
|
|
17
|
+
expect(start).toBeGreaterThan(-1);
|
|
18
|
+
expect(surfaceRule).toContain("flex-shrink: 0");
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("keeps the primary emphasis a quiet accent rather than a solid fill", () => {
|
|
22
|
+
const selector = '.persona-suggestion[data-emphasis="primary"] {';
|
|
23
|
+
const start = widgetCss.indexOf(selector);
|
|
24
|
+
const rule = widgetCss.slice(start, widgetCss.indexOf("\n}", start));
|
|
25
|
+
|
|
26
|
+
expect(start).toBeGreaterThan(-1);
|
|
27
|
+
expect(rule).toContain("border-color: var(--persona-primary)");
|
|
28
|
+
// Plain declaration first so engines without color-mix stay transparent.
|
|
29
|
+
expect(rule).toContain("background: transparent");
|
|
30
|
+
expect(rule).toContain(
|
|
31
|
+
"background: color-mix(in srgb, var(--persona-primary) 6%, transparent)",
|
|
32
|
+
);
|
|
33
|
+
expect(rule).not.toContain("--persona-text-inverse");
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
it("strengthens the primary wash on hover instead of filling it", () => {
|
|
37
|
+
const selector =
|
|
38
|
+
'.persona-suggestion[data-emphasis="primary"]:hover {';
|
|
39
|
+
const start = widgetCss.indexOf(selector);
|
|
40
|
+
const hoverRule = widgetCss.slice(start, widgetCss.indexOf("\n }", start));
|
|
41
|
+
|
|
42
|
+
expect(start).toBeGreaterThan(-1);
|
|
43
|
+
expect(hoverRule).toContain("border-color: var(--persona-primary)");
|
|
44
|
+
expect(hoverRule).toContain(
|
|
45
|
+
"color-mix(in srgb, var(--persona-primary) 12%, transparent)",
|
|
46
|
+
);
|
|
47
|
+
expect(hoverRule).not.toContain("filter: brightness");
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it("rests cards flat and reserves the shadow plus lift for hover", () => {
|
|
51
|
+
const restStart = widgetCss.indexOf(".persona-suggestion--card {");
|
|
52
|
+
const restRule = widgetCss.slice(
|
|
53
|
+
restStart,
|
|
54
|
+
widgetCss.indexOf("\n}", restStart),
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
expect(restStart).toBeGreaterThan(-1);
|
|
58
|
+
expect(restRule).toContain(
|
|
59
|
+
"box-shadow: var(--persona-components-suggestion-card-shadow, none)",
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
const hoverStart = widgetCss.indexOf(".persona-suggestion--card:hover {");
|
|
63
|
+
const hoverRule = widgetCss.slice(
|
|
64
|
+
hoverStart,
|
|
65
|
+
widgetCss.indexOf("\n }", hoverStart),
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
expect(hoverStart).toBeGreaterThan(-1);
|
|
69
|
+
expect(hoverRule).toContain("box-shadow: var(--persona-shadow-sm)");
|
|
70
|
+
expect(hoverRule).toContain("transform: translateY(-1px)");
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
it("reveals the suggestion arrow only on hover and keyboard focus", () => {
|
|
74
|
+
// Searched past `__copy`: the bare selector also appears in the shared
|
|
75
|
+
// `flex: 0 0 auto` group above it and the coarse-pointer override below.
|
|
76
|
+
const restStart = widgetCss.indexOf(
|
|
77
|
+
".persona-suggestion__arrow {",
|
|
78
|
+
widgetCss.indexOf(".persona-suggestion__copy {"),
|
|
79
|
+
);
|
|
80
|
+
const restRule = widgetCss.slice(
|
|
81
|
+
restStart,
|
|
82
|
+
widgetCss.indexOf("\n}", restStart),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
expect(restStart).toBeGreaterThan(-1);
|
|
86
|
+
expect(restRule).toContain("opacity: 0;");
|
|
87
|
+
expect(restRule).toContain("transition: opacity");
|
|
88
|
+
expect(widgetCss).toContain(
|
|
89
|
+
".persona-suggestion:focus-visible .persona-suggestion__arrow",
|
|
90
|
+
);
|
|
91
|
+
expect(widgetCss).toContain(
|
|
92
|
+
".persona-suggestion:hover .persona-suggestion__arrow",
|
|
93
|
+
);
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it("caps the starter card grid so wide panels get two columns", () => {
|
|
97
|
+
expect(widgetCss).toContain(
|
|
98
|
+
"grid-template-columns: repeat(auto-fit, minmax(min(100%, 240px), 1fr))",
|
|
99
|
+
);
|
|
100
|
+
const selector =
|
|
101
|
+
'.persona-suggestions[data-variant="card"][data-persona-suggestion-surface="starter"] {';
|
|
102
|
+
const start = widgetCss.indexOf(selector);
|
|
103
|
+
const rule = widgetCss.slice(start, widgetCss.indexOf("\n}", start));
|
|
104
|
+
|
|
105
|
+
expect(start).toBeGreaterThan(-1);
|
|
106
|
+
expect(rule).toContain("max-width: 600px");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("masks only the scrollable chip edges that still contain content", () => {
|
|
110
|
+
expect(widgetCss).toContain(
|
|
111
|
+
'[data-overflow="scroll"][data-scroll-right]:not(',
|
|
112
|
+
);
|
|
113
|
+
expect(widgetCss).toContain(
|
|
114
|
+
'[data-overflow="scroll"][data-scroll-left]:not(',
|
|
115
|
+
);
|
|
116
|
+
expect(widgetCss).toContain(
|
|
117
|
+
'[data-overflow="scroll"][data-scroll-left][data-scroll-right]',
|
|
118
|
+
);
|
|
119
|
+
expect(widgetCss).toContain(
|
|
120
|
+
"--persona-suggestion-scroll-fade-size: 32px",
|
|
121
|
+
);
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
describe("composer spacing styles", () => {
|
|
126
|
+
it("themes the composer form padding and gap with utility-matching fallbacks", () => {
|
|
127
|
+
const selector =
|
|
128
|
+
"[data-persona-root] .persona-widget-composer:not(.persona-pill-composer) {";
|
|
129
|
+
const start = widgetCss.indexOf(selector);
|
|
130
|
+
const rule = widgetCss.slice(start, widgetCss.indexOf("\n}", start));
|
|
131
|
+
|
|
132
|
+
expect(start).toBeGreaterThan(-1);
|
|
133
|
+
// Fallbacks reproduce `persona-px-4 persona-py-3` and `persona-gap-2`.
|
|
134
|
+
expect(rule).toContain(
|
|
135
|
+
"padding: var(--persona-composer-padding, 0.75rem 1rem)",
|
|
136
|
+
);
|
|
137
|
+
expect(rule).toContain("gap: var(--persona-composer-gap, 0.5rem)");
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("themes the composer textarea type with text-sm fallbacks", () => {
|
|
141
|
+
const selector = "[data-persona-root] .persona-composer-textarea {";
|
|
142
|
+
const start = widgetCss.indexOf(selector);
|
|
143
|
+
const rule = widgetCss.slice(start, widgetCss.indexOf("\n}", start));
|
|
144
|
+
|
|
145
|
+
expect(start).toBeGreaterThan(-1);
|
|
146
|
+
expect(rule).toContain(
|
|
147
|
+
"font-size: var(--persona-composer-font-size, 0.875rem)",
|
|
148
|
+
);
|
|
149
|
+
expect(rule).toContain(
|
|
150
|
+
"line-height: var(--persona-composer-line-height, 1.25rem)",
|
|
151
|
+
);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("leaves the pill composer on its own single-row geometry", () => {
|
|
155
|
+
const start = widgetCss.indexOf(".persona-pill-composer {");
|
|
156
|
+
const rule = widgetCss.slice(start, widgetCss.indexOf("\n}", start));
|
|
157
|
+
|
|
158
|
+
expect(start).toBeGreaterThan(-1);
|
|
159
|
+
expect(rule).toContain("padding: 6px 14px");
|
|
160
|
+
expect(rule).toContain("gap: 8px");
|
|
161
|
+
});
|
|
162
|
+
});
|