@runtypelabs/persona 3.16.0 → 3.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.
Files changed (71) hide show
  1. package/README.md +142 -0
  2. package/dist/animations/glyph-cycle.cjs +279 -0
  3. package/dist/animations/glyph-cycle.d.cts +5 -0
  4. package/dist/animations/glyph-cycle.d.ts +5 -0
  5. package/dist/animations/glyph-cycle.js +252 -0
  6. package/dist/animations/types-cwY5HaFD.d.cts +307 -0
  7. package/dist/animations/types-cwY5HaFD.d.ts +307 -0
  8. package/dist/animations/wipe.cjs +107 -0
  9. package/dist/animations/wipe.d.cts +5 -0
  10. package/dist/animations/wipe.d.ts +5 -0
  11. package/dist/animations/wipe.js +80 -0
  12. package/dist/index.cjs +49 -48
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +504 -1
  15. package/dist/index.d.ts +504 -1
  16. package/dist/index.global.js +143 -88
  17. package/dist/index.global.js.map +1 -1
  18. package/dist/index.js +49 -48
  19. package/dist/index.js.map +1 -1
  20. package/dist/testing.cjs +85 -0
  21. package/dist/testing.d.cts +39 -0
  22. package/dist/testing.d.ts +39 -0
  23. package/dist/testing.js +56 -0
  24. package/dist/theme-editor.cjs +2095 -207
  25. package/dist/theme-editor.d.cts +432 -2
  26. package/dist/theme-editor.d.ts +432 -2
  27. package/dist/theme-editor.js +2093 -207
  28. package/dist/theme-reference.cjs +1 -1
  29. package/dist/theme-reference.d.cts +14 -0
  30. package/dist/theme-reference.d.ts +14 -0
  31. package/dist/widget.css +565 -0
  32. package/package.json +20 -3
  33. package/src/animations/glyph-cycle.ts +332 -0
  34. package/src/animations/wipe.ts +66 -0
  35. package/src/client.test.ts +275 -0
  36. package/src/client.ts +99 -0
  37. package/src/components/ask-user-question-bubble.test.ts +583 -0
  38. package/src/components/ask-user-question-bubble.ts +924 -0
  39. package/src/components/composer-builder.ts +61 -10
  40. package/src/components/message-bubble.test.ts +181 -2
  41. package/src/components/message-bubble.ts +209 -14
  42. package/src/components/messages.ts +33 -1
  43. package/src/components/panel.ts +45 -5
  44. package/src/defaults.ts +37 -0
  45. package/src/index-global.ts +31 -0
  46. package/src/index.ts +34 -1
  47. package/src/plugins/types.ts +57 -0
  48. package/src/session.test.ts +276 -1
  49. package/src/session.ts +247 -3
  50. package/src/styles/widget.css +565 -0
  51. package/src/testing/index.ts +11 -0
  52. package/src/testing/mock-stream.test.ts +80 -0
  53. package/src/testing/mock-stream.ts +94 -0
  54. package/src/testing.ts +2 -0
  55. package/src/theme-editor/index.ts +4 -0
  56. package/src/theme-editor/preview-utils.test.ts +60 -0
  57. package/src/theme-editor/preview-utils.ts +129 -0
  58. package/src/theme-editor/sections.test.ts +19 -0
  59. package/src/theme-editor/sections.ts +84 -1
  60. package/src/types/theme.ts +15 -0
  61. package/src/types.ts +360 -0
  62. package/src/ui.ask-user-question-plugin.test.ts +649 -0
  63. package/src/ui.stop-button.test.ts +165 -0
  64. package/src/ui.ts +706 -11
  65. package/src/utils/message-fingerprint.ts +2 -0
  66. package/src/utils/morph.ts +7 -0
  67. package/src/utils/storage.ts +10 -2
  68. package/src/utils/stream-animation.test.ts +417 -0
  69. package/src/utils/stream-animation.ts +449 -0
  70. package/src/utils/theme.test.ts +36 -0
  71. package/src/utils/tokens.ts +23 -0
package/src/types.ts CHANGED
@@ -205,6 +205,31 @@ export type AgentMessageMetadata = {
205
205
  * or `prompt` step inside the nested flow). Stable key for that step.
206
206
  */
207
207
  parentStepId?: string;
