@runtypelabs/persona 4.17.1 → 4.18.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/chunk-3I2S3I5P.js +1 -0
- package/dist/chunk-76IELU7F.js +1 -0
- package/dist/chunk-EFJBUH5F.js +1 -0
- package/dist/chunk-PDPVORUF.js +1 -0
- package/dist/chunk-Q735PSZL.js +1 -0
- package/dist/chunk-VK6JOIYJ.js +1 -0
- package/dist/codegen.cjs +4 -4
- package/dist/codegen.js +4 -4
- package/dist/{context-mentions-BTSTIBFC.js → context-mentions-MJL6S6P2.js} +1 -1
- package/dist/context-mentions.cjs +9 -8
- package/dist/context-mentions.d.cts +1212 -14
- package/dist/context-mentions.d.ts +1212 -14
- package/dist/context-mentions.js +9 -8
- package/dist/event-stream-view-4EZ7BGFK.js +1 -0
- package/dist/event-stream-view.cjs +129 -0
- package/dist/event-stream-view.d.cts +8384 -0
- package/dist/event-stream-view.d.ts +8384 -0
- package/dist/event-stream-view.js +129 -0
- package/dist/history-view-BD6OXLNA.js +898 -0
- package/dist/history-view.cjs +898 -0
- package/dist/history-view.d.cts +1461 -0
- package/dist/history-view.d.ts +1461 -0
- package/dist/history-view.js +898 -0
- package/dist/index.cjs +77 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1987 -17
- package/dist/index.d.ts +1987 -17
- package/dist/index.global.js +67 -66
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +77 -77
- package/dist/index.js.map +1 -1
- package/dist/launcher.global.js +5 -4
- package/dist/launcher.global.js.map +1 -1
- package/dist/plugin-kit.cjs +1 -1
- package/dist/plugin-kit.d.cts +0 -8
- package/dist/plugin-kit.d.ts +0 -8
- package/dist/plugin-kit.js +1 -1
- package/dist/smart-dom-reader.d.cts +1212 -14
- package/dist/smart-dom-reader.d.ts +1212 -14
- package/dist/theme-editor-preview.cjs +970 -73
- package/dist/theme-editor-preview.d.cts +1331 -15
- package/dist/theme-editor-preview.d.ts +1331 -15
- package/dist/theme-editor-preview.js +81 -81
- package/dist/theme-editor.cjs +7 -7
- package/dist/theme-editor.d.cts +1215 -15
- package/dist/theme-editor.d.ts +1215 -15
- package/dist/theme-editor.js +9 -9
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +226 -7
- package/dist/theme-reference.d.ts +226 -7
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +1 -1
- package/package.json +16 -4
- package/src/client-history-rest.test.ts +1034 -0
- package/src/client-visitor-history.test.ts +596 -0
- package/src/client.ts +1040 -24
- package/src/components/artifact-inline.ts +8 -1
- package/src/components/artifact-pane.test.ts +278 -1
- package/src/components/artifact-pane.ts +135 -67
- package/src/components/artifact-preview.test.ts +14 -1
- package/src/components/artifact-preview.ts +9 -0
- package/src/components/header-builder.ts +3 -0
- package/src/components/header-layouts.test.ts +171 -7
- package/src/components/header-layouts.ts +83 -53
- package/src/components/header-parts.test.ts +154 -1
- package/src/components/header-parts.ts +266 -138
- package/src/components/history-confirm-dialog.ts +177 -0
- package/src/components/history-shell-copy.ts +99 -0
- package/src/components/history-view/announcer.ts +40 -0
- package/src/components/history-view/copy.ts +105 -0
- package/src/components/history-view/css.ts +1078 -0
- package/src/components/history-view/grouping.test.ts +142 -0
- package/src/components/history-view/grouping.ts +142 -0
- package/src/components/history-view/icons.ts +73 -0
- package/src/components/history-view/rows.ts +341 -0
- package/src/components/history-view/state.ts +309 -0
- package/src/components/history-view.test.ts +2660 -0
- package/src/components/history-view.ts +1891 -0
- package/src/components/launcher-surface.test.ts +133 -0
- package/src/components/launcher.test.ts +18 -0
- package/src/components/launcher.ts +7 -7
- package/src/components/panel.ts +17 -7
- package/src/components/welcome.ts +72 -2
- package/src/defaults.ts +36 -3
- package/src/event-stream-view-bundle.test.ts +69 -0
- package/src/event-stream-view-entry.ts +19 -0
- package/src/event-stream-view-loader.ts +31 -0
- package/src/event-stream-view.ts +19 -0
- package/src/generated/runtype-openapi-contract.ts +1 -0
- package/src/history-render.ts +325 -0
- package/src/history-view-bundle.test.ts +78 -0
- package/src/history-view-entry.ts +41 -0
- package/src/history-view-loader.test.ts +30 -0
- package/src/history-view-loader.ts +32 -0
- package/src/history-view.ts +28 -0
- package/src/index-core.ts +50 -2
- package/src/index-global.ts +55 -0
- package/src/internal/demo-history-provider.test.ts +402 -0
- package/src/internal/demo-history-provider.ts +689 -0
- package/src/internal/history-provider-registry.ts +25 -0
- package/src/internal/history-provider.ts +150 -0
- package/src/internal/runtype-history-provider.test.ts +313 -0
- package/src/internal/runtype-history-provider.ts +240 -0
- package/src/plugin-kit.test.ts +16 -0
- package/src/plugin-kit.ts +13 -5
- package/src/plugins/types.ts +177 -1
- package/src/session-history.test.ts +1397 -0
- package/src/session.ts +1200 -26
- package/src/styles/widget-styles.test.ts +133 -0
- package/src/styles/widget.css +227 -8
- package/src/theme-editor/preview-utils.test.ts +19 -0
- package/src/theme-editor/preview-utils.ts +15 -1
- package/src/theme-editor/preview.ts +7 -1
- package/src/theme-editor/role-mappings.ts +4 -1
- package/src/theme-editor/sections.test.ts +168 -1
- package/src/theme-editor/sections.ts +136 -7
- package/src/theme-editor/webmcp/summary.ts +16 -2
- package/src/theme-reference.ts +20 -6
- package/src/types/theme.ts +213 -7
- package/src/types.ts +927 -7
- package/src/ui.artifact-rail-resize.test.ts +235 -0
- package/src/ui.attachments-drop.test.ts +1 -0
- package/src/ui.header-update-stability.test.ts +2 -2
- package/src/ui.history-chunk.test.ts +72 -0
- package/src/ui.history-config-provider.test.ts +318 -0
- package/src/ui.history-controller.test.ts +711 -0
- package/src/ui.history-lifecycle.test.ts +456 -0
- package/src/ui.history-render-hooks.test.ts +904 -0
- package/src/ui.history-shell.test.ts +1887 -0
- package/src/ui.rail-overlay.test.ts +573 -0
- package/src/ui.rail-resize.test.ts +284 -0
- package/src/ui.rebuild-composer.test.ts +21 -0
- package/src/ui.render-welcome.test.ts +42 -0
- package/src/ui.shortcuts.test.ts +166 -0
- package/src/ui.ts +3071 -240
- package/src/utils/__fixtures__/client-conversation-history-wire.json +108 -0
- package/src/utils/artifact-file.ts +15 -0
- package/src/utils/artifact-gate.test.ts +4 -0
- package/src/utils/artifact-gate.ts +13 -17
- package/src/utils/artifact-resize.test.ts +28 -0
- package/src/utils/artifact-resize.ts +9 -2
- package/src/utils/buttons.ts +188 -1
- package/src/utils/config-merge.test.ts +91 -0
- package/src/utils/dropdown.ts +10 -1
- package/src/utils/history-messages.test.ts +284 -0
- package/src/utils/history-messages.ts +284 -0
- package/src/utils/icons.ts +3 -0
- package/src/utils/shortcuts.test.ts +288 -0
- package/src/utils/shortcuts.ts +221 -0
- package/src/utils/theme.test.ts +501 -4
- package/src/utils/theme.ts +42 -3
- package/src/utils/tokens.ts +153 -15
- package/src/utils/tooltip-timing.ts +4 -0
- package/src/utils/tooltip.test.ts +249 -7
- package/src/utils/tooltip.ts +146 -4
- package/src/utils/visitor-store.test.ts +425 -0
- package/src/utils/visitor-store.ts +337 -0
- package/dist/chunk-QJ4LV2KW.js +0 -1
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History render-hook arbitration (plan D7, "Public rendering customization
|
|
3
|
+
* contract").
|
|
4
|
+
*
|
|
5
|
+
* Lives in CORE, not in the lazy view chunk: plugins must arbitrate before any
|
|
6
|
+
* chunk-owned DOM exists, and the hook contexts are part of the public package
|
|
7
|
+
* surface. Only type imports reach into `history-view-entry`, so nothing here
|
|
8
|
+
* pulls the chunk into the main bundles.
|
|
9
|
+
*
|
|
10
|
+
* Arbitration:
|
|
11
|
+
* - plugins run in priority order; the first non-null `renderHistoryView` wins
|
|
12
|
+
* - null falls back to the default full view, which then applies the first
|
|
13
|
+
* non-null header/row/state hook at each slot
|
|
14
|
+
* - the full hook's `defaultRenderer()` bypasses only that hook: the default
|
|
15
|
+
* view is built and its lower slots still arbitrate, including the same
|
|
16
|
+
* plugin's. Hook-kind separation is what prevents recursion.
|
|
17
|
+
* - a thrown hook is reported and skipped; the default renderer stands in and
|
|
18
|
+
* no history state is lost.
|
|
19
|
+
*
|
|
20
|
+
* Contexts are frozen, field-by-field snapshots. The provider, credentials,
|
|
21
|
+
* proofs, visitor ids, raw responses, and prepared commit/discard never appear:
|
|
22
|
+
* every action routes back through the view's own operations, which are the
|
|
23
|
+
* same ones the default DOM invokes.
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { AgentWidgetPlugin } from "./plugins/types";
|
|
27
|
+
import type {
|
|
28
|
+
AgentWidgetConfig,
|
|
29
|
+
AgentWidgetHistoryRenderActions,
|
|
30
|
+
AgentWidgetRenderHistoryConversationContext,
|
|
31
|
+
AgentWidgetRenderHistoryHeaderContext,
|
|
32
|
+
AgentWidgetRenderHistoryStateContext,
|
|
33
|
+
AgentWidgetRenderHistoryViewContext,
|
|
34
|
+
HistoryConversationSummary,
|
|
35
|
+
HistoryPendingAction,
|
|
36
|
+
HistoryReturnSurface,
|
|
37
|
+
ResolvedHistoryPresentation,
|
|
38
|
+
} from "./types";
|
|
39
|
+
import type {
|
|
40
|
+
HistoryRailSection,
|
|
41
|
+
HistoryViewHandle,
|
|
42
|
+
HistoryViewModel,
|
|
43
|
+
HistoryViewSlotRenderers,
|
|
44
|
+
} from "./history-view-entry";
|
|
45
|
+
|
|
46
|
+
export interface HistoryRenderSurfaceOptions {
|
|
47
|
+
plugins: AgentWidgetPlugin[];
|
|
48
|
+
config: AgentWidgetConfig;
|
|
49
|
+
getPresentation: () => ResolvedHistoryPresentation;
|
|
50
|
+
getReturnSurface: () => HistoryReturnSurface;
|
|
51
|
+
/** Shell close: records the return surface and restores focus. */
|
|
52
|
+
close: () => void;
|
|
53
|
+
/**
|
|
54
|
+
* Builds the default view with the core-owned slots wired in. Invoked once;
|
|
55
|
+
* `renderDom` starts false so no slot runs before the full hook arbitrates.
|
|
56
|
+
*/
|
|
57
|
+
createView: (options: {
|
|
58
|
+
slots: HistoryViewSlotRenderers;
|
|
59
|
+
renderDom: boolean;
|
|
60
|
+
onModelChange: () => void;
|
|
61
|
+
/** Plugin rail sections, for the core to merge behind the config ones. */
|
|
62
|
+
railSections: HistoryRailSection[];
|
|
63
|
+
}) => HistoryViewHandle;
|
|
64
|
+
/** Mount (previous === null) or swap the arbitrated element. */
|
|
65
|
+
onElementChanged: (next: HTMLElement, previous: HTMLElement | null) => void;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface HistoryRenderSurface {
|
|
69
|
+
readonly view: HistoryViewHandle;
|
|
70
|
+
/** Currently arbitrated element: the default view, or a plugin's. */
|
|
71
|
+
readonly element: HTMLElement;
|
|
72
|
+
/** Re-run arbitration with the current snapshot. Inert after dispose. */
|
|
73
|
+
requestRender(): void;
|
|
74
|
+
/** Runs pending cleanups and makes `requestRender()` inert. */
|
|
75
|
+
dispose(): void;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const warn = (hook: string, error: unknown): void => {
|
|
79
|
+
// eslint-disable-next-line no-console
|
|
80
|
+
console.warn(`[persona] ${hook} threw`, error);
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
/** Frozen shallow copy: a plugin can neither mutate nor retain view state. */
|
|
84
|
+
const frozen = <T extends object>(value: T): T => Object.freeze({ ...value });
|
|
85
|
+
|
|
86
|
+
/** Field-by-field copy: an unknown provider field can never reach a plugin. */
|
|
87
|
+
const snapshotConversation = (
|
|
88
|
+
conversation: HistoryConversationSummary
|
|
89
|
+
): HistoryConversationSummary =>
|
|
90
|
+
Object.freeze({
|
|
91
|
+
id: conversation.id,
|
|
92
|
+
title: conversation.title,
|
|
93
|
+
targetId: conversation.targetId,
|
|
94
|
+
preview: conversation.preview,
|
|
95
|
+
messageCount: conversation.messageCount,
|
|
96
|
+
createdAt: conversation.createdAt,
|
|
97
|
+
updatedAt: conversation.updatedAt,
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const snapshotPending = (pending: HistoryPendingAction): HistoryPendingAction =>
|
|
101
|
+
pending ? frozen(pending) : null;
|
|
102
|
+
|
|
103
|
+
export function createHistoryRenderSurface(
|
|
104
|
+
options: HistoryRenderSurfaceOptions
|
|
105
|
+
): HistoryRenderSurface {
|
|
106
|
+
const { plugins, config } = options;
|
|
107
|
+
let disposed = false;
|
|
108
|
+
let arbitrating = false;
|
|
109
|
+
/** The view constructor emits a model signal; nothing may arbitrate yet. */
|
|
110
|
+
let ready = false;
|
|
111
|
+
/** Lower-level slots run only when the default view owns the surface. */
|
|
112
|
+
let slotsActive = false;
|
|
113
|
+
let element: HTMLElement | null = null;
|
|
114
|
+
const cleanups: Array<() => void> = [];
|
|
115
|
+
|
|
116
|
+
const runCleanups = (): void => {
|
|
117
|
+
for (const callback of cleanups.splice(0)) {
|
|
118
|
+
try {
|
|
119
|
+
callback();
|
|
120
|
+
} catch (error) {
|
|
121
|
+
warn("renderHistoryView cleanup", error);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
type SlotHook =
|
|
127
|
+
| "renderHistoryHeader"
|
|
128
|
+
| "renderHistoryConversation"
|
|
129
|
+
| "renderHistoryState";
|
|
130
|
+
|
|
131
|
+
/** One first-non-null pass per slot; a throw is reported and skipped. */
|
|
132
|
+
const arbitrateSlot = <C>(hook: SlotHook, context: C): HTMLElement | null => {
|
|
133
|
+
for (const plugin of plugins) {
|
|
134
|
+
const render = plugin[hook] as
|
|
135
|
+
| ((context: C) => HTMLElement | null)
|
|
136
|
+
| undefined;
|
|
137
|
+
if (!render) continue;
|
|
138
|
+
try {
|
|
139
|
+
const custom = render(context);
|
|
140
|
+
if (custom) return custom;
|
|
141
|
+
} catch (error) {
|
|
142
|
+
warn(hook, error);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
/** Fields every slot context carries. */
|
|
149
|
+
const slotBase = (): {
|
|
150
|
+
presentation: ResolvedHistoryPresentation;
|
|
151
|
+
config: AgentWidgetConfig;
|
|
152
|
+
} => ({ presentation: options.getPresentation(), config });
|
|
153
|
+
|
|
154
|
+
const slots: HistoryViewSlotRenderers = {
|
|
155
|
+
header: (slot) =>
|
|
156
|
+
slotsActive
|
|
157
|
+
? arbitrateSlot<AgentWidgetRenderHistoryHeaderContext>(
|
|
158
|
+
"renderHistoryHeader",
|
|
159
|
+
Object.freeze({
|
|
160
|
+
...slotBase(),
|
|
161
|
+
returnSurface: options.getReturnSurface(),
|
|
162
|
+
identityStatus: frozen(slot.identityStatus),
|
|
163
|
+
pendingAction: snapshotPending(slot.pendingAction),
|
|
164
|
+
copy: frozen(slot.copy),
|
|
165
|
+
actions: Object.freeze({
|
|
166
|
+
close: actions.close,
|
|
167
|
+
startNewConversation: actions.startNewConversation,
|
|
168
|
+
}),
|
|
169
|
+
defaultRenderer: slot.defaultRenderer,
|
|
170
|
+
})
|
|
171
|
+
)
|
|
172
|
+
: null,
|
|
173
|
+
conversation: (slot) =>
|
|
174
|
+
slotsActive
|
|
175
|
+
? arbitrateSlot<AgentWidgetRenderHistoryConversationContext>(
|
|
176
|
+
"renderHistoryConversation",
|
|
177
|
+
Object.freeze({
|
|
178
|
+
...slotBase(),
|
|
179
|
+
conversation: snapshotConversation(slot.conversation),
|
|
180
|
+
active: slot.active,
|
|
181
|
+
pending: slot.pending,
|
|
182
|
+
open: slot.open,
|
|
183
|
+
requestDelete: slot.requestDelete,
|
|
184
|
+
defaultRenderer: slot.defaultRenderer,
|
|
185
|
+
})
|
|
186
|
+
)
|
|
187
|
+
: null,
|
|
188
|
+
state: (slot) =>
|
|
189
|
+
slotsActive
|
|
190
|
+
? arbitrateSlot<AgentWidgetRenderHistoryStateContext>(
|
|
191
|
+
"renderHistoryState",
|
|
192
|
+
Object.freeze({
|
|
193
|
+
...slotBase(),
|
|
194
|
+
state: frozen(slot.state),
|
|
195
|
+
identityStatus: frozen(slot.identityStatus),
|
|
196
|
+
copy: frozen(slot.copy),
|
|
197
|
+
...(slot.retry ? { retry: slot.retry } : {}),
|
|
198
|
+
...(slot.startNewConversation
|
|
199
|
+
? { startNewConversation: slot.startNewConversation }
|
|
200
|
+
: {}),
|
|
201
|
+
defaultRenderer: slot.defaultRenderer,
|
|
202
|
+
})
|
|
203
|
+
)
|
|
204
|
+
: null,
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Plugin rail sections, in plugin order. Only the context is assembled here;
|
|
209
|
+
* a throwing `render` is the view's to warn about once and drop. `actions`
|
|
210
|
+
* resolves at invocation, which never happens before the view owns the DOM.
|
|
211
|
+
*/
|
|
212
|
+
const railSections: HistoryRailSection[] = plugins.flatMap((plugin) =>
|
|
213
|
+
(plugin.railSections ?? []).map((section) => ({
|
|
214
|
+
...section,
|
|
215
|
+
placement: section.placement ?? "above-conversations",
|
|
216
|
+
items: [],
|
|
217
|
+
render: (collapsed: boolean) =>
|
|
218
|
+
section.render(
|
|
219
|
+
Object.freeze({
|
|
220
|
+
collapsed,
|
|
221
|
+
presentation: options.getPresentation(),
|
|
222
|
+
actions,
|
|
223
|
+
})
|
|
224
|
+
),
|
|
225
|
+
}))
|
|
226
|
+
);
|
|
227
|
+
|
|
228
|
+
const view = options.createView({
|
|
229
|
+
slots,
|
|
230
|
+
renderDom: false,
|
|
231
|
+
onModelChange: () => requestRender(),
|
|
232
|
+
railSections,
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
/** Every action IS the operation the default DOM invokes; close is the shell's. */
|
|
236
|
+
const actions: AgentWidgetHistoryRenderActions = Object.freeze({
|
|
237
|
+
...view.operations,
|
|
238
|
+
close: options.close,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
/** Builds (and renders) the default view; lower slots arbitrate from here on. */
|
|
242
|
+
const buildDefault = (): HTMLElement => {
|
|
243
|
+
slotsActive = true;
|
|
244
|
+
view.setDomRenderEnabled(true);
|
|
245
|
+
return view.element;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
const buildViewContext = (
|
|
249
|
+
model: HistoryViewModel
|
|
250
|
+
): AgentWidgetRenderHistoryViewContext =>
|
|
251
|
+
Object.freeze({
|
|
252
|
+
conversations: Object.freeze(
|
|
253
|
+
model.conversations.map(snapshotConversation)
|
|
254
|
+
),
|
|
255
|
+
activeConversationId: model.activeConversationId,
|
|
256
|
+
state: frozen(model.state),
|
|
257
|
+
pendingAction: snapshotPending(model.pendingAction),
|
|
258
|
+
identityStatus: frozen(model.identityStatus),
|
|
259
|
+
nextCursor: model.nextCursor,
|
|
260
|
+
presentation: options.getPresentation(),
|
|
261
|
+
returnSurface: options.getReturnSurface(),
|
|
262
|
+
config,
|
|
263
|
+
copy: frozen(view.copy),
|
|
264
|
+
actions,
|
|
265
|
+
defaultRenderer: buildDefault,
|
|
266
|
+
requestRender: () => requestRender(),
|
|
267
|
+
onCleanup: (callback: () => void) => {
|
|
268
|
+
cleanups.push(callback);
|
|
269
|
+
},
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
const arbitrate = (): HTMLElement => {
|
|
273
|
+
slotsActive = false;
|
|
274
|
+
let custom: HTMLElement | null = null;
|
|
275
|
+
for (const plugin of plugins) {
|
|
276
|
+
if (!plugin.renderHistoryView) continue;
|
|
277
|
+
try {
|
|
278
|
+
custom = plugin.renderHistoryView(buildViewContext(view.getModel()));
|
|
279
|
+
} catch (error) {
|
|
280
|
+
warn("renderHistoryView", error);
|
|
281
|
+
custom = null;
|
|
282
|
+
}
|
|
283
|
+
if (custom) break;
|
|
284
|
+
}
|
|
285
|
+
// Returning the default view (or a wrapper around it) is composition, not
|
|
286
|
+
// a takeover: it keeps rendering and its slots keep arbitrating.
|
|
287
|
+
// `contains` is true for the node itself, so this covers a plain return.
|
|
288
|
+
const usesDefault = !custom || custom.contains(view.element);
|
|
289
|
+
slotsActive = usesDefault;
|
|
290
|
+
view.setDomRenderEnabled(usesDefault);
|
|
291
|
+
return custom ?? view.element;
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
function requestRender(): void {
|
|
295
|
+
// A hook calling requestRender() from inside its own render would recurse.
|
|
296
|
+
if (!ready || disposed || arbitrating) return;
|
|
297
|
+
arbitrating = true;
|
|
298
|
+
try {
|
|
299
|
+
// Cleanups belong to the render being replaced, so they run first.
|
|
300
|
+
runCleanups();
|
|
301
|
+
const next = arbitrate();
|
|
302
|
+
if (next === element) return;
|
|
303
|
+
const previous = element;
|
|
304
|
+
element = next;
|
|
305
|
+
options.onElementChanged(next, previous);
|
|
306
|
+
} finally {
|
|
307
|
+
arbitrating = false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
ready = true;
|
|
312
|
+
requestRender();
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
view,
|
|
316
|
+
get element() {
|
|
317
|
+
return element ?? view.element;
|
|
318
|
+
},
|
|
319
|
+
requestRender,
|
|
320
|
+
dispose: () => {
|
|
321
|
+
disposed = true;
|
|
322
|
+
runCleanups();
|
|
323
|
+
},
|
|
324
|
+
};
|
|
325
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { describe, it, expect } from "vitest";
|
|
2
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Bundle guard for the lazy history-view chunk (see
|
|
7
|
+
* `docs/visitor-history-implementation-plan.md` D7, Bundle size).
|
|
8
|
+
*
|
|
9
|
+
* The Messages view must NOT land in ANY core bundle shipped to consumers —
|
|
10
|
+
* neither the CDN IIFE (`dist/index.global.js`) nor the ESM/CJS bundles
|
|
11
|
+
* (`dist/index.js` / `dist/index.cjs`). It ships as the sibling chunk
|
|
12
|
+
* `dist/history-view.{js,cjs}`, loaded on demand: the IIFE via a sibling URL,
|
|
13
|
+
* ESM/CJS via the external `@runtypelabs/persona/history-view` subpath. A stray
|
|
14
|
+
* static import would pull the view back in; this fails then.
|
|
15
|
+
*
|
|
16
|
+
* Rejected-load retry (the other half of the transport contract) is covered by
|
|
17
|
+
* `history-view-loader.test.ts`.
|
|
18
|
+
*
|
|
19
|
+
* Skips when `dist/` hasn't been built (e.g. a test-only CI step).
|
|
20
|
+
*/
|
|
21
|
+
const dist = (f: string) => resolve(__dirname, "..", "dist", f);
|
|
22
|
+
|
|
23
|
+
// Chunk-only literals. `persona-history-view` is the view's root class and is
|
|
24
|
+
// never emitted by core code; the sibling-URL stub in the core bundles is the
|
|
25
|
+
// bare filename `history-view.js`, which does not contain these.
|
|
26
|
+
const RUNTIME_MARKERS = ["persona-history-view", "persona-history-view-loading"];
|
|
27
|
+
|
|
28
|
+
const SUBPATH = "@runtypelabs/persona/history-view";
|
|
29
|
+
|
|
30
|
+
describe("history-view bundle split", () => {
|
|
31
|
+
const iifeBuilt =
|
|
32
|
+
existsSync(dist("index.global.js")) && existsSync(dist("history-view.js"));
|
|
33
|
+
const esmBuilt = existsSync(dist("index.js")) && existsSync(dist("index.cjs"));
|
|
34
|
+
|
|
35
|
+
it.runIf(iifeBuilt)("keeps the history view OUT of the core IIFE bundle", () => {
|
|
36
|
+
const core = readFileSync(dist("index.global.js"), "utf8");
|
|
37
|
+
for (const marker of RUNTIME_MARKERS) {
|
|
38
|
+
expect(core.includes(marker), `IIFE bundle unexpectedly contains "${marker}"`).toBe(
|
|
39
|
+
false
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
// The loader stub (sibling-URL reference) must remain so the chunk can load.
|
|
43
|
+
expect(core).toContain("history-view.js");
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it.runIf(esmBuilt)("keeps the history view OUT of the ESM/CJS bundles", () => {
|
|
47
|
+
for (const file of ["index.js", "index.cjs"]) {
|
|
48
|
+
const core = readFileSync(dist(file), "utf8");
|
|
49
|
+
for (const marker of RUNTIME_MARKERS) {
|
|
50
|
+
expect(core.includes(marker), `${file} unexpectedly contains "${marker}"`).toBe(
|
|
51
|
+
false
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
// TODO(history-ui): the ESM/CJS core only reaches the loader once `ui.ts`
|
|
55
|
+
// mounts the view, so today these bundles carry no history reference at
|
|
56
|
+
// all. The invariant asserted meanwhile — and automatically once the UI
|
|
57
|
+
// lands — is that any reference goes through the external subpath rather
|
|
58
|
+
// than an inlined relative import.
|
|
59
|
+
if (core.includes("history-view")) {
|
|
60
|
+
expect(
|
|
61
|
+
core.includes(SUBPATH),
|
|
62
|
+
`${file} must reach the view via the external subpath`
|
|
63
|
+
).toBe(true);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it.runIf(iifeBuilt)("ships the history view in the sibling chunk", () => {
|
|
69
|
+
const chunk = readFileSync(dist("history-view.js"), "utf8");
|
|
70
|
+
for (const marker of RUNTIME_MARKERS) {
|
|
71
|
+
expect(chunk.includes(marker), `chunk is missing "${marker}"`).toBe(true);
|
|
72
|
+
}
|
|
73
|
+
// A CJS twin exists for require()-based consumers.
|
|
74
|
+
expect(existsSync(dist("history-view.cjs"))).toBe(true);
|
|
75
|
+
// Declarations ship with the chunk so the subpath is typed.
|
|
76
|
+
expect(existsSync(dist("history-view.d.ts"))).toBe(true);
|
|
77
|
+
});
|
|
78
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* History-view runtime entry — the module that assembles the lazily loaded
|
|
3
|
+
* Messages surface. It is NEVER statically imported on the core path: the
|
|
4
|
+
* IIFE/CDN build marks `@runtypelabs/persona/history-view` external
|
|
5
|
+
* (`tsup.global.config.ts`) and loads the standalone `history-view.js` chunk
|
|
6
|
+
* from a sibling URL on first open of the history UI. The core reaches it
|
|
7
|
+
* through `history-view-loader.ts`.
|
|
8
|
+
*
|
|
9
|
+
* Mirrors `context-mentions-entry.ts`; see
|
|
10
|
+
* `docs/visitor-history-implementation-plan.md` D7 (Bundle size).
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export { createHistoryView } from "./components/history-view";
|
|
14
|
+
export type {
|
|
15
|
+
HistoryViewOptions,
|
|
16
|
+
HistoryViewHandle,
|
|
17
|
+
HistoryViewPresentation,
|
|
18
|
+
HistoryHeaderPlacement,
|
|
19
|
+
HistoryViewPendingAction,
|
|
20
|
+
HistoryViewModel,
|
|
21
|
+
HistoryViewOperations,
|
|
22
|
+
HistoryRailSection,
|
|
23
|
+
HistoryRailSectionItem,
|
|
24
|
+
HistoryRailSectionPlacement,
|
|
25
|
+
HistoryViewSlotRenderers,
|
|
26
|
+
HistoryHeaderSlotContext,
|
|
27
|
+
HistoryConversationSlotContext,
|
|
28
|
+
HistoryStateSlotContext,
|
|
29
|
+
} from "./components/history-view";
|
|
30
|
+
export {
|
|
31
|
+
HISTORY_VIEW_COPY_DEFAULTS,
|
|
32
|
+
resolveHistoryViewCopy,
|
|
33
|
+
} from "./components/history-view/copy";
|
|
34
|
+
export type {
|
|
35
|
+
HistoryViewCopyInput,
|
|
36
|
+
ResolvedHistoryViewCopy,
|
|
37
|
+
} from "./components/history-view/copy";
|
|
38
|
+
export type {
|
|
39
|
+
HistoryListState,
|
|
40
|
+
HistoryListErrorReason,
|
|
41
|
+
} from "./components/history-view/state";
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
loadHistoryView,
|
|
4
|
+
setHistoryViewLoader,
|
|
5
|
+
type HistoryViewModule,
|
|
6
|
+
} from "./history-view-loader";
|
|
7
|
+
|
|
8
|
+
const fakeModule = {
|
|
9
|
+
createHistoryView: vi.fn(),
|
|
10
|
+
} as unknown as HistoryViewModule;
|
|
11
|
+
|
|
12
|
+
describe("loadHistoryView", () => {
|
|
13
|
+
it("retries after a rejected load instead of caching the failure forever", async () => {
|
|
14
|
+
const loader = vi
|
|
15
|
+
.fn<() => Promise<HistoryViewModule>>()
|
|
16
|
+
.mockRejectedValueOnce(new Error("network"))
|
|
17
|
+
.mockResolvedValueOnce(fakeModule);
|
|
18
|
+
setHistoryViewLoader(loader);
|
|
19
|
+
|
|
20
|
+
// First call surfaces the rejection to the caller.
|
|
21
|
+
await expect(loadHistoryView()).rejects.toThrow("network");
|
|
22
|
+
// A later call retries (the failed promise was not cached) and resolves.
|
|
23
|
+
await expect(loadHistoryView()).resolves.toBe(fakeModule);
|
|
24
|
+
expect(loader).toHaveBeenCalledTimes(2);
|
|
25
|
+
|
|
26
|
+
// Once resolved, the module is cached (no third loader call).
|
|
27
|
+
await expect(loadHistoryView()).resolves.toBe(fakeModule);
|
|
28
|
+
expect(loader).toHaveBeenCalledTimes(2);
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loader indirection for the lazy history-view chunk (core bundle, tiny).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `context-mentions-loader.ts`: the IIFE/CDN entry (`index-global.ts`)
|
|
5
|
+
* registers a loader that imports the standalone `history-view.js` chunk from a
|
|
6
|
+
* sibling URL. ESM/CJS consumers fall back to importing the package's
|
|
7
|
+
* `./history-view` subpath — which `build:client` marks external, so the view is
|
|
8
|
+
* NOT inlined into `dist/index.{js,cjs}` and stays out of the core bundle until
|
|
9
|
+
* the history UI is first opened. (A relative `./history-view-entry` import
|
|
10
|
+
* would be inlined by the `--splitting false` build, defeating the split.)
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { createChunkLoader } from "./utils/chunk-loader";
|
|
14
|
+
import type {
|
|
15
|
+
HistoryViewOptions,
|
|
16
|
+
HistoryViewHandle,
|
|
17
|
+
} from "./history-view-entry";
|
|
18
|
+
|
|
19
|
+
export type HistoryViewModule = {
|
|
20
|
+
createHistoryView: (options: HistoryViewOptions) => HistoryViewHandle;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// IIFE/CDN: sibling-URL chunk via the registered loader.
|
|
24
|
+
// ESM/CJS fallback: the package's own `./history-view` subpath (external, so the
|
|
25
|
+
// runtime chunk is code-split out of dist/index.{js,cjs} rather than inlined).
|
|
26
|
+
// Memoization + rejection-retry semantics live in `createChunkLoader`.
|
|
27
|
+
const { setLoader, load } = createChunkLoader<HistoryViewModule>({
|
|
28
|
+
fallbackImport: () => import("@runtypelabs/persona/history-view"),
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export const setHistoryViewLoader = setLoader;
|
|
32
|
+
export const loadHistoryView = load;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone history-view chunk (`dist/history-view.js`).
|
|
3
|
+
*
|
|
4
|
+
* The IIFE/CDN widget bundle marks `@runtypelabs/persona/history-view` external
|
|
5
|
+
* so the Messages surface is excluded from the core payload; this file is built
|
|
6
|
+
* as a self-contained sibling chunk (`tsup.history-view.config.ts`) and loaded on
|
|
7
|
+
* demand by the loader registered in `index-global.ts`. ESM/CJS consumers reach
|
|
8
|
+
* it via this same (external) subpath.
|
|
9
|
+
*
|
|
10
|
+
* Lazy-runtime transport entry only: exports are limited to the mount contract
|
|
11
|
+
* the loader needs, NOT the public history provider/controller seam.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
export { createHistoryView } from "./history-view-entry";
|
|
15
|
+
export {
|
|
16
|
+
HISTORY_VIEW_COPY_DEFAULTS,
|
|
17
|
+
resolveHistoryViewCopy,
|
|
18
|
+
} from "./history-view-entry";
|
|
19
|
+
export type {
|
|
20
|
+
HistoryViewOptions,
|
|
21
|
+
HistoryViewHandle,
|
|
22
|
+
HistoryViewPresentation,
|
|
23
|
+
HistoryViewPendingAction,
|
|
24
|
+
HistoryViewCopyInput,
|
|
25
|
+
ResolvedHistoryViewCopy,
|
|
26
|
+
HistoryListState,
|
|
27
|
+
HistoryListErrorReason,
|
|
28
|
+
} from "./history-view-entry";
|
package/src/index-core.ts
CHANGED
|
@@ -32,6 +32,7 @@ export type {
|
|
|
32
32
|
AgentWidgetMessage,
|
|
33
33
|
AgentWidgetLauncherConfig,
|
|
34
34
|
AgentWidgetLauncherTeaserConfig,
|
|
35
|
+
AgentWidgetTooltipConfig,
|
|
35
36
|
AgentWidgetDockConfig,
|
|
36
37
|
AgentWidgetEvent,
|
|
37
38
|
AgentWidgetStreamParser,
|
|
@@ -93,6 +94,7 @@ export type {
|
|
|
93
94
|
// Client token types
|
|
94
95
|
ClientSession,
|
|
95
96
|
ClientInitResponse,
|
|
97
|
+
PreparedClientSession,
|
|
96
98
|
ClientChatRequest,
|
|
97
99
|
ClientFeedbackRequest,
|
|
98
100
|
ClientFeedbackType,
|
|
@@ -142,6 +144,30 @@ export type {
|
|
|
142
144
|
AskUserQuestionOption,
|
|
143
145
|
AgentWidgetAskUserQuestionFeature,
|
|
144
146
|
AgentWidgetAskUserQuestionStyles,
|
|
147
|
+
// Visitor conversation history (client-token mode) types
|
|
148
|
+
AgentWidgetHistoryFeature,
|
|
149
|
+
AgentWidgetHistoryListAction,
|
|
150
|
+
AgentWidgetHistoryCopy,
|
|
151
|
+
HistoryScope,
|
|
152
|
+
HistoryIdentityStatus,
|
|
153
|
+
ResolvedHistoryPresentation,
|
|
154
|
+
HistoryReturnSurface,
|
|
155
|
+
HistoryConversationSummary,
|
|
156
|
+
HistoryConversationPatch,
|
|
157
|
+
HistoryConversationPage,
|
|
158
|
+
HistoryConversationDetail,
|
|
159
|
+
HistoryDisplayProjection,
|
|
160
|
+
// History rendering customization (plugin render hooks; types only)
|
|
161
|
+
ResolvedHistoryCopy,
|
|
162
|
+
HistoryViewState,
|
|
163
|
+
HistoryPendingAction,
|
|
164
|
+
AgentWidgetHistoryRenderActions,
|
|
165
|
+
AgentWidgetRenderHistoryViewContext,
|
|
166
|
+
AgentWidgetRenderHistoryHeaderContext,
|
|
167
|
+
AgentWidgetRenderHistoryConversationContext,
|
|
168
|
+
AgentWidgetRenderHistoryStateContext,
|
|
169
|
+
AgentWidgetRenderHistoryOpenErrorContext,
|
|
170
|
+
ClientVisitorGrant,
|
|
145
171
|
AgentWidgetSuggestion,
|
|
146
172
|
AgentWidgetSuggestionChipsConfig,
|
|
147
173
|
AgentWidgetResolvedSuggestion,
|
|
@@ -159,6 +185,26 @@ export type {
|
|
|
159
185
|
AgentWidgetWelcomeDismiss
|
|
160
186
|
} from "./types";
|
|
161
187
|
|
|
188
|
+
// Custom conversation-history backends (`features.history.provider`). The
|
|
189
|
+
// provider registry beside this module stays private.
|
|
190
|
+
export type {
|
|
191
|
+
HistoryProvider,
|
|
192
|
+
HistoryOperationContext,
|
|
193
|
+
PreparedHistoryActivation,
|
|
194
|
+
HistoryListOptions,
|
|
195
|
+
HistoryListResult,
|
|
196
|
+
HistoryPageOptions,
|
|
197
|
+
HistoryPageResult,
|
|
198
|
+
HistoryDeleteAllOptions,
|
|
199
|
+
HistoryProviderErrorCode,
|
|
200
|
+
} from "./internal/history-provider";
|
|
201
|
+
|
|
202
|
+
// Value export: custom providers construct and `instanceof` this.
|
|
203
|
+
export {
|
|
204
|
+
HistoryProviderError,
|
|
205
|
+
isHistoryProviderError,
|
|
206
|
+
} from "./internal/history-provider";
|
|
207
|
+
|
|
162
208
|
export {
|
|
163
209
|
applyFeaturePreferences,
|
|
164
210
|
parseWidgetPreferenceSlice,
|
|
@@ -347,7 +393,9 @@ export type {
|
|
|
347
393
|
AgentWidgetTransformSuggestionsContext,
|
|
348
394
|
AgentWidgetRenderSuggestionContext,
|
|
349
395
|
AgentWidgetRenderWelcomeContext,
|
|
350
|
-
AgentWidgetSuggestionSelectContext
|
|
396
|
+
AgentWidgetSuggestionSelectContext,
|
|
397
|
+
AgentWidgetRailSection,
|
|
398
|
+
AgentWidgetRailSectionContext
|
|
351
399
|
} from "./plugins/types";
|
|
352
400
|
// Welcome resolver: `renderWelcome` receives the resolved shape.
|
|
353
401
|
export { resolveWelcomeConfig, isWelcomeVisible } from "./welcome";
|
|
@@ -398,7 +446,7 @@ export { renderLucideIcon } from "./utils/icons";
|
|
|
398
446
|
export type { IconName } from "./utils/icons";
|
|
399
447
|
|
|
400
448
|
// Button utility exports
|
|
401
|
-
export { createIconButton, createLabelButton, createToggleGroup, createComboButton } from "./utils/buttons";
|
|
449
|
+
export { createIconButton, createLabelButton, createToggleGroup, createComboButton, createSplitButton } from "./utils/buttons";
|
|
402
450
|
export type {
|
|
403
451
|
CreateIconButtonOptions,
|
|
404
452
|
CreateLabelButtonOptions,
|
package/src/index-global.ts
CHANGED
|
@@ -188,3 +188,58 @@ setContextMentionsInlineLoader(() => {
|
|
|
188
188
|
}
|
|
189
189
|
return import(/* @vite-ignore */ chunkUrl);
|
|
190
190
|
});
|
|
191
|
+
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// Deferred history-view loading.
|
|
194
|
+
//
|
|
195
|
+
// This bundle is built with `@runtypelabs/persona/history-view` external (see
|
|
196
|
+
// `tsup.global.config.ts`): the Messages navigation surface is kept out of the
|
|
197
|
+
// CDN payload and fetched only when the visitor first opens history. Same
|
|
198
|
+
// sibling-URL scheme as the chunks above.
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
|
|
201
|
+
import { setHistoryViewLoader } from "./history-view-loader";
|
|
202
|
+
|
|
203
|
+
setHistoryViewLoader(() => {
|
|
204
|
+
const chunkUrl = widgetScriptSrc?.replace(
|
|
205
|
+
/index\.global\.js($|\?)/,
|
|
206
|
+
"history-view.js$1",
|
|
207
|
+
);
|
|
208
|
+
if (!chunkUrl || chunkUrl === widgetScriptSrc) {
|
|
209
|
+
return Promise.reject(
|
|
210
|
+
new Error(
|
|
211
|
+
"Could not derive the history-view.js URL from the widget script URL " +
|
|
212
|
+
`(${widgetScriptSrc ?? "unavailable"}). Self-hosted deployments that ` +
|
|
213
|
+
"rename index.global.js should host history-view.js alongside it.",
|
|
214
|
+
),
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
return import(/* @vite-ignore */ chunkUrl);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
// Deferred event-stream-view loading.
|
|
222
|
+
//
|
|
223
|
+
// Same scheme as history-view: `@runtypelabs/persona/event-stream-view` is
|
|
224
|
+
// external here (see `tsup.global.config.ts`), and the observability panel is
|
|
225
|
+
// fetched from a sibling URL only when the visitor first opens it.
|
|
226
|
+
// ---------------------------------------------------------------------------
|
|
227
|
+
|
|
228
|
+
import { setEventStreamViewLoader } from "./event-stream-view-loader";
|
|
229
|
+
|
|
230
|
+
setEventStreamViewLoader(() => {
|
|
231
|
+
const chunkUrl = widgetScriptSrc?.replace(
|
|
232
|
+
/index\.global\.js($|\?)/,
|
|
233
|
+
"event-stream-view.js$1",
|
|
234
|
+
);
|
|
235
|
+
if (!chunkUrl || chunkUrl === widgetScriptSrc) {
|
|
236
|
+
return Promise.reject(
|
|
237
|
+
new Error(
|
|
238
|
+
"Could not derive the event-stream-view.js URL from the widget script URL " +
|
|
239
|
+
`(${widgetScriptSrc ?? "unavailable"}). Self-hosted deployments that ` +
|
|
240
|
+
"rename index.global.js should host event-stream-view.js alongside it.",
|
|
241
|
+
),
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
return import(/* @vite-ignore */ chunkUrl);
|
|
245
|
+
});
|