@rubytech/create-maxy 1.0.473 → 1.0.474

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/plugins/admin/PLUGIN.md +2 -2
  3. package/payload/platform/plugins/docs/references/migration-guide.md +29 -21
  4. package/payload/platform/plugins/memory/PLUGIN.md +6 -0
  5. package/payload/platform/plugins/memory/mcp/dist/index.js +58 -22
  6. package/payload/platform/plugins/memory/mcp/dist/index.js.map +1 -1
  7. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts +1 -0
  8. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.d.ts.map +1 -1
  9. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js +35 -23
  10. package/payload/platform/plugins/memory/mcp/dist/tools/memory-reindex.js.map +1 -1
  11. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts +5 -0
  12. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.d.ts.map +1 -1
  13. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js +40 -7
  14. package/payload/platform/plugins/memory/mcp/dist/tools/memory-write.js.map +1 -1
  15. package/payload/platform/scripts/migrate-import.sh +17 -11
  16. package/payload/platform/scripts/seed-neo4j.sh +2 -55
  17. package/payload/server/public/assets/ChatInput-BZayQkAS.css +1 -0
  18. package/payload/server/public/assets/{admin-BEbxw46k.js → admin-CW4uB1GJ.js} +2 -2
  19. package/payload/server/public/assets/public-DEZaIlO-.js +5 -0
  20. package/payload/server/public/index.html +3 -3
  21. package/payload/server/public/public.html +3 -3
  22. package/payload/server/server.js +44 -102
  23. package/payload/platform/plugins/admin/hooks/agent-creation-approval.sh +0 -161
  24. package/payload/platform/plugins/admin/hooks/agent-creation-gate.sh +0 -317
  25. package/payload/platform/plugins/admin/hooks/agent-creation-post.sh +0 -165
  26. package/payload/platform/plugins/admin/hooks/session-start.sh +0 -104
  27. package/payload/platform/plugins/admin/hooks/test-agent-creation-gate.sh +0 -926
  28. package/payload/server/public/assets/ChatInput-BEwQxFL9.css +0 -1
  29. package/payload/server/public/assets/public-OdyNuhVE.js +0 -5
  30. /package/payload/server/public/assets/{ChatInput-Dnp1FLis.js → ChatInput-DgjefIvo.js} +0 -0
