@rubytech/create-realagent 1.0.442 → 1.0.444
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/maxy/public/assets/{ChatInput-CU1KuGpJ.css → ChatInput-rWA47V05.css} +1 -1
- package/payload/maxy/public/assets/{admin-D-BSLXqy.js → admin-CisU37jg.js} +4 -4
- package/payload/maxy/public/assets/{public-vjAu4hiw.js → public-ClFTMlS3.js} +1 -1
- package/payload/maxy/public/index.html +3 -3
- package/payload/maxy/public/public.html +3 -3
- package/payload/maxy/server.js +16 -16
- package/payload/platform/knowledge/maxy.md +22 -6
- package/payload/platform/plugins/admin/skills/plugin-management/skill.md +1 -1
- package/payload/platform/plugins/admin/skills/public-agent-manager/skill.md +1 -1
- package/payload/platform/plugins/business-assistant/PLUGIN.md +1 -1
- package/payload/platform/plugins/business-assistant/references/crm.md +2 -2
- package/payload/platform/plugins/contacts/PLUGIN.md +1 -1
- package/payload/platform/plugins/docs/PLUGIN.md +1 -1
- package/payload/platform/plugins/email/PLUGIN.md +1 -1
- package/payload/platform/plugins/projects/PLUGIN.md +1 -1
- package/payload/platform/plugins/sales/PLUGIN.md +1 -1
- package/payload/platform/plugins/scheduling/PLUGIN.md +1 -1
- package/payload/platform/plugins/tasks/PLUGIN.md +1 -1
- package/payload/platform/plugins/waitlist/PLUGIN.md +1 -1
- package/payload/platform/plugins/whatsapp/PLUGIN.md +1 -0
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js +4 -1
- package/payload/platform/plugins/whatsapp/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +11 -14
- package/payload/platform/plugins/workflows/PLUGIN.md +1 -1
- /package/payload/maxy/public/assets/{ChatInput-CeiIhy_D.js → ChatInput--kmj83_w.js} +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{A as e,C as t,E as n,O as r,S as i,_ as a,a as o,b as s,d as c,f as l,g as u,h as d,i as f,m as p,n as m,p as h,r as g,s as _,t as v,u as y,v as b,w as x,x as S,y as C}from"./ChatInput-CeiIhy_D.js";var w=n(),T=e(r(),1),E=`image/jpeg,image/png,image/gif,image/webp,application/pdf,text/plain,text/markdown,text/csv`,D=new Set(E.split(`,`)),O=20*1024*1024,k=typeof window<`u`&&/^(localhost|127\.0\.0\.1|[a-z0-9-]+\.local)(:|$)/.test(window.location.hostname);function A(){let e=crypto.getRandomValues(new Uint8Array(16));e[6]=e[6]&15|64,e[8]=e[8]&63|128;let t=[...e].map(e=>e.toString(16).padStart(2,`0`)).join(``);return`${t.slice(0,8)}-${t.slice(8,12)}-${t.slice(12,16)}-${t.slice(16,20)}-${t.slice(20)}`}function j(){let e=window.location.pathname.match(/^\/([a-z][a-z0-9-]{2,49})$/);return e?e[1]:void 0}function M(e){return[{key:`length`,label:`At least 8 characters`,met:e.length>=8},{key:`number`,label:`Contains a number`,met:/\d/.test(e)},{key:`special`,label:`Contains a special character`,met:/[^A-Za-z0-9]/.test(e)},{key:`whitespace`,label:`No leading or trailing spaces`,met:e.length>0&&e===e.trim()}]}function N(e,t){if(t===`phone`)return e.length>4?e.slice(0,e.length-4).replace(/\d/g,`•`)+` `+e.slice(-4):e;let n=e.indexOf(`@`);return n<=2?e:e.slice(0,2)+`•••`+e.slice(n)}function P(e){let[t,n]=(0,T.useState)(null),[r,i]=(0,T.useState)(null),[a,o]=(0,T.useState)(`loading`),[s,c]=(0,T.useState)(``),[l,u]=(0,T.useState)(null),d=(0,T.useMemo)(()=>j(),[]),f=(0,T.useRef)(d||``),p=(0,T.useRef)(null),m=(0,T.useRef)(null),h=(0,T.useRef)(!1),g=(0,T.useRef)(null),[_,v]=(0,T.useState)(`sign-in`),[y,b]=(0,T.useState)(null),x=(0,T.useRef)(null),S=(0,T.useRef)(null);(0,T.useEffect)(()=>{try{let e=sessionStorage.getItem(`maxy_session`);e&&(S.current=e)}catch{}},[]);let C=(0,T.useCallback)(t=>{p.current=t,n(t);try{sessionStorage.setItem(`maxy_session`,t)}catch{}o(`chat`),e(t)},[e]),w=(0,T.useCallback)(()=>{p.current=null;try{sessionStorage.removeItem(`maxy_session`)}catch{}n(null),o(`auth-required`),v(`sign-in`)},[]),E=(0,T.useCallback)(async e=>{try{let t=f.current,n=await fetch(`/api/access/verify-token`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({token:e,agentSlug:t})}),r=await n.json();n.ok?(x.current=r.session_key,b(r.grant),v(`set-password`),window.history.replaceState({},``,window.location.pathname)):v(`link-expired`)}catch{v(`sign-in`)}},[]);return{sessionId:t,sessionKeyRef:p,sessionError:r,pageState:a,setPageState:o,agentDisplayName:s,agentSlug:d,branding:l,resolvedSlugRef:f,ensureSession:(0,T.useCallback)(async()=>{if(p.current)return p.current;if(m.current)return m.current;let e=(async()=>{try{let e=S.current,t=await fetch(`/api/session`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({session_id:A(),...d?{agent:d}:{},...e?{session_key:e}:{}})});if(!t.ok){let e=await t.json().catch(()=>({error:``}));return k&&console.error(`[session] POST /api/session failed: ${t.status} ${t.statusText}`,e),t.status===404?i(e.error||`Agent not found`):t.status===503&&i(e.error||`Service unavailable`),null}let r=await t.json();if(r.auth_required){r.agent_id&&(f.current=r.agent_id),c(r.displayName||``),r.branding&&u(r.branding),o(`auth-required`);let e=new URLSearchParams(window.location.search).get(`token`);return e?E(e):v(`sign-in`),null}p.current=r.session_key,n(r.session_key),r.branding&&u(r.branding),o(`chat`),r.resumed&&r.messages&&(h.current=!0,g.current=r.messages);try{sessionStorage.setItem(`maxy_session`,r.session_key)}catch{}return r.session_key}catch(e){return k&&console.error(`[session] fetch /api/session threw:`,e),i(`Unable to connect. Please check your connection and try again.`),null}finally{m.current=null}})();return m.current=e,e},[d,E]),enterChat:C,enterGate:w,resumedRef:h,resumeMessagesRef:g,gateState:_,setGateState:v,grantInfo:y,setGrantInfo:b,gateSessionKeyRef:x}}function F({sessionKeyRef:e,ensureSession:t,sessionError:n,pageState:r,inputRef:i}){let[a,o]=(0,T.useState)([]),[s,c]=(0,T.useState)(!1),l=(0,T.useRef)(!1),d=e=>{l.current=e,c(e)},f=(0,T.useCallback)(async a=>{if(!l.current){d(!0),o([{role:`maxy`,content:``,timestamp:Date.now()}]);try{let n=await fetch(`/api/chat`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({greeting:!0,session_key:a})});if(n.status===401){e.current=null;try{sessionStorage.removeItem(`maxy_session`)}catch{}k&&console.warn(`[greeting] stale session, retrying with fresh session`);let r=await t();if(!r)return;n=await fetch(`/api/chat`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({greeting:!0,session_key:r})})}if(!n.ok){let e=await n.json().catch(()=>({}));throw Error(e.error||`Something went wrong`)}let r=``,i=``,s=()=>{i&&(r+=i,i=``,o(e=>{let t=[...e];return t[0]={...t[0],content:r},t}))},c=setInterval(s,60);try{for await(let e of u(n)){if(e.error)throw Object.assign(Error(e.error),{fromSSE:!0});e.text&&(i+=e.text),e.type===`component`&&(s(),o(t=>{let n=[...t],r={...n[0]},i=[...r.components||[]];return i.push({name:e.name,data:e.data,submitted:!1}),n[0]={...r,components:i},n}))}}finally{clearInterval(c),s()}}catch(e){if(k&&console.error(`[chat] greeting failed:`,e),r===`auth-required`)return;let t=e instanceof Error?e.message:String(e);o([{role:`maxy`,content:e instanceof Error&&e.fromSSE?t:n??`I'm having trouble connecting right now. Try refreshing the page.`,timestamp:Date.now()}])}finally{d(!1),r===`chat`&&i.current?.focus()}}},[e,t,n,r,i]),p=(0,T.useCallback)(async(a,s)=>{let c=s?.hidden??!1,f=s?.files??[];if(!a&&f.length===0||l.current)return;d(!0);let p=f.map(e=>({filename:e.name,mimeType:e.type})),m={role:`visitor`,content:a,attachments:p.length>0?p:void 0,timestamp:Date.now(),hidden:c},h;o(e=>{let t=[...e,m,{role:`maxy`,content:``,timestamp:Date.now()}];return h=t.length-1,t});try{let n=await t();if(!n)throw Error(`session`);let r=e=>{if(f.length>0){let t=new FormData;t.append(`message`,a),t.append(`session_key`,e);for(let e of f)t.append(`attachments`,e);return{body:t,headers:{}}}return{body:JSON.stringify({message:a,session_key:e}),headers:{"Content-Type":`application/json`}}},{body:i,headers:s}=r(n),c=await fetch(`/api/chat`,{method:`POST`,headers:s,body:i});if(c.status===401){e.current=null;try{sessionStorage.removeItem(`maxy_session`)}catch{}k&&console.warn(`[session-expired] stale key cleared, retrying with fresh session`);let n=await t();if(!n)throw Error(`session`);({body:i,headers:s}=r(n)),c=await fetch(`/api/chat`,{method:`POST`,headers:s,body:i})}if(!c.ok){let e=await c.json().catch(()=>({}));throw Error(e.error||`Something went wrong`)}let l=``,d=``,p=()=>{d&&(l+=d,d=``,o(e=>{let t=[...e];return t[h]={...t[h],content:l},t}))},m=setInterval(p,60);try{for await(let e of u(c)){if(e.error)throw Object.assign(Error(e.error),{fromSSE:!0});e.text&&(d+=e.text),e.type===`component`&&(p(),o(t=>{let n=[...t],r={...n[h]},i=[...r.components||[]];return i.push({name:e.name,data:e.data,submitted:!1}),n[h]={...r,components:i},n}))}}finally{clearInterval(m),p()}}catch(e){if(k&&console.error(`[chat] sendMessage failed:`,e),r===`auth-required`)return;let t=e instanceof Error?e.message:String(e),i=e instanceof Error&&e.fromSSE;o(e=>{let r=[...e];return r[h]={...r[h],content:i?t:t===`session`?n??`I'm having trouble connecting right now. Try refreshing the page.`:`Sorry, I hit a snag. Try again in a moment.`},r})}finally{d(!1),r===`chat`&&i.current?.focus()}},[e,t,n,r,i]);return{messages:a,setMessages:o,isStreaming:s,sendMessage:p,sendGreeting:f,handleComponentSubmit:(0,T.useCallback)((e,t,n)=>{o(n=>{let r=[...n],i={...r[e]},a=[...i.components||[]];return a[t]={...a[t],submitted:!0},r[e]={...i,components:a},r}),p(n,{hidden:!0})},[p])}}var I=l();function L({value:e,onChange:t,onComplete:n,disabled:r}){let i=(0,T.useRef)([]);function a(n,r){r.key===`Backspace`?(r.preventDefault(),e[n]?t(e.slice(0,n)+e.slice(n+1)):n>0&&(t(e.slice(0,n-1)+e.slice(n)),i.current[n-1]?.focus())):r.key===`ArrowLeft`&&n>0?i.current[n-1]?.focus():r.key===`ArrowRight`&&n<5&&i.current[n+1]?.focus()}function o(r,a){let o=a.nativeEvent.data;if(!o||!/^\d$/.test(o))return;let s=e.split(``);for(s[r]=o;s.length<r;)s.push(``);let c=s.join(``).replace(/\D/g,``).slice(0,6);t(c),c.length===6?n(c):r<5&&i.current[r+1]?.focus()}function s(e){e.preventDefault();let r=e.clipboardData.getData(`text`).replace(/\D/g,``).slice(0,6);r&&(t(r),r.length===6?n(r):i.current[r.length]?.focus())}return(0,I.jsx)(`div`,{className:`gate-otp-field`,children:Array.from({length:6}).map((t,n)=>(0,I.jsx)(`input`,{ref:e=>{i.current[n]=e},type:`text`,inputMode:`numeric`,className:`pin-box${e[n]?` pin-box-filled`:``}`,value:e[n]||``,onKeyDown:e=>a(n,e),onInput:e=>o(n,e),onPaste:s,onFocus:e=>e.target.select(),autoFocus:n===0,autoComplete:`off`,maxLength:1,disabled:r,"aria-label":`Code digit ${n+1}`},n))})}function R({gateState:e,setGateState:t,grantInfo:n,setGrantInfo:r,gateSessionKeyRef:i,resolvedSlugRef:a,onAuthenticated:o}){let[s,l]=(0,T.useState)(``),[u,d]=(0,T.useState)(``),[f,p]=(0,T.useState)(``),[m,h]=(0,T.useState)(!1),[g,_]=(0,T.useState)(``),[v,b]=(0,T.useState)(``),[x,S]=(0,T.useState)(!1),[C,w]=(0,T.useState)(null),[E,D]=(0,T.useState)(null),[O,k]=(0,T.useState)(!1);async function A(e){if(e.preventDefault(),!O){w(null),k(!0);try{let e=a.current,n=await fetch(`/api/access/login`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({contact:s,password:u,agentSlug:e})}),i=await n.json();n.ok?o(i.session_key):n.status===401?i.error?.includes(`setup not complete`)||i.error?.includes(`invitation link`)?(t(`private-agent`),w(null)):w(i.error||`Invalid credentials`):n.status===403?(t(`access-expired`),i.expiresAt&&r(e=>e?{...e,expiresAt:i.expiresAt}:{displayName:null,contactValue:s,contactMethod:`email`,expiresAt:i.expiresAt,status:`expired`})):n.status===429?w(i.error||`Too many attempts. Please try again later.`):w(i.error||`Something went wrong`)}catch{w(`Unable to connect. Please try again.`)}finally{k(!1)}}}async function j(e){if(!O){w(null),k(!0);try{let n=a.current,o=await fetch(`/api/access/verify-otp`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({phone:v,code:e,agentSlug:n})}),s=await o.json();o.ok?(i.current=s.session_key,r(s.grant),t(`set-password`)):o.status===429?t(`otp-failed`):(w(s.error||`Invalid code`),_(``))}catch{w(`Unable to connect. Please try again.`)}finally{k(!1)}}}async function P(e){if(e.preventDefault(),!O){if(w(null),u!==f){w(`Passwords do not match`);return}if(!M(u).every(e=>e.met)){w(`Password does not meet requirements`);return}k(!0);try{let e=i.current;if(!e){w(`Session expired. Please use your invitation link again.`);return}let t=await fetch(`/api/access/create-credentials`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({session_key:e,password:u})}),n=await t.json();t.ok?o(n.session_key):t.status===400&&n.requirements?w(n.requirements.filter(e=>!e.met).map(e=>e.label).join(`, `)):w(n.error||`Something went wrong`)}catch{w(`Unable to connect. Please try again.`)}finally{k(!1)}}}async function F(e){if(e.preventDefault(),!(O||!s)){w(null),k(!0);try{let e=a.current,t=await fetch(`/api/access/forgot-password`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({contact:s,agentSlug:e})});t.status===429?w((await t.json()).error||`Too many attempts. Please try again later.`):(D(`If an account exists, a reset link has been sent.`),setTimeout(()=>{S(!1),D(null)},4e3))}catch{w(`Unable to connect. Please try again.`)}finally{k(!1)}}}switch(e){case`set-password`:{let e=M(u),t=e.every(e=>e.met),r=u===f,i=t&&r&&f.length>0;return(0,I.jsx)(`div`,{className:`gate-wrap`,children:(0,I.jsxs)(`div`,{className:`gate-card`,children:[n?.expiresAt&&(0,I.jsxs)(`div`,{className:`gate-expiry-badge`,children:[`Access until `,new Date(n.expiresAt).toLocaleDateString(`en-GB`,{day:`numeric`,month:`short`,year:`numeric`})]}),(0,I.jsxs)(`h2`,{className:`gate-title`,children:[`Welcome, `,n?.displayName||`there`]}),(0,I.jsx)(`p`,{className:`gate-subtitle`,children:`Create a password to access this agent in the future.`}),(0,I.jsxs)(`form`,{className:`gate-form`,onSubmit:P,children:[(0,I.jsxs)(`div`,{className:`gate-field`,children:[(0,I.jsx)(`label`,{htmlFor:`gate-display-name`,children:`Display name`}),(0,I.jsx)(`input`,{id:`gate-display-name`,type:`text`,value:n?.displayName||``,disabled:!0})]}),(0,I.jsxs)(`div`,{className:`gate-field gate-pw-row`,children:[(0,I.jsx)(`label`,{htmlFor:`gate-pw`,children:`Password`}),(0,I.jsx)(`div`,{className:`gate-pw-toggle`,children:(0,I.jsx)(c,{checked:m,onChange:h,label:`Show`})}),(0,I.jsx)(`input`,{id:`gate-pw`,type:m?`text`:`password`,value:u,onChange:e=>d(e.target.value),placeholder:`Your password`,autoFocus:!0,autoComplete:`new-password`}),u.length>0&&(0,I.jsx)(`div`,{className:`gate-strength`,children:e.map(e=>(0,I.jsxs)(`span`,{className:`gate-strength-item${e.met?` met`:``}`,children:[e.met?`✓`:`○`,` `,e.label]},e.key))})]}),(0,I.jsxs)(`div`,{className:`gate-field`,children:[(0,I.jsx)(`label`,{htmlFor:`gate-pw-confirm`,children:`Confirm password`}),(0,I.jsx)(`input`,{id:`gate-pw-confirm`,type:m?`text`:`password`,value:f,onChange:e=>p(e.target.value),placeholder:`Confirm your password`,autoComplete:`new-password`}),f.length>0&&!r&&(0,I.jsx)(`div`,{className:`gate-error`,children:`Passwords do not match`})]}),C&&(0,I.jsx)(`div`,{className:`gate-error`,children:C}),(0,I.jsx)(`div`,{className:`gate-submit`,children:(0,I.jsx)(y,{variant:`primary`,type:`submit`,fullWidth:!0,disabled:!i||O,loading:O,children:`Create account & enter chat`})})]})]})})}case`enter-otp`:return(0,I.jsx)(`div`,{className:`gate-wrap`,children:(0,I.jsxs)(`div`,{className:`gate-card`,children:[(0,I.jsx)(`h2`,{className:`gate-title`,children:`Enter your code`}),(0,I.jsxs)(`p`,{className:`gate-subtitle`,children:[`Enter the 6-digit code sent to `,N(v,`phone`)]}),(0,I.jsx)(L,{value:g,onChange:_,onComplete:j,disabled:O}),C&&(0,I.jsx)(`div`,{className:`gate-error`,children:C}),O&&(0,I.jsxs)(`div`,{className:`gate-loading`,children:[(0,I.jsx)(`span`,{className:`spinner`}),`Verifying...`]}),(0,I.jsx)(`div`,{className:`gate-resend`,children:(0,I.jsx)(`button`,{type:`button`,className:`gate-link`,onClick:()=>{t(`sign-in`),_(``),w(null)},children:`Use a different number`})})]})});case`sign-in`:case`private-agent`:return(0,I.jsx)(`div`,{className:`gate-wrap`,children:(0,I.jsxs)(`div`,{className:`gate-card`,children:[(0,I.jsx)(`h2`,{className:`gate-title`,children:e===`private-agent`?`Private Agent`:`Welcome back`}),(0,I.jsx)(`p`,{className:`gate-subtitle`,children:e===`private-agent`?`This agent is invitation-only. If you have an account, sign in below.`:`Sign in to continue your conversation.`}),x?(0,I.jsxs)(`form`,{className:`gate-form`,onSubmit:F,children:[(0,I.jsxs)(`div`,{className:`gate-field`,children:[(0,I.jsx)(`label`,{htmlFor:`gate-forgot-contact`,children:`Email or phone`}),(0,I.jsx)(`input`,{id:`gate-forgot-contact`,type:`text`,value:s,onChange:e=>l(e.target.value),placeholder:`you@example.com`,autoFocus:!0,autoComplete:`email`})]}),C&&(0,I.jsx)(`div`,{className:`gate-error`,children:C}),E&&(0,I.jsx)(`div`,{className:`gate-success`,children:E}),(0,I.jsx)(`div`,{className:`gate-submit`,children:(0,I.jsx)(y,{variant:`primary`,type:`submit`,fullWidth:!0,disabled:!s.trim()||O,loading:O,children:`Send reset link`})}),(0,I.jsx)(`div`,{className:`gate-actions`,children:(0,I.jsx)(`button`,{type:`button`,className:`gate-link`,onClick:()=>{S(!1),w(null),D(null)},children:`Back to sign in`})})]}):(0,I.jsxs)(`form`,{className:`gate-form`,onSubmit:A,children:[(0,I.jsxs)(`div`,{className:`gate-field`,children:[(0,I.jsx)(`label`,{htmlFor:`gate-contact`,children:`Email or phone`}),(0,I.jsx)(`input`,{id:`gate-contact`,type:`text`,value:s,onChange:e=>l(e.target.value),placeholder:`you@example.com`,autoFocus:!0,autoComplete:`email`})]}),(0,I.jsxs)(`div`,{className:`gate-field gate-pw-row`,children:[(0,I.jsx)(`label`,{htmlFor:`gate-login-pw`,children:`Password`}),(0,I.jsx)(`div`,{className:`gate-pw-toggle`,children:(0,I.jsx)(c,{checked:m,onChange:h,label:`Show`})}),(0,I.jsx)(`input`,{id:`gate-login-pw`,type:m?`text`:`password`,value:u,onChange:e=>d(e.target.value),placeholder:`Your password`,autoComplete:`current-password`})]}),C&&(0,I.jsx)(`div`,{className:`gate-error`,children:C}),(0,I.jsx)(`div`,{className:`gate-submit`,children:(0,I.jsx)(y,{variant:`primary`,type:`submit`,fullWidth:!0,disabled:!s.trim()||!u||O,loading:O,children:`Sign in`})}),(0,I.jsx)(`div`,{className:`gate-actions`,children:(0,I.jsx)(`button`,{type:`button`,className:`gate-link`,onClick:()=>{S(!0),w(null)},children:`Forgot password?`})})]}),e===`private-agent`&&(0,I.jsx)(`p`,{className:`gate-hint`,children:`No account? You need an invitation from the agent owner.`})]})});case`access-expired`:return(0,I.jsx)(`div`,{className:`gate-wrap`,children:(0,I.jsxs)(`div`,{className:`gate-card`,children:[(0,I.jsx)(`div`,{className:`gate-icon`,children:`⏰`}),(0,I.jsx)(`h2`,{className:`gate-title`,children:`Access expired`}),(0,I.jsxs)(`p`,{className:`gate-subtitle`,children:[n?.expiresAt?`Your access expired on ${new Date(n.expiresAt).toLocaleDateString(`en-GB`,{day:`numeric`,month:`long`,year:`numeric`})}.`:`Your access to this agent has expired.`,` `,`Contact the agent owner to renew.`]}),(0,I.jsx)(`div`,{className:`gate-actions`,children:(0,I.jsx)(y,{variant:`secondary`,onClick:()=>{t(`sign-in`),w(null),d(``)},children:`Sign in with a different account`})})]})});case`link-expired`:return(0,I.jsx)(`div`,{className:`gate-wrap`,children:(0,I.jsxs)(`div`,{className:`gate-card`,children:[(0,I.jsx)(`div`,{className:`gate-icon`,children:`⚠️`}),(0,I.jsx)(`h2`,{className:`gate-title`,children:`Link expired`}),(0,I.jsx)(`p`,{className:`gate-subtitle`,children:`This invitation link has expired or has already been used. If you already set your password, sign in below. Otherwise, ask the agent owner to send a new invitation.`}),(0,I.jsx)(`div`,{className:`gate-actions`,children:(0,I.jsx)(y,{variant:`primary`,onClick:()=>{t(`sign-in`),w(null)},children:`Go to sign in`})})]})});case`otp-failed`:return(0,I.jsx)(`div`,{className:`gate-wrap`,children:(0,I.jsxs)(`div`,{className:`gate-card`,children:[(0,I.jsx)(`div`,{className:`gate-icon`,children:`⛔`}),(0,I.jsx)(`h2`,{className:`gate-title`,children:`Too many attempts`}),(0,I.jsx)(`p`,{className:`gate-subtitle`,children:`You've entered the wrong code too many times. Ask the agent owner to send a new code.`}),(0,I.jsx)(`div`,{className:`gate-actions`,children:(0,I.jsx)(y,{variant:`secondary`,onClick:()=>{t(`sign-in`),w(null),_(``)},children:`Back to sign in`})})]})})}}var z={"single-select":o,"multi-select":f};function B({name:e,data:t,onSubmit:n,submitted:r}){let i=z[e];return i?(0,I.jsx)(i,{data:t,onSubmit:n,submitted:r}):null}function V({messages:e,isStreaming:t,sessionError:n,selectionMode:r,selectedItems:a,toggleSelectItem:o,onComponentSubmit:s,remainingSuggestions:l,onSuggestionClick:u,isAtBottom:d,setIsAtBottom:f}){let[p,m]=(0,T.useState)(new Set),[h,v]=(0,T.useState)(new Set),b=(0,T.useRef)(null),C=(0,T.useRef)(null),w=(0,T.useRef)(null),E=(0,T.useRef)(!1),D=(0,T.useRef)(!1),O=(0,T.useRef)(0),k=(0,T.useRef)(!1),A=(0,T.useRef)(new Map);k.current=t,(0,T.useEffect)(()=>{t&&!E.current&&d&&(D.current=!0)},[t,d]),(0,T.useEffect)(()=>{if(!D.current&&!d){E.current=t;return}if(E.current&&!t&&w.current&&C.current){let e=w.current;if(e.offsetHeight>C.current.clientHeight){e.scrollIntoView({behavior:`smooth`,block:`start`}),E.current=t;return}}(d||D.current)&&b.current?.scrollIntoView({behavior:`smooth`}),E.current=t},[e,d,t]),(0,T.useEffect)(()=>{let e=C.current;if(!e)return;let t=()=>{let t=e.scrollTop;k.current&&t<O.current&&(D.current=!1),O.current=t,f(e.scrollHeight-e.scrollTop-e.clientHeight<80)};return e.addEventListener(`scroll`,t,{passive:!0}),t(),()=>e.removeEventListener(`scroll`,t)},[f]),(0,T.useEffect)(()=>{function e(){v(e=>{let t=new Set;return A.current.forEach((n,r)=>{n.isConnected&&(p.has(r)?e.has(r)&&t.add(r):n.scrollHeight>n.clientHeight+2&&t.add(r))}),e.size===t.size&&[...e].every(e=>t.has(e))?e:t})}e();let t=new ResizeObserver(e);return A.current.forEach(e=>t.observe(e)),()=>t.disconnect()},[e.length,p]);let j=e.reduce((e,t,n)=>t.role===`maxy`&&!t.hidden?n:e,-1);return(0,I.jsxs)(`div`,{className:`chat-messages-wrap`,children:[r&&(0,I.jsx)(`div`,{className:`selection-overlay-band`}),(0,I.jsxs)(`div`,{className:`chat-messages`,ref:C,children:[n&&e.length===0&&(0,I.jsx)(`div`,{className:`session-error`,children:(0,I.jsx)(`p`,{children:n})}),e.map((n,l)=>{if(n.hidden)return null;let u=t&&l===e.length-1,d=`${n.timestamp}_${n.role}`,f=a.has(d);return(0,I.jsxs)(`div`,{ref:l===j?w:void 0,className:`message ${n.role}${f?` selected`:``}`,children:[r&&!u&&(0,I.jsx)(`div`,{className:`message-select-check`,children:(0,I.jsx)(c,{checked:f,onChange:()=>o(d)})}),(0,I.jsxs)(`div`,{className:`bubble`,children:[n.role===`visitor`&&n.content?(()=>{let e=p.has(l),t=h.has(l);return(0,I.jsxs)(I.Fragment,{children:[(0,I.jsx)(`div`,{ref:e=>{e?A.current.set(l,e):A.current.delete(l)},className:e?`bubble-content`:`bubble-content clamped${t?` overflowing`:``}`,children:n.content}),t&&(0,I.jsx)(`div`,{className:`bubble-expand`,children:(0,I.jsx)(y,{variant:`icon`,className:e?`expanded`:``,onClick:()=>m(e=>{let t=new Set(e);return t.has(l)?t.delete(l):t.add(l),t}),"aria-label":e?`Collapse message`:`Expand message`,children:(0,I.jsx)(x,{size:14})})})]})})():n.content?(0,I.jsx)(_,{content:n.content,timestamp:g(n.timestamp)}):(0,I.jsxs)(`span`,{className:`typing-indicator`,children:[(0,I.jsx)(`span`,{className:`typing-dot`}),(0,I.jsx)(`span`,{className:`typing-dot`}),(0,I.jsx)(`span`,{className:`typing-dot`})]}),n.attachments&&n.attachments.length>0&&(0,I.jsx)(`div`,{className:`message-attachments`,children:n.attachments.map((e,t)=>(0,I.jsxs)(`span`,{className:`message-attachment-chip no-action`,children:[e.mimeType===`application/pdf`?(0,I.jsx)(S,{size:12}):e.mimeType.startsWith(`text/`)?(0,I.jsx)(i,{size:12}):null,(0,I.jsx)(`span`,{children:e.filename})]},t))}),n.role===`visitor`&&(0,I.jsx)(`span`,{className:`message-timestamp`,children:g(n.timestamp)})]}),n.role===`maxy`&&n.components&&n.components.length>0&&(0,I.jsx)(`div`,{className:`public-components`,children:n.components.map((e,t)=>(0,I.jsx)(`div`,{className:`public-component-enter`,children:(0,I.jsx)(B,{name:e.name,data:e.data,onSubmit:e=>s(l,t,e),submitted:e.submitted})},t))})]},l)}),!t&&l.length>0&&(0,I.jsx)(`div`,{className:`chat-suggestions`,children:l.map(e=>(0,I.jsx)(y,{variant:`suggestion`,onClick:()=>u(e),children:e},e))}),(0,I.jsx)(`div`,{ref:b})]}),!d&&(0,I.jsx)(`button`,{className:`scroll-to-bottom`,onClick:()=>b.current?.scrollIntoView({behavior:`smooth`}),"aria-label":`Scroll to bottom`})]})}function H({selectedItems:e,messages:n,copySelected:r,exitSelection:i,showCopyToast:o}){let[s,c]=(0,T.useState)(!1),l=(0,T.useRef)(null),u=(0,T.useRef)(!1);function d(e){let t=e.lastIndexOf(`_`),r=e.slice(0,t),i=e.slice(t+1),a=parseInt(r,10);return n.find(e=>e.timestamp===a&&e.role===i)?.content??``}function f(e,t){return parseInt(e.slice(0,e.lastIndexOf(`_`)),10)-parseInt(t.slice(0,t.lastIndexOf(`_`)),10)}function p(){let e=[...n].sort((e,t)=>e.timestamp-t.timestamp),t=[];for(let n of e){if(n.hidden||!n.content)continue;let e=n.role===`visitor`?`Visitor`:`Maxy`;t.push(`${e}:\n${n.content}`)}return t.join(`
|
|
1
|
+
import{A as e,C as t,E as n,O as r,S as i,_ as a,a as o,b as s,d as c,f as l,g as u,h as d,i as f,m as p,n as m,p as h,r as g,s as _,t as v,u as y,v as b,w as x,x as S,y as C}from"./ChatInput--kmj83_w.js";var w=n(),T=e(r(),1),E=`image/jpeg,image/png,image/gif,image/webp,application/pdf,text/plain,text/markdown,text/csv`,D=new Set(E.split(`,`)),O=20*1024*1024,k=typeof window<`u`&&/^(localhost|127\.0\.0\.1|[a-z0-9-]+\.local)(:|$)/.test(window.location.hostname);function A(){let e=crypto.getRandomValues(new Uint8Array(16));e[6]=e[6]&15|64,e[8]=e[8]&63|128;let t=[...e].map(e=>e.toString(16).padStart(2,`0`)).join(``);return`${t.slice(0,8)}-${t.slice(8,12)}-${t.slice(12,16)}-${t.slice(16,20)}-${t.slice(20)}`}function j(){let e=window.location.pathname.match(/^\/([a-z][a-z0-9-]{2,49})$/);return e?e[1]:void 0}function M(e){return[{key:`length`,label:`At least 8 characters`,met:e.length>=8},{key:`number`,label:`Contains a number`,met:/\d/.test(e)},{key:`special`,label:`Contains a special character`,met:/[^A-Za-z0-9]/.test(e)},{key:`whitespace`,label:`No leading or trailing spaces`,met:e.length>0&&e===e.trim()}]}function N(e,t){if(t===`phone`)return e.length>4?e.slice(0,e.length-4).replace(/\d/g,`•`)+` `+e.slice(-4):e;let n=e.indexOf(`@`);return n<=2?e:e.slice(0,2)+`•••`+e.slice(n)}function P(e){let[t,n]=(0,T.useState)(null),[r,i]=(0,T.useState)(null),[a,o]=(0,T.useState)(`loading`),[s,c]=(0,T.useState)(``),[l,u]=(0,T.useState)(null),d=(0,T.useMemo)(()=>j(),[]),f=(0,T.useRef)(d||``),p=(0,T.useRef)(null),m=(0,T.useRef)(null),h=(0,T.useRef)(!1),g=(0,T.useRef)(null),[_,v]=(0,T.useState)(`sign-in`),[y,b]=(0,T.useState)(null),x=(0,T.useRef)(null),S=(0,T.useRef)(null);(0,T.useEffect)(()=>{try{let e=sessionStorage.getItem(`maxy_session`);e&&(S.current=e)}catch{}},[]);let C=(0,T.useCallback)(t=>{p.current=t,n(t);try{sessionStorage.setItem(`maxy_session`,t)}catch{}o(`chat`),e(t)},[e]),w=(0,T.useCallback)(()=>{p.current=null;try{sessionStorage.removeItem(`maxy_session`)}catch{}n(null),o(`auth-required`),v(`sign-in`)},[]),E=(0,T.useCallback)(async e=>{try{let t=f.current,n=await fetch(`/api/access/verify-token`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({token:e,agentSlug:t})}),r=await n.json();n.ok?(x.current=r.session_key,b(r.grant),v(`set-password`),window.history.replaceState({},``,window.location.pathname)):v(`link-expired`)}catch{v(`sign-in`)}},[]);return{sessionId:t,sessionKeyRef:p,sessionError:r,pageState:a,setPageState:o,agentDisplayName:s,agentSlug:d,branding:l,resolvedSlugRef:f,ensureSession:(0,T.useCallback)(async()=>{if(p.current)return p.current;if(m.current)return m.current;let e=(async()=>{try{let e=S.current,t=await fetch(`/api/session`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({session_id:A(),...d?{agent:d}:{},...e?{session_key:e}:{}})});if(!t.ok){let e=await t.json().catch(()=>({error:``}));return k&&console.error(`[session] POST /api/session failed: ${t.status} ${t.statusText}`,e),t.status===404?i(e.error||`Agent not found`):t.status===503&&i(e.error||`Service unavailable`),null}let r=await t.json();if(r.auth_required){r.agent_id&&(f.current=r.agent_id),c(r.displayName||``),r.branding&&u(r.branding),o(`auth-required`);let e=new URLSearchParams(window.location.search).get(`token`);return e?E(e):v(`sign-in`),null}p.current=r.session_key,n(r.session_key),r.branding&&u(r.branding),o(`chat`),r.resumed&&r.messages&&(h.current=!0,g.current=r.messages);try{sessionStorage.setItem(`maxy_session`,r.session_key)}catch{}return r.session_key}catch(e){return k&&console.error(`[session] fetch /api/session threw:`,e),i(`Unable to connect. Please check your connection and try again.`),null}finally{m.current=null}})();return m.current=e,e},[d,E]),enterChat:C,enterGate:w,resumedRef:h,resumeMessagesRef:g,gateState:_,setGateState:v,grantInfo:y,setGrantInfo:b,gateSessionKeyRef:x}}function F({sessionKeyRef:e,ensureSession:t,sessionError:n,pageState:r,inputRef:i}){let[a,o]=(0,T.useState)([]),[s,c]=(0,T.useState)(!1),l=(0,T.useRef)(!1),d=e=>{l.current=e,c(e)},f=(0,T.useCallback)(async a=>{if(!l.current){d(!0),o([{role:`maxy`,content:``,timestamp:Date.now()}]);try{let n=await fetch(`/api/chat`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({greeting:!0,session_key:a})});if(n.status===401){e.current=null;try{sessionStorage.removeItem(`maxy_session`)}catch{}k&&console.warn(`[greeting] stale session, retrying with fresh session`);let r=await t();if(!r)return;n=await fetch(`/api/chat`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({greeting:!0,session_key:r})})}if(!n.ok){let e=await n.json().catch(()=>({}));throw Error(e.error||`Something went wrong`)}let r=``,i=``,s=()=>{i&&(r+=i,i=``,o(e=>{let t=[...e];return t[0]={...t[0],content:r},t}))},c=setInterval(s,60);try{for await(let e of u(n)){if(e.error)throw Object.assign(Error(e.error),{fromSSE:!0});e.text&&(i+=e.text),e.type===`component`&&(s(),o(t=>{let n=[...t],r={...n[0]},i=[...r.components||[]];return i.push({name:e.name,data:e.data,submitted:!1}),n[0]={...r,components:i},n}))}}finally{clearInterval(c),s()}}catch(e){if(k&&console.error(`[chat] greeting failed:`,e),r===`auth-required`)return;let t=e instanceof Error?e.message:String(e);o([{role:`maxy`,content:e instanceof Error&&e.fromSSE?t:n??`I'm having trouble connecting right now. Try refreshing the page.`,timestamp:Date.now()}])}finally{d(!1),r===`chat`&&i.current?.focus()}}},[e,t,n,r,i]),p=(0,T.useCallback)(async(a,s)=>{let c=s?.hidden??!1,f=s?.files??[];if(!a&&f.length===0||l.current)return;d(!0);let p=f.map(e=>({filename:e.name,mimeType:e.type})),m={role:`visitor`,content:a,attachments:p.length>0?p:void 0,timestamp:Date.now(),hidden:c},h;o(e=>{let t=[...e,m,{role:`maxy`,content:``,timestamp:Date.now()}];return h=t.length-1,t});try{let n=await t();if(!n)throw Error(`session`);let r=e=>{if(f.length>0){let t=new FormData;t.append(`message`,a),t.append(`session_key`,e);for(let e of f)t.append(`attachments`,e);return{body:t,headers:{}}}return{body:JSON.stringify({message:a,session_key:e}),headers:{"Content-Type":`application/json`}}},{body:i,headers:s}=r(n),c=await fetch(`/api/chat`,{method:`POST`,headers:s,body:i});if(c.status===401){e.current=null;try{sessionStorage.removeItem(`maxy_session`)}catch{}k&&console.warn(`[session-expired] stale key cleared, retrying with fresh session`);let n=await t();if(!n)throw Error(`session`);({body:i,headers:s}=r(n)),c=await fetch(`/api/chat`,{method:`POST`,headers:s,body:i})}if(!c.ok){let e=await c.json().catch(()=>({}));throw Error(e.error||`Something went wrong`)}let l=``,d=``,p=()=>{d&&(l+=d,d=``,o(e=>{let t=[...e];return t[h]={...t[h],content:l},t}))},m=setInterval(p,60);try{for await(let e of u(c)){if(e.error)throw Object.assign(Error(e.error),{fromSSE:!0});e.text&&(d+=e.text),e.type===`component`&&(p(),o(t=>{let n=[...t],r={...n[h]},i=[...r.components||[]];return i.push({name:e.name,data:e.data,submitted:!1}),n[h]={...r,components:i},n}))}}finally{clearInterval(m),p()}}catch(e){if(k&&console.error(`[chat] sendMessage failed:`,e),r===`auth-required`)return;let t=e instanceof Error?e.message:String(e),i=e instanceof Error&&e.fromSSE;o(e=>{let r=[...e];return r[h]={...r[h],content:i?t:t===`session`?n??`I'm having trouble connecting right now. Try refreshing the page.`:`Sorry, I hit a snag. Try again in a moment.`},r})}finally{d(!1),r===`chat`&&i.current?.focus()}},[e,t,n,r,i]);return{messages:a,setMessages:o,isStreaming:s,sendMessage:p,sendGreeting:f,handleComponentSubmit:(0,T.useCallback)((e,t,n)=>{o(n=>{let r=[...n],i={...r[e]},a=[...i.components||[]];return a[t]={...a[t],submitted:!0},r[e]={...i,components:a},r}),p(n,{hidden:!0})},[p])}}var I=l();function L({value:e,onChange:t,onComplete:n,disabled:r}){let i=(0,T.useRef)([]);function a(n,r){r.key===`Backspace`?(r.preventDefault(),e[n]?t(e.slice(0,n)+e.slice(n+1)):n>0&&(t(e.slice(0,n-1)+e.slice(n)),i.current[n-1]?.focus())):r.key===`ArrowLeft`&&n>0?i.current[n-1]?.focus():r.key===`ArrowRight`&&n<5&&i.current[n+1]?.focus()}function o(r,a){let o=a.nativeEvent.data;if(!o||!/^\d$/.test(o))return;let s=e.split(``);for(s[r]=o;s.length<r;)s.push(``);let c=s.join(``).replace(/\D/g,``).slice(0,6);t(c),c.length===6?n(c):r<5&&i.current[r+1]?.focus()}function s(e){e.preventDefault();let r=e.clipboardData.getData(`text`).replace(/\D/g,``).slice(0,6);r&&(t(r),r.length===6?n(r):i.current[r.length]?.focus())}return(0,I.jsx)(`div`,{className:`gate-otp-field`,children:Array.from({length:6}).map((t,n)=>(0,I.jsx)(`input`,{ref:e=>{i.current[n]=e},type:`text`,inputMode:`numeric`,className:`pin-box${e[n]?` pin-box-filled`:``}`,value:e[n]||``,onKeyDown:e=>a(n,e),onInput:e=>o(n,e),onPaste:s,onFocus:e=>e.target.select(),autoFocus:n===0,autoComplete:`off`,maxLength:1,disabled:r,"aria-label":`Code digit ${n+1}`},n))})}function R({gateState:e,setGateState:t,grantInfo:n,setGrantInfo:r,gateSessionKeyRef:i,resolvedSlugRef:a,onAuthenticated:o}){let[s,l]=(0,T.useState)(``),[u,d]=(0,T.useState)(``),[f,p]=(0,T.useState)(``),[m,h]=(0,T.useState)(!1),[g,_]=(0,T.useState)(``),[v,b]=(0,T.useState)(``),[x,S]=(0,T.useState)(!1),[C,w]=(0,T.useState)(null),[E,D]=(0,T.useState)(null),[O,k]=(0,T.useState)(!1);async function A(e){if(e.preventDefault(),!O){w(null),k(!0);try{let e=a.current,n=await fetch(`/api/access/login`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({contact:s,password:u,agentSlug:e})}),i=await n.json();n.ok?o(i.session_key):n.status===401?i.error?.includes(`setup not complete`)||i.error?.includes(`invitation link`)?(t(`private-agent`),w(null)):w(i.error||`Invalid credentials`):n.status===403?(t(`access-expired`),i.expiresAt&&r(e=>e?{...e,expiresAt:i.expiresAt}:{displayName:null,contactValue:s,contactMethod:`email`,expiresAt:i.expiresAt,status:`expired`})):n.status===429?w(i.error||`Too many attempts. Please try again later.`):w(i.error||`Something went wrong`)}catch{w(`Unable to connect. Please try again.`)}finally{k(!1)}}}async function j(e){if(!O){w(null),k(!0);try{let n=a.current,o=await fetch(`/api/access/verify-otp`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({phone:v,code:e,agentSlug:n})}),s=await o.json();o.ok?(i.current=s.session_key,r(s.grant),t(`set-password`)):o.status===429?t(`otp-failed`):(w(s.error||`Invalid code`),_(``))}catch{w(`Unable to connect. Please try again.`)}finally{k(!1)}}}async function P(e){if(e.preventDefault(),!O){if(w(null),u!==f){w(`Passwords do not match`);return}if(!M(u).every(e=>e.met)){w(`Password does not meet requirements`);return}k(!0);try{let e=i.current;if(!e){w(`Session expired. Please use your invitation link again.`);return}let t=await fetch(`/api/access/create-credentials`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({session_key:e,password:u})}),n=await t.json();t.ok?o(n.session_key):t.status===400&&n.requirements?w(n.requirements.filter(e=>!e.met).map(e=>e.label).join(`, `)):w(n.error||`Something went wrong`)}catch{w(`Unable to connect. Please try again.`)}finally{k(!1)}}}async function F(e){if(e.preventDefault(),!(O||!s)){w(null),k(!0);try{let e=a.current,t=await fetch(`/api/access/forgot-password`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({contact:s,agentSlug:e})});t.status===429?w((await t.json()).error||`Too many attempts. Please try again later.`):(D(`If an account exists, a reset link has been sent.`),setTimeout(()=>{S(!1),D(null)},4e3))}catch{w(`Unable to connect. Please try again.`)}finally{k(!1)}}}switch(e){case`set-password`:{let e=M(u),t=e.every(e=>e.met),r=u===f,i=t&&r&&f.length>0;return(0,I.jsx)(`div`,{className:`gate-wrap`,children:(0,I.jsxs)(`div`,{className:`gate-card`,children:[n?.expiresAt&&(0,I.jsxs)(`div`,{className:`gate-expiry-badge`,children:[`Access until `,new Date(n.expiresAt).toLocaleDateString(`en-GB`,{day:`numeric`,month:`short`,year:`numeric`})]}),(0,I.jsxs)(`h2`,{className:`gate-title`,children:[`Welcome, `,n?.displayName||`there`]}),(0,I.jsx)(`p`,{className:`gate-subtitle`,children:`Create a password to access this agent in the future.`}),(0,I.jsxs)(`form`,{className:`gate-form`,onSubmit:P,children:[(0,I.jsxs)(`div`,{className:`gate-field`,children:[(0,I.jsx)(`label`,{htmlFor:`gate-display-name`,children:`Display name`}),(0,I.jsx)(`input`,{id:`gate-display-name`,type:`text`,value:n?.displayName||``,disabled:!0})]}),(0,I.jsxs)(`div`,{className:`gate-field gate-pw-row`,children:[(0,I.jsx)(`label`,{htmlFor:`gate-pw`,children:`Password`}),(0,I.jsx)(`div`,{className:`gate-pw-toggle`,children:(0,I.jsx)(c,{checked:m,onChange:h,label:`Show`})}),(0,I.jsx)(`input`,{id:`gate-pw`,type:m?`text`:`password`,value:u,onChange:e=>d(e.target.value),placeholder:`Your password`,autoFocus:!0,autoComplete:`new-password`}),u.length>0&&(0,I.jsx)(`div`,{className:`gate-strength`,children:e.map(e=>(0,I.jsxs)(`span`,{className:`gate-strength-item${e.met?` met`:``}`,children:[e.met?`✓`:`○`,` `,e.label]},e.key))})]}),(0,I.jsxs)(`div`,{className:`gate-field`,children:[(0,I.jsx)(`label`,{htmlFor:`gate-pw-confirm`,children:`Confirm password`}),(0,I.jsx)(`input`,{id:`gate-pw-confirm`,type:m?`text`:`password`,value:f,onChange:e=>p(e.target.value),placeholder:`Confirm your password`,autoComplete:`new-password`}),f.length>0&&!r&&(0,I.jsx)(`div`,{className:`gate-error`,children:`Passwords do not match`})]}),C&&(0,I.jsx)(`div`,{className:`gate-error`,children:C}),(0,I.jsx)(`div`,{className:`gate-submit`,children:(0,I.jsx)(y,{variant:`primary`,type:`submit`,fullWidth:!0,disabled:!i||O,loading:O,children:`Create account & enter chat`})})]})]})})}case`enter-otp`:return(0,I.jsx)(`div`,{className:`gate-wrap`,children:(0,I.jsxs)(`div`,{className:`gate-card`,children:[(0,I.jsx)(`h2`,{className:`gate-title`,children:`Enter your code`}),(0,I.jsxs)(`p`,{className:`gate-subtitle`,children:[`Enter the 6-digit code sent to `,N(v,`phone`)]}),(0,I.jsx)(L,{value:g,onChange:_,onComplete:j,disabled:O}),C&&(0,I.jsx)(`div`,{className:`gate-error`,children:C}),O&&(0,I.jsxs)(`div`,{className:`gate-loading`,children:[(0,I.jsx)(`span`,{className:`spinner`}),`Verifying...`]}),(0,I.jsx)(`div`,{className:`gate-resend`,children:(0,I.jsx)(`button`,{type:`button`,className:`gate-link`,onClick:()=>{t(`sign-in`),_(``),w(null)},children:`Use a different number`})})]})});case`sign-in`:case`private-agent`:return(0,I.jsx)(`div`,{className:`gate-wrap`,children:(0,I.jsxs)(`div`,{className:`gate-card`,children:[(0,I.jsx)(`h2`,{className:`gate-title`,children:e===`private-agent`?`Private Agent`:`Welcome back`}),(0,I.jsx)(`p`,{className:`gate-subtitle`,children:e===`private-agent`?`This agent is invitation-only. If you have an account, sign in below.`:`Sign in to continue your conversation.`}),x?(0,I.jsxs)(`form`,{className:`gate-form`,onSubmit:F,children:[(0,I.jsxs)(`div`,{className:`gate-field`,children:[(0,I.jsx)(`label`,{htmlFor:`gate-forgot-contact`,children:`Email or phone`}),(0,I.jsx)(`input`,{id:`gate-forgot-contact`,type:`text`,value:s,onChange:e=>l(e.target.value),placeholder:`you@example.com`,autoFocus:!0,autoComplete:`email`})]}),C&&(0,I.jsx)(`div`,{className:`gate-error`,children:C}),E&&(0,I.jsx)(`div`,{className:`gate-success`,children:E}),(0,I.jsx)(`div`,{className:`gate-submit`,children:(0,I.jsx)(y,{variant:`primary`,type:`submit`,fullWidth:!0,disabled:!s.trim()||O,loading:O,children:`Send reset link`})}),(0,I.jsx)(`div`,{className:`gate-actions`,children:(0,I.jsx)(`button`,{type:`button`,className:`gate-link`,onClick:()=>{S(!1),w(null),D(null)},children:`Back to sign in`})})]}):(0,I.jsxs)(`form`,{className:`gate-form`,onSubmit:A,children:[(0,I.jsxs)(`div`,{className:`gate-field`,children:[(0,I.jsx)(`label`,{htmlFor:`gate-contact`,children:`Email or phone`}),(0,I.jsx)(`input`,{id:`gate-contact`,type:`text`,value:s,onChange:e=>l(e.target.value),placeholder:`you@example.com`,autoFocus:!0,autoComplete:`email`})]}),(0,I.jsxs)(`div`,{className:`gate-field gate-pw-row`,children:[(0,I.jsx)(`label`,{htmlFor:`gate-login-pw`,children:`Password`}),(0,I.jsx)(`div`,{className:`gate-pw-toggle`,children:(0,I.jsx)(c,{checked:m,onChange:h,label:`Show`})}),(0,I.jsx)(`input`,{id:`gate-login-pw`,type:m?`text`:`password`,value:u,onChange:e=>d(e.target.value),placeholder:`Your password`,autoComplete:`current-password`})]}),C&&(0,I.jsx)(`div`,{className:`gate-error`,children:C}),(0,I.jsx)(`div`,{className:`gate-submit`,children:(0,I.jsx)(y,{variant:`primary`,type:`submit`,fullWidth:!0,disabled:!s.trim()||!u||O,loading:O,children:`Sign in`})}),(0,I.jsx)(`div`,{className:`gate-actions`,children:(0,I.jsx)(`button`,{type:`button`,className:`gate-link`,onClick:()=>{S(!0),w(null)},children:`Forgot password?`})})]}),e===`private-agent`&&(0,I.jsx)(`p`,{className:`gate-hint`,children:`No account? You need an invitation from the agent owner.`})]})});case`access-expired`:return(0,I.jsx)(`div`,{className:`gate-wrap`,children:(0,I.jsxs)(`div`,{className:`gate-card`,children:[(0,I.jsx)(`div`,{className:`gate-icon`,children:`⏰`}),(0,I.jsx)(`h2`,{className:`gate-title`,children:`Access expired`}),(0,I.jsxs)(`p`,{className:`gate-subtitle`,children:[n?.expiresAt?`Your access expired on ${new Date(n.expiresAt).toLocaleDateString(`en-GB`,{day:`numeric`,month:`long`,year:`numeric`})}.`:`Your access to this agent has expired.`,` `,`Contact the agent owner to renew.`]}),(0,I.jsx)(`div`,{className:`gate-actions`,children:(0,I.jsx)(y,{variant:`secondary`,onClick:()=>{t(`sign-in`),w(null),d(``)},children:`Sign in with a different account`})})]})});case`link-expired`:return(0,I.jsx)(`div`,{className:`gate-wrap`,children:(0,I.jsxs)(`div`,{className:`gate-card`,children:[(0,I.jsx)(`div`,{className:`gate-icon`,children:`⚠️`}),(0,I.jsx)(`h2`,{className:`gate-title`,children:`Link expired`}),(0,I.jsx)(`p`,{className:`gate-subtitle`,children:`This invitation link has expired or has already been used. If you already set your password, sign in below. Otherwise, ask the agent owner to send a new invitation.`}),(0,I.jsx)(`div`,{className:`gate-actions`,children:(0,I.jsx)(y,{variant:`primary`,onClick:()=>{t(`sign-in`),w(null)},children:`Go to sign in`})})]})});case`otp-failed`:return(0,I.jsx)(`div`,{className:`gate-wrap`,children:(0,I.jsxs)(`div`,{className:`gate-card`,children:[(0,I.jsx)(`div`,{className:`gate-icon`,children:`⛔`}),(0,I.jsx)(`h2`,{className:`gate-title`,children:`Too many attempts`}),(0,I.jsx)(`p`,{className:`gate-subtitle`,children:`You've entered the wrong code too many times. Ask the agent owner to send a new code.`}),(0,I.jsx)(`div`,{className:`gate-actions`,children:(0,I.jsx)(y,{variant:`secondary`,onClick:()=>{t(`sign-in`),w(null),_(``)},children:`Back to sign in`})})]})})}}var z={"single-select":o,"multi-select":f};function B({name:e,data:t,onSubmit:n,submitted:r}){let i=z[e];return i?(0,I.jsx)(i,{data:t,onSubmit:n,submitted:r}):(console.warn(`[PublicComponentRenderer] Unknown component: "${e}". Registered: ${Object.keys(z).join(`, `)}`),(0,I.jsx)(`div`,{className:`component-card component-card--error`,children:(0,I.jsxs)(`p`,{style:{fontFamily:`var(--font-body)`,fontSize:12,color:`var(--text-secondary)`},children:[`Component “`,e,`” is not available. This may require a platform update.`]})}))}function V({messages:e,isStreaming:t,sessionError:n,selectionMode:r,selectedItems:a,toggleSelectItem:o,onComponentSubmit:s,remainingSuggestions:l,onSuggestionClick:u,isAtBottom:d,setIsAtBottom:f}){let[p,m]=(0,T.useState)(new Set),[h,v]=(0,T.useState)(new Set),b=(0,T.useRef)(null),C=(0,T.useRef)(null),w=(0,T.useRef)(null),E=(0,T.useRef)(!1),D=(0,T.useRef)(!1),O=(0,T.useRef)(0),k=(0,T.useRef)(!1),A=(0,T.useRef)(new Map);k.current=t,(0,T.useEffect)(()=>{t&&!E.current&&d&&(D.current=!0)},[t,d]),(0,T.useEffect)(()=>{if(!D.current&&!d){E.current=t;return}if(E.current&&!t&&w.current&&C.current){let e=w.current;if(e.offsetHeight>C.current.clientHeight){e.scrollIntoView({behavior:`smooth`,block:`start`}),E.current=t;return}}(d||D.current)&&b.current?.scrollIntoView({behavior:`smooth`}),E.current=t},[e,d,t]),(0,T.useEffect)(()=>{let e=C.current;if(!e)return;let t=()=>{let t=e.scrollTop;k.current&&t<O.current&&(D.current=!1),O.current=t,f(e.scrollHeight-e.scrollTop-e.clientHeight<80)};return e.addEventListener(`scroll`,t,{passive:!0}),t(),()=>e.removeEventListener(`scroll`,t)},[f]),(0,T.useEffect)(()=>{function e(){v(e=>{let t=new Set;return A.current.forEach((n,r)=>{n.isConnected&&(p.has(r)?e.has(r)&&t.add(r):n.scrollHeight>n.clientHeight+2&&t.add(r))}),e.size===t.size&&[...e].every(e=>t.has(e))?e:t})}e();let t=new ResizeObserver(e);return A.current.forEach(e=>t.observe(e)),()=>t.disconnect()},[e.length,p]);let j=e.reduce((e,t,n)=>t.role===`maxy`&&!t.hidden?n:e,-1);return(0,I.jsxs)(`div`,{className:`chat-messages-wrap`,children:[r&&(0,I.jsx)(`div`,{className:`selection-overlay-band`}),(0,I.jsxs)(`div`,{className:`chat-messages`,ref:C,children:[n&&e.length===0&&(0,I.jsx)(`div`,{className:`session-error`,children:(0,I.jsx)(`p`,{children:n})}),e.map((n,l)=>{if(n.hidden)return null;let u=t&&l===e.length-1,d=`${n.timestamp}_${n.role}`,f=a.has(d);return(0,I.jsxs)(`div`,{ref:l===j?w:void 0,className:`message ${n.role}${f?` selected`:``}`,children:[r&&!u&&(0,I.jsx)(`div`,{className:`message-select-check`,children:(0,I.jsx)(c,{checked:f,onChange:()=>o(d)})}),(0,I.jsxs)(`div`,{className:`bubble`,children:[n.role===`visitor`&&n.content?(()=>{let e=p.has(l),t=h.has(l);return(0,I.jsxs)(I.Fragment,{children:[(0,I.jsx)(`div`,{ref:e=>{e?A.current.set(l,e):A.current.delete(l)},className:e?`bubble-content`:`bubble-content clamped${t?` overflowing`:``}`,children:n.content}),t&&(0,I.jsx)(`div`,{className:`bubble-expand`,children:(0,I.jsx)(y,{variant:`icon`,className:e?`expanded`:``,onClick:()=>m(e=>{let t=new Set(e);return t.has(l)?t.delete(l):t.add(l),t}),"aria-label":e?`Collapse message`:`Expand message`,children:(0,I.jsx)(x,{size:14})})})]})})():n.content?(0,I.jsx)(_,{content:n.content,timestamp:g(n.timestamp)}):(0,I.jsxs)(`span`,{className:`typing-indicator`,children:[(0,I.jsx)(`span`,{className:`typing-dot`}),(0,I.jsx)(`span`,{className:`typing-dot`}),(0,I.jsx)(`span`,{className:`typing-dot`})]}),n.attachments&&n.attachments.length>0&&(0,I.jsx)(`div`,{className:`message-attachments`,children:n.attachments.map((e,t)=>(0,I.jsxs)(`span`,{className:`message-attachment-chip no-action`,children:[e.mimeType===`application/pdf`?(0,I.jsx)(S,{size:12}):e.mimeType.startsWith(`text/`)?(0,I.jsx)(i,{size:12}):null,(0,I.jsx)(`span`,{children:e.filename})]},t))}),n.role===`visitor`&&(0,I.jsx)(`span`,{className:`message-timestamp`,children:g(n.timestamp)})]}),n.role===`maxy`&&n.components&&n.components.length>0&&(0,I.jsx)(`div`,{className:`public-components`,children:n.components.map((e,t)=>(0,I.jsx)(`div`,{className:`public-component-enter`,children:(0,I.jsx)(B,{name:e.name,data:e.data,onSubmit:e=>s(l,t,e),submitted:e.submitted})},t))})]},l)}),!t&&l.length>0&&(0,I.jsx)(`div`,{className:`chat-suggestions`,children:l.map(e=>(0,I.jsx)(y,{variant:`suggestion`,onClick:()=>u(e),children:e},e))}),(0,I.jsx)(`div`,{ref:b})]}),!d&&(0,I.jsx)(`button`,{className:`scroll-to-bottom`,onClick:()=>b.current?.scrollIntoView({behavior:`smooth`}),"aria-label":`Scroll to bottom`})]})}function H({selectedItems:e,messages:n,copySelected:r,exitSelection:i,showCopyToast:o}){let[s,c]=(0,T.useState)(!1),l=(0,T.useRef)(null),u=(0,T.useRef)(!1);function d(e){let t=e.lastIndexOf(`_`),r=e.slice(0,t),i=e.slice(t+1),a=parseInt(r,10);return n.find(e=>e.timestamp===a&&e.role===i)?.content??``}function f(e,t){return parseInt(e.slice(0,e.lastIndexOf(`_`)),10)-parseInt(t.slice(0,t.lastIndexOf(`_`)),10)}function p(){let e=[...n].sort((e,t)=>e.timestamp-t.timestamp),t=[];for(let n of e){if(n.hidden||!n.content)continue;let e=n.role===`visitor`?`Visitor`:`Maxy`;t.push(`${e}:\n${n.content}`)}return t.join(`
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Real Agent</title>
|
|
7
7
|
<link rel="icon" href="/favicon.ico">
|
|
8
|
-
<script type="module" crossorigin src="/assets/admin-
|
|
9
|
-
<link rel="modulepreload" crossorigin href="/assets/ChatInput
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/assets/ChatInput-
|
|
8
|
+
<script type="module" crossorigin src="/assets/admin-CisU37jg.js"></script>
|
|
9
|
+
<link rel="modulepreload" crossorigin href="/assets/ChatInput--kmj83_w.js">
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/ChatInput-rWA47V05.css">
|
|
11
11
|
<link rel="stylesheet" href="/brand-defaults.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
|
@@ -5,9 +5,9 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Real Agent</title>
|
|
7
7
|
<link rel="icon" href="/favicon.ico">
|
|
8
|
-
<script type="module" crossorigin src="/assets/public-
|
|
9
|
-
<link rel="modulepreload" crossorigin href="/assets/ChatInput
|
|
10
|
-
<link rel="stylesheet" crossorigin href="/assets/ChatInput-
|
|
8
|
+
<script type="module" crossorigin src="/assets/public-ClFTMlS3.js"></script>
|
|
9
|
+
<link rel="modulepreload" crossorigin href="/assets/ChatInput--kmj83_w.js">
|
|
10
|
+
<link rel="stylesheet" crossorigin href="/assets/ChatInput-rWA47V05.css">
|
|
11
11
|
<link rel="stylesheet" href="/brand-defaults.css">
|
|
12
12
|
</head>
|
|
13
13
|
<body>
|
package/payload/maxy/server.js
CHANGED
|
@@ -4480,13 +4480,13 @@ function parsePluginFrontmatter(pluginDir) {
|
|
|
4480
4480
|
const tools = parseYamlList("tools");
|
|
4481
4481
|
const hidden = parseYamlList("hidden");
|
|
4482
4482
|
const requires = parseYamlList("requires");
|
|
4483
|
-
let
|
|
4483
|
+
let platform = {};
|
|
4484
4484
|
const metaMatch = fm.match(/^metadata:\s*(.+)$/m);
|
|
4485
4485
|
if (metaMatch) {
|
|
4486
4486
|
try {
|
|
4487
4487
|
const metadata = JSON.parse(metaMatch[1]);
|
|
4488
|
-
if (metadata.
|
|
4489
|
-
|
|
4488
|
+
if (metadata.platform && typeof metadata.platform === "object") {
|
|
4489
|
+
platform = metadata.platform;
|
|
4490
4490
|
}
|
|
4491
4491
|
} catch {
|
|
4492
4492
|
console.warn(`[plugins] ${pluginDir}/PLUGIN.md: invalid metadata JSON`);
|
|
@@ -4498,7 +4498,7 @@ function parsePluginFrontmatter(pluginDir) {
|
|
|
4498
4498
|
tools,
|
|
4499
4499
|
hidden,
|
|
4500
4500
|
requires,
|
|
4501
|
-
|
|
4501
|
+
platform
|
|
4502
4502
|
};
|
|
4503
4503
|
}
|
|
4504
4504
|
function assemblePublicPluginContent(pluginDir) {
|
|
@@ -4608,15 +4608,15 @@ function loadEmbeddedPlugins(agentType, selectedPlugins, enabledPlugins) {
|
|
|
4608
4608
|
for (const dir of dirs) {
|
|
4609
4609
|
const parsed = parsePluginFrontmatter(dir);
|
|
4610
4610
|
if (!parsed) continue;
|
|
4611
|
-
const {
|
|
4612
|
-
const embed2 =
|
|
4611
|
+
const { platform } = parsed;
|
|
4612
|
+
const embed2 = platform.embed;
|
|
4613
4613
|
if (!Array.isArray(embed2) || !embed2.includes(agentType)) {
|
|
4614
4614
|
if (typeof embed2 === "boolean") {
|
|
4615
4615
|
console.warn(`[plugins] ${dir}/PLUGIN.md: embed must be an array of agent types, got boolean \u2014 skipping`);
|
|
4616
4616
|
}
|
|
4617
4617
|
continue;
|
|
4618
4618
|
}
|
|
4619
|
-
if (
|
|
4619
|
+
if (platform.optional) {
|
|
4620
4620
|
const enabled = Array.isArray(enabledPlugins) ? enabledPlugins : [];
|
|
4621
4621
|
if (!enabled.includes(dir)) {
|
|
4622
4622
|
console.log(`[plugins] ${dir}: optional plugin not enabled \u2014 skipping embed`);
|
|
@@ -4628,7 +4628,7 @@ function loadEmbeddedPlugins(agentType, selectedPlugins, enabledPlugins) {
|
|
|
4628
4628
|
const missing = parsed.requires.filter((req) => {
|
|
4629
4629
|
if (enabled.includes(req)) return false;
|
|
4630
4630
|
const reqParsed = parsePluginFrontmatter(req);
|
|
4631
|
-
if (reqParsed && !reqParsed.
|
|
4631
|
+
if (reqParsed && !reqParsed.platform.optional) return false;
|
|
4632
4632
|
return true;
|
|
4633
4633
|
});
|
|
4634
4634
|
if (missing.length > 0) {
|
|
@@ -4772,12 +4772,12 @@ async function buildPluginManifest(enabledPlugins) {
|
|
|
4772
4772
|
for (const dir of dirs.sort()) {
|
|
4773
4773
|
const parsed = parsePluginFrontmatter(dir);
|
|
4774
4774
|
if (!parsed) continue;
|
|
4775
|
-
if (parsed.
|
|
4775
|
+
if (parsed.platform.optional && !enabled.includes(dir)) continue;
|
|
4776
4776
|
if (parsed.requires.length > 0) {
|
|
4777
4777
|
const missing = parsed.requires.filter((req) => {
|
|
4778
4778
|
if (enabled.includes(req)) return false;
|
|
4779
4779
|
const reqParsed = parsePluginFrontmatter(req);
|
|
4780
|
-
return !reqParsed || !!reqParsed.
|
|
4780
|
+
return !reqParsed || !!reqParsed.platform.optional;
|
|
4781
4781
|
});
|
|
4782
4782
|
if (missing.length > 0) continue;
|
|
4783
4783
|
}
|
|
@@ -4858,9 +4858,9 @@ ${toolLines.join("\n")}`);
|
|
|
4858
4858
|
for (const dir of dirs.sort()) {
|
|
4859
4859
|
const parsed = parsePluginFrontmatter(dir);
|
|
4860
4860
|
if (!parsed) continue;
|
|
4861
|
-
if (!parsed.
|
|
4861
|
+
if (!parsed.platform.optional) continue;
|
|
4862
4862
|
if (enabled.includes(dir)) continue;
|
|
4863
|
-
if (parsed.
|
|
4863
|
+
if (parsed.platform.always) continue;
|
|
4864
4864
|
let entry = `${dir} \u2014 ${parsed.description}`;
|
|
4865
4865
|
if (parsed.tools.length > 0) {
|
|
4866
4866
|
entry += `
|
|
@@ -5074,7 +5074,7 @@ function getMcpServers(accountId, enabledPlugins) {
|
|
|
5074
5074
|
"whatsapp": {
|
|
5075
5075
|
command: "node",
|
|
5076
5076
|
args: [resolve4(PLATFORM_ROOT3, "plugins/whatsapp/mcp/dist/index.js")],
|
|
5077
|
-
env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT3 }
|
|
5077
|
+
env: { ACCOUNT_ID: accountId, PLATFORM_ROOT: PLATFORM_ROOT3, PLATFORM_PORT: process.env.PORT ?? "19200" }
|
|
5078
5078
|
},
|
|
5079
5079
|
"admin": {
|
|
5080
5080
|
command: "node",
|
|
@@ -5123,13 +5123,13 @@ function getMcpServers(accountId, enabledPlugins) {
|
|
|
5123
5123
|
for (const dir of dirs) {
|
|
5124
5124
|
if (servers[dir]) continue;
|
|
5125
5125
|
const parsed = parsePluginFrontmatter(dir);
|
|
5126
|
-
if (!parsed?.
|
|
5126
|
+
if (!parsed?.platform.optional) continue;
|
|
5127
5127
|
if (!enabledPlugins.includes(dir)) continue;
|
|
5128
5128
|
if (parsed.requires.length > 0) {
|
|
5129
5129
|
const missing = parsed.requires.filter((req) => {
|
|
5130
5130
|
if (enabledPlugins.includes(req)) return false;
|
|
5131
5131
|
const reqParsed = parsePluginFrontmatter(req);
|
|
5132
|
-
return !reqParsed || !!reqParsed.
|
|
5132
|
+
return !reqParsed || !!reqParsed.platform.optional;
|
|
5133
5133
|
});
|
|
5134
5134
|
if (missing.length > 0) {
|
|
5135
5135
|
console.warn(`[plugins] ${dir} MCP: requires [${missing.join(", ")}] not enabled \u2014 skipping`);
|
|
@@ -5232,7 +5232,7 @@ function getAdminAllowedTools(enabledPlugins) {
|
|
|
5232
5232
|
for (const dir of dirs) {
|
|
5233
5233
|
if (!enabledPlugins.includes(dir)) continue;
|
|
5234
5234
|
const parsed = parsePluginFrontmatter(dir);
|
|
5235
|
-
if (!parsed?.
|
|
5235
|
+
if (!parsed?.platform.optional) continue;
|
|
5236
5236
|
for (const toolName of parsed.tools) {
|
|
5237
5237
|
tools.push(`mcp__${dir}__${toolName}`);
|
|
5238
5238
|
}
|
|
@@ -127,7 +127,7 @@ Connect Maxy to new capabilities by asking — finance tracking, home management
|
|
|
127
127
|
2. **Open your browser and set a PIN.** Your PIN protects the admin chat. Only you can manage your Maxy.
|
|
128
128
|
3. **Connect your Claude account.** Maxy is powered by Claude, Anthropic's AI. You sign up directly with Anthropic and connect via your browser.
|
|
129
129
|
4. **Start talking.** Tell Maxy about your life, your business, your customers. It learns as you go.
|
|
130
|
-
5. **Access from anywhere.** Connect Telegram for notifications and mobile access. Pro users get their own branded domain via Cloudflare Tunnel. Maxy is also available at maxy.chat (live sales agent) and maxy.bot (technical support for existing customers).
|
|
130
|
+
5. **Access from anywhere.** Connect WhatsApp or Telegram for notifications and mobile access. Pro users get their own branded domain via Cloudflare Tunnel. Maxy is also available at maxy.chat (live sales agent) and maxy.bot (technical support for existing customers).
|
|
131
131
|
|
|
132
132
|
You're fully operational within 15 minutes. No software to install, no settings to configure, no training to complete. If you can send a text message, you can use Maxy.
|
|
133
133
|
|
|
@@ -137,7 +137,7 @@ You're fully operational within 15 minutes. No software to install, no settings
|
|
|
137
137
|
|
|
138
138
|
### Maxy Solo — for you
|
|
139
139
|
|
|
140
|
-
One person, one assistant. Scheduling, contacts, tasks, notes, reminders, document generation. Web chat on your local network, plus Telegram when you're out. Email integration for formal documents. One public agent for your personal website or freelancer enquiries. Everything you need to stay on top of your life without another app to manage.
|
|
140
|
+
One person, one assistant. Scheduling, contacts, tasks, notes, reminders, document generation. Web chat on your local network, plus WhatsApp or Telegram when you're out. Email integration for formal documents. One public agent for your personal website or freelancer enquiries. Everything you need to stay on top of your life without another app to manage.
|
|
141
141
|
|
|
142
142
|
### Maxy Family — for your household
|
|
143
143
|
|
|
@@ -329,7 +329,11 @@ Price hikes, privacy concerns, and a UX that feels 20 years old. Using Google me
|
|
|
329
329
|
|
|
330
330
|
### vs WhatsApp Business
|
|
331
331
|
|
|
332
|
-
|
|
332
|
+
WhatsApp Business lets you message customers on WhatsApp. Maxy does the same thing — but through your own number, with no Meta Business account, no message templates, and no approval process. You scan a QR code and your existing WhatsApp number is connected. Your customers message you as they always have. The difference is that Maxy reads and responds on your behalf, routes conversations to the right agent, enforces access policies, and logs everything to your local CRM — all on your device, not Meta's servers.
|
|
333
|
+
|
|
334
|
+
WhatsApp Business is one channel doing one thing. Maxy is a complete business operating system that happens to include WhatsApp as one of many channels. It also does CRM, invoicing, quoting, scheduling, workflows, proactive briefings, email, Telegram, web chat, custom domains, customer-facing agents, document handling, and web research — all through conversation, all on your premises. WhatsApp Business has no path to any of this.
|
|
335
|
+
|
|
336
|
+
Meta banned AI chatbots on WhatsApp in January 2026, restricted new accounts to 250 messages per day, and changes the rules without warning. None of that affects Maxy — it connects as a linked device on your personal number, not as a Meta-managed bot. Your business runs on your terms, not Meta's.
|
|
333
337
|
|
|
334
338
|
### vs Claude Desktop (and Cowork)
|
|
335
339
|
|
|
@@ -421,6 +425,18 @@ Your primary interface. Open a browser on any device — phone, tablet, laptop
|
|
|
421
425
|
|
|
422
426
|
Connect Telegram for notifications and mobile access. Get notified about customer enquiries, appointment reminders, and task updates on your phone. Maxy supports both an admin bot (for you) and a public bot (for your customers).
|
|
423
427
|
|
|
428
|
+
### WhatsApp
|
|
429
|
+
|
|
430
|
+
Connect your existing WhatsApp number to Maxy — no new number, no Meta Business account, no message templates. You scan a QR code in the admin chat, and Maxy links to your phone as a paired device. Your number stays yours. Your contacts see your name, not a bot.
|
|
431
|
+
|
|
432
|
+
Inbound messages from customers route to your public agent automatically. Messages from your own phone route to your admin agent. You control who gets through — open to everyone, restricted to an allowlist, or gated behind a pairing handshake. Group messages work the same way, with separate policies for which groups the agent responds in and whether it needs to be mentioned first.
|
|
433
|
+
|
|
434
|
+
Multiple WhatsApp accounts are supported. Link a personal number and a business number, and Maxy routes outbound messages through the right one — business number for customer conversations, personal number for your own contacts.
|
|
435
|
+
|
|
436
|
+
Voice notes are supported in both directions. Maxy sends audio as native WhatsApp voice messages, not file attachments. If the connection drops, Maxy reconnects automatically with backoff — and messages sent while offline are recovered when it comes back.
|
|
437
|
+
|
|
438
|
+
Set up through conversation — tell the admin agent "connect WhatsApp" and it walks you through the QR pairing. No setup pages, no configuration files, no command line.
|
|
439
|
+
|
|
424
440
|
### Email
|
|
425
441
|
|
|
426
442
|
Maxy gets its own dedicated email address — completely separate from your personal email. It sends quotes, invoices, confirmations, and follow-ups directly to your customers. It reads incoming emails to its own account, extracts verification codes during service setup, and searches its mailbox when you ask. Supports Gmail, Outlook, iCloud, Yahoo, Protonmail, and custom domains. If your business uses a custom domain, Maxy can send from your business address while authenticating through a separate mailbox. Set up through conversation — just tell Maxy the email address and app password.
|
|
@@ -565,7 +581,7 @@ Maxy works alongside your existing CRM — it doesn't replace it. Your data live
|
|
|
565
581
|
|
|
566
582
|
### What exactly is Maxy?
|
|
567
583
|
|
|
568
|
-
Maxy is an AI assistant that runs on a small device on your premises — home or office. You talk to it through a web browser or Telegram. It handles admin tasks — scheduling, customer enquiries, reminders, quotes, invoices — so you don't have to.
|
|
584
|
+
Maxy is an AI assistant that runs on a small device on your premises — home or office. You talk to it through a web browser, WhatsApp, or Telegram. It handles admin tasks — scheduling, customer enquiries, reminders, quotes, invoices — so you don't have to.
|
|
569
585
|
|
|
570
586
|
### Do I need to be technical?
|
|
571
587
|
|
|
@@ -633,7 +649,7 @@ Maxy needs an internet connection to reach Claude (the AI). If your internet dro
|
|
|
633
649
|
|
|
634
650
|
### Can Maxy make phone calls?
|
|
635
651
|
|
|
636
|
-
Not yet. Voice capability is planned for a future release. Currently, Maxy communicates through web chat, Telegram, and email.
|
|
652
|
+
Not yet. Voice capability is planned for a future release. Currently, Maxy communicates through web chat, WhatsApp, Telegram, and email.
|
|
637
653
|
|
|
638
654
|
### Can multiple people use Maxy at the same time?
|
|
639
655
|
|
|
@@ -681,7 +697,7 @@ Yes. Maxy has its own dedicated email address, separate from yours. It sends quo
|
|
|
681
697
|
|
|
682
698
|
### Can I access Maxy from outside my home?
|
|
683
699
|
|
|
684
|
-
Yes. Connect Telegram for mobile access. Pro users also get a custom branded domain (e.g. admin.yourbusiness.com) via Cloudflare Tunnel — accessible from any browser, anywhere. Remote access is always password-protected with rate limiting.
|
|
700
|
+
Yes. Connect WhatsApp or Telegram for mobile access. Pro users also get a custom branded domain (e.g. admin.yourbusiness.com) via Cloudflare Tunnel — accessible from any browser, anywhere. Remote access is always password-protected with rate limiting.
|
|
685
701
|
|
|
686
702
|
### Does Maxy remember previous conversations?
|
|
687
703
|
|
|
@@ -23,7 +23,7 @@ Built-in plugins under `$PLATFORM_ROOT/plugins/`:
|
|
|
23
23
|
- Enable: read `account.json` via `account-manage`, add the plugin name to the `enabledPlugins` array, write back via `Edit`. Activates the plugin's behaviour embed and MCP server from the next session. Plugins with scheduled behaviour (declared via `lifecycle` in PLUGIN.md frontmatter) are activated automatically by the platform heartbeat within one minute — no separate setup command needed.
|
|
24
24
|
- Disable: same process, remove the name from the `enabledPlugins` array. Deactivates from the next session. Any scheduled Events owned by the plugin (`sourcePlugin` field) are cancelled automatically by the platform heartbeat.
|
|
25
25
|
- Core plugins (admin, memory, docs, cloudflare, anthropic) cannot be disabled — they are always active.
|
|
26
|
-
- Validate: before enabling, confirm the plugin directory exists under `$PLATFORM_ROOT/plugins/` and its PLUGIN.md has `optional: true` in `metadata.
|
|
26
|
+
- Validate: before enabling, confirm the plugin directory exists under `$PLATFORM_ROOT/plugins/` and its PLUGIN.md has `optional: true` in `metadata.platform`.
|
|
27
27
|
|
|
28
28
|
## Premium Plugins
|
|
29
29
|
|
|
@@ -65,7 +65,7 @@ Present as a `select` field within the agent configuration `form` during creatio
|
|
|
65
65
|
|
|
66
66
|
### Plugin Selection
|
|
67
67
|
|
|
68
|
-
Present available plugins as a `multi-select` component. Selected plugin names are stored in `config.json`. The platform filters embedded plugins per-agent at runtime — only plugins whose `metadata.
|
|
68
|
+
Present available plugins as a `multi-select` component. Selected plugin names are stored in `config.json`. The platform filters embedded plugins per-agent at runtime — only plugins whose `metadata.platform.embed` includes `"public"` are actually embedded.
|
|
69
69
|
|
|
70
70
|
Only present plugins from the curated list below. Before presenting, verify each plugin's directory exists under `$PLATFORM_ROOT/plugins/` (premium plugins are only available when delivered). Omit any plugin whose directory is absent.
|
|
71
71
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: business-assistant
|
|
3
3
|
description: "Business assistant for small businesses. Handles customer enquiries, appointment booking, quote formatting, invoice generation, and daily briefings. Responds instantly, triages by urgency, and never lets a message fall through the cracks."
|
|
4
|
-
metadata: {"
|
|
4
|
+
metadata: {"platform":{"always":true,"embed":["admin"],"pluginKey":"business-assistant","optional":true,"recommended":true}}
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Business Assistant
|
|
@@ -6,7 +6,7 @@ The **contact record** is the highest-level directory for every customer relatio
|
|
|
6
6
|
|
|
7
7
|
| Concept | What it is | Storage |
|
|
8
8
|
|---------|-----------|---------|
|
|
9
|
-
| **Contact record** | Structured directory entry (phone, name, pipeline status, address) |
|
|
9
|
+
| **Contact record** | Structured directory entry (phone, name, pipeline status, address) | Neo4j graph via `contact_*` tools |
|
|
10
10
|
| **User memory** | Freeform narrative, conversation history, media, documents | `memory/users/{phone}/` via `memory_*` tools |
|
|
11
11
|
|
|
12
12
|
The contact record determines the relationship. User memory provides the detail. A memory profile without a contact record is orphaned data. A contact record without memory is a directory entry awaiting context.
|
|
@@ -82,7 +82,7 @@ Update the status at each transition. This enables the business owner to see the
|
|
|
82
82
|
|
|
83
83
|
## Bot Number Registration
|
|
84
84
|
|
|
85
|
-
Customers who operate their own bots (e.g.
|
|
85
|
+
Customers who operate their own bots (e.g. an automated service) can have those bot phone numbers registered on their contact record. This enables inbound bot detection — when a message arrives from a registered bot number, the system identifies it as automated rather than human.
|
|
86
86
|
|
|
87
87
|
**Registering a bot number:** Use `contact_update` to set a `bot_number` field on the customer's contact record. The value is the bot's phone number in E.164 format. If a customer has multiple bots, use `bot_number_1`, `bot_number_2`, etc.
|
|
88
88
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: docs
|
|
3
3
|
description: "Comprehensive user guide and platform documentation for Maxy. Load references when users ask how-to, setup, or troubleshooting questions. Also contains platform architecture references for admin use."
|
|
4
|
-
metadata: {"
|
|
4
|
+
metadata: {"platform":{"always":true,"embed":["public","admin"],"pluginKey":"docs"}}
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Maxy Documentation
|
|
@@ -3,7 +3,7 @@ name: projects
|
|
|
3
3
|
description: "Structured project execution. Phased sprints, operational investigations, deliverable reviews, and retrospectives — backed by graph tasks with dependency tracking and health signals."
|
|
4
4
|
requires:
|
|
5
5
|
- tasks
|
|
6
|
-
metadata: {"
|
|
6
|
+
metadata: {"platform":{"always":false,"embed":["admin"],"pluginKey":"projects","optional":true,"recommended":true}}
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# Projects
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sales
|
|
3
3
|
description: "Unified sales plugin for Maxy Phase 0. Detects buying signals, closes sales, frames value, handles objections, and captures waitlist signups. Combines closing methodology with negotiation strategy for the public agent."
|
|
4
|
-
metadata: {"
|
|
4
|
+
metadata: {"platform":{"always":true,"embed":["public"],"pluginKey":"sales","optional":true}}
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# Sales
|
|
@@ -11,7 +11,7 @@ tools:
|
|
|
11
11
|
- task-ready
|
|
12
12
|
- session-list
|
|
13
13
|
- session-name
|
|
14
|
-
metadata: {"
|
|
14
|
+
metadata: {"platform":{"always":false,"embed":[],"pluginKey":"tasks","optional":true,"recommended":true}}
|
|
15
15
|
---
|
|
16
16
|
|
|
17
17
|
# Tasks
|
|
@@ -7,7 +7,10 @@
|
|
|
7
7
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
8
8
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
9
9
|
import { z } from "zod";
|
|
10
|
-
const PLATFORM_PORT =
|
|
10
|
+
const PLATFORM_PORT = process.env.PLATFORM_PORT;
|
|
11
|
+
if (!PLATFORM_PORT) {
|
|
12
|
+
throw new Error("PLATFORM_PORT environment variable is required — set by getMcpServers() in claude-agent.ts");
|
|
13
|
+
}
|
|
11
14
|
const BASE_URL = `http://127.0.0.1:${PLATFORM_PORT}`;
|
|
12
15
|
const server = new McpServer({
|
|
13
16
|
name: "maxy-whatsapp",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,aAAa,GAAG,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC;AAChD,IAAI,CAAC,aAAa,EAAE,CAAC;IACnB,MAAM,IAAI,KAAK,CAAC,4FAA4F,CAAC,CAAC;AAChH,CAAC;AACD,MAAM,QAAQ,GAAG,oBAAoB,aAAa,EAAE,CAAC;AAErD,MAAM,MAAM,GAAG,IAAI,SAAS,CAAC;IAC3B,IAAI,EAAE,eAAe;IACrB,OAAO,EAAE,OAAO;CACjB,CAAC,CAAC;AAEH,KAAK,UAAU,OAAO,CAAC,IAAY,EAAE,SAAyB,MAAM,EAAE,IAAc;IAClF,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,GAAG,QAAQ,GAAG,IAAI,EAAE,EAAE;QAC5C,MAAM;QACN,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC,CAAC,SAAS;QAClE,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS;KAC9C,CAAC,CAAC;IACH,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;AACpB,CAAC;AAED,SAAS,UAAU,CAAC,IAAY,EAAE,OAAO,GAAG,KAAK;IAC/C,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAe,EAAE,IAAI,EAAE,CAAC;QAC1C,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACtC,CAAC;AACJ,CAAC;AAED,0EAA0E;AAE1E,MAAM,CAAC,IAAI,CACT,sBAAsB,EACtB,sLAAsL,EACtL;IACE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;IAC5E,KAAK,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,yCAAyC,CAAC;CAClF,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,EAAE;IAC7B,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,2BAA2B,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAQ,CAAC;QAC/F,IAAI,MAAM,CAAC,KAAK;YAAE,OAAO,UAAU,CAAC,uBAAuB,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,CAAC;QAEjF,IAAI,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC;QAC9B,IAAI,MAAM,CAAC,KAAK,EAAE,CAAC;YACjB,QAAQ,IAAI,gCAAgC,MAAM,CAAC,KAAK,EAAE,CAAC;QAC7D,CAAC;QACD,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACrB,QAAQ,IAAI,mBAAmB,MAAM,CAAC,SAAS,EAAE,CAAC;QACpD,CAAC;QACD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,UAAU,CAAC,uBAAuB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACrG,CAAC;AACH,CAAC,CACF,CAAC;AAEF,0EAA0E;AAE1E,MAAM,CAAC,IAAI,CACT,qBAAqB,EACrB,wIAAwI,EACxI;IACE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,iCAAiC,CAAC;IAC5E,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,sCAAsC,CAAC;CAClF,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,EAAE,EAAE;IACjC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,0BAA0B,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,SAAS,EAAE,CAAQ,CAAC;QAClG,IAAI,MAAM,CAAC,KAAK;YAAE,OAAO,UAAU,CAAC,sBAAsB,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,CAAC;QAEhF,IAAI,QAAQ,GAAG,MAAM,CAAC,OAAO,CAAC;QAC9B,IAAI,MAAM,CAAC,SAAS,IAAI,MAAM,CAAC,SAAS,EAAE,CAAC;YACzC,QAAQ,IAAI,mBAAmB,MAAM,CAAC,SAAS,EAAE,CAAC;QACpD,CAAC;QACD,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC9B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,UAAU,CAAC,sBAAsB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACpG,CAAC;AACH,CAAC,CACF,CAAC;AAEF,0EAA0E;AAE1E,MAAM,CAAC,IAAI,CACT,iBAAiB,EACjB,6IAA6I,EAC7I,EAAE,EACF,KAAK,IAAI,EAAE;IACT,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,sBAAsB,EAAE,KAAK,CAAQ,CAAC;QACnE,IAAI,MAAM,CAAC,KAAK;YAAE,OAAO,UAAU,CAAC,wBAAwB,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,CAAC;QAElF,MAAM,QAAQ,GAAG,MAAM,CAAC,QAAQ,IAAI,EAAE,CAAC;QACvC,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO,UAAU,CAAC,2EAA2E,CAAC,CAAC;QACjG,CAAC;QAED,MAAM,KAAK,GAAG,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAM,EAAE,EAAE;YACpC,MAAM,MAAM,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,gBAAgB,CAAC;YAC9D,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,MAAM,IAAI,GAAG,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACxD,MAAM,KAAK,GAAG,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,SAAS,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;YACrD,MAAM,OAAO,GAAG,CAAC,CAAC,iBAAiB,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,iBAAiB,sBAAsB,CAAC,CAAC,CAAC,EAAE,CAAC;YAC9F,OAAO,GAAG,CAAC,CAAC,SAAS,GAAG,IAAI,KAAK,MAAM,GAAG,KAAK,GAAG,KAAK,GAAG,OAAO,EAAE,CAAC;QACtE,CAAC,CAAC,CAAC;QAEH,OAAO,UAAU,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACtC,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,UAAU,CAAC,wBAAwB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACtG,CAAC;AACH,CAAC,CACF,CAAC;AAEF,0EAA0E;AAE1E,MAAM,CAAC,IAAI,CACT,qBAAqB,EACrB,iFAAiF,EACjF;IACE,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,+CAA+C,CAAC;CAC3F,EACD,KAAK,EAAE,EAAE,SAAS,EAAE,EAAE,EAAE;IACtB,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,0BAA0B,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,CAAQ,CAAC;QACvF,IAAI,MAAM,CAAC,KAAK;YAAE,OAAO,UAAU,CAAC,sBAAsB,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,CAAC;QAChF,OAAO,UAAU,CAAC,qBAAqB,MAAM,CAAC,SAAS,iBAAiB,CAAC,CAAC;IAC5E,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,UAAU,CAAC,sBAAsB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IACpG,CAAC;AACH,CAAC,CACF,CAAC;AAEF,0EAA0E;AAE1E,MAAM,CAAC,IAAI,CACT,eAAe,EACf,sHAAsH,EACtH;IACE,EAAE,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,0CAA0C,CAAC;IACnE,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,sBAAsB,CAAC;IACjD,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE,CAAC,QAAQ,CAAC,2CAA2C,CAAC;CACvF,EACD,KAAK,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,EAAE,EAAE;IAChC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,OAAO,CAAC,oBAAoB,EAAE,MAAM,EAAE,EAAE,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,CAAQ,CAAC;QAC3F,IAAI,MAAM,CAAC,KAAK;YAAE,OAAO,UAAU,CAAC,gBAAgB,MAAM,CAAC,KAAK,EAAE,EAAE,IAAI,CAAC,CAAC;QAC1E,OAAO,UAAU,CAAC,4BAA4B,MAAM,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACxG,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,OAAO,UAAU,CAAC,gBAAgB,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,EAAE,IAAI,CAAC,CAAC;IAC9F,CAAC;AACH,CAAC,CACF,CAAC;AAEF,0EAA0E;AAE1E,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;AAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# WhatsApp Channels
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The platform supports two WhatsApp connection methods via Baileys multi-account:
|
|
4
4
|
|
|
5
5
|
| Provider | Use Case | Connection |
|
|
6
6
|
|----------|----------|------------|
|
|
@@ -16,7 +16,7 @@ Most users will have two Baileys accounts (two phone numbers, both linked via QR
|
|
|
16
16
|
| **Personal** | Admin self-chat, groups, personal contacts |
|
|
17
17
|
| **Business** | Customer-facing DMs (can be WhatsApp Business app or regular WhatsApp) |
|
|
18
18
|
|
|
19
|
-
WhatsApp Business **app** vs personal WhatsApp is irrelevant from
|
|
19
|
+
WhatsApp Business **app** vs personal WhatsApp is irrelevant from the platform's perspective — Baileys connects to both identically via QR. The Business app just adds business profile, catalog, and labels in the phone UI.
|
|
20
20
|
|
|
21
21
|
**Linked device note:** The primary phone must come online periodically (~every 14 days) to keep the Baileys linked-device session alive. It does NOT need to be on 24/7.
|
|
22
22
|
|
|
@@ -79,19 +79,19 @@ Cloud API requirements: Meta Business Account, System User Access Token, Cloudfl
|
|
|
79
79
|
|
|
80
80
|
Baileys uses the same WebSocket protocol as WhatsApp Web/Desktop linked devices. Meta cannot blanket-block it without breaking their own official clients for hundreds of millions of users.
|
|
81
81
|
|
|
82
|
-
**Responsibility model:**
|
|
82
|
+
**Responsibility model:** The platform and its other users are not at risk — bans are per-number, targeting individual accounts for abusive behaviour. Users should be told: *"Don't use this for bulk messaging or spam. Meta may ban your number for abusive patterns. Normal business conversations are fine."*
|
|
83
83
|
|
|
84
84
|
- Normal conversational use (replies, scheduling, quotes) = effectively zero risk
|
|
85
85
|
- Ack reactions, typing indicators, and reply delays mimic real linked-device behaviour
|
|
86
86
|
- Risk increases only with spam patterns: mass messaging, rapid-fire sends to unknown contacts, broadcasting
|
|
87
|
-
- If a user's number is banned, it's between them and Meta — other
|
|
87
|
+
- If a user's number is banned, it's between them and Meta — other users are unaffected
|
|
88
88
|
- Cloud API code is available as a fallback for enterprise users who want official Meta support
|
|
89
89
|
|
|
90
90
|
## WhatsApp Outbound Policy — No Automated Broadcasts
|
|
91
91
|
|
|
92
92
|
**WhatsApp's Terms of Service explicitly prohibit automated bulk/broadcast messaging.** This applies to both Baileys and Cloud API connections. Violations result in permanent number bans with no appeal.
|
|
93
93
|
|
|
94
|
-
|
|
94
|
+
The platform enforces this at multiple levels:
|
|
95
95
|
|
|
96
96
|
1. **Broadcast action blocked** — The `message broadcast` action (which sends to multiple targets) always excludes WhatsApp as a target channel, regardless of config. This is a hard block that cannot be overridden.
|
|
97
97
|
2. **Agent tool suppression** — When an agent session is on WhatsApp, the `broadcast` action is removed from the message tool schema entirely. Agents cannot invoke it.
|
|
@@ -154,7 +154,7 @@ The Public Agent Settings modal includes a **Model** dropdown that sets the defa
|
|
|
154
154
|
**How it works:**
|
|
155
155
|
1. UI resolves the selected workspace's public agent ID (e.g. `joes-coffee-public`)
|
|
156
156
|
2. On change, writes the model to config via `writeAgentModelToConfig` (two params: agentId + model)
|
|
157
|
-
3. Config stored in `agents.list[{agentId}].model` in
|
|
157
|
+
3. Config stored in `agents.list[{agentId}].model` in `account.json`
|
|
158
158
|
4. No gateway restart needed — `agents` prefix has `kind: "none"` reload rule (read dynamically)
|
|
159
159
|
|
|
160
160
|
**Runtime model resolution — config is the single source of truth.** Every model read (LLM API call, `/status` display, chat UI initial load) calls `loadConfig()` fresh and resolves via `resolveDefaultModelForAgent({ cfg, agentId })`. There are no session-level model overrides — the model is always read from config at the point of use.
|
|
@@ -182,7 +182,7 @@ The Public Agent Settings modal includes a **Thinking** dropdown that sets the d
|
|
|
182
182
|
**How it works:**
|
|
183
183
|
1. UI resolves the selected workspace's public agent ID (e.g. `joes-coffee-public`)
|
|
184
184
|
2. On change, writes the thinking level to config via `writeAgentThinkingToConfig` (two params: agentId + thinkingLevel)
|
|
185
|
-
3. Config stored in `agents.list[{agentId}].thinkingLevel` in
|
|
185
|
+
3. Config stored in `agents.list[{agentId}].thinkingLevel` in `account.json`
|
|
186
186
|
4. No gateway restart needed — `agents` prefix has `kind: "none"` reload rule (read dynamically)
|
|
187
187
|
|
|
188
188
|
**Runtime thinking resolution — config is the single source of truth.** Every thinking level read calls `loadConfig()` fresh and resolves via `resolveThinkingDefault({ cfg, provider, model, catalog, agentId })`. There are no session-level thinking overrides — thinking is always read from config at the point of use.
|
|
@@ -254,22 +254,19 @@ Baileys stores WhatsApp session credentials (linked-device state) on disk. The a
|
|
|
254
254
|
| Priority | Path | When |
|
|
255
255
|
|----------|------|------|
|
|
256
256
|
| 1. Explicit config | `channels.whatsapp.accounts.{id}.authDir` | User overrides in config |
|
|
257
|
-
| 2. Default | `~/.
|
|
258
|
-
| 3. Legacy fallback | `~/.taskmaster/credentials/` | Old single-account installs (only if creds exist here and not at default path) |
|
|
259
|
-
|
|
260
|
-
The state dir (`~/.taskmaster/`) can be overridden via `TASKMASTER_STATE_DIR` env var; the credentials subdirectory via `TASKMASTER_OAUTH_DIR`.
|
|
257
|
+
| 2. Default | `~/.maxy/credentials/whatsapp/{accountId}/` | Normal operation |
|
|
261
258
|
|
|
262
259
|
**Diagnostics on the Pi:**
|
|
263
260
|
|
|
264
261
|
```bash
|
|
265
262
|
# List all WhatsApp auth directories
|
|
266
|
-
ls -la ~/.
|
|
263
|
+
ls -la ~/.maxy/credentials/whatsapp/
|
|
267
264
|
|
|
268
265
|
# Check if credentials exist for an account
|
|
269
|
-
ls ~/.
|
|
266
|
+
ls ~/.maxy/credentials/whatsapp/default/creds.json
|
|
270
267
|
|
|
271
268
|
# Check credential freshness (should be recent if just linked)
|
|
272
|
-
stat ~/.
|
|
269
|
+
stat ~/.maxy/credentials/whatsapp/default/creds.json
|
|
273
270
|
```
|
|
274
271
|
|
|
275
272
|
If `creds.json` is missing, the session was never linked or was deleted. If it exists but WhatsApp rejects it (401), the session was invalidated (cleared from phone, expired after 14 days offline, or account banned).
|