@runtypelabs/persona 3.31.1 → 3.34.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 (51) hide show
  1. package/README.md +17 -10
  2. package/dist/animations/glyph-cycle.d.cts +1 -1
  3. package/dist/animations/glyph-cycle.d.ts +1 -1
  4. package/dist/animations/{types-quh7NmYD.d.cts → types-CthJFfNx.d.cts} +7 -0
  5. package/dist/animations/{types-quh7NmYD.d.ts → types-CthJFfNx.d.ts} +7 -0
  6. package/dist/animations/wipe.d.cts +1 -1
  7. package/dist/animations/wipe.d.ts +1 -1
  8. package/dist/codegen.cjs +1 -1
  9. package/dist/codegen.js +1 -1
  10. package/dist/index.cjs +43 -43
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +320 -8
  13. package/dist/index.d.ts +320 -8
  14. package/dist/index.global.js +193 -192
  15. package/dist/index.global.js.map +1 -1
  16. package/dist/index.js +43 -43
  17. package/dist/index.js.map +1 -1
  18. package/dist/launcher.global.js +117 -117
  19. package/dist/launcher.global.js.map +1 -1
  20. package/dist/smart-dom-reader.d.cts +110 -2
  21. package/dist/smart-dom-reader.d.ts +110 -2
  22. package/dist/theme-editor.cjs +30 -30
  23. package/dist/theme-editor.d.cts +124 -5
  24. package/dist/theme-editor.d.ts +124 -5
  25. package/dist/theme-editor.js +30 -30
  26. package/package.json +3 -3
  27. package/src/ask-user-question-tool.test.ts +148 -0
  28. package/src/ask-user-question-tool.ts +138 -0
  29. package/src/client.ts +43 -9
  30. package/src/codegen.test.ts +0 -14
  31. package/src/components/messages.ts +10 -0
  32. package/src/components/suggestions.ts +49 -6
  33. package/src/index-core.ts +15 -0
  34. package/src/runtime/host-layout.test.ts +206 -9
  35. package/src/runtime/host-layout.ts +121 -8
  36. package/src/runtime/init.test.ts +2 -2
  37. package/src/session.ts +188 -32
  38. package/src/session.webmcp.test.ts +48 -0
  39. package/src/suggest-replies-tool.test.ts +445 -0
  40. package/src/suggest-replies-tool.ts +152 -0
  41. package/src/theme-editor/index.ts +2 -0
  42. package/src/theme-editor/webmcp/index.ts +2 -0
  43. package/src/theme-editor/webmcp/types.ts +16 -3
  44. package/src/types.ts +108 -2
  45. package/src/ui.docked.test.ts +2 -2
  46. package/src/ui.suggest-replies.test.ts +237 -0
  47. package/src/ui.ts +57 -13
  48. package/src/utils/dock.test.ts +23 -1
  49. package/src/utils/dock.ts +2 -0
  50. package/src/voice/voice.test.ts +0 -51
  51. package/src/install-config.test.ts +0 -38
package/README.md CHANGED
@@ -17,6 +17,8 @@ pnpm build
17
17
  - `dist/index.js` (ESM), `dist/index.cjs` (CJS), and `dist/index.global.js` (IIFE) provide different module formats.
18
18
  - `dist/widget.css` is the prefixed Tailwind bundle.
19
19
  - `dist/install.global.js` is the automatic installer script for easy script tag installation.
20
+ - `dist/launcher.global.js` is the tiny critical launcher used by deferred script-tag installs before the full panel bundle loads.
21
+ - `dist/webmcp-polyfill.js` is the lazy WebMCP polyfill chunk used by the IIFE bundle only when `config.webmcp.enabled` is true and the page has no `document.modelContext`.
20
22
 
21
23
  ### Using with modules
22
24
 
