@runtypelabs/persona 3.31.0 → 3.33.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 +2 -0
  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 +4 -4
  9. package/dist/codegen.js +3 -3
  10. package/dist/index.cjs +43 -43
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +288 -8
  13. package/dist/index.d.ts +288 -8
  14. package/dist/index.global.js +76 -79
  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.map +1 -1
  19. package/dist/smart-dom-reader.d.cts +78 -2
  20. package/dist/smart-dom-reader.d.ts +78 -2
  21. package/dist/theme-editor.cjs +30 -30
  22. package/dist/theme-editor.d.cts +92 -5
  23. package/dist/theme-editor.d.ts +92 -5
  24. package/dist/theme-editor.js +30 -30
  25. package/dist/webmcp-polyfill.js +4 -0
  26. package/package.json +4 -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/components/messages.ts +10 -0
  31. package/src/components/suggestions.ts +49 -6
  32. package/src/index-core.ts +15 -0
  33. package/src/index-global.ts +39 -0
  34. package/src/runtime/host-layout.test.ts +158 -3
  35. package/src/runtime/host-layout.ts +95 -1
  36. package/src/session.ts +188 -32
  37. package/src/session.webmcp.test.ts +48 -0
  38. package/src/suggest-replies-tool.test.ts +445 -0
  39. package/src/suggest-replies-tool.ts +152 -0
  40. package/src/theme-editor/index.ts +2 -0
  41. package/src/theme-editor/webmcp/index.ts +2 -0
  42. package/src/theme-editor/webmcp/types.ts +16 -3
  43. package/src/types.ts +79 -2
  44. package/src/ui.suggest-replies.test.ts +237 -0
  45. package/src/ui.ts +57 -13
  46. package/src/utils/dock.test.ts +23 -1
  47. package/src/utils/dock.ts +2 -0
  48. package/src/version.ts +5 -2
  49. package/src/webmcp-bridge.test.ts +60 -0
  50. package/src/webmcp-bridge.ts +34 -1
  51. package/src/webmcp-polyfill.ts +16 -0