@@ -0,0 +1,5 @@
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-DgjefIvo.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,text/html`,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,f]=(0,T.useState)(null),[p,m]=(0,T.useState)(!1),[h,g]=(0,T.useState)(null),_=(0,T.useMemo)(()=>j(),[]),v=(0,T.useRef)(_||``),y=(0,T.useRef)(null),b=(0,T.useRef)(null),x=(0,T.useRef)(!1),S=(0,T.useRef)(null),[C,w]=(0,T.useState)(`sign-in`),[E,D]=(0,T.useState)(null),O=(0,T.useRef)(null),M=(0,T.useRef)(null);(0,T.useEffect)(()=>{try{let e=sessionStorage.getItem(`maxy_session`);e&&(M.current=e)}catch{}},[]);let N=(0,T.useCallback)(t=>{y.current=t,n(t);try{sessionStorage.setItem(`maxy_session`,t)}catch{}o(`chat`),e(t)},[e]),P=(0,T.useCallback)(()=>{y.current=null;try{sessionStorage.removeItem(`maxy_session`)}catch{}n(null),o(`auth-required`),w(`sign-in`)},[]),F=(0,T.useCallback)(async e=>{try{let t=v.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?(O.current=r.session_key,D(r.grant),w(`set-password`),window.history.replaceState({},``,window.location.pathname)):w(`link-expired`)}catch{w(`sign-in`)}},[]);return{sessionId:t,sessionKeyRef:y,sessionError:r,pageState:a,setPageState:o,agentDisplayName:s,agentImage:l,agentImageShape:d,showAgentName:p,agentSlug:_,branding:h,resolvedSlugRef:v,ensureSession:(0,T.useCallback)(async()=>{if(y.current)return y.current;if(b.current)return b.current;let e=(async()=>{try{let e=M.current,t=await fetch(`/api/session`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({session_id:A(),..._?{agent:_}:{},...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&&(v.current=r.agent_id),c(r.displayName||``),r.agentImage&&u(r.agentImage),r.agentImageShape&&f(r.agentImageShape),r.showAgentName&&m(r.showAgentName),r.branding&&g(r.branding),o(`auth-required`);let e=new URLSearchParams(window.location.search).get(`token`);return e?F(e):w(`sign-in`),null}y.current=r.session_key,n(r.session_key),r.displayName&&c(r.displayName),r.agentImage&&u(r.agentImage),r.agentImageShape&&f(r.agentImageShape),r.showAgentName&&m(r.showAgentName),r.branding&&g(r.branding),o(`chat`),r.resumed&&r.messages&&(x.current=!0,S.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{b.current=null}})();return b.current=e,e},[_,F]),enterChat:N,enterGate:P,resumedRef:x,resumeMessagesRef:S,gateState:C,setGateState:w,grantInfo:E,setGrantInfo:D,gateSessionKeyRef:O}}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)(!1),k=(0,T.useRef)(0),A=(0,T.useRef)(!1),j=(0,T.useRef)(new Map);A.current=i,(0,T.useEffect)(()=>{i&&!E.current&&p&&(D.current=!0)},[i,p]),(0,T.useEffect)(()=>{if(!D.current&&!p){E.current=i,O.current=!1;return}if(E.current&&!i&&w.current&&C.current){let e=w.current;if(e.offsetHeight>C.current.clientHeight){D.current=!1,O.current=!0,e.scrollIntoView({behavior:`smooth`,block:`start`}),E.current=i;return}}(p||D.current)&&!O.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;A.current&&t<k.current&&(D.current=!1),k.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 j.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 j.current.forEach(e=>t.observe(e)),()=>t.disconnect()},[t.length,h]);let M=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===M?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?j.current.set(d,e):j.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`,children:(0,I.jsx)(r,{size:18})})]})}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
+
3
+ ---
4
+
5
+ `)}T.useEffect(()=>{if(!a)return;let e=e=>{e.target.closest(`.selection-copy-wrap`)||o(!1)};return document.addEventListener(`pointerdown`,e),()=>document.removeEventListener(`pointerdown`,e)},[a]);function f(){s.current!==null&&(clearTimeout(s.current),s.current=null)}return(0,I.jsx)(`div`,{className:`chat-input-area`,children:(0,I.jsxs)(`div`,{className:`selection-bar`,children:[(0,I.jsxs)(`span`,{className:`selection-count`,children:[e.size,` Selected`]}),(0,I.jsxs)(`div`,{className:`selection-copy-wrap`,children:[(0,I.jsxs)(`button`,{type:`button`,className:`selection-copy`,onPointerDown:()=>{if(a){o(!1);return}c.current=!1,s.current=setTimeout(()=>{c.current=!0,o(!0),s.current=null},500)},onPointerUp:f,onPointerLeave:f,onPointerCancel:f,onClick:async()=>{c.current||await n(l,u)},children:[(0,I.jsx)(x,{size:18}),(0,I.jsx)(`span`,{children:`Copy`})]}),a&&(0,I.jsx)(`div`,{className:`copy-menu`,children:(0,I.jsx)(`button`,{type:`button`,className:`copy-menu-item`,onClick:async()=>{let e=d();e&&i(await b(e)),r()},children:`Copy all`})})]}),(0,I.jsx)(`button`,{type:`button`,className:`selection-cancel`,onClick:r,children:`Cancel`})]})})}function U({input:t,setInput:r,isStreaming:i,pendingFiles:a,setPendingFiles:o,attachError:s,setAttachError:c,isDragOver:l,setIsDragOver:u,inputRef:d,onSubmit:f}){let p=(0,T.useRef)(null);function h(e){c(null);let t=e.find(e=>!D.has(e.type));if(t){c(`Unsupported file type: "${t.type}". Supported: images, PDF, plain text, markdown, CSV.`);return}let n=e.find(e=>e.size>O);if(n){c(`"${n.name}" exceeds the 20 MB limit.`);return}o(t=>[...t,...e].slice(0,5))}function g(e){e.preventDefault(),u(!0)}function _(){u(!1)}function b(e){e.preventDefault(),u(!1),h([...e.dataTransfer.files])}return(0,I.jsxs)(`div`,{className:`chat-input-area`,children:[(0,I.jsx)(`input`,{ref:p,type:`file`,multiple:!0,accept:E,style:{display:`none`},onChange:e=>{e.target.files&&h([...e.target.files]),e.target.value=``}}),a.length>0&&(0,I.jsx)(`div`,{className:`attachment-strip`,children:a.map((t,r)=>(0,I.jsxs)(`div`,{className:`attachment-chip`,children:[t.type.startsWith(`image/`)?(0,I.jsx)(`img`,{src:URL.createObjectURL(t),alt:t.name,className:`attachment-chip-thumb`}):t.type===`application/pdf`?(0,I.jsx)(n,{size:14}):(0,I.jsx)(e,{size:14}),(0,I.jsx)(`span`,{className:`attachment-chip-name`,children:t.name}),(0,I.jsx)(`button`,{type:`button`,className:`attachment-chip-remove`,onClick:()=>o(e=>e.filter((e,t)=>t!==r)),"aria-label":`Remove ${t.name}`,children:`×`})]},r))}),s&&(0,I.jsx)(`p`,{className:`attach-error`,children:s}),(0,I.jsx)(m,{inputRef:d}),(0,I.jsxs)(`form`,{className:`chat-form${l?` drag-over`:``}`,onSubmit:f,onDragOver:g,onDragLeave:_,onDrop:b,onPaste:e=>{let t=e.clipboardData?.items;if(!t)return;let n=[];for(let e of t){if(e.kind!==`file`)continue;let t=e.getAsFile();if(!t)continue;let r=t.type.split(`/`)[1]?.replace(`jpeg`,`jpg`)||`png`;n.push(new File([t],`pasted-image-${Date.now()}.${r}`,{type:t.type}))}n.length>0&&h(n)},children:[(0,I.jsx)(y,{variant:`icon`,type:`button`,onClick:()=>p.current?.click(),disabled:i,"aria-label":`Attach file`,children:(0,I.jsx)(S,{size:14})}),(0,I.jsx)(v,{ref:d,value:t,onChange:r,placeholder:`Type a message...`}),(0,I.jsx)(y,{variant:`send`,type:`submit`,disabled:i||!t.trim()&&a.length===0,"aria-label":`Send message`,children:(0,I.jsxs)(`svg`,{viewBox:`0 0 24 24`,fill:`none`,stroke:`currentColor`,strokeWidth:`2`,strokeLinecap:`round`,strokeLinejoin:`round`,children:[(0,I.jsx)(`line`,{x1:`5`,y1:`12`,x2:`19`,y2:`12`}),(0,I.jsx)(`polyline`,{points:`12 5 19 12 12 19`})]})})]})]})}var W=[];function G(){let[e,t]=(0,T.useState)(``),[n,r]=(0,T.useState)([]),[i,a]=(0,T.useState)(!1),[s,c]=(0,T.useState)(null),[u,d]=(0,T.useState)(W),[f,m]=(0,T.useState)(!0),g=(0,T.useRef)(null),{selectionMode:_,selectedItems:v,copyToast:y,exitSelection:b,enterSelection:x,toggleSelectItem:S,copySelected:w,showCopyToast:E}=C(),D=(0,T.useRef)(()=>{}),O=P((0,T.useCallback)(e=>{D.current(e)},[])),{sessionId:k,sessionKeyRef:A,sessionError:j,pageState:M,agentDisplayName:N,agentImage:L,agentImageShape:z,showAgentName:B,branding:G,ensureSession:K,resumedRef:q,resumeMessagesRef:J}=O,{messages:Y,setMessages:X,isStreaming:Z,sendMessage:ee,sendGreeting:Q,handleComponentSubmit:te}=F({sessionKeyRef:A,ensureSession:K,sessionError:j,pageState:M,inputRef:g});(0,T.useEffect)(()=>{D.current=Q},[Q]),(0,T.useEffect)(()=>{let e=!1;return(async()=>{let t=await K();if(!e&&t){if(q.current&&J.current&&J.current.length>0){X(J.current.map(e=>({role:e.role,content:e.content,timestamp:e.timestamp}))),J.current=null;return}Q(t)}})(),()=>{e=!0}},[]),(0,T.useEffect)(()=>{M===`chat`&&g.current?.focus()},[M]),(0,T.useEffect)(()=>{if(!G)return;let e=document.documentElement;if(G.primaryColor&&(e.style.setProperty(`--sage`,G.primaryColor),e.style.setProperty(`--sage-hover`,G.primaryColor),e.style.setProperty(`--sage-subtle`,G.primaryColor+`14`),e.style.setProperty(`--sage-glow`,G.primaryColor+`26`)),G.accentColor&&e.style.setProperty(`--visitor-bubble`,G.accentColor),G.backgroundColor&&e.style.setProperty(`--bg`,G.backgroundColor),document.title=G.name,G.primaryColor){let e=document.querySelector(`meta[name="theme-color"]`);e||(e=document.createElement(`meta`),e.name=`theme-color`,document.head.appendChild(e)),e.content=G.primaryColor}if(G.faviconUrl){let e=document.querySelector(`link[rel="icon"]`);e||(e=document.createElement(`link`),e.rel=`icon`,document.head.appendChild(e)),e.href=G.faviconUrl}},[G]),(0,T.useEffect)(()=>{function e(e){e.key===`Escape`&&_&&b()}return window.addEventListener(`keydown`,e),()=>window.removeEventListener(`keydown`,e)},[_,b]);function $(e){if(!e&&n.length===0||Z)return;X(e=>e.map(e=>e.components?.some(e=>!e.submitted)?{...e,components:e.components.map(e=>e.submitted?e:{...e,submitted:!0})}:e)),d(t=>t.filter(t=>t!==e)),m(!0);let i=[...n];t(``),r([]),c(null),ee(e,{files:i})}function ne(t){t.preventDefault(),$(e.trim()),g.current?.resetHeight()}return(0,I.jsxs)(`div`,{className:`chat-page`,children:[(0,I.jsxs)(`header`,{className:`chat-header`,children:[(0,I.jsx)(`img`,{src:L||G?.logoUrl||l,alt:G?.name||h.productName,className:`chat-logo${L&&z===`circle`?` chat-logo--circle`:``}${L&&z===`rounded`?` chat-logo--rounded`:``}`,onError:e=>{e.target.src=G?.logoUrl||l}}),(0,I.jsxs)(`div`,{className:`chat-header-text`,children:[B!==`none`&&(0,I.jsx)(`h1`,{className:`chat-tagline`,children:B&&N||G?.name||h.productName}),(0,I.jsx)(`p`,{className:`chat-intro`,children:G?.tagline||``})]}),M===`chat`&&!_&&(0,I.jsx)(`button`,{className:`chat-header-select`,onClick:x,title:`Select messages`,"aria-label":`Select messages`,children:(0,I.jsx)(o,{size:16})})]}),M===`loading`&&(0,I.jsx)(`div`,{className:`gate-wrap`,children:(0,I.jsx)(`div`,{className:`gate-loading`,children:(0,I.jsx)(`span`,{className:`spinner`})})}),M===`auth-required`&&(0,I.jsx)(R,{gateState:O.gateState,setGateState:O.setGateState,grantInfo:O.grantInfo,setGrantInfo:O.setGrantInfo,gateSessionKeyRef:O.gateSessionKeyRef,resolvedSlugRef:O.resolvedSlugRef,onAuthenticated:O.enterChat}),M===`chat`&&(0,I.jsxs)(I.Fragment,{children:[(0,I.jsx)(V,{messages:Y,isStreaming:Z,sessionError:j,selectionMode:_,selectedItems:v,toggleSelectItem:S,onComponentSubmit:te,remainingSuggestions:u,onSuggestionClick:$,isAtBottom:f,setIsAtBottom:m}),_?(0,I.jsx)(H,{selectedItems:v,messages:Y,copySelected:w,exitSelection:b,showCopyToast:E}):(0,I.jsx)(U,{input:e,setInput:t,isStreaming:Z,pendingFiles:n,setPendingFiles:r,attachError:s,setAttachError:c,isDragOver:i,setIsDragOver:a,inputRef:g,onSubmit:ne})]}),y&&(0,I.jsx)(`span`,{className:`copy-toast${y===`failed`?` copy-toast-failed`:``}`,children:y===`copied`?`Copied`:`Copy failed`}),(0,I.jsxs)(`footer`,{className:`chat-footer`,children:[(0,I.jsxs)(`a`,{href:p,children:[h.domain,` `,`↗`]}),k&&(0,I.jsxs)(`span`,{style:{opacity:.35,fontSize:`10px`,fontFamily:`monospace`,marginLeft:`1rem`},children:[`Session · `,k.slice(0,8)]})]})]})}(0,w.createRoot)(document.getElementById(`root`)).render((0,I.jsx)(G,{}));
@@ -5,9 +5,9 @@
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/admin-BEbxw46k.js"></script>
9
- <link rel="modulepreload" crossorigin href="/assets/ChatInput-Dnp1FLis.js">
10
- <link rel="stylesheet" crossorigin href="/assets/ChatInput-BEwQxFL9.css">
8
+ <script type="module" crossorigin src="/assets/admin-CW4uB1GJ.js"></script>
9
+ <link rel="modulepreload" crossorigin href="/assets/ChatInput-DgjefIvo.js">
10
+ <link rel="stylesheet" crossorigin href="/assets/ChatInput-BZayQkAS.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>Maxy</title>
7
7
  <link rel="icon" href="/favicon.ico">
8
- <script type="module" crossorigin src="/assets/public-OdyNuhVE.js"></script>
9
- <link rel="modulepreload" crossorigin href="/assets/ChatInput-Dnp1FLis.js">
10
- <link rel="stylesheet" crossorigin href="/assets/ChatInput-BEwQxFL9.css">
8
+ <script type="module" crossorigin src="/assets/public-DEZaIlO-.js"></script>
9
+ <link rel="modulepreload" crossorigin href="/assets/ChatInput-DgjefIvo.js">
10
+ <link rel="stylesheet" crossorigin href="/assets/ChatInput-BZayQkAS.css">
11
11
  <link rel="stylesheet" href="/brand-defaults.css">
12
12
  </head>
13
13
  <body>
@@ -2851,7 +2851,7 @@ var serveStatic = (options = { root: "" }) => {
2851
2851
  };
2852
2852
 
2853
2853
  // server/index.ts
2854
- import { readFileSync as readFileSync20, existsSync as existsSync20, watchFile } from "fs";
2854
+ import { readFileSync as readFileSync19, existsSync as existsSync19, watchFile } from "fs";
2855
2855
  import { resolve as resolve18, join as join9, basename as basename4 } from "path";
2856
2856
  import { homedir as homedir3 } from "os";
2857
2857
 
@@ -7612,7 +7612,8 @@ var SUPPORTED_MIME_TYPES = /* @__PURE__ */ new Set([
7612
7612
  "application/pdf",
7613
7613
  "text/plain",
7614
7614
  "text/markdown",
7615
- "text/csv"
7615
+ "text/csv",
7616
+ "text/html"
7616
7617
  ]);
7617
7618
  var MAX_FILE_SIZE_BYTES = 20 * 1024 * 1024;
7618
7619
  var MAX_FILES_PER_MESSAGE = 5;
@@ -25599,8 +25600,6 @@ async function POST19(req) {
25599
25600
  }
25600
25601
 
25601
25602
  // app/api/admin/chat/route.ts
25602
- import { existsSync as existsSync14, readFileSync as readFileSync14, writeFileSync as writeFileSync9, mkdirSync as mkdirSync6 } from "fs";
25603
- import { dirname as dirname2 } from "path";
25604
25603
  function isComponentDone(parsed) {
25605
25604
  return typeof parsed === "object" && parsed !== null && parsed._componentDone === true && typeof parsed.component === "string" && typeof parsed.payload === "string";
25606
25605
  }
@@ -25625,60 +25624,6 @@ ${inner.content}`;
25625
25624
  }