208
+ /**
209
+ * Set to `true` on a tool-variant message produced from a `step_await`
210
+ * event (`awaitReason: "local_tool_required"`). Signals to UI code that
211
+ * the tool call is a LOCAL tool and the server is paused waiting for a
212
+ * `POST /v1/dispatch/resume` with the user's answer keyed by tool name.
213
+ */
214
+ awaitingLocalTool?: boolean;
215
+ /**
216
+ * Set to `true` once the user has picked / typed / dismissed an answer for
217
+ * an `ask_user_question` tool call, so renderers stop re-mounting the
218
+ * answer-pill sheet for this tool call on subsequent render passes.
219
+ */
220
+ askUserQuestionAnswered?: boolean;
221
+ /**
222
+ * In-progress answers for a multi-question `ask_user_question` payload,
223
+ * keyed by question text. Persisted across refresh so the user lands back
224
+ * where they were if the page reloads mid-flow. Cleared once
225
+ * `askUserQuestionAnswered` flips to `true`.
226
+ */
227
+ askUserQuestionAnswers?: Record<string, string | string[]>;
228
+ /**
229
+ * Current page index for a multi-question `ask_user_question` payload's
230
+ * paginated stepper. Persists alongside `askUserQuestionAnswers`.
231
+ */
232
+ askUserQuestionIndex?: number;
208
233
  };
209
234
 