@@ -92,33 +94,36 @@ const docked = initAgentWidget({
92
94
  | --- | --- | --- |
93
95
  | `target` | `string \| HTMLElement` | CSS selector or element where widget mounts. |
94
96
  | `config` | `AgentWidgetConfig` | Widget configuration object (see the [Configuration Reference](./docs/CONFIGURATION-REFERENCE.md)). |
95
- | `useShadowDom` | `boolean` | Use Shadow DOM for style isolation (default: `true`). |
97
+ | `useShadowDom` | `boolean` | Use Shadow DOM for style isolation (default: `false`). |
96
98
  | `onChatReady` | `() => void` | Callback fired when the widget is initialized and its API is callable. |
97
99
  | `onReady` | `() => void` | **Deprecated** alias of `onChatReady`; still works, removed in the next major. |
98
100
  | `windowKey` | `string` | If provided, stores the controller on `window[windowKey]` for global access. Automatically cleaned up on `destroy()`. |
99
101
 
100
102
  When `config.launcher.mountMode` is `'docked'`, `target` is treated as the page container that Persona should wrap. Use a concrete element such as `#workspace-main`; `body` and `html` are rejected.
101
103
 
104
+ **Height contract:** the docked shell sizes itself with `height: 100%`, so give it a definite height — usually `html, body { height: 100% }` or a fixed-height app-shell container around the target. If no ancestor provides one, the panel is clamped to `dock.maxHeight` (default `100dvh`; `resize`/`emerge` are also sticky-pinned — `push`/`overlay` get the cap only) so it stays viewport-sized and scrolls internally, and a console warning explains the fix. Override the cap with a CSS length or disable the guard with `dock.maxHeight: false`.
105
+
102
106
  With **`dock.reveal: 'resize'`** (default), a **closed** dock uses a **`0px`** column. **`'emerge'`** uses the same **column width** animation (content reflows) but the chat panel stays **`dock.width`** wide and is **clipped** by the growing slot—like a normal-width widget emerging from the edge. **`'overlay'`** overlays with `transform`. **`'push'`** uses a sliding track (Shopify-style). The built-in launcher stays hidden in docked mode—open with **`controller.open()`** (or your own chrome).
103
107
 
104
108
  **Rounded / card layout:** `initAgentWidget` inserts a flex **shell** as the **direct child** of your target’s **parent**, with your `target` in the content column and the dock beside it. Put border-radius, border, and `overflow: hidden` on that **parent** (or an ancestor that wraps only the shell) so the dock column sits inside the same visual card as your content.
105
109
 
106
- **Inner push/overlay:** With `reveal: 'push'` or `'overlay'`, only the wrapped node moves. Use a **narrow `target`** (e.g. a main canvas div). For **`dock.side: 'left'`**, place a persistent rail **in flow** next to the stage (e.g. flex `[nav | stage]`) so the dock doesn’t open **under** the sidebar. For a **right** dock, you can instead use a **full-width** stage with an **absolute** left rail if you want the canvas to translate **behind** that rail.
110
+ **Inner push/overlay:** With `reveal: 'push'` or `'overlay'`, only the wrapped node moves. Use a **narrow `target`** (e.g. a main canvas div). For **`dock.side: 'left'`**, place a persistent rail **in flow** next to the stage (e.g. flex `[nav | stage]`) so the dock doesn’t open **under** the sidebar. For a **right** dock, you can instead use a **full-width** stage with an **absolute** left rail if you want the canvas to translate **behind** that rail. `position: fixed`/`sticky` content inside the target stays **viewport-anchored** (it is not pushed), so offset it while the dock is open if needed, e.g. `[data-persona-dock-open="true"] .my-fixed-bar { right: 420px; }`.
107
111
 
108
- > **Security note:** When you return HTML from `postprocessMessage`, make sure you sanitise it before injecting into the page. The provided postprocessors (`markdownPostprocessor`, `directivePostprocessor`) do not perform sanitisation.
112
+ > **Security note:** Persona sanitizes rendered message HTML with DOMPurify by default (`sanitize: true`), including output returned from `postprocessMessage`, `markdownPostprocessor`, and `directivePostprocessor`. If your custom postprocessor intentionally returns tags or attributes outside the built-in allowlist, provide `sanitize: (html) => ...`; only set `sanitize: false` for fully trusted content.
109
113
 
110
114
  ### Documentation
111
115
 
112
116
  The full reference lives in [`docs/`](./docs/) and the theming guide:
113
117
 
114
118
  - [Programmatic Control & Events](./docs/PROGRAMMATIC-CONTROL.md) — controller API, message hooks and injection, enriched DOM context, WebMCP page tools, DOM and controller events, state loading
115
- - [UI Features & Components](./docs/UI-COMPONENTS.md) — message actions and feedback, loading indicators, ask-user-question, dropdown menus, button utilities, dynamic forms
116
- - [Script Tag Installation & Framework Integration](./docs/INSTALLATION-FRAMEWORKS.md) — automatic installer, manual script tag setup, React, Next.js, Remix, Gatsby, and Astro guides
117
- - [Configuration Reference](./docs/CONFIGURATION-REFERENCE.md) — every config option: core, client token mode, agent mode, UI & theme, launcher, layout, voice, tool calls, suggestion chips, state & storage
119
+ - [UI Features & Components](./docs/UI-COMPONENTS.md) — message actions and feedback, loading/idle indicators, approvals, built-in `ask_user_question` and `suggest_replies` tools, dropdown menus, button utilities, dynamic forms
120
+ - [Script Tag Installation & Framework Integration](./docs/INSTALLATION-FRAMEWORKS.md) — automatic installer, deferred launcher lifecycle hooks, manual script tag setup, React, Next.js, Remix, Gatsby, and Astro guides
121
+ - [Configuration Reference](./docs/CONFIGURATION-REFERENCE.md) — every config option: core, client token mode, agent mode, UI & theme, launcher/docking, layout, voice, WebMCP, tool calls, features, suggestion chips, state & storage
118
122
  - [Stream Parser Configuration](./docs/STREAM-PARSERS.md) — JSON, XML, and plain-text stream parsers and custom parser factories
119
- - [THEME-CONFIG.md](./THEME-CONFIG.md) — the complete theme and design-token reference
120
123
  - [Message Injection](./docs/MESSAGE-INJECTION.md) — full injection and component-directive reference
121
124
  - [Dynamic Forms](./docs/DYNAMIC-FORMS.md) — field schema, form styles, and recipes
125
+ - [Code Generator](./docs/CODE-GENERATOR.md) — `@runtypelabs/persona/codegen` options for CLI/server-side snippet generation
126
+ - [THEME-CONFIG.md](./THEME-CONFIG.md) — the complete theme and design-token reference
122
127
 
123
128
  ### Optional proxy server
124
129
 
@@ -196,7 +201,9 @@ Add `RUNTYPE_API_KEY` to your environment. The proxy constructs the Runtype payl
196
201
 
197
202
  ### Development notes
198
203
 
199
- - The widget streams results using SSE and mirrors the backend `flow_complete`/`step_chunk` events.
200
- - Tailwind-esc classes are prefixed with `tvw-` and scoped to `[data-persona-root]`, so they won't collide with the host page.
201
- - Run `pnpm dev` from the repository root to boot the example proxy (`examples/proxy`) and the vanilla demo (`examples/embedded-app`).
204
+ - The widget streams results using SSE and mirrors Runtype flow/agent events, including `step_await` local-tool pauses and `/resume` continuations.
205
+ - Tailwind classes are prefixed with `tvw-` and scoped to `[data-persona-root]`, so they won't collide with the host page.
206
+ - Run `pnpm dev` from the repository root to boot the example proxy (`examples/vercel-edge`) and the vanilla demo (`examples/embedded-app`).
202
207
  - The proxy prefers port `43111` but automatically selects the next free port if needed.
208
+ - `features.askUserQuestion.expose` and `features.suggestReplies.expose` advertise built-in LOCAL client tools through `clientTools[]`; leave `expose` off if the flow already declares those tools server-side.
209
+ - `webmcp: { enabled: true }` snapshots page-registered tools on `document.modelContext`, sends them as `clientTools[]`, executes returned `webmcp:*` calls in the browser, and resumes the paused execution.
@@ -1,4 +1,4 @@
1
- import { S as StreamAnimationPlugin } from './types-quh7NmYD.cjs';
1
+ import { S as StreamAnimationPlugin } from './types-CthJFfNx.cjs';
2
2
 
3
3
  declare const glyphCycle: StreamAnimationPlugin;
4
4
 
@@ -1,4 +1,4 @@
1
- import { S as StreamAnimationPlugin } from './types-quh7NmYD.js';
1
+ import { S as StreamAnimationPlugin } from './types-CthJFfNx.js';
2
2
 
3
3
  declare const glyphCycle: StreamAnimationPlugin;
4
4
 
@@ -659,6 +659,13 @@ type AgentMessageMetadata = {
659
659
  * paginated stepper. Persists alongside `askUserQuestionAnswers`.
660
660
  */
661
661
  askUserQuestionIndex?: number;
662
+ /**
663
+ * Set to `true` once a `suggest_replies` tool call's fire-and-forget
664
+ * `/resume` has been accepted by the server. Persisted belt-and-suspenders
665
+ * mirror of the in-memory resolved-key dedupe, so hydration/re-emit paths
666
+ * never re-resume the call.
667
+ */
668
+ suggestRepliesResolved?: boolean;
662
669
  };
663
670
  /**
664
671
  * Context passed to plugin lifecycle hooks. Carries the live DOM references
@@ -659,6 +659,13 @@ type AgentMessageMetadata = {
659
659
  * paginated stepper. Persists alongside `askUserQuestionAnswers`.
660
660
  */
661
661
  askUserQuestionIndex?: number;
662
+ /**
663
+ * Set to `true` once a `suggest_replies` tool call's fire-and-forget
664
+ * `/resume` has been accepted by the server. Persisted belt-and-suspenders
665
+ * mirror of the in-memory resolved-key dedupe, so hydration/re-emit paths
666
+ * never re-resume the call.
667
+ */
668
+ suggestRepliesResolved?: boolean;
662
669
  };
663
670
  /**
664
671
  * Context passed to plugin lifecycle hooks. Carries the live DOM references
@@ -1,4 +1,4 @@
1
- import { S as StreamAnimationPlugin } from './types-quh7NmYD.cjs';
1
+ import { S as StreamAnimationPlugin } from './types-CthJFfNx.cjs';
2
2
 
3
3
  declare const wipe: StreamAnimationPlugin;
4
4
 
@@ -1,4 +1,4 @@
1
- import { S as StreamAnimationPlugin } from './types-quh7NmYD.js';
1
+ import { S as StreamAnimationPlugin } from './types-CthJFfNx.js';
2
2
 
3
3
  declare const wipe: StreamAnimationPlugin;
4
4
 
package/dist/codegen.cjs CHANGED
@@ -1,4 +1,4 @@
1
- "use strict";var $=Object.defineProperty;var M=Object.getOwnPropertyDescriptor;var R=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var I=(e,r)=>{for(var n in r)$(e,n,{get:r[n],enumerable:!0})},W=(e,r,n,s)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of R(r))!v.call(e,o)&&o!==n&&$(e,o,{get:()=>r[o],enumerable:!(s=M(r,o))||s.enumerable});return e};var H=e=>W($({},"__esModule",{value:!0}),e);var B={};I(B,{generateCodeSnippet:()=>_});module.exports=H(B);var x="3.31.1";var c=x;function u(e){if(e!==void 0)return typeof e=="string"?e:Array.isArray(e)?`[${e.map(r=>r.toString()).join(", ")}]`:e.toString()}function D(e){if(e)return{getHeaders:u(e.getHeaders),onFeedback:u(e.onFeedback),onCopy:u(e.onCopy),requestMiddleware:u(e.requestMiddleware),actionHandlers:u(e.actionHandlers),actionParsers:u(e.actionParsers),postprocessMessage:u(e.postprocessMessage),contextProviders:u(e.contextProviders),streamParser:u(e.streamParser)}}var A=`({ text, message }: any) => {
1
+ "use strict";var $=Object.defineProperty;var M=Object.getOwnPropertyDescriptor;var R=Object.getOwnPropertyNames;var v=Object.prototype.hasOwnProperty;var I=(e,r)=>{for(var n in r)$(e,n,{get:r[n],enumerable:!0})},W=(e,r,n,s)=>{if(r&&typeof r=="object"||typeof r=="function")for(let o of R(r))!v.call(e,o)&&o!==n&&$(e,o,{get:()=>r[o],enumerable:!(s=M(r,o))||s.enumerable});return e};var H=e=>W($({},"__esModule",{value:!0}),e);var B={};I(B,{generateCodeSnippet:()=>_});module.exports=H(B);var x="3.34.0";var c=x;function u(e){if(e!==void 0)return typeof e=="string"?e:Array.isArray(e)?`[${e.map(r=>r.toString()).join(", ")}]`:e.toString()}function D(e){if(e)return{getHeaders:u(e.getHeaders),onFeedback:u(e.onFeedback),onCopy:u(e.onCopy),requestMiddleware:u(e.requestMiddleware),actionHandlers:u(e.actionHandlers),actionParsers:u(e.actionParsers),postprocessMessage:u(e.postprocessMessage),contextProviders:u(e.contextProviders),streamParser:u(e.streamParser)}}var A=`({ text, message }: any) => {
2
2
  const jsonSource = (message as any).rawContent || text || message.content;
3
3
  if (!jsonSource || typeof jsonSource !== 'string') return null;
4
4
  let cleanJson = jsonSource
package/dist/codegen.js CHANGED
@@ -1,4 +1,4 @@
1
- var S="3.31.1";var c=S;function u(e){if(e!==void 0)return typeof e=="string"?e:Array.isArray(e)?`[${e.map(r=>r.toString()).join(", ")}]`:e.toString()}function T(e){if(e)return{getHeaders:u(e.getHeaders),onFeedback:u(e.onFeedback),onCopy:u(e.onCopy),requestMiddleware:u(e.requestMiddleware),actionHandlers:u(e.actionHandlers),actionParsers:u(e.actionParsers),postprocessMessage:u(e.postprocessMessage),contextProviders:u(e.contextProviders),streamParser:u(e.streamParser)}}var x=`({ text, message }: any) => {
1
+ var S="3.34.0";var c=S;function u(e){if(e!==void 0)return typeof e=="string"?e:Array.isArray(e)?`[${e.map(r=>r.toString()).join(", ")}]`:e.toString()}function T(e){if(e)return{getHeaders:u(e.getHeaders),onFeedback:u(e.onFeedback),onCopy:u(e.onCopy),requestMiddleware:u(e.requestMiddleware),actionHandlers:u(e.actionHandlers),actionParsers:u(e.actionParsers),postprocessMessage:u(e.postprocessMessage),contextProviders:u(e.contextProviders),streamParser:u(e.streamParser)}}var x=`({ text, message }: any) => {
2
2
  const jsonSource = (message as any).rawContent || text || message.content;
3
3
  if (!jsonSource || typeof jsonSource !== 'string') return null;
4
4
  let cleanJson = jsonSource