25626
25625
  return payload;
25627
25626
  }
25628
- var VOLATILE_STATE_FILE = "/tmp/maxy-agent-create-state.json";
25629
- function advanceGateIfNeeded(envelope, accountDir, logFn) {
25630
- if (!existsSync14(VOLATILE_STATE_FILE)) return;
25631
- const { component, payload } = envelope;
25632
- let gateFlag = null;
25633
- let agentSlug = "";
25634
- if (component === "document-editor") {
25635
- try {
25636
- const inner = typeof payload === "string" ? JSON.parse(payload) : payload;
25637
- if (typeof inner === "object" && inner !== null && typeof inner.filePath === "string") {
25638
- const match2 = inner.filePath.match(/agents\/([^/]+)\/(SOUL\.md|KNOWLEDGE\.md)$/);
25639
- if (match2 && match2[1] !== "admin") {
25640
- agentSlug = match2[1];
25641
- gateFlag = match2[2] === "SOUL.md" ? "soul" : "knowledge";
25642
- }
25643
- }
25644
- } catch {
25645
- }
25646
- } else if (component === "form") {
25647
- gateFlag = "config";
25648
- }
25649
- if (!gateFlag) return;
25650
- let state;
25651
- try {
25652
- state = JSON.parse(readFileSync14(VOLATILE_STATE_FILE, "utf-8"));
25653
- } catch (err) {
25654
- logFn(`ERROR \u2014 failed to read state file for advancement: ${err instanceof Error ? err.message : String(err)}`);
25655
- return;
25656
- }
25657
- const gates = state.gates;
25658
- if (!gates || typeof gates !== "object") {
25659
- logFn("WARNING \u2014 state file has unexpected structure, skipping advancement");
25660
- return;
25661
- }
25662
- if (!state.slug && agentSlug) {
25663
- state.slug = agentSlug;
25664
- }
25665
- if (gates[gateFlag] === true) return;
25666
- gates[gateFlag] = true;
25667
- try {
25668
- writeFileSync9(VOLATILE_STATE_FILE, JSON.stringify(state));
25669
- } catch (err) {
25670
- logFn(`ERROR \u2014 failed to write volatile state after advancement: ${err instanceof Error ? err.message : String(err)}`);
25671
- }
25672
- const durablePath = `${accountDir}/.claude/agent-create-state.json`;
25673
- try {
25674
- mkdirSync6(dirname2(durablePath), { recursive: true });
25675
- writeFileSync9(durablePath, JSON.stringify(state));
25676
- } catch (err) {
25677
- logFn(`WARNING \u2014 failed to write durable state after advancement: ${err instanceof Error ? err.message : String(err)}`);
25678
- }
25679
- const pending = Object.entries(gates).filter(([, v]) => !v).map(([k]) => k);
25680
- logFn(`${gateFlag} gate advanced (route-level). Pending: ${pending.length > 0 ? pending.join(", ") : "none"}`);
25681
- }
25682
25627
  async function POST20(req) {
25683
25628
  const contentType = req.headers.get("content-type") ?? "";
25684
25629
  let message;
@@ -25822,9 +25767,6 @@ async function POST20(req) {
25822
25767
  if (isComponentDone(parsed)) {
25823
25768
  const componentLog = agentLogStream("component-lifecycle", account.accountDir);
25824
25769
  const ts = (/* @__PURE__ */ new Date()).toISOString().slice(11, 23);
25825
- const logFn = (msg) => componentLog.write(`[${ts}] [agent-creation-approval:route] ${msg}
25826
- `);
25827
- advanceGateIfNeeded(parsed, account.accountDir, logFn);
25828
25770
  message = transformComponentDone(parsed);
25829
25771
  componentLog.write(`[${ts}] [component:${parsed.component}] componentDone \u2192 transformed
25830
25772
  `);
@@ -25942,7 +25884,7 @@ async function POST21(req) {
25942
25884
  }
25943
25885
 
25944
25886
  // app/api/admin/logs/route.ts
25945
- import { existsSync as existsSync15, readdirSync as readdirSync3, readFileSync as readFileSync15, statSync as statSync3 } from "fs";
25887
+ import { existsSync as existsSync14, readdirSync as readdirSync3, readFileSync as readFileSync14, statSync as statSync3 } from "fs";
25946
25888
  import { resolve as resolve12, basename as basename3 } from "path";
25947
25889
  var TAIL_BYTES = 8192;
25948
25890
  async function GET4(request) {
@@ -25958,7 +25900,7 @@ async function GET4(request) {
25958
25900
  if (!dir) continue;
25959
25901
  const filePath = resolve12(dir, safe);
25960
25902
  try {
25961
- const content = readFileSync15(filePath, "utf-8");
25903
+ const content = readFileSync14(filePath, "utf-8");
25962
25904
  const headers = { "Content-Type": "text/plain; charset=utf-8" };
25963
25905
  if (download) headers["Content-Disposition"] = `attachment; filename="${safe}"`;
25964
25906
  return new Response(content, { headers });
@@ -25984,7 +25926,7 @@ async function GET4(request) {
25984
25926
  if (!dir) continue;
25985
25927
  const filePath = resolve12(dir, fileName);
25986
25928
  try {
25987
- const content = readFileSync15(filePath, "utf-8");
25929
+ const content = readFileSync14(filePath, "utf-8");
25988
25930
  const headers = { "Content-Type": "text/plain; charset=utf-8" };
25989
25931
  if (download) headers["Content-Disposition"] = `attachment; filename="${fileName}"`;
25990
25932
  return new Response(content, { headers });
@@ -25996,7 +25938,7 @@ async function GET4(request) {
25996
25938
  const seen = /* @__PURE__ */ new Set();
25997
25939
  const logs = {};
25998
25940
  for (const dir of [accountLogDir, LOG_DIR]) {
25999
- if (!dir || !existsSync15(dir)) continue;
25941
+ if (!dir || !existsSync14(dir)) continue;
26000
25942
  let files;
26001
25943
  try {
26002
25944
  files = readdirSync3(dir).filter((f) => f.endsWith(".log"));
@@ -26006,7 +25948,7 @@ async function GET4(request) {
26006
25948
  files.filter((f) => !seen.has(f)).map((f) => ({ name: f, mtime: statSync3(resolve12(dir, f)).mtimeMs })).sort((a, b) => b.mtime - a.mtime).forEach(({ name }) => {
26007
25949
  seen.add(name);
26008
25950
  try {
26009
- const content = readFileSync15(resolve12(dir, name));
25951
+ const content = readFileSync14(resolve12(dir, name));
26010
25952
  const tail = content.length > TAIL_BYTES ? content.subarray(content.length - TAIL_BYTES).toString("utf-8") : content.toString("utf-8");
26011
25953
  logs[name] = tail.trim() || "(empty)";
26012
25954
  } catch {
@@ -26041,7 +25983,7 @@ async function GET5() {
26041
25983
 
26042
25984
  // app/api/admin/attachment/[attachmentId]/route.ts
26043
25985
  import { readFile as readFile3, readdir } from "fs/promises";
26044
- import { existsSync as existsSync16 } from "fs";
25986
+ import { existsSync as existsSync15 } from "fs";
26045
25987
  import { resolve as resolve13 } from "path";
26046
25988
  async function GET6(req, attachmentId) {
26047
25989
  const sessionKey = new URL(req.url).searchParams.get("session_key") ?? "";
@@ -26056,11 +25998,11 @@ async function GET6(req, attachmentId) {
26056
25998
  return new Response("Not found", { status: 404 });
26057
25999
  }
26058
26000
  const dir = resolve13(ATTACHMENTS_ROOT, accountId, attachmentId);
26059
- if (!existsSync16(dir)) {
26001
+ if (!existsSync15(dir)) {
26060
26002
  return new Response("Not found", { status: 404 });
26061
26003
  }
26062
26004
  const metaPath = resolve13(dir, `${attachmentId}.meta.json`);
26063
- if (!existsSync16(metaPath)) {
26005
+ if (!existsSync15(metaPath)) {
26064
26006
  return new Response("Not found", { status: 404 });
26065
26007
  }
26066
26008
  let meta3;
@@ -26086,7 +26028,7 @@ async function GET6(req, attachmentId) {
26086
26028
  }
26087
26029
 
26088
26030
  // app/api/admin/account/route.ts
26089
- import { readFileSync as readFileSync16, writeFileSync as writeFileSync10 } from "fs";
26031
+ import { readFileSync as readFileSync15, writeFileSync as writeFileSync9 } from "fs";
26090
26032
  import { resolve as resolve14 } from "path";
26091
26033
  var VALID_CONTEXT_MODES = ["managed", "claude-code"];
26092
26034
  async function PATCH(req) {
@@ -26112,10 +26054,10 @@ async function PATCH(req) {
26112
26054
  }
26113
26055
  const configPath2 = resolve14(account.accountDir, "account.json");
26114
26056
  try {
26115
- const raw2 = readFileSync16(configPath2, "utf-8");
26057
+ const raw2 = readFileSync15(configPath2, "utf-8");
26116
26058
  const config2 = JSON.parse(raw2);
26117
26059
  config2.contextMode = contextMode;
26118
- writeFileSync10(configPath2, JSON.stringify(config2, null, 2) + "\n", "utf-8");
26060
+ writeFileSync9(configPath2, JSON.stringify(config2, null, 2) + "\n", "utf-8");
26119
26061
  console.error(`[account-update] contextMode=${contextMode}`);
26120
26062
  return Response.json({ ok: true, contextMode });
26121
26063
  } catch (err) {
@@ -26126,14 +26068,14 @@ async function PATCH(req) {
26126
26068
 
26127
26069
  // app/api/admin/agents/route.ts
26128
26070
  import { resolve as resolve15 } from "path";
26129
- import { readdirSync as readdirSync4, readFileSync as readFileSync17, existsSync as existsSync17 } from "fs";
26071
+ import { readdirSync as readdirSync4, readFileSync as readFileSync16, existsSync as existsSync16 } from "fs";
26130
26072
  async function GET7() {
26131
26073
  const account = resolveAccount();
26132
26074
  if (!account) {
26133
26075
  return Response.json({ agents: [] });
26134
26076
  }
26135
26077
  const agentsDir = resolve15(account.accountDir, "agents");
26136
- if (!existsSync17(agentsDir)) {
26078
+ if (!existsSync16(agentsDir)) {
26137
26079
  return Response.json({ agents: [] });
26138
26080
  }
26139
26081
  const agents = [];
@@ -26143,9 +26085,9 @@ async function GET7() {
26143
26085
  if (!entry.isDirectory()) continue;
26144
26086
  if (entry.name === "admin") continue;
26145
26087
  const configPath2 = resolve15(agentsDir, entry.name, "config.json");
26146
- if (!existsSync17(configPath2)) continue;
26088
+ if (!existsSync16(configPath2)) continue;
26147
26089
  try {
26148
- const config2 = JSON.parse(readFileSync17(configPath2, "utf-8"));
26090
+ const config2 = JSON.parse(readFileSync16(configPath2, "utf-8"));
26149
26091
  agents.push({
26150
26092
  slug: entry.name,
26151
26093
  displayName: config2.displayName ?? entry.name,
@@ -26163,15 +26105,15 @@ async function GET7() {
26163
26105
  }
26164
26106
 
26165
26107
  // app/api/admin/version/route.ts
26166
- import { readFileSync as readFileSync18, existsSync as existsSync18 } from "fs";
26108
+ import { readFileSync as readFileSync17, existsSync as existsSync17 } from "fs";
26167
26109
  import { resolve as resolve16, join as join7 } from "path";
26168
26110
  var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT ?? resolve16(process.cwd(), "..");
26169
26111
  var brandHostname = "maxy";
26170
26112
  var brandNpmPackage = "@rubytech/create-maxy";
26171
26113
  var brandJsonPath = join7(PLATFORM_ROOT7, "config", "brand.json");
26172
- if (existsSync18(brandJsonPath)) {
26114
+ if (existsSync17(brandJsonPath)) {
26173
26115
  try {
26174
- const brand = JSON.parse(readFileSync18(brandJsonPath, "utf-8"));
26116
+ const brand = JSON.parse(readFileSync17(brandJsonPath, "utf-8"));
26175
26117
  if (brand.hostname) brandHostname = brand.hostname;
26176
26118
  if (brand.npm?.packageName) brandNpmPackage = brand.npm.packageName;
26177
26119
  } catch {
@@ -26184,8 +26126,8 @@ var CACHE_TTL_MS = 60 * 60 * 1e3;
26184
26126
  var FETCH_TIMEOUT_MS = 5e3;
26185
26127
  var latestCache = null;
26186
26128
  function readInstalled() {
26187
- if (!existsSync18(VERSION_FILE)) return "unknown";
26188
- const content = readFileSync18(VERSION_FILE, "utf-8").trim();
26129
+ if (!existsSync17(VERSION_FILE)) return "unknown";
26130
+ const content = readFileSync17(VERSION_FILE, "utf-8").trim();
26189
26131
  return content || "unknown";
26190
26132
  }
26191
26133
  async function fetchLatest() {
@@ -26239,15 +26181,15 @@ async function GET8() {
26239
26181
 
26240
26182
  // app/api/admin/version/upgrade/route.ts
26241
26183
  import { spawn as spawn4 } from "child_process";
26242
- import { existsSync as existsSync19, statSync as statSync4, writeFileSync as writeFileSync11, readFileSync as readFileSync19, openSync as openSync2, closeSync as closeSync2 } from "fs";
26184
+ import { existsSync as existsSync18, statSync as statSync4, writeFileSync as writeFileSync10, readFileSync as readFileSync18, openSync as openSync2, closeSync as closeSync2 } from "fs";
26243
26185
  import { resolve as resolve17, join as join8 } from "path";
26244
26186
  var PLATFORM_ROOT8 = process.env.MAXY_PLATFORM_ROOT ?? resolve17(process.cwd(), "..");
26245
26187
  var upgradePkg = "@rubytech/create-maxy";
26246
26188
  var upgradeHostname = "maxy";
26247
26189
  var brandPath = join8(PLATFORM_ROOT8, "config", "brand.json");
26248
- if (existsSync19(brandPath)) {
26190
+ if (existsSync18(brandPath)) {
26249
26191
  try {
26250
- const brand = JSON.parse(readFileSync19(brandPath, "utf-8"));
26192
+ const brand = JSON.parse(readFileSync18(brandPath, "utf-8"));
26251
26193
  if (brand.npm?.packageName) upgradePkg = brand.npm.packageName;
26252
26194
  if (brand.hostname) upgradeHostname = brand.hostname;
26253
26195
  } catch {
@@ -26257,7 +26199,7 @@ var LOCK_FILE = `/tmp/${upgradeHostname}-upgrade.lock`;
26257
26199
  var LOG_FILE = `/tmp/${upgradeHostname}-upgrade.log`;
26258
26200
  var LOCK_MAX_AGE_MS = 3 * 60 * 1e3;
26259
26201
  function isLockFresh() {
26260
- if (!existsSync19(LOCK_FILE)) return false;
26202
+ if (!existsSync18(LOCK_FILE)) return false;
26261
26203
  try {
26262
26204
  const stat3 = statSync4(LOCK_FILE);
26263
26205
  return Date.now() - stat3.mtimeMs < LOCK_MAX_AGE_MS;
@@ -26283,7 +26225,7 @@ async function POST22(req) {
26283
26225
  return Response.json({ ok: false, error: "upgrade already in progress" }, { status: 409 });
26284
26226
  }
26285
26227
  try {
26286
- writeFileSync11(LOCK_FILE, String(Date.now()));
26228
+ writeFileSync10(LOCK_FILE, String(Date.now()));
26287
26229
  } catch (err) {
26288
26230
  console.error("[admin/version/upgrade] failed to write lock file:", err);
26289
26231
  }
@@ -26413,12 +26355,12 @@ async function POST23() {
26413
26355
  var PLATFORM_ROOT9 = process.env.MAXY_PLATFORM_ROOT || "";
26414
26356
  var BRAND_JSON_PATH = PLATFORM_ROOT9 ? join9(PLATFORM_ROOT9, "config", "brand.json") : "";
26415
26357
  var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", domain: "getmaxy.com" };
26416
- if (BRAND_JSON_PATH && !existsSync20(BRAND_JSON_PATH)) {
26358
+ if (BRAND_JSON_PATH && !existsSync19(BRAND_JSON_PATH)) {
26417
26359
  console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
26418
26360
  }
26419
- if (BRAND_JSON_PATH && existsSync20(BRAND_JSON_PATH)) {
26361
+ if (BRAND_JSON_PATH && existsSync19(BRAND_JSON_PATH)) {
26420
26362
  try {
26421
- const parsed = JSON.parse(readFileSync20(BRAND_JSON_PATH, "utf-8"));
26363
+ const parsed = JSON.parse(readFileSync19(BRAND_JSON_PATH, "utf-8"));
26422
26364
  BRAND = { ...BRAND, ...parsed };
26423
26365
  } catch (err) {
26424
26366
  console.error(`[brand] Failed to parse brand.json: ${err.message}`);
@@ -26433,8 +26375,8 @@ var brandLoginOpts = {
26433
26375
  var ALIAS_DOMAINS_PATH = join9(homedir3(), BRAND.configDir, "alias-domains.json");
26434
26376
  function loadAliasDomains() {
26435
26377
  try {
26436
- if (!existsSync20(ALIAS_DOMAINS_PATH)) return null;
26437
- const parsed = JSON.parse(readFileSync20(ALIAS_DOMAINS_PATH, "utf-8"));
26378
+ if (!existsSync19(ALIAS_DOMAINS_PATH)) return null;
26379
+ const parsed = JSON.parse(readFileSync19(ALIAS_DOMAINS_PATH, "utf-8"));
26438
26380
  if (!Array.isArray(parsed)) {
26439
26381
  console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
26440
26382
  return null;
@@ -26805,14 +26747,14 @@ app.get("/agent-assets/:slug/:filename", (c) => {
26805
26747
  console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
26806
26748
  return c.text("Forbidden", 403);
26807
26749
  }
26808
- if (!existsSync20(filePath)) {
26750
+ if (!existsSync19(filePath)) {
26809
26751
  console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
26810
26752
  return c.text("Not found", 404);
26811
26753
  }
26812
26754
  const ext = "." + filename.split(".").pop()?.toLowerCase();
26813
26755
  const contentType = IMAGE_MIME[ext] || "application/octet-stream";
26814
26756
  console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
26815
- const body = readFileSync20(filePath);
26757
+ const body = readFileSync19(filePath);
26816
26758
  return c.body(body, 200, {
26817
26759
  "Content-Type": contentType,
26818
26760
  "Cache-Control": "public, max-age=3600"
@@ -26821,9 +26763,9 @@ app.get("/agent-assets/:slug/:filename", (c) => {
26821
26763
  var htmlCache = /* @__PURE__ */ new Map();
26822
26764
  var brandLogoPath = "/brand/maxy-monochrome.png";
26823
26765
  var brandIconPath = "/brand/maxy-monochrome.png";
26824
- if (BRAND_JSON_PATH && existsSync20(BRAND_JSON_PATH)) {
26766
+ if (BRAND_JSON_PATH && existsSync19(BRAND_JSON_PATH)) {
26825
26767
  try {
26826
- const fullBrand = JSON.parse(readFileSync20(BRAND_JSON_PATH, "utf-8"));
26768
+ const fullBrand = JSON.parse(readFileSync19(BRAND_JSON_PATH, "utf-8"));
26827
26769
  if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
26828
26770
  brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
26829
26771
  } catch {
@@ -26840,7 +26782,7 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
26840
26782
  function cachedHtml(file2) {
26841
26783
  let html = htmlCache.get(file2);
26842
26784
  if (!html) {
26843
- html = readFileSync20(resolve18(process.cwd(), "public", file2), "utf-8");
26785
+ html = readFileSync19(resolve18(process.cwd(), "public", file2), "utf-8");
26844
26786
  html = html.replace("</head>", `${brandScript}
26845
26787
  </head>`);
26846
26788
  htmlCache.set(file2, html);
@@ -26852,13 +26794,13 @@ function loadBrandingCache(agentSlug) {
26852
26794
  const configDir2 = join9(homedir3(), BRAND.configDir);
26853
26795
  try {
26854
26796
  const accountJsonPath = join9(configDir2, "account.json");
26855
- if (!existsSync20(accountJsonPath)) return null;
26856
- const account = JSON.parse(readFileSync20(accountJsonPath, "utf-8"));
26797
+ if (!existsSync19(accountJsonPath)) return null;
26798
+ const account = JSON.parse(readFileSync19(accountJsonPath, "utf-8"));
26857
26799
  const accountId = account.accountId;
26858
26800
  if (!accountId) return null;
26859
26801
  const cachePath = join9(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
26860
- if (!existsSync20(cachePath)) return null;
26861
- return JSON.parse(readFileSync20(cachePath, "utf-8"));
26802
+ if (!existsSync19(cachePath)) return null;
26803
+ return JSON.parse(readFileSync19(cachePath, "utf-8"));
26862
26804
  } catch {
26863
26805
  return null;
26864
26806
  }
@@ -26867,8 +26809,8 @@ function resolveDefaultSlug() {
26867
26809
  try {
26868
26810
  const configDir2 = join9(homedir3(), BRAND.configDir);
26869
26811
  const accountJsonPath = join9(configDir2, "account.json");
26870
- if (!existsSync20(accountJsonPath)) return null;
26871
- const account = JSON.parse(readFileSync20(accountJsonPath, "utf-8"));
26812
+ if (!existsSync19(accountJsonPath)) return null;
26813
+ const account = JSON.parse(readFileSync19(accountJsonPath, "utf-8"));
26872
26814
  return account.defaultAgent || null;
26873
26815
  } catch {
26874
26816
  return null;
@@ -1,161 +0,0 @@
1
- #!/usr/bin/env bash
2
- # UserPromptSubmit hook — agent creation gate advancement.
3
- #
4
- # Advances gates when the user approves a component during agent creation.
5
- # The UI wraps all component completions in a _componentDone envelope:
6
- #
7
- # {"_componentDone": true, "component": "<name>", "payload": "<original msg>"}
8
- #
9
- # Gate advancement rules:
10
- # _componentDone + component="document-editor" + payload.filePath matching
11
- # agents/{non-admin}/SOUL.md → gates.soul = true
12
- # _componentDone + component="document-editor" + payload.filePath matching
13
- # agents/{non-admin}/KNOWLEDGE.md → gates.knowledge = true
14
- # _componentDone + component="form" (during active creation) → gates.config = true
15
- #
16
- # This hook fires on the user's response (after they click Approve or Submit),
17
- # not when the agent renders a component. Gate enforcement (blocking writes)
18
- # is handled by agent-creation-gate.sh (PreToolUse).
19
- #
20
- # Exit 0 always — UserPromptSubmit hooks must never block user messages
21
- # for gate advancement purposes.
22
- # Env: AGENT_CREATE_STATE_FILE (override volatile path, for testing)
23
- # ACCOUNT_DIR (account directory for durable state)
24
- # GATE_LOG_FILE (override log path, for testing)
25
-
26
- if [ -n "$GATE_LOG_FILE" ]; then
27
- GATE_LOG="$GATE_LOG_FILE"
28
- elif [ -n "$ACCOUNT_DIR" ]; then
29
- mkdir -p "$ACCOUNT_DIR/logs"
30
- GATE_LOG="$ACCOUNT_DIR/logs/maxy-gate.log"
31
- else
32
- # UserPromptSubmit hooks must never block — exit 0 on misconfiguration
33
- exit 0
34
- fi
35
- STATE_FILE="${AGENT_CREATE_STATE_FILE:-/tmp/maxy-agent-create-state.json}"
36
- ACCOUNT_DIR="${ACCOUNT_DIR:-}"
37
- DURABLE_STATE="${ACCOUNT_DIR:+$ACCOUNT_DIR/.claude/agent-create-state.json}"
38
-
39
- # Fast path: no state file → no agent creation in progress, skip stdin read entirely
40
- [[ -f "$STATE_FILE" ]] || exit 0
41
-
42
- # Read stdin (user prompt JSON from Claude Code hook protocol)
43
- INPUT=$(cat)
44
-
45
- export _HOOK_INPUT="$INPUT"
46
- export _STATE_FILE="$STATE_FILE"
47
- export _DURABLE_STATE="${DURABLE_STATE:-}"
48
- export _GATE_LOG="$GATE_LOG"
49
-
50
- python3 -c '
51
- import os, sys, json, re
52
-
53
- hook_input_raw = os.environ.get("_HOOK_INPUT", "")
54
- state_file = os.environ.get("_STATE_FILE", "/tmp/maxy-agent-create-state.json")
55
- durable_file = os.environ.get("_DURABLE_STATE", "")
56
- log_file = os.environ.get("_GATE_LOG", "/tmp/maxy-gate.log")
57
-
58
- def log(msg):
59
- try:
60
- with open(log_file, "a") as lf:
61
- lf.write("agent-creation-approval: %s\n" % msg)
62
- except OSError:
63
- pass
64
-
65
- # Parse hook input — UserPromptSubmit provides {"prompt": "..."} on stdin
66
- try:
67
- hook_input = json.loads(hook_input_raw)
68
- except Exception as e:
69
- log("ERROR — failed to parse UserPromptSubmit input: %s" % e)
70
- sys.exit(0)
71
-
72
- if not isinstance(hook_input, dict):
73
- sys.exit(0)
74
-
75
- prompt = hook_input.get("prompt", "")
76
- if not isinstance(prompt, str) or not prompt:
77
- sys.exit(0)
78
-
79
- # --- Detect _componentDone wrapper ---
80
- try:
81
- msg = json.loads(prompt)
82
- except (json.JSONDecodeError, TypeError):
83
- # Not JSON — not a wrapped component message, nothing to advance
84
- sys.exit(0)
85
-
86
- if not isinstance(msg, dict) or not msg.get("_componentDone"):
87
- sys.exit(0)
88
-
89
- component = msg.get("component", "")
90
- payload_raw = msg.get("payload", "")
91
-
92
- # --- Determine which gate to advance ---
93
- gate_flag = ""
94
- agent_slug = ""
95
-
96
- if component == "document-editor":
97
- # Parse payload for filePath
98
- try:
99
- payload = json.loads(payload_raw) if isinstance(payload_raw, str) else payload_raw
100
- except (json.JSONDecodeError, TypeError):
101
- payload = {}
102
- if isinstance(payload, dict):
103
- file_path = payload.get("filePath", "")
104
- if isinstance(file_path, str):
105
- m = re.search(r"agents/([^/]+)/(SOUL\.md|KNOWLEDGE\.md)$", file_path)
106
- if m and m.group(1) != "admin":
107
- agent_slug = m.group(1)
108
- gate_flag = "soul" if m.group(2) == "SOUL.md" else "knowledge"
109
-
110
- elif component == "form":
111
- # Any form submission during active creation advances the config gate.
112
- # The state file existing is sufficient context — the workflow presents
113
- # exactly one form (the agent config form) during creation.
114
- gate_flag = "config"
115
-
116
- if not gate_flag:
117
- sys.exit(0)
118
-
119
- # --- Read and advance the gate ---
120
- try:
121
- with open(state_file) as f:
122
- state = json.load(f)
123
- except Exception as e:
124
- log("ERROR — failed to read state file for advancement: %s" % e)
125
- sys.exit(0)
126
-
127
- if not isinstance(state, dict) or not isinstance(state.get("gates"), dict):
128
- log("WARNING — state file has unexpected structure, skipping advancement")
129
- sys.exit(0)
130
-
131
- # Populate slug from document-editor filePath if empty
132
- if not state.get("slug") and agent_slug:
133
- state["slug"] = agent_slug
134
-
135
- # Already advanced — idempotent
136
- if state["gates"].get(gate_flag) is True:
137
- sys.exit(0)
138
-
139
- state["gates"][gate_flag] = True
140
-
141
- # Write updated state
142
- try:
143
- with open(state_file, "w") as f:
144
- json.dump(state, f)
145
- except OSError as e:
146
- log("ERROR — failed to write volatile state after advancement: %s" % e)
147
-
148
- if durable_file:
149
- try:
150
- durable_dir = os.path.dirname(durable_file)
151
- os.makedirs(durable_dir, exist_ok=True)
152
- with open(durable_file, "w") as f:
153
- json.dump(state, f)
154
- except OSError as e:
155
- log("WARNING — failed to write durable state after advancement: %s" % e)
156
-
157
- pending = [k for k, v in state.get("gates", {}).items() if not v]
158
- log("%s gate advanced (user approval detected). Pending: %s" % (gate_flag, ", ".join(pending) if pending else "none"))
159
- ' 2>>"$GATE_LOG" || true
160
-
161
- exit 0