210
235
  export type AgentWidgetRequestMiddlewareContext = {
@@ -270,6 +295,8 @@ export type AgentWidgetActionHandler = (
270
295
  export type AgentWidgetStoredState = {
271
296
  messages?: AgentWidgetMessage[];
272
297
  metadata?: Record<string, unknown>;
298
+ artifacts?: PersonaArtifactRecord[];
299
+ selectedArtifactId?: string | null;
273
300
  };
274
301
 
275
302
  export interface AgentWidgetStorageAdapter {
@@ -680,6 +707,167 @@ export type AgentWidgetReasoningDisplayFeature = {
680
707
  loadingAnimation?: AgentWidgetToolCallLoadingAnimation;
681
708
  };
682
709
 
710
+ /**
711
+ * Reveal animation applied to assistant message text while it is streaming.
712
+ *
713
+ * Built-in types always available:
714
+ * - `none` — text appears as tokens arrive (default).
715
+ * - `typewriter` — characters fade in with a blinking caret.
716
+ * - `pop-bubble` — the bubble scales in; text streams normally afterward.
717
+ * - `letter-rise` — per-char translateY + fade reveal.
718
+ * - `word-fade` — per-word blur + translateY fade-in.
719
+ *
720
+ * Subpath plugins (import from `@runtypelabs/persona/animations/*` to register):
721
+ * - `wipe`, `glyph-cycle`.
722
+ *
723
+ * Custom types are allowed — register a plugin with any string name and
724
+ * reference it by that name in `type`.
725
+ */
726
+ export type AgentWidgetStreamAnimationBuiltinType =
727
+ | "none"
728
+ | "typewriter"
729
+ | "word-fade"
730
+ | "letter-rise"
731
+ | "glyph-cycle"
732
+ | "wipe"
733
+ | "pop-bubble";
734
+
735
+ export type AgentWidgetStreamAnimationType =
736
+ | AgentWidgetStreamAnimationBuiltinType
737
+ | (string & {});
738
+
739
+ /**
740
+ * Placeholder shown inside a streaming assistant bubble before the first token arrives.
741
+ * - `none` — use the default typing-dots indicator (existing behavior).
742
+ * - `skeleton` — shimmer bars, replaced by streaming content once it starts.
743
+ */
744
+ export type AgentWidgetStreamAnimationPlaceholder = "none" | "skeleton";
745
+
746
+ /**
747
+ * How much of the accumulated streaming content to display while tokens are
748
+ * still arriving. Trimming to a boundary means in-progress words or lines
749
+ * stay hidden until they complete — useful for animations that benefit from
750
+ * unit-complete reveals (e.g. wipe, glyph-cycle).
751
+ * - `none` — show every character as it arrives (default).
752
+ * - `word` — trim to the last whitespace boundary.
753
+ * - `line` — trim to the last newline boundary.
754
+ */
755
+ export type AgentWidgetStreamAnimationBuffer = "none" | "word" | "line";
756
+
757
+ /**
758
+ * Context passed to plugin lifecycle hooks. Carries the live DOM references
759
+ * and resolved animation settings for the currently-streaming message.
760
+ */
761
+ export type StreamAnimationContext = {
762
+ /** The `.persona-message-content` element owning the streamed text. */
763
+ container: HTMLElement;
764
+ /** The outer message bubble element. */
765
+ bubble: HTMLElement;
766
+ /** ID of the streaming message. */
767
+ messageId: string;
768
+ /** Read-only reference to the message being streamed. */
769
+ message: AgentWidgetMessage;
770
+ /** Effective `speed` from `streamAnimation.speed`. */
771
+ speed: number;
772
+ /** Effective `duration` from `streamAnimation.duration`. */
773
+ duration: number;
774
+ };
775
+
776
+ /**
777
+ * Pluggable stream animation. Third-party packages and inline registrations
778
+ * implement this interface to add custom reveal effects.
779
+ *
780
+ * Lifecycle:
781
+ * - When the widget mounts and detects a plugin (either passed via config or
782
+ * auto-registered in the IIFE bundle), it injects `styles` once into the
783
+ * widget's style host.
784
+ * - For each streaming assistant message whose `type` matches `name`, the
785
+ * widget applies `containerClass` / `bubbleClass`, wraps text per `wrap`,
786
+ * and — if `useCaret` is true — appends a blinking caret.
787
+ * - Hooks fire after the live DOM is morphed; plugins use stable element IDs
788
+ * and `data-preserve-animation` to safely mutate per-char or per-word spans
789
+ * without idiomorph clobbering in-flight work.
790
+ */
791
+ export type StreamAnimationPlugin = {
792
+ /** Plugin identifier. Matches the `type` field in `streamAnimation`. */
793
+ name: string;
794
+ /** Class added to `.persona-message-content` while streaming. */
795
+ containerClass?: string;
796
+ /** Class added to the bubble element (e.g. a one-shot scale animation). */
797
+ bubbleClass?: string;
798
+ /** Wrap mode applied to text nodes during streaming. @default "none" */
799
+ wrap?: "none" | "char" | "word";
800
+ /**
801
+ * HTML tags whose descendant text is skipped during wrapping. Defaults to
802
+ * `["pre", "code", "a", "script", "style"]` — useful for keeping code
803
+ * blocks legible and link click-targets intact. Plugins that want to
804
+ * animate characters inside inline code (e.g. `glyph-cycle`) can narrow
805
+ * the list.
806
+ */
807
+ skipTags?: string[];
808
+ /** Append a blinking caret after the last rendered char/word. */
809
+ useCaret?: boolean;
810
+ /** CSS string injected into the widget style host on first activation. */
811
+ styles?: string;
812
+ /**
813
+ * Optional custom buffering strategy. Returns the portion of `content`
814
+ * that should be rendered during streaming. Use this for buffering
815
+ * schemes beyond the built-in `word` / `line` strategies.
816
+ */
817
+ bufferContent?: (content: string, message: AgentWidgetMessage) => string;
818
+ /**
819
+ * Fires once when the plugin is first activated inside a widget instance.
820
+ * Use this to set up MutationObservers or other long-lived listeners.
821
+ * Return an optional cleanup function that runs on widget destroy.
822
+ */
823
+ onAttach?: (root: HTMLElement | ShadowRoot) => (() => void) | void;
824
+ /** Fires after each render that reaches the live DOM. */
825
+ onAfterRender?: (ctx: StreamAnimationContext) => void;
826
+ /** Fires when a streamed message's `streaming` flag flips to false. */
827
+ onStreamComplete?: (ctx: StreamAnimationContext) => void;
828
+ /**
829
+ * Report whether the plugin still has in-flight animation work for a
830
+ * message. When `true`, the widget keeps rendering the message in its
831
+ * "streaming-animated" mode even after `message.streaming` flips false —
832
+ * preventing the final non-animated render from yanking the rug out from
833
+ * under unfinished per-char cycles or reveals.
834
+ */
835
+ isAnimating?: (message: AgentWidgetMessage) => boolean;
836
+ };
837
+
838
+ export type AgentWidgetStreamAnimationFeature = {
839
+ /** Reveal animation to apply while streaming. @default "none" */
840
+ type?: AgentWidgetStreamAnimationType;
841
+ /** Pre-first-token placeholder. @default "none" */
842
+ placeholder?: AgentWidgetStreamAnimationPlaceholder;
843
+ /**
844
+ * Per-unit animation duration (ms) for `typewriter`, `letter-rise`, `word-fade`,
845
+ * and per-unit plugin animations. Each arriving character/word animates from
846
+ * invisible to visible over this duration, independent of its position — the
847
+ * streaming cadence itself provides the visible stagger.
848
+ * @default 120
849
+ */
850
+ speed?: number;
851
+ /**
852
+ * Total duration of container-level animations (`pop-bubble` and custom
853
+ * plugin animations), in milliseconds.
854
+ * @default 1800
855
+ */
856
+ duration?: number;
857
+ /**
858
+ * Trim the accumulated streaming content to a word or line boundary before
859
+ * rendering. Hides in-progress units until they complete.
860
+ * @default "none"
861
+ */
862
+ buffer?: AgentWidgetStreamAnimationBuffer;
863
+ /**
864
+ * Extra animation plugins available to this widget instance. Keys are
865
+ * plugin names; the matching plugin activates when `type` is set to that
866
+ * name. Built-in types (`typewriter`, `pop-bubble`) are always registered.
867
+ */
868
+ plugins?: Record<string, StreamAnimationPlugin>;
869
+ };
870
+
683
871
  export type AgentWidgetFeatureFlags = {
684
872
  showReasoning?: boolean;
685
873
  showToolCalls?: boolean;
@@ -694,6 +882,120 @@ export type AgentWidgetFeatureFlags = {
694
882
  eventStream?: EventStreamConfig;
695
883
  /** Optional artifact sidebar (split pane / mobile drawer) */
696
884
  artifacts?: AgentWidgetArtifactsFeature;
885
+ /** Reveal animation for streaming assistant text. */
886
+ streamAnimation?: AgentWidgetStreamAnimationFeature;
887
+ /**
888
+ * Built-in interactive answer-pill sheet shown when the assistant invokes
889
+ * the `ask_user_question` tool. Slides up over the composer with tappable
890
+ * pills + optional free-text input.
891
+ */
892
+ askUserQuestion?: AgentWidgetAskUserQuestionFeature;
893
+ };
894
+
895
+ /**
896
+ * Single selectable option in an `ask_user_question` prompt.
897
+ * Mirrors Anthropic's AskUserQuestion schema.
898
+ */
899
+ export type AskUserQuestionOption = {
900
+ /** Pill label (required). */
901
+ label: string;
902
+ /** Optional long-form description (shown as a subtitle on tap-hover). */
903
+ description?: string;
904
+ /** Optional rich preview — reserved for future rendering; ignored in v1. */
905
+ preview?: string;
906
+ };
907
+
908
+ /**
909
+ * A single question in an `ask_user_question` tool call.
910
+ * The tool may carry 1–8 prompts. When more than one is supplied, the built-in
911
+ * renderer paginates them as a "Question N of M" stepper with Back / Next /
912
+ * Submit-all controls; single-question payloads render without stepper chrome.
913
+ */
914
+ export type AskUserQuestionPrompt = {
915
+ /** The question text shown to the user. */
916
+ question: string;
917
+ /** Optional short header label (≤12 chars) used as a compact group title. */
918
+ header?: string;
919
+ /** 2–4 selectable options. */
920
+ options: AskUserQuestionOption[];
921
+ /** When true, the user can pick multiple options and submit together. Default false. */
922
+ multiSelect?: boolean;
923
+ /** When true, a free-text "Other…" pill expands to an input. Default true. */
924
+ allowFreeText?: boolean;
925
+ };
926
+
927
+ /** Parsed payload of an `ask_user_question` tool call. */
928
+ export type AskUserQuestionPayload = {
929
+ /** 1–8 questions. Anything beyond the renderer's cap is truncated with a console warning. */
930
+ questions: AskUserQuestionPrompt[];
931
+ };
932
+
933
+ /**
934
+ * Style overrides for the answer-pill sheet. All values are raw CSS strings
935
+ * and are plumbed through as CSS custom properties on the sheet root.
936
+ */
937
+ export type AgentWidgetAskUserQuestionStyles = {
938
+ sheetBackground?: string;
939
+ sheetBorder?: string;
940
+ sheetShadow?: string;
941
+ pillBackground?: string;
942
+ pillBackgroundSelected?: string;
943
+ pillTextColor?: string;
944
+ pillTextColorSelected?: string;
945
+ pillBorderRadius?: string;
946
+ customInputBackground?: string;
947
+ };
948
+
949
+ /**
950
+ * Feature config for the built-in `ask_user_question` answer-pill sheet.
951
+ * When a tool call with the name `ask_user_question` arrives, the widget
952
+ * renders an interactive sheet over the composer in place of the generic
953
+ * tool bubble.
954
+ */
955
+ export type AgentWidgetAskUserQuestionFeature = {
956
+ /** Enable the feature. Defaults to true. When false, `ask_user_question` renders as a regular tool bubble. */
957
+ enabled?: boolean;
958
+ /** Slide-in animation duration in ms. Defaults to 180. */
959
+ slideInMs?: number;
960
+ /** Label for the free-text pill. Defaults to "Other…". */
961
+ freeTextLabel?: string;
962
+ /** Placeholder text in the free-text input. Defaults to "Type your answer…". */
963
+ freeTextPlaceholder?: string;
964
+ /** Button label for submitting multi-select / free-text answers. Defaults to "Send". */
965
+ submitLabel?: string;
966
+ /** Button label advancing to the next question in grouped (paginated) payloads. Defaults to "Next". */
967
+ nextLabel?: string;
968
+ /** Button label moving back to the previous question in grouped payloads. Defaults to "Back". */
969
+ backLabel?: string;
970
+ /** Button label submitting all answers from the final page of a grouped payload. Defaults to "Submit all". */
971
+ submitAllLabel?: string;
972
+ /**
973
+ * In grouped (multi-question) mode, auto-advance to the next page after a
974
+ * single-select pill pick or free-text submit on intermediate pages.
975
+ * Defaults to `true`. The final page never auto-submits — users always
976
+ * confirm with an explicit "Submit all" click. Multi-select pages always
977
+ * require an explicit Next regardless of this setting.
978
+ */
979
+ groupedAutoAdvance?: boolean;
980
+ /**
981
+ * Visual layout for the option list.
982
+ * - `"rows"` (default) — full-width stacked rows with always-visible
983
+ * descriptions, right-edge number badges (single-select) or checkboxes
984
+ * (multi-select), and an always-visible inline "Other" input.
985
+ * - `"pills"` — legacy compact pill list with horizontal wrap; description
986
+ * surfaces as a tooltip and the "Other…" pill expands on click.
987
+ */
988
+ layout?: "rows" | "pills";
989
+ /**
990
+ * Button label for skipping the current question in grouped payloads.
991
+ * Defaults to "Skip". On intermediate pages Skip advances without recording
992
+ * an answer; on the final page Skip submits the partial answer record
993
+ * (skipped questions absent from the resolved object). For single-question
994
+ * payloads Skip behaves like dismiss.
995
+ */
996
+ skipLabel?: string;
997
+ /** Style overrides for the sheet and pills. */
998
+ styles?: AgentWidgetAskUserQuestionStyles;
697
999
  };
698
1000
 
699
1001
  export type SSEEventRecord = {
@@ -996,6 +1298,10 @@ export type AgentWidgetSendButtonConfig = {
996
1298
  backgroundColor?: string;
997
1299
  textColor?: string;
998
1300
  size?: string;
1301
+ /** Lucide icon name shown while a response is streaming. Clicking the button in this state aborts the stream. Default: "square". */
1302
+ stopIconName?: string;
1303
+ /** Tooltip text shown while streaming. Default: "Stop generating". */
1304
+ stopTooltipText?: string;
999
1305
  };
1000
1306
 
1001
1307
  /** Optional composer UI state for custom `renderComposer` implementations. */
@@ -2608,11 +2914,21 @@ export type AgentWidgetConfig = {
2608
2914
  welcomeSubtitle?: string;
2609
2915
  inputPlaceholder?: string;
2610
2916
  sendButtonLabel?: string;
2917
+ /** Button label shown in text mode while a response is streaming. Default: "Stop". */
2918
+ stopButtonLabel?: string;
2611
2919
  /**
2612
2920
  * When false, the welcome / intro card is not shown above the message list.
2613
2921
  * @default true
2614
2922
  */
2615
2923
  showWelcomeCard?: boolean;
2924
+ /**
2925
+ * Per-stop-reason copy for the inline notice rendered on assistant
2926
+ * bubbles when the runtime reports a non-natural stop (e.g. the agent
2927
+ * loop hit `max_tool_calls` and was cut off mid-loop). Each key is
2928
+ * optional — keys you omit fall back to the built-in defaults. Set a
2929
+ * key to an empty string to suppress the notice for that reason.
2930
+ */
2931
+ stopReasonNotice?: Partial<Record<StopReasonKind, string>>;
2616
2932
  };
2617
2933
  /**
2618
2934
  * Semantic design tokens (`palette`, `semantic`, `components`).
@@ -2646,6 +2962,17 @@ export type AgentWidgetConfig = {
2646
2962
  autoFocusInput?: boolean;
2647
2963
  launcher?: AgentWidgetLauncherConfig;
2648
2964
  initialMessages?: AgentWidgetMessage[];
2965
+ /**
2966
+ * Artifacts to hydrate into the pane at init. Typically populated from
2967
+ * `storageAdapter.load()` alongside `initialMessages` so the artifact pane
2968
+ * survives a page refresh.
2969
+ */
2970
+ initialArtifacts?: PersonaArtifactRecord[];
2971
+ /**
2972
+ * Which artifact id (if any) should be selected in the pane at init. Paired
2973
+ * with `initialArtifacts`.
2974
+ */
2975
+ initialSelectedArtifactId?: string | null;
2649
2976
  suggestionChips?: string[];
2650
2977
  suggestionChipsConfig?: AgentWidgetSuggestionChipsConfig;
2651
2978
  debug?: boolean;
@@ -3121,6 +3448,28 @@ export type AgentWidgetApproval = {
3121
3448
 
3122
3449
  export type AgentWidgetMessageVariant = "assistant" | "reasoning" | "tool" | "approval";
3123
3450
 
3451
+ /**
3452
+ * Per-turn / per-step stop reason emitted by the runtime on
3453
+ * `agent_turn_complete` and `step_complete` SSE events. The vocabulary is
3454
+ * owned by the upstream Runtype API — do not extend without coordination.
3455
+ *
3456
+ * - `end_turn` — natural completion (no affordance needed)
3457
+ * - `max_tool_calls` — agent loop tripped the configured tool-call ceiling
3458
+ * - `length` — provider hit max output tokens
3459
+ * - `content_filter` — provider content filter intervened
3460
+ * - `error` — provider/runtime error (prefer existing error rendering)
3461
+ * - `unknown` — explicitly reported but uninformative
3462
+ *
3463
+ * Absent (`undefined`) means "not reported" — distinct from `'unknown'`.
3464
+ */
3465
+ export type StopReasonKind =
3466
+ | 'end_turn'
3467
+ | 'max_tool_calls'
3468
+ | 'length'
3469
+ | 'content_filter'
3470
+ | 'error'
3471
+ | 'unknown';
3472
+
3124
3473
  /**
3125
3474
  * Represents a message in the chat conversation.
3126
3475
  *
@@ -3207,6 +3556,17 @@ export type AgentWidgetMessage = {
3207
3556
  * Contains execution context like iteration number and turn ID.
3208
3557
  */
3209
3558
  agentMetadata?: AgentMessageMetadata;
3559
+ /**
3560
+ * Per-turn stop reason reported by the runtime on `agent_turn_complete`
3561
+ * (agent-loop path) or the last `step_complete` for a prompt step
3562
+ * (dispatch / flow path). Absent when the API did not report a value.
3563
+ *
3564
+ * When set to a non-natural value (`max_tool_calls`, `length`,
3565
+ * `content_filter`, `error`), the widget renders an inline notice on
3566
+ * the assistant bubble. See `config.copy.stopReasonNotice` to override
3567
+ * the default copy.
3568
+ */
3569
+ stopReason?: StopReasonKind;
3210
3570
  };
3211
3571
 
3212
3572
  // ============================================================================