@rubytech/create-maxy 1.0.449 → 1.0.451
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/{public-DwwV2tyL.js → public-D-52e3uT.js} +1 -1
- package/payload/maxy/public/public.html +1 -1
- package/payload/maxy/server.js +56 -24
- package/payload/platform/plugins/admin/hooks/session-start.sh +21 -5
- package/payload/platform/plugins/admin/hooks/test-agent-creation-gate.sh +43 -10
- package/payload/platform/plugins/admin/skills/public-agent-manager/skill.md +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +2 -0
- package/payload/platform/templates/agents/admin/LEARNINGS.md +0 -18
package/package.json
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import{C as e,D as t,S as n,T as r,_ as i,a,b as o,d as s,f as c,h as l,i as u,j as d,k 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-C_H-xxtM.js";var w=t(),T=d(f(),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:a}){let[o,s]=(0,T.useState)([]),[c,l]=(0,T.useState)(!1),u=(0,T.useRef)(!1),d=e=>{u.current=e,l(e)},f=(0,T.useCallback)(async o=>{if(!u.current){d(!0),s([{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:o})});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=``,a=``,c=()=>{a&&(r+=a,a=``,s(e=>{let t=[...e];return t[0]={...t[0],content:r},t}))},l=setInterval(c,60);try{for await(let e of i(n)){if(e.error)throw Object.assign(Error(e.error),{fromSSE:!0});e.text&&(a+=e.text),e.type===`component`&&(c(),s(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(l),c()}}catch(e){if(k&&console.error(`[chat] greeting failed:`,e),r===`auth-required`)return;let t=e instanceof Error?e.message:String(e);s([{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`&&a.current?.focus()}}},[e,t,n,r,a]),p=(0,T.useCallback)(async(o,c)=>{let l=c?.hidden??!1,f=c?.files??[];if(!o&&f.length===0||u.current)return;d(!0);let p=f.map(e=>({filename:e.name,mimeType:e.type})),m={role:`visitor`,content:o,attachments:p.length>0?p:void 0,timestamp:Date.now(),hidden:l},h;s(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`,o),t.append(`session_key`,e);for(let e of f)t.append(`attachments`,e);return{body:t,headers:{}}}return{body:JSON.stringify({message:o,session_key:e}),headers:{"Content-Type":`application/json`}}},{body:a,headers:c}=r(n),l=await fetch(`/api/chat`,{method:`POST`,headers:c,body:a});if(l.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:a,headers:c}=r(n)),l=await fetch(`/api/chat`,{method:`POST`,headers:c,body:a})}if(!l.ok){let e=await l.json().catch(()=>({}));throw Error(e.error||`Something went wrong`)}let u=``,d=``,p=()=>{d&&(u+=d,d=``,s(e=>{let t=[...e];return t[h]={...t[h],content:u},t}))},m=setInterval(p,60);try{for await(let e of i(l)){if(e.error)throw Object.assign(Error(e.error),{fromSSE:!0});e.text&&(d+=e.text),e.type===`component`&&(p(),s(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;s(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`&&a.current?.focus()}},[e,t,n,r,a]);return{messages:o,setMessages:s,isStreaming:c,sendMessage:p,sendGreeting:f,handleComponentSubmit:(0,T.useCallback)((e,t,n)=>{s(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=c();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[c,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:c,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:c,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||!c)){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:c,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)(s,{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:c,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:!c.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:c,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)(s,{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:!c.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":a,"multi-select":u};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:t,isStreaming:i,sessionError:a,selectionMode:o,selectedItems:c,toggleSelectItem:l,onComponentSubmit:u,remainingSuggestions:d,onSuggestionClick:f,isAtBottom:p,setIsAtBottom:m}){let[h,v]=(0,T.useState)(new Set),[b,x]=(0,T.useState)(new Set),S=(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=i,(0,T.useEffect)(()=>{i&&!E.current&&p&&(D.current=!0)},[i,p]),(0,T.useEffect)(()=>{if(!D.current&&!p){E.current=i;return}if(E.current&&!i&&w.current&&C.current){let e=w.current;if(e.offsetHeight>C.current.clientHeight){e.scrollIntoView({behavior:`smooth`,block:`start`}),E.current=i;return}}(p||D.current)&&S.current?.scrollIntoView({behavior:`smooth`}),E.current=i},[t,p,i]),(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,m(e.scrollHeight-e.scrollTop-e.clientHeight<80)};return e.addEventListener(`scroll`,t,{passive:!0}),t(),()=>e.removeEventListener(`scroll`,t)},[m]),(0,T.useEffect)(()=>{function e(){x(e=>{let t=new Set;return A.current.forEach((n,r)=>{n.isConnected&&(h.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()},[t.length,h]);let j=t.reduce((e,t,n)=>t.role===`maxy`&&!t.hidden?n:e,-1);return(0,I.jsxs)(`div`,{className:`chat-messages-wrap`,children:[o&&(0,I.jsx)(`div`,{className:`selection-overlay-band`}),(0,I.jsxs)(`div`,{className:`chat-messages`,ref:C,children:[a&&t.length===0&&(0,I.jsx)(`div`,{className:`session-error`,children:(0,I.jsx)(`p`,{children:a})}),t.map((a,d)=>{if(a.hidden)return null;let f=i&&d===t.length-1,p=`${a.timestamp}_${a.role}`,m=c.has(p);return(0,I.jsxs)(`div`,{ref:d===j?w:void 0,className:`message ${a.role}${m?` selected`:``}`,children:[o&&!f&&(0,I.jsx)(`div`,{className:`message-select-check`,children:(0,I.jsx)(s,{checked:m,onChange:()=>l(p)})}),(0,I.jsxs)(`div`,{className:`bubble`,children:[a.role===`visitor`&&a.content?(()=>{let e=h.has(d),t=b.has(d);return(0,I.jsxs)(I.Fragment,{children:[(0,I.jsx)(`div`,{ref:e=>{e?A.current.set(d,e):A.current.delete(d)},className:e?`bubble-content`:`bubble-content clamped${t?` overflowing`:``}`,children:a.content}),t&&(0,I.jsx)(`div`,{className:`bubble-expand`,children:(0,I.jsx)(y,{variant:`icon`,className:e?`expanded`:``,onClick:()=>v(e=>{let t=new Set(e);return t.has(d)?t.delete(d):t.add(d),t}),"aria-label":e?`Collapse message`:`Expand message`,children:(0,I.jsx)(r,{size:14})})})]})})():a.content?(0,I.jsx)(_,{content:a.content,timestamp:g(a.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`})]}),a.attachments&&a.attachments.length>0&&(0,I.jsx)(`div`,{className:`message-attachments`,children:a.attachments.map((t,r)=>(0,I.jsxs)(`span`,{className:`message-attachment-chip no-action`,children:[t.mimeType===`application/pdf`?(0,I.jsx)(n,{size:12}):t.mimeType.startsWith(`text/`)?(0,I.jsx)(e,{size:12}):null,(0,I.jsx)(`span`,{children:t.filename})]},r))}),a.role===`visitor`&&(0,I.jsx)(`span`,{className:`message-timestamp`,children:g(a.timestamp)})]}),a.role===`maxy`&&a.components&&a.components.length>0&&(0,I.jsx)(`div`,{className:`public-components`,children:a.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=>u(d,t,e),submitted:e.submitted})},t))})]},d)}),!i&&d.length>0&&(0,I.jsx)(`div`,{className:`chat-suggestions`,children:d.map(e=>(0,I.jsx)(y,{variant:`suggestion`,onClick:()=>f(e),children:e},e))}),(0,I.jsx)(`div`,{ref:S})]}),!p&&(0,I.jsx)(`button`,{className:`scroll-to-bottom`,onClick:()=>S.current?.scrollIntoView({behavior:`smooth`}),"aria-label":`Scroll to bottom`})]})}function H({selectedItems:e,messages:t,copySelected:n,exitSelection:r,showCopyToast:i}){let[a,o]=(0,T.useState)(!1),s=(0,T.useRef)(null),c=(0,T.useRef)(!1);function l(e){let n=e.lastIndexOf(`_`),r=e.slice(0,n),i=e.slice(n+1),a=parseInt(r,10);return t.find(e=>e.timestamp===a&&e.role===i)?.content??``}function u(e,t){return parseInt(e.slice(0,e.lastIndexOf(`_`)),10)-parseInt(t.slice(0,t.lastIndexOf(`_`)),10)}function d(){let e=[...t].sort((e,t)=>e.timestamp-t.timestamp),n=[];for(let t of e){if(t.hidden||!t.content)continue;let e=t.role===`visitor`?`Visitor`:`Maxy`;n.push(`${e}:\n${t.content}`)}return n.join(`
|
|
1
|
+
import{C as e,D as t,S as n,T as r,_ as i,a,b as o,d as s,f as c,h as l,i as u,j as d,k 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-C_H-xxtM.js";var w=t(),T=d(f(),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:a}){let[o,s]=(0,T.useState)([]),[c,l]=(0,T.useState)(!1),u=(0,T.useRef)(!1),d=e=>{u.current=e,l(e)},f=(0,T.useCallback)(async o=>{if(!u.current){d(!0),s([{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:o})});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=``,a=``,c=()=>{a&&(r+=a,a=``,s(e=>{let t=[...e];return t[0]={...t[0],content:r},t}))},l=setInterval(c,60);try{for await(let e of i(n)){if(e.error)throw Object.assign(Error(e.error),{fromSSE:!0});e.text&&(a+=e.text),e.type===`component`&&(c(),s(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(l),c()}}catch(e){if(k&&console.error(`[chat] greeting failed:`,e),r===`auth-required`)return;let t=e instanceof Error?e.message:String(e);s([{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`&&a.current?.focus()}}},[e,t,n,r,a]),p=(0,T.useCallback)(async(o,c)=>{let l=c?.hidden??!1,f=c?.files??[];if(!o&&f.length===0||u.current)return;d(!0);let p=f.map(e=>({filename:e.name,mimeType:e.type})),m={role:`visitor`,content:o,attachments:p.length>0?p:void 0,timestamp:Date.now(),hidden:l},h;s(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`,o),t.append(`session_key`,e);for(let e of f)t.append(`attachments`,e);return{body:t,headers:{}}}return{body:JSON.stringify({message:o,session_key:e}),headers:{"Content-Type":`application/json`}}},{body:a,headers:c}=r(n),l=await fetch(`/api/chat`,{method:`POST`,headers:c,body:a});if(l.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:a,headers:c}=r(n)),l=await fetch(`/api/chat`,{method:`POST`,headers:c,body:a})}if(!l.ok){let e=await l.json().catch(()=>({}));throw Error(e.error||`Something went wrong`)}let u=``,d=``,p=()=>{d&&(u+=d,d=``,s(e=>{let t=[...e];return t[h]={...t[h],content:u},t}))},m=setInterval(p,60);try{for await(let e of i(l)){if(e.error)throw Object.assign(Error(e.error),{fromSSE:!0});e.text&&(d+=e.text),e.type===`component`&&(p(),s(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;s(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`&&a.current?.focus()}},[e,t,n,r,a]);return{messages:o,setMessages:s,isStreaming:c,sendMessage:p,sendGreeting:f,handleComponentSubmit:(0,T.useCallback)((e,t,n)=>{s(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=c();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[c,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:c,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:c,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||!c)){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:c,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)(s,{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:c,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:!c.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:c,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)(s,{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:!c.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":a,"multi-select":u};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:t,isStreaming:i,sessionError:a,selectionMode:o,selectedItems:c,toggleSelectItem:l,onComponentSubmit:u,remainingSuggestions:d,onSuggestionClick:f,isAtBottom:p,setIsAtBottom:m}){let[h,v]=(0,T.useState)(new Set),[b,x]=(0,T.useState)(new Set),S=(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=i,(0,T.useEffect)(()=>{i&&!E.current&&p&&(D.current=!0)},[i,p]),(0,T.useEffect)(()=>{if(!D.current&&!p){E.current=i;return}if(E.current&&!i&&w.current&&C.current){let e=w.current;if(e.offsetHeight>C.current.clientHeight){D.current=!1,e.scrollIntoView({behavior:`smooth`,block:`start`}),E.current=i;return}}(p||D.current)&&S.current?.scrollIntoView({behavior:`smooth`}),E.current=i},[t,p,i]),(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,m(e.scrollHeight-e.scrollTop-e.clientHeight<80)};return e.addEventListener(`scroll`,t,{passive:!0}),t(),()=>e.removeEventListener(`scroll`,t)},[m]),(0,T.useEffect)(()=>{function e(){x(e=>{let t=new Set;return A.current.forEach((n,r)=>{n.isConnected&&(h.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()},[t.length,h]);let j=t.reduce((e,t,n)=>t.role===`maxy`&&!t.hidden?n:e,-1);return(0,I.jsxs)(`div`,{className:`chat-messages-wrap`,children:[o&&(0,I.jsx)(`div`,{className:`selection-overlay-band`}),(0,I.jsxs)(`div`,{className:`chat-messages`,ref:C,children:[a&&t.length===0&&(0,I.jsx)(`div`,{className:`session-error`,children:(0,I.jsx)(`p`,{children:a})}),t.map((a,d)=>{if(a.hidden)return null;let f=i&&d===t.length-1,p=`${a.timestamp}_${a.role}`,m=c.has(p);return(0,I.jsxs)(`div`,{ref:d===j?w:void 0,className:`message ${a.role}${m?` selected`:``}`,children:[o&&!f&&(0,I.jsx)(`div`,{className:`message-select-check`,children:(0,I.jsx)(s,{checked:m,onChange:()=>l(p)})}),(0,I.jsxs)(`div`,{className:`bubble`,children:[a.role===`visitor`&&a.content?(()=>{let e=h.has(d),t=b.has(d);return(0,I.jsxs)(I.Fragment,{children:[(0,I.jsx)(`div`,{ref:e=>{e?A.current.set(d,e):A.current.delete(d)},className:e?`bubble-content`:`bubble-content clamped${t?` overflowing`:``}`,children:a.content}),t&&(0,I.jsx)(`div`,{className:`bubble-expand`,children:(0,I.jsx)(y,{variant:`icon`,className:e?`expanded`:``,onClick:()=>v(e=>{let t=new Set(e);return t.has(d)?t.delete(d):t.add(d),t}),"aria-label":e?`Collapse message`:`Expand message`,children:(0,I.jsx)(r,{size:14})})})]})})():a.content?(0,I.jsx)(_,{content:a.content,timestamp:g(a.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`})]}),a.attachments&&a.attachments.length>0&&(0,I.jsx)(`div`,{className:`message-attachments`,children:a.attachments.map((t,r)=>(0,I.jsxs)(`span`,{className:`message-attachment-chip no-action`,children:[t.mimeType===`application/pdf`?(0,I.jsx)(n,{size:12}):t.mimeType.startsWith(`text/`)?(0,I.jsx)(e,{size:12}):null,(0,I.jsx)(`span`,{children:t.filename})]},r))}),a.role===`visitor`&&(0,I.jsx)(`span`,{className:`message-timestamp`,children:g(a.timestamp)})]}),a.role===`maxy`&&a.components&&a.components.length>0&&(0,I.jsx)(`div`,{className:`public-components`,children:a.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=>u(d,t,e),submitted:e.submitted})},t))})]},d)}),!i&&d.length>0&&(0,I.jsx)(`div`,{className:`chat-suggestions`,children:d.map(e=>(0,I.jsx)(y,{variant:`suggestion`,onClick:()=>f(e),children:e},e))}),(0,I.jsx)(`div`,{ref:S})]}),!p&&(0,I.jsx)(`button`,{className:`scroll-to-bottom`,onClick:()=>S.current?.scrollIntoView({behavior:`smooth`}),"aria-label":`Scroll to bottom`})]})}function H({selectedItems:e,messages:t,copySelected:n,exitSelection:r,showCopyToast:i}){let[a,o]=(0,T.useState)(!1),s=(0,T.useRef)(null),c=(0,T.useRef)(!1);function l(e){let n=e.lastIndexOf(`_`),r=e.slice(0,n),i=e.slice(n+1),a=parseInt(r,10);return t.find(e=>e.timestamp===a&&e.role===i)?.content??``}function u(e,t){return parseInt(e.slice(0,e.lastIndexOf(`_`)),10)-parseInt(t.slice(0,t.lastIndexOf(`_`)),10)}function d(){let e=[...t].sort((e,t)=>e.timestamp-t.timestamp),n=[];for(let t of e){if(t.hidden||!t.content)continue;let e=t.role===`visitor`?`Visitor`:`Maxy`;n.push(`${e}:\n${t.content}`)}return n.join(`
|
|
2
2
|
|
|
3
3
|
---
|
|
4
4
|
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
6
|
<title>Maxy</title>
|
|
7
7
|
<link rel="icon" href="/favicon.ico">
|
|
8
|
-
<script type="module" crossorigin src="/assets/public-
|
|
8
|
+
<script type="module" crossorigin src="/assets/public-D-52e3uT.js"></script>
|
|
9
9
|
<link rel="modulepreload" crossorigin href="/assets/ChatInput-C_H-xxtM.js">
|
|
10
10
|
<link rel="stylesheet" crossorigin href="/assets/ChatInput-rWA47V05.css">
|
|
11
11
|
<link rel="stylesheet" href="/brand-defaults.css">
|
package/payload/maxy/server.js
CHANGED
|
@@ -6084,7 +6084,14 @@ async function* parseClaudeStream(proc, streamLog, adminModel) {
|
|
|
6084
6084
|
if (block.type === "tool_result") {
|
|
6085
6085
|
const name = block.tool_use_id && toolIdToName.get(block.tool_use_id) || block.tool_use_id || "unknown";
|
|
6086
6086
|
if (name === "__component__" || name === "__session_reset__" || name === "__session_resume__") continue;
|
|
6087
|
-
|
|
6087
|
+
let output;
|
|
6088
|
+
if (typeof block.content === "string") {
|
|
6089
|
+
output = block.content;
|
|
6090
|
+
} else if (Array.isArray(block.content)) {
|
|
6091
|
+
output = block.content.filter((b) => b.type === "text" && typeof b.text === "string").map((b) => b.text).join("\n");
|
|
6092
|
+
} else {
|
|
6093
|
+
output = JSON.stringify(block.content);
|
|
6094
|
+
}
|
|
6088
6095
|
const outputPreview = output.slice(0, 300);
|
|
6089
6096
|
if (name === "Agent") {
|
|
6090
6097
|
streamLog.write(`[${isoTs()}] [agent-return] error=${!!block.is_error} output=${JSON.stringify(outputPreview)}
|
|
@@ -9391,8 +9398,9 @@ async function safeSaveCreds(authDir, saveCreds) {
|
|
|
9391
9398
|
}
|
|
9392
9399
|
try {
|
|
9393
9400
|
await Promise.resolve(saveCreds());
|
|
9401
|
+
console.error(`${TAG2} creds saved successfully to ${authDir}`);
|
|
9394
9402
|
} catch (err) {
|
|
9395
|
-
console.error(`${TAG2} failed saving creds: ${String(err)}`);
|
|
9403
|
+
console.error(`${TAG2} failed saving creds to ${authDir}: ${String(err)}`);
|
|
9396
9404
|
}
|
|
9397
9405
|
}
|
|
9398
9406
|
function enqueueSaveCreds(authDir, saveCreds) {
|
|
@@ -9406,44 +9414,63 @@ async function createWaSocket(opts) {
|
|
|
9406
9414
|
maybeRestoreCredsFromBackup(authDir);
|
|
9407
9415
|
const { state, saveCreds } = await useMultiFileAuthState(authDir);
|
|
9408
9416
|
const { version: version2 } = await fetchLatestBaileysVersion();
|
|
9409
|
-
const
|
|
9410
|
-
|
|
9411
|
-
|
|
9412
|
-
|
|
9413
|
-
|
|
9414
|
-
|
|
9415
|
-
|
|
9416
|
-
|
|
9417
|
-
|
|
9418
|
-
|
|
9419
|
-
|
|
9420
|
-
|
|
9421
|
-
|
|
9422
|
-
|
|
9423
|
-
}
|
|
9424
|
-
|
|
9417
|
+
const BAILEYS_TAG = "[whatsapp:baileys]";
|
|
9418
|
+
function makeBaileysLogger() {
|
|
9419
|
+
const logger = {
|
|
9420
|
+
level: "warn",
|
|
9421
|
+
child: () => makeBaileysLogger(),
|
|
9422
|
+
trace: () => {
|
|
9423
|
+
},
|
|
9424
|
+
debug: () => {
|
|
9425
|
+
},
|
|
9426
|
+
info: () => {
|
|
9427
|
+
},
|
|
9428
|
+
warn: (...args) => console.error(BAILEYS_TAG, "WARN", ...args),
|
|
9429
|
+
error: (...args) => console.error(BAILEYS_TAG, "ERROR", ...args),
|
|
9430
|
+
fatal: (...args) => console.error(BAILEYS_TAG, "FATAL", ...args)
|
|
9431
|
+
};
|
|
9432
|
+
return logger;
|
|
9433
|
+
}
|
|
9434
|
+
const baileysLogger = makeBaileysLogger();
|
|
9425
9435
|
const sock = makeWASocket({
|
|
9426
9436
|
auth: {
|
|
9427
9437
|
creds: state.creds,
|
|
9428
|
-
keys: makeCacheableSignalKeyStore(state.keys,
|
|
9438
|
+
keys: makeCacheableSignalKeyStore(state.keys, baileysLogger)
|
|
9429
9439
|
},
|
|
9430
9440
|
version: version2,
|
|
9431
|
-
logger:
|
|
9441
|
+
logger: baileysLogger,
|
|
9432
9442
|
printQRInTerminal: false,
|
|
9433
9443
|
browser: Browsers.macOS("Chrome"),
|
|
9434
9444
|
syncFullHistory: false,
|
|
9435
9445
|
markOnlineOnConnect: false
|
|
9436
9446
|
});
|
|
9437
|
-
sock.ev.on("creds.update", () =>
|
|
9447
|
+
sock.ev.on("creds.update", () => {
|
|
9448
|
+
console.error(`${TAG2} creds.update received \u2014 saving to ${authDir}`);
|
|
9449
|
+
enqueueSaveCreds(authDir, saveCreds);
|
|
9450
|
+
});
|
|
9451
|
+
let qrSequence = 0;
|
|
9438
9452
|
sock.ev.on("connection.update", (update) => {
|
|
9439
9453
|
try {
|
|
9454
|
+
const parts = [];
|
|
9455
|
+
if (update.connection) parts.push(`connection=${update.connection}`);
|
|
9456
|
+
if (update.qr) parts.push(`qr=#${++qrSequence}`);
|
|
9457
|
+
if (update.isNewLogin !== void 0) parts.push(`isNewLogin=${update.isNewLogin}`);
|
|
9458
|
+
if (update.receivedPendingNotifications !== void 0) parts.push(`pendingNotifications=${update.receivedPendingNotifications}`);
|
|
9459
|
+
if (update.connection === "close" && update.lastDisconnect) {
|
|
9460
|
+
const status = getStatusCode(update.lastDisconnect.error);
|
|
9461
|
+
parts.push(`disconnectStatus=${status ?? "unknown"}`);
|
|
9462
|
+
parts.push(`disconnectReason=${formatError(update.lastDisconnect.error)}`);
|
|
9463
|
+
}
|
|
9464
|
+
if (!silent && parts.length > 0) {
|
|
9465
|
+
console.error(`${TAG2} connection.update ${parts.join(" ")}`);
|
|
9466
|
+
}
|
|
9440
9467
|
if (update.qr && onQr) {
|
|
9441
9468
|
onQr(update.qr);
|
|
9442
9469
|
}
|
|
9443
9470
|
if (update.connection === "close") {
|
|
9444
9471
|
const status = getStatusCode(update.lastDisconnect?.error);
|
|
9445
9472
|
if (status === DisconnectReason.loggedOut && !silent) {
|
|
9446
|
-
console.error(`${TAG2} session logged out \u2014 re-link required`);
|
|
9473
|
+
console.error(`${TAG2} session logged out (401) \u2014 re-link required`);
|
|
9447
9474
|
}
|
|
9448
9475
|
}
|
|
9449
9476
|
if (update.connection === "open" && !silent) {
|
|
@@ -9573,16 +9600,21 @@ async function startLogin(opts) {
|
|
|
9573
9600
|
);
|
|
9574
9601
|
let sock;
|
|
9575
9602
|
let pendingQr = null;
|
|
9603
|
+
let loginQrCount = 0;
|
|
9576
9604
|
try {
|
|
9577
9605
|
sock = await createWaSocket({
|
|
9578
9606
|
authDir,
|
|
9579
9607
|
onQr: (qr2) => {
|
|
9580
|
-
|
|
9608
|
+
loginQrCount++;
|
|
9609
|
+
if (pendingQr) {
|
|
9610
|
+
console.error(`${TAG3} QR rotation #${loginQrCount} received for account=${accountId} \u2014 not forwarded (initial QR already captured)`);
|
|
9611
|
+
return;
|
|
9612
|
+
}
|
|
9581
9613
|
pendingQr = qr2;
|
|
9582
9614
|
const current = activeLogins.get(accountId);
|
|
9583
9615
|
if (current && !current.qr) current.qr = qr2;
|
|
9584
9616
|
clearTimeout(qrTimer);
|
|
9585
|
-
console.error(`${TAG3} QR received for account=${accountId}`);
|
|
9617
|
+
console.error(`${TAG3} QR #${loginQrCount} received for account=${accountId} \u2014 forwarding to caller`);
|
|
9586
9618
|
resolveQr?.(qr2);
|
|
9587
9619
|
}
|
|
9588
9620
|
});
|
|
@@ -30,9 +30,13 @@ if [ ! -f "$DURABLE_STATE" ]; then
|
|
|
30
30
|
fi
|
|
31
31
|
|
|
32
32
|
# Staleness + completion check — reject durable state that is stale (>6h, missing
|
|
33
|
-
# timestamp) OR already complete (all gates true
|
|
34
|
-
# completed durable file would be recovered on
|
|
35
|
-
# misleading "resume creation" message.
|
|
33
|
+
# timestamp) OR already complete (all gates true AND all gated files exist on
|
|
34
|
+
# disk). Without this, a stale or completed durable file would be recovered on
|
|
35
|
+
# every new session, injecting a misleading "resume creation" message.
|
|
36
|
+
#
|
|
37
|
+
# "All gates true" alone is NOT completion — it means user approval is done but
|
|
38
|
+
# file writes may still be pending. Deleting state at that point causes the
|
|
39
|
+
# remaining writes to fail with "invoke the public-agent-manager skill first."
|
|
36
40
|
DURABLE_VALID=$(python3 -c "
|
|
37
41
|
import json, sys
|
|
38
42
|
from datetime import datetime, timezone
|
|
@@ -49,10 +53,22 @@ try:
|
|
|
49
53
|
if age_hours > 6 or age_hours < 0:
|
|
50
54
|
print('stale')
|
|
51
55
|
sys.exit(0)
|
|
52
|
-
# All gates true
|
|
56
|
+
# All gates true AND all files on disk = truly complete. All gates true
|
|
57
|
+
# alone means the user approved everything but writes may still be pending.
|
|
58
|
+
# Must match the PostToolUse completion check in agent-creation-post.sh.
|
|
53
59
|
gates = state.get('gates', {})
|
|
54
60
|
if isinstance(gates, dict) and set(gates.keys()) == {'soul', 'knowledge', 'config'} and all(v is True for v in gates.values()):
|
|
55
|
-
|
|
61
|
+
slug = state.get('slug', '')
|
|
62
|
+
if slug:
|
|
63
|
+
import os
|
|
64
|
+
agent_dir = os.path.join('$ACCOUNT_DIR', 'agents', slug)
|
|
65
|
+
gated_files = ['SOUL.md', 'KNOWLEDGE.md', 'config.json']
|
|
66
|
+
if all(os.path.isfile(os.path.join(agent_dir, f)) for f in gated_files):
|
|
67
|
+
print('complete')
|
|
68
|
+
else:
|
|
69
|
+
print('valid')
|
|
70
|
+
else:
|
|
71
|
+
print('valid')
|
|
56
72
|
else:
|
|
57
73
|
print('valid')
|
|
58
74
|
except Exception:
|
|
@@ -13,7 +13,8 @@
|
|
|
13
13
|
# - Allow Bash writes after gates pass
|
|
14
14
|
# - Allow all gated writes when all gates pass
|
|
15
15
|
# - Clean up state immediately when all files exist on disk (PostToolUse)
|
|
16
|
-
# -
|
|
16
|
+
# - Recover all-gates-true state on session start when files not yet written
|
|
17
|
+
# - Detect and clean up truly completed state on session start (all gates true + files on disk)
|
|
17
18
|
# - Ignore non-document-editor/form components
|
|
18
19
|
# - Ignore _componentDone for admin agent
|
|
19
20
|
# - Handle gate advancement idempotently
|
|
@@ -721,10 +722,11 @@ fi
|
|
|
721
722
|
rm -f "$STATE_FILE"
|
|
722
723
|
|
|
723
724
|
# ---------------------------------------------------------------------------
|
|
724
|
-
# Test 30: Session-start
|
|
725
|
+
# Test 30: Session-start RECOVERS all-gates-true state when files are missing
|
|
725
726
|
# ---------------------------------------------------------------------------
|
|
726
|
-
echo "Test 30: Session-start
|
|
727
|
+
echo "Test 30: Session-start recovers all-gates-true state when files are missing"
|
|
727
728
|
rm -f "$STATE_FILE"
|
|
729
|
+
rm -f "$ACCOUNT_DIR/agents/test-agent/SOUL.md" "$ACCOUNT_DIR/agents/test-agent/KNOWLEDGE.md" "$ACCOUNT_DIR/agents/test-agent/config.json" 2>/dev/null || true
|
|
728
730
|
DURABLE_FILE="$ACCOUNT_DIR/.claude/agent-create-state.json"
|
|
729
731
|
|
|
730
732
|
python3 -c "
|
|
@@ -741,16 +743,47 @@ with open('$DURABLE_FILE', 'w') as f:
|
|
|
741
743
|
|
|
742
744
|
bash "$SCRIPT_DIR/session-start.sh" "$ACCOUNT_DIR" admin 2>/dev/null || true
|
|
743
745
|
|
|
744
|
-
if [
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
746
|
+
if [ -f "$STATE_FILE" ] && [ -f "$DURABLE_FILE" ]; then
|
|
747
|
+
pass "All-gates-true state recovered (files not yet written — writes still pending)"
|
|
748
|
+
else
|
|
749
|
+
fail "All-gates-true state should be recovered when gated files are missing from disk"
|
|
750
|
+
fi
|
|
751
|
+
|
|
752
|
+
rm -f "$STATE_FILE"
|
|
753
|
+
|
|
754
|
+
# ---------------------------------------------------------------------------
|
|
755
|
+
# Test 30a: Session-start does NOT recover truly completed state (all gates true + all files on disk)
|
|
756
|
+
# ---------------------------------------------------------------------------
|
|
757
|
+
echo "Test 30a: Session-start does NOT recover truly completed state"
|
|
758
|
+
|
|
759
|
+
# Create all gated files on disk
|
|
760
|
+
touch "$ACCOUNT_DIR/agents/test-agent/SOUL.md"
|
|
761
|
+
touch "$ACCOUNT_DIR/agents/test-agent/KNOWLEDGE.md"
|
|
762
|
+
touch "$ACCOUNT_DIR/agents/test-agent/config.json"
|
|
763
|
+
|
|
764
|
+
python3 -c "
|
|
765
|
+
import json
|
|
766
|
+
from datetime import datetime, timezone
|
|
767
|
+
state = {
|
|
768
|
+
'slug': 'test-agent',
|
|
769
|
+
'started': datetime.now(timezone.utc).isoformat(),
|
|
770
|
+
'gates': {'soul': True, 'knowledge': True, 'config': True}
|
|
771
|
+
}
|
|
772
|
+
with open('$DURABLE_FILE', 'w') as f:
|
|
773
|
+
json.dump(state, f)
|
|
774
|
+
"
|
|
775
|
+
|
|
776
|
+
bash "$SCRIPT_DIR/session-start.sh" "$ACCOUNT_DIR" admin 2>/dev/null || true
|
|
777
|
+
|
|
778
|
+
if [ ! -f "$STATE_FILE" ] && [ ! -f "$DURABLE_FILE" ]; then
|
|
779
|
+
pass "Truly completed state (all gates true + all files on disk) deleted"
|
|
750
780
|
else
|
|
751
|
-
fail "
|
|
781
|
+
fail "Truly completed state should be deleted — both gates passed and files written"
|
|
752
782
|
fi
|
|
753
783
|
|
|
784
|
+
# Clean up gated files for subsequent tests
|
|
785
|
+
rm -f "$ACCOUNT_DIR/agents/test-agent/SOUL.md" "$ACCOUNT_DIR/agents/test-agent/KNOWLEDGE.md" "$ACCOUNT_DIR/agents/test-agent/config.json"
|
|
786
|
+
|
|
754
787
|
# ---------------------------------------------------------------------------
|
|
755
788
|
# Test 31: PostToolUse cleans up state after last gated file is written
|
|
756
789
|
# ---------------------------------------------------------------------------
|
|
@@ -94,7 +94,7 @@ SOUL.md is strictly personality. When the user provides content for SOUL.md, rou
|
|
|
94
94
|
- **Engagement strategy, qualification flows, component usage** → handled by selected plugins
|
|
95
95
|
- **Boundaries, tool rules, procedural instructions** → IDENTITY.md
|
|
96
96
|
|
|
97
|
-
SOUL.md answers "what does this agent feel like to talk to?" — tone, warmth, formality, humour, greeting personality. SOUL.md must not restate constraints from IDENTITY.md. When user-provided content could belong to either file, apply this test: "does this restrict what the agent does?" → IDENTITY.md. "Does this describe how the agent sounds?" → SOUL.md. If content does both (e.g. "always respond politely"), route to SOUL — the constraint is a consequence of tone, not an operational boundary. Present SOUL.md via `document-editor` for the user to review and approve.
|
|
97
|
+
SOUL.md answers "what does this agent feel like to talk to?" — tone, warmth, formality, humour, greeting personality. SOUL.md must not restate constraints from IDENTITY.md. When user-provided content could belong to either file, apply this test: "does this restrict what the agent does?" → IDENTITY.md. "Does this describe how the agent sounds?" → SOUL.md. If content does both (e.g. "always respond politely"), route to SOUL — the constraint is a consequence of tone, not an operational boundary. Present SOUL.md via `document-editor` for the user to review and approve. Follow the document-editor encoding constraint in IDENTITY.md when generating content.
|
|
98
98
|
|
|
99
99
|
## KNOWLEDGE.md Population
|
|
100
100
|
|
|
@@ -127,6 +127,8 @@ The user can also launch the browser independently from the header menu. Both pa
|
|
|
127
127
|
|
|
128
128
|
When the user asks to view, review, attach, or download a file or document, render it via `render-component` with `name: "document-editor"` and `data: { title, content }`. This gives a render-review-edit-download flow — the user sees the content inline, can edit it, and can download it as a `.md` file directly from the component. Do not use `memory-write`, `memory-ingest`, or other tools to deliver file content to the user. Synthesised content (summaries, reports, drafts) follows the same path — render via `document-editor` so the user can review and download.
|
|
129
129
|
|
|
130
|
+
The document-editor's markdown parser fails silently on these specific typographical characters: em dashes (U+2014), en dashes (U+2013), curly double quotes (U+201C, U+201D), curly single quotes (U+2018, U+2019), and horizontal ellipsis (U+2026). Replace them with their plain equivalents — hyphens, straight quotes, three periods. Currency symbols (£, €), accented characters, and other Unicode are unaffected — use them normally.
|
|
131
|
+
|
|
130
132
|
## Plugins and Skills
|
|
131
133
|
|
|
132
134
|
Your behaviour is defined by your loaded plugins. Follow them. The `<plugin-manifest>` lists every active plugin's skills and references with their file paths. To load any skill or reference, call `plugin-read` with the plugin's directory name and the file path from the manifest. The manifest is authoritative for plugin discovery and skill loading.
|
|
@@ -1,21 +1,3 @@
|
|
|
1
1
|
# Learnings
|
|
2
2
|
|
|
3
3
|
Accumulated tool-call patterns. Each entry records a mistake and the correct approach. Consult before calling unfamiliar tools.
|
|
4
|
-
|
|
5
|
-
---
|
|
6
|
-
|
|
7
|
-
### Deferred tool schemas must be loaded before use
|
|
8
|
-
|
|
9
|
-
MCP tools have deferred schemas — parameter names, types, and required fields are not in context until fetched. Calling an MCP tool without loading its schema produces wrong parameter names, wrong types, and missing required fields. Always call `ToolSearch` for a tool before its first use in a session. This applies to every `mcp__*` tool — memory, tasks, scheduling, contacts, admin, cloudflare, telegram, documents.
|
|
10
|
-
|
|
11
|
-
### memory-update requires Neo4j element IDs
|
|
12
|
-
|
|
13
|
-
The `memory-update` tool identifies nodes by their Neo4j element ID — an integer returned in `memory-search` results (the `elementId` field). Do not pass UUIDs, attachment IDs, or string identifiers. If you do not have the element ID, run `memory-search` first to retrieve it.
|
|
14
|
-
|
|
15
|
-
### memory-ingest complex parameters must be arrays
|
|
16
|
-
|
|
17
|
-
The `memory-ingest` tool's `sections` and `keywords` parameters are arrays, not strings. `sections` is an array of objects; `keywords` is an array of strings. Passing a string where an array is expected produces a schema validation error. Load the tool schema via `ToolSearch` to confirm the expected types before calling.
|
|
18
|
-
|
|
19
|
-
### document-editor content must be ASCII-safe
|
|
20
|
-
|
|
21
|
-
The `document-editor` markdown parser silently fails on Unicode typographical characters — em dashes (—), en dashes (–), curly quotes (" " ' '), and ellipsis (…). When these appear in the `content` field, the editor renders empty and the user sees a blank document. Use ASCII alternatives: hyphens (-), straight quotes (" '), and three periods (...). This applies to all `document-editor` content including SOUL.md, KNOWLEDGE.md, and any other document presented for review.
|