@runtypelabs/persona 4.5.0 → 4.6.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/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/{types-C6tFDxKy.d.cts → types-8RICZWQe.d.cts} +6 -0
- package/dist/animations/{types-C6tFDxKy.d.ts → types-8RICZWQe.d.ts} +6 -0
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/chunk-DFBSCFYN.js +1 -0
- package/dist/codegen.cjs +1 -1
- package/dist/codegen.js +1 -1
- package/dist/index.cjs +51 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +228 -2
- package/dist/index.d.ts +228 -2
- package/dist/index.global.js +37 -37
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +51 -51
- package/dist/index.js.map +1 -1
- package/dist/launcher.global.js.map +1 -1
- package/dist/markdown-parsers-entry-NVFT3TE6.js +1 -0
- package/dist/runtype-tts-entry-HFUV2UF7.js +1 -0
- package/dist/session-reconnect-U77QFUR7.js +1 -0
- package/dist/smart-dom-reader.d.cts +90 -0
- package/dist/smart-dom-reader.d.ts +90 -0
- package/dist/theme-editor-preview.cjs +48 -48
- package/dist/theme-editor-preview.d.cts +130 -1
- package/dist/theme-editor-preview.d.ts +130 -1
- package/dist/theme-editor-preview.js +48 -48
- package/dist/theme-editor.d.cts +90 -0
- package/dist/theme-editor.d.ts +90 -0
- package/package.json +2 -2
- package/src/client.ts +48 -7
- package/src/generated/runtype-openapi-contract.ts +6 -0
- package/src/reconnect-wake.test.ts +162 -0
- package/src/reconnect.test.ts +430 -0
- package/src/session-reconnect.ts +282 -0
- package/src/session.ts +408 -5
- package/src/types.ts +107 -1
- package/src/ui.stream-animation-update.test.ts +99 -0
- package/src/ui.ts +71 -1
- package/src/utils/constants.ts +3 -1
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
|
+
|
|
5
|
+
import { createAgentExperience } from "./ui";
|
|
6
|
+
import {
|
|
7
|
+
registerStreamAnimationPlugin,
|
|
8
|
+
unregisterStreamAnimationPlugin,
|
|
9
|
+
} from "./utils/stream-animation";
|
|
10
|
+
import type { StreamAnimationPlugin } from "./types";
|
|
11
|
+
|
|
12
|
+
// A plugin animation carries its own CSS (built-in animations live in
|
|
13
|
+
// widget.css). Plugin styles are injected only via ensurePluginActive, which the
|
|
14
|
+
// initial mount runs but controller.update() historically skipped — so switching
|
|
15
|
+
// to a plugin animation live used to set the type without ever injecting the CSS.
|
|
16
|
+
const TEST_PLUGIN: StreamAnimationPlugin = {
|
|
17
|
+
name: "test-reveal",
|
|
18
|
+
containerClass: "test-reveal-stream",
|
|
19
|
+
wrap: "char",
|
|
20
|
+
styles: ".test-reveal-stream .persona-stream-char { opacity: 0; }",
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
const createMount = () => {
|
|
24
|
+
const mount = document.createElement("div");
|
|
25
|
+
document.body.appendChild(mount);
|
|
26
|
+
return mount;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const styleTag = (mount: HTMLElement) =>
|
|
30
|
+
mount.querySelector('style[data-persona-animation="test-reveal"]');
|
|
31
|
+
|
|
32
|
+
describe("createAgentExperience: stream-animation plugin activation on update()", () => {
|
|
33
|
+
beforeEach(() => {
|
|
34
|
+
vi.stubGlobal("requestAnimationFrame", (cb: (time: number) => void) => {
|
|
35
|
+
cb(0);
|
|
36
|
+
return 1;
|
|
37
|
+
});
|
|
38
|
+
vi.stubGlobal("cancelAnimationFrame", () => {});
|
|
39
|
+
window.scrollTo = vi.fn();
|
|
40
|
+
registerStreamAnimationPlugin(TEST_PLUGIN);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
afterEach(() => {
|
|
44
|
+
unregisterStreamAnimationPlugin("test-reveal");
|
|
45
|
+
document.body.innerHTML = "";
|
|
46
|
+
vi.restoreAllMocks();
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("injects a plugin animation's styles when switched in via update()", () => {
|
|
50
|
+
const mount = createMount();
|
|
51
|
+
const controller = createAgentExperience(mount, {
|
|
52
|
+
apiUrl: "https://api.example.com/chat",
|
|
53
|
+
launcher: { enabled: false },
|
|
54
|
+
features: { streamAnimation: { type: "none" } },
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Nothing injected while the type is "none".
|
|
58
|
+
expect(styleTag(mount)).toBeNull();
|
|
59
|
+
|
|
60
|
+
controller.update({
|
|
61
|
+
features: { streamAnimation: { type: "test-reveal" } },
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// The plugin's <style> is now present, so the animation can actually render.
|
|
65
|
+
expect(styleTag(mount)).not.toBeNull();
|
|
66
|
+
|
|
67
|
+
controller.destroy();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("activates the plugin at mount when configured up front", () => {
|
|
71
|
+
const mount = createMount();
|
|
72
|
+
const controller = createAgentExperience(mount, {
|
|
73
|
+
apiUrl: "https://api.example.com/chat",
|
|
74
|
+
launcher: { enabled: false },
|
|
75
|
+
features: { streamAnimation: { type: "test-reveal" } },
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
expect(styleTag(mount)).not.toBeNull();
|
|
79
|
+
controller.destroy();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("does not duplicate the style tag when the same plugin is re-selected", () => {
|
|
83
|
+
const mount = createMount();
|
|
84
|
+
const controller = createAgentExperience(mount, {
|
|
85
|
+
apiUrl: "https://api.example.com/chat",
|
|
86
|
+
launcher: { enabled: false },
|
|
87
|
+
features: { streamAnimation: { type: "test-reveal" } },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
controller.update({ features: { streamAnimation: { type: "none" } } });
|
|
91
|
+
controller.update({ features: { streamAnimation: { type: "test-reveal" } } });
|
|
92
|
+
|
|
93
|
+
expect(
|
|
94
|
+
mount.querySelectorAll('style[data-persona-animation="test-reveal"]'),
|
|
95
|
+
).toHaveLength(1);
|
|
96
|
+
|
|
97
|
+
controller.destroy();
|
|
98
|
+
});
|
|
99
|
+
});
|
package/src/ui.ts
CHANGED
|
@@ -294,6 +294,11 @@ type Controller = {
|
|
|
294
294
|
clearChat: () => void;
|
|
295
295
|
setMessage: (message: string) => boolean;
|
|
296
296
|
submitMessage: (message?: string) => boolean;
|
|
297
|
+
/**
|
|
298
|
+
* Manually retry a dropped durable stream (e.g. from a "Reconnect" button).
|
|
299
|
+
* No-op unless a resumable durable turn dropped and `reconnectStream` is set.
|
|
300
|
+
*/
|
|
301
|
+
reconnect: () => void;
|
|
297
302
|
startVoiceRecognition: () => boolean;
|
|
298
303
|
stopVoiceRecognition: () => boolean;
|
|
299
304
|
/**
|
|
@@ -775,6 +780,8 @@ export const createAgentExperience = (
|
|
|
775
780
|
if (status === "connecting") return statusConfig.connectingText ?? statusCopy.connecting;
|
|
776
781
|
if (status === "connected") return statusConfig.connectedText ?? statusCopy.connected;
|
|
777
782
|
if (status === "error") return statusConfig.errorText ?? statusCopy.error;
|
|
783
|
+
if (status === "paused") return statusConfig.pausedText ?? statusCopy.paused;
|
|
784
|
+
if (status === "resuming") return statusConfig.resumingText ?? statusCopy.resuming;
|
|
778
785
|
return statusCopy[status];
|
|
779
786
|
};
|
|
780
787
|
|
|
@@ -5243,6 +5250,8 @@ export const createAgentExperience = (
|
|
|
5243
5250
|
if (s === "connecting") return currentStatusConfig.connectingText ?? statusCopy.connecting;
|
|
5244
5251
|
if (s === "connected") return currentStatusConfig.connectedText ?? statusCopy.connected;
|
|
5245
5252
|
if (s === "error") return currentStatusConfig.errorText ?? statusCopy.error;
|
|
5253
|
+
if (s === "paused") return currentStatusConfig.pausedText ?? statusCopy.paused;
|
|
5254
|
+
if (s === "resuming") return currentStatusConfig.resumingText ?? statusCopy.resuming;
|
|
5246
5255
|
return statusCopy[s];
|
|
5247
5256
|
};
|
|
5248
5257
|
applyStatusToElement(statusText, getCurrentStatusText(status), currentStatusConfig, status);
|
|
@@ -5310,11 +5319,31 @@ export const createAgentExperience = (
|
|
|
5310
5319
|
lastArtifactsState = state;
|
|
5311
5320
|
syncArtifactPane();
|
|
5312
5321
|
persistState();
|
|
5322
|
+
},
|
|
5323
|
+
onReconnect(event) {
|
|
5324
|
+
// Map the durable-reconnect lifecycle to public controller events.
|
|
5325
|
+
const { executionId, lastEventId } = event.handle;
|
|
5326
|
+
if (event.phase === "paused") {
|
|
5327
|
+
eventBus.emit("stream:paused", { executionId, after: lastEventId });
|
|
5328
|
+
} else if (event.phase === "resuming") {
|
|
5329
|
+
eventBus.emit("stream:resuming", {
|
|
5330
|
+
executionId,
|
|
5331
|
+
after: lastEventId,
|
|
5332
|
+
attempt: event.attempt ?? 1,
|
|
5333
|
+
});
|
|
5334
|
+
} else {
|
|
5335
|
+
eventBus.emit("stream:resumed", { executionId, after: lastEventId });
|
|
5336
|
+
}
|
|
5313
5337
|
}
|
|
5314
5338
|
});
|
|
5315
5339
|
|
|
5316
5340
|
sessionRef.current = session;
|
|
5317
5341
|
|
|
5342
|
+
// On teardown, cancel any in-flight turn/reconnect so a pending durable
|
|
5343
|
+
// reconnect's backoff timer and focus/online listeners don't outlive the
|
|
5344
|
+
// widget (cancel() → teardownReconnect()).
|
|
5345
|
+
destroyCallbacks.push(() => session.cancel());
|
|
5346
|
+
|
|
5318
5347
|
// Mirror read-aloud playback state into the action buttons, and surface it as
|
|
5319
5348
|
// a controller event (parallel to message:copy / message:feedback).
|
|
5320
5349
|
let lastReadAloudId: string | null = null;
|
|
@@ -5380,6 +5409,17 @@ export const createAgentExperience = (
|
|
|
5380
5409
|
});
|
|
5381
5410
|
}
|
|
5382
5411
|
|
|
5412
|
+
// Durable-session reconnect boot path: once history is in place,
|
|
5413
|
+
// if the host passed a non-terminal resume handle (+ a reconnect transport),
|
|
5414
|
+
// immediately re-enter `resuming` and replay everything past the cursor into
|
|
5415
|
+
// the restored conversation. Fires AFTER hydration so the trailing partial
|
|
5416
|
+
// assistant bubble exists for the replay to append to.
|
|
5417
|
+
const maybeBootResume = () => {
|
|
5418
|
+
if (config.resume && typeof config.reconnectStream === "function") {
|
|
5419
|
+
session.resumeFromHandle(config.resume);
|
|
5420
|
+
}
|
|
5421
|
+
};
|
|
5422
|
+
|
|
5383
5423
|
if (pendingStoredState) {
|
|
5384
5424
|
pendingStoredState
|
|
5385
5425
|
.then((state) => {
|
|
@@ -5410,7 +5450,10 @@ export const createAgentExperience = (
|
|
|
5410
5450
|
// eslint-disable-next-line no-console
|
|
5411
5451
|
console.error("[AgentWidget] Failed to hydrate stored state:", error);
|
|
5412
5452
|
}
|
|
5413
|
-
})
|
|
5453
|
+
})
|
|
5454
|
+
.finally(() => maybeBootResume());
|
|
5455
|
+
} else {
|
|
5456
|
+
maybeBootResume();
|
|
5414
5457
|
}
|
|
5415
5458
|
|
|
5416
5459
|
// Centralized so both the default composer (`handleSubmit`) and the plugin
|
|
@@ -6683,6 +6726,7 @@ export const createAgentExperience = (
|
|
|
6683
6726
|
const previousShowToolCalls = config.features?.showToolCalls;
|
|
6684
6727
|
const previousToolCallDisplay = config.features?.toolCallDisplay;
|
|
6685
6728
|
const previousReasoningDisplay = config.features?.reasoningDisplay;
|
|
6729
|
+
const previousStreamAnimationType = config.features?.streamAnimation?.type;
|
|
6686
6730
|
config = { ...config, ...nextConfig };
|
|
6687
6731
|
// applyFullHeightStyles resets mount.style.cssText, so call it before applyThemeVariables
|
|
6688
6732
|
applyFullHeightStyles();
|
|
@@ -6932,6 +6976,29 @@ export const createAgentExperience = (
|
|
|
6932
6976
|
renderMessagesWithPlugins(messagesWrapper, session.getMessages(), postprocess);
|
|
6933
6977
|
}
|
|
6934
6978
|
|
|
6979
|
+
// Re-activate the stream-animation plugin when the type changes via
|
|
6980
|
+
// update(). Built-in animations (typewriter, word-fade, letter-rise,
|
|
6981
|
+
// pop-bubble) carry their CSS in widget.css, so swapping the type is
|
|
6982
|
+
// enough. Plugin animations (wipe, glyph-cycle, and custom plugins
|
|
6983
|
+
// registered via registerStreamAnimationPlugin) inject their styles and
|
|
6984
|
+
// run onAttach only through ensurePluginActive, which the initial mount
|
|
6985
|
+
// calls but update() otherwise skips. Without this, switching to a plugin
|
|
6986
|
+
// animation live sets the type but never injects the CSS, so it silently
|
|
6987
|
+
// does nothing. ensurePluginActive is idempotent (styles inject once per
|
|
6988
|
+
// root), so re-selecting a previously-activated plugin is a no-op.
|
|
6989
|
+
const nextStreamAnimationType = config.features?.streamAnimation?.type;
|
|
6990
|
+
if (
|
|
6991
|
+
nextStreamAnimationType !== previousStreamAnimationType &&
|
|
6992
|
+
nextStreamAnimationType &&
|
|
6993
|
+
nextStreamAnimationType !== "none"
|
|
6994
|
+
) {
|
|
6995
|
+
const streamAnimationPlugin = resolveStreamAnimationPlugin(
|
|
6996
|
+
nextStreamAnimationType,
|
|
6997
|
+
config.features?.streamAnimation?.plugins
|
|
6998
|
+
);
|
|
6999
|
+
if (streamAnimationPlugin) ensurePluginActive(streamAnimationPlugin, mount);
|
|
7000
|
+
}
|
|
7001
|
+
|
|
6935
7002
|
// Update panel icon sizes
|
|
6936
7003
|
const launcher = config.launcher ?? {};
|
|
6937
7004
|
const headerIconHidden = launcher.headerIconHidden ?? false;
|
|
@@ -7954,6 +8021,9 @@ export const createAgentExperience = (
|
|
|
7954
8021
|
if (!isPanelToggleable()) return;
|
|
7955
8022
|
setOpenState(!open, "api");
|
|
7956
8023
|
},
|
|
8024
|
+
reconnect() {
|
|
8025
|
+
session.reconnectNow();
|
|
8026
|
+
},
|
|
7957
8027
|
clearChat() {
|
|
7958
8028
|
// Clear messages in session (this will trigger onMessagesChanged which re-renders)
|
|
7959
8029
|
artifactsPaneUserHidden = false;
|
package/src/utils/constants.ts
CHANGED