@@ -0,0 +1,4 @@
1
+ var He=Object.defineProperty;var Fe=(e,t,r)=>t in e?He(e,t,{enumerable:!0,configurable:!0,writable:!0,value:r}):e[t]=r;var _=(e,t,r)=>Fe(e,typeof t!="symbol"?t+"":t,r);function A(e,t){let r=typeof e;if(r!==typeof t)return!1;if(Array.isArray(e)){if(!Array.isArray(t))return!1;let n=e.length;if(n!==t.length)return!1;for(let i=0;i<n;i++)if(!A(e[i],t[i]))return!1;return!0}if(r==="object"){if(!e||!t)return e===t;let n=Object.keys(e),i=Object.keys(t);if(n.length!==i.length)return!1;for(let o of n)if(!A(e[o],t[o]))return!1;return!0}return e===t}function S(e){return encodeURI(Be(e))}function Be(e){return e.replace(/~/g,"~0").replace(/\//g,"~1")}var Ke={prefixItems:!0,items:!0,allOf:!0,anyOf:!0,oneOf:!0},Ge={$defs:!0,definitions:!0,properties:!0,patternProperties:!0,dependentSchemas:!0},Ye={id:!0,$id:!0,$ref:!0,$schema:!0,$anchor:!0,$vocabulary:!0,$comment:!0,default:!0,enum:!0,const:!0,required:!0,type:!0,maximum:!0,minimum:!0,exclusiveMaximum:!0,exclusiveMinimum:!0,multipleOf:!0,maxLength:!0,minLength:!0,pattern:!0,format:!0,maxItems:!0,minItems:!0,uniqueItems:!0,maxProperties:!0,minProperties:!0},Ze=typeof self!="undefined"&&self.location&&self.location.origin!=="null"?new URL(self.location.origin+self.location.pathname+location.search):new URL("https://github.com/cfworker");function E(e,t=Object.create(null),r=Ze,n=""){if(e&&typeof e=="object"&&!Array.isArray(e)){let d=e.$id||e.id;if(d){let o=new URL(d,r.href);o.hash.length>1?t[o.href]=e:(o.hash="",n===""?r=o:E(e,t,r))}}else if(e!==!0&&e!==!1)return t;let i=r.href+(n?"#"+n:"");if(t[i]!==void 0)throw new Error(`Duplicate schema URI "${i}".`);if(t[i]=e,e===!0||e===!1)return t;if(e.__absolute_uri__===void 0&&Object.defineProperty(e,"__absolute_uri__",{enumerable:!1,value:i}),e.$ref&&e.__absolute_ref__===void 0){let d=new URL(e.$ref,r.href);d.hash=d.hash,Object.defineProperty(e,"__absolute_ref__",{enumerable:!1,value:d.href})}if(e.$recursiveRef&&e.__absolute_recursive_ref__===void 0){let d=new URL(e.$recursiveRef,r.href);d.hash=d.hash,Object.defineProperty(e,"__absolute_recursive_ref__",{enumerable:!1,value:d.href})}if(e.$anchor){let d=new URL("#"+e.$anchor,r.href);t[d.href]=e}for(let d in e){if(Ye[d])continue;let o=`${n}/${S(d)}`,s=e[d];if(Array.isArray(s)){if(Ke[d]){let h=s.length;for(let x=0;x<h;x++)E(s[x],t,r,`${o}/${x}`)}}else if(Ge[d])for(let h in s)E(s[h],t,r,`${o}/${S(h)}`);else E(s,t,r,o)}return t}var Xe=/^(\d\d\d\d)-(\d\d)-(\d\d)$/,Qe=[0,31,28,31,30,31,30,31,31,30,31,30,31],Le=/^(\d\d):(\d\d):(\d\d)(\.\d+)?(z|[+-]\d\d(?::?\d\d)?)?$/i,et=/^(?=.{1,253}\.?$)[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?(?:\.[a-z0-9](?:[-0-9a-z]{0,61}[0-9a-z])?)*\.?$/i,tt=/^(?:[a-z][a-z0-9+\-.]*:)?(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'"()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?(?:\?(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i,rt=/^(?:(?:[^\x00-\x20"'<>%\\^`{|}]|%[0-9a-f]{2})|\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?)*\})*$/i,nt=/^(?:(?:https?|ftp):\/\/)(?:\S+(?::\S*)?@)?(?:(?!10(?:\.\d{1,3}){3})(?!127(?:\.\d{1,3}){3})(?!169\.254(?:\.\d{1,3}){2})(?!192\.168(?:\.\d{1,3}){2})(?!172\.(?:1[6-9]|2\d|3[0-1])(?:\.\d{1,3}){2})(?:[1-9]\d?|1\d\d|2[01]\d|22[0-3])(?:\.(?:1?\d{1,2}|2[0-4]\d|25[0-5])){2}(?:\.(?:[1-9]\d?|1\d\d|2[0-4]\d|25[0-4]))|(?:(?:[a-z\u{00a1}-\u{ffff}0-9]+-?)*[a-z\u{00a1}-\u{ffff}0-9]+)(?:\.(?:[a-z\u{00a1}-\u{ffff}0-9]+-?)*[a-z\u{00a1}-\u{ffff}0-9]+)*(?:\.(?:[a-z\u{00a1}-\u{ffff}]{2,})))(?::\d{2,5})?(?:\/[^\s]*)?$/iu,ot=/^(?:urn:uuid:)?[0-9a-f]{8}-(?:[0-9a-f]{4}-){3}[0-9a-f]{12}$/i,it=/^(?:\/(?:[^~/]|~0|~1)*)*$/,st=/^#(?:\/(?:[a-z0-9_\-.!$&'()*+,;:=@]|%[0-9a-f]{2}|~0|~1)*)*$/i,dt=/^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/,at=e=>{if(e[0]==='"')return!1;let[t,r,...n]=e.split("@");return!t||!r||n.length!==0||t.length>64||r.length>253||t[0]==="."||t.endsWith(".")||t.includes("..")||!/^[a-z0-9.-]+$/i.test(r)||!/^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+$/i.test(t)?!1:r.split(".").every(i=>/^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/i.test(i))},ft=/^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/,lt=/^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i,ut=e=>e.length>1&&e.length<80&&(/^P\d+([.,]\d+)?W$/.test(e)||/^P[\dYMDTHS]*(\d[.,]\d+)?[YMDHS]$/.test(e)&&/^P([.,\d]+Y)?([.,\d]+M)?([.,\d]+D)?(T([.,\d]+H)?([.,\d]+M)?([.,\d]+S)?)?$/.test(e));function k(e){return e.test.bind(e)}var me={date:Ee,time:Me.bind(void 0,!1),"date-time":pt,duration:ut,uri:$t,"uri-reference":k(tt),"uri-template":k(rt),url:k(nt),email:at,hostname:k(et),ipv4:k(ft),ipv6:k(lt),regex:wt,uuid:k(ot),"json-pointer":k(it),"json-pointer-uri-fragment":k(st),"relative-json-pointer":k(dt)};function mt(e){return e%4===0&&(e%100!==0||e%400===0)}function Ee(e){let t=e.match(Xe);if(!t)return!1;let r=+t[1],n=+t[2],i=+t[3];return n>=1&&n<=12&&i>=1&&i<=(n==2&&mt(r)?29:Qe[n])}function Me(e,t){let r=t.match(Le);if(!r)return!1;let n=+r[1],i=+r[2],d=+r[3],o=!!r[5];return(n<=23&&i<=59&&d<=59||n==23&&i==59&&d==60)&&(!e||o)}var ct=/t|\s/i;function pt(e){let t=e.split(ct);return t.length==2&&Ee(t[0])&&Me(!0,t[1])}var yt=/\/|:/,ht=/^(?:[a-z][a-z0-9+\-.]*:)(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'()*+,;=:@]|%[0-9a-f]{2})*)*)(?:\?(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;function $t(e){return yt.test(e)&&ht.test(e)}var gt=/[^\\]\\Z/;function wt(e){if(gt.test(e))return!1;try{return new RegExp(e,"u"),!0}catch{return!1}}var Ce;(function(e){e[e.Flag=1]="Flag",e[e.Basic=2]="Basic",e[e.Detailed=4]="Detailed"})(Ce||(Ce={}));function Ne(e){let t=0,r=e.length,n=0,i;for(;n<r;)t++,i=e.charCodeAt(n++),i>=55296&&i<=56319&&n<r&&(i=e.charCodeAt(n),(i&64512)==56320&&n++);return t}function w(e,t,r="2019-09",n=E(t),i=!0,d=null,o="#",s="#",h=Object.create(null)){if(t===!0)return{valid:!0,errors:[]};if(t===!1)return{valid:!1,errors:[{instanceLocation:o,keyword:"false",keywordLocation:o,error:"False boolean schema."}]};let x=typeof e,$;switch(x){case"boolean":case"number":case"string":$=x;break;case"object":e===null?$="null":Array.isArray(e)?$="array":$="object";break;default:throw new Error(`Instances of "${x}" type are not supported.`)}let{$ref:T,$recursiveRef:q,$recursiveAnchor:M,type:I,const:z,enum:D,required:ye,not:he,anyOf:ee,allOf:te,oneOf:$e,if:ge,then:we,else:xe,format:U,properties:re,patternProperties:ne,additionalProperties:be,unevaluatedProperties:ve,minProperties:oe,maxProperties:ie,propertyNames:Se,dependentRequired:se,dependentSchemas:de,dependencies:ae,prefixItems:fe,items:R,additionalItems:Te,unevaluatedItems:_e,contains:Ie,minContains:O,maxContains:V,minItems:le,maxItems:ue,uniqueItems:Ue,minimum:C,maximum:N,exclusiveMinimum:J,exclusiveMaximum:W,multipleOf:H,minLength:F,maxLength:B,pattern:ke,__absolute_ref__:K,__absolute_recursive_ref__:Ve}=t,f=[];if(M===!0&&d===null&&(d=t),q==="#"){let m=d===null?n[Ve]:d,u=`${s}/$recursiveRef`,y=w(e,d===null?t:d,r,n,i,m,o,u,h);y.valid||f.push({instanceLocation:o,keyword:"$recursiveRef",keywordLocation:u,error:"A subschema had errors."},...y.errors)}if(T!==void 0){let u=n[K||T];if(u===void 0){let a=`Unresolved $ref "${T}".`;throw K&&K!==T&&(a+=` Absolute URI "${K}".`),a+=`
2
+ Known schemas:
3
+ - ${Object.keys(n).join(`
4
+ - `)}`,new Error(a)}let y=`${s}/$ref`,l=w(e,u,r,n,i,d,o,y,h);if(l.valid||f.push({instanceLocation:o,keyword:"$ref",keywordLocation:y,error:"A subschema had errors."},...l.errors),r==="4"||r==="7")return{valid:f.length===0,errors:f}}if(Array.isArray(I)){let m=I.length,u=!1;for(let y=0;y<m;y++)if($===I[y]||I[y]==="integer"&&$==="number"&&e%1===0&&e===e){u=!0;break}u||f.push({instanceLocation:o,keyword:"type",keywordLocation:`${s}/type`,error:`Instance type "${$}" is invalid. Expected "${I.join('", "')}".`})}else I==="integer"?($!=="number"||e%1||e!==e)&&f.push({instanceLocation:o,keyword:"type",keywordLocation:`${s}/type`,error:`Instance type "${$}" is invalid. Expected "${I}".`}):I!==void 0&&$!==I&&f.push({instanceLocation:o,keyword:"type",keywordLocation:`${s}/type`,error:`Instance type "${$}" is invalid. Expected "${I}".`});if(z!==void 0&&($==="object"||$==="array"?A(e,z)||f.push({instanceLocation:o,keyword:"const",keywordLocation:`${s}/const`,error:`Instance does not match ${JSON.stringify(z)}.`}):e!==z&&f.push({instanceLocation:o,keyword:"const",keywordLocation:`${s}/const`,error:`Instance does not match ${JSON.stringify(z)}.`})),D!==void 0&&($==="object"||$==="array"?D.some(m=>A(e,m))||f.push({instanceLocation:o,keyword:"enum",keywordLocation:`${s}/enum`,error:`Instance does not match any of ${JSON.stringify(D)}.`}):D.some(m=>e===m)||f.push({instanceLocation:o,keyword:"enum",keywordLocation:`${s}/enum`,error:`Instance does not match any of ${JSON.stringify(D)}.`})),he!==void 0){let m=`${s}/not`;w(e,he,r,n,i,d,o,m).valid&&f.push({instanceLocation:o,keyword:"not",keywordLocation:m,error:'Instance matched "not" schema.'})}let G=[];if(ee!==void 0){let m=`${s}/anyOf`,u=f.length,y=!1;for(let l=0;l<ee.length;l++){let a=ee[l],p=Object.create(h),c=w(e,a,r,n,i,M===!0?d:null,o,`${m}/${l}`,p);f.push(...c.errors),y=y||c.valid,c.valid&&G.push(p)}y?f.length=u:f.splice(u,0,{instanceLocation:o,keyword:"anyOf",keywordLocation:m,error:"Instance does not match any subschemas."})}if(te!==void 0){let m=`${s}/allOf`,u=f.length,y=!0;for(let l=0;l<te.length;l++){let a=te[l],p=Object.create(h),c=w(e,a,r,n,i,M===!0?d:null,o,`${m}/${l}`,p);f.push(...c.errors),y=y&&c.valid,c.valid&&G.push(p)}y?f.length=u:f.splice(u,0,{instanceLocation:o,keyword:"allOf",keywordLocation:m,error:"Instance does not match every subschema."})}if($e!==void 0){let m=`${s}/oneOf`,u=f.length,y=$e.filter((l,a)=>{let p=Object.create(h),c=w(e,l,r,n,i,M===!0?d:null,o,`${m}/${a}`,p);return f.push(...c.errors),c.valid&&G.push(p),c.valid}).length;y===1?f.length=u:f.splice(u,0,{instanceLocation:o,keyword:"oneOf",keywordLocation:m,error:`Instance does not match exactly one subschema (${y} matches).`})}if(($==="object"||$==="array")&&Object.assign(h,...G),ge!==void 0){let m=`${s}/if`;if(w(e,ge,r,n,i,d,o,m,h).valid){if(we!==void 0){let y=w(e,we,r,n,i,d,o,`${s}/then`,h);y.valid||f.push({instanceLocation:o,keyword:"if",keywordLocation:m,error:'Instance does not match "then" schema.'},...y.errors)}}else if(xe!==void 0){let y=w(e,xe,r,n,i,d,o,`${s}/else`,h);y.valid||f.push({instanceLocation:o,keyword:"if",keywordLocation:m,error:'Instance does not match "else" schema.'},...y.errors)}}if($==="object"){if(ye!==void 0)for(let l of ye)l in e||f.push({instanceLocation:o,keyword:"required",keywordLocation:`${s}/required`,error:`Instance does not have required property "${l}".`});let m=Object.keys(e);if(oe!==void 0&&m.length<oe&&f.push({instanceLocation:o,keyword:"minProperties",keywordLocation:`${s}/minProperties`,error:`Instance does not have at least ${oe} properties.`}),ie!==void 0&&m.length>ie&&f.push({instanceLocation:o,keyword:"maxProperties",keywordLocation:`${s}/maxProperties`,error:`Instance does not have at least ${ie} properties.`}),Se!==void 0){let l=`${s}/propertyNames`;for(let a in e){let p=`${o}/${S(a)}`,c=w(a,Se,r,n,i,d,p,l);c.valid||f.push({instanceLocation:o,keyword:"propertyNames",keywordLocation:l,error:`Property name "${a}" does not match schema.`},...c.errors)}}if(se!==void 0){let l=`${s}/dependantRequired`;for(let a in se)if(a in e){let p=se[a];for(let c of p)c in e||f.push({instanceLocation:o,keyword:"dependentRequired",keywordLocation:l,error:`Instance has "${a}" but does not have "${c}".`})}}if(de!==void 0)for(let l in de){let a=`${s}/dependentSchemas`;if(l in e){let p=w(e,de[l],r,n,i,d,o,`${a}/${S(l)}`,h);p.valid||f.push({instanceLocation:o,keyword:"dependentSchemas",keywordLocation:a,error:`Instance has "${l}" but does not match dependant schema.`},...p.errors)}}if(ae!==void 0){let l=`${s}/dependencies`;for(let a in ae)if(a in e){let p=ae[a];if(Array.isArray(p))for(let c of p)c in e||f.push({instanceLocation:o,keyword:"dependencies",keywordLocation:l,error:`Instance has "${a}" but does not have "${c}".`});else{let c=w(e,p,r,n,i,d,o,`${l}/${S(a)}`);c.valid||f.push({instanceLocation:o,keyword:"dependencies",keywordLocation:l,error:`Instance has "${a}" but does not match dependant schema.`},...c.errors)}}}let u=Object.create(null),y=!1;if(re!==void 0){let l=`${s}/properties`;for(let a in re){if(!(a in e))continue;let p=`${o}/${S(a)}`,c=w(e[a],re[a],r,n,i,d,p,`${l}/${S(a)}`);if(c.valid)h[a]=u[a]=!0;else if(y=i,f.push({instanceLocation:o,keyword:"properties",keywordLocation:l,error:`Property "${a}" does not match schema.`},...c.errors),y)break}}if(!y&&ne!==void 0){let l=`${s}/patternProperties`;for(let a in ne){let p=new RegExp(a,"u"),c=ne[a];for(let v in e){if(!p.test(v))continue;let Pe=`${o}/${S(v)}`,Oe=w(e[v],c,r,n,i,d,Pe,`${l}/${S(a)}`);Oe.valid?h[v]=u[v]=!0:(y=i,f.push({instanceLocation:o,keyword:"patternProperties",keywordLocation:l,error:`Property "${v}" matches pattern "${a}" but does not match associated schema.`},...Oe.errors))}}}if(!y&&be!==void 0){let l=`${s}/additionalProperties`;for(let a in e){if(u[a])continue;let p=`${o}/${S(a)}`,c=w(e[a],be,r,n,i,d,p,l);c.valid?h[a]=!0:(y=i,f.push({instanceLocation:o,keyword:"additionalProperties",keywordLocation:l,error:`Property "${a}" does not match additional properties schema.`},...c.errors))}}else if(!y&&ve!==void 0){let l=`${s}/unevaluatedProperties`;for(let a in e)if(!h[a]){let p=`${o}/${S(a)}`,c=w(e[a],ve,r,n,i,d,p,l);c.valid?h[a]=!0:f.push({instanceLocation:o,keyword:"unevaluatedProperties",keywordLocation:l,error:`Property "${a}" does not match unevaluated properties schema.`},...c.errors)}}}else if($==="array"){ue!==void 0&&e.length>ue&&f.push({instanceLocation:o,keyword:"maxItems",keywordLocation:`${s}/maxItems`,error:`Array has too many items (${e.length} > ${ue}).`}),le!==void 0&&e.length<le&&f.push({instanceLocation:o,keyword:"minItems",keywordLocation:`${s}/minItems`,error:`Array has too few items (${e.length} < ${le}).`});let m=e.length,u=0,y=!1;if(fe!==void 0){let l=`${s}/prefixItems`,a=Math.min(fe.length,m);for(;u<a;u++){let p=w(e[u],fe[u],r,n,i,d,`${o}/${u}`,`${l}/${u}`);if(h[u]=!0,!p.valid&&(y=i,f.push({instanceLocation:o,keyword:"prefixItems",keywordLocation:l,error:"Items did not match schema."},...p.errors),y))break}}if(R!==void 0){let l=`${s}/items`;if(Array.isArray(R)){let a=Math.min(R.length,m);for(;u<a;u++){let p=w(e[u],R[u],r,n,i,d,`${o}/${u}`,`${l}/${u}`);if(h[u]=!0,!p.valid&&(y=i,f.push({instanceLocation:o,keyword:"items",keywordLocation:l,error:"Items did not match schema."},...p.errors),y))break}}else for(;u<m;u++){let a=w(e[u],R,r,n,i,d,`${o}/${u}`,l);if(h[u]=!0,!a.valid&&(y=i,f.push({instanceLocation:o,keyword:"items",keywordLocation:l,error:"Items did not match schema."},...a.errors),y))break}if(!y&&Te!==void 0){let a=`${s}/additionalItems`;for(;u<m;u++){let p=w(e[u],Te,r,n,i,d,`${o}/${u}`,a);h[u]=!0,p.valid||(y=i,f.push({instanceLocation:o,keyword:"additionalItems",keywordLocation:a,error:"Items did not match additional items schema."},...p.errors))}}}if(Ie!==void 0)if(m===0&&O===void 0)f.push({instanceLocation:o,keyword:"contains",keywordLocation:`${s}/contains`,error:"Array is empty. It must contain at least one item matching the schema."});else if(O!==void 0&&m<O)f.push({instanceLocation:o,keyword:"minContains",keywordLocation:`${s}/minContains`,error:`Array has less items (${m}) than minContains (${O}).`});else{let l=`${s}/contains`,a=f.length,p=0;for(let c=0;c<m;c++){let v=w(e[c],Ie,r,n,i,d,`${o}/${c}`,l);v.valid?(h[c]=!0,p++):f.push(...v.errors)}p>=(O||0)&&(f.length=a),O===void 0&&V===void 0&&p===0?f.splice(a,0,{instanceLocation:o,keyword:"contains",keywordLocation:l,error:"Array does not contain item matching schema."}):O!==void 0&&p<O?f.push({instanceLocation:o,keyword:"minContains",keywordLocation:`${s}/minContains`,error:`Array must contain at least ${O} items matching schema. Only ${p} items were found.`}):V!==void 0&&p>V&&f.push({instanceLocation:o,keyword:"maxContains",keywordLocation:`${s}/maxContains`,error:`Array may contain at most ${V} items matching schema. ${p} items were found.`})}if(!y&&_e!==void 0){let l=`${s}/unevaluatedItems`;for(u;u<m;u++){if(h[u])continue;let a=w(e[u],_e,r,n,i,d,`${o}/${u}`,l);h[u]=!0,a.valid||f.push({instanceLocation:o,keyword:"unevaluatedItems",keywordLocation:l,error:"Items did not match unevaluated items schema."},...a.errors)}}if(Ue)for(let l=0;l<m;l++){let a=e[l],p=typeof a=="object"&&a!==null;for(let c=0;c<m;c++){if(l===c)continue;let v=e[c];(a===v||p&&(typeof v=="object"&&v!==null)&&A(a,v))&&(f.push({instanceLocation:o,keyword:"uniqueItems",keywordLocation:`${s}/uniqueItems`,error:`Duplicate items at indexes ${l} and ${c}.`}),l=Number.MAX_SAFE_INTEGER,c=Number.MAX_SAFE_INTEGER)}}}else if($==="number"){if(r==="4"?(C!==void 0&&(J===!0&&e<=C||e<C)&&f.push({instanceLocation:o,keyword:"minimum",keywordLocation:`${s}/minimum`,error:`${e} is less than ${J?"or equal to ":""} ${C}.`}),N!==void 0&&(W===!0&&e>=N||e>N)&&f.push({instanceLocation:o,keyword:"maximum",keywordLocation:`${s}/maximum`,error:`${e} is greater than ${W?"or equal to ":""} ${N}.`})):(C!==void 0&&e<C&&f.push({instanceLocation:o,keyword:"minimum",keywordLocation:`${s}/minimum`,error:`${e} is less than ${C}.`}),N!==void 0&&e>N&&f.push({instanceLocation:o,keyword:"maximum",keywordLocation:`${s}/maximum`,error:`${e} is greater than ${N}.`}),J!==void 0&&e<=J&&f.push({instanceLocation:o,keyword:"exclusiveMinimum",keywordLocation:`${s}/exclusiveMinimum`,error:`${e} is less than ${J}.`}),W!==void 0&&e>=W&&f.push({instanceLocation:o,keyword:"exclusiveMaximum",keywordLocation:`${s}/exclusiveMaximum`,error:`${e} is greater than or equal to ${W}.`})),H!==void 0){let m=e%H;Math.abs(0-m)>=11920929e-14&&Math.abs(H-m)>=11920929e-14&&f.push({instanceLocation:o,keyword:"multipleOf",keywordLocation:`${s}/multipleOf`,error:`${e} is not a multiple of ${H}.`})}}else if($==="string"){let m=F===void 0&&B===void 0?0:Ne(e);F!==void 0&&m<F&&f.push({instanceLocation:o,keyword:"minLength",keywordLocation:`${s}/minLength`,error:`String is too short (${m} < ${F}).`}),B!==void 0&&m>B&&f.push({instanceLocation:o,keyword:"maxLength",keywordLocation:`${s}/maxLength`,error:`String is too long (${m} > ${B}).`}),ke!==void 0&&!new RegExp(ke,"u").test(e)&&f.push({instanceLocation:o,keyword:"pattern",keywordLocation:`${s}/pattern`,error:"String does not match pattern."}),U!==void 0&&me[U]&&!me[U](e)&&f.push({instanceLocation:o,keyword:"format",keywordLocation:`${s}/format`,error:`String does not match format "${U}".`})}return{valid:f.length===0,errors:f}}var Y=class{constructor(t,r="2019-09",n=!0){_(this,"schema");_(this,"draft");_(this,"shortCircuit");_(this,"lookup");this.schema=t,this.draft=r,this.shortCircuit=n,this.lookup=E(t)}validate(t){return w(t,this.schema,this.draft,this.lookup,this.shortCircuit)}addSchema(t,r){r&&(t={...t,$id:r}),E(t,this.lookup)}};var Ae="Failed to parse input arguments",Q="Tool was executed but the invocation failed. For example, the script function threw an error",ce="Tool was cancelled",Z={type:"object",properties:{}},xt=["draft-2020-12","draft-07"],bt="__isWebMCPPolyfill",ze=Symbol("standardValidator"),g={installed:!1,previousNavigatorModelContextDescriptor:void 0,previousNavigatorModelContextTestingDescriptor:void 0,previousDocumentModelContextDescriptor:void 0,installedNavigatorModelContext:!1,installedNavigatorModelContextTesting:!1,installedDocumentModelContext:!1},vt=class extends EventTarget{constructor(){super(...arguments);_(this,"tools",new Map);_(this,"testingShim",null);_(this,"_ontoolchange",null);_(this,"unregisterToolDeprecationWarned",!1)}get ontoolchange(){return this._ontoolchange}set ontoolchange(t){this._ontoolchange=t}registerTool(t,r){var d;let n=r==null?void 0:r.signal;if(n!=null&&n.aborted){console.warn(`[WebMCPPolyfill] registerTool("${(d=t==null?void 0:t.name)!=null?d:"<unknown>"}") skipped: options.signal was already aborted.`);return}let i=Et(t,this.tools);this.tools.set(i.name,i),this.notifyToolsChanged(),n&&n.addEventListener("abort",()=>{this.tools.delete(i.name)&&this.notifyToolsChanged()},{once:!0})}unregisterTool(t){this.warnUnregisterToolDeprecationOnce();let r=_t(t);this.tools.delete(r)&&this.notifyToolsChanged()}getTools(){return Promise.resolve(this.getRegisteredToolInfos())}executeTool(t,r,n){return this.executeToolByName(t.name,r,n,!1)}getTestingShim(){return this.testingShim||(this.testingShim=new St(this)),this.testingShim}getToolInfos(){return[...this.tools.values()].map(t=>{var n;let r;try{r=JSON.stringify((n=t.inputSchema)!=null?n:{type:"object"})}catch{r='{"type":"object"}'}return{name:t.name,description:t.description,inputSchema:r}})}getRegisteredToolInfos(){return this.getToolInfos().map(t=>{var n,i,d;let r=this.tools.get(t.name);return{...t,title:(n=r==null?void 0:r.title)!=null?n:"",origin:(d=(i=globalThis.location)==null?void 0:i.origin)!=null?d:"",window:globalThis.window}})}async executeToolForTesting(t,r,n){return this.executeToolByName(t,r,n,!0)}async executeToolByName(t,r,n,i){var $;if(($=n==null?void 0:n.signal)!=null&&$.aborted)throw P(ce);let d=this.tools.get(t);if(!d)throw P(`Tool not found: ${t}`);let o=Tt(r),s=await At(o,d);if(s)throw P(s);let h=!0,x={requestUserInteraction:async T=>{if(!h)throw new Error(`ModelContextClient for tool "${t}" is no longer active after execute() resolved`);if(typeof T!="function")throw new TypeError("requestUserInteraction(callback) requires a function callback");return T()}};try{let T=d.execute(o,x),q=await Ut(Promise.resolve(T),n==null?void 0:n.signal);if(i)return qt(Jt(q));let M=JSON.stringify(q);return M===void 0?null:M}catch(T){throw P(T instanceof Error?`${Q}: ${T.message}`:Q)}finally{h=!1}}notifyToolsChanged(){queueMicrotask(()=>{var r,n;let t=new Event("toolchange");try{(r=this._ontoolchange)==null||r.call(this,t)}catch(i){console.warn("[WebMCPPolyfill] navigator.modelContext.ontoolchange handler threw:",i)}this.dispatchEvent(t),(n=this.testingShim)==null||n.dispatchToolChange()})}warnUnregisterToolDeprecationOnce(){this.unregisterToolDeprecationWarned||(this.unregisterToolDeprecationWarned=!0,console.warn("[WebMCPPolyfill] navigator.modelContext.unregisterTool() is deprecated. The April 23, 2026 WebMCP draft removed it in favor of registerTool(tool, { signal }) \u2014 pass an AbortSignal and abort it to unregister."))}},St=class extends EventTarget{constructor(t){super();_(this,"context");_(this,"_ontoolchange",null);this.context=t}listTools(){return this.context.getToolInfos()}executeTool(t,r,n){return this.context.executeToolForTesting(t,r,n)}getCrossDocumentScriptToolResult(){return Promise.resolve("[]")}get ontoolchange(){return this._ontoolchange}set ontoolchange(t){this._ontoolchange=t}registerToolsChangedCallback(t){if(typeof t!="function")throw new TypeError("Failed to execute 'registerToolsChangedCallback' on 'ModelContextTesting': parameter 1 is not of type 'Function'.");this.addEventListener("toolchange",t)}dispatchToolChange(){var r;let t=new Event("toolchange");try{(r=this._ontoolchange)==null||r.call(this,t)}catch(n){console.warn("[WebMCPPolyfill] ontoolchange handler threw:",n)}this.dispatchEvent(t),this.dispatchEvent(new Event("toolschanged"))}};function P(e){try{return new DOMException(e,"UnknownError")}catch{let t=new Error(e);return t.name="UnknownError",t}}function Tt(e){let t;try{t=JSON.parse(e)}catch{throw P(Ae)}if(!t||typeof t!="object"||Array.isArray(t))throw P(Ae);return t}function _t(e){if(typeof e=="string")return e;if(b(e)&&typeof e.name=="string")return e.name;throw new TypeError("Failed to execute 'unregisterTool' on 'ModelContext': parameter 1 must be a string or an object with a string name.")}function b(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function De(e){if(!b(e))return null;let t=e["~standard"];return b(t)?t:null}function It(e){let t=De(e);return!!(t&&t.version===1&&typeof t.validate=="function")}function kt(e){let t=De(e);return!t||t.version!==1||!b(t.jsonSchema)?!1:typeof t.jsonSchema.input=="function"}function X(e){return{"~standard":{version:1,vendor:"@mcp-b/webmcp-polyfill-json-schema",validate(t){if(!b(t))return{issues:[{message:"expected object arguments"}]};let r=Mt(t,e);return r?{issues:[r]}:{value:t}}}}}function Pt(e){for(let t of xt)try{let r=e["~standard"].jsonSchema.input({target:t});return Re(r),r}catch(r){console.warn(`[WebMCPPolyfill] Standard JSON Schema conversion failed for target "${t}":`,r)}throw new Error("Failed to convert Standard JSON Schema inputSchema to a JSON Schema object")}function Ot(e){if(e===void 0){let r=Z;return{inputSchema:r,standardValidator:X(r)}}if(kt(e)){let r=Pt(e);return{inputSchema:r,standardValidator:X(r)}}if(It(e))return{inputSchema:Z,standardValidator:e};if(Re(e),Object.keys(e).length===0)return{inputSchema:Z,standardValidator:X(Z)};let t=e.type===void 0?{type:"object",...e}:e;return{inputSchema:t,standardValidator:X(t)}}function Re(e){if(!b(e))throw new Error("inputSchema must be a JSON Schema object");j(e,"$")}function j(e,t){let r=e.type;if(r!==void 0&&typeof r!="string"&&!(Array.isArray(r)&&r.every(s=>typeof s=="string"&&s.length>0)))throw new Error(`Invalid JSON Schema at ${t}: "type" must be a string or string[]`);let n=e.required;if(n!==void 0&&!(Array.isArray(n)&&n.every(s=>typeof s=="string")))throw new Error(`Invalid JSON Schema at ${t}: "required" must be an array of strings`);let i=e.properties;if(i!==void 0){if(!b(i))throw new Error(`Invalid JSON Schema at ${t}: "properties" must be an object`);for(let[s,h]of Object.entries(i)){if(!b(h))throw new Error(`Invalid JSON Schema at ${t}.properties.${s}: expected object schema`);j(h,`${t}.properties.${s}`)}}let d=e.items;if(d!==void 0)if(Array.isArray(d))for(let[s,h]of d.entries()){if(!b(h))throw new Error(`Invalid JSON Schema at ${t}.items[${s}]: expected object schema`);j(h,`${t}.items[${s}]`)}else if(b(d))j(d,`${t}.items`);else throw new Error(`Invalid JSON Schema at ${t}: "items" must be an object or object[]`);for(let s of["allOf","anyOf","oneOf"]){let h=e[s];if(h!==void 0){if(!Array.isArray(h))throw new Error(`Invalid JSON Schema at ${t}: "${s}" must be an array`);for(let[x,$]of h.entries()){if(!b($))throw new Error(`Invalid JSON Schema at ${t}.${s}[${x}]: expected object schema`);j($,`${t}.${s}[${x}]`)}}}let o=e.not;if(o!==void 0){if(!b(o))throw new Error(`Invalid JSON Schema at ${t}: "not" must be an object schema`);j(o,`${t}.not`)}try{JSON.stringify(e)}catch{throw new Error(`Invalid JSON Schema at ${t}: schema must be JSON-serializable`)}}function Et(e,t){if(!e||typeof e!="object")throw new TypeError("registerTool(tool) requires a tool object");if(typeof e.name!="string"||e.name.length===0)throw new TypeError('Tool "name" must be a non-empty string');if(typeof e.description!="string"||e.description.length===0)throw new TypeError('Tool "description" must be a non-empty string');if(typeof e.execute!="function")throw new TypeError('Tool "execute" must be a function');if(t.has(e.name))throw new Error(`Tool already registered: ${e.name}`);let r=Ot(e.inputSchema),n=e.annotations,i=n?{...n,...n.readOnlyHint==="true"?{readOnlyHint:!0}:n.readOnlyHint==="false"?{readOnlyHint:!1}:{},...n.destructiveHint==="true"?{destructiveHint:!0}:n.destructiveHint==="false"?{destructiveHint:!1}:{},...n.idempotentHint==="true"?{idempotentHint:!0}:n.idempotentHint==="false"?{idempotentHint:!1}:{},...n.openWorldHint==="true"?{openWorldHint:!0}:n.openWorldHint==="false"?{openWorldHint:!1}:{}}:void 0;return{...e,...i?{annotations:i}:{},inputSchema:r.inputSchema,[ze]:r.standardValidator}}function Mt(e,t){let r=new Y(t,"2020-12",!0).validate(e);if(r.valid)return null;let n=r.errors[r.errors.length-1];return n?{message:n.error}:{message:"Input validation failed"}}function Ct(e){if(!e||e.length===0)return null;let t=e.map(r=>b(r)&&"key"in r?r.key:r).map(r=>String(r)).filter(r=>r.length>0);return t.length===0?null:t.join(".")}async function Nt(e,t){let r;try{r=await Promise.resolve(t["~standard"].validate(e))}catch(d){let o=d instanceof Error?`: ${d.message}`:"";return console.error("[WebMCPPolyfill] Standard Schema validation threw unexpectedly:",d),`Input validation error: schema validation failed${o}`}if(!r.issues||r.issues.length===0)return null;let n=r.issues[0];if(!n)return"Input validation error";let i=Ct(n==null?void 0:n.path);return i?`Input validation error: ${n.message} at ${i}`:`Input validation error: ${n.message}`}async function At(e,t){return Nt(e,t[ze])}function jt(e){return b(e)&&Array.isArray(e.content)}function zt(e){return e===null||typeof e=="string"||typeof e=="number"||typeof e=="boolean"}function pe(e){return zt(e)?Number.isFinite(e)||typeof e!="number":Array.isArray(e)?e.every(t=>pe(t)):b(e)?Object.values(e).every(t=>pe(t)):!1}function Dt(e){if(!(!b(e)||!pe(e)))return e}function Rt(e){var t;if(typeof e=="string")return e;try{return(t=JSON.stringify(e))!=null?t:String(e)}catch{return String(e)}}function Jt(e){if(jt(e))return e;let t=Dt(e);return{content:[{type:"text",text:Rt(e)}],...t?{structuredContent:t}:{},isError:!1}}function Wt(e){var t;for(let r of(t=e.content)!=null?t:[])if(r.type==="text"&&"text"in r&&typeof r.text=="string")return r.text;return null}function qt(e){var r;if(e.isError)throw P(((r=Wt(e))==null?void 0:r.replace(/^Error:\s*/i,"").trim())||Q);let t=e.metadata;if(t&&typeof t=="object"&&t.willNavigate)return null;try{return JSON.stringify(e)}catch{throw P(Q)}}function Ut(e,t){return t?t.aborted?Promise.reject(P(ce)):new Promise((r,n)=>{let i=()=>{d(),n(P(ce))},d=()=>{t.removeEventListener("abort",i)};t.addEventListener("abort",i,{once:!0}),e.then(o=>{d(),r(o)},o=>{d(),n(o)})}):e}function Je(){return typeof navigator!="undefined"?navigator:null}function We(){return typeof document!="undefined"?document:null}function Vt(e,t,r){Object.defineProperty(e,t,{configurable:!0,enumerable:!0,writable:!1,value:r})}function je(e,t){Object.defineProperty(e,"modelContext",{configurable:!0,enumerable:!0,writable:!1,value:t})}var L=!1;function Ht(e,t){Object.defineProperty(e,"modelContext",{configurable:!0,enumerable:!0,get(){return L||(L=!0,console.warn("[WebMCPPolyfill] navigator.modelContext is deprecated. The May 27, 2026 WebMCP draft moved the modelContext getter from Navigator to Document \u2014 use document.modelContext instead. See https://github.com/webmachinelearning/webmcp/pull/184.")),t}})}function qe(e){var h;let t=Je(),r=We();if(!t&&!r||(r==null?void 0:r.modelContext))return;let i=t==null?void 0:t.modelContext,d=!!i;if(g.installed&&Ft(),r&&i){g.previousDocumentModelContextDescriptor=Object.getOwnPropertyDescriptor(r,"modelContext"),je(r,i),g.installedDocumentModelContext=!0,g.installed=!0;return}if(d)return;let o=new vt,s=o;if(s[bt]=!0,r&&(g.previousDocumentModelContextDescriptor=Object.getOwnPropertyDescriptor(r,"modelContext"),je(r,s),g.installedDocumentModelContext=!0),t){g.previousNavigatorModelContextDescriptor=Object.getOwnPropertyDescriptor(t,"modelContext"),g.previousNavigatorModelContextTestingDescriptor=Object.getOwnPropertyDescriptor(t,"modelContextTesting"),L=!1,Ht(t,s),g.installedNavigatorModelContext=!0;let x=(h=e==null?void 0:e.installTestingShim)!=null?h:"if-missing",$=!!t.modelContextTesting;(x==="always"||(x===!0||x==="if-missing")&&!$)&&(Vt(t,"modelContextTesting",o.getTestingShim()),g.installedNavigatorModelContextTesting=!0)}g.installed=!0}function Ft(){if(!g.installed)return;let e=(n,i,d)=>{if(d){Object.defineProperty(n,i,d);return}delete n[i]},t=Je(),r=We();r&&g.installedDocumentModelContext&&e(r,"modelContext",g.previousDocumentModelContextDescriptor),t&&g.installedNavigatorModelContext&&e(t,"modelContext",g.previousNavigatorModelContextDescriptor),t&&g.installedNavigatorModelContextTesting&&e(t,"modelContextTesting",g.previousNavigatorModelContextTestingDescriptor),g.installed=!1,g.previousDocumentModelContextDescriptor=void 0,g.previousNavigatorModelContextDescriptor=void 0,g.previousNavigatorModelContextTestingDescriptor=void 0,g.installedDocumentModelContext=!1,g.installedNavigatorModelContext=!1,g.installedNavigatorModelContextTesting=!1,L=!1}if(typeof window!="undefined"&&typeof document!="undefined"){let e=window.__webMCPPolyfillOptions;if((e==null?void 0:e.autoInitialize)!==!1)try{qe(e)}catch(t){console.error("[WebMCPPolyfill] Auto-initialization failed:",t)}}export{qe as initializeWebMCPPolyfill};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@runtypelabs/persona",
3
- "version": "3.31.0",
3
+ "version": "3.33.0",
4
4
  "description": "Themeable, pluggable streaming agent widget for websites, in plain JS with support for voice input and reasoning / tool output.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",
@@ -109,7 +109,7 @@
109
109
  "access": "public"
110
110
  },
111
111
  "scripts": {
112
- "build": "rimraf dist && pnpm run build:styles && pnpm run build:client && pnpm run build:installer && pnpm run build:launcher && pnpm run build:theme-ref && pnpm run build:codegen && pnpm run build:theme-editor && pnpm run build:testing && pnpm run build:smart-dom-reader && pnpm run build:plugin-kit && pnpm run build:animations",
112
+ "build": "rimraf dist && pnpm run build:styles && pnpm run build:client && pnpm run build:installer && pnpm run build:launcher && pnpm run build:webmcp-polyfill && pnpm run build:theme-ref && pnpm run build:codegen && pnpm run build:theme-editor && pnpm run build:testing && pnpm run build:smart-dom-reader && pnpm run build:plugin-kit && pnpm run build:animations",
113
113
  "build:plugin-kit": "tsup src/plugin-kit.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting",
114
114
  "build:theme-editor": "tsup src/theme-editor.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting",
115
115
  "build:testing": "tsup src/testing.ts --format esm,cjs --minify --dts --out-dir dist --no-splitting",
@@ -118,7 +118,8 @@
118
118
  "build:theme-ref": "tsup src/theme-reference.ts --format esm,cjs --minify --dts",
119
119
  "build:codegen": "tsup src/codegen.ts --format esm,cjs --minify --dts",
120
120
  "build:styles": "node -e \"const fs=require('fs');fs.mkdirSync('dist',{recursive:true});fs.copyFileSync('src/styles/widget.css','dist/widget.css');\"",
121
- "build:client": "tsup src/index.ts --format esm,cjs --minify --sourcemap --splitting false --dts --loader \".css=text\" && tsup src/index-global.ts --format iife --global-name AgentWidget --minify --sourcemap --splitting false --out-dir dist --loader \".css=text\" && node -e \"const fs=require('fs');for(const ext of ['.global.js','.global.js.map']){const from='dist/index-global'+ext;if(fs.existsSync(from))fs.renameSync(from,'dist/index'+ext);}\"",
121
+ "build:client": "tsup src/index.ts --format esm,cjs --minify --sourcemap --splitting false --dts --loader \".css=text\" && tsup --config tsup.global.config.ts && node -e \"const fs=require('fs');for(const ext of ['.global.js','.global.js.map']){const from='dist/index-global'+ext;if(fs.existsSync(from))fs.renameSync(from,'dist/index'+ext);}\"",
122
+ "build:webmcp-polyfill": "tsup --config tsup.webmcp-polyfill.config.ts",
122
123
  "build:installer": "tsup src/install.ts --format iife --global-name SiteAgentInstaller --out-dir dist --minify --sourcemap --no-splitting",
123
124
  "build:launcher": "tsup src/launcher-global.ts --format iife --global-name AgentWidgetLauncher --minify --sourcemap --splitting false --out-dir dist && node -e \"const fs=require('fs');for(const ext of ['.global.js','.global.js.map']){const from='dist/launcher-global'+ext;if(fs.existsSync(from))fs.renameSync(from,'dist/launcher'+ext);}\"",
124
125
  "lint": "eslint . --ext .ts",
@@ -0,0 +1,148 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import {
3
+ ASK_USER_QUESTION_CLIENT_TOOL,
4
+ ASK_USER_QUESTION_PARAMETERS_SCHEMA,
5
+ builtInClientToolsForDispatch,
6
+ } from "./ask-user-question-tool";
7
+ import {
8
+ ASK_USER_QUESTION_MAX,
9
+ ASK_USER_QUESTION_TOOL_NAME,
10
+ } from "./components/ask-user-question-bubble";
11
+ import { AgentWidgetClient } from "./client";
12
+ import { computeClientToolsFingerprint } from "./webmcp-bridge";
13
+ import type { AgentWidgetConfig } from "./types";
14
+
15
+ describe("ASK_USER_QUESTION_CLIENT_TOOL definition", () => {
16
+ it("matches the renderer's tool name and origin/annotation contract", () => {
17
+ expect(ASK_USER_QUESTION_CLIENT_TOOL.name).toBe(ASK_USER_QUESTION_TOOL_NAME);
18
+ // `'sdk'` keeps the bare name on the wire (the server only prefixes
19
+ // `origin: 'webmcp'` tools) so the step_await routes to the answer sheet,
20
+ // not the WebMCP bridge. `'local'` is NOT a valid server-side origin.
21
+ expect(ASK_USER_QUESTION_CLIENT_TOOL.origin).toBe("sdk");
22
+ expect(ASK_USER_QUESTION_CLIENT_TOOL.annotations?.readOnlyHint).toBe(true);
23
+ });
24
+
25
+ it("bounds questions to the renderer cap and options to 2-4", () => {
26
+ const questions = ASK_USER_QUESTION_PARAMETERS_SCHEMA.properties.questions;
27
+ expect(questions.minItems).toBe(1);
28
+ expect(questions.maxItems).toBe(ASK_USER_QUESTION_MAX);
29
+ const options = questions.items.properties.options;
30
+ expect(options.minItems).toBe(2);
31
+ expect(options.maxItems).toBe(4);
32
+ expect(questions.items.required).toEqual(["question", "options"]);
33
+ });
34
+ });
35
+
36
+ describe("builtInClientToolsForDispatch", () => {
37
+ it("returns nothing by default (expose is opt-in)", () => {
38
+ expect(builtInClientToolsForDispatch(undefined)).toEqual([]);
39
+ expect(builtInClientToolsForDispatch({} as AgentWidgetConfig)).toEqual([]);
40
+ expect(
41
+ builtInClientToolsForDispatch({
42
+ features: { askUserQuestion: {} },
43
+ } as AgentWidgetConfig)
44
+ ).toEqual([]);
45
+ });
46
+
47
+ it("returns the tool when expose is true", () => {
48
+ expect(
49
+ builtInClientToolsForDispatch({
50
+ features: { askUserQuestion: { expose: true } },
51
+ } as AgentWidgetConfig)
52
+ ).toEqual([ASK_USER_QUESTION_CLIENT_TOOL]);
53
+ });
54
+
55
+ it("ignores expose when the answer sheet is disabled", () => {
56
+ // Offering the agent a question tool the widget can't render an answer
57
+ // UI for would park the execution on a generic tool bubble forever.
58
+ expect(
59
+ builtInClientToolsForDispatch({
60
+ features: { askUserQuestion: { expose: true, enabled: false } },
61
+ } as AgentWidgetConfig)
62
+ ).toEqual([]);
63
+ });
64
+ });
65
+
66
+ describe("AgentWidgetClient - built-in ask_user_question exposure", () => {
67
+ const captureDispatchBody = () => {
68
+ const captured: { body: string | null } = { body: null };
69
+ global.fetch = vi
70
+ .fn()
71
+ .mockImplementation(async (_url: string, options: { body: string }) => {
72
+ captured.body = options.body;
73
+ const encoder = new TextEncoder();
74
+ const stream = new ReadableStream({
75
+ start(controller) {
76
+ controller.enqueue(encoder.encode('data: {"type":"done"}\n\n'));
77
+ controller.close();
78
+ },
79
+ });
80
+ return new Response(stream, {
81
+ headers: { "Content-Type": "text/event-stream" },
82
+ });
83
+ });
84
+ return captured;
85
+ };
86
+
87
+ const userMessage = () => ({
88
+ id: "u1",
89
+ role: "user" as const,
90
+ content: "hi",
91
+ createdAt: new Date().toISOString(),
92
+ });
93
+
94
+ it("ships ask_user_question on clientTools when expose is on (no WebMCP)", async () => {
95
+ const captured = captureDispatchBody();
96
+ const client = new AgentWidgetClient({
97
+ apiUrl: "http://localhost:8000",
98
+ features: { askUserQuestion: { expose: true } },
99
+ });
100
+ await client.dispatch({ messages: [userMessage()] }, () => undefined);
101
+ const parsed = JSON.parse(captured.body!);
102
+ expect(parsed.clientTools).toEqual([ASK_USER_QUESTION_CLIENT_TOOL]);
103
+ });
104
+
105
+ it("omits clientTools entirely when expose is off and no WebMCP tools exist", async () => {
106
+ const captured = captureDispatchBody();
107
+ const client = new AgentWidgetClient({
108
+ apiUrl: "http://localhost:8000",
109
+ });
110
+ await client.dispatch({ messages: [userMessage()] }, () => undefined);
111
+ const parsed = JSON.parse(captured.body!);
112
+ expect(parsed.clientTools).toBeUndefined();
113
+ });
114
+
115
+ it("leads the WebMCP snapshot when both are present", async () => {
116
+ const captured = captureDispatchBody();
117
+ const client = new AgentWidgetClient({
118
+ apiUrl: "http://localhost:8000",
119
+ features: { askUserQuestion: { expose: true } },
120
+ });
121
+ (
122
+ client as unknown as {
123
+ webMcpBridge: { snapshotForDispatch: () => unknown[] } | null;
124
+ }
125
+ ).webMcpBridge = {
126
+ snapshotForDispatch: () => [
127
+ { name: "search", description: "s", origin: "webmcp" },
128
+ ],
129
+ };
130
+ await client.dispatch({ messages: [userMessage()] }, () => undefined);
131
+ const parsed = JSON.parse(captured.body!);
132
+ expect(parsed.clientTools).toEqual([
133
+ ASK_USER_QUESTION_CLIENT_TOOL,
134
+ { name: "search", description: "s", origin: "webmcp" },
135
+ ]);
136
+ });
137
+
138
+ it("changes the clientTools fingerprint when toggled (diff-only resend)", () => {
139
+ // In client-token mode the widget only resends the full clientTools[]
140
+ // when the fingerprint changes — toggling expose must change it so the
141
+ // server actually learns about (or forgets) the built-in tool.
142
+ const webMcpOnly = [{ name: "search", description: "s" }];
143
+ const withBuiltIn = [ASK_USER_QUESTION_CLIENT_TOOL, ...webMcpOnly];
144
+ expect(computeClientToolsFingerprint(withBuiltIn)).not.toBe(
145
+ computeClientToolsFingerprint(webMcpOnly)
146
+ );
147
+ });
148
+ });
@@ -0,0 +1,138 @@
1
+ /**
2
+ * Built-in `ask_user_question` client tool.
3
+ *
4
+ * The widget can advertise this tool to the agent on every dispatch via
5
+ * `clientTools[]` (set `features.askUserQuestion.expose: true`) — the same
6
+ * wire surface WebMCP page tools ride. The server registers it as a LOCAL
7
+ * tool under its bare name (`origin: 'sdk'` tools are not `webmcp:`-prefixed),
8
+ * so when the model calls it the execution pauses with a `step_await`
9
+ * (`awaitReason: "local_tool_required"`), the widget's answer-pill sheet
10
+ * renders, and `session.resolveAskUserQuestion()` resumes the execution with
11
+ * the structured answers.
12
+ *
13
+ * This replaces the previous integrator burden of hand-declaring the tool in
14
+ * a flow's `runtimeTools` and keeping that schema in sync with the renderer.
15
+ * Flows that already declare `ask_user_question` server-side should leave
16
+ * `expose` off — the model would otherwise see the tool twice.
17
+ */
18
+
19
+ import {
20
+ ASK_USER_QUESTION_MAX,
21
+ ASK_USER_QUESTION_TOOL_NAME,
22
+ } from "./components/ask-user-question-bubble";
23
+ import {
24
+ SUGGEST_REPLIES_CLIENT_TOOL,
25
+ shouldExposeSuggestReplies,
26
+ } from "./suggest-replies-tool";
27
+ import type { AgentWidgetConfig, ClientToolDefinition } from "./types";
28
+
29
+ /**
30
+ * JSON Schema for the tool's parameters. Mirrors {@link AskUserQuestionPayload}
31
+ * — the shape `parseAskUserQuestionPayload` hydrates the answer sheet from —
32
+ * so the schema the model is held to and the schema the renderer expects can
33
+ * never drift.
34
+ */
35
+ export const ASK_USER_QUESTION_PARAMETERS_SCHEMA = {
36
+ type: "object",
37
+ properties: {
38
+ questions: {
39
+ type: "array",
40
+ minItems: 1,
41
+ maxItems: ASK_USER_QUESTION_MAX,
42
+ description: "Questions to ask the user. Prefer a single question.",
43
+ items: {
44
+ type: "object",
45
+ properties: {
46
+ question: {
47
+ type: "string",
48
+ description: "The complete question, ending with a question mark.",
49
+ },
50
+ header: {
51
+ type: "string",
52
+ maxLength: 12,
53
+ description: 'Short topic label, e.g. "Auth method".',
54
+ },
55
+ options: {
56
+ type: "array",
57
+ minItems: 2,
58
+ maxItems: 4,
59
+ description:
60
+ '2-4 distinct choices. Do NOT add an "Other" option — free text is automatic.',
61
+ items: {
62
+ type: "object",
63
+ properties: {
64
+ label: {
65
+ type: "string",
66
+ description: "Concise choice text (1-5 words).",
67
+ },
68
+ description: {
69
+ type: "string",
70
+ description: "What the option means or implies.",
71
+ },
72
+ },
73
+ required: ["label"],
74
+ additionalProperties: false,
75
+ },
76
+ },
77
+ multiSelect: {
78
+ type: "boolean",
79
+ description: "Allow selecting multiple options. Default false.",
80
+ },
81
+ allowFreeText: {
82
+ type: "boolean",
83
+ description: "Show a free-text input. Default true.",
84
+ },
85
+ },
86
+ required: ["question", "options"],
87
+ additionalProperties: false,
88
+ },
89
+ },
90
+ },
91
+ required: ["questions"],
92
+ additionalProperties: false,
93
+ } as const;
94
+
95
+ /**
96
+ * The `ClientToolDefinition` shipped on `dispatch.clientTools[]` when
97
+ * `features.askUserQuestion.expose` is on. Exported so integrators who prefer
98
+ * declaring the tool server-side (a flow's `runtimeTools`) can reuse the same
99
+ * description and schema instead of hand-writing them.
100
+ */
101
+ export const ASK_USER_QUESTION_CLIENT_TOOL: ClientToolDefinition = {
102
+ name: ASK_USER_QUESTION_TOOL_NAME,
103
+ description:
104
+ "Ask the user multiple-choice questions and wait for their answers. Use " +
105
+ "only when blocked on a decision that is the user's to make — a " +
106
+ "preference, a choice between valid approaches, or information you " +
107
+ "cannot infer. Each question offers 2-4 options plus an automatic " +
108
+ "free-text input. The result maps each question to its answer (an array " +
109
+ "when multiSelect); a question absent from the result was skipped.",
110
+ parametersSchema: ASK_USER_QUESTION_PARAMETERS_SCHEMA,
111
+ origin: "sdk",
112
+ annotations: { readOnlyHint: true },
113
+ };
114
+
115
+ /**
116
+ * Built-in client tools to append to a dispatch's `clientTools[]` for the
117
+ * given widget config: `ask_user_question` and `suggest_replies`; future
118
+ * built-in local tools join here.
119
+ *
120
+ * Each tool is gated on BOTH of its feature flags: `expose` opts the tool
121
+ * into the agent's catalog, and `enabled !== false` guarantees the widget can
122
+ * actually render UI (and, for fire-and-forget tools, auto-resume) for it —
123
+ * exposing a tool with its feature disabled would park the execution on a
124
+ * generic tool bubble with no way to advance.
125
+ */
126
+ export const builtInClientToolsForDispatch = (
127
+ config: AgentWidgetConfig | undefined,
128
+ ): ClientToolDefinition[] => {
129
+ const tools: ClientToolDefinition[] = [];
130
+ const ask = config?.features?.askUserQuestion;
131
+ if (ask?.expose === true && ask.enabled !== false) {
132
+ tools.push(ASK_USER_QUESTION_CLIENT_TOOL);
133
+ }
134
+ if (shouldExposeSuggestReplies(config)) {
135
+ tools.push(SUGGEST_REPLIES_CLIENT_TOOL);
136
+ }
137
+ return tools;
138
+ };
package/src/client.ts CHANGED
@@ -24,6 +24,7 @@ import {
24
24
  WebMcpConfirmHandler
25
25
  } from "./types";
26
26
  import { WebMcpBridge, computeClientToolsFingerprint, isWebMcpToolName } from "./webmcp-bridge";
27
+ import { builtInClientToolsForDispatch } from "./ask-user-question-tool";
27
28
  import {
28
29
  extractTextFromJson,
29
30
  createPlainTextParser,
@@ -201,6 +202,28 @@ export class AgentWidgetClient {
201
202
  config.webmcp?.enabled === true ? new WebMcpBridge(config.webmcp) : null;
202
203
  }
203
204
 
205
+ /**
206
+ * Refresh config in place WITHOUT tearing down the live connection or the
207
+ * WebMCP bridge. `AgentWidgetSession.updateConfig` calls this when only
208
+ * connection-irrelevant fields changed (theme, copy, layout, suggestions, …),
209
+ * so a UI update that lands mid-turn — e.g. a `webmcp:*` tool restyling the
210
+ * widget while the agent's turn is still streaming — doesn't abandon the
211
+ * in-flight stream/resume. Connection or request-shaping changes (apiUrl,
212
+ * clientToken, webmcp, headers, parser, …) take the full client rebuild path
213
+ * in the session instead, which is the only place the bridge is recreated.
214
+ *
215
+ * Only the live-read `config` is refreshed (e.g. `iterationDisplay`); the
216
+ * constructor-derived request-shaping fields (apiUrl, headers, parser,
217
+ * contextProviders, middleware, …) are left untouched because the session
218
+ * routes any change to those down the full-rebuild path instead — so they are
219
+ * guaranteed unchanged here. The `webMcpBridge` instance and its
220
+ * installed-polyfill memo are deliberately preserved, which keeps any
221
+ * in-flight resolve alive.
222
+ */
223
+ public updateConfig(next: AgentWidgetConfig): void {
224
+ this.config = next;
225
+ }
226
+
204
227
  /**
205
228
  * Set callback for capturing raw SSE events
206
229
  */
@@ -1035,11 +1058,18 @@ export class AgentWidgetClient {
1035
1058
  }
1036
1059
  };
1037
1060
 
1038
- // WebMCP: snapshot the page tool registry per turn and ship as
1039
- // `clientTools[]`. The server merges these under the `webmcp:` namespace.
1040
- const webMcpSnapshot = await this.webMcpBridge?.snapshotForDispatch();
1041
- if (webMcpSnapshot && webMcpSnapshot.length > 0) {
1042
- payload.clientTools = webMcpSnapshot;
1061
+ // Client tools: built-in widget tools (ask_user_question, when exposed)
1062
+ // plus the per-turn WebMCP page-registry snapshot. Name collisions are
1063
+ // impossible WebMCP entries are `webmcp:`-prefixed server-side while
1064
+ // `sdk`-origin built-ins keep bare names. Both kinds ride the same
1065
+ // diff-only fingerprint path in client-token mode. Kept to a single await
1066
+ // so dispatch microtask timing is unchanged.
1067
+ const clientTools = [
1068
+ ...builtInClientToolsForDispatch(this.config),
1069
+ ...((await this.webMcpBridge?.snapshotForDispatch()) ?? []),
1070
+ ];
1071
+ if (clientTools.length > 0) {
1072
+ payload.clientTools = clientTools;
1043
1073
  }
1044
1074
 
1045
1075
  // Add context from providers
@@ -1096,10 +1126,14 @@ export class AgentWidgetClient {
1096
1126
  ...(this.config.flowId && { flowId: this.config.flowId })
1097
1127
  };
1098
1128
 
1099
- // WebMCP: same per-turn snapshot as buildAgentPayload (flow-dispatch path).
1100
- const webMcpSnapshot = await this.webMcpBridge?.snapshotForDispatch();
1101
- if (webMcpSnapshot && webMcpSnapshot.length > 0) {
1102
- payload.clientTools = webMcpSnapshot;
1129
+ // Client tools: same built-in + WebMCP merge as buildAgentPayload
1130
+ // (flow-dispatch path).
1131
+ const clientTools = [
1132
+ ...builtInClientToolsForDispatch(this.config),
1133
+ ...((await this.webMcpBridge?.snapshotForDispatch()) ?? []),
1134
+ ];
1135
+ if (clientTools.length > 0) {
1136
+ payload.clientTools = clientTools;
1103
1137
  }
1104
1138
 
1105
1139
  if (this.contextProviders.length) {
@@ -9,6 +9,7 @@ import {
9
9
  isAskUserQuestionMessage,
10
10
  removeAskUserQuestionSheet,
11
11
  } from "./ask-user-question-bubble";
12
+ import { isSuggestRepliesMessage } from "../suggest-replies-tool";
12
13
 
13
14
  export const renderMessages = (
14
15
  container: HTMLElement,
@@ -40,6 +41,15 @@ export const renderMessages = (
40
41
  ensureAskUserQuestionSheet(message, config, composerOverlay ?? null);
41
42
  }
42
43
  return;
44
+ } else if (
45
+ isSuggestRepliesMessage(message) &&
46
+ config?.features?.suggestReplies?.enabled !== false
47
+ ) {
48
+ // No transcript bubble — the chips above the composer are the only UI.
49
+ // When the feature is disabled the message falls through to the generic
50
+ // tool bubble below (and is never auto-resumed), making the parked
51
+ // execution visible instead of silently swallowed.
52
+ return;
43
53
  } else if (message.variant === "tool" && message.toolCall) {
44
54
  if (!showToolCalls) return;
45
55
  bubble = createToolBubble(message, config);
@@ -9,29 +9,50 @@ export interface SuggestionButtons {
9
9
  session: AgentWidgetSession,
10
10
  textarea: HTMLTextAreaElement,
11
11
  messages?: AgentWidgetMessage[],
12
- config?: AgentWidgetSuggestionChipsConfig
12
+ config?: AgentWidgetSuggestionChipsConfig,
13
+ opts?: SuggestionRenderOptions
13
14
  ) => void;
14
15
  }
15
16
 
17
+ export interface SuggestionRenderOptions {
18
+ /**
19
+ * Chips pushed by the agent's `suggest_replies` tool rather than the
20
+ * static `suggestionChips` config. Skips the before-first-user-message
21
+ * gate (the caller already applied the latest-turn visibility rule) and
22
+ * dispatches `persona:suggestReplies:*` DOM events.
23
+ */
24
+ agentPushed?: boolean;
25
+ }
26
+
16
27
  export const createSuggestions = (container: HTMLElement): SuggestionButtons => {
17
28
  const suggestionButtons: HTMLButtonElement[] = [];
29
+ // render() runs on every message change; only announce agent-pushed chips
30
+ // when the visible set actually changes, not on each re-render pass.
31
+ let lastAgentShownKey: string | null = null;
18
32
 
19
33
  const render = (
20
34
  chips: string[] | undefined,
21
35
  session: AgentWidgetSession,
22
36
  textarea: HTMLTextAreaElement,
23
37
  messages?: AgentWidgetMessage[],
24
- chipsConfig?: AgentWidgetSuggestionChipsConfig
38
+ chipsConfig?: AgentWidgetSuggestionChipsConfig,
39
+ opts?: SuggestionRenderOptions
25
40
  ) => {
26
41
  container.innerHTML = "";
27
42
  suggestionButtons.length = 0;
43
+ const agentPushed = opts?.agentPushed === true;
44
+ if (!agentPushed) lastAgentShownKey = null;
28
45
  if (!chips || !chips.length) return;
29
46
 
30
- // Hide suggestions after the first user message is sent
47
+ // Hide config suggestions after the first user message is sent.
48
+ // Agent-pushed chips skip this gate — their visibility is the caller's
49
+ // latest-turn rule (last suggest_replies call with no user message after).
31
50
  // Use provided messages or get from session
32
- const messagesToCheck = messages ?? (session ? session.getMessages() : []);
33
- const hasUserMessage = messagesToCheck.some((msg) => msg.role === "user");
34
- if (hasUserMessage) return;
51
+ if (!agentPushed) {
52
+ const messagesToCheck = messages ?? (session ? session.getMessages() : []);
53
+ const hasUserMessage = messagesToCheck.some((msg) => msg.role === "user");
54
+ if (hasUserMessage) return;
55
+ }
35
56
 
36
57
  const fragment = document.createDocumentFragment();
37
58
  const streaming = session ? session.isStreaming() : false;
@@ -79,12 +100,34 @@ export const createSuggestions = (container: HTMLElement): SuggestionButtons =>
79
100
  btn.addEventListener("click", () => {
80
101
  if (!session || session.isStreaming()) return;
81
102
  textarea.value = "";
103
+ if (agentPushed) {
104
+ container.dispatchEvent(
105
+ new CustomEvent("persona:suggestReplies:selected", {
106
+ detail: { suggestion: chip },
107
+ bubbles: true,
108
+ composed: true,
109
+ })
110
+ );
111
+ }
82
112
  session.sendMessage(chip);
83
113
  });
84
114
  fragment.appendChild(btn);
85
115
  suggestionButtons.push(btn);
86
116
  });
87
117
  container.appendChild(fragment);
118
+ if (agentPushed) {
119
+ const shownKey = JSON.stringify(chips);
120
+ if (shownKey !== lastAgentShownKey) {
121
+ lastAgentShownKey = shownKey;
122
+ container.dispatchEvent(
123
+ new CustomEvent("persona:suggestReplies:shown", {
124
+ detail: { suggestions: [...chips] },
125
+ bubbles: true,
126
+ composed: true,
127
+ })
128
+ );
129
+ }
130
+ }
88
131
  };
89
132
 
90
133
  return {
package/src/index-core.ts CHANGED
@@ -134,6 +134,21 @@ export {
134
134
  parseAskUserQuestionPayload
135
135
  } from "./components/ask-user-question-bubble";
136
136
 
137
+ export {
138
+ ASK_USER_QUESTION_CLIENT_TOOL,
139
+ ASK_USER_QUESTION_PARAMETERS_SCHEMA,
140
+ builtInClientToolsForDispatch
141
+ } from "./ask-user-question-tool";
142
+
143
+ export {
144
+ SUGGEST_REPLIES_TOOL_NAME,
145
+ SUGGEST_REPLIES_CLIENT_TOOL,
146
+ SUGGEST_REPLIES_PARAMETERS_SCHEMA,
147
+ isSuggestRepliesMessage,
148
+ parseSuggestRepliesPayload,
149
+ latestAgentSuggestions
150
+ } from "./suggest-replies-tool";
151
+
137
152
  export { initAgentWidgetFn as initAgentWidget };
138
153
  export {
139
154
  createWidgetHostLayout,