@com55/node-red-ai-access 0.2.0-beta.6 → 0.2.0-beta.8

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/dist/plugin.cjs CHANGED
@@ -1,15 +1,15 @@
1
- "use strict";var kl=Object.create;var gn=Object.defineProperty;var Al=Object.getOwnPropertyDescriptor;var Sl=Object.getOwnPropertyNames;var xl=Object.getPrototypeOf,Tl=Object.prototype.hasOwnProperty;var a=(e,t)=>gn(e,"name",{value:t,configurable:!0});var U=(e,t)=>()=>(e&&(t=e(e=0)),t);var Os=(e,t)=>{for(var n in t)gn(e,n,{get:t[n],enumerable:!0})},$s=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of Sl(t))!Tl.call(e,o)&&o!==n&&gn(e,o,{get:()=>t[o],enumerable:!(r=Al(t,o))||r.enumerable});return e};var yn=(e,t,n)=>(n=e!=null?kl(xl(e)):{},$s(t||!e||!e.__esModule?gn(n,"default",{value:e,enumerable:!0}):n,e)),Rl=e=>$s(gn({},"__esModule",{value:!0}),e);function Je(e,t,n){let r=n.pretty?`${JSON.stringify(e,null,2)}
1
+ "use strict";var tu=Object.create;var Jn=Object.defineProperty;var nu=Object.getOwnPropertyDescriptor;var ru=Object.getOwnPropertyNames;var ou=Object.getPrototypeOf,su=Object.prototype.hasOwnProperty;var a=(e,t)=>Jn(e,"name",{value:t,configurable:!0});var q=(e,t)=>()=>(e&&(t=e(e=0)),t);var Qi=(e,t)=>{for(var n in t)Jn(e,n,{get:t[n],enumerable:!0})},ea=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of ru(t))!su.call(e,o)&&o!==n&&Jn(e,o,{get:()=>t[o],enumerable:!(r=nu(t,o))||r.enumerable});return e};var Vn=(e,t,n)=>(n=e!=null?tu(ou(e)):{},ea(t||!e||!e.__esModule?Jn(n,"default",{value:e,enumerable:!0}):n,e)),iu=e=>ea(Jn({},"__esModule",{value:!0}),e);function ht(e,t,n){let r=n.pretty?`${JSON.stringify(e,null,2)}
2
2
  `:`${JSON.stringify(e)}
3
- `,o=Buffer.from(r,"utf8");if(o.byteLength>t)throw new Y(n.kind,o.byteLength,t);return{bytes:o}}function Ds(e,t){let n=(0,wn.resolve)(t),r=(0,wn.resolve)(e);if(r!==n&&!r.startsWith(n+wn.sep))throw new Error("Path escapes .ai-access storage root");return r}function Ot(e,t,n){let r=Ds(t,e),o=`${r}.${process.pid}.tmp`;(0,Be.writeFileSync)(o,n.bytes,{mode:er}),(0,Be.renameSync)(o,r),(0,Be.chmodSync)(r,er)}function js(e,t,n){let r=Ds(t,e);(0,Be.appendFileSync)(r,n.bytes,{mode:er});try{(0,Be.chmodSync)(r,er)}catch{}}var Be,wn,ko,er,H,Ms,Y,G=U(()=>{"use strict";Be=require("fs"),wn=require("path"),ko=448,er=384,H=15e5,Ms=8192,Y=class extends Error{constructor(n,r,o){super(`${n} size ${r} bytes exceeds limit of ${o} bytes`);this.kind=n;this.size=r;this.maxBytes=o;this.name="StoragePayloadTooLargeError"}static{a(this,"StoragePayloadTooLargeError")}code="payload_too_large";statusCode=413};a(Je,"encodeTrustedJson");a(Ds,"assertUnderAiAccess");a(Ot,"writeTrustedAtomic");a(js,"appendTrusted")});function vn(e){if(typeof e!="string"||e.length===0||e.length>2048)return;let t;try{t=new URL(e)}catch{return}if(!(t.protocol!=="http:"&&t.protocol!=="https:")&&!(t.username||t.password||t.search||t.hash)&&!(t.pathname!=="/"||/^[a-z][a-z0-9+.-]*:\/\/[^/]*\//i.test(e)))return t.origin.toLowerCase()}function Ll(e,t){let n=e.getHeader("Vary"),r=String(n??"").split(",").map(o=>o.trim()).filter(Boolean);r.some(o=>o.toLowerCase()===t.toLowerCase())||r.push(t),e.setHeader("Vary",r.join(", "))}function Us(e){return(t,n,r)=>{if(!_l.test(t.path)){r();return}for(let c of n.getHeaderNames())c.startsWith("access-control-")&&n.removeHeader(c);let o=n.setHeader.bind(n),s=!1;n.setHeader=((c,u)=>!s&&/^access-control-/i.test(c)?n:o(c,u));let i=vn(t.get("origin")),d=i===void 0?void 0:e().find(c=>c===i),l=d!==void 0;if(Ll(n,"Origin"),!l){if(t.method==="OPTIONS"&&t.get("access-control-request-method")){n.status(204).end();return}r();return}if(s=!0,n.setHeader("Access-Control-Allow-Origin",d),n.setHeader("Access-Control-Expose-Headers",Cl),t.method==="OPTIONS"&&t.get("access-control-request-method")){n.setHeader("Access-Control-Allow-Methods",Pl),n.setHeader("Access-Control-Allow-Headers",El),n.setHeader("Access-Control-Max-Age",String(600)),n.status(204).end();return}s=!1,r()}}function Bs(e,t){let n=e.parent,r=n&&typeof n.use=="function"?n:e;r.use(t);let o=r,i=(o._router??o.router)?.stack;if(!Array.isArray(i)||i.length<2)return;let d=i.pop(),l=0;for(;l<i.length;){let c=i[l].name;if(c!=="query"&&c!=="expressInit")break;l+=1}i.splice(l,0,d)}var _l,Pl,El,Cl,rr=U(()=>{"use strict";_l=/^\/mcp(?:\/?$|\/(?:a(?:\/|$)|api(?:\/|$)|rpc\/?$))/,Pl="GET, POST, DELETE, OPTIONS",El="Authorization, Content-Type, Accept, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID",Cl="Mcp-Session-Id, WWW-Authenticate, Retry-After, RateLimit, RateLimit-Policy, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset";a(vn,"canonicalOrigin");a(Ll,"appendVary");a(Us,"aiCorsMiddleware");a(Bs,"installAiCorsMiddleware")});function sr(e){return e.replace(/([a-z])([A-Z])/g,"$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1 $2").replace(/([A-Za-z])([0-9])/g,"$1 $2").replace(/([0-9])([A-Za-z])/g,"$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean).map(t=>t.toUpperCase())}function An(e){if(!Array.isArray(e))return;let t=[];for(let n of e){if(typeof n!="string")continue;let r=n.trim().toUpperCase();if(!(!or.test(r)||t.includes(r))&&(t.push(r),t.length>=kn))break}return t}var te,ct,or,kn,Sn=U(()=>{"use strict";te="[REDACTED]",ct=Object.freeze(["PASSWORD","PASSWD","PASS","PWD","PW","PASSPHRASE","SECRET","CLIENT_SECRET","TOKEN","API_KEY","APIKEY","KEY","PRIVATE","CREDENTIAL","AUTH","AUTHORIZATION","BEARER","SESSION","COOKIE","CONNECTION_STRING","CONN_STR","CONN","DSN"]),or=/^[A-Za-z0-9_-]{2,40}$/,kn=100;a(sr,"tokenizeName");a(An,"normalizeSecretNamePatterns")});function Nl(e){return e==="nodes"||e==="flows"||e==="full"}function ir(e,t){if(!e||typeof e!="object"||Array.isArray(e))return{};let n=e,r={};if(typeof n.enabled=="boolean"&&(r.enabled=n.enabled),typeof n.allowWithoutAdminAuth=="boolean"&&(r.allowWithoutAdminAuth=n.allowWithoutAdminAuth),typeof n.defaultLinkTtlSeconds=="number"&&Number.isFinite(n.defaultLinkTtlSeconds)&&n.defaultLinkTtlSeconds>0&&(r.defaultLinkTtlSeconds=Math.floor(n.defaultLinkTtlSeconds)),Nl(n.defaultDeploymentType)&&(r.defaultDeploymentType=n.defaultDeploymentType),typeof n.maxActiveLinks=="number"&&Number.isFinite(n.maxActiveLinks)&&n.maxActiveLinks>0&&(r.maxActiveLinks=Math.floor(n.maxActiveLinks)),typeof n.publicBaseUrl=="string")try{r.publicBaseUrl=Dt(n.publicBaseUrl)}catch{t?.(`AI Access: invalid publicBaseUrl value ${JSON.stringify(n.publicBaseUrl)} was dropped`)}if(Array.isArray(n.corsAllowedOrigins)){let o=new Set;for(let s of n.corsAllowedOrigins){let i=vn(s);i?o.add(i):t?.(`AI Access: invalid CORS origin value ${JSON.stringify(s)} was dropped`)}r.corsAllowedOrigins=[...o]}for(let o of["rateLimitWindowMs","rateLimitMaxPerIp","rateLimitMaxPerToken"]){let s=n[o],i=o==="rateLimitWindowMs"?100:1,d=o==="rateLimitWindowMs"?36e5:1e6;typeof s=="number"&&Number.isInteger(s)&&s>=i&&s<=d&&(r[o]=s)}if(n.secretNamePatterns!==void 0){let o=An(n.secretNamePatterns);o?(Array.isArray(n.secretNamePatterns)&&o.length<n.secretNamePatterns.length&&t?.("AI Access: invalid or duplicate secretNamePatterns entries were dropped"),r.secretNamePatterns=o):t?.("AI Access: secretNamePatterns must be a list of strings; ignored")}if(n.oauth&&typeof n.oauth=="object"&&!Array.isArray(n.oauth)){let o=n.oauth;r.oauth={},typeof o.enabled=="boolean"&&(r.oauth.enabled=o.enabled);for(let i of["accessTokenTtlSec","refreshIdleTtlSec","dcrMaxClients"]){let d=o[i];typeof d=="number"&&Number.isInteger(d)&&d>0&&d<=31536e3&&(r.oauth[i]=d)}let s=o.refreshMaxLifetimeSec;typeof s=="number"&&Number.isInteger(s)&&s>=0&&s<=31536e4&&(r.oauth.refreshMaxLifetimeSec=s)}return r}function Fl(e,t){if(!(0,Tn.existsSync)(e))return{};try{let n=(0,Tn.readFileSync)(e,"utf8");return ir(JSON.parse(n),t)}catch(n){return t?.(`AI Access: config.json is corrupt or unreadable; defaults are used (${String(n)})`),{}}}function ar(e,t){let n={},r={...xn};for(let s of Object.keys(xn).filter(i=>i!=="oauth"))n[s]=e[s]!==void 0?"settings.js":t[s]!==void 0?"disk":"default",Object.assign(r,{[s]:e[s]??t[s]??xn[s]});let o={...r,sources:n};return e.oauth!==void 0||t.oauth!==void 0?(o.oauth={...$t,...t.oauth??{},...e.oauth??{}},n.oauth=e.oauth!==void 0?"settings.js":"disk"):(o.oauth={...$t},n.oauth="default"),o}function xo(e,t){let n=ir(e??{},t);for(let o of Object.keys(xn).filter(s=>s!=="oauth"))e&&Object.hasOwn(e,o)&&n[o]===void 0&&Object.assign(n,{[o]:xn[o]});let r=e?.oauth;if(r&&typeof r=="object"&&!Array.isArray(r)){let o=n.oauth??={};for(let s of Object.keys($t))Object.hasOwn(r,s)&&o[s]===void 0&&(t?.(`AI Access: settings.js aiAccess.oauth.${s} is invalid; using the default (${String($t[s])})`),Object.assign(o,{[s]:$t[s]}))}return n}var Tn,So,Il,$t,xn,Mt,dr=U(()=>{"use strict";Tn=require("fs"),So=require("path");ce();rr();Sn();G();Il=16384,$t={enabled:!1,accessTokenTtlSec:3600,refreshIdleTtlSec:2592e3,refreshMaxLifetimeSec:7776e3,dcrMaxClients:5},xn={enabled:!0,allowWithoutAdminAuth:!1,defaultLinkTtlSeconds:14400,maxActiveLinks:10,defaultDeploymentType:"nodes",publicBaseUrl:"",corsAllowedOrigins:[],rateLimitWindowMs:6e4,rateLimitMaxPerIp:600,rateLimitMaxPerToken:300,secretNamePatterns:[...ct],oauth:{...$t}};a(Nl,"isDeploymentType");a(ir,"sanitizePartial");a(Fl,"readDiskFile");a(ar,"mergeAiAccessSettings");a(xo,"settingsJsPartial");Mt=class{constructor(t,n,r){this.paths=t;this.filePath=(0,So.join)(t.root,"config.json"),this.settingsJs=xo(n,r),this.disk=Fl((0,Tn.existsSync)(this.filePath)?this.filePath:(0,So.join)(t.root,"settings.json"),r),this.cached=ar(this.settingsJs,this.disk)}static{a(this,"SettingsStore")}filePath;disk;settingsJs;cached;getEffective(){return this.cached}getDisk(){return{...this.disk}}update(t){let n=ir(t);for(let r of Object.keys(this.settingsJs))delete n[r];return this.persist(n),this.disk=n,this.cached=ar(this.settingsJs,this.disk),this.cached}patch(t,n=[]){let r=ir(t),o={...this.disk,...r,...r.oauth?{oauth:{...this.disk.oauth,...r.oauth}}:{}};for(let s of n)delete o[s];return this.update(o)}persist(t){let n=Je(t,Il,{kind:"settings",pretty:!0});Ot(this.paths.root,this.filePath,n)}}});function cr(e){lr=e}function qs(){return lr}function z(e){return e.adminAuth!=null&&e.adminAuth!==!1}function ve(e){return lr?lr.getEffective():ar(xo(e.aiAccess),{})}function ke(e){let t=ve(e);return t.enabled?z(e)?!0:t.allowWithoutAdminAuth:!1}var lr,ue=U(()=>{"use strict";dr();lr=null;a(cr,"bindSettingsStore");a(qs,"getSettingsStore");a(z,"isAdminAuthEnabled");a(ve,"resolveAiAccessSettings");a(ke,"areAiFeaturesEnabled")});function pe(e){let t=`/${e??""}`.replace(/\/{2,}/g,"/");for(;t.length>1&&t.endsWith("/");)t=t.slice(0,-1);return t}function fe(e){let t=pe(e);return t==="/"?"/mcp/api/v1":`${t}/mcp/api/v1`}function Rn(e){let t=pe(e);return t==="/"?"/ai-access":`${t}/ai-access`}function Ve(e){let t=pe(e);return t==="/"?"/mcp/rpc":`${t}/mcp/rpc`}function Ol(e){let t=e.authInfo?.scope;if(t)return Array.isArray(t)?t:[t];let n=e.user?.permissions;return n?Array.isArray(n)?n:[n]:[]}function Ye(e){if(e.auth)return e.auth.scopes.includes("propose")?"propose":e.auth.scopes.includes("read")?"read":null;let t=Ol(e);return t.includes("*")?"*":t.includes("read")?"read":null}function $l(e,t){return!t||!e?{view:!0,manageLinks:!1,approveProposals:!1}:e==="*"?{view:!0,manageLinks:!0,approveProposals:!0}:{view:!0,manageLinks:!1,approveProposals:!1}}function zs(e,t={error:"Authentication required",code:"auth_required"},n){let r=e.end.bind(e),o=!0;return e.end=((...s)=>o&&e.statusCode===401&&!e.headersSent?(o=!1,e.removeHeader("Content-Length"),e.setHeader("Content-Type","application/json; charset=utf-8"),n&&e.setHeader("WWW-Authenticate",n),r(JSON.stringify(t))):r(...s)),()=>{o=!1,e.end=r}}function Ae(e,t){let n="read";return(r,o,s)=>{if(!z(e.settings)){s();return}let i=t==="api"?zs(o):null;e.auth.needsPermission(n)(r,o,d=>{if(i?.(),d){s(d);return}if(!r.user){if(t==="page"){o.redirect(pe(e.settings.httpAdminRoot));return}o.status(401).json({error:"Authentication required",code:"auth_required"});return}if(!Ye(r)){o.status(401).json({error:"Insufficient permissions",code:"insufficient_permissions"});return}s()})}}function ur(e,t){let n=z(e.settings),r=ve(e.settings),o=ke(e.settings),s=null;return n?s=Ye(t):o&&(s="*"),{adminAuthEnabled:n,aiFeaturesEnabled:o,allowWithoutAdminAuth:r.allowWithoutAdminAuth,permission:s,capabilities:$l(s,o)}}function jt(e){return(t,n,r)=>{if(!ke(e.settings)){n.status(403).json({error:"AI Access features are disabled. Enable adminAuth, or set aiAccess.allowWithoutAdminAuth to true for local-only use.",code:"ai_features_disabled"});return}r()}}function _n(e){return(t,n,r)=>{if(!z(e.settings)){if(ke(e.settings)){r();return}n.status(403).json({error:"AI Access features are disabled",code:"ai_features_disabled"});return}if(Ye(t)!=="*"){n.status(403).json({error:"Full admin permission (*) is required for this action",code:"insufficient_permissions"});return}r()}}function Ml(e){return e.startsWith("aia_")}function Dl(e){return Ml(e)?!0:e.length<24}function jl(e){if(e.length<24||e.startsWith("aia_")&&e.length<40)return"The token may be truncated \u2014 ask the user for the full access-link token."}function Ws(e,t,n,r){if(!z(e.settings)){r();return}let o={error:me,code:Ie},s=zs(n,o,X);e.auth.needsPermission("read")(t,n,i=>{if(s(),i){r(i);return}if(!n.headersSent){if(!t.user||!Ye(t)){n.setHeader("WWW-Authenticate",X),n.status(401).json(o);return}r()}})}function Gs(e,t,n,r,o){let s=e.headers.authorization;if(typeof s!="string"||!/^Bearer\s+\S+/i.test(s.trim()))return"continue";let d=/^Bearer\s+(\S+)$/i.exec(s.trim())?.[1];if(!d)return"continue";let l=n(d);if(l.status==="active")return l.link.kind!==r?(Cn(t,r,l.link.kind),"wrong_kind"):(e.accessLink=l.link,"ok");if(l.status==="expired")return En(e,t,o,l.expiresAt),"invalid";if(Dl(d)){t.setHeader("WWW-Authenticate",X);let c=jl(d);return t.status(401).json({error:ut,code:Pn,...c?{hint:c}:{}}),"invalid"}return"continue"}function Ks(e,t,n="rest"){return(r,o,s)=>{if(!ke(e.settings)){o.status(403).json({error:"AI Access features are disabled. Enable adminAuth, or set aiAccess.allowWithoutAdminAuth to true for local-only use.",code:"ai_features_disabled"});return}let i=Gs(r,o,t,n,e.settings.httpAdminRoot);if(i==="ok"){s();return}i==="invalid"||i==="wrong_kind"||Ws(e,r,o,s)}}function Js(e,t,n="rest"){return(r,o,s)=>{let i=Gs(r,o,t,n,e.settings.httpAdminRoot);if(i==="ok"){s();return}i==="invalid"||i==="wrong_kind"||Ws(e,r,o,s)}}var Le=U(()=>{"use strict";pt();ue();a(pe,"normalizeHttpAdminRoot");a(fe,"mcpApiBase");a(Rn,"dashboardPath");a(Ve,"mcpRpcPath");a(Ol,"scopesFromRequest");a(Ye,"resolvePermission");a($l,"capabilitiesFor");a(zs,"interceptNeedsPermissionUnauthorized");a(Ae,"requireAdminSession");a(ur,"describeAccess");a(jt,"requireAiFeatures");a(_n,"requireStarPermission");a(Ml,"looksLikeAccessLinkToken");a(Dl,"looksLikeFailedAccessLinkAttempt");a(jl,"truncatedTokenHint");a(Ws,"continueAsAdminSessionOrAccessLinkMissing");a(Gs,"tryAccessLinkBearer");a(Ks,"requireAdminReadOrAccessLink");a(Js,"requireAdminSessionOrAccessLink")});function Hl(e,t){let n=pe(e);return`${n==="/"?"/mcp/a":`${n}/mcp/a`}/${t}`}function Ul(e){return!!e.app.get("trust proxy")}function Vs(e,t){let n=e.get(t);if(typeof n!="string"||n.length===0)return;let r=n.split(",")[0]?.trim();return r&&r.length>0?r:void 0}function Bl(e){if(typeof e!="string"||e.trim().length===0)return;let t;try{t=new URL(e.trim())}catch{return}if(!(t.protocol!=="http:"&&t.protocol!=="https:"))return`${t.origin}${t.pathname.replace(/\/+$/,"")}`}function ft(e){try{let n=new URL(`http://${e.includes(":")&&!e.startsWith("[")?`[${e}]`:e}`).hostname.replace(/^\[|\]$/g,"");if(!(0,To.isIP)(n))return;if(n.startsWith("::ffff:")){let r=n.slice(7);if((0,To.isIP)(r)===4)return r;let o=r.split(":");if(o.length===2){let s=parseInt(o[0],16)*65536+parseInt(o[1],16);return[24,16,8,0].map(i=>s>>>i&255).join(".")}}return n}catch{return}}function Dt(e){if(e==="")return"";let t="publicBaseUrl must be an HTTP(S) origin with no path, query, fragment, or credentials";if(typeof e!="string")throw new Error(t);let n;try{n=new URL(e.trim())}catch{throw new Error(t)}if(n.protocol!=="https:"&&n.protocol!=="http:"||!/^https?:\/\/[^/?#]+\/?$/i.test(e.trim())||n.pathname!=="/"||n.search||n.hash||n.username||n.password||e.includes("?")||e.includes("#"))throw new Error(t);return n.origin}function pr(e){Ro=Bl(e)}function Xs(e){if(typeof e!="string"||e.length===0||e.length>260)return!1;let t,n,r=/^(\[[^\]]*\])(?::(\d+))?$/.exec(e);if(r){if(t=r[1],n=r[2],!Wl.test(t))return!1;try{new URL(`http://${t}/`)}catch{return!1}}else{let o=/^([^:]+)(?::(\d+))?$/.exec(e);if(!o||(t=o[1],n=o[2],t.length>253)||!zl.test(t)&&!ql.test(t))return!1}if(n!==void 0){if(!/^\d{1,5}$/.test(n))return!1;let o=Number(n);if(o<1||o>65535)return!1}return!0}function Qs(e){let t=e?.trim().toLowerCase();return t==="http"||t==="https"?t:void 0}function Gl(e){let t=e.socket;return t?.encrypted?"https":t?"http":Qs(e.protocol)??"http"}function _o(e){if(Ro)return Ro;let t=Ul(e),n=t?Vs(e,"x-forwarded-host"):void 0,r=e.get("host")?.trim(),o=Xs(n)?n:Xs(r)?r:void 0;if(!o)return;let s=(t?Qs(Vs(e,"x-forwarded-proto")):void 0)??Gl(e);try{return new URL(`${s}://${o}`).origin}catch{return}}function Ln(e,t){let n=_o(e);return n?`${n}${t}`:t}function ei(e,t,n){return Ln(e,Hl(t,n))}function Po(e){return e.replace(/\/mcp\/a\/[^/?#]+/gi,"/mcp/a/<redacted>")}var To,Ht,Zs,Ro,Ys,ql,zl,Wl,ce=U(()=>{"use strict";To=require("net");Le();Ht=8192,Zs=1800*1e3;a(Hl,"mcpAccessLinkPath");a(Ul,"isTrustProxyEnabled");a(Vs,"firstForwardedValue");a(Bl,"normalizePublicBaseUrl");a(ft,"canonicalizeIp");a(Dt,"validateSettingsPublicBaseUrl");a(pr,"bindPublicBaseUrl");Ys="[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?",ql=new RegExp(`^${Ys}(?:\\.${Ys})*$`),zl=/^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/,Wl=/^\[[0-9A-Fa-f:.]+\]$/;a(Xs,"isValidHostHeader");a(Qs,"validScheme");a(Gl,"connectionScheme");a(_o,"requestPublicOrigin");a(Ln,"absolutizePath");a(ei,"buildAccessLinkUrl");a(Po,"redactAccessLinkTokenInPath")});function Zl(e){if(!e||e.trim().length===0)return{kind:"missing"};let t=e.trim();if(/^Basic\s+/i.test(t))return{kind:"basic"};let n=/^Bearer\s+(\S+)$/i.exec(t);return n?.[1]?{kind:"bearer",token:n[1]}:{kind:"other"}}function Eo(e,t,n,r){e.setHeader("WWW-Authenticate",X),e.status(401).json({error:t,code:n,...r?{hint:r}:{}})}function Ql(e){let t=e??"";for(;t.endsWith("/");)t=t.slice(0,-1);return`${t}/ai-access#links`}function ec(e,t,n,r){let o=Ln(e,Ql(t));return{error:Kl,code:Vl,...n?{expiredAt:n}:{},accessLinksUrl:o,hint:r??`Ask the user to create a new access link in Node-RED on the /ai-access dashboard \u2192 Access tokens (${o}).`}}function En(e,t,n,r,o){t.setHeader("WWW-Authenticate",X),t.status(401).json(ec(e,n,r,o))}function tc(e){if(e.length<24||e.startsWith("aia_")&&e.length<40)return"The token may be truncated \u2014 ask the user for the full access-link token."}function fr(e){return e==="get_link"?"Please open Node-RED \u2192 /ai-access \u2192 Access tokens and create a get_link access URL, then send me that /mcp/a/<token> link.":`Please open Node-RED \u2192 /ai-access \u2192 Access tokens and create a kind=${e} token, then send me the Bearer token (not a get_link path URL).`}function rc(e,t){let n=nc;return e==="get_link"?`This URL requires a get_link token. This token is kind=${t} \u2014 ${n} for a get_link access URL (/mcp/a/<token>/\u2026). What to tell the user: ${fr("get_link")}`:e==="rest"?`This REST endpoint requires a rest token. This token is kind=${t} \u2014 ${n}. What to tell the user: ${fr("rest")}`:`This MCP endpoint requires an mcp token. This token is kind=${t} \u2014 ${n}. What to tell the user: ${fr("mcp")}`}function Cn(e,t,n){e.setHeader("WWW-Authenticate",X),e.status(403).json({error:`Access link kind mismatch: expected ${t}, got ${n}`,code:Xl,expectedKind:t,actualKind:n,hint:rc(t,n),tellUser:fr(t)})}function Ut(e,t,n){return(r,o,s)=>{if(!ke(e.settings)){o.status(403).json({error:"AI Access features are disabled. Enable adminAuth, or set aiAccess.allowWithoutAdminAuth to true for local-only use.",code:"ai_features_disabled"});return}let i=Zl(r.headers.authorization);if(i.kind==="basic"){Eo(o,Jl,Yl);return}if(i.kind==="missing"||i.kind==="other"){Eo(o,me,Ie);return}let d=t.validateResult(i.token);if(d.status==="expired"){En(r,o,e.settings.httpAdminRoot,d.expiresAt);return}if(d.status!=="active"){Eo(o,ut,Pn,tc(i.token));return}if(d.link.kind!==n){Cn(o,n,d.link.kind);return}r.accessLink=d.link,s()}}var ut,Kl,me,Jl,X,Ie,Pn,Vl,Yl,Xl,nc,pt=U(()=>{"use strict";ce();ue();ut="Access link is invalid, revoked, or no longer available (it may have expired, or Node-RED may have restarted). Ask the user for a new access link.",Kl="Access link token has expired. Ask the user for a new access link.",me="Access link Bearer token is missing. Ask the user for a new access link and send Authorization: Bearer <token>.",Jl="Authorization must use the Bearer scheme (Authorization: Bearer <access-link-token>), not Basic. Ask the user for a new access link.",X='Bearer realm="node-red-ai-access", charset="UTF-8"',Ie="access_link_missing",Pn="access_link_invalid",Vl="token_expired",Yl="bearer_scheme_required",Xl="wrong_token_kind";a(Zl,"parseAuthorizationHeader");a(Eo,"sendUnauthorized");a(Ql,"accessLinksPagePath");a(ec,"tokenExpiredBody");a(En,"sendTokenExpired");a(tc,"truncatedBearerHint");nc="ask the owner: Node-RED /ai-access \u2192 Access tokens \u2192 create kind=rest/mcp (or get_link)";a(fr,"whatToTellUserWrongTokenKind");a(rc,"wrongTokenKindHint");a(Cn,"sendWrongTokenKind");a(Ut,"requireAccessLink")});var K,W,Se,Ne,hr,xe=U(()=>{"use strict";K=class extends Error{static{a(this,"FlowRevisionConflictError")}code="version_mismatch";statusCode=409;expectedRev;currentRev;context;constructor(t,n,r="updateFlow"){super(`Flow revision conflict in ${r}: expectedRev '${t}' does not match current rev '${n}'. The flows changed since you last read them \u2014 re-read flows, re-apply your change, and retry.`),this.name="FlowRevisionConflictError",this.expectedRev=t,this.currentRev=n,this.context=r}},W=class extends Error{static{a(this,"FlowNotFoundError")}code="flow_not_found";statusCode=404;flowId;constructor(t){super(`Flow '${t}' was not found`),this.name="FlowNotFoundError",this.flowId=t}},Se=class extends Error{static{a(this,"AdapterUnavailableError")}code="adapter_unavailable";statusCode=502;constructor(t){super(t),this.name="AdapterUnavailableError"}},Ne=class extends Error{static{a(this,"PluginMisconfiguredError")}code="plugin_misconfigured";statusCode=502;constructor(t){super(t),this.name="PluginMisconfiguredError"}},hr="Node-RED AI Access could not use the in-process flow runtime and the HTTP Admin API fallback returned Unauthorized. The plugin is misconfigured \u2014 tell the admin to ensure the plugin loads inside Node-RED so RED.runtime.flows is available (do not rely on unauthenticated localhost HTTP when adminAuth is enabled)."});function Fe(e,t){if(!e||!oc.test(e)||e.includes(".."))throw new Error(`Invalid ${t}`);return e}function gr(e){if(!e||!sc.test(e)||e.includes(".."))throw new Error("Invalid proposal id");return e}function Co(e,t){let n=(0,mt.basename)(t);if(!n||n!==t||n.includes(".."))throw new Error("Invalid file name");let r=(0,mt.resolve)(e),o=(0,mt.resolve)(r,n);if(o!==r&&!o.startsWith(r+mt.sep))throw new Error("Path escapes storage directory");return o}function ti(e,t=1e5){if(typeof e=="string")return e.replace(/\0/g,"").slice(0,t)}function ht(e){let t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},n={};for(let o of ic){if(!(o in t))continue;let s=t[o];if(o==="disabled"&&typeof s=="boolean"){n[o]=s;continue}if(o==="env"&&Array.isArray(s)){let d=JSON.stringify(s);d.length<=5e5&&(n[o]=JSON.parse(d));continue}let i=ti(s,1e4);i!==void 0&&(n[o]=i)}for(let o of["nodes","configs"]){let s=t[o];if(!Array.isArray(s))continue;let i=JSON.stringify(s),d=Buffer.byteLength(i,"utf8");if(d>H)throw new Y(`flow.${o}`,d,H);n[o]=JSON.parse(i)}let r=Buffer.byteLength(JSON.stringify(n),"utf8");if(r>H)throw new Y("flow",r,H);return n}function ni(e){let t={};for(let n of ac){if(!(n in e))continue;let r=e[n];if(dc.has(n)){if(typeof r=="number"&&Number.isFinite(r))t[n]=r;else if(typeof r=="string"&&r.trim()!==""){let s=Number(r);Number.isFinite(s)&&(t[n]=s)}continue}let o=ti(r,2e3);o!==void 0&&(t[n]=o)}return t}var mt,oc,sc,ic,ac,dc,yr=U(()=>{"use strict";mt=require("path");G();oc=/^[A-Za-z0-9_.-]+$/,sc=/^prop-[A-Za-z0-9_-]+$/,ic=["id","type","label","disabled","info","env"],ac=["proposalId","flowId","expectedRev","resultRev","reason","code","currentRev","title","displayName","flowLabel","linkId","label","expiresAt","createdAt","ttlSeconds","kind","redactions","restoredRedactions","operation"],dc=new Set(["ttlSeconds","redactions","restoredRedactions"]);a(Fe,"assertSafeSegment");a(gr,"assertSafeProposalId");a(Co,"resolveUnderDir");a(ti,"sanitizeString");a(ht,"sanitizeFlowTab");a(ni,"sanitizeAuditDetails")});function vr(e,t){return`oauth-${(0,br.createHash)("sha256").update(`${e}\0${t}`).digest("hex")}`}function gt(e){return e.kind==="oauth"?vr(e.clientId,e.user):e.linkId}function lc(e){return e==="pending"||e==="approved"||e==="rejected"||e==="failed"||e==="withdrawn"||e==="expired"||e==="superseded"}function uc(e){if(!Array.isArray(e)||e.length===0)return;let t=[];for(let n of e.slice(0,200)){if(!n||typeof n!="object")continue;let{nodeId:r,property:o}=n;typeof r!="string"||typeof o!="string"||t.push({nodeId:r.replace(/\0/g,"").slice(0,200),property:o.replace(/\0/g,"").slice(0,200)})}return t.length>0?t:void 0}function ri(e){if(!Array.isArray(e)||e.length===0)return;let t=[];for(let n of e.slice(0,cc)){if(!n||typeof n!="object"||Array.isArray(n))continue;let r=n;if(typeof r.code!="string"||!r.code||typeof r.error!="string"||!r.error)continue;let o={code:r.code.replace(/\0/g,"").slice(0,80),error:r.error.replace(/\0/g,"").slice(0,500)};typeof r.nodeId=="string"&&r.nodeId&&(o.nodeId=r.nodeId.replace(/\0/g,"").slice(0,200)),typeof r.type=="string"&&r.type&&(o.type=r.type.replace(/\0/g,"").slice(0,200)),typeof r.property=="string"&&r.property&&(o.property=r.property.replace(/\0/g,"").slice(0,200)),typeof r.configType=="string"&&r.configType&&(o.configType=r.configType.replace(/\0/g,"").slice(0,200)),Array.isArray(r.candidates)&&(o.candidates=r.candidates.filter(s=>typeof s=="string"&&s.length>0).slice(0,20).map(s=>s.replace(/\0/g,"").slice(0,200))),t.push(o)}return t.length>0?t:void 0}var br,Xe,oi,se,ne,Bt,In,cc,wr,Ze=U(()=>{"use strict";br=require("crypto"),Xe=require("fs"),oi=require("path");yr();G();a(vr,"oauthOwnerId");a(gt,"proposalOwnerId");se=class extends Error{static{a(this,"ProposalNotFoundError")}code="proposal_not_found";statusCode=404;constructor(t){super(`Proposal '${t}' was not found`),this.name="ProposalNotFoundError"}},ne=class extends Error{static{a(this,"ProposalNotPendingError")}code="proposal_not_pending";statusCode=409;constructor(t,n){super(`Proposal '${t}' is '${n}', not pending`),this.name="ProposalNotPendingError"}},Bt=class extends Error{static{a(this,"ProposalNotOwnedError")}code="proposal_not_owned";statusCode=403;constructor(t){super(`Proposal '${t}' was not created by this access link`),this.name="ProposalNotOwnedError"}},In=class extends Error{static{a(this,"ProposalConcurrentApproveError")}code="proposal_approve_in_progress";statusCode=409;constructor(t){super(`Proposal '${t}' is already being approved`),this.name="ProposalConcurrentApproveError"}};a(lc,"isProposalStatus");cc=50;a(uc,"sanitizeRestoredSecrets");a(ri,"sanitizeProposalWarnings");wr=class{constructor(t,n={}){this.paths=t;this.now=n.now??(()=>Date.now()),this.randomId=n.randomId??(()=>(0,br.randomBytes)(12).toString("hex")),this.maxProposalBytes=n.maxProposalBytes??H}static{a(this,"ProposalStore")}inFlight=new Set;now;randomId;maxProposalBytes;fileFor(t){let n;try{n=gr(t)}catch{throw new se(t)}return Co(this.paths.proposals,`${n}.json`)}toDiskRecord(t){let n=gr(t.id),r=Fe(String(t.expectedRev).slice(0,200),"expectedRev"),o=Fe(String(t.payload.flowId).slice(0,200),"flowId"),s=ht(t.payload.flow),i=t.baseFlow!==void 0?ht(t.baseFlow):void 0,d=t.createdBy.kind==="oauth"?{kind:"oauth",clientId:Fe(String(t.createdBy.clientId).slice(0,200),"clientId"),user:String(t.createdBy.user).replace(/\0/g,"").slice(0,200)}:{kind:"access-link",linkId:Fe(String(t.createdBy.linkId).slice(0,200),"linkId"),...typeof t.createdBy.label=="string"?{label:t.createdBy.label.replace(/\0/g,"").slice(0,200)}:{}},l={id:n,type:"replace_flow",status:lc(t.status)?t.status:"pending",expectedRev:r,payload:{flowId:o,flow:s},createdBy:d,createdAt:String(t.createdAt).slice(0,64),updatedAt:String(t.updatedAt).slice(0,64)};i!==void 0&&(l.baseFlow=i),typeof t.title=="string"&&t.title.length>0&&(l.title=t.title.replace(/\0/g,"").slice(0,80).trim(),l.title||delete l.title);let c=ri(t.warnings);c&&(l.warnings=c);let u=uc(t.restoredSecrets);if(u&&(l.restoredSecrets=u),t.decidedBy)if(t.decidedBy.kind==="access-link"){let p={kind:"access-link",linkId:Fe(String(t.decidedBy.linkId).slice(0,200),"linkId")};typeof t.decidedBy.label=="string"&&(p.label=t.decidedBy.label.replace(/\0/g,"").slice(0,200)),l.decidedBy=p}else{let p={kind:"admin"};typeof t.decidedBy.username=="string"&&(p.username=t.decidedBy.username.replace(/\0/g,"").slice(0,200)),l.decidedBy=p}return typeof t.reason=="string"&&(l.reason=t.reason.replace(/\0/g,"").slice(0,2e3)),typeof t.resultRev=="string"&&(l.resultRev=Fe(t.resultRev.slice(0,200),"resultRev")),l}writeAtomic(t,n){let r=this.fileFor(t),o=this.toDiskRecord(n),s=Je(o,this.maxProposalBytes,{kind:"proposal",pretty:!0});Ot(this.paths.root,r,s)}create(t){let n=new Date(this.now()).toISOString(),r=gr(`prop-${this.randomId()}`),o={id:r,type:"replace_flow",status:"pending",expectedRev:t.expectedRev,payload:{flowId:t.payload.flowId,flow:t.payload.flow},baseFlow:t.baseFlow,title:t.title,createdBy:t.createdBy,createdAt:n,updatedAt:n},s=ri(t.warnings);return s&&(o.warnings=s),t.restoredSecrets?.length&&(o.restoredSecrets=t.restoredSecrets),this.writeAtomic(r,o),this.get(r)}get(t){let n=this.fileFor(t);if(!(0,Xe.existsSync)(n))throw new se(t);return JSON.parse((0,Xe.readFileSync)(n,"utf8"))}list(t){if(!(0,Xe.existsSync)(this.paths.proposals))return[];let n=(0,Xe.readdirSync)(this.paths.proposals).filter(o=>o.endsWith(".json")),r=[];for(let o of n){let s=(0,oi.basename)(o);if(!(s!==o||s.includes("..")))try{let i=JSON.parse((0,Xe.readFileSync)(Co(this.paths.proposals,s),"utf8"));if(t?.linkId&&gt(i.createdBy)!==t.linkId||t?.status&&i.status!==t.status||t?.flowId&&i.payload.flowId!==t.flowId)continue;r.push(i)}catch{}}return r.sort((o,s)=>o.createdAt<s.createdAt?1:-1)}beginApprove(t){if(this.inFlight.has(t))throw new In(t);let n=this.get(t);if(n.status!=="pending")throw new ne(t,n.status);return this.inFlight.add(t),n}releaseLock(t){this.inFlight.delete(t)}markApproved(t,n,r){let o=this.get(t);return o.status="approved",o.decidedBy=n,o.resultRev=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markRejected(t,n,r){let o=this.get(t);return o.status="rejected",n&&(o.decidedBy=n),o.reason=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markFailed(t,n,r){let o=this.get(t);return o.status="failed",n&&(o.decidedBy=n),o.reason=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markWithdrawn(t,n,r){let o=this.get(t);if(o.status!=="pending")throw new ne(t,o.status);return o.status="withdrawn",o.decidedBy=n,o.reason=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markClosed(t,n,r,o){let s=this.get(t);if(s.status!=="pending")throw new ne(t,s.status);return s.status=n,o&&(s.decidedBy=o),s.reason=r,s.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,s),this.get(t)}}});function si(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`${t} must be a node object`);let n=e;if(typeof n.id!="string"||n.id.length===0)throw new Error(`${t} needs a non-empty string id`);if(typeof n.type!="string"||n.type.length===0)throw new Error(`${t} '${n.id}' needs a non-empty string type`);return n}function kr(e,t,n){let r=e.find(d=>d.id===t);if(!r||r.type!=="tab"&&r.type!=="subflow")throw new W(t);let o={...r,id:t,type:r.type};delete o.nodes,delete o.configs,delete o.subflows,n.label!==void 0&&(o.label=n.label),n.disabled!==void 0&&(o.disabled=n.disabled),n.info!==void 0&&(o.info=n.info),n.env!==void 0&&(o.env=n.env);let s=[];for(let[d,l]of(n.nodes??[]).entries())s.push({...si(l,`flowData.nodes[${d}]`),z:t});for(let[d,l]of(n.configs??[]).entries())s.push({...si(l,`flowData.configs[${d}]`),z:t});return[...e.filter(d=>d.id!==t&&d.z!==t),o,...s]}function Nn(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`Expected ${t} to be an object with rev and flows`);let n=e;if(typeof n.rev!="string"||n.rev.length===0)throw new Error(`Expected ${t}.rev to be a non-empty string`);if(!Array.isArray(n.flows))throw new Error(`Expected ${t}.flows to be an array`);return{rev:n.rev,flows:n.flows}}function qt(e,t){if(Array.isArray(t)){let d=t.find(u=>u&&typeof u=="object"&&u.id===e&&(u.type==="tab"||u.type==="subflow"));if(!d)throw new W(e);let l=t.filter(u=>u&&typeof u=="object"&&u.id!==e&&u.z===e),c={id:e,type:d.type,nodes:l};return typeof d.label=="string"&&(c.label=d.label),typeof d.disabled=="boolean"&&(c.disabled=d.disabled),typeof d.info=="string"&&(c.info=d.info),c}if(!t||typeof t!="object")throw new W(e);let n=t,r=typeof n.id=="string"&&n.id.length>0?n.id:e,o=typeof n.type=="string"&&n.type.length>0?n.type:"tab",s=Array.isArray(n.nodes)?n.nodes:[],i={...n,id:r,type:o,nodes:s};return Array.isArray(n.configs)&&(i.configs=n.configs),i}var Ar=U(()=>{"use strict";xe();a(si,"asFlowNode");a(kr,"replaceFlowTabInFlatFlows");a(Nn,"assertFlowsSnapshot");a(qt,"normalizeFlowTab")});function Fo(e){return e?pc.has(e)?!0:e.startsWith("subflow:"):!1}function Qe(e,t="node.type"){if(e==null||e==="")throw new y(`${t} is required`,"missing_node_type");if(typeof e!="string")throw new y(`${t} must be a non-empty string (got ${typeof e})`,"invalid_node_type",{details:{type:e,got:typeof e}});if(!e.trim())throw new y(`${t} is required`,"missing_node_type");return e}function bc(e,t){if(typeof e!="string"||typeof t!="string")return Number.MAX_SAFE_INTEGER;let n=e.toLowerCase(),r=t.toLowerCase();if(n===r)return 0;let o=n.length,s=r.length;if(o===0)return s;if(s===0)return o;let i=new Array(s+1),d=new Array(s+1);for(let l=0;l<=s;l++)i[l]=l;for(let l=1;l<=o;l++){d[0]=l;let c=n.charAt(l-1);for(let u=1;u<=s;u++){let p=c===r.charAt(u-1)?0:1;d[u]=Math.min((d[u-1]??0)+1,(i[u]??0)+1,(i[u-1]??0)+p)}for(let u=0;u<=s;u++)i[u]=d[u]??0}return i[s]??s}function vc(e,t){let n=e.toLowerCase(),r=t.toLowerCase(),o=Math.min(n.length,r.length),s=0;for(;s<o&&n.charAt(s)===r.charAt(s);)s+=1;return s}function Ac(e){return Math.max(2,Math.floor(e.length/3))}function Oo(e,t,n=5){if(typeof e!="string"||!e)return[];let r=Ac(e);return t.filter(s=>typeof s=="string"&&s!==e&&!kc.has(s)&&!s.startsWith("_")&&!s.startsWith("subflow:")).map(s=>({c:s,dist:bc(e,s),prefix:vc(e,s)})).filter(s=>s.dist<=r).sort((s,i)=>s.dist-i.dist||i.prefix-s.prefix||s.c.localeCompare(i.c)).slice(0,n).map(s=>s.c)}function Sc(e){return e.endsWith("/")?e.slice(0,-1):e}function pi(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,ui.createRequire)(e)}function xc(){try{let t=pi()("@node-red/runtime/lib/settings");return typeof t.available=="function"&&t.available()===!0}catch{return!1}}function fi(e){let t=e.types,n=Array.isArray(t)?t.filter(i=>typeof i=="string"&&i.length>0):[],r="";if(typeof e.module=="string"&&e.module)r=e.module;else if(typeof e.name=="string"&&e.name)r=e.name;else if(typeof e.id=="string"){let i=String(e.id).split("/")[0];r=i&&i.length>0?i:String(e.id)}let o=typeof e.id=="string"&&e.id?e.id:n.length===1&&r?`${r}/${n[0]}`:r;if(!o||n.length===0)return null;let s={id:o,module:r.length>0?r:o,types:n};return typeof e.version=="string"&&(s.version=e.version),typeof e.enabled=="boolean"&&(s.enabled=e.enabled),typeof e.name=="string"&&(s.name=e.name),s}function $o(e=wc,t={}){let n=t.module??"node-red",r=t.version??"4.0.0",o=[...e],s=t.configs??{};return{listNodeSets(){return Promise.resolve([{id:n,module:n,version:r,types:o,enabled:!0,name:n}])},getNodeConfigHtml(i){if(s[i])return Promise.resolve(s[i]??null);let d=i.includes("/")?i.slice(i.lastIndexOf("/")+1):i;return s[d]?Promise.resolve(s[d]??null):Promise.resolve(null)}}}function mi(e={}){if(xc())try{let n=pi()("@node-red/runtime");if(n.nodes&&typeof n.nodes.getNodeList=="function")return new No(n.nodes)}catch{}if(e.baseUrl)return new Io({baseUrl:e.baseUrl,...e.accessToken?{accessToken:e.accessToken}:{},...e.fetch?{fetch:e.fetch}:{}});throw new Error("No usable NodeTypesAdapter: runtime.nodes unavailable and no HTTP baseUrl")}async function Mo(e){let t=await e.listNodeSets(),n=new Map;for(let r of t)for(let o of r.types){if(n.has(o))continue;let s={type:o,module:r.module,setId:r.id};typeof r.enabled=="boolean"&&(s.enabled=r.enabled),r.version&&(s.version=r.version),n.set(o,s)}return{byType:n,allNames:[...n.keys()]}}function Tc(e,t){let n=t.toLowerCase();return[e.type,e.module,e.category??"",e.description??""].join(`
4
- `).toLowerCase().includes(n)}async function xr(e,t={}){let n=t.limit??fc,{byType:r}=await Mo(e),o=[...r.values()].sort((u,p)=>u.type.localeCompare(p.type)),s=t.q?.trim();s&&(o=o.filter(u=>Tc(u,s)));let i=o.length,d=i>n,c={types:o.slice(0,n),total:i,truncated:d};return s&&(c.q=s),c}function Rc(e,t){let n=`data-help-name="${t}"`,r=`data-help-name='${t}'`,o=e.toLowerCase(),s=0;for(;s<e.length;){let i=e.indexOf(n,s),d=e.indexOf(r,s),l=-1;if(i>=0&&(d<0||i<d)?l=i:d>=0&&(l=d),l<0)return null;let c=o.lastIndexOf("<script",l);if(c<0||c<s){s=l+1;continue}let u=e.indexOf(">",l);if(u<0)return null;let p=o.indexOf("</script",u+1);if(p<0)return null;let f=p+8;for(;f<e.length&&/\s/.test(e.charAt(f));)f+=1;if(e.charAt(f)!==">"){s=p+1;continue}return e.slice(u+1,p)}return null}function ai(e,t){let n=`<${t}`,r=`</${t}`,o="",s=0,i=e.toLowerCase();for(;s<e.length;){let d=i.indexOf(n,s);if(d<0){o+=e.slice(s);break}o+=e.slice(s,d);let l=e.indexOf(">",d);if(l<0)break;let c=i.indexOf(r,l+1);if(c<0)break;let u=c+r.length;for(;u<e.length&&/\s/.test(e.charAt(u));)u+=1;e.charAt(u)===">"?(s=u+1,o+=" "):s=c+r.length}return o}function _c(e,t=2e3){let n=ai(e,"script");return n=ai(n,"style"),n=n.replace(/<[^>]+>/g," ").replace(/&nbsp;/gi," ").replace(/[#*_`]+/g," ").replace(/\s+/g," ").trim(),n.length>t&&(n=`${n.slice(0,t-1)}\u2026`),n}function Pc(e,t){try{let n=/RED\.nodes\.registerType\s*\(\s*['"]([^'"]+)['"]\s*,\s*\{/.exec(e);if(!n?.[1])return null;let r=n[1];if(t&&r!==t){let o=/RED\.nodes\.registerType\s*\(\s*['"]([^'"]+)['"]\s*,\s*\{/g,s,i=null,d=-1;for(;(s=o.exec(e))!==null;)if(s[1]===t){i=s[1],d=s.index;break}return!i||d<0?null:di(e,d,i)}return di(e,n.index,r)}catch{return null}}function di(e,t,n){let r=e.slice(t),o=r.indexOf("{");if(o<0)return{type:n};let s=Fn(r.slice(o));if(!s)return{type:n};let i=li(s,"category"),d=li(s,"color"),l=ci(s,"inputs"),c=ci(s,"outputs"),u=Cc(s),p=[];if(u){for(let g of Object.values(u))if(g&&typeof g=="object"&&!Array.isArray(g)){let k=g.type;typeof k=="string"&&k.length>0&&p.push(k)}}let f,m=Rc(e,n);m&&(f=_c(m));let h={type:n};return i&&(h.category=i),d&&(h.color=d),l!==void 0&&(h.inputs=l),c!==void 0&&(h.outputs=c),u&&(h.defaults=u),p.length>0&&(h.requiredConfigNodeTypes=[...new Set(p)]),f&&(h.help=f),h}function Ec(e){let t=e.trim();if(!t)return{};if(!/\n(?=#{1,3}\s*(inputs?|outputs?)\b)/i.test(`
5
- ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(let l of r){let c=l.trim();if(!c)continue;let u=/^(#{1,3})\s*(inputs?|outputs?)\b[\s:]*\n?([\s\S]*)$/i.exec(c);if(u){let p=(u[2]??"").toLowerCase(),f=(u[3]??"").trim();p.startsWith("input")?s=f||c:i=f||c}else o||(o=c.replace(/^#{1,3}\s*/,"").trim())}let d={};return o&&(d.summary=o),s&&(d.inputs=s),i&&(d.outputs=i),!d.summary&&!d.inputs&&!d.outputs&&(d.text=t),d}function Fn(e){if(!e.startsWith("{"))return null;let t=0,n=null,r=!1;for(let o=0;o<e.length;o++){let s=e[o];if(n){if(r){r=!1;continue}if(s==="\\"){r=!0;continue}s===n&&(n=null);continue}if(s==='"'||s==="'"||s==="`"){n=s;continue}if(s==="{")t+=1;else if(s==="}"&&(t-=1,t===0))return e.slice(0,o+1)}return null}function li(e,t){let n=new RegExp(`\\b${t}\\s*:\\s*['"]([^'"]+)['"]`);return e.match(n)?.[1]}function ci(e,t){let n=new RegExp(`\\b${t}\\s*:\\s*(-?\\d+(?:\\.\\d+)?)`),r=e.match(n);if(!r?.[1])return;let o=Number(r[1]);return Number.isFinite(o)?o:void 0}function Cc(e){let t=e.search(/\bdefaults\s*:/);if(t<0)return;let n=e.slice(t),r=n.indexOf("{");if(r<0)return;let o=Fn(n.slice(r));if(!o)return;let s={},i=/([a-zA-Z_$][\w$]*)\s*:\s*\{/g,d;for(;(d=i.exec(o))!==null;){let l=d[1];if(!l||l==="defaults")continue;let c=Fn(o.slice(d.index+d[0].length-1));if(!c)continue;let u={},p=/\btype\s*:\s*['"]([^'"]+)['"]/.exec(c);p?.[1]&&(u.type=p[1]),/\brequired\s*:\s*true\b/.test(c)&&(u.required=!0);let f=/\bvalue\s*:\s*/.exec(c);if(f){let m=c.slice(f.index+f[0].length).trimStart();u.value=Nc(m)}s[l]=u}return Object.keys(s).length>0?s:void 0}function Lc(e,t){let n=e.charAt(t);if(n==="u"&&/^[0-9a-fA-F]{4}/.test(e.slice(t+1,t+5)))return{char:String.fromCharCode(Number.parseInt(e.slice(t+1,t+5),16)),next:t+5};if(n==="x"&&/^[0-9a-fA-F]{2}/.test(e.slice(t+1,t+3)))return{char:String.fromCharCode(Number.parseInt(e.slice(t+1,t+3),16)),next:t+3};let r;switch(n){case"n":r=`
6
- `;break;case"r":r="\r";break;case"t":r=" ";break;case"b":r="\b";break;case"f":r="\f";break;case"v":r="\v";break;case"0":r="\0";break;case"\\":r="\\";break;case"'":r="'";break;case'"':r='"';break;case"/":r="/";break;default:r=n;break}return{char:r,next:t+1}}function Ic(e){return e==="_DEFAULT_"?"":e}function Sr(e){let t=e.trimStart();if(t.startsWith("'")||t.startsWith('"')){let r=t[0],o=1,s="";for(;o<t.length;){let i=t[o];if(i==="\\"&&o+1<t.length){let d=Lc(t,o+1);s+=d.char,o=d.next;continue}if(i===r)break;s+=i,o+=1}return Ic(s)}if(t.startsWith("[")){let r=hi(t);if(!r)return;let o=r.slice(1,-1).trim();if(!o)return[];let s=[],i=0;for(;i<o.length;){for(;i<o.length&&/[\s,]/.test(o.charAt(i));)i+=1;if(i>=o.length)break;let d=o.slice(i),l=Sr(d);s.push(l);let c=Lo(d);i+=c>0?c:1}return s}if(t.startsWith("{")){let r=Fn(t);if(!r)return;let o=r.slice(1,-1);if(!o.trim())return{};let s={},i=0;for(;i<o.length;){for(;i<o.length&&/[\s,]/.test(o.charAt(i));)i+=1;if(i>=o.length)break;let d;if(o[i]==="'"||o[i]==='"'){let c=Sr(o.slice(i));d=typeof c=="string"?c:"",i+=Lo(o.slice(i))}else{let c=/^[a-zA-Z_$][\w$]*/.exec(o.slice(i));if(!c)break;d=c[0],i+=d.length}for(;i<o.length&&/\s/.test(o.charAt(i));)i+=1;if(o[i]!==":")break;for(i+=1;i<o.length&&/\s/.test(o.charAt(i));)i+=1;let l=o.slice(i);s[d]=Sr(l),i+=Lo(l)}return s}let n=/^-?\d+(?:\.\d+)?/.exec(t);if(n)return Number(n[0]);if(t.startsWith("true"))return!0;if(t.startsWith("false"))return!1;if(t.startsWith("null"))return null}function Lo(e){let t=e.trimStart(),n=e.length-t.length;if(t.startsWith("'")||t.startsWith('"')){let o=t[0],s=1;for(;s<t.length;){if(t[s]==="\\"&&s+1<t.length){s+=2;continue}if(t[s]===o)return n+s+1;s+=1}return n+t.length}if(t.startsWith("[")){let o=hi(t);return n+(o?.length??1)}if(t.startsWith("{")){let o=Fn(t);return n+(o?.length??1)}let r=/^(?:-?\d+(?:\.\d+)?|true|false|null)/.exec(t);return r?n+r[0].length:n+1}function hi(e){if(!e.startsWith("["))return null;let t=0,n=null,r=!1;for(let o=0;o<e.length;o++){let s=e[o];if(n){if(r){r=!1;continue}if(s==="\\"){r=!0;continue}s===n&&(n=null);continue}if(s==="'"||s==='"'){n=s;continue}if(s==="[")t+=1;else if(s==="]"&&(t-=1,t===0))return e.slice(0,o+1)}return null}function Nc(e){return Sr(e)}function Fc(e,t){for(let n of e)if(n.types.includes(t)){let r=n.id.includes("/")||n.types.length===1?n.id:`${n.module}/${t}`;return{set:n,setId:r}}return null}function Do(e,t){let n=e.toLowerCase();return t.includes(e)?e:t.find(r=>r.toLowerCase()===n)}async function yt(e,t,n={}){if(!t||typeof t!="string")throw new y("Missing node type","missing_node_type");let r=n.includeHelp!==!1;if(Fo(t)){let l={type:t,module:"node-red",category:"config",inputs:0,outputs:0,defaults:null};return r&&(l.help={summary:"Built-in structural Node-RED type (not a palette node)."}),l}let o=await e.listNodeSets(),s=o.flatMap(l=>l.types),i=Fc(o,t);if(!i){let l=Do(t,s);throw l&&l!==t?new y(`Node type '${t}' was not found \u2014 did you mean '${l}'? Types are case-sensitive.`,"node_type_not_found",{statusCode:404,details:{type:t,didYouMean:l,caseSensitive:!0,nearest:[l]}}):new y(`Node type '${t}' was not found`,"node_type_not_found",{statusCode:404,details:{type:t,nearest:Oo(t,s,5)}})}let d={type:t,module:i.set.module,setId:i.setId,inputs:1,outputs:1,defaults:null};if(i.set.version&&(d.version=i.set.version),typeof i.set.enabled=="boolean"&&(d.enabled=i.set.enabled),typeof e.getNodeConfigHtml=="function")try{let l=await e.getNodeConfigHtml(i.setId)??await e.getNodeConfigHtml(`${i.set.module}/${t}`)??await e.getNodeConfigHtml(t);if(l){let c=Pc(l,t);c&&(c.category&&(d.category=c.category),c.color&&(d.color=c.color),d.inputs=c.inputs??d.inputs,d.outputs=c.outputs??d.outputs,c.defaults&&Object.keys(c.defaults).length>0?d.defaults=c.defaults:d.defaults=null,c.requiredConfigNodeTypes&&(d.requiredConfigNodeTypes=c.requiredConfigNodeTypes),r&&c.help&&(d.help=Ec(c.help)))}}catch{}return d}async function gi(e,t){let{allNames:n}=await Mo(e),r=new Set(n),o=new Set,s=a(l=>{let c=Qe(l,"node.type");if(o.has(c)||Fo(c)){o.add(c);return}if(o.add(c),r.has(c))return;let u=Do(c,n);throw u&&u!==c?new y(`Unknown node type '${c}' \u2014 did you mean '${u}'? Types are case-sensitive.`,"unknown_node_type",{details:{type:c,didYouMean:u,caseSensitive:!0,nearest:[u]}}):new y(`Unknown node type '${c}'. Call node-types / palette search; ask the user to install via Manage palette, then recheck.`,"unknown_node_type",{details:{type:c,nearest:Oo(c,n,5)}})},"checkType"),i=a(l=>{if(!l||typeof l!="object"||Array.isArray(l))return;let c=l;typeof c.id=="string"&&c.id.length>0&&s(c.type)},"visitNodeOrConfig");a(l=>{if(!l||typeof l!="object"||Array.isArray(l))return;let c=l;if(c.type!==void 0&&s(c.type),Array.isArray(c.nodes))for(let u of c.nodes)i(u);if(Array.isArray(c.configs))for(let u of c.configs)i(u)},"visitTab")(t)}async function yi(e,t){let n=Qe(t,"node.type");if(Fo(n))return;let{allNames:r}=await Mo(e);if(r.includes(n))return;let o=Do(n,r);throw o&&o!==n?new y(`Unknown node type '${n}' \u2014 did you mean '${o}'? Types are case-sensitive.`,"unknown_node_type",{details:{type:n,didYouMean:o,caseSensitive:!0,nearest:[o]}}):new y(`Unknown node type '${n}'. Call node-types / palette search; ask the user to install via Manage palette, then recheck.`,"unknown_node_type",{details:{type:n,nearest:Oo(n,r,5)}})}async function Oc(e,t){let n=new Headers({Accept:"application/json"});t&&n.set("If-None-Match",t);let r=await fetch(e,{headers:n}),o=r.headers.get("etag")??void 0;if(r.status===304)return{status:304,...o?{etag:o}:{}};if(!r.ok)return{status:r.status,body:null,...o?{etag:o}:{}};let s=await r.json();return{status:r.status,body:s,...o?{etag:o}:{}}}async function $c(e,t){let n=Date.now();if(Te&&n-Te.fetchedAt<yc)return Te.body;let r=Te?.etag,o=await e(t,r);if(o.status===304&&Te)return Te={...Te,fetchedAt:n,...o.etag?{etag:o.etag}:{}},Te.body;if(!o.body||!Array.isArray(o.body.modules)){if(Te)return Te.body;throw new y("Node-RED catalogue unavailable","catalogue_unavailable",{statusCode:502})}return Te={fetchedAt:n,body:o.body,...o.etag?{etag:o.etag}:{}},o.body}async function Tr(e,t,n={}){let r=t.trim();if(!r)throw new y("q query parameter is required","missing_query");let o=n.limit??mc,s=n.catalogueUrl??gc,i=n.catalogueFetcher??Oc,d=await $c(i,s),l=Array.isArray(d.modules)?d.modules:[],c=await e.listNodeSets(),u=new Set(c.flatMap(g=>{let k=[g.module,g.id];return g.id.includes("/")&&k.push(g.id.split("/")[0]??g.module),k.map(T=>T.toLowerCase())})),p=r.toLowerCase(),f=[];for(let g of l){let k=g.id.toLowerCase(),T=(g.keywords??[]).map(Ue=>String(Ue).toLowerCase()),C=(g.description??"").toLowerCase(),L=Mc(p,k,T,C);if(L<=0)continue;let N={module:g.id,installed:u.has(k)};typeof g.description=="string"&&(N.description=g.description),typeof g.version=="string"&&(N.version=g.version),typeof g.updated_at=="string"&&(N.updated=g.updated_at),typeof g.downloads=="number"&&(N.downloads=g.downloads),typeof g.url=="string"&&(N.link=g.url),f.push({item:N,score:L})}f.sort((g,k)=>k.score-g.score||(k.item.downloads??0)-(g.item.downloads??0)||g.item.module.localeCompare(k.item.module));let m=f.length,h=m>o;return{q:r,modules:f.slice(0,o).map(g=>g.item),total:m,truncated:h,hint:hc}}function Mc(e,t,n,r){if(!e)return 0;if(t===e||t===`node-red-contrib-${e}`||t===`node-red-node-${e}`)return 1e4;let o=0;t.startsWith(`node-red-contrib-${e}`)||t.startsWith(`node-red-node-${e}`)?o+=500:t.startsWith(e)?o+=80:t.includes(e)&&(o+=60);for(let s of n)s===e?o+=50:s.startsWith(e)||e.startsWith(s)?o+=35:s.includes(e)&&(o+=25);return r.includes(e)&&(o+=10),o}var ui,pc,fc,mc,hc,gc,yc,wc,kc,Io,ii,No,Te,wt=U(()=>{"use strict";ui=require("module");J();pc=new Set(["tab","subflow","group","junction"]),fc=100,mc=30,hc="Recommend only \u2014 ask the user to install via Manage palette, then recheck node-types. This API never installs. Core Node-RED nodes (module node-red) are not listed in the public catalogue; use node-types for installed core types.",gc="https://catalogue.nodered.org/catalogue.json",yc=3600*1e3,wc=["inject","debug","function","template","comment","change","switch","delay","trigger","link in","link out","link call","mqtt in","mqtt out","http in","http response","http request","websocket in","websocket out","tcp in","tcp out","udp in","udp out","catch","status","complete","join","split","sort","batch","csv","html","json","xml","yaml","file","file in","watch","exec","range","rbe"];a(Fo,"isStructuralNodeType");a(Qe,"requireNodeTypeName");a(bc,"editDistance");a(vc,"sharedPrefixLength");kc=new Set(["unknown","tab","subflow","group"]);a(Ac,"nearestDistanceCutoff");a(Oo,"nearestNodeTypes");a(Sc,"trimTrailingSlash");a(pi,"createPluginRequire");a(xc,"isRuntimeSettingsAvailable");a(fi,"normalizeNodeSet");a($o,"createStaticNodeTypesAdapter");Io=class{static{a(this,"HttpNodeTypesAdapter")}baseUrl;accessToken;fetchImpl;constructor(t){this.baseUrl=Sc(t.baseUrl),this.accessToken=t.accessToken,this.fetchImpl=t.fetch??globalThis.fetch.bind(globalThis)}headers(t){let n=new Headers;return n.set("Accept",t),this.accessToken&&n.set("Authorization",`Bearer ${this.accessToken}`),n}async listNodeSets(){let t=await this.fetchImpl(`${this.baseUrl}/nodes`,{headers:this.headers("application/json")});if(!t.ok)throw new Error(`Admin API GET /nodes failed (${t.status})`);let n=await t.json();if(!Array.isArray(n))throw new Error("Admin API GET /nodes returned non-array JSON");let r=[];for(let o of n){if(!o||typeof o!="object"||Array.isArray(o))continue;let s=fi(o);s&&r.push(s)}return r}async getNodeConfigHtml(t,n){let r=t.split("/").map(i=>encodeURIComponent(i)).join("/"),o=n?`?lang=${encodeURIComponent(n)}`:"",s=await this.fetchImpl(`${this.baseUrl}/nodes/${r}${o}`,{headers:this.headers("text/html")});return s.status===404||!s.ok?null:s.text()}},ii={user:null,req:null},No=class{constructor(t){this.nodes=t}static{a(this,"RedNodeTypesAdapter")}async listNodeSets(){if(typeof this.nodes.getNodeList!="function")return[];let t,n=this.nodes.getNodeList;try{t=await Promise.resolve(n(ii))}catch{try{t=await Promise.resolve(n())}catch{return[]}}if(!Array.isArray(t))return[];let r=[];for(let o of t){if(!o||typeof o!="object"||Array.isArray(o))continue;let s=fi(o);s&&r.push(s)}return r}async getNodeConfigHtml(t,n){if(typeof this.nodes.getNodeConfig!="function")return null;try{let r=await Promise.resolve(this.nodes.getNodeConfig({id:t,lang:n??"en-US",...ii}));if(typeof r=="string"&&r.length>0)return r}catch{}try{let r=await Promise.resolve(this.nodes.getNodeConfig(t,n));return typeof r=="string"&&r.length>0?r:null}catch{return null}}};a(mi,"createNodeTypesAdapter");a(Mo,"flattenInstalledTypes");a(Tc,"matchesQuery");a(xr,"listNodeTypes");a(Rc,"extractHelpScriptBody");a(ai,"removeHtmlBlocks");a(_c,"stripHelpText");a(Pc,"parseRegisterTypeFromHtml");a(di,"parseRegisterTypeAt");a(Ec,"splitHelpSections");a(Fn,"extractBalancedObject");a(li,"matchStringProp");a(ci,"matchNumberProp");a(Cc,"parseDefaultsBlock");a(Lc,"unescapeJsStringEscape");a(Ic,"normalizeDefaultValue");a(Sr,"parseJsLiteral");a(Lo,"measureJsLiteralLength");a(hi,"extractBalancedArray");a(Nc,"parseLooseValue");a(Fc,"findSetForType");a(Do,"findCaseInsensitiveType");a(yt,"getNodeTypeDetail");a(gi,"assertKnownNodeTypes");a(yi,"assertKnownNodeType");Te=null;a(Oc,"defaultCatalogueFetcher");a($c,"loadCatalogue");a(Tr,"searchPalette");a(Mc,"paletteMatchScore")});function qe(e){if(e.length===0)return;let t=e[0];if(!t)return;let n=e.map(s=>{let i={...s};return delete i.statusCode,i});if(e.length>1)throw new y(`${e.length} validation errors`,"validation_failed",{statusCode:400,details:{errors:n,errorCount:e.length}});let r={errors:n};for(let[s,i]of Object.entries(t))s==="code"||s==="error"||s==="errors"||s==="statusCode"||i!==void 0&&(r[s]=i);let o=typeof t.statusCode=="number"?t.statusCode:400;throw new y(t.error,t.code,{statusCode:o,details:r})}function jo(e,t="payload.flow"){let n=new Map;for(let o of e)for(let s of o){if(!s||typeof s!="object"||Array.isArray(s))continue;let i=s.id;typeof i=="string"&&i.length>0&&n.set(i,(n.get(i)??0)+1)}let r=[];for(let[o,s]of n)s>1&&r.push({code:"duplicate_node_id",error:`Node id '${o}' appears ${s} times in ${t}; node ids must be unique`,nodeId:o,count:s});return r}function Dc(e){let t=typeof e.label=="string"&&e.label?` '${e.label}'`:"";return e.type==="tab"?`tab${t||` '${e.id}'`}`:e.type==="subflow"?`subflow${t||` '${e.id}'`}`:typeof e.z=="string"&&e.z?`${e.type} node on '${e.z}'`:`global ${e.type} config node`}function bi(e,t,n){let r=new Map;for(let i of n)!i||typeof i.id!="string"||i.id===t||i.z===t||r.set(i.id,i);let o=[],s=new Set;for(let i of[e.nodes,e.configs])if(Array.isArray(i))for(let d of i){let l=d&&typeof d=="object"?d.id:void 0;if(typeof l!="string"||!l||s.has(l))continue;if(s.add(l),l===t){o.push({code:"duplicate_node_id",error:`Node id '${l}' equals the tab id; node ids must be unique across all flows`,nodeId:l});continue}let c=r.get(l);c&&o.push({code:"duplicate_node_id",error:`Node id '${l}' is already used by ${Dc(c)}; node ids must be unique across all flows`,nodeId:l})}return o}function wi(e){let t=new Map;for(let n of[e.nodes,e.configs])if(Array.isArray(n))for(let r of n){if(!r||typeof r!="object"||Array.isArray(r))continue;let o=r;typeof o.id=="string"&&t.set(o.id,o)}return t}function Hc(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]);for(let r of n)if(!jc.has(r)&&JSON.stringify(e[r])!==JSON.stringify(t[r]))return!0;return!1}function Uc(e,t){let n=wi(t);if(!e)return new Set(n.keys());let r=wi(e),o=new Set;for(let[s,i]of n){let d=r.get(s);(!d||Hc(d,i))&&o.add(s)}return o}function vi(e,t){let n=[],r=e;typeof r.id!="string"||r.id.length===0?n.push({code:"missing_flow_id",error:"payload.flow.id is required and must match payload.flowId"}):r.id!==t&&n.push({code:"invalid_flow_id",error:`payload.flow.id ('${r.id}') must match payload.flowId ('${t}')`}),"nodes"in r?Array.isArray(r.nodes)||n.push({code:"invalid_nodes",error:"payload.flow.nodes must be an array"}):n.push({code:"invalid_nodes",error:"payload.flow.nodes must be an array (use [] to clear the tab intentionally)"}),"configs"in r&&r.configs!==void 0&&!Array.isArray(r.configs)&&n.push({code:"invalid_nodes",error:"payload.flow.configs must be an array"});let o=[];Array.isArray(r.nodes)&&o.push({key:"nodes",list:r.nodes}),Array.isArray(r.configs)&&o.push({key:"configs",list:r.configs}),n.push(...jo(o.map(s=>s.list)));for(let{key:s,list:i}of o)for(let d=0;d<i.length;d+=1){let l=i[d];if(!l||typeof l!="object"||Array.isArray(l)){n.push({code:"invalid_node",error:`payload.flow.${s}[${d}] must be an object`});continue}let c=l;(typeof c.id!="string"||c.id.length===0)&&n.push({code:"missing_node_id",error:`payload.flow.${s}[${d}] needs a non-empty string id`});try{Qe(c.type,"node.type")}catch(u){u instanceof y&&n.push({code:u.code,error:u.message,...typeof c.id=="string"?{nodeId:c.id}:{}})}"wires"in c&&zt(c.wires,n,typeof c.id=="string"?c.id:void 0)}return n}function zt(e,t,n){if(!Array.isArray(e))return t.push({code:"invalid_wires",error:"wires must be an array of output ports (each a list of node ids)",...n?{nodeId:n}:{}}),!1;for(let r of e){if(!Array.isArray(r))return t.push({code:"invalid_wires",error:"wires must be an array of output ports (each a list of node ids)",...n?{nodeId:n}:{}}),!1;for(let o of r)if(typeof o!="string"||o.length===0)return t.push({code:"invalid_wires",error:"wires targets must be non-empty node id strings",...n?{nodeId:n}:{}}),!1}return!0}async function Bc(e,t){if(typeof e.outputs=="number"&&Number.isFinite(e.outputs)&&e.outputs>=0)return Math.trunc(e.outputs);let n=typeof e.type=="string"?e.type:"";if(!(!t||!n))try{return(await yt(t,n,{includeHelp:!1})).outputs}catch{return}}function qc(e,t,n){let r=typeof e.type=="string"?e.type:"node";if(!(n.length<=t))return t===0?{code:"invalid_wires",error:`${r} has 0 outputs; wires must be []`,nodeId:e.id,type:r}:{code:"invalid_wires",error:`${r} has ${t} outputs; wires has ${n.length} port arrays (max ${t})`,nodeId:e.id,type:r}}function zc(e){return Array.isArray(e)&&e.some(t=>typeof t=="string"&&t.length>0)}function Wc(e,t){return t<=0?!1:!("wires"in e)||e.wires===void 0?!0:Array.isArray(e.wires)?!e.wires.some(zc):!1}async function Ho(e,t,n={}){let r=[],o=[],s=new Set(e.map(d=>d.id)),i=n.softWarningNodeIds;for(let d of e){let l=await Bc(d,t),c=typeof d.type=="string"?d.type:"node",u=!i||i.has(d.id);if("wires"in d&&d.wires!==void 0){if(!zt(d.wires,r,d.id))continue;let p=d.wires;if(l!==void 0){let f=qc(d,l,p);f?(f.cause="excess_ports",r.push(f)):u&&p.length<l&&o.push({code:"incomplete_wires",error:`${c} '${d.id}' declares ${l} outputs but wires has only ${p.length} port(s)`,nodeId:d.id,type:c})}for(let f of p)for(let m of f){let h=String(m);s.has(h)||r.push({code:"invalid_wires",error:`Wire target '${h}' on node '${d.id}' was not found in this flow (add the node in the same patch or use an existing id)`,nodeId:d.id,cause:"missing_target",targetId:h})}}u&&l!==void 0&&Wc(d,l)&&o.push({code:"unwired_node",error:`Node '${d.id}' (${c}) has ${l} output(s) but is not wired to anything yet`,nodeId:d.id,type:c})}return{errors:r,warnings:o}}function Uo(e,t){let n=[];for(let r of e)r.z===void 0||r.z===""||typeof r.z=="string"&&r.z!==t&&n.push({code:"z_mismatch",error:`Node '${r.id}' has z='${r.z}' but flow id is '${t}'`,nodeId:r.id});return n}function Gc(e,t){return e.filter(n=>n.type===t).map(n=>n.id)}async function Bo(e,t,n){let r=[],o=[...t];for(let s of e){let i;try{i=Qe(s.type,"node.type")}catch{continue}let d;try{d=await yt(n,i,{includeHelp:!1})}catch{continue}let l=d.defaults;if(!l||typeof l!="object")continue;let c=new Set(d.requiredConfigNodeTypes??[]);for(let[u,p]of Object.entries(l)){if(!p||typeof p!="object"||Array.isArray(p))continue;let f=p.type;if(typeof f!="string"||!f)continue;let h=p.required===!0||c.has(f),g=s[u],k=Gc(o,f);if(typeof g!="string"||g.length===0){h&&r.push({code:"missing_config_node",error:`Node '${s.id}' (${i}) has no ${f} on '${u}' yet \u2014 configure before deploy if needed`,nodeId:s.id,type:i,property:u,configType:f,candidates:k});continue}o.find(C=>C.id===g)?.type!==f&&r.push({code:"missing_config_node",error:`Node '${s.id}' property '${u}' does not resolve to a ${f} config yet`,nodeId:s.id,type:i,property:u,configType:f,candidates:k})}}return r}async function ki(e,t={}){let n=t.flowId??(typeof e.id=="string"?e.id:""),r=Array.isArray(e.nodes)?e.nodes:[],o=Array.isArray(e.configs)?e.configs:[],s=[],i=[];n&&s.push(...Uo(r,n));let d=t.baseFlow?Uc(t.baseFlow,e):void 0,l=await Ho(r,t.nodeTypes,{...d?{softWarningNodeIds:d}:{}});if(s.push(...l.errors),i.push(...l.warnings),t.nodeTypes){let c=await Bo(r,o,t.nodeTypes);d?i.push(...c.filter(u=>!u.nodeId||d.has(u.nodeId))):i.push(...c)}return{errors:s,warnings:i}}var jc,qo=U(()=>{"use strict";J();wt();a(qe,"throwValidationIssues");a(jo,"collectDuplicateNodeIdIssues");a(Dc,"describeLiveNode");a(bi,"collectCrossFlowDuplicateIssues");jc=new Set(["x","y","w","h","g","id","z"]);a(wi,"collectNodesMap");a(Hc,"nodeContentChanged");a(Uc,"changedNodeIds");a(vi,"collectFullFlowShapeIssues");a(zt,"assertValidWiresShape");a(Bc,"resolveNodeOutputCount");a(qc,"wireCountIssue");a(zc,"portHasTargets");a(Wc,"nodeIsUnwired");a(Ho,"collectWireValidation");a(Uo,"collectZMismatchIssues");a(Gc,"configCandidates");a(Bo,"collectConfigWarnings");a(ki,"validateFlowTab")});function et(e,t="node"){return typeof e=="string"&&e.trim()?e.trim():t}function Rr(e){let t=new Map;for(let n of e)n.type==="tab"&&typeof n.id=="string"&&typeof n.label=="string"&&n.label.trim()&&t.set(n.id,n.label.trim());return t}function Si(e,t){e[Ai]=t}function Vc(e){if(!e)return;let t=e[Ai];return t instanceof Map?t:void 0}function Yc(e){return typeof e!="string"||!e.trim()?new Set:new Set(e.split(",").map(t=>t.trim()).filter(Boolean))}function Xc(e){if(e===!0||e===1)return!0;if(typeof e=="string"){let t=e.trim().toLowerCase();return t==="1"||t==="true"||t==="yes"}return!1}function ie(e){let t=Yc(e.include);return Xc(e.include_payload)&&(t.add("payload"),t.add("baseFlow")),t}function q(e,t){let n={req:e};t!==void 0&&(n.httpAdminRoot=t);let r=Vc(e);return r&&(n.liveFlowLabels=r),n}function Wt(e){return typeof e.name=="string"&&e.name.trim()?e.name.trim():typeof e.label=="string"&&e.label.trim()?e.label.trim():""}function On(e){return`"${e.replace(/"/g,"'")}"`}function Gt(e){let t=new Map;for(let n of e.nodes??[])t.set(n.id,n);for(let n of e.configs??[])t.set(n.id,n);return t}function Zc(e,t){let n=new Set(["x","y","w","h","g","id","z"]),r=new Set([...Object.keys(e),...Object.keys(t)]);for(let o of r)if(!n.has(o)&&JSON.stringify(e[o])!==JSON.stringify(t[o]))return!0;return!1}function Qc(e,t){let n=Wt(e),r=Wt(t);if(!n||!r||n===r)return!1;let o=new Set(["x","y","w","h","g","id","z","name","label"]),s=new Set([...Object.keys(e),...Object.keys(t)]);for(let i of s)if(!o.has(i)&&JSON.stringify(e[i])!==JSON.stringify(t[i]))return!1;return!0}function zo(e,t){let n=[],r=[],o=[];for(let[s,i]of t)e.has(s)||n.push(i);for(let[s,i]of e)t.has(s)||r.push(i);for(let[s,i]of t){let d=e.get(s);d&&Zc(d,i)&&o.push({before:d,after:i})}return{added:n,removed:r,modified:o}}function _r(e){return e===1?"node":"nodes"}function eu(e){return e.length===1&&e[0]?`adds 1 ${et(e[0].type)}`:`adds ${e.length} ${_r(e.length)}`}function tu(e){return`deletes ${e} ${_r(e)}`}function nu(e){return`edits ${e} ${_r(e)}`}function ru(e){let t=[];e.removed>0&&t.push(tu(e.removed));let n=e.added??[],r=e.addedCount??n.length;r>0&&t.push(n.length===r?eu(n):`adds ${r} ${_r(r)}`),(e.modified??0)>0&&t.push(nu(e.modified??0));let o=t.length?t.join(", "):"replaces nodes",s=e.flowLabel?.trim();return s?`Replace ${s}: ${o}`:`Replace flow: ${o}`}function ou(e){let{added:t,removed:n,modified:r}=e;if(t.length+n.length+r.length!==1)return;if(t[0]){let u=t[0],p=et(u.type),f=Wt(u);return f?`Add ${p} node ${On(f)}`:`Add ${p} node`}if(n[0]){let u=n[0],p=et(u.type),f=Wt(u);return f?`Remove ${p} node ${On(f)}`:`Remove ${p} node`}let s=r[0];if(!s)return;let i=et(s.after.type,et(s.before.type)),d=Wt(s.before),l=Wt(s.after);if(Qc(s.before,s.after)&&d&&l)return`Rename ${i} node ${On(d)} \u2192 ${On(l)}`;let c=l||d;return c?`Edit ${i} ${On(c)}`:`Edit ${i}`}function Wo(e,t){return typeof e?.label=="string"&&e.label.trim()?e.label.trim():typeof t?.label=="string"&&t.label.trim()?t.label.trim():""}function Pr(e,t){if(typeof e.flowLabel=="string"&&e.flowLabel.trim())return e.flowLabel.trim();let n=Wo(e.payload?.flow,e.baseFlow);if(n)return n;let r=e.payload?.flowId;return r&&t?.has(r)?t.get(r)??"":""}function xi(e,t){let n=t.trim(),r=e.trim();if(!n||!r||r.includes(n))return r;let o=r.lastIndexOf(" in ");if(o>=0){let s=r.slice(o+4);if(s.length>0&&n.startsWith(s))return r}return`${r} in ${n}`}function Go(e,t){let n=zo(Gt(e??{}),Gt(t)),r=n.added.length+n.removed.length+n.modified.length,o=Wo(t,e);if(r===0)return o?`Update ${o}`:"Update flow";if(n.removed.length>0)return ru({...o?{flowLabel:o}:{},removed:n.removed.length,added:n.added,modified:n.modified.length});if(r>Jc)return o?`${r} changes in ${o}`:`${r} changes`;let s=ou(n)??(o?`Update ${o}`:"Update flow");return xi(s,o)}function su(e,t){let n=(e??"").trim();return!!(!n||n.includes("[object Object]")||/^Update(\s|$)/i.test(n)||t>0&&!/^Replace\b/i.test(n))}function Ko(e,t){return`${e} \xB7 ${au(t)}`}function iu(e){let t=e>=0?"+":"-",n=Math.abs(e),r=String(Math.floor(n/60)).padStart(2,"0"),o=String(n%60).padStart(2,"0");return`${t}${r}:${o}`}function au(e,t=new Date(e)){let n=Number.isNaN(t.getTime())?new Date(e):t;if(Number.isNaN(n.getTime()))return e.slice(0,16);let r=n.getDate(),s=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][n.getMonth()]??"Jan",i=String(n.getHours()).padStart(2,"0"),d=String(n.getMinutes()).padStart(2,"0"),l=iu(-n.getTimezoneOffset());return`${r} ${s} ${i}:${d} ${l}`}function Er(e){if(typeof e!="string")return;let t=e.replace(/\0/g,"").replace(/[\u0001-\u001F\u007F]/g," ").replace(/\s+/g," ").trim().slice(0,Kc).trim();return t.length>0?t:void 0}function du(e,t){let n=pe(t);return`${n==="/"?"/ai-access":`${n}/ai-access`}#proposal=${encodeURIComponent(e)}`}function Jo(e,t={}){let n=Pr(e,t.liveFlowLabels),r=Er(e.title),o=Go(e.baseFlow,n&&!Wo(e.payload.flow,e.baseFlow)?{...e.payload.flow,label:n}:e.payload.flow),s=zo(Gt(e.baseFlow??{}),Gt(e.payload.flow)),i=su(r,s.removed.length)?o:r??o;return xi(i,n)}function lu(e){return typeof e.storedTitle=="string"&&e.storedTitle?e.storedTitle:Er(e.title)}function ae(e,t={}){let n=Jo(e,t),r=Pr(e,t.liveFlowLabels),o=lu(e)??n,s=du(e.id,t.httpAdminRoot),i=t.req?Ln(t.req,s):s;return{...e,title:n,storedTitle:o,flowLabel:r,displayName:Ko(n,e.createdAt),reviewUrl:i,reviewPath:s}}function cu(e){let t=zo(Gt(e.baseFlow??{}),Gt(e.payload.flow)),n=[];return t.added.length>0&&n.push(t.added.length===1?"Adds 1 node":`Adds ${t.added.length} nodes`),t.removed.length>0&&n.push(t.removed.length===1?"Deletes 1 node":`Deletes ${t.removed.length} nodes`),t.modified.length>0&&n.push(t.modified.length===1?"Edits 1 node":`Edits ${t.modified.length} nodes`),n.length===0?"No node changes":n.map((r,o)=>o===0?r:r.charAt(0).toLowerCase()+r.slice(1)).join(", ")}function uu(e){if(e.kind==="oauth")return{kind:"oauth",clientId:e.clientId,user:e.user};let t={kind:e.kind,linkId:e.linkId};return typeof e.label=="string"&&e.label.length>0&&(t.label=e.label),t}function he(e,t={}){let n=ae(e,t),r=t.include??new Set,o={id:n.id,status:n.status,type:n.type,title:n.title,storedTitle:n.storedTitle,displayName:n.displayName,flowLabel:n.flowLabel,reviewUrl:n.reviewUrl,reviewPath:n.reviewPath,createdAt:n.createdAt,updatedAt:n.updatedAt,expectedRev:n.expectedRev,createdBy:uu(n.createdBy),changeSummary:cu(e)};return r.has("payload")&&(o.payload=n.payload),r.has("baseFlow")&&n.baseFlow!==void 0&&(o.baseFlow=n.baseFlow),n.warnings&&n.warnings.length>0&&(o.warnings=n.warnings),typeof e.payload.flowId=="string"&&(o.flowId=e.payload.flowId),e.restoredSecrets&&e.restoredSecrets.length>0&&(o.restoredSecrets=e.restoredSecrets),n.baseRevStale!==void 0&&(o.baseRevStale=n.baseRevStale),n.decidedBy!==void 0&&(o.decidedBy=n.decidedBy),n.reason!==void 0&&(o.reason=n.reason),n.resultRev!==void 0&&(o.resultRev=n.resultRev),o}function Ti(e,t={}){return e.map(n=>he(n,t))}var Kc,Jc,Ai,Re=U(()=>{"use strict";ce();Le();Kc=80,Jc=1;a(et,"nodeTypeText");Ai=Symbol.for("ai-access.liveFlowLabels");a(Rr,"liveFlowLabelsFromFlows");a(Si,"setRequestFlowLabels");a(Vc,"getRequestFlowLabels");a(Yc,"parseIncludeQuery");a(Xc,"isTruthyFlag");a(ie,"resolveIncludeFromQuery");a(q,"presentOptionsFor");a(Wt,"nodeLabel");a(On,"quoteName");a(Gt,"collectNodes");a(Zc,"nodeContentChanged");a(Qc,"isRenameOnly");a(zo,"classifyFlowChanges");a(_r,"pluralNodes");a(eu,"describeAddedClause");a(tu,"describeRemovedClause");a(nu,"describeModifiedClause");a(ru,"buildReplaceFlowTitle");a(ou,"describeOneChange");a(Wo,"flowLabelHint");a(Pr,"resolveFlowLabel");a(xi,"ensureTitleIncludesFlowName");a(Go,"autoGenerateProposalTitle");a(su,"shouldRefreshDisplayTitle");a(Ko,"buildProposalDisplayName");a(iu,"formatUtcOffset");a(au,"formatProposalSubmittedAt");a(Er,"sanitizeProposalTitle");a(du,"buildProposalReviewPath");a(Jo,"resolveProposalTitle");a(lu,"storedTitleOf");a(ae,"presentProposal");a(cu,"buildChangeSummary");a(uu,"presentCreatedByForApi");a(he,"presentProposalForApi");a(Ti,"presentProposalsForApi")});function Ri(e){let t=new Map;for(let n of e.nodes??[])t.set(n.id,n);for(let n of e.configs??[])t.set(n.id,n);return t}function fu(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]),r=[];for(let o of n)pu.has(o)||JSON.stringify(e[o])!==JSON.stringify(t[o])&&r.push(o);return r.sort()}function _i(e){let t=Ri(e.baseFlow??{}),n=Ri(e.payload.flow),r=new Map;for(let[o,s]of n){let i=t.get(o);if(!i){r.set(o,new Set(["*"]));continue}let d=fu(i,s);d.length>0&&r.set(o,new Set(d))}for(let[o]of t)n.has(o)||r.set(o,new Set(["*"]));return r}function mu(e,t){let n=[],r=[];for(let[o,s]of e){let i=t.get(o);if(!i)continue;n.push(o);let d=[];for(let c of s){if(c==="*"){d.push(...[...i].filter(u=>u!=="*")),i.has("*")&&d.push("*");continue}(i.has(c)||i.has("*"))&&d.push(c)}let l=[...new Set(d)].sort();l.length>0&&r.push({nodeId:o,fields:l})}return n.sort(),{nodeIds:n,fields:r}}function V(e,t,n={}){let r=0;for(let o of e.list())if(o.status==="pending"&&!(n.flowId&&o.payload.flowId!==n.flowId)&&o.expectedRev!==t)try{e.markClosed(o.id,"expired",hu),r+=1}catch{}return r}function Pi(e,t,n,r){let o=`Superseded by approved proposal ${n} (resultRev ${r}). Re-read the flow and resubmit if still needed.`,s=0;for(let i of e.list())if(!(i.id===n||i.status!=="pending"||i.payload.flowId!==t))try{e.markClosed(i.id,"superseded",o),s+=1}catch{}return s}function Oe(e,t,n){let r=_i(t),o=[];for(let s of e.list()){if(s.id===t.id||s.status!=="pending"||s.payload.flowId!==t.payload.flowId)continue;let i=Jo(s),d=n?gt(s.createdBy)===n:gt(s.createdBy)===gt(t.createdBy),l=mu(r,_i(s)),c=s.expectedRev===t.expectedRev,u=l.nodeIds.length>0;if(!c&&!u)continue;let p={id:s.id,title:i,displayName:Ko(i,s.createdAt),mine:d};l.nodeIds.length>0&&(p.overlappingNodeIds=l.nodeIds),l.fields.length>0&&(p.overlappingFields=l.fields),o.push(p)}return{conflictingProposals:o}}function $e(e){let t={};return e.conflictingProposals.length>0&&(t.conflictingProposals=e.conflictingProposals,t.warning="Other pending proposals touch the same flow \u2014 approve order may conflict (like a git merge)."),t}function Z(e){return e>0?{autoClosedCount:e}:{}}var pu,hu,tt=U(()=>{"use strict";Ze();Re();pu=new Set(["x","y","w","h","g","id","z"]);a(Ri,"collectNodes");a(fu,"changedFieldKeys");a(_i,"computeProposalTouches");a(mu,"overlapBetween");hu="Proposal expired: the live flow revision moved past expectedRev. Re-read the flow and resubmit.";a(V,"expireStalePendingProposals");a(Pi,"supersedePendingOnFlow");a(Oe,"findConflictingProposals");a($e,"conflictWarningFields");a(Z,"autoClosedFields")});function Nr(e){let t=e.join("\0"),n=Cr.get(t);if(n)return n;let r=[...new Set(e.map(d=>sr(d).join("")).filter(Boolean))],o=new Map,s={patterns:[...e],matches(d){if(typeof d!="string"||d.length===0||r.length===0)return!1;let l=o.get(d);if(l!==void 0)return l;let c=i(d);return o.size>4096&&o.clear(),o.set(d,c),c}};function i(d){{let l=sr(d),c=l[l.length-1];if(!c||gu.has(c))return!1;for(let u of r){let p=`${u}S`;for(let f=0;f<l.length;f++){let m="";for(let g=f;g<l.length;g++){if(m+=l[g],m===u||m===p)return!0;if(m.length>p.length)break}let h=l[f]??"";if(u.length>=6&&(h.endsWith(u)||h.endsWith(p)))return!0}}return!1}}return a(i,"matchUncached"),Cr.size>32&&Cr.clear(),Cr.set(t,s),s}function Kt(){return qs()?.getEffective()?.secretNamePatterns??ct}function Dn(e){let t=e.trim();return!(!t||t===te||yu.has(t.toLowerCase())||/^\$\{[A-Za-z0-9_]+\}$/.test(t))}function bu(e){let t=Li.get(e);return t||(t=vu(e),Li.set(e,t)),t}function vu(e){return[{hints:["Buffer.from","btoa"],re:/\b(?:Buffer\.from|btoa)\([ \t]{0,8}(?<q>["'`])[^"'`:\s]{1,256}:(?<s>[^"'`\s]{1,1024})\k<q>/dg,accept:a(t=>!t.includes("${"),"accept")},{hints:["PRIVATE KEY-----"],re:/-----BEGIN ((?:[A-Z0-9]{1,16} ){0,4})PRIVATE KEY-----(?<s>[^-]{1,16384})-----END \1PRIVATE KEY-----/dg},{hints:["AKIA","ASIA","ABIA","ACCA"],re:/(?<![A-Za-z0-9])(?<s>(?:AKIA|ASIA|ABIA|ACCA)[0-9A-Z]{16})(?![A-Za-z0-9])/dg},{hints:["ghp_","gho_","ghu_","ghs_","ghr_"],re:/(?<![A-Za-z0-9_])(?<s>gh[pousr]_[A-Za-z0-9]{36,255})(?![A-Za-z0-9_])/dg},{hints:["github_pat_"],re:/(?<![A-Za-z0-9_])(?<s>github_pat_[A-Za-z0-9_]{22,255})(?![A-Za-z0-9_])/dg},{hints:["xox"],re:/(?<![A-Za-z0-9_-])(?<s>xox[abposr]-[A-Za-z0-9-]{10,512})/dg},{hints:["hooks.slack.com"],re:/hooks\.slack\.com\/services\/(?<s>[A-Za-z0-9_/]{16,512})/dg},{hints:["sk-"],re:/(?<![A-Za-z0-9_-])(?<s>sk-[A-Za-z0-9_-]{20,512})/dg},{hints:["k_live_","k_test_"],re:/(?<![A-Za-z0-9_])(?<s>[sr]k_(?:live|test)_[A-Za-z0-9]{16,512})/dg},{hints:["AIza"],re:/(?<![A-Za-z0-9_-])(?<s>AIza[0-9A-Za-z_-]{35})(?![A-Za-z0-9_-])/dg},{hints:["eyJ"],re:/(?<![A-Za-z0-9_-])(?<s>eyJ[A-Za-z0-9_-]{5,8192}\.[A-Za-z0-9_-]{5,8192}\.[A-Za-z0-9_-]{5,8192})/dg},{hints:["earer"],re:/\b[Bb]earer[ \t]{1,8}(?<s>(?=[A-Za-z0-9\-._~+/]{0,4096}[0-9\-._~+/])[A-Za-z0-9\-._~+/]{16,4096}={0,3})/dg},{hints:["asic"],re:/\b[Bb]asic[ \t]{1,8}(?<s>(?=[A-Za-z0-9+/]{0,4096}[0-9+/])[A-Za-z0-9+/]{8,4096}={0,2})(?![A-Za-z0-9+/])/dg},{hints:["@"],re:/\b[A-Za-z][A-Za-z0-9+.-]{1,20}:\/\/[^\s:@/'"`<>]{1,256}:(?<s>[^\s@/'"`<>]{1,1024})@/dg,accept:a(t=>!/^(?:\$\{|\{\{|<|%)/.test(t)&&t!==te,"accept")},{hints:["?","&"],re:/[?&](?<k>[A-Za-z_][\w.-]{0,63})=(?<s>[^&#\s"'`<>]{1,2048})/dg,accept:a((t,n)=>e.matches(n.groups?.k??"")&&Dn(Tu(t)),"accept")},{hints:[":","="],re:/(?<![\w$-])(?<q>["'`]?)(?<k>[A-Za-z_$][\w$-]{0,63})\k<q>\]?[ \t]{0,8}(?::|=(?![=>]))[ \t]{0,8}(?<vq>["'`])(?<s>(?:\\.|(?!\k<vq>)[^\\\r\n]){1,1024})\k<vq>/dg,accept:a((t,n)=>{let r=n.groups?.k??"";return!e.matches(r)||n.groups?.vq==="`"&&t.includes("${")||t.length<3||/\s/.test(t)?!1:Dn(t)},"accept")}]}function Au(e){let t=new Map;for(let r of e)t.set(r,(t.get(r)??0)+1);let n=0;for(let r of t.values()){let o=r/e.length;n-=o*Math.log2(o)}return n}function Su(e){return/[^A-Za-z]/.test(e)||e.length>=16?!0:[/[a-z]/,/[A-Z]/,/[0-9]/,/[^A-Za-z0-9]/].filter(n=>n.test(e)).length>=3||Au(e)>=3}function xu(e){let t=e.patterns.filter(r=>ku.includes(r.toUpperCase())),n=Nr(t);return{hints:[":","="],re:/(?<![\w$.-])(?<k>[A-Za-z_][\w-]{0,63})[ \t]{0,8}(?::(?!\/\/)|=)[ \t]{0,8}(?<s>[^\s;,'"`&<>(){}[\]]{3,1024})/dg,accept:a((r,o)=>e.matches(o.groups?.k??"")&&Dn(r)&&!r.startsWith("$")&&!r.startsWith("{{")&&(n.matches(o.groups?.k??"")||Su(r))&&!/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(r)&&o.input.charAt(o.indices?.groups?.s?.[1]??0)!=="(","accept")}}function Tu(e){try{return decodeURIComponent(e)}catch{return e}}function Ru(e,t,n){let r=e.indexOf("=",t);if(r<=t||r>=n||r-t>48)return;let o=e.slice(t,r),s=Math.max(o.lastIndexOf('"'),o.lastIndexOf("'"),o.lastIndexOf("`")),i=o.slice(s+1).trim();if(!/^[A-Za-z][\w -]{0,40}$/.test(i))return;let d=r+1;for(;d<n&&(e[d]===" "||e[d]===" ");)d++;let l=d;for(;l<n&&!"\"'`\r".includes(e[l]??"");)l++;for(;l>d&&(e[l-1]===" "||e[l-1]===" ");)l--;return{k:i,vs:d,ve:l}}function _u(e,t){if(!e.includes(";")||!e.includes("="))return[];let n=[],r=0;for(;r<=e.length;){let o=e.indexOf(`
7
- `,r);o<0&&(o=e.length);let s=[],i=r,d=!1;for(;i<o;){let l=e.indexOf(";",i);l<0||l>o?l=o:d=!0;let c=Ru(e,i,l);c&&s.push(c),i=l+1}if(d&&s.length>=2)for(let l of s)l.ve<=l.vs||!t.matches(l.k)||Dn(e.slice(l.vs,l.ve))&&n.push({start:l.vs,end:l.ve});r=o+1}return n}function Pu(e,t,n={}){if(e.length===0)return{text:e,secrets:[]};let r=[],o=bu(t);if(!n.code){let c=Ii.get(t);c||(c=xu(t),Ii.set(t,c)),o=[...o,c]}r.push(..._u(e,t));for(let c of o){if(c.hints&&!c.hints.some(p=>e.includes(p)))continue;c.re.lastIndex=0;let u;for(;(u=c.re.exec(e))!==null;){let p=u.indices?.groups?.s,f=u.groups?.s;p&&f!==void 0&&f.length>0&&(!c.accept||c.accept(f,u))&&r.push({start:p[0],end:p[1]}),u[0].length===0&&c.re.lastIndex++}}if(r.length===0)return{text:e,secrets:[]};r.sort((c,u)=>c.start-u.start||u.end-c.end);let s=[];for(let c of r){let u=s[s.length-1];u&&c.start<u.end?u.end=Math.max(u.end,c.end):s.push({...c})}let i="",d=0,l=[];for(let c of s){let u=e.slice(c.start,c.end);u!==te&&(i+=e.slice(d,c.start)+te,l.push(u),d=c.end)}return i+=e.slice(d),{text:i,secrets:l}}function re(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function Eu(e){return re(e)&&typeof e.id=="string"&&typeof e.type=="string"}function Ui(e,t){let n=new Yo(t,e.id),r=e[$n];if(Array.isArray(r))for(let s of r)typeof s=="string"&&n.paths.push(s);let o={};for(let[s,i]of Object.entries(e))s!==$n&&(Mi.has(s)?o[s]=i:Hi.has(s)&&Array.isArray(i)?o[s]=i.map(d=>Zo(d,t)):s==="env"&&Array.isArray(i)?o[s]=n.env(i,"env"):o[s]=n.value(i,s,s,e));return n.paths.length>0&&(o[$n]=n.paths),o}function Zo(e,t){return Array.isArray(e)?e.map(n=>Zo(n,t)):Eu(e)?Ui(e,t):e}function Ir(e,t=Kt()){let n={matcher:Nr(t),count:0};return{value:Zo(e,n),count:n.count}}function Jt(e,t=Kt()){let n={...e,payload:{...e.payload,flow:Ir(e.payload.flow,t).value}};return e.baseFlow!==void 0&&(n.baseFlow=Ir(e.baseFlow,t).value),n}function bt(e,t=Kt){if(Bi(e))return e;let n=t===Kt,r=n?Ni.get(e):void 0;if(r)return r;let o={kind:e.kind,[Qo]:e,async getFlows(){let s=await e.getFlows();return{rev:s.rev,flows:Ir(s.flows,t()).value}},async getFlow(s){let i=await e.getFlow(s);return{rev:i.rev,flow:Ir(i.flow,t()).value}},updateFlow:a(s=>e.updateFlow(s),"updateFlow")};return n&&Ni.set(e,o),o}function Bi(e){return Qo in e}function qi(e){return Bi(e)?e[Qo]:e}function Me(e,t){return e.accessLink||e.auth?bt(t):t}function Fr(e){let t=0,n=e.indexOf(te);for(;n!==-1;)t++,n=e.indexOf(te,n+te.length);return t}function Fi(e){let t=e.split(te),n=[];for(let r=1;r<t.length;r++){let o=t[r-1]??"",s=t[r]??"",i=o.lastIndexOf(`
3
+ `,o=Buffer.from(r,"utf8");if(o.byteLength>t)throw new ce(n.kind,o.byteLength,t);return{bytes:o}}function na(e,t){let n=(0,Yn.resolve)(t),r=(0,Yn.resolve)(e);if(r!==n&&!r.startsWith(n+Yn.sep))throw new Error("Path escapes .ai-access storage root");return r}function un(e,t,n){let r=na(t,e),o=`${r}.${process.pid}.tmp`;(0,it.writeFileSync)(o,n.bytes,{mode:Hr}),(0,it.renameSync)(o,r),(0,it.chmodSync)(r,Hr)}function ra(e,t,n){let r=na(t,e);(0,it.appendFileSync)(r,n.bytes,{mode:Hr});try{(0,it.chmodSync)(r,Hr)}catch{}}var it,Yn,ys,Hr,K,ta,ce,oe=q(()=>{"use strict";it=require("fs"),Yn=require("path"),ys=448,Hr=384,K=15e5,ta=8192,ce=class extends Error{constructor(n,r,o){super(`${n} size ${r} bytes exceeds limit of ${o} bytes`);this.kind=n;this.size=r;this.maxBytes=o;this.name="StoragePayloadTooLargeError"}static{a(this,"StoragePayloadTooLargeError")}code="payload_too_large";statusCode=413};a(ht,"encodeTrustedJson");a(na,"assertUnderAiAccess");a(un,"writeTrustedAtomic");a(ra,"appendTrusted")});function Zn(e){if(typeof e!="string"||e.length===0||e.length>2048)return;let t;try{t=new URL(e)}catch{return}if(!(t.protocol!=="http:"&&t.protocol!=="https:")&&!(t.username||t.password||t.search||t.hash)&&!(t.pathname!=="/"||/^[a-z][a-z0-9+.-]*:\/\/[^/]*\//i.test(e)))return t.origin.toLowerCase()}function uu(e,t){let n=e.getHeader("Vary"),r=String(n??"").split(",").map(o=>o.trim()).filter(Boolean);r.some(o=>o.toLowerCase()===t.toLowerCase())||r.push(t),e.setHeader("Vary",r.join(", "))}function sa(e){return(t,n,r)=>{if(!au.test(t.path)){r();return}for(let c of n.getHeaderNames())c.startsWith("access-control-")&&n.removeHeader(c);let o=n.setHeader.bind(n),s=!1;n.setHeader=((c,u)=>!s&&/^access-control-/i.test(c)?n:o(c,u));let i=Zn(t.get("origin")),d=i===void 0?void 0:e().find(c=>c===i),l=d!==void 0;if(uu(n,"Origin"),!l){if(t.method==="OPTIONS"&&t.get("access-control-request-method")){n.status(204).end();return}r();return}if(s=!0,n.setHeader("Access-Control-Allow-Origin",d),n.setHeader("Access-Control-Expose-Headers",cu),t.method==="OPTIONS"&&t.get("access-control-request-method")){n.setHeader("Access-Control-Allow-Methods",du),n.setHeader("Access-Control-Allow-Headers",lu),n.setHeader("Access-Control-Max-Age",String(600)),n.status(204).end();return}s=!1,r()}}function ia(e,t){let n=e.parent,r=n&&typeof n.use=="function"?n:e;r.use(t);let o=r,i=(o._router??o.router)?.stack;if(!Array.isArray(i)||i.length<2)return;let d=i.pop(),l=0;for(;l<i.length;){let c=i[l].name;if(c!=="query"&&c!=="expressInit")break;l+=1}i.splice(l,0,d)}var au,du,lu,cu,Br=q(()=>{"use strict";au=/^\/mcp(?:\/?$|\/(?:a(?:\/|$)|api(?:\/|$)|rpc\/?$))/,du="GET, POST, DELETE, OPTIONS",lu="Authorization, Content-Type, Accept, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID",cu="Mcp-Session-Id, WWW-Authenticate, Retry-After, RateLimit, RateLimit-Policy, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset";a(Zn,"canonicalOrigin");a(uu,"appendVary");a(sa,"aiCorsMiddleware");a(ia,"installAiCorsMiddleware")});function zr(e){return e.replace(/([a-z])([A-Z])/g,"$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g,"$1 $2").replace(/([A-Za-z])([0-9])/g,"$1 $2").replace(/([0-9])([A-Za-z])/g,"$1 $2").split(/[^A-Za-z0-9]+/).filter(Boolean).map(t=>t.toUpperCase())}function er(e){if(!Array.isArray(e))return;let t=[];for(let n of e){if(typeof n!="string")continue;let r=n.trim().toUpperCase();if(!(!qr.test(r)||t.includes(r))&&(t.push(r),t.length>=Qn))break}return t}var J,Ot,qr,Qn,pn=q(()=>{"use strict";J="[REDACTED]",Ot=Object.freeze(["PASSWORD","PASSWD","PASS","PWD","PW","PASSPHRASE","SECRET","CLIENT_SECRET","TOKEN","API_KEY","APIKEY","KEY","PRIVATE","CREDENTIAL","AUTH","AUTHORIZATION","BEARER","SESSION","COOKIE","CONNECTION_STRING","CONN_STR","CONN","DSN"]),qr=/^[A-Za-z0-9_-]{2,40}$/,Qn=100;a(zr,"tokenizeName");a(er,"normalizeSecretNamePatterns")});function fu(e){return e==="nodes"||e==="flows"||e==="full"}function Wr(e,t){if(!e||typeof e!="object"||Array.isArray(e))return{};let n=e,r={};if(typeof n.enabled=="boolean"&&(r.enabled=n.enabled),typeof n.allowWithoutAdminAuth=="boolean"&&(r.allowWithoutAdminAuth=n.allowWithoutAdminAuth),typeof n.defaultLinkTtlSeconds=="number"&&Number.isInteger(n.defaultLinkTtlSeconds)&&n.defaultLinkTtlSeconds>0&&(r.defaultLinkTtlSeconds=Math.floor(n.defaultLinkTtlSeconds)),typeof n.proposalTtlDays=="number"&&Number.isInteger(n.proposalTtlDays)&&n.proposalTtlDays>=1&&n.proposalTtlDays<=365&&(r.proposalTtlDays=n.proposalTtlDays),fu(n.defaultDeploymentType)&&(r.defaultDeploymentType=n.defaultDeploymentType),typeof n.maxActiveLinks=="number"&&Number.isInteger(n.maxActiveLinks)&&n.maxActiveLinks>0&&(r.maxActiveLinks=Math.floor(n.maxActiveLinks)),typeof n.publicBaseUrl=="string")try{r.publicBaseUrl=mn(n.publicBaseUrl)}catch{t?.(`AI Access: invalid publicBaseUrl value ${JSON.stringify(n.publicBaseUrl)} was dropped`)}if(Array.isArray(n.corsAllowedOrigins)){let o=new Set;for(let s of n.corsAllowedOrigins){let i=Zn(s);i?o.add(i):t?.(`AI Access: invalid CORS origin value ${JSON.stringify(s)} was dropped`)}r.corsAllowedOrigins=[...o]}for(let o of["rateLimitWindowMs","rateLimitMaxPerIp","rateLimitMaxPerToken"]){let s=n[o],i=o==="rateLimitWindowMs"?100:1,d=o==="rateLimitWindowMs"?36e5:1e6;typeof s=="number"&&Number.isInteger(s)&&s>=i&&s<=d&&(r[o]=s)}if(n.secretNamePatterns!==void 0){let o=er(n.secretNamePatterns);o?(Array.isArray(n.secretNamePatterns)&&o.length<n.secretNamePatterns.length&&t?.("AI Access: invalid or duplicate secretNamePatterns entries were dropped"),r.secretNamePatterns=o):t?.("AI Access: secretNamePatterns must be a list of strings; ignored")}if(n.oauth&&typeof n.oauth=="object"&&!Array.isArray(n.oauth)){let o=n.oauth;r.oauth={},typeof o.enabled=="boolean"&&(r.oauth.enabled=o.enabled);for(let i of["accessTokenTtlSec","refreshIdleTtlSec","dcrMaxClients"]){let d=o[i];typeof d=="number"&&Number.isInteger(d)&&d>0&&d<=31536e3&&(r.oauth[i]=d)}let s=o.refreshMaxLifetimeSec;typeof s=="number"&&Number.isInteger(s)&&s>=0&&s<=31536e4&&(r.oauth.refreshMaxLifetimeSec=s)}return r}function hu(e,t){if(!(0,nr.existsSync)(e))return{};try{let n=(0,nr.readFileSync)(e,"utf8");return Wr(JSON.parse(n),t)}catch(n){return t?.(`AI Access: config.json is corrupt or unreadable; defaults are used (${String(n)})`),{}}}function Gr(e,t){let n={},r={...tr};for(let s of Object.keys(tr).filter(i=>i!=="oauth"))n[s]=e[s]!==void 0?"settings.js":t[s]!==void 0?"disk":"default",Object.assign(r,{[s]:e[s]??t[s]??tr[s]});let o={...r,sources:n};return e.oauth!==void 0||t.oauth!==void 0?(o.oauth={...fn,...t.oauth??{},...e.oauth??{}},n.oauth=e.oauth!==void 0?"settings.js":"disk"):(o.oauth={...fn},n.oauth="default"),o}function vs(e,t){let n=Wr(e??{},t);for(let o of Object.keys(tr).filter(s=>s!=="oauth"))e&&Object.hasOwn(e,o)&&n[o]===void 0&&Object.assign(n,{[o]:tr[o]});let r=e?.oauth;if(r&&typeof r=="object"&&!Array.isArray(r)){let o=n.oauth??={};for(let s of Object.keys(fn))Object.hasOwn(r,s)&&o[s]===void 0&&(t?.(`AI Access: settings.js aiAccess.oauth.${s} is invalid; using the default (${String(fn[s])})`),Object.assign(o,{[s]:fn[s]}))}return n}var nr,bs,pu,fn,tr,hn,Kr=q(()=>{"use strict";nr=require("fs"),bs=require("path");ve();Br();pn();oe();pu=16384,fn={enabled:!1,accessTokenTtlSec:3600,refreshIdleTtlSec:2592e3,refreshMaxLifetimeSec:7776e3,dcrMaxClients:5},tr={enabled:!0,allowWithoutAdminAuth:!1,defaultLinkTtlSeconds:14400,proposalTtlDays:7,maxActiveLinks:10,defaultDeploymentType:"nodes",publicBaseUrl:"",corsAllowedOrigins:[],rateLimitWindowMs:6e4,rateLimitMaxPerIp:600,rateLimitMaxPerToken:300,secretNamePatterns:[...Ot],oauth:{...fn}};a(fu,"isDeploymentType");a(Wr,"sanitizePartial");a(hu,"readDiskFile");a(Gr,"mergeAiAccessSettings");a(vs,"settingsJsPartial");hn=class{constructor(t,n,r){this.paths=t;this.filePath=(0,bs.join)(t.root,"config.json"),this.settingsJs=vs(n,r),this.disk=hu((0,nr.existsSync)(this.filePath)?this.filePath:(0,bs.join)(t.root,"settings.json"),r),this.cached=Gr(this.settingsJs,this.disk)}static{a(this,"SettingsStore")}filePath;disk;settingsJs;cached;getEffective(){return this.cached}getDisk(){return{...this.disk}}update(t){let n=Wr(t);for(let r of Object.keys(this.settingsJs))delete n[r];return this.persist(n),this.disk=n,this.cached=Gr(this.settingsJs,this.disk),this.cached}patch(t,n=[]){if(t.defaultLinkTtlSeconds!==void 0&&(!Number.isInteger(t.defaultLinkTtlSeconds)||t.defaultLinkTtlSeconds<1))throw new Error("defaultLinkTtlSeconds must be an integer with a minimum of 1");if(t.proposalTtlDays!==void 0&&(!Number.isInteger(t.proposalTtlDays)||t.proposalTtlDays<1||t.proposalTtlDays>365))throw new Error("proposalTtlDays must be an integer between 1 and 365");let r=Wr(t),o={...this.disk,...r,...r.oauth?{oauth:{...this.disk.oauth,...r.oauth}}:{}};for(let s of n)delete o[s];return this.update(o)}persist(t){let n=ht(t,pu,{kind:"settings",pretty:!0});un(this.paths.root,this.filePath,n)}}});function Vr(e){Jr=e}function aa(){return Jr}function re(e){return e.adminAuth!=null&&e.adminAuth!==!1}function Ce(e){return Jr?Jr.getEffective():Gr(vs(e.aiAccess),{})}function Ee(e){let t=Ce(e);return t.enabled?re(e)?!0:t.allowWithoutAdminAuth:!1}var Jr,ke=q(()=>{"use strict";Kr();Jr=null;a(Vr,"bindSettingsStore");a(aa,"getSettingsStore");a(re,"isAdminAuthEnabled");a(Ce,"resolveAiAccessSettings");a(Ee,"areAiFeaturesEnabled")});function Ae(e){let t=`/${e??""}`.replace(/\/{2,}/g,"/");for(;t.length>1&&t.endsWith("/");)t=t.slice(0,-1);return t}function xe(e){let t=Ae(e);return t==="/"?"/mcp/api/v1":`${t}/mcp/api/v1`}function rr(e){let t=Ae(e);return t==="/"?"/ai-access":`${t}/ai-access`}function mt(e){let t=Ae(e);return t==="/"?"/mcp/rpc":`${t}/mcp/rpc`}function mu(e){let t=e.authInfo?.scope;if(t)return Array.isArray(t)?t:[t];let n=e.user?.permissions;return n?Array.isArray(n)?n:[n]:[]}function gt(e){if(e.auth)return e.auth.scopes.includes("propose")?"propose":e.auth.scopes.includes("read")?"read":null;let t=mu(e);return t.includes("*")?"*":t.includes("read")?"read":null}function gu(e,t){return!t||!e?{view:!0,manageLinks:!1,approveProposals:!1}:e==="*"?{view:!0,manageLinks:!0,approveProposals:!0}:{view:!0,manageLinks:!1,approveProposals:!1}}function da(e,t={error:"Authentication required",code:"auth_required"},n){let r=e.end.bind(e),o=!0;return e.end=((...s)=>o&&e.statusCode===401&&!e.headersSent?(o=!1,e.removeHeader("Content-Length"),e.setHeader("Content-Type","application/json; charset=utf-8"),n&&e.setHeader("WWW-Authenticate",n),r(JSON.stringify(t))):r(...s)),()=>{o=!1,e.end=r}}function Se(e,t){let n="read";return(r,o,s)=>{if(!re(e.settings)){s();return}let i=t==="api"?da(o):null;e.auth.needsPermission(n)(r,o,d=>{if(i?.(),d){s(d);return}if(!r.user){if(t==="page"){o.redirect(Ae(e.settings.httpAdminRoot));return}o.status(401).json({error:"Authentication required",code:"auth_required"});return}if(!gt(r)){o.status(401).json({error:"Insufficient permissions",code:"insufficient_permissions"});return}s()})}}function Yr(e,t){let n=re(e.settings),r=Ce(e.settings),o=Ee(e.settings),s=null;return n?s=gt(t):o&&(s="*"),{adminAuthEnabled:n,aiFeaturesEnabled:o,allowWithoutAdminAuth:r.allowWithoutAdminAuth,permission:s,capabilities:gu(s,o)}}function gn(e){return(t,n,r)=>{if(!Ee(e.settings)){n.status(403).json({error:"AI Access features are disabled. Enable adminAuth, or set aiAccess.allowWithoutAdminAuth to true for local-only use.",code:"ai_features_disabled"});return}r()}}function yn(e){return(t,n,r)=>{if(!re(e.settings)){if(Ee(e.settings)){r();return}n.status(403).json({error:"AI Access features are disabled",code:"ai_features_disabled"});return}if(gt(t)!=="*"){n.status(403).json({error:"Full admin permission (*) is required for this action",code:"insufficient_permissions"});return}r()}}function yu(e){return e.startsWith("aia_")}function wu(e){return yu(e)?!0:e.length<24}function bu(e){if(e.length<24||e.startsWith("aia_")&&e.length<40)return"The token may be truncated \u2014 ask the user for the full access-link token."}function la(e,t,n,r){if(!re(e.settings)){r();return}let o={error:Ie,code:at},s=da(n,o,fe);e.auth.needsPermission("read")(t,n,i=>{if(s(),i){r(i);return}if(!n.headersSent){if(!t.user||!gt(t)){n.setHeader("WWW-Authenticate",fe),n.status(401).json(o);return}r()}})}function ca(e,t,n,r,o){let s=e.headers.authorization;if(typeof s!="string"||!/^Bearer\s+\S+/i.test(s.trim()))return"continue";let d=/^Bearer\s+(\S+)$/i.exec(s.trim())?.[1];if(!d)return"continue";let l=n(d);if(l.status==="active")return l.link.kind!==r?(ir(t,r,l.link.kind),"wrong_kind"):(e.accessLink=l.link,"ok");if(l.status==="expired")return sr(e,t,o,l.expiresAt),"invalid";if(wu(d)){t.setHeader("WWW-Authenticate",fe);let c=bu(d);return t.status(401).json({error:$t,code:or,...c?{hint:c}:{}}),"invalid"}return"continue"}function ua(e,t,n="rest"){return(r,o,s)=>{if(!Ee(e.settings)){o.status(403).json({error:"AI Access features are disabled. Enable adminAuth, or set aiAccess.allowWithoutAdminAuth to true for local-only use.",code:"ai_features_disabled"});return}let i=ca(r,o,t,n,e.settings.httpAdminRoot);if(i==="ok"){s();return}i==="invalid"||i==="wrong_kind"||la(e,r,o,s)}}function pa(e,t,n="rest"){return(r,o,s)=>{let i=ca(r,o,t,n,e.settings.httpAdminRoot);if(i==="ok"){s();return}i==="invalid"||i==="wrong_kind"||la(e,r,o,s)}}var ze=q(()=>{"use strict";Mt();ke();a(Ae,"normalizeHttpAdminRoot");a(xe,"mcpApiBase");a(rr,"dashboardPath");a(mt,"mcpRpcPath");a(mu,"scopesFromRequest");a(gt,"resolvePermission");a(gu,"capabilitiesFor");a(da,"interceptNeedsPermissionUnauthorized");a(Se,"requireAdminSession");a(Yr,"describeAccess");a(gn,"requireAiFeatures");a(yn,"requireStarPermission");a(yu,"looksLikeAccessLinkToken");a(wu,"looksLikeFailedAccessLinkAttempt");a(bu,"truncatedTokenHint");a(la,"continueAsAdminSessionOrAccessLinkMissing");a(ca,"tryAccessLinkBearer");a(ua,"requireAdminReadOrAccessLink");a(pa,"requireAdminSessionOrAccessLink")});function vu(e,t){let n=Ae(e);return`${n==="/"?"/mcp/a":`${n}/mcp/a`}/${t}`}function ku(e){return!!e.app.get("trust proxy")}function fa(e,t){let n=e.get(t);if(typeof n!="string"||n.length===0)return;let r=n.split(",")[0]?.trim();return r&&r.length>0?r:void 0}function Au(e){if(typeof e!="string"||e.trim().length===0)return;let t;try{t=new URL(e.trim())}catch{return}if(!(t.protocol!=="http:"&&t.protocol!=="https:"))return`${t.origin}${t.pathname.replace(/\/+$/,"")}`}function Dt(e){try{let n=new URL(`http://${e.includes(":")&&!e.startsWith("[")?`[${e}]`:e}`).hostname.replace(/^\[|\]$/g,"");if(!(0,ks.isIP)(n))return;if(n.startsWith("::ffff:")){let r=n.slice(7);if((0,ks.isIP)(r)===4)return r;let o=r.split(":");if(o.length===2){let s=parseInt(o[0],16)*65536+parseInt(o[1],16);return[24,16,8,0].map(i=>s>>>i&255).join(".")}}return n}catch{return}}function mn(e){if(e==="")return"";let t="publicBaseUrl must be an HTTP(S) origin with no path, query, fragment, or credentials";if(typeof e!="string")throw new Error(t);let n;try{n=new URL(e.trim())}catch{throw new Error(t)}if(n.protocol!=="https:"&&n.protocol!=="http:"||!/^https?:\/\/[^/?#]+\/?$/i.test(e.trim())||n.pathname!=="/"||n.search||n.hash||n.username||n.password||e.includes("?")||e.includes("#"))throw new Error(t);return n.origin}function Xr(e){As=Au(e)}function ma(e){if(typeof e!="string"||e.length===0||e.length>260)return!1;let t,n,r=/^(\[[^\]]*\])(?::(\d+))?$/.exec(e);if(r){if(t=r[1],n=r[2],!Tu.test(t))return!1;try{new URL(`http://${t}/`)}catch{return!1}}else{let o=/^([^:]+)(?::(\d+))?$/.exec(e);if(!o||(t=o[1],n=o[2],t.length>253)||!Su.test(t)&&!xu.test(t))return!1}if(n!==void 0){if(!/^\d{1,5}$/.test(n))return!1;let o=Number(n);if(o<1||o>65535)return!1}return!0}function ya(e){let t=e?.trim().toLowerCase();return t==="http"||t==="https"?t:void 0}function Ru(e){let t=e.socket;return t?.encrypted?"https":t?"http":ya(e.protocol)??"http"}function xs(e){if(As)return As;let t=ku(e),n=t?fa(e,"x-forwarded-host"):void 0,r=e.get("host")?.trim(),o=ma(n)?n:ma(r)?r:void 0;if(!o)return;let s=(t?ya(fa(e,"x-forwarded-proto")):void 0)??Ru(e);try{return new URL(`${s}://${o}`).origin}catch{return}}function ar(e,t){let n=xs(e);return n?`${n}${t}`:t}function wa(e,t,n){return ar(e,vu(t,n))}function Ss(e){return e.replace(/\/mcp\/a\/[^/?#]+/gi,"/mcp/a/<redacted>")}var ks,wn,ga,As,ha,xu,Su,Tu,ve=q(()=>{"use strict";ks=require("net");ze();wn=8192,ga=1800*1e3;a(vu,"mcpAccessLinkPath");a(ku,"isTrustProxyEnabled");a(fa,"firstForwardedValue");a(Au,"normalizePublicBaseUrl");a(Dt,"canonicalizeIp");a(mn,"validateSettingsPublicBaseUrl");a(Xr,"bindPublicBaseUrl");ha="[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?",xu=new RegExp(`^${ha}(?:\\.${ha})*$`),Su=/^(?:(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)$/,Tu=/^\[[0-9A-Fa-f:.]+\]$/;a(ma,"isValidHostHeader");a(ya,"validScheme");a(Ru,"connectionScheme");a(xs,"requestPublicOrigin");a(ar,"absolutizePath");a(wa,"buildAccessLinkUrl");a(Ss,"redactAccessLinkTokenInPath")});function Lu(e){if(!e||e.trim().length===0)return{kind:"missing"};let t=e.trim();if(/^Basic\s+/i.test(t))return{kind:"basic"};let n=/^Bearer\s+(\S+)$/i.exec(t);return n?.[1]?{kind:"bearer",token:n[1]}:{kind:"other"}}function Ts(e,t,n,r){e.setHeader("WWW-Authenticate",fe),e.status(401).json({error:t,code:n,...r?{hint:r}:{}})}function Nu(e){let t=e??"";for(;t.endsWith("/");)t=t.slice(0,-1);return`${t}/ai-access#links`}function Fu(e,t,n,r){let o=ar(e,Nu(t));return{error:Pu,code:Cu,...n?{expiredAt:n}:{},accessLinksUrl:o,hint:r??`Ask the user to create a new access link in Node-RED on the /ai-access dashboard \u2192 Access tokens (${o}).`}}function sr(e,t,n,r,o){t.setHeader("WWW-Authenticate",fe),t.status(401).json(Fu(e,n,r,o))}function Ou(e){if(e.length<24||e.startsWith("aia_")&&e.length<40)return"The token may be truncated \u2014 ask the user for the full access-link token."}function Zr(e){return e==="get_link"?"Please open Node-RED \u2192 /ai-access \u2192 Access tokens and create a get_link access URL, then send me that /mcp/a/<token> link.":`Please open Node-RED \u2192 /ai-access \u2192 Access tokens and create a kind=${e} token, then send me the Bearer token (not a get_link path URL).`}function Mu(e,t){let n=$u;return e==="get_link"?`This URL requires a get_link token. This token is kind=${t} \u2014 ${n} for a get_link access URL (/mcp/a/<token>/\u2026). What to tell the user: ${Zr("get_link")}`:e==="rest"?`This REST endpoint requires a rest token. This token is kind=${t} \u2014 ${n}. What to tell the user: ${Zr("rest")}`:`This MCP endpoint requires an mcp token. This token is kind=${t} \u2014 ${n}. What to tell the user: ${Zr("mcp")}`}function ir(e,t,n){e.setHeader("WWW-Authenticate",fe),e.status(403).json({error:`Access link kind mismatch: expected ${t}, got ${n}`,code:Iu,expectedKind:t,actualKind:n,hint:Mu(t,n),tellUser:Zr(t)})}function bn(e,t,n){return(r,o,s)=>{if(!Ee(e.settings)){o.status(403).json({error:"AI Access features are disabled. Enable adminAuth, or set aiAccess.allowWithoutAdminAuth to true for local-only use.",code:"ai_features_disabled"});return}let i=Lu(r.headers.authorization);if(i.kind==="basic"){Ts(o,_u,Eu);return}if(i.kind==="missing"||i.kind==="other"){Ts(o,Ie,at);return}let d=t.validateResult(i.token);if(d.status==="expired"){sr(r,o,e.settings.httpAdminRoot,d.expiresAt);return}if(d.status!=="active"){Ts(o,$t,or,Ou(i.token));return}if(d.link.kind!==n){ir(o,n,d.link.kind);return}r.accessLink=d.link,s()}}var $t,Pu,Ie,_u,fe,at,or,Cu,Eu,Iu,$u,Mt=q(()=>{"use strict";ve();ke();$t="Access link is invalid, revoked, or no longer available (it may have expired, or Node-RED may have restarted). Ask the user for a new access link.",Pu="Access link token has expired. Ask the user for a new access link.",Ie="Access link Bearer token is missing. Ask the user for a new access link and send Authorization: Bearer <token>.",_u="Authorization must use the Bearer scheme (Authorization: Bearer <access-link-token>), not Basic. Ask the user for a new access link.",fe='Bearer realm="node-red-ai-access", charset="UTF-8"',at="access_link_missing",or="access_link_invalid",Cu="token_expired",Eu="bearer_scheme_required",Iu="wrong_token_kind";a(Lu,"parseAuthorizationHeader");a(Ts,"sendUnauthorized");a(Nu,"accessLinksPagePath");a(Fu,"tokenExpiredBody");a(sr,"sendTokenExpired");a(Ou,"truncatedBearerHint");$u="ask the owner: Node-RED /ai-access \u2192 Access tokens \u2192 create kind=rest/mcp (or get_link)";a(Zr,"whatToTellUserWrongTokenKind");a(Mu,"wrongTokenKindHint");a(ir,"sendWrongTokenKind");a(bn,"requireAccessLink")});var Q,X,Le,We,eo,Te=q(()=>{"use strict";Q=class extends Error{static{a(this,"FlowRevisionConflictError")}code="version_mismatch";statusCode=409;expectedRev;currentRev;context;constructor(t,n,r="updateFlow"){super(`Flow revision conflict in ${r}: expectedRev '${t}' does not match current rev '${n}'. The flows changed since you last read them \u2014 re-read flows, re-apply your change, and retry.`),this.name="FlowRevisionConflictError",this.expectedRev=t,this.currentRev=n,this.context=r}},X=class extends Error{static{a(this,"FlowNotFoundError")}code="flow_not_found";statusCode=404;flowId;constructor(t){super(`Flow '${t}' was not found`),this.name="FlowNotFoundError",this.flowId=t}},Le=class extends Error{static{a(this,"AdapterUnavailableError")}code="adapter_unavailable";statusCode=502;constructor(t){super(t),this.name="AdapterUnavailableError"}},We=class extends Error{static{a(this,"PluginMisconfiguredError")}code="plugin_misconfigured";statusCode=502;constructor(t){super(t),this.name="PluginMisconfiguredError"}},eo="Node-RED AI Access could not use the in-process flow runtime and the HTTP Admin API fallback returned Unauthorized. The plugin is misconfigured \u2014 tell the admin to ensure the plugin loads inside Node-RED so RED.runtime.flows is available (do not rely on unauthenticated localhost HTTP when adminAuth is enabled)."});function Ge(e,t){if(!e||!Du.test(e)||e.includes(".."))throw new Error(`Invalid ${t}`);return e}function to(e){if(!e||!Hu.test(e)||e.includes(".."))throw new Error("Invalid proposal id");return e}function Rs(e,t){let n=(0,Ht.basename)(t);if(!n||n!==t||n.includes(".."))throw new Error("Invalid file name");let r=(0,Ht.resolve)(e),o=(0,Ht.resolve)(r,n);if(o!==r&&!o.startsWith(r+Ht.sep))throw new Error("Path escapes storage directory");return o}function ba(e,t=1e5){if(typeof e=="string")return e.replace(/\0/g,"").slice(0,t)}function Ne(e){let t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},n={};for(let o of ju){if(!(o in t))continue;let s=t[o];if(o==="disabled"&&typeof s=="boolean"){n[o]=s;continue}if(o==="env"&&Array.isArray(s)){let d=JSON.stringify(s);d.length<=5e5&&(n[o]=JSON.parse(d));continue}let i=ba(s,1e4);i!==void 0&&(n[o]=i)}for(let o of["nodes","configs"]){let s=t[o];if(!Array.isArray(s))continue;let i=JSON.stringify(s),d=Buffer.byteLength(i,"utf8");if(d>K)throw new ce(`flow.${o}`,d,K);n[o]=JSON.parse(i)}let r=Buffer.byteLength(JSON.stringify(n),"utf8");if(r>K)throw new ce("flow",r,K);return n}function va(e){let t={};for(let n of Uu){if(!(n in e))continue;let r=e[n];if(Bu.has(n)){if(typeof r=="number"&&Number.isFinite(r))t[n]=r;else if(typeof r=="string"&&r.trim()!==""){let s=Number(r);Number.isFinite(s)&&(t[n]=s)}continue}let o=ba(r,2e3);o!==void 0&&(t[n]=o)}return t}var Ht,Du,Hu,ju,Uu,Bu,no=q(()=>{"use strict";Ht=require("path");oe();Du=/^[A-Za-z0-9_.-]+$/,Hu=/^prop-[A-Za-z0-9_-]+$/,ju=["id","type","label","disabled","info","env"],Uu=["proposalId","withdrawalAuthority","originalOwnerId","flowId","expectedRev","resultRev","reason","code","currentRev","title","displayName","flowLabel","linkId","label","expiresAt","createdAt","ttlSeconds","kind","redactions","restoredRedactions","operation"],Bu=new Set(["ttlSeconds","redactions","restoredRedactions"]);a(Ge,"assertSafeSegment");a(to,"assertSafeProposalId");a(Rs,"resolveUnderDir");a(ba,"sanitizeString");a(Ne,"sanitizeFlowTab");a(va,"sanitizeAuditDetails")});function U(e){return e==="pending"||e==="conflict"}function so(e,t){return`oauth-${(0,oo.createHash)("sha256").update(`${e}\0${t}`).digest("hex")}`}function dt(e){return e.kind==="oauth"?so(e.clientId,e.user):e.linkId}function qu(e){return e==="pending"||e==="conflict"||e==="approved"||e==="rejected"||e==="failed"||e==="withdrawn"||e==="expired"||e==="superseded"}function Ke(e,t){return typeof e=="string"&&e.length>0?e.replace(/\0/g,"").slice(0,t):void 0}function Wu(e){if(!e||typeof e!="object"||Array.isArray(e))return;let t=e,n=Ke(t.rev,200),r=t.state;if(!n||r!=="clean"&&r!=="merged"&&r!=="conflict")return;let o=[];if(Array.isArray(t.conflicts))for(let d of t.conflicts.slice(0,200)){if(!d||typeof d!="object")continue;let l=d,c=Ke(l.kind,64),u=Ke(l.message,500);if(!c||!u)continue;let p={kind:c,message:u,choices:Array.isArray(l.choices)?l.choices.filter(g=>g==="live"||g==="proposed"):[]},f=Ke(l.nodeId,200);f&&(p.nodeId=f);let m=Ke(l.nodeType,200);m&&(p.nodeType=m);let h=Ke(l.nodeName,120);h&&(p.nodeName=h),Array.isArray(l.fields)&&(p.fields=l.fields.filter(g=>typeof g=="string"&&g.length>0).slice(0,100).map(g=>g.replace(/\0/g,"").slice(0,200))),o.push(p)}let s={rev:n,state:r,conflicts:o,liveChangedCount:typeof t.liveChangedCount=="number"&&Number.isFinite(t.liveChangedCount)?Math.max(0,Math.floor(t.liveChangedCount)):0,checkedAt:Ke(t.checkedAt,64)??""},i=Ke(t.mergedHash,64);return i&&/^[0-9a-f]+$/.test(i)&&(s.mergedHash=i),s}function Gu(e){if(!Array.isArray(e)||e.length===0)return;let t=[];for(let n of e.slice(0,200)){if(!n||typeof n!="object")continue;let{nodeId:r,property:o}=n;typeof r!="string"||typeof o!="string"||t.push({nodeId:r.replace(/\0/g,"").slice(0,200),property:o.replace(/\0/g,"").slice(0,200)})}return t.length>0?t:void 0}function Ps(e){if(!Array.isArray(e)||e.length===0)return;let t=[];for(let n of e.slice(0,zu)){if(!n||typeof n!="object"||Array.isArray(n))continue;let r=n;if(typeof r.code!="string"||!r.code||typeof r.error!="string"||!r.error)continue;let o={code:r.code.replace(/\0/g,"").slice(0,80),error:r.error.replace(/\0/g,"").slice(0,500)};typeof r.nodeId=="string"&&r.nodeId&&(o.nodeId=r.nodeId.replace(/\0/g,"").slice(0,200)),typeof r.type=="string"&&r.type&&(o.type=r.type.replace(/\0/g,"").slice(0,200)),typeof r.property=="string"&&r.property&&(o.property=r.property.replace(/\0/g,"").slice(0,200)),typeof r.configType=="string"&&r.configType&&(o.configType=r.configType.replace(/\0/g,"").slice(0,200)),Array.isArray(r.candidates)&&(o.candidates=r.candidates.filter(s=>typeof s=="string"&&s.length>0).slice(0,20).map(s=>s.replace(/\0/g,"").slice(0,200))),t.push(o)}return t.length>0?t:void 0}function ka(e){if(!Array.isArray(e))return;let t=e.filter(n=>{if(!n||typeof n!="object"||Array.isArray(n))return!1;let r=n;return typeof r.nodeId=="string"&&Array.isArray(r.fields)});return t.length?t.map(n=>({nodeId:n.nodeId.replace(/\0/g,"").slice(0,200),fields:n.fields.filter(r=>typeof r=="string").map(r=>r.replace(/\0/g,"").slice(0,500))})):void 0}var oo,yt,Aa,me,Y,wt,Re,zu,Je,ro,Ve=q(()=>{"use strict";oo=require("crypto"),yt=require("fs"),Aa=require("path");no();oe();a(U,"isOpenProposalStatus");a(so,"oauthOwnerId");a(dt,"proposalOwnerId");me=class extends Error{static{a(this,"ProposalNotFoundError")}code="proposal_not_found";statusCode=404;constructor(t){super(`Proposal '${t}' was not found`),this.name="ProposalNotFoundError"}},Y=class extends Error{static{a(this,"ProposalNotPendingError")}code="proposal_not_pending";statusCode=409;constructor(t,n){super(`Proposal '${t}' is '${n}', not pending`),this.name="ProposalNotPendingError"}},wt=class extends Error{static{a(this,"ProposalNotOwnedError")}code="proposal_not_owned";statusCode=403;constructor(t){super(`Proposal '${t}' was not created by this access link`),this.name="ProposalNotOwnedError"}},Re=class extends Error{static{a(this,"ProposalConcurrentApproveError")}code="proposal_approve_in_progress";statusCode=409;constructor(t){super(`Proposal '${t}' is already being approved`),this.name="ProposalConcurrentApproveError"}};a(qu,"isProposalStatus");zu=50;a(Ke,"cleanText");a(Wu,"sanitizeRebaseInfo");Je=class extends Error{static{a(this,"ProposalConflictError")}code="proposal_conflict";statusCode=409;conflicts;constructor(t,n){super(`Proposal '${t}' conflicts with changes made in the editor since it was submitted; resolve the conflicts before approving.`),this.name="ProposalConflictError",this.conflicts=n}};a(Gu,"sanitizeRestoredSecrets");a(Ps,"sanitizeProposalWarnings");a(ka,"sanitizeAiOverrides");ro=class{constructor(t,n={}){this.paths=t;this.now=n.now??(()=>Date.now()),this.randomId=n.randomId??(()=>(0,oo.randomBytes)(12).toString("hex")),this.maxProposalBytes=n.maxProposalBytes??K}static{a(this,"ProposalStore")}inFlight=new Set;now;randomId;maxProposalBytes;fileFor(t){let n;try{n=to(t)}catch{throw new me(t)}return Rs(this.paths.proposals,`${n}.json`)}toDiskRecord(t){let n=to(t.id),r=Ge(String(t.expectedRev).slice(0,200),"expectedRev"),o=Ge(String(t.payload.flowId).slice(0,200),"flowId"),s=Ne(t.payload.flow),i=t.baseFlow!==void 0?Ne(t.baseFlow):void 0,d=t.createdBy.kind==="oauth"?{kind:"oauth",clientId:Ge(String(t.createdBy.clientId).slice(0,200),"clientId"),user:String(t.createdBy.user).replace(/\0/g,"").slice(0,200)}:{kind:"access-link",linkId:Ge(String(t.createdBy.linkId).slice(0,200),"linkId"),...typeof t.createdBy.label=="string"?{label:t.createdBy.label.replace(/\0/g,"").slice(0,200)}:{}},l={id:n,type:"replace_flow",status:qu(t.status)?t.status:"pending",expectedRev:r,payload:{flowId:o,flow:s},createdBy:d,createdAt:String(t.createdAt).slice(0,64),updatedAt:String(t.updatedAt).slice(0,64)};i!==void 0&&(l.baseFlow=i),typeof t.title=="string"&&t.title.length>0&&(l.title=t.title.replace(/\0/g,"").slice(0,80).trim(),l.title||delete l.title);let c=Ps(t.warnings);c&&(l.warnings=c);let u=Gu(t.restoredSecrets);if(u&&(l.restoredSecrets=u),t.deployPending&&U(l.status)){let h=Ke(t.deployPending.mergedHash,64),g=Ke(t.deployPending.reason,500);h&&/^[0-9a-f]+$/.test(h)&&g&&(l.deployPending={mergedHash:h,reason:g},t.deployPending.queueReleased===!0&&(l.deployPending.queueReleased=!0))}let p=ka(t.aiConflictOverrides);p&&U(l.status)&&(l.aiConflictOverrides=p);let f=t.conflictChoice;f&&U(l.status)&&(f.choice==="live"||f.choice==="proposed")&&typeof f.nodeId=="string"&&f.undo&&(l.conflictChoice={nodeId:f.nodeId.replace(/\0/g,"").slice(0,200),choice:f.choice,...f.chooser==="ai"||f.chooser==="human"?{chooser:f.chooser}:{},undo:{baseFlow:Ne(f.undo.baseFlow),flow:Ne(f.undo.flow),warnings:Ps(f.undo.warnings)??[],aiConflictOverrides:ka(f.undo.aiConflictOverrides)??[]}});let m=Wu(t.rebase);if((m&&U(l.status)||m&&m.state!=="conflict")&&(l.rebase=m),t.decidedBy)if(t.decidedBy.kind==="access-link"){let h={kind:"access-link",linkId:Ge(String(t.decidedBy.linkId).slice(0,200),"linkId")};typeof t.decidedBy.label=="string"&&(h.label=t.decidedBy.label.replace(/\0/g,"").slice(0,200)),l.decidedBy=h}else{let h={kind:"admin"};typeof t.decidedBy.username=="string"&&(h.username=t.decidedBy.username.replace(/\0/g,"").slice(0,200)),l.decidedBy=h}return typeof t.reason=="string"&&(l.reason=t.reason.replace(/\0/g,"").slice(0,2e3)),typeof t.resultRev=="string"&&(l.resultRev=Ge(t.resultRev.slice(0,200),"resultRev")),l}writeAtomic(t,n){let r=this.fileFor(t),o=this.toDiskRecord(n),s=ht(o,this.maxProposalBytes,{kind:"proposal",pretty:!0});un(this.paths.root,r,s)}create(t){let n=new Date(this.now()).toISOString(),r=to(`prop-${this.randomId()}`),o={id:r,type:"replace_flow",status:"pending",expectedRev:t.expectedRev,payload:{flowId:t.payload.flowId,flow:t.payload.flow},baseFlow:t.baseFlow,title:t.title,createdBy:t.createdBy,createdAt:n,updatedAt:n},s=Ps(t.warnings);return s&&(o.warnings=s),t.restoredSecrets?.length&&(o.restoredSecrets=t.restoredSecrets),this.writeAtomic(r,o),this.get(r)}get(t){let n=this.fileFor(t);if(!(0,yt.existsSync)(n))throw new me(t);return JSON.parse((0,yt.readFileSync)(n,"utf8"))}list(t){if(!(0,yt.existsSync)(this.paths.proposals))return[];let n=(0,yt.readdirSync)(this.paths.proposals).filter(o=>o.endsWith(".json")),r=[];for(let o of n){let s=(0,Aa.basename)(o);if(!(s!==o||s.includes("..")))try{let i=JSON.parse((0,yt.readFileSync)(Rs(this.paths.proposals,s),"utf8"));if(t?.linkId&&dt(i.createdBy)!==t.linkId||t?.status&&i.status!==t.status||t?.flowId&&i.payload.flowId!==t.flowId)continue;r.push(i)}catch{}}return r.sort((o,s)=>o.createdAt<s.createdAt?1:-1)}beginApprove(t){if(this.inFlight.has(t))throw new Re(t);let n=this.get(t);if(!U(n.status))throw new Y(t,n.status);return this.inFlight.add(t),n}releaseLock(t){this.inFlight.delete(t)}recordDeployPending(t,n){let r=this.get(t);if(!U(r.status))throw new Y(t,r.status);return n?r.deployPending=n:delete r.deployPending,r.status="pending",r.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,r),this.get(t)}markApproved(t,n,r){let o=this.get(t);return o.status="approved",delete o.deployPending,o.decidedBy=n,o.resultRev=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markRejected(t,n,r){if(this.inFlight.has(t))throw new Re(t);let o=this.get(t);return o.status="rejected",n&&(o.decidedBy=n),o.reason=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markFailed(t,n,r){let o=this.get(t);return o.status="failed",n&&(o.decidedBy=n),o.reason=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markWithdrawn(t,n,r){if(this.inFlight.has(t))throw new Re(t);let o=this.get(t);if(!U(o.status))throw new Y(t,o.status);return o.status="withdrawn",o.decidedBy=n,o.reason=r,o.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,o),this.get(t)}markClosed(t,n,r,o){let s=this.get(t);if(!U(s.status))throw new Y(t,s.status);return s.status=n,o&&(s.decidedBy=o),s.reason=r,s.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,s),this.get(t)}recordRebase(t,n){let r=this.get(t);if(!U(r.status))return r;let o=n.state==="conflict"?"conflict":"pending",s=r.rebase;return r.status===o&&s!==void 0&&s.rev===n.rev&&s.state===n.state&&s.mergedHash===n.mergedHash&&s.liveChangedCount===n.liveChangedCount&&JSON.stringify(s.conflicts)===JSON.stringify(n.conflicts)?r:(r.status=o,r.rebase=n,r.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,r),this.get(t))}updateBody(t,n){let r=this.get(t);if(!U(r.status))throw new Y(t,r.status);if(this.inFlight.has(t))throw new Re(t);return r.baseFlow=n.baseFlow,r.payload={flowId:r.payload.flowId,flow:n.flow},n.expectedRev!==void 0&&(r.expectedRev=n.expectedRev),n.title!==void 0&&(r.title=n.title),n.restoredSecrets!==void 0&&(n.restoredSecrets.length>0?r.restoredSecrets=n.restoredSecrets:delete r.restoredSecrets),n.warnings!==void 0&&(n.warnings.length>0?r.warnings=n.warnings:delete r.warnings),n.aiConflictOverrides?.length?r.aiConflictOverrides=n.aiConflictOverrides:delete r.aiConflictOverrides,n.conflictChoice?r.conflictChoice=n.conflictChoice:delete r.conflictChoice,r.status="pending",delete r.rebase,r.updatedAt=new Date(this.now()).toISOString(),this.writeAtomic(t,r),this.get(t)}isApproving(t){return this.inFlight.has(t)}}});function xa(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`${t} must be a node object`);let n=e;if(typeof n.id!="string"||n.id.length===0)throw new Error(`${t} needs a non-empty string id`);if(typeof n.type!="string"||n.type.length===0)throw new Error(`${t} '${n.id}' needs a non-empty string type`);return n}function io(e,t,n){let r=e.find(d=>d.id===t);if(!r||r.type!=="tab"&&r.type!=="subflow")throw new X(t);let o={...r,id:t,type:r.type};delete o.nodes,delete o.configs,delete o.subflows,n.label!==void 0&&(o.label=n.label),n.disabled!==void 0&&(o.disabled=n.disabled),n.info!==void 0&&(o.info=n.info),n.env!==void 0&&(o.env=n.env);let s=[];for(let[d,l]of(n.nodes??[]).entries())s.push({...xa(l,`flowData.nodes[${d}]`),z:t});for(let[d,l]of(n.configs??[]).entries())s.push({...xa(l,`flowData.configs[${d}]`),z:t});return[...e.filter(d=>d.id!==t&&d.z!==t),o,...s]}function dr(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw new Error(`Expected ${t} to be an object with rev and flows`);let n=e;if(typeof n.rev!="string"||n.rev.length===0)throw new Error(`Expected ${t}.rev to be a non-empty string`);if(!Array.isArray(n.flows))throw new Error(`Expected ${t}.flows to be an array`);return{rev:n.rev,flows:n.flows}}function vn(e,t){if(Array.isArray(t)){let d=t.find(u=>u&&typeof u=="object"&&u.id===e&&(u.type==="tab"||u.type==="subflow"));if(!d)throw new X(e);let l=t.filter(u=>u&&typeof u=="object"&&u.id!==e&&u.z===e),c={id:e,type:d.type,nodes:l};return typeof d.label=="string"&&(c.label=d.label),typeof d.disabled=="boolean"&&(c.disabled=d.disabled),typeof d.info=="string"&&(c.info=d.info),c}if(!t||typeof t!="object")throw new X(e);let n=t,r=typeof n.id=="string"&&n.id.length>0?n.id:e,o=typeof n.type=="string"&&n.type.length>0?n.type:"tab",s=Array.isArray(n.nodes)?n.nodes:[],i={...n,id:r,type:o,nodes:s};return Array.isArray(n.configs)&&(i.configs=n.configs),i}var ao=q(()=>{"use strict";Te();a(xa,"asFlowNode");a(io,"replaceFlowTabInFlatFlows");a(dr,"assertFlowsSnapshot");a(vn,"normalizeFlowTab")});function Sa(e,t=new _s){return lo in e?e:{kind:e.kind,[lo]:t,async getFlows(){let r=await e.getFlows();return t.record(r),r},async getFlow(r){let o=await e.getFlow(r);if(!t.has(o.rev))try{t.record(await e.getFlows())}catch{}return o},async updateFlow(r){return e.updateFlow(r)}}}function Cs(e){return lo in e?e[lo]:void 0}function co(e,t){return{kind:t,async getFlows(){return{rev:e.rev,flows:JSON.parse(JSON.stringify(e.flows))}},async getFlow(n){let r=JSON.parse(JSON.stringify(e.flows)),o=r.find(l=>l.id===n&&(l.type==="tab"||l.type==="subflow"));if(!o)throw new X(n);let s=[],i=[];for(let l of r){if(l.id===n||l.z!==n)continue;(l.type!=="group"&&l.x===void 0&&l.y===void 0&&l.wires===void 0?i:s).push(l)}let d={id:n,type:o.type,nodes:s};for(let l of["label","disabled","info","env"])o[l]!==void 0&&(d[l]=o[l]);return i.length>0&&(d.configs=i),{rev:e.rev,flow:d}},async updateFlow(){throw new Error("snapshot adapter is read-only")}}}var Ku,_s,lo,Es=q(()=>{"use strict";Te();Ku=24,_s=class{constructor(t=Ku){this.maxRevs=t}static{a(this,"RevHistory")}byRev=new Map;record(t){if(!(!t||typeof t.rev!="string"||!Array.isArray(t.flows))&&!this.byRev.has(t.rev))for(this.byRev.set(t.rev,{rev:t.rev,flows:JSON.parse(JSON.stringify(t.flows))});this.byRev.size>this.maxRevs;){let n=this.byRev.keys().next().value;if(n===void 0)break;this.byRev.delete(n)}}get(t){let n=this.byRev.get(t);return n?{rev:n.rev,flows:JSON.parse(JSON.stringify(n.flows))}:void 0}has(t){return this.byRev.has(t)}},lo=Symbol.for("ai-access.revHistory");a(Sa,"withRevHistory");a(Cs,"revHistoryOf");a(co,"snapshotAdapter")});function Fs(e){return e?Ju.has(e)?!0:e.startsWith("subflow:"):!1}function bt(e,t="node.type"){if(e==null||e==="")throw new k(`${t} is required`,"missing_node_type");if(typeof e!="string")throw new k(`${t} must be a non-empty string (got ${typeof e})`,"invalid_node_type",{details:{type:e,got:typeof e}});if(!e.trim())throw new k(`${t} is required`,"missing_node_type");return e}function tp(e,t){if(typeof e!="string"||typeof t!="string")return Number.MAX_SAFE_INTEGER;let n=e.toLowerCase(),r=t.toLowerCase();if(n===r)return 0;let o=n.length,s=r.length;if(o===0)return s;if(s===0)return o;let i=new Array(s+1),d=new Array(s+1);for(let l=0;l<=s;l++)i[l]=l;for(let l=1;l<=o;l++){d[0]=l;let c=n.charAt(l-1);for(let u=1;u<=s;u++){let p=c===r.charAt(u-1)?0:1;d[u]=Math.min((d[u-1]??0)+1,(i[u]??0)+1,(i[u-1]??0)+p)}for(let u=0;u<=s;u++)i[u]=d[u]??0}return i[s]??s}function np(e,t){let n=e.toLowerCase(),r=t.toLowerCase(),o=Math.min(n.length,r.length),s=0;for(;s<o&&n.charAt(s)===r.charAt(s);)s+=1;return s}function op(e){return Math.max(2,Math.floor(e.length/3))}function Os(e,t,n=5){if(typeof e!="string"||!e)return[];let r=op(e);return t.filter(s=>typeof s=="string"&&s!==e&&!rp.has(s)&&!s.startsWith("_")&&!s.startsWith("subflow:")).map(s=>({c:s,dist:tp(e,s),prefix:np(e,s)})).filter(s=>s.dist<=r).sort((s,i)=>s.dist-i.dist||i.prefix-s.prefix||s.c.localeCompare(i.c)).slice(0,n).map(s=>s.c)}function sp(e){return e.endsWith("/")?e.slice(0,-1):e}function Ia(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,Ea.createRequire)(e)}function ip(){try{let t=Ia()("@node-red/runtime/lib/settings");return typeof t.available=="function"&&t.available()===!0}catch{return!1}}function La(e){let t=e.types,n=Array.isArray(t)?t.filter(i=>typeof i=="string"&&i.length>0):[],r="";if(typeof e.module=="string"&&e.module)r=e.module;else if(typeof e.name=="string"&&e.name)r=e.name;else if(typeof e.id=="string"){let i=String(e.id).split("/")[0];r=i&&i.length>0?i:String(e.id)}let o=typeof e.id=="string"&&e.id?e.id:n.length===1&&r?`${r}/${n[0]}`:r;if(!o||n.length===0)return null;let s={id:o,module:r.length>0?r:o,types:n};return typeof e.version=="string"&&(s.version=e.version),typeof e.enabled=="boolean"&&(s.enabled=e.enabled),typeof e.name=="string"&&(s.name=e.name),s}function $s(e=ep,t={}){let n=t.module??"node-red",r=t.version??"4.0.0",o=[...e],s=t.configs??{};return{listNodeSets(){return Promise.resolve([{id:n,module:n,version:r,types:o,enabled:!0,name:n}])},getNodeConfigHtml(i){if(s[i])return Promise.resolve(s[i]??null);let d=i.includes("/")?i.slice(i.lastIndexOf("/")+1):i;return s[d]?Promise.resolve(s[d]??null):Promise.resolve(null)}}}function Na(e={}){if(ip())try{let n=Ia()("@node-red/runtime");if(n.nodes&&typeof n.nodes.getNodeList=="function")return new Ns(n.nodes)}catch{}if(e.baseUrl)return new Ls({baseUrl:e.baseUrl,...e.accessToken?{accessToken:e.accessToken}:{},...e.fetch?{fetch:e.fetch}:{}});throw new Error("No usable NodeTypesAdapter: runtime.nodes unavailable and no HTTP baseUrl")}async function Ms(e){let t=await e.listNodeSets(),n=new Map;for(let r of t)for(let o of r.types){if(n.has(o))continue;let s={type:o,module:r.module,setId:r.id};typeof r.enabled=="boolean"&&(s.enabled=r.enabled),r.version&&(s.version=r.version),n.set(o,s)}return{byType:n,allNames:[...n.keys()]}}function ap(e,t){let n=t.toLowerCase();return[e.type,e.module,e.category??"",e.description??""].join(`
4
+ `).toLowerCase().includes(n)}async function po(e,t={}){let n=t.limit??Vu,{byType:r}=await Ms(e),o=[...r.values()].sort((u,p)=>u.type.localeCompare(p.type)),s=t.q?.trim();s&&(o=o.filter(u=>ap(u,s)));let i=o.length,d=i>n,c={types:o.slice(0,n),total:i,truncated:d};return s&&(c.q=s),c}function dp(e,t){let n=`data-help-name="${t}"`,r=`data-help-name='${t}'`,o=e.toLowerCase(),s=0;for(;s<e.length;){let i=e.indexOf(n,s),d=e.indexOf(r,s),l=-1;if(i>=0&&(d<0||i<d)?l=i:d>=0&&(l=d),l<0)return null;let c=o.lastIndexOf("<script",l);if(c<0||c<s){s=l+1;continue}let u=e.indexOf(">",l);if(u<0)return null;let p=o.indexOf("</script",u+1);if(p<0)return null;let f=p+8;for(;f<e.length&&/\s/.test(e.charAt(f));)f+=1;if(e.charAt(f)!==">"){s=p+1;continue}return e.slice(u+1,p)}return null}function Ra(e,t){let n=`<${t}`,r=`</${t}`,o="",s=0,i=e.toLowerCase();for(;s<e.length;){let d=i.indexOf(n,s);if(d<0){o+=e.slice(s);break}o+=e.slice(s,d);let l=e.indexOf(">",d);if(l<0)break;let c=i.indexOf(r,l+1);if(c<0)break;let u=c+r.length;for(;u<e.length&&/\s/.test(e.charAt(u));)u+=1;e.charAt(u)===">"?(s=u+1,o+=" "):s=c+r.length}return o}function lp(e,t=2e3){let n=Ra(e,"script");return n=Ra(n,"style"),n=n.replace(/<[^>]+>/g," ").replace(/&nbsp;/gi," ").replace(/[#*_`]+/g," ").replace(/\s+/g," ").trim(),n.length>t&&(n=`${n.slice(0,t-1)}\u2026`),n}function cp(e,t){try{let n=/RED\.nodes\.registerType\s*\(\s*['"]([^'"]+)['"]\s*,\s*\{/.exec(e);if(!n?.[1])return null;let r=n[1];if(t&&r!==t){let o=/RED\.nodes\.registerType\s*\(\s*['"]([^'"]+)['"]\s*,\s*\{/g,s,i=null,d=-1;for(;(s=o.exec(e))!==null;)if(s[1]===t){i=s[1],d=s.index;break}return!i||d<0?null:Pa(e,d,i)}return Pa(e,n.index,r)}catch{return null}}function Pa(e,t,n){let r=e.slice(t),o=r.indexOf("{");if(o<0)return{type:n};let s=lr(r.slice(o));if(!s)return{type:n};let i=_a(s,"category"),d=_a(s,"color"),l=Ca(s,"inputs"),c=Ca(s,"outputs"),u=pp(s),p=[];if(u){for(let g of Object.values(u))if(g&&typeof g=="object"&&!Array.isArray(g)){let y=g.type;typeof y=="string"&&y.length>0&&p.push(y)}}let f,m=dp(e,n);m&&(f=lp(m));let h={type:n};return i&&(h.category=i),d&&(h.color=d),l!==void 0&&(h.inputs=l),c!==void 0&&(h.outputs=c),u&&(h.defaults=u),p.length>0&&(h.requiredConfigNodeTypes=[...new Set(p)]),f&&(h.help=f),h}function up(e){let t=e.trim();if(!t)return{};if(!/\n(?=#{1,3}\s*(inputs?|outputs?)\b)/i.test(`
5
+ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(let l of r){let c=l.trim();if(!c)continue;let u=/^(#{1,3})\s*(inputs?|outputs?)\b[\s:]*\n?([\s\S]*)$/i.exec(c);if(u){let p=(u[2]??"").toLowerCase(),f=(u[3]??"").trim();p.startsWith("input")?s=f||c:i=f||c}else o||(o=c.replace(/^#{1,3}\s*/,"").trim())}let d={};return o&&(d.summary=o),s&&(d.inputs=s),i&&(d.outputs=i),!d.summary&&!d.inputs&&!d.outputs&&(d.text=t),d}function lr(e){if(!e.startsWith("{"))return null;let t=0,n=null,r=!1;for(let o=0;o<e.length;o++){let s=e[o];if(n){if(r){r=!1;continue}if(s==="\\"){r=!0;continue}s===n&&(n=null);continue}if(s==='"'||s==="'"||s==="`"){n=s;continue}if(s==="{")t+=1;else if(s==="}"&&(t-=1,t===0))return e.slice(0,o+1)}return null}function _a(e,t){let n=new RegExp(`\\b${t}\\s*:\\s*['"]([^'"]+)['"]`);return e.match(n)?.[1]}function Ca(e,t){let n=new RegExp(`\\b${t}\\s*:\\s*(-?\\d+(?:\\.\\d+)?)`),r=e.match(n);if(!r?.[1])return;let o=Number(r[1]);return Number.isFinite(o)?o:void 0}function pp(e){let t=e.search(/\bdefaults\s*:/);if(t<0)return;let n=e.slice(t),r=n.indexOf("{");if(r<0)return;let o=lr(n.slice(r));if(!o)return;let s={},i=/([a-zA-Z_$][\w$]*)\s*:\s*\{/g,d;for(;(d=i.exec(o))!==null;){let l=d[1];if(!l||l==="defaults")continue;let c=lr(o.slice(d.index+d[0].length-1));if(!c)continue;let u={},p=/\btype\s*:\s*['"]([^'"]+)['"]/.exec(c);p?.[1]&&(u.type=p[1]),/\brequired\s*:\s*true\b/.test(c)&&(u.required=!0);let f=/\bvalue\s*:\s*/.exec(c);if(f){let m=c.slice(f.index+f[0].length).trimStart();u.value=mp(m)}s[l]=u}return Object.keys(s).length>0?s:void 0}function fp(e,t){let n=e.charAt(t);if(n==="u"&&/^[0-9a-fA-F]{4}/.test(e.slice(t+1,t+5)))return{char:String.fromCharCode(Number.parseInt(e.slice(t+1,t+5),16)),next:t+5};if(n==="x"&&/^[0-9a-fA-F]{2}/.test(e.slice(t+1,t+3)))return{char:String.fromCharCode(Number.parseInt(e.slice(t+1,t+3),16)),next:t+3};let r;switch(n){case"n":r=`
6
+ `;break;case"r":r="\r";break;case"t":r=" ";break;case"b":r="\b";break;case"f":r="\f";break;case"v":r="\v";break;case"0":r="\0";break;case"\\":r="\\";break;case"'":r="'";break;case'"':r='"';break;case"/":r="/";break;default:r=n;break}return{char:r,next:t+1}}function hp(e){return e==="_DEFAULT_"?"":e}function uo(e){let t=e.trimStart();if(t.startsWith("'")||t.startsWith('"')){let r=t[0],o=1,s="";for(;o<t.length;){let i=t[o];if(i==="\\"&&o+1<t.length){let d=fp(t,o+1);s+=d.char,o=d.next;continue}if(i===r)break;s+=i,o+=1}return hp(s)}if(t.startsWith("[")){let r=Fa(t);if(!r)return;let o=r.slice(1,-1).trim();if(!o)return[];let s=[],i=0;for(;i<o.length;){for(;i<o.length&&/[\s,]/.test(o.charAt(i));)i+=1;if(i>=o.length)break;let d=o.slice(i),l=uo(d);s.push(l);let c=Is(d);i+=c>0?c:1}return s}if(t.startsWith("{")){let r=lr(t);if(!r)return;let o=r.slice(1,-1);if(!o.trim())return{};let s={},i=0;for(;i<o.length;){for(;i<o.length&&/[\s,]/.test(o.charAt(i));)i+=1;if(i>=o.length)break;let d;if(o[i]==="'"||o[i]==='"'){let c=uo(o.slice(i));d=typeof c=="string"?c:"",i+=Is(o.slice(i))}else{let c=/^[a-zA-Z_$][\w$]*/.exec(o.slice(i));if(!c)break;d=c[0],i+=d.length}for(;i<o.length&&/\s/.test(o.charAt(i));)i+=1;if(o[i]!==":")break;for(i+=1;i<o.length&&/\s/.test(o.charAt(i));)i+=1;let l=o.slice(i);s[d]=uo(l),i+=Is(l)}return s}let n=/^-?\d+(?:\.\d+)?/.exec(t);if(n)return Number(n[0]);if(t.startsWith("true"))return!0;if(t.startsWith("false"))return!1;if(t.startsWith("null"))return null}function Is(e){let t=e.trimStart(),n=e.length-t.length;if(t.startsWith("'")||t.startsWith('"')){let o=t[0],s=1;for(;s<t.length;){if(t[s]==="\\"&&s+1<t.length){s+=2;continue}if(t[s]===o)return n+s+1;s+=1}return n+t.length}if(t.startsWith("[")){let o=Fa(t);return n+(o?.length??1)}if(t.startsWith("{")){let o=lr(t);return n+(o?.length??1)}let r=/^(?:-?\d+(?:\.\d+)?|true|false|null)/.exec(t);return r?n+r[0].length:n+1}function Fa(e){if(!e.startsWith("["))return null;let t=0,n=null,r=!1;for(let o=0;o<e.length;o++){let s=e[o];if(n){if(r){r=!1;continue}if(s==="\\"){r=!0;continue}s===n&&(n=null);continue}if(s==="'"||s==='"'){n=s;continue}if(s==="[")t+=1;else if(s==="]"&&(t-=1,t===0))return e.slice(0,o+1)}return null}function mp(e){return uo(e)}function gp(e,t){for(let n of e)if(n.types.includes(t)){let r=n.id.includes("/")||n.types.length===1?n.id:`${n.module}/${t}`;return{set:n,setId:r}}return null}function Ds(e,t){let n=e.toLowerCase();return t.includes(e)?e:t.find(r=>r.toLowerCase()===n)}async function jt(e,t,n={}){if(!t||typeof t!="string")throw new k("Missing node type","missing_node_type");let r=n.includeHelp!==!1;if(Fs(t)){let l={type:t,module:"node-red",category:"config",inputs:0,outputs:0,defaults:null};return r&&(l.help={summary:"Built-in structural Node-RED type (not a palette node)."}),l}let o=await e.listNodeSets(),s=o.flatMap(l=>l.types),i=gp(o,t);if(!i){let l=Ds(t,s);throw l&&l!==t?new k(`Node type '${t}' was not found \u2014 did you mean '${l}'? Types are case-sensitive.`,"node_type_not_found",{statusCode:404,details:{type:t,didYouMean:l,caseSensitive:!0,nearest:[l]}}):new k(`Node type '${t}' was not found`,"node_type_not_found",{statusCode:404,details:{type:t,nearest:Os(t,s,5)}})}let d={type:t,module:i.set.module,setId:i.setId,inputs:1,outputs:1,defaults:null};if(i.set.version&&(d.version=i.set.version),typeof i.set.enabled=="boolean"&&(d.enabled=i.set.enabled),typeof e.getNodeConfigHtml=="function")try{let l=await e.getNodeConfigHtml(i.setId)??await e.getNodeConfigHtml(`${i.set.module}/${t}`)??await e.getNodeConfigHtml(t);if(l){let c=cp(l,t);c&&(c.category&&(d.category=c.category),c.color&&(d.color=c.color),d.inputs=c.inputs??d.inputs,d.outputs=c.outputs??d.outputs,c.defaults&&Object.keys(c.defaults).length>0?d.defaults=c.defaults:d.defaults=null,c.requiredConfigNodeTypes&&(d.requiredConfigNodeTypes=c.requiredConfigNodeTypes),r&&c.help&&(d.help=up(c.help)))}}catch{}return d}async function Oa(e,t){let{allNames:n}=await Ms(e),r=new Set(n),o=new Set,s=a(l=>{let c=bt(l,"node.type");if(o.has(c)||Fs(c)){o.add(c);return}if(o.add(c),r.has(c))return;let u=Ds(c,n);throw u&&u!==c?new k(`Unknown node type '${c}' \u2014 did you mean '${u}'? Types are case-sensitive.`,"unknown_node_type",{details:{type:c,didYouMean:u,caseSensitive:!0,nearest:[u]}}):new k(`Unknown node type '${c}'. Call node-types / palette search; ask the user to install via Manage palette, then recheck.`,"unknown_node_type",{details:{type:c,nearest:Os(c,n,5)}})},"checkType"),i=a(l=>{if(!l||typeof l!="object"||Array.isArray(l))return;let c=l;typeof c.id=="string"&&c.id.length>0&&s(c.type)},"visitNodeOrConfig");a(l=>{if(!l||typeof l!="object"||Array.isArray(l))return;let c=l;if(c.type!==void 0&&s(c.type),Array.isArray(c.nodes))for(let u of c.nodes)i(u);if(Array.isArray(c.configs))for(let u of c.configs)i(u)},"visitTab")(t)}async function $a(e,t){let n=bt(t,"node.type");if(Fs(n))return;let{allNames:r}=await Ms(e);if(r.includes(n))return;let o=Ds(n,r);throw o&&o!==n?new k(`Unknown node type '${n}' \u2014 did you mean '${o}'? Types are case-sensitive.`,"unknown_node_type",{details:{type:n,didYouMean:o,caseSensitive:!0,nearest:[o]}}):new k(`Unknown node type '${n}'. Call node-types / palette search; ask the user to install via Manage palette, then recheck.`,"unknown_node_type",{details:{type:n,nearest:Os(n,r,5)}})}async function yp(e,t){let n=new Headers({Accept:"application/json"});t&&n.set("If-None-Match",t);let r=await fetch(e,{headers:n}),o=r.headers.get("etag")??void 0;if(r.status===304)return{status:304,...o?{etag:o}:{}};if(!r.ok)return{status:r.status,body:null,...o?{etag:o}:{}};let s=await r.json();return{status:r.status,body:s,...o?{etag:o}:{}}}async function wp(e,t){let n=Date.now();if(Fe&&n-Fe.fetchedAt<Qu)return Fe.body;let r=Fe?.etag,o=await e(t,r);if(o.status===304&&Fe)return Fe={...Fe,fetchedAt:n,...o.etag?{etag:o.etag}:{}},Fe.body;if(!o.body||!Array.isArray(o.body.modules)){if(Fe)return Fe.body;throw new k("Node-RED catalogue unavailable","catalogue_unavailable",{statusCode:502})}return Fe={fetchedAt:n,body:o.body,...o.etag?{etag:o.etag}:{}},o.body}async function fo(e,t,n={}){let r=t.trim();if(!r)throw new k("q query parameter is required","missing_query");let o=n.limit??Yu,s=n.catalogueUrl??Zu,i=n.catalogueFetcher??yp,d=await wp(i,s),l=Array.isArray(d.modules)?d.modules:[],c=await e.listNodeSets(),u=new Set(c.flatMap(g=>{let y=[g.module,g.id];return g.id.includes("/")&&y.push(g.id.split("/")[0]??g.module),y.map(w=>w.toLowerCase())})),p=r.toLowerCase(),f=[];for(let g of l){let y=g.id.toLowerCase(),w=(g.keywords??[]).map(I=>String(I).toLowerCase()),v=(g.description??"").toLowerCase(),L=bp(p,y,w,v);if(L<=0)continue;let P={module:g.id,installed:u.has(y)};typeof g.description=="string"&&(P.description=g.description),typeof g.version=="string"&&(P.version=g.version),typeof g.updated_at=="string"&&(P.updated=g.updated_at),typeof g.downloads=="number"&&(P.downloads=g.downloads),typeof g.url=="string"&&(P.link=g.url),f.push({item:P,score:L})}f.sort((g,y)=>y.score-g.score||(y.item.downloads??0)-(g.item.downloads??0)||g.item.module.localeCompare(y.item.module));let m=f.length,h=m>o;return{q:r,modules:f.slice(0,o).map(g=>g.item),total:m,truncated:h,hint:Xu}}function bp(e,t,n,r){if(!e)return 0;if(t===e||t===`node-red-contrib-${e}`||t===`node-red-node-${e}`)return 1e4;let o=0;t.startsWith(`node-red-contrib-${e}`)||t.startsWith(`node-red-node-${e}`)?o+=500:t.startsWith(e)?o+=80:t.includes(e)&&(o+=60);for(let s of n)s===e?o+=50:s.startsWith(e)||e.startsWith(s)?o+=35:s.includes(e)&&(o+=25);return r.includes(e)&&(o+=10),o}var Ea,Ju,Vu,Yu,Xu,Zu,Qu,ep,rp,Ls,Ta,Ns,Fe,Ut=q(()=>{"use strict";Ea=require("module");se();Ju=new Set(["tab","subflow","group","junction"]),Vu=100,Yu=30,Xu="Recommend only \u2014 ask the user to install via Manage palette, then recheck node-types. This API never installs. Core Node-RED nodes (module node-red) are not listed in the public catalogue; use node-types for installed core types.",Zu="https://catalogue.nodered.org/catalogue.json",Qu=3600*1e3,ep=["inject","debug","function","template","comment","change","switch","delay","trigger","link in","link out","link call","mqtt in","mqtt out","http in","http response","http request","websocket in","websocket out","tcp in","tcp out","udp in","udp out","catch","status","complete","join","split","sort","batch","csv","html","json","xml","yaml","file","file in","watch","exec","range","rbe"];a(Fs,"isStructuralNodeType");a(bt,"requireNodeTypeName");a(tp,"editDistance");a(np,"sharedPrefixLength");rp=new Set(["unknown","tab","subflow","group"]);a(op,"nearestDistanceCutoff");a(Os,"nearestNodeTypes");a(sp,"trimTrailingSlash");a(Ia,"createPluginRequire");a(ip,"isRuntimeSettingsAvailable");a(La,"normalizeNodeSet");a($s,"createStaticNodeTypesAdapter");Ls=class{static{a(this,"HttpNodeTypesAdapter")}baseUrl;accessToken;fetchImpl;constructor(t){this.baseUrl=sp(t.baseUrl),this.accessToken=t.accessToken,this.fetchImpl=t.fetch??globalThis.fetch.bind(globalThis)}headers(t){let n=new Headers;return n.set("Accept",t),this.accessToken&&n.set("Authorization",`Bearer ${this.accessToken}`),n}async listNodeSets(){let t=await this.fetchImpl(`${this.baseUrl}/nodes`,{headers:this.headers("application/json")});if(!t.ok)throw new Error(`Admin API GET /nodes failed (${t.status})`);let n=await t.json();if(!Array.isArray(n))throw new Error("Admin API GET /nodes returned non-array JSON");let r=[];for(let o of n){if(!o||typeof o!="object"||Array.isArray(o))continue;let s=La(o);s&&r.push(s)}return r}async getNodeConfigHtml(t,n){let r=t.split("/").map(i=>encodeURIComponent(i)).join("/"),o=n?`?lang=${encodeURIComponent(n)}`:"",s=await this.fetchImpl(`${this.baseUrl}/nodes/${r}${o}`,{headers:this.headers("text/html")});return s.status===404||!s.ok?null:s.text()}},Ta={user:null,req:null},Ns=class{constructor(t){this.nodes=t}static{a(this,"RedNodeTypesAdapter")}async listNodeSets(){if(typeof this.nodes.getNodeList!="function")return[];let t,n=this.nodes.getNodeList;try{t=await Promise.resolve(n(Ta))}catch{try{t=await Promise.resolve(n())}catch{return[]}}if(!Array.isArray(t))return[];let r=[];for(let o of t){if(!o||typeof o!="object"||Array.isArray(o))continue;let s=La(o);s&&r.push(s)}return r}async getNodeConfigHtml(t,n){if(typeof this.nodes.getNodeConfig!="function")return null;try{let r=await Promise.resolve(this.nodes.getNodeConfig({id:t,lang:n??"en-US",...Ta}));if(typeof r=="string"&&r.length>0)return r}catch{}try{let r=await Promise.resolve(this.nodes.getNodeConfig(t,n));return typeof r=="string"&&r.length>0?r:null}catch{return null}}};a(Na,"createNodeTypesAdapter");a(Ms,"flattenInstalledTypes");a(ap,"matchesQuery");a(po,"listNodeTypes");a(dp,"extractHelpScriptBody");a(Ra,"removeHtmlBlocks");a(lp,"stripHelpText");a(cp,"parseRegisterTypeFromHtml");a(Pa,"parseRegisterTypeAt");a(up,"splitHelpSections");a(lr,"extractBalancedObject");a(_a,"matchStringProp");a(Ca,"matchNumberProp");a(pp,"parseDefaultsBlock");a(fp,"unescapeJsStringEscape");a(hp,"normalizeDefaultValue");a(uo,"parseJsLiteral");a(Is,"measureJsLiteralLength");a(Fa,"extractBalancedArray");a(mp,"parseLooseValue");a(gp,"findSetForType");a(Ds,"findCaseInsensitiveType");a(jt,"getNodeTypeDetail");a(Oa,"assertKnownNodeTypes");a($a,"assertKnownNodeType");Fe=null;a(yp,"defaultCatalogueFetcher");a(wp,"loadCatalogue");a(fo,"searchPalette");a(bp,"paletteMatchScore")});function lt(e){if(e.length===0)return;let t=e[0];if(!t)return;let n=e.map(s=>{let i={...s};return delete i.statusCode,i});if(e.length>1)throw new k(`${e.length} validation errors`,"validation_failed",{statusCode:400,details:{errors:n,errorCount:e.length}});let r={errors:n};for(let[s,i]of Object.entries(t))s==="code"||s==="error"||s==="errors"||s==="statusCode"||i!==void 0&&(r[s]=i);let o=typeof t.statusCode=="number"?t.statusCode:400;throw new k(t.error,t.code,{statusCode:o,details:r})}function Hs(e,t="payload.flow"){let n=new Map;for(let o of e)for(let s of o){if(!s||typeof s!="object"||Array.isArray(s))continue;let i=s.id;typeof i=="string"&&i.length>0&&n.set(i,(n.get(i)??0)+1)}let r=[];for(let[o,s]of n)s>1&&r.push({code:"duplicate_node_id",error:`Node id '${o}' appears ${s} times in ${t}; node ids must be unique`,nodeId:o,count:s});return r}function vp(e){let t=typeof e.label=="string"&&e.label?` '${e.label}'`:"";return e.type==="tab"?`tab${t||` '${e.id}'`}`:e.type==="subflow"?`subflow${t||` '${e.id}'`}`:typeof e.z=="string"&&e.z?`${e.type} node on '${e.z}'`:`global ${e.type} config node`}function Da(e,t,n){let r=new Map;for(let i of n)!i||typeof i.id!="string"||i.id===t||i.z===t||r.set(i.id,i);let o=[],s=new Set;for(let i of[e.nodes,e.configs])if(Array.isArray(i))for(let d of i){let l=d&&typeof d=="object"?d.id:void 0;if(typeof l!="string"||!l||s.has(l))continue;if(s.add(l),l===t){o.push({code:"duplicate_node_id",error:`Node id '${l}' equals the tab id; node ids must be unique across all flows`,nodeId:l});continue}let c=r.get(l);c&&o.push({code:"duplicate_node_id",error:`Node id '${l}' is already used by ${vp(c)}; node ids must be unique across all flows`,nodeId:l})}return o}function Ma(e){let t=new Map;for(let n of[e.nodes,e.configs])if(Array.isArray(n))for(let r of n){if(!r||typeof r!="object"||Array.isArray(r))continue;let o=r;typeof o.id=="string"&&t.set(o.id,o)}return t}function Ap(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]);for(let r of n)if(!kp.has(r)&&JSON.stringify(e[r])!==JSON.stringify(t[r]))return!0;return!1}function xp(e,t){let n=Ma(t);if(!e)return new Set(n.keys());let r=Ma(e),o=new Set;for(let[s,i]of n){let d=r.get(s);(!d||Ap(d,i))&&o.add(s)}return o}function Ha(e,t){let n=[],r=e;typeof r.id!="string"||r.id.length===0?n.push({code:"missing_flow_id",error:"payload.flow.id is required and must match payload.flowId"}):r.id!==t&&n.push({code:"invalid_flow_id",error:`payload.flow.id ('${r.id}') must match payload.flowId ('${t}')`}),"nodes"in r?Array.isArray(r.nodes)||n.push({code:"invalid_nodes",error:"payload.flow.nodes must be an array"}):n.push({code:"invalid_nodes",error:"payload.flow.nodes must be an array (use [] to clear the tab intentionally)"}),"configs"in r&&r.configs!==void 0&&!Array.isArray(r.configs)&&n.push({code:"invalid_nodes",error:"payload.flow.configs must be an array"});let o=[];Array.isArray(r.nodes)&&o.push({key:"nodes",list:r.nodes}),Array.isArray(r.configs)&&o.push({key:"configs",list:r.configs}),n.push(...Hs(o.map(s=>s.list)));for(let{key:s,list:i}of o)for(let d=0;d<i.length;d+=1){let l=i[d];if(!l||typeof l!="object"||Array.isArray(l)){n.push({code:"invalid_node",error:`payload.flow.${s}[${d}] must be an object`});continue}let c=l;(typeof c.id!="string"||c.id.length===0)&&n.push({code:"missing_node_id",error:`payload.flow.${s}[${d}] needs a non-empty string id`});try{bt(c.type,"node.type")}catch(u){u instanceof k&&n.push({code:u.code,error:u.message,...typeof c.id=="string"?{nodeId:c.id}:{}})}"wires"in c&&kn(c.wires,n,typeof c.id=="string"?c.id:void 0)}return n}function kn(e,t,n){if(!Array.isArray(e))return t.push({code:"invalid_wires",error:"wires must be an array of output ports (each a list of node ids)",...n?{nodeId:n}:{}}),!1;for(let r of e){if(!Array.isArray(r))return t.push({code:"invalid_wires",error:"wires must be an array of output ports (each a list of node ids)",...n?{nodeId:n}:{}}),!1;for(let o of r)if(typeof o!="string"||o.length===0)return t.push({code:"invalid_wires",error:"wires targets must be non-empty node id strings",...n?{nodeId:n}:{}}),!1}return!0}async function Sp(e,t){if(typeof e.outputs=="number"&&Number.isFinite(e.outputs)&&e.outputs>=0)return Math.trunc(e.outputs);let n=typeof e.type=="string"?e.type:"";if(!(!t||!n))try{return(await jt(t,n,{includeHelp:!1})).outputs}catch{return}}function Tp(e,t,n){let r=typeof e.type=="string"?e.type:"node";if(!(n.length<=t))return t===0?{code:"invalid_wires",error:`${r} has 0 outputs; wires must be []`,nodeId:e.id,type:r}:{code:"invalid_wires",error:`${r} has ${t} outputs; wires has ${n.length} port arrays (max ${t})`,nodeId:e.id,type:r}}function Rp(e){return Array.isArray(e)&&e.some(t=>typeof t=="string"&&t.length>0)}function Pp(e,t){return t<=0?!1:!("wires"in e)||e.wires===void 0?!0:Array.isArray(e.wires)?!e.wires.some(Rp):!1}async function js(e,t,n={}){let r=[],o=[],s=new Set(e.map(d=>d.id)),i=n.softWarningNodeIds;for(let d of e){let l=await Sp(d,t),c=typeof d.type=="string"?d.type:"node",u=!i||i.has(d.id);if("wires"in d&&d.wires!==void 0){if(!kn(d.wires,r,d.id))continue;let p=d.wires;if(l!==void 0){let f=Tp(d,l,p);f?(f.cause="excess_ports",r.push(f)):u&&p.length<l&&o.push({code:"incomplete_wires",error:`${c} '${d.id}' declares ${l} outputs but wires has only ${p.length} port(s)`,nodeId:d.id,type:c})}for(let f of p)for(let m of f){let h=String(m);s.has(h)||r.push({code:"invalid_wires",error:`Wire target '${h}' on node '${d.id}' was not found in this flow (add the node in the same patch or use an existing id)`,nodeId:d.id,cause:"missing_target",targetId:h})}}u&&l!==void 0&&Pp(d,l)&&o.push({code:"unwired_node",error:`Node '${d.id}' (${c}) has ${l} output(s) but is not wired to anything yet`,nodeId:d.id,type:c})}return{errors:r,warnings:o}}function Us(e,t){let n=[];for(let r of e)r.z===void 0||r.z===""||typeof r.z=="string"&&r.z!==t&&n.push({code:"z_mismatch",error:`Node '${r.id}' has z='${r.z}' but flow id is '${t}'`,nodeId:r.id});return n}function _p(e,t){return e.filter(n=>n.type===t).map(n=>n.id)}async function Bs(e,t,n){let r=[],o=[...t];for(let s of e){let i;try{i=bt(s.type,"node.type")}catch{continue}let d;try{d=await jt(n,i,{includeHelp:!1})}catch{continue}let l=d.defaults;if(!l||typeof l!="object")continue;let c=new Set(d.requiredConfigNodeTypes??[]);for(let[u,p]of Object.entries(l)){if(!p||typeof p!="object"||Array.isArray(p))continue;let f=p.type;if(typeof f!="string"||!f)continue;let h=p.required===!0||c.has(f),g=s[u],y=_p(o,f);if(typeof g!="string"||g.length===0){h&&r.push({code:"missing_config_node",error:`Node '${s.id}' (${i}) has no ${f} on '${u}' yet \u2014 configure before deploy if needed`,nodeId:s.id,type:i,property:u,configType:f,candidates:y});continue}o.find(v=>v.id===g)?.type!==f&&r.push({code:"missing_config_node",error:`Node '${s.id}' property '${u}' does not resolve to a ${f} config yet`,nodeId:s.id,type:i,property:u,configType:f,candidates:y})}}return r}async function ja(e,t={}){let n=t.flowId??(typeof e.id=="string"?e.id:""),r=Array.isArray(e.nodes)?e.nodes:[],o=Array.isArray(e.configs)?e.configs:[],s=[],i=[];n&&s.push(...Us(r,n));let d=t.baseFlow?xp(t.baseFlow,e):void 0,l=await js(r,t.nodeTypes,{...d?{softWarningNodeIds:d}:{}});if(s.push(...l.errors),i.push(...l.warnings),t.nodeTypes){let c=await Bs(r,o,t.nodeTypes);d?i.push(...c.filter(u=>!u.nodeId||d.has(u.nodeId))):i.push(...c)}return{errors:s,warnings:i}}var kp,qs=q(()=>{"use strict";se();Ut();a(lt,"throwValidationIssues");a(Hs,"collectDuplicateNodeIdIssues");a(vp,"describeLiveNode");a(Da,"collectCrossFlowDuplicateIssues");kp=new Set(["x","y","w","h","g","id","z"]);a(Ma,"collectNodesMap");a(Ap,"nodeContentChanged");a(xp,"changedNodeIds");a(Ha,"collectFullFlowShapeIssues");a(kn,"assertValidWiresShape");a(Sp,"resolveNodeOutputCount");a(Tp,"wireCountIssue");a(Rp,"portHasTargets");a(Pp,"nodeIsUnwired");a(js,"collectWireValidation");a(Us,"collectZMismatchIssues");a(_p,"configCandidates");a(Bs,"collectConfigWarnings");a(ja,"validateFlowTab")});function vt(e,t="node"){return typeof e=="string"&&e.trim()?e.trim():t}function ho(e){let t=new Map;for(let n of e)n.type==="tab"&&typeof n.id=="string"&&typeof n.label=="string"&&n.label.trim()&&t.set(n.id,n.label.trim());return t}function Ba(e,t){e[Ua]=t}function Ip(e){if(!e)return;let t=e[Ua];return t instanceof Map?t:void 0}function Lp(e){return typeof e!="string"||!e.trim()?new Set:new Set(e.split(",").map(t=>t.trim()).filter(Boolean))}function Np(e){if(e===!0||e===1)return!0;if(typeof e=="string"){let t=e.trim().toLowerCase();return t==="1"||t==="true"||t==="yes"}return!1}function ge(e){let t=Lp(e.include);return Np(e.include_payload)&&(t.add("payload"),t.add("baseFlow")),t}function V(e,t){let n={req:e};t!==void 0&&(n.httpAdminRoot=t);let r=Ip(e);return r&&(n.liveFlowLabels=r),n}function An(e){return typeof e.name=="string"&&e.name.trim()?e.name.trim():typeof e.label=="string"&&e.label.trim()?e.label.trim():""}function cr(e){return`"${e.replace(/"/g,"'")}"`}function xn(e){let t=new Map;for(let n of e.nodes??[])t.set(n.id,n);for(let n of e.configs??[])t.set(n.id,n);return t}function Fp(e,t){let n=new Set(["x","y","w","h","g","id","z"]),r=new Set([...Object.keys(e),...Object.keys(t)]);for(let o of r)if(!n.has(o)&&JSON.stringify(e[o])!==JSON.stringify(t[o]))return!0;return!1}function Op(e,t){let n=An(e),r=An(t);if(!n||!r||n===r)return!1;let o=new Set(["x","y","w","h","g","id","z","name","label"]),s=new Set([...Object.keys(e),...Object.keys(t)]);for(let i of s)if(!o.has(i)&&JSON.stringify(e[i])!==JSON.stringify(t[i]))return!1;return!0}function zs(e,t){let n=[],r=[],o=[];for(let[s,i]of t)e.has(s)||n.push(i);for(let[s,i]of e)t.has(s)||r.push(i);for(let[s,i]of t){let d=e.get(s);d&&Fp(d,i)&&o.push({before:d,after:i})}return{added:n,removed:r,modified:o}}function mo(e){return e===1?"node":"nodes"}function $p(e){return e.length===1&&e[0]?`adds 1 ${vt(e[0].type)}`:`adds ${e.length} ${mo(e.length)}`}function Mp(e){return`deletes ${e} ${mo(e)}`}function Dp(e){return`edits ${e} ${mo(e)}`}function Hp(e){let t=[];e.removed>0&&t.push(Mp(e.removed));let n=e.added??[],r=e.addedCount??n.length;r>0&&t.push(n.length===r?$p(n):`adds ${r} ${mo(r)}`),(e.modified??0)>0&&t.push(Dp(e.modified??0));let o=t.length?t.join(", "):"replaces nodes",s=e.flowLabel?.trim();return s?`Replace ${s}: ${o}`:`Replace flow: ${o}`}function jp(e){let{added:t,removed:n,modified:r}=e;if(t.length+n.length+r.length!==1)return;if(t[0]){let u=t[0],p=vt(u.type),f=An(u);return f?`Add ${p} node ${cr(f)}`:`Add ${p} node`}if(n[0]){let u=n[0],p=vt(u.type),f=An(u);return f?`Remove ${p} node ${cr(f)}`:`Remove ${p} node`}let s=r[0];if(!s)return;let i=vt(s.after.type,vt(s.before.type)),d=An(s.before),l=An(s.after);if(Op(s.before,s.after)&&d&&l)return`Rename ${i} node ${cr(d)} \u2192 ${cr(l)}`;let c=l||d;return c?`Edit ${i} ${cr(c)}`:`Edit ${i}`}function Ws(e,t){return typeof e?.label=="string"&&e.label.trim()?e.label.trim():typeof t?.label=="string"&&t.label.trim()?t.label.trim():""}function go(e,t){if(typeof e.flowLabel=="string"&&e.flowLabel.trim())return e.flowLabel.trim();let n=Ws(e.payload?.flow,e.baseFlow);if(n)return n;let r=e.payload?.flowId;return r&&t?.has(r)?t.get(r)??"":""}function qa(e,t){let n=t.trim(),r=e.trim();if(!n||!r||r.includes(n))return r;let o=r.lastIndexOf(" in ");if(o>=0){let s=r.slice(o+4);if(s.length>0&&n.startsWith(s))return r}return`${r} in ${n}`}function Gs(e,t){let n=zs(xn(e??{}),xn(t)),r=n.added.length+n.removed.length+n.modified.length,o=Ws(t,e);if(r===0)return o?`Update ${o}`:"Update flow";if(n.removed.length>0)return Hp({...o?{flowLabel:o}:{},removed:n.removed.length,added:n.added,modified:n.modified.length});if(r>Ep)return o?`${r} changes in ${o}`:`${r} changes`;let s=jp(n)??(o?`Update ${o}`:"Update flow");return qa(s,o)}function Up(e,t){let n=(e??"").trim();return!!(!n||n.includes("[object Object]")||/^Update(\s|$)/i.test(n)||t>0&&!/^Replace\b/i.test(n))}function Ks(e,t){return`${e} \xB7 ${qp(t)}`}function Bp(e){let t=e>=0?"+":"-",n=Math.abs(e),r=String(Math.floor(n/60)).padStart(2,"0"),o=String(n%60).padStart(2,"0");return`${t}${r}:${o}`}function qp(e,t=new Date(e)){let n=Number.isNaN(t.getTime())?new Date(e):t;if(Number.isNaN(n.getTime()))return e.slice(0,16);let r=n.getDate(),s=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"][n.getMonth()]??"Jan",i=String(n.getHours()).padStart(2,"0"),d=String(n.getMinutes()).padStart(2,"0"),l=Bp(-n.getTimezoneOffset());return`${r} ${s} ${i}:${d} ${l}`}function ur(e){if(typeof e!="string")return;let t=e.replace(/\0/g,"").replace(/[\u0001-\u001F\u007F]/g," ").replace(/\s+/g," ").trim().slice(0,Cp).trim();return t.length>0?t:void 0}function zp(e,t){let n=Ae(t);return`${n==="/"?"/ai-access":`${n}/ai-access`}#proposal=${encodeURIComponent(e)}`}function Js(e,t={}){let n=go(e,t.liveFlowLabels),r=ur(e.title),o=Gs(e.baseFlow,n&&!Ws(e.payload.flow,e.baseFlow)?{...e.payload.flow,label:n}:e.payload.flow),s=zs(xn(e.baseFlow??{}),xn(e.payload.flow)),i=Up(r,s.removed.length)?o:r??o;return qa(i,n)}function Wp(e){return typeof e.storedTitle=="string"&&e.storedTitle?e.storedTitle:ur(e.title)}function ue(e,t={}){let n=Js(e,t),r=go(e,t.liveFlowLabels),o=Wp(e)??n,s=zp(e.id,t.httpAdminRoot),i=t.req?ar(t.req,s):s;return{...e,...e.conflictChoice?{conflictChoice:{nodeId:e.conflictChoice.nodeId,choice:e.conflictChoice.choice,...e.conflictChoice.chooser?{chooser:e.conflictChoice.chooser}:{}}}:{},title:n,storedTitle:o,flowLabel:r,displayName:Ks(n,e.createdAt),reviewUrl:i,reviewPath:s}}function Gp(e){let t=zs(xn(e.baseFlow??{}),xn(e.payload.flow)),n=[];return t.added.length>0&&n.push(t.added.length===1?"Adds 1 node":`Adds ${t.added.length} nodes`),t.removed.length>0&&n.push(t.removed.length===1?"Deletes 1 node":`Deletes ${t.removed.length} nodes`),t.modified.length>0&&n.push(t.modified.length===1?"Edits 1 node":`Edits ${t.modified.length} nodes`),n.length===0?"No node changes":n.map((r,o)=>o===0?r:r.charAt(0).toLowerCase()+r.slice(1)).join(", ")}function Kp(e){if(e.kind==="oauth")return{kind:"oauth",clientId:e.clientId,user:e.user};let t={kind:e.kind,linkId:e.linkId};return typeof e.label=="string"&&e.label.length>0&&(t.label=e.label),t}function ie(e,t={}){let n=ue(e,t),r=t.include??new Set,o={id:n.id,status:n.status,type:n.type,title:n.title,storedTitle:n.storedTitle,displayName:n.displayName,flowLabel:n.flowLabel,reviewUrl:n.reviewUrl,reviewPath:n.reviewPath,createdAt:n.createdAt,updatedAt:n.updatedAt,expectedRev:n.expectedRev,baseRev:n.expectedRev,createdBy:Kp(n.createdBy),changeSummary:Gp(e)};return r.has("payload")&&(o.payload=n.payload),r.has("baseFlow")&&n.baseFlow!==void 0&&(o.baseFlow=n.baseFlow),n.warnings&&n.warnings.length>0&&(o.warnings=n.warnings),typeof e.payload.flowId=="string"&&(o.flowId=e.payload.flowId),e.restoredSecrets&&e.restoredSecrets.length>0&&(o.restoredSecrets=e.restoredSecrets),n.deployPending&&(o.deployPending=n.deployPending),n.aiConflictOverrides&&(o.aiConflictOverrides=n.aiConflictOverrides),n.conflictChoice&&(o.conflictChoice=n.conflictChoice),n.rebase&&(o.rebase=n.rebase),n.rebase&&(n.status==="pending"||n.status==="conflict")&&(o.baseRevChanged=n.expectedRev!==n.rebase.rev),n.baseRevChanged!==void 0&&(o.baseRevChanged=n.baseRevChanged),n.decidedBy!==void 0&&(o.decidedBy=n.decidedBy),n.reason!==void 0&&(o.reason=n.reason),n.resultRev!==void 0&&(o.resultRev=n.resultRev),o}function za(e,t={}){return e.map(n=>ie(n,t))}var Cp,Ep,Ua,Oe=q(()=>{"use strict";ve();ze();Cp=80,Ep=1;a(vt,"nodeTypeText");Ua=Symbol.for("ai-access.liveFlowLabels");a(ho,"liveFlowLabelsFromFlows");a(Ba,"setRequestFlowLabels");a(Ip,"getRequestFlowLabels");a(Lp,"parseIncludeQuery");a(Np,"isTruthyFlag");a(ge,"resolveIncludeFromQuery");a(V,"presentOptionsFor");a(An,"nodeLabel");a(cr,"quoteName");a(xn,"collectNodes");a(Fp,"nodeContentChanged");a(Op,"isRenameOnly");a(zs,"classifyFlowChanges");a(mo,"pluralNodes");a($p,"describeAddedClause");a(Mp,"describeRemovedClause");a(Dp,"describeModifiedClause");a(Hp,"buildReplaceFlowTitle");a(jp,"describeOneChange");a(Ws,"flowLabelHint");a(go,"resolveFlowLabel");a(qa,"ensureTitleIncludesFlowName");a(Gs,"autoGenerateProposalTitle");a(Up,"shouldRefreshDisplayTitle");a(Ks,"buildProposalDisplayName");a(Bp,"formatUtcOffset");a(qp,"formatProposalSubmittedAt");a(ur,"sanitizeProposalTitle");a(zp,"buildProposalReviewPath");a(Js,"resolveProposalTitle");a(Wp,"storedTitleOf");a(ue,"presentProposal");a(Gp,"buildChangeSummary");a(Kp,"presentCreatedByForApi");a(ie,"presentProposalForApi");a(za,"presentProposalsForApi")});function gr(e){let t=e.join("\0"),n=yo.get(t);if(n)return n;let r=[...new Set(e.map(d=>zr(d).join("")).filter(Boolean))],o=new Map,s={patterns:[...e],matches(d){if(typeof d!="string"||d.length===0||r.length===0)return!1;let l=o.get(d);if(l!==void 0)return l;let c=i(d);return o.size>4096&&o.clear(),o.set(d,c),c}};function i(d){{let l=zr(d),c=l[l.length-1];if(!c||Jp.has(c))return!1;for(let u of r){let p=`${u}S`;for(let f=0;f<l.length;f++){let m="";for(let g=f;g<l.length;g++){if(m+=l[g],m===u||m===p)return!0;if(m.length>p.length)break}let h=l[f]??"";if(u.length>=6&&(h.endsWith(u)||h.endsWith(p)))return!0}}return!1}}return a(i,"matchUncached"),yo.size>32&&yo.clear(),yo.set(t,s),s}function kt(){return aa()?.getEffective()?.secretNamePatterns??Ot}function fr(e){let t=e.trim();return!(!t||t===J||Vp.has(t.toLowerCase())||/^\$\{[A-Za-z0-9_]+\}$/.test(t))}function Xp(e){let t=Ka.get(e);return t||(t=Zp(e),Ka.set(e,t)),t}function Zp(e){return[{hints:["Buffer.from","btoa"],re:/\b(?:Buffer\.from|btoa)\([ \t]{0,8}(?<q>["'`])[^"'`:\s]{1,256}:(?<s>[^"'`\s]{1,1024})\k<q>/dg,accept:a(t=>!t.includes("${"),"accept")},{hints:["PRIVATE KEY-----"],re:/-----BEGIN ((?:[A-Z0-9]{1,16} ){0,4})PRIVATE KEY-----(?<s>[^-]{1,16384})-----END \1PRIVATE KEY-----/dg},{hints:["AKIA","ASIA","ABIA","ACCA"],re:/(?<![A-Za-z0-9])(?<s>(?:AKIA|ASIA|ABIA|ACCA)[0-9A-Z]{16})(?![A-Za-z0-9])/dg},{hints:["ghp_","gho_","ghu_","ghs_","ghr_"],re:/(?<![A-Za-z0-9_])(?<s>gh[pousr]_[A-Za-z0-9]{36,255})(?![A-Za-z0-9_])/dg},{hints:["github_pat_"],re:/(?<![A-Za-z0-9_])(?<s>github_pat_[A-Za-z0-9_]{22,255})(?![A-Za-z0-9_])/dg},{hints:["xox"],re:/(?<![A-Za-z0-9_-])(?<s>xox[abposr]-[A-Za-z0-9-]{10,512})/dg},{hints:["hooks.slack.com"],re:/hooks\.slack\.com\/services\/(?<s>[A-Za-z0-9_/]{16,512})/dg},{hints:["sk-"],re:/(?<![A-Za-z0-9_-])(?<s>sk-[A-Za-z0-9_-]{20,512})/dg},{hints:["k_live_","k_test_"],re:/(?<![A-Za-z0-9_])(?<s>[sr]k_(?:live|test)_[A-Za-z0-9]{16,512})/dg},{hints:["AIza"],re:/(?<![A-Za-z0-9_-])(?<s>AIza[0-9A-Za-z_-]{35})(?![A-Za-z0-9_-])/dg},{hints:["eyJ"],re:/(?<![A-Za-z0-9_-])(?<s>eyJ[A-Za-z0-9_-]{5,8192}\.[A-Za-z0-9_-]{5,8192}\.[A-Za-z0-9_-]{5,8192})/dg},{hints:["earer"],re:/\b[Bb]earer[ \t]{1,8}(?<s>(?=[A-Za-z0-9\-._~+/]{0,4096}[0-9\-._~+/])[A-Za-z0-9\-._~+/]{16,4096}={0,3})/dg},{hints:["asic"],re:/\b[Bb]asic[ \t]{1,8}(?<s>(?=[A-Za-z0-9+/]{0,4096}[0-9+/])[A-Za-z0-9+/]{8,4096}={0,2})(?![A-Za-z0-9+/])/dg},{hints:["@"],re:/\b[A-Za-z][A-Za-z0-9+.-]{1,20}:\/\/[^\s:@/'"`<>]{1,256}:(?<s>[^\s@/'"`<>]{1,1024})@/dg,accept:a(t=>!/^(?:\$\{|\{\{|<|%)/.test(t)&&t!==J,"accept")},{hints:["?","&"],re:/[?&](?<k>[A-Za-z_][\w.-]{0,63})=(?<s>[^&#\s"'`<>]{1,2048})/dg,accept:a((t,n)=>e.matches(n.groups?.k??"")&&fr(rf(t)),"accept")},{hints:[":","="],re:/(?<![\w$-])(?<q>["'`]?)(?<k>[A-Za-z_$][\w$-]{0,63})\k<q>\]?[ \t]{0,8}(?::|=(?![=>]))[ \t]{0,8}(?<vq>["'`])(?<s>(?:\\.|(?!\k<vq>)[^\\\r\n]){1,1024})\k<vq>/dg,accept:a((t,n)=>{let r=n.groups?.k??"";return!e.matches(r)||n.groups?.vq==="`"&&t.includes("${")||t.length<3||/\s/.test(t)?!1:fr(t)},"accept")}]}function ef(e){let t=new Map;for(let r of e)t.set(r,(t.get(r)??0)+1);let n=0;for(let r of t.values()){let o=r/e.length;n-=o*Math.log2(o)}return n}function tf(e){return/[^A-Za-z]/.test(e)||e.length>=16?!0:[/[a-z]/,/[A-Z]/,/[0-9]/,/[^A-Za-z0-9]/].filter(n=>n.test(e)).length>=3||ef(e)>=3}function nf(e){let t=e.patterns.filter(r=>Qp.includes(r.toUpperCase())),n=gr(t);return{hints:[":","="],re:/(?<![\w$.-])(?<k>[A-Za-z_][\w-]{0,63})[ \t]{0,8}(?::(?!\/\/)|=)[ \t]{0,8}(?<s>[^\s;,'"`&<>(){}[\]]{3,1024})/dg,accept:a((r,o)=>e.matches(o.groups?.k??"")&&fr(r)&&!r.startsWith("$")&&!r.startsWith("{{")&&(n.matches(o.groups?.k??"")||tf(r))&&!/^[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)+$/.test(r)&&o.input.charAt(o.indices?.groups?.s?.[1]??0)!=="(","accept")}}function rf(e){try{return decodeURIComponent(e)}catch{return e}}function of(e,t,n){let r=e.indexOf("=",t);if(r<=t||r>=n||r-t>48)return;let o=e.slice(t,r),s=Math.max(o.lastIndexOf('"'),o.lastIndexOf("'"),o.lastIndexOf("`")),i=o.slice(s+1).trim();if(!/^[A-Za-z][\w -]{0,40}$/.test(i))return;let d=r+1;for(;d<n&&(e[d]===" "||e[d]===" ");)d++;let l=d;for(;l<n&&!"\"'`\r".includes(e[l]??"");)l++;for(;l>d&&(e[l-1]===" "||e[l-1]===" ");)l--;return{k:i,vs:d,ve:l}}function sf(e,t){if(!e.includes(";")||!e.includes("="))return[];let n=[],r=0;for(;r<=e.length;){let o=e.indexOf(`
7
+ `,r);o<0&&(o=e.length);let s=[],i=r,d=!1;for(;i<o;){let l=e.indexOf(";",i);l<0||l>o?l=o:d=!0;let c=of(e,i,l);c&&s.push(c),i=l+1}if(d&&s.length>=2)for(let l of s)l.ve<=l.vs||!t.matches(l.k)||fr(e.slice(l.vs,l.ve))&&n.push({start:l.vs,end:l.ve});r=o+1}return n}function af(e,t,n={}){if(e.length===0)return{text:e,secrets:[],spans:[]};let r=[],o=Xp(t);if(!n.code){let u=Ja.get(t);u||(u=nf(t),Ja.set(t,u)),o=[...o,u]}r.push(...sf(e,t));for(let u of o){if(u.hints&&!u.hints.some(f=>e.includes(f)))continue;u.re.lastIndex=0;let p;for(;(p=u.re.exec(e))!==null;){let f=p.indices?.groups?.s,m=p.groups?.s;f&&m!==void 0&&m.length>0&&(!u.accept||u.accept(m,p))&&r.push({start:f[0],end:f[1]}),p[0].length===0&&u.re.lastIndex++}}if(r.length===0)return{text:e,secrets:[],spans:[]};r.sort((u,p)=>u.start-p.start||p.end-u.end);let s=[];for(let u of r){let p=s[s.length-1];p&&u.start<p.end?p.end=Math.max(p.end,u.end):s.push({...u})}let i="",d=0,l=[],c=[];for(let u of s){let p=e.slice(u.start,u.end);p!==J&&(i+=e.slice(d,u.start)+J,l.push(p),c.push(u),d=u.end)}return i+=e.slice(d),{text:i,secrets:l,spans:c}}function Zs(e,t){let n=[],r=0,o=a(s=>{let i=ko(s);for(let d=0;d<i;d++)n.push({kind:"literal",value:J})},"literals");for(let s of t)o(e.slice(r,s.start)),n.push({kind:"secret",value:e.slice(s.start,s.end)}),r=s.end;return o(e.slice(r)),n}function ee(e){return!!e&&typeof e=="object"&&!Array.isArray(e)}function df(e){return ee(e)&&typeof e.id=="string"&&typeof e.type=="string"}function Qs(e,t){let n=new Ys(t,e.id),r=e[pr];if(Array.isArray(r))for(let s of r)typeof s=="string"&&n.paths.push(s);let o={};for(let[s,i]of Object.entries(e))s!==pr&&(Xa.has(s)?o[s]=i:ed.has(s)&&Array.isArray(i)?o[s]=i.map(d=>ei(d,t)):s==="env"&&Array.isArray(i)?o[s]=n.env(i,"env"):o[s]=n.value(i,s,s,e));return n.paths.length>0&&(o[pr]=n.paths),o}function ei(e,t){return Array.isArray(e)?e.map(n=>ei(n,t)):df(e)?Qs(e,t):e}function ct(e,t=kt()){let n={matcher:gr(t),count:0};return{value:ei(e,n),count:n.count}}function ti(e,t=kt()){return e.map(n=>{if(n.nodeName===void 0)return{...n};let r=ct({id:"conflict",type:"node",name:n.nodeName},t);return{...n,nodeName:r.value.name}})}function Ye(e,t=kt()){let n={...e,payload:{...e.payload,flow:ct(e.payload.flow,t).value}};return e.conflictChoice&&(n.conflictChoice={nodeId:e.conflictChoice.nodeId,choice:e.conflictChoice.choice,...e.conflictChoice.chooser?{chooser:e.conflictChoice.chooser}:{}}),e.baseFlow!==void 0&&(n.baseFlow=ct(e.baseFlow,t).value),e.rebase&&(n.rebase={...e.rebase,conflicts:ti(e.rebase.conflicts,t)}),n}function At(e,t=kt){if(td(e))return e;let n=t===kt,r=n?Va.get(e):void 0;if(r)return r;let o={kind:e.kind,[ni]:e,async getFlows(){let s=await e.getFlows();return{rev:s.rev,flows:ct(s.flows,t()).value}},async getFlow(s){let i=await e.getFlow(s);return{rev:i.rev,flow:ct(i.flow,t()).value}},updateFlow:a(s=>e.updateFlow(s),"updateFlow")};return n&&Va.set(e,o),o}function td(e){return ni in e}function $e(e){return td(e)?e[ni]:e}function Xe(e,t){return e.accessLink||e.auth?At(t):t}function ko(e){let t=0,n=e.indexOf(J);for(;n!==-1;)t++,n=e.indexOf(J,n+J.length);return t}function hr(e){let t=e.split(J),n=[];for(let r=1;r<t.length;r++){let o=t[r-1]??"",s=t[r]??"",i=o.lastIndexOf(`
8
8
  `),d=s.indexOf(`
9
- `);n.push({before:i>=0?o.slice(i+1):o,after:d>=0?s.slice(0,d):s})}return n}function Cu(e,t,n,r){if(e===t)return{text:n,restored:Fr(e)};let o=Fi(e),s=Fi(t);if(o.length>s.length)return{error:`contains ${o.length} [REDACTED] placeholders but the original had ${s.length}`};let i=new Set,d=e.split(te),l=d[0]??"";for(let c=0;c<o.length;c++){let u=o[c];if(!u)break;let p=s.findIndex((f,m)=>!i.has(m)&&f.before===u.before&&f.after===u.after);if(p<0)return{error:"a [REDACTED] placeholder is not on an unchanged original line"};i.add(p),l+=(r[p]??te)+(d[c+1]??"")}return{text:l,restored:o.length}}function Oi(e){if(re(e))for(let t of Lu){let n=e[t];if(typeof n=="string"&&n.length>0)return`${t}=${n}`}}function Lr(e,t,n,r,o,s){if(typeof e=="string"){if(!e.includes(te))return e;let i=s.secrets.get(`${r}\0${o}`);if(typeof t=="string"&&typeof n=="string"&&i?.length){let d=Cu(e,t,n,i);return"error"in d?(s.unresolved.push({nodeId:r,property:o,reason:d.error}),e):(s.restored+=d.restored,s.restoredFields.push({nodeId:r,property:o}),d.text)}return s.unresolved.push({nodeId:r,property:o,reason:"no redacted secret exists at this node id and field"}),e}if(Array.isArray(e)){let i=Array.isArray(t)?t:[],d=Array.isArray(n)?n:[],l=new Set;return e.map(c=>{let u=re(c)&&c.redacted===!0?Xo(c,"redacted"):c,p=Oi(u),f=-1;if(p!==void 0){let m=d.map((h,g)=>Oi(h)===p&&!l.has(g)?g:-1).filter(h=>h>=0);m.length>0&&(f=m[0]??-1)}return f>=0?(l.add(f),Lr(u,i[f],d[f],r,`${o}[${f}]`,s)):Lr(u,void 0,void 0,r,`${o}[?]`,s)})}if(re(e)){let i=re(t)?t:{},d=re(n)?n:{},l={};for(let[c,u]of Object.entries(e))l[c]=Lr(u,i[c],d[c],r,o?`${o}.${c}`:c,s);return l}return e}function Xo(e,t){let n={...e};return delete n[t],n}function Iu(e){let t=Xo(e,$n);return Array.isArray(t.env)&&(t.env=t.env.map(n=>!re(n)||n.redacted!==!0?n:Xo(n,"redacted"))),t}function zi(e,t,n,r){let o=typeof e.id=="string"?e.id:"",s=o?t.get(o):void 0,i={},d=s?Ui({...s,id:o},r):void 0;for(let[l,c]of Object.entries(e))if(l!==$n){if(Hi.has(l)&&Array.isArray(c)){i[l]=c.map(u=>re(u)?zi(u,t,n,r):u);continue}if(Mi.has(l)){i[l]=c;continue}i[l]=Lr(c,d?.[l],s?.[l],o,l,n)}return Iu(i)}function Wi(e,t,n=Kt()){let r=new Map;for(let l of t)re(l)&&typeof l.id=="string"&&r.set(l.id,l);let o=new Map,s={matcher:Nr(n),count:0,secrets:o},i={secrets:o,restored:0,restoredFields:[],unresolved:[]};return{flow:zi(e,r,i,s),restored:i.restored,restoredFields:i.restoredFields,unresolved:i.unresolved}}function $u(e){return typeof e.type=="string"&&Fu.has(e.type)?!0:Ou.some(t=>typeof e[t]=="string"&&e[t]!=="")}function $i(e){let t=new Set,n=a((r,o)=>{if(!(o>12)){if(typeof r=="string"){if(r.length>ji)return;let s=/(?:\b(?:env|global|flow)\.get|\$env)\([ \t]{0,8}["'`]([^"'`\r\n]{1,128})["'`]|\$\{([A-Za-z_][\w.-]{0,127})\}/g,i;for(;(i=s.exec(r))!==null;)t.add(i[1]??i[2]??"");return}if(Array.isArray(r)){for(let s of r)n(s,o+1);return}if(re(r))for(let[s,i]of Object.entries(r))[`${s}Type`,s==="v"?"vt":"",s==="to"?"tot":"",s==="value"?"type":"",s==="valueValue"?"valueType":"",s==="keyValue"?"keyType":""].some(c=>c&&typeof r[c]=="string"&&Mu.has(r[c]))&&typeof i=="string"&&t.add(i),n(i,o+1)}},"walk");return n(e,0),t.delete(""),t}function Mn(e,t){return JSON.stringify(e)===JSON.stringify(t)}function Gi(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]);for(let r of n)if(!(Du.has(r)||r==="nodes"||r==="configs")&&!Mn(e[r],t[r]))return!0;return!1}function ju(e,t,n){let r=new Set([n]),o=typeof e.g=="string"?e.g:void 0;for(let s=0;o&&s<32&&!r.has(o);s++){r.add(o);let i=t.get(o);o=i&&typeof i.g=="string"?i.g:void 0}for(let s of Object.values(e))typeof s=="string"&&s!==e.id&&t.get(s)?.z===void 0&&t.has(s)&&r.add(s);for(let[s,i]of t)i.type==="global-config"&&r.add(s);return[...r]}function Hu(e,t){return e.filter(n=>n.nodeId===t).map(n=>n.property).join(", ")}function Uu(e,t,n,r){let o=[],s=Nu.filter(d=>!Mn(n[d],r[d]));s.length>0&&o.push({code:"secret_destination_changed",error:`SECURITY: node ${e} holds a secret (${t}) restored from the live flow, and this proposal changes where it sends data (${s.join(", ")}). Verify the new destination before approving.`,nodeId:e,property:s.join(",")});let i=[...Di].filter(d=>!Mn(n[d],r[d]));return i.length>0&&o.push({code:"secret_node_code_changed",error:`Node ${e} holds a secret (${t}) and its code changed (${i.join(", ")}). Check the new code does not send the secret anywhere unexpected.`,nodeId:e,property:i.join(",")}),Mn(n.wires,r.wires)||o.push({code:"secret_wires_changed",error:`SECURITY: node ${e} holds a secret (${t}) and its output wires changed. Check where its messages (which may carry the secret) now go.`,nodeId:e,property:"wires"}),o}function Bu(e,t,n,r,o,s){let i=[];for(let d of ju(t,n,o)){if(d===e||s.has(d))continue;let l=n.get(d),c=d===o,u=r.get(d)??(l&&!c&&l.z===void 0?l:void 0);if(!(c?!Mn(l?.env,u?.env):l!==void 0&&u!==void 0&&Gi(l,u)))continue;s.add(d);let f=c?"tab":typeof l?.type=="string"?l.type:"node";i.push({code:"secret_scope_changed",error:`SECURITY: ${f} ${d} scopes or configures secret-holding node ${e} (env vars like \${HOST} or a config node) and it changed. Check URLs/hosts it resolves to.`,nodeId:d,property:c?"env":"config"})}return i}function qu(e,t,n,r,o){let s=[],i=[];for(let[d,l]of n){if(d===r)continue;let c=t.get(d);if(c&&!Gi(c,l))continue;e.includes(d)||i.push(d);let u=typeof l.type=="string"?l.type:"node";$u(l)&&!e.includes(d)&&s.push({code:"outbound_node_changed",error:`SECURITY: this proposal ${c?"changes":"adds"} outbound-capable node ${d} (${u}). Check where it sends data before approving.`,nodeId:d,property:"type"});let p=c?$i(c):new Set,f=[...$i(l)].filter(m=>!p.has(m)&&o.matches(m));f.length>0&&s.push({code:"secret_reference_added",error:`SECURITY: node ${d} (${u}) now reads secret-named value(s) ${f.slice(0,5).join(", ")} (env / global / flow). The AI cannot see these values but the flow will use them at runtime; check they are not sent anywhere unexpected.`,nodeId:d,property:f.slice(0,5).join(",")})}if(e.length===0)return s;for(let[d,l]of t)l.z===r&&!n.has(d)&&i.push(d);if(i.length>0){let d=i.slice(0,10).join(", ")+(i.length>10?", \u2026":"");s.push({code:"secret_tab_changed",error:`This proposal restores secrets in this tab and also changes ${i.length} other node(s) (${d}). Review every change: any of them could route secret data elsewhere.`,nodeId:r,property:"nodes"})}return s}function zu(e,t){let n=new Map;n.set(t,e);for(let r of["nodes","configs"]){let o=e[r];if(Array.isArray(o))for(let s of o)re(s)&&typeof s.id=="string"&&n.set(s.id,s)}return n}function Ki(e,t,n,r=Kt()){let o=[...new Set(e.map(u=>u.nodeId))],s=String(t.id),i=new Map;for(let u of n)re(u)&&typeof u.id=="string"&&i.set(u.id,u);let d=zu(t,s),l=[],c=new Set;for(let u of o){let p=i.get(u),f=d.get(u);!p||!f||(l.push(...Uu(u,Hu(e,u),p,f)),l.push(...Bu(u,p,i,d,s,c)))}return l.push(...qu(o,i,d,s,Nr(r))),l}var $n,gu,yu,Vo,Mi,wu,Di,ji,Hi,Ei,Ci,Cr,Li,ku,Ii,Yo,Qo,Ni,Lu,Nu,Fu,Ou,Mu,Du,nt=U(()=>{"use strict";Sn();ue();Sn();$n="_aiRedactions",gu=new Set(["TYPE","TYPES","MODE","METHOD","FIELD","FIELDS","PROP","PROPERTY","FORMAT","TIMEOUT","EXPIRY","EXPIRES","TTL","LENGTH","LEN","SIZE","COUNT","ENABLED","DISABLED","ENABLE","USE","URL","URI","ENDPOINT","HOST","PORT","PATH","HEADER","NAME","LABEL","FILE"]),yu=new Set(["basic","digest","bearer","none","true","false","null","undefined","oauth","oauth2","jwt","apikey","api_key","token","password","header","query","body","cookie","msg","flow","global","env","str","num","json","bool","other"]),Vo=new Set(["env","msg","flow","global","jsonata","bool","date","node","cred"]),Mi=new Set(["id","type","z","g","x","y","w","h","wires","inputs","outputs","links","scope","icon","color","style","d","l","in","out","rev"]),wu=new Set(["name","label","info"]),Di=new Set(["func","initialize","finalize"]),ji=256*1024,Hi=new Set(["nodes","configs","subflows"]),Ei=["name","p","keyValue","key","property","k"],Ci={value:"type",v:"vt",to:"tot",valueValue:"valueType"},Cr=new Map;a(Nr,"createSecretNameMatcher");a(Kt,"currentSecretNamePatterns");a(Dn,"isSecretLikeValue");Li=new WeakMap;a(bu,"contentRules");a(vu,"buildContentRules");ku=["PASSWORD","PASSWD","PASSPHRASE","SECRET","CLIENT_SECRET","TOKEN","API_KEY","APIKEY","PRIVATE","CREDENTIAL","PWD","PW"];a(Au,"shannonEntropy");a(Su,"looksRandom");a(xu,"unquotedRule");a(Tu,"decodeURIComponentSafe");a(Ru,"parseConnSegment");a(_u,"connectionStringSpans");Ii=new WeakMap;a(Pu,"scanTextForSecrets");a(re,"isPlainObject");a(Eu,"isFlowEntity");Yo=class{constructor(t,n){this.ctx=t;this.entityId=n}static{a(this,"EntityRedactor")}paths=[];record(t,n){this.paths.includes(t)||this.paths.push(t),this.ctx.count+=n.length||1,this.ctx.secrets?.set(`${this.entityId}\0${t}`,n)}string(t,n,r){if(r&&Dn(t)||t.length>ji)return this.record(n,[t]),te;let o=n.split(/[.[]/,1)[0]??"",s=Pu(t,this.ctx.matcher,{code:Di.has(o)});return s.secrets.length>0?(this.record(n,s.secrets),s.text):t}env(t,n){return t.map((r,o)=>{if(!re(r))return r;let s=typeof r.type=="string"?r.type:"";if(s==="cred"||typeof r.value!="string"||Vo.has(s))return r;let i=typeof r.name=="string"?r.name:"",d=r.value,l=this.string(d,`${n}[${o}].value`,this.ctx.matcher.matches(i));return l===d?r:{...r,value:l,redacted:!0}})}value(t,n,r,o){if(typeof t=="string"){if(wu.has(r))return this.string(t,n,!1);let s=o?.[`${r}Type`],i=typeof s=="string"&&Vo.has(s);return this.string(t,n,!i&&this.ctx.matcher.matches(r))}return Array.isArray(t)?r==="env"?this.env(t,n):t.map((s,i)=>this.value(s,`${n}[${i}]`,"",void 0)):re(t)?this.object(t,n):t}object(t,n){let r=!1;for(let s of Ei){let i=t[s];if(typeof i=="string"&&this.ctx.matcher.matches(i)){r=!0;break}}let o={};for(let[s,i]of Object.entries(t)){let d=n?`${n}.${s}`:s;if(r&&s in Ci&&typeof i=="string"){let l=Ci[s],c=l?t[l]:void 0,u=typeof c=="string"&&Vo.has(c);o[s]=this.string(i,d,!u);continue}if(Ei.includes(s)&&typeof i=="string"){o[s]=this.string(i,d,!1);continue}o[s]=this.value(i,d,s,t)}return o}};a(Ui,"redactEntity");a(Zo,"redactValueInner");a(Ir,"redactFlowData");a(Jt,"redactProposalForAi");Qo=Symbol.for("ai-access.rawFlowAdapter"),Ni=new WeakMap;a(bt,"aiRedactingAdapter");a(Bi,"isRedactingAdapter");a(qi,"rawFlowAdapter");a(Me,"adapterForRequest");a(Fr,"countRedactionPlaceholders");a(Fi,"placeholderContexts");a(Cu,"restoreString");Lu=["name","keyValue","p","property","key","k"];a(Oi,"stableElementKey");a(Lr,"restoreWalk");a(Xo,"withoutKey");a(Iu,"stripMarkers");a(zi,"restoreEntity");a(Wi,"restoreRedactedPlaceholders");Nu=["type","url","host","hostname","server","broker","port","path","method","headers","endpoint","uri","protocol","proxy","tls","to","ret"],Fu=new Set(["http request","http response","link out","link call","mqtt out","mqtt-broker","tcp out","tcp request","udp out","e-mail","websocket out","websocket-client","websocket-listener","function","exec","file"]),Ou=["url","host","hostname","server","broker","endpoint","uri"];a($u,"isOutboundNode");Mu=new Set(["env","global","flow"]);a($i,"referencedNames");Du=new Set(["x","y","w","h","l"]);a(Mn,"sameJson");a(Gi,"nodeChanged");a(ju,"scopeIds");a(Hu,"restoredFieldList");a(Uu,"secretNodeWarnings");a(Bu,"scopeWarnings");a(qu,"tabWarnings");a(zu,"proposedNodeMap");a(Ki,"secretChangeWarnings")});var ta={};Os(ta,{applyFlowNodeOps:()=>vt,applyReplaceTextToValue:()=>ea,buildPatchedFlowTab:()=>jn,collectSetProps:()=>es,decodeBase64UrlJson:()=>Hn,findTextOccurrences:()=>Qi,interpretPatch:()=>Un});function Zi(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function Wu(e){if(typeof e!="object"||e===null||Array.isArray(e))throw new y(`add op needs node as an object (got ${Zi(e)})`,"invalid_node")}async function jn(e,t,n){return vt(e,t,n.map(r=>({op:"set",nodeId:r.nodeId,set:r.set})))}function Qi(e,t){if(!t)return[];let n=[],r=0;for(;r<=e.length;){let o=e.indexOf(t,r);if(o<0)break;n.push(o),r=o+t.length}return n}function ea(e,t,n,r,o){if(Xi.has(t))throw new y(`Cannot change protected node property '${t}' via replace_text`,"protected_property");if(typeof n!="string"||n.length===0)throw new y("find must be a non-empty string","missing_find");if(typeof r!="string")throw new y("replace must be a string","invalid_replace");if(e===void 0)throw new y(`Field '${t}' was not found on the node`,"field_not_found");if(typeof e!="string")throw new y(`Field '${t}' is not a string (got ${e===null?"null":typeof e})`,"field_not_string");let s=Qi(e,n);if(s.length===0)throw new y(`Text not found in field '${t}'`,"text_not_found",{details:{matchCount:0}});let i=o;if(i===void 0){if(s.length>1)throw new y(`Found ${s.length} matches in '${t}'; pass occurrence (1-based) to pick one`,"ambiguous_match",{statusCode:409,details:{matchCount:s.length}});i=1}if(!Number.isInteger(i)||i<1||i>s.length)throw new y(`occurrence must be an integer from 1 to ${s.length}`,"invalid_occurrence",{details:{matchCount:s.length}});let d=s[i-1];if(d===void 0)throw new y(`occurrence must be an integer from 1 to ${s.length}`,"invalid_occurrence",{details:{matchCount:s.length}});return e.slice(0,d)+r+e.slice(d+n.length)}function Gu(e){let t=[];for(let n of e)if(n.op==="add"){let r=n.node;if(typeof r!="object"||r===null||Array.isArray(r)){t.push({code:"invalid_node",error:`add op needs node as an object (got ${Zi(r)})`});continue}let o=r;(!o.id||typeof o.id!="string")&&t.push({code:"missing_node_id",error:"add op needs node.id"});try{Qe(o.type,"node.type")}catch(s){s instanceof y&&t.push({code:s.code,error:s.message,nodeId:o.id})}"wires"in o&&zt(o.wires,t,typeof o.id=="string"?o.id:void 0)}else n.op==="set"&&typeof n.set=="object"&&"wires"in n.set&&zt(n.set.wires,t,n.nodeId);return t}function Ku(e){let t=new Set;for(let n of e)n.op==="add"&&typeof n.node.id=="string"?t.add(n.node.id):n.op!=="add"&&typeof n.nodeId=="string"&&t.add(n.nodeId);return t}async function vt(e,t,n,r={}){if(!t)throw new y("flow id is required","missing_flow_id");if(!n.length)throw new y("At least one node edit is required","missing_edits");let o=Gu(n);o.push(...jo([n.flatMap(h=>h.op==="add"?[h.node]:[])],"the batch (add ops)")),qe(o);let s=await e.getFlow(t);if(r.expectedRev!==void 0&&s.rev!==r.expectedRev){let h=Array.isArray(s.flow.nodes)?[...s.flow.nodes]:[];return{expectedRev:s.rev,flow:{...s.flow,id:s.flow.id,type:s.flow.type??"tab",nodes:h}}}let i=Array.isArray(s.flow.nodes)?[...s.flow.nodes]:[],d=Array.isArray(s.flow.configs)?[...s.flow.configs]:[],l={...s.flow,id:s.flow.id,type:s.flow.type??"tab",nodes:i,configs:d},c=i.map(h=>({...h})),u=[];for(let h of n)try{h.op==="add"&&r.nodeTypes&&await yi(r.nodeTypes,h.node.type),Vi(c,t,h)}catch(g){if(g instanceof y)u.push({code:g.code,error:g.message,...g.statusCode!==400?{statusCode:g.statusCode}:{},...g.details??{}});else throw g}u.push(...Uo(c,t));let p=Ku(n),f=await Ho(c,r.nodeTypes,{softWarningNodeIds:p});u.push(...f.errors),qe(u);for(let h of n)Vi(i,t,h);let m=[...f.warnings];if(r.nodeTypes){let h=await Bo(i,d,r.nodeTypes);m.push(...h.filter(g=>!g.nodeId||p.has(g.nodeId)))}return{expectedRev:s.rev,flow:l,...m.length>0?{warnings:m}:{}}}function Ji(e,t,n){if(!t||typeof t!="string")throw new y("Each edit needs a node id","missing_node_id");let r=e.findIndex(o=>o.id===t);if(r<0||!e[r])throw new y(`Node '${t}' was not found in flow '${n}'`,"node_not_found",{statusCode:404});return r}function Vi(e,t,n){if(n.op==="add"){let i=n.node;if(!i.id||typeof i.id!="string")throw new y("add op needs node.id","missing_node_id");if(Qe(i.type,"node.type"),e.some(d=>d.id===i.id))throw new y(`Node '${i.id}' already exists in flow '${t}'`,"node_already_exists");e.push({...i,id:i.id,type:i.type,z:typeof i.z=="string"&&i.z?i.z:t});return}if(n.op==="remove"){let i=Ji(e,n.nodeId,t);e.splice(i,1);return}let r=Ji(e,n.nodeId,t),o=e[r];if(!o)throw new y(`Node '${n.nodeId}' was not found in flow '${t}'`,"node_not_found",{statusCode:404});if(n.op==="replace_text"){if(!(n.field in o))throw new y(`Field '${n.field}' was not found on the node`,"field_not_found");let i=ea(o[n.field],n.field,n.find,n.replace,n.occurrence);e[r]={...o,id:o.id,type:o.type,[n.field]:i};return}let s={...o,id:o.id,type:o.type};for(let[i,d]of Object.entries(n.set)){if(Xi.has(i))throw new y(`Cannot change protected node property '${i}' via GET propose`,"protected_property");if(i==="wires"){let l=[];zt(d,l,n.nodeId),qe(l)}s[i]=d}e[r]=s}function Yi(e){if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean"||typeof e=="bigint")return String(e)}function es(e){let t={},n=e.set;if(n&&typeof n=="object"&&!Array.isArray(n))for(let[r,o]of Object.entries(n)){let s=Yi(o);s!==void 0&&(t[r]=s)}for(let[r,o]of Object.entries(e)){if(!r.startsWith("set.")||r.length<=4)continue;let s=r.slice(4);if(typeof o=="string")t[s]=o;else if(Array.isArray(o)&&typeof o[0]=="string")t[s]=o[0];else{let i=Yi(o);i!==void 0&&(t[s]=i)}}return t}function Hn(e){let t;try{t=Buffer.from(e,"base64url").toString("utf8")}catch{throw new y("patch must be base64url-encoded JSON","invalid_patch")}try{return JSON.parse(t)}catch{throw new y("patch JSON is malformed","invalid_patch_json")}}function Ju(e,t){if(typeof e.flowId=="string"&&e.flowId)return e.flowId;let n=e.flow;if(n&&typeof n=="object"&&!Array.isArray(n)){let r=n.id;if(typeof r=="string"&&r)return r}return t}function ts(e){return typeof e.id=="string"&&e.id?e.id:typeof e.nodeId=="string"&&e.nodeId?e.nodeId:typeof e.node=="string"&&e.node?e.node:""}function Vu(e){if(e==null||e==="")return;let t;if(typeof e=="number")t=e;else if(typeof e=="string")t=Number(e);else if(typeof e=="boolean"||typeof e=="bigint")t=Number(e);else throw new y("occurrence must be a positive integer","invalid_occurrence");if(!Number.isFinite(t))throw new y("occurrence must be a positive integer","invalid_occurrence");return Math.trunc(t)}function Yu(e){let t=e.node;if(t!==void 0)return Wu(t),{op:"add",node:t};let n={...e};return delete n.op,{op:"add",node:n}}function Xu(e){let t=ts(e);if(!t)throw new y("replace_text op needs node id","missing_node_id");let n=typeof e.field=="string"?e.field:"";if(!n)throw new y("replace_text op needs field","missing_field");if(typeof e.find!="string")throw new y("replace_text op needs find string","missing_find");if(typeof e.replace!="string")throw new y("replace_text op needs replace string","invalid_replace");let r=Vu(e.occurrence),o={op:"replace_text",nodeId:t,field:n,find:e.find,replace:e.replace};return r!==void 0&&(o.occurrence=r),o}function Zu(e){let t=ts(e);if(!t)throw new y("Each patch.nodes entry needs id","missing_node_id");let n=e.set,r={};if(n&&typeof n=="object"&&!Array.isArray(n))Object.assign(r,n);else for(let[o,s]of Object.entries(e))o==="id"||o==="nodeId"||o==="node"||o==="set"||o==="op"||(r[o]=s);return{op:"set",nodeId:t,set:r}}function Qu(e){let t=typeof e.op=="string"?e.op.toLowerCase():"set";if(t==="add")return Yu(e);if(t==="remove"){let n=ts(e);if(!n)throw new y("remove op needs id","missing_node_id");return{op:"remove",nodeId:n}}if(t==="replace_text")return Xu(e);if(t!=="set")throw new y("nodes[].op must be 'add', 'remove', 'set', or 'replace_text'","invalid_patch");return Zu(e)}function Un(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw new y("patch must be a JSON object","invalid_patch");let n=e;if(n.type==="replace_flow"||n.flow&&typeof n.flow=="object"){let r=Ju(n,t);if(!r)throw new y("patch.replace_flow needs flowId","missing_flow_id");return{kind:"replace_flow",flowId:r,flow:n.flow}}if(Array.isArray(n.nodes)){let r=typeof n.flowId=="string"?n.flowId:t;if(!r)throw new y("patch.nodes needs flow id","missing_flow_id");let o=[];for(let i of n.nodes){if(!i||typeof i!="object"||Array.isArray(i))throw new y("Each patch.nodes entry must be an object","invalid_patch");o.push(Qu(i))}let s=o.filter(i=>i.op==="set").map(i=>({nodeId:i.nodeId,set:i.set}));return{kind:"nodes",flowId:r,ops:o,edits:s}}throw new y("patch must include nodes[] edits or type/flow for replace_flow","invalid_patch")}var Xi,Or=U(()=>{"use strict";J();qo();wt();Xi=new Set(["id","type","z"]);a(Zi,"describeNonObjectNode");a(Wu,"assertAddNodeObject");a(jn,"buildPatchedFlowTab");a(Qi,"findTextOccurrences");a(ea,"applyReplaceTextToValue");a(Gu,"collectOpShapeIssues");a(Ku,"touchedNodeIdsFromOps");a(vt,"applyFlowNodeOps");a(Ji,"requireNodeIndex");a(Vi,"applyOneNodeOp");a(Yi,"stringifyQueryValue");a(es,"collectSetProps");a(Hn,"decodeBase64UrlJson");a(Ju,"resolveReplaceFlowId");a(ts,"resolveNodeId");a(Vu,"parseOccurrence");a(Yu,"parseAddOp");a(Xu,"parseReplaceTextOp");a(Zu,"parseSetOp");a(Qu,"parseNodeOp");a(Un,"interpretPatch")});function Vt(e,t){return gt(e.createdBy)===t.id}function ns(e,t){return!t||t.length===0||t.includes(e)}function ep(e){return typeof e=="string"&&ra.has(e)}function Xt(e,t){let n=tp[e],r=e!=="status"&&Array.isArray(t);return new y(r?`Invalid ${e} filter: pass it at most once (multiple values are not supported). Allowed: ${n}`:`Invalid ${e} filter. Allowed: ${n}`,"invalid_filter",{details:{parameter:e,allowed:n,hint:`Pass ${e} as ${n}, or omit it.`}})}function $r(e,t){if(Array.isArray(t))throw Xt(e,t);return t}function np(e){return typeof e=="number"?Number.isInteger(e)?e:null:typeof e=="string"&&/^-?\d+$/.test(e.trim())?Number(e.trim()):null}function rs(e){return e==null||e===""}function rp(e){if(!rs(e)){if(e==="true"||e==="1"||e===!0)return!0;if(e==="false"||e==="0"||e===!1)return!1;throw Xt("mine",e)}}function os(e){if(rs(e))return;let t=(Array.isArray(e)?e:[e]).flatMap(r=>typeof r=="string"?r.split(","):[r]),n=[];for(let r of t){let o=typeof r=="string"?r.trim():r;if(o!==""){if(!ep(o))throw Xt("status",e);n.includes(o)||n.push(o)}}return n.length>0?n:void 0}function na(e,t,n,r){if(rs(t))return;let o=np(t);if(o===null||o<n||o>r)throw Xt(e,t);return o}function Mr(e){let t={},n=rp($r("mine",e.mine));n!==void 0&&(t.mine=n);let r=os(e.status);r&&(t.status=r);let o=$r("flowId",e.flowId??e.flow_id??e.flow);typeof o=="string"&&o.length>0&&(t.flowId=o);let s=na("limit",$r("limit",e.limit),1,Yt);s!==void 0&&(t.limit=s);let i=na("offset",$r("offset",e.offset),0,Number.MAX_SAFE_INTEGER);return i!==void 0&&(t.offset=i),t}function kt(e){let t=e.offset+e.returned,n=t<e.total;return{total:e.total,offset:e.offset,...e.limit!==null?{limit:e.limit}:{},hasMore:n,...n?{nextOffset:t}:{}}}function op(e,t){let n=e.length,r=t.offset!==void 0&&t.offset>0?t.offset:0;if(t.limit===void 0)return{items:r>0?e.slice(r):e,total:n,limit:null,offset:r};let o=Math.min(Math.max(1,t.limit),Yt);return{items:e.slice(r,r+o),total:n,limit:o,offset:r}}function Zt(e,t,n={},r={}){let o=r.mine===!0?t.id:void 0,s={};o&&(s.linkId=o),r.flowId&&(s.flowId=r.flowId);let d=e.list(s).filter(c=>r.mine!==!1||!Vt(c,t)).filter(c=>ns(c.status,r.status)).map(c=>({...ae(Jt(c),n),mine:Vt(c,t)})),l=op(d,r);return{proposals:l.items,total:l.total,limit:l.limit,offset:l.offset}}function ze(e,t,n,r={}){if(!n)throw new y("Missing proposal id","missing_proposal_id");let o=e.get(n);return{...ae(Jt(o),r),mine:Vt(o,t)}}function sp(e){let t={kind:"access-link",linkId:e.id};return typeof e.label=="string"&&e.label&&(t.label=e.label),t}function Qt(e,t,n,r,o,s={}){if(!r)throw new y("Missing proposal id","missing_proposal_id");let i;try{i=e.get(r)}catch(f){throw f instanceof se,f}if(!Vt(i,n))throw new Bt(r);if(i.status!=="pending")throw new ne(r,i.status);let d=typeof o=="string"&&o.trim()?o.replace(/\0/g,"").trim().slice(0,2e3):"Withdrawn by access link",l=sp(n),c=e.markWithdrawn(r,l,d),u=ae(Jt(c),s),p=u.flowLabel||void 0;return t.append("proposal.withdrawn",l,{proposalId:c.id,flowId:c.payload.flowId,title:u.title,displayName:u.displayName,reason:d,...p?{flowLabel:p}:{}}),{...u,mine:!0}}function ip(e,t,n,r={}){let o;try{o=e.get(t.id)}catch{return{}}if(r.liveRev){V(e,r.liveRev,{flowId:o.payload.flowId});try{o=e.get(t.id)}catch{return{}}}return o.status!=="pending"?{}:$e(Oe(e,o,n))}function ge(e,t,n,r={},o={}){let s="mine"in n?n:{...ae(n,r),mine:Vt(n,t)},i=0;if(o.liveRev)try{let c=e.get(s.id).payload.flowId;i=V(e,o.liveRev,{flowId:c});let u=e.get(s.id);s={...ae(u,r),mine:Vt(u,t)}}catch{}let d=he(Jt(s),r),l={...d,mine:s.mine,...Z(i)};return o.includeConflicts!==!1&&d.status==="pending"&&Object.assign(l,ip(e,d,t.id)),l}async function ye(e){let t=await e.getFlows();return{rev:t.rev,flows:t.flows}}async function _e(e,t){if(!t)throw new y("Missing flow id","missing_flow_id");let n=await e.getFlow(t);return{rev:n.rev,flow:qt(t,n.flow)}}function ap(e){if(typeof e.expectedRev=="string"&&e.expectedRev.length>0)return e.expectedRev;if(typeof e.expected_rev=="string"&&e.expected_rev.length>0)return e.expected_rev}function oa(e,t){if("nodes"in e&&e.nodes!==void 0&&!Array.isArray(e.nodes))throw new y("payload.nodes must be an array of node ops","invalid_nodes");if(t!==void 0&&!Array.isArray(t))throw new y("nodes must be an array of node ops","invalid_nodes")}async function dp(e,t,n,r,o){let{applyFlowNodeOps:s,interpretPatch:i}=await Promise.resolve().then(()=>(Or(),ta));oa(n,r);let d=Array.isArray(n.nodes)?n.nodes:Array.isArray(r)?r:null;if(!d)throw new y("payload.flow is required","missing_flow");let l=n.flowId,c=typeof l=="string"?l:"";if(!c)throw new y("payload.flowId is required","missing_flow_id");let u=i({nodes:d,flowId:c},c);if(u.kind!=="nodes")throw new y("payload.nodes must be node ops","invalid_patch");let p=await s(e,u.flowId,u.ops,{expectedRev:t,...o?{nodeTypes:o}:{}});if(p.expectedRev!==t)throw new K(t,p.expectedRev,"submit_proposal");return{flowId:u.flowId,flow:p.flow}}async function lp(e,t,n,r,o){let s=n.flowId,i=n.flow,d=!i||typeof i!="object"||Array.isArray(i);if(d&&oa(n,r),d&&(Array.isArray(n.nodes)||Array.isArray(r))){let l=await dp(e,t,n,r,o);s=l.flowId,i=l.flow}if(typeof s!="string"||s.length===0)throw new y("payload.flowId is required","missing_flow_id");if(!i||typeof i!="object"||Array.isArray(i))throw new y("payload.flow is required","missing_flow");return{flowId:s,flow:i}}function cp(e,t){try{return{safeExpectedRev:Fe(e.slice(0,200),"expectedRev"),safeFlowId:Fe(t.slice(0,200),"flowId")}}catch{throw new y("expectedRev and payload.flowId must be safe path segments","invalid_path_segment")}}function up(e,t){let n=Wi(ht(e),t);return n.unresolved.length>0&&qe(n.unresolved.map(r=>({code:"redacted_placeholder_mismatch",error:`Node ${r.nodeId} field ${r.property}: ${r.reason}. [REDACTED] placeholders are restored only when left exactly where they were shown (same node, field, env/header name, and unchanged line). Do not copy, move, or add placeholders; keep those lines unchanged or supply a real new value.`,nodeId:r.nodeId,property:r.property,statusCode:400}))),n}async function rt(e,t,n,r,o,s={},i){let d=r.type,l=ap(r),c=r.payload;if(d!=="replace_flow")throw new y("type must be 'replace_flow'","invalid_proposal_type");if(!l)throw new y("expectedRev is required (top-level field next to type and payload)","missing_expected_rev");if(!c||typeof c!="object"||Array.isArray(c))throw new y("payload must be an object","invalid_payload");let u=c,p=qi(o),f=await lp(bt(p),l,u,r.nodes,i),{safeExpectedRev:m,safeFlowId:h}=cp(l,f.flowId),g=f.flow,k=Buffer.byteLength(JSON.stringify(c),"utf8");if(k>H)throw new Y("proposal payload",k,H);qe(vi(g,h));let C=await p.getFlows();if(C.rev!==m)throw new K(m,C.rev,"submit_proposal");let L=up(g,C.flows),N=ht(L.flow);qe(bi(N,h,C.flows));let Ue=await p.getFlow(h);if(Ue.rev!==m)throw new K(m,Ue.rev,"submit_proposal");let Ce=ht(Ue.flow);i&&await gi(i,N);let{errors:S,warnings:b}=await ki(N,{flowId:h,baseFlow:Ce,...i?{nodeTypes:i}:{}});qe(S),b.unshift(...Ki(L.restoredFields,N,C.flows).map(le=>({...le})));let w={kind:"access-link",linkId:n.id};n.label!==void 0&&(w.label=n.label);let v=n.oauthClientId&&n.oauthUser?{kind:"oauth",clientId:n.oauthClientId,user:n.oauthUser}:w,A=Er(r.title)??Go(Ce,N),E=e.create({type:"replace_flow",expectedRev:m,payload:{flowId:h,flow:N},baseFlow:Ce,...L.restoredFields.length>0?{restoredSecrets:L.restoredFields}:{},title:A,createdBy:v,...b.length>0?{warnings:b}:{}}),$=ae(Jt(E),s),F=$.flowLabel||void 0;return t.append("proposal.created",w,{proposalId:E.id,flowId:h,expectedRev:m,title:$.title,displayName:$.displayName,...F?{flowLabel:F}:{},...b.length>0?{warningCount:b.length}:{},...L.restored>0?{restoredRedactions:L.restored}:{}}),$}function M(e,t="rest"){return Bn(e,t)}var Yt,ra,tp,y,J=U(()=>{"use strict";xe();Ar();Ze();yr();G();De();qo();wt();tt();Re();nt();a(Vt,"isMineProposal");Yt=200,ra=new Set(["pending","approved","rejected","failed","withdrawn","expired","superseded"]);a(ns,"matchesStatusFilter");a(ep,"isProposalStatus");tp={status:[...ra].join(", "),limit:`integer 1\u2013${Yt}`,offset:"integer \u2265 0",mine:"true, false, 1, 0",flowId:"one flow tab id"};a(Xt,"invalidFilterError");a($r,"singleQueryValue");a(np,"parseIntegerFilter");a(rs,"isUnset");a(rp,"parseMineFilter");a(os,"parseStatusFilter");a(na,"parseRangeFilter");a(Mr,"parseListProposalsQuery");a(kt,"listPageFields");a(op,"applyListPagination");a(Zt,"listAccessibleProposals");a(ze,"getAccessibleProposal");a(sp,"accessLinkActor");a(Qt,"withdrawMyProposal");a(ip,"conflictsForProposal");a(ge,"presentAccessibleProposalForApi");y=class extends Error{static{a(this,"AiOpsValidationError")}statusCode;code;details;constructor(t,n="validation_error",r={}){super(t),this.name="AiOpsValidationError",this.code=n,this.statusCode=r.statusCode??400,r.details&&(this.details=r.details)}};a(ye,"listFlows");a(_e,"getFlow");a(ap,"resolveExpectedRev");a(oa,"assertNodesArrayOrAbsent");a(dp,"materializePayloadFromNodes");a(lp,"resolveReplaceFlowPayload");a(cp,"assertSafeRevAndFlowId");a(up,"strictRestoreOrThrow");a(rt,"submitReplaceFlowProposal");a(M,"aiOpsErrorToHttp")});function ss(e){return e==="get_link"?86400:2592e3}function is(e,t){return e==="get_link"?`get_link ttlSeconds must be \u2264 ${t} (24 hours)`:`ttlSeconds must be \u2264 ${t} (30 days) for ${e} tokens`}var At,qn=U(()=>{"use strict";a(ss,"maxTtlSecondsForKind");a(is,"ttlCapErrorMessage");At="ttlSeconds must be a positive number of seconds within the cap for the token kind: get_link \u2264 86400 (24h); rest and mcp \u2264 2592000 (30 days)."});function x(e,t,n,r,o=oe,s="error"){return{code:e,status:t,meaning:n,hint:r,channels:o,kind:s}}function zn(e){return fp.get(e)?.hint}function sa(e){return e.replace(/\\/g,"\\\\").replace(/\|/g,"\\|")}function ia(){let e=a(s=>`| \`${s.code}\` | ${s.status} | ${sa(s.meaning)} | ${sa(s.hint)} | ${s.channels.join(", ")} |`,"row"),t=["| Code | HTTP | Meaning | Next step | Channels |","| --- | --- | --- | --- | --- |"],n=Dr.filter(s=>s.kind==="error"),r=Dr.filter(s=>s.kind==="warning"),o=Dr.filter(s=>s.kind==="status");return[...t,...n.map(e),"","**Warnings** (in `warnings[]` on a successful propose; never blocking):","",...t,...r.map(e),"","**Auto-closed proposal statuses.** Pending proposals are closed automatically: `expired` when the live flow rev moves past their `expectedRev`, `superseded` when another proposal on the same flow is approved. Submit, get, and list responses include `autoClosedCount` when that request auto-closed proposals (omitted when zero).","",...t,...o.map(e)].join(`
10
- `)}var oe,en,as,pp,tn,Dr,fp,jr=U(()=>{"use strict";qn();oe=["GET","REST","MCP"],en=["GET"],as=["REST","MCP"],pp=["GET","REST"],tn=["admin"];a(x,"e");Dr=[x("access_link_missing",401,"No Bearer / path token","Ask the user for a new access link of the right kind"),x("bearer_scheme_required",401,"Auth used Basic (or non-Bearer)","Send `Authorization: Bearer <token>`",as),x("token_expired",401,"Known token past `expiresAt` (body includes `expiredAt` and `accessLinksUrl`)","Ask the user to create a new link on the `/ai-access` dashboard \u2192 Access tokens (`accessLinksUrl`)"),x("access_link_invalid",401,"Unknown, revoked, or cleared after restart","Ask the user for a new access link"),x("wrong_token_kind",403,"Token kind does not match this surface","Mint the matching kind on `/ai-access` \u2192 Access tokens; show `tellUser` to the human"),x("ai_features_disabled",403,"Plugin AI features off","Owner enables AI Access / `adminAuth`"),x("auth_required",401,"Admin session required","Sign in to the Node-RED editor",tn),x("insufficient_permissions",403,"Admin lacks required scope","Need `*` for link mint / approve",tn),x("invalid_link_kind",400,"Unknown `kind` on create","Use `get_link`, `rest`, or `mcp`",tn),x("invalid_ttl",400,"`ttlSeconds` is zero, negative, not a number, or over the cap for the kind",At,tn),x("access_link_limit",409,"Too many active links","Revoke an existing link first",tn),x("access_link_not_found",404,"Unknown link id on revoke","List links and use an existing id",tn),x("link_create_not_supported",403,"Attempted mint under a GET link","Admin mints via `POST /mcp/api/v1/links` only",en),x("rate_limited",429,"Per-IP or per-token limit","Wait `retryAfter` / `Retry-After`"),x("payload_too_large",413,"Body / proposal over ~1.5MB","Shrink content"),x("invalid_json",400,"Malformed JSON body","Fix Content-Type / body",as),x("unsupported_media_type",415,"POST body is not `application/json`","Send `Content-Type: application/json`",as),x("url_too_long",414,"GET URL over soft cap (~8KB)","Use chunked drafts or REST/MCP",en),x("not_found",404,"Unknown path or resource","Re-read docs / discovery"),x("missing_query",400,"Required search `q` missing","Pass `?q=`",pp),x("invalid_filter",400,"Invalid list filter (`status`, `limit`, `offset`, `mine`); body names `parameter` and `allowed`","Use an allowed value: status one of the proposal statuses; limit integer 1\u2013200; offset integer \u2265 0; mine true|false"),x("invalid_arguments",400,"MCP tool arguments failed schema validation; body names `parameter`","Fix the named argument to match the tool input schema (see tools/list)",["MCP"]),x("invalid_diff_format",400,"Unknown `format` on proposal diff","Use `format=summary` (default) or `format=full`"),x("confirm_required",400,"GET withdraw without `confirm=1`","Repeat the request with `confirm=1`",en),x("missing_expected_rev",400,"Proposal submitted without `expectedRev` / `rev`","Read the flow and pass its `rev`"),x("version_mismatch",409,"Stale `rev` / `expectedRev`","Re-read flows and resubmit"),x("proposal_not_found",404,"Unknown proposal id","List proposals and use an existing id"),x("proposal_not_pending",409,"Action needs a pending proposal","Only pending can withdraw / decide"),x("proposal_not_owned",403,"Withdraw of another token\u2019s proposal","Only the creating token can withdraw"),x("validation_failed",400,"Several validation errors at once (message `N validation errors`)","Fix every entry in errors[] and resubmit."),x("duplicate_node_id",400,"The same node id appears more than once in a full tab or batch","Give every node a unique id"),x("missing_node_id",400,"Node / op without an id","Include a non-empty string id"),x("missing_node_type",400,"Node without a `type`","Set `type` to an installed palette type name"),x("invalid_node_type",400,"`type` is not a string","Use the palette type name exactly"),x("invalid_nodes",400,"`flow.nodes` / `flow.configs` not an array","Send arrays (use `[]` to clear)"),x("invalid_node",400,"Node entry is not an object","Send `{ id, type, \u2026 }` objects"),x("invalid_wires",400,"Bad wires shape, missing target, or more ports than outputs","Wire only to existing node ids / ports"),x("z_mismatch",400,"Node `z` differs from the flow id","Set `z` to the tab id or omit it"),x("node_already_exists",400,"`add` op for an id already in the flow","Use `set` or a new id"),x("protected_property",400,"Tried to change id / type / z","Omit id, type, and z from edits"),x("node_not_found",404,"Unknown node (single error only; several errors \u2192 `validation_failed`)","Re-read the flow"),x("field_not_found",404,"Unknown field on a node","Re-read the node"),x("text_not_found",400,"`replace_text` find had no match","Re-read field; find is exact"),x("ambiguous_match",409,"`replace_text` matched more than once","Pass `occurrence` (1-based) or unique find"),x("unknown_node_type",400,"Proposal uses a type that is not installed","List node-types / palette search; ask user to install"),x("node_type_not_found",404,"Unknown type on node-types detail","List types or search palette"),x("chunk_conflict",409,"Same draft seq re-sent with different data","Re-send identical data or start a new draft",en),x("missing_chunks",400,"Draft submit with gaps in seq","Upload every seq from 0 through max",en),x("draft_hash_mismatch",400,"Draft sha256 does not match assembled bytes","Send the hex sha256 of the assembled UTF-8",en),x("missing_config_node",201,"Warning: node has no (or an unresolved) config node yet","Configure before deploy if needed; see `candidates`",oe,"warning"),x("unwired_node",201,"Warning: node has outputs but no wires","Wire it, or ignore while scaffolding",oe,"warning"),x("incomplete_wires",201,"Warning: fewer wire ports than outputs","Add ports if outputs should connect",oe,"warning"),x("redacted_placeholder_mismatch",400,"A `[REDACTED]` placeholder was added, duplicated, moved, or its env/header name changed","Keep placeholders exactly where they were shown (same line, same name) or supply a real value",oe),x("secret_destination_changed",201,"Warning: a node holding a restored secret changed where it sends data (url/host/method/headers/\u2026)","Reviewer must verify the new destination before approving",oe,"warning"),x("secret_node_code_changed",201,"Warning: a node holding a restored secret had its code changed","Reviewer must check the code does not send the secret elsewhere",oe,"warning"),x("secret_wires_changed",201,"Warning: a node holding a restored secret had its output wires changed","Reviewer must check where its messages now go",oe,"warning"),x("secret_scope_changed",201,"Warning: tab/group env or a config node scoping a secret-holding node changed","Reviewer must check env-resolved hosts/URLs (e.g. `${API_HOST}`)",oe,"warning"),x("outbound_node_changed",201,"Warning: proposal adds/changes an outbound-capable node (http request/response, mqtt, tcp, udp, email, websocket, link out/call, function, exec, file, or any node with url/host fields)","Reviewer must check it cannot send secret data elsewhere",oe,"warning"),x("secret_tab_changed",201,"Warning: proposal with restored secrets also changes other nodes in the tab","Review every change before approving",oe,"warning"),x("secret_reference_added",201,"Warning: an added/changed node now reads a secret-named env/global/flow value (env.get, $env, ${\u2026}, typed input)","Reviewer must check the value is not sent anywhere unexpected",oe,"warning"),x("expired",200,"Status: pending proposal auto-closed because the live flow rev moved past `expectedRev`","Re-read the flow and submit a new proposal",oe,"status"),x("superseded",200,"Status: pending proposal auto-closed because another proposal on the same flow was approved","Re-read the flow and submit a new proposal if still needed",oe,"status"),x("upstream_error",502,"Node-RED runtime/flows error","Retry; check Node-RED logs"),x("internal_error",500,"Unexpected server error","Retry; check Node-RED logs")],fp=new Map(Dr.map(e=>[e.code,e]));a(zn,"registryHint");a(sa,"cell");a(ia,"renderGetLinkErrorTable")});function de(e,t,n={}){return ds(e,t,n)}function mp(e,t){return t==="excess_ports"?e==="mcp"?"This node has more wire ports than its type declares \u2014 remove extra ports or raise the node outputs, then retry.":e==="rest"?"This node has more wire ports than its type declares \u2014 trim wires[] or raise outputs, then retry.":"This node has more wire ports than its type declares \u2014 trim wires or raise outputs, then retry.":t==="missing_target"?e==="mcp"?"Wire targets must be node ids in this flow \u2014 add the node in the same patch or use get_flow ids.":e==="rest"?"Wire targets must be node ids in this flow \u2014 add the node in the same patch or use GET /flows/{id} ids.":"wires must list existing node ids (or add those nodes in the same patch).":e==="mcp"||e==="rest"?"Fix wires: targets must exist in this flow, and port count must not exceed the node outputs.":"wires must list existing node ids (or add those nodes in the same patch)."}function hp(e){return e==="mcp"?"Call list_node_types or search_palette. Ask the user to install via Manage palette, then recheck. This API never installs.":e==="rest"?"Call GET /node-types?q= or GET /palette/search?q=. Ask the user to install via Manage palette, then recheck. This API never installs.":"Call GET \u2026/node-types?q= or \u2026/palette/search?q=. Ask the user to install via Manage palette, then recheck. This API never installs."}function ot(e,t,n,r){return e==="mcp"?t:e==="rest"?n:r}function ds(e,t,n={}){switch(e){case"version_mismatch":return ot(t,"Re-read with list_flows/get_flow and retry with the current rev.","Re-read the flow and retry with the current rev.","Re-read the flow and retry with the current rev.");case"node_not_found":return ot(t,"Call get_flow or get_node and use a node id from that response.","Call GET /flows/{id} and use a node id from that response.","Call GET \u2026/flows/<id> and use a node id from that response.");case"flow_not_found":return ot(t,"Call list_flows to list valid ids.","Call GET /flows to list valid ids.","Use an id from GET ./flows.");case"proposal_not_found":return ot(t,"Call list_proposals and use an id from that response.","List proposals and use an id from that response.","List proposals and use an id from that response.");case"proposal_not_owned":return"Only the access link that created a pending proposal can withdraw it.";case"proposal_not_pending":return"Only pending proposals can be withdrawn or decided.";case"confirm_required":return"Add confirm=1 to the withdraw URL (required so link prefetchers cannot withdraw).";case"invalid_wires":return mp(t,n.cause);case"node_type_not_found":case"unknown_node_type":return hp(t);case"missing_node_type":return'Each add op needs node.type as a non-empty string (e.g. "inject").';case"invalid_line_range":return ot(t,'Pass lines as "A" or "A-B" (1-based inclusive). Reversed ranges (end < start) are invalid.',"Use lines=A or lines=A-B (1-based inclusive). Reversed ranges (end < start) are invalid.","Use lines=A or lines=A-B (1-based inclusive). Reversed ranges (end < start) are invalid.");case"line_range_out_of_bounds":return"start is past the end of the field \u2014 check totalLines and request a lower lines range.";case"ambiguous_match":return"find matched more than once \u2014 pass occurrence=N (1-based) or a more unique find string.";case"missing_expected_rev":return ot(t,"Pass expectedRev from list_flows/get_flow (top-level, next to type and payload).","Pass expectedRev from GET /flows or GET /flows/{id} (top-level, next to type and payload).","Pass rev from GET \u2026/flows or \u2026/flows/<id>.");case"missing_query":return ot(t,"Pass a non-empty q string.","q query parameter is required.","q query parameter is required.");case"unsupported_media_type":return"Send Content-Type: application/json with a JSON body.";case"text_not_found":return"Re-read the node field; find must match exactly (case-sensitive).";case"field_not_found":return"Re-read the node; field must be an existing string property (template, func, \u2026).";case"field_not_string":return ot(t,"This tool only returns string fields (template, func, \u2026). Use get_node for other property types.","This endpoint only returns string fields (template, func, \u2026). Use GET /nodes/{id} for other property types.","This endpoint only returns string fields (template, func, \u2026). Use GET /nodes/{id} for other property types.");default:return}}function Bn(e,t,n={}){let r=n.canonicalizeMessage!==!1,o=null;if(e instanceof y?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,...e.details??{}}}:e instanceof K?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,expectedRev:e.expectedRev,currentRev:e.currentRev}}:e instanceof W?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,flowId:e.flowId}}:e instanceof se||e instanceof Bt||e instanceof ne||e instanceof Y?o={statusCode:e.statusCode,body:{error:e.message,code:e.code}}:(e instanceof Ne||e instanceof Se)&&(o={statusCode:e.statusCode,body:{error:e.message,code:e.code}}),!o)return null;let s=o.body.code;if(r&&!Array.isArray(o.body.errors)){let i=je[s];i&&(o.body.error=i)}if(typeof o.body.hint!="string"||!o.body.hint){let i=ds(s,t,{cause:o.body.cause})??zn(s);i&&(o.body.hint=i)}return o}function aa(e,t,n={}){let o={error:n.error??je[t]??"Request failed",code:t,...n.details??{}},s=ds(t,e,{cause:o.cause})??zn(t);return s&&(o.hint=s),{statusCode:n.statusCode??400,body:o}}var je,De=U(()=>{"use strict";xe();Ze();G();J();jr();je={node_not_found:"Node was not found",flow_not_found:"Flow was not found",field_not_found:"Field was not found",field_not_string:"Field is not a string",missing_query:"q query parameter is required",missing_flow_id:"Missing flow id",missing_node_id:"Missing node id",missing_field:"Missing field name",invalid_line_range:"lines must be A or A-B (1-based inclusive; end >= start)",line_range_out_of_bounds:"lines range is out of bounds",proposal_not_found:"Proposal was not found",proposal_not_owned:"Proposal was not created by this access link",proposal_not_pending:"Proposal is not pending",confirm_required:"Withdraw over GET requires confirm=1",unsupported_media_type:"Content-Type must be application/json",upstream_error:"Upstream request failed",internal_error:"Internal server error"};a(de,"channelErrorHint");a(mp,"invalidWiresHint");a(hp,"nodeTypeHint");a(ot,"pickChannel");a(ds,"hintFor");a(Bn,"mapErrorForChannel");a(aa,"buildChannelError")});var og={};Os(og,{ACCESS_LINK_INVALID_MESSAGE:()=>ut,ACCESS_LINK_MISSING_MESSAGE:()=>me,AccessLinkStore:()=>Ct,EDITOR_SIDEBAR_ASSET_VERSION:()=>on,MAX_PROPOSAL_JSON_BYTES:()=>H,SettingsStore:()=>Mt,acceptedMediaTypes:()=>wl,areAiFeaturesEnabled:()=>ke,bindSettingsStore:()=>cr,createPluginServices:()=>bo,default:()=>rg,getStorageDir:()=>co,isAdminAuthEnabled:()=>z,isMcpStreamRequest:()=>bl,packageVersion:()=>rn,registerRoutes:()=>vl,renderEditorSidebarJs:()=>Hr,renderEditorSidebarLoaderHtml:()=>ua,requireAccessLink:()=>Ut,resolveAiAccessSettings:()=>ve});module.exports=Rl(og);var yl=require("fs");var tr=require("fs"),bn=require("path");G();function nr(e){let t=(0,bn.join)(e,".ai-access");return{root:t,proposals:(0,bn.join)(t,"proposals"),logs:(0,bn.join)(t,"logs"),settings:(0,bn.join)(t,"config.json")}}a(nr,"resolveAiAccessPaths");function Ao(e){(0,tr.mkdirSync)(e,{recursive:!0,mode:ko});try{(0,tr.chmodSync)(e,ko)}catch{}}a(Ao,"mkdirSecure");function Hs(e){Ao(e.root),Ao(e.proposals),Ao(e.logs)}a(Hs,"ensureAiAccessDirs");pt();Le();function mr(e){let t=pe(e);return`auth-tokens${(t==="/"?"":t).replace(/\//g,"-")}`}a(mr,"editorAuthTokenKey");Le();De();function gp(e){if(!e)return!1;let t=e.split(";")[0]?.trim().toLowerCase()??"";return t==="application/json"||t.endsWith("+json")}a(gp,"isJsonContentType");function yp(e){let t=e.headers["content-length"];if(typeof t=="string"&&t.length>0){let n=Number(t);if(Number.isFinite(n)&&n>0)return!0;if(n===0)return!1}return e.headers["transfer-encoding"]!==void 0}a(yp,"hasRequestBody");function We(e="rest"){return(t,n,r)=>{let o=t.headers["content-type"];if(gp(o)){r();return}if(!o&&!yp(t)){r();return}let s=aa(e,"unsupported_media_type",{statusCode:415});n.status(s.statusCode).json(s.body)}}a(We,"requireJsonContentType");var wp="text/plain; charset=utf-8",bp="text/html; charset=utf-8",vp="default-src 'none'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'";function kp(e){let t=e.query?.format;return typeof t=="string"?t.toLowerCase():void 0}a(kp,"formatParam");function Ap(e){let t=kp(e);if(t==="md"||t==="json")return"native";if(!e.get("accept"))return"html";let n=e.accepts(["text/html","application/json","text/markdown","text/plain"]);return n==="application/json"||n==="text/markdown"?"native":n==="text/plain"?"plain":"html"}a(Ap,"chooseGetLinkRepresentation");function da(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;")}a(da,"escapeHtml");var Sp=/(?<![A-Za-z0-9_.~\-/])\.\/[A-Za-z0-9._~\-/]+(?:\?[A-Za-z0-9._~\-=&%]+)?/g;function xp(e){return e.replace(Sp,(t,n)=>{if(e.startsWith("&lt;",n+t.length))return t;let r=t.replace(/[.]+$/,""),o=t.slice(r.length);return r.length<=2||r.includes("..")||r.includes("//")||r.includes("&")?t:`<a href="${r}">${r}</a>${o}`})}a(xp,"linkifyEscaped");function Tp(e,t){return`<!doctype html>
11
- <html><head><meta charset="utf-8">${t?`<base href="${da(t)}">`:""}<title>Node-RED AI Access</title></head><body><pre>${xp(da(e))}</pre></body></html>
12
- `}a(Tp,"renderGetLinkHtml");function Rp(e){let t=e.split("?")[0]??"",n=/^(.*?\/mcp\/a\/[A-Za-z0-9_-]+)(?:\/|$)/.exec(t);if(!(!n?.[1]||!/^[A-Za-z0-9._~\-/]*$/.test(n[1])))return`${n[1]}/`}a(Rp,"getLinkBaseHref");function _p(e){try{return JSON.stringify(JSON.parse(e),null,2)}catch{return e}}a(_p,"prettyJson");function la(){return(e,t,n)=>{let r=Ap(e),o=Rp(e.originalUrl);t.vary("Accept"),t.setHeader("Cache-Control","no-store"),t.setHeader("Referrer-Policy","no-referrer"),t.setHeader("X-Robots-Tag","noindex, nofollow"),t.setHeader("X-Content-Type-Options","nosniff");let s=t.send.bind(t);t.send=(i=>{let d=String(t.getHeader("Content-Type")??"").toLowerCase(),l=d.startsWith("application/json"),c=d.startsWith("text/markdown");if(r==="native"||!l&&!c||t.headersSent)return s(i);let u=typeof i=="string"?i:Buffer.isBuffer(i)?i.toString("utf8"):void 0;return u===void 0?s(i):r==="plain"?(t.setHeader("Content-Type",wp),s(u)):(t.setHeader("Content-Type",bp),t.setHeader("Content-Security-Policy",vp),s(Tp(l?_p(u):u,o)))}),n()}}a(la,"getLinkContentNegotiation");rr();Le();function Pp(e){let t=fe(e.settings.httpAdminRoot),n=Ve(e.settings.httpAdminRoot),r="0.2.0-beta.6",o=[{method:"GET",path:"/",auth:"none",summary:"This discovery index"},{method:"GET",path:"/openapi.yaml",auth:"none",summary:"OpenAPI 3.1 specification (YAML)"},{method:"GET",path:"/openapi.json",auth:"none",summary:"OpenAPI 3.1 specification (JSON)"},{method:"GET",path:"/status",auth:"admin-or-access-link",summary:"Plugin status and caller info"},{method:"GET",path:"/flows",auth:"admin-or-access-link",summary:"List flows + rev"},{method:"GET",path:"/flows/{id}",auth:"admin-or-access-link",summary:"Get one flow tab + rev (?summary=1 for node list without field contents)"},{method:"GET",path:"/nodes/{id}",auth:"admin-or-access-link",summary:"Get one full node (optional ?flow=)"},{method:"GET",path:"/nodes/{id}/field/{name}",auth:"admin-or-access-link",summary:"Get one node field (?lines=A-B optional)"},{method:"GET",path:"/search",auth:"admin-or-access-link",summary:"Text search across string fields (?q=&flow=)"},{method:"GET",path:"/node-types",auth:"admin-or-access-link",summary:"List installed palette node types (?q= filter)"},{method:"GET",path:"/node-types/{type}",auth:"admin-or-access-link",summary:"Detail for one installed node type (defaults JSON, inputs/outputs, help; ?help=0 omits help)"},{method:"GET",path:"/palette/search",auth:"admin-or-access-link",summary:"Search Node-RED catalogue (recommend only; never installs) (?q=)"},{method:"POST",path:"/proposals",auth:"access-link",summary:"Create a replace_flow proposal"},{method:"GET",path:"/proposals",auth:"admin-or-access-link",summary:"List proposals (admin: all; link: own)"},{method:"GET",path:"/proposals/{id}",auth:"admin-or-access-link",summary:"Get one proposal"},{method:"GET",path:"/proposals/{id}/diff",auth:"admin-or-access-link",summary:"Diff proposal vs current flow"}];return{ok:!0,name:"node-red-ai-access",version:r,description:"Temporary, owner-approved Node-RED access for AI clients over plain HTTP (REST) or MCP.",auth:{scheme:"Bearer",header:"Authorization: Bearer <token>",description:"An admin with * mints tokens on the /ai-access dashboard (Access tokens tab) (POST /links only \u2014 never under GET /mcp/a/<token>). Each token has kind get_link | rest | mcp and only works on that surface. Send rest/mcp tokens as Authorization: Bearer <token>. Missing/invalid \u2192 401 access_link_missing / access_link_invalid; wrong kind \u2192 403 wrong_token_kind. GET /mcp/a links cannot create tokens.",tokenKinds:["get_link","rest","mcp"],mint:{path:"/links",method:"POST",auth:"admin",note:"Admin * only. Not available under /mcp/a/<token>."}},openapi:`${t}/openapi.yaml`,openapiJson:`${t}/openapi.json`,mcp:n,apiBase:t,getLink:{base:"/mcp/a/<token>",canMintTokens:!1,note:"Self-contained GET surface for get_link tokens only. No POST /links equivalent."},endpoints:o}}a(Pp,"buildDiscoveryIndex");function ls(e){return(t,n)=>{n.status(200).json(Pp(e))}}a(ls,"discoveryIndexHandler");function ca(){return(e,t)=>{t.status(404).json({error:"Not found",code:"not_found"})}}a(ca,"mcpApiNotFoundHandler");var nn;function rn(){return nn||(nn="0.2.0-beta.6",nn)}a(rn,"packageVersion");var on=rn();function Ep(){return`#red-ui-sidebar-ai-access {
9
+ `);n.push({before:i>=0?o.slice(i+1):o,after:d>=0?s.slice(0,d):s})}return n}function vo(e){return JSON.stringify([e.before,e.after])}function wo(e){let t=new Map;return hr(e.text).forEach((n,r)=>{let o=vo(n),s=t.get(o)??[];s.push(r),t.set(o,s)}),t}function nd(e,t,n,r){if(e===t)return{text:n,restored:r.filter(f=>f.kind==="secret").length};let o=hr(e),s=hr(t);if(o.length>s.length)return{error:`contains ${o.length} [REDACTED] placeholders but the original had ${s.length}`};let i=wo({text:t,occurrences:r}),d=new Set([...i].filter(([,f])=>new Set(f.map(m=>r[m]?.kind)).size>1).map(([f])=>f)),l=new Map,c=e.split(J),u=c[0]??"",p=0;for(let f=0;f<o.length;f++){let m=o[f];if(!m)break;let h=vo(m),g=i.get(h)??[];if(d.has(h))return{error:"ambiguous literal and secret [REDACTED] occurrences"};let y=l.get(h)??0,w=g[y]??-1;if(w<0)return{error:"a [REDACTED] placeholder is not on an unchanged original line"};l.set(h,y+1);let v=r[w];v?.kind==="secret"&&p++,u+=(v?.value??J)+(c[f+1]??"")}return{text:u,restored:p}}function Bt(e){if(ee(e))for(let t of lf){let n=e[t];if(typeof n=="string"&&n.length>0)return`${t}=${n}`}}function yr(e,t){let n=a(s=>[s,...s.nodes??[],...s.configs??[]],"entities"),r=new Map(n(e).map(s=>[s.id,s])),o=[];for(let s of n(t)){let i=r.get(s.id);if(JSON.stringify(i?.env)===JSON.stringify(s.env))continue;let d=a(l=>{let c=new Set;return Array.isArray(l)?l.some(u=>!ee(u)||typeof u.name!="string"||!u.name?!1:c.has(u.name)?!0:(c.add(u.name),!1)):!1},"hasDuplicates");!d(i?.env)&&!d(s.env)||o.push({code:"duplicate_env_keys",...s.id?{nodeId:s.id}:{},property:"env",error:"Duplicate env names make pairing by name ambiguous when entries are reordered. Review the env values before approval."})}return o}function ri(e,t,n,r=[]){let o=a(c=>{let u=new Map;return Qs(c,{matcher:gr(kt()),count:0,fields:u}),u},"fieldsFor"),s=o(e),i=o(t),d=o(n),l=a((c,u,p,f,m=f,h=f)=>{if(typeof u=="string"){let g=s.get(`${e.id}\0${f}`);if(!g?.occurrences.some(E=>E.kind==="secret"))return u;let y=i.get(`${t.id}\0${m}`),w=d.get(`${n.id}\0${h}`),v=hr(g.text),L=wo(g),P=y?wo(y):new Map,I=w?wo(w):new Map,D=y?.occurrences.map(E=>E.value)??[],T=y?hr(y.text):[],x=y?.occurrences.some((E,A)=>{let S=T[A];return E.kind==="secret"&&S&&!L.has(vo(S))}),C=!1;return g.occurrences.forEach((E,A)=>{if(E.kind!=="secret")return;let S=v[A],N=S?vo(S):"",b=L.get(N)??[],O=P.get(N)??[],M=I.get(N)??[];if(b.length>1&&O.length>0&&O.length<b.length&&JSON.stringify(b.map(Kn=>g.occurrences[Kn]?.value))!==JSON.stringify(M.map(Kn=>w?.occurrences[Kn]?.value)))throw new mr;let F=a((Kn,eu)=>{let Zi=Kn.get(N)??[],[gs]=Zi;return Zi.length===1&&gs!==void 0&&eu?.occurrences[gs]?.kind==="secret"?gs:void 0},"matches"),le=F(L,g),cn=F(P,y),Ft=F(I,w);if(!P.has(N)&&!x)return;if(le===void 0||cn===void 0||Ft===void 0){C=!0;return}let Xi=w?.occurrences[Ft];y?.occurrences[cn]?.value===E.value&&Xi&&(D[cn]=Xi.value)}),C&&r.push({code:"secret_may_revert",nodeId:e.id,property:m,error:"Secret spans could not be aligned reliably. The proposed value was kept and may revert an editor secret change. Review this field before approval."}),!y||C?u:y.text.split(J).reduce((E,A,S)=>E+(S?D[S-1]??J:"")+A,"")}if(Array.isArray(u)){let g=Array.isArray(c)?c:[],y=Array.isArray(p)?p:[],w=new Set,v=new Set;return u.map((L,P)=>{let I=Bt(L),D=a((C,E)=>{let A=I===void 0?P:C.findIndex((S,N)=>!E.has(N)&&Bt(S)===I);return E.add(A),A},"match"),T=D(g,w),x=D(y,v);return l(g[T],L,y[x],`${f}[${T}]`,`${m}[${P}]`,`${h}[${x}]`)})}if(ee(u)){let g=ee(c)?c:{},y=ee(p)?p:{},w=a((v,L)=>v?`${v}.${L}`:L,"path");return Object.fromEntries(Object.entries(u).map(([v,L])=>[v,l(g[v],L,y[v],w(f,v),w(m,v),w(h,v))]).filter(([,v])=>v!==void 0))}return u},"walk");return l(e,t,n,"")}function rd(e,t){let n=Zs(t,[]);return"error"in nd(e,t,t,n)}function bo(e,t,n,r,o,s){if(typeof e=="string"){if(!e.includes(J))return e;let i=s.fields.get(`${r}\0${o}`);if(typeof t=="string"&&typeof n=="string"&&(i||n.includes(J))){let d=nd(e,t,n,i?.occurrences??Zs(n,[]));return"error"in d?(s.unresolved.push({nodeId:r,property:o,reason:d.error}),e):(d.restored>0&&(s.restored+=d.restored,s.restoredFields.push({nodeId:r,property:o})),d.text)}return s.unresolved.push({nodeId:r,property:o,reason:"no redacted secret exists at this node id and field"}),e}if(Array.isArray(e)){let i=Array.isArray(t)?t:[],d=Array.isArray(n)?n:[],l=new Set;return e.map(c=>{let u=ee(c)&&c.redacted===!0?Xs(c,"redacted"):c,p=Bt(u),f=-1;if(p!==void 0){let m=d.map((h,g)=>Bt(h)===p&&!l.has(g)?g:-1).filter(h=>h>=0);m.length>0&&(f=m[0]??-1)}return f>=0?(l.add(f),bo(u,i[f],d[f],r,`${o}[${f}]`,s)):bo(u,void 0,void 0,r,`${o}[?]`,s)})}if(ee(e)){let i=ee(t)?t:{},d=ee(n)?n:{},l={};for(let[c,u]of Object.entries(e))l[c]=bo(u,i[c],d[c],r,o?`${o}.${c}`:c,s);return l}return e}function Xs(e,t){let n={...e};return delete n[t],n}function cf(e){let t=Xs(e,pr);return Array.isArray(t.env)&&(t.env=t.env.map(n=>!ee(n)||n.redacted!==!0?n:Xs(n,"redacted"))),t}function od(e,t,n,r){let o=typeof e.id=="string"?e.id:"",s=o?t.get(o):void 0,i={},d=s?Qs({...s,id:o},r):void 0;for(let[l,c]of Object.entries(e))if(l!==pr){if(ed.has(l)&&Array.isArray(c)){i[l]=c.map(u=>ee(u)?od(u,t,n,r):u);continue}if(Xa.has(l)){i[l]=c;continue}i[l]=bo(c,d?.[l],s?.[l],o,l,n)}return cf(i)}function sd(e,t,n=kt()){let r=new Map;for(let l of t)ee(l)&&typeof l.id=="string"&&r.set(l.id,l);let o=new Map,s={matcher:gr(n),count:0,fields:o},i={fields:o,restored:0,restoredFields:[],unresolved:[]};return{flow:od(e,r,i,s),restored:i.restored,restoredFields:i.restoredFields,unresolved:i.unresolved}}function hf(e){return typeof e.type=="string"&&pf.has(e.type)?!0:ff.some(t=>typeof e[t]=="string"&&e[t]!=="")}function Ya(e){let t=new Set,n=a((r,o)=>{if(!(o>12)){if(typeof r=="string"){if(r.length>Qa)return;let s=/(?:\b(?:env|global|flow)\.get|\$env)\([ \t]{0,8}["'`]([^"'`\r\n]{1,128})["'`]|\$\{([A-Za-z_][\w.-]{0,127})\}/g,i;for(;(i=s.exec(r))!==null;)t.add(i[1]??i[2]??"");return}if(Array.isArray(r)){for(let s of r)n(s,o+1);return}if(ee(r))for(let[s,i]of Object.entries(r))[`${s}Type`,s==="v"?"vt":"",s==="to"?"tot":"",s==="value"?"type":"",s==="valueValue"?"valueType":"",s==="keyValue"?"keyType":""].some(c=>c&&typeof r[c]=="string"&&mf.has(r[c]))&&typeof i=="string"&&t.add(i),n(i,o+1)}},"walk");return n(e,0),t.delete(""),t}function Sn(e,t){return JSON.stringify(e)===JSON.stringify(t)}function ad(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]);for(let r of n)if(!(id.has(r)||r==="nodes"||r==="configs")&&!Sn(e[r],t[r]))return!0;return!1}function gf(e,t,n){let r=new Set([n]),o=typeof e.g=="string"?e.g:void 0;for(let s=0;o&&s<32&&!r.has(o);s++){r.add(o);let i=t.get(o);o=i&&typeof i.g=="string"?i.g:void 0}for(let s of Object.values(e))typeof s=="string"&&s!==e.id&&t.get(s)?.z===void 0&&t.has(s)&&r.add(s);for(let[s,i]of t)i.type==="global-config"&&r.add(s);return[...r]}function yf(e,t){return e.filter(n=>n.nodeId===t).map(n=>n.property).join(", ")}function wf(e,t,n,r){let o=[],s=uf.filter(d=>!Sn(n[d],r[d]));s.length>0&&o.push({code:"secret_destination_changed",error:`SECURITY: node ${e} holds a secret (${t}) restored from the live flow, and this proposal changes where it sends data (${s.join(", ")}). Verify the new destination before approving.`,nodeId:e,property:s.join(",")});let i=[...Za].filter(d=>!Sn(n[d],r[d]));return i.length>0&&o.push({code:"secret_node_code_changed",error:`Node ${e} holds a secret (${t}) and its code changed (${i.join(", ")}). Check the new code does not send the secret anywhere unexpected.`,nodeId:e,property:i.join(",")}),Sn(n.wires,r.wires)||o.push({code:"secret_wires_changed",error:`SECURITY: node ${e} holds a secret (${t}) and its output wires changed. Check where its messages (which may carry the secret) now go.`,nodeId:e,property:"wires"}),o}function bf(e,t,n,r,o,s){let i=[];for(let d of gf(t,n,o)){if(d===e||s.has(d))continue;let l=n.get(d),c=d===o,u=r.get(d)??(l&&!c&&l.z===void 0?l:void 0);if(!(c?!Sn(l?.env,u?.env):l!==void 0&&u!==void 0&&ad(l,u)))continue;s.add(d);let f=c?"tab":typeof l?.type=="string"?l.type:"node";i.push({code:"secret_scope_changed",error:`SECURITY: ${f} ${d} scopes or configures secret-holding node ${e} (env vars like \${HOST} or a config node) and it changed. Check URLs/hosts it resolves to.`,nodeId:d,property:c?"env":"config"})}return i}function vf(e,t,n,r,o){let s=[],i=[];for(let[d,l]of n){if(d===r)continue;let c=t.get(d);if(c&&!ad(c,l))continue;e.includes(d)||i.push(d);let u=typeof l.type=="string"?l.type:"node";hf(l)&&!e.includes(d)&&s.push({code:"outbound_node_changed",error:`SECURITY: this proposal ${c?"changes":"adds"} outbound-capable node ${d} (${u}). Check where it sends data before approving.`,nodeId:d,property:[...new Set([...Object.keys(c??{}),...Object.keys(l)])].filter(m=>!id.has(m)&&m!=="nodes"&&m!=="configs"&&!Sn(c?.[m],l[m])).sort().join(",")});let p=c?Ya(c):new Set,f=[...Ya(l)].filter(m=>!p.has(m)&&o.matches(m));f.length>0&&s.push({code:"secret_reference_added",error:`SECURITY: node ${d} (${u}) now reads secret-named value(s) ${f.slice(0,5).join(", ")} (env / global / flow). The AI cannot see these values but the flow will use them at runtime; check they are not sent anywhere unexpected.`,nodeId:d,property:f.slice(0,5).join(",")})}if(e.length===0)return s;for(let[d,l]of t)l.z===r&&!n.has(d)&&i.push(d);if(i.length>0){let d=i.slice(0,10).join(", ")+(i.length>10?", \u2026":"");s.push({code:"secret_tab_changed",error:`This proposal restores secrets in this tab and also changes ${i.length} other node(s) (${d}). Review every change: any of them could route secret data elsewhere.`,nodeId:r,property:"nodes"})}return s}function kf(e,t){let n=new Map;n.set(t,e);for(let r of["nodes","configs"]){let o=e[r];if(Array.isArray(o))for(let s of o)ee(s)&&typeof s.id=="string"&&n.set(s.id,s)}return n}function dd(e,t,n,r=kt()){let o=[...new Set(e.map(u=>u.nodeId))],s=String(t.id),i=new Map;for(let u of n)ee(u)&&typeof u.id=="string"&&i.set(u.id,u);let d=kf(t,s),l=[],c=new Set;for(let u of o){let p=i.get(u),f=d.get(u);!p||!f||(l.push(...wf(u,yf(e,u),p,f)),l.push(...bf(u,p,i,d,s,c)))}return l.push(...vf(o,i,d,s,gr(r))),l}var pr,Jp,Vp,Vs,Xa,Yp,Za,Qa,ed,Wa,Ga,yo,Ka,Qp,Ja,Ys,ni,Va,lf,mr,uf,pf,ff,mf,id,Pe=q(()=>{"use strict";pn();ke();pn();pr="_aiRedactions",Jp=new Set(["TYPE","TYPES","MODE","METHOD","FIELD","FIELDS","PROP","PROPERTY","FORMAT","TIMEOUT","EXPIRY","EXPIRES","TTL","LENGTH","LEN","SIZE","COUNT","ENABLED","DISABLED","ENABLE","USE","URL","URI","ENDPOINT","HOST","PORT","PATH","HEADER","NAME","LABEL","FILE"]),Vp=new Set(["basic","digest","bearer","none","true","false","null","undefined","oauth","oauth2","jwt","apikey","api_key","token","password","header","query","body","cookie","msg","flow","global","env","str","num","json","bool","other"]),Vs=new Set(["env","msg","flow","global","jsonata","bool","date","node","cred"]),Xa=new Set(["id","type","z","g","x","y","w","h","wires","inputs","outputs","links","scope","icon","color","style","d","l","in","out","rev"]),Yp=new Set(["name","label","info"]),Za=new Set(["func","initialize","finalize"]),Qa=256*1024,ed=new Set(["nodes","configs","subflows"]),Wa=["name","p","keyValue","key","property","k"],Ga={value:"type",v:"vt",to:"tot",valueValue:"valueType"},yo=new Map;a(gr,"createSecretNameMatcher");a(kt,"currentSecretNamePatterns");a(fr,"isSecretLikeValue");Ka=new WeakMap;a(Xp,"contentRules");a(Zp,"buildContentRules");Qp=["PASSWORD","PASSWD","PASSPHRASE","SECRET","CLIENT_SECRET","TOKEN","API_KEY","APIKEY","PRIVATE","CREDENTIAL","PWD","PW"];a(ef,"shannonEntropy");a(tf,"looksRandom");a(nf,"unquotedRule");a(rf,"decodeURIComponentSafe");a(of,"parseConnSegment");a(sf,"connectionStringSpans");Ja=new WeakMap;a(af,"scanTextForSecrets");a(Zs,"classifyPlaceholders");a(ee,"isPlainObject");a(df,"isFlowEntity");Ys=class{constructor(t,n){this.ctx=t;this.entityId=n}static{a(this,"EntityRedactor")}paths=[];record(t,n){this.paths.includes(t)||this.paths.push(t),this.ctx.count+=n.length||1,this.ctx.secrets?.set(`${this.entityId}\0${t}`,n)}string(t,n,r){if(r&&fr(t)||t.length>Qa)return this.record(n,[t]),this.ctx.fields?.set(`${this.entityId}\0${n}`,{text:J,occurrences:[{kind:"secret",value:t}]}),J;let o=n.split(/[.[]/,1)[0]??"",s=af(t,this.ctx.matcher,{code:Za.has(o)});return this.ctx.fields&&s.text.includes(J)&&this.ctx.fields.set(`${this.entityId}\0${n}`,{text:s.text,occurrences:Zs(t,s.spans)}),s.secrets.length>0?(this.record(n,s.secrets),s.text):t}env(t,n){return t.map((r,o)=>{if(!ee(r))return r;let s=typeof r.type=="string"?r.type:"";if(s==="cred"||typeof r.value!="string"||Vs.has(s))return r;let i=typeof r.name=="string"?r.name:"",d=r.value,l=this.string(d,`${n}[${o}].value`,this.ctx.matcher.matches(i));return l===d?r:{...r,value:l,redacted:!0}})}value(t,n,r,o){if(typeof t=="string"){if(Yp.has(r))return this.string(t,n,!1);let s=o?.[`${r}Type`],i=typeof s=="string"&&Vs.has(s);return this.string(t,n,!i&&this.ctx.matcher.matches(r))}return Array.isArray(t)?r==="env"?this.env(t,n):t.map((s,i)=>this.value(s,`${n}[${i}]`,"",void 0)):ee(t)?this.object(t,n):t}object(t,n){let r=!1;for(let s of Wa){let i=t[s];if(typeof i=="string"&&this.ctx.matcher.matches(i)){r=!0;break}}let o={};for(let[s,i]of Object.entries(t)){let d=n?`${n}.${s}`:s;if(r&&s in Ga&&typeof i=="string"){let l=Ga[s],c=l?t[l]:void 0,u=typeof c=="string"&&Vs.has(c);o[s]=this.string(i,d,!u);continue}if(Wa.includes(s)&&typeof i=="string"){o[s]=this.string(i,d,!1);continue}o[s]=this.value(i,d,s,t)}return o}};a(Qs,"redactEntity");a(ei,"redactValueInner");a(ct,"redactFlowData");a(ti,"redactConflictLabels");a(Ye,"redactProposalForAi");ni=Symbol.for("ai-access.rawFlowAdapter"),Va=new WeakMap;a(At,"aiRedactingAdapter");a(td,"isRedactingAdapter");a($e,"rawFlowAdapter");a(Xe,"adapterForRequest");a(ko,"countRedactionPlaceholders");a(hr,"placeholderContexts");a(vo,"contextKey");a(wo,"indexOccurrences");a(nd,"restoreString");lf=["name","keyValue","p","property","key","k"];a(Bt,"stableElementKey");a(yr,"duplicateEnvWarnings");mr=class extends Error{static{a(this,"SecretAlignmentConflictError")}constructor(){super("A duplicate secret line was removed while its live values changed. Resolve this field against the live flow."),this.name="SecretAlignmentConflictError"}};a(ri,"preserveLiveSecrets");a(rd,"hasNewLiteralPlaceholder");a(bo,"restoreWalk");a(Xs,"withoutKey");a(cf,"stripMarkers");a(od,"restoreEntity");a(sd,"restoreRedactedPlaceholders");uf=["type","url","host","hostname","server","broker","port","path","method","headers","endpoint","uri","protocol","proxy","tls","to","ret"],pf=new Set(["http request","http response","link out","link call","mqtt out","mqtt-broker","tcp out","tcp request","udp out","e-mail","websocket out","websocket-client","websocket-listener","function","exec","file"]),ff=["url","host","hostname","server","broker","endpoint","uri"];a(hf,"isOutboundNode");mf=new Set(["env","global","flow"]);a(Ya,"referencedNames");id=new Set(["x","y","w","h","l"]);a(Sn,"sameJson");a(ad,"nodeChanged");a(gf,"scopeIds");a(yf,"restoredFieldList");a(wf,"secretNodeWarnings");a(bf,"scopeWarnings");a(vf,"tabWarnings");a(kf,"proposedNodeMap");a(dd,"secretChangeWarnings")});function qt(e){if(e===null||typeof e!="object")return JSON.stringify(e)??"undefined";if(Array.isArray(e))return`[${e.map(qt).join(",")}]`;let t=e;return`{${Object.keys(t).filter(r=>t[r]!==void 0).sort().map(r=>`${JSON.stringify(r)}:${qt(t[r])}`).join(",")}}`}function Tf(e){return(0,ld.createHash)("sha256").update(qt(e)).digest("hex").slice(0,16)}function ye(e){return Array.isArray(e)?e.filter(t=>t&&typeof t=="object"&&typeof t.id=="string"):[]}function Rn(e){let t=new Map;for(let n of ye(e.nodes))t.set(n.id,{node:n,config:!1});for(let n of ye(e.configs))t.set(n.id,{node:n,config:!0});return t}function Ze(e){let t={};for(let n of ai)e[n]!==void 0&&(t[n]=e[n]);return t}function Ao(e,t){let n=new Set([...Object.keys(e??{}),...Object.keys(t??{})]),r=[];for(let o of n)ud.has(o)||qt(e?.[o])!==qt(t?.[o])&&r.push(o);return r.sort()}function oi(e){if(e===void 0)return new Map;if(!Array.isArray(e))return;let t=new Map;for(let n of e){if(!Tn(n)||typeof n.name!="string"||!n.name||t.has(n.name))return;t.set(n.name,n)}return t}function Tn(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}function pd(e,t){let n=[];return wr(e,t,e,void 0,n),n.sort()}function wr(e,t,n,r,o){let s=[],i=a((f,m)=>qt(f)===qt(m),"same"),d=new Set([e.type,t.type,n.type].includes("function")?Af:[]);d.size&&(!i(e.outputs,t.outputs)||!i(e.outputs,n.outputs))&&d.add("wires");let l=a(f=>[...d].some(m=>!i(e[m],f[m])),"changedInGroup"),c=l(t)&&l(n),u=a((f,m,h,g)=>{if(i(f,m))return h;if(g==="env"){let y=oi(f),w=oi(m),v=oi(h);if(y&&w&&v){let L=[];for(let P of new Set([...w.keys(),...v.keys(),...y.keys()])){let I=u(y.get(P),w.get(P),v.get(P),`env[${JSON.stringify(P)}]`);I!==void 0&&L.push(I)}return L}}if((g==="credentials"||g.startsWith("credentials.")||g.startsWith("env["))&&Tn(m)&&(Tn(h)||h===void 0&&f===void 0)&&(f===void 0||Tn(f))){let y=Tn(f)?f:{};return p(y,m,Tn(h)?h:{},g)}return o?.push(g),i(f,h)||xf.has(g)?m:(s.push(g),r==="live"?h:m)},"merge"),p=a((f,m,h,g)=>{let y=[];for(let w of new Set([...Object.keys(f),...Object.keys(m),...Object.keys(h)])){if(!g&&ud.has(w))continue;let v=g?`${g}.${w}`:w,L;!g&&c&&d.has(w)?((!i(f[w],m[w])||!i(f[w],h[w]))&&s.push(w),i(f[w],m[w])||o?.push(w),L=r==="live"?h[w]:m[w]):L=u(f[w],m[w],h[w],v),L!==void 0&&y.push([w,L])}return Object.fromEntries(y)},"mergeObject");return{value:p(e,t,n,""),conflicts:s.sort()}}function xt(e){if(!e)return{};let t={};return typeof e.type=="string"&&(t.nodeType=e.type),typeof e.name=="string"&&e.name&&(t.nodeName=e.name.slice(0,120)),t}function fd(e,t){let n=e.find(r=>r.id===t&&(r.type==="tab"||r.type==="subflow"));if(n)return{tab:n,nodes:e.filter(r=>r.id!==t&&r.z===t)}}function zt(e,t,n){let r=fd(e,t);if(!r)return;let o=new Set(ye(n?.configs).map(c=>c.id)),s=new Set(ye(n?.nodes).map(c=>c.id)),i=[],d=[];for(let c of r.nodes)((o.has(c.id)?!0:s.has(c.id)?!1:c.type!=="group"&&c.x===void 0&&c.y===void 0&&c.wires===void 0)?d:i).push(c);let l={id:t,type:r.tab.type,...Ze(r.tab),nodes:i};return d.length>0&&(l.configs=d),l}function xo(e,t){if(typeof e=="string")return e.includes(J)&&(typeof t!="string"||rd(e,t));if(Array.isArray(e)){let n=Array.isArray(t)?t:[],r=new Set;return e.some((o,s)=>{let i=Bt(o),d=i===void 0?s:n.findIndex((l,c)=>!r.has(c)&&Bt(l)===i);return r.add(d),xo(o,n[d])})}if(e&&typeof e=="object"){let n=t&&typeof t=="object"?t:{};return Object.entries(e).some(([r,o])=>xo(o,n[r]))}return!1}function hd(e){let{flowId:t,proposed:n,liveFlows:r,liveRev:o}=e,s=[],i=a(b=>{s.length<Sf&&s.push(b)},"push"),d=fd(r,t);if(!d)return i({kind:"tab_deleted",nodeId:t,message:"The flow tab this proposal edits was deleted in the editor.",choices:[]}),{state:"conflict",rev:o,conflicts:s,liveChangedIds:[t]};let l=e.base??(e.baseRev===o?zt(r,t,n):void 0);if(l===void 0)return i({kind:"base_unavailable",nodeId:t,message:"This proposal was stored before base snapshots existed and the flows changed since, so live edits cannot be told apart from the proposal. Ask the AI to resubmit, or apply the proposal over the live tab.",choices:["proposed"]}),{state:"conflict",rev:o,conflicts:s,liveChangedIds:[]};let c=zt(r,t,n),u=Rn(l),p=Rn(c),f=Rn(n),m=[],h=Ze(l),g=Ze(d.tab),y=Ze(n),w=Ao(h,g),v=wr(h,y,g),L=v.value;w.length>0&&m.push(t),v.conflicts.length>0&&i({kind:"tab_changed_both",nodeId:t,fields:v.conflicts,message:"Both the editor and the proposal changed the same flow tab fields.",choices:["live","proposed"]}),L={...L};for(let b of ai)g[b]!==void 0&&L[b]===void 0&&(L[b]=b==="env"?[]:cd[b]);let P=new Map,I=new Set([...u.keys(),...p.keys(),...f.keys()]),D=new Map;for(let b of r)b.id!==t&&b.z!==t&&D.set(b.id,b);for(let b of I){let O=u.get(b),M=p.get(b),F=f.get(b),le=O&&M?Ao(O.node,M.node):[],cn=O&&F?Ao(O.node,F.node):[];if((O&&(!M||le.length>0)||!O&&M)&&m.push(b),F&&O&&D.has(b))i({kind:"id_collision",nodeId:b,...xt(F.node),message:"This node id now exists outside this flow tab.",choices:["live"]});else if(O&&M&&F){let Ft=wr(O.node,F.node,M.node);Ft.conflicts.length===0?P.set(b,{...F,node:Ft.value}):i({kind:"node_changed_both",nodeId:b,...xt(M.node),fields:Ft.conflicts,message:"Both the editor and the proposal changed the same node fields.",choices:["live","proposed"]})}else O&&M&&!F?le.length===0?P.set(b,null):i({kind:"node_deleted_in_proposal_modified_live",nodeId:b,...xt(M.node),fields:le,message:"The proposal deletes this node, but it was changed in the editor.",choices:["live","proposed"]}):O&&!M&&F?cn.length===0?P.set(b,null):i({kind:"node_deleted_live",nodeId:b,...xt(F.node),fields:cn,message:"This node was deleted in the editor, but the proposal changes it.",choices:["live","proposed"]}):O&&!M&&!F?P.set(b,null):!O&&M&&F?i({kind:"id_collision",nodeId:b,...xt(F.node),fields:Ao(M.node,F.node),message:"The proposal adds a node whose id was also added in the editor.",choices:["live","proposed"]}):!O&&M&&!F?P.set(b,M):!O&&!M&&F&&(D.get(b)??(b===t?d.tab:void 0)?i({kind:"id_collision",nodeId:b,...xt(F.node),message:"The proposal adds a node whose id now exists on another flow tab.",choices:["live"]}):P.set(b,F))}if(s.length>0)return{state:"conflict",rev:o,conflicts:s,liveChangedIds:si(m)};let T=[],x=[],C=new Set,E=a(b=>{if(C.has(b))return;let O=P.get(b);C.add(b),O&&(O.config?x:T).push(md(O.node))},"emit");for(let b of ye(n.nodes))E(b.id);for(let b of ye(n.configs))E(b.id);for(let b of ye(c.nodes))E(b.id);for(let b of ye(c.configs))E(b.id);let A={id:t,type:d.tab.type,...L,nodes:T};(x.length>0||Array.isArray(n.configs))&&(A.configs=x);let S=new Set([...T,...x].map(b=>b.id)),N=new Set(r.filter(b=>b.id!==t&&b.z!==t).map(b=>b.id));for(let b of[...T,...x]){let O=[];if(Array.isArray(b.wires)){for(let F of b.wires)if(Array.isArray(F))for(let le of F)typeof le=="string"&&!S.has(le)&&O.push(le)}if(typeof b.g=="string"&&b.g.length>0&&!S.has(b.g)&&O.push(b.g),b.type==="group"&&Array.isArray(b.nodes))for(let F of b.nodes)typeof F=="string"&&!S.has(F)&&O.push(F);if(Array.isArray(b.links))for(let F of b.links)typeof F=="string"&&!S.has(F)&&!N.has(F)&&O.push(F);let M=[...new Set(O)].sort();M.length>0&&i({kind:"dangling_reference",nodeId:b.id,...xt(b),fields:M,message:"After merging, this node points to a node that no longer exists (deleted in the editor).",choices:["live"]}),xo(b,p.get(b.id)?.node)&&i({kind:"redacted_placeholder",nodeId:b.id,...xt(b),message:"A [REDACTED] placeholder would be deployed over a real value. Ask the AI to resubmit.",choices:[]})}if(d.tab.type==="subflow"){let b=new Set;for(let O of[d.tab.in,d.tab.out])if(Array.isArray(O)){for(let M of O)if(Array.isArray(M?.wires))for(let F of M.wires)typeof F?.id=="string"&&!S.has(F.id)&&b.add(F.id)}b.size>0&&i({kind:"dangling_reference",nodeId:t,fields:[...b].sort(),message:"A subflow port points to a node that would no longer exist.",choices:[]})}return xo(L,g)&&i({kind:"redacted_placeholder",nodeId:t,message:"A [REDACTED] placeholder would be deployed into the tab settings. Ask the AI to resubmit.",choices:[]}),s.length>0?{state:"conflict",rev:o,conflicts:s,liveChangedIds:si(m)}:Rf(A,n,o,[],si(m))}function Rf(e,t,n,r,o){let s=br(e);return{state:s===br(t)?"clean":"merged",rev:n,conflicts:r,merged:e,mergedHash:s,liveChangedIds:o}}function br(e){return Tf(Pf(e))}function Pf(e){let t=[...ye(e.nodes),...ye(e.configs)].map(md).sort((n,r)=>n.id<r.id?-1:n.id>r.id?1:0);return{...cd,...Ze(e),nodes:t}}function md(e){if(!("z"in e))return e;let t={...e};return delete t.z,t}function si(e){return[...new Set(e)]}function gd(e){let{flowId:t,nodeId:n,choice:r,liveFlows:o}=e,s=[],i=zt(o,t,e.proposed);if(!i)throw new Error("flow tab deleted");if(e.base===void 0){if(r!=="proposed")throw new Error("invalid choice");return{base:St(i),proposed:St(e.proposed),warnings:s}}let d=St(e.base),l=St(e.proposed);if(r==="proposed"&&(s.push(...yr(d,l).filter(m=>m.nodeId===n)),s.push(...yr(i,l).filter(m=>m.nodeId===n&&!s.some(h=>h.nodeId===m.nodeId)))),n===t){let m=Ze(i),h=r==="proposed"?ri({id:t,type:i.type??"tab",...Ze(d)},{id:t,type:i.type??"tab",...Ze(l)},{id:t,type:i.type??"tab",...m},s):Ze(l),g=wr(Ze(d),h,m,r).value;for(let y of ai)delete d[y],m[y]!==void 0&&(d[y]=St(m[y])),delete l[y],g[y]!==void 0&&(l[y]=St(g[y]));return{base:d,proposed:l,warnings:s}}let u=Rn(i).get(n),p=Rn(d).get(n),f=Rn(l).get(n);if(p&&f&&u){let m=r==="proposed"?ri(p.node,f.node,u.node,s):f.node;ii(l,n,{...f,node:wr(p.node,m,u.node,r).value})}else r==="live"&&ii(l,n,u?{...u,node:St(u.node)}:void 0);return ii(d,n,u?{...u,node:St(u.node)}:void 0),{base:d,proposed:l,warnings:s}}function ii(e,t,n){let r=ye(e.nodes),o=ye(e.configs),s=o.some(l=>l.id===t),i=r.some(l=>l.id===t);if(e.nodes=r.filter(l=>l.id!==t),Array.isArray(e.configs)&&(e.configs=o.filter(l=>l.id!==t)),!n)return;s||!i&&n.config?e.configs=[...ye(e.configs),n.node]:e.nodes=[...e.nodes,n.node]}function St(e){return e===void 0?e:JSON.parse(JSON.stringify(e))}var ld,ai,Af,xf,cd,ud,Sf,Pn=q(()=>{"use strict";ld=require("crypto");pn();Pe();ai=["label","disabled","info","env"],Af=["func","libs","initialize","finalize","outputs"],xf=new Set(["x","y"]),cd={label:"",disabled:!1,info:"",env:[]},ud=new Set(["z"]),Sf=200;a(qt,"stableStringify");a(Tf,"hashTab");a(ye,"asNodes");a(Rn,"mapTab");a(Ze,"tabProps");a(Ao,"changedFields");a(oi,"envMap");a(Tn,"isObject");a(pd,"changedFieldPaths");a(wr,"mergeFields");a(xt,"nodeLabel");a(fd,"liveTabFromFlows");a(zt,"nestedLiveTab");a(xo,"containsNewPlaceholder");a(hd,"rebaseFlowTab");a(Rf,"finish");a(br,"hashFlowTab");a(Pf,"normalizeForHash");a(md,"withoutZ");a(si,"dedupe");a(gd,"applyConflictChoice");a(ii,"setNode");a(St,"clone")});function yd(e){let t=new Map;for(let n of e.nodes??[])t.set(n.id,n);for(let n of e.configs??[])t.set(n.id,n);return t}function wd(e,t){return pd(e,t).filter(n=>!_f.has(n))}function bd(e){let t=yd(e.baseFlow??{}),n=yd(e.payload.flow),r=new Map;for(let[d,l]of n){let c=t.get(d);if(!c){r.set(d,new Set(["*"]));continue}let u=wd(c,l);u.length>0&&r.set(d,new Set(u))}for(let[d]of t)n.has(d)||r.set(d,new Set(["*"]));let o=Object.fromEntries(["label","disabled","info","env"].map(d=>[d,e.baseFlow?.[d]])),s=Object.fromEntries(["label","disabled","info","env"].map(d=>[d,e.payload.flow[d]])),i=wd(o,s);return i.length>0&&r.set(e.payload.flowId,new Set(i)),r}function Cf(e,t){let n=[],r=[];for(let[o,s]of e){let i=t.get(o);if(!i)continue;let d=[];for(let c of s){if(c==="*"){d.push(...[...i].filter(u=>u!=="*")),i.has("*")&&d.push("*");continue}(i.has("*")||[...i].some(u=>u===c||u.startsWith(`${c}.`)||c.startsWith(`${u}.`)||u.startsWith(`${c}[`)||c.startsWith(`${u}[`)))&&d.push(c)}let l=[...new Set(d)].sort();l.length>0&&(n.push(o),r.push({nodeId:o,fields:l}))}return n.sort(),{nodeIds:n,fields:r}}function Qe(e,t,n){let r=bd(t),o=[];for(let s of e.list()){if(s.id===t.id||!U(s.status)||s.payload.flowId!==t.payload.flowId)continue;let i=Js(s),d=n?dt(s.createdBy)===n:dt(s.createdBy)===dt(t.createdBy),l=Cf(r,bd(s));if(l.nodeIds.length===0)continue;let c={id:s.id,title:i,displayName:Ks(i,s.createdAt),mine:d};l.nodeIds.length>0&&(c.overlappingNodeIds=l.nodeIds),l.fields.length>0&&(c.overlappingFields=l.fields),o.push(c)}return{conflictingProposals:o}}function et(e){let t={};return e.conflictingProposals.length>0&&(t.conflictingProposals=e.conflictingProposals,t.warning="Other pending proposals touch the same flow \u2014 approve order may conflict (like a git merge)."),t}function pe(e){return e>0?{autoClosedCount:e}:{}}var _f,Wt=q(()=>{"use strict";Ve();Oe();Pn();_f=new Set(["x","y","id","z"]);a(yd,"collectNodes");a(wd,"changedFieldKeys");a(bd,"computeProposalTouches");a(Cf,"overlapBetween");a(Qe,"findConflictingProposals");a(et,"conflictWarningFields");a(pe,"autoClosedFields")});function xd(e,t){Ad=e,So=t??(()=>Date.now())}function Sd(){let e=Ad();return(Number.isFinite(e)&&e>=1?e:vd)*kd}function Ef(e){return`Proposal expired: it stayed open longer than the proposal TTL (${e} day${e===1?"":"s"}). Ask the AI to resubmit if still needed.`}function To(e,t=So()){let n=Date.parse(e.createdAt);return Number.isFinite(n)&&t-n>Sd()}function te(e,t={}){let n=So(),r=Math.round(Sd()/kd),o=0;for(let s of e.list())if(U(s.status)&&!(t.flowId&&s.payload.flowId!==t.flowId)&&!e.isApproving(s.id)&&To(s,n))try{e.markClosed(s.id,"expired",Ef(r)),o+=1}catch{}return o}function di(e,t){let n=hd({flowId:e.payload.flowId,base:e.baseFlow,proposed:e.payload.flow,liveFlows:t.flows,liveRev:t.rev,baseRev:e.expectedRev});return n.state==="clean"&&e.aiConflictOverrides?.length&&(n.state="merged"),n}function li(e,t=So()){let n={rev:e.rev,state:e.state,conflicts:e.conflicts.map(r=>({...r,choices:[...r.choices]})),liveChangedCount:e.liveChangedIds.length,checkedAt:new Date(t).toISOString()};return e.mergedHash&&(n.mergedHash=e.mergedHash),n}function Me(e,t,n,r={}){let o=di(t,n);return{proposal:e.isApproving(t.id)&&!r.duringApprove?e.get(t.id):e.recordRebase(t.id,li(o)),result:o}}async function vr(e,t,n={}){let r=te(e,n.flowId?{flowId:n.flowId}:{}),o=n.live;if(!o&&t)try{o=await $e(t).getFlows()}catch{}if(!o)return{expired:r,conflicts:0,checked:0};let s=De(e,o,n);return{...s,expired:r+s.expired}}function De(e,t,n={}){let r={expired:0,conflicts:0,checked:0};for(let o of e.list())if(U(o.status)&&!(n.flowId&&o.payload.flowId!==n.flowId)&&!e.isApproving(o.id))try{if(To(o)){r.expired+=te(e,{flowId:o.payload.flowId});continue}let{proposal:s}=Me(e,o,t);r.checked+=1,s.status==="conflict"&&(r.conflicts+=1)}catch{}return r}function Td(e,t=500){let n,r,o=!1,s=a(async()=>{if(r)return o=!0,r;r=(async()=>{do{o=!1;try{await e()}catch{}}while(o)})();try{await r}finally{r=void 0}},"exec");return{trigger(){n&&clearTimeout(n),n=setTimeout(()=>{n=void 0,s()},t),n.unref?.()},async flush(){n&&(clearTimeout(n),n=void 0),await s()},cancel(){n&&clearTimeout(n),n=void 0}}}async function tt(e){if(e)try{return await $e(e).getFlows()}catch{return}}var vd,kd,Ad,So,nt=q(()=>{"use strict";Ve();Pn();Pe();vd=7,kd=864e5,Ad=a(()=>vd,"ttlDaysProvider"),So=a(()=>Date.now(),"nowProvider");a(xd,"configureProposalTtl");a(Sd,"proposalTtlMs");a(Ef,"ttlExpiredReason");a(To,"isPastTtl");a(te,"expireByTtl");a(di,"rebaseProposal");a(li,"rebaseInfoFrom");a(Me,"syncOneProposal");a(vr,"syncOpenProposals");a(De,"rebaseOpenProposalsWith");a(Td,"createDebouncedSync");a(tt,"tryRawLive")});var Nd={};Qi(Nd,{applyFlowNodeOps:()=>Gt,applyReplaceTextToValue:()=>Ld,buildPatchedFlowTab:()=>kr,collectSetProps:()=>ci,decodeBase64UrlJson:()=>Ar,findTextOccurrences:()=>Id,interpretPatch:()=>xr});function Ed(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function If(e){if(typeof e!="object"||e===null||Array.isArray(e))throw new k(`add op needs node as an object (got ${Ed(e)})`,"invalid_node")}async function kr(e,t,n){return Gt(e,t,n.map(r=>({op:"set",nodeId:r.nodeId,set:r.set})))}function Id(e,t){if(!t)return[];let n=[],r=0;for(;r<=e.length;){let o=e.indexOf(t,r);if(o<0)break;n.push(o),r=o+t.length}return n}function Ld(e,t,n,r,o){if(Cd.has(t))throw new k(`Cannot change protected node property '${t}' via replace_text`,"protected_property");if(typeof n!="string"||n.length===0)throw new k("find must be a non-empty string","missing_find");if(typeof r!="string")throw new k("replace must be a string","invalid_replace");if(e===void 0)throw new k(`Field '${t}' was not found on the node`,"field_not_found");if(typeof e!="string")throw new k(`Field '${t}' is not a string (got ${e===null?"null":typeof e})`,"field_not_string");let s=Id(e,n);if(s.length===0)throw new k(`Text not found in field '${t}'`,"text_not_found",{details:{matchCount:0}});let i=o;if(i===void 0){if(s.length>1)throw new k(`Found ${s.length} matches in '${t}'; pass occurrence (1-based) to pick one`,"ambiguous_match",{statusCode:409,details:{matchCount:s.length}});i=1}if(!Number.isInteger(i)||i<1||i>s.length)throw new k(`occurrence must be an integer from 1 to ${s.length}`,"invalid_occurrence",{details:{matchCount:s.length}});let d=s[i-1];if(d===void 0)throw new k(`occurrence must be an integer from 1 to ${s.length}`,"invalid_occurrence",{details:{matchCount:s.length}});return e.slice(0,d)+r+e.slice(d+n.length)}function Lf(e){let t=[];for(let n of e)if(n.op==="add"){let r=n.node;if(typeof r!="object"||r===null||Array.isArray(r)){t.push({code:"invalid_node",error:`add op needs node as an object (got ${Ed(r)})`});continue}let o=r;(!o.id||typeof o.id!="string")&&t.push({code:"missing_node_id",error:"add op needs node.id"});try{bt(o.type,"node.type")}catch(s){s instanceof k&&t.push({code:s.code,error:s.message,nodeId:o.id})}"wires"in o&&kn(o.wires,t,typeof o.id=="string"?o.id:void 0)}else n.op==="set"&&typeof n.set=="object"&&"wires"in n.set&&kn(n.set.wires,t,n.nodeId);return t}function Nf(e){let t=new Set;for(let n of e)n.op==="add"&&typeof n.node.id=="string"?t.add(n.node.id):n.op!=="add"&&typeof n.nodeId=="string"&&t.add(n.nodeId);return t}async function Gt(e,t,n,r={}){if(!t)throw new k("flow id is required","missing_flow_id");if(!n.length)throw new k("At least one node edit is required","missing_edits");let o=Lf(n);o.push(...Hs([n.flatMap(h=>h.op==="add"?[h.node]:[])],"the batch (add ops)")),lt(o);let s=await e.getFlow(t);if(r.expectedRev!==void 0&&s.rev!==r.expectedRev){let h=Array.isArray(s.flow.nodes)?[...s.flow.nodes]:[];return{expectedRev:s.rev,flow:{...s.flow,id:s.flow.id,type:s.flow.type??"tab",nodes:h}}}let i=Array.isArray(s.flow.nodes)?[...s.flow.nodes]:[],d=Array.isArray(s.flow.configs)?[...s.flow.configs]:[],l={...s.flow,id:s.flow.id,type:s.flow.type??"tab",nodes:i,configs:d},c=i.map(h=>({...h})),u=[];for(let h of n)try{h.op==="add"&&r.nodeTypes&&await $a(r.nodeTypes,h.node.type),Pd(c,t,h)}catch(g){if(g instanceof k)u.push({code:g.code,error:g.message,...g.statusCode!==400?{statusCode:g.statusCode}:{},...g.details??{}});else throw g}u.push(...Us(c,t));let p=Nf(n),f=await js(c,r.nodeTypes,{softWarningNodeIds:p});u.push(...f.errors),lt(u);for(let h of n)Pd(i,t,h);let m=[...f.warnings];if(r.nodeTypes){let h=await Bs(i,d,r.nodeTypes);m.push(...h.filter(g=>!g.nodeId||p.has(g.nodeId)))}return{expectedRev:s.rev,flow:l,...m.length>0?{warnings:m}:{}}}function Rd(e,t,n){if(!t||typeof t!="string")throw new k("Each edit needs a node id","missing_node_id");let r=e.findIndex(o=>o.id===t);if(r<0||!e[r])throw new k(`Node '${t}' was not found in flow '${n}'`,"node_not_found",{statusCode:404});return r}function Pd(e,t,n){if(n.op==="add"){let i=n.node;if(!i.id||typeof i.id!="string")throw new k("add op needs node.id","missing_node_id");if(bt(i.type,"node.type"),e.some(d=>d.id===i.id))throw new k(`Node '${i.id}' already exists in flow '${t}'`,"node_already_exists");e.push({...i,id:i.id,type:i.type,z:typeof i.z=="string"&&i.z?i.z:t});return}if(n.op==="remove"){let i=Rd(e,n.nodeId,t);e.splice(i,1);return}let r=Rd(e,n.nodeId,t),o=e[r];if(!o)throw new k(`Node '${n.nodeId}' was not found in flow '${t}'`,"node_not_found",{statusCode:404});if(n.op==="replace_text"){if(!(n.field in o))throw new k(`Field '${n.field}' was not found on the node`,"field_not_found");let i=Ld(o[n.field],n.field,n.find,n.replace,n.occurrence);e[r]={...o,id:o.id,type:o.type,[n.field]:i};return}let s={...o,id:o.id,type:o.type};for(let[i,d]of Object.entries(n.set)){if(Cd.has(i))throw new k(`Cannot change protected node property '${i}' via GET propose`,"protected_property");if(i==="wires"){let l=[];kn(d,l,n.nodeId),lt(l)}s[i]=d}e[r]=s}function _d(e){if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean"||typeof e=="bigint")return String(e)}function ci(e){let t={},n=e.set;if(n&&typeof n=="object"&&!Array.isArray(n))for(let[r,o]of Object.entries(n)){let s=_d(o);s!==void 0&&(t[r]=s)}for(let[r,o]of Object.entries(e)){if(!r.startsWith("set.")||r.length<=4)continue;let s=r.slice(4);if(typeof o=="string")t[s]=o;else if(Array.isArray(o)&&typeof o[0]=="string")t[s]=o[0];else{let i=_d(o);i!==void 0&&(t[s]=i)}}return t}function Ar(e){let t;try{t=Buffer.from(e,"base64url").toString("utf8")}catch{throw new k("patch must be base64url-encoded JSON","invalid_patch")}try{return JSON.parse(t)}catch{throw new k("patch JSON is malformed","invalid_patch_json")}}function Ff(e,t){if(typeof e.flowId=="string"&&e.flowId)return e.flowId;let n=e.flow;if(n&&typeof n=="object"&&!Array.isArray(n)){let r=n.id;if(typeof r=="string"&&r)return r}return t}function ui(e){return typeof e.id=="string"&&e.id?e.id:typeof e.nodeId=="string"&&e.nodeId?e.nodeId:typeof e.node=="string"&&e.node?e.node:""}function Of(e){if(e==null||e==="")return;let t;if(typeof e=="number")t=e;else if(typeof e=="string")t=Number(e);else if(typeof e=="boolean"||typeof e=="bigint")t=Number(e);else throw new k("occurrence must be a positive integer","invalid_occurrence");if(!Number.isFinite(t))throw new k("occurrence must be a positive integer","invalid_occurrence");return Math.trunc(t)}function $f(e){let t=e.node;if(t!==void 0)return If(t),{op:"add",node:t};let n={...e};return delete n.op,{op:"add",node:n}}function Mf(e){let t=ui(e);if(!t)throw new k("replace_text op needs node id","missing_node_id");let n=typeof e.field=="string"?e.field:"";if(!n)throw new k("replace_text op needs field","missing_field");if(typeof e.find!="string")throw new k("replace_text op needs find string","missing_find");if(typeof e.replace!="string")throw new k("replace_text op needs replace string","invalid_replace");let r=Of(e.occurrence),o={op:"replace_text",nodeId:t,field:n,find:e.find,replace:e.replace};return r!==void 0&&(o.occurrence=r),o}function Df(e){let t=ui(e);if(!t)throw new k("Each patch.nodes entry needs id","missing_node_id");let n=e.set,r={};if(n&&typeof n=="object"&&!Array.isArray(n))Object.assign(r,n);else for(let[o,s]of Object.entries(e))o==="id"||o==="nodeId"||o==="node"||o==="set"||o==="op"||(r[o]=s);return{op:"set",nodeId:t,set:r}}function Hf(e){let t=typeof e.op=="string"?e.op.toLowerCase():"set";if(t==="add")return $f(e);if(t==="remove"){let n=ui(e);if(!n)throw new k("remove op needs id","missing_node_id");return{op:"remove",nodeId:n}}if(t==="replace_text")return Mf(e);if(t!=="set")throw new k("nodes[].op must be 'add', 'remove', 'set', or 'replace_text'","invalid_patch");return Df(e)}function xr(e,t){if(!e||typeof e!="object"||Array.isArray(e))throw new k("patch must be a JSON object","invalid_patch");let n=e;if(n.type==="replace_flow"||n.flow&&typeof n.flow=="object"){let r=Ff(n,t);if(!r)throw new k("patch.replace_flow needs flowId","missing_flow_id");return{kind:"replace_flow",flowId:r,flow:n.flow}}if(Array.isArray(n.nodes)){let r=typeof n.flowId=="string"?n.flowId:t;if(!r)throw new k("patch.nodes needs flow id","missing_flow_id");let o=[];for(let i of n.nodes){if(!i||typeof i!="object"||Array.isArray(i))throw new k("Each patch.nodes entry must be an object","invalid_patch");o.push(Hf(i))}let s=o.filter(i=>i.op==="set").map(i=>({nodeId:i.nodeId,set:i.set}));return{kind:"nodes",flowId:r,ops:o,edits:s}}throw new k("patch must include nodes[] edits or type/flow for replace_flow","invalid_patch")}var Cd,Ro=q(()=>{"use strict";se();qs();Ut();Cd=new Set(["id","type","z"]);a(Ed,"describeNonObjectNode");a(If,"assertAddNodeObject");a(kr,"buildPatchedFlowTab");a(Id,"findTextOccurrences");a(Ld,"applyReplaceTextToValue");a(Lf,"collectOpShapeIssues");a(Nf,"touchedNodeIdsFromOps");a(Gt,"applyFlowNodeOps");a(Rd,"requireNodeIndex");a(Pd,"applyOneNodeOp");a(_d,"stringifyQueryValue");a(ci,"collectSetProps");a(Ar,"decodeBase64UrlJson");a(Ff,"resolveReplaceFlowId");a(ui,"resolveNodeId");a(Of,"parseOccurrence");a($f,"parseAddOp");a(Mf,"parseReplaceTextOp");a(Df,"parseSetOp");a(Hf,"parseNodeOp");a(xr,"interpretPatch")});function Kt(e,t){return dt(e.createdBy)===t.id}function fi(e,t){return!t||t.length===0||t.includes(e)}function jf(e){return typeof e=="string"&&Od.has(e)}function Cn(e,t){let n=Uf[e],r=e!=="status"&&Array.isArray(t);return new k(r?`Invalid ${e} filter: pass it at most once (multiple values are not supported). Allowed: ${n}`:`Invalid ${e} filter. Allowed: ${n}`,"invalid_filter",{details:{parameter:e,allowed:n,hint:`Pass ${e} as ${n}, or omit it.`}})}function Po(e,t){if(Array.isArray(t))throw Cn(e,t);return t}function Bf(e){return typeof e=="number"?Number.isInteger(e)?e:null:typeof e=="string"&&/^-?\d+$/.test(e.trim())?Number(e.trim()):null}function hi(e){return e==null||e===""}function qf(e){if(!hi(e)){if(e==="true"||e==="1"||e===!0)return!0;if(e==="false"||e==="0"||e===!1)return!1;throw Cn("mine",e)}}function mi(e){if(hi(e))return;let t=(Array.isArray(e)?e:[e]).flatMap(r=>typeof r=="string"?r.split(","):[r]),n=[];for(let r of t){let o=typeof r=="string"?r.trim():r;if(o!==""){if(!jf(o))throw Cn("status",e);n.includes(o)||n.push(o)}}return n.length>0?n:void 0}function Fd(e,t,n,r){if(hi(t))return;let o=Bf(t);if(o===null||o<n||o>r)throw Cn(e,t);return o}function _o(e){let t={},n=qf(Po("mine",e.mine));n!==void 0&&(t.mine=n);let r=mi(e.status);r&&(t.status=r);let o=Po("flowId",e.flowId??e.flow_id??e.flow);typeof o=="string"&&o.length>0&&(t.flowId=o);let s=Fd("limit",Po("limit",e.limit),1,_n);s!==void 0&&(t.limit=s);let i=Fd("offset",Po("offset",e.offset),0,Number.MAX_SAFE_INTEGER);return i!==void 0&&(t.offset=i),t}function Jt(e){let t=e.offset+e.returned,n=t<e.total;return{total:e.total,offset:e.offset,...e.limit!==null?{limit:e.limit}:{},hasMore:n,...n?{nextOffset:t}:{}}}function zf(e,t){let n=e.length,r=t.offset!==void 0&&t.offset>0?t.offset:0;if(t.limit===void 0)return{items:r>0?e.slice(r):e,total:n,limit:null,offset:r};let o=Math.min(Math.max(1,t.limit),_n);return{items:e.slice(r,r+o),total:n,limit:o,offset:r}}function En(e,t,n={},r={}){let o=r.mine===!0?t.id:void 0,s={};o&&(s.linkId=o),r.flowId&&(s.flowId=r.flowId);let d=e.list(s).filter(c=>r.mine!==!1||!Kt(c,t)).filter(c=>fi(c.status,r.status)).map(c=>({...ue(Ye(c),n),mine:Kt(c,t)})),l=zf(d,r);return{proposals:l.items,total:l.total,limit:l.limit,offset:l.offset}}function ut(e,t,n,r={}){if(!n)throw new k("Missing proposal id","missing_proposal_id");let o=e.get(n);return{...ue(Ye(o),r),mine:Kt(o,t)}}function Wf(e){let t={kind:"access-link",linkId:e.id};return typeof e.label=="string"&&e.label&&(t.label=e.label),t}function In(e,t,n,r,o,s={}){if(!r)throw new k("Missing proposal id","missing_proposal_id");let i;try{i=e.get(r)}catch(d){throw d instanceof me,d}if(!Kt(i,n))throw new wt(r);if(!U(i.status))throw new Y(r,i.status);return{...gi(e,t,Wf(n),r,o,s),mine:!0}}function gi(e,t,n,r,o,s={}){let i=e.get(r);if(n.kind==="access-link"&&dt(i.createdBy)!==n.linkId)throw new wt(r);let d=typeof o=="string"&&o.trim()?o.replace(/\0/g,"").trim().slice(0,2e3):n.kind==="admin"?"Withdrawn by administrator (token recovery)":"Withdrawn by access link",l=e.markWithdrawn(r,n,d),c=ue(Ye(l),s);return t.append("proposal.withdrawn",n,{proposalId:r,flowId:i.payload.flowId,title:c.title,displayName:c.displayName,reason:d,withdrawalAuthority:n.kind==="admin"?"admin-recovery":"owner-token",originalOwnerId:dt(i.createdBy),...c.flowLabel?{flowLabel:c.flowLabel}:{}}),c}function Gf(e,t,n,r={}){let o;try{o=e.get(t.id)}catch{return{}}if(r.live){De(e,r.live,{flowId:o.payload.flowId});try{o=e.get(t.id)}catch{return{}}}return U(o.status)?et(Qe(e,o,n)):{}}function he(e,t,n,r={},o={}){let s="mine"in n?n:{...ue(n,r),mine:Kt(n,t)},i=0;if(o.live)try{let c=e.get(s.id).payload.flowId;i=De(e,o.live,{flowId:c}).expired;let u=e.get(s.id);s={...ue(u,r),mine:Kt(u,t)}}catch{}let d=ie(Ye(s),r),l={...d,mine:s.mine,...pe(i)};return o.includeConflicts!==!1&&U(d.status)&&Object.assign(l,Gf(e,d,t.id)),l}async function pt(e){let t=await e.getFlows();return{rev:t.rev,flows:t.flows}}async function He(e,t){if(!t)throw new k("Missing flow id","missing_flow_id");let n=await e.getFlow(t);return{rev:n.rev,flow:vn(t,n.flow)}}function Kf(e){if(typeof e.expectedRev=="string"&&e.expectedRev.length>0)return e.expectedRev;if(typeof e.expected_rev=="string"&&e.expected_rev.length>0)return e.expected_rev}function $d(e,t){if("nodes"in e&&e.nodes!==void 0&&!Array.isArray(e.nodes))throw new k("payload.nodes must be an array of node ops","invalid_nodes");if(t!==void 0&&!Array.isArray(t))throw new k("nodes must be an array of node ops","invalid_nodes")}async function Jf(e,t,n,r,o){let{applyFlowNodeOps:s,interpretPatch:i}=await Promise.resolve().then(()=>(Ro(),Nd));$d(n,r);let d=Array.isArray(n.nodes)?n.nodes:Array.isArray(r)?r:null;if(!d)throw new k("payload.flow is required","missing_flow");let l=n.flowId,c=typeof l=="string"?l:"";if(!c)throw new k("payload.flowId is required","missing_flow_id");let u=i({nodes:d,flowId:c},c);if(u.kind!=="nodes")throw new k("payload.nodes must be node ops","invalid_patch");let p=await s(e,u.flowId,u.ops,{expectedRev:t,...o?{nodeTypes:o}:{}});if(p.expectedRev!==t)throw new Q(t,p.expectedRev,"submit_proposal");return{flowId:u.flowId,flow:p.flow}}async function Vf(e,t,n,r,o){let s=n.flowId,i=n.flow,d=!i||typeof i!="object"||Array.isArray(i);if(d&&$d(n,r),d&&(Array.isArray(n.nodes)||Array.isArray(r))){let l=await Jf(e,t,n,r,o);s=l.flowId,i=l.flow}if(typeof s!="string"||s.length===0)throw new k("payload.flowId is required","missing_flow_id");if(!i||typeof i!="object"||Array.isArray(i))throw new k("payload.flow is required","missing_flow");return{flowId:s,flow:i}}function Yf(e,t){try{return{safeExpectedRev:Ge(e.slice(0,200),"expectedRev"),safeFlowId:Ge(t.slice(0,200),"flowId")}}catch{throw new k("expectedRev and payload.flowId must be safe path segments","invalid_path_segment")}}function Xf(e,t){let n=sd(Ne(e),t);return n.unresolved.length>0&&lt(n.unresolved.map(r=>({code:"redacted_placeholder_mismatch",error:`Node ${r.nodeId} field ${r.property}: ${r.reason}. [REDACTED] placeholders are restored only when left exactly where they were shown (same node, field, env/header name, and unchanged line). Do not copy, move, or add placeholders; keep those lines unchanged or supply a real new value.`,nodeId:r.nodeId,property:r.property,statusCode:400}))),n}async function Md(e,t,n){let r=e.type,o=Kf(e),s=e.payload;if(r!=="replace_flow")throw new k("type must be 'replace_flow'","invalid_proposal_type");if(!o)throw new k("expectedRev is required (top-level field next to type and payload)","missing_expected_rev");if(!s||typeof s!="object"||Array.isArray(s))throw new k("payload must be an object","invalid_payload");let i=s,d=$e(t),l=await d.getFlows(),c=co(l,d.kind);if(l.rev!==o){let T=Cs(d)?.get(o);if(!T)throw new Q(o,l.rev,"submit_proposal");c=co(T,d.kind)}let u=await Vf(At(c),o,i,e.nodes,n),{safeExpectedRev:p,safeFlowId:f}=Yf(o,u.flowId),m=u.flow,h=Buffer.byteLength(JSON.stringify(s),"utf8");if(h>K)throw new ce("proposal payload",h,K);lt(Ha(m,f));let y=await c.getFlows();if(y.rev!==p)throw new Q(p,y.rev,"submit_proposal");let w=Xf(m,y.flows),v=Ne(w.flow);lt(Da(v,f,y.flows));let L=await c.getFlow(f);if(L.rev!==p)throw new Q(p,L.rev,"submit_proposal");let P=Ne(L.flow);n&&await Oa(n,v);let{errors:I,warnings:D}=await ja(v,{flowId:f,baseFlow:P,...n?{nodeTypes:n}:{}});return lt(I),D.unshift(...yr(P,v).map(T=>({...T})),...dd(w.restoredFields,v,y.flows).map(T=>({...T}))),{safeExpectedRev:p,safeFlowId:f,safeFlow:v,safeBaseFlow:P,restoration:w,flowWarnings:D,head:l}}function pi(e){let t={kind:"access-link",linkId:e.id};return e.label!==void 0&&(t.label=e.label),t}async function Tt(e,t,n,r,o,s={},i){let d=await Md(r,o,i),{safeExpectedRev:l,safeFlowId:c,safeFlow:u,safeBaseFlow:p,restoration:f,flowWarnings:m}=d,h=pi(n),g=n.oauthClientId&&n.oauthUser?{kind:"oauth",clientId:n.oauthClientId,user:n.oauthUser}:h,y=ur(r.title)??Gs(p,u),w=e.create({type:"replace_flow",expectedRev:l,payload:{flowId:c,flow:u},baseFlow:p,...f.restoredFields.length>0?{restoredSecrets:f.restoredFields}:{},title:y,createdBy:g,...m.length>0?{warnings:m}:{}}),v=Me(e,w,d.head).proposal,L=ue(Ye(v),s),P=L.flowLabel||void 0;return t.append("proposal.created",h,{proposalId:v.id,flowId:c,expectedRev:l,...d.head.rev!==l?{liveRev:d.head.rev}:{},...v.rebase?{rebaseState:v.rebase.state}:{},title:L.title,displayName:L.displayName,...P?{flowLabel:P}:{},...m.length>0?{warningCount:m.length}:{},...f.restored>0?{restoredRedactions:f.restored}:{}}),L}async function Co(e,t,n,r,o,s,i={},d){if(!r)throw new k("Missing proposal id","missing_proposal_id");let l=e.get(r);if(!Kt(l,n))throw new wt(r);if(!U(l.status))throw new Y(r,l.status);if(o.choice!==void 0||o.nodeId!==void 0){if(o.payload!==void 0||o.nodes!==void 0||o.expectedRev!==void 0||o.title!==void 0)throw new k("Use nodeId + choice alone, or expectedRev + payload to replace the body; do not mix the modes.","invalid_choice");let h=await yi(e,t,pi(n),r,o.nodeId,o.choice,s);return{...ue(Ye(h),i),mine:!0}}let c=await Md({type:"replace_flow",...o,payload:o.payload??{flowId:l.payload.flowId}},s,d);if(c.safeFlowId!==l.payload.flowId)throw new k(`payload.flowId must stay '${l.payload.flowId}' (submit a new proposal for another flow)`,"flow_id_mismatch");let u=ur(o.title),p=e.updateBody(r,{baseFlow:c.safeBaseFlow,flow:c.safeFlow,expectedRev:c.safeExpectedRev,restoredSecrets:c.restoration.restoredFields,warnings:c.flowWarnings,...u?{title:u}:{}}),f=Me(e,p,c.head).proposal,m=ue(Ye(f),i);return t.append("proposal.resolved",pi(n),{proposalId:r,flowId:f.payload.flowId,expectedRev:c.safeExpectedRev,...c.restoration.restored>0?{restoredRedactions:c.restoration.restored}:{},title:m.title,displayName:m.displayName,...f.rebase?{rebaseState:f.rebase.state}:{}}),{...m,mine:!0}}async function yi(e,t,n,r,o,s,i){if(s!=="live"&&s!=="proposed")throw new k("choice must be 'live' or 'proposed'","invalid_choice");if(typeof o!="string"||o.length===0||o.length>200)throw new k("nodeId is required","missing_node_id");let d=await $e(i).getFlows(),l=e.get(r);if(!U(l.status))throw new Y(r,l.status);let c=Me(e,l,d).proposal,u=c.rebase?.conflicts.find(h=>h.nodeId===o);if(!u)throw new k("No current conflict for that node (it may already be resolved). Reload the proposal.","conflict_not_found",{statusCode:409});if(!u.choices.includes(s))throw new k("That choice is not available for this conflict.","invalid_choice");let p;try{p=gd({flowId:c.payload.flowId,base:c.baseFlow,proposed:c.payload.flow,liveFlows:d.flows,nodeId:o,choice:s})}catch(h){throw h instanceof mr?new Je(r,c.rebase?.conflicts??[]):new k("This conflict cannot be resolved by hand.","invalid_choice")}let f=e.updateBody(r,{baseFlow:Ne(p.base),flow:Ne(p.proposed),conflictChoice:{nodeId:o,choice:s,chooser:n.kind==="admin"?"human":"ai",undo:{baseFlow:c.baseFlow??{},flow:c.payload.flow,warnings:c.warnings??[],aiConflictOverrides:c.aiConflictOverrides??[]}},aiConflictOverrides:[...(c.aiConflictOverrides??[]).filter(h=>h.nodeId!==o),...n.kind!=="admin"&&s==="proposed"?[{nodeId:o,fields:u.fields?.length?u.fields:[""]}]:[]],warnings:[...(c.warnings??[]).filter(h=>h.code!=="secret_may_revert"||h.nodeId!==o),...p.warnings]}),m=Me(e,f,d).proposal;return t.append("proposal.conflict_resolved",n,{proposalId:r,flowId:m.payload.flowId,nodeId:o,choice:s,kind:u.kind,chooser:n.kind==="admin"?"human":"ai",status:m.status}),m}async function Sr(e,t){let n=$e(e),r;try{r=(await n.getFlows()).rev}catch{return e}if(r===t)return e;let o=Cs(n)?.get(t);if(!o)return e;let s=co(o,n.kind);return e===n?s:At(s)}function W(e,t="rest"){return Tr(e,t)}var _n,Od,Uf,k,se=q(()=>{"use strict";Te();ao();Es();Ve();no();oe();rt();qs();Ut();Wt();Oe();Pn();nt();Pe();a(Kt,"isMineProposal");_n=200,Od=new Set(["pending","conflict","conflict","approved","rejected","failed","withdrawn","expired","superseded"]);a(fi,"matchesStatusFilter");a(jf,"isProposalStatus");Uf={status:[...Od].join(", "),limit:`integer 1\u2013${_n}`,offset:"integer \u2265 0",mine:"true, false, 1, 0",flowId:"one flow tab id"};a(Cn,"invalidFilterError");a(Po,"singleQueryValue");a(Bf,"parseIntegerFilter");a(hi,"isUnset");a(qf,"parseMineFilter");a(mi,"parseStatusFilter");a(Fd,"parseRangeFilter");a(_o,"parseListProposalsQuery");a(Jt,"listPageFields");a(zf,"applyListPagination");a(En,"listAccessibleProposals");a(ut,"getAccessibleProposal");a(Wf,"accessLinkActor");a(In,"withdrawMyProposal");a(gi,"withdrawProposalAsActor");a(Gf,"conflictsForProposal");a(he,"presentAccessibleProposalForApi");k=class extends Error{static{a(this,"AiOpsValidationError")}statusCode;code;details;constructor(t,n="validation_error",r={}){super(t),this.name="AiOpsValidationError",this.code=n,this.statusCode=r.statusCode??400,r.details&&(this.details=r.details)}};a(pt,"listFlows");a(He,"getFlow");a(Kf,"resolveExpectedRev");a($d,"assertNodesArrayOrAbsent");a(Jf,"materializePayloadFromNodes");a(Vf,"resolveReplaceFlowPayload");a(Yf,"assertSafeRevAndFlowId");a(Xf,"strictRestoreOrThrow");a(Md,"prepareReplaceFlowSubmission");a(pi,"auditActorFor");a(Tt,"submitReplaceFlowProposal");a(Co,"resolveMyProposal");a(yi,"resolveConflictChoice");a(Sr,"adapterAtRev");a(W,"aiOpsErrorToHttp")});function wi(e){return e==="get_link"?86400:2592e3}function bi(e,t){return e==="get_link"?`get_link ttlSeconds must be \u2264 ${t} (24 hours)`:`ttlSeconds must be \u2264 ${t} (30 days) for ${e} tokens`}var Vt,Rr=q(()=>{"use strict";a(wi,"maxTtlSecondsForKind");a(bi,"ttlCapErrorMessage");Vt="ttlSeconds must be a positive number of seconds within the cap for the token kind: get_link \u2264 86400 (24h); rest and mcp \u2264 2592000 (30 days)."});function R(e,t,n,r,o=ne,s="error"){return{code:e,status:t,meaning:n,hint:r,channels:o,kind:s}}function Pr(e){return Qf.get(e)?.hint}function Dd(e){return e.replace(/\\/g,"\\\\").replace(/\|/g,"\\|")}function Hd(){let e=a(s=>`| \`${s.code}\` | ${s.status} | ${Dd(s.meaning)} | ${Dd(s.hint)} | ${s.channels.join(", ")} |`,"row"),t=["| Code | HTTP | Meaning | Next step | Channels |","| --- | --- | --- | --- | --- |"],n=Eo.filter(s=>s.kind==="error"),r=Eo.filter(s=>s.kind==="warning"),o=Eo.filter(s=>s.kind==="status");return[...t,...n.map(e),"","**Warnings** (in `warnings[]` on a successful propose; never blocking):","",...t,...r.map(e),"","**Proposal statuses.** Open proposals are closed automatically: `expired` after the configured TTL (default 7 days). Revision changes auto-rebase open proposals; `conflict` blocks approval until resolved. Legacy closed proposals stay closed. Submit, get, and list responses include `autoClosedCount` when that request auto-closed proposals (omitted when zero).","",...t,...o.map(e)].join(`
10
+ `)}var ne,Ln,Nn,Zf,Rt,Eo,Qf,Io=q(()=>{"use strict";Rr();ne=["GET","REST","MCP"],Ln=["GET"],Nn=["REST","MCP"],Zf=["GET","REST"],Rt=["admin"];a(R,"e");Eo=[R("duplicate_env_keys",200,"Changed env arrays contain duplicate names; pairing by name is ambiguous when entries are reordered","Review the env values before approval",[...ne,"admin"],"warning"),R("approve_queue_timeout",504,"Approval exceeded its queue-wait deadline (default 60 seconds); no deployment was started for this request","Check the live flow and retry approval",Rt),R("deploy_timeout",504,"Deployment exceeded its deadline (default 60 seconds); approvals wait for settlement or the hard cap (default 5 minutes)","Before the cap, late settlement updates the proposal; after the cap, deployPending.queueReleased is true and the outcome stays unknown. Check the live flow before approving again; approval always re-reads live",Rt),R("secret_may_revert",200,"Secret spans could not be aligned during proposed conflict resolution; proposed values were kept","Review the warned field against live flows before approval; an editor secret change may be reverted",[...ne,"admin"],"warning"),R("proposal_conflict",409,"Editor and proposal changes conflict (conflicts contains field names only)","Read the current flow and resolve the proposal before approval",[...ne,"admin"]),R("rebase_ack_required",409,"The merged result needs review (mergedHash included)","Review the diff and approve with its mergedHash",["admin"]),R("rebase_race",409,"Flows kept changing during approval","Recheck the proposal and retry approval",["admin"]),R("conflict_not_found",409,"The requested conflict no longer exists","Reload the proposal and its conflicts",[...Nn,"admin"]),R("invalid_choice",400,"Invalid manual conflict choice","Use nodeId plus an available live or proposed choice, without replacement payload or revision",[...Nn,"admin"]),R("flow_id_mismatch",400,"Resolution tried to change the proposal flow id","Keep the same flowId or submit a new proposal",Nn),R("access_link_missing",401,"No Bearer / path token","Ask the user for a new access link of the right kind"),R("bearer_scheme_required",401,"Auth used Basic (or non-Bearer)","Send `Authorization: Bearer <token>`",Nn),R("token_expired",401,"Known token past `expiresAt` (body includes `expiredAt` and `accessLinksUrl`)","Ask the user to create a new link on the `/ai-access` dashboard \u2192 Access tokens (`accessLinksUrl`)"),R("access_link_invalid",401,"Unknown, revoked, or cleared after restart","Ask the user for a new access link"),R("wrong_token_kind",403,"Token kind does not match this surface","Mint the matching kind on `/ai-access` \u2192 Access tokens; show `tellUser` to the human"),R("ai_features_disabled",403,"Plugin AI features off","Owner enables AI Access / `adminAuth`"),R("auth_required",401,"Admin session required","Sign in to the Node-RED editor",Rt),R("insufficient_permissions",403,"Admin lacks required scope","Need `*` for link mint / approve",Rt),R("invalid_link_kind",400,"Unknown `kind` on create","Use `get_link`, `rest`, or `mcp`",Rt),R("invalid_ttl",400,"`ttlSeconds` is zero, negative, not a number, or over the cap for the kind",Vt,Rt),R("access_link_limit",409,"Too many active links","Revoke an existing link first",Rt),R("access_link_not_found",404,"Unknown link id on revoke","List links and use an existing id",Rt),R("link_create_not_supported",403,"Attempted mint under a GET link","Admin mints via `POST /mcp/api/v1/links` only",Ln),R("rate_limited",429,"Per-IP or per-token limit","Wait `retryAfter` / `Retry-After`"),R("payload_too_large",413,"Body / proposal over ~1.5MB","Shrink content"),R("invalid_json",400,"Malformed JSON body","Fix Content-Type / body",Nn),R("unsupported_media_type",415,"POST body is not `application/json`","Send `Content-Type: application/json`",Nn),R("url_too_long",414,"GET URL over soft cap (~8KB)","Use chunked drafts or REST/MCP",Ln),R("not_found",404,"Unknown path or resource","Re-read docs / discovery"),R("missing_query",400,"Required search `q` missing","Pass `?q=`",Zf),R("invalid_filter",400,"Invalid list filter (`status`, `limit`, `offset`, `mine`); body names `parameter` and `allowed`","Use an allowed value: status one of the proposal statuses; limit integer 1\u2013200; offset integer \u2265 0; mine true|false"),R("invalid_arguments",400,"MCP tool arguments failed schema validation; body names `parameter`","Fix the named argument to match the tool input schema (see tools/list)",["MCP"]),R("invalid_diff_format",400,"Unknown `format` on proposal diff","Use `format=summary` (default) or `format=full`"),R("confirm_required",400,"GET withdraw without `confirm=1`","Repeat the request with `confirm=1`",Ln),R("missing_expected_rev",400,"Proposal submitted without `expectedRev` / `rev`","Read the flow and pass its `rev`"),R("version_mismatch",409,"Unknown or unavailable `rev` / `expectedRev` snapshot","Re-read flows and resubmit"),R("proposal_not_found",404,"Unknown proposal id","List proposals and use an existing id"),R("proposal_not_pending",409,"Action needs an open proposal","Only pending or conflict proposals can withdraw / decide"),R("proposal_not_owned",403,"Resolve or withdraw another token\u2019s proposal","Use the creating token; lost-token withdrawal requires a full admin session"),R("validation_failed",400,"Several validation errors at once (message `N validation errors`)","Fix every entry in errors[] and resubmit."),R("duplicate_node_id",400,"The same node id appears more than once in a full tab or batch","Give every node a unique id"),R("missing_node_id",400,"Node / op without an id","Include a non-empty string id"),R("missing_node_type",400,"Node without a `type`","Set `type` to an installed palette type name"),R("invalid_node_type",400,"`type` is not a string","Use the palette type name exactly"),R("invalid_nodes",400,"`flow.nodes` / `flow.configs` not an array","Send arrays (use `[]` to clear)"),R("invalid_node",400,"Node entry is not an object","Send `{ id, type, \u2026 }` objects"),R("invalid_wires",400,"Bad wires shape, missing target, or more ports than outputs","Wire only to existing node ids / ports"),R("z_mismatch",400,"Node `z` differs from the flow id","Set `z` to the tab id or omit it"),R("node_already_exists",400,"`add` op for an id already in the flow","Use `set` or a new id"),R("protected_property",400,"Tried to change id / type / z","Omit id, type, and z from edits"),R("node_not_found",404,"Unknown node (single error only; several errors \u2192 `validation_failed`)","Re-read the flow"),R("field_not_found",404,"Unknown field on a node","Re-read the node"),R("text_not_found",400,"`replace_text` find had no match","Re-read field; find is exact"),R("ambiguous_match",409,"`replace_text` matched more than once","Pass `occurrence` (1-based) or unique find"),R("unknown_node_type",400,"Proposal uses a type that is not installed","List node-types / palette search; ask user to install"),R("node_type_not_found",404,"Unknown type on node-types detail","List types or search palette"),R("chunk_conflict",409,"Same draft seq re-sent with different data","Re-send identical data or start a new draft",Ln),R("missing_chunks",400,"Draft submit with gaps in seq","Upload every seq from 0 through max",Ln),R("draft_hash_mismatch",400,"Draft sha256 does not match assembled bytes","Send the hex sha256 of the assembled UTF-8",Ln),R("missing_config_node",201,"Warning: node has no (or an unresolved) config node yet","Configure before deploy if needed; see `candidates`",ne,"warning"),R("unwired_node",201,"Warning: node has outputs but no wires","Wire it, or ignore while scaffolding",ne,"warning"),R("incomplete_wires",201,"Warning: fewer wire ports than outputs","Add ports if outputs should connect",ne,"warning"),R("redacted_placeholder_mismatch",400,"A `[REDACTED]` placeholder was added, duplicated, moved, or its env/header name changed","Keep placeholders exactly where they were shown (same line, same name) or supply a real value",ne),R("secret_destination_changed",201,"Warning: a node holding a restored secret changed where it sends data (url/host/method/headers/\u2026)","Reviewer must verify the new destination before approving",ne,"warning"),R("secret_node_code_changed",201,"Warning: a node holding a restored secret had its code changed","Reviewer must check the code does not send the secret elsewhere",ne,"warning"),R("secret_wires_changed",201,"Warning: a node holding a restored secret had its output wires changed","Reviewer must check where its messages now go",ne,"warning"),R("secret_scope_changed",201,"Warning: tab/group env or a config node scoping a secret-holding node changed","Reviewer must check env-resolved hosts/URLs (e.g. `${API_HOST}`)",ne,"warning"),R("outbound_node_changed",201,"Warning: proposal adds/changes an outbound-capable node (http request/response, mqtt, tcp, udp, email, websocket, link out/call, function, exec, file, or any node with url/host fields)","Reviewer must check it cannot send secret data elsewhere",ne,"warning"),R("secret_tab_changed",201,"Warning: proposal with restored secrets also changes other nodes in the tab","Review every change before approving",ne,"warning"),R("secret_reference_added",201,"Warning: an added/changed node now reads a secret-named env/global/flow value (env.get, $env, ${\u2026}, typed input)","Reviewer must check the value is not sent anywhere unexpected",ne,"warning"),R("conflict",200,"Status: open proposal has conflicting editor changes; approval blocked","Read the current flow and resolve the proposal",ne,"status"),R("expired",200,"Status: open proposal expired after the configured TTL (default 7 days)","Re-read the flow and submit a new proposal",ne,"status"),R("superseded",200,"Legacy closed status; new approvals rebase other open proposals","Re-read the flow and submit a new proposal if still needed",ne,"status"),R("upstream_error",502,"Node-RED runtime/flows error","Retry; check Node-RED logs"),R("internal_error",500,"Unexpected server error","Retry; check Node-RED logs")],Qf=new Map(Eo.map(e=>[e.code,e]));a(Pr,"registryHint");a(Dd,"cell");a(Hd,"renderGetLinkErrorTable")});function jd(e){let t=ue(e),n=go(e)||void 0;return{proposalId:e.id,title:t.title,displayName:t.displayName,flowId:e.payload.flowId,...n?{flowLabel:n}:{}}}function vi(e,t){return e!==void 0&&Number.isFinite(e)&&e>0?e:t}var Yt,Xt,Pt,Zt,Lo,eh,Ud,No,Fo=q(()=>{"use strict";Te();Ve();Oe();Pn();nt();Pe();a(jd,"auditProposalMeta");Yt=class extends Error{static{a(this,"ProposalRebaseAckRequiredError")}code="rebase_ack_required";statusCode=409;mergedHash;rebaseState;constructor(t,n,r){super(`Proposal '${t}' was rebased onto newer editor changes since you viewed it. Review the updated diff, then approve again with the new mergedHash.`),this.name="ProposalRebaseAckRequiredError",this.mergedHash=n,this.rebaseState=r}},Xt=class extends Error{static{a(this,"ProposalRebaseRaceError")}code="rebase_race";statusCode=409;constructor(t){super(`Proposal '${t}' could not be applied because the flows kept changing during approve. It is still pending \u2014 try again.`),this.name="ProposalRebaseRaceError"}},Pt=class extends Error{static{a(this,"ProposalDeployTimeoutError")}code="deploy_timeout";statusCode=504;constructor(){super("Deployment timed out; the deploy outcome is unknown. The proposal remains pending with deployPending set. Approvals wait until the runtime settles or the deploy hard cap is reached. Check the live flow before approving again."),this.name="ProposalDeployTimeoutError"}},Zt=class extends Error{static{a(this,"ProposalApproveQueueTimeoutError")}code="approve_queue_timeout";statusCode=504;constructor(){super("Approval timed out waiting in the queue. No deployment was started for this request. Check the live flow and retry."),this.name="ProposalApproveQueueTimeoutError"}},Lo=class extends Error{static{a(this,"DeployHardCapError")}},eh=3;a(vi,"deadlineMs");Ud=Promise.resolve(),No=class{constructor(t,n,r,o,s={}){this.proposals=t;this.audit=n;this.adapter=r;this.getDeploymentType=o;this.serviceOptions=s}static{a(this,"ProposalService")}async approve(t,n,r={}){let o=this.proposals.get(t),s=o.deployPending!==void 0;if(!U(o.status))throw new Y(t,o.status);if(!s&&this.proposals.isApproving(t))throw new Re(t);let i=Ud,d,l=new Promise(c=>{d=c});return Ud=i.then(()=>l),new Promise((c,u)=>{let p=!1,f,m=new Promise((g,y)=>{f=setTimeout(()=>y(new Zt),vi(this.serviceOptions.approveQueueTimeoutMs,6e4)),f.unref?.()});a(async()=>{try{await Promise.race([i,m]),clearTimeout(f);let g=this.proposals.get(t);if(s&&g.status==="approved")return g;let y=this.proposals.beginApprove(t);return p=!0,await this.approveLocked(y,n,r,u)}finally{clearTimeout(f),p&&this.proposals.releaseLock(t),d()}},"run")().then(c,u)})}async approveLocked(t,n,r,o){let s=t.id,i,d=jd(t),l=r.deploymentType??this.getDeploymentType(),c=t.type;if(c!=="replace_flow"){let f=this.proposals.markFailed(s,n,`Unsupported proposal type '${c}'`);return this.audit.append("proposal.failed",n,{...d,reason:f.reason}),f}if(To(t))throw this.proposals.releaseLock(s),te(this.proposals,{flowId:t.payload.flowId}),new Y(s,this.proposals.get(s).status);let u=$e(this.adapter),p=a((f,m)=>{let h=this.proposals.markApproved(s,n,m);return this.audit.append("proposal.approved",n,{...d,resultRev:m,liveRev:m,baseRev:f.expectedRev,alreadyApplied:!0}),h},"markAlreadyApplied");for(let f=0;f<eh;f++){let m=await u.getFlows(),h=this.proposals.get(s),g=zt(m.flows,h.payload.flowId,h.payload.flow),y=g&&br(g);if(y&&(y===h.deployPending?.mergedHash||y===br(h.payload.flow))){i=p(h,m.rev);break}let{result:w}=Me(this.proposals,h,m,{duringApprove:!0});if(w.state==="conflict"||!w.merged||!w.mergedHash)throw this.audit.append("proposal.conflict",n,{...d,liveRev:m.rev,conflictCount:w.conflicts.length}),new Je(s,li(w).conflicts);if(y===w.mergedHash){i=p(h,m.rev);break}if((w.state==="merged"||r.mergedHash!==void 0)&&r.mergedHash!==w.mergedHash)throw new Yt(s,w.mergedHash,w.state);let L=w.merged,P=w.mergedHash,I=!1;try{let D=await this.withDeployDeadline(()=>u.updateFlow({flowId:h.payload.flowId,flow:L,expectedRev:m.rev,deploymentType:l}),()=>{I=!0;let T=new Pt;this.proposals.recordDeployPending(s,{mergedHash:P,reason:"Deploy outcome unknown: the runtime has not settled. Approvals are waiting."}),this.audit.append("proposal.deploy_timeout",n,{...d,code:T.code}),o(T)},()=>{this.proposals.recordDeployPending(s,{mergedHash:P,queueReleased:!0,reason:"Deploy outcome unknown: the runtime did not settle before the hard cap. The approval queue was released. Check the live flow before approving again."}),o(new Pt)});i=this.proposals.markApproved(s,n,D.rev),this.audit.append("proposal.approved",n,{...d,resultRev:D.rev,flowId:h.payload.flowId,baseRev:h.expectedRev,liveRev:m.rev,rebaseState:w.state});break}catch(D){if(i||D instanceof Lo)throw D;if(I)throw this.proposals.recordDeployPending(s,void 0),D;if(D instanceof Q)continue;let T=D instanceof Error?D.message:String(D),x=this.proposals.markFailed(s,n,T);return this.audit.append("proposal.failed",n,{...d,reason:T}),x}}if(!i)throw new Xt(s);try{await vr(this.proposals,this.adapter,{flowId:i.payload.flowId})}catch{}return i}async withDeployDeadline(t,n,r){let o=setTimeout(n,vi(this.serviceOptions.deployTimeoutMs,6e4));o.unref?.();let s,i=new Promise((d,l)=>{s=setTimeout(()=>{try{r()}finally{l(new Lo)}},vi(this.serviceOptions.deployHardCapMs,3e5)),s.unref?.()});try{return await Promise.race([t(),i])}finally{clearTimeout(o),clearTimeout(s)}}reject(t,n,r){let o=this.proposals.get(t);if(!U(o.status))throw new Y(t,o.status);let s=jd(o),i=this.proposals.markRejected(t,n,r.trim()||"Rejected by admin");return this.audit.append("proposal.rejected",n,{...s,reason:i.reason}),i}}});function we(e,t,n={}){return ki(e,t,n)}function th(e,t){return t==="excess_ports"?e==="mcp"?"This node has more wire ports than its type declares \u2014 remove extra ports or raise the node outputs, then retry.":e==="rest"?"This node has more wire ports than its type declares \u2014 trim wires[] or raise outputs, then retry.":"This node has more wire ports than its type declares \u2014 trim wires or raise outputs, then retry.":t==="missing_target"?e==="mcp"?"Wire targets must be node ids in this flow \u2014 add the node in the same patch or use get_flow ids.":e==="rest"?"Wire targets must be node ids in this flow \u2014 add the node in the same patch or use GET /flows/{id} ids.":"wires must list existing node ids (or add those nodes in the same patch).":e==="mcp"||e==="rest"?"Fix wires: targets must exist in this flow, and port count must not exceed the node outputs.":"wires must list existing node ids (or add those nodes in the same patch)."}function nh(e){return e==="mcp"?"Call list_node_types or search_palette. Ask the user to install via Manage palette, then recheck. This API never installs.":e==="rest"?"Call GET /node-types?q= or GET /palette/search?q=. Ask the user to install via Manage palette, then recheck. This API never installs.":"Call GET \u2026/node-types?q= or \u2026/palette/search?q=. Ask the user to install via Manage palette, then recheck. This API never installs."}function _t(e,t,n,r){return e==="mcp"?t:e==="rest"?n:r}function ki(e,t,n={}){switch(e){case"version_mismatch":return _t(t,"Re-read with list_flows/get_flow and retry with the current rev.","Re-read the flow and retry with the current rev.","Re-read the flow and retry with the current rev.");case"node_not_found":return _t(t,"Call get_flow or get_node and use a node id from that response.","Call GET /flows/{id} and use a node id from that response.","Call GET \u2026/flows/<id> and use a node id from that response.");case"flow_not_found":return _t(t,"Call list_flows to list valid ids.","Call GET /flows to list valid ids.","Use an id from GET ./flows.");case"proposal_not_found":return _t(t,"Call list_proposals and use an id from that response.","List proposals and use an id from that response.","List proposals and use an id from that response.");case"proposal_not_owned":return"Use the creating access link to resolve or withdraw. If that token is lost, an admin with full permission can withdraw through POST /proposals/{id}/withdraw.";case"proposal_not_pending":return"Only open (pending or conflict) proposals can be withdrawn or rejected.";case"confirm_required":return"Add confirm=1 to the withdraw URL (required so link prefetchers cannot withdraw).";case"invalid_wires":return th(t,n.cause);case"node_type_not_found":case"unknown_node_type":return nh(t);case"missing_node_type":return'Each add op needs node.type as a non-empty string (e.g. "inject").';case"invalid_line_range":return _t(t,'Pass lines as "A" or "A-B" (1-based inclusive). Reversed ranges (end < start) are invalid.',"Use lines=A or lines=A-B (1-based inclusive). Reversed ranges (end < start) are invalid.","Use lines=A or lines=A-B (1-based inclusive). Reversed ranges (end < start) are invalid.");case"line_range_out_of_bounds":return"start is past the end of the field \u2014 check totalLines and request a lower lines range.";case"ambiguous_match":return"find matched more than once \u2014 pass occurrence=N (1-based) or a more unique find string.";case"missing_expected_rev":return _t(t,"Pass expectedRev from list_flows/get_flow (top-level, next to type and payload).","Pass expectedRev from GET /flows or GET /flows/{id} (top-level, next to type and payload).","Pass rev from GET \u2026/flows or \u2026/flows/<id>.");case"missing_query":return _t(t,"Pass a non-empty q string.","q query parameter is required.","q query parameter is required.");case"unsupported_media_type":return"Send Content-Type: application/json with a JSON body.";case"text_not_found":return"Re-read the node field; find must match exactly (case-sensitive).";case"field_not_found":return"Re-read the node; field must be an existing string property (template, func, \u2026).";case"field_not_string":return _t(t,"This tool only returns string fields (template, func, \u2026). Use get_node for other property types.","This endpoint only returns string fields (template, func, \u2026). Use GET /nodes/{id} for other property types.","This endpoint only returns string fields (template, func, \u2026). Use GET /nodes/{id} for other property types.");default:return}}function Tr(e,t,n={}){let r=n.canonicalizeMessage!==!1,o=null;if(e instanceof Je?o={statusCode:409,body:{error:e.message,code:e.code,conflicts:e.conflicts}}:e instanceof Yt?o={statusCode:409,body:{error:e.message,code:e.code,mergedHash:e.mergedHash}}:e instanceof Xt||e instanceof Pt||e instanceof Zt||e instanceof Re?o={statusCode:e.statusCode,body:{error:e.message,code:e.code}}:e instanceof k?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,...e.details??{}}}:e instanceof Q?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,expectedRev:e.expectedRev,currentRev:e.currentRev}}:e instanceof X?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,flowId:e.flowId}}:e instanceof me||e instanceof wt||e instanceof Y||e instanceof ce?o={statusCode:e.statusCode,body:{error:e.message,code:e.code}}:(e instanceof We||e instanceof Le)&&(o={statusCode:e.statusCode,body:{error:e.message,code:e.code}}),!o)return null;let s=o.body.code;if(r&&!Array.isArray(o.body.errors)){let i=ot[s];i&&(o.body.error=i)}if(typeof o.body.hint!="string"||!o.body.hint){let i=ki(s,t,{cause:o.body.cause})??Pr(s);i&&(o.body.hint=i)}return o}function Bd(e,t,n={}){let o={error:n.error??ot[t]??"Request failed",code:t,...n.details??{}},s=ki(t,e,{cause:o.cause})??Pr(t);return s&&(o.hint=s),{statusCode:n.statusCode??400,body:o}}var ot,rt=q(()=>{"use strict";Te();Ve();oe();se();Io();Fo();ot={proposal_conflict:"Proposal conflicts with current editor changes",rebase_ack_required:"Review and acknowledge the merged proposal",rebase_race:"Flows kept changing during approval",deploy_timeout:"Deployment timed out; the deploy outcome is unknown. The proposal remains pending with deployPending set. Approvals wait until the runtime settles or the deploy hard cap is reached. Check the live flow before approving again.",approve_queue_timeout:"Approval timed out waiting in the queue. No deployment was started for this request. Check the live flow and retry.",conflict_not_found:"Conflict no longer exists",invalid_choice:"Invalid conflict choice",flow_id_mismatch:"Proposal flow id cannot change",node_not_found:"Node was not found",flow_not_found:"Flow was not found",field_not_found:"Field was not found",field_not_string:"Field is not a string",missing_query:"q query parameter is required",missing_flow_id:"Missing flow id",missing_node_id:"Missing node id",missing_field:"Missing field name",invalid_line_range:"lines must be A or A-B (1-based inclusive; end >= start)",line_range_out_of_bounds:"lines range is out of bounds",proposal_not_found:"Proposal was not found",proposal_not_owned:"Proposal was not created by this access link",proposal_not_pending:"Proposal is not pending",confirm_required:"Withdraw over GET requires confirm=1",unsupported_media_type:"Content-Type must be application/json",upstream_error:"Upstream request failed",internal_error:"Internal server error"};a(we,"channelErrorHint");a(th,"invalidWiresHint");a(nh,"nodeTypeHint");a(_t,"pickChannel");a(ki,"hintFor");a(Tr,"mapErrorForChannel");a(Bd,"buildChannelError")});var Uy={};Qi(Uy,{ACCESS_LINK_INVALID_MESSAGE:()=>$t,ACCESS_LINK_MISSING_MESSAGE:()=>Ie,AccessLinkStore:()=>an,EDITOR_SIDEBAR_ASSET_VERSION:()=>$n,MAX_PROPOSAL_JSON_BYTES:()=>K,SettingsStore:()=>hn,acceptedMediaTypes:()=>Xc,areAiFeaturesEnabled:()=>Ee,bindSettingsStore:()=>Vr,createPluginServices:()=>hs,default:()=>jy,getStorageDir:()=>ss,isAdminAuthEnabled:()=>re,isMcpStreamRequest:()=>Zc,packageVersion:()=>On,registerRoutes:()=>Qc,renderEditorSidebarJs:()=>Oo,renderEditorSidebarLoaderHtml:()=>Gd,requireAccessLink:()=>bn,resolveAiAccessSettings:()=>Ce});module.exports=iu(Uy);var Yc=require("fs");var jr=require("fs"),Xn=require("path");oe();function Ur(e){let t=(0,Xn.join)(e,".ai-access");return{root:t,proposals:(0,Xn.join)(t,"proposals"),logs:(0,Xn.join)(t,"logs"),settings:(0,Xn.join)(t,"config.json")}}a(Ur,"resolveAiAccessPaths");function ws(e){(0,jr.mkdirSync)(e,{recursive:!0,mode:ys});try{(0,jr.chmodSync)(e,ys)}catch{}}a(ws,"mkdirSecure");function oa(e){ws(e.root),ws(e.proposals),ws(e.logs)}a(oa,"ensureAiAccessDirs");Mt();ze();function Qr(e){let t=Ae(e);return`auth-tokens${(t==="/"?"":t).replace(/\//g,"-")}`}a(Qr,"editorAuthTokenKey");ze();rt();function rh(e){if(!e)return!1;let t=e.split(";")[0]?.trim().toLowerCase()??"";return t==="application/json"||t.endsWith("+json")}a(rh,"isJsonContentType");function oh(e){let t=e.headers["content-length"];if(typeof t=="string"&&t.length>0){let n=Number(t);if(Number.isFinite(n)&&n>0)return!0;if(n===0)return!1}return e.headers["transfer-encoding"]!==void 0}a(oh,"hasRequestBody");function je(e="rest"){return(t,n,r)=>{let o=t.headers["content-type"];if(rh(o)){r();return}if(!o&&!oh(t)){r();return}let s=Bd(e,"unsupported_media_type",{statusCode:415});n.status(s.statusCode).json(s.body)}}a(je,"requireJsonContentType");var sh="text/plain; charset=utf-8",ih="text/html; charset=utf-8",ah="default-src 'none'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'";function dh(e){let t=e.query?.format;return typeof t=="string"?t.toLowerCase():void 0}a(dh,"formatParam");function lh(e){let t=dh(e);if(t==="md"||t==="json")return"native";if(!e.get("accept"))return"html";let n=e.accepts(["text/html","application/json","text/markdown","text/plain"]);return n==="application/json"||n==="text/markdown"?"native":n==="text/plain"?"plain":"html"}a(lh,"chooseGetLinkRepresentation");function qd(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;")}a(qd,"escapeHtml");var ch=/(?<![A-Za-z0-9_.~\-/])\.\/[A-Za-z0-9._~\-/]+(?:\?[A-Za-z0-9._~\-=&%]+)?/g;function uh(e){return e.replace(ch,(t,n)=>{if(e.startsWith("&lt;",n+t.length))return t;let r=t.replace(/[.]+$/,""),o=t.slice(r.length);return r.length<=2||r.includes("..")||r.includes("//")||r.includes("&")?t:`<a href="${r}">${r}</a>${o}`})}a(uh,"linkifyEscaped");function ph(e,t){return`<!doctype html>
11
+ <html><head><meta charset="utf-8">${t?`<base href="${qd(t)}">`:""}<title>Node-RED AI Access</title></head><body><pre>${uh(qd(e))}</pre></body></html>
12
+ `}a(ph,"renderGetLinkHtml");function fh(e){let t=e.split("?")[0]??"",n=/^(.*?\/mcp\/a\/[A-Za-z0-9_-]+)(?:\/|$)/.exec(t);if(!(!n?.[1]||!/^[A-Za-z0-9._~\-/]*$/.test(n[1])))return`${n[1]}/`}a(fh,"getLinkBaseHref");function hh(e){try{return JSON.stringify(JSON.parse(e),null,2)}catch{return e}}a(hh,"prettyJson");function zd(){return(e,t,n)=>{let r=lh(e),o=fh(e.originalUrl);t.vary("Accept"),t.setHeader("Cache-Control","no-store"),t.setHeader("Referrer-Policy","no-referrer"),t.setHeader("X-Robots-Tag","noindex, nofollow"),t.setHeader("X-Content-Type-Options","nosniff");let s=t.send.bind(t);t.send=(i=>{let d=String(t.getHeader("Content-Type")??"").toLowerCase(),l=d.startsWith("application/json"),c=d.startsWith("text/markdown");if(r==="native"||!l&&!c||t.headersSent)return s(i);let u=typeof i=="string"?i:Buffer.isBuffer(i)?i.toString("utf8"):void 0;return u===void 0?s(i):r==="plain"?(t.setHeader("Content-Type",sh),s(u)):(t.setHeader("Content-Type",ih),t.setHeader("Content-Security-Policy",ah),s(ph(l?hh(u):u,o)))}),n()}}a(zd,"getLinkContentNegotiation");Br();ze();function mh(e){let t=xe(e.settings.httpAdminRoot),n=mt(e.settings.httpAdminRoot),r="0.2.0-beta.8",o=[{method:"GET",path:"/",auth:"none",summary:"This discovery index"},{method:"GET",path:"/openapi.yaml",auth:"none",summary:"OpenAPI 3.1 specification (YAML)"},{method:"GET",path:"/openapi.json",auth:"none",summary:"OpenAPI 3.1 specification (JSON)"},{method:"GET",path:"/status",auth:"admin-or-access-link",summary:"Plugin status and caller info"},{method:"GET",path:"/flows",auth:"admin-or-access-link",summary:"List flows + rev"},{method:"GET",path:"/flows/{id}",auth:"admin-or-access-link",summary:"Get one flow tab + rev (?summary=1 for node list without field contents)"},{method:"GET",path:"/nodes/{id}",auth:"admin-or-access-link",summary:"Get one full node (optional ?flow=)"},{method:"GET",path:"/nodes/{id}/field/{name}",auth:"admin-or-access-link",summary:"Get one node field (?lines=A-B optional)"},{method:"GET",path:"/search",auth:"admin-or-access-link",summary:"Text search across string fields (?q=&flow=)"},{method:"GET",path:"/node-types",auth:"admin-or-access-link",summary:"List installed palette node types (?q= filter)"},{method:"GET",path:"/node-types/{type}",auth:"admin-or-access-link",summary:"Detail for one installed node type (defaults JSON, inputs/outputs, help; ?help=0 omits help)"},{method:"GET",path:"/palette/search",auth:"admin-or-access-link",summary:"Search Node-RED catalogue (recommend only; never installs) (?q=)"},{method:"POST",path:"/proposals",auth:"access-link",summary:"Create a replace_flow proposal"},{method:"GET",path:"/proposals",auth:"admin-or-access-link",summary:"List proposals (admin: all; link: own)"},{method:"GET",path:"/proposals/{id}",auth:"admin-or-access-link",summary:"Get one proposal"},{method:"GET",path:"/proposals/{id}/diff",auth:"admin-or-access-link",summary:"Diff proposal vs current flow"}];return{ok:!0,name:"node-red-ai-access",version:r,description:"Temporary, owner-approved Node-RED access for AI clients over plain HTTP (REST) or MCP.",auth:{scheme:"Bearer",header:"Authorization: Bearer <token>",description:"An admin with * mints tokens on the /ai-access dashboard (Access tokens tab) (POST /links only \u2014 never under GET /mcp/a/<token>). Each token has kind get_link | rest | mcp and only works on that surface. Send rest/mcp tokens as Authorization: Bearer <token>. Missing/invalid \u2192 401 access_link_missing / access_link_invalid; wrong kind \u2192 403 wrong_token_kind. GET /mcp/a links cannot create tokens.",tokenKinds:["get_link","rest","mcp"],mint:{path:"/links",method:"POST",auth:"admin",note:"Admin * only. Not available under /mcp/a/<token>."}},openapi:`${t}/openapi.yaml`,openapiJson:`${t}/openapi.json`,mcp:n,apiBase:t,getLink:{base:"/mcp/a/<token>",canMintTokens:!1,note:"Self-contained GET surface for get_link tokens only. No POST /links equivalent."},endpoints:o}}a(mh,"buildDiscoveryIndex");function Ai(e){return(t,n)=>{n.status(200).json(mh(e))}}a(Ai,"discoveryIndexHandler");function Wd(){return(e,t)=>{t.status(404).json({error:"Not found",code:"not_found"})}}a(Wd,"mcpApiNotFoundHandler");var Fn;function On(){return Fn||(Fn="0.2.0-beta.8",Fn)}a(On,"packageVersion");var $n=On();function gh(){return`#red-ui-sidebar-ai-access {
13
13
  position: relative;
14
14
  height: 100%;
15
15
  display: flex;
@@ -165,7 +165,7 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
165
165
  border: 0;
166
166
  background: transparent;
167
167
  color: transparent;
168
- }`}a(Ep,"editorSidebarStyles");function Cp(){return`
168
+ }`}a(gh,"editorSidebarStyles");function yh(){return`
169
169
  /**
170
170
  * Editor right-hand sidebar tab: AI Access.
171
171
  * Served from /mcp/editor/ai-access-sidebar.js (no-store + ?v= cache-bust).
@@ -463,7 +463,7 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
463
463
  saving = false;
464
464
  });
465
465
  }
466
- `}a(Cp,"editorSidebarScriptPartA");function Lp(){return`
466
+ `}a(yh,"editorSidebarScriptPartA");function wh(){return`
467
467
  function buildContent() {
468
468
  var content = $('<div></div>', { id: CONTENT_ID, class: 'red-ui-sidebar-ai-access' });
469
469
 
@@ -565,7 +565,7 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
565
565
  },
566
566
  });
567
567
  })();
568
- `}a(Lp,"editorSidebarScriptPartB");function Ip(){return Cp()+Lp()}a(Ip,"editorSidebarScriptBody");function Np(){return`(function () {
568
+ `}a(wh,"editorSidebarScriptPartB");function bh(){return yh()+wh()}a(bh,"editorSidebarScriptBody");function vh(){return`(function () {
569
569
  if (typeof RED === 'undefined' || typeof BroadcastChannel === 'undefined') return;
570
570
  if (window.__com55AiAccessRevealListener) return;
571
571
  window.__com55AiAccessRevealListener = true;
@@ -596,8 +596,8 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
596
596
  try { window.focus(); } catch (e) {}
597
597
  }
598
598
  };
599
- })();`}a(Np,"editorRevealListenerScript");function Hr(){let e=JSON.stringify(Ep());return[`/* @com55/node-red-ai-access editor sidebar v=${on} */`,"(function(){"," if (typeof document === 'undefined') return;"," if (!document.getElementById('com55-ai-access-sidebar-css')) {"," var style = document.createElement('style');"," style.id = 'com55-ai-access-sidebar-css';",` style.textContent = ${e};`," document.head.appendChild(style);"," }","})();",Np(),Ip()].join(`
600
- `)}a(Hr,"renderEditorSidebarJs");function ua(e=on){return`<script>
599
+ })();`}a(vh,"editorRevealListenerScript");function Oo(){let e=JSON.stringify(gh());return[`/* @com55/node-red-ai-access editor sidebar v=${$n} */`,"(function(){"," if (typeof document === 'undefined') return;"," if (!document.getElementById('com55-ai-access-sidebar-css')) {"," var style = document.createElement('style');"," style.id = 'com55-ai-access-sidebar-css';",` style.textContent = ${e};`," document.head.appendChild(style);"," }","})();",vh(),bh()].join(`
600
+ `)}a(Oo,"renderEditorSidebarJs");function Gd(e=$n){return`<script>
601
601
  /**
602
602
  * Thin loader for the AI Access editor sidebar.
603
603
  * Logic lives at GET /mcp/editor/ai-access-sidebar.js (Cache-Control: no-store)
@@ -631,8 +631,8 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
631
631
  (document.head || document.documentElement).appendChild(s);
632
632
  })();
633
633
  </script>
634
- `}a(ua,"renderEditorSidebarLoaderHtml");J();De();nt();function pa(e){return je[e]??"Request failed"}a(pa,"safeErrorText");function Fp(e,t){let n=M(t,"rest");if(!n)return!1;let r=n.body.code,o={error:pa(r),code:r};return typeof n.body.hint=="string"&&(o.hint=n.body.hint),typeof n.body.expectedRev=="string"&&(o.expectedRev=n.body.expectedRev),typeof n.body.currentRev=="string"&&(o.currentRev=n.body.currentRev),e.setHeader("X-Content-Type-Options","nosniff"),e.status(n.statusCode).json(o),!0}a(Fp,"sendMappedFlowError");function fa(e){return async(t,n)=>{try{n.json(await ye(Me(t,e)))}catch(r){if(Fp(n,r))return;n.setHeader("X-Content-Type-Options","nosniff"),n.status(502).json({error:pa("upstream_error"),code:"upstream_error"})}}}a(fa,"listFlowsHandler");ce();J();Le();De();function sn(e){return e==null||e===""?{ok:!0,format:"summary"}:e==="summary"||e==="full"?{ok:!0,format:e}:{ok:!1}}a(sn,"resolveDiffFormat");function Op(e,t=2){if(e.length===0)return{lines:e,truncated:!1};let n=new Array(e.length).fill(!1);for(let i=0;i<e.length;i+=1){let d=e[i];if(!d||d.op==="equal")continue;let l=Math.max(0,i-t),c=Math.min(e.length-1,i+t);for(let u=l;u<=c;u+=1)n[u]=!0}if(!n.some(Boolean)){let i=Math.min(e.length,t*2+1);for(let d=0;d<i;d+=1)n[d]=!0}let r=[],o=!1,s=0;for(;s<e.length;){if(n[s]){let l=e[s];l&&r.push(l),s+=1;continue}let i=s;for(;i<e.length&&!n[i];)i+=1;let d=i-s;o=!0,r.push({op:"equal",text:`\u2026 (${d} unchanged line${d===1?"":"s"}) \u2026`}),s=i}return{lines:r,truncated:o}}a(Op,"compactLineDiff");function $p(e,t){let n=e.before??"",r=e.after??"",o=Buffer.byteLength(n,"utf8"),s=Buffer.byteLength(r,"utf8");if(t==="full")return{...e,before:n,after:r,beforeBytes:o,afterBytes:s};let i=Op(e.lines),d={key:e.key,kind:"text",beforeBytes:o,afterBytes:s,lines:i.lines};return i.truncated&&(d.truncated=!0),d}a($p,"formatTextChange");function Mp(e,t){return e.map(n=>n.kind==="text"?$p(n,t):n)}a(Mp,"formatPropertyChanges");function ma(e,t){if(t==="full")return e;let n=a(r=>{if(typeof r!="string")return r;let o=Buffer.byteLength(r,"utf8");return o<=4e3?r:`${r.slice(0,500)}
635
- \u2026 (${o} bytes total; use ?format=full for complete text) \u2026`},"trimText");return{...e,keyFields:e.keyFields.map(r=>r.kind==="text"?{...r,value:n(r.value)}:r),properties:e.properties.map(r=>r.kind==="text"?{...r,value:n(r.value)}:r)}}a(ma,"formatPresenceText");function ha(e,t){return{summary:e.summary,added:e.added.map(n=>ma(n,t)),removed:e.removed.map(n=>ma(n,t)),modified:e.modified.map(n=>({...n,changes:Mp(n.changes,t)}))}}a(ha,"formatSections");function an(e,t="summary"){let n=ha(e,t),r={...e,...n,format:t};return e.vsCurrent&&(r.vsCurrent=ha(e.vsCurrent,t)),r}a(an,"applyDiffFormat");G();ce();jr();function dn(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;")}a(dn,"escapeForDocs");var ga="https://YOUR-NODE-RED/mcp/rpc";function Dp(e,t,n,r=ga){return`## Also available (prefer when your client can send headers)
634
+ `}a(Gd,"renderEditorSidebarLoaderHtml");se();rt();Pe();function Kd(e){return ot[e]??"Request failed"}a(Kd,"safeErrorText");function kh(e,t){let n=W(t,"rest");if(!n)return!1;let r=n.body.code,o={error:Kd(r),code:r};return typeof n.body.hint=="string"&&(o.hint=n.body.hint),typeof n.body.expectedRev=="string"&&(o.expectedRev=n.body.expectedRev),typeof n.body.currentRev=="string"&&(o.currentRev=n.body.currentRev),e.setHeader("X-Content-Type-Options","nosniff"),e.status(n.statusCode).json(o),!0}a(kh,"sendMappedFlowError");function Jd(e){return async(t,n)=>{try{n.json(await pt(Xe(t,e)))}catch(r){if(kh(n,r))return;n.setHeader("X-Content-Type-Options","nosniff"),n.status(502).json({error:Kd("upstream_error"),code:"upstream_error"})}}}a(Jd,"listFlowsHandler");ve();se();ze();rt();function Mn(e){return e==null||e===""?{ok:!0,format:"summary"}:e==="summary"||e==="full"?{ok:!0,format:e}:{ok:!1}}a(Mn,"resolveDiffFormat");function Ah(e,t=2){if(e.length===0)return{lines:e,truncated:!1};let n=new Array(e.length).fill(!1);for(let i=0;i<e.length;i+=1){let d=e[i];if(!d||d.op==="equal")continue;let l=Math.max(0,i-t),c=Math.min(e.length-1,i+t);for(let u=l;u<=c;u+=1)n[u]=!0}if(!n.some(Boolean)){let i=Math.min(e.length,t*2+1);for(let d=0;d<i;d+=1)n[d]=!0}let r=[],o=!1,s=0;for(;s<e.length;){if(n[s]){let l=e[s];l&&r.push(l),s+=1;continue}let i=s;for(;i<e.length&&!n[i];)i+=1;let d=i-s;o=!0,r.push({op:"equal",text:`\u2026 (${d} unchanged line${d===1?"":"s"}) \u2026`}),s=i}return{lines:r,truncated:o}}a(Ah,"compactLineDiff");function xh(e,t){let n=e.before??"",r=e.after??"",o=Buffer.byteLength(n,"utf8"),s=Buffer.byteLength(r,"utf8");if(t==="full")return{...e,before:n,after:r,beforeBytes:o,afterBytes:s};let i=Ah(e.lines),d={key:e.key,kind:"text",beforeBytes:o,afterBytes:s,lines:i.lines};return i.truncated&&(d.truncated=!0),d}a(xh,"formatTextChange");function Sh(e,t){return e.map(n=>n.kind==="text"?xh(n,t):n)}a(Sh,"formatPropertyChanges");function Vd(e,t){if(t==="full")return e;let n=a(r=>{if(typeof r!="string")return r;let o=Buffer.byteLength(r,"utf8");return o<=4e3?r:`${r.slice(0,500)}
635
+ \u2026 (${o} bytes total; use ?format=full for complete text) \u2026`},"trimText");return{...e,keyFields:e.keyFields.map(r=>r.kind==="text"?{...r,value:n(r.value)}:r),properties:e.properties.map(r=>r.kind==="text"?{...r,value:n(r.value)}:r)}}a(Vd,"formatPresenceText");function xi(e,t){return{summary:e.summary,...e.tabProperties?{tabProperties:e.tabProperties}:{},added:e.added.map(n=>Vd(n,t)),removed:e.removed.map(n=>Vd(n,t)),modified:e.modified.map(n=>({...n,changes:Sh(n.changes,t)}))}}a(xi,"formatSections");function Dn(e,t="summary"){let n=xi(e,t),r={...e,...n,format:t};return e.liveChanges&&(r.liveChanges=xi(e.liveChanges,t)),e.vsCurrent&&(r.vsCurrent=xi(e.vsCurrent,t)),r}a(Dn,"applyDiffFormat");oe();ve();Io();function Hn(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;")}a(Hn,"escapeForDocs");var Yd="https://YOUR-NODE-RED/mcp/rpc";function Th(e,t,n,r=Yd){return`## Also available (prefer when your client can send headers)
636
636
 
637
637
  **Prefer MCP or REST** whenever the AI client can send custom headers and JSON
638
638
  bodies \u2014 no URL length limits and no chunked drafts. Use this **GET link
@@ -701,12 +701,15 @@ curl -sS -X POST "${r}" \\
701
701
  -H "Accept: application/json, text/event-stream" \\
702
702
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
703
703
  \`\`\`
704
- `}a(Dp,"docsAlsoAvailable");function jp(e,t){return`# Node-RED AI Access \u2014 self-contained GET link (get_link)${e}
704
+ `}a(Th,"docsAlsoAvailable");function Rh(e,t){return`# Node-RED AI Access \u2014 self-contained GET link (get_link)${e}
705
705
 
706
706
  **Git mental model:** a proposal is like a pull request. Human approve = merge;
707
- reject = close; withdraw = you close your own PR. If \`rev\`/\`expectedRev\` does
708
- not match, treat it as a conflict: re-read the flow, rebase the edit, and
709
- resubmit. Every access link can see all proposals (\`mine: true|false\`).
707
+ reject = close; withdraw = you close your own PR. Revision changes auto-rebase
708
+ open proposals. A \`conflict\` blocks approval until resolved. Recent known
709
+ revisions are accepted on submit; unknown revisions return \`version_mismatch\`.
710
+ Open proposals expire after the configured TTL (default 7 days).
711
+ Use REST POST /proposals/{id}/resolve or MCP resolve_proposal_conflict to
712
+ replace your own open proposal with a resolution based on the current flow. Every access link can see all proposals (\`mine: true|false\`).
710
713
 
711
714
  **Which channel to use:** prefer **MCP** (\`/mcp/rpc\` + mcp Bearer) or **REST**
712
715
  (\`/mcp/api/v1\` + rest Bearer) when your client can send headers and JSON \u2014
@@ -776,7 +779,7 @@ fields at the top level** (no \`ok\` / \`proposal\` wrapper). Read
776
779
 
777
780
  Lean by default (no \`payload\` / \`baseFlow\`). Pass
778
781
  \`?include=payload,baseFlow\` or \`?include_payload=true\` to include them.
779
- `}a(jp,"docsIntro");function Hp(){return"## Step-by-step workflow\n\n1. `GET ./status` \u2014 confirm the link works; note `caller.expiresAt` /\n `caller.minutesRemaining`.\n2. `GET ./flows` \u2014 list tabs + current `rev`.\n3. **Layered read (prefer this before loading whole tabs):**\n - `GET ./flows/<id>?summary=1` \u2014 node list with `id`/`type`/`name`/`wires` +\n per-node `bytes` (no field contents).\n - `GET ./search?q=<text>&flow=<id>` \u2014 hits with node id, field, line numbers,\n short snippet (`truncated:true` when the result cap is hit).\n - `GET ./nodes/<id>` \u2014 one full node (`flowId` included when known).\n - `GET ./nodes/<id>/field/<name>?lines=A-B` \u2014 one field (optional 1-based\n line range) plus `totalLines` and `bytes`.\n4. Prefer a **targeted text replace** for small edits inside long string fields\n (`template`, `func`, \u2026) \u2014 unique `find` (or `occurrence`) keeps the diff short:\n\n```\nGET ./propose?flow=<flowId>&rev=<rev>&node=<nodeId>&field=<prop>&find=<text>&replace=<text>&occurrence=<n>&title=<text>\n```\n\n5. Or set whole properties (optional `title` \u226480 chars; `summary` is a title fallback):\n\n```\nGET ./propose?flow=<flowId>&rev=<rev>&node=<nodeId>&set.<prop>=<urlencoded>&title=<text>\n```\n\n6. Or multi-node / add / remove / `replace_text` / full tab via base64url JSON patch:\n\n```\nGET ./propose?flow=<flowId>&rev=<rev>&patch=<base64url-json>&title=<text>\n```\n\n7. Poll `GET ./proposals` / `./proposals/<id>` until approved, rejected, or\n withdrawn. Every token can list/read **all** proposals; each item has\n `mine:true|false`. Filter with `?mine=true` (own) or `?mine=false` (others'),\n `?status=pending,superseded` (comma-separated or repeated), `?flowId=`;\n paginate with `?limit=` (1\u2013200) / `?offset=` (response includes `total`,\n `offset`, `hasMore`, and `nextOffset` when more remain). Invalid filters\n return `400 invalid_filter` naming the parameter. Cite `displayName` +\n `reviewUrl` when updating the user.\n8. Optional: `GET ./proposals/<id>/diff` (default `format=summary` compact\n line hunks + byte sizes; `format=full` for complete text) against the\n **base snapshot** stored at submit. Added/removed nodes are presence blocks\n with a summary sentence; pending+stale may include `vsCurrent`.\n9. Withdraw your own pending proposal:\n `GET ./proposals/<id>/withdraw?confirm=1&reason=<text>` (status becomes `withdrawn`,\n not `rejected`). Only the link that created a proposal can withdraw it;\n others get `403 proposal_not_owned`.\n\n**Recommended edit loop:** summary \u2192 search \u2192 read a line range \u2192\n`replace_text` \u2192 propose.\n\nOn submit and on read of a pending proposal, the response may include\n`conflictingProposals` (same-rev or overlapping pending work; list omits this) with\n`overlappingNodeIds` / `overlappingFields` when detectable \u2014 treat like a\ngit conflict warning; prefer one pending change per flow when possible.\n\n## Node types and palette (read-only)\n\nBefore proposing unfamiliar nodes:\n\n1. `GET ./node-types?q=<text>` \u2014 list installed palette types (filter optional;\n results capped; may include `truncated` / `total`).\n2. `GET ./node-types/<type>` \u2014 detail (`defaults` as JSON, `inputs`/`outputs`,\n `requiredConfigNodeTypes`, help sections). Pass `?help=0` to omit help.\n Types are **case-sensitive** (`Debug` \u2192 did you mean `debug`?).\n3. `GET ./palette/search?q=<text>` \u2014 search the public Node-RED catalogue.\n **Recommend only** \u2014 ask the user to install via **Manage palette**, then\n recheck `./node-types`. This API **never installs** packages.\n Core `node-red` types are **not** in the catalogue \u2014 use `./node-types`.\n\nSubmit/propose with an unknown `node.type` returns `400 unknown_node_type`\nwith `nearest` suggestions \u2014 call node-types / palette search and ask the user\nto install the module, then retry.\n"}a(Hp,"docsWorkflow");function Up(){return'## Exact URLs for this token (relative)\n\n| Purpose | Method + path |\n| --- | --- |\n| These docs | `GET ./` (this URL) |\n| Status | `GET ./status` |\n| List flows | `GET ./flows` |\n| One flow | `GET ./flows/<id>` |\n| Flow summary (no field bodies) | `GET ./flows/<id>?summary=1` |\n| One node | `GET ./nodes/<id>` |\n| One field (optional lines) | `GET ./nodes/<id>/field/<name>?lines=A-B` |\n| Text search | `GET ./search?q=&flow=` |\n| List node types | `GET ./node-types?q=` |\n| One node type | `GET ./node-types/<type>` (`?help=0` omits help) |\n| Palette search (recommend only) | `GET ./palette/search?q=` |\n| All proposals (`?mine=` / `?status=` / `?flowId=` / `?limit=` / `?offset=`) | `GET ./proposals` |\n| One proposal | `GET ./proposals/<id>` |\n| Proposal diff | `GET ./proposals/<id>/diff` |\n| Withdraw own pending | `GET ./proposals/<id>/withdraw?confirm=1&reason=` |\n| Propose (query) | `GET ./propose?\u2026` |\n| Draft start | `GET ./draft/new?flow=&rev=&node=&field=&title=` |\n| Draft chunk | `GET ./draft/<draftId>/append?seq=N&data=<base64url>` |\n| Draft submit | `GET ./draft/<draftId>/submit?sha256=<hex>&title=` |\n\nThere is **no** `POST ./links` (or any create-link) under this URL \u2014 GET links\n**cannot mint tokens**.\n\nUnknown paths under this link return JSON `{ "code": "not_found" }` (never HTML,\nand the token is never echoed). Attempted mint paths (`./links`) return\n`403 link_create_not_supported`.\n'}a(Up,"docsUrlTable");function Bp(){return`## Propose examples
782
+ `}a(Rh,"docsIntro");function Ph(){return"## Step-by-step workflow\n\n1. `GET ./status` \u2014 confirm the link works; note `caller.expiresAt` /\n `caller.minutesRemaining`.\n2. `GET ./flows` \u2014 list tabs + current `rev`.\n3. **Layered read (prefer this before loading whole tabs):**\n - `GET ./flows/<id>?summary=1` \u2014 node list with `id`/`type`/`name`/`wires` +\n per-node `bytes` (no field contents).\n - `GET ./search?q=<text>&flow=<id>` \u2014 hits with node id, field, line numbers,\n short snippet (`truncated:true` when the result cap is hit).\n - `GET ./nodes/<id>` \u2014 one full node (`flowId` included when known).\n - `GET ./nodes/<id>/field/<name>?lines=A-B` \u2014 one field (optional 1-based\n line range) plus `totalLines` and `bytes`.\n4. Prefer a **targeted text replace** for small edits inside long string fields\n (`template`, `func`, \u2026) \u2014 unique `find` (or `occurrence`) keeps the diff short:\n\n```\nGET ./propose?flow=<flowId>&rev=<rev>&node=<nodeId>&field=<prop>&find=<text>&replace=<text>&occurrence=<n>&title=<text>\n```\n\n5. Or set whole properties (optional `title` \u226480 chars; `summary` is a title fallback):\n\n```\nGET ./propose?flow=<flowId>&rev=<rev>&node=<nodeId>&set.<prop>=<urlencoded>&title=<text>\n```\n\n6. Or multi-node / add / remove / `replace_text` / full tab via base64url JSON patch:\n\n```\nGET ./propose?flow=<flowId>&rev=<rev>&patch=<base64url-json>&title=<text>\n```\n\n7. Poll `GET ./proposals` / `./proposals/<id>` until approved, rejected, or\n withdrawn. Every token can list/read **all** proposals; each item has\n `mine:true|false`. Filter with `?mine=true` (own) or `?mine=false` (others'),\n `?status=pending,superseded` (comma-separated or repeated), `?flowId=`;\n paginate with `?limit=` (1\u2013200) / `?offset=` (response includes `total`,\n `offset`, `hasMore`, and `nextOffset` when more remain). Invalid filters\n return `400 invalid_filter` naming the parameter. Cite `displayName` +\n `reviewUrl` when updating the user.\n8. Optional: `GET ./proposals/<id>/diff` (default `format=summary` compact\n line hunks + byte sizes; `format=full` for complete text) against the\n **base snapshot** stored at submit. Added/removed nodes are presence blocks\n with a summary sentence; open proposals include `rebase` and `liveChanges`; `vsCurrent` shows the merged deployment when there are no conflicts.\n9. Withdraw your own open (pending or conflict) proposal:\n `GET ./proposals/<id>/withdraw?confirm=1&reason=<text>` (status becomes `withdrawn`,\n not `rejected`). Only the link that created a proposal can withdraw it;\n others get `403 proposal_not_owned`. If the token is lost, an admin with full permission can withdraw using an authenticated dashboard session via `POST /mcp/api/v1/proposals/<id>/withdraw`; this recovery is audit-logged.\n\n**Recommended edit loop:** summary \u2192 search \u2192 read a line range \u2192\n`replace_text` \u2192 propose.\n\nOn submit and on read of an open (pending or conflict) proposal, the response may include\n`conflictingProposals` (overlapping open work; list omits this) with\n`overlappingNodeIds` / `overlappingFields` when detectable \u2014 treat like a\ngit conflict warning; prefer one pending change per flow when possible.\n\n## Node types and palette (read-only)\n\nBefore proposing unfamiliar nodes:\n\n1. `GET ./node-types?q=<text>` \u2014 list installed palette types (filter optional;\n results capped; may include `truncated` / `total`).\n2. `GET ./node-types/<type>` \u2014 detail (`defaults` as JSON, `inputs`/`outputs`,\n `requiredConfigNodeTypes`, help sections). Pass `?help=0` to omit help.\n Types are **case-sensitive** (`Debug` \u2192 did you mean `debug`?).\n3. `GET ./palette/search?q=<text>` \u2014 search the public Node-RED catalogue.\n **Recommend only** \u2014 ask the user to install via **Manage palette**, then\n recheck `./node-types`. This API **never installs** packages.\n Core `node-red` types are **not** in the catalogue \u2014 use `./node-types`.\n\nSubmit/propose with an unknown `node.type` returns `400 unknown_node_type`\nwith `nearest` suggestions \u2014 call node-types / palette search and ask the user\nto install the module, then retry.\n"}a(Ph,"docsWorkflow");function _h(){return'## Exact URLs for this token (relative)\n\n| Purpose | Method + path |\n| --- | --- |\n| These docs | `GET ./` (this URL) |\n| Status | `GET ./status` |\n| List flows | `GET ./flows` |\n| One flow | `GET ./flows/<id>` |\n| Flow summary (no field bodies) | `GET ./flows/<id>?summary=1` |\n| One node | `GET ./nodes/<id>` |\n| One field (optional lines) | `GET ./nodes/<id>/field/<name>?lines=A-B` |\n| Text search | `GET ./search?q=&flow=` |\n| List node types | `GET ./node-types?q=` |\n| One node type | `GET ./node-types/<type>` (`?help=0` omits help) |\n| Palette search (recommend only) | `GET ./palette/search?q=` |\n| All proposals (`?mine=` / `?status=` / `?flowId=` / `?limit=` / `?offset=`) | `GET ./proposals` |\n| One proposal | `GET ./proposals/<id>` |\n| Proposal diff | `GET ./proposals/<id>/diff` |\n| Withdraw own pending | `GET ./proposals/<id>/withdraw?confirm=1&reason=` |\n| Propose (query) | `GET ./propose?\u2026` |\n| Draft start | `GET ./draft/new?flow=&rev=&node=&field=&title=` |\n| Draft chunk | `GET ./draft/<draftId>/append?seq=N&data=<base64url>` |\n| Draft submit | `GET ./draft/<draftId>/submit?sha256=<hex>&title=` |\n\nThere is **no** `POST ./links` (or any create-link) under this URL \u2014 GET links\n**cannot mint tokens**.\n\nUnknown paths under this link return JSON `{ "code": "not_found" }` (never HTML,\nand the token is never echoed). Attempted mint paths (`./links`) return\n`403 link_create_not_supported`.\n'}a(_h,"docsUrlTable");function Ch(){return`## Propose examples
780
783
 
781
784
  **Recommended for small edits to long fields** \u2014 \`replace_text\` (find must be
782
785
  **unique** unless you pass \`occurrence\` as a 1-based index). Creates a normal
@@ -854,11 +857,11 @@ GET ./propose?flow=tab1&rev=abc123&patch=<base64url>
854
857
  Node-level proposes create a normal \`replace_flow\` proposal; owners should
855
858
  approve with deployment type **\`nodes\`** (plugin default) so only changed
856
859
  nodes deploy.
857
- `}a(Bp,"docsProposeExamples");function qp(){return`## Chunked drafts (long HTML / large fields)
860
+ `}a(Ch,"docsProposeExamples");function Eh(){return`## Chunked drafts (long HTML / large fields)
858
861
 
859
- When the GET URL would exceed **${Ht} bytes**, do not
862
+ When the GET URL would exceed **${wn} bytes**, do not
860
863
  stuff everything into \`propose\`. Use drafts (expire after **30 minutes**,
861
- max **${H}** decoded bytes):
864
+ max **${K}** decoded bytes):
862
865
 
863
866
  ### Recommended chunk size
864
867
 
@@ -903,7 +906,7 @@ Or a full \`replace_flow\` document. JSON-mode drafts **may add nodes**, so
903
906
  \`draft/new\` does not require an existing \`node\`/\`field\`. Use JSON mode for
904
907
  structural changes; use field-mode only when you need to replace one whole
905
908
  string field that is too large for a single propose URL.
906
- `}a(qp,"docsDrafts");function zp(){return`## Last resort: content larger than chunked drafts
909
+ `}a(Eh,"docsDrafts");function Ih(){return`## Last resort: content larger than chunked drafts
907
910
 
908
911
  If the field is still too large for chunked drafts (decoded size over the
909
912
  proposal cap), use this fallback only after drafts fail:
@@ -917,9 +920,9 @@ proposal cap), use this fallback only after drafts fail:
917
920
  **Trade-off:** pasted content is **not** reviewed through the proposal diff.
918
921
  Prefer \`replace_text\` or chunked drafts whenever they fit so the human can
919
922
  still see a readable change on \`/mcp\`.
920
- `}a(zp,"docsLastResortPaste");function Wp(){return`## Size cap and REST/MCP nodes payload shapes
923
+ `}a(Ih,"docsLastResortPaste");function Lh(){return`## Size cap and REST/MCP nodes payload shapes
921
924
 
922
- The proposal JSON cap is **${H} bytes**
925
+ The proposal JSON cap is **${K} bytes**
923
926
  (\`MAX_PROPOSAL_JSON_BYTES\` / ~1.5MB) on **every** channel: GET propose/drafts,
924
927
  REST \`POST /proposals\`, and MCP \`submit_proposal\`. Oversized bodies return
925
928
  \`413 payload_too_large\` (never truncated).
@@ -963,7 +966,7 @@ Example (canonical):
963
966
  **Alternative (also accepted):** top-level \`nodes\` array as an alias for
964
967
  \`payload.nodes\` (same op shapes). Full-tab replace remains supported via
965
968
  \`payload.flow\` (from \`GET ./flows/<id>\` / REST \`GET /flows/{id}\`).
966
- `}a(Wp,"docsRestNodesPayload");function Gp(){return`## Known issues
969
+ `}a(Lh,"docsRestNodesPayload");function Nh(){return`## Known issues
967
970
 
968
971
  - **Rate limits are per token (and per IP).** Each access-link token has its own
969
972
  token bucket. Several tokens can each use the full per-token limit at the same
@@ -971,20 +974,20 @@ Example (canonical):
971
974
  - **\`RateLimit-*\` headers describe the per-token bucket** (default
972
975
  \`RateLimit-Policy: 300;w=60\`) on token requests; requests without a token
973
976
  report the per-IP bucket (\`600;w=60\`). Both are enforced.
974
- `}a(Gp,"docsKnownIssues");function Kp(){return`## Error codes (JSON unless docs)
977
+ `}a(Nh,"docsKnownIssues");function Fh(){return`## Error codes (JSON unless docs)
975
978
 
976
- ${ia()}
979
+ ${Hd()}
977
980
 
978
981
  All JSON responses use \`Cache-Control: no-store\`. Never put secrets in query
979
982
  strings you will paste publicly beyond this temporary link.
980
- `}a(Kp,"docsErrorsAndAlso");function ya(e){let{link:t,openapiPath:n,restApiBase:r,mcpRpcPath:o,publicOrigin:s}=e,i=dn(t.expiresAt),d=t.label?` (${dn(t.label)})`:"",l=dn(n),c=dn(r),u=dn(o);return[jp(d,i),Dp(c,l,u,s?dn(`${s}${o}`):ga),Hp(),Up(),Bp(),Wp(),qp(),zp(),Gp(),Kp()].join(`
981
- `)}a(ya,"renderGetLinkDocs");var Pa=require("crypto");G();J();De();tt();Re();J();function Ur(e){return{error:e instanceof Error&&e.message.trim()?e.message:"Internal server error",code:"internal_error"}}a(Ur,"internalErrorBody");function Jp(e,t){e.headersSent||e.status(500).json(Ur(t))}a(Jp,"sendInternalError");function D(e,t){if(e.headersSent)return;let n=M(t);if(n){e.status(n.statusCode).json(n.body);return}Jp(e,t)}a(D,"sendCaughtRouteError");function _(e){return(t,n,r)=>{Promise.resolve().then(()=>e(t,n,r)).catch(o=>{if(n.headersSent){r(o instanceof Error?o:new Error(String(o)));return}D(n,o)})}}a(_,"asyncRoute");var qr=require("crypto");G();ce();var j=class extends Error{static{a(this,"DraftValidationError")}statusCode=400;code;constructor(t,n="draft_invalid"){super(t),this.name="DraftValidationError",this.code=n}},St=class extends Error{static{a(this,"DraftNotFoundError")}statusCode=404;code="draft_not_found";constructor(t){super(`Draft '${t}' was not found or has expired`),this.name="DraftNotFoundError"}},xt=class extends Error{static{a(this,"DraftAlreadySubmittedError")}statusCode=409;code="draft_already_submitted";proposalId;constructor(t,n){super(`Draft '${t}' was already submitted as proposal '${n}'`),this.name="DraftAlreadySubmittedError",this.proposalId=n}},Vp=/^[A-Za-z0-9_-]+={0,2}$/;function wa(e){if(!Vp.test(e)||e.replace(/=+$/,"").length===0)throw new j("data must be non-empty base64url (A-Z, a-z, 0-9, -, _)","invalid_chunk_data");let t;try{t=Buffer.from(e,"base64url")}catch{throw new j("data must be non-empty base64url (A-Z, a-z, 0-9, -, _)","invalid_chunk_data")}if(t.byteLength===0&&e.length>0)throw new j("data must be non-empty base64url (A-Z, a-z, 0-9, -, _)","invalid_chunk_data");return t}a(wa,"assertValidBase64UrlChunk");var Br=class{static{a(this,"DraftStore")}drafts=new Map;submitted=new Map;now;randomId;maxBytes;ttlMs;constructor(t={}){this.now=t.now??(()=>Date.now()),this.randomId=t.randomId??(()=>(0,qr.randomBytes)(12).toString("hex")),this.maxBytes=t.maxBytes??H,this.ttlMs=t.ttlMs??Zs}purgeExpired(t){for(let[n,r]of this.drafts)r.expiresAtMs<=t&&this.drafts.delete(n);for(let[n,r]of this.submitted)r.expiresAtMs<=t&&this.submitted.delete(n)}create(t){let n=this.now();this.purgeExpired(n);let r=this.randomId(),o={id:r,linkId:t.linkId,flowId:t.flowId,expectedRev:t.expectedRev,mode:t.mode,chunks:new Map,createdAtMs:n,expiresAtMs:n+this.ttlMs,totalBytes:0};return t.nodeId!==void 0&&(o.nodeId=t.nodeId),t.field!==void 0&&(o.field=t.field),t.targets!==void 0&&(o.targets=t.targets),t.summary!==void 0&&(o.summary=t.summary),t.title!==void 0&&(o.title=t.title),this.drafts.set(r,o),o}getForLink(t,n){let r=this.now();this.purgeExpired(r);let o=this.submitted.get(t);if(o?.linkId===n)throw new xt(t,o.proposalId);let s=this.drafts.get(t);if(s?.linkId!==n)throw new St(t);if(s.expiresAtMs<=r)throw new St(t);return s}markSubmitted(t,n,r){let o=this.now();this.drafts.delete(t),this.submitted.set(t,{linkId:n,proposalId:r,expiresAtMs:o+this.ttlMs})}append(t,n,r,o){if(!Number.isInteger(r)||r<0)throw new j("seq must be a non-negative integer","invalid_seq");if(typeof o!="string")throw new j("data must be a base64url string","invalid_chunk");let s=this.getForLink(t,n),i=s.chunks.get(r);if(i!==void 0){if(i!==o)throw new j(`Chunk seq ${r} already stored with different data`,"chunk_conflict")}else{let u=wa(o),p=s.totalBytes+u.byteLength;if(p>this.maxBytes)throw new j(`Draft exceeds limit of ${this.maxBytes} bytes (decoded)`,"payload_too_large");s.chunks.set(r,o),s.totalBytes=p}let d=[...s.chunks.keys()].sort((u,p)=>u-p),l=[],c=d.at(-1);if(c!==void 0)for(let u=0;u<=c;u+=1)s.chunks.has(u)||l.push(u);return{draft:s,receivedSeqs:d,missingSeqs:l,totalBytes:s.totalBytes,maxBytes:this.maxBytes}}getMaxBytes(){return this.maxBytes}assembleAndVerify(t,n,r){let o=this.getForLink(t,n);if(o.chunks.size===0)throw new j("Draft has no chunks","draft_empty");let i=[...o.chunks.keys()].sort((m,h)=>m-h).at(-1);if(i===void 0)throw new j("Draft has no chunks","draft_empty");let d=[];for(let m=0;m<=i;m+=1)o.chunks.has(m)||d.push(m);if(d.length>0)throw new j(`Missing chunk seqs: ${d.join(",")}`,"missing_chunks");let l=[];for(let m=0;m<=i;m+=1){let h=o.chunks.get(m);if(h===void 0)throw new j(`Missing chunk seqs: ${m}`,"missing_chunks");l.push(h)}let c=l.map(m=>wa(m)),u=Buffer.concat(c);if(u.byteLength>this.maxBytes)throw new j(`Assembled draft exceeds limit of ${this.maxBytes} bytes`,"payload_too_large");let p=r.trim().toLowerCase();if(!/^[0-9a-f]{64}$/.test(p))throw new j("invalid_sha256: expected 64 hex chars","invalid_sha256");if((0,qr.createHash)("sha256").update(u).digest("hex")!==p)throw new j("sha256 does not match assembled draft content","draft_hash_mismatch");return{draft:o,assembled:u.toString("utf8")}}size(){return this.purgeExpired(this.now()),this.drafts.size}clear(){this.drafts.clear(),this.submitted.clear()}};Or();pt();ce();var st=yn(require("express-rate-limit"),1);var us=6e4,ba=600,va=300;function cs(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?Math.max(1,Math.floor(e)):t}a(cs,"positiveInt");function Ge(e){if(!e)return{windowMs:us,maxPerIp:ba,maxPerToken:va};let t=e;return{windowMs:cs(t.windowMs??t.rateLimitWindowMs,us),maxPerIp:cs(t.maxPerIp??t.rateLimitMaxPerIp,ba),maxPerToken:cs(t.maxPerToken??t.rateLimitMaxPerToken,va)}}a(Ge,"resolveRateLimitConfig");function Yp(e,t){let r=e.rateLimit?.resetTime;return r instanceof Date&&!Number.isNaN(r.getTime())?Math.max(1,Math.ceil((r.getTime()-Date.now())/1e3)):Math.max(1,Math.ceil(t/1e3))}a(Yp,"retryAfterSeconds");function ka(e,t,n=us){let r=Yp(e,n);t.setHeader("Retry-After",String(r)),t.setHeader("Cache-Control","no-store"),t.status(429).json({error:"Too many requests. Slow down and retry.",code:"rate_limited",retryAfter:r,hint:"Wait retryAfter seconds, then retry. Prefer summary/search reads over full field downloads."})}a(ka,"sendRateLimited");function Xp(e){return(t,n,r,o)=>{ka(t,n,e)}}a(Xp,"rateLimitedHandler");function zr(e){return`ip:${(0,st.ipKeyGenerator)(e.ip??"unknown")}`}a(zr,"clientIpKey");function Wr(e){let t=e.params.token;if(typeof t=="string"&&t.length>0)return`token:${t.slice(0,48)}`;let n=e.get("authorization")??e.headers.authorization;if(typeof n=="string"){let s=/^Bearer\s+(\S+)/i.exec(n.trim())?.[1];if(s&&(s.startsWith("aia_")||s.length>=24))return`token:${s.slice(0,48)}`}let r=e;return r.accessLink?.id?`token:link:${r.accessLink.id}`:null}a(Wr,"accessTokenRateLimitKey");function Aa(e,t,n,r){let o={windowMs:t,max:e,standardHeaders:!0,legacyHeaders:!1,keyGenerator:n,handler:Xp(t)};return r&&(o.skip=r),(0,st.default)(o)}a(Aa,"createLimiter");function ps(e){let t=Ge(e);return Aa(t.maxPerIp,t.windowMs,zr)}a(ps,"createAiAccessRateLimiter");function Zp(e){let t=Ge(e);return Aa(t.maxPerToken,t.windowMs,n=>Wr(n)??zr(n),n=>Wr(n)==null)}a(Zp,"createAiAccessTokenRateLimiter");function Sa(e){let t=Ge(e),n=ps(t),r=Zp(t);return{ip:n,token:r,stack:[n,r],config:t}}a(Sa,"createAiAccessDualRateLimiters");function xa(e){let t=Ge(e()).windowMs,n=Ge(e()).maxPerIp,r=Ge(e()).maxPerToken,o=new st.MemoryStore,s=new st.MemoryStore,i=a(()=>{let h=Ge(e());if(h.windowMs!==t||h.maxPerIp!==n||h.maxPerToken!==r){let g=h.windowMs!==t;t=h.windowMs,n=h.maxPerIp,r=h.maxPerToken,o.resetAll(),s.resetAll(),g&&(o.init?.({windowMs:t}),s.init?.({windowMs:t}))}return h},"refreshWindow"),d=new WeakSet,l=a(h=>{if(d.has(h))return;d.add(h);let g=h.setHeader.bind(h);h.setHeader=((k,T)=>(k.toLowerCase()==="ratelimit-policy"&&typeof T=="string"&&(T=T.replace(/;w=\d+/,`;w=${Math.ceil(t/1e3)}`)),g(k,T)))},"useLiveWindowPolicy"),c=a((h,g)=>(0,st.default)({store:g,windowMs:t,limit:a(()=>{let k=Ge(e());return h?k.maxPerToken:k.maxPerIp},"limit"),standardHeaders:!0,legacyHeaders:!1,keyGenerator:h?k=>Wr(k)??zr(k):zr,skip:a(k=>h&&Wr(k)==null,"skip"),handler:a((k,T,C)=>{ka(k,T,Ge(e()).windowMs)},"handler")}),"make"),u=c(!1,o),p=c(!0,s);return{ip:a((h,g,k)=>(i(),l(g),u(h,g,k)),"ip"),token:a((h,g,k)=>(i(),l(g),p(h,g,k)),"token")}}a(xa,"createLiveAiAccessDualRateLimiters");ue();function O(e){e.setHeader("Cache-Control","no-store"),e.setHeader("Pragma","no-cache")}a(O,"setNoStore");function fs(e){let t=e.originalUrl||e.url||"",n=Po(t);n!==t&&(Object.defineProperty(e,"originalUrl",{value:n,writable:!0,configurable:!0}),typeof e.url=="string"&&Object.defineProperty(e,"url",{value:Po(e.url),writable:!0,configurable:!0}))}a(fs,"redactTokenFromRequestUrl");function Ta(e,t){return(n,r,o)=>{O(r);let s=n.originalUrl||n.url||"";if(n.getLinkUrlBytes=Buffer.byteLength(s,"utf8"),fs(n),!ke(e.settings)){r.status(403).json({error:"AI Access features are disabled. Enable adminAuth, or set aiAccess.allowWithoutAdminAuth to true for local-only use.",code:"ai_features_disabled",hint:"Ask the Node-RED owner to enable AI Access."});return}let i=n.params.token;if(typeof i!="string"||i.length===0){r.setHeader("WWW-Authenticate",X),r.status(401).json({error:me,code:Ie,hint:"Ask the user for a new self-contained access link URL."});return}let d=i;try{d=decodeURIComponent(i)}catch{}let l=t.validateResult(d);if(l.status==="expired"){En(n,r,e.settings.httpAdminRoot,l.expiresAt);return}if(l.status!=="active"){let c=d.length<24||d.startsWith("aia_")&&d.length<40?"The access-link URL may be truncated \u2014 ask the user for the full /mcp/a/<token> link.":"Ask the user for a new self-contained access link URL (it may have expired, or Node-RED may have restarted).";r.setHeader("WWW-Authenticate",X),r.status(401).json({error:ut,code:Pn,hint:c});return}if(l.link.kind!=="get_link"){Cn(r,"get_link",l.link.kind);return}n.accessLink=l.link,n.getLinkToken=d,o()}}a(Ta,"requireGetLinkToken");function ln(e,t){let n=e.getLinkUrlBytes;return(typeof n=="number"?n:Buffer.byteLength(e.originalUrl||e.url||"","utf8"))>Ht?(O(t),t.status(414).json({error:`Request URL exceeds ${Ht} bytes`,code:"url_too_long",hint:"Use chunked drafts: GET \u2026/draft/new then \u2026/draft/<id>/append?seq=&data= then \u2026/draft/<id>/submit?sha256=, or use POST /mcp/api/v1/proposals with Authorization: Bearer <token>.",limitBytes:Ht}),!0):!1}a(ln,"rejectOversizedGetUrl");function I(e,t,n,r,o,s){O(e),e.status(t).json({error:n,code:r,...o?{hint:o}:{},...s})}a(I,"jsonError");function Ra(){return(e,t)=>{fs(e),O(t),t.status(404).json({error:"Not found",code:"not_found"})}}a(Ra,"getLinkNotFoundHandler");var Qp="link_create_not_supported";function _a(){return(e,t)=>{fs(e),O(t),t.status(403).json({error:"GET access links cannot create tokens",code:Qp,hint:"Ask an admin with * permission to mint tokens via POST /mcp/api/v1/links on the /ai-access dashboard (Access tokens tab). This get_link URL cannot create, list, or revoke tokens."})}}a(_a,"getLinkMintNotSupportedHandler");function P(e,t){let n=e[t];if(typeof n=="string"&&n.length>0)return n;if(Array.isArray(n)&&typeof n[0]=="string"&&n[0].length>0)return n[0]}a(P,"queryString");function Pe(e){switch(e){case"draft_not_found":return"Start a new draft with GET \u2026/draft/new.";case"draft_already_submitted":return"This draft already created a proposal; poll that proposal or start a new draft.";case"chunk_conflict":return"Re-send the same data for that seq, or start a new draft.";case"payload_too_large":return"Shrink the content; totalBytes counts decoded bytes against maxBytes.";case"invalid_seq":return"seq must be a non-negative integer (0, 1, 2, \u2026).";case"invalid_chunk":case"invalid_chunk_data":return"data must be base64url of the raw chunk bytes (A-Z, a-z, 0-9, -, _).";case"missing_chunks":return"Upload every seq from 0 through max before submit.";case"invalid_sha256":return"sha256 must be exactly 64 hexadecimal characters.";case"draft_hash_mismatch":return"sha256 must be the hex digest of the assembled UTF-8 bytes (before base64url).";case"draft_empty":return"Append at least one chunk before submit.";case"node_not_found":return"Call GET \u2026/flows/<id> and use a node id from that response (or use mode=json with op:add).";case"field_not_found":return"Use a property that exists on the node, or mode=json to add/replace nodes.";case"missing_draft_params":return"Example: \u2026/draft/new?flow=tab1&rev=abc&node=n1&field=template";case"version_mismatch":return"Re-read the flow and start a new draft with the current rev.";case"protected_property":return"Omit id, type, and z from set props. wires are allowed when targets exist in the flow.";case"invalid_wires":return"wires must list existing node ids (or add those nodes in the same patch).";default:return}}a(Pe,"draftErrorHint");async function ef(e,t,n){let r;try{r=await e.getFlow(t)}catch(s){let i=M(s);if(i)return{code:i.body.code,message:i.body.error};throw s}let o=[...Array.isArray(r.flow.nodes)?r.flow.nodes:[],...Array.isArray(r.flow.configs)?r.flow.configs:[]];for(let s of n){let i=o.find(d=>d.id===s.nodeId);if(!i)return{code:"node_not_found",message:`Node '${s.nodeId}' was not found in flow '${t}'`};if(!(s.field in i))return{code:"field_not_found",message:`Field '${s.field}' was not found on node '${s.nodeId}'`}}return null}a(ef,"validateFieldDraftTargets");function tf(e,t,n,r){if(r)try{let o=Hn(r);if(!Array.isArray(o)||o.length===0)throw new Error("targets must be a non-empty array");return{ok:!0,mode:"field",targets:o.map(i=>{if(!i||typeof i!="object")throw new Error("bad target");let d=i,l=typeof d.nodeId=="string"?d.nodeId:typeof d.node=="string"?d.node:"",c=typeof d.field=="string"?d.field:"";if(!l||!c)throw new Error("bad target");return{nodeId:l,field:c}})}}catch{return{ok:!1,status:400,error:"targets must be base64url JSON array of {nodeId,field}",code:"invalid_targets"}}if(n==="json"||!e&&!t)return{ok:!0,mode:"json"};if(!e||!t){let o=Pe("missing_draft_params");return{ok:!1,status:400,error:"node and field are required for field drafts (or pass mode=json)",code:"missing_draft_params",...o?{hint:o}:{}}}return{ok:!0,mode:"field"}}a(tf,"planDraftMode");async function nf(e,t,n,r){try{let o=await ef(t,n,r);if(o){let s=o.code==="node_not_found"||o.code==="field_not_found"?404:400;return I(e,s,o.message,o.code,Pe(o.code)),!0}}catch(o){let s=M(o);return s?(I(e,s.statusCode,s.body.error,s.body.code,Pe(s.body.code)),!0):(D(e,o),!0)}return!1}a(nf,"rejectInvalidFieldTargets");function Ea(e,t){return async(n,r)=>{if(ln(n,r))return;O(r);let o=P(n.query,"flow"),s=P(n.query,"rev"),i=P(n.query,"node"),d=P(n.query,"field"),l=P(n.query,"summary"),c=P(n.query,"title")??l,u=P(n.query,"mode"),p=P(n.query,"targets");if(!o||!s){I(r,400,"flow and rev query parameters are required","missing_draft_params",Pe("missing_draft_params"));return}if(!n.accessLink){I(r,401,"Access link required","access_link_missing");return}try{let h=await t.getFlow(o);if(h.rev!==s){r.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:s,currentRev:h.rev,hint:Pe("version_mismatch")});return}}catch(h){let g=M(h);if(g){I(r,g.statusCode,g.body.error,g.body.code,g.body.code==="flow_not_found"?de("flow_not_found","get")??"Use an id from GET ./flows":Pe(g.body.code));return}D(r,h);return}let f=tf(i,d,u,p);if(!f.ok){I(r,f.status,f.error,f.code,f.hint);return}if(f.mode==="field"){let h=f.targets&&f.targets.length>0?f.targets:i&&d?[{nodeId:i,field:d}]:[];if(h.length>0&&await nf(r,t,o,h))return}let m=e.create({linkId:n.accessLink.id,flowId:o,expectedRev:s,mode:f.mode,...i?{nodeId:i}:{},...d?{field:d}:{},...f.targets?{targets:f.targets}:{},...l?{summary:l}:{},...c?{title:c}:{}});r.status(201).json({ok:!0,draftId:m.id,mode:m.mode,expiresAt:new Date(m.expiresAtMs).toISOString(),maxBytes:H,hint:"Upload base64url chunks (~4\u20135 KB raw each; base64 adds ~33%; keep URL under ~8 KB) with GET \u2026/draft/<draftId>/append?seq=N&data=\u2026 then submit with sha256."})}}a(Ea,"getLinkDraftNewHandler");function Ca(e){return(t,n)=>{if(ln(t,n))return;O(n);let r=t.params.draftId,o=P(t.query,"seq"),s=P(t.query,"data");if(typeof r!="string"||!r){I(n,400,"Missing draft id","missing_draft_id");return}if(!t.accessLink){I(n,401,"Access link required","access_link_missing");return}if(o===void 0||s===void 0){I(n,400,"seq and data query parameters are required","missing_chunk_params","data must be base64url of the raw chunk bytes.");return}let i=Number(o);try{let d=e.append(r,t.accessLink.id,i,s);n.json({ok:!0,draftId:r,receivedSeqs:d.receivedSeqs,missingSeqs:d.missingSeqs,totalBytes:d.totalBytes,maxBytes:d.maxBytes,hint:d.missingSeqs.length===0?"No gaps so far; call submit when all chunks are uploaded":`Upload missing seqs: ${d.missingSeqs.join(",")}`})}catch(d){if(d instanceof St||d instanceof xt||d instanceof j){I(n,d.statusCode,d.message,d.code,Pe(d.code),d instanceof xt?{proposalId:d.proposalId}:void 0);return}D(n,d);return}}}a(Ca,"getLinkDraftAppendHandler");function rf(e,t){let n=[];if(e.targets&&e.targets.length>0){let r;try{r=JSON.parse(t)}catch{let o=e.targets[0];if(e.targets.length===1&&o)return[{nodeId:o.nodeId,set:{[o.field]:t}}];throw new j("Multi-target draft body must be JSON","invalid_draft_body")}if(Array.isArray(r)){for(let o of r){let s=o;if(!s.nodeId||!s.field)throw new j("Invalid multi-target entry","invalid_draft_body");n.push({nodeId:s.nodeId,set:{[s.field]:s.value}})}return n}if(r&&typeof r=="object"){let o=r;for(let s of e.targets){let i=`${s.nodeId}.${s.field}`;if(!(i in o)&&!(s.field in o))throw new j(`Missing value for ${i} in draft JSON`,"invalid_draft_body");n.push({nodeId:s.nodeId,set:{[s.field]:i in o?o[i]:o[s.field]}})}return n}throw new j("Invalid multi-target draft JSON","invalid_draft_body")}if(e.nodeId&&e.field)return[{nodeId:e.nodeId,set:{[e.field]:t}}];throw new j("Field draft missing node/field","invalid_draft")}a(rf,"editsFromFieldDraft");function La(e,t,n,r,o,s){return async(i,d)=>{if(ln(i,d))return;O(d);let l=i.params.draftId,c=P(i.query,"sha256"),u=P(i.query,"title");if(typeof l!="string"||!l){I(d,400,"Missing draft id","missing_draft_id");return}if(!i.accessLink){I(d,401,"Access link required","access_link_missing");return}if(!c){I(d,400,"sha256 query parameter is required","missing_sha256","Pass sha256 hex of the assembled UTF-8 bytes (before base64url).");return}try{let{draft:p,assembled:f}=e.assembleAndVerify(l,i.accessLink.id,c),m=p.flowId,h,g=p.expectedRev;if(p.mode==="field"){let S=rf(p,f),b=await jn(r,p.flowId,S);if(b.expectedRev!==p.expectedRev){d.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:p.expectedRev,currentRev:b.expectedRev,draftDeleted:!1,hint:"Draft was kept. Re-read the flow and start a new draft with the current rev (or retry if you refresh rev)."});return}h=b.flow,g=b.expectedRev}else{let S=Un(JSON.parse(f),p.flowId);if(m=S.flowId,S.kind==="replace_flow")h={...S.flow,id:S.flow.id||m,type:S.flow.type??"tab",nodes:S.flow.nodes??[]};else{let b=await vt(r,m,S.ops,{expectedRev:p.expectedRev,...s?{nodeTypes:s}:{}});if(b.expectedRev!==p.expectedRev){d.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:p.expectedRev,currentRev:b.expectedRev,draftDeleted:!1,hint:"Draft was kept. Re-read the flow and start a new draft with the current rev."});return}h=b.flow,g=b.expectedRev}}let k=u??p.title??p.summary,T=q(i,o),C=ie(i.query),L=await rt(t,n,i.accessLink,{type:"replace_flow",expectedRev:g,payload:{flowId:m,flow:h},...k?{title:k}:{}},r,T,s);e.markSubmitted(l,i.accessLink.id,L.id);let N={...he(L,{...T,include:C}),mine:!0},Ue=V(t,L.expectedRev,{flowId:L.payload.flowId}),Ce=Oe(t,t.get(L.id),i.accessLink.id);d.status(201).json({ok:!0,proposal:N,sha256:(0,Pa.createHash)("sha256").update(Buffer.from(f,"utf8")).digest("hex"),...$e(Ce),...Z(Ue),hint:`Waiting for human approval. Tell the user about "${N.displayName}" (${N.reviewUrl}). Poll GET \u2026/proposals/<id> for status.`})}catch(p){if(p instanceof xt){I(d,p.statusCode,p.message,p.code,Pe(p.code),{proposalId:p.proposalId});return}if(p instanceof St||p instanceof j){I(d,p.statusCode,p.message,p.code,Pe(p.code));return}if(p instanceof SyntaxError){I(d,400,"Assembled draft is not valid JSON","invalid_draft_body");return}let f=M(p);if(f){d.status(f.statusCode).json({...f.body,...Pe(f.body.code)?{hint:Pe(f.body.code)}:{}});return}D(d,p);return}}}a(La,"getLinkDraftSubmitHandler");J();De();jr();tt();Re();Or();var of={node_already_exists:"Choose a new node id, or use op:set / op:remove on the existing node.",protected_property:"Omit id, type, and z from set props. wires are allowed when targets exist in the flow.",invalid_wires:"wires must list existing node ids (or add those nodes in the same patch).",invalid_patch:"Pass patch as base64url JSON with nodes[{op,\u2026.}] or type/flow for replace_flow.",invalid_patch_json:"Pass patch as base64url JSON with nodes[{op,\u2026.}] or type/flow for replace_flow.",missing_edits:"Provide set.<prop>=value, find+replace+field, or patch nodes with at least one op.",missing_node_id:"Pass node=<id> with set.<prop>= or find/replace, or include id on each patch.nodes entry.",missing_flow_id:"Pass flow=<id> from GET \u2026/flows.",missing_field:"Pass field=<prop> (e.g. template, func) with find= and replace=.",missing_find:"Pass a non-empty find= string (URL-encoded).",invalid_occurrence:"occurrence must be a 1-based index within the match count.",invalid_replace:"Pass replace= as a string (URL-encoded; empty string is allowed to delete the find text).",conflicting_edits:"Use either find/replace/field or set.<prop>=, not both in one propose URL.",invalid_node_type:"node.type must be a string (not a number or object). Use the palette type name exactly.",invalid_node:"add op needs node as a JSON object: { id, type, \u2026 } \u2014 not a string or array.",z_mismatch:"Each node.z must match the flow id (tab id) you are editing."};function ms(e,t){return de(e,"get",{cause:t})??of[e]??zn(e)}a(ms,"proposeErrorHint");function sf(e,t,n){e.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:t,currentRev:n,hint:ms("version_mismatch")})}a(sf,"sendVersionMismatch");async function af(e,t,n,r,o){let s=Un(Hn(t),n),i=s.flowId;if(s.kind==="replace_flow")return{ok:!0,builtFlowId:i,builtFlow:{...s.flow,id:s.flow.id||i,type:s.flow.type??"tab",nodes:s.flow.nodes??[]},expectedRev:r};let d=await vt(e,i,s.ops,{expectedRev:r,...o?{nodeTypes:o}:{}});return d.expectedRev!==r?{ok:!1,kind:"version_mismatch",expectedRev:r,currentRev:d.expectedRev}:{ok:!0,builtFlowId:i,builtFlow:d.flow,expectedRev:d.expectedRev}}a(af,"buildFromPatch");async function df(e,t,n,r,o){let s=await jn(e,t,[{nodeId:n,set:r}]);return s.expectedRev!==o?{ok:!1,kind:"version_mismatch",expectedRev:o,currentRev:s.expectedRev}:{ok:!0,builtFlowId:t,builtFlow:s.flow,expectedRev:s.expectedRev}}a(df,"buildFromSetProps");async function lf(e,t,n,r,o,s,i,d){let l=await vt(e,t,[{op:"replace_text",nodeId:n,field:r,find:o,replace:s,...i!==void 0?{occurrence:i}:{}}]);return l.expectedRev!==d?{ok:!1,kind:"version_mismatch",expectedRev:d,currentRev:l.expectedRev}:{ok:!0,builtFlowId:t,builtFlow:l.flow,expectedRev:l.expectedRev}}a(lf,"buildFromReplaceText");function cf(e){let t=Object.prototype.hasOwnProperty.call(e.query,"replace");return{flowId:P(e.query,"flow"),rev:P(e.query,"rev"),nodeId:P(e.query,"node"),patchEncoded:P(e.query,"patch"),setProps:es(e.query),findText:P(e.query,"find"),replaceText:t?P(e.query,"replace")??"":void 0,fieldName:P(e.query,"field"),occurrenceRaw:P(e.query,"occurrence")}}a(cf,"readProposeQuery");function Ee(e,t,n){I(e,400,n,t,ms(t))}a(Ee,"jsonProposeError");function uf(e,t){if(t===void 0||t==="")return{ok:!0};let n=Number(t);return Number.isFinite(n)?{ok:!0,occurrence:Math.trunc(n)}:(Ee(e,"invalid_occurrence","occurrence must be a positive integer"),{ok:!1})}a(uf,"parseOccurrenceQuery");async function pf(e,t,n,r,o){if(n.patchEncoded)return af(t,n.patchEncoded,n.flowId,r,o);if(n.findText!==void 0||n.replaceText!==void 0||n.fieldName!==void 0){if(Object.keys(n.setProps).length>0)return Ee(e,"conflicting_edits","Cannot combine replace_text (find/replace/field) with set.<prop> in one request"),null;if(!n.flowId)return Ee(e,"missing_flow_id","flow query parameter is required"),null;if(!n.nodeId)return Ee(e,"missing_node_id","node query parameter is required"),null;if(!n.fieldName)return Ee(e,"missing_field","field query parameter is required for replace_text"),null;if(n.findText===void 0||n.findText.length===0)return Ee(e,"missing_find","find query parameter must be a non-empty string"),null;if(n.replaceText===void 0)return Ee(e,"invalid_replace","replace query parameter is required (may be empty)"),null;let i=uf(e,n.occurrenceRaw);return i.ok?lf(t,n.flowId,n.nodeId,n.fieldName,n.findText,n.replaceText,i.occurrence,r):null}return n.flowId?n.nodeId?Object.keys(n.setProps).length===0?(Ee(e,"missing_edits","At least one set.<prop>=value is required"),null):df(t,n.flowId,n.nodeId,n.setProps,r):(Ee(e,"missing_node_id","node query parameter is required (or pass patch=)"),null):(Ee(e,"missing_flow_id","flow query parameter is required"),null)}a(pf,"buildProposeFromQuery");function Ia(e,t,n,r,o){return async(s,i)=>{if(ln(s,i))return;O(i);let d=cf(s),l=P(s.query,"summary"),c=P(s.query,"title")??l;if(!d.rev){Ee(i,"missing_expected_rev","rev query parameter is required");return}if(!s.accessLink){I(i,401,"Access link required","access_link_missing");return}try{let u=await pf(i,n,d,d.rev,o);if(!u)return;if(!u.ok){sf(i,u.expectedRev,u.currentRev);return}let p=q(s,r),f=ie(s.query),m=await rt(e,t,s.accessLink,{type:"replace_flow",expectedRev:u.expectedRev,payload:{flowId:u.builtFlowId,flow:u.builtFlow},...c?{title:c}:{}},n,p,o),h={...he(m,{...p,include:f}),mine:!0},g=V(e,m.expectedRev,{flowId:m.payload.flowId}),k=Oe(e,e.get(m.id),s.accessLink.id),T=m.warnings;i.status(201).json({ok:!0,proposal:h,...l?{summary:l}:{},...$e(k),...Z(g),...T&&T.length>0?{warnings:T}:{},hint:`Waiting for human approval. Tell the user about "${h.displayName}" (${h.reviewUrl}). Poll GET \u2026/proposals/<id> for status.${T&&T.length>0?` Soft warnings (${T.length}) are non-blocking \u2014 configure later if needed.`:""}`})}catch(u){let p=M(u,"get");if(p){let f=ms(p.body.code,p.body.cause);i.status(p.statusCode).json({...p.body,...f?{hint:f}:{}});return}D(i,u)}}}a(Ia,"getLinkProposeHandler");J();De();J();var ff=50,Na=40,mf=20;function hf(e){return Buffer.byteLength(JSON.stringify(e),"utf8")}a(hf,"nodeBytes");function gf(e){let t={id:e.id,type:e.type,bytes:hf(e)};return typeof e.name=="string"&&(t.name=e.name),"wires"in e&&(t.wires=e.wires),t}a(gf,"summarizeNode");async function Gr(e,t){let{rev:n,flow:r}=await _e(e,t),o=r,s=Array.isArray(o.nodes)?o.nodes.map(gf):[],i={rev:n,flowId:o.id||t,nodes:s,nodeCount:s.length};return typeof o.label=="string"&&(i.label=o.label),typeof o.type=="string"&&(i.type=o.type),i}a(Gr,"getFlowSummary");function Fa(e){let t=[];for(let n of e){if(!n||typeof n!="object"||Array.isArray(n))continue;let r=n;(r.type==="tab"||r.type==="subflow"||Array.isArray(r.nodes))&&t.push(r)}return t}a(Fa,"collectTabs");async function Oa(e,t,n){if(!t)throw new y("Missing node id","missing_node_id");if(n){let{rev:d,flow:l}=await _e(e,n),c=l,u=Array.isArray(c.nodes)?c.nodes:[],p=Array.isArray(c.configs)?c.configs:[],f=[...u,...p].find(m=>m.id===t);if(!f)throw new y("Node was not found","node_not_found",{statusCode:404});return{rev:d,flowId:n,node:f}}let{rev:r,flows:o}=await ye(e),s,i=null;for(let d of o){if(!d||typeof d!="object"||Array.isArray(d))continue;let l=d;if(l.id===t&&typeof l.type=="string"&&l.type!=="tab"&&l.type!=="subflow"){s=l,i=typeof l.z=="string"&&l.z?l.z:null;break}}if(!s)for(let d of Fa(o)){let l=d.id;if(l)try{let u=(await _e(e,l)).flow,f=[...u.nodes??[],...u.configs??[]].find(m=>m.id===t);if(f){s=f,i=l;break}}catch{}}if(!s)throw new y("Node was not found","node_not_found",{statusCode:404});return{rev:r,flowId:i,node:s}}a(Oa,"findNodeInFlows");async function Kr(e,t,n){let r=await Oa(e,t,n);return{rev:r.rev,flowId:r.flowId,node:r.node}}a(Kr,"getNodeById");function yf(e){if(e===void 0||e==="")return;let t=/^(\d+)(?:-(\d+))?$/.exec(e.trim());if(!t)throw new y("lines must be A or A-B (1-based inclusive; end >= start)","invalid_line_range");let n=Number(t[1]),r=t[2]!==void 0?Number(t[2]):n;if(!Number.isInteger(n)||!Number.isInteger(r)||n<1)throw new y("lines must be A or A-B (1-based inclusive; end >= start)","invalid_line_range");if(r<n)throw new y("lines must be A or A-B (1-based inclusive; end >= start)","invalid_line_range");return{start:n,end:r}}a(yf,"parseLineRange");async function Jr(e,t,n,r={}){if(!n)throw new y("Missing field name","missing_field");let o=await Oa(e,t,r.flowId);if(!(n in o.node))throw new y("Field was not found on the node","field_not_found");let s=o.node[n];if(typeof s!="string")throw new y("Field is not a string","field_not_string");let i=s.split(`
982
- `),d=i.length,l=Buffer.byteLength(s,"utf8"),c=yf(r.lines);if(!c)return{rev:o.rev,flowId:o.flowId,nodeId:t,field:n,value:s,totalLines:d,bytes:l};if(c.start>d)throw new y(`lines range is out of bounds (start ${c.start} > ${d} total lines)`,"line_range_out_of_bounds",{details:{totalLines:d}});let u=c.start-1,p=Math.min(c.end,i.length),f=i.slice(u,p).join(`
983
- `);return{rev:o.rev,flowId:o.flowId,nodeId:t,field:n,value:f,totalLines:d,bytes:l,lineRange:{start:c.start,end:Math.min(c.end,d)}}}a(Jr,"getNodeField");function wf(e){let t=[];for(let[n,r]of Object.entries(e))n==="id"||n==="type"||n==="z"||n==="wires"||typeof r=="string"&&r.length>0&&t.push({field:n,value:r});return t}a(wf,"stringFields");function bf(e,t,n){let r=Math.max(0,t-Na),o=Math.min(e.length,t+n+Na),s=e.slice(r,o).replace(/\s+/g," ");return r>0&&(s=`\u2026${s}`),o<e.length&&(s=`${s}\u2026`),s}a(bf,"snippetAround");function vf(e,t){let n=1;for(let r=0;r<t&&r<e.length;r+=1)e.charCodeAt(r)===10&&(n+=1);return n}a(vf,"lineNumberAtIndex");function hs(e,t,n,r,o){for(let{field:s,value:i}of wf(e)){if(r.length>=o)return!0;let d=[],l=new Set,c=-1,u=a((f,m)=>{l.has(f)||(l.add(f),d.push(f)),c<0&&(c=m)},"pushLine");if(!n.includes(`
983
+ `}a(Fh,"docsErrorsAndAlso");function Xd(e){let{link:t,openapiPath:n,restApiBase:r,mcpRpcPath:o,publicOrigin:s}=e,i=Hn(t.expiresAt),d=t.label?` (${Hn(t.label)})`:"",l=Hn(n),c=Hn(r),u=Hn(o);return[Rh(d,i),Th(c,l,u,s?Hn(`${s}${o}`):Yd),Ph(),_h(),Ch(),Lh(),Eh(),Ih(),Nh(),Fh()].join(`
984
+ `)}a(Xd,"renderGetLinkDocs");var dl=require("crypto");oe();se();rt();Wt();Oe();nt();se();function $o(e){return{error:e instanceof Error&&e.message.trim()?e.message:"Internal server error",code:"internal_error"}}a($o,"internalErrorBody");function Oh(e,t){e.headersSent||e.status(500).json($o(t))}a(Oh,"sendInternalError");function B(e,t){if(e.headersSent)return;let n=W(t);if(n){e.status(n.statusCode).json(n.body);return}Oh(e,t)}a(B,"sendCaughtRouteError");function $(e){return(t,n,r)=>{Promise.resolve().then(()=>e(t,n,r)).catch(o=>{if(n.headersSent){r(o instanceof Error?o:new Error(String(o)));return}B(n,o)})}}a($,"asyncRoute");var Do=require("crypto");oe();ve();var G=class extends Error{static{a(this,"DraftValidationError")}statusCode=400;code;constructor(t,n="draft_invalid"){super(t),this.name="DraftValidationError",this.code=n}},Qt=class extends Error{static{a(this,"DraftNotFoundError")}statusCode=404;code="draft_not_found";constructor(t){super(`Draft '${t}' was not found or has expired`),this.name="DraftNotFoundError"}},en=class extends Error{static{a(this,"DraftAlreadySubmittedError")}statusCode=409;code="draft_already_submitted";proposalId;constructor(t,n){super(`Draft '${t}' was already submitted as proposal '${n}'`),this.name="DraftAlreadySubmittedError",this.proposalId=n}},$h=/^[A-Za-z0-9_-]+={0,2}$/;function Zd(e){if(!$h.test(e)||e.replace(/=+$/,"").length===0)throw new G("data must be non-empty base64url (A-Z, a-z, 0-9, -, _)","invalid_chunk_data");let t;try{t=Buffer.from(e,"base64url")}catch{throw new G("data must be non-empty base64url (A-Z, a-z, 0-9, -, _)","invalid_chunk_data")}if(t.byteLength===0&&e.length>0)throw new G("data must be non-empty base64url (A-Z, a-z, 0-9, -, _)","invalid_chunk_data");return t}a(Zd,"assertValidBase64UrlChunk");var Mo=class{static{a(this,"DraftStore")}drafts=new Map;submitted=new Map;now;randomId;maxBytes;ttlMs;constructor(t={}){this.now=t.now??(()=>Date.now()),this.randomId=t.randomId??(()=>(0,Do.randomBytes)(12).toString("hex")),this.maxBytes=t.maxBytes??K,this.ttlMs=t.ttlMs??ga}purgeExpired(t){for(let[n,r]of this.drafts)r.expiresAtMs<=t&&this.drafts.delete(n);for(let[n,r]of this.submitted)r.expiresAtMs<=t&&this.submitted.delete(n)}create(t){let n=this.now();this.purgeExpired(n);let r=this.randomId(),o={id:r,linkId:t.linkId,flowId:t.flowId,expectedRev:t.expectedRev,mode:t.mode,chunks:new Map,createdAtMs:n,expiresAtMs:n+this.ttlMs,totalBytes:0};return t.nodeId!==void 0&&(o.nodeId=t.nodeId),t.field!==void 0&&(o.field=t.field),t.targets!==void 0&&(o.targets=t.targets),t.summary!==void 0&&(o.summary=t.summary),t.title!==void 0&&(o.title=t.title),this.drafts.set(r,o),o}getForLink(t,n){let r=this.now();this.purgeExpired(r);let o=this.submitted.get(t);if(o?.linkId===n)throw new en(t,o.proposalId);let s=this.drafts.get(t);if(s?.linkId!==n)throw new Qt(t);if(s.expiresAtMs<=r)throw new Qt(t);return s}markSubmitted(t,n,r){let o=this.now();this.drafts.delete(t),this.submitted.set(t,{linkId:n,proposalId:r,expiresAtMs:o+this.ttlMs})}append(t,n,r,o){if(!Number.isInteger(r)||r<0)throw new G("seq must be a non-negative integer","invalid_seq");if(typeof o!="string")throw new G("data must be a base64url string","invalid_chunk");let s=this.getForLink(t,n),i=s.chunks.get(r);if(i!==void 0){if(i!==o)throw new G(`Chunk seq ${r} already stored with different data`,"chunk_conflict")}else{let u=Zd(o),p=s.totalBytes+u.byteLength;if(p>this.maxBytes)throw new G(`Draft exceeds limit of ${this.maxBytes} bytes (decoded)`,"payload_too_large");s.chunks.set(r,o),s.totalBytes=p}let d=[...s.chunks.keys()].sort((u,p)=>u-p),l=[],c=d.at(-1);if(c!==void 0)for(let u=0;u<=c;u+=1)s.chunks.has(u)||l.push(u);return{draft:s,receivedSeqs:d,missingSeqs:l,totalBytes:s.totalBytes,maxBytes:this.maxBytes}}getMaxBytes(){return this.maxBytes}assembleAndVerify(t,n,r){let o=this.getForLink(t,n);if(o.chunks.size===0)throw new G("Draft has no chunks","draft_empty");let i=[...o.chunks.keys()].sort((m,h)=>m-h).at(-1);if(i===void 0)throw new G("Draft has no chunks","draft_empty");let d=[];for(let m=0;m<=i;m+=1)o.chunks.has(m)||d.push(m);if(d.length>0)throw new G(`Missing chunk seqs: ${d.join(",")}`,"missing_chunks");let l=[];for(let m=0;m<=i;m+=1){let h=o.chunks.get(m);if(h===void 0)throw new G(`Missing chunk seqs: ${m}`,"missing_chunks");l.push(h)}let c=l.map(m=>Zd(m)),u=Buffer.concat(c);if(u.byteLength>this.maxBytes)throw new G(`Assembled draft exceeds limit of ${this.maxBytes} bytes`,"payload_too_large");let p=r.trim().toLowerCase();if(!/^[0-9a-f]{64}$/.test(p))throw new G("invalid_sha256: expected 64 hex chars","invalid_sha256");if((0,Do.createHash)("sha256").update(u).digest("hex")!==p)throw new G("sha256 does not match assembled draft content","draft_hash_mismatch");return{draft:o,assembled:u.toString("utf8")}}size(){return this.purgeExpired(this.now()),this.drafts.size}clear(){this.drafts.clear(),this.submitted.clear()}};Ro();Mt();ve();var Ct=Vn(require("express-rate-limit"),1);var Ti=6e4,Qd=600,el=300;function Si(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?Math.max(1,Math.floor(e)):t}a(Si,"positiveInt");function ft(e){if(!e)return{windowMs:Ti,maxPerIp:Qd,maxPerToken:el};let t=e;return{windowMs:Si(t.windowMs??t.rateLimitWindowMs,Ti),maxPerIp:Si(t.maxPerIp??t.rateLimitMaxPerIp,Qd),maxPerToken:Si(t.maxPerToken??t.rateLimitMaxPerToken,el)}}a(ft,"resolveRateLimitConfig");function Mh(e,t){let r=e.rateLimit?.resetTime;return r instanceof Date&&!Number.isNaN(r.getTime())?Math.max(1,Math.ceil((r.getTime()-Date.now())/1e3)):Math.max(1,Math.ceil(t/1e3))}a(Mh,"retryAfterSeconds");function tl(e,t,n=Ti){let r=Mh(e,n);t.setHeader("Retry-After",String(r)),t.setHeader("Cache-Control","no-store"),t.status(429).json({error:"Too many requests. Slow down and retry.",code:"rate_limited",retryAfter:r,hint:"Wait retryAfter seconds, then retry. Prefer summary/search reads over full field downloads."})}a(tl,"sendRateLimited");function Dh(e){return(t,n,r,o)=>{tl(t,n,e)}}a(Dh,"rateLimitedHandler");function Ho(e){return`ip:${(0,Ct.ipKeyGenerator)(e.ip??"unknown")}`}a(Ho,"clientIpKey");function jo(e){let t=e.params.token;if(typeof t=="string"&&t.length>0)return`token:${t.slice(0,48)}`;let n=e.get("authorization")??e.headers.authorization;if(typeof n=="string"){let s=/^Bearer\s+(\S+)/i.exec(n.trim())?.[1];if(s&&(s.startsWith("aia_")||s.length>=24))return`token:${s.slice(0,48)}`}let r=e;return r.accessLink?.id?`token:link:${r.accessLink.id}`:null}a(jo,"accessTokenRateLimitKey");function nl(e,t,n,r){let o={windowMs:t,max:e,standardHeaders:!0,legacyHeaders:!1,keyGenerator:n,handler:Dh(t)};return r&&(o.skip=r),(0,Ct.default)(o)}a(nl,"createLimiter");function Ri(e){let t=ft(e);return nl(t.maxPerIp,t.windowMs,Ho)}a(Ri,"createAiAccessRateLimiter");function Hh(e){let t=ft(e);return nl(t.maxPerToken,t.windowMs,n=>jo(n)??Ho(n),n=>jo(n)==null)}a(Hh,"createAiAccessTokenRateLimiter");function rl(e){let t=ft(e),n=Ri(t),r=Hh(t);return{ip:n,token:r,stack:[n,r],config:t}}a(rl,"createAiAccessDualRateLimiters");function ol(e){let t=ft(e()).windowMs,n=ft(e()).maxPerIp,r=ft(e()).maxPerToken,o=new Ct.MemoryStore,s=new Ct.MemoryStore,i=a(()=>{let h=ft(e());if(h.windowMs!==t||h.maxPerIp!==n||h.maxPerToken!==r){let g=h.windowMs!==t;t=h.windowMs,n=h.maxPerIp,r=h.maxPerToken,o.resetAll(),s.resetAll(),g&&(o.init?.({windowMs:t}),s.init?.({windowMs:t}))}return h},"refreshWindow"),d=new WeakSet,l=a(h=>{if(d.has(h))return;d.add(h);let g=h.setHeader.bind(h);h.setHeader=((y,w)=>(y.toLowerCase()==="ratelimit-policy"&&typeof w=="string"&&(w=w.replace(/;w=\d+/,`;w=${Math.ceil(t/1e3)}`)),g(y,w)))},"useLiveWindowPolicy"),c=a((h,g)=>(0,Ct.default)({store:g,windowMs:t,limit:a(()=>{let y=ft(e());return h?y.maxPerToken:y.maxPerIp},"limit"),standardHeaders:!0,legacyHeaders:!1,keyGenerator:h?y=>jo(y)??Ho(y):Ho,skip:a(y=>h&&jo(y)==null,"skip"),handler:a((y,w,v)=>{tl(y,w,ft(e()).windowMs)},"handler")}),"make"),u=c(!1,o),p=c(!0,s);return{ip:a((h,g,y)=>(i(),l(g),u(h,g,y)),"ip"),token:a((h,g,y)=>(i(),l(g),p(h,g,y)),"token")}}a(ol,"createLiveAiAccessDualRateLimiters");ke();function z(e){e.setHeader("Cache-Control","no-store"),e.setHeader("Pragma","no-cache")}a(z,"setNoStore");function Pi(e){let t=e.originalUrl||e.url||"",n=Ss(t);n!==t&&(Object.defineProperty(e,"originalUrl",{value:n,writable:!0,configurable:!0}),typeof e.url=="string"&&Object.defineProperty(e,"url",{value:Ss(e.url),writable:!0,configurable:!0}))}a(Pi,"redactTokenFromRequestUrl");function sl(e,t){return(n,r,o)=>{z(r);let s=n.originalUrl||n.url||"";if(n.getLinkUrlBytes=Buffer.byteLength(s,"utf8"),Pi(n),!Ee(e.settings)){r.status(403).json({error:"AI Access features are disabled. Enable adminAuth, or set aiAccess.allowWithoutAdminAuth to true for local-only use.",code:"ai_features_disabled",hint:"Ask the Node-RED owner to enable AI Access."});return}let i=n.params.token;if(typeof i!="string"||i.length===0){r.setHeader("WWW-Authenticate",fe),r.status(401).json({error:Ie,code:at,hint:"Ask the user for a new self-contained access link URL."});return}let d=i;try{d=decodeURIComponent(i)}catch{}let l=t.validateResult(d);if(l.status==="expired"){sr(n,r,e.settings.httpAdminRoot,l.expiresAt);return}if(l.status!=="active"){let c=d.length<24||d.startsWith("aia_")&&d.length<40?"The access-link URL may be truncated \u2014 ask the user for the full /mcp/a/<token> link.":"Ask the user for a new self-contained access link URL (it may have expired, or Node-RED may have restarted).";r.setHeader("WWW-Authenticate",fe),r.status(401).json({error:$t,code:or,hint:c});return}if(l.link.kind!=="get_link"){ir(r,"get_link",l.link.kind);return}n.accessLink=l.link,n.getLinkToken=d,o()}}a(sl,"requireGetLinkToken");function jn(e,t){let n=e.getLinkUrlBytes;return(typeof n=="number"?n:Buffer.byteLength(e.originalUrl||e.url||"","utf8"))>wn?(z(t),t.status(414).json({error:`Request URL exceeds ${wn} bytes`,code:"url_too_long",hint:"Use chunked drafts: GET \u2026/draft/new then \u2026/draft/<id>/append?seq=&data= then \u2026/draft/<id>/submit?sha256=, or use POST /mcp/api/v1/proposals with Authorization: Bearer <token>.",limitBytes:wn}),!0):!1}a(jn,"rejectOversizedGetUrl");function j(e,t,n,r,o,s){z(e),e.status(t).json({error:n,code:r,...o?{hint:o}:{},...s})}a(j,"jsonError");function il(){return(e,t)=>{Pi(e),z(t),t.status(404).json({error:"Not found",code:"not_found"})}}a(il,"getLinkNotFoundHandler");var jh="link_create_not_supported";function al(){return(e,t)=>{Pi(e),z(t),t.status(403).json({error:"GET access links cannot create tokens",code:jh,hint:"Ask an admin with * permission to mint tokens via POST /mcp/api/v1/links on the /ai-access dashboard (Access tokens tab). This get_link URL cannot create, list, or revoke tokens."})}}a(al,"getLinkMintNotSupportedHandler");function H(e,t){let n=e[t];if(typeof n=="string"&&n.length>0)return n;if(Array.isArray(n)&&typeof n[0]=="string"&&n[0].length>0)return n[0]}a(H,"queryString");function Ue(e){switch(e){case"draft_not_found":return"Start a new draft with GET \u2026/draft/new.";case"draft_already_submitted":return"This draft already created a proposal; poll that proposal or start a new draft.";case"chunk_conflict":return"Re-send the same data for that seq, or start a new draft.";case"payload_too_large":return"Shrink the content; totalBytes counts decoded bytes against maxBytes.";case"invalid_seq":return"seq must be a non-negative integer (0, 1, 2, \u2026).";case"invalid_chunk":case"invalid_chunk_data":return"data must be base64url of the raw chunk bytes (A-Z, a-z, 0-9, -, _).";case"missing_chunks":return"Upload every seq from 0 through max before submit.";case"invalid_sha256":return"sha256 must be exactly 64 hexadecimal characters.";case"draft_hash_mismatch":return"sha256 must be the hex digest of the assembled UTF-8 bytes (before base64url).";case"draft_empty":return"Append at least one chunk before submit.";case"node_not_found":return"Call GET \u2026/flows/<id> and use a node id from that response (or use mode=json with op:add).";case"field_not_found":return"Use a property that exists on the node, or mode=json to add/replace nodes.";case"missing_draft_params":return"Example: \u2026/draft/new?flow=tab1&rev=abc&node=n1&field=template";case"version_mismatch":return"Re-read the flow and start a new draft with the current rev.";case"protected_property":return"Omit id, type, and z from set props. wires are allowed when targets exist in the flow.";case"invalid_wires":return"wires must list existing node ids (or add those nodes in the same patch).";default:return}}a(Ue,"draftErrorHint");async function Uh(e,t,n){let r;try{r=await e.getFlow(t)}catch(s){let i=W(s);if(i)return{code:i.body.code,message:i.body.error};throw s}let o=[...Array.isArray(r.flow.nodes)?r.flow.nodes:[],...Array.isArray(r.flow.configs)?r.flow.configs:[]];for(let s of n){let i=o.find(d=>d.id===s.nodeId);if(!i)return{code:"node_not_found",message:`Node '${s.nodeId}' was not found in flow '${t}'`};if(!(s.field in i))return{code:"field_not_found",message:`Field '${s.field}' was not found on node '${s.nodeId}'`}}return null}a(Uh,"validateFieldDraftTargets");function Bh(e,t,n,r){if(r)try{let o=Ar(r);if(!Array.isArray(o)||o.length===0)throw new Error("targets must be a non-empty array");return{ok:!0,mode:"field",targets:o.map(i=>{if(!i||typeof i!="object")throw new Error("bad target");let d=i,l=typeof d.nodeId=="string"?d.nodeId:typeof d.node=="string"?d.node:"",c=typeof d.field=="string"?d.field:"";if(!l||!c)throw new Error("bad target");return{nodeId:l,field:c}})}}catch{return{ok:!1,status:400,error:"targets must be base64url JSON array of {nodeId,field}",code:"invalid_targets"}}if(n==="json"||!e&&!t)return{ok:!0,mode:"json"};if(!e||!t){let o=Ue("missing_draft_params");return{ok:!1,status:400,error:"node and field are required for field drafts (or pass mode=json)",code:"missing_draft_params",...o?{hint:o}:{}}}return{ok:!0,mode:"field"}}a(Bh,"planDraftMode");async function qh(e,t,n,r){try{let o=await Uh(t,n,r);if(o){let s=o.code==="node_not_found"||o.code==="field_not_found"?404:400;return j(e,s,o.message,o.code,Ue(o.code)),!0}}catch(o){let s=W(o);return s?(j(e,s.statusCode,s.body.error,s.body.code,Ue(s.body.code)),!0):(B(e,o),!0)}return!1}a(qh,"rejectInvalidFieldTargets");function ll(e,t){return async(n,r)=>{if(jn(n,r))return;z(r);let o=H(n.query,"flow"),s=H(n.query,"rev"),i=H(n.query,"node"),d=H(n.query,"field"),l=H(n.query,"summary"),c=H(n.query,"title")??l,u=H(n.query,"mode"),p=H(n.query,"targets");if(!o||!s){j(r,400,"flow and rev query parameters are required","missing_draft_params",Ue("missing_draft_params"));return}if(!n.accessLink){j(r,401,"Access link required","access_link_missing");return}try{let h=await(await Sr(t,s)).getFlow(o);if(h.rev!==s){r.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:s,currentRev:h.rev,hint:Ue("version_mismatch")});return}}catch(h){let g=W(h);if(g){j(r,g.statusCode,g.body.error,g.body.code,g.body.code==="flow_not_found"?we("flow_not_found","get")??"Use an id from GET ./flows":Ue(g.body.code));return}B(r,h);return}let f=Bh(i,d,u,p);if(!f.ok){j(r,f.status,f.error,f.code,f.hint);return}if(f.mode==="field"){let h=f.targets&&f.targets.length>0?f.targets:i&&d?[{nodeId:i,field:d}]:[];if(h.length>0&&await qh(r,t,o,h))return}let m=e.create({linkId:n.accessLink.id,flowId:o,expectedRev:s,mode:f.mode,...i?{nodeId:i}:{},...d?{field:d}:{},...f.targets?{targets:f.targets}:{},...l?{summary:l}:{},...c?{title:c}:{}});r.status(201).json({ok:!0,draftId:m.id,mode:m.mode,expiresAt:new Date(m.expiresAtMs).toISOString(),maxBytes:K,hint:"Upload base64url chunks (~4\u20135 KB raw each; base64 adds ~33%; keep URL under ~8 KB) with GET \u2026/draft/<draftId>/append?seq=N&data=\u2026 then submit with sha256."})}}a(ll,"getLinkDraftNewHandler");function cl(e){return(t,n)=>{if(jn(t,n))return;z(n);let r=t.params.draftId,o=H(t.query,"seq"),s=H(t.query,"data");if(typeof r!="string"||!r){j(n,400,"Missing draft id","missing_draft_id");return}if(!t.accessLink){j(n,401,"Access link required","access_link_missing");return}if(o===void 0||s===void 0){j(n,400,"seq and data query parameters are required","missing_chunk_params","data must be base64url of the raw chunk bytes.");return}let i=Number(o);try{let d=e.append(r,t.accessLink.id,i,s);n.json({ok:!0,draftId:r,receivedSeqs:d.receivedSeqs,missingSeqs:d.missingSeqs,totalBytes:d.totalBytes,maxBytes:d.maxBytes,hint:d.missingSeqs.length===0?"No gaps so far; call submit when all chunks are uploaded":`Upload missing seqs: ${d.missingSeqs.join(",")}`})}catch(d){if(d instanceof Qt||d instanceof en||d instanceof G){j(n,d.statusCode,d.message,d.code,Ue(d.code),d instanceof en?{proposalId:d.proposalId}:void 0);return}B(n,d);return}}}a(cl,"getLinkDraftAppendHandler");function zh(e,t){let n=[];if(e.targets&&e.targets.length>0){let r;try{r=JSON.parse(t)}catch{let o=e.targets[0];if(e.targets.length===1&&o)return[{nodeId:o.nodeId,set:{[o.field]:t}}];throw new G("Multi-target draft body must be JSON","invalid_draft_body")}if(Array.isArray(r)){for(let o of r){let s=o;if(!s.nodeId||!s.field)throw new G("Invalid multi-target entry","invalid_draft_body");n.push({nodeId:s.nodeId,set:{[s.field]:s.value}})}return n}if(r&&typeof r=="object"){let o=r;for(let s of e.targets){let i=`${s.nodeId}.${s.field}`;if(!(i in o)&&!(s.field in o))throw new G(`Missing value for ${i} in draft JSON`,"invalid_draft_body");n.push({nodeId:s.nodeId,set:{[s.field]:i in o?o[i]:o[s.field]}})}return n}throw new G("Invalid multi-target draft JSON","invalid_draft_body")}if(e.nodeId&&e.field)return[{nodeId:e.nodeId,set:{[e.field]:t}}];throw new G("Field draft missing node/field","invalid_draft")}a(zh,"editsFromFieldDraft");function ul(e,t,n,r,o,s){return async(i,d)=>{if(jn(i,d))return;z(d);let l=i.params.draftId,c=H(i.query,"sha256"),u=H(i.query,"title");if(typeof l!="string"||!l){j(d,400,"Missing draft id","missing_draft_id");return}if(!i.accessLink){j(d,401,"Access link required","access_link_missing");return}if(!c){j(d,400,"sha256 query parameter is required","missing_sha256","Pass sha256 hex of the assembled UTF-8 bytes (before base64url).");return}try{let{draft:p,assembled:f}=e.assembleAndVerify(l,i.accessLink.id,c),m=p.flowId,h,g=p.expectedRev,y=await Sr(r,p.expectedRev);if(p.mode==="field"){let x=zh(p,f),C=await kr(y,p.flowId,x);if(C.expectedRev!==p.expectedRev){d.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:p.expectedRev,currentRev:C.expectedRev,draftDeleted:!1,hint:"Draft was kept. Re-read the flow and start a new draft with the current rev (or retry if you refresh rev)."});return}h=C.flow,g=C.expectedRev}else{let x=xr(JSON.parse(f),p.flowId);if(m=x.flowId,x.kind==="replace_flow")h={...x.flow,id:x.flow.id||m,type:x.flow.type??"tab",nodes:x.flow.nodes??[]};else{let C=await Gt(y,m,x.ops,{expectedRev:p.expectedRev,...s?{nodeTypes:s}:{}});if(C.expectedRev!==p.expectedRev){d.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:p.expectedRev,currentRev:C.expectedRev,draftDeleted:!1,hint:"Draft was kept. Re-read the flow and start a new draft with the current rev."});return}h=C.flow,g=C.expectedRev}}let w=u??p.title??p.summary,v=V(i,o),L=ge(i.query),P=await Tt(t,n,i.accessLink,{type:"replace_flow",expectedRev:g,payload:{flowId:m,flow:h},...w?{title:w}:{}},r,v,s);e.markSubmitted(l,i.accessLink.id,P.id);let I={...ie(P,{...v,include:L}),mine:!0},D=te(t),T=Qe(t,t.get(P.id),i.accessLink.id);d.status(201).json({ok:!0,proposal:I,sha256:(0,dl.createHash)("sha256").update(Buffer.from(f,"utf8")).digest("hex"),...et(T),...pe(D),hint:`Waiting for human approval. Tell the user about "${I.displayName}" (${I.reviewUrl}). Poll GET \u2026/proposals/<id> for status.`})}catch(p){if(p instanceof en){j(d,p.statusCode,p.message,p.code,Ue(p.code),{proposalId:p.proposalId});return}if(p instanceof Qt||p instanceof G){j(d,p.statusCode,p.message,p.code,Ue(p.code));return}if(p instanceof SyntaxError){j(d,400,"Assembled draft is not valid JSON","invalid_draft_body");return}let f=W(p);if(f){d.status(f.statusCode).json({...f.body,...Ue(f.body.code)?{hint:Ue(f.body.code)}:{}});return}B(d,p);return}}}a(ul,"getLinkDraftSubmitHandler");se();rt();Io();Wt();Oe();nt();Ro();var Wh={node_already_exists:"Choose a new node id, or use op:set / op:remove on the existing node.",protected_property:"Omit id, type, and z from set props. wires are allowed when targets exist in the flow.",invalid_wires:"wires must list existing node ids (or add those nodes in the same patch).",invalid_patch:"Pass patch as base64url JSON with nodes[{op,\u2026.}] or type/flow for replace_flow.",invalid_patch_json:"Pass patch as base64url JSON with nodes[{op,\u2026.}] or type/flow for replace_flow.",missing_edits:"Provide set.<prop>=value, find+replace+field, or patch nodes with at least one op.",missing_node_id:"Pass node=<id> with set.<prop>= or find/replace, or include id on each patch.nodes entry.",missing_flow_id:"Pass flow=<id> from GET \u2026/flows.",missing_field:"Pass field=<prop> (e.g. template, func) with find= and replace=.",missing_find:"Pass a non-empty find= string (URL-encoded).",invalid_occurrence:"occurrence must be a 1-based index within the match count.",invalid_replace:"Pass replace= as a string (URL-encoded; empty string is allowed to delete the find text).",conflicting_edits:"Use either find/replace/field or set.<prop>=, not both in one propose URL.",invalid_node_type:"node.type must be a string (not a number or object). Use the palette type name exactly.",invalid_node:"add op needs node as a JSON object: { id, type, \u2026 } \u2014 not a string or array.",z_mismatch:"Each node.z must match the flow id (tab id) you are editing."};function _i(e,t){return we(e,"get",{cause:t})??Wh[e]??Pr(e)}a(_i,"proposeErrorHint");function Gh(e,t,n){e.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:t,currentRev:n,hint:_i("version_mismatch")})}a(Gh,"sendVersionMismatch");async function Kh(e,t,n,r,o){let s=xr(Ar(t),n),i=s.flowId;if(s.kind==="replace_flow")return{ok:!0,builtFlowId:i,builtFlow:{...s.flow,id:s.flow.id||i,type:s.flow.type??"tab",nodes:s.flow.nodes??[]},expectedRev:r};let d=await Gt(e,i,s.ops,{expectedRev:r,...o?{nodeTypes:o}:{}});return d.expectedRev!==r?{ok:!1,kind:"version_mismatch",expectedRev:r,currentRev:d.expectedRev}:{ok:!0,builtFlowId:i,builtFlow:d.flow,expectedRev:d.expectedRev}}a(Kh,"buildFromPatch");async function Jh(e,t,n,r,o){let s=await kr(e,t,[{nodeId:n,set:r}]);return s.expectedRev!==o?{ok:!1,kind:"version_mismatch",expectedRev:o,currentRev:s.expectedRev}:{ok:!0,builtFlowId:t,builtFlow:s.flow,expectedRev:s.expectedRev}}a(Jh,"buildFromSetProps");async function Vh(e,t,n,r,o,s,i,d){let l=await Gt(e,t,[{op:"replace_text",nodeId:n,field:r,find:o,replace:s,...i!==void 0?{occurrence:i}:{}}]);return l.expectedRev!==d?{ok:!1,kind:"version_mismatch",expectedRev:d,currentRev:l.expectedRev}:{ok:!0,builtFlowId:t,builtFlow:l.flow,expectedRev:l.expectedRev}}a(Vh,"buildFromReplaceText");function Yh(e){let t=Object.prototype.hasOwnProperty.call(e.query,"replace");return{flowId:H(e.query,"flow"),rev:H(e.query,"rev"),nodeId:H(e.query,"node"),patchEncoded:H(e.query,"patch"),setProps:ci(e.query),findText:H(e.query,"find"),replaceText:t?H(e.query,"replace")??"":void 0,fieldName:H(e.query,"field"),occurrenceRaw:H(e.query,"occurrence")}}a(Yh,"readProposeQuery");function Be(e,t,n){j(e,400,n,t,_i(t))}a(Be,"jsonProposeError");function Xh(e,t){if(t===void 0||t==="")return{ok:!0};let n=Number(t);return Number.isFinite(n)?{ok:!0,occurrence:Math.trunc(n)}:(Be(e,"invalid_occurrence","occurrence must be a positive integer"),{ok:!1})}a(Xh,"parseOccurrenceQuery");async function Zh(e,t,n,r,o){if(n.patchEncoded)return Kh(t,n.patchEncoded,n.flowId,r,o);if(n.findText!==void 0||n.replaceText!==void 0||n.fieldName!==void 0){if(Object.keys(n.setProps).length>0)return Be(e,"conflicting_edits","Cannot combine replace_text (find/replace/field) with set.<prop> in one request"),null;if(!n.flowId)return Be(e,"missing_flow_id","flow query parameter is required"),null;if(!n.nodeId)return Be(e,"missing_node_id","node query parameter is required"),null;if(!n.fieldName)return Be(e,"missing_field","field query parameter is required for replace_text"),null;if(n.findText===void 0||n.findText.length===0)return Be(e,"missing_find","find query parameter must be a non-empty string"),null;if(n.replaceText===void 0)return Be(e,"invalid_replace","replace query parameter is required (may be empty)"),null;let i=Xh(e,n.occurrenceRaw);return i.ok?Vh(t,n.flowId,n.nodeId,n.fieldName,n.findText,n.replaceText,i.occurrence,r):null}return n.flowId?n.nodeId?Object.keys(n.setProps).length===0?(Be(e,"missing_edits","At least one set.<prop>=value is required"),null):Jh(t,n.flowId,n.nodeId,n.setProps,r):(Be(e,"missing_node_id","node query parameter is required (or pass patch=)"),null):(Be(e,"missing_flow_id","flow query parameter is required"),null)}a(Zh,"buildProposeFromQuery");function pl(e,t,n,r,o){return async(s,i)=>{if(jn(s,i))return;z(i);let d=Yh(s),l=H(s.query,"summary"),c=H(s.query,"title")??l;if(!d.rev){Be(i,"missing_expected_rev","rev query parameter is required");return}if(!s.accessLink){j(i,401,"Access link required","access_link_missing");return}try{let u=await Sr(n,d.rev),p=await Zh(i,u,d,d.rev,o);if(!p)return;if(!p.ok){Gh(i,p.expectedRev,p.currentRev);return}let f=V(s,r),m=ge(s.query),h=await Tt(e,t,s.accessLink,{type:"replace_flow",expectedRev:p.expectedRev,payload:{flowId:p.builtFlowId,flow:p.builtFlow},...c?{title:c}:{}},n,f,o),g={...ie(h,{...f,include:m}),mine:!0},y=te(e),w=Qe(e,e.get(h.id),s.accessLink.id),v=h.warnings;i.status(201).json({ok:!0,proposal:g,...l?{summary:l}:{},...et(w),...pe(y),...v&&v.length>0?{warnings:v}:{},hint:`Waiting for human approval. Tell the user about "${g.displayName}" (${g.reviewUrl}). Poll GET \u2026/proposals/<id> for status.${v&&v.length>0?` Soft warnings (${v.length}) are non-blocking \u2014 configure later if needed.`:""}`})}catch(u){let p=W(u,"get");if(p){let f=_i(p.body.code,p.body.cause);i.status(p.statusCode).json({...p.body,...f?{hint:f}:{}});return}B(i,u)}}}a(pl,"getLinkProposeHandler");se();rt();se();var Qh=50,fl=40,em=20;function tm(e){return Buffer.byteLength(JSON.stringify(e),"utf8")}a(tm,"nodeBytes");function nm(e){let t={id:e.id,type:e.type,bytes:tm(e)};return typeof e.name=="string"&&(t.name=e.name),"wires"in e&&(t.wires=e.wires),t}a(nm,"summarizeNode");async function Uo(e,t){let{rev:n,flow:r}=await He(e,t),o=r,s=Array.isArray(o.nodes)?o.nodes.map(nm):[],i={rev:n,flowId:o.id||t,nodes:s,nodeCount:s.length};return typeof o.label=="string"&&(i.label=o.label),typeof o.type=="string"&&(i.type=o.type),i}a(Uo,"getFlowSummary");function hl(e){let t=[];for(let n of e){if(!n||typeof n!="object"||Array.isArray(n))continue;let r=n;(r.type==="tab"||r.type==="subflow"||Array.isArray(r.nodes))&&t.push(r)}return t}a(hl,"collectTabs");async function ml(e,t,n){if(!t)throw new k("Missing node id","missing_node_id");if(n){let{rev:d,flow:l}=await He(e,n),c=l,u=Array.isArray(c.nodes)?c.nodes:[],p=Array.isArray(c.configs)?c.configs:[],f=[...u,...p].find(m=>m.id===t);if(!f)throw new k("Node was not found","node_not_found",{statusCode:404});return{rev:d,flowId:n,node:f}}let{rev:r,flows:o}=await pt(e),s,i=null;for(let d of o){if(!d||typeof d!="object"||Array.isArray(d))continue;let l=d;if(l.id===t&&typeof l.type=="string"&&l.type!=="tab"&&l.type!=="subflow"){s=l,i=typeof l.z=="string"&&l.z?l.z:null;break}}if(!s)for(let d of hl(o)){let l=d.id;if(l)try{let u=(await He(e,l)).flow,f=[...u.nodes??[],...u.configs??[]].find(m=>m.id===t);if(f){s=f,i=l;break}}catch{}}if(!s)throw new k("Node was not found","node_not_found",{statusCode:404});return{rev:r,flowId:i,node:s}}a(ml,"findNodeInFlows");async function Bo(e,t,n){let r=await ml(e,t,n);return{rev:r.rev,flowId:r.flowId,node:r.node}}a(Bo,"getNodeById");function rm(e){if(e===void 0||e==="")return;let t=/^(\d+)(?:-(\d+))?$/.exec(e.trim());if(!t)throw new k("lines must be A or A-B (1-based inclusive; end >= start)","invalid_line_range");let n=Number(t[1]),r=t[2]!==void 0?Number(t[2]):n;if(!Number.isInteger(n)||!Number.isInteger(r)||n<1)throw new k("lines must be A or A-B (1-based inclusive; end >= start)","invalid_line_range");if(r<n)throw new k("lines must be A or A-B (1-based inclusive; end >= start)","invalid_line_range");return{start:n,end:r}}a(rm,"parseLineRange");async function qo(e,t,n,r={}){if(!n)throw new k("Missing field name","missing_field");let o=await ml(e,t,r.flowId);if(!(n in o.node))throw new k("Field was not found on the node","field_not_found");let s=o.node[n];if(typeof s!="string")throw new k("Field is not a string","field_not_string");let i=s.split(`
985
+ `),d=i.length,l=Buffer.byteLength(s,"utf8"),c=rm(r.lines);if(!c)return{rev:o.rev,flowId:o.flowId,nodeId:t,field:n,value:s,totalLines:d,bytes:l};if(c.start>d)throw new k(`lines range is out of bounds (start ${c.start} > ${d} total lines)`,"line_range_out_of_bounds",{details:{totalLines:d}});let u=c.start-1,p=Math.min(c.end,i.length),f=i.slice(u,p).join(`
986
+ `);return{rev:o.rev,flowId:o.flowId,nodeId:t,field:n,value:f,totalLines:d,bytes:l,lineRange:{start:c.start,end:Math.min(c.end,d)}}}a(qo,"getNodeField");function om(e){let t=[];for(let[n,r]of Object.entries(e))n==="id"||n==="type"||n==="z"||n==="wires"||typeof r=="string"&&r.length>0&&t.push({field:n,value:r});return t}a(om,"stringFields");function sm(e,t,n){let r=Math.max(0,t-fl),o=Math.min(e.length,t+n+fl),s=e.slice(r,o).replace(/\s+/g," ");return r>0&&(s=`\u2026${s}`),o<e.length&&(s=`${s}\u2026`),s}a(sm,"snippetAround");function im(e,t){let n=1;for(let r=0;r<t&&r<e.length;r+=1)e.charCodeAt(r)===10&&(n+=1);return n}a(im,"lineNumberAtIndex");function Ci(e,t,n,r,o){for(let{field:s,value:i}of om(e)){if(r.length>=o)return!0;let d=[],l=new Set,c=-1,u=a((f,m)=>{l.has(f)||(l.add(f),d.push(f)),c<0&&(c=m)},"pushLine");if(!n.includes(`
984
987
  `)){let f=i.split(`
985
- `);for(let m=0;m<f.length;m+=1){let g=(f[m]??"").indexOf(n);if(g>=0){let k=0;for(let T=0;T<m;T+=1)k+=(f[T]?.length??0)+1;u(m+1,k+g)}}}if(n.includes(`
986
- `)){let f=0;for(;f<=i.length;){let m=i.indexOf(n,f);if(m<0)break;u(vf(i,m),m),f=m+Math.max(1,n.length)}}if(d.length===0)continue;let p=d.length;r.push({flowId:t,nodeId:e.id,field:s,lines:d.slice(0,mf),lineCount:p,snippet:bf(i,Math.max(0,c),n.length)})}return r.length>=o}a(hs,"searchInNode");function $a(e){return[...e.nodes??[],...e.configs??[]]}a($a,"nodePool");async function kf(e,t,n,r,o){let s=await _e(e,t);for(let i of $a(s.flow))if(hs(i,t,n,r,o))return{rev:s.rev,truncated:!0};return{rev:s.rev,truncated:!1}}a(kf,"searchOneFlow");async function Af(e,t,n,r){let o=await ye(e),s=new Set;for(let i of Fa(o.flows))if(i.id)try{let d=await _e(e,i.id);for(let l of $a(d.flow))if(s.add(l.id),hs(l,i.id,t,n,r))return{rev:o.rev,truncated:!0}}catch{}for(let i of o.flows){if(!i||typeof i!="object"||Array.isArray(i))continue;let d=i;if(!d.id||s.has(d.id)||d.type==="tab"||d.type==="subflow")continue;let l=typeof d.z=="string"?d.z:null;if(hs(d,l,t,n,r))return{rev:o.rev,truncated:!0}}return{rev:o.rev,truncated:!1}}a(Af,"searchAllFlows");async function Vr(e,t,n={}){if(typeof t!="string"||!t.trim())throw new y("q query parameter is required","missing_query");let r=typeof n.cap=="number"&&n.cap>0?Math.floor(n.cap):ff,o=[],s=n.flowId?await kf(e,n.flowId,t,o,r):await Af(e,t,o,r),i={rev:s.rev,q:t,hits:o,hitCount:o.length,truncated:s.truncated,cap:r};return n.flowId&&(i.flowId=n.flowId),i}a(Vr,"searchFlows");function Ma(e){if(e===void 0)return!1;let t=e.toLowerCase();return t==="1"||t==="true"||t==="yes"}a(Ma,"isTruthyQueryFlag");nt();function Sf(e,t,n){switch(e){case"node_not_found":case"flow_not_found":case"field_not_found":return n??de(e,t);default:return de(e,t)}}a(Sf,"layeredReadErrorHint");function Tt(e){return je[e]??"Request failed"}a(Tt,"safeErrorText");function we(e,t,n){e.setHeader("X-Content-Type-Options","nosniff"),e.setHeader("Content-Type","application/json; charset=utf-8"),e.status(t).json(n)}a(we,"sendJson");function Yr(e,t,n,r){let o=M(t,n);if(!o)return!1;let s=o.body.code,i={error:Tt(s),code:s};typeof o.body.matchCount=="number"&&(i.matchCount=o.body.matchCount),typeof o.body.totalLines=="number"&&(i.totalLines=o.body.totalLines),typeof o.body.expectedRev=="string"&&(i.expectedRev=o.body.expectedRev),typeof o.body.currentRev=="string"&&(i.currentRev=o.body.currentRev);let d=Sf(s,n,r);return d&&(i.hint=d),we(e,o.statusCode,i),!0}a(Yr,"sendMappedError");function Xr(e){we(e,502,{error:Tt("upstream_error"),code:"upstream_error"})}a(Xr,"sendUpstreamError");function Zr(e,t="rest"){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){we(r,400,{error:Tt("missing_flow_id"),code:"missing_flow_id"});return}let s=Ma(P(n.query,"summary"));try{if(s){we(r,200,await Gr(Me(n,e),o));return}we(r,200,await _e(Me(n,e),o))}catch(i){if(Yr(r,i,t))return;Xr(r)}}}a(Zr,"getFlowOrSummaryHandler");function Qr(e,t="rest"){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||!o){we(r,400,{error:Tt("missing_node_id"),code:"missing_node_id"});return}let s=P(n.query,"flow");try{we(r,200,await Kr(Me(n,e),o,s))}catch(i){if(Yr(r,i,t,t==="get"?"Call GET \u2026/flows/<id>?summary=1 or \u2026/search?q=\u2026":t==="mcp"?void 0:"Call GET \u2026/flows/<id>?summary=1 or \u2026/search?q=\u2026"))return;Xr(r)}}}a(Qr,"getNodeHandler");function eo(e,t="rest"){return async(n,r)=>{let o=n.params.id,s=n.params.name;if(typeof o!="string"||!o){we(r,400,{error:Tt("missing_node_id"),code:"missing_node_id"});return}if(typeof s!="string"||!s){we(r,400,{error:Tt("missing_field"),code:"missing_field"});return}let i=P(n.query,"flow"),d=P(n.query,"lines");try{we(r,200,await Jr(Me(n,e),o,s,{...i?{flowId:i}:{},...d?{lines:d}:{}}))}catch(l){if(Yr(r,l,t,"Use summary/search to discover node ids and fields."))return;Xr(r)}}}a(eo,"getNodeFieldHandler");function to(e,t="rest"){return async(n,r)=>{let o=P(n.query,"q");if(!o){we(r,400,{error:Tt("missing_query"),code:"missing_query",hint:t==="get"?"Example: GET \u2026/search?q=TODO&flow=tab1":de("missing_query",t)});return}let s=P(n.query,"flow");try{we(r,200,await Vr(Me(n,e),o,s?{flowId:s}:{}))}catch(i){if(Yr(r,i,t))return;Xr(r)}}}a(to,"searchFlowsHandler");J();wt();var xf={missing_node_type:"Missing node type",missing_query:"q query parameter is required",node_type_not_found:"Node type was not found",catalogue_unavailable:"Node-RED catalogue unavailable",unknown_node_type:"Unknown node type",upstream_error:"Upstream error"};function Wn(e){return xf[e]??"Request failed"}a(Wn,"safeErrorText");function it(e,t,n){e.setHeader("X-Content-Type-Options","nosniff"),e.setHeader("Content-Type","application/json; charset=utf-8"),e.setHeader("Cache-Control","no-store"),e.status(t).json(n)}a(it,"sendJson");function gs(e,t){let n=M(t);if(!n)return!1;let r=n.body.code,o={error:Wn(r),code:r};return typeof n.body.type=="string"&&(o.type=n.body.type),Array.isArray(n.body.nearest)&&(o.nearest=n.body.nearest),typeof n.body.didYouMean=="string"?(o.didYouMean=n.body.didYouMean,o.caseSensitive=!0,o.hint=`Types are case-sensitive \u2014 use '${n.body.didYouMean}' exactly.`):(r==="node_type_not_found"||r==="unknown_node_type")&&(o.hint="Call GET \u2026/node-types?q= or \u2026/palette/search?q=. Ask the user to install via Manage palette, then recheck node-types. This API never installs."),r==="catalogue_unavailable"&&(o.hint="Retry later; palette search is recommend-only and never installs packages."),it(e,n.statusCode,o),!0}a(gs,"sendMappedError");function no(e){return async(t,n)=>{try{let r=P(t.query,"q"),o=await xr(e.nodeTypes,r?{q:r}:{});it(n,200,o)}catch(r){if(gs(n,r))return;it(n,502,{error:Wn("upstream_error"),code:"upstream_error"})}}}a(no,"listNodeTypesHandler");function ro(e){return async(t,n)=>{try{let r=t.params.type,o=typeof r=="string"?decodeURIComponent(r):"",s=P(t.query,"help"),i=!(s==="0"||s==="false"||s==="no"||s==="omit"),d=await yt(e.nodeTypes,o,{includeHelp:i});it(n,200,d)}catch(r){if(gs(n,r))return;it(n,502,{error:Wn("upstream_error"),code:"upstream_error"})}}}a(ro,"getNodeTypeHandler");function oo(e){return async(t,n)=>{try{let r=P(t.query,"q");if(!r){it(n,400,{error:Wn("missing_query"),code:"missing_query",hint:"Pass ?q= to search the Node-RED catalogue (recommend only)."});return}let o=await Tr(e.nodeTypes,r,{...e.catalogueFetcher?{catalogueFetcher:e.catalogueFetcher}:{},...e.catalogueUrl?{catalogueUrl:e.catalogueUrl}:{}});it(n,200,o)}catch(r){if(gs(n,r))return;it(n,502,{error:Wn("upstream_error"),code:"upstream_error"})}}}a(oo,"searchPaletteHandler");tt();xe();function cn(e,t){return e[t]??""}a(cn,"lineAt");function Gn(e,t,n){let r=e[t];return r?r[n]??0:0}a(Gn,"cell");function Da(e,t,n,r){let o=e[t];o&&(o[n]=r)}a(Da,"setCell");function Ha(e,t){let n=ja(e),r=ja(t),o=n.length,s=r.length,i=[];for(let u=0;u<=o;u+=1){let p=[];for(let f=0;f<=s;f+=1)p.push(0);i.push(p)}for(let u=o-1;u>=0;u-=1)for(let p=s-1;p>=0;p-=1)cn(n,u)===cn(r,p)?Da(i,u,p,Gn(i,u+1,p+1)+1):Da(i,u,p,Math.max(Gn(i,u+1,p),Gn(i,u,p+1)));let d=[],l=0,c=0;for(;l<o&&c<s;){let u=cn(n,l),p=cn(r,c);u===p?(d.push({op:"equal",text:u}),l+=1,c+=1):Gn(i,l+1,c)>=Gn(i,l,c+1)?(d.push({op:"remove",text:u}),l+=1):(d.push({op:"add",text:p}),c+=1)}for(;l<o;)d.push({op:"remove",text:cn(n,l)}),l+=1;for(;c<s;)d.push({op:"add",text:cn(r,c)}),c+=1;return d}a(Ha,"diffLines");function ja(e){return e.length===0?[]:e.split(`
987
- `)}a(ja,"splitLines");Re();var qa=new Set(["func","template","info","payload","code","text","format","syntax"]),Tf=new Set(["x","y","w","h","wires","g"]),Rf=new Set(["w","h","g"]);function Rt(e){if(typeof e.name=="string"&&e.name.length>0)return e.name;if(typeof e.label=="string"&&e.label.length>0)return e.label}a(Rt,"nodeName");function Ua(e){let t=new Map;for(let n of e.nodes??[])t.set(n.id,n);for(let n of e.configs??[])t.set(n.id,n);return t}a(Ua,"collectNodes");function ao(e){if(e===void 0)return"undefined";if(e===null||typeof e!="object")return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(ao).join(",")}]`;let t=e;return`{${Object.keys(t).sort().map(r=>`${JSON.stringify(r)}:${ao(t[r])}`).join(",")}}`}a(ao,"stableStringify");function za(e,t){return ao(e)===ao(t)}a(za,"valuesEqual");function lo(e){return typeof e=="string"?e:null}a(lo,"asText");function _f(e){return/<\/?[a-z][\s\S]*>/i.test(e.slice(0,800))}a(_f,"looksLikeHtml");function Pf(e,t,n){if(t!=="template")return!1;let r=typeof e.format=="string"?e.format.toLowerCase():"";return r==="html"||r==="handlebars"||r==="mustache"?!0:_f(n)}a(Pf,"isHtmlTemplateField");function Wa(e,t,n){let r=lo(n);if(qa.has(t)&&r!==null){let o={key:t,kind:"text",value:r};return Pf(e,t,r)&&(o.preview="html"),o}return{key:t,kind:"scalar",value:n===void 0?null:n}}a(Wa,"toPresenceProperty");function Ef(e,t){let n=[];for(let r of[...Object.keys(e)].sort())if(!(Rf.has(r)||r==="id"||r==="z")){if(r==="wires"){let o=Ga(e,t);n.push({key:"wires",kind:"scalar",value:o.length>0?o:e.wires??null});continue}n.push(Wa(e,r,e[r]))}return n}a(Ef,"nodeProperties");function Ga(e,t){let n=e.wires;if(!Array.isArray(n)||n.length===0)return[];let r=[];return n.forEach((o,s)=>{if(!Array.isArray(o))return;let i=[];for(let d of o){if(typeof d!="string"||!d)continue;let l=t.get(d);i.push({id:d,name:l?Rt(l)??l.type:d})}r.push({port:s,to:i})}),r}a(Ga,"describeWiresStructured");var Cf=new Set(["func","template","code","text"]);function Lf(e,t){let n=[],r=Rt(e);r&&n.push({key:"name",kind:"scalar",value:r});let o=Ga(e,t);o.length>0&&n.push({key:"wires",kind:"scalar",value:o});for(let s of Cf){if(!(s in e))continue;let i=lo(e[s]);i===null||i.length===0||n.push(Wa(e,s,i))}return n}a(Lf,"buildKeyFields");function If(e,t,n){let r=et(e.type),o=Rt(e),s=o?` "${o}"`:"",i=t?` in "${t}"`:"",d=n===1?"1 field":`${n} fields`;return`Changes ${r} node${s}${i} (${d})`}a(If,"buildModifiedSummary");function Nf(e,t,n){let r=et(e.type),o=Rt(e),s=t==="added"?"Adds":"Removes",i=t==="added"?"to":"from",d=o?` ${JSON.stringify(o)}`:"",l=n?` ${i} flow ${JSON.stringify(n)}`:"",c=`${s} ${r} node${d}${l}`;if(t==="added"&&r==="template"){let u=typeof e.field=="string"&&e.field?e.field:"";u&&(c+=`, output to msg.${u}`)}return c}a(Nf,"buildPresenceSummary");function so(e,t,n,r){let o={id:e.id,type:e.type,summary:Nf(e,t,n),keyFields:Lf(e,r),properties:Ef(e,r)},s=Rt(e);return s!==void 0&&(o.name=s),o}a(so,"toPresenceNode");function Ff(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]),r=[];for(let o of[...n].sort()){if(Tf.has(o)||o==="id"||o==="z")continue;let s=e[o],i=t[o];if(za(s,i))continue;let d=lo(s),l=lo(i);if(qa.has(o)&&(d!==null||l!==null)){let c=d??"",u=l??"";r.push({key:o,kind:"text",before:c,after:u,lines:Ha(c,u)});continue}r.push({key:o,kind:"scalar",before:s===void 0?null:s,after:i===void 0?null:i})}return r}a(Ff,"compareNodes");function Of(e,t){let n=["label","disabled","info","env"],r=[];for(let o of n)za(e[o],t[o])||r.push(o);return r}a(Of,"compareTabMeta");function io(e){return typeof e.label=="string"&&e.label.trim()?e.label.trim():""}a(io,"flowLabelOf");function Ka(e,t){let n=Ua(t),r=Ua(e),o=io(e)||io(t),s=io(t)||io(e),i=[],d=[],l=[];for(let c of e.nodes??[])n.has(c.id)||i.push(so(c,"added",o,r));for(let c of e.configs??[])n.has(c.id)||i.push(so(c,"added",o,r));for(let c of t.nodes??[])r.has(c.id)||d.push(so(c,"removed",s,n));for(let c of t.configs??[])r.has(c.id)||d.push(so(c,"removed",s,n));for(let[c,u]of r){let p=n.get(c);if(!p)continue;let f=Ff(p,u);if(f.length>0){let m={id:c,type:u.type||p.type,summary:If(u,o||s,f.length),changes:f},h=Rt(u)??Rt(p);h!==void 0&&(m.name=h),l.push(m)}}return l.sort((c,u)=>c.id.localeCompare(u.id)),{summary:{added:i.length,removed:d.length,modified:l.length,tabChanges:Of(t,e),beforeNodeCount:n.size,afterNodeCount:r.size},added:i,removed:d,modified:l}}a(Ka,"buildDiffSections");function Ba(e,t,n={}){let r=Ka(e.payload.flow,t.flow),o=n.currentRev??t.rev,s=e.expectedRev,i=n.baseRevStale??(e.status==="pending"&&s!==o),d={proposalId:e.id,flowId:e.payload.flowId,expectedRev:s,currentRev:o,baseRevStale:i,compareAgainst:n.compareAgainst??"current",...r};return e.warnings&&e.warnings.length>0&&(d.warnings=e.warnings.map(l=>({...l}))),n.baseSnapshotMissing&&(d.baseSnapshotMissing=!0),n.vsCurrent&&(d.vsCurrent=n.vsCurrent),d}a(Ba,"buildProposalDiff");async function un(e,t){let n=e.payload.flowId,r={id:n,type:"tab",nodes:[],configs:[]},o=e.expectedRev,s=r;try{let c=await t.getFlow(n);o=c.rev,s=c.flow}catch(c){if(!(c instanceof W))throw c;try{o=(await t.getFlows()).rev}catch{}}let d=e.status==="pending"&&e.expectedRev!==o,l=e.baseFlow;if(l!==void 0){let c=d?Ka(e.payload.flow,s):void 0;return Ba(e,{rev:e.expectedRev,flow:l},{currentRev:o,compareAgainst:"base",baseRevStale:d,...c?{vsCurrent:c}:{}})}return Ba(e,{rev:o,flow:s},{compareAgainst:"current",baseSnapshotMissing:!0,baseRevStale:d})}a(un,"computeProposalDiff");Re();Le();ue();var Ja=require("path");function co(e){return(0,Ja.join)(e.userDir,".ai-access")}a(co,"getStorageDir");var $f=["status:read","flows:read","proposals:create","proposals:read-own"];function ys(){return rn()}a(ys,"readPackageVersion");function Mf(e,t=Date.now()){let n=Date.parse(e);return Number.isFinite(n)?Math.max(0,Math.ceil((n-t)/1e3)):0}a(Mf,"secondsRemainingUntil");function Df(e,t=Date.now()){let n=Date.parse(e);return Number.isFinite(n)?Math.max(0,Math.ceil((n-t)/6e4)):0}a(Df,"minutesRemainingUntil");function jf(e,t){let n=fe(t),r=Ve(t),o=`${n}/openapi.yaml`,s=`${n}/openapi.json`;return e==="rest"?{api:n,openapi:o,openapiJson:s,discovery:n}:e==="mcp"?{rpc:r,discovery:n}:{discovery:n}}a(jf,"baseUrlsForTokenKind");function pn(e,t){let n=ur(e,t),r=ve(e.settings);if(t.auth){let o=t.auth.extra?.user;return{ok:!0,version:ys(),adminAuthEnabled:n.adminAuthEnabled,aiAccessEnabled:r.enabled,aiFeaturesEnabled:n.aiFeaturesEnabled,allowWithoutAdminAuth:n.allowWithoutAdminAuth,permission:t.auth.scopes.includes("propose")?"propose":"read",capabilities:{view:!0,manageLinks:!1,approveProposals:!1},defaultLinkTtlSeconds:r.defaultLinkTtlSeconds,maxActiveLinks:r.maxActiveLinks,defaultDeploymentType:r.defaultDeploymentType,caller:{kind:"oauth",clientId:t.auth.clientId,user:typeof o=="string"?o:"owner",scopes:t.auth.scopes}}}if(t.accessLink){let o=Date.now();return{ok:!0,version:ys(),adminAuthEnabled:n.adminAuthEnabled,aiAccessEnabled:r.enabled,aiFeaturesEnabled:n.aiFeaturesEnabled,allowWithoutAdminAuth:n.allowWithoutAdminAuth,permission:null,capabilities:{view:!0,manageLinks:!1,approveProposals:!1},defaultLinkTtlSeconds:r.defaultLinkTtlSeconds,maxActiveLinks:r.maxActiveLinks,defaultDeploymentType:r.defaultDeploymentType,caller:{kind:"access-link",linkId:t.accessLink.id,tokenKind:t.accessLink.kind,...t.accessLink.label!==void 0?{label:t.accessLink.label}:{},expiresAt:t.accessLink.expiresAt,minutesRemaining:Df(t.accessLink.expiresAt,o),secondsRemaining:Mf(t.accessLink.expiresAt,o),permissions:[...$f],baseUrls:jf(t.accessLink.kind,e.settings.httpAdminRoot)}}}return{ok:!0,version:ys(),adminAuthEnabled:n.adminAuthEnabled,aiAccessEnabled:r.enabled,aiFeaturesEnabled:n.aiFeaturesEnabled,allowWithoutAdminAuth:n.allowWithoutAdminAuth,permission:n.permission,capabilities:n.capabilities,storageDir:co(e.settings),defaultLinkTtlSeconds:r.defaultLinkTtlSeconds,maxActiveLinks:r.maxActiveLinks,defaultDeploymentType:r.defaultDeploymentType,caller:{kind:"admin",...n.permission?{permission:n.permission}:{}}}}a(pn,"buildStatus");function Va(e,t){let n=t.draftStore??new Br,r=t.rateLimiters??Sa(t.rateLimit??e.settings.aiAccess),o=e.httpAdmin,s="/mcp/a/:token",i=[r.ip,Ta(e,t.linkStore),r.token];o.use("/mcp/a",la()),o.get(s,...i,_((u,p)=>{O(p),p.setHeader("X-Content-Type-Options","nosniff");let f=fe(e.settings.httpAdminRoot),m=u.accessLink;if(!m){I(p,401,"Access link required","access_link_missing");return}let h=ya({link:m,openapiPath:`${f}/openapi.yaml`,restApiBase:f,mcpRpcPath:Ve(e.settings.httpAdminRoot),publicOrigin:_o(u)});p.status(200).type("text/markdown; charset=utf-8").send(h)})),o.get(`${s}/status`,...i,_((u,p)=>{O(p),p.json(pn(e,u))})),o.get(`${s}/flows`,...i,_(async(u,p)=>{O(p);try{p.json(await ye(t.flowAdapter))}catch(f){let m=M(f);if(m){p.status(m.statusCode).json({...m.body,hint:"Retry after the owner fixes the plugin runtime."});return}I(p,502,f instanceof Error?f.message:String(f),"upstream_error")}})),o.get(`${s}/flows/:id`,...i,_(async(u,p)=>{O(p),await Zr(t.flowAdapter,"get")(u,p)})),o.get(`${s}/nodes/:id`,...i,_(async(u,p)=>{O(p),await Qr(t.flowAdapter,"get")(u,p)})),o.get(`${s}/nodes/:id/field/:name`,...i,_(async(u,p)=>{O(p),await eo(t.flowAdapter,"get")(u,p)})),o.get(`${s}/search`,...i,_(async(u,p)=>{O(p),await to(t.flowAdapter,"get")(u,p)}));let d={nodeTypes:t.nodeTypesAdapter,...t.catalogueFetcher?{catalogueFetcher:t.catalogueFetcher}:{},...t.catalogueUrl?{catalogueUrl:t.catalogueUrl}:{}};o.get(`${s}/node-types`,...i,_(async(u,p)=>{O(p),await no(d)(u,p)})),o.get(`${s}/node-types/:type`,...i,_(async(u,p)=>{O(p),await ro(d)(u,p)})),o.get(`${s}/palette/search`,...i,_(async(u,p)=>{O(p),await oo(d)(u,p)}));let l=e.settings.httpAdminRoot;Hf(o,s,i,t,l),o.get(`${s}/propose`,...i,_(Ia(t.proposalStore,t.auditLog,t.flowAdapter,l,t.nodeTypesAdapter))),qf(o,s,i,n,t,l);let c=_a();return o.all(`${s}/links`,c),o.all(`${s}/links/:id`,c),o.use("/mcp/a",Ra()),n}a(Va,"registerGetLinkRoutes");function Hf(e,t,n,r,o){e.get(`${t}/proposals`,...n,_(async(s,i)=>{O(i);let d=s.accessLink;if(!d){I(i,401,"Access link required","access_link_missing");return}let l=q(s,o),c=ie(s.query),u=Mr(s.query),p;try{p=(await ye(r.flowAdapter)).rev}catch{}let f=p?V(r.proposalStore,p):0,m=Zt(r.proposalStore,d,l,u);i.json({proposals:m.proposals.map(h=>ge(r.proposalStore,d,h,{...l,include:c},{includeConflicts:!1})),...kt({...m,returned:m.proposals.length}),...Z(f)})})),e.get(`${t}/proposals/:id`,...n,_(async(s,i)=>{O(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){I(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){I(i,401,"Access link required","access_link_missing");return}try{let c=q(s,o),u=ie(s.query),p;try{p=(await ye(r.flowAdapter)).rev}catch{}let f=ze(r.proposalStore,l,d,c);i.json(ge(r.proposalStore,l,f,{...c,include:u},{...p?{liveRev:p}:{}}))}catch(c){let u=M(c,"get");if(u){i.status(u.statusCode).json({...u.body,hint:u.body.hint??"List proposals with GET \u2026/proposals."});return}D(i,c);return}})),Uf(e,t,n,r,o),Bf(e,t,n,r,o)}a(Hf,"registerGetLinkProposalRoutes");function Uf(e,t,n,r,o){e.get(`${t}/proposals/:id/withdraw`,...n,_((s,i)=>{O(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){I(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){I(i,401,"Access link required","access_link_missing");return}let c=s.query.confirm;if(c!=="1"&&c!=="true"){I(i,400,"Withdraw over GET requires confirm=1","confirm_required",de("confirm_required","get"));return}let u=typeof s.query.reason=="string"?s.query.reason:void 0,p=q(s,o),f=ie(s.query);try{let m=Qt(r.proposalStore,r.auditLog,l,d,u,p);i.json(ge(r.proposalStore,l,m,{...p,include:f}))}catch(m){let h=M(m,"get");if(h){let g=h.body.code;I(i,h.statusCode,je[g]??"Request failed",g,de(g,"get"));return}I(i,500,"Internal server error","internal_error");return}}))}a(Uf,"registerGetLinkWithdrawRoute");function Bf(e,t,n,r,o){e.get(`${t}/proposals/:id/diff`,...n,_(async(s,i)=>{O(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){I(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){I(i,401,"Access link required","access_link_missing");return}try{let c=ze(r.proposalStore,l,d,q(s,o)),u=P(s.query,"format"),p=sn(u==="json"||u==="md"?void 0:u);if(!p.ok){I(i,400,"Invalid diff format","invalid_diff_format","Valid formats: summary, full (default summary).");return}i.json(an(await un(c,r.flowAdapter),p.format))}catch(c){let u=M(c);if(u){i.status(u.statusCode).json(u.body);return}D(i,c);return}}))}a(Bf,"registerGetLinkDiffRoute");function qf(e,t,n,r,o,s){e.get(`${t}/draft/new`,...n,_(Ea(r,o.flowAdapter))),e.get(`${t}/draft/:draftId/append`,...n,_(Ca(r))),e.get(`${t}/draft/:draftId/submit`,...n,_(La(r,o.proposalStore,o.auditLog,o.flowAdapter,s,o.nodeTypesAdapter)))}a(qf,"registerGetLinkDraftRoutes");G();var Ya=require("module");function zf(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,Ya.createRequire)(e)}a(zf,"createPluginRequire");function Wf(e){try{let r=zf()("@node-red/runtime")._?.adminApi?.httpAdmin;if(r&&typeof r.use=="function"&&r!==e.httpAdmin)return r}catch{}return null}a(Wf,"resolveEditorHttpAdmin");function Xa(e){let t=[e.httpAdmin],n=Wf(e);return n&&t.push(n),t}a(Xa,"httpAdminAppsForMcpErrors");var Za=Symbol.for("com55.ai-access.mcpJsonErrorHandler");function Gf(e){let t=e??"/";for(;t.endsWith("/");)t=t.slice(0,-1);let n=new Set;for(let r of t?["",t]:[""])for(let o of["/mcp","/mcp/rpc"])n.add(r+o),n.add(`${r+o}/`);return r=>n.has(r)}a(Gf,"mcpRpcMatcher");function ws(e){return e.includes("/mcp")}a(ws,"isMcpPath");function Kf(e){return e.path||e.url||e.originalUrl||""}a(Kf,"requestPath");function Jf(e){if(!e||typeof e!="object")return!1;let t=e;if(t.type==="entity.parse.failed"||e instanceof SyntaxError)return!0;let n=typeof e.message=="string"?e.message:"";return/JSON/i.test(n)&&(t.status===400||t.statusCode===400)}a(Jf,"isJsonParseError");function Vf(e){if(!e||typeof e!="object")return!1;let t=e;return t.type==="entity.too.large"||t.status===413||t.statusCode===413||t.message==="request entity too large"}a(Vf,"isPayloadTooLarge");function Yf(e){let t=Gf(e);return(n,r,o,s)=>{if(o.headersSent){s(n);return}let i=Kf(r);if(Jf(n)&&ws(i)){if(t(i)){o.status(400).json({jsonrpc:"2.0",error:{code:-32700,message:"Parse error"},id:null});return}o.status(400).json({error:"Invalid JSON body",code:"invalid_json"});return}if(Vf(n)&&ws(i)){o.status(413).json({error:`Request body exceeds limit of ${H} bytes`,code:"payload_too_large"});return}if(ws(i)){if(t(i)){o.status(500).json({jsonrpc:"2.0",error:{code:-32603,message:Ur(n).error,data:{code:"internal_error"}},id:null});return}o.status(500).json(Ur(n));return}s(n)}}a(Yf,"mcpJsonErrorHandler");function Xf(e,t){let n=e;n[Za]||(n[Za]=!0,e.use(Yf(t)))}a(Xf,"attachOnce");function Qa(e){for(let t of Xa(e))Xf(t,e.settings.httpAdminRoot)}a(Qa,"attachMcpJsonErrorHandlers");qn();var _t=class extends Error{static{a(this,"AccessLinkLimitError")}code="access_link_limit";statusCode=409;constructor(t){super(`Cannot create access link: at most ${t} active links are allowed. Revoke an existing link first.`),this.name="AccessLinkLimitError"}},Pt=class extends Error{static{a(this,"AccessLinkNotFoundError")}code="access_link_not_found";statusCode=404;constructor(t){super(`Access link '${t}' was not found`),this.name="AccessLinkNotFoundError"}},Kn=class extends Error{static{a(this,"AccessLinkValidationError")}code="access_link_invalid";statusCode=400;constructor(t){super(t),this.name="AccessLinkValidationError"}},at=class extends Error{static{a(this,"AccessLinkTtlError")}code="invalid_ttl";statusCode=400;hint=At;constructor(t){super(t),this.name="AccessLinkTtlError"}};var Et=require("crypto");qn();function bs(e){return e==="get_link"||e==="rest"||e==="mcp"}a(bs,"isAccessLinkKind");function vs(e){return e??"get_link"}a(vs,"resolveAccessLinkKind");function ed(e){return(0,Et.createHash)("sha256").update(e,"utf8").digest("hex")}a(ed,"hashToken");function Zf(e,t){let n=Buffer.from(e,"utf8"),r=Buffer.from(t,"utf8");return n.length!==r.length?!1:(0,Et.timingSafeEqual)(n,r)}a(Zf,"hashesEqual");function Jn(e){let t={id:e.id,kind:vs(e.kind),createdAt:new Date(e.createdAtMs).toISOString(),expiresAt:new Date(e.expiresAtMs).toISOString()};return e.label!==void 0&&(t.label=e.label),e.lastUsedAtMs!==void 0&&(t.lastUsedAt=new Date(e.lastUsedAtMs).toISOString()),e.createdBy!==void 0&&(t.createdBy=e.createdBy),t}a(Jn,"toPublic");var Ct=class{static{a(this,"AccessLinkStore")}links=new Map;defaultTtlSeconds;maxActiveLinks;now;randomId;randomToken;constructor(t={}){this.defaultTtlSeconds=t.defaultTtlSeconds??14400,this.maxActiveLinks=t.maxActiveLinks??10,this.now=t.now??(()=>Date.now()),this.randomId=t.randomId??(()=>(0,Et.randomBytes)(12).toString("hex")),this.randomToken=t.randomToken??(()=>`aia_${(0,Et.randomBytes)(32).toString("base64url")}`)}configureDefaults(t){typeof t.defaultTtlSeconds=="number"&&Number.isFinite(t.defaultTtlSeconds)&&t.defaultTtlSeconds>0&&(this.defaultTtlSeconds=Math.floor(t.defaultTtlSeconds)),typeof t.maxActiveLinks=="number"&&Number.isFinite(t.maxActiveLinks)&&t.maxActiveLinks>0&&(this.maxActiveLinks=Math.floor(t.maxActiveLinks))}isActive(t,n){return t.revokedAtMs!==void 0?!1:t.expiresAtMs>n}list(){let t=this.now();return[...this.links.values()].filter(n=>this.isActive(n,t)).sort((n,r)=>r.createdAtMs-n.createdAtMs).map(Jn)}create(t={}){let n=this.now();if([...this.links.values()].filter(p=>this.isActive(p,n)).length>=this.maxActiveLinks)throw new _t(this.maxActiveLinks);let o=this.defaultTtlSeconds;if(t.ttlSeconds!==void 0){if(typeof t.ttlSeconds!="number"||!Number.isFinite(t.ttlSeconds)||t.ttlSeconds<=0)throw new at("ttlSeconds must be a positive number");o=Math.floor(t.ttlSeconds)}let s=typeof t.label=="string"&&t.label.trim().length>0?t.label.trim():void 0,i=vs(t.kind),d=ss(i);if(o>d)throw new at(is(i,d));let l=this.randomId(),c=this.randomToken(),u={id:l,tokenHash:ed(c),kind:i,createdAtMs:n,expiresAtMs:n+o*1e3};return s!==void 0&&(u.label=s),t.createdBy!==void 0&&(u.createdBy=t.createdBy),this.links.set(l,u),{...Jn(u),token:c}}revoke(t){let n=this.links.get(t);if(!n)throw new Pt(t);return n.revokedAtMs??=this.now(),Jn(n)}getById(t){if(typeof t!="string"||t.length===0)return null;let n=this.links.get(t);return n?Jn(n):null}validateResult(t){if(typeof t!="string"||t.length===0)return{status:"invalid"};let n=ed(t),r=this.now();for(let o of this.links.values())if(Zf(o.tokenHash,n))return o.revokedAtMs!==void 0?{status:"invalid"}:o.expiresAtMs<=r?{status:"expired",expiresAt:new Date(o.expiresAtMs).toISOString()}:(o.lastUsedAtMs=r,{status:"active",link:Jn(o)});return{status:"invalid"}}validate(t){let n=this.validateResult(t);return n.status==="active"?n.link:null}size(){return this.links.size}clear(){this.links.clear()}upsertRecordForTest(t){let n={...t};delete n.token,this.links.set(t.id,n)}};qn();ce();ue();function ks(e,t){return t}a(ks,"storeForRequest");function nd(e){let t=typeof e.user?.username=="string"?e.user.username:void 0;return t?{kind:"admin",username:t}:{kind:"admin"}}a(nd,"adminActor");function uo(e){let t=ve(e.settings);return new Ct({defaultTtlSeconds:t.defaultLinkTtlSeconds,maxActiveLinks:t.maxActiveLinks})}a(uo,"createConfiguredLinkStore");function rd(e){e.setHeader("Cache-Control","no-store"),e.setHeader("Pragma","no-cache")}a(rd,"noStore");function od(e,t){return(n,r)=>{let s=ks(e,t).list();rd(r),r.json({links:s})}}a(od,"listLinksHandler");function sd(e,t,n){return(r,o)=>{let s=r.body??{},i={};if(s.ttlSeconds!==void 0){if(typeof s.ttlSeconds!="number"){o.status(400).json({error:"ttlSeconds must be a positive number",code:"invalid_ttl",hint:At});return}i.ttlSeconds=s.ttlSeconds}if(typeof s.label=="string"&&(i.label=s.label),s.kind!==void 0){if(!bs(s.kind)){o.status(400).json({error:"kind must be one of: 'get_link', 'rest', 'mcp'",code:"invalid_link_kind"});return}i.kind=s.kind}typeof r.user?.username=="string"&&(i.createdBy=r.user.username);try{let d=ks(e,t).create(i),l=d.kind;if(n){let c=Date.parse(d.createdAt),u=Date.parse(d.expiresAt),p=Number.isFinite(c)&&Number.isFinite(u)&&u>c?Math.round((u-c)/1e3):void 0;n.append("link.created",nd(r),{linkId:d.id,label:d.label,expiresAt:d.expiresAt,createdAt:d.createdAt,kind:l,...p!==void 0?{ttlSeconds:p}:{}})}if(rd(o),l==="get_link"){let c=ei(r,e.settings.httpAdminRoot,d.token);o.status(201).json({...d,url:c});return}o.status(201).json({...d})}catch(d){if(d instanceof _t){o.status(d.statusCode).json({error:d.message,code:d.code});return}if(d instanceof at){o.status(d.statusCode).json({error:d.message,code:d.code,hint:d.hint});return}if(d instanceof Kn){o.status(d.statusCode).json({error:d.message,code:d.code});return}D(o,d);return}}}a(sd,"createLinkHandler");function id(e,t,n){return(r,o)=>{let s=r.params.id;if(typeof s!="string"||s.length===0){o.status(400).json({error:"Missing link id"});return}try{let i=ks(e,t).revoke(s);if(n){let d=Date.parse(i.createdAt),l=Date.parse(i.expiresAt),c=Number.isFinite(d)&&Number.isFinite(l)&&l>d?Math.round((l-d)/1e3):void 0;n.append("link.revoked",nd(r),{linkId:i.id,label:i.label,expiresAt:i.expiresAt,createdAt:i.createdAt,kind:i.kind,...c!==void 0?{ttlSeconds:c}:{}})}o.json({revoked:!0,link:i})}catch(i){if(i instanceof Pt){o.status(i.statusCode).json({error:i.message,code:i.code});return}D(o,i);return}}}a(id,"revokeLinkHandler");function Qf(e){let t=Date.parse(e.createdAt),n=Date.parse(e.expiresAt);if(Number.isFinite(t)&&Number.isFinite(n)&&n>t)return Math.round((n-t)/1e3)}a(Qf,"ttlSecondsFromLink");function em(e,t){if(!t||e.action!=="link.created"&&e.action!=="link.revoked")return e;let n={...e.details??{}},r=typeof n.linkId=="string"?n.linkId:"";if(!r)return e;let o=n.label==null||n.label==="",s=n.expiresAt==null||n.expiresAt==="",i=n.createdAt==null||n.createdAt==="",d=n.ttlSeconds==null;if(!o&&!s&&!i&&!d)return e;let l=t.getById(r);if(!l)return e;if(o&&l.label&&(n.label=l.label),s&&(n.expiresAt=l.expiresAt),i&&(n.createdAt=l.createdAt),d){let c=Qf(l);c!==void 0&&(n.ttlSeconds=c)}return{...e,details:n}}a(em,"enrichAuditLogEntry");function ad(e,t){return(n,r)=>{let o=n.query.limit,s=100;if(typeof o=="string"&&o.length>0){let d=Number(o);Number.isFinite(d)&&d>0&&(s=Math.min(Math.floor(d),500))}let i=e.list(s).map(d=>em(d,t));r.json({logs:i})}}a(ad,"listLogsHandler");var Ss=require("@modelcontextprotocol/sdk/server/mcp.js"),hd=require("@modelcontextprotocol/sdk/server/streamableHttp.js"),R=require("zod");Ze();G();pt();J();De();wt();tt();Re();var dd=require("async_hooks");nt();var tm="ai.redacted",ld=new dd.AsyncLocalStorage;function cd(e,t,n,r){if(!(r<=0))try{e.append(tm,t,{redactions:r,operation:n})}catch{}}a(cd,"appendRedactionAudit");function ud(e){let t=ld.getStore();if(!t)return;let n=Fr(e);n>0&&t.record(n)}a(ud,"noteResponseRedactions");function pd(e,t,n,r){return ld.run({record:a(o=>cd(e,t,n,o),"record")},r)}a(pd,"runWithRedactionAudit");function fd(e){let t=Array.isArray(e)?e[0]:e;if(t&&typeof t=="object"){let n=t;if(n.method==="tools/call"&&typeof n.params?.name=="string")return`mcp:${n.params.name.replace(/[^A-Za-z0-9_.-]/g,"").slice(0,64)}`;if(typeof n.method=="string")return`mcp:${n.method.slice(0,64)}`}return"mcp"}a(fd,"mcpOperationName");function As(e){let t=e.accessLink;if(t)return{kind:"access-link",linkId:t.id,...t.label!==void 0?{label:t.label}:{}}}a(As,"accessLinkAuditActor");function md(e){return(t,n,r)=>{if(!t.path.startsWith("/mcp/")||t.path.startsWith("/mcp/rpc")){r();return}let o=n.json.bind(n);n.json=(s=>{let i=As(t);if(i&&n.statusCode<400){let d;try{d=String(JSON.stringify(s))}catch{d=""}let l=t.route?.path,c=`${t.method} ${typeof l=="string"?l:"unknown"}`;cd(e,i,c,Fr(d))}return o(s)}),r()}}a(md,"redactionAuditMiddleware");nt();function gd(){return"0.2.0-beta.6"}a(gd,"pluginVersion");async function yd(e){try{return(await ye(e)).rev}catch{return}}a(yd,"tryLiveRev");function Q(e){let t=JSON.stringify(e);return ud(t),{content:[{type:"text",text:t}]}}a(Q,"jsonResult");function xs(e){return e===!0?new Set(["payload","baseFlow"]):new Set}a(xs,"includeSetFromPayloadFlag");function ee(e,t=500){let n=Bn(e,"mcp");if(n)return{isError:!0,content:[{type:"text",text:JSON.stringify({...n.body,statusCode:n.statusCode})}]};let r=e instanceof Error?e.message:String(e);return{isError:!0,content:[{type:"text",text:JSON.stringify({error:r,code:"internal_error",statusCode:t})}]}}a(ee,"jsonError");var nm=new Set(["list_proposals","list_my_proposals"]),rm=new Set(["status","limit","offset","mine","flowId"]);function om(e){return!!e&&typeof e=="object"&&typeof e.safeParseAsync=="function"}a(om,"isZodLikeSchema");function sm(e,t,n){let r=t&&typeof t=="object"?t:{},o=n[0],s=o?.path&&o.path.length>0?String(o.path[0]):"(arguments)";if(nm.has(e)&&rm.has(s))return Xt(s,r[s]);let i=n.map(l=>({parameter:l.path&&l.path.length>0?l.path.map(String).join("."):"(arguments)",message:l.message??"Invalid value"})),d=i.length===1?`Invalid argument '${i[0]?.parameter}' for tool ${e}: ${i[0]?.message}`:`${i.length} invalid arguments for tool ${e}`;return new y(d,"invalid_arguments",{details:{parameter:s,errors:i,hint:`Fix '${s}' to match the ${e} input schema (see tools/list).`}})}a(sm,"mcpArgumentError");function wd(e){let t=e,n=t.validateToolInput;typeof n=="function"&&(t.validateToolInput=async(r,o,s)=>{let i=r.inputSchema;if(!om(i))return n.call(e,r,o,s);let d=await i.safeParseAsync(o??{});if(d.success)return d.data;let l=Bn(sm(s,o,d.error.issues),"mcp"),c=l?{...l.body,statusCode:l.statusCode}:{};throw new Error(JSON.stringify(c))})}a(wd,"installMcpArgumentErrorMapping");function bd(e,t,n,r){e.registerTool("get_status",{description:'Return plugin status (version, AI feature flags, storage path, deploy defaults). For access-link callers, permission is null and caller.kind is "access-link" \u2014 that is expected, not an error. Use before other tools to confirm the plugin is enabled.'},()=>{try{return Q(pn(t,r))}catch(o){return ee(o)}}),e.registerTool("list_flows",{description:'List all flows as a flat Admin API v2 array plus the current rev string. Likely secrets are shown as "[REDACTED]" (nodes list redacted paths in _aiRedactions). Returns { rev, flows }. Call this (or get_flow) before submit_proposal so expectedRev matches.'},async()=>{try{return Q(await ye(n.flowAdapter))}catch(o){return ee(o,502)}}),e.registerTool("get_flow",{description:"Get one tab/subflow by id with nested nodes/configs and the current rev. Pass summary=true for a lightweight node list (id/type/name/wires/bytes, no field contents). Prefer summary \u2192 search_flows \u2192 get_node_field(lines) \u2192 replace_text \u2192 submit_proposal. Returns { rev, flow } or summary shape. Use rev as expectedRev when submitting.",inputSchema:{id:R.z.string().min(1).describe("Flow / tab id"),summary:R.z.boolean().optional().describe("When true, return node list with sizes only (no field contents)")}},async({id:o,summary:s})=>{try{return Q(s?await Gr(n.flowAdapter,o):await _e(n.flowAdapter,o))}catch(i){return ee(i,502)}}),e.registerTool("get_node",{description:"Get one full node by id (includes owning flowId when known). Optional flow scopes the lookup.",inputSchema:{id:R.z.string().min(1).describe("Node id"),flow:R.z.string().min(1).optional().describe("Optional tab id to scope the lookup")}},async({id:o,flow:s})=>{try{return Q(await Kr(n.flowAdapter,o,s))}catch(i){return ee(i,502)}}),e.registerTool("get_node_field",{description:'Get one string field from a node. Optional lines="A-B" (1-based inclusive) returns a slice; response always includes totalLines and bytes.',inputSchema:{id:R.z.string().min(1).describe("Node id"),field:R.z.string().min(1).describe("Field name (e.g. template, func)"),lines:R.z.string().optional().describe("Optional 1-based line range A or A-B"),flow:R.z.string().min(1).optional().describe("Optional tab id to scope the lookup")}},async({id:o,field:s,lines:i,flow:d})=>{try{return Q(await Jr(n.flowAdapter,o,s,{...d?{flowId:d}:{},...i?{lines:i}:{}}))}catch(l){return ee(l,502)}}),e.registerTool("search_flows",{description:"Text search across string fields. Returns hits with node id, field, line numbers, and a short snippet. Results are capped; truncated=true when the cap is hit. Optional flow scopes to one tab.",inputSchema:{q:R.z.string().describe("Substring to find (case-sensitive; non-empty required)"),flow:R.z.string().min(1).optional().describe("Optional tab id")}},async({q:o,flow:s})=>{try{return Q(await Vr(n.flowAdapter,o,s?{flowId:s}:{}))}catch(i){return ee(i,502)}}),e.registerTool("list_node_types",{description:"List installed Node-RED palette node types (type, module, category, description, \u2026). Optional q filters type/module/category/description (case-insensitive). Results are capped. Call this before proposing nodes with unfamiliar types.",inputSchema:{q:R.z.string().optional().describe("Optional case-insensitive filter")}},async({q:o})=>{try{return Q(await xr(n.nodeTypesAdapter,o?{q:o}:{}))}catch(s){return ee(s,502)}}),e.registerTool("get_node_type",{description:"Get detail for one installed node type: defaults (JSON), inputs/outputs (always set), requiredConfigNodeTypes, help sections (summary/inputs/outputs), category/module/color. Types are case-sensitive. Pass includeHelp=false to omit help. 404 node_type_not_found when unknown (didYouMean on case mismatch; else nearest names).",inputSchema:{type:R.z.string().min(1).describe("Node type id (e.g. inject, template) \u2014 case-sensitive"),includeHelp:R.z.boolean().optional().describe("When false, omit help sections (default true)")}},async({type:o,includeHelp:s})=>{try{return Q(await yt(n.nodeTypesAdapter,o,{includeHelp:s!==!1}))}catch(i){return ee(i,502)}}),e.registerTool("search_palette",{description:"Search the public Node-RED catalogue for modules to recommend. Returns module id, description, version, updated, link, and installed boolean (cross-checked against installed modules). Core node-red types are not in the catalogue \u2014 use list_node_types. RECOMMEND ONLY \u2014 never installs.",inputSchema:{q:R.z.string().describe("Search string (id / description / keywords; non-empty required)")}},async({q:o})=>{try{return Q(await Tr(n.nodeTypesAdapter,o,{...n.catalogueFetcher?{catalogueFetcher:n.catalogueFetcher}:{},...n.catalogueUrl?{catalogueUrl:n.catalogueUrl}:{}}))}catch(s){return ee(s,502)}})}a(bd,"registerReadTools");function vd(e,t,n,r,o){let s=q(r,t.settings.httpAdminRoot);e.registerTool("submit_proposal",{description:`Submit a replace_flow proposal for owner approval (does not deploy). Preferred for small string edits: payload: { flowId, nodes: [{ op:"replace_text", node, field, find, replace, occurrence? }] } \u2014 find must be unique unless occurrence (1-based) is set; multiple replace_text ops apply in order; yields a short readable diff. Or send a full tab: payload.flow from get_flow (id, type, label?, nodes[], configs?). Inputs (all top-level, next to each other): type="replace_flow", expectedRev (required string from list_flows/get_flow rev; expected_rev alias accepted), optional title (\u226480 chars), and payload: { flowId, flow? } and/or payload.nodes / top-level nodes. Do not nest expectedRev inside payload. Secret values appear as "[REDACTED]" in reads; leave the placeholder unchanged to keep the stored secret (it is restored on submit), or replace it with a new value to change it. Payload size limit is ${H} bytes (413 if exceeded). expectedRev is checked against the live rev at submit time (409 if stale). replace_text errors: 400 text_not_found; 409 ambiguous_match with matchCount. Unknown palette types return 400 unknown_node_type with nearest matches \u2014 call list_node_types / search_palette and ask the user to install via Manage palette (never auto-install). Returns a lean proposal (id, status, title, storedTitle, flowLabel, displayName, reviewUrl, changeSummary, \u2026) by default; title may gain " in <flow>"; storedTitle is the title as saved (your title without that suffix, or the auto-generated title, which may already name the flow). Pass include_payload=true to include payload and baseFlow. When telling the user about the proposal, cite displayName and reviewUrl \u2014 not the raw id. Humans approve on the /ai-access dashboard (reviewUrl opens/scrolls to it).`,inputSchema:{type:R.z.literal("replace_flow").describe("Must be 'replace_flow'"),expectedRev:R.z.string().min(1).optional().describe("Top-level current flow revision from get_flow/list_flows (required at submit; sit next to type and payload, not inside payload)"),expected_rev:R.z.string().min(1).optional().describe("Alias for expectedRev (snake_case); prefer expectedRev"),title:R.z.string().max(80).optional().describe("Optional short human title (\u226480 chars). Auto-generated from changes when omitted."),include_payload:R.z.boolean().optional().describe("When true, include payload and baseFlow in the response (default lean)"),nodes:R.z.array(R.z.record(R.z.string(),R.z.unknown())).optional().describe("Optional node ops (set/add/remove/replace_text). Same as payload.nodes when flow is omitted."),payload:R.z.object({flowId:R.z.string().min(1).describe("Tab id to replace"),flow:R.z.object({id:R.z.string().optional(),type:R.z.string().optional().describe("Usually 'tab' (included by get_flow)"),label:R.z.string().optional(),nodes:R.z.array(R.z.record(R.z.string(),R.z.unknown())).optional().describe("Child nodes for the tab (from get_flow.nodes)"),configs:R.z.array(R.z.record(R.z.string(),R.z.unknown())).optional()}).passthrough().optional().describe("Full tab from get_flow; omit when using nodes ops"),nodes:R.z.array(R.z.record(R.z.string(),R.z.unknown())).optional().describe("Node ops applied to the live tab (prefer replace_text for small string edits)")}).describe("Replacement flow payload and/or node ops")}},async i=>{try{let d=await rt(n.proposalStore,n.auditLog,o,{...i,expectedRev:typeof i.expectedRev=="string"&&i.expectedRev.length>0?i.expectedRev:i.expected_rev},n.flowAdapter,s,n.nodeTypesAdapter),l={...he(d,{...s,include:xs(i.include_payload)}),mine:!0},c=V(n.proposalStore,d.expectedRev,{flowId:d.payload.flowId}),u=$e(Oe(n.proposalStore,n.proposalStore.get(d.id),o.id));return Q({...l,...u,...Z(c)})}catch(d){return ee(d)}}),am(e,n,o,s),dm(e,n,o,s)}a(vd,"registerProposalTools");function im(e,t,n,r){let o={mine:R.z.boolean().optional().describe("true: only proposals created by this access link; false: only other links\u2019 proposals"),status:R.z.union([R.z.string(),R.z.array(R.z.string())]).optional().describe("Filter by proposal status: one value, an array, or a comma-separated string (pending, approved, rejected, failed, withdrawn, expired, superseded). An empty array or empty string means no status filter."),flowId:R.z.string().optional().describe("Filter by flow tab id (payload.flowId)"),limit:R.z.number().int().min(1).max(Yt).optional().describe(`Page size (1\u2013${Yt}). Omit to return all matches.`),offset:R.z.number().int().min(0).optional().describe("Skip this many filtered results (default 0)"),include_payload:R.z.boolean().optional().describe("When true, include payload and baseFlow on each proposal")},s=a(async i=>{try{let d=xs(i.include_payload),l=await yd(t.flowAdapter),c=l?V(t.proposalStore,l):0,u={};typeof i.mine=="boolean"&&(u.mine=i.mine);let p=os(i.status);p&&(u.status=p),typeof i.flowId=="string"&&i.flowId.length>0&&(u.flowId=i.flowId),typeof i.limit=="number"&&(u.limit=i.limit),typeof i.offset=="number"&&(u.offset=i.offset);let f=Zt(t.proposalStore,n,r,u);return Q({proposals:f.proposals.map(m=>ge(t.proposalStore,n,m,{...r,include:d},{includeConflicts:!1})),...kt({...f,returned:f.proposals.length}),...Z(c)})}catch(d){return ee(d)}},"listProposalsHandler");e.registerTool("list_proposals",{description:"List proposals visible to this access link (all proposals by default). Each item includes mine:true|false for the caller. Filter with mine, status, and/or flowId; paginate with limit/offset (response includes total, offset, hasMore, nextOffset). Invalid filters return invalid_filter. Returns lean { proposals, total, \u2026 } with title, displayName, reviewUrl, changeSummary (no payload/baseFlow unless include_payload=true). Prefer displayName + reviewUrl when referring to a proposal with the user (not the raw id).",inputSchema:o},s),e.registerTool("list_my_proposals",{description:"Alias of list_proposals. Prefer list_proposals. Lists proposals visible to this access link with optional mine/status/flowId filters and limit/offset pagination.",inputSchema:o},s)}a(im,"registerListProposalsTools");function am(e,t,n,r){im(e,t,n,r),e.registerTool("get_proposal",{description:"Get any proposal by id (all tokens can read). Response includes mine:true|false and, for pending proposals, conflictingProposals when other pending work touches the same flow. Lean by default (no payload/baseFlow); pass include_payload=true for full bodies. Cite displayName + reviewUrl when talking to the user.",inputSchema:{id:R.z.string().min(1).describe("Proposal id (prop-\u2026)"),include_payload:R.z.boolean().optional().describe("When true, include payload and baseFlow")}},async({id:o,include_payload:s})=>{try{let i=await yd(t.flowAdapter),d=ze(t.proposalStore,n,o,r);return Q(ge(t.proposalStore,n,d,{...r,include:xs(s)},{...i?{liveRev:i}:{}}))}catch(i){return ee(i)}})}a(am,"registerProposalQueryTools");function dm(e,t,n,r){e.registerTool("get_proposal_diff",{description:"Diff any proposal against its stored baseFlow snapshot (what changed). Optional format=summary|full (default summary). Pending + stale also includes vsCurrent.",inputSchema:{id:R.z.string().min(1).describe("Proposal id (prop-\u2026)"),format:R.z.enum(["summary","full"]).optional().describe("Diff payload format (default summary)")}},async({id:o,format:s})=>{try{let i=ze(t.proposalStore,n,o),d=sn(s??"summary");if(!d.ok)return ee(new y("Invalid diff format","invalid_diff_format",{details:{hint:"Valid formats: summary, full (default summary)."}}));let l=an(await un(i,t.flowAdapter),d.format);return Q(l)}catch(i){return ee(i)}}),e.registerTool("withdraw_proposal",{description:"Withdraw one of YOUR pending proposals (status becomes withdrawn, not rejected). Optional reason. Only the creating access link can withdraw; other tokens get 403. Cite displayName + reviewUrl when telling the user.",inputSchema:{id:R.z.string().min(1).describe("Proposal id (prop-\u2026) owned by this access link"),reason:R.z.string().optional().describe("Optional withdraw reason")}},({id:o,reason:s})=>{try{let i=Qt(t.proposalStore,t.auditLog,n,o,s,r);return Q(ge(t.proposalStore,n,i,r))}catch(i){return ee(i)}})}a(dm,"registerProposalActionTools");function lm(e,t,n){let r=new Ss.McpServer({name:"node-red-ai-access",version:gd()});wd(r);let o=n.accessLink;if(!o)throw new Error("Access link required");return bd(r,e,t,n),vd(r,e,t,n,o),r}a(lm,"createAiMcpServer");function cm(e){let t=As(e);if(t)return t;let n=e.auth?.clientId??"",r=typeof e.auth?.extra?.user=="string"?e.auth.extra.user:"owner";return{kind:"access-link",linkId:vr(n,r),label:"oauth"}}a(cm,"mcpAuditActor");function Ts(e,t){return async(n,r,o)=>{try{if(!n.accessLink&&!n.auth){r.setHeader("WWW-Authenticate",X),r.status(401).json({error:me,code:Ie});return}let s={...t,flowAdapter:bt(t.flowAdapter)},i=n.accessLink?lm(e,s,n):um(e,s,n),d=new hd.StreamableHTTPServerTransport({enableJsonResponse:!0});await i.connect(d),await pd(t.auditLog,cm(n),fd(n.body),()=>d.handleRequest(n,r,n.body)),r.on("close",()=>{d.close(),i.close()})}catch(s){if(!r.headersSent){D(r,s);return}o(s instanceof Error?s:new Error(String(s)))}}}a(Ts,"mcpRpcPostHandler");function um(e,t,n){let r=new Ss.McpServer({name:"node-red-ai-access",version:gd()});if(wd(r),bd(r,e,t,n),(n.auth?.scopes??[]).includes("propose")){let s=typeof n.auth?.extra?.user=="string"?n.auth.extra.user:"owner",i=n.auth?.clientId??"",l={id:vr(i,s),kind:"mcp",createdAt:new Date().toISOString(),expiresAt:new Date((n.auth?.expiresAt??0)*1e3).toISOString(),createdBy:s,oauthClientId:i,oauthUser:s};vd(r,e,t,n,l)}return r}a(um,"createOAuthMcpServer");function Vn(e,t){t.status(405).set("Allow","POST").json({jsonrpc:"2.0",error:{code:-32e3,message:"Method not allowed. Use POST for Streamable HTTP."},id:null})}a(Vn,"mcpRpcMethodNotAllowed");var Yn=require("crypto"),mn=require("fs"),Ps=require("path"),B=require("@modelcontextprotocol/sdk/server/auth/errors.js"),Td=require("@modelcontextprotocol/sdk/server/auth/handlers/authorize.js"),Es=require("@modelcontextprotocol/sdk/server/auth/handlers/metadata.js"),Rd=require("@modelcontextprotocol/sdk/server/auth/handlers/register.js"),_d=require("@modelcontextprotocol/sdk/server/auth/handlers/revoke.js"),Pd=require("@modelcontextprotocol/sdk/server/auth/handlers/token.js"),Ed=require("@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js"),Cd=require("@modelcontextprotocol/sdk/server/auth/router.js"),Ld=yn(require("express"),1);G();Le();function He(e){return e.replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[t]??t)}a(He,"esc");var kd={read:'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5c-5 0-9 4.5-10 7 1 2.5 5 7 10 7s9-4.5 10-7c-1-2.5-5-7-10-7Zm0 11.5a4.5 4.5 0 1 1 0-9 4.5 4.5 0 0 1 0 9Zm0-2.3a2.2 2.2 0 1 0 0-4.4 2.2 2.2 0 0 0 0 4.4Z"/></svg>',propose:'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 17.2V20h2.8l8.3-8.3-2.8-2.8L4 17.2Zm13.7-7.6a1 1 0 0 0 0-1.4l-1.9-1.9a1 1 0 0 0-1.4 0l-1.4 1.4 2.8 2.8 1.9-1.9Z"/></svg>'},pm={read:["Read your flows","View flows, nodes, node types and AI Access logs."],propose:["Propose flow changes","Submit change proposals. Nothing is deployed until you approve it in Node-RED."]},fm='<svg class="logo" viewBox="0 0 48 48" aria-hidden="true"><rect width="48" height="48" rx="11" fill="#8f0000"/><path d="M11 15h9a4 4 0 0 1 4 4v10a4 4 0 0 0 4 4h9" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round"/><rect x="6" y="11" width="9" height="8" rx="2.5" fill="#fff"/><rect x="33" y="29" width="9" height="8" rx="2.5" fill="#fff"/><circle cx="37.5" cy="16" r="5" fill="none" stroke="#fff" stroke-width="2.4"/><path d="M37.5 13.5v5M35 16h5" stroke="#fff" stroke-width="2.2" stroke-linecap="round"/></svg>',mm=`
988
+ `);for(let m=0;m<f.length;m+=1){let g=(f[m]??"").indexOf(n);if(g>=0){let y=0;for(let w=0;w<m;w+=1)y+=(f[w]?.length??0)+1;u(m+1,y+g)}}}if(n.includes(`
989
+ `)){let f=0;for(;f<=i.length;){let m=i.indexOf(n,f);if(m<0)break;u(im(i,m),m),f=m+Math.max(1,n.length)}}if(d.length===0)continue;let p=d.length;r.push({flowId:t,nodeId:e.id,field:s,lines:d.slice(0,em),lineCount:p,snippet:sm(i,Math.max(0,c),n.length)})}return r.length>=o}a(Ci,"searchInNode");function gl(e){return[...e.nodes??[],...e.configs??[]]}a(gl,"nodePool");async function am(e,t,n,r,o){let s=await He(e,t);for(let i of gl(s.flow))if(Ci(i,t,n,r,o))return{rev:s.rev,truncated:!0};return{rev:s.rev,truncated:!1}}a(am,"searchOneFlow");async function dm(e,t,n,r){let o=await pt(e),s=new Set;for(let i of hl(o.flows))if(i.id)try{let d=await He(e,i.id);for(let l of gl(d.flow))if(s.add(l.id),Ci(l,i.id,t,n,r))return{rev:o.rev,truncated:!0}}catch{}for(let i of o.flows){if(!i||typeof i!="object"||Array.isArray(i))continue;let d=i;if(!d.id||s.has(d.id)||d.type==="tab"||d.type==="subflow")continue;let l=typeof d.z=="string"?d.z:null;if(Ci(d,l,t,n,r))return{rev:o.rev,truncated:!0}}return{rev:o.rev,truncated:!1}}a(dm,"searchAllFlows");async function zo(e,t,n={}){if(typeof t!="string"||!t.trim())throw new k("q query parameter is required","missing_query");let r=typeof n.cap=="number"&&n.cap>0?Math.floor(n.cap):Qh,o=[],s=n.flowId?await am(e,n.flowId,t,o,r):await dm(e,t,o,r),i={rev:s.rev,q:t,hits:o,hitCount:o.length,truncated:s.truncated,cap:r};return n.flowId&&(i.flowId=n.flowId),i}a(zo,"searchFlows");function yl(e){if(e===void 0)return!1;let t=e.toLowerCase();return t==="1"||t==="true"||t==="yes"}a(yl,"isTruthyQueryFlag");Pe();function lm(e,t,n){switch(e){case"node_not_found":case"flow_not_found":case"field_not_found":return n??we(e,t);default:return we(e,t)}}a(lm,"layeredReadErrorHint");function tn(e){return ot[e]??"Request failed"}a(tn,"safeErrorText");function _e(e,t,n){e.setHeader("X-Content-Type-Options","nosniff"),e.setHeader("Content-Type","application/json; charset=utf-8"),e.status(t).json(n)}a(_e,"sendJson");function Wo(e,t,n,r){let o=W(t,n);if(!o)return!1;let s=o.body.code,i={error:tn(s),code:s};typeof o.body.matchCount=="number"&&(i.matchCount=o.body.matchCount),typeof o.body.totalLines=="number"&&(i.totalLines=o.body.totalLines),typeof o.body.expectedRev=="string"&&(i.expectedRev=o.body.expectedRev),typeof o.body.currentRev=="string"&&(i.currentRev=o.body.currentRev);let d=lm(s,n,r);return d&&(i.hint=d),_e(e,o.statusCode,i),!0}a(Wo,"sendMappedError");function Go(e){_e(e,502,{error:tn("upstream_error"),code:"upstream_error"})}a(Go,"sendUpstreamError");function Ko(e,t="rest"){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){_e(r,400,{error:tn("missing_flow_id"),code:"missing_flow_id"});return}let s=yl(H(n.query,"summary"));try{if(s){_e(r,200,await Uo(Xe(n,e),o));return}_e(r,200,await He(Xe(n,e),o))}catch(i){if(Wo(r,i,t))return;Go(r)}}}a(Ko,"getFlowOrSummaryHandler");function Jo(e,t="rest"){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||!o){_e(r,400,{error:tn("missing_node_id"),code:"missing_node_id"});return}let s=H(n.query,"flow");try{_e(r,200,await Bo(Xe(n,e),o,s))}catch(i){if(Wo(r,i,t,t==="get"?"Call GET \u2026/flows/<id>?summary=1 or \u2026/search?q=\u2026":t==="mcp"?void 0:"Call GET \u2026/flows/<id>?summary=1 or \u2026/search?q=\u2026"))return;Go(r)}}}a(Jo,"getNodeHandler");function Vo(e,t="rest"){return async(n,r)=>{let o=n.params.id,s=n.params.name;if(typeof o!="string"||!o){_e(r,400,{error:tn("missing_node_id"),code:"missing_node_id"});return}if(typeof s!="string"||!s){_e(r,400,{error:tn("missing_field"),code:"missing_field"});return}let i=H(n.query,"flow"),d=H(n.query,"lines");try{_e(r,200,await qo(Xe(n,e),o,s,{...i?{flowId:i}:{},...d?{lines:d}:{}}))}catch(l){if(Wo(r,l,t,"Use summary/search to discover node ids and fields."))return;Go(r)}}}a(Vo,"getNodeFieldHandler");function Yo(e,t="rest"){return async(n,r)=>{let o=H(n.query,"q");if(!o){_e(r,400,{error:tn("missing_query"),code:"missing_query",hint:t==="get"?"Example: GET \u2026/search?q=TODO&flow=tab1":we("missing_query",t)});return}let s=H(n.query,"flow");try{_e(r,200,await zo(Xe(n,e),o,s?{flowId:s}:{}))}catch(i){if(Wo(r,i,t))return;Go(r)}}}a(Yo,"searchFlowsHandler");se();Ut();var cm={missing_node_type:"Missing node type",missing_query:"q query parameter is required",node_type_not_found:"Node type was not found",catalogue_unavailable:"Node-RED catalogue unavailable",unknown_node_type:"Unknown node type",upstream_error:"Upstream error"};function _r(e){return cm[e]??"Request failed"}a(_r,"safeErrorText");function Et(e,t,n){e.setHeader("X-Content-Type-Options","nosniff"),e.setHeader("Content-Type","application/json; charset=utf-8"),e.setHeader("Cache-Control","no-store"),e.status(t).json(n)}a(Et,"sendJson");function Ei(e,t){let n=W(t);if(!n)return!1;let r=n.body.code,o={error:_r(r),code:r};return typeof n.body.type=="string"&&(o.type=n.body.type),Array.isArray(n.body.nearest)&&(o.nearest=n.body.nearest),typeof n.body.didYouMean=="string"?(o.didYouMean=n.body.didYouMean,o.caseSensitive=!0,o.hint=`Types are case-sensitive \u2014 use '${n.body.didYouMean}' exactly.`):(r==="node_type_not_found"||r==="unknown_node_type")&&(o.hint="Call GET \u2026/node-types?q= or \u2026/palette/search?q=. Ask the user to install via Manage palette, then recheck node-types. This API never installs."),r==="catalogue_unavailable"&&(o.hint="Retry later; palette search is recommend-only and never installs packages."),Et(e,n.statusCode,o),!0}a(Ei,"sendMappedError");function Xo(e){return async(t,n)=>{try{let r=H(t.query,"q"),o=await po(e.nodeTypes,r?{q:r}:{});Et(n,200,o)}catch(r){if(Ei(n,r))return;Et(n,502,{error:_r("upstream_error"),code:"upstream_error"})}}}a(Xo,"listNodeTypesHandler");function Zo(e){return async(t,n)=>{try{let r=t.params.type,o=typeof r=="string"?decodeURIComponent(r):"",s=H(t.query,"help"),i=!(s==="0"||s==="false"||s==="no"||s==="omit"),d=await jt(e.nodeTypes,o,{includeHelp:i});Et(n,200,d)}catch(r){if(Ei(n,r))return;Et(n,502,{error:_r("upstream_error"),code:"upstream_error"})}}}a(Zo,"getNodeTypeHandler");function Qo(e){return async(t,n)=>{try{let r=H(t.query,"q");if(!r){Et(n,400,{error:_r("missing_query"),code:"missing_query",hint:"Pass ?q= to search the Node-RED catalogue (recommend only)."});return}let o=await fo(e.nodeTypes,r,{...e.catalogueFetcher?{catalogueFetcher:e.catalogueFetcher}:{},...e.catalogueUrl?{catalogueUrl:e.catalogueUrl}:{}});Et(n,200,o)}catch(r){if(Ei(n,r))return;Et(n,502,{error:_r("upstream_error"),code:"upstream_error"})}}}a(Qo,"searchPaletteHandler");Wt();Te();Ve();function Un(e,t){return e[t]??""}a(Un,"lineAt");function Cr(e,t,n){let r=e[t];return r?r[n]??0:0}a(Cr,"cell");function wl(e,t,n,r){let o=e[t];o&&(o[n]=r)}a(wl,"setCell");function vl(e,t){let n=bl(e),r=bl(t),o=n.length,s=r.length,i=[];for(let u=0;u<=o;u+=1){let p=[];for(let f=0;f<=s;f+=1)p.push(0);i.push(p)}for(let u=o-1;u>=0;u-=1)for(let p=s-1;p>=0;p-=1)Un(n,u)===Un(r,p)?wl(i,u,p,Cr(i,u+1,p+1)+1):wl(i,u,p,Math.max(Cr(i,u+1,p),Cr(i,u,p+1)));let d=[],l=0,c=0;for(;l<o&&c<s;){let u=Un(n,l),p=Un(r,c);u===p?(d.push({op:"equal",text:u}),l+=1,c+=1):Cr(i,l+1,c)>=Cr(i,l,c+1)?(d.push({op:"remove",text:u}),l+=1):(d.push({op:"add",text:p}),c+=1)}for(;l<o;)d.push({op:"remove",text:Un(n,l)}),l+=1;for(;c<s;)d.push({op:"add",text:Un(r,c)}),c+=1;return d}a(vl,"diffLines");function bl(e){return e.length===0?[]:e.split(`
990
+ `)}a(bl,"splitLines");Oe();Pn();nt();Pe();var xl=new Set(["func","template","info","payload","code","text","format","syntax"]),um=new Set(["x","y","w","h","wires","g"]),pm=new Set(["w","h","g"]);function nn(e){if(typeof e.name=="string"&&e.name.length>0)return e.name;if(typeof e.label=="string"&&e.label.length>0)return e.label}a(nn,"nodeName");function kl(e){let t=new Map;for(let n of e.nodes??[])t.set(n.id,n);for(let n of e.configs??[])t.set(n.id,n);return t}a(kl,"collectNodes");function rs(e){if(e===void 0)return"undefined";if(e===null||typeof e!="object")return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(rs).join(",")}]`;let t=e;return`{${Object.keys(t).sort().map(r=>`${JSON.stringify(r)}:${rs(t[r])}`).join(",")}}`}a(rs,"stableStringify");function Sl(e,t){return rs(e)===rs(t)}a(Sl,"valuesEqual");function os(e){return typeof e=="string"?e:null}a(os,"asText");function fm(e){return/<\/?[a-z][\s\S]*>/i.test(e.slice(0,800))}a(fm,"looksLikeHtml");function hm(e,t,n){if(t!=="template")return!1;let r=typeof e.format=="string"?e.format.toLowerCase():"";return r==="html"||r==="handlebars"||r==="mustache"?!0:fm(n)}a(hm,"isHtmlTemplateField");function Tl(e,t,n){let r=os(n);if(xl.has(t)&&r!==null){let o={key:t,kind:"text",value:r};return hm(e,t,r)&&(o.preview="html"),o}return{key:t,kind:"scalar",value:n===void 0?null:n}}a(Tl,"toPresenceProperty");function mm(e,t){let n=[];for(let r of[...Object.keys(e)].sort())if(!(pm.has(r)||r==="id"||r==="z")){if(r==="wires"){let o=Rl(e,t);n.push({key:"wires",kind:"scalar",value:o.length>0?o:e.wires??null});continue}n.push(Tl(e,r,e[r]))}return n}a(mm,"nodeProperties");function Rl(e,t){let n=e.wires;if(!Array.isArray(n)||n.length===0)return[];let r=[];return n.forEach((o,s)=>{if(!Array.isArray(o))return;let i=[];for(let d of o){if(typeof d!="string"||!d)continue;let l=t.get(d);i.push({id:d,name:l?nn(l)??l.type:d})}r.push({port:s,to:i})}),r}a(Rl,"describeWiresStructured");var gm=new Set(["func","template","code","text"]);function ym(e,t){let n=[],r=nn(e);r&&n.push({key:"name",kind:"scalar",value:r});let o=Rl(e,t);o.length>0&&n.push({key:"wires",kind:"scalar",value:o});for(let s of gm){if(!(s in e))continue;let i=os(e[s]);i===null||i.length===0||n.push(Tl(e,s,i))}return n}a(ym,"buildKeyFields");function wm(e,t,n){let r=vt(e.type),o=nn(e),s=o?` "${o}"`:"",i=t?` in "${t}"`:"",d=n===1?"1 field":`${n} fields`;return`Changes ${r} node${s}${i} (${d})`}a(wm,"buildModifiedSummary");function bm(e,t,n){let r=vt(e.type),o=nn(e),s=t==="added"?"Adds":"Removes",i=t==="added"?"to":"from",d=o?` ${JSON.stringify(o)}`:"",l=n?` ${i} flow ${JSON.stringify(n)}`:"",c=`${s} ${r} node${d}${l}`;if(t==="added"&&r==="template"){let u=typeof e.field=="string"&&e.field?e.field:"";u&&(c+=`, output to msg.${u}`)}return c}a(bm,"buildPresenceSummary");function es(e,t,n,r){let o={id:e.id,type:e.type,summary:bm(e,t,n),keyFields:ym(e,r),properties:mm(e,r)},s=nn(e);return s!==void 0&&(o.name=s),o}a(es,"toPresenceNode");function vm(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]),r=[];for(let o of[...n].sort()){if(um.has(o)||o==="id"||o==="z")continue;let s=e[o],i=t[o];if(Sl(s,i))continue;let d=os(s),l=os(i);if(xl.has(o)&&(d!==null||l!==null)){let c=d??"",u=l??"";r.push({key:o,kind:"text",before:c,after:u,lines:vl(c,u)});continue}r.push({key:o,kind:"scalar",before:s===void 0?null:s,after:i===void 0?null:i})}return r}a(vm,"compareNodes");function Al(e,t){let n=["label","disabled","info","env"],r=[];for(let o of n){let s=o==="disabled"?!1:o==="env"?[]:"";Sl(e[o]??s,t[o]??s)||r.push(o)}return r}a(Al,"compareTabMeta");function ts(e){return typeof e.label=="string"&&e.label.trim()?e.label.trim():""}a(ts,"flowLabelOf");function Er(e,t){let n=t.match(/\["(?:[^"\\]|\\.)*"\]|[^.[\]]+/g)??[],r=e;for(let o of n)if(o.startsWith("[")){let s=JSON.parse(o.slice(1,-1));r=Array.isArray(r)?r.find(i=>!!i&&typeof i=="object"&&i.name===s):void 0}else r=r&&typeof r=="object"&&Object.hasOwn(r,o)?r[o]:void 0;return r}a(Er,"conflictFieldValue");function ns(e,t){let n=kl(t),r=kl(e),o=ts(e)||ts(t),s=ts(t)||ts(e),i=[],d=[],l=[];for(let c of e.nodes??[])n.has(c.id)||i.push(es(c,"added",o,r));for(let c of e.configs??[])n.has(c.id)||i.push(es(c,"added",o,r));for(let c of t.nodes??[])r.has(c.id)||d.push(es(c,"removed",s,n));for(let c of t.configs??[])r.has(c.id)||d.push(es(c,"removed",s,n));for(let[c,u]of r){let p=n.get(c);if(!p)continue;let f=vm(p,u);if(f.length>0){let m={id:c,type:u.type||p.type,summary:wm(u,o||s,f.length),changes:f},h=nn(u)??nn(p);h!==void 0&&(m.name=h),l.push(m)}}return l.sort((c,u)=>c.id.localeCompare(u.id)),{summary:{added:i.length,removed:d.length,modified:l.length,tabChanges:Al(t,e),beforeNodeCount:n.size,afterNodeCount:r.size},tabProperties:Al(t,e).map(c=>({key:c,kind:"scalar",before:t[c],after:e[c]})),added:i,removed:d,modified:l}}a(ns,"buildDiffSections");function Ii(e,t,n={}){let r=ns(e.payload.flow,t.flow),o=n.currentRev??t.rev,s=e.expectedRev,i=n.baseRevChanged??(U(e.status)&&s!==o),d={proposalId:e.id,flowId:e.payload.flowId,expectedRev:s,baseRev:s,currentRev:o,baseRevChanged:i,compareAgainst:n.compareAgainst??"current",...r};return e.warnings&&e.warnings.length>0&&(d.warnings=e.warnings.map(l=>({...l}))),n.baseSnapshotMissing&&(d.baseSnapshotMissing=!0),n.vsCurrent&&(d.vsCurrent=n.vsCurrent),d}a(Ii,"buildProposalDiff");async function Bn(e,t,n=e){let r=e.payload.flowId,o={id:r,type:"tab",nodes:[],configs:[]},s=e.expectedRev,i=o;try{let u=await t.getFlow(r);s=u.rev,i=u.flow}catch(u){if(!(u instanceof X))throw u;try{s=(await t.getFlows()).rev}catch{}}let d=U(e.status),l=d&&e.expectedRev!==s,c=e.baseFlow;if(d){let u=$e(t),p=await u.getFlows(),f=di(n,p),m=u!==t,h=m?Ye(n):n,g=zt(p.flows,r,n.baseFlow)??o,y=m?ct(g).value:g,w=Ii(h,{rev:e.expectedRev,flow:h.baseFlow??y},{currentRev:p.rev,compareAgainst:h.baseFlow?"base":"current",baseSnapshotMissing:h.baseFlow===void 0,baseRevChanged:e.expectedRev!==p.rev});w.rebase={rev:f.rev,state:f.state,conflicts:m?ti(f.conflicts):f.conflicts,...f.mergedHash?{mergedHash:f.mergedHash}:{}};let v=a((P,I)=>I===r?P:[...P?.nodes??[],...P?.configs??[]].find(D=>D.id===I),"target");w.conflictValues=f.conflicts.map(P=>({nodeId:P.nodeId??"",fields:(P.fields?.length?P.fields:[""]).map(I=>({field:I,base:I?Er(v(h.baseFlow,P.nodeId),I):v(h.baseFlow,P.nodeId),live:I?Er(v(y,P.nodeId),I):v(y,P.nodeId),proposed:I?Er(v(h.payload.flow,P.nodeId),I):v(h.payload.flow,P.nodeId)}))}));let L=f.merged?m?ct(f.merged).value:f.merged:h.payload.flow;return w.aiOverrideValues=(h.aiConflictOverrides??[]).map(P=>({nodeId:P.nodeId,fields:P.fields.map(I=>({field:I,live:I?Er(v(y,P.nodeId),I):v(y,P.nodeId),proposed:I?Er(v(L,P.nodeId),I):v(L,P.nodeId)}))})),w.liveChanges=ns(y,h.baseFlow??y),f.state!=="conflict"&&f.merged&&(w.vsCurrent=ns(m?ct(f.merged).value:f.merged,y)),w}if(c!==void 0){let u=l?ns(e.payload.flow,i):void 0;return Ii(e,{rev:e.expectedRev,flow:c},{currentRev:s,compareAgainst:"base",baseRevChanged:l,...u?{vsCurrent:u}:{}})}return Ii(e,{rev:s,flow:i},{compareAgainst:"current",baseSnapshotMissing:!0,baseRevChanged:l})}a(Bn,"computeProposalDiff");Oe();nt();ze();ke();var Pl=require("path");function ss(e){return(0,Pl.join)(e.userDir,".ai-access")}a(ss,"getStorageDir");var km=["status:read","flows:read","proposals:create","proposals:read-own"];function Li(){return On()}a(Li,"readPackageVersion");function Am(e,t=Date.now()){let n=Date.parse(e);return Number.isFinite(n)?Math.max(0,Math.ceil((n-t)/1e3)):0}a(Am,"secondsRemainingUntil");function xm(e,t=Date.now()){let n=Date.parse(e);return Number.isFinite(n)?Math.max(0,Math.ceil((n-t)/6e4)):0}a(xm,"minutesRemainingUntil");function Sm(e,t){let n=xe(t),r=mt(t),o=`${n}/openapi.yaml`,s=`${n}/openapi.json`;return e==="rest"?{api:n,openapi:o,openapiJson:s,discovery:n}:e==="mcp"?{rpc:r,discovery:n}:{discovery:n}}a(Sm,"baseUrlsForTokenKind");function qn(e,t){let n=Yr(e,t),r=Ce(e.settings);if(t.auth){let o=t.auth.extra?.user;return{ok:!0,version:Li(),adminAuthEnabled:n.adminAuthEnabled,aiAccessEnabled:r.enabled,aiFeaturesEnabled:n.aiFeaturesEnabled,allowWithoutAdminAuth:n.allowWithoutAdminAuth,permission:t.auth.scopes.includes("propose")?"propose":"read",capabilities:{view:!0,manageLinks:!1,approveProposals:!1},defaultLinkTtlSeconds:r.defaultLinkTtlSeconds,maxActiveLinks:r.maxActiveLinks,defaultDeploymentType:r.defaultDeploymentType,caller:{kind:"oauth",clientId:t.auth.clientId,user:typeof o=="string"?o:"owner",scopes:t.auth.scopes}}}if(t.accessLink){let o=Date.now();return{ok:!0,version:Li(),adminAuthEnabled:n.adminAuthEnabled,aiAccessEnabled:r.enabled,aiFeaturesEnabled:n.aiFeaturesEnabled,allowWithoutAdminAuth:n.allowWithoutAdminAuth,permission:null,capabilities:{view:!0,manageLinks:!1,approveProposals:!1},defaultLinkTtlSeconds:r.defaultLinkTtlSeconds,maxActiveLinks:r.maxActiveLinks,defaultDeploymentType:r.defaultDeploymentType,caller:{kind:"access-link",linkId:t.accessLink.id,tokenKind:t.accessLink.kind,...t.accessLink.label!==void 0?{label:t.accessLink.label}:{},expiresAt:t.accessLink.expiresAt,minutesRemaining:xm(t.accessLink.expiresAt,o),secondsRemaining:Am(t.accessLink.expiresAt,o),permissions:[...km],baseUrls:Sm(t.accessLink.kind,e.settings.httpAdminRoot)}}}return{ok:!0,version:Li(),adminAuthEnabled:n.adminAuthEnabled,aiAccessEnabled:r.enabled,aiFeaturesEnabled:n.aiFeaturesEnabled,allowWithoutAdminAuth:n.allowWithoutAdminAuth,permission:n.permission,capabilities:n.capabilities,storageDir:ss(e.settings),defaultLinkTtlSeconds:r.defaultLinkTtlSeconds,maxActiveLinks:r.maxActiveLinks,defaultDeploymentType:r.defaultDeploymentType,caller:{kind:"admin",...n.permission?{permission:n.permission}:{}}}}a(qn,"buildStatus");function _l(e,t){let n=t.draftStore??new Mo,r=t.rateLimiters??rl(t.rateLimit??e.settings.aiAccess),o=e.httpAdmin,s="/mcp/a/:token",i=[r.ip,sl(e,t.linkStore),r.token];o.use("/mcp/a",zd()),o.get(s,...i,$((u,p)=>{z(p),p.setHeader("X-Content-Type-Options","nosniff");let f=xe(e.settings.httpAdminRoot),m=u.accessLink;if(!m){j(p,401,"Access link required","access_link_missing");return}let h=Xd({link:m,openapiPath:`${f}/openapi.yaml`,restApiBase:f,mcpRpcPath:mt(e.settings.httpAdminRoot),publicOrigin:xs(u)});p.status(200).type("text/markdown; charset=utf-8").send(h)})),o.get(`${s}/status`,...i,$((u,p)=>{z(p),p.json(qn(e,u))})),o.get(`${s}/flows`,...i,$(async(u,p)=>{z(p);try{p.json(await pt(t.flowAdapter))}catch(f){let m=W(f);if(m){p.status(m.statusCode).json({...m.body,hint:"Retry after the owner fixes the plugin runtime."});return}j(p,502,f instanceof Error?f.message:String(f),"upstream_error")}})),o.get(`${s}/flows/:id`,...i,$(async(u,p)=>{z(p),await Ko(t.flowAdapter,"get")(u,p)})),o.get(`${s}/nodes/:id`,...i,$(async(u,p)=>{z(p),await Jo(t.flowAdapter,"get")(u,p)})),o.get(`${s}/nodes/:id/field/:name`,...i,$(async(u,p)=>{z(p),await Vo(t.flowAdapter,"get")(u,p)})),o.get(`${s}/search`,...i,$(async(u,p)=>{z(p),await Yo(t.flowAdapter,"get")(u,p)}));let d={nodeTypes:t.nodeTypesAdapter,...t.catalogueFetcher?{catalogueFetcher:t.catalogueFetcher}:{},...t.catalogueUrl?{catalogueUrl:t.catalogueUrl}:{}};o.get(`${s}/node-types`,...i,$(async(u,p)=>{z(p),await Xo(d)(u,p)})),o.get(`${s}/node-types/:type`,...i,$(async(u,p)=>{z(p),await Zo(d)(u,p)})),o.get(`${s}/palette/search`,...i,$(async(u,p)=>{z(p),await Qo(d)(u,p)}));let l=e.settings.httpAdminRoot;Tm(o,s,i,t,l),o.get(`${s}/propose`,...i,$(pl(t.proposalStore,t.auditLog,t.flowAdapter,l,t.nodeTypesAdapter))),_m(o,s,i,n,t,l);let c=al();return o.all(`${s}/links`,c),o.all(`${s}/links/:id`,c),o.use("/mcp/a",il()),n}a(_l,"registerGetLinkRoutes");function Tm(e,t,n,r,o){e.get(`${t}/proposals`,...n,$(async(s,i)=>{z(i);let d=s.accessLink;if(!d){j(i,401,"Access link required","access_link_missing");return}let l=V(s,o),c=ge(s.query),u=_o(s.query),p=await tt(r.flowAdapter),f=p?De(r.proposalStore,p).expired:te(r.proposalStore),m=En(r.proposalStore,d,l,u);i.json({proposals:m.proposals.map(h=>he(r.proposalStore,d,h,{...l,include:c},{includeConflicts:!1})),...Jt({...m,returned:m.proposals.length}),...pe(f)})})),e.get(`${t}/proposals/:id`,...n,$(async(s,i)=>{z(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){j(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){j(i,401,"Access link required","access_link_missing");return}try{let c=V(s,o),u=ge(s.query),p=await tt(r.flowAdapter),f=ut(r.proposalStore,l,d,c);i.json(he(r.proposalStore,l,f,{...c,include:u},{...p?{live:p}:{}}))}catch(c){let u=W(c,"get");if(u){i.status(u.statusCode).json({...u.body,hint:u.body.hint??"List proposals with GET \u2026/proposals."});return}B(i,c);return}})),Rm(e,t,n,r,o),Pm(e,t,n,r,o)}a(Tm,"registerGetLinkProposalRoutes");function Rm(e,t,n,r,o){e.get(`${t}/proposals/:id/withdraw`,...n,$((s,i)=>{z(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){j(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){j(i,401,"Access link required","access_link_missing");return}let c=s.query.confirm;if(c!=="1"&&c!=="true"){j(i,400,"Withdraw over GET requires confirm=1","confirm_required",we("confirm_required","get"));return}let u=typeof s.query.reason=="string"?s.query.reason:void 0,p=V(s,o),f=ge(s.query);try{let m=In(r.proposalStore,r.auditLog,l,d,u,p);i.json(he(r.proposalStore,l,m,{...p,include:f}))}catch(m){let h=W(m,"get");if(h){let g=h.body.code;j(i,h.statusCode,ot[g]??"Request failed",g,we(g,"get"));return}j(i,500,"Internal server error","internal_error");return}}))}a(Rm,"registerGetLinkWithdrawRoute");function Pm(e,t,n,r,o){e.get(`${t}/proposals/:id/diff`,...n,$(async(s,i)=>{z(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){j(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){j(i,401,"Access link required","access_link_missing");return}try{let c=ut(r.proposalStore,l,d,V(s,o)),u=H(s.query,"format"),p=Mn(u==="json"||u==="md"?void 0:u);if(!p.ok){j(i,400,"Invalid diff format","invalid_diff_format","Valid formats: summary, full (default summary).");return}i.json(Dn(await Bn(c,r.flowAdapter,r.proposalStore.get(d)),p.format))}catch(c){let u=W(c);if(u){i.status(u.statusCode).json(u.body);return}B(i,c);return}}))}a(Pm,"registerGetLinkDiffRoute");function _m(e,t,n,r,o,s){e.get(`${t}/draft/new`,...n,$(ll(r,o.flowAdapter))),e.get(`${t}/draft/:draftId/append`,...n,$(cl(r))),e.get(`${t}/draft/:draftId/submit`,...n,$(ul(r,o.proposalStore,o.auditLog,o.flowAdapter,s,o.nodeTypesAdapter)))}a(_m,"registerGetLinkDraftRoutes");oe();var Cl=require("module");function Cm(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,Cl.createRequire)(e)}a(Cm,"createPluginRequire");function Em(e){try{let r=Cm()("@node-red/runtime")._?.adminApi?.httpAdmin;if(r&&typeof r.use=="function"&&r!==e.httpAdmin)return r}catch{}return null}a(Em,"resolveEditorHttpAdmin");function El(e){let t=[e.httpAdmin],n=Em(e);return n&&t.push(n),t}a(El,"httpAdminAppsForMcpErrors");var Il=Symbol.for("com55.ai-access.mcpJsonErrorHandler");function Im(e){let t=e??"/";for(;t.endsWith("/");)t=t.slice(0,-1);let n=new Set;for(let r of t?["",t]:[""])for(let o of["/mcp","/mcp/rpc"])n.add(r+o),n.add(`${r+o}/`);return r=>n.has(r)}a(Im,"mcpRpcMatcher");function Ni(e){return e.includes("/mcp")}a(Ni,"isMcpPath");function Lm(e){return e.path||e.url||e.originalUrl||""}a(Lm,"requestPath");function Nm(e){if(!e||typeof e!="object")return!1;let t=e;if(t.type==="entity.parse.failed"||e instanceof SyntaxError)return!0;let n=typeof e.message=="string"?e.message:"";return/JSON/i.test(n)&&(t.status===400||t.statusCode===400)}a(Nm,"isJsonParseError");function Fm(e){if(!e||typeof e!="object")return!1;let t=e;return t.type==="entity.too.large"||t.status===413||t.statusCode===413||t.message==="request entity too large"}a(Fm,"isPayloadTooLarge");function Om(e){let t=Im(e);return(n,r,o,s)=>{if(o.headersSent){s(n);return}let i=Lm(r);if(Nm(n)&&Ni(i)){if(t(i)){o.status(400).json({jsonrpc:"2.0",error:{code:-32700,message:"Parse error"},id:null});return}o.status(400).json({error:"Invalid JSON body",code:"invalid_json"});return}if(Fm(n)&&Ni(i)){o.status(413).json({error:`Request body exceeds limit of ${K} bytes`,code:"payload_too_large"});return}if(Ni(i)){if(t(i)){o.status(500).json({jsonrpc:"2.0",error:{code:-32603,message:$o(n).error,data:{code:"internal_error"}},id:null});return}o.status(500).json($o(n));return}s(n)}}a(Om,"mcpJsonErrorHandler");function $m(e,t){let n=e;n[Il]||(n[Il]=!0,e.use(Om(t)))}a($m,"attachOnce");function Ll(e){for(let t of El(e))$m(t,e.settings.httpAdminRoot)}a(Ll,"attachMcpJsonErrorHandlers");Rr();var rn=class extends Error{static{a(this,"AccessLinkLimitError")}code="access_link_limit";statusCode=409;constructor(t){super(`Cannot create access link: at most ${t} active links are allowed. Revoke an existing link first.`),this.name="AccessLinkLimitError"}},on=class extends Error{static{a(this,"AccessLinkNotFoundError")}code="access_link_not_found";statusCode=404;constructor(t){super(`Access link '${t}' was not found`),this.name="AccessLinkNotFoundError"}},Ir=class extends Error{static{a(this,"AccessLinkValidationError")}code="access_link_invalid";statusCode=400;constructor(t){super(t),this.name="AccessLinkValidationError"}},It=class extends Error{static{a(this,"AccessLinkTtlError")}code="invalid_ttl";statusCode=400;hint=Vt;constructor(t){super(t),this.name="AccessLinkTtlError"}};var sn=require("crypto");Rr();function Fi(e){return e==="get_link"||e==="rest"||e==="mcp"}a(Fi,"isAccessLinkKind");function Oi(e){return e??"get_link"}a(Oi,"resolveAccessLinkKind");function Nl(e){return(0,sn.createHash)("sha256").update(e,"utf8").digest("hex")}a(Nl,"hashToken");function Mm(e,t){let n=Buffer.from(e,"utf8"),r=Buffer.from(t,"utf8");return n.length!==r.length?!1:(0,sn.timingSafeEqual)(n,r)}a(Mm,"hashesEqual");function Lr(e){let t={id:e.id,kind:Oi(e.kind),createdAt:new Date(e.createdAtMs).toISOString(),expiresAt:new Date(e.expiresAtMs).toISOString()};return e.label!==void 0&&(t.label=e.label),e.lastUsedAtMs!==void 0&&(t.lastUsedAt=new Date(e.lastUsedAtMs).toISOString()),e.createdBy!==void 0&&(t.createdBy=e.createdBy),t}a(Lr,"toPublic");var an=class{static{a(this,"AccessLinkStore")}links=new Map;defaultTtlSeconds;maxActiveLinks;now;randomId;randomToken;constructor(t={}){this.defaultTtlSeconds=t.defaultTtlSeconds??14400,this.maxActiveLinks=t.maxActiveLinks??10,this.now=t.now??(()=>Date.now()),this.randomId=t.randomId??(()=>(0,sn.randomBytes)(12).toString("hex")),this.randomToken=t.randomToken??(()=>`aia_${(0,sn.randomBytes)(32).toString("base64url")}`)}configureDefaults(t){typeof t.defaultTtlSeconds=="number"&&Number.isFinite(t.defaultTtlSeconds)&&t.defaultTtlSeconds>0&&(this.defaultTtlSeconds=Math.floor(t.defaultTtlSeconds)),typeof t.maxActiveLinks=="number"&&Number.isFinite(t.maxActiveLinks)&&t.maxActiveLinks>0&&(this.maxActiveLinks=Math.floor(t.maxActiveLinks))}isActive(t,n){return t.revokedAtMs!==void 0?!1:t.expiresAtMs>n}list(){let t=this.now();return[...this.links.values()].filter(n=>this.isActive(n,t)).sort((n,r)=>r.createdAtMs-n.createdAtMs).map(Lr)}create(t={}){let n=this.now();if([...this.links.values()].filter(p=>this.isActive(p,n)).length>=this.maxActiveLinks)throw new rn(this.maxActiveLinks);let o=this.defaultTtlSeconds;if(t.ttlSeconds!==void 0){if(typeof t.ttlSeconds!="number"||!Number.isFinite(t.ttlSeconds)||t.ttlSeconds<=0)throw new It("ttlSeconds must be a positive number");o=Math.floor(t.ttlSeconds)}let s=typeof t.label=="string"&&t.label.trim().length>0?t.label.trim():void 0,i=Oi(t.kind),d=wi(i);if(o>d)throw new It(bi(i,d));let l=this.randomId(),c=this.randomToken(),u={id:l,tokenHash:Nl(c),kind:i,createdAtMs:n,expiresAtMs:n+o*1e3};return s!==void 0&&(u.label=s),t.createdBy!==void 0&&(u.createdBy=t.createdBy),this.links.set(l,u),{...Lr(u),token:c}}revoke(t){let n=this.links.get(t);if(!n)throw new on(t);return n.revokedAtMs??=this.now(),Lr(n)}getById(t){if(typeof t!="string"||t.length===0)return null;let n=this.links.get(t);return n?Lr(n):null}validateResult(t){if(typeof t!="string"||t.length===0)return{status:"invalid"};let n=Nl(t),r=this.now();for(let o of this.links.values())if(Mm(o.tokenHash,n))return o.revokedAtMs!==void 0?{status:"invalid"}:o.expiresAtMs<=r?{status:"expired",expiresAt:new Date(o.expiresAtMs).toISOString()}:(o.lastUsedAtMs=r,{status:"active",link:Lr(o)});return{status:"invalid"}}validate(t){let n=this.validateResult(t);return n.status==="active"?n.link:null}size(){return this.links.size}clear(){this.links.clear()}upsertRecordForTest(t){let n={...t};delete n.token,this.links.set(t.id,n)}};Rr();ve();ke();function $i(e,t){return t}a($i,"storeForRequest");function Ol(e){let t=typeof e.user?.username=="string"?e.user.username:void 0;return t?{kind:"admin",username:t}:{kind:"admin"}}a(Ol,"adminActor");function is(e){let t=Ce(e.settings);return new an({defaultTtlSeconds:t.defaultLinkTtlSeconds,maxActiveLinks:t.maxActiveLinks})}a(is,"createConfiguredLinkStore");function $l(e){e.setHeader("Cache-Control","no-store"),e.setHeader("Pragma","no-cache")}a($l,"noStore");function Ml(e,t){return(n,r)=>{let s=$i(e,t).list();$l(r),r.json({links:s})}}a(Ml,"listLinksHandler");function Dl(e,t,n){return(r,o)=>{let s=r.body??{},i={};if(s.ttlSeconds!==void 0){if(typeof s.ttlSeconds!="number"){o.status(400).json({error:"ttlSeconds must be a positive number",code:"invalid_ttl",hint:Vt});return}i.ttlSeconds=s.ttlSeconds}if(typeof s.label=="string"&&(i.label=s.label),s.kind!==void 0){if(!Fi(s.kind)){o.status(400).json({error:"kind must be one of: 'get_link', 'rest', 'mcp'",code:"invalid_link_kind"});return}i.kind=s.kind}typeof r.user?.username=="string"&&(i.createdBy=r.user.username);try{let d=$i(e,t).create(i),l=d.kind;if(n){let c=Date.parse(d.createdAt),u=Date.parse(d.expiresAt),p=Number.isFinite(c)&&Number.isFinite(u)&&u>c?Math.round((u-c)/1e3):void 0;n.append("link.created",Ol(r),{linkId:d.id,label:d.label,expiresAt:d.expiresAt,createdAt:d.createdAt,kind:l,...p!==void 0?{ttlSeconds:p}:{}})}if($l(o),l==="get_link"){let c=wa(r,e.settings.httpAdminRoot,d.token);o.status(201).json({...d,url:c});return}o.status(201).json({...d})}catch(d){if(d instanceof rn){o.status(d.statusCode).json({error:d.message,code:d.code});return}if(d instanceof It){o.status(d.statusCode).json({error:d.message,code:d.code,hint:d.hint});return}if(d instanceof Ir){o.status(d.statusCode).json({error:d.message,code:d.code});return}B(o,d);return}}}a(Dl,"createLinkHandler");function Hl(e,t,n){return(r,o)=>{let s=r.params.id;if(typeof s!="string"||s.length===0){o.status(400).json({error:"Missing link id"});return}try{let i=$i(e,t).revoke(s);if(n){let d=Date.parse(i.createdAt),l=Date.parse(i.expiresAt),c=Number.isFinite(d)&&Number.isFinite(l)&&l>d?Math.round((l-d)/1e3):void 0;n.append("link.revoked",Ol(r),{linkId:i.id,label:i.label,expiresAt:i.expiresAt,createdAt:i.createdAt,kind:i.kind,...c!==void 0?{ttlSeconds:c}:{}})}o.json({revoked:!0,link:i})}catch(i){if(i instanceof on){o.status(i.statusCode).json({error:i.message,code:i.code});return}B(o,i);return}}}a(Hl,"revokeLinkHandler");function Dm(e){let t=Date.parse(e.createdAt),n=Date.parse(e.expiresAt);if(Number.isFinite(t)&&Number.isFinite(n)&&n>t)return Math.round((n-t)/1e3)}a(Dm,"ttlSecondsFromLink");function Hm(e,t){if(!t||e.action!=="link.created"&&e.action!=="link.revoked")return e;let n={...e.details??{}},r=typeof n.linkId=="string"?n.linkId:"";if(!r)return e;let o=n.label==null||n.label==="",s=n.expiresAt==null||n.expiresAt==="",i=n.createdAt==null||n.createdAt==="",d=n.ttlSeconds==null;if(!o&&!s&&!i&&!d)return e;let l=t.getById(r);if(!l)return e;if(o&&l.label&&(n.label=l.label),s&&(n.expiresAt=l.expiresAt),i&&(n.createdAt=l.createdAt),d){let c=Dm(l);c!==void 0&&(n.ttlSeconds=c)}return{...e,details:n}}a(Hm,"enrichAuditLogEntry");function jl(e,t){return(n,r)=>{let o=n.query.limit,s=100;if(typeof o=="string"&&o.length>0){let d=Number(o);Number.isFinite(d)&&d>0&&(s=Math.min(Math.floor(d),500))}let i=e.list(s).map(d=>Hm(d,t));r.json({logs:i})}}a(jl,"listLogsHandler");var Di=require("@modelcontextprotocol/sdk/server/mcp.js"),Jl=require("@modelcontextprotocol/sdk/server/streamableHttp.js"),_=require("zod");Ve();oe();Mt();se();rt();Ut();Wt();Oe();nt();var Ul=require("async_hooks");Pe();var jm="ai.redacted",Bl=new Ul.AsyncLocalStorage;function ql(e,t,n,r){if(!(r<=0))try{e.append(jm,t,{redactions:r,operation:n})}catch{}}a(ql,"appendRedactionAudit");function zl(e){let t=Bl.getStore();if(!t)return;let n=ko(e);n>0&&t.record(n)}a(zl,"noteResponseRedactions");function Wl(e,t,n,r){return Bl.run({record:a(o=>ql(e,t,n,o),"record")},r)}a(Wl,"runWithRedactionAudit");function Gl(e){let t=Array.isArray(e)?e[0]:e;if(t&&typeof t=="object"){let n=t;if(n.method==="tools/call"&&typeof n.params?.name=="string")return`mcp:${n.params.name.replace(/[^A-Za-z0-9_.-]/g,"").slice(0,64)}`;if(typeof n.method=="string")return`mcp:${n.method.slice(0,64)}`}return"mcp"}a(Gl,"mcpOperationName");function Mi(e){let t=e.accessLink;if(t)return{kind:"access-link",linkId:t.id,...t.label!==void 0?{label:t.label}:{}}}a(Mi,"accessLinkAuditActor");function Kl(e){return(t,n,r)=>{if(!t.path.startsWith("/mcp/")||t.path.startsWith("/mcp/rpc")){r();return}let o=n.json.bind(n);n.json=(s=>{let i=Mi(t);if(i&&n.statusCode<400){let d;try{d=String(JSON.stringify(s))}catch{d=""}let l=t.route?.path,c=`${t.method} ${typeof l=="string"?l:"unknown"}`;ql(e,i,c,ko(d))}return o(s)}),r()}}a(Kl,"redactionAuditMiddleware");Pe();function Vl(){return"0.2.0-beta.8"}a(Vl,"pluginVersion");function ae(e){let t=JSON.stringify(e);return zl(t),{content:[{type:"text",text:t}]}}a(ae,"jsonResult");function Hi(e){return e===!0?new Set(["payload","baseFlow"]):new Set}a(Hi,"includeSetFromPayloadFlag");function de(e,t=500){let n=Tr(e,"mcp");if(n)return{isError:!0,content:[{type:"text",text:JSON.stringify({...n.body,statusCode:n.statusCode})}]};let r=e instanceof Error?e.message:String(e);return{isError:!0,content:[{type:"text",text:JSON.stringify({error:r,code:"internal_error",statusCode:t})}]}}a(de,"jsonError");var Um=new Set(["list_proposals","list_my_proposals"]),Bm=new Set(["status","limit","offset","mine","flowId"]);function qm(e){return!!e&&typeof e=="object"&&typeof e.safeParseAsync=="function"}a(qm,"isZodLikeSchema");function zm(e,t,n){let r=t&&typeof t=="object"?t:{},o=n[0],s=o?.path&&o.path.length>0?String(o.path[0]):"(arguments)";if(Um.has(e)&&Bm.has(s))return Cn(s,r[s]);let i=n.map(l=>({parameter:l.path&&l.path.length>0?l.path.map(String).join("."):"(arguments)",message:l.message??"Invalid value"})),d=i.length===1?`Invalid argument '${i[0]?.parameter}' for tool ${e}: ${i[0]?.message}`:`${i.length} invalid arguments for tool ${e}`;return new k(d,"invalid_arguments",{details:{parameter:s,errors:i,hint:`Fix '${s}' to match the ${e} input schema (see tools/list).`}})}a(zm,"mcpArgumentError");function Yl(e){let t=e,n=t.validateToolInput;typeof n=="function"&&(t.validateToolInput=async(r,o,s)=>{let i=r.inputSchema;if(!qm(i))return n.call(e,r,o,s);let d=await i.safeParseAsync(o??{});if(d.success)return d.data;let l=Tr(zm(s,o,d.error.issues),"mcp"),c=l?{...l.body,statusCode:l.statusCode}:{};throw new Error(JSON.stringify(c))})}a(Yl,"installMcpArgumentErrorMapping");function Xl(e,t,n,r){e.registerTool("get_status",{description:'Return plugin status (version, AI feature flags, storage path, deploy defaults). For access-link callers, permission is null and caller.kind is "access-link" \u2014 that is expected, not an error. Use before other tools to confirm the plugin is enabled.'},()=>{try{return ae(qn(t,r))}catch(o){return de(o)}}),e.registerTool("list_flows",{description:'List all flows as a flat Admin API v2 array plus the current rev string. Likely secrets are shown as "[REDACTED]" (nodes list redacted paths in _aiRedactions). Returns { rev, flows }. Call this (or get_flow) before submit_proposal so expectedRev matches.'},async()=>{try{return ae(await pt(n.flowAdapter))}catch(o){return de(o,502)}}),e.registerTool("get_flow",{description:"Get one tab/subflow by id with nested nodes/configs and the current rev. Pass summary=true for a lightweight node list (id/type/name/wires/bytes, no field contents). Prefer summary \u2192 search_flows \u2192 get_node_field(lines) \u2192 replace_text \u2192 submit_proposal. Returns { rev, flow } or summary shape. Use rev as expectedRev when submitting.",inputSchema:{id:_.z.string().min(1).describe("Flow / tab id"),summary:_.z.boolean().optional().describe("When true, return node list with sizes only (no field contents)")}},async({id:o,summary:s})=>{try{return ae(s?await Uo(n.flowAdapter,o):await He(n.flowAdapter,o))}catch(i){return de(i,502)}}),e.registerTool("get_node",{description:"Get one full node by id (includes owning flowId when known). Optional flow scopes the lookup.",inputSchema:{id:_.z.string().min(1).describe("Node id"),flow:_.z.string().min(1).optional().describe("Optional tab id to scope the lookup")}},async({id:o,flow:s})=>{try{return ae(await Bo(n.flowAdapter,o,s))}catch(i){return de(i,502)}}),e.registerTool("get_node_field",{description:'Get one string field from a node. Optional lines="A-B" (1-based inclusive) returns a slice; response always includes totalLines and bytes.',inputSchema:{id:_.z.string().min(1).describe("Node id"),field:_.z.string().min(1).describe("Field name (e.g. template, func)"),lines:_.z.string().optional().describe("Optional 1-based line range A or A-B"),flow:_.z.string().min(1).optional().describe("Optional tab id to scope the lookup")}},async({id:o,field:s,lines:i,flow:d})=>{try{return ae(await qo(n.flowAdapter,o,s,{...d?{flowId:d}:{},...i?{lines:i}:{}}))}catch(l){return de(l,502)}}),e.registerTool("search_flows",{description:"Text search across string fields. Returns hits with node id, field, line numbers, and a short snippet. Results are capped; truncated=true when the cap is hit. Optional flow scopes to one tab.",inputSchema:{q:_.z.string().describe("Substring to find (case-sensitive; non-empty required)"),flow:_.z.string().min(1).optional().describe("Optional tab id")}},async({q:o,flow:s})=>{try{return ae(await zo(n.flowAdapter,o,s?{flowId:s}:{}))}catch(i){return de(i,502)}}),e.registerTool("list_node_types",{description:"List installed Node-RED palette node types (type, module, category, description, \u2026). Optional q filters type/module/category/description (case-insensitive). Results are capped. Call this before proposing nodes with unfamiliar types.",inputSchema:{q:_.z.string().optional().describe("Optional case-insensitive filter")}},async({q:o})=>{try{return ae(await po(n.nodeTypesAdapter,o?{q:o}:{}))}catch(s){return de(s,502)}}),e.registerTool("get_node_type",{description:"Get detail for one installed node type: defaults (JSON), inputs/outputs (always set), requiredConfigNodeTypes, help sections (summary/inputs/outputs), category/module/color. Types are case-sensitive. Pass includeHelp=false to omit help. 404 node_type_not_found when unknown (didYouMean on case mismatch; else nearest names).",inputSchema:{type:_.z.string().min(1).describe("Node type id (e.g. inject, template) \u2014 case-sensitive"),includeHelp:_.z.boolean().optional().describe("When false, omit help sections (default true)")}},async({type:o,includeHelp:s})=>{try{return ae(await jt(n.nodeTypesAdapter,o,{includeHelp:s!==!1}))}catch(i){return de(i,502)}}),e.registerTool("search_palette",{description:"Search the public Node-RED catalogue for modules to recommend. Returns module id, description, version, updated, link, and installed boolean (cross-checked against installed modules). Core node-red types are not in the catalogue \u2014 use list_node_types. RECOMMEND ONLY \u2014 never installs.",inputSchema:{q:_.z.string().describe("Search string (id / description / keywords; non-empty required)")}},async({q:o})=>{try{return ae(await fo(n.nodeTypesAdapter,o,{...n.catalogueFetcher?{catalogueFetcher:n.catalogueFetcher}:{},...n.catalogueUrl?{catalogueUrl:n.catalogueUrl}:{}}))}catch(s){return de(s,502)}})}a(Xl,"registerReadTools");function Zl(e,t,n,r,o){let s=V(r,t.settings.httpAdminRoot);e.registerTool("submit_proposal",{description:`Submit a replace_flow proposal for owner approval (does not deploy). Preferred for small string edits: payload: { flowId, nodes: [{ op:"replace_text", node, field, find, replace, occurrence? }] } \u2014 find must be unique unless occurrence (1-based) is set; multiple replace_text ops apply in order; yields a short readable diff. Or send a full tab: payload.flow from get_flow (id, type, label?, nodes[], configs?). Inputs (all top-level, next to each other): type="replace_flow", expectedRev (required string from list_flows/get_flow rev; expected_rev alias accepted), optional title (\u226480 chars), and payload: { flowId, flow? } and/or payload.nodes / top-level nodes. Do not nest expectedRev inside payload. Secret values appear as "[REDACTED]" in reads; leave the placeholder unchanged to keep the stored secret (it is restored on submit), or replace it with a new value to change it. Payload size limit is ${K} bytes (413 if exceeded). Recent known expectedRev snapshots are accepted and auto-rebased; unknown revisions return 409 version_mismatch. Open proposals auto-rebase on revision changes; conflict blocks approval until resolved. Proposals expire after the configured TTL (default 7 days). replace_text errors: 400 text_not_found; 409 ambiguous_match with matchCount. Unknown palette types return 400 unknown_node_type with nearest matches \u2014 call list_node_types / search_palette and ask the user to install via Manage palette (never auto-install). Returns a lean proposal (id, status, title, storedTitle, flowLabel, displayName, reviewUrl, changeSummary, \u2026) by default; title may gain " in <flow>"; storedTitle is the title as saved (your title without that suffix, or the auto-generated title, which may already name the flow). Pass include_payload=true to include payload and baseFlow. When telling the user about the proposal, cite displayName and reviewUrl \u2014 not the raw id. Humans approve on the /ai-access dashboard (reviewUrl opens/scrolls to it).`,inputSchema:{type:_.z.literal("replace_flow").describe("Must be 'replace_flow'"),expectedRev:_.z.string().min(1).optional().describe("Top-level current flow revision from get_flow/list_flows (required at submit; sit next to type and payload, not inside payload)"),expected_rev:_.z.string().min(1).optional().describe("Alias for expectedRev (snake_case); prefer expectedRev"),title:_.z.string().max(80).optional().describe("Optional short human title (\u226480 chars). Auto-generated from changes when omitted."),include_payload:_.z.boolean().optional().describe("When true, include payload and baseFlow in the response (default lean)"),nodes:_.z.array(_.z.record(_.z.string(),_.z.unknown())).optional().describe("Optional node ops (set/add/remove/replace_text). Same as payload.nodes when flow is omitted."),payload:_.z.object({flowId:_.z.string().min(1).describe("Tab id to replace"),flow:_.z.object({id:_.z.string().optional(),type:_.z.string().optional().describe("Usually 'tab' (included by get_flow)"),label:_.z.string().optional(),nodes:_.z.array(_.z.record(_.z.string(),_.z.unknown())).optional().describe("Child nodes for the tab (from get_flow.nodes)"),configs:_.z.array(_.z.record(_.z.string(),_.z.unknown())).optional()}).passthrough().optional().describe("Full tab from get_flow; omit when using nodes ops"),nodes:_.z.array(_.z.record(_.z.string(),_.z.unknown())).optional().describe("Node ops applied to the live tab (prefer replace_text for small string edits)")}).describe("Replacement flow payload and/or node ops")}},async i=>{try{let d=await Tt(n.proposalStore,n.auditLog,o,{...i,expectedRev:typeof i.expectedRev=="string"&&i.expectedRev.length>0?i.expectedRev:i.expected_rev},n.flowAdapter,s,n.nodeTypesAdapter),l={...ie(d,{...s,include:Hi(i.include_payload)}),mine:!0},c=te(n.proposalStore),u=et(Qe(n.proposalStore,n.proposalStore.get(d.id),o.id));return ae({...l,...u,...pe(c)})}catch(d){return de(d)}}),Gm(e,n,o,s),Km(e,n,o,s)}a(Zl,"registerProposalTools");function Wm(e,t,n,r){let o={mine:_.z.boolean().optional().describe("true: only proposals created by this access link; false: only other links\u2019 proposals"),status:_.z.union([_.z.string(),_.z.array(_.z.string())]).optional().describe("Filter by proposal status: one value, an array, or a comma-separated string (pending, conflict, approved, rejected, failed, withdrawn, expired, superseded). An empty array or empty string means no status filter."),flowId:_.z.string().optional().describe("Filter by flow tab id (payload.flowId)"),limit:_.z.number().int().min(1).max(_n).optional().describe(`Page size (1\u2013${_n}). Omit to return all matches.`),offset:_.z.number().int().min(0).optional().describe("Skip this many filtered results (default 0)"),include_payload:_.z.boolean().optional().describe("When true, include payload and baseFlow on each proposal")},s=a(async i=>{try{let d=Hi(i.include_payload),l=await tt(t.flowAdapter),c=l?De(t.proposalStore,l).expired:te(t.proposalStore),u={};typeof i.mine=="boolean"&&(u.mine=i.mine);let p=mi(i.status);p&&(u.status=p),typeof i.flowId=="string"&&i.flowId.length>0&&(u.flowId=i.flowId),typeof i.limit=="number"&&(u.limit=i.limit),typeof i.offset=="number"&&(u.offset=i.offset);let f=En(t.proposalStore,n,r,u);return ae({proposals:f.proposals.map(m=>he(t.proposalStore,n,m,{...r,include:d},{includeConflicts:!1})),...Jt({...f,returned:f.proposals.length}),...pe(c)})}catch(d){return de(d)}},"listProposalsHandler");e.registerTool("list_proposals",{description:"List proposals visible to this access link (all proposals by default). Each item includes mine:true|false for the caller. Filter with mine, status, and/or flowId; paginate with limit/offset (response includes total, offset, hasMore, nextOffset). Invalid filters return invalid_filter. Returns lean { proposals, total, \u2026 } with title, displayName, reviewUrl, changeSummary (no payload/baseFlow unless include_payload=true). Prefer displayName + reviewUrl when referring to a proposal with the user (not the raw id).",inputSchema:o},s),e.registerTool("list_my_proposals",{description:"Alias of list_proposals. Prefer list_proposals. Lists proposals visible to this access link with optional mine/status/flowId filters and limit/offset pagination.",inputSchema:o},s)}a(Wm,"registerListProposalsTools");function Gm(e,t,n,r){Wm(e,t,n,r),e.registerTool("get_proposal",{description:"Get any proposal by id (all tokens can read). Response includes mine:true|false and, for open proposals (pending or conflict), conflictingProposals when other open work touches the same flow. baseRev aliases expectedRev; baseRevChanged is informational. rebase.state=clean|merged means mergeable; conflict blocks approval. Lean by default (no payload/baseFlow); pass include_payload=true for full bodies. Cite displayName + reviewUrl when talking to the user.",inputSchema:{id:_.z.string().min(1).describe("Proposal id (prop-\u2026)"),include_payload:_.z.boolean().optional().describe("When true, include payload and baseFlow")}},async({id:o,include_payload:s})=>{try{let i=await tt(t.flowAdapter),d=ut(t.proposalStore,n,o,r);return ae(he(t.proposalStore,n,d,{...r,include:Hi(s)},{...i?{live:i}:{}}))}catch(i){return de(i)}})}a(Gm,"registerProposalQueryTools");function Km(e,t,n,r){e.registerTool("get_proposal_diff",{description:"Diff any proposal against its stored baseFlow snapshot (what changed). Optional format=summary|full (default summary). Open proposals include rebase and liveChanges; conflict blocks approval, otherwise vsCurrent shows the merged deployment.",inputSchema:{id:_.z.string().min(1).describe("Proposal id (prop-\u2026)"),format:_.z.enum(["summary","full"]).optional().describe("Diff payload format (default summary)")}},async({id:o,format:s})=>{try{let i=ut(t.proposalStore,n,o),d=Mn(s??"summary");if(!d.ok)return de(new k("Invalid diff format","invalid_diff_format",{details:{hint:"Valid formats: summary, full (default summary)."}}));let l=Dn(await Bn(i,t.flowAdapter,t.proposalStore.get(o)),d.format);return ae(l)}catch(i){return de(i)}}),e.registerTool("resolve_proposal_conflict",{description:"Resolve your own open proposal. Different node/tab fields auto-merge; only fields changed on both sides conflict (x/y never conflict). Choose one current conflict using nodeId + choice, preserving all non-conflicting fields. Alternatively replace the proposal using expectedRev + payload: nodes ops apply to the flow snapshot at expectedRev, NOT the stored proposal or merged result; include every intended proposal edit. Read get_proposal_diff for conflicts and get_flow for a fresh rev. These two modes cannot be mixed.",inputSchema:{id:_.z.string().min(1).describe("Your open proposal id (prop-\u2026)"),nodeId:_.z.string().min(1).optional().describe("Conflict nodeId from rebase.conflicts; use the flow id for tab/env conflicts. Required with choice."),choice:_.z.enum(["live","proposed"]).optional().describe("For this conflict, keep live or proposed values of overlapping fields only. Disjoint edits survive. Use with nodeId, without expectedRev/title/payload."),expectedRev:_.z.string().min(1).optional().describe("Revision of the flow snapshot on which replacement payload ops run. Required with payload; normally the current get_flow rev."),title:_.z.string().max(80).optional().describe("Optional replacement title (body replacement mode only)"),payload:_.z.object({flowId:_.z.string().min(1).describe("Target flow id; must remain the proposal\u2019s flow id"),flow:_.z.record(_.z.string(),_.z.unknown()).optional().describe("Full replacement tab based on expectedRev; preserve all desired edits. Use either flow or nodes."),nodes:_.z.array(_.z.record(_.z.string(),_.z.unknown())).optional().describe("set/add/remove/replace_text operations applied to the flow at expectedRev, not to the stored proposal. Env merges by unique name and credentials by key; same-field edits conflict.")}).optional().describe("Replacement body; required with expectedRev, omitted with nodeId + choice")}},async({id:o,...s})=>{try{let i=await Co(t.proposalStore,t.auditLog,n,o,s,t.flowAdapter,r,t.nodeTypesAdapter);return ae(he(t.proposalStore,n,i,r))}catch(i){return de(i)}}),e.registerTool("withdraw_proposal",{description:"Withdraw one of YOUR open (pending or conflict) proposals (status becomes withdrawn, not rejected). Optional reason. Only the creating access link can withdraw via MCP; other tokens get 403. If it is lost, a full admin session can recover via POST /proposals/{id}/withdraw. Cite displayName + reviewUrl when telling the user.",inputSchema:{id:_.z.string().min(1).describe("Proposal id (prop-\u2026) owned by this access link"),reason:_.z.string().optional().describe("Optional withdraw reason")}},({id:o,reason:s})=>{try{let i=In(t.proposalStore,t.auditLog,n,o,s,r);return ae(he(t.proposalStore,n,i,r))}catch(i){return de(i)}})}a(Km,"registerProposalActionTools");function Jm(e,t,n){let r=new Di.McpServer({name:"node-red-ai-access",version:Vl()});Yl(r);let o=n.accessLink;if(!o)throw new Error("Access link required");return Xl(r,e,t,n),Zl(r,e,t,n,o),r}a(Jm,"createAiMcpServer");function Vm(e){let t=Mi(e);if(t)return t;let n=e.auth?.clientId??"",r=typeof e.auth?.extra?.user=="string"?e.auth.extra.user:"owner";return{kind:"access-link",linkId:so(n,r),label:"oauth"}}a(Vm,"mcpAuditActor");function ji(e,t){return async(n,r,o)=>{try{if(!n.accessLink&&!n.auth){r.setHeader("WWW-Authenticate",fe),r.status(401).json({error:Ie,code:at});return}let s={...t,flowAdapter:At(t.flowAdapter)},i=n.accessLink?Jm(e,s,n):Ym(e,s,n),d=new Jl.StreamableHTTPServerTransport({enableJsonResponse:!0});await i.connect(d),await Wl(t.auditLog,Vm(n),Gl(n.body),()=>d.handleRequest(n,r,n.body)),r.on("close",()=>{d.close(),i.close()})}catch(s){if(!r.headersSent){B(r,s);return}o(s instanceof Error?s:new Error(String(s)))}}}a(ji,"mcpRpcPostHandler");function Ym(e,t,n){let r=new Di.McpServer({name:"node-red-ai-access",version:Vl()});if(Yl(r),Xl(r,e,t,n),(n.auth?.scopes??[]).includes("propose")){let s=typeof n.auth?.extra?.user=="string"?n.auth.extra.user:"owner",i=n.auth?.clientId??"",l={id:so(i,s),kind:"mcp",createdAt:new Date().toISOString(),expiresAt:new Date((n.auth?.expiresAt??0)*1e3).toISOString(),createdBy:s,oauthClientId:i,oauthUser:s};Zl(r,e,t,n,l)}return r}a(Ym,"createOAuthMcpServer");function Nr(e,t){t.status(405).set("Allow","POST").json({jsonrpc:"2.0",error:{code:-32e3,message:"Method not allowed. Use POST for Streamable HTTP."},id:null})}a(Nr,"mcpRpcMethodNotAllowed");var Fr=require("crypto"),Wn=require("fs"),zi=require("path"),Z=require("@modelcontextprotocol/sdk/server/auth/errors.js"),rc=require("@modelcontextprotocol/sdk/server/auth/handlers/authorize.js"),Wi=require("@modelcontextprotocol/sdk/server/auth/handlers/metadata.js"),oc=require("@modelcontextprotocol/sdk/server/auth/handlers/register.js"),sc=require("@modelcontextprotocol/sdk/server/auth/handlers/revoke.js"),ic=require("@modelcontextprotocol/sdk/server/auth/handlers/token.js"),ac=require("@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js"),dc=require("@modelcontextprotocol/sdk/server/auth/router.js"),lc=Vn(require("express"),1);oe();ze();function st(e){return e.replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[t]??t)}a(st,"esc");var Ql={read:'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M12 5c-5 0-9 4.5-10 7 1 2.5 5 7 10 7s9-4.5 10-7c-1-2.5-5-7-10-7Zm0 11.5a4.5 4.5 0 1 1 0-9 4.5 4.5 0 0 1 0 9Zm0-2.3a2.2 2.2 0 1 0 0-4.4 2.2 2.2 0 0 0 0 4.4Z"/></svg>',propose:'<svg viewBox="0 0 24 24" aria-hidden="true"><path d="M4 17.2V20h2.8l8.3-8.3-2.8-2.8L4 17.2Zm13.7-7.6a1 1 0 0 0 0-1.4l-1.9-1.9a1 1 0 0 0-1.4 0l-1.4 1.4 2.8 2.8 1.9-1.9Z"/></svg>'},Xm={read:["Read your flows","View flows, nodes, node types and AI Access logs."],propose:["Propose flow changes","Submit change proposals. Nothing is deployed until you approve it in Node-RED."]},Ui='<svg class="logo" viewBox="0 0 48 48" aria-hidden="true"><rect width="48" height="48" rx="11" fill="#8f0000"/><path d="M11 15h9a4 4 0 0 1 4 4v10a4 4 0 0 0 4 4h9" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round"/><rect x="6" y="11" width="9" height="8" rx="2.5" fill="#fff"/><rect x="33" y="29" width="9" height="8" rx="2.5" fill="#fff"/><circle cx="37.5" cy="16" r="5" fill="none" stroke="#fff" stroke-width="2.4"/><path d="M37.5 13.5v5M35 16h5" stroke="#fff" stroke-width="2.2" stroke-linecap="round"/></svg>',Zm=`
988
991
  :root{color-scheme:light;--bg:#f3f4f6;--card:#fff;--text:#111827;--muted:#4b5563;--line:#e5e7eb;--soft:#f9fafb;--brand:#8f0000;--brand-2:#b91c1c;--ok:#0f766e;--warn-bg:#fff7ed;--warn:#9a3412;--focus:#2563eb}
989
992
  *{box-sizing:border-box}
990
993
  body{margin:0;min-height:100vh;display:flex;align-items:center;justify-content:center;padding:24px 16px;background:var(--bg);color:var(--text);font:15px/1.5 system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
@@ -1020,7 +1023,34 @@ form.login input{font:inherit;padding:9px 11px;border-radius:10px;border:1px sol
1020
1023
  a{color:var(--focus)}
1021
1024
  [hidden]{display:none!important}
1022
1025
  @media (max-width:420px){.actions{flex-direction:column-reverse}.secondary{flex:1}}
1023
- `;function Ad(e){let t=e.scopes.map(n=>{let[r,o]=pm[n]??[n,""];return`<li data-scope="${He(n)}">${kd[n]??kd.read}<div><b>${He(r)}</b><span>${He(o)}</span></div></li>`}).join("");return`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="referrer" content="no-referrer"><title>Authorize MCP client \xB7 Node-RED AI Access</title><style>${mm}</style></head><body><main class="card" data-request="${He(e.requestId)}" data-token-key="${He(e.tokenKey)}" data-admin-root="${He(e.adminRoot)}"><div class="head">${fm}<div class="brand"><b>Node-RED AI Access</b><span>Authorization request</span></div></div><div class="body"><h1><span class="app">${He(e.redirectHost)}</span> wants to access your Node-RED</h1><p class="sub">Access tokens will be sent to <span class="host">${He(e.redirectHost)}</span>. App name (provided by the client, unverified): <b>${He(e.clientName)}</b></p>${e.loopbackRedirect?'<p class="warn">Warning: this redirect address is reachable only on this machine.</p>':""}<ul class="perms" aria-label="Requested permissions">${t}</ul><p id="status" class="status" role="status" aria-live="polite">Checking your Node-RED session\u2026</p><form id="login" class="login" hidden autocomplete="on"><label>Username<input id="username" name="username" autocomplete="username" required></label><label>Password<input id="password" name="password" type="password" autocomplete="current-password" required></label><div class="actions"><button id="login-submit" class="primary" type="submit">Sign in</button></div></form><p id="grant" class="sub" hidden></p><p id="user" class="user" hidden><span class="avatar" id="avatar" aria-hidden="true"></span><span>Signed in as <b id="username-label"></b></span></p><div class="actions"><button id="deny" class="secondary" type="button" hidden>Deny</button><button id="allow" class="primary" type="button" hidden>Allow access</button><button id="retry" class="secondary" type="button" hidden>Retry</button></div></div><div class="foot">You can revoke this app at any time from the AI Access dashboard (OAuth tab) in Node-RED.</div></main><script nonce="${He(e.nonce)}">${e.script}</script></body></html>`}a(Ad,"renderConsentPage");var Sd=`(function () {
1026
+ `;function ec(e){let t=e.scopes.map(n=>{let[r,o]=Xm[n]??[n,""];return`<li data-scope="${st(n)}">${Ql[n]??Ql.read}<div><b>${st(r)}</b><span>${st(o)}</span></div></li>`}).join("");return`<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="referrer" content="no-referrer"><title>Authorize MCP client \xB7 Node-RED AI Access</title><style>${Zm}</style></head><body><main class="card" data-request="${st(e.requestId)}" data-token-key="${st(e.tokenKey)}" data-admin-root="${st(e.adminRoot)}"><div class="head">${Ui}<div class="brand"><b>Node-RED AI Access</b><span>Authorization request</span></div></div><div class="body"><h1><span class="app">${st(e.redirectHost)}</span> wants to access your Node-RED</h1><p class="sub">Access tokens will be sent to <span class="host">${st(e.redirectHost)}</span>. App name (provided by the client, unverified): <b>${st(e.clientName)}</b></p>${e.loopbackRedirect?'<p class="warn">Warning: this redirect address is reachable only on this machine.</p>':""}<ul class="perms" aria-label="Requested permissions">${t}</ul><p id="status" class="status" role="status" aria-live="polite">Checking your Node-RED session\u2026</p><form id="login" class="login" hidden autocomplete="on"><label>Username<input id="username" name="username" autocomplete="username" required></label><label>Password<input id="password" name="password" type="password" autocomplete="current-password" required></label><div class="actions"><button id="login-submit" class="primary" type="submit">Sign in</button></div></form><p id="grant" class="sub" hidden></p><p id="user" class="user" hidden><span class="avatar" id="avatar" aria-hidden="true"></span><span>Signed in as <b id="username-label"></b></span></p><div class="actions"><button id="deny" class="secondary" type="button" hidden>Deny</button><button id="allow" class="primary" type="button" hidden>Allow access</button><button id="retry" class="secondary" type="button" hidden>Retry</button></div></div><div class="foot">You can revoke this app at any time from the AI Access dashboard (OAuth tab) in Node-RED.</div></main><script nonce="${st(e.nonce)}">${e.script}</script></body></html>`}a(ec,"renderConsentPage");var as=`
1027
+ function nrLoginType(adminRootHref) {
1028
+ return fetch(new URL('auth/login', adminRootHref).href, {
1029
+ headers: { 'Node-RED-API-Version': 'v2' }, credentials: 'same-origin'
1030
+ })
1031
+ .then(function (r) { return r.json(); })
1032
+ .then(function (cfg) { return cfg && typeof cfg.type === 'string' ? cfg.type : null; })
1033
+ .catch(function () { return null; });
1034
+ }
1035
+ function nrPasswordSignIn(adminRootHref, tokenKey, username, password) {
1036
+ var body = new URLSearchParams({
1037
+ client_id: 'node-red-editor', grant_type: 'password', scope: '*',
1038
+ username: username, password: password
1039
+ });
1040
+ return fetch(new URL('auth/token', adminRootHref).href, {
1041
+ method: 'POST', credentials: 'same-origin',
1042
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Node-RED-API-Version': 'v2' },
1043
+ body: body.toString()
1044
+ })
1045
+ .then(function (r) { return r.json().catch(function () { return {}; }).then(function (b) { return { r: r, b: b }; }); })
1046
+ .then(function (x) {
1047
+ if (!x.r.ok || !x.b.access_token) return { ok: false };
1048
+ try { localStorage.setItem(tokenKey, JSON.stringify(x.b)); } catch (e) {}
1049
+ return { ok: true };
1050
+ });
1051
+ }
1052
+ `;var tc=`(function () {
1053
+ ${as}
1024
1054
  var main = document.querySelector('main');
1025
1055
  var id = main.getAttribute('data-request');
1026
1056
  var key = main.getAttribute('data-token-key');
@@ -1054,10 +1084,9 @@ a{color:var(--focus)}
1054
1084
  }
1055
1085
  function showSignIn() {
1056
1086
  show(allow, false); show(deny, false); show(user, false); show(grant, false);
1057
- fetch(new URL('auth/login', adminRoot).href, { headers: { 'Node-RED-API-Version': 'v2' }, credentials: 'same-origin' })
1058
- .then(function (r) { return r.json(); })
1059
- .then(function (cfg) {
1060
- if (cfg && cfg.type === 'credentials') {
1087
+ nrLoginType(adminRoot.href)
1088
+ .then(function (type) {
1089
+ if (type === 'credentials') {
1061
1090
  setStatus('Sign in with your Node-RED account to continue.');
1062
1091
  show(login, true);
1063
1092
  $('username').focus();
@@ -1065,10 +1094,6 @@ a{color:var(--focus)}
1065
1094
  editorLink('Sign in to the ', ' in this browser, then press Retry.');
1066
1095
  show(retry, true);
1067
1096
  }
1068
- })
1069
- .catch(function () {
1070
- editorLink('Sign in to the ', ' in this browser, then press Retry.');
1071
- show(retry, true);
1072
1097
  });
1073
1098
  }
1074
1099
  function load() {
@@ -1100,21 +1125,11 @@ a{color:var(--focus)}
1100
1125
  var btn = $('login-submit');
1101
1126
  btn.disabled = true;
1102
1127
  setStatus('Signing in\u2026');
1103
- var body = new URLSearchParams({
1104
- client_id: 'node-red-editor', grant_type: 'password', scope: '*',
1105
- username: $('username').value, password: $('password').value
1106
- });
1107
- fetch(new URL('auth/token', adminRoot).href, {
1108
- method: 'POST', credentials: 'same-origin',
1109
- headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Node-RED-API-Version': 'v2' },
1110
- body: body.toString()
1111
- })
1112
- .then(function (r) { return r.json().catch(function () { return {}; }).then(function (b) { return { r: r, b: b }; }); })
1128
+ nrPasswordSignIn(adminRoot.href, key, $('username').value, $('password').value)
1113
1129
  .then(function (x) {
1114
1130
  btn.disabled = false;
1115
1131
  $('password').value = '';
1116
- if (!x.r.ok || !x.b.access_token) { setStatus('Sign in failed. Check your username and password.', true); return; }
1117
- try { localStorage.setItem(key, JSON.stringify(x.b)); } catch (e) {}
1132
+ if (!x.ok) { setStatus('Sign in failed. Check your username and password.', true); return; }
1118
1133
  load();
1119
1134
  })
1120
1135
  .catch(function (e) { btn.disabled = false; setStatus('Error: ' + e.message, true); });
@@ -1159,14 +1174,14 @@ a{color:var(--focus)}
1159
1174
  document.addEventListener('visibilitychange', function () { if (document.visibilityState === 'visible') autoRetry(false); });
1160
1175
  window.addEventListener('focus', function () { autoRetry(false); });
1161
1176
  load();
1162
- })();`;ue();var dt=a(e=>(0,Yn.createHash)("sha256").update(e).digest("hex"),"hash"),It=a(()=>(0,Yn.randomBytes)(32).toString("base64url"),"secret"),hm=36e5,gm=25,ym=5,wm=500,xd=a(()=>({clients:[],codes:[],tokens:[]}),"emptyDb"),po=class{constructor(t,n){this.maxClients=n;this.path=(0,Ps.join)(t.root,"oauth.json");try{let r=(0,mn.existsSync)(this.path)?JSON.parse((0,mn.readFileSync)(this.path,"utf8")):xd();if(!r||typeof r!="object"||!("clients"in r)||!("codes"in r)||!("tokens"in r)||!Array.isArray(r.clients)||!Array.isArray(r.codes)||!Array.isArray(r.tokens))throw new Error("Invalid OAuth store");this.db=r}catch{this.db=xd()}(0,mn.mkdirSync)(t.root,{recursive:!0,mode:448}),this.prune()}static{a(this,"OAuthStore")}db;path;save(){Ot((0,Ps.join)(this.path,".."),this.path,Je(this.db,8e6,{kind:"oauth",pretty:!0}))}prune(t=Date.now()){let n=this.db.clients.length+this.db.codes.length+this.db.tokens.length,r=new Set(this.db.clients.filter(o=>o.approved?(o.lastUsedAt??(o.client_id_issued_at??0)*1e3)>t-30*864e5:(o.client_id_issued_at??0)*1e3>t-hm).map(o=>o.client_id));this.db.clients=this.db.clients.filter(o=>r.has(o.client_id)),this.db.codes=this.db.codes.filter(o=>o.expires>t&&r.has(o.clientId)),this.db.tokens=this.db.tokens.filter(o=>r.has(o.clientId)&&o.expires>t),this.db.clients.length+this.db.codes.length+this.db.tokens.length!==n&&this.save()}getClient(t){return this.prune(),this.db.clients.find(n=>n.client_id===t)}registerClient(t){if(this.prune(),this.approvedCount()>=this.maxClients())throw new B.InvalidClientMetadataError("Maximum active OAuth clients reached");if(this.db.clients.length-this.approvedCount()>=gm)throw new B.InvalidClientMetadataError("Too many OAuth clients awaiting consent");if(!Array.isArray(t.redirect_uris)||!t.redirect_uris.length||t.redirect_uris.some(r=>{try{let o=new URL(r);return!!o.username||!!o.password||!!o.hash||o.protocol!=="https:"&&!(o.protocol==="http:"&&["localhost","127.0.0.1","[::1]"].includes(o.hostname))}catch{return!0}}))throw new B.InvalidClientMetadataError("Redirect URIs must use HTTPS or loopback HTTP");if(t.token_endpoint_auth_method&&t.token_endpoint_auth_method!=="none")throw new B.InvalidClientMetadataError("Only public clients are supported");let n={...t,client_secret:void 0,client_secret_expires_at:void 0,client_id:It(),client_id_issued_at:Math.floor(Date.now()/1e3),token_endpoint_auth_method:"none",lastUsedAt:Date.now()};return this.db.clients.push(n),this.save(),n}approvedCount(){return this.db.clients.filter(t=>t.approved).length}approve(t){let n=this.db.clients.find(r=>r.client_id===t);if(!(!n||n.approved)){if(this.approvedCount()>=this.maxClients())throw new B.InvalidClientMetadataError("Maximum active OAuth clients reached");n.approved=!0,this.save()}}get data(){return this.db}persist(){this.touchTimer&&(clearTimeout(this.touchTimer),this.touchTimer=void 0),this.save()}touchTimer;touch(){this.touchTimer||(this.touchTimer=setTimeout(()=>{this.touchTimer=void 0,this.save()},5e3),this.touchTimer.unref?.())}revokeClient(t){let n=this.db.clients.length;return this.db.clients=this.db.clients.filter(r=>r.client_id!==t),this.db.tokens=this.db.tokens.filter(r=>r.clientId!==t),this.db.codes=this.db.codes.filter(r=>r.clientId!==t),this.save(),this.db.clients.length!==n}};function Ls(e){switch(e.source){case"http-in":return`Route ${e.path} is already used by http-in node '${e.nodeName??e.nodeId??"unnamed"}'`;case"httpStatic":return`File ${e.path} is served by httpStatic`;case"middleware":return`Path prefix is already handled by mounted middleware (${e.layer??"unknown"})`;default:return`Route ${e.path} is already registered by another handler (${e.layer??"route"})`}}a(Ls,"describeOAuthRouteCollision");var Is=a((e,t)=>e.toLowerCase().startsWith(t.toLowerCase()),"startsWithCi");function Rs(e){return e===!1?void 0:`/${typeof e=="string"?e:"/"}`.replace(/\/+/g,"/").replace(/\/$/,"")}a(Rs,"normalizeRoot");function _s(e,t){if(e!==void 0){if(!e)return t;if(Is(t,`${e}/`))return t.slice(e.length)}}a(_s,"relativePrefix");function bm(e){let t=new Map;for(let r of e)r.id&&t.set(r.id,r);let n=a((r,o=new Set)=>{if(!r)return!0;let s=t.get(r);return s?s.type==="tab"?!s.disabled&&!s.d:s.type!=="subflow"||o.has(r)?!1:(o.add(r),e.some(i=>i.type===`subflow:${r}`&&!i.d&&n(i.z,o))):!0},"live");return n}a(bm,"makeLiveCheck");function vm(e,t){if(!e||!t.length)return[];let n=[];try{e(s=>n.push(s))}catch{return[]}let r=bm(n),o=[];for(let s of n){if(s.type!=="http in"||typeof s.url!="string"||s.d)continue;let i=s.url.startsWith("/")?s.url:`/${s.url}`;!t.some(d=>Is(i,d))||!r(s.z)||o.push({path:i,source:"http-in",nodeId:s.id,nodeName:s.name?s.name:void 0,flowId:s.z})}return o}a(vm,"findHttpInCollisions");var km=new Set(["bound dispatch","handle"]),Am=["authorize","token","register","revoke"],Sm=["authorization-server","protected-resource"];function xm(e){let t=e.length;for(;t>0&&e.charCodeAt(t-1)===47;)t--;return e.slice(0,t)}a(xm,"trimTrailingSlashes");function Tm(e){let t=new Set;return e.filter(n=>{let r=`${n.source}\0${n.path.toLowerCase()}`;return t.has(r)?!1:(t.add(r),!0)})}a(Tm,"dedupe");function Id(e){let t=Rs(e.httpAdminRoot),n=e.httpNode?Rs(e.httpNodeRoot):void 0,r=`${t??""}/mcp/oauth/`,o=e.metadataFallback?`${t??""}/mcp/.well-known/oauth-`:`${n??""}/.well-known/oauth-`,s=a(p=>{let f=_s(t,p),m=_s(n,p),h=m?vm(e.eachNode,[m]):[],g=new Set(h.map(C=>C.path.toLowerCase())),k=m?Cs(e.httpNode,[m]).filter(C=>!(C.source==="route"&&km.has(C.layer??"")&&g.has(C.path.toLowerCase()))):[],T=f?Cs(e.httpAdmin,[f]):[];return Tm([...T,...k,...h])},"scan"),i=s(r),d=s(o),l=e.fileExists??(()=>!1),c=(e.staticDirs??[]).map(p=>typeof p=="string"?{path:p,root:e.httpStaticRoot}:p),u=a((p,f)=>{for(let m of c){let h=Rs(m.root),g=xm(m.path);for(let k of p){let T=_s(h,k);!T||!l(`${g}${T}`)||f.push({path:k,source:"httpStatic",file:`${g}${T}`})}}},"checkStatic");return u(Am.map(p=>`${r}${p}`),i),u(Sm.map(p=>`${o}${p}`),d),{authorization:i,metadata:d}}a(Id,"scanOAuthRouteCollisions");function Rm(e){let t=a(n=>{try{return e?.[n]}catch{return}},"read");return t("_router")?.stack??t("router")?.stack??[]}a(Rm,"routerStack");function _m(e){let t=e.route?.path;return t?(Array.isArray(t)?t:[t]).map(String):[]}a(_m,"layerPaths");function Pm(e,t){if(e.name==="router")return!1;let n=e.regexp,r=e.matchers??(n?[s=>n.test(s)]:[]);if(!r.length)return!1;let o=a(s=>r.some(i=>!!i(s)),"matches");return o("/unrelated-ai-access-collision-probe")?!1:t.some(s=>o(`${s}token`)||o(`${s}mcp`))}a(Pm,"middlewareHits");function Cs(e,t=["/.well-known/oauth-","/mcp/oauth/"]){let n=[];for(let r of Rm(e)){if(r.handle?.oauthOwned)continue;let o=_m(r);for(let s of o)t.some(i=>Is(s,i))&&n.push({path:s,source:"route",layer:r.name??"route"});!o.length&&Pm(r,t)&&n.push({path:t.join(", "),source:"middleware",layer:r.name??"route"}),r.handle?.stack&&n.push(...Cs({router:{stack:r.handle.stack}},t))}return n}a(Cs,"detectOAuthRouteCollisionDetails");var fo=class{constructor(t,n,r,o,s=()=>0){this.clientsStore=t;this.resource=n;this.accessTtl=r;this.refreshTtl=o;this.refreshMax=s}static{a(this,"LocalOAuthProvider")}skipLocalPkceValidation=!1;inflight=new Map;async authorize(t,n,r){throw new B.InvalidGrantError("Admin consent is required")}async challengeForAuthorizationCode(t,n){let r=this.findCode(t,n);return this.clientsStore.data.codes=this.clientsStore.data.codes.filter(o=>o!==r),this.clientsStore.persist(),this.inflight.set(r.hash,r),setTimeout(()=>this.inflight.delete(r.hash),3e4).unref?.(),r.challenge}async exchangeAuthorizationCode(t,n,r,o,s){let i=this.inflight.get(dt(n))??this.findCode(t,n);if(this.inflight.delete(i.hash),this.clientsStore.data.codes=this.clientsStore.data.codes.filter(d=>d!==i),this.clientsStore.persist(),i.clientId!==t.client_id||i.expires<=Date.now()||o!==i.redirectUri||s&&lt(s.href)!==i.resource)throw new B.InvalidGrantError("Invalid authorization code");return this.issue(t.client_id,i.scopes,i.resource,i.user)}async exchangeRefreshToken(t,n,r,o){let s=this.clientsStore.data.tokens.find(l=>l.hash===dt(n)&&l.type==="refresh"&&l.clientId===t.client_id);if(!s||s.expires<=Date.now())throw new B.InvalidGrantError("Invalid refresh token");if(s.rotated)throw this.clientsStore.data.tokens=this.clientsStore.data.tokens.filter(l=>l.family!==s.family),this.clientsStore.persist(),new B.InvalidGrantError("Refresh token reuse detected");let i=this.refreshMax()*1e3,d=s.familyStart??Date.now();if(i>0&&d+i<=Date.now())throw new B.InvalidGrantError("Refresh token family reached its maximum lifetime");if(o&&lt(o.href)!==s.resource)throw new B.InvalidGrantError("Resource mismatch");if(r?.some(l=>!s.scopes.includes(l)))throw new B.InvalidGrantError("Invalid scope");return s.rotated=!0,this.issue(t.client_id,r??s.scopes,s.resource,s.user,s.family,d)}async verifyAccessToken(t){let n=this.clientsStore.data.tokens.find(o=>o.hash===dt(t)&&o.type==="access"&&o.expires>Date.now());if(!n||n.resource!==lt(this.resource()))throw new B.InvalidTokenError("Invalid access token");let r=this.clientsStore.data.clients.find(o=>o.client_id===n.clientId);if(!r)throw new B.InvalidTokenError("Invalid client");return r.lastUsedAt=Date.now(),this.clientsStore.touch(),{token:t,clientId:n.clientId,scopes:n.scopes,expiresAt:Math.floor(n.expires/1e3),resource:new URL(n.resource),extra:{user:n.user}}}async revokeToken(t,n){let r=this.clientsStore.data.tokens.find(o=>o.hash===dt(n.token)&&o.clientId===t.client_id);r&&(this.clientsStore.data.tokens=this.clientsStore.data.tokens.filter(o=>o.family!==r.family)),this.clientsStore.persist()}createCode(t,n,r){let o=It();return this.clientsStore.data.codes.push({hash:dt(o),clientId:t.client_id,challenge:n.codeChallenge,redirectUri:n.redirectUri,scopes:n.scopes??["read"],resource:this.validateResource(n.resource),expires:Date.now()+6e4,user:r}),this.clientsStore.persist(),o}validateResource(t){let n=lt(this.resource());if(t&&lt(t.href)!==n)throw new B.InvalidGrantError("Resource mismatch");return n}findCode(t,n){let r=this.clientsStore.data.codes.find(o=>o.hash===dt(n)&&o.clientId===t.client_id);if(!r||r.expires<=Date.now())throw new B.InvalidGrantError("Invalid or expired authorization code");return r}issue(t,n,r,o,s=It(),i=Date.now()){let d=It(),l=It(),c=Date.now(),u=this.refreshMax()*1e3,p=Math.min(c+this.refreshTtl()*1e3,u>0?i+u:1/0),f=this.clientsStore.data,m=f.clients.find(h=>h.client_id===t);return m&&(m.lastUsedAt=c),f.tokens.push({hash:dt(d),type:"access",clientId:t,family:s,scopes:n,resource:r,expires:c+this.accessTtl()*1e3,user:o,rotated:!1},{hash:dt(l),type:"refresh",clientId:t,family:s,scopes:n,resource:r,expires:p,user:o,rotated:!1,familyStart:i}),this.clientsStore.persist(),{access_token:d,token_type:"Bearer",expires_in:this.accessTtl(),refresh_token:l,scope:n.join(" ")}}};function lt(e){let t=new URL(e);if(!["http:","https:"].includes(t.protocol)||t.username||t.password||t.search||t.hash)throw new Error("Invalid resource URL");return`${t.protocol}//${t.host}${t.pathname.replace(/\/+$/,"")}`}a(lt,"canonicalResource");var fn=["read","propose"];function Em(e){let t=(e??"/").split("/").filter(Boolean).join("/");return t?`/${t}/`:"/"}a(Em,"consentAdminRoot");var Cm=new Set(["localhost","127.0.0.1","[::1]"]);function Lt(e){return typeof e=="string"?e:""}a(Lt,"value");function Nd(e,t,n,r){let o=a(S=>`${n().replace(/\/$/,"")}${S}`,"abs"),s=a((S,b,w,v)=>{let A=b.url;b.url="/",S(b,w,E=>{b.url=A,v(E)})},"atRoot"),i=new Map,d=a(()=>{let S=Date.now();for(let[b,w]of i)w.expires<=S&&i.delete(b)},"sweep"),l=a(S=>{d();let b=[...i].filter(([,w])=>w.clientId===S);for(;b.length>=ym;)i.delete(b.shift()[0]);return i.size<wm},"reservePending"),c=a((S,b)=>{S.setHeader("Cache-Control","no-store"),S.setHeader("X-Frame-Options","DENY"),S.setHeader("Referrer-Policy","no-referrer"),S.setHeader("X-Content-Type-Options","nosniff"),S.setHeader("Content-Security-Policy",`default-src 'none'; ${b?`script-src 'nonce-${b}'; `:""}style-src 'unsafe-inline'; connect-src 'self'; form-action 'none'; base-uri 'none'; frame-ancestors 'none'`)},"securityHeaders"),u=mr(e.settings.httpAdminRoot),p=a(S=>{if(!z(e.settings))return[...fn];let b=Ye(S);return b==="*"?[...fn]:b==="read"?["read"]:[]},"grantableFor"),f=a(S=>{let b=S.get("sec-fetch-site");if(b&&b!=="same-origin")return!1;let w=S.get("origin");if(!w)return!0;let v=new Set;try{v.add(new URL(n()).origin)}catch{}let A=S.get("host");return A&&(v.add(`http://${A}`),v.add(`https://${A}`)),v.has(w)},"sameOrigin"),m=a((S,b)=>{d();let w=S.query,v=t.clientsStore.getClient(Lt(w.client_id));if(!v){b.status(400).send("Unknown OAuth client");return}let A=Lt(w.redirect_uri)||(v.redirect_uris.length===1?v.redirect_uris[0]:"");if(!v.redirect_uris.some(vo=>(0,Td.redirectUriMatches)(A,vo))){b.status(400).send("Unregistered redirect URI");return}if(w.response_type!=="code"||w.code_challenge_method!=="S256"||!/^[A-Za-z0-9_-]{43,128}$/.test(Lt(w.code_challenge))){b.status(400).send("Authorization code with PKCE S256 is required");return}let E=[...new Set((Lt(w.scope)||"read").split(" ").filter(Boolean))];if(E.some(vo=>!fn.includes(vo))){b.status(400).send("Unsupported scope");return}let $=Lt(w.resource)||r(),F;try{F=lt($)}catch{b.status(400).send("Invalid resource");return}if(F!==lt(r())){b.status(400).send("Resource mismatch");return}if(!l(v.client_id)){b.status(429).send("Too many pending authorization requests; try again later");return}let le=It();i.set(le,{clientId:v.client_id,redirect:A,challenge:Lt(w.code_challenge),scopes:E,resource:$,state:Lt(w.state),csrf:It(),expires:Date.now()+10*6e4});let Ke=new URL(A).host,Ft=(0,Yn.randomBytes)(16).toString("base64");c(b,Ft),b.status(200).type("html").send(Ad({requestId:le,tokenKey:u,adminRoot:Em(e.settings.httpAdminRoot),nonce:Ft,redirectHost:Ke,loopbackRedirect:Cm.has(new URL(A).hostname),clientName:v.client_name||"Unnamed client",scopes:E,script:Sd}))},"authorizePage"),h=a((S,b,w)=>{c(b),Ld.default.json({limit:"4kb"})(S,b,()=>Ae(e,"api")(S,b,()=>{try{d();let v=/^\/mcp\/oauth\/consent\/([A-Za-z0-9_-]+)\/?$/.exec(S.path)?.[1]??"",A=i.get(v),E=A&&t.clientsStore.getClient(A.clientId);if(!A||!E){b.status(404).json({error:"Consent request expired or unknown"});return}let $=z(e.settings)&&S.user?.username||"owner",F=A.scopes.filter(Ft=>p(S).includes(Ft));if(!F.length){b.status(403).json({error:"Your Node-RED permissions do not allow granting the requested scopes",grantable:F});return}if(S.method==="GET"){A.approver=$,b.json({client_name:E.client_name??null,redirect_host:new URL(A.redirect).host,requested:A.scopes,grantable:F,user:$,csrf:A.csrf});return}if(S.method!=="POST"){b.status(405).end();return}let le=S.body??{};if(!f(S)||le.csrf!==A.csrf||A.approver!==$){b.status(403).json({error:"Consent request verification failed"});return}i.delete(v);let Ke=new URL(A.redirect);if(le.decision!=="allow")Ke.searchParams.set("error","access_denied");else{try{t.clientsStore.approve(E.client_id)}catch{b.status(409).json({error:"Maximum active OAuth clients reached"});return}let Ft=t.createCode(E,{redirectUri:A.redirect,codeChallenge:A.challenge,scopes:F,resource:new URL(A.resource)},$);Ke.searchParams.set("code",Ft)}A.state&&Ke.searchParams.set("state",A.state),b.json({redirect:Ke.href})}catch(v){w(v)}}))},"consentApi"),g=(0,Rd.clientRegistrationHandler)({clientsStore:t.clientsStore,rateLimit:{windowMs:36e5,max:10,standardHeaders:!0,legacyHeaders:!1}}),k=(0,Pd.tokenHandler)({provider:t}),T=(0,_d.revocationHandler)({provider:t}),C,L="";return{server:a((S,b,w)=>{let v=S.path,A=v.replace(/\/+$/,"").toLowerCase();if(A==="/mcp/oauth/authorize"){S.method==="GET"?m(S,b,w):b.status(405).set("Allow","GET").end();return}if(A.startsWith("/mcp/oauth/consent/")){h(S,b,w);return}if(!["/mcp/oauth/token","/mcp/oauth/register","/mcp/oauth/revoke"].includes(A)){w();return}let E=n();if(E.startsWith("https://")||/^http:\/\/(localhost|127\.0\.0\.1)(:|\/|$)/.test(E)){if(L!==E)try{C=(0,Cd.mcpAuthRouter)({provider:t,issuerUrl:new URL(E),baseUrl:new URL(`${E.replace(/\/$/,"")}/mcp/oauth/`),resourceServerUrl:new URL(r()),scopesSupported:fn,clientRegistrationOptions:{rateLimit:{windowMs:36e5,max:10}}}),L=E}catch{C=void 0}if(C){let $=S.url;S.url=S.url.slice(10)||"/",C(S,b,F=>{S.url=$,w(F)});return}}if(v==="/mcp/oauth/token"){s(k,S,b,w);return}if(v==="/mcp/oauth/register"){s(g,S,b,w);return}if(v==="/mcp/oauth/revoke"){s(T,S,b,w);return}w()},"server"),metadata:a((S,b,w)=>{let v=S.path;if(v==="/.well-known/oauth-authorization-server"){s((0,Es.metadataHandler)({issuer:n(),authorization_endpoint:o("/mcp/oauth/authorize"),token_endpoint:o("/mcp/oauth/token"),registration_endpoint:o("/mcp/oauth/register"),revocation_endpoint:o("/mcp/oauth/revoke"),response_types_supported:["code"],code_challenge_methods_supported:["S256"],grant_types_supported:["authorization_code","refresh_token"],token_endpoint_auth_methods_supported:["none"],scopes_supported:fn}),S,b,w);return}if(v==="/.well-known/oauth-protected-resource/mcp"||v==="/.well-known/oauth-protected-resource"){s((0,Es.metadataHandler)({resource:lt(r()),authorization_servers:[n()],scopes_supported:fn}),S,b,w);return}w()},"metadata"),auth:a((S,b,w)=>{try{let v=b.set.bind(b);b.set=((A,E)=>(A==="WWW-Authenticate"&&typeof E=="string"&&(E=E.replace('scope="read"','scope="read propose"')),typeof A=="string"?v(A,E??""):v(A))),(0,Ed.requireBearerAuth)({verifier:t,requiredScopes:["read"],resourceMetadataUrl:o(e.settings.httpNodeRoot===!1||!e.httpNode?"/mcp/.well-known/oauth-protected-resource/mcp":"/.well-known/oauth-protected-resource/mcp"),expectedResource:new URL(r())})(S,b,A=>{b.set=v,w(A)})}catch(v){w(v)}},"auth")}}a(Nd,"makeOAuthHandlers");var Xn=require("fs"),hn=require("path"),Fd=require("url"),Od=require("yaml"),Fm={};function Lm(){return typeof __dirname<"u"?__dirname:(0,hn.dirname)((0,Fd.fileURLToPath)(Fm.url))}a(Lm,"moduleDir");function $d(){let e=Lm(),t=[(0,hn.join)(e,"..","docs","api","openapi.yaml"),(0,hn.join)(e,"..","..","docs","api","openapi.yaml"),(0,hn.join)(process.cwd(),"docs","api","openapi.yaml")];for(let n of t)if((0,Xn.existsSync)(n))return n;return null}a($d,"resolveOpenApiYamlPath");function Im(){let e=$d();if(!e)throw new Error("OpenAPI specification file was not found");return(0,Xn.readFileSync)(e,"utf8")}a(Im,"loadOpenApiYaml");function Md(){return(e,t)=>{try{let n=Im();t.status(200).type("application/yaml").send(n)}catch(n){let r=n instanceof Error?n.message:String(n);t.status(500).json({error:r})}}}a(Md,"openApiYamlHandler");var Ns;function Nm(){let e=$d();if(!e)throw new Error("OpenAPI specification file was not found");return Ns?.path!==e&&(Ns={path:e,json:JSON.stringify((0,Od.parse)((0,Xn.readFileSync)(e,"utf8")))}),Ns.json}a(Nm,"loadOpenApiJson");function Dd(){return(e,t)=>{try{t.status(200).type("application/json").send(Nm())}catch(n){let r=n instanceof Error?n.message:String(n);t.status(500).json({error:r})}}}a(Dd,"openApiJsonHandler");function Om(){return`
1177
+ })();`;ke();var Lt=a(e=>(0,Fr.createHash)("sha256").update(e).digest("hex"),"hash"),ln=a(()=>(0,Fr.randomBytes)(32).toString("base64url"),"secret"),Qm=36e5,eg=25,tg=5,ng=500,nc=a(()=>({clients:[],codes:[],tokens:[]}),"emptyDb"),ds=class{constructor(t,n){this.maxClients=n;this.path=(0,zi.join)(t.root,"oauth.json");try{let r=(0,Wn.existsSync)(this.path)?JSON.parse((0,Wn.readFileSync)(this.path,"utf8")):nc();if(!r||typeof r!="object"||!("clients"in r)||!("codes"in r)||!("tokens"in r)||!Array.isArray(r.clients)||!Array.isArray(r.codes)||!Array.isArray(r.tokens))throw new Error("Invalid OAuth store");this.db=r}catch{this.db=nc()}(0,Wn.mkdirSync)(t.root,{recursive:!0,mode:448}),this.prune()}static{a(this,"OAuthStore")}db;path;save(){un((0,zi.join)(this.path,".."),this.path,ht(this.db,8e6,{kind:"oauth",pretty:!0}))}prune(t=Date.now()){let n=this.db.clients.length+this.db.codes.length+this.db.tokens.length,r=new Set(this.db.clients.filter(o=>o.approved?(o.lastUsedAt??(o.client_id_issued_at??0)*1e3)>t-30*864e5:(o.client_id_issued_at??0)*1e3>t-Qm).map(o=>o.client_id));this.db.clients=this.db.clients.filter(o=>r.has(o.client_id)),this.db.codes=this.db.codes.filter(o=>o.expires>t&&r.has(o.clientId)),this.db.tokens=this.db.tokens.filter(o=>r.has(o.clientId)&&o.expires>t),this.db.clients.length+this.db.codes.length+this.db.tokens.length!==n&&this.save()}getClient(t){return this.prune(),this.db.clients.find(n=>n.client_id===t)}registerClient(t){if(this.prune(),this.approvedCount()>=this.maxClients())throw new Z.InvalidClientMetadataError("Maximum active OAuth clients reached");if(this.db.clients.length-this.approvedCount()>=eg)throw new Z.InvalidClientMetadataError("Too many OAuth clients awaiting consent");if(!Array.isArray(t.redirect_uris)||!t.redirect_uris.length||t.redirect_uris.some(r=>{try{let o=new URL(r);return!!o.username||!!o.password||!!o.hash||o.protocol!=="https:"&&!(o.protocol==="http:"&&["localhost","127.0.0.1","[::1]"].includes(o.hostname))}catch{return!0}}))throw new Z.InvalidClientMetadataError("Redirect URIs must use HTTPS or loopback HTTP");if(t.token_endpoint_auth_method&&t.token_endpoint_auth_method!=="none")throw new Z.InvalidClientMetadataError("Only public clients are supported");let n={...t,client_secret:void 0,client_secret_expires_at:void 0,client_id:ln(),client_id_issued_at:Math.floor(Date.now()/1e3),token_endpoint_auth_method:"none",lastUsedAt:Date.now()};return this.db.clients.push(n),this.save(),n}approvedCount(){return this.db.clients.filter(t=>t.approved).length}approve(t){let n=this.db.clients.find(r=>r.client_id===t);if(!(!n||n.approved)){if(this.approvedCount()>=this.maxClients())throw new Z.InvalidClientMetadataError("Maximum active OAuth clients reached");n.approved=!0,this.save()}}get data(){return this.db}persist(){this.touchTimer&&(clearTimeout(this.touchTimer),this.touchTimer=void 0),this.save()}touchTimer;touch(){this.touchTimer||(this.touchTimer=setTimeout(()=>{this.touchTimer=void 0,this.save()},5e3),this.touchTimer.unref?.())}revokeClient(t){let n=this.db.clients.length;return this.db.clients=this.db.clients.filter(r=>r.client_id!==t),this.db.tokens=this.db.tokens.filter(r=>r.clientId!==t),this.db.codes=this.db.codes.filter(r=>r.clientId!==t),this.save(),this.db.clients.length!==n}};function Ki(e){switch(e.source){case"http-in":return`Route ${e.path} is already used by http-in node '${e.nodeName??e.nodeId??"unnamed"}'`;case"httpStatic":return`File ${e.path} is served by httpStatic`;case"middleware":return`Path prefix is already handled by mounted middleware (${e.layer??"unknown"})`;default:return`Route ${e.path} is already registered by another handler (${e.layer??"route"})`}}a(Ki,"describeOAuthRouteCollision");var Ji=a((e,t)=>e.toLowerCase().startsWith(t.toLowerCase()),"startsWithCi");function Bi(e){return e===!1?void 0:`/${typeof e=="string"?e:"/"}`.replace(/\/+/g,"/").replace(/\/$/,"")}a(Bi,"normalizeRoot");function qi(e,t){if(e!==void 0){if(!e)return t;if(Ji(t,`${e}/`))return t.slice(e.length)}}a(qi,"relativePrefix");function rg(e){let t=new Map;for(let r of e)r.id&&t.set(r.id,r);let n=a((r,o=new Set)=>{if(!r)return!0;let s=t.get(r);return s?s.type==="tab"?!s.disabled&&!s.d:s.type!=="subflow"||o.has(r)?!1:(o.add(r),e.some(i=>i.type===`subflow:${r}`&&!i.d&&n(i.z,o))):!0},"live");return n}a(rg,"makeLiveCheck");function og(e,t){if(!e||!t.length)return[];let n=[];try{e(s=>n.push(s))}catch{return[]}let r=rg(n),o=[];for(let s of n){if(s.type!=="http in"||typeof s.url!="string"||s.d)continue;let i=s.url.startsWith("/")?s.url:`/${s.url}`;!t.some(d=>Ji(i,d))||!r(s.z)||o.push({path:i,source:"http-in",nodeId:s.id,nodeName:s.name?s.name:void 0,flowId:s.z})}return o}a(og,"findHttpInCollisions");var sg=new Set(["bound dispatch","handle"]),ig=["authorize","token","register","revoke"],ag=["authorization-server","protected-resource"];function dg(e){let t=e.length;for(;t>0&&e.charCodeAt(t-1)===47;)t--;return e.slice(0,t)}a(dg,"trimTrailingSlashes");function lg(e){let t=new Set;return e.filter(n=>{let r=`${n.source}\0${n.path.toLowerCase()}`;return t.has(r)?!1:(t.add(r),!0)})}a(lg,"dedupe");function cc(e){let t=Bi(e.httpAdminRoot),n=e.httpNode?Bi(e.httpNodeRoot):void 0,r=`${t??""}/mcp/oauth/`,o=e.metadataFallback?`${t??""}/mcp/.well-known/oauth-`:`${n??""}/.well-known/oauth-`,s=a(p=>{let f=qi(t,p),m=qi(n,p),h=m?og(e.eachNode,[m]):[],g=new Set(h.map(v=>v.path.toLowerCase())),y=m?Gi(e.httpNode,[m]).filter(v=>!(v.source==="route"&&sg.has(v.layer??"")&&g.has(v.path.toLowerCase()))):[],w=f?Gi(e.httpAdmin,[f]):[];return lg([...w,...y,...h])},"scan"),i=s(r),d=s(o),l=e.fileExists??(()=>!1),c=(e.staticDirs??[]).map(p=>typeof p=="string"?{path:p,root:e.httpStaticRoot}:p),u=a((p,f)=>{for(let m of c){let h=Bi(m.root),g=dg(m.path);for(let y of p){let w=qi(h,y);!w||!l(`${g}${w}`)||f.push({path:y,source:"httpStatic",file:`${g}${w}`})}}},"checkStatic");return u(ig.map(p=>`${r}${p}`),i),u(ag.map(p=>`${o}${p}`),d),{authorization:i,metadata:d}}a(cc,"scanOAuthRouteCollisions");function cg(e){let t=a(n=>{try{return e?.[n]}catch{return}},"read");return t("_router")?.stack??t("router")?.stack??[]}a(cg,"routerStack");function ug(e){let t=e.route?.path;return t?(Array.isArray(t)?t:[t]).map(String):[]}a(ug,"layerPaths");function pg(e,t){if(e.name==="router")return!1;let n=e.regexp,r=e.matchers??(n?[s=>n.test(s)]:[]);if(!r.length)return!1;let o=a(s=>r.some(i=>!!i(s)),"matches");return o("/unrelated-ai-access-collision-probe")?!1:t.some(s=>o(`${s}token`)||o(`${s}mcp`))}a(pg,"middlewareHits");function Gi(e,t=["/.well-known/oauth-","/mcp/oauth/"]){let n=[];for(let r of cg(e)){if(r.handle?.oauthOwned)continue;let o=ug(r);for(let s of o)t.some(i=>Ji(s,i))&&n.push({path:s,source:"route",layer:r.name??"route"});!o.length&&pg(r,t)&&n.push({path:t.join(", "),source:"middleware",layer:r.name??"route"}),r.handle?.stack&&n.push(...Gi({router:{stack:r.handle.stack}},t))}return n}a(Gi,"detectOAuthRouteCollisionDetails");var ls=class{constructor(t,n,r,o,s=()=>0){this.clientsStore=t;this.resource=n;this.accessTtl=r;this.refreshTtl=o;this.refreshMax=s}static{a(this,"LocalOAuthProvider")}skipLocalPkceValidation=!1;inflight=new Map;async authorize(t,n,r){throw new Z.InvalidGrantError("Admin consent is required")}async challengeForAuthorizationCode(t,n){let r=this.findCode(t,n);return this.clientsStore.data.codes=this.clientsStore.data.codes.filter(o=>o!==r),this.clientsStore.persist(),this.inflight.set(r.hash,r),setTimeout(()=>this.inflight.delete(r.hash),3e4).unref?.(),r.challenge}async exchangeAuthorizationCode(t,n,r,o,s){let i=this.inflight.get(Lt(n))??this.findCode(t,n);if(this.inflight.delete(i.hash),this.clientsStore.data.codes=this.clientsStore.data.codes.filter(d=>d!==i),this.clientsStore.persist(),i.clientId!==t.client_id||i.expires<=Date.now()||o!==i.redirectUri||s&&Nt(s.href)!==i.resource)throw new Z.InvalidGrantError("Invalid authorization code");return this.issue(t.client_id,i.scopes,i.resource,i.user)}async exchangeRefreshToken(t,n,r,o){let s=this.clientsStore.data.tokens.find(l=>l.hash===Lt(n)&&l.type==="refresh"&&l.clientId===t.client_id);if(!s||s.expires<=Date.now())throw new Z.InvalidGrantError("Invalid refresh token");if(s.rotated)throw this.clientsStore.data.tokens=this.clientsStore.data.tokens.filter(l=>l.family!==s.family),this.clientsStore.persist(),new Z.InvalidGrantError("Refresh token reuse detected");let i=this.refreshMax()*1e3,d=s.familyStart??Date.now();if(i>0&&d+i<=Date.now())throw new Z.InvalidGrantError("Refresh token family reached its maximum lifetime");if(o&&Nt(o.href)!==s.resource)throw new Z.InvalidGrantError("Resource mismatch");if(r?.some(l=>!s.scopes.includes(l)))throw new Z.InvalidGrantError("Invalid scope");return s.rotated=!0,this.issue(t.client_id,r??s.scopes,s.resource,s.user,s.family,d)}async verifyAccessToken(t){let n=this.clientsStore.data.tokens.find(o=>o.hash===Lt(t)&&o.type==="access"&&o.expires>Date.now());if(!n||n.resource!==Nt(this.resource()))throw new Z.InvalidTokenError("Invalid access token");let r=this.clientsStore.data.clients.find(o=>o.client_id===n.clientId);if(!r)throw new Z.InvalidTokenError("Invalid client");return r.lastUsedAt=Date.now(),this.clientsStore.touch(),{token:t,clientId:n.clientId,scopes:n.scopes,expiresAt:Math.floor(n.expires/1e3),resource:new URL(n.resource),extra:{user:n.user}}}async revokeToken(t,n){let r=this.clientsStore.data.tokens.find(o=>o.hash===Lt(n.token)&&o.clientId===t.client_id);r&&(this.clientsStore.data.tokens=this.clientsStore.data.tokens.filter(o=>o.family!==r.family)),this.clientsStore.persist()}createCode(t,n,r){let o=ln();return this.clientsStore.data.codes.push({hash:Lt(o),clientId:t.client_id,challenge:n.codeChallenge,redirectUri:n.redirectUri,scopes:n.scopes??["read"],resource:this.validateResource(n.resource),expires:Date.now()+6e4,user:r}),this.clientsStore.persist(),o}validateResource(t){let n=Nt(this.resource());if(t&&Nt(t.href)!==n)throw new Z.InvalidGrantError("Resource mismatch");return n}findCode(t,n){let r=this.clientsStore.data.codes.find(o=>o.hash===Lt(n)&&o.clientId===t.client_id);if(!r||r.expires<=Date.now())throw new Z.InvalidGrantError("Invalid or expired authorization code");return r}issue(t,n,r,o,s=ln(),i=Date.now()){let d=ln(),l=ln(),c=Date.now(),u=this.refreshMax()*1e3,p=Math.min(c+this.refreshTtl()*1e3,u>0?i+u:1/0),f=this.clientsStore.data,m=f.clients.find(h=>h.client_id===t);return m&&(m.lastUsedAt=c),f.tokens.push({hash:Lt(d),type:"access",clientId:t,family:s,scopes:n,resource:r,expires:c+this.accessTtl()*1e3,user:o,rotated:!1},{hash:Lt(l),type:"refresh",clientId:t,family:s,scopes:n,resource:r,expires:p,user:o,rotated:!1,familyStart:i}),this.clientsStore.persist(),{access_token:d,token_type:"Bearer",expires_in:this.accessTtl(),refresh_token:l,scope:n.join(" ")}}};function Nt(e){let t=new URL(e);if(!["http:","https:"].includes(t.protocol)||t.username||t.password||t.search||t.hash)throw new Error("Invalid resource URL");return`${t.protocol}//${t.host}${t.pathname.replace(/\/+$/,"")}`}a(Nt,"canonicalResource");var zn=["read","propose"];function fg(e){let t=(e??"/").split("/").filter(Boolean).join("/");return t?`/${t}/`:"/"}a(fg,"consentAdminRoot");var hg=new Set(["localhost","127.0.0.1","[::1]"]);function dn(e){return typeof e=="string"?e:""}a(dn,"value");function uc(e,t,n,r){let o=a(T=>`${n().replace(/\/$/,"")}${T}`,"abs"),s=a((T,x,C,E)=>{let A=x.url;x.url="/",T(x,C,S=>{x.url=A,E(S)})},"atRoot"),i=new Map,d=a(()=>{let T=Date.now();for(let[x,C]of i)C.expires<=T&&i.delete(x)},"sweep"),l=a(T=>{d();let x=[...i].filter(([,C])=>C.clientId===T);for(;x.length>=tg;)i.delete(x.shift()[0]);return i.size<ng},"reservePending"),c=a((T,x)=>{T.setHeader("Cache-Control","no-store"),T.setHeader("X-Frame-Options","DENY"),T.setHeader("Referrer-Policy","no-referrer"),T.setHeader("X-Content-Type-Options","nosniff"),T.setHeader("Content-Security-Policy",`default-src 'none'; ${x?`script-src 'nonce-${x}'; `:""}style-src 'unsafe-inline'; connect-src 'self'; form-action 'none'; base-uri 'none'; frame-ancestors 'none'`)},"securityHeaders"),u=Qr(e.settings.httpAdminRoot),p=a(T=>{if(!re(e.settings))return[...zn];let x=gt(T);return x==="*"?[...zn]:x==="read"?["read"]:[]},"grantableFor"),f=a(T=>{let x=T.get("sec-fetch-site");if(x&&x!=="same-origin")return!1;let C=T.get("origin");if(!C)return!0;let E=new Set;try{E.add(new URL(n()).origin)}catch{}let A=T.get("host");return A&&(E.add(`http://${A}`),E.add(`https://${A}`)),E.has(C)},"sameOrigin"),m=a((T,x)=>{d();let C=T.query,E=t.clientsStore.getClient(dn(C.client_id));if(!E){x.status(400).send("Unknown OAuth client");return}let A=dn(C.redirect_uri)||(E.redirect_uris.length===1?E.redirect_uris[0]:"");if(!E.redirect_uris.some(le=>(0,rc.redirectUriMatches)(A,le))){x.status(400).send("Unregistered redirect URI");return}if(C.response_type!=="code"||C.code_challenge_method!=="S256"||!/^[A-Za-z0-9_-]{43,128}$/.test(dn(C.code_challenge))){x.status(400).send("Authorization code with PKCE S256 is required");return}let S=[...new Set((dn(C.scope)||"read").split(" ").filter(Boolean))];if(S.some(le=>!zn.includes(le))){x.status(400).send("Unsupported scope");return}let N=dn(C.resource)||r(),b;try{b=Nt(N)}catch{x.status(400).send("Invalid resource");return}if(b!==Nt(r())){x.status(400).send("Resource mismatch");return}if(!l(E.client_id)){x.status(429).send("Too many pending authorization requests; try again later");return}let O=ln();i.set(O,{clientId:E.client_id,redirect:A,challenge:dn(C.code_challenge),scopes:S,resource:N,state:dn(C.state),csrf:ln(),expires:Date.now()+10*6e4});let M=new URL(A).host,F=(0,Fr.randomBytes)(16).toString("base64");c(x,F),x.status(200).type("html").send(ec({requestId:O,tokenKey:u,adminRoot:fg(e.settings.httpAdminRoot),nonce:F,redirectHost:M,loopbackRedirect:hg.has(new URL(A).hostname),clientName:E.client_name||"Unnamed client",scopes:S,script:tc}))},"authorizePage"),h=a((T,x,C)=>{c(x),lc.default.json({limit:"4kb"})(T,x,()=>Se(e,"api")(T,x,()=>{try{d();let E=/^\/mcp\/oauth\/consent\/([A-Za-z0-9_-]+)\/?$/.exec(T.path)?.[1]??"",A=i.get(E),S=A&&t.clientsStore.getClient(A.clientId);if(!A||!S){x.status(404).json({error:"Consent request expired or unknown"});return}let N=re(e.settings)&&T.user?.username||"owner",b=A.scopes.filter(F=>p(T).includes(F));if(!b.length){x.status(403).json({error:"Your Node-RED permissions do not allow granting the requested scopes",grantable:b});return}if(T.method==="GET"){A.approver=N,x.json({client_name:S.client_name??null,redirect_host:new URL(A.redirect).host,requested:A.scopes,grantable:b,user:N,csrf:A.csrf});return}if(T.method!=="POST"){x.status(405).end();return}let O=T.body??{};if(!f(T)||O.csrf!==A.csrf||A.approver!==N){x.status(403).json({error:"Consent request verification failed"});return}i.delete(E);let M=new URL(A.redirect);if(O.decision!=="allow")M.searchParams.set("error","access_denied");else{try{t.clientsStore.approve(S.client_id)}catch{x.status(409).json({error:"Maximum active OAuth clients reached"});return}let F=t.createCode(S,{redirectUri:A.redirect,codeChallenge:A.challenge,scopes:b,resource:new URL(A.resource)},N);M.searchParams.set("code",F)}A.state&&M.searchParams.set("state",A.state),x.json({redirect:M.href})}catch(E){C(E)}}))},"consentApi"),g=(0,oc.clientRegistrationHandler)({clientsStore:t.clientsStore,rateLimit:{windowMs:36e5,max:10,standardHeaders:!0,legacyHeaders:!1}}),y=(0,ic.tokenHandler)({provider:t}),w=(0,sc.revocationHandler)({provider:t}),v,L="";return{server:a((T,x,C)=>{let E=T.path,A=E.replace(/\/+$/,"").toLowerCase();if(A==="/mcp/oauth/authorize"){T.method==="GET"?m(T,x,C):x.status(405).set("Allow","GET").end();return}if(A.startsWith("/mcp/oauth/consent/")){h(T,x,C);return}if(!["/mcp/oauth/token","/mcp/oauth/register","/mcp/oauth/revoke"].includes(A)){C();return}let S=n();if(S.startsWith("https://")||/^http:\/\/(localhost|127\.0\.0\.1)(:|\/|$)/.test(S)){if(L!==S)try{v=(0,dc.mcpAuthRouter)({provider:t,issuerUrl:new URL(S),baseUrl:new URL(`${S.replace(/\/$/,"")}/mcp/oauth/`),resourceServerUrl:new URL(r()),scopesSupported:zn,clientRegistrationOptions:{rateLimit:{windowMs:36e5,max:10}}}),L=S}catch{v=void 0}if(v){let N=T.url;T.url=T.url.slice(10)||"/",v(T,x,b=>{T.url=N,C(b)});return}}if(E==="/mcp/oauth/token"){s(y,T,x,C);return}if(E==="/mcp/oauth/register"){s(g,T,x,C);return}if(E==="/mcp/oauth/revoke"){s(w,T,x,C);return}C()},"server"),metadata:a((T,x,C)=>{let E=T.path;if(E==="/.well-known/oauth-authorization-server"){s((0,Wi.metadataHandler)({issuer:n(),authorization_endpoint:o("/mcp/oauth/authorize"),token_endpoint:o("/mcp/oauth/token"),registration_endpoint:o("/mcp/oauth/register"),revocation_endpoint:o("/mcp/oauth/revoke"),response_types_supported:["code"],code_challenge_methods_supported:["S256"],grant_types_supported:["authorization_code","refresh_token"],token_endpoint_auth_methods_supported:["none"],scopes_supported:zn}),T,x,C);return}if(E==="/.well-known/oauth-protected-resource/mcp"||E==="/.well-known/oauth-protected-resource"){s((0,Wi.metadataHandler)({resource:Nt(r()),authorization_servers:[n()],scopes_supported:zn}),T,x,C);return}C()},"metadata"),auth:a((T,x,C)=>{try{let E=x.set.bind(x);x.set=((A,S)=>(A==="WWW-Authenticate"&&typeof S=="string"&&(S=S.replace('scope="read"','scope="read propose"')),typeof A=="string"?E(A,S??""):E(A))),(0,ac.requireBearerAuth)({verifier:t,requiredScopes:["read"],resourceMetadataUrl:o(e.settings.httpNodeRoot===!1||!e.httpNode?"/mcp/.well-known/oauth-protected-resource/mcp":"/.well-known/oauth-protected-resource/mcp"),expectedResource:new URL(r())})(T,x,A=>{x.set=E,C(A)})}catch(E){C(E)}},"auth")}}a(uc,"makeOAuthHandlers");var Or=require("fs"),Gn=require("path"),pc=require("url"),fc=require("yaml"),wg={};function mg(){return typeof __dirname<"u"?__dirname:(0,Gn.dirname)((0,pc.fileURLToPath)(wg.url))}a(mg,"moduleDir");function hc(){let e=mg(),t=[(0,Gn.join)(e,"..","docs","api","openapi.yaml"),(0,Gn.join)(e,"..","..","docs","api","openapi.yaml"),(0,Gn.join)(process.cwd(),"docs","api","openapi.yaml")];for(let n of t)if((0,Or.existsSync)(n))return n;return null}a(hc,"resolveOpenApiYamlPath");function gg(){let e=hc();if(!e)throw new Error("OpenAPI specification file was not found");return(0,Or.readFileSync)(e,"utf8")}a(gg,"loadOpenApiYaml");function mc(){return(e,t)=>{try{let n=gg();t.status(200).type("application/yaml").send(n)}catch(n){let r=n instanceof Error?n.message:String(n);t.status(500).json({error:r})}}}a(mc,"openApiYamlHandler");var Vi;function yg(){let e=hc();if(!e)throw new Error("OpenAPI specification file was not found");return Vi?.path!==e&&(Vi={path:e,json:JSON.stringify((0,fc.parse)((0,Or.readFileSync)(e,"utf8")))}),Vi.json}a(yg,"loadOpenApiJson");function gc(){return(e,t)=>{try{t.status(200).type("application/json").send(yg())}catch(n){let r=n instanceof Error?n.message:String(n);t.status(500).json({error:r})}}}a(gc,"openApiJsonHandler");function bg(){return`
1163
1178
  var settingsData = null;
1164
1179
  var settingsSaving = false;
1165
1180
  var settingsTimer = null;
1166
1181
  var SETTINGS_FIELDS = {
1167
1182
  enabled: 'enabled', allowWithoutAdminAuth: 'allow-without',
1168
1183
  defaultLinkTtlSeconds: 'ttl', defaultDeploymentType: 'deploy',
1169
- maxActiveLinks: 'max-links', publicBaseUrl: 'public-url',
1184
+ proposalTtlDays: 'proposal-ttl', maxActiveLinks: 'max-links', publicBaseUrl: 'public-url',
1170
1185
  corsAllowedOrigins: 'cors', rateLimitWindowMs: 'rate-window',
1171
1186
  rateLimitMaxPerIp: 'rate-ip', rateLimitMaxPerToken: 'rate-token'
1172
1187
  };
@@ -1220,6 +1235,7 @@ a{color:var(--focus)}
1220
1235
  $('set-deploy').value = s.defaultDeploymentType || 'nodes';
1221
1236
  $('set-deploy-full-warn').hidden = $('set-deploy').value !== 'full';
1222
1237
  $('set-ttl').value = String(Math.max(1, Math.round((s.defaultLinkTtlSeconds || 14400) / 3600)));
1238
+ $('set-proposal-ttl').value = String(s.proposalTtlDays == null ? 7 : s.proposalTtlDays);
1223
1239
  $('set-max-links').value = s.maxActiveLinks == null ? '' : String(s.maxActiveLinks);
1224
1240
  $('set-public-url').value = s.publicBaseUrl || '';
1225
1241
  $('set-cors').value = (s.corsAllowedOrigins || []).join(', ');
@@ -1269,8 +1285,9 @@ a{color:var(--focus)}
1269
1285
  var payload = {
1270
1286
  enabled: $('set-enabled').checked,
1271
1287
  allowWithoutAdminAuth: $('set-allow-without').checked,
1272
- defaultLinkTtlSeconds: Math.max(1, Math.floor(Number($('set-ttl').value || 4) * 3600)),
1288
+ defaultLinkTtlSeconds: Number($('set-ttl').value) * 3600,
1273
1289
  defaultDeploymentType: $('set-deploy').value,
1290
+ proposalTtlDays: Number($('set-proposal-ttl').value),
1274
1291
  maxActiveLinks: Number($('set-max-links').value),
1275
1292
  publicBaseUrl: String($('set-public-url').value).trim(),
1276
1293
  corsAllowedOrigins: String($('set-cors').value).split(',').map(function (x) { return x.trim(); }).filter(Boolean),
@@ -1295,7 +1312,7 @@ a{color:var(--focus)}
1295
1312
  return payload;
1296
1313
  }
1297
1314
 
1298
- `}a(Om,"settingsPartForm");function $m(){return`
1315
+ `}a(bg,"settingsPartForm");function vg(){return`
1299
1316
  function secretPatternsEditable() {
1300
1317
  return settingsCanEdit() && !fieldLocked('secretNamePatterns');
1301
1318
  }
@@ -1383,7 +1400,7 @@ a{color:var(--focus)}
1383
1400
  });
1384
1401
  }
1385
1402
 
1386
- `}a($m,"settingsPartSecrets");function Mm(){return`
1403
+ `}a(vg,"settingsPartSecrets");function kg(){return`
1387
1404
  function settingsFlash(msg, ok) {
1388
1405
  var tab = document.querySelector('nav.tabs button[aria-selected="true"]');
1389
1406
  var target = tab && tab.getAttribute('data-tab') === 'oauth' ? 'oauth-flash' : 'settings-flash';
@@ -1406,7 +1423,6 @@ a{color:var(--focus)}
1406
1423
  loadOAuthStatus();
1407
1424
  } catch (e) {
1408
1425
  settingsFlash(e.message || String(e), false);
1409
- loadSettings();
1410
1426
  } finally {
1411
1427
  settingsSaving = false;
1412
1428
  }
@@ -1414,6 +1430,20 @@ a{color:var(--focus)}
1414
1430
 
1415
1431
  function saveSettingsNow() {
1416
1432
  if (!settingsCanEdit() || settingsSaving) return;
1433
+ var fields = ['set-ttl', 'set-proposal-ttl', 'set-max-links', 'set-rate-window', 'set-rate-ip', 'set-rate-token',
1434
+ 'oauth-access-ttl', 'oauth-refresh-ttl', 'oauth-refresh-max', 'oauth-dcr-cap'];
1435
+ for (var i = 0; i < fields.length; i++) {
1436
+ var input = $(fields[i]);
1437
+ if (!input || input.disabled) continue;
1438
+ var n = Number(input.value), min = Number(input.min || 0);
1439
+ var max = input.max ? Number(input.max) : Infinity;
1440
+ if (!input.value.trim() || !Number.isInteger(n) || n < min || n > max) {
1441
+ input.setAttribute('aria-invalid', 'true');
1442
+ settingsFlash('Enter a whole number with a minimum of ' + min + (max < Infinity ? ' and a maximum of ' + max : '') + '.', false);
1443
+ return;
1444
+ }
1445
+ input.removeAttribute('aria-invalid');
1446
+ }
1417
1447
  var payload = collectSettingsPayload();
1418
1448
  var was = settingsData && settingsData.settings && settingsData.settings.oauth && settingsData.settings.oauth.enabled;
1419
1449
  if (!was && payload.oauth && payload.oauth.enabled) {
@@ -1456,6 +1486,7 @@ a{color:var(--focus)}
1456
1486
  el.className = 'check-result' + (state ? ' is-' + state : '');
1457
1487
  el.textContent = text || '';
1458
1488
  el.hidden = !text;
1489
+ el.setAttribute('role', state === 'fail' ? 'alert' : 'status');
1459
1490
  }
1460
1491
 
1461
1492
  async function checkPublicUrl() {
@@ -1471,7 +1502,7 @@ a{color:var(--focus)}
1471
1502
  }
1472
1503
  }
1473
1504
 
1474
- `}a(Mm,"settingsPartSave");function Dm(){return`
1505
+ `}a(kg,"settingsPartSave");function Ag(){return`
1475
1506
  function editorNodeHref(c) {
1476
1507
  var base = editorRoot && editorRoot !== '/' ? editorRoot.replace(/\\/$/, '') + '/' : '/';
1477
1508
  if (c.nodeId) return base + '#node/' + encodeURIComponent(c.nodeId);
@@ -1579,7 +1610,7 @@ a{color:var(--focus)}
1579
1610
  }
1580
1611
  }
1581
1612
 
1582
- `}a(Dm,"settingsPartOAuth");function jm(){return`
1613
+ `}a(Ag,"settingsPartOAuth");function xg(){return`
1583
1614
  function loadOAuthTab() {
1584
1615
  loadSettings();
1585
1616
  loadOAuthStatus();
@@ -1612,7 +1643,7 @@ a{color:var(--focus)}
1612
1643
  showToast('Server URL copied.', true);
1613
1644
  });
1614
1645
  })();
1615
- `}a(jm,"settingsPartWire");function jd(){return Om()+$m()+Mm()+Dm()+jm()}a(jd,"pageClientSettingsScript");function Um(){return`
1646
+ `}a(xg,"settingsPartWire");function yc(){return bg()+vg()+kg()+Ag()+xg()}a(yc,"pageClientSettingsScript");function Tg(){return`
1616
1647
  (function () {
1617
1648
  var root = document.getElementById('mcp-root');
1618
1649
  if (!root) return;
@@ -1696,6 +1727,7 @@ a{color:var(--focus)}
1696
1727
  function showFlash(el, message, ok) {
1697
1728
  if (!el) return;
1698
1729
  el.className = 'flash ' + (ok ? 'ok' : 'error');
1730
+ el.setAttribute('role', ok ? 'status' : 'alert');
1699
1731
  el.textContent = message;
1700
1732
  el.hidden = !message;
1701
1733
  }
@@ -1727,6 +1759,95 @@ a{color:var(--focus)}
1727
1759
  return target;
1728
1760
  }
1729
1761
 
1762
+ /*
1763
+ * Signed-out card (same look/flow as the OAuth consent page). Credentials
1764
+ * login: inline form \u2192 <adminRoot>/auth/token, token stored under the
1765
+ * editor's key (editor stays signed in). Strategy login: open the editor in
1766
+ * the named window; continue automatically when its token appears (storage
1767
+ * event) or on focus / visibility with a new token; Retry as fallback.
1768
+ */
1769
+ var authGateShown = false;
1770
+ var authGateToken = null;
1771
+ var authGateLastAuto = 0;
1772
+ var authGateEditorOpened = false;
1773
+ function authGateAdminHref() {
1774
+ var root = editorLoginUrl();
1775
+ if (root.charAt(root.length - 1) !== '/') root += '/';
1776
+ return new URL(root, location.href).href;
1777
+ }
1778
+ function authGateStatus(text, isError) {
1779
+ var st = $('auth-gate-status');
1780
+ if (!st) return;
1781
+ st.textContent = text;
1782
+ st.className = 'sg-status' + (isError ? ' error' : '');
1783
+ st.setAttribute('role', isError ? 'alert' : 'status');
1784
+ st.hidden = !text;
1785
+ }
1786
+ function authGateContinue() {
1787
+ authGateStatus('Signed in. Loading AI Access\u2026');
1788
+ location.reload();
1789
+ }
1790
+ function authGateTyping() {
1791
+ var form = $('auth-gate-login');
1792
+ return !!form && !form.hidden &&
1793
+ !!(($('auth-gate-username') || {}).value || ($('auth-gate-password') || {}).value);
1794
+ }
1795
+ function authGateAuto(fromStorage) {
1796
+ if (!authGateShown) return;
1797
+ var tok = readEditorToken();
1798
+ if (!tok || tok === authGateToken) return;
1799
+ // Leave a half-typed form alone unless the user chose the editor handoff;
1800
+ // a new token written by another tab is definitive.
1801
+ if (!fromStorage && !authGateEditorOpened && authGateTyping()) return;
1802
+ if (Date.now() - authGateLastAuto < 1000) return;
1803
+ authGateLastAuto = Date.now();
1804
+ authGateContinue();
1805
+ }
1806
+ function wireAuthGate() {
1807
+ var form = $('auth-gate-login');
1808
+ if (form) form.addEventListener('submit', function (ev) {
1809
+ ev.preventDefault();
1810
+ var btn = $('auth-gate-submit');
1811
+ btn.disabled = true;
1812
+ authGateStatus('Signing in\u2026');
1813
+ nrPasswordSignIn(authGateAdminHref(), authTokenKey, $('auth-gate-username').value, $('auth-gate-password').value)
1814
+ .then(function (x) {
1815
+ btn.disabled = false;
1816
+ $('auth-gate-password').value = '';
1817
+ if (!x.ok) { authGateStatus('Sign in failed. Check your username and password.', true); return; }
1818
+ authGateContinue();
1819
+ })
1820
+ .catch(function (e) { btn.disabled = false; authGateStatus('Error: ' + (e.message || String(e)), true); });
1821
+ });
1822
+ wireAuthGateEditor();
1823
+ }
1824
+ // Both the strategy button and the optional credentials link use this handoff.
1825
+ function wireAuthGateEditor() {
1826
+ ['auth-gate-editor', 'auth-gate-editor-link'].forEach(function (id) {
1827
+ var ed = $(id);
1828
+ if (!ed) return;
1829
+ if (id === 'auth-gate-editor-link') ed.href = editorLoginUrl();
1830
+ ed.addEventListener('click', function (ev) {
1831
+ ev.preventDefault();
1832
+ authGateEditorOpened = true;
1833
+ window.open(editorLoginUrl(), 'node-red-editor');
1834
+ $('auth-gate-retry').hidden = false;
1835
+ authGateStatus('Sign in in the Node-RED editor window. This page continues automatically.');
1836
+ });
1837
+ });
1838
+ var retry = $('auth-gate-retry');
1839
+ if (retry) retry.addEventListener('click', function () {
1840
+ if (readEditorToken()) authGateContinue();
1841
+ else authGateStatus('Not signed in yet. Sign in in the Node-RED editor, then come back here.', true);
1842
+ });
1843
+ window.addEventListener('storage', function (ev) {
1844
+ if (ev.key === authTokenKey && ev.newValue) authGateAuto(true);
1845
+ });
1846
+ document.addEventListener('visibilitychange', function () {
1847
+ if (document.visibilityState === 'visible') authGateAuto(false);
1848
+ });
1849
+ window.addEventListener('focus', function () { authGateAuto(false); });
1850
+ }
1730
1851
  function showAuthGate() {
1731
1852
  var gate = $('auth-gate');
1732
1853
  var nav = $('main-nav');
@@ -1735,20 +1856,27 @@ a{color:var(--focus)}
1735
1856
  var el = $(id);
1736
1857
  if (el) el.hidden = true;
1737
1858
  });
1738
- if (gate) {
1739
- gate.hidden = false;
1740
- var ed = $('auth-gate-editor');
1741
- if (ed) ed.setAttribute('href', editorLoginUrl());
1742
- var ret = $('auth-gate-return');
1743
- if (ret) {
1744
- ret.setAttribute('href', (typeof location !== 'undefined' ? location.pathname : (cfg.dashboardPath || '/ai-access')) +
1745
- (typeof location !== 'undefined' ? location.search : '') +
1746
- (typeof location !== 'undefined' ? location.hash : ''));
1859
+ if (!gate || authGateShown) return;
1860
+ authGateShown = true;
1861
+ authGateToken = readEditorToken();
1862
+ gate.hidden = false;
1863
+ if (document.body && document.body.classList) document.body.classList.add('signed-out');
1864
+ wireAuthGate();
1865
+ authGateStatus('Checking how Node-RED signs you in\u2026');
1866
+ nrLoginType(authGateAdminHref()).then(function (type) {
1867
+ if (type === 'credentials') {
1868
+ authGateStatus('Sign in with your Node-RED account to continue.');
1869
+ $('auth-gate-login').hidden = false;
1870
+ $('auth-gate-editor-link').hidden = false;
1871
+ $('auth-gate-username').focus();
1872
+ } else {
1873
+ authGateStatus('Sign in with the Node-RED editor in this browser. This page continues automatically.');
1874
+ $('auth-gate-editor').hidden = false;
1875
+ $('auth-gate-retry').hidden = false;
1747
1876
  }
1748
- }
1749
- showFlash($('boot-flash'), 'Please sign in to Node-RED first.', false);
1877
+ });
1750
1878
  }
1751
-
1879
+ ${as}
1752
1880
  function isStatusPath(path) {
1753
1881
  return path === '/status' || path.indexOf('/status?') === 0;
1754
1882
  }
@@ -1763,6 +1891,7 @@ a{color:var(--focus)}
1763
1891
  opts = opts || {};
1764
1892
  var el = document.createElement('div');
1765
1893
  el.className = 'toast ' + (ok ? 'ok' : 'error');
1894
+ el.setAttribute('role', ok ? 'status' : 'alert');
1766
1895
  if (opts.title) {
1767
1896
  var titleEl = document.createElement('span');
1768
1897
  titleEl.className = 'toast-title';
@@ -1836,7 +1965,11 @@ a{color:var(--focus)}
1836
1965
  topic: 'Topic',
1837
1966
  payload: 'Payload',
1838
1967
  complete: 'Complete',
1839
- info: 'Info',
1968
+ info: 'Description',
1969
+ label: 'Flow name',
1970
+ env: 'Environment variables',
1971
+ disabled: 'Flow disabled',
1972
+ credentials: 'Credentials',
1840
1973
  format: 'Format',
1841
1974
  syntax: 'Syntax',
1842
1975
  outputs: 'Outputs',
@@ -1881,7 +2014,7 @@ a{color:var(--focus)}
1881
2014
  }
1882
2015
  return body;
1883
2016
  }
1884
- `}a(Um,"pageClientScriptPartA");function Bm(){return`
2017
+ `}a(Tg,"pageClientScriptPartA");function Rg(){return`
1885
2018
  // Time helpers \u2014 keep in sync with page-time.ts (always browser-local Intl).
1886
2019
  function titleFromDisplayName(displayName) {
1887
2020
  var s = String(displayName || '');
@@ -1988,7 +2121,7 @@ a{color:var(--focus)}
1988
2121
  btn.disabled = false;
1989
2122
  }, 1600);
1990
2123
  }
1991
- `}a(Bm,"pageClientScriptPartATimeCopy");function qm(){return`
2124
+ `}a(Rg,"pageClientScriptPartATimeCopy");function Pg(){return`
1992
2125
  function shortId(value, keep) {
1993
2126
  var s = String(value == null ? '' : value);
1994
2127
  var n = keep || 8;
@@ -2061,6 +2194,9 @@ a{color:var(--focus)}
2061
2194
  'proposal.approved': 'Approved',
2062
2195
  'proposal.rejected': 'Rejected',
2063
2196
  'proposal.failed': 'Failed',
2197
+ 'proposal.conflict': 'Conflict',
2198
+ 'proposal.resolved': 'Resolved',
2199
+ 'proposal.conflict_resolved': 'Conflict resolved',
2064
2200
  'proposal.withdrawn': 'Withdrawn',
2065
2201
  'link.created': 'Link created',
2066
2202
  'link.revoked': 'Link revoked'
@@ -2079,6 +2215,9 @@ a{color:var(--focus)}
2079
2215
  'proposal.approved': 'Approved',
2080
2216
  'proposal.rejected': 'Rejected',
2081
2217
  'proposal.failed': 'Failed',
2218
+ 'proposal.conflict': 'Conflict',
2219
+ 'proposal.resolved': 'Resolved',
2220
+ 'proposal.conflict_resolved': 'Conflict resolved',
2082
2221
  'proposal.withdrawn': 'Withdrawn',
2083
2222
  'link.created': 'Link created',
2084
2223
  'link.revoked': 'Link revoked'
@@ -2118,7 +2257,7 @@ a{color:var(--focus)}
2118
2257
  }
2119
2258
  return '';
2120
2259
  }
2121
- `}a(qm,"pageClientScriptPartAFormat");function zm(){return`
2260
+ `}a(Pg,"pageClientScriptPartAFormat");function _g(){return`
2122
2261
  function lookupCachedProposal(id) {
2123
2262
  if (!id) return null;
2124
2263
  return cachedProposals.filter(function (x) { return x.id === id; })[0] || null;
@@ -2239,7 +2378,7 @@ a{color:var(--focus)}
2239
2378
  return k + '=' + String(v);
2240
2379
  }).join(', '));
2241
2380
  }
2242
- `}a(zm,"pageClientScriptPartAAudit");function Wm(){return`
2381
+ `}a(_g,"pageClientScriptPartAAudit");function Cg(){return`
2243
2382
  // Hash helpers \u2014 keep in sync with page-hash.ts
2244
2383
  var TAB_IDS = ['status', 'settings', 'links', 'oauth', 'proposals'];
2245
2384
  function hashForTab(tab) {
@@ -2525,7 +2664,7 @@ a{color:var(--focus)}
2525
2664
  }
2526
2665
  }
2527
2666
 
2528
- `}a(Wm,"pageClientScriptPartATabs");function Gm(){return`
2667
+ `}a(Cg,"pageClientScriptPartATabs");function Eg(){return`
2529
2668
  async function loadLinks(opts) {
2530
2669
  opts = opts || {};
2531
2670
  if (!aiOn || !canManage) {
@@ -2599,7 +2738,7 @@ a{color:var(--focus)}
2599
2738
  showToast(e.message || String(e), false);
2600
2739
  }
2601
2740
  }
2602
- `}a(Gm,"pageClientScriptPartA2");function Km(){return`
2741
+ `}a(Eg,"pageClientScriptPartA2");function Ig(){return`
2603
2742
 
2604
2743
  function normalizeLinkKind(kind) {
2605
2744
  if (kind === 'rest' || kind === 'mcp' || kind === 'get_link') return kind;
@@ -3057,6 +3196,7 @@ a{color:var(--focus)}
3057
3196
 
3058
3197
  function plainDiffSentence(diff, flowLabel) {
3059
3198
  if (!diff || !diff.summary) return '';
3199
+ if (proposalHasNoNetChange(diff)) return 'Nothing changes in the live flow';
3060
3200
  var sum = diff.summary;
3061
3201
  var parts = [];
3062
3202
  if (sum.added) parts.push('Adds ' + sum.added + (sum.added === 1 ? ' node' : ' nodes'));
@@ -3064,7 +3204,8 @@ a{color:var(--focus)}
3064
3204
  (sum.removed === 1 ? ' node' : ' nodes'));
3065
3205
  if (sum.modified) parts.push((parts.length ? 'changes ' : 'Changes ') + sum.modified +
3066
3206
  (sum.modified === 1 ? ' node' : ' nodes'));
3067
- if (!parts.length) parts.push('No node changes');
3207
+ if ((sum.tabChanges || []).length) parts.push('Changes flow settings');
3208
+ if (!parts.length) parts.push('No changes');
3068
3209
  var sentence = parts.join(', ');
3069
3210
  if (flowLabel) sentence += ' in ' + flowLabel;
3070
3211
  return sentence;
@@ -3098,10 +3239,14 @@ a{color:var(--focus)}
3098
3239
 
3099
3240
  /** Soft validation notes from the proposal and/or its diff. */
3100
3241
  function proposalWarnings(proposal, diff) {
3101
- var fromDiff = diff && Array.isArray(diff.warnings) ? diff.warnings : [];
3102
- if (fromDiff.length) return fromDiff;
3103
- if (proposal && Array.isArray(proposal.warnings)) return proposal.warnings;
3104
- return [];
3242
+ var all = (proposal && Array.isArray(proposal.warnings) ? proposal.warnings : [])
3243
+ .concat(diff && Array.isArray(diff.warnings) ? diff.warnings : []);
3244
+ var seen = {};
3245
+ return all.filter(function (w) {
3246
+ var key = JSON.stringify([w.code, w.nodeId, w.property, w.error]);
3247
+ if (seen[key]) return false;
3248
+ seen[key] = true; return true;
3249
+ });
3105
3250
  }
3106
3251
 
3107
3252
  /**
@@ -3110,33 +3255,22 @@ a{color:var(--focus)}
3110
3255
  */
3111
3256
  var SECRET_WARN_CODES = ['secret_destination_changed', 'secret_node_code_changed',
3112
3257
  'secret_wires_changed', 'secret_scope_changed', 'outbound_node_changed', 'secret_tab_changed',
3113
- 'secret_reference_added'];
3114
- function renderRestoredSecrets(p) {
3258
+ 'secret_reference_added', 'secret_may_revert'];
3259
+ function renderReviewWarnings(p, diff) {
3260
+ if (proposalHasNoNetChange(diff)) return '';
3261
+ var warnings = proposalWarnings(p, diff);
3262
+ var security = warnings.filter(function (w) { return SECRET_WARN_CODES.indexOf(w.code) >= 0; });
3263
+ var config = warnings.filter(function (w) { return SECRET_WARN_CODES.indexOf(w.code) < 0; });
3115
3264
  var fields = p && Array.isArray(p.restoredSecrets) ? p.restoredSecrets : [];
3116
- var warns = (p && Array.isArray(p.warnings) ? p.warnings : []).filter(function (w) {
3117
- return w && SECRET_WARN_CODES.indexOf(w.code) >= 0;
3118
- });
3119
- if (!fields.length && !warns.length) return '';
3120
- var strong = warns.some(function (w) { return w.code !== 'secret_node_code_changed'; });
3121
- var items = fields.map(function (f) {
3122
- return '<li><code>' + esc(String(f.nodeId)) + '</code> \xB7 <code>' + esc(String(f.property)) + '</code></li>';
3123
- }).join('');
3124
- var warnItems = warns.map(function (w) {
3125
- return '<li><strong>' + esc(String(w.code)) + '</strong> \u2014 ' + esc(String(w.error || '')) + '</li>';
3126
- }).join('');
3127
- return '<div class="proposal-warnings restored-secrets' + (strong ? ' restored-secrets-danger' : '') +
3128
- '" role="' + (strong ? 'alert' : 'status') + '">' +
3129
- '<p class="proposal-warnings-title">' +
3130
- esc(strong
3131
- ? 'Security review: this proposal changes what could send data (or secrets) elsewhere'
3132
- : 'Secrets restored from the live flow (' + fields.length + ')') +
3133
- '</p>' +
3134
- (warnItems ? '<ul>' + warnItems + '</ul>' : '') +
3135
- (fields.length
3136
- ? '<p class="muted">The AI saw [REDACTED] in these fields; approving keeps the real values:</p>' +
3137
- '<ul>' + items + '</ul>'
3138
- : '') +
3139
- '</div>';
3265
+ var count = warnings.length + fields.length;
3266
+ if (!count) return '';
3267
+ return '<aside class="proposal-warnings review-warnings" role="' + (security.length ? 'alert' : 'status') + '">' +
3268
+ '<h3>Review notes (' + count + ')</h3>' +
3269
+ renderValidationWarnings(security, { title: 'Security review (' + security.length + ')' }) +
3270
+ (fields.length ? '<details class="restored-secrets"><summary>Secrets retained from the live flow (' + fields.length + ')</summary><ul>' + fields.map(function (f) {
3271
+ return '<li><code>' + esc(f.nodeId) + '</code> \xB7 ' + esc(f.property) + '</li>';
3272
+ }).join('') + '</ul></details>' : '') +
3273
+ renderValidationWarnings(config, { title: 'Needs configuration (' + config.length + ')' }) + '</aside>';
3140
3274
  }
3141
3275
 
3142
3276
  function renderValidationWarnings(warnings, opts) {
@@ -3177,16 +3311,16 @@ a{color:var(--focus)}
3177
3311
  }
3178
3312
 
3179
3313
  function proposalIsStale(p) {
3180
- if (!p || p.status !== 'pending') return false;
3181
- if (p.baseRevStale === true) return true;
3314
+ if (!p || (p.status !== 'pending' && p.status !== 'conflict')) return false;
3182
3315
  var cached = proposalDiffCache[p.id];
3183
- return !!(cached && cached.baseRevStale);
3316
+ var rebase = (cached && cached.rebase) || p.rebase;
3317
+ return p.status === 'conflict' || !!(rebase && rebase.state === 'conflict');
3184
3318
  }
3185
3319
 
3186
3320
  function formatPendingBadgeText(live, stale) {
3187
3321
  var parts = [];
3188
3322
  if (live > 0) parts.push(live + ' to review');
3189
- if (stale > 0) parts.push(stale + ' out of date');
3323
+ if (stale > 0) parts.push(stale + ' conflict' + (stale === 1 ? '' : 's'));
3190
3324
  return parts.length ? parts.join(' \xB7 ') : '0';
3191
3325
  }
3192
3326
 
@@ -3317,7 +3451,7 @@ a{color:var(--focus)}
3317
3451
  }
3318
3452
 
3319
3453
  function countPendingBuckets() {
3320
- var pending = cachedProposals.filter(function (p) { return p.status === 'pending'; });
3454
+ var pending = cachedProposals.filter(function (p) { return (p.status === 'pending' || p.status === 'conflict'); });
3321
3455
  var stale = 0;
3322
3456
  pending.forEach(function (p) {
3323
3457
  if (proposalIsStale(p)) stale += 1;
@@ -3326,10 +3460,10 @@ a{color:var(--focus)}
3326
3460
  pending: pending.length,
3327
3461
  live: pending.length - stale,
3328
3462
  stale: stale,
3329
- decided: Math.max(decidedTotal, cachedProposals.filter(function (p) { return p.status !== 'pending'; }).length)
3463
+ decided: Math.max(decidedTotal, cachedProposals.filter(function (p) { return (p.status !== 'pending' && p.status !== 'conflict'); }).length)
3330
3464
  };
3331
3465
  }
3332
- `}a(Km,"pageClientScriptPartB");function Jm(){return`
3466
+ `}a(Ig,"pageClientScriptPartB");function Lg(){return`
3333
3467
  var DIFF_MODE_KEY = 'ai-access-diff-mode';
3334
3468
  var DIFF_SPLIT_MIN_PX = ${900};
3335
3469
  var PRESENCE_SHORT_TEXT_MAX = ${120};
@@ -3446,7 +3580,7 @@ a{color:var(--focus)}
3446
3580
  '</div>';
3447
3581
  }
3448
3582
 
3449
- `}a(Jm,"pageClientScriptPartBDiff");function Vm(){return`
3583
+ `}a(Lg,"pageClientScriptPartBDiff");function Ng(){return`
3450
3584
  function isCollapsiblePresenceText(value) {
3451
3585
  var s = String(value == null ? '' : value);
3452
3586
  if (s.indexOf('\\n') >= 0) return true;
@@ -3549,7 +3683,7 @@ a{color:var(--focus)}
3549
3683
  });
3550
3684
  });
3551
3685
  }
3552
- `}a(Vm,"pageClientScriptPartBDiffPresence");function Ym(){return`
3686
+ `}a(Ng,"pageClientScriptPartBDiffPresence");function Fg(){return`
3553
3687
  function textChangeLineCount(ch) {
3554
3688
  return (ch.lines || []).length;
3555
3689
  }
@@ -3608,9 +3742,13 @@ a{color:var(--focus)}
3608
3742
  '<code class="diff-before" title="before">' + esc(JSON.stringify(ch.before)) + '</code>' +
3609
3743
  ' \u2192 <code class="diff-after" title="after">' + esc(JSON.stringify(ch.after)) + '</code></div>';
3610
3744
  }
3611
- `}a(Ym,"pageClientScriptPartBDiffView");function Xm(){return`
3745
+ `}a(Fg,"pageClientScriptPartBDiffView");function Og(){return`
3612
3746
  function renderDiffSections(sections, mode, emptyMsg) {
3613
- var parts = '';
3747
+ var parts = sections.summary && (sections.summary.tabChanges || []).length
3748
+ ? '<p>Flow settings changed: ' + esc(sections.summary.tabChanges.join(', ')) + '</p>' : '';
3749
+ if (sections.tabProperties && sections.tabProperties.length) {
3750
+ parts += sections.tabProperties.map(function (ch) { return renderPropertyChange(ch, mode); }).join('');
3751
+ }
3614
3752
  if (sections.added && sections.added.length) {
3615
3753
  parts += '<div class="diff-section"><h4>Added nodes</h4>' +
3616
3754
  sections.added.map(function (n) { return renderPresenceNode(n, 'added'); }).join('') +
@@ -3644,6 +3782,23 @@ a{color:var(--focus)}
3644
3782
  return parts;
3645
3783
  }
3646
3784
 
3785
+ function diffHasChanges(diff) {
3786
+ if (!diff || !diff.summary) return false;
3787
+ var sum = diff.summary;
3788
+ return !!(sum.added || sum.removed || sum.modified || (sum.tabChanges || []).length);
3789
+ }
3790
+ function proposalHasNoNetChange(diff) {
3791
+ return !!(diff && diff.rebase && diff.rebase.state !== 'conflict' &&
3792
+ diff.vsCurrent && diff.vsCurrent.summary && !diffHasChanges(diff.vsCurrent));
3793
+ }
3794
+ function diffComparisonKey(diff) {
3795
+ if (!diff) return '';
3796
+ return JSON.stringify([diff.summary && diff.summary.tabChanges, diff.tabProperties || [],
3797
+ (diff.added || []).map(function (n) { return [n.id, n.properties]; }),
3798
+ (diff.removed || []).map(function (n) { return [n.id, n.properties]; }),
3799
+ (diff.modified || []).map(function (n) { return [n.id, n.changes]; })]);
3800
+ }
3801
+
3647
3802
  function renderHumanDiff(diff, mode) {
3648
3803
  if (!diff) return '<p class="muted">Diff unavailable.</p>';
3649
3804
  mode = mode || getDiffMode();
@@ -3651,7 +3806,8 @@ a{color:var(--focus)}
3651
3806
  ? '<p class="muted">No starting snapshot was saved with this proposal, so changes are compared with the live flow.</p>'
3652
3807
  : '';
3653
3808
  // Hide +/-/~ counters and tab:label jargon \u2014 the card sentence already says it.
3654
- var showModeToggle = diffHasTextChanges(diff);
3809
+ var showModeToggle = diffHasTextChanges(diff) || diffHasTextChanges(diff.liveChanges || {}) ||
3810
+ diffHasTextChanges(diff.vsCurrent || {});
3655
3811
  var toolbar = '';
3656
3812
  if (showModeToggle) {
3657
3813
  toolbar =
@@ -3664,27 +3820,122 @@ a{color:var(--focus)}
3664
3820
  (mode === 'unified' ? 'true' : 'false') + '">Unified</button>' +
3665
3821
  '</div></div>';
3666
3822
  }
3667
- var warnHtml = renderValidationWarnings(diff.warnings, {
3668
- title: 'Scaffold warnings \u2014 configure before deploy if needed',
3669
- className: 'diff-warnings'
3670
- });
3671
- var sections = renderDiffSections(
3672
- diff,
3673
- mode,
3674
- diff.compareAgainst === 'base'
3675
- ? 'No node changes compared with what the AI started from.'
3676
- : 'No node changes compared with the live flow.'
3677
- );
3678
- var vsCurrent = '';
3679
- if (diff.vsCurrent) {
3680
- var vc = diff.vsCurrent.summary || {};
3681
- var places = (vc.added || 0) + (vc.removed || 0) + (vc.modified || 0);
3682
- if (places > 0) {
3683
- vsCurrent = '<p class="muted vs-current-plain">The flow also changed in ' +
3684
- places + ' other place' + (places === 1 ? '' : 's') + ' since then.</p>';
3685
- }
3823
+ var sections = renderDiffSections(diff, mode, 'Nothing changes.');
3824
+ var liveChanged = diffHasChanges(diff.liveChanges);
3825
+ var identical = diff.vsCurrent && diffComparisonKey(diff) === diffComparisonKey(diff.vsCurrent);
3826
+ var liveHtml = liveChanged
3827
+ ? '<details class="editor-changes"><summary>Changed in editor since submit</summary>' +
3828
+ renderDiffSections(diff.liveChanges, mode, 'No editor changes.') + '</details>' : '';
3829
+ var deployHtml = diff.vsCurrent
3830
+ ? renderDiffSections(diff.vsCurrent, mode, 'Nothing changes in the live flow.')
3831
+ : '<p class="muted">Resolve conflicts to preview what will deploy.</p>';
3832
+ if (proposalHasNoNetChange(diff)) {
3833
+ return '<p class="no-net-change" role="status"><strong>Nothing changes in the live flow.</strong> ' +
3834
+ 'The proposal is already reflected in the editor. You can reject it to close it.</p>' + liveHtml;
3835
+ }
3836
+ if (identical || (!liveChanged && !(diff.rebase && diff.rebase.state === 'conflict'))) {
3837
+ return missing + toolbar + '<h3>Proposed change</h3>' + sections + liveHtml;
3838
+ }
3839
+ return missing + toolbar + '<h3>Proposed change (vs base)</h3>' + sections +
3840
+ liveHtml + '<h3>Will deploy (vs live)</h3>' + deployHtml;
3841
+ }
3842
+
3843
+ function conflictReason(c) {
3844
+ var reasons = {
3845
+ node_changed_both: 'The editor and proposal changed the same fields on this node.',
3846
+ tab_changed_both: 'The editor and proposal changed the same flow settings.',
3847
+ node_deleted_live: 'This node was deleted in the editor and edited in the proposal.',
3848
+ node_deleted_in_proposal_modified_live: 'The proposal deletes a node that was edited in the editor.',
3849
+ id_collision: 'The editor and proposal added different nodes with the same ID.',
3850
+ tab_deleted: 'This flow was deleted in the editor.',
3851
+ base_unavailable: 'The original flow snapshot is unavailable. Ask the AI to submit a new proposal.',
3852
+ dangling_reference: 'A connection or configuration refers to a missing node.',
3853
+ redacted_placeholder: 'A hidden secret could not be matched safely. Review it in the editor.'
3854
+ };
3855
+ return reasons[c.kind] || c.message || 'Review the competing changes.';
3856
+ }
3857
+ function conflictValueText(value) {
3858
+ return value === undefined ? '(not present)' : typeof value === 'string' ? value : JSON.stringify(value, null, 2);
3859
+ }
3860
+ function renderConflictValues(c, diff) {
3861
+ var entry = ((diff && diff.conflictValues) || []).filter(function (v) { return v.nodeId === (c.nodeId || ''); })[0];
3862
+ if (!entry) return '<p class="muted">Loading before and after values\u2026</p>';
3863
+ return entry.fields.map(function (v) {
3864
+ return '<div class="conflict-values"><h4>' + esc(v.field || 'Node') + '</h4><dl>' +
3865
+ ['base', 'live', 'proposed'].map(function (side) {
3866
+ return '<div><dt>' + (side === 'base' ? 'Base (before submission)' : side === 'live' ? 'Live (in editor)' : 'Proposed (by AI)') +
3867
+ '</dt><dd><pre>' + esc(conflictValueText(v[side])) + '</pre></dd></div>';
3868
+ }).join('') + '</dl></div>';
3869
+ }).join('');
3870
+ }
3871
+
3872
+ function renderConflictPanel(p, diff) {
3873
+ var rebase = (diff && diff.rebase) || p.rebase;
3874
+ var selected = p.conflictChoice;
3875
+ var chosen = selected ? '<div class="conflict-choice" role="status"><strong>Chosen: ' +
3876
+ (selected.choice === 'live' ? 'Keep live' : 'Use proposed') + '</strong> \xB7 ' + esc(selected.nodeId) +
3877
+ (canApprove ? ' <button type="button" class="action secondary resolve-conflict-btn" data-id="' + esc(p.id) +
3878
+ '" data-node-id="' + esc(selected.nodeId) + '" data-choice="undo">Undo / change choice</button>' : '') +
3879
+ '<p>This choice changes the proposal only. Nothing is deployed until approval. Undo is available until the next proposal edit or choice.</p></div>' : '';
3880
+ if ((p.aiConflictOverrides || []).length) {
3881
+ chosen += '<div class="warning ai-conflict-override" role="alert"><strong>AI chose its own value over your editor change</strong>' +
3882
+ '<p>Review the fields below before approving this proposal.</p>' +
3883
+ ((diff && diff.aiOverrideValues) || []).map(function (entry) {
3884
+ return '<h4>' + esc(entry.nodeId) + '</h4>' + entry.fields.map(function (v) {
3885
+ return '<div class="conflict-values"><h4>' + esc(v.field || 'Node') + '</h4><dl>' +
3886
+ '<div><dt>Before (live editor)</dt><dd><pre>' + esc(conflictValueText(v.live)) + '</pre></dd></div>' +
3887
+ '<div><dt>After (AI proposed)</dt><dd><pre>' + esc(conflictValueText(v.proposed)) + '</pre></dd></div></dl></div>';
3888
+ }).join('');
3889
+ }).join('') + (!diff ? '<p>Loading before and after values\u2026</p>' : '') + '</div>';
3890
+ }
3891
+ if (!rebase || rebase.state !== 'conflict') return chosen;
3892
+ return chosen + '<div class="proposal-conflicts" role="region" aria-label="Proposal conflicts">' +
3893
+ '<h3>Conflicts</h3><p>Resolve these conflicts before approving.</p>' +
3894
+ (rebase.conflicts || []).map(function (c) {
3895
+ var tabConflict = c.nodeId === proposalFlowId(p) || /^tab_/.test(c.kind || '');
3896
+ return '<div class="proposal-conflict"><strong>' + esc(flowLabelOf(p) || 'Flow') + ' \xB7 ' +
3897
+ esc(c.nodeName || (tabConflict ? 'Flow settings' : 'Unnamed node')) + '</strong>' +
3898
+ ' \xB7 ' + esc(c.nodeType || (tabConflict ? 'tab' : 'unknown type')) + ' \xB7 <code>' + esc(c.nodeId || '') + '</code>' +
3899
+ '<p>' + esc(conflictReason(c)) + '</p>' + renderConflictValues(c, diff) +
3900
+ (canApprove ? (c.choices || []).filter(function (choice) {
3901
+ return choice === 'live' || choice === 'proposed';
3902
+ }).map(function (choice) {
3903
+ return '<div class="conflict-option"><button type="button" class="action secondary resolve-conflict-btn" data-id="' + esc(p.id) +
3904
+ '" data-node-id="' + esc(c.nodeId || '') + '" data-choice="' + esc(choice) + '">' +
3905
+ (choice === 'live' ? 'Keep live' : 'Use proposed') + '</button>' +
3906
+ '<p class="hint">' + (choice === 'live' ? 'Keep the editor value for the conflicting fields.' :
3907
+ 'Replace the conflicting editor fields with the AI values.') + ' Other field changes are preserved.</p></div>';
3908
+ }).join('') : '') + '</div>';
3909
+ }).join('') + (canApprove ? '<button type="button" class="action secondary recheck-btn" data-id="' +
3910
+ esc(p.id) + '">Re-check</button>' : '') + '</div>';
3911
+ }
3912
+
3913
+ async function updateProposalConflict(id, nodeId, choice, button) {
3914
+ proposalActionsInFlight += 1;
3915
+ var label = button && button.textContent;
3916
+ if (button) {
3917
+ button.disabled = true;
3918
+ button.setAttribute('aria-busy', 'true');
3919
+ button.innerHTML = '<span class="spinner" aria-hidden="true"></span> ' + (choice ? 'Saving\u2026' : 'Re-checking\u2026');
3920
+ }
3921
+ try {
3922
+ var result = await api('/proposals/' + encodeURIComponent(id) + (choice ? '/resolve-conflict' : '/recheck'), {
3923
+ method: 'POST', body: JSON.stringify(choice ? { nodeId: nodeId, choice: choice } : {})
3924
+ });
3925
+ delete proposalDiffCache[id];
3926
+ await loadProposals();
3927
+ await loadProposalDiff(id);
3928
+ var current = cachedProposals.filter(function (p) { return p.id === id; })[0];
3929
+ var conflict = current ? proposalIsStale(current) : result && result.proposal && result.proposal.status === 'conflict';
3930
+ showToast(choice === 'undo' ? 'Choice undone. Review the conflict again.' :
3931
+ conflict ? 'Still conflicting. Review the remaining conflicts.' : 'Resolved. Review the updated proposal.', true);
3932
+ notifySidebarPendingRefresh();
3933
+ } catch (e) {
3934
+ showFlash($('proposals-flash'), e.message || String(e), false);
3935
+ } finally {
3936
+ proposalActionsInFlight -= 1;
3937
+ if (button) { button.disabled = false; button.removeAttribute('aria-busy'); button.textContent = label; }
3686
3938
  }
3687
- return missing + warnHtml + toolbar + sections + vsCurrent;
3688
3939
  }
3689
3940
 
3690
3941
  function renderDiffRevMeta(diff) {
@@ -3697,24 +3948,24 @@ a{color:var(--focus)}
3697
3948
  // Full view kept for mode-toggle re-render inside .proposal-changes.
3698
3949
  return renderHumanDiff(diff, mode);
3699
3950
  }
3700
- `}a(Xm,"pageClientScriptPartBDiffView2");function Zm(){return`
3951
+ `}a(Og,"pageClientScriptPartBDiffView2");function $g(){return`
3701
3952
  function updatePendingBadges() {
3702
3953
  var counts = countPendingBuckets();
3703
3954
  var fullText = formatPendingBadgeText(counts.live, counts.stale);
3704
3955
  var badge = $('proposals-pending-badge');
3705
3956
  if (badge) {
3706
- // Tab badge: to-review count only; full wording lives in the tooltip.
3707
- badge.textContent = String(counts.live);
3957
+ // Include conflicts in the open proposal count.
3958
+ badge.textContent = String(counts.pending);
3708
3959
  badge.hidden = counts.pending <= 0;
3709
3960
  badge.title = fullText;
3710
3961
  }
3711
3962
  var filterCount = $('filter-pending-count');
3712
- if (filterCount) filterCount.textContent = fullText;
3963
+ if (filterCount) { filterCount.textContent = String(counts.pending); filterCount.title = fullText; }
3713
3964
  var decidedCount = $('filter-decided-count');
3714
3965
  if (decidedCount) decidedCount.textContent = String(counts.decided);
3715
3966
  var dashPending = $('dash-pending');
3716
3967
  if (dashPending) {
3717
- dashPending.textContent = fullText;
3968
+ dashPending.textContent = String(counts.pending);
3718
3969
  dashPending.title = fullText;
3719
3970
  }
3720
3971
  }
@@ -3752,6 +4003,21 @@ a{color:var(--focus)}
3752
4003
  });
3753
4004
  });
3754
4005
  });
4006
+ list.querySelectorAll('.resolve-conflict-btn, .recheck-btn').forEach(function (btn) {
4007
+ bindOnce(btn, 'click', function () {
4008
+ if (btn.disabled) return;
4009
+ var id = btn.getAttribute('data-id'), nodeId = btn.getAttribute('data-node-id'), choice = btn.getAttribute('data-choice');
4010
+ if (!choice) { updateProposalConflict(id, nodeId, choice, btn); return; }
4011
+ openConfirmDialog({
4012
+ title: choice === 'undo' ? 'Undo this choice?' : choice === 'live' ? 'Keep live values?' : 'Use proposed values?',
4013
+ message: choice === 'undo' ? 'Restore the proposal before the latest choice, then re-check against the current editor.' :
4014
+ (choice === 'live' ? 'The conflicting fields will keep the editor values.' : 'The conflicting fields will use the AI values.') +
4015
+ ' Independent field changes are preserved. This updates the proposal; it does not deploy.',
4016
+ confirmLabel: choice === 'undo' ? 'Undo choice' : 'Confirm choice',
4017
+ onConfirm: function () { updateProposalConflict(id, nodeId, choice, btn); }
4018
+ });
4019
+ });
4020
+ });
3755
4021
  list.querySelectorAll('.approve-btn').forEach(function (btn) {
3756
4022
  bindOnce(btn, 'click', function () {
3757
4023
  if (btn.disabled) return;
@@ -3763,24 +4029,6 @@ a{color:var(--focus)}
3763
4029
  openRejectDialog(btn.getAttribute('data-id'));
3764
4030
  });
3765
4031
  });
3766
- list.querySelectorAll('.dismiss-stale-btn').forEach(function (btn) {
3767
- bindOnce(btn, 'click', function () {
3768
- var id = btn.getAttribute('data-id');
3769
- openConfirmDialog({
3770
- title: 'Dismiss proposal',
3771
- message: 'This closes the out-of-date item from your queue. The flow is unchanged.',
3772
- confirmLabel: 'Dismiss',
3773
- danger: true,
3774
- onConfirm: function () {
3775
- rejectProposal(id, 'Dismissed \u2014 ask the AI to resubmit');
3776
- }
3777
- });
3778
- });
3779
- });
3780
- var dismissAll = list.querySelector('#dismiss-all-stale');
3781
- bindOnce(dismissAll, 'click', function () {
3782
- dismissAllStaleProposals();
3783
- });
3784
4032
  list.querySelectorAll('.deploy-type').forEach(function (sel) {
3785
4033
  bindOnce(sel, 'change', function () {
3786
4034
  var warn = list.querySelector('.full-deploy-warn[data-id="' + sel.getAttribute('data-id') + '"]');
@@ -3813,7 +4061,7 @@ a{color:var(--focus)}
3813
4061
  });
3814
4062
  });
3815
4063
  }
3816
- `}a(Zm,"pageClientScriptPartB2");function Qm(){return`
4064
+ `}a($g,"pageClientScriptPartB2");function Mg(){return`
3817
4065
  function renderDeleteBanner(diff) {
3818
4066
  if (!isLargeDelete(diff)) return '';
3819
4067
  var beforeN = diff.summary.beforeNodeCount || 0;
@@ -3823,23 +4071,15 @@ a{color:var(--focus)}
3823
4071
  }
3824
4072
 
3825
4073
  function renderPendingActions(p, stale) {
3826
- if (!canApprove || p.status !== 'pending') return '';
3827
- // Stale items live in the Out of date group (Dismiss only).
3828
- if (stale) return '';
4074
+ if (!canApprove || (p.status !== 'pending' && p.status !== 'conflict')) return '';
3829
4075
  var cached = proposalDiffCache[p.id];
3830
- var warns = proposalWarnings(p, cached);
3831
- var warnNote = warns.length
3832
- ? renderValidationWarnings(warns, {
3833
- title: 'Approve will deploy with ' + warns.length +
3834
- ' configuration warning' + (warns.length === 1 ? '' : 's') +
3835
- ' \u2014 you can finish setup in the editor afterward'
3836
- })
3837
- : '';
3838
- return warnNote +
3839
- '<p class="muted approve-help">Approve deploys this change to Node-RED. Nothing is live until you approve.</p>' +
4076
+ if (proposalHasNoNetChange(cached)) {
4077
+ return '<button type="button" class="action secondary reject-btn" data-id="' + esc(p.id) + '">Reject to close</button>';
4078
+ }
4079
+ return '<p class="muted approve-help">Approve deploys this change to Node-RED. Nothing is live until you approve.</p>' +
3840
4080
  '<div class="row">' +
3841
4081
  '<button type="button" class="action approve-btn" data-id="' + esc(p.id) +
3842
- '">Approve &amp; deploy</button>' +
4082
+ '"' + (stale ? ' disabled title="Resolve conflicts before approving"' : (!cached ? ' disabled title="Wait for the diff to load"' : '')) + '>Approve &amp; deploy</button>' +
3843
4083
  '<button type="button" class="action secondary reject-btn" data-id="' + esc(p.id) +
3844
4084
  '">Reject</button>' +
3845
4085
  '</div>' +
@@ -3859,43 +4099,7 @@ a{color:var(--focus)}
3859
4099
  '</details>';
3860
4100
  }
3861
4101
 
3862
- function dismissAllStaleProposals() {
3863
- var staleIds = cachedProposals
3864
- .filter(function (p) { return p.status === 'pending' && proposalIsStale(p); })
3865
- .map(function (p) { return p.id; });
3866
- if (!staleIds.length) return;
3867
- var n = staleIds.length;
3868
- openConfirmDialog({
3869
- title: 'Dismiss out-of-date proposals',
3870
- message: 'Dismiss ' + n + ' out-of-date proposal' + (n === 1 ? '' : 's') +
3871
- ' from your queue? The flow is unchanged.',
3872
- confirmLabel: n === 1 ? 'Dismiss' : 'Dismiss all',
3873
- danger: true,
3874
- onConfirm: function () { doDismissAllStaleProposals(staleIds); }
3875
- });
3876
- }
3877
-
3878
- async function doDismissAllStaleProposals(staleIds) {
3879
- var flash = $('proposals-flash');
3880
- var failed = 0;
3881
- for (var i = 0; i < staleIds.length; i += 1) {
3882
- try {
3883
- await rejectProposal(staleIds[i], 'Dismissed \u2014 ask the AI to resubmit', { silent: true });
3884
- } catch (e) {
3885
- failed += 1;
3886
- }
3887
- }
3888
- if (failed) {
3889
- showFlash(flash, 'Dismissed with ' + failed + ' error(s).', false);
3890
- } else {
3891
- showFlash(flash, 'Dismissed ' + staleIds.length + ' out-of-date proposal' +
3892
- (staleIds.length === 1 ? '' : 's') + '.', true);
3893
- showToast('Out-of-date proposals dismissed.', true);
3894
- }
3895
- loadProposals();
3896
- loadLogs();
3897
- }
3898
- `}a(Qm,"pageClientScriptPartB2Actions");function eh(){return`
4102
+ `}a(Mg,"pageClientScriptPartB2Actions");function Dg(){return`
3899
4103
  function renderRequestedVia(p) {
3900
4104
  if (!p.createdBy) return '';
3901
4105
  if (p.createdBy.label) {
@@ -3946,6 +4150,13 @@ a{color:var(--focus)}
3946
4150
  '</span>';
3947
4151
  }
3948
4152
 
4153
+ function renderRebaseChip(p, diff) {
4154
+ var rebase = (diff && diff.rebase) || p.rebase;
4155
+ var count = rebase && rebase.liveChangedCount;
4156
+ return '<span class="rebase-chip"' + (p.status === 'pending' && rebase && rebase.state === 'merged' ? '' : ' hidden') +
4157
+ '>Rebased \xB7 ' + esc(count == null ? 0 : count) + ' live changes</span>';
4158
+ }
4159
+
3949
4160
  function renderProposalCard(p) {
3950
4161
  var cached = proposalDiffCache[p.id];
3951
4162
  var title = titleForProposalCard(p);
@@ -3956,12 +4167,15 @@ a{color:var(--focus)}
3956
4167
  : '';
3957
4168
  var plain = cached ? plainDiffSentence(cached, flowLabel) : '';
3958
4169
  // Show for any large delete \u2014 including stale \u2014 so owners see the risk.
3959
- var deleteBanner = cached ? renderDeleteBanner(cached) : '';
4170
+ var deleteBanner = cached && !proposalHasNoNetChange(cached) ? renderDeleteBanner(cached) : '';
3960
4171
  // Card-level banner even before diff loads (warnings come from list API).
3961
- var cardWarns = proposalWarnings(p, cached);
3962
- var warnBanner = renderRestoredSecrets(p) + (cardWarns.length && !cached
3963
- ? renderValidationWarnings(cardWarns)
3964
- : '');
4172
+ var deployBanner = p.deployPending
4173
+ ? '<div class="proposal-warnings" role="status"><p class="proposal-warnings-title">Deploy outcome unknown</p><p>' +
4174
+ esc(p.deployPending.queueReleased
4175
+ ? 'The approval queue was released. Check the live flow before approving again.'
4176
+ : p.deployPending.reason) + '</p></div>'
4177
+ : '';
4178
+ var warnBanner = deployBanner + renderReviewWarnings(p, cached);
3965
4179
  var stale = proposalIsStale(p);
3966
4180
  var actions = renderPendingActions(p, stale);
3967
4181
  var changes =
@@ -3973,67 +4187,22 @@ a{color:var(--focus)}
3973
4187
  '" data-id="' + esc(p.id) + '" data-status="' + esc(p.status) + '">' +
3974
4188
  '<header class="proposal-card-head">' +
3975
4189
  '<h3 class="proposal-heading" data-title-for="' + esc(p.id) + '">' + esc(title) + '</h3>' +
3976
- '<span class="status-pill ' + esc(p.status) + '">' + esc(p.status) + '</span> ' +
3977
- renderProposalMetaLine(p) +
4190
+ '<span class="status-pill ' + esc(p.status) + '">' + esc(p.status === 'conflict' ? 'Conflict' : p.status) + '</span> ' +
4191
+ renderRebaseChip(p, cached) + renderProposalMetaLine(p) +
3978
4192
  '</header>' +
3979
4193
  (createdBy ? createdBy + '<br/>' : '') +
3980
4194
  '<p class="proposal-plain-summary" data-plain-for="' + esc(p.id) + '">' +
3981
4195
  (plain ? esc(plain) : '<span class="muted">Loading summary\u2026</span>') + '</p>' +
3982
4196
  deleteBanner +
3983
- warnBanner +
4197
+ '<div class="proposal-review-notes">' + warnBanner + '</div>' +
3984
4198
  reason +
4199
+ '<div class="proposal-conflict-host">' + renderConflictPanel(p, cached) + '</div>' +
3985
4200
  changes +
3986
4201
  renderProposalTech(p, cached) +
3987
- actions +
4202
+ '<div class="proposal-actions">' + actions + '</div>' +
3988
4203
  '</article>';
3989
4204
  }
3990
- `}a(eh,"pageClientScriptPartB2Cards");function th(){return`
3991
- function renderStaleItem(p) {
3992
- var cached = proposalDiffCache[p.id];
3993
- var title = titleForProposalCard(p);
3994
- var deleteBanner = cached ? renderDeleteBanner(cached) : '';
3995
- var dismissBtn = canApprove
3996
- ? '<button type="button" class="action secondary dismiss-stale-btn" data-id="' +
3997
- esc(p.id) + '">Dismiss</button>'
3998
- : '';
3999
- return '<div class="stale-item proposal-detail" id="proposal-' + esc(p.id) +
4000
- '" data-id="' + esc(p.id) + '" data-status="pending" data-stale="1">' +
4001
- '<div class="stale-item-row">' +
4002
- '<div class="stale-item-main">' +
4003
- '<strong class="proposal-heading" data-title-for="' + esc(p.id) + '">' + esc(title) +
4004
- '</strong>' +
4005
- '<span class="stale-reason muted">Flow changed since this was submitted</span>' +
4006
- renderProposalMetaLine(p) +
4007
- '</div>' +
4008
- dismissBtn +
4009
- '</div>' +
4010
- deleteBanner +
4011
- '<details class="stale-item-diff">' +
4012
- '<summary>Show changes</summary>' +
4013
- '<div class="proposal-changes proposal-diff" data-diff-for="' + esc(p.id) +
4014
- '" data-changes-for="' + esc(p.id) + '">' +
4015
- (cached ? renderHumanDiff(cached) : '<p class="muted">Loading changes\u2026</p>') +
4016
- '</div>' +
4017
- renderProposalTech(p, cached) +
4018
- '</details></div>';
4019
- }
4020
-
4021
- function renderStaleGroup(items) {
4022
- if (!items.length) return '';
4023
- return '<details class="stale-group">' +
4024
- '<summary>Out of date (' + items.length + ')</summary>' +
4025
- '<p class="muted dismiss-vs-reject">Dismiss closes an out-of-date item from your queue ' +
4026
- '(the AI is asked to resubmit). Reject tells the AI the change was refused \u2014 use Reject ' +
4027
- 'only on proposals that are still current.</p>' +
4028
- (canApprove
4029
- ? '<div class="stale-group-actions">' +
4030
- '<button type="button" class="action secondary" id="dismiss-all-stale">' +
4031
- 'Dismiss all out-of-date</button></div>'
4032
- : '') +
4033
- items.map(renderStaleItem).join('') +
4034
- '</details>';
4035
- }
4036
-
4205
+ `}a(Dg,"pageClientScriptPartB2Cards");function Hg(){return`
4037
4206
  function renderCompactProposal(p) {
4038
4207
  var title = titleForProposalCard(p);
4039
4208
  return '<div class="proposal-card-compact" data-id="' + esc(p.id) +
@@ -4066,8 +4235,8 @@ a{color:var(--focus)}
4066
4235
  }
4067
4236
 
4068
4237
  function countSearchMatchesInFilter(filter) {
4069
- var pending = cachedProposals.filter(function (p) { return p.status === 'pending'; });
4070
- var decided = cachedProposals.filter(function (p) { return p.status !== 'pending'; });
4238
+ var pending = cachedProposals.filter(function (p) { return (p.status === 'pending' || p.status === 'conflict'); });
4239
+ var decided = cachedProposals.filter(function (p) { return (p.status !== 'pending' && p.status !== 'conflict'); });
4071
4240
  if (filter === 'decided') {
4072
4241
  return decided.filter(proposalMatchesSearch).length;
4073
4242
  }
@@ -4105,8 +4274,8 @@ a{color:var(--focus)}
4105
4274
  var list = $('proposals-list');
4106
4275
  if (!list) return;
4107
4276
  var filters = $('proposals-filters');
4108
- var pending = cachedProposals.filter(function (p) { return p.status === 'pending'; });
4109
- var decided = cachedProposals.filter(function (p) { return p.status !== 'pending'; });
4277
+ var pending = cachedProposals.filter(function (p) { return (p.status === 'pending' || p.status === 'conflict'); });
4278
+ var decided = cachedProposals.filter(function (p) { return (p.status !== 'pending' && p.status !== 'conflict'); });
4110
4279
  updatePendingBadges();
4111
4280
  if (filters) filters.hidden = cachedProposals.length === 0;
4112
4281
  if (!cachedProposals.length) {
@@ -4117,13 +4286,8 @@ a{color:var(--focus)}
4117
4286
  return;
4118
4287
  }
4119
4288
  if (proposalsFilter === 'pending') {
4120
- var live = pending.filter(function (p) {
4121
- return !proposalIsStale(p) && proposalMatchesSearch(p);
4122
- });
4123
- var stale = pending.filter(function (p) {
4124
- return proposalIsStale(p) && proposalMatchesSearch(p);
4125
- });
4126
- if (!live.length && !stale.length) {
4289
+ var live = pending.filter(proposalMatchesSearch);
4290
+ if (!live.length) {
4127
4291
  var decidedMatches = countSearchMatchesInFilter('decided');
4128
4292
  list.innerHTML = renderEmptyProposalsState(
4129
4293
  proposalsSearchQuery.trim() ? 'No matching proposals' : 'No pending proposals',
@@ -4135,9 +4299,9 @@ a{color:var(--focus)}
4135
4299
  bindCrossTabHint(list);
4136
4300
  return;
4137
4301
  }
4138
- disambiguatedTitleCache = buildDisambiguatedTitles(live.concat(stale));
4139
- list.innerHTML = live.map(renderProposalCard).join('') + renderStaleGroup(stale);
4140
- live.concat(stale).forEach(function (p) { loadProposalDiff(p.id); });
4302
+ disambiguatedTitleCache = buildDisambiguatedTitles(live);
4303
+ list.innerHTML = live.map(renderProposalCard).join('');
4304
+ live.forEach(function (p) { loadProposalDiff(p.id); });
4141
4305
  } else {
4142
4306
  var matchedDecided = decided.filter(proposalMatchesSearch);
4143
4307
  if (proposalsSearchQuery.trim() && decidedHasMore) {
@@ -4162,7 +4326,7 @@ a{color:var(--focus)}
4162
4326
  bindProposalActions(list);
4163
4327
  focusProposalFromHash();
4164
4328
  }
4165
- `}a(th,"pageClientScriptPartB2CardsList");function nh(){return`
4329
+ `}a(Hg,"pageClientScriptPartB2CardsList");function jg(){return`
4166
4330
  async function refreshFlowLabels() {
4167
4331
  try {
4168
4332
  var data = await api('/flows');
@@ -4187,7 +4351,7 @@ a{color:var(--focus)}
4187
4351
  }
4188
4352
  try {
4189
4353
  await refreshFlowLabels();
4190
- var pendingData = await api('/proposals?status=pending');
4354
+ var pendingData = await api('/proposals?status=pending,conflict');
4191
4355
  // Refresh keeps however many decided cards were already loaded.
4192
4356
  var decidedLimit = Math.min(200, Math.max(DECIDED_PAGE_SIZE, decidedLoaded));
4193
4357
  var decidedData = await api('/proposals?status=' + DECIDED_STATUSES +
@@ -4204,7 +4368,7 @@ a{color:var(--focus)}
4204
4368
  if (id && label) cachedFlowLabels[id] = String(label);
4205
4369
  });
4206
4370
  var sig = JSON.stringify([decidedHasMore, decidedTotal, cachedProposals.map(function (p) {
4207
- return [p.id, p.status, p.updatedAt, p.baseRevStale, p.flowLabel, p.title];
4371
+ return [p.id, p.status, p.updatedAt, p.baseRevChanged, p.flowLabel, p.title];
4208
4372
  })]);
4209
4373
  var unchanged = sameAsLastRender('proposals', sig);
4210
4374
  if (opts.skipRender || (opts.auto && unchanged)) {
@@ -4294,7 +4458,7 @@ a{color:var(--focus)}
4294
4458
  needsRerender = true;
4295
4459
  }
4296
4460
  var p = cachedProposals.filter(function (x) { return x.id === id; })[0];
4297
- if (p && p.status !== 'pending' && proposalsFilter === 'pending') {
4461
+ if (p && (p.status !== 'pending' && p.status !== 'conflict') && proposalsFilter === 'pending') {
4298
4462
  proposalsFilter = 'decided';
4299
4463
  var filtersEl = $('proposals-filters');
4300
4464
  if (filtersEl) {
@@ -4337,7 +4501,7 @@ a{color:var(--focus)}
4337
4501
  replaceLocationHash('#proposals');
4338
4502
  return true;
4339
4503
  }
4340
- `}a(nh,"pageClientScriptPartB2Focus");function rh(){return`
4504
+ `}a(jg,"pageClientScriptPartB2Focus");function Ug(){return`
4341
4505
  function bindDiffModeToggle(host, diff) {
4342
4506
  host.querySelectorAll('[data-diff-mode]').forEach(function (btn) {
4343
4507
  btn.addEventListener('click', function () {
@@ -4352,14 +4516,18 @@ a{color:var(--focus)}
4352
4516
  }
4353
4517
 
4354
4518
  function applyDiffToProposalCard(id, diff) {
4355
- proposalDiffCache[id] = diff;
4356
4519
  var p = cachedProposals.filter(function (x) { return x.id === id; })[0];
4357
4520
  var wasStale = p ? proposalIsStale(p) : false;
4358
- if (p && typeof diff.baseRevStale === 'boolean') {
4359
- p.baseRevStale = diff.baseRevStale;
4521
+ proposalDiffCache[id] = diff;
4522
+ if (p && diff.rebase && (p.status === 'pending' || p.status === 'conflict')) {
4523
+ p.rebase = Object.assign({}, p.rebase || {}, diff.rebase);
4524
+ p.status = diff.rebase.state === 'conflict' ? 'conflict' : 'pending';
4360
4525
  }
4361
- // Newly discovered stale \u2192 re-render into the Out of date group.
4362
- if (p && p.status === 'pending' && !wasStale && diff.baseRevStale) {
4526
+ if (p && typeof diff.baseRevChanged === 'boolean') {
4527
+ p.baseRevChanged = diff.baseRevChanged;
4528
+ }
4529
+ // Move cards between review and conflict groups when the rebase state changes.
4530
+ if (p && (p.status === 'pending' || p.status === 'conflict') && wasStale !== proposalIsStale(p)) {
4363
4531
  renderProposalsList();
4364
4532
  return;
4365
4533
  }
@@ -4381,12 +4549,26 @@ a{color:var(--focus)}
4381
4549
  updatePendingBadges();
4382
4550
  return;
4383
4551
  }
4384
- if (diff.baseRevStale) article.setAttribute('data-stale', '1');
4552
+ if (p) {
4553
+ article.setAttribute('data-status', p.status);
4554
+ var pill = article.querySelector('.status-pill');
4555
+ if (pill) { pill.className = 'status-pill ' + p.status; pill.textContent = p.status === 'conflict' ? 'Conflict' : p.status; }
4556
+ var conflictHost = article.querySelector('.proposal-conflict-host');
4557
+ if (conflictHost) conflictHost.innerHTML = renderConflictPanel(p, diff);
4558
+ var actionHost = article.querySelector('.proposal-actions');
4559
+ if (actionHost) actionHost.innerHTML = renderPendingActions(p, proposalIsStale(p));
4560
+ var notes = article.querySelector('.proposal-review-notes');
4561
+ if (notes && !p.deployPending) notes.innerHTML = renderReviewWarnings(p, diff);
4562
+ var chip = article.querySelector('.rebase-chip');
4563
+ if (chip) chip.outerHTML = renderRebaseChip(p, diff);
4564
+ bindProposalActions(article);
4565
+ }
4566
+ if (p && proposalIsStale(p)) article.setAttribute('data-stale', '1');
4385
4567
  else article.removeAttribute('data-stale');
4386
4568
 
4387
4569
  // Banner for any large delete (stale or live) \u2014 owners must see the risk.
4388
4570
  var existingBanner = article.querySelector('.delete-banner');
4389
- if (isLargeDelete(diff)) {
4571
+ if (!proposalHasNoNetChange(diff) && isLargeDelete(diff)) {
4390
4572
  var beforeN = diff.summary.beforeNodeCount || 0;
4391
4573
  var removedN = diff.summary.removed || 0;
4392
4574
  var html = 'Deletes ' + removedN + ' of ' + beforeN + ' nodes';
@@ -4411,7 +4593,7 @@ a{color:var(--focus)}
4411
4593
 
4412
4594
  // Refresh headings from the live diff; rebuild disambiguation across peers
4413
4595
  // (includes stale cards \u2014 relative "12h ago" alone must not be the only cue).
4414
- var pendingPeers = cachedProposals.filter(function (x) { return x.status === 'pending'; });
4596
+ var pendingPeers = cachedProposals.filter(function (x) { return (x.status === 'pending' || x.status === 'conflict'); });
4415
4597
  disambiguatedTitleCache = buildDisambiguatedTitles(pendingPeers);
4416
4598
  pendingPeers.forEach(function (peer) {
4417
4599
  var el = document.querySelector('[data-title-for="' + peer.id + '"]');
@@ -4445,11 +4627,11 @@ a{color:var(--focus)}
4445
4627
  }
4446
4628
  } catch (e) {
4447
4629
  if (host) {
4448
- host.innerHTML = '<p class="muted">Could not load changes: ' +
4630
+ host.innerHTML = '<p class="muted" role="alert">Could not load changes: ' +
4449
4631
  esc(e.message || String(e)) + '</p>';
4450
4632
  }
4451
4633
  }
4452
- }`}a(rh,"pageClientScriptPartB2Apply");function oh(){return`
4634
+ }`}a(Ug,"pageClientScriptPartB2Apply");function Bg(){return`
4453
4635
 
4454
4636
  function dialogFocusables(dialog) {
4455
4637
  if (!dialog) return [];
@@ -4552,7 +4734,7 @@ a{color:var(--focus)}
4552
4734
  } else if (destructive) {
4553
4735
  heading.textContent = 'Confirm approve & deploy';
4554
4736
  } else if (warns.length) {
4555
- heading.textContent = 'Approve with configuration warnings';
4737
+ heading.textContent = 'Approve with review notes';
4556
4738
  } else {
4557
4739
  heading.textContent = 'Confirm approve & deploy';
4558
4740
  }
@@ -4578,7 +4760,7 @@ a{color:var(--focus)}
4578
4760
  msg.className = 'danger-text';
4579
4761
  msg.hidden = false;
4580
4762
  } else if (warns.length) {
4581
- msg.textContent = 'This proposal has configuration gaps (scaffold). You can approve now and finish setup in the editor afterward.';
4763
+ msg.textContent = 'Review the notes below before deploying. Configuration gaps can be completed in the editor.';
4582
4764
  msg.className = 'muted';
4583
4765
  msg.hidden = false;
4584
4766
  } else {
@@ -4592,9 +4774,7 @@ a{color:var(--focus)}
4592
4774
  if (warnEl) {
4593
4775
  if (warns.length) {
4594
4776
  warnEl.hidden = false;
4595
- warnEl.innerHTML = renderValidationWarnings(warns, {
4596
- title: 'Configuration warnings'
4597
- });
4777
+ warnEl.innerHTML = renderReviewWarnings(p, diff);
4598
4778
  } else {
4599
4779
  warnEl.hidden = true;
4600
4780
  warnEl.innerHTML = '';
@@ -4627,7 +4807,7 @@ a{color:var(--focus)}
4627
4807
  renderProposalsList();
4628
4808
  return;
4629
4809
  }
4630
- if (status && status !== 'pending' && proposalsFilter === 'pending') {
4810
+ if (status && status !== 'pending' && status !== 'conflict' && proposalsFilter === 'pending') {
4631
4811
  var decidedMatches = countSearchMatchesInFilter('decided');
4632
4812
  if (decidedMatches > 0) {
4633
4813
  setProposalsFilter('decided');
@@ -4668,17 +4848,17 @@ a{color:var(--focus)}
4668
4848
  var expectedRev = pBefore && pBefore.expectedRev;
4669
4849
  var peersBefore = expectedRev
4670
4850
  ? cachedProposals.filter(function (x) {
4671
- return x.id !== id && x.status === 'pending' && x.expectedRev === expectedRev;
4851
+ return x.id !== id && (x.status === 'pending' || x.status === 'conflict') && x.expectedRev === expectedRev;
4672
4852
  }).length
4673
4853
  : 0;
4674
4854
  try {
4675
4855
  var result = await api('/proposals/' + encodeURIComponent(id) + '/approve', {
4676
4856
  method: 'POST',
4677
- body: JSON.stringify({ deploymentType: deploymentType })
4857
+ body: JSON.stringify({ deploymentType: deploymentType, mergedHash: proposalDiffCache[id] && proposalDiffCache[id].rebase && proposalDiffCache[id].rebase.mergedHash })
4678
4858
  });
4679
4859
  var viewOpts = { title: toastTitle, href: proposalViewHref(id), linkLabel: 'View' };
4680
4860
  if (result.status === 'rejected') {
4681
- var rejectMsg = friendlyReason(result.reason) || 'Proposal rejected (revision conflict).';
4861
+ var rejectMsg = result.reason || 'Proposal rejected.';
4682
4862
  showFlash(flash, rejectMsg, false);
4683
4863
  showToast(rejectMsg, false, viewOpts);
4684
4864
  } else if (result.status === 'failed') {
@@ -4692,11 +4872,11 @@ a{color:var(--focus)}
4692
4872
  showToast('Approved & deployed.', true, viewOpts);
4693
4873
  }
4694
4874
  delete proposalDiffCache[id];
4695
- // Clear diff cache for peers that shared the rev \u2014 they are now out of date.
4875
+ // Re-check peer diffs against the newly deployed flow.
4696
4876
  cachedProposals.forEach(function (peer) {
4697
- if (peer.id !== id && peer.status === 'pending' && peer.expectedRev === expectedRev) {
4877
+ if (peer.id !== id && (peer.status === 'pending' || peer.status === 'conflict') && peer.expectedRev === expectedRev) {
4698
4878
  delete proposalDiffCache[peer.id];
4699
- peer.baseRevStale = true;
4879
+ peer.baseRevChanged = true;
4700
4880
  }
4701
4881
  });
4702
4882
  await loadProposals({ skipRender: true });
@@ -4704,8 +4884,8 @@ a{color:var(--focus)}
4704
4884
  notifySidebarPendingRefresh();
4705
4885
  if (result.status !== 'rejected' && result.status !== 'failed' && peersBefore > 0) {
4706
4886
  var note = peersBefore === 1
4707
- ? '1 other proposal is now out of date'
4708
- : peersBefore + ' other proposals are now out of date';
4887
+ ? '1 other proposal was rechecked'
4888
+ : peersBefore + ' other proposals were rechecked';
4709
4889
  var priorOk = flash && !flash.hidden ? String(flash.textContent || '').trim() : '';
4710
4890
  showFlash(flash, (priorOk ? priorOk + ' ' : '') + note + '.', true);
4711
4891
  showToast(note + '.', true);
@@ -4713,7 +4893,18 @@ a{color:var(--focus)}
4713
4893
  settleSearchAfterAction(id, 'approved');
4714
4894
  loadLogs();
4715
4895
  } catch (e) {
4716
- var errMsg = friendlyReason(e.message) || String(e);
4896
+ var code = e.body && e.body.code;
4897
+ var errMsg = e.message || String(e);
4898
+ if (e.status === 409 && (code === 'rebase_ack_required' || code === 'proposal_conflict')) {
4899
+ errMsg = code === 'rebase_ack_required'
4900
+ ? 'This proposal was rebased onto newer editor changes since you viewed it \u2014 review the updated diff'
4901
+ : 'Resolve the conflicts before approving this proposal.';
4902
+ delete proposalDiffCache[id];
4903
+ await loadProposals();
4904
+ await loadProposalDiff(id);
4905
+ } else if (e.status === 409 && code === 'rebase_race') {
4906
+ errMsg = 'Flows kept changing, try again';
4907
+ }
4717
4908
  showFlash(flash, errMsg, false);
4718
4909
  showToast(errMsg, false, toastTitle ? { title: toastTitle } : undefined);
4719
4910
  }
@@ -4724,10 +4915,16 @@ a{color:var(--focus)}
4724
4915
  var deploymentType = select ? select.value : defaultDeploy;
4725
4916
  var diff = proposalDiffCache[id];
4726
4917
  var p = cachedProposals.filter(function (x) { return x.id === id; })[0];
4727
- if (diff && diff.baseRevStale) {
4728
- showToast('This proposal is out of date. Ask the AI to resubmit.', false);
4918
+ if ((p && p.status === 'conflict') || (diff && diff.rebase && diff.rebase.state === 'conflict')) {
4919
+ showToast('Resolve the conflicts before approving this proposal.', false);
4729
4920
  return;
4730
4921
  }
4922
+ if (!diff) {
4923
+ await loadProposalDiff(id);
4924
+ showToast('Review the loaded diff, then click Approve again.', false);
4925
+ return;
4926
+ }
4927
+ if (proposalHasNoNetChange(diff)) { showToast('Nothing changes in the live flow. Reject this proposal to close it.', true); return; }
4731
4928
  if (needsApproveConfirm(diff, p)) {
4732
4929
  openDeleteConfirmDialog(id, deploymentType, diff);
4733
4930
  return;
@@ -4781,7 +4978,7 @@ a{color:var(--focus)}
4781
4978
  throw e;
4782
4979
  }
4783
4980
  }
4784
- `}a(oh,"pageClientScriptPartC");function sh(){return`
4981
+ `}a(Bg,"pageClientScriptPartC");function qg(){return`
4785
4982
  var cachedLogs = [];
4786
4983
 
4787
4984
  function renderLogsTable(logs) {
@@ -4863,7 +5060,7 @@ a{color:var(--focus)}
4863
5060
  showFlash(flash, e.message || String(e), false);
4864
5061
  }
4865
5062
  }
4866
- `}a(sh,"pageClientScriptPartCLogs");function ih(){return`
5063
+ `}a(qg,"pageClientScriptPartCLogs");function zg(){return`
4867
5064
  function applyStatus(s) {
4868
5065
  aiOn = !!s.aiFeaturesEnabled;
4869
5066
  canManage = !!(s.capabilities && s.capabilities.manageLinks);
@@ -4926,8 +5123,8 @@ a{color:var(--focus)}
4926
5123
  }
4927
5124
 
4928
5125
  $('proposals-hint').textContent = canApprove
4929
- ? 'Review changes from your AI assistant. Each card shows a short id (first 8 hex after prop-; click to copy the full id). Approve & deploy makes them live; Reject leaves the flow unchanged; Dismiss closes out-of-date items.'
4930
- : 'Approve, reject, and dismiss require full admin permission (*). You can still review proposals. Short ids are the first 8 hex characters after prop- (click to copy the full id).';
5126
+ ? 'Review your AI assistant\u2019s changes. Approve & deploy applies them to Node-RED. Reject closes a proposal without changing the flow.'
5127
+ : 'Review proposals here. A full Node-RED admin can approve or reject them.';
4931
5128
 
4932
5129
  renderLinksManage();
4933
5130
  }
@@ -4936,10 +5133,14 @@ a{color:var(--focus)}
4936
5133
  var nav = $('main-nav');
4937
5134
  if (!nav) return;
4938
5135
  function updateStuck() {
4939
- var top = nav.getBoundingClientRect().top;
5136
+ var rect = nav.getBoundingClientRect();
5137
+ if (rect.height > 0) document.documentElement.style.setProperty('--nav-height', (rect.height + 20) + 'px');
5138
+ var top = rect.top;
4940
5139
  nav.classList.toggle('is-stuck', top <= 0);
4941
5140
  }
4942
5141
  window.addEventListener('scroll', updateStuck, { passive: true });
5142
+ window.addEventListener('resize', updateStuck);
5143
+ if (typeof ResizeObserver !== 'undefined') new ResizeObserver(updateStuck).observe(nav);
4943
5144
  updateStuck();
4944
5145
  }
4945
5146
 
@@ -5024,7 +5225,7 @@ a{color:var(--focus)}
5024
5225
  });
5025
5226
  }
5026
5227
  }
5027
- `}a(ih,"pageClientScriptPartD");function ah(){return`
5228
+ `}a(zg,"pageClientScriptPartD");function Wg(){return`
5028
5229
  async function boot() {
5029
5230
  accessToken = readEditorToken();
5030
5231
  if (!accessToken) {
@@ -5128,11 +5329,11 @@ a{color:var(--focus)}
5128
5329
  loadTabData(tab);
5129
5330
  });
5130
5331
 
5131
- ${dh()}
5132
- ${lh()}
5332
+ ${Gg()}
5333
+ ${Kg()}
5133
5334
  boot();
5134
5335
  })();
5135
- `}a(ah,"pageClientScriptPartDBoot");function dh(){return`
5336
+ `}a(Wg,"pageClientScriptPartDBoot");function Gg(){return`
5136
5337
  /*
5137
5338
  * Jump links to the editor (#flow/<id>, #node/<id>). A click first asks an
5138
5339
  * already-open editor tab (BroadcastChannel 'node-red-ai-access', answered by
@@ -5197,7 +5398,7 @@ ${lh()}
5197
5398
  ev.stopPropagation();
5198
5399
  revealInEditor(a.getAttribute('data-reveal-kind'), a.getAttribute('data-reveal-id'), a.getAttribute('href'));
5199
5400
  });
5200
- `}a(dh,"pageClientEditorJumpScript");function lh(){return`
5401
+ `}a(Gg,"pageClientEditorJumpScript");function Kg(){return`
5201
5402
  /*
5202
5403
  * Auto-refresh when a tab left open becomes visible / focused again.
5203
5404
  * Throttled; skipped while a dialog (e.g. the one-time token modal) is open or
@@ -5247,7 +5448,7 @@ ${lh()}
5247
5448
  });
5248
5449
  window.addEventListener('focus', autoRefresh);
5249
5450
 
5250
- `}a(lh,"pageClientAutoRefreshScript");function Hd(){return(Um()+Bm()+qm()+zm()+Wm()+Gm()+Km()+Jm()+Vm()+Ym()+Xm()+Zm()+Qm()+eh()+th()+nh()+rh()+oh()+sh()+ih()+jd()+ah()).trim()}a(Hd,"pageClientScript");function ch(){return`
5451
+ `}a(Kg,"pageClientAutoRefreshScript");function wc(){return(Tg()+Rg()+Pg()+_g()+Cg()+Eg()+Ig()+Lg()+Ng()+Fg()+Og()+$g()+Mg()+Dg()+Hg()+jg()+Ug()+Bg()+qg()+zg()+yc()+Wg()).trim()}a(wc,"pageClientScript");function Jg(){return`
5251
5452
  :root {
5252
5453
  color-scheme: light;
5253
5454
  --bg: #eef2f5;
@@ -5275,10 +5476,12 @@ body {
5275
5476
  linear-gradient(165deg, #e8eef3 0%, var(--bg) 55%, #e4ebe6 100%);
5276
5477
  color: var(--ink);
5277
5478
  min-height: 100vh;
5479
+ font-size: 17px;
5480
+ line-height: 1.55;
5278
5481
  }
5279
5482
  main {
5280
5483
  width: 100%;
5281
- max-width: min(1400px, 95vw);
5484
+ max-width: min(1640px, 97vw);
5282
5485
  margin: 0 auto;
5283
5486
  padding: 2.25rem clamp(0.85rem, 2.5vw, 1.75rem) 3.5rem;
5284
5487
  }
@@ -5304,26 +5507,52 @@ h1 {
5304
5507
  }
5305
5508
  .how-it-works li { margin: 0.2rem 0; }
5306
5509
  .how-it-works strong { color: var(--accent); }
5510
+ /* Signed-out card: same look as the OAuth consent / sign-in page (light only). */
5307
5511
  .auth-gate {
5308
- background: var(--card);
5309
- border: 1px solid var(--line);
5310
- border-radius: 10px;
5311
- padding: 1.25rem 1.35rem;
5312
- margin-bottom: 1rem;
5313
- }
5314
- .auth-gate h2 { margin: 0 0 0.4rem; font-size: 1.2rem; }
5315
- .auth-gate a.action {
5316
- display: inline-block;
5317
- text-decoration: none;
5318
- text-align: center;
5319
- }
5320
- `}a(ch,"pageStylesPartA");function uh(){return`
5512
+ --sg-bg: #f3f4f6; --sg-card: #fff; --sg-text: #111827; --sg-muted: #4b5563; --sg-line: #e5e7eb;
5513
+ --sg-soft: #f9fafb; --sg-brand: #0a5c45; --sg-brand-2: #084b38; --sg-focus: #2563eb;
5514
+ color-scheme: light;
5515
+ position: fixed; inset: 0; z-index: 1000; overflow: auto;
5516
+ display: flex; align-items: center; justify-content: center; padding: 24px 16px;
5517
+ background: var(--sg-bg); color: var(--sg-text);
5518
+ font: 15px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
5519
+ }
5520
+ .auth-gate[hidden] { display: none !important; }
5521
+ body.signed-out { overflow: hidden; }
5522
+ .auth-gate * { box-sizing: border-box; }
5523
+ .auth-gate .sg-card { width: 100%; max-width: 440px; margin: 0; background: var(--sg-card); border: 1px solid var(--sg-line); border-radius: 16px; box-shadow: 0 10px 30px rgba(0,0,0,.08); overflow: hidden; }
5524
+ .auth-gate .sg-head { display: flex; align-items: center; gap: 12px; padding: 18px 22px; border-bottom: 1px solid var(--sg-line); }
5525
+ .auth-gate .logo { width: 40px; height: 40px; flex: none; }
5526
+ .auth-gate .sg-brand b { display: block; font-size: 15px; }
5527
+ .auth-gate .sg-brand span { font-size: 13px; color: var(--sg-muted); }
5528
+ .auth-gate .sg-body { padding: 20px 22px 22px; }
5529
+ .auth-gate h1 { font-size: 19px; line-height: 1.3; margin: 0 0 6px; color: var(--sg-text); }
5530
+ .auth-gate .sg-sub { margin: 0 0 14px; color: var(--sg-muted); font-size: 14px; }
5531
+ .auth-gate .sg-status { font-size: 14px; color: var(--sg-muted); margin: 0 0 14px; }
5532
+ .auth-gate .sg-status.error { color: var(--danger); }
5533
+ .auth-gate .sg-actions { display: flex; gap: 10px; }
5534
+ .auth-gate button { font: inherit; border-radius: 10px; padding: 10px 16px; cursor: pointer; border: 1px solid var(--sg-line); background: var(--sg-card); color: var(--sg-text); }
5535
+ .auth-gate button:focus-visible, .auth-gate input:focus-visible, .auth-gate a:focus-visible { outline: 3px solid var(--sg-focus); outline-offset: 2px; }
5536
+ .auth-gate button[disabled] { opacity: .6; cursor: default; }
5537
+ .auth-gate .sg-primary { flex: 1; background: var(--sg-brand); border-color: var(--sg-brand); color: #fff; font-weight: 600; }
5538
+ .auth-gate .sg-primary:hover:not([disabled]) { background: var(--sg-brand-2); }
5539
+ .auth-gate .sg-secondary { flex: none; }
5540
+ .auth-gate .sg-login { display: grid; gap: 10px; margin: 0 0 4px; }
5541
+ .auth-gate .sg-login label { display: grid; gap: 4px; font-size: 13px; color: var(--sg-muted); }
5542
+ .auth-gate .sg-login input { font: inherit; padding: 9px 11px; border-radius: 10px; border: 1px solid var(--sg-line); background: var(--sg-soft); color: var(--sg-text); }
5543
+ .auth-gate .sg-editor-option { display: inline-block; margin: 10px 0 14px; font-size: 14px; }
5544
+ .auth-gate .sg-editor-option[hidden] { display: none; }
5545
+ .auth-gate .sg-foot { padding: 12px 22px; border-top: 1px solid var(--sg-line); font-size: 14px; font-weight: 500; color: var(--sg-text); background: var(--sg-soft); }
5546
+ .auth-gate a { color: var(--sg-focus); }
5547
+ @media (max-width: 420px) { .auth-gate .sg-actions { flex-direction: column-reverse; } .auth-gate .sg-secondary { flex: 1; } }
5548
+ `}a(Jg,"pageStylesPartA");function Vg(){return`
5321
5549
  nav.tabs {
5322
5550
  position: sticky;
5323
5551
  top: 0;
5324
5552
  z-index: 40;
5325
5553
  display: flex;
5326
- flex-wrap: wrap;
5554
+ flex-wrap: nowrap;
5555
+ overflow-x: auto;
5327
5556
  gap: 0.4rem;
5328
5557
  margin: 0 0 1.1rem;
5329
5558
  padding: 0.55rem 0;
@@ -5337,6 +5566,7 @@ nav.tabs.is-stuck {
5337
5566
  background: var(--soft);
5338
5567
  }
5339
5568
  nav.tabs button {
5569
+ flex: 0 0 auto;
5340
5570
  border: 1px solid var(--line);
5341
5571
  background: var(--card);
5342
5572
  color: var(--ink);
@@ -5359,7 +5589,7 @@ nav.tabs button[aria-selected="true"] {
5359
5589
  border-radius: 999px;
5360
5590
  background: var(--card);
5361
5591
  color: var(--accent);
5362
- font-size: 0.72rem;
5592
+ font-size: 0.85rem;
5363
5593
  font-weight: 700;
5364
5594
  line-height: 1.25;
5365
5595
  vertical-align: middle;
@@ -5411,7 +5641,7 @@ dd {
5411
5641
  padding: 0.22rem 0.55rem;
5412
5642
  border-radius: 6px;
5413
5643
  cursor: help;
5414
- background: var`}a(uh,"pageStylesPartA2");function ph(){return`(--accent-soft);
5644
+ background: var`}a(Vg,"pageStylesPartA2");function Yg(){return`(--accent-soft);
5415
5645
  color: var(--accent);
5416
5646
  font-size: 0.82rem;
5417
5647
  font-weight: 650;
@@ -5448,7 +5678,7 @@ dd {
5448
5678
  display: inline-block;
5449
5679
  padding: 0.12rem 0.45rem;
5450
5680
  border-radius: 4px;
5451
- font-size: 0.78rem;
5681
+ font-size: 0.88rem;
5452
5682
  font-weight: 650;
5453
5683
  white-space: nowrap;
5454
5684
  background: var(--soft);
@@ -5602,7 +5832,7 @@ dialog.token-modal { width: min(720px, 94vw); max-width: min(720px, 94vw); max-h
5602
5832
  color: var(--muted);
5603
5833
  font-size: 0.92rem;
5604
5834
  }
5605
- `}a(ph,"pageStylesPartB");function fh(){return`
5835
+ `}a(Yg,"pageStylesPartB");function Xg(){return`
5606
5836
  .logs-toolbar { margin-bottom: 0.65rem; }
5607
5837
  table.audit-table {
5608
5838
  table-layout: fixed;
@@ -5641,7 +5871,7 @@ table.audit-table {
5641
5871
  display: inline-block;
5642
5872
  padding: 0.12rem 0.45rem;
5643
5873
  border-radius: 5px;
5644
- font-size: 0.78rem;
5874
+ font-size: 0.88rem;
5645
5875
  font-weight: 650;
5646
5876
  letter-spacing: 0.01em;
5647
5877
  white-space: nowrap;
@@ -5668,7 +5898,7 @@ table.audit-table {
5668
5898
  word-break: break-word;
5669
5899
  max-height: 10rem;
5670
5900
  overflow: auto;
5671
- font-size: 0.78rem;
5901
+ font-size: 0.88rem;
5672
5902
  background: var(--soft);
5673
5903
  padding: 0.4rem 0.5rem;
5674
5904
  border-radius: 6px;
@@ -5678,7 +5908,7 @@ table.audit-table {
5678
5908
  cursor: help;
5679
5909
  border-bottom: 1px dotted var(--line);
5680
5910
  }
5681
- `}a(fh,"pageStylesPartC");function mh(){return`
5911
+ `}a(Xg,"pageStylesPartC");function Zg(){return`
5682
5912
  .proposal-diff {
5683
5913
  margin: 0.65rem 0;
5684
5914
  padding: 0.55rem 0.65rem;
@@ -5716,6 +5946,10 @@ table.audit-table {
5716
5946
  color: #fff;
5717
5947
  }
5718
5948
  .diff-summary { margin: 0 0 0.35rem; }
5949
+ .status-pill.conflict { background: #fff1df; color: #9a4700; border: 1px solid #edbd80; }
5950
+ .rebase-chip { font-size: 0.8rem; color: #245d46; background: #eaf5ee; border-radius: 4px; padding: 0.2rem 0.4rem; }
5951
+ .proposal-conflicts { background: #fff8ee; border: 1px solid #edbd80; border-radius: 6px; padding: 1rem; margin: 0.75rem 0; overflow-wrap: anywhere; }
5952
+ .proposal-conflict { border-bottom: 1px solid #edbd80; padding-bottom: 0.75rem; margin-bottom: 0.75rem; }
5719
5953
  .stale-banner {
5720
5954
  margin: 0 0 0.55rem;
5721
5955
  padding: 0.45rem 0.6rem;
@@ -5751,15 +5985,15 @@ table.audit-table {
5751
5985
  }
5752
5986
  .prop-change-scalar .scalar-side .side-label {
5753
5987
  display: block;
5754
- font-size: 0.75rem;
5988
+ font-size: 0.88rem;
5755
5989
  color: var(--muted);
5756
5990
  margin-bottom: 0.15rem;
5757
5991
  }
5758
- `}a(mh,"pageStylesPartD");function hh(){return`
5992
+ `}a(Zg,"pageStylesPartD");function Qg(){return`
5759
5993
  .diff-block {
5760
5994
  margin-top: 0.25rem;
5761
5995
  font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
5762
- font-size: 0.78rem;
5996
+ font-size: 0.88rem;
5763
5997
  line-height: 1.4;
5764
5998
  overflow-x: hidden;
5765
5999
  overflow-y: auto;
@@ -5815,7 +6049,7 @@ table.audit-table {
5815
6049
  overflow-y: auto;
5816
6050
  max-height: 18rem;
5817
6051
  font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
5818
- font-size: 0.78rem;
6052
+ font-size: 0.88rem;
5819
6053
  line-height: 1.4;
5820
6054
  background: var(--soft);
5821
6055
  }
@@ -5828,7 +6062,7 @@ table.audit-table {
5828
6062
  z-index: 1;
5829
6063
  background: #eef2f5;
5830
6064
  color: var(--muted);
5831
- font-size: 0.72rem;
6065
+ font-size: 0.85rem;
5832
6066
  font-weight: 650;
5833
6067
  letter-spacing: 0.02em;
5834
6068
  text-transform: uppercase;
@@ -5837,9 +6071,9 @@ table.audit-table {
5837
6071
  }
5838
6072
  .diff-before { color: var(--danger); word-break: break-word; }
5839
6073
  .diff-after { color: var(--ok); word-break: break-word; }
5840
- `}a(hh,"pageStylesPartE");function gh(){return`
6074
+ `}a(Qg,"pageStylesPartE");function ey(){return`
5841
6075
  .proposal-detail {
5842
- scroll-margin-top: 4.5rem;
6076
+ scroll-margin-top: var(--nav-height, 6rem);
5843
6077
  }
5844
6078
  .proposal-detail.proposal-highlight {
5845
6079
  outline: 2px solid var(--accent);
@@ -5931,7 +6165,7 @@ table.audit-table {
5931
6165
  overflow: auto;
5932
6166
  max-height: 14rem;
5933
6167
  font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
5934
- font-size: 0.78rem;
6168
+ font-size: 0.88rem;
5935
6169
  line-height: 1.4;
5936
6170
  background: var(--card);
5937
6171
  border: 1px solid var(--line);
@@ -5963,7 +6197,7 @@ table.audit-table {
5963
6197
  .audit-table .col-action { width: 5.5rem; min-width: 5.5rem; }
5964
6198
  .audit-table .col-actor { width: 7rem; min-width: 6rem; }
5965
6199
  }
5966
- `}a(gh,"pageStylesPartF");function yh(){return`
6200
+ `}a(ey,"pageStylesPartF");function ty(){return`
5967
6201
  .dash-grid {
5968
6202
  display: grid;
5969
6203
  grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
@@ -5991,7 +6225,8 @@ table.audit-table {
5991
6225
  }
5992
6226
  .filter-row {
5993
6227
  display: flex;
5994
- flex-wrap: wrap;
6228
+ flex-wrap: nowrap;
6229
+ overflow-x: auto;
5995
6230
  gap: 0.4rem;
5996
6231
  margin: 0 0 0.75rem;
5997
6232
  align-items: center;
@@ -6195,7 +6430,7 @@ button.id-copy:focus {
6195
6430
  background: var(--code-bg, var(--soft));
6196
6431
  border-radius: 4px;
6197
6432
  }
6198
- `}a(yh,"pageStylesPartG");function wh(){return`
6433
+ `}a(ty,"pageStylesPartG");function ny(){return`
6199
6434
  .stale-group {
6200
6435
  margin: 1rem 0 0;
6201
6436
  border: 1px solid var(--warn-border);
@@ -6341,7 +6576,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6341
6576
  display: inline-block;
6342
6577
  padding: 0.1rem 0.45rem;
6343
6578
  border-radius: 5px;
6344
- font-size: 0.78rem;
6579
+ font-size: 0.88rem;
6345
6580
  font-weight: 650;
6346
6581
  text-transform: capitalize;
6347
6582
  }
@@ -6359,7 +6594,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6359
6594
  margin-left: auto;
6360
6595
  }
6361
6596
  .debug-toggle input { width: auto; }
6362
- `}a(wh,"pageStylesPartH");function bh(){return`
6597
+ `}a(ny,"pageStylesPartH");function ry(){return`
6363
6598
  .card {
6364
6599
  background: var(--card);
6365
6600
  border: 1px solid var(--line);
@@ -6372,12 +6607,26 @@ dialog h3 { margin: 0 0 0.4rem; }
6372
6607
  .card-head h3 { margin: 0; }
6373
6608
  .settings-grid {
6374
6609
  display: grid;
6375
- grid-template-columns: repeat(auto-fit, minmax(min(100%, 420px), 1fr));
6610
+ grid-template-columns: repeat(auto-fit, minmax(min(100%, 540px), 1fr));
6376
6611
  gap: 1rem;
6377
6612
  align-items: start;
6378
6613
  margin-bottom: 1rem;
6379
6614
  }
6380
- .settings-grid .card { margin: 0; }
6615
+ .settings-grid .card { margin: 0; min-width: 0; }
6616
+ #secret-redaction-card { grid-column: 1 / -1; }
6617
+ section.panel { scroll-margin-top: var(--nav-height, 6rem); }
6618
+ .proposal-conflict, .conflict-choice { padding: 1rem; margin: 0.75rem 0; border: 1px solid var(--warn-border); border-radius: 8px; background: var(--warn-bg); }
6619
+ .conflict-option { margin: 0.8rem 0; }
6620
+ .conflict-values dl { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 0.75rem; }
6621
+ .conflict-values dl > div { min-width: 0; }
6622
+ .conflict-values dd { margin: 0; }
6623
+ .conflict-values pre { max-height: 20rem; overflow: auto; white-space: pre-wrap; overflow-wrap: anywhere; font-size: 0.92rem; }
6624
+ .review-warnings .proposal-warnings { border: 0; padding: 0; background: transparent; }
6625
+ .no-net-change { padding: 1rem; border: 1px solid var(--line); border-radius: 8px; }
6626
+ .spinner { display: inline-block; width: 1em; height: 1em; border: 2px solid currentColor; border-right-color: transparent; border-radius: 50%; animation: recheck-spin .7s linear infinite; }
6627
+ @keyframes recheck-spin { to { transform: rotate(360deg); } }
6628
+ @media (max-width: 850px) { .conflict-values dl { grid-template-columns: 1fr; } }
6629
+ @media (prefers-reduced-motion: reduce) { .spinner { animation: none; } }
6381
6630
  .form-row { margin: 0 0 0.9rem; }
6382
6631
  .form-row:last-child { margin-bottom: 0; }
6383
6632
  .form-row > label, .form-row > .label { display: block; font-weight: 600; margin: 0 0 0.3rem; }
@@ -6426,10 +6675,11 @@ dialog h3 { margin: 0 0 0.4rem; }
6426
6675
  .conflict-list li { margin: 0.25rem 0; }
6427
6676
  @media (max-width: 640px) {
6428
6677
  nav.tabs { overflow-x: auto; flex-wrap: nowrap; }
6429
- nav.tabs button { flex: 0 0 auto; }
6678
+ nav.tabs button {
6679
+ flex: 0 0 auto; flex: 0 0 auto; }
6430
6680
  .field-line { flex-wrap: wrap; }
6431
6681
  }
6432
- `}a(bh,"pageStylesDashboard");function Ud(){return(ch()+uh()+ph()+fh()+mh()+hh()+gh()+yh()+wh()+bh()).trim()}a(Ud,"pageStyles");function vh(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;")}a(vh,"escapeHtml");function kh(){return`
6682
+ `}a(ry,"pageStylesDashboard");function bc(){return(Jg()+Vg()+Yg()+Xg()+Zg()+Qg()+ey()+ty()+ny()+ry()).trim()}a(bc,"pageStyles");function oy(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;")}a(oy,"escapeHtml");function sy(){return`
6433
6683
  <header class="page-header">
6434
6684
  <h1>Node-RED AI Access</h1>
6435
6685
  <p class="lede">
@@ -6443,12 +6693,25 @@ dialog h3 { margin: 0 0 0.4rem; }
6443
6693
  </header>
6444
6694
  <div id="boot-flash" class="flash" hidden></div>
6445
6695
  <div id="auth-gate" class="auth-gate" hidden>
6446
- <h2>Please sign in to Node-RED first</h2>
6447
- <p>Open the editor, sign in, then return here to manage AI Access.</p>
6448
- <p class="row">
6449
- <a class="action" id="auth-gate-editor" href="/">Open Node-RED editor</a>
6450
- <a class="action secondary" id="auth-gate-return" href="/ai-access">Return to AI Access</a>
6451
- </p>
6696
+ <div class="sg-card" role="dialog" aria-modal="true" aria-labelledby="auth-gate-title">
6697
+ <div class="sg-head">${Ui}<div class="sg-brand"><b>Node-RED AI Access</b><span>Dashboard</span></div></div>
6698
+ <div class="sg-body">
6699
+ <h1 id="auth-gate-title">Sign in to Node-RED</h1>
6700
+ <p class="sg-sub">AI Access uses your Node-RED editor account. Signing in here also signs in the editor in this browser.</p>
6701
+ <p id="auth-gate-status" class="sg-status" role="status" aria-live="polite">Checking how Node-RED signs you in\u2026</p>
6702
+ <form id="auth-gate-login" method="post" class="sg-login" hidden autocomplete="on">
6703
+ <label>Username<input id="auth-gate-username" name="username" autocomplete="username" required></label>
6704
+ <label>Password<input id="auth-gate-password" name="password" type="password" autocomplete="current-password" required></label>
6705
+ <div class="sg-actions"><button id="auth-gate-submit" class="sg-primary" type="submit">Sign in</button></div>
6706
+ </form>
6707
+ <a id="auth-gate-editor-link" class="sg-editor-option" target="node-red-editor" rel="noopener" hidden>or sign in via the editor</a>
6708
+ <div class="sg-actions">
6709
+ <button id="auth-gate-editor" class="sg-primary" type="button" hidden>Sign in with the Node-RED editor</button>
6710
+ <button id="auth-gate-retry" class="sg-secondary" type="button" hidden>Retry</button>
6711
+ </div>
6712
+ </div>
6713
+ <div class="sg-foot">You only need to sign in once \u2014 the dashboard continues automatically.</div>
6714
+ </div>
6452
6715
  </div>
6453
6716
  <div id="toast-host" class="toast-host" aria-live="polite"></div>
6454
6717
  <nav class="tabs" aria-label="Admin views" hidden id="main-nav">
@@ -6460,7 +6723,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6460
6723
  Proposals <span class="tab-badge" id="proposals-pending-badge" hidden>0</span>
6461
6724
  </button>
6462
6725
  <button type="button" data-tab="logs" aria-selected="false">Audit log</button>
6463
- </nav>`}a(kh,"renderMcpPageHeader");function Ah(){return`
6726
+ </nav>`}a(sy,"renderMcpPageHeader");function iy(){return`
6464
6727
  <section class="panel active" id="section-status" aria-label="Overview" hidden>
6465
6728
  <h2>Overview</h2>
6466
6729
  <div id="status-flash" class="flash" hidden></div>
@@ -6504,7 +6767,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6504
6767
  <div class="row" style="margin-top:1rem">
6505
6768
  <button type="button" class="action secondary" id="refresh-status">Refresh</button>
6506
6769
  </div>
6507
- </section>`}a(Ah,"renderMcpPageStatusSection");function Sh(){return`
6770
+ </section>`}a(iy,"renderMcpPageStatusSection");function ay(){return`
6508
6771
  <section class="panel" id="section-links" aria-label="Access tokens" hidden>
6509
6772
  <h2>Access tokens</h2>
6510
6773
  <p class="muted" id="links-intro">
@@ -6515,7 +6778,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6515
6778
  <div id="links-flash" class="flash" hidden></div>
6516
6779
  <div id="links-manage"></div>
6517
6780
  <div id="links-list"></div>
6518
- </section>`}a(Sh,"renderMcpPageLinksSection");function be(e){return`<span class="lock-badge" id="lock-${e}" hidden>Locked by settings.js</span>`}a(be,"lockBadge");function xh(){return`
6781
+ </section>`}a(ay,"renderMcpPageLinksSection");function be(e){return`<span class="lock-badge" id="lock-${e}" hidden>Locked by settings.js</span>`}a(be,"lockBadge");function dy(){return`
6519
6782
  <section class="panel" id="section-settings" aria-label="Settings" hidden>
6520
6783
  <h2>Settings</h2>
6521
6784
  <p class="muted">Changes save automatically and apply immediately (no restart).</p>
@@ -6552,10 +6815,18 @@ dialog h3 { margin: 0 0 0.4rem; }
6552
6815
  <label for="set-ttl">Default access token expiry</label>
6553
6816
  <div class="field-line">
6554
6817
  <input type="number" id="set-ttl" min="1" step="1" class="num" />
6555
- <span class="unit">hours</span>
6818
+ <span class="unit">hours (minimum 1)</span>
6556
6819
  ${be("ttl")}
6557
6820
  </div>
6558
6821
  </div>
6822
+ <div class="form-row">
6823
+ <label for="set-proposal-ttl">Proposal expiry (days)</label>
6824
+ <div class="field-line">
6825
+ <input type="number" id="set-proposal-ttl" min="1" max="365" step="1" value="7" class="num" />
6826
+ ${be("proposal-ttl")}
6827
+ </div>
6828
+ <p class="hint">Open proposals expire after this many days from submission (1\u2013365, default 7).</p>
6829
+ </div>
6559
6830
  <div class="form-row">
6560
6831
  <label for="set-max-links">Maximum active tokens</label>
6561
6832
  <div class="field-line">
@@ -6642,7 +6913,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6642
6913
  <p class="hint" id="set-trust-proxy-inline"></p>
6643
6914
  </div>
6644
6915
  </div>
6645
- </section>`}a(xh,"renderMcpPageSettingsSection");function Th(){return`
6916
+ </section>`}a(dy,"renderMcpPageSettingsSection");function ly(){return`
6646
6917
  <section class="panel" id="section-oauth" aria-label="OAuth" hidden>
6647
6918
  <h2>MCP OAuth</h2>
6648
6919
  <p class="muted">MCP clients (ChatGPT, Claude, \u2026) can request read or propose access through OAuth.</p>
@@ -6717,7 +6988,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6717
6988
  <button type="button" class="action secondary" id="oauth-refresh">Refresh</button></div>
6718
6989
  <div id="oauth-clients" aria-live="polite"><p class="muted">Loading\u2026</p></div>
6719
6990
  </div>
6720
- </section>`}a(Th,"renderMcpPageOAuthSection");function Rh(){return`
6991
+ </section>`}a(ly,"renderMcpPageOAuthSection");function cy(){return`
6721
6992
  <section class="panel" id="section-proposals" aria-label="Proposals" hidden>
6722
6993
  <h2>Proposals</h2>
6723
6994
  <p id="proposals-hint" class="muted"></p>
@@ -6730,12 +7001,12 @@ dialog h3 { margin: 0 0 0.4rem; }
6730
7001
  </button>
6731
7002
  <input type="search" id="proposals-search" class="proposals-search"
6732
7003
  placeholder="Search title or id (e.g. efd159aa)\u2026"
6733
- aria-label="Search proposals by title or short id (first 8 hex after prop-)"
7004
+ aria-label="Search proposals by title or id"
6734
7005
  autocomplete="off" />
6735
7006
  </div>
6736
7007
  <div id="proposals-flash" class="flash" hidden></div>
6737
7008
  <div id="proposals-list"><p class="muted">Loading\u2026</p></div>
6738
- </section>`}a(Rh,"renderMcpPageProposalsSection");function _h(){return`
7009
+ </section>`}a(cy,"renderMcpPageProposalsSection");function uy(){return`
6739
7010
  <section class="panel" id="section-logs" aria-label="Audit log" hidden>
6740
7011
  <h2>Audit log</h2>
6741
7012
  <div id="logs-flash" class="flash" hidden></div>
@@ -6751,7 +7022,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6751
7022
  </label>
6752
7023
  </div>
6753
7024
  <div id="logs-list"><p class="muted">Loading\u2026</p></div>
6754
- </section>`}a(_h,"renderMcpPageLogsSection");function Ph(){return`
7025
+ </section>`}a(uy,"renderMcpPageLogsSection");function py(){return`
6755
7026
  <dialog id="reject-dialog">
6756
7027
  <form method="dialog" id="reject-form">
6757
7028
  <h3>Reject proposal</h3>
@@ -6793,7 +7064,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6793
7064
  <button type="submit" value="confirm" class="action danger" id="confirm-action-btn">Confirm</button>
6794
7065
  </div>
6795
7066
  </form>
6796
- </dialog>`}a(Ph,"renderMcpPageDialogs");function Bd(e){let t={apiBase:e.apiBase,mcpRpc:e.mcpRpc??"/mcp/rpc",dashboardPath:e.dashboardPath??"/ai-access",editorRoot:e.editorRoot,authTokenKey:e.authTokenKey};return`<!DOCTYPE html>
7067
+ </dialog>`}a(py,"renderMcpPageDialogs");function vc(e){let t={apiBase:e.apiBase,mcpRpc:e.mcpRpc??"/mcp/rpc",dashboardPath:e.dashboardPath??"/ai-access",editorRoot:e.editorRoot,authTokenKey:e.authTokenKey};return`<!DOCTYPE html>
6797
7068
  <html lang="en">
6798
7069
  <head>
6799
7070
  <meta charset="utf-8" />
@@ -6801,23 +7072,23 @@ dialog h3 { margin: 0 0 0.4rem; }
6801
7072
  <meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" />
6802
7073
  <meta http-equiv="Pragma" content="no-cache" />
6803
7074
  <title>Node-RED AI Access</title>
6804
- <style>${Ud()}</style>
7075
+ <style>${bc()}</style>
6805
7076
  </head>
6806
7077
  <body>
6807
7078
  <main
6808
7079
  id="mcp-root"
6809
- data-config="${vh(JSON.stringify(t))}"
6810
- >${kh()}
6811
- ${Ah()}
6812
- ${xh()}
6813
- ${Sh()}
6814
- ${Th()}
6815
- ${Rh()}
6816
- ${_h()}
6817
- ${Ph()}
7080
+ data-config="${oy(JSON.stringify(t))}"
7081
+ >${sy()}
7082
+ ${iy()}
7083
+ ${dy()}
7084
+ ${ay()}
7085
+ ${ly()}
7086
+ ${cy()}
7087
+ ${uy()}
7088
+ ${py()}
6818
7089
  </main>
6819
- <script>${Hd()}</script>
7090
+ <script>${wc()}</script>
6820
7091
  </body>
6821
- </html>`}a(Bd,"renderMcpPage");Re();xe();Ze();G();pt();J();De();tt();Re();nt();function Nt(e,t,n,r={}){e.setHeader("X-Content-Type-Options","nosniff"),e.setHeader("Content-Type","application/json; charset=utf-8");let o={error:je[n]??"Request failed",code:n,...r},s=de(n,"rest");s&&typeof o.hint!="string"&&(o.hint=s),e.status(t).json(o)}a(Nt,"sendSafeJsonError");function qd(e){let t={kind:"admin"};return typeof e.user?.username=="string"&&(t.username=e.user.username),t}a(qd,"adminActor");function zd(e,t,n,r,o){return async(s,i)=>{if(!s.accessLink){i.setHeader("WWW-Authenticate",X),i.status(401).json({error:me,code:Ie});return}try{let d=q(s,r),l=ie(s.query),c=await rt(e,t,s.accessLink,s.body??{},n,d,o),u=V(e,c.expectedRev,{flowId:c.payload.flowId}),p=Oe(e,e.get(c.id),s.accessLink.id),f=c.warnings;i.status(201).json({...he(c,{...d,include:l}),mine:!0,...$e(p),...Z(u),...f&&f.length>0?{warnings:f}:{}})}catch(d){let l=M(d,"rest");if(l){i.status(l.statusCode).json(l.body);return}D(i,d);return}}}a(zd,"createProposalHandler");function Wd(e,t,n){return async(r,o)=>{let s,i;try{let T=await t.getFlows();s=T.rev,i=Rr(T.flows)}catch{}let d={...q(r,n),...i?{liveFlowLabels:i}:{}},l=ie(r.query),c;try{c=Mr(r.query)}catch(T){D(o,T);return}let u=s?V(e,s):0,p=r.accessLink;if(p){let T=Zt(e,p,d,c),C=T.proposals.map(L=>{let N=ge(e,p,L,{...d,include:l},{includeConflicts:!1});return N.status!=="pending"||s===void 0?N:{...N,baseRevStale:N.expectedRev!==s}});o.json({proposals:C,...kt({...T,returned:T.proposals.length}),...Z(u)});return}let f={};c.flowId&&(f.flowId=c.flowId);let m=Ti(e.list(f).filter(T=>ns(T.status,c.status)),{...d,include:l}),h=c.offset??0,k=(c.limit!==void 0?m.slice(h,h+c.limit):h>0?m.slice(h):m).map(T=>T.status!=="pending"||s===void 0?T:{...T,baseRevStale:T.expectedRev!==s});o.json({proposals:k,...kt({total:m.length,limit:c.limit??null,offset:h,returned:k.length}),...Z(u)})}}a(Wd,"listProposalsHandler");function Gd(e,t,n){return async(r,o)=>{let s=r.params.id;if(typeof s!="string"||s.length===0){o.status(400).json({error:"Missing proposal id"});return}let i=q(r,t),d=ie(r.query),l;if(n)try{l=(await n.getFlows()).rev}catch{}try{if(r.accessLink){let c=ze(e,r.accessLink,s,i);o.json(ge(e,r.accessLink,c,{...i,include:d},{...l?{liveRev:l}:{}}));return}l&&V(e,l),o.json(he(e.get(s),{...i,include:d}))}catch(c){if(c instanceof se){Nt(o,404,c.code);return}let u=M(c,"rest");if(u){o.status(u.statusCode).json(u.body);return}D(o,c);return}}}a(Gd,"getProposalHandler");function Kd(e,t){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){r.status(400).json({error:"Missing proposal id"});return}let s=n.body??{},i={};if(s.deploymentType!==void 0){if(s.deploymentType!=="nodes"&&s.deploymentType!=="flows"&&s.deploymentType!=="full"){r.status(400).json({error:"deploymentType must be 'nodes', 'flows', or 'full'"});return}i.deploymentType=s.deploymentType}try{let d=await e.approve(o,qd(n),i);r.json(ae(d,q(n,t)))}catch(d){if(d instanceof se||d instanceof ne||d instanceof In||d instanceof Y){Nt(r,d.statusCode,d.code);return}D(r,d);return}}}a(Kd,"approveProposalHandler");function Jd(e,t){return(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){r.status(400).json({error:"Missing proposal id"});return}let s=n.body??{},i=typeof s.reason=="string"?s.reason:"Rejected by admin";try{let d=ae(e.reject(o,qd(n),i),q(n,t));r.json(d)}catch(d){if(d instanceof se||d instanceof ne||d instanceof Y){Nt(r,d.statusCode,d.code);return}D(r,d);return}}}a(Jd,"rejectProposalHandler");function Vd(e,t,n){return(r,o)=>{if(!r.accessLink){o.setHeader("WWW-Authenticate",X),o.status(401).json({error:me,code:Ie});return}let s=r.params.id;if(typeof s!="string"||s.length===0){o.status(400).json({error:"Missing proposal id",code:"missing_proposal_id"});return}let i=r.body??{},d=q(r,n),l=ie(r.query);try{let c=Qt(e,t,r.accessLink,s,i.reason,d);o.json(ge(e,r.accessLink,c,{...d,include:l}))}catch(c){let u=M(c,"rest");if(u){Nt(o,u.statusCode,u.body.code);return}Nt(o,500,"internal_error");return}}}a(Vd,"withdrawProposalHandler");function Yd(e,t){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){r.status(400).json({error:"Missing proposal id"});return}try{let s=n.accessLink?ze(e,n.accessLink,o):e.get(o),i=sn(n.query.format);if(!i.ok){r.status(400).json({error:"Invalid diff format",code:"invalid_diff_format",hint:"Valid formats: summary, full (default summary)."});return}let d=an(await un(s,Me(n,t)),i.format);r.json(d)}catch(s){if(s instanceof se){Nt(r,404,s.code);return}if(s instanceof W||s instanceof Se||s instanceof Ne){Nt(r,s.statusCode,s.code);return}let i=M(s,"rest");if(i){r.status(i.statusCode).json(i.body);return}D(r,s);return}}}a(Yd,"getProposalDiffHandler");nt();xe();xe();Ar();function Eh(e){return e.endsWith("/")?e.slice(0,-1):e}a(Eh,"trimTrailingSlash");var mo=class{static{a(this,"HttpAdapter")}kind="http";baseUrl;accessToken;fetchImpl;constructor(t){this.baseUrl=Eh(t.baseUrl),this.accessToken=t.accessToken,this.fetchImpl=t.fetch??globalThis.fetch.bind(globalThis)}headers(t){let n=new Headers(t);return n.set("Accept","application/json"),n.set("Node-RED-API-Version","v2"),this.accessToken&&n.set("Authorization",`Bearer ${this.accessToken}`),n}async request(t,n={}){return await this.fetchImpl(`${this.baseUrl}${t}`,{...n,headers:this.headers(n.headers)})}async readJson(t,n){let r=await t.text();if(!t.ok){let o=r;try{let s=JSON.parse(r);o=s.message??s.code??r}catch{}throw t.status===404?new W(n):t.status===409?new K("unknown","unknown",`Admin API ${n}`):t.status===401||t.status===403?new Ne(hr):new Error(`Admin API ${n} failed (${t.status}): ${o}`)}if(!r)return null;try{return JSON.parse(r)}catch{throw new Error(`Admin API ${n} returned non-JSON`)}}async getFlows(){let t=await this.request("/flows"),n=await this.readJson(t,"GET /flows");return Nn(n,"GET /flows")}async getFlow(t){let n=encodeURIComponent(t),[r,o]=await Promise.all([this.request("/flows"),this.request(`/flow/${n}`)]);if(o.status===404)throw new W(t);let s=await this.readJson(r,"GET /flows"),i=await this.readJson(o,`GET /flow/${t}`);return{rev:Nn(s,"GET /flows").rev,flow:qt(t,i)}}async updateFlow(t){let n=t.deploymentType??"nodes",r=await this.getFlows();if(r.rev!==t.expectedRev)throw new K(t.expectedRev,r.rev,"updateFlow");let o=kr(r.flows,t.flowId,t.flow),s=await this.request("/flows",{method:"POST",headers:{"Content-Type":"application/json","Node-RED-Deployment-Type":n},body:JSON.stringify({flows:o,rev:t.expectedRev})});if(s.status===409){let d=r.rev;try{let l=await s.clone().json();typeof l.current_rev=="string"&&(d=l.current_rev)}catch{}throw new K(t.expectedRev,d,"updateFlow")}let i=await this.readJson(s,"POST /flows");if(!i||typeof i!="object"||typeof i.rev!="string")throw new Error("Admin API POST /flows did not return a rev");return{flowId:t.flowId,rev:i.rev}}};xe();Ar();function Ch(e){let t=e.runtime?.flows;if(!t?.getFlows||!t.getFlow||!t.setFlows)throw new Error("RED.runtime.flows is missing getFlows, getFlow, or setFlows");return t}a(Ch,"requireFlowsApi");function Lh(e){if(typeof e=="string")return e;if(typeof e.rev=="string"&&e.rev.length>0)return e.rev;throw new Error("RED.runtime.flows.setFlows did not return a rev")}a(Lh,"resolveRev");var ho=class{static{a(this,"RedAdapter")}kind="red";flows;constructor(t){this.flows=Ch(t)}async getFlows(){let t=await this.flows.getFlows({});return Nn(t,"RED.runtime.flows.getFlows()")}async getFlow(t){let n=await this.getFlows(),r;try{r=await this.flows.getFlow({id:t})}catch(o){throw Ih(o)?new W(t):o}return{rev:n.rev,flow:qt(t,r)}}async updateFlow(t){let n=t.deploymentType??"nodes",r=await this.getFlows();if(r.rev!==t.expectedRev)throw new K(t.expectedRev,r.rev,"updateFlow");let o=kr(r.flows,t.flowId,t.flow),s=await this.flows.setFlows({flows:{flows:o,rev:t.expectedRev},deploymentType:n});return{flowId:t.flowId,rev:Lh(s)}}};function Xd(e){let t=e?.runtime?.flows;return!!(t?.getFlows&&t.getFlow&&t.setFlows)}a(Xd,"canUseRedRuntime");function Ih(e){if(!e||typeof e!="object")return!1;let t=e;if((typeof t.status=="number"?t.status:t.statusCode)===404||t.code==="not_found"||t.code==="flow_not_found")return!0;let r=typeof t.message=="string"?t.message.toLowerCase():"";return r.includes("not found")||r.includes("unknown flow")}a(Ih,"isRuntimeFlowNotFound");var Zd=require("module");function Qd(e){if(!e||typeof e!="object")return!1;let t=e;return typeof t.getFlows=="function"&&typeof t.getFlow=="function"&&typeof t.setFlows=="function"}a(Qd,"isFlowsApi");function Nh(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,Zd.createRequire)(e)}a(Nh,"createPluginRequire");function Fh(e){try{let t=e("@node-red/runtime/lib/settings");return typeof t.available=="function"&&t.available()===!0}catch{return!1}}a(Fh,"isNodeRedRuntimeInitialized");function Oh(){try{let e=Nh();if(!Fh(e))return null;let t=e("@node-red/runtime");return Qd(t.flows)?t.flows:null}catch{return null}}a(Oh,"tryRequireInitializedRuntimeFlows");function el(e){if(e?.runtime&&Qd(e.runtime.flows))return e;let t=Oh();return t?{runtime:{flows:t}}:null}a(el,"resolveRuntimeHost");function tl(e={}){let t=el(e.RED??null);if(t&&Xd(t))return new ho(t);let n=e.adminAuthEnabled===!0;if(e.http?.baseUrl){if(n&&!e.http.accessToken)throw new Se("In-process Node-RED flow runtime is unavailable and HTTP Admin API fallback has no credentials while adminAuth is enabled. Ensure the plugin runs inside Node-RED so @node-red/runtime.flows can be used.");return new mo(e.http)}throw new Se("No usable Node-RED flow adapter: in-process flows API is unavailable and no HTTP Admin API baseUrl was provided")}a(tl,"createFlowAdapter");xe();var yo=require("fs"),nl=require("path");yr();G();var $h=/^[a-z][a-z0-9._-]{0,63}$/;function Mh(e){if(e.kind==="system")return{kind:"system"};if(e.kind==="access-link"){let n={kind:"access-link",linkId:String(e.linkId).replace(/\0/g,"").slice(0,200)};return typeof e.label=="string"&&(n.label=e.label.replace(/\0/g,"").slice(0,200)),n}let t={kind:"admin"};return typeof e.username=="string"&&(t.username=e.username.replace(/\0/g,"").slice(0,200)),t}a(Mh,"sanitizeActor");var go=class{constructor(t,n={}){this.paths=t;this.filePath=(0,nl.join)(t.logs,"audit.jsonl"),this.now=n.now??(()=>Date.now()),this.maxAppendBytes=n.maxAppendBytes??Ms}static{a(this,"AuditLog")}filePath;now;maxAppendBytes;seq=0;append(t,n,r){let o=$h.test(t)?t:"unknown";this.seq+=1;let s={id:`log-${this.now()}-${this.seq}`,at:new Date(this.now()).toISOString(),action:o,actor:Mh(n)};r!==void 0&&(s.details=ni(r));let i=Je(s,this.maxAppendBytes,{kind:"audit"});return js(this.paths.root,this.filePath,i),s}list(t=100){if(!(0,yo.existsSync)(this.filePath))return[];let n=(0,yo.readFileSync)(this.filePath,"utf8").split(`
6822
- `).map(o=>o.trim()).filter(Boolean),r=[];for(let o of n)try{r.push(JSON.parse(o))}catch{}return r.slice(-Math.max(1,t)).reverse()}};Ze();dr();ce();wt();xe();Ze();tt();Re();var Dh="Flow revision conflict: the expectedRev is stale. Ask the AI to re-read flows and resubmit a new proposal.";function rl(e){let t=ae(e),n=Pr(e)||void 0;return{proposalId:e.id,title:t.title,displayName:t.displayName,flowId:e.payload.flowId,...n?{flowLabel:n}:{}}}a(rl,"auditProposalMeta");var wo=class{constructor(t,n,r,o){this.proposals=t;this.audit=n;this.adapter=r;this.getDeploymentType=o}static{a(this,"ProposalService")}async approve(t,n,r={}){let o=this.proposals.beginApprove(t),s=rl(o),i=r.deploymentType??this.getDeploymentType();try{let d=o.type;if(d!=="replace_flow"){let l=this.proposals.markFailed(t,n,`Unsupported proposal type '${d}'`);return this.audit.append("proposal.failed",n,{...s,reason:l.reason}),l}try{let l=await this.adapter.updateFlow({flowId:o.payload.flowId,flow:o.payload.flow,expectedRev:o.expectedRev,deploymentType:i}),c=this.proposals.markApproved(t,n,l.rev);return this.audit.append("proposal.approved",n,{...s,resultRev:l.rev,flowId:o.payload.flowId}),Pi(this.proposals,o.payload.flowId,t,l.rev),c}catch(l){if(l instanceof K){let p=this.proposals.markRejected(t,n,Dh);return this.audit.append("proposal.rejected",n,{...s,reason:p.reason,code:l.code,expectedRev:l.expectedRev,currentRev:l.currentRev}),p}let c=l instanceof Error?l.message:String(l),u=this.proposals.markFailed(t,n,c);return this.audit.append("proposal.failed",n,{...s,reason:c}),u}}finally{this.proposals.releaseLock(t)}}reject(t,n,r){let o=this.proposals.get(t);if(o.status!=="pending")throw new ne(t,o.status);let s=rl(o),i=this.proposals.markRejected(t,n,r.trim()||"Rejected by admin");return this.audit.append("proposal.rejected",n,{...s,reason:i.reason}),i}};ue();function jh(e){let t=a(()=>Promise.reject(new Ne(e)),"fail");return{kind:"http",getFlows:t,getFlow:t,updateFlow:t}}a(jh,"misconfiguredAdapter");function ol(e){let t=e.settings,n=typeof t.uiPort=="number"?t.uiPort:1880,r=t.httpAdminRoot??"/",o=!r||r==="/"?"":r.replace(/\/$/,"");return`http://127.0.0.1:${n}${o}`}a(ol,"localAdminBaseUrl");function bo(e,t={}){let n=nr(e.settings.userDir);Hs(n);let r=t.settingsStore??new Mt(n,e.settings.aiAccess,u=>e.log?.warn?.(u));t.bindSettings!==!1&&cr(r),pr(r.getEffective().publicBaseUrl);let o=t.linkStore??uo(e);o.configureDefaults({defaultTtlSeconds:r.getEffective().defaultLinkTtlSeconds,maxActiveLinks:r.getEffective().maxActiveLinks});let s=new wr(n),i=new go(n),d;if(t.flowAdapter)d=t.flowAdapter;else try{d=tl({RED:e,http:{baseUrl:ol(e)},adminAuthEnabled:z(e.settings)})}catch(u){let p=u instanceof Se?u.message:hr;e.log?.warn?.(`AI Access flow adapter unavailable: ${p}`),d=jh(p)}let l;if(t.nodeTypesAdapter)l=t.nodeTypesAdapter;else if(t.flowAdapter)l=$o();else try{l=mi({baseUrl:ol(e)})}catch(u){let p=u instanceof Error?u.message:String(u);e.log?.warn?.(`AI Access node-types adapter unavailable: ${p}`),l=$o([])}let c=new wo(s,i,d,()=>ve(e.settings).defaultDeploymentType);return{linkStore:o,proposalStore:s,auditLog:i,flowAdapter:d,nodeTypesAdapter:l,proposalService:c,settingsStore:r,...t.catalogueFetcher?{catalogueFetcher:t.catalogueFetcher}:{},...t.catalogueUrl?{catalogueUrl:t.catalogueUrl}:{}}}a(bo,"createPluginServices");G();ce();Le();rr();var sl=yn(require("dns"),1),il=yn(require("http"),1),al=yn(require("https"),1),Zn=require("net");ce();function Hh(e,t){return e===10||e===172&&t>=16&&t<=31||e===192&&t===168}a(Hh,"isPrivateV4");function Uh(e,t){return e===169&&t===254}a(Uh,"isLinkLocalV4");function Bh(e,t,n){return e===0||e===100&&t>=64&&t<=127||e>=224||e===192&&t===0&&(n===0||n===2)||e===198&&(t===18||t===19||t===51&&n===100)||e===203&&t===0&&n===113}a(Bh,"isSpecialUseV4");function qh(e){let[t=0,n=0,r=0]=e.split(".").map(Number);return t===127?"loopback":Hh(t,n)||Uh(t,n)||Bh(t,n,r)||e==="168.63.129.16"?"blocked":"public"}a(qh,"classifyIpv4");function zh(e){let t=e.split(":"),n=t.indexOf(""),r=[];for(let o of t){if(!o)continue;if(o.includes(".")){let i=o.split(".").map(Number);if(i.length!==4||i.some(d=>!Number.isInteger(d)||d<0||d>255))return;r.push((i[0]??0)<<8|(i[1]??0)),r.push((i[2]??0)<<8|(i[3]??0));continue}let s=Number.parseInt(o,16);if(!/^[\da-f]{1,4}$/i.test(o)||!Number.isInteger(s))return;r.push(s)}if(n>=0){let o=8-r.length;if(o<1)return;let s=t.slice(0,n).filter(Boolean).length;r.splice(s,0,...Array(o).fill(0))}else if(r.length!==8)return;return r.length===8?r:void 0}a(zh,"expandIpv6");function Wh(e){let t=zh(e);if(!t)return"blocked";if(t.slice(0,7).every(s=>s===0)&&t[7]===1)return"loopback";let n=t[0]??0,r=t[1]??0;return n===0||n<8192||n>16383||n===8193&&r<=511||n===8193&&r===3512||n===16383&&r<=4095||n===8194?"blocked":"public"}a(Wh,"classifyIpv6");function Qn(e){let t=ft(e);return t?(0,Zn.isIP)(t)===4?qh(t):Wh(t):"blocked"}a(Qn,"classifyPublicUrlAddress");function Gh(e){let t=e.toLowerCase().replace(/\.$/,"");return["metadata","metadata.google.internal","metadata.google.com","metadata.goog","instance-data","instance-data.ec2.internal","metadata.azure.internal","metadata.aws.internal"].some(n=>t===n||t.endsWith(`.${n}`))}a(Gh,"isMetadataHostname");async function dl(e,t,n){let r=Dt(e);if(!r)throw new Error("publicBaseUrl is required for Check");let o=new URL(r),s=o.hostname.replace(/^\[|\]$/g,"");if(Gh(s))throw new Error("Public URL Check cannot target metadata hosts");let i=ft(s),l=(typeof e=="string"?/^https?:\/\/(\[[^\]]+\]|[^/?#:]+)/i.exec(e.trim())?.[1]:void 0)?.replace(/^\[|\]$/g,"");if(i&&l&&l.toLowerCase()!==i.toLowerCase())throw new Error("Public URL Check requires canonical IP address notation");let c=s.toLowerCase().replace(/\.$/,"")==="localhost"||i!==void 0&&Qn(i)==="loopback",u=Number(o.port||(o.protocol==="https:"?443:80));if(c&&u!==t)throw new Error("Public URL Check may only target this Node-RED instance's port for loopback URLs");let p=a(g=>{let k=Qn(g);if(k==="blocked"||k==="loopback"&&!c||c&&k!=="loopback")throw new Error("Public URL Check cannot target private or non-public addresses")},"validateAddress");if(i)return p(i),{origin:r,address:i,family:(0,Zn.isIP)(i)};let f=await new Promise((g,k)=>{let T=a(()=>k(n.reason instanceof Error?n.reason:new Error(String(n.reason))),"abort");n.throwIfAborted(),n.addEventListener("abort",T,{once:!0}),sl.default.lookup(s,{all:!0},(C,L)=>{n.removeEventListener("abort",T),C?k(C):g(L)})});if(n.throwIfAborted(),!f.length)throw new Error("Public URL Check hostname has no addresses");for(let g of f)p(g.address);let m=[];for(let g of f){let k=ft(g.address);if(!k)throw new Error("Public URL Check returned an invalid address");m.some(T=>T.address===k)||m.push({address:k,family:(0,Zn.isIP)(k)})}c&&m.sort((g,k)=>g.family-k.family);let h=m[0];if(!h)throw new Error("Public URL Check hostname has no addresses");return{origin:r,...h,addresses:m}}a(dl,"preparePublicUrlCheck");function Kh(e,t,n){let r=e.hostname.replace(/^\[|\]$/g,"");return new Promise((o,s)=>{let d=(e.protocol==="https:"?al.default:il.default).request(e,{method:"GET",agent:!1,signal:n,headers:{Host:e.host},...(0,Zn.isIP)(r)?{}:{servername:r},lookup:a((l,c,u)=>{c.all?u(null,[{address:t.address,family:t.family}]):u(null,t.address,t.family)},"lookup")},l=>{o(l.statusCode??0),l.destroy()});d.on("error",s),d.end()})}a(Kh,"requestPinned");async function ll(e,t,n){let r=new URL(e.origin+t),o=e.addresses?.length?e.addresses:[{address:e.address,family:e.family}],s;for(let i of o){n.throwIfAborted();try{return await Kh(r,i,n)}catch(d){if(n.aborted)throw d;s=d}}throw s instanceof Error?s:new Error("Public URL Check request failed")}a(ll,"requestPublicUrlCheck");Sn();ue();function Jh(e){return Rn(e.settings.httpAdminRoot)}a(Jh,"mcpPageUrl");function Vh(e){return typeof e=="boolean"||typeof e=="number"||typeof e=="string"?e:Array.isArray(e)?e.join(", "):typeof e=="function"?"custom function":!1}a(Vh,"normalizeTrustProxy");function pl(e){if(!e)return"Set a Public URL before enabling OAuth.";let t=new URL(e),n=ft(t.hostname),r=t.hostname.toLowerCase().replace(/\.$/,"")==="localhost"||n==="::1"||n?.startsWith("127.");return t.protocol==="http:"&&!r?"OAuth Public URL uses HTTP; HTTPS is recommended.":null}a(pl,"oauthPublicUrlWarning");function fl(e,t,n){let{sources:r,...o}=t.getEffective(),{permission:s}=ur(e,n);return{ok:!0,adminAuthEnabled:z(e.settings),canEdit:s==="*",permission:s,mcpPageUrl:Jh(e),apiBase:fe(e.settings.httpAdminRoot),settings:o,oauthPublicUrlWarning:pl(o.publicBaseUrl),sources:r,disk:t.getDisk(),trustProxy:Vh(n.app.get("trust proxy")),precedence:["settings.js","disk","default"],defaults:{secretNamePatterns:[...ct]}}}a(fl,"settingsResponse");function ml(e,t){return(n,r)=>{r.json(fl(e,t,n))}}a(ml,"getSettingsHandler");function cl(e,t,n){let r=e[t];if(r!==void 0){if(typeof r!="boolean")throw new Error(`${t} must be a boolean`);n[t]=r}}a(cl,"parseBooleanField");function ul(e,t,n){let r=e[t];if(r!==void 0){if(typeof r!="number"||!Number.isFinite(r)||r<=0)throw new Error(`${t} must be a positive number`);n[t]=Math.floor(r)}}a(ul,"parsePositiveIntField");function Yh(e,t,n){let r=e[t];if(r===void 0)return;let o=t==="rateLimitWindowMs"?100:1,s=t==="rateLimitWindowMs"?36e5:1e6;if(typeof r!="number"||!Number.isInteger(r)||r<o||r>s)throw new Error(`${t} must be an integer between ${o} and ${s}`);n[t]=r}a(Yh,"parseRateLimitField");function Xh(e,t){if(e.corsAllowedOrigins===void 0)return;if(!Array.isArray(e.corsAllowedOrigins))throw new Error("corsAllowedOrigins must be a list of HTTP(S) origins");let n=e.corsAllowedOrigins.map(r=>vn(r));if(n.some(r=>r===void 0))throw new Error("corsAllowedOrigins must contain only HTTP(S) origins without paths, credentials, queries, or fragments");t.corsAllowedOrigins=[...new Set(n)]}a(Xh,"parseCorsField");function Zh(e,t,n){let r=e.secretNamePatterns;if(r!==void 0){if(r===null){n.push("secretNamePatterns");return}if(!Array.isArray(r)||r.some(o=>typeof o!="string"))throw new Error("secretNamePatterns must be a list of strings (or null to reset)");if(r.length>kn)throw new Error(`secretNamePatterns allows at most ${kn} entries`);for(let o of r)if(!or.test(o.trim()))throw new Error('secretNamePatterns entries must be 2-40 letters, digits, "_" or "-" (e.g. API_KEY)');t.secretNamePatterns=An(r)??[]}}a(Zh,"parseSecretPatternsField");function hl(e,t,n){return(r,o)=>{let s=r.body;if(!s||typeof s!="object"||Array.isArray(s)){o.status(400).json({error:"Request body must be a settings object",code:"invalid_body"});return}let i={...s};for(let[c,u]of Object.entries(t.getEffective().sources))u==="settings.js"&&delete i[c];t.getEffective().sources.oauth==="settings.js"&&delete i.oauth;let d={},l=[];try{if(i.oauth!==void 0){if(!i.oauth||typeof i.oauth!="object"||Array.isArray(i.oauth))throw new Error("oauth must be an object");let c=i.oauth,u={};if(c.enabled!==void 0){if(typeof c.enabled!="boolean")throw new Error("oauth.enabled must be a boolean");u.enabled=c.enabled}for(let f of["accessTokenTtlSec","refreshIdleTtlSec","dcrMaxClients"])if(c[f]!==void 0){if(typeof c[f]!="number"||!Number.isInteger(c[f])||c[f]<1||c[f]>31536e3)throw new Error(`oauth.${f} must be a positive integer`);u[f]=c[f]}let p=c.refreshMaxLifetimeSec;if(p!==void 0){if(typeof p!="number"||!Number.isInteger(p)||p<0||p>31536e4)throw new Error("oauth.refreshMaxLifetimeSec must be a non-negative integer");u.refreshMaxLifetimeSec=p}d.oauth=u}cl(i,"enabled",d),cl(i,"allowWithoutAdminAuth",d),ul(i,"defaultLinkTtlSeconds",d),ul(i,"maxActiveLinks",d)}catch(c){o.status(400).json({error:c.message,code:"invalid_body"});return}if(i.defaultDeploymentType!==void 0){let c=i.defaultDeploymentType;if(c!=="nodes"&&c!=="flows"&&c!=="full"){o.status(400).json({error:"defaultDeploymentType must be 'nodes', 'flows', or 'full'",code:"invalid_body"});return}d.defaultDeploymentType=c}try{i.publicBaseUrl!==void 0&&(d.publicBaseUrl=Dt(i.publicBaseUrl)),Xh(i,d),Zh(i,d,l);for(let c of["rateLimitWindowMs","rateLimitMaxPerIp","rateLimitMaxPerToken"])Yh(i,c,d)}catch(c){o.status(400).json({error:c.message,code:"invalid_body"});return}try{let c=t.patch(d,l);pr(c.publicBaseUrl),n.configureDefaults({defaultTtlSeconds:c.defaultLinkTtlSeconds,maxActiveLinks:c.maxActiveLinks});let u=fl(e,t,r),p=c.publicBaseUrl?new URL(c.publicBaseUrl):void 0,f=p?ft(p.hostname.replace(/^\[|\]$/g,"")):void 0,m=f?Qn(f):void 0,h=[];m!==void 0&&m!=="public"&&h.push("Public URL uses a private or non-public IP address.");let g=pl(c.publicBaseUrl);g&&c.publicBaseUrl&&h.push(g),o.json({...u,...h.length?{warnings:h}:{}})}catch(c){if(c instanceof Y){o.status(c.statusCode).json({error:c.message,code:c.code});return}D(o,c);return}}}a(hl,"putSettingsHandler");function gl(e){return async(t,n)=>{let r=new AbortController,o=setTimeout(()=>r.abort(),5e3),s;try{let i;try{let c=e.settings,p=e.server?.address(),f=t.body?.publicBaseUrl,m=typeof f=="string"?new URL(f.trim()).hostname:"";if((m.toLowerCase().replace(/^\[|\]$/g,"").replace(/\.$/,"")==="localhost"||Qn(m.replace(/^\[|\]$/g,""))==="loopback")&&typeof p=="string")throw new Error("Public URL Check does not support loopback URLs when Node-RED listens on a Unix socket");let g=p&&typeof p=="object"?p.port:c.uiPort??1880;i=await dl(t.body?.publicBaseUrl,g,r.signal)}catch(c){if(r.signal.aborted)throw c;n.status(400).json({error:c.message,code:"invalid_body"});return}s=i.origin+fe(e.settings.httpAdminRoot);let d=await ll(i,fe(e.settings.httpAdminRoot),r.signal),l={status:d,ok:d>=200&&d<300};n.json({ok:l.ok,status:l.status,url:s,message:l.ok?"Discovery endpoint is reachable":`Discovery endpoint returned HTTP ${l.status}`})}catch{n.json({ok:!1,status:null,url:s,error:r.signal.aborted?"Public URL check timed out after 5 seconds":"Could not reach the discovery endpoint"})}finally{clearTimeout(o)}}}a(gl,"checkPublicUrlHandler");ue();pt();ue();J();dr();var Qh="com55-ai-access";function wl(e){let t=new Set;for(let n of String(e??"").split(",")){let[r,...o]=n.split(";").map(d=>d.trim().toLowerCase());if(!r)continue;let s=o.find(d=>d.startsWith("q=")),i=s?Number(s.slice(2)):1;Number.isFinite(i)&&i>0&&t.add(r)}return t}a(wl,"acceptedMediaTypes");function bl(e){let t=wl(e.headers.accept);return t.has("text/event-stream")&&!t.has("text/html")}a(bl,"isMcpStreamRequest");function eg(e,t,n){e.httpAdmin.get("/mcp/api/v1",t,n,ls(e)),e.httpAdmin.get("/mcp/api/v1/",t,n,ls(e)),e.httpAdmin.get("/mcp/api/v1/openapi.yaml",t,n,Md()),e.httpAdmin.get("/mcp/api/v1/openapi.json",t,n,Dd()),e.httpAdmin.get("/mcp/editor/ai-access-sidebar.js",t,(r,o)=>{o.setHeader("Cache-Control","no-store, no-cache, must-revalidate, max-age=0"),o.setHeader("Pragma","no-cache"),o.setHeader("Expires","0"),o.setHeader("X-AI-Access-Sidebar-Version",on),o.type("application/javascript").status(200).send(Hr())})}a(eg,"registerMcpShellAndDiscovery");var tg=/^\/mcp\/(?:api\/v1\/proposals|rpc$|a\/[^/]+\/(?:proposals|propose|draft))/;function vl(e,t={}){let n=bo(e,{...t,linkStore:t.linkStore??uo(e)}),r=xa(()=>n.settingsStore.getEffective()),{ip:o,token:s}=r,i=new po(nr(e.settings.userDir),()=>n.settingsStore.getEffective().oauth.dcrMaxClients),d=a(()=>n.settingsStore.getEffective().publicBaseUrl,"publicBase"),l=a(()=>`${d().replace(/\/$/,"")}/mcp`,"mcpUrl"),c=new fo(i,l,()=>n.settingsStore.getEffective().oauth.accessTokenTtlSec,()=>n.settingsStore.getEffective().oauth.refreshIdleTtlSec,()=>n.settingsStore.getEffective().oauth.refreshMaxLifetimeSec),u=Nd(e,c,d,l);Bs(e.httpAdmin,Us(()=>n.settingsStore.getEffective().corsAllowedOrigins));let p=a(w=>(Array.isArray(w)?w:w?[w]:[]).flatMap(v=>{if(typeof v=="string")return[v];let A=v;return A&&typeof A.path=="string"?[{path:A.path,root:typeof A.root=="string"?A.root:void 0}]:[]}),"staticMounts"),f={authorization:[],metadata:[],details:[],httpNodeRootFallback:e.settings.httpNodeRoot===!1||!e.httpNode},m="",h=a(()=>{let w=Id({httpAdmin:e.httpAdmin,httpNode:e.httpNode,httpAdminRoot:e.settings.httpAdminRoot,httpNodeRoot:e.settings.httpNodeRoot,metadataFallback:f.httpNodeRootFallback,eachNode:e.nodes?F=>e.nodes?.eachNode(F):void 0,staticDirs:p(e.settings.httpStatic),httpStaticRoot:e.settings.httpStaticRoot,fileExists:yl.existsSync}),v=w.authorization,A=w.metadata;f.authorization=v.map(Ls),f.metadata=A.map(Ls),f.details=[...v.map(F=>({...F,kind:"authorization"})),...A.map(F=>({...F,kind:"metadata"}))];let E=[...f.authorization.map(F=>`${F} \u2014 OAuth authorization routes disabled`),...f.metadata.map(F=>`${F} \u2014 OAuth metadata disabled`)],$=E.join(`
6823
- `);if($!==m)for(let F of E)e.log?.warn?.(`AI Access OAuth route collision: ${F}`);m=$},"refreshCollisions");h(),e.events?.on("flows:started",h);let g=ps({windowMs:6e4,maxPerIp:30}),k=a((w,v,A)=>{if(!n.settingsStore.getEffective().oauth.enabled||f.authorization.length||!w.path.startsWith("/mcp/oauth/")){A();return}let E=w.path.replace(/\/+$/,"").toLowerCase(),$=a(()=>jt(e)(w,v,()=>u.server(w,v,A)),"run");E==="/mcp/oauth/authorize"?o(w,v,()=>g(w,v,$)):E.startsWith("/mcp/oauth/consent/")?o(w,v,$):$()},"oauthServerMiddleware");k.oauthOwned=!0,e.httpAdmin.use(k);let T=a((w,v,A)=>{if(!n.settingsStore.getEffective().oauth.enabled||f.metadata.length){A();return}u.metadata(w,v,A)},"metadataHandler");T.oauthOwned=!0,f.httpNodeRootFallback?e.httpAdmin.use("/mcp",T):e.httpNode?.use(T),e.httpAdmin.get("/mcp/api/v1/oauth/clients",o,s,Ae(e,"api"),(w,v)=>v.json({clients:i.data.clients.map(A=>({client_id:A.client_id,client_name:A.client_name,redirect_uris:A.redirect_uris,lastUsedAt:A.lastUsedAt}))})),e.httpAdmin.get("/mcp/api/v1/oauth/status",o,s,Ae(e,"api"),(w,v)=>{let A=!z(e.settings)||Ye(w)==="*";v.json({...f,details:A?f.details:f.details.map(({file:E,...$})=>$),collisions:[...f.authorization,...f.metadata],oauthEnabled:n.settingsStore.getEffective().oauth.enabled})}),e.httpAdmin.delete("/mcp/api/v1/oauth/clients/:id",o,s,Ae(e,"api"),_n(e),(w,v)=>v.json({revoked:i.revokeClient(String(w.params.id))})),e.httpAdmin.use((w,v,A)=>{if(!tg.test(w.path)){A();return}n.flowAdapter.getFlows().then(E=>Si(w,Rr(E.flows))).catch(()=>{}).finally(()=>A())}),e.httpAdmin.use(md(n.auditLog)),Va(e,{linkStore:n.linkStore,proposalStore:n.proposalStore,auditLog:n.auditLog,flowAdapter:bt(n.flowAdapter),nodeTypesAdapter:n.nodeTypesAdapter,...n.catalogueFetcher?{catalogueFetcher:n.catalogueFetcher}:{},...n.catalogueUrl?{catalogueUrl:n.catalogueUrl}:{},rateLimiters:r}),e.httpAdmin.get("/ai-access",o,s,(w,v)=>{v.setHeader("Cache-Control","no-store, no-cache, must-revalidate, max-age=0"),v.setHeader("Pragma","no-cache"),v.setHeader("Expires","0"),v.type("html").status(200).send(Bd({apiBase:fe(e.settings.httpAdminRoot),mcpRpc:Ve(e.settings.httpAdminRoot),editorRoot:pe(e.settings.httpAdminRoot),authTokenKey:mr(e.settings.httpAdminRoot),dashboardPath:Rn(e.settings.httpAdminRoot)}))}),e.httpAdmin.get("/mcp",o,(w,v,A)=>{if(bl(w)){A();return}v.setHeader("Cache-Control","no-store"),v.redirect(302,Rn(e.settings.httpAdminRoot))}),eg(e,o,s),e.httpAdmin.get("/mcp/api/v1/status",o,s,Js(e,w=>n.linkStore.validateResult(w)),_((w,v)=>{v.json(pn(e,w))})),e.httpAdmin.get("/mcp/api/v1/settings",o,s,Ae(e,"api"),_(ml(e,n.settingsStore))),e.httpAdmin.put("/mcp/api/v1/settings",o,s,Ae(e,"api"),_n(e),We("rest"),_(hl(e,n.settingsStore,n.linkStore))),e.httpAdmin.post("/mcp/api/v1/settings/check-public-url",o,s,Ae(e,"api"),_n(e),We("rest"),_(gl(e)));let C=[o,s,Ae(e,"api"),jt(e),_n(e)];e.httpAdmin.get("/mcp/api/v1/links",...C,_(od(e,n.linkStore))),e.httpAdmin.post("/mcp/api/v1/links",...C,_(sd(e,n.linkStore,n.auditLog))),e.httpAdmin.delete("/mcp/api/v1/links/:id",...C,_(id(e,n.linkStore,n.auditLog)));let L=[o,s,jt(e),Ks(e,w=>n.linkStore.validateResult(w))],N=n.flowAdapter,Ce={nodeTypes:n.nodeTypesAdapter,...n.catalogueFetcher?{catalogueFetcher:n.catalogueFetcher}:{},...n.catalogueUrl?{catalogueUrl:n.catalogueUrl}:{}};e.httpAdmin.get("/mcp/api/v1/flows",...L,_(fa(N))),e.httpAdmin.get("/mcp/api/v1/flows/:id",...L,_(Zr(N))),e.httpAdmin.get("/mcp/api/v1/nodes/:id",...L,_(Qr(N))),e.httpAdmin.get("/mcp/api/v1/nodes/:id/field/:name",...L,_(eo(N))),e.httpAdmin.get("/mcp/api/v1/search",...L,_(to(N))),e.httpAdmin.get("/mcp/api/v1/node-types",...L,_(no(Ce))),e.httpAdmin.get("/mcp/api/v1/node-types/:type",...L,_(ro(Ce))),e.httpAdmin.get("/mcp/api/v1/palette/search",...L,_(oo(Ce)));let S=e.settings.httpAdminRoot;e.httpAdmin.get("/mcp/api/v1/proposals",...L,_(Wd(n.proposalStore,n.flowAdapter,S))),e.httpAdmin.get("/mcp/api/v1/proposals/:id",...L,_(Gd(n.proposalStore,S,n.flowAdapter))),e.httpAdmin.get("/mcp/api/v1/proposals/:id/diff",...L,_(Yd(n.proposalStore,n.flowAdapter))),e.httpAdmin.post("/mcp/api/v1/proposals",o,s,We("rest"),Ut(e,n.linkStore,"rest"),_(zd(n.proposalStore,n.auditLog,n.flowAdapter,S,n.nodeTypesAdapter))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/approve",...C,We("rest"),_(Kd(n.proposalService,S))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/reject",...C,We("rest"),_(Jd(n.proposalService,S))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/withdraw",o,s,We("rest"),Ut(e,n.linkStore,"rest"),_(Vd(n.proposalStore,n.auditLog,S)));let b=[o,s,(w,v,A)=>{let E=w.headers.authorization;if(!n.settingsStore.getEffective().oauth.enabled||E?.toLowerCase().startsWith("bearer aia_")){if(n.settingsStore.getEffective().oauth.enabled){let $=v.setHeader.bind(v);v.setHeader=((F,le)=>{if(F.toLowerCase()==="www-authenticate"&&typeof le=="string"&&!le.includes("resource_metadata=")){let Ke=`${d().replace(/\/$/,"")}${f.httpNodeRootFallback?"/mcp":""}/.well-known/oauth-protected-resource/mcp`;le+=`, resource_metadata="${Ke}", scope="read propose"`}return $(F,le)})}Ut(e,n.linkStore,"mcp")(w,v,A);return}jt(e)(w,v,()=>u.auth(w,v,A))}];e.httpAdmin.post("/mcp/rpc",...b,We("mcp"),_(Ts(e,n))),e.httpAdmin.get("/mcp/rpc",...b,Vn),e.httpAdmin.delete("/mcp/rpc",...b,Vn),e.httpAdmin.post("/mcp",...b,We("mcp"),_(Ts(e,n))),e.httpAdmin.get("/mcp",...b,Vn),e.httpAdmin.delete("/mcp",...b,Vn),e.httpAdmin.get("/mcp/api/v1/logs",o,s,Ae(e,"api"),jt(e),_(ad(n.auditLog,n.linkStore))),e.httpAdmin.use("/mcp/api/v1",ca()),Qa(e)}a(vl,"registerRoutes");function ng(e){e.plugins.registerPlugin(Qh,{type:"com55-ai-access",onadd(){vl(e),e.log?.info?.("Node-RED AI Access plugin loaded (dashboard /ai-access, MCP /mcp)")}})}a(ng,"registerAiAccessPlugin");var rg=ng;0&&(module.exports={ACCESS_LINK_INVALID_MESSAGE,ACCESS_LINK_MISSING_MESSAGE,AccessLinkStore,EDITOR_SIDEBAR_ASSET_VERSION,MAX_PROPOSAL_JSON_BYTES,SettingsStore,acceptedMediaTypes,areAiFeaturesEnabled,bindSettingsStore,createPluginServices,getStorageDir,isAdminAuthEnabled,isMcpStreamRequest,packageVersion,registerRoutes,renderEditorSidebarJs,renderEditorSidebarLoaderHtml,requireAccessLink,resolveAiAccessSettings});
7092
+ </html>`}a(vc,"renderMcpPage");Oe();nt();Te();Ve();oe();Mt();se();rt();Wt();Oe();Fo();nt();Pe();function qe(e,t,n,r={}){e.setHeader("X-Content-Type-Options","nosniff"),e.setHeader("Content-Type","application/json; charset=utf-8");let o={error:ot[n]??"Request failed",code:n,...r},s=we(n,"rest");s&&typeof o.hint!="string"&&(o.hint=s),e.status(t).json(o)}a(qe,"sendSafeJsonError");function $r(e){let t={kind:"admin"};return typeof e.user?.username=="string"&&(t.username=e.user.username),t}a($r,"adminActor");function kc(e,t,n,r,o){return async(s,i)=>{if(!s.accessLink){i.setHeader("WWW-Authenticate",fe),i.status(401).json({error:Ie,code:at});return}try{let d=V(s,r),l=ge(s.query),c=await Tt(e,t,s.accessLink,s.body??{},n,d,o),u=te(e),p=Qe(e,e.get(c.id),s.accessLink.id),f=c.warnings;i.status(201).json({...ie(c,{...d,include:l}),mine:!0,...et(p),...pe(u),...f&&f.length>0?{warnings:f}:{}})}catch(d){let l=W(d,"rest");if(l){i.status(l.statusCode).json(l.body);return}B(i,d);return}}}a(kc,"createProposalHandler");function Ac(e,t,n){return async(r,o)=>{let s=await tt(t),i=s?.rev,d=s?ho(s.flows):void 0,l={...V(r,n),...d?{liveFlowLabels:d}:{}},c=ge(r.query),u;try{u=_o(r.query)}catch(v){B(o,v);return}let p=s?De(e,s).expired:te(e),f=r.accessLink;if(f){let v=En(e,f,l,u),L=v.proposals.map(P=>{let I=he(e,f,P,{...l,include:c},{includeConflicts:!1});return!U(I.status)||i===void 0?I:{...I,baseRevChanged:I.expectedRev!==i}});o.json({proposals:L,...Jt({...v,returned:v.proposals.length}),...pe(p)});return}let m={};u.flowId&&(m.flowId=u.flowId);let h=za(e.list(m).filter(v=>fi(v.status,u.status)),{...l,include:c}),g=u.offset??0,w=(u.limit!==void 0?h.slice(g,g+u.limit):g>0?h.slice(g):h).map(v=>!U(v.status)||i===void 0?v:{...v,baseRevChanged:v.expectedRev!==i});o.json({proposals:w,...Jt({total:h.length,limit:u.limit??null,offset:g,returned:w.length}),...pe(p)})}}a(Ac,"listProposalsHandler");function xc(e,t,n){return async(r,o)=>{let s=r.params.id;if(typeof s!="string"||s.length===0){o.status(400).json({error:"Missing proposal id"});return}let i=V(r,t),d=ge(r.query),l=await tt(n);try{if(r.accessLink){let c=ut(e,r.accessLink,s,i);o.json(he(e,r.accessLink,c,{...i,include:d},{...l?{live:l}:{}}));return}l?De(e,l):te(e),o.json(ie(e.get(s),{...i,include:d}))}catch(c){if(c instanceof me){qe(o,404,c.code);return}let u=W(c,"rest");if(u){o.status(u.statusCode).json(u.body);return}B(o,c);return}}}a(xc,"getProposalHandler");function Sc(e,t){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){r.status(400).json({error:"Missing proposal id"});return}let s=n.body??{},i={};if(s.mergedHash!==void 0){if(typeof s.mergedHash!="string"||!/^[0-9a-f]{8,64}$/.test(s.mergedHash)){r.status(400).json({error:"mergedHash must be a hex string",code:"invalid_body"});return}i.mergedHash=s.mergedHash}if(s.deploymentType!==void 0){if(s.deploymentType!=="nodes"&&s.deploymentType!=="flows"&&s.deploymentType!=="full"){r.status(400).json({error:"deploymentType must be 'nodes', 'flows', or 'full'"});return}i.deploymentType=s.deploymentType}try{let d=await e.approve(o,$r(n),i);r.json(ue(d,V(n,t)))}catch(d){if(d instanceof Je){qe(r,409,d.code,{conflicts:d.conflicts});return}if(d instanceof Yt){qe(r,409,d.code,{mergedHash:d.mergedHash,rebaseState:d.rebaseState});return}if(d instanceof Xt||d instanceof Pt||d instanceof Zt){qe(r,d.statusCode,d.code);return}if(d instanceof me||d instanceof Y||d instanceof Re||d instanceof ce){qe(r,d.statusCode,d.code);return}B(r,d);return}}}a(Sc,"approveProposalHandler");function Tc(e,t){return(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){r.status(400).json({error:"Missing proposal id"});return}let s=n.body??{},i=typeof s.reason=="string"?s.reason:"Rejected by admin";try{let d=ue(e.reject(o,$r(n),i),V(n,t));r.json(d)}catch(d){if(d instanceof me||d instanceof Y||d instanceof ce){qe(r,d.statusCode,d.code);return}B(r,d);return}}}a(Tc,"rejectProposalHandler");function Rc(e,t,n){return(r,o)=>{let s=r.params.id;if(typeof s!="string"||s.length===0){o.status(400).json({error:"Missing proposal id",code:"missing_proposal_id"});return}let i=r.body??{},d=V(r,n),l=ge(r.query);try{if(!r.accessLink){let u=gi(e,t,$r(r),s,i.reason,d);o.json(ie(u,{...d,include:l}));return}let c=In(e,t,r.accessLink,s,i.reason,d);o.json(he(e,r.accessLink,c,{...d,include:l}))}catch(c){let u=W(c,"rest");if(u){qe(o,u.statusCode,u.body.code);return}qe(o,500,"internal_error");return}}}a(Rc,"withdrawProposalHandler");function Pc(e,t){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){r.status(400).json({error:"Missing proposal id"});return}try{let s=n.accessLink?ut(e,n.accessLink,o):e.get(o),i=Mn(n.query.format);if(!i.ok){r.status(400).json({error:"Invalid diff format",code:"invalid_diff_format",hint:"Valid formats: summary, full (default summary)."});return}let d=Dn(await Bn(s,Xe(n,t),e.get(o)),i.format);r.json(d)}catch(s){if(s instanceof me){qe(r,404,s.code);return}if(s instanceof X||s instanceof Le||s instanceof We){qe(r,s.statusCode,s.code);return}let i=W(s,"rest");if(i){r.status(i.statusCode).json(i.body);return}B(r,s);return}}}a(Pc,"getProposalDiffHandler");function _c(e,t,n,r,o){return async(s,i)=>{if(!s.accessLink){qe(i,401,"access_link_missing");return}try{let d=V(s,r),l=await Co(e,t,s.accessLink,String(s.params.id),s.body??{},n,d,o);i.json(he(e,s.accessLink,l,d))}catch(d){B(i,d)}}}a(_c,"resolveProposalHandler");function Cc(e,t,n,r){return async(o,s)=>{try{let i=o.body??{};if(i.choice==="undo"){let l=e.get(String(o.params.id)),c=l.conflictChoice;if(!c?.undo||c.nodeId!==i.nodeId){s.status(409).json({code:"conflict_not_found",error:"This choice can no longer be undone. Reload the proposal."});return}let u=e.updateBody(l.id,c.undo),p=await n.getFlows(),f=Me(e,u,p).proposal;t.append("proposal.conflict_resolved",$r(o),{proposalId:l.id,nodeId:c.nodeId,choice:"undo"}),s.json(ie(f,V(o,r)));return}let d=await yi(e,t,$r(o),String(o.params.id),i.nodeId,i.choice,n);s.json(ie(d,V(o,r)))}catch(i){B(s,i)}}}a(Cc,"resolveProposalConflictHandler");function Ec(e,t,n){return async(r,o)=>{try{te(e);let s=e.get(String(r.params.id)),i=await tt(t);i&&U(s.status)&&(s=Me(e,s,i).proposal),o.json(ie(s,V(r,n)))}catch(s){B(o,s)}}}a(Ec,"recheckProposalHandler");Pe();Te();Te();ao();function fy(e){return e.endsWith("/")?e.slice(0,-1):e}a(fy,"trimTrailingSlash");var cs=class{static{a(this,"HttpAdapter")}kind="http";baseUrl;accessToken;fetchImpl;constructor(t){this.baseUrl=fy(t.baseUrl),this.accessToken=t.accessToken,this.fetchImpl=t.fetch??globalThis.fetch.bind(globalThis)}headers(t){let n=new Headers(t);return n.set("Accept","application/json"),n.set("Node-RED-API-Version","v2"),this.accessToken&&n.set("Authorization",`Bearer ${this.accessToken}`),n}async request(t,n={}){return await this.fetchImpl(`${this.baseUrl}${t}`,{...n,headers:this.headers(n.headers)})}async readJson(t,n){let r=await t.text();if(!t.ok){let o=r;try{let s=JSON.parse(r);o=s.message??s.code??r}catch{}throw t.status===404?new X(n):t.status===409?new Q("unknown","unknown",`Admin API ${n}`):t.status===401||t.status===403?new We(eo):new Error(`Admin API ${n} failed (${t.status}): ${o}`)}if(!r)return null;try{return JSON.parse(r)}catch{throw new Error(`Admin API ${n} returned non-JSON`)}}async getFlows(){let t=await this.request("/flows"),n=await this.readJson(t,"GET /flows");return dr(n,"GET /flows")}async getFlow(t){let n=encodeURIComponent(t),[r,o]=await Promise.all([this.request("/flows"),this.request(`/flow/${n}`)]);if(o.status===404)throw new X(t);let s=await this.readJson(r,"GET /flows"),i=await this.readJson(o,`GET /flow/${t}`);return{rev:dr(s,"GET /flows").rev,flow:vn(t,i)}}async updateFlow(t){let n=t.deploymentType??"nodes",r=await this.getFlows();if(r.rev!==t.expectedRev)throw new Q(t.expectedRev,r.rev,"updateFlow");let o=io(r.flows,t.flowId,t.flow),s=await this.request("/flows",{method:"POST",headers:{"Content-Type":"application/json","Node-RED-Deployment-Type":n},body:JSON.stringify({flows:o,rev:t.expectedRev})});if(s.status===409){let d=r.rev;try{let l=await s.clone().json();typeof l.current_rev=="string"&&(d=l.current_rev)}catch{}throw new Q(t.expectedRev,d,"updateFlow")}let i=await this.readJson(s,"POST /flows");if(!i||typeof i!="object"||typeof i.rev!="string")throw new Error("Admin API POST /flows did not return a rev");return{flowId:t.flowId,rev:i.rev}}};Te();ao();function hy(e){let t=e.runtime?.flows;if(!t?.getFlows||!t.getFlow||!t.setFlows)throw new Error("RED.runtime.flows is missing getFlows, getFlow, or setFlows");return t}a(hy,"requireFlowsApi");function my(e){if(typeof e=="string")return e;if(typeof e.rev=="string"&&e.rev.length>0)return e.rev;throw new Error("RED.runtime.flows.setFlows did not return a rev")}a(my,"resolveRev");var us=class{static{a(this,"RedAdapter")}kind="red";flows;constructor(t){this.flows=hy(t)}async getFlows(){let t=await this.flows.getFlows({});return dr(t,"RED.runtime.flows.getFlows()")}async getFlow(t){let n=await this.getFlows(),r;try{r=await this.flows.getFlow({id:t})}catch(o){throw gy(o)?new X(t):o}return{rev:n.rev,flow:vn(t,r)}}async updateFlow(t){let n=t.deploymentType??"nodes",r=await this.getFlows();if(r.rev!==t.expectedRev)throw new Q(t.expectedRev,r.rev,"updateFlow");let o=io(r.flows,t.flowId,t.flow),s;try{s=await this.flows.setFlows({flows:{flows:o,rev:t.expectedRev},deploymentType:n})}catch(i){if(i&&typeof i=="object"&&"code"in i&&i.code==="version_mismatch"){let d=await this.getFlows();throw new Q(t.expectedRev,d.rev,"updateFlow")}throw i}return{flowId:t.flowId,rev:my(s)}}};function Ic(e){let t=e?.runtime?.flows;return!!(t?.getFlows&&t.getFlow&&t.setFlows)}a(Ic,"canUseRedRuntime");function gy(e){if(!e||typeof e!="object")return!1;let t=e;if((typeof t.status=="number"?t.status:t.statusCode)===404||t.code==="not_found"||t.code==="flow_not_found")return!0;let r=typeof t.message=="string"?t.message.toLowerCase():"";return r.includes("not found")||r.includes("unknown flow")}a(gy,"isRuntimeFlowNotFound");var Lc=require("module");function Nc(e){if(!e||typeof e!="object")return!1;let t=e;return typeof t.getFlows=="function"&&typeof t.getFlow=="function"&&typeof t.setFlows=="function"}a(Nc,"isFlowsApi");function yy(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,Lc.createRequire)(e)}a(yy,"createPluginRequire");function wy(e){try{let t=e("@node-red/runtime/lib/settings");return typeof t.available=="function"&&t.available()===!0}catch{return!1}}a(wy,"isNodeRedRuntimeInitialized");function by(){try{let e=yy();if(!wy(e))return null;let t=e("@node-red/runtime");return Nc(t.flows)?t.flows:null}catch{return null}}a(by,"tryRequireInitializedRuntimeFlows");function Fc(e){if(e?.runtime&&Nc(e.runtime.flows))return e;let t=by();return t?{runtime:{flows:t}}:null}a(Fc,"resolveRuntimeHost");function Oc(e={}){let t=Fc(e.RED??null);if(t&&Ic(t))return new us(t);let n=e.adminAuthEnabled===!0;if(e.http?.baseUrl){if(n&&!e.http.accessToken)throw new Le("In-process Node-RED flow runtime is unavailable and HTTP Admin API fallback has no credentials while adminAuth is enabled. Ensure the plugin runs inside Node-RED so @node-red/runtime.flows can be used.");return new cs(e.http)}throw new Le("No usable Node-RED flow adapter: in-process flows API is unavailable and no HTTP Admin API baseUrl was provided")}a(Oc,"createFlowAdapter");Te();Es();var fs=require("fs"),$c=require("path");no();oe();var vy=/^[a-z][a-z0-9._-]{0,63}$/;function ky(e){if(e.kind==="system")return{kind:"system"};if(e.kind==="access-link"){let n={kind:"access-link",linkId:String(e.linkId).replace(/\0/g,"").slice(0,200)};return typeof e.label=="string"&&(n.label=e.label.replace(/\0/g,"").slice(0,200)),n}let t={kind:"admin"};return typeof e.username=="string"&&(t.username=e.username.replace(/\0/g,"").slice(0,200)),t}a(ky,"sanitizeActor");var ps=class{constructor(t,n={}){this.paths=t;this.filePath=(0,$c.join)(t.logs,"audit.jsonl"),this.now=n.now??(()=>Date.now()),this.maxAppendBytes=n.maxAppendBytes??ta}static{a(this,"AuditLog")}filePath;now;maxAppendBytes;seq=0;append(t,n,r){let o=vy.test(t)?t:"unknown";this.seq+=1;let s={id:`log-${this.now()}-${this.seq}`,at:new Date(this.now()).toISOString(),action:o,actor:ky(n)};r!==void 0&&(s.details=va(r));let i=ht(s,this.maxAppendBytes,{kind:"audit"});return ra(this.paths.root,this.filePath,i),s}list(t=100){if(!(0,fs.existsSync)(this.filePath))return[];let n=(0,fs.readFileSync)(this.filePath,"utf8").split(`
7093
+ `).map(o=>o.trim()).filter(Boolean),r=[];for(let o of n)try{r.push(JSON.parse(o))}catch{}return r.slice(-Math.max(1,t)).reverse()}};Ve();Kr();ve();Ut();Fo();ke();function Ay(e){let t=a(()=>Promise.reject(new We(e)),"fail");return{kind:"http",getFlows:t,getFlow:t,updateFlow:t}}a(Ay,"misconfiguredAdapter");function Mc(e){let t=e.settings,n=typeof t.uiPort=="number"?t.uiPort:1880,r=t.httpAdminRoot??"/",o=!r||r==="/"?"":r.replace(/\/$/,"");return`http://127.0.0.1:${n}${o}`}a(Mc,"localAdminBaseUrl");function hs(e,t={}){let n=Ur(e.settings.userDir);oa(n);let r=t.settingsStore??new hn(n,e.settings.aiAccess,u=>e.log?.warn?.(u));t.bindSettings!==!1&&Vr(r),Xr(r.getEffective().publicBaseUrl);let o=t.linkStore??is(e);o.configureDefaults({defaultTtlSeconds:r.getEffective().defaultLinkTtlSeconds,maxActiveLinks:r.getEffective().maxActiveLinks});let s=new ro(n),i=new ps(n),d;if(t.flowAdapter)d=t.flowAdapter;else try{d=Oc({RED:e,http:{baseUrl:Mc(e)},adminAuthEnabled:re(e.settings)})}catch(u){let p=u instanceof Le?u.message:eo;e.log?.warn?.(`AI Access flow adapter unavailable: ${p}`),d=Ay(p)}d=Sa(d);let l;if(t.nodeTypesAdapter)l=t.nodeTypesAdapter;else if(t.flowAdapter)l=$s();else try{l=Na({baseUrl:Mc(e)})}catch(u){let p=u instanceof Error?u.message:String(u);e.log?.warn?.(`AI Access node-types adapter unavailable: ${p}`),l=$s([])}let c=new No(s,i,d,()=>Ce(e.settings).defaultDeploymentType,e.settings.aiAccess);return{linkStore:o,proposalStore:s,auditLog:i,flowAdapter:d,nodeTypesAdapter:l,proposalService:c,settingsStore:r,...t.catalogueFetcher?{catalogueFetcher:t.catalogueFetcher}:{},...t.catalogueUrl?{catalogueUrl:t.catalogueUrl}:{}}}a(hs,"createPluginServices");oe();ve();ze();Br();var Dc=Vn(require("dns"),1),Hc=Vn(require("http"),1),jc=Vn(require("https"),1),Mr=require("net");ve();function xy(e,t){return e===10||e===172&&t>=16&&t<=31||e===192&&t===168}a(xy,"isPrivateV4");function Sy(e,t){return e===169&&t===254}a(Sy,"isLinkLocalV4");function Ty(e,t,n){return e===0||e===100&&t>=64&&t<=127||e>=224||e===192&&t===0&&(n===0||n===2)||e===198&&(t===18||t===19||t===51&&n===100)||e===203&&t===0&&n===113}a(Ty,"isSpecialUseV4");function Ry(e){let[t=0,n=0,r=0]=e.split(".").map(Number);return t===127?"loopback":xy(t,n)||Sy(t,n)||Ty(t,n,r)||e==="168.63.129.16"?"blocked":"public"}a(Ry,"classifyIpv4");function Py(e){let t=e.split(":"),n=t.indexOf(""),r=[];for(let o of t){if(!o)continue;if(o.includes(".")){let i=o.split(".").map(Number);if(i.length!==4||i.some(d=>!Number.isInteger(d)||d<0||d>255))return;r.push((i[0]??0)<<8|(i[1]??0)),r.push((i[2]??0)<<8|(i[3]??0));continue}let s=Number.parseInt(o,16);if(!/^[\da-f]{1,4}$/i.test(o)||!Number.isInteger(s))return;r.push(s)}if(n>=0){let o=8-r.length;if(o<1)return;let s=t.slice(0,n).filter(Boolean).length;r.splice(s,0,...Array(o).fill(0))}else if(r.length!==8)return;return r.length===8?r:void 0}a(Py,"expandIpv6");function _y(e){let t=Py(e);if(!t)return"blocked";if(t.slice(0,7).every(s=>s===0)&&t[7]===1)return"loopback";let n=t[0]??0,r=t[1]??0;return n===0||n<8192||n>16383||n===8193&&r<=511||n===8193&&r===3512||n===16383&&r<=4095||n===8194?"blocked":"public"}a(_y,"classifyIpv6");function Dr(e){let t=Dt(e);return t?(0,Mr.isIP)(t)===4?Ry(t):_y(t):"blocked"}a(Dr,"classifyPublicUrlAddress");function Cy(e){let t=e.toLowerCase().replace(/\.$/,"");return["metadata","metadata.google.internal","metadata.google.com","metadata.goog","instance-data","instance-data.ec2.internal","metadata.azure.internal","metadata.aws.internal"].some(n=>t===n||t.endsWith(`.${n}`))}a(Cy,"isMetadataHostname");async function Uc(e,t,n){let r=mn(e);if(!r)throw new Error("publicBaseUrl is required for Check");let o=new URL(r),s=o.hostname.replace(/^\[|\]$/g,"");if(Cy(s))throw new Error("Public URL Check cannot target metadata hosts");let i=Dt(s),l=(typeof e=="string"?/^https?:\/\/(\[[^\]]+\]|[^/?#:]+)/i.exec(e.trim())?.[1]:void 0)?.replace(/^\[|\]$/g,"");if(i&&l&&l.toLowerCase()!==i.toLowerCase())throw new Error("Public URL Check requires canonical IP address notation");let c=s.toLowerCase().replace(/\.$/,"")==="localhost"||i!==void 0&&Dr(i)==="loopback",u=Number(o.port||(o.protocol==="https:"?443:80));if(c&&u!==t)throw new Error("Public URL Check may only target this Node-RED instance's port for loopback URLs");let p=a(g=>{let y=Dr(g);if(y==="blocked"||y==="loopback"&&!c||c&&y!=="loopback")throw new Error("Public URL Check cannot target private or non-public addresses")},"validateAddress");if(i)return p(i),{origin:r,address:i,family:(0,Mr.isIP)(i)};let f=await new Promise((g,y)=>{let w=a(()=>y(n.reason instanceof Error?n.reason:new Error(String(n.reason))),"abort");n.throwIfAborted(),n.addEventListener("abort",w,{once:!0}),Dc.default.lookup(s,{all:!0},(v,L)=>{n.removeEventListener("abort",w),v?y(v):g(L)})});if(n.throwIfAborted(),!f.length)throw new Error("Public URL Check hostname has no addresses");for(let g of f)p(g.address);let m=[];for(let g of f){let y=Dt(g.address);if(!y)throw new Error("Public URL Check returned an invalid address");m.some(w=>w.address===y)||m.push({address:y,family:(0,Mr.isIP)(y)})}c&&m.sort((g,y)=>g.family-y.family);let h=m[0];if(!h)throw new Error("Public URL Check hostname has no addresses");return{origin:r,...h,addresses:m}}a(Uc,"preparePublicUrlCheck");function Ey(e,t,n){let r=e.hostname.replace(/^\[|\]$/g,"");return new Promise((o,s)=>{let d=(e.protocol==="https:"?jc.default:Hc.default).request(e,{method:"GET",agent:!1,signal:n,headers:{Host:e.host},...(0,Mr.isIP)(r)?{}:{servername:r},lookup:a((l,c,u)=>{c.all?u(null,[{address:t.address,family:t.family}]):u(null,t.address,t.family)},"lookup")},l=>{o(l.statusCode??0),l.destroy()});d.on("error",s),d.end()})}a(Ey,"requestPinned");async function Bc(e,t,n){let r=new URL(e.origin+t),o=e.addresses?.length?e.addresses:[{address:e.address,family:e.family}],s;for(let i of o){n.throwIfAborted();try{return await Ey(r,i,n)}catch(d){if(n.aborted)throw d;s=d}}throw s instanceof Error?s:new Error("Public URL Check request failed")}a(Bc,"requestPublicUrlCheck");pn();ke();function Iy(e){return rr(e.settings.httpAdminRoot)}a(Iy,"mcpPageUrl");function Ly(e){return typeof e=="boolean"||typeof e=="number"||typeof e=="string"?e:Array.isArray(e)?e.join(", "):typeof e=="function"?"custom function":!1}a(Ly,"normalizeTrustProxy");function Wc(e){if(!e)return"Set a Public URL before enabling OAuth.";let t=new URL(e),n=Dt(t.hostname),r=t.hostname.toLowerCase().replace(/\.$/,"")==="localhost"||n==="::1"||n?.startsWith("127.");return t.protocol==="http:"&&!r?"OAuth Public URL uses HTTP; HTTPS is recommended.":null}a(Wc,"oauthPublicUrlWarning");function Gc(e,t,n){let{sources:r,...o}=t.getEffective(),{permission:s}=Yr(e,n);return{ok:!0,adminAuthEnabled:re(e.settings),canEdit:s==="*",permission:s,mcpPageUrl:Iy(e),apiBase:xe(e.settings.httpAdminRoot),settings:o,oauthPublicUrlWarning:Wc(o.publicBaseUrl),sources:r,disk:t.getDisk(),trustProxy:Ly(n.app.get("trust proxy")),precedence:["settings.js","disk","default"],defaults:{secretNamePatterns:[...Ot]}}}a(Gc,"settingsResponse");function Kc(e,t){return(n,r)=>{r.json(Gc(e,t,n))}}a(Kc,"getSettingsHandler");function qc(e,t,n){let r=e[t];if(r!==void 0){if(typeof r!="boolean")throw new Error(`${t} must be a boolean`);n[t]=r}}a(qc,"parseBooleanField");function zc(e,t,n){let r=e[t];if(r!==void 0){if(typeof r!="number"||!Number.isInteger(r)||r<1)throw new Error(`${t} must be an integer with a minimum of 1`);n[t]=Math.floor(r)}}a(zc,"parsePositiveIntField");function Ny(e,t,n){let r=e[t];if(r===void 0)return;let o=t==="rateLimitWindowMs"?100:1,s=t==="rateLimitWindowMs"?36e5:1e6;if(typeof r!="number"||!Number.isInteger(r)||r<o||r>s)throw new Error(`${t} must be an integer between ${o} and ${s}`);n[t]=r}a(Ny,"parseRateLimitField");function Fy(e,t){if(e.corsAllowedOrigins===void 0)return;if(!Array.isArray(e.corsAllowedOrigins))throw new Error("corsAllowedOrigins must be a list of HTTP(S) origins");let n=e.corsAllowedOrigins.map(r=>Zn(r));if(n.some(r=>r===void 0))throw new Error("corsAllowedOrigins must contain only HTTP(S) origins without paths, credentials, queries, or fragments");t.corsAllowedOrigins=[...new Set(n)]}a(Fy,"parseCorsField");function Oy(e,t,n){let r=e.secretNamePatterns;if(r!==void 0){if(r===null){n.push("secretNamePatterns");return}if(!Array.isArray(r)||r.some(o=>typeof o!="string"))throw new Error("secretNamePatterns must be a list of strings (or null to reset)");if(r.length>Qn)throw new Error(`secretNamePatterns allows at most ${Qn} entries`);for(let o of r)if(!qr.test(o.trim()))throw new Error('secretNamePatterns entries must be 2-40 letters, digits, "_" or "-" (e.g. API_KEY)');t.secretNamePatterns=er(r)??[]}}a(Oy,"parseSecretPatternsField");function Jc(e,t,n){return(r,o)=>{let s=r.body;if(!s||typeof s!="object"||Array.isArray(s)){o.status(400).json({error:"Request body must be a settings object",code:"invalid_body"});return}let i={...s};for(let[c,u]of Object.entries(t.getEffective().sources))u==="settings.js"&&delete i[c];t.getEffective().sources.oauth==="settings.js"&&delete i.oauth;let d={},l=[];try{if(i.proposalTtlDays!==void 0){let c=i.proposalTtlDays;if(typeof c!="number"||!Number.isInteger(c)||c<1||c>365)throw new Error("proposalTtlDays must be an integer between 1 and 365");d.proposalTtlDays=c}if(i.oauth!==void 0){if(!i.oauth||typeof i.oauth!="object"||Array.isArray(i.oauth))throw new Error("oauth must be an object");let c=i.oauth,u={};if(c.enabled!==void 0){if(typeof c.enabled!="boolean")throw new Error("oauth.enabled must be a boolean");u.enabled=c.enabled}for(let f of["accessTokenTtlSec","refreshIdleTtlSec","dcrMaxClients"])if(c[f]!==void 0){if(typeof c[f]!="number"||!Number.isInteger(c[f])||c[f]<1||c[f]>31536e3)throw new Error(`oauth.${f} must be an integer with a minimum of 1`);u[f]=c[f]}let p=c.refreshMaxLifetimeSec;if(p!==void 0){if(typeof p!="number"||!Number.isInteger(p)||p<0||p>31536e4)throw new Error("oauth.refreshMaxLifetimeSec must be a non-negative integer");u.refreshMaxLifetimeSec=p}d.oauth=u}qc(i,"enabled",d),qc(i,"allowWithoutAdminAuth",d),zc(i,"defaultLinkTtlSeconds",d),zc(i,"maxActiveLinks",d)}catch(c){o.status(400).json({error:c.message,code:"invalid_body"});return}if(i.defaultDeploymentType!==void 0){let c=i.defaultDeploymentType;if(c!=="nodes"&&c!=="flows"&&c!=="full"){o.status(400).json({error:"defaultDeploymentType must be 'nodes', 'flows', or 'full'",code:"invalid_body"});return}d.defaultDeploymentType=c}try{i.publicBaseUrl!==void 0&&(d.publicBaseUrl=mn(i.publicBaseUrl)),Fy(i,d),Oy(i,d,l);for(let c of["rateLimitWindowMs","rateLimitMaxPerIp","rateLimitMaxPerToken"])Ny(i,c,d)}catch(c){o.status(400).json({error:c.message,code:"invalid_body"});return}try{let c=t.patch(d,l);Xr(c.publicBaseUrl),n.configureDefaults({defaultTtlSeconds:c.defaultLinkTtlSeconds,maxActiveLinks:c.maxActiveLinks});let u=Gc(e,t,r),p=c.publicBaseUrl?new URL(c.publicBaseUrl):void 0,f=p?Dt(p.hostname.replace(/^\[|\]$/g,"")):void 0,m=f?Dr(f):void 0,h=[];m!==void 0&&m!=="public"&&h.push("Public URL uses a private or non-public IP address.");let g=Wc(c.publicBaseUrl);g&&c.publicBaseUrl&&h.push(g),o.json({...u,...h.length?{warnings:h}:{}})}catch(c){if(c instanceof ce){o.status(c.statusCode).json({error:c.message,code:c.code});return}B(o,c);return}}}a(Jc,"putSettingsHandler");function Vc(e){return async(t,n)=>{let r=new AbortController,o=setTimeout(()=>r.abort(),5e3),s;try{let i;try{let c=e.settings,p=e.server?.address(),f=t.body?.publicBaseUrl,m=typeof f=="string"?new URL(f.trim()).hostname:"";if((m.toLowerCase().replace(/^\[|\]$/g,"").replace(/\.$/,"")==="localhost"||Dr(m.replace(/^\[|\]$/g,""))==="loopback")&&typeof p=="string")throw new Error("Public URL Check does not support loopback URLs when Node-RED listens on a Unix socket");let g=p&&typeof p=="object"?p.port:c.uiPort??1880;i=await Uc(t.body?.publicBaseUrl,g,r.signal)}catch(c){if(r.signal.aborted)throw c;n.status(400).json({error:c.message,code:"invalid_body"});return}s=i.origin+xe(e.settings.httpAdminRoot);let d=await Bc(i,xe(e.settings.httpAdminRoot),r.signal),l={status:d,ok:d>=200&&d<300};n.json({ok:l.ok,status:l.status,url:s,message:l.ok?"Discovery endpoint is reachable":`Discovery endpoint returned HTTP ${l.status}`})}catch{n.json({ok:!1,status:null,url:s,error:r.signal.aborted?"Public URL check timed out after 5 seconds":"Could not reach the discovery endpoint"})}finally{clearTimeout(o)}}}a(Vc,"checkPublicUrlHandler");ke();Mt();ke();se();Kr();var $y="com55-ai-access",ms=new WeakMap;function Xc(e){let t=new Set;for(let n of String(e??"").split(",")){let[r,...o]=n.split(";").map(d=>d.trim().toLowerCase());if(!r)continue;let s=o.find(d=>d.startsWith("q=")),i=s?Number(s.slice(2)):1;Number.isFinite(i)&&i>0&&t.add(r)}return t}a(Xc,"acceptedMediaTypes");function Zc(e){let t=Xc(e.headers.accept);return t.has("text/event-stream")&&!t.has("text/html")}a(Zc,"isMcpStreamRequest");function My(e,t,n){e.httpAdmin.get("/mcp/api/v1",t,n,Ai(e)),e.httpAdmin.get("/mcp/api/v1/",t,n,Ai(e)),e.httpAdmin.get("/mcp/api/v1/openapi.yaml",t,n,mc()),e.httpAdmin.get("/mcp/api/v1/openapi.json",t,n,gc()),e.httpAdmin.get("/mcp/editor/ai-access-sidebar.js",t,(r,o)=>{o.setHeader("Cache-Control","no-store, no-cache, must-revalidate, max-age=0"),o.setHeader("Pragma","no-cache"),o.setHeader("Expires","0"),o.setHeader("X-AI-Access-Sidebar-Version",$n),o.type("application/javascript").status(200).send(Oo())})}a(My,"registerMcpShellAndDiscovery");var Dy=/^\/mcp\/(?:api\/v1\/proposals|rpc$|a\/[^/]+\/(?:proposals|propose|draft))/;function Qc(e,t={}){let n=hs(e,{...t,linkStore:t.linkStore??is(e)});xd(()=>n.settingsStore.getEffective().proposalTtlDays),ms.get(e)?.();let r=Td(()=>vr(n.proposalStore,n.flowAdapter),500),o=a(()=>{r.cancel(),e.events?.removeListener?.("flows:started",r.trigger)},"cleanup");ms.set(e,o),e.events?.on("flows:started",r.trigger);let s=ol(()=>n.settingsStore.getEffective()),{ip:i,token:d}=s,l=new ds(Ur(e.settings.userDir),()=>n.settingsStore.getEffective().oauth.dcrMaxClients),c=a(()=>n.settingsStore.getEffective().publicBaseUrl,"publicBase"),u=a(()=>`${c().replace(/\/$/,"")}/mcp`,"mcpUrl"),p=new ls(l,u,()=>n.settingsStore.getEffective().oauth.accessTokenTtlSec,()=>n.settingsStore.getEffective().oauth.refreshIdleTtlSec,()=>n.settingsStore.getEffective().oauth.refreshMaxLifetimeSec),f=uc(e,p,c,u);ia(e.httpAdmin,sa(()=>n.settingsStore.getEffective().corsAllowedOrigins));let m=a(A=>(Array.isArray(A)?A:A?[A]:[]).flatMap(S=>{if(typeof S=="string")return[S];let N=S;return N&&typeof N.path=="string"?[{path:N.path,root:typeof N.root=="string"?N.root:void 0}]:[]}),"staticMounts"),h={authorization:[],metadata:[],details:[],httpNodeRootFallback:e.settings.httpNodeRoot===!1||!e.httpNode},g="",y=a(()=>{let A=cc({httpAdmin:e.httpAdmin,httpNode:e.httpNode,httpAdminRoot:e.settings.httpAdminRoot,httpNodeRoot:e.settings.httpNodeRoot,metadataFallback:h.httpNodeRootFallback,eachNode:e.nodes?M=>e.nodes?.eachNode(M):void 0,staticDirs:m(e.settings.httpStatic),httpStaticRoot:e.settings.httpStaticRoot,fileExists:Yc.existsSync}),S=A.authorization,N=A.metadata;h.authorization=S.map(Ki),h.metadata=N.map(Ki),h.details=[...S.map(M=>({...M,kind:"authorization"})),...N.map(M=>({...M,kind:"metadata"}))];let b=[...h.authorization.map(M=>`${M} \u2014 OAuth authorization routes disabled`),...h.metadata.map(M=>`${M} \u2014 OAuth metadata disabled`)],O=b.join(`
7094
+ `);if(O!==g)for(let M of b)e.log?.warn?.(`AI Access OAuth route collision: ${M}`);g=O},"refreshCollisions");y(),e.events?.on("flows:started",y);let w=Ri({windowMs:6e4,maxPerIp:30}),v=a((A,S,N)=>{if(!n.settingsStore.getEffective().oauth.enabled||h.authorization.length||!A.path.startsWith("/mcp/oauth/")){N();return}let b=A.path.replace(/\/+$/,"").toLowerCase(),O=a(()=>gn(e)(A,S,()=>f.server(A,S,N)),"run");b==="/mcp/oauth/authorize"?i(A,S,()=>w(A,S,O)):b.startsWith("/mcp/oauth/consent/")?i(A,S,O):O()},"oauthServerMiddleware");v.oauthOwned=!0,e.httpAdmin.use(v);let L=a((A,S,N)=>{if(!n.settingsStore.getEffective().oauth.enabled||h.metadata.length){N();return}f.metadata(A,S,N)},"metadataHandler");L.oauthOwned=!0,h.httpNodeRootFallback?e.httpAdmin.use("/mcp",L):e.httpNode?.use(L),e.httpAdmin.get("/mcp/api/v1/oauth/clients",i,d,Se(e,"api"),(A,S)=>S.json({clients:l.data.clients.map(N=>({client_id:N.client_id,client_name:N.client_name,redirect_uris:N.redirect_uris,lastUsedAt:N.lastUsedAt}))})),e.httpAdmin.get("/mcp/api/v1/oauth/status",i,d,Se(e,"api"),(A,S)=>{let N=!re(e.settings)||gt(A)==="*";S.json({...h,details:N?h.details:h.details.map(({file:b,...O})=>O),collisions:[...h.authorization,...h.metadata],oauthEnabled:n.settingsStore.getEffective().oauth.enabled})}),e.httpAdmin.delete("/mcp/api/v1/oauth/clients/:id",i,d,Se(e,"api"),yn(e),(A,S)=>S.json({revoked:l.revokeClient(String(A.params.id))})),e.httpAdmin.use((A,S,N)=>{if(!Dy.test(A.path)){N();return}n.flowAdapter.getFlows().then(b=>Ba(A,ho(b.flows))).catch(()=>{}).finally(()=>N())}),e.httpAdmin.use(Kl(n.auditLog)),_l(e,{linkStore:n.linkStore,proposalStore:n.proposalStore,auditLog:n.auditLog,flowAdapter:At(n.flowAdapter),nodeTypesAdapter:n.nodeTypesAdapter,...n.catalogueFetcher?{catalogueFetcher:n.catalogueFetcher}:{},...n.catalogueUrl?{catalogueUrl:n.catalogueUrl}:{},rateLimiters:s}),e.httpAdmin.get("/ai-access",i,d,(A,S)=>{S.append("Content-Security-Policy","form-action 'none'; frame-ancestors 'none'"),S.setHeader("X-Frame-Options","DENY"),S.setHeader("Cache-Control","no-store, no-cache, must-revalidate, max-age=0"),S.setHeader("Pragma","no-cache"),S.setHeader("Expires","0"),S.type("html").status(200).send(vc({apiBase:xe(e.settings.httpAdminRoot),mcpRpc:mt(e.settings.httpAdminRoot),editorRoot:Ae(e.settings.httpAdminRoot),authTokenKey:Qr(e.settings.httpAdminRoot),dashboardPath:rr(e.settings.httpAdminRoot)}))}),e.httpAdmin.get("/mcp",i,(A,S,N)=>{if(Zc(A)){N();return}S.setHeader("Cache-Control","no-store"),S.redirect(302,rr(e.settings.httpAdminRoot))}),My(e,i,d),e.httpAdmin.get("/mcp/api/v1/status",i,d,pa(e,A=>n.linkStore.validateResult(A)),$((A,S)=>{S.json(qn(e,A))})),e.httpAdmin.get("/mcp/api/v1/settings",i,d,Se(e,"api"),$(Kc(e,n.settingsStore))),e.httpAdmin.put("/mcp/api/v1/settings",i,d,Se(e,"api"),yn(e),je("rest"),$(Jc(e,n.settingsStore,n.linkStore))),e.httpAdmin.post("/mcp/api/v1/settings/check-public-url",i,d,Se(e,"api"),yn(e),je("rest"),$(Vc(e)));let P=[i,d,Se(e,"api"),gn(e),yn(e)];e.httpAdmin.get("/mcp/api/v1/links",...P,$(Ml(e,n.linkStore))),e.httpAdmin.post("/mcp/api/v1/links",...P,$(Dl(e,n.linkStore,n.auditLog))),e.httpAdmin.delete("/mcp/api/v1/links/:id",...P,$(Hl(e,n.linkStore,n.auditLog)));let I=[i,d,gn(e),ua(e,A=>n.linkStore.validateResult(A))],D=n.flowAdapter,T=n.nodeTypesAdapter,x={nodeTypes:T,...n.catalogueFetcher?{catalogueFetcher:n.catalogueFetcher}:{},...n.catalogueUrl?{catalogueUrl:n.catalogueUrl}:{}};e.httpAdmin.get("/mcp/api/v1/flows",...I,$(Jd(D))),e.httpAdmin.get("/mcp/api/v1/flows/:id",...I,$(Ko(D))),e.httpAdmin.get("/mcp/api/v1/nodes/:id",...I,$(Jo(D))),e.httpAdmin.get("/mcp/api/v1/nodes/:id/field/:name",...I,$(Vo(D))),e.httpAdmin.get("/mcp/api/v1/search",...I,$(Yo(D))),e.httpAdmin.get("/mcp/api/v1/node-types",...I,$(Xo(x))),e.httpAdmin.get("/mcp/api/v1/node-types/:type",...I,$(Zo(x))),e.httpAdmin.get("/mcp/api/v1/palette/search",...I,$(Qo(x)));let C=e.settings.httpAdminRoot;e.httpAdmin.get("/mcp/api/v1/proposals",...I,$(Ac(n.proposalStore,n.flowAdapter,C))),e.httpAdmin.get("/mcp/api/v1/proposals/:id",...I,$(xc(n.proposalStore,C,n.flowAdapter))),e.httpAdmin.get("/mcp/api/v1/proposals/:id/diff",...I,$(Pc(n.proposalStore,n.flowAdapter))),e.httpAdmin.post("/mcp/api/v1/proposals",i,d,je("rest"),bn(e,n.linkStore,"rest"),$(kc(n.proposalStore,n.auditLog,n.flowAdapter,C,n.nodeTypesAdapter))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/approve",...P,je("rest"),$(Sc(n.proposalService,C))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/reject",...P,je("rest"),$(Tc(n.proposalService,C))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/withdraw",...I,(A,S,N)=>{A.accessLink?N():Se(e,"api")(A,S,()=>yn(e)(A,S,N))},je("rest"),$(Rc(n.proposalStore,n.auditLog,C))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/resolve-conflict",...P,je("rest"),$(Cc(n.proposalStore,n.auditLog,D,C))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/recheck",...P,$(Ec(n.proposalStore,D,C))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/resolve",i,d,je("rest"),bn(e,n.linkStore,"rest"),$(_c(n.proposalStore,n.auditLog,D,C,T)));let E=[i,d,(A,S,N)=>{let b=A.headers.authorization;if(!n.settingsStore.getEffective().oauth.enabled||b?.toLowerCase().startsWith("bearer aia_")){if(n.settingsStore.getEffective().oauth.enabled){let O=S.setHeader.bind(S);S.setHeader=((M,F)=>{if(M.toLowerCase()==="www-authenticate"&&typeof F=="string"&&!F.includes("resource_metadata=")){let le=`${c().replace(/\/$/,"")}${h.httpNodeRootFallback?"/mcp":""}/.well-known/oauth-protected-resource/mcp`;F+=`, resource_metadata="${le}", scope="read propose"`}return O(M,F)})}bn(e,n.linkStore,"mcp")(A,S,N);return}gn(e)(A,S,()=>f.auth(A,S,N))}];e.httpAdmin.post("/mcp/rpc",...E,je("mcp"),$(ji(e,n))),e.httpAdmin.get("/mcp/rpc",...E,Nr),e.httpAdmin.delete("/mcp/rpc",...E,Nr),e.httpAdmin.post("/mcp",...E,je("mcp"),$(ji(e,n))),e.httpAdmin.get("/mcp",...E,Nr),e.httpAdmin.delete("/mcp",...E,Nr),e.httpAdmin.get("/mcp/api/v1/logs",i,d,Se(e,"api"),gn(e),$(jl(n.auditLog,n.linkStore))),e.httpAdmin.use("/mcp/api/v1",Wd()),Ll(e)}a(Qc,"registerRoutes");function Hy(e){e.plugins.registerPlugin($y,{type:"com55-ai-access",onremove(){ms.get(e)?.(),ms.delete(e)},onadd(){Qc(e),e.log?.info?.("Node-RED AI Access plugin loaded (dashboard /ai-access, MCP /mcp)")}})}a(Hy,"registerAiAccessPlugin");var jy=Hy;0&&(module.exports={ACCESS_LINK_INVALID_MESSAGE,ACCESS_LINK_MISSING_MESSAGE,AccessLinkStore,EDITOR_SIDEBAR_ASSET_VERSION,MAX_PROPOSAL_JSON_BYTES,SettingsStore,acceptedMediaTypes,areAiFeaturesEnabled,bindSettingsStore,createPluginServices,getStorageDir,isAdminAuthEnabled,isMcpStreamRequest,packageVersion,registerRoutes,renderEditorSidebarJs,renderEditorSidebarLoaderHtml,requireAccessLink,resolveAiAccessSettings});