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

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,12 +1,15 @@
1
- "use strict";var kd=Object.create;var dn=Object.defineProperty;var Ad=Object.getOwnPropertyDescriptor;var xd=Object.getOwnPropertyNames;var Sd=Object.getPrototypeOf,Td=Object.prototype.hasOwnProperty;var a=(e,t)=>dn(e,"name",{value:t,configurable:!0});var U=(e,t)=>()=>(e&&(t=e(e=0)),t);var ds=(e,t)=>{for(var n in t)dn(e,n,{get:t[n],enumerable:!0})},ls=(e,t,n,r)=>{if(t&&typeof t=="object"||typeof t=="function")for(let o of xd(t))!Td.call(e,o)&&o!==n&&dn(e,o,{get:()=>t[o],enumerable:!(r=Ad(t,o))||r.enumerable});return e};var ln=(e,t,n)=>(n=e!=null?kd(Sd(e)):{},ls(t||!e||!e.__esModule?dn(n,"default",{value:e,enumerable:!0}):n,e)),Pd=e=>ls(dn({},"__esModule",{value:!0}),e);function Be(e,t,n){let r=n.pretty?`${JSON.stringify(e,null,2)}
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)}
2
2
  `:`${JSON.stringify(e)}
3
- `,o=Buffer.from(r,"utf8");if(o.byteLength>t)throw new X(n.kind,o.byteLength,t);return{bytes:o}}function us(e,t){let n=(0,cn.resolve)(t),r=(0,cn.resolve)(e);if(r!==n&&!r.startsWith(n+cn.sep))throw new Error("Path escapes .ai-access storage root");return r}function Rt(e,t,n){let r=us(t,e),o=`${r}.${process.pid}.tmp`;(0,Me.writeFileSync)(o,n.bytes,{mode:Hn}),(0,Me.renameSync)(o,r),(0,Me.chmodSync)(r,Hn)}function ps(e,t,n){let r=us(t,e);(0,Me.appendFileSync)(r,n.bytes,{mode:Hn});try{(0,Me.chmodSync)(r,Hn)}catch{}}var Me,cn,Yr,Hn,j,cs,X,G=U(()=>{"use strict";Me=require("fs"),cn=require("path"),Yr=448,Hn=384,j=15e5,cs=8192,X=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(Be,"encodeTrustedJson");a(us,"assertUnderAiAccess");a(Rt,"writeTrustedAtomic");a(ps,"appendTrusted")});function pn(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 Ed(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 ms(e){return(t,n,r)=>{if(!Rd.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=pn(t.get("origin")),d=i===void 0?void 0:e().find(c=>c===i),l=d!==void 0;if(Ed(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",Cd),t.method==="OPTIONS"&&t.get("access-control-request-method")){n.setHeader("Access-Control-Allow-Methods",_d),n.setHeader("Access-Control-Allow-Headers",Ld),n.setHeader("Access-Control-Max-Age",String(600)),n.status(204).end();return}s=!1,r()}}function hs(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 Rd,_d,Ld,Cd,qn=U(()=>{"use strict";Rd=/^\/mcp(?:\/?$|\/(?:a(?:\/|$)|api(?:\/|$)|rpc\/?$))/,_d="GET, POST, DELETE, OPTIONS",Ld="Authorization, Content-Type, Accept, Mcp-Session-Id, Mcp-Protocol-Version, Last-Event-ID",Cd="Mcp-Session-Id, WWW-Authenticate, Retry-After, RateLimit, RateLimit-Policy, RateLimit-Limit, RateLimit-Remaining, RateLimit-Reset";a(pn,"canonicalOrigin");a(Ed,"appendVary");a(ms,"aiCorsMiddleware");a(hs,"installAiCorsMiddleware")});function Nd(e){return e==="nodes"||e==="flows"||e==="full"}function Wn(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)),Nd(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=Ct(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=pn(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.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 Fd(e,t){if(!(0,mn.existsSync)(e))return{};try{let n=(0,mn.readFileSync)(e,"utf8");return Wn(JSON.parse(n),t)}catch(n){return t?.(`AI Access: config.json is corrupt or unreadable; defaults are used (${String(n)})`),{}}}function zn(e,t){let n={},r={...fn};for(let s of Object.keys(fn).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]??fn[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 eo(e,t){let n=Wn(e??{},t);for(let o of Object.keys(fn).filter(s=>s!=="oauth"))e&&Object.hasOwn(e,o)&&n[o]===void 0&&Object.assign(n,{[o]:fn[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 mn,Zr,Id,_t,fn,Lt,Gn=U(()=>{"use strict";mn=require("fs"),Zr=require("path");ie();qn();G();Id=16384,_t={enabled:!1,accessTokenTtlSec:3600,refreshIdleTtlSec:2592e3,refreshMaxLifetimeSec:7776e3,dcrMaxClients:5},fn={enabled:!0,allowWithoutAdminAuth:!1,defaultLinkTtlSeconds:14400,maxActiveLinks:10,defaultDeploymentType:"nodes",publicBaseUrl:"",corsAllowedOrigins:[],rateLimitWindowMs:6e4,rateLimitMaxPerIp:600,rateLimitMaxPerToken:300,oauth:{..._t}};a(Nd,"isDeploymentType");a(Wn,"sanitizePartial");a(Fd,"readDiskFile");a(zn,"mergeAiAccessSettings");a(eo,"settingsJsPartial");Lt=class{constructor(t,n,r){this.paths=t;this.filePath=(0,Zr.join)(t.root,"config.json"),this.settingsJs=eo(n,r),this.disk=Fd((0,mn.existsSync)(this.filePath)?this.filePath:(0,Zr.join)(t.root,"settings.json"),r),this.cached=zn(this.settingsJs,this.disk)}static{a(this,"SettingsStore")}filePath;disk;settingsJs;cached;getEffective(){return this.cached}getDisk(){return{...this.disk}}update(t){let n=Wn(t);for(let r of Object.keys(this.settingsJs))delete n[r];return this.persist(n),this.disk=n,this.cached=zn(this.settingsJs,this.disk),this.cached}patch(t){let n=Wn(t);return this.update({...this.disk,...n,...n.oauth?{oauth:{...this.disk.oauth,...n.oauth}}:{}})}persist(t){let n=Be(t,Id,{kind:"settings",pretty:!0});Rt(this.paths.root,this.filePath,n)}}});function Jn(e){to=e}function W(e){return e.adminAuth!=null&&e.adminAuth!==!1}function he(e){return to?to.getEffective():zn(eo(e.aiAccess),{})}function ge(e){let t=he(e);return t.enabled?W(e)?!0:t.allowWithoutAdminAuth:!1}var to,ye=U(()=>{"use strict";Gn();to=null;a(Jn,"bindSettingsStore");a(W,"isAdminAuthEnabled");a(he,"resolveAiAccessSettings");a(ge,"areAiFeaturesEnabled")});function ae(e){let t=`/${e??""}`.replace(/\/{2,}/g,"/");for(;t.length>1&&t.endsWith("/");)t=t.slice(0,-1);return t}function de(e){let t=ae(e);return t==="/"?"/mcp/api/v1":`${t}/mcp/api/v1`}function hn(e){let t=ae(e);return t==="/"?"/ai-access":`${t}/ai-access`}function qe(e){let t=ae(e);return t==="/"?"/mcp/rpc":`${t}/mcp/rpc`}function Od(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 We(e){if(e.auth)return e.auth.scopes.includes("propose")?"propose":e.auth.scopes.includes("read")?"read":null;let t=Od(e);return t.includes("*")?"*":t.includes("read")?"read":null}function $d(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 gs(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 we(e,t){let n="read";return(r,o,s)=>{if(!W(e.settings)){s();return}let i=t==="api"?gs(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(!We(r)){o.status(401).json({error:"Insufficient permissions",code:"insufficient_permissions"});return}s()})}}function Kn(e,t){let n=W(e.settings),r=he(e.settings),o=ge(e.settings),s=null;return n?s=We(t):o&&(s="*"),{adminAuthEnabled:n,aiFeaturesEnabled:o,allowWithoutAdminAuth:r.allowWithoutAdminAuth,permission:s,capabilities:$d(s,o)}}function Et(e){return(t,n,r)=>{if(!ge(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 gn(e){return(t,n,r)=>{if(!W(e.settings)){if(ge(e.settings)){r();return}n.status(403).json({error:"AI Access features are disabled",code:"ai_features_disabled"});return}if(We(t)!=="*"){n.status(403).json({error:"Full admin permission (*) is required for this action",code:"insufficient_permissions"});return}r()}}function Md(e){return e.startsWith("aia_")}function Dd(e){return Md(e)?!0:e.length<24}function jd(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 ys(e,t,n,r){if(!W(e.settings)){r();return}let o={error:le,code:Le},s=gs(n,o,Y);e.auth.needsPermission("read")(t,n,i=>{if(s(),i){r(i);return}if(!n.headersSent){if(!t.user||!We(t)){n.setHeader("WWW-Authenticate",Y),n.status(401).json(o);return}r()}})}function ws(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?(bn(t,r,l.link.kind),"wrong_kind"):(e.accessLink=l.link,"ok");if(l.status==="expired")return wn(e,t,o,l.expiresAt),"invalid";if(Dd(d)){t.setHeader("WWW-Authenticate",Y);let c=jd(d);return t.status(401).json({error:st,code:yn,...c?{hint:c}:{}}),"invalid"}return"continue"}function bs(e,t,n="rest"){return(r,o,s)=>{if(!ge(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=ws(r,o,t,n,e.settings.httpAdminRoot);if(i==="ok"){s();return}i==="invalid"||i==="wrong_kind"||ys(e,r,o,s)}}function vs(e,t,n="rest"){return(r,o,s)=>{let i=ws(r,o,t,n,e.settings.httpAdminRoot);if(i==="ok"){s();return}i==="invalid"||i==="wrong_kind"||ys(e,r,o,s)}}var _e=U(()=>{"use strict";it();ye();a(ae,"normalizeHttpAdminRoot");a(de,"mcpApiBase");a(hn,"dashboardPath");a(qe,"mcpRpcPath");a(Od,"scopesFromRequest");a(We,"resolvePermission");a($d,"capabilitiesFor");a(gs,"interceptNeedsPermissionUnauthorized");a(we,"requireAdminSession");a(Kn,"describeAccess");a(Et,"requireAiFeatures");a(gn,"requireStarPermission");a(Md,"looksLikeAccessLinkToken");a(Dd,"looksLikeFailedAccessLinkAttempt");a(jd,"truncatedTokenHint");a(ys,"continueAsAdminSessionOrAccessLinkMissing");a(ws,"tryAccessLinkBearer");a(bs,"requireAdminReadOrAccessLink");a(vs,"requireAdminSessionOrAccessLink")});function Hd(e,t){let n=ae(e);return`${n==="/"?"/mcp/a":`${n}/mcp/a`}/${t}`}function Ud(e){return!!e.app.get("trust proxy")}function ks(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 Bd(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 at(e){try{let n=new URL(`http://${e.includes(":")&&!e.startsWith("[")?`[${e}]`:e}`).hostname.replace(/^\[|\]$/g,"");if(!(0,no.isIP)(n))return;if(n.startsWith("::ffff:")){let r=n.slice(7);if((0,no.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 Ct(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 Vn(e){ro=Bd(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],!zd.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)||!Wd.test(t)&&!qd.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 Ts(e){let t=e?.trim().toLowerCase();return t==="http"||t==="https"?t:void 0}function Gd(e){let t=e.socket;return t?.encrypted?"https":t?"http":Ts(e.protocol)??"http"}function oo(e){if(ro)return ro;let t=Ud(e),n=t?ks(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?Ts(ks(e,"x-forwarded-proto")):void 0)??Gd(e);try{return new URL(`${s}://${o}`).origin}catch{return}}function vn(e,t){let n=oo(e);return n?`${n}${t}`:t}function Ps(e,t,n){return vn(e,Hd(t,n))}function so(e){return e.replace(/\/mcp\/a\/[^/?#]+/gi,"/mcp/a/<redacted>")}var no,It,Ss,ro,As,qd,Wd,zd,ie=U(()=>{"use strict";no=require("net");_e();It=8192,Ss=1800*1e3;a(Hd,"mcpAccessLinkPath");a(Ud,"isTrustProxyEnabled");a(ks,"firstForwardedValue");a(Bd,"normalizePublicBaseUrl");a(at,"canonicalizeIp");a(Ct,"validateSettingsPublicBaseUrl");a(Vn,"bindPublicBaseUrl");As="[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?",qd=new RegExp(`^${As}(?:\\.${As})*$`),Wd=/^(?:(?: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)$/,zd=/^\[[0-9A-Fa-f:.]+\]$/;a(xs,"isValidHostHeader");a(Ts,"validScheme");a(Gd,"connectionScheme");a(oo,"requestPublicOrigin");a(vn,"absolutizePath");a(Ps,"buildAccessLinkUrl");a(so,"redactAccessLinkTokenInPath")});function Qd(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 io(e,t,n,r){e.setHeader("WWW-Authenticate",Y),e.status(401).json({error:t,code:n,...r?{hint:r}:{}})}function Zd(e){let t=e??"";for(;t.endsWith("/");)t=t.slice(0,-1);return`${t}/ai-access#links`}function el(e,t,n,r){let o=vn(e,Zd(t));return{error:Jd,code:Vd,...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 wn(e,t,n,r,o){t.setHeader("WWW-Authenticate",Y),t.status(401).json(el(e,n,r,o))}function tl(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 Xn(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 rl(e,t){let n=nl;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: ${Xn("get_link")}`:e==="rest"?`This REST endpoint requires a rest token. This token is kind=${t} \u2014 ${n}. What to tell the user: ${Xn("rest")}`:`This MCP endpoint requires an mcp token. This token is kind=${t} \u2014 ${n}. What to tell the user: ${Xn("mcp")}`}function bn(e,t,n){e.setHeader("WWW-Authenticate",Y),e.status(403).json({error:`Access link kind mismatch: expected ${t}, got ${n}`,code:Yd,expectedKind:t,actualKind:n,hint:rl(t,n),tellUser:Xn(t)})}function Nt(e,t,n){return(r,o,s)=>{if(!ge(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=Qd(r.headers.authorization);if(i.kind==="basic"){io(o,Kd,Xd);return}if(i.kind==="missing"||i.kind==="other"){io(o,le,Le);return}let d=t.validateResult(i.token);if(d.status==="expired"){wn(r,o,e.settings.httpAdminRoot,d.expiresAt);return}if(d.status!=="active"){io(o,st,yn,tl(i.token));return}if(d.link.kind!==n){bn(o,n,d.link.kind);return}r.accessLink=d.link,s()}}var st,Jd,le,Kd,Y,Le,yn,Vd,Xd,Yd,nl,it=U(()=>{"use strict";ie();ye();st="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.",Jd="Access link token has expired. Ask the user for a new access link.",le="Access link Bearer token is missing. Ask the user for a new access link and send Authorization: Bearer <token>.",Kd="Authorization must use the Bearer scheme (Authorization: Bearer <access-link-token>), not Basic. Ask the user for a new access link.",Y='Bearer realm="node-red-ai-access", charset="UTF-8"',Le="access_link_missing",yn="access_link_invalid",Vd="token_expired",Xd="bearer_scheme_required",Yd="wrong_token_kind";a(Qd,"parseAuthorizationHeader");a(io,"sendUnauthorized");a(Zd,"accessLinksPagePath");a(el,"tokenExpiredBody");a(wn,"sendTokenExpired");a(tl,"truncatedBearerHint");nl="ask the owner: Node-RED /ai-access \u2192 Access tokens \u2192 create kind=rest/mcp (or get_link)";a(Xn,"whatToTellUserWrongTokenKind");a(rl,"wrongTokenKindHint");a(bn,"sendWrongTokenKind");a(Nt,"requireAccessLink")});var J,z,be,Ce,Qn,ve=U(()=>{"use strict";J=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}},z=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}},be=class extends Error{static{a(this,"AdapterUnavailableError")}code="adapter_unavailable";statusCode=502;constructor(t){super(t),this.name="AdapterUnavailableError"}},Ce=class extends Error{static{a(this,"PluginMisconfiguredError")}code="plugin_misconfigured";statusCode=502;constructor(t){super(t),this.name="PluginMisconfiguredError"}},Qn="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 Ee(e,t){if(!e||!ol.test(e)||e.includes(".."))throw new Error(`Invalid ${t}`);return e}function Zn(e){if(!e||!sl.test(e)||e.includes(".."))throw new Error("Invalid proposal id");return e}function ao(e,t){let n=(0,dt.basename)(t);if(!n||n!==t||n.includes(".."))throw new Error("Invalid file name");let r=(0,dt.resolve)(e),o=(0,dt.resolve)(r,n);if(o!==r&&!o.startsWith(r+dt.sep))throw new Error("Path escapes storage directory");return o}function Rs(e,t=1e5){if(typeof e=="string")return e.replace(/\0/g,"").slice(0,t)}function Ft(e){let t=e&&typeof e=="object"&&!Array.isArray(e)?e:{},n={};for(let o of il){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=Rs(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>j)throw new X(`flow.${o}`,d,j);n[o]=JSON.parse(i)}let r=Buffer.byteLength(JSON.stringify(n),"utf8");if(r>j)throw new X("flow",r,j);return n}function _s(e){let t={};for(let n of al){if(!(n in e))continue;let r=e[n];if(dl.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=Rs(r,2e3);o!==void 0&&(t[n]=o)}return t}var dt,ol,sl,il,al,dl,er=U(()=>{"use strict";dt=require("path");G();ol=/^[A-Za-z0-9_.-]+$/,sl=/^prop-[A-Za-z0-9_-]+$/,il=["id","type","label","disabled","info","env"],al=["proposalId","flowId","expectedRev","resultRev","reason","code","currentRev","title","displayName","flowLabel","linkId","label","expiresAt","createdAt","ttlSeconds","kind"],dl=new Set(["ttlSeconds"]);a(Ee,"assertSafeSegment");a(Zn,"assertSafeProposalId");a(ao,"resolveUnderDir");a(Rs,"sanitizeString");a(Ft,"sanitizeFlowTab");a(_s,"sanitizeAuditDetails")});function lo(e,t){return`oauth-${(0,nr.createHash)("sha256").update(`${e}\0${t}`).digest("hex")}`}function lt(e){return e.kind==="oauth"?lo(e.clientId,e.user):e.linkId}function ll(e){return e==="pending"||e==="approved"||e==="rejected"||e==="failed"||e==="withdrawn"||e==="expired"||e==="superseded"}function Ls(e){if(!Array.isArray(e)||e.length===0)return;let t=[];for(let n of e.slice(0,cl)){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 nr,ze,Cs,ne,te,Ot,kn,cl,tr,Ge=U(()=>{"use strict";nr=require("crypto"),ze=require("fs"),Cs=require("path");er();G();a(lo,"oauthOwnerId");a(lt,"proposalOwnerId");ne=class extends Error{static{a(this,"ProposalNotFoundError")}code="proposal_not_found";statusCode=404;constructor(t){super(`Proposal '${t}' was not found`),this.name="ProposalNotFoundError"}},te=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"}},Ot=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"}},kn=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(ll,"isProposalStatus");cl=50;a(Ls,"sanitizeProposalWarnings");tr=class{constructor(t,n={}){this.paths=t;this.now=n.now??(()=>Date.now()),this.randomId=n.randomId??(()=>(0,nr.randomBytes)(12).toString("hex")),this.maxProposalBytes=n.maxProposalBytes??j}static{a(this,"ProposalStore")}inFlight=new Set;now;randomId;maxProposalBytes;fileFor(t){let n;try{n=Zn(t)}catch{throw new ne(t)}return ao(this.paths.proposals,`${n}.json`)}toDiskRecord(t){let n=Zn(t.id),r=Ee(String(t.expectedRev).slice(0,200),"expectedRev"),o=Ee(String(t.payload.flowId).slice(0,200),"flowId"),s=Ft(t.payload.flow),i=t.baseFlow!==void 0?Ft(t.baseFlow):void 0,d=t.createdBy.kind==="oauth"?{kind:"oauth",clientId:Ee(String(t.createdBy.clientId).slice(0,200),"clientId"),user:String(t.createdBy.user).replace(/\0/g,"").slice(0,200)}:{kind:"access-link",linkId:Ee(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:ll(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=Ls(t.warnings);if(c&&(l.warnings=c),t.decidedBy)if(t.decidedBy.kind==="access-link"){let u={kind:"access-link",linkId:Ee(String(t.decidedBy.linkId).slice(0,200),"linkId")};typeof t.decidedBy.label=="string"&&(u.label=t.decidedBy.label.replace(/\0/g,"").slice(0,200)),l.decidedBy=u}else{let u={kind:"admin"};typeof t.decidedBy.username=="string"&&(u.username=t.decidedBy.username.replace(/\0/g,"").slice(0,200)),l.decidedBy=u}return typeof t.reason=="string"&&(l.reason=t.reason.replace(/\0/g,"").slice(0,2e3)),typeof t.resultRev=="string"&&(l.resultRev=Ee(t.resultRev.slice(0,200),"resultRev")),l}writeAtomic(t,n){let r=this.fileFor(t),o=this.toDiskRecord(n),s=Be(o,this.maxProposalBytes,{kind:"proposal",pretty:!0});Rt(this.paths.root,r,s)}create(t){let n=new Date(this.now()).toISOString(),r=Zn(`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=Ls(t.warnings);return s&&(o.warnings=s),this.writeAtomic(r,o),this.get(r)}get(t){let n=this.fileFor(t);if(!(0,ze.existsSync)(n))throw new ne(t);return JSON.parse((0,ze.readFileSync)(n,"utf8"))}list(t){if(!(0,ze.existsSync)(this.paths.proposals))return[];let n=(0,ze.readdirSync)(this.paths.proposals).filter(o=>o.endsWith(".json")),r=[];for(let o of n){let s=(0,Cs.basename)(o);if(!(s!==o||s.includes("..")))try{let i=JSON.parse((0,ze.readFileSync)(ao(this.paths.proposals,s),"utf8"));if(t?.linkId&&lt(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 kn(t);let n=this.get(t);if(n.status!=="pending")throw new te(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 te(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 te(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 Es(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 rr(e,t,n){let r=e.find(d=>d.id===t);if(!r||r.type!=="tab"&&r.type!=="subflow")throw new z(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({...Es(l,`flowData.nodes[${d}]`),z:t});for(let[d,l]of(n.configs??[]).entries())s.push({...Es(l,`flowData.configs[${d}]`),z:t});return[...e.filter(d=>d.id!==t&&d.z!==t),o,...s]}function An(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 $t(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 z(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 z(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 or=U(()=>{"use strict";ve();a(Es,"asFlowNode");a(rr,"replaceFlowTabInFlatFlows");a(An,"assertFlowsSnapshot");a($t,"normalizeFlowTab")});function fo(e){return e?ul.has(e)?!0:e.startsWith("subflow:"):!1}function Je(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 wl(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 bl(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 kl(e){return Math.max(2,Math.floor(e.length/3))}function mo(e,t,n=5){if(typeof e!="string"||!e)return[];let r=kl(e);return t.filter(s=>typeof s=="string"&&s!==e&&!vl.has(s)&&!s.startsWith("_")&&!s.startsWith("subflow:")).map(s=>({c:s,dist:wl(e,s),prefix:bl(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 Al(e){return e.endsWith("/")?e.slice(0,-1):e}function Ds(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,Ms.createRequire)(e)}function xl(){try{let t=Ds()("@node-red/runtime/lib/settings");return typeof t.available=="function"&&t.available()===!0}catch{return!1}}function js(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 ho(e=yl,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 Hs(e={}){if(xl())try{let n=Ds()("@node-red/runtime");if(n.nodes&&typeof n.nodes.getNodeList=="function")return new po(n.nodes)}catch{}if(e.baseUrl)return new uo({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 go(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 Sl(e,t){let n=t.toLowerCase();return[e.type,e.module,e.category??"",e.description??""].join(`
4
- `).toLowerCase().includes(n)}async function ir(e,t={}){let n=t.limit??pl,{byType:r}=await go(e),o=[...r.values()].sort((u,p)=>u.type.localeCompare(p.type)),s=t.q?.trim();s&&(o=o.filter(u=>Sl(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 Tl(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 Ns(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 Pl(e,t=2e3){let n=Ns(e,"script");return n=Ns(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 Rl(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:Fs(e,d,i)}return Fs(e,n.index,r)}catch{return null}}function Fs(e,t,n){let r=e.slice(t),o=r.indexOf("{");if(o<0)return{type:n};let s=xn(r.slice(o));if(!s)return{type:n};let i=Os(s,"category"),d=Os(s,"color"),l=$s(s,"inputs"),c=$s(s,"outputs"),u=Ll(s),p=[];if(u){for(let g of Object.values(u))if(g&&typeof g=="object"&&!Array.isArray(g)){let x=g.type;typeof x=="string"&&x.length>0&&p.push(x)}}let f,m=Tl(e,n);m&&(f=Pl(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 _l(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 xn(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 Os(e,t){let n=new RegExp(`\\b${t}\\s*:\\s*['"]([^'"]+)['"]`);return e.match(n)?.[1]}function $s(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 Ll(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=xn(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=xn(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=Il(m)}s[l]=u}return Object.keys(s).length>0?s:void 0}function Cl(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 El(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=Cl(t,o+1);s+=d.char,o=d.next;continue}if(i===r)break;s+=i,o+=1}return El(s)}if(t.startsWith("[")){let r=Us(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=co(d);i+=c>0?c:1}return s}if(t.startsWith("{")){let r=xn(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+=co(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+=co(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 co(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=Us(t);return n+(o?.length??1)}if(t.startsWith("{")){let o=xn(t);return n+(o?.length??1)}let r=/^(?:-?\d+(?:\.\d+)?|true|false|null)/.exec(t);return r?n+r[0].length:n+1}function Us(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 Il(e){return sr(e)}function Nl(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 yo(e,t){let n=e.toLowerCase();return t.includes(e)?e:t.find(r=>r.toLowerCase()===n)}async function ct(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=Nl(o,t);if(!i){let l=yo(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:mo(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=Rl(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=_l(c.help)))}}catch{}return d}async function Bs(e,t){let{allNames:n}=await go(e),r=new Set(n),o=new Set,s=a(l=>{let c=Je(l,"node.type");if(o.has(c)||fo(c)){o.add(c);return}if(o.add(c),r.has(c))return;let u=yo(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:mo(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 qs(e,t){let n=Je(t,"node.type");if(fo(n))return;let{allNames:r}=await go(e);if(r.includes(n))return;let o=yo(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:mo(n,r,5)}})}async function Fl(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 Ol(e,t){let n=Date.now();if(ke&&n-ke.fetchedAt<gl)return ke.body;let r=ke?.etag,o=await e(t,r);if(o.status===304&&ke)return ke={...ke,fetchedAt:n,...o.etag?{etag:o.etag}:{}},ke.body;if(!o.body||!Array.isArray(o.body.modules)){if(ke)return ke.body;throw new y("Node-RED catalogue unavailable","catalogue_unavailable",{statusCode:502})}return ke={fetchedAt:n,body:o.body,...o.etag?{etag:o.etag}:{}},o.body}async function ar(e,t,n={}){let r=t.trim();if(!r)throw new y("q query parameter is required","missing_query");let o=n.limit??fl,s=n.catalogueUrl??hl,i=n.catalogueFetcher??Fl,d=await Ol(i,s),l=Array.isArray(d.modules)?d.modules:[],c=await e.listNodeSets(),u=new Set(c.flatMap(g=>{let x=[g.module,g.id];return g.id.includes("/")&&x.push(g.id.split("/")[0]??g.module),x.map(S=>S.toLowerCase())})),p=r.toLowerCase(),f=[];for(let g of l){let x=g.id.toLowerCase(),S=(g.keywords??[]).map(ot=>String(ot).toLowerCase()),E=(g.description??"").toLowerCase(),I=$l(p,x,S,E);if(I<=0)continue;let N={module:g.id,installed:u.has(x)};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:I})}f.sort((g,x)=>x.score-g.score||(x.item.downloads??0)-(g.item.downloads??0)||g.item.module.localeCompare(x.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:ml}}function $l(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 Ms,ul,pl,fl,ml,hl,gl,yl,vl,uo,Is,po,ke,ut=U(()=>{"use strict";Ms=require("module");K();ul=new Set(["tab","subflow","group","junction"]),pl=100,fl=30,ml="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.",hl="https://catalogue.nodered.org/catalogue.json",gl=3600*1e3,yl=["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(Je,"requireNodeTypeName");a(wl,"editDistance");a(bl,"sharedPrefixLength");vl=new Set(["unknown","tab","subflow","group"]);a(kl,"nearestDistanceCutoff");a(mo,"nearestNodeTypes");a(Al,"trimTrailingSlash");a(Ds,"createPluginRequire");a(xl,"isRuntimeSettingsAvailable");a(js,"normalizeNodeSet");a(ho,"createStaticNodeTypesAdapter");uo=class{static{a(this,"HttpNodeTypesAdapter")}baseUrl;accessToken;fetchImpl;constructor(t){this.baseUrl=Al(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=js(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()}},Is={user:null,req:null},po=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(Is))}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=js(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",...Is}));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(Hs,"createNodeTypesAdapter");a(go,"flattenInstalledTypes");a(Sl,"matchesQuery");a(ir,"listNodeTypes");a(Tl,"extractHelpScriptBody");a(Ns,"removeHtmlBlocks");a(Pl,"stripHelpText");a(Rl,"parseRegisterTypeFromHtml");a(Fs,"parseRegisterTypeAt");a(_l,"splitHelpSections");a(xn,"extractBalancedObject");a(Os,"matchStringProp");a($s,"matchNumberProp");a(Ll,"parseDefaultsBlock");a(Cl,"unescapeJsStringEscape");a(El,"normalizeDefaultValue");a(sr,"parseJsLiteral");a(co,"measureJsLiteralLength");a(Us,"extractBalancedArray");a(Il,"parseLooseValue");a(Nl,"findSetForType");a(yo,"findCaseInsensitiveType");a(ct,"getNodeTypeDetail");a(Bs,"assertKnownNodeTypes");a(qs,"assertKnownNodeType");ke=null;a(Fl,"defaultCatalogueFetcher");a(Ol,"loadCatalogue");a(ar,"searchPalette");a($l,"paletteMatchScore")});function Ke(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 wo(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 Ml(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 zs(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 ${Ml(c)}; node ids must be unique across all flows`,nodeId:l})}return o}function Ws(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 jl(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]);for(let r of n)if(!Dl.has(r)&&JSON.stringify(e[r])!==JSON.stringify(t[r]))return!0;return!1}function Hl(e,t){let n=Ws(t);if(!e)return new Set(n.keys());let r=Ws(e),o=new Set;for(let[s,i]of n){let d=r.get(s);(!d||jl(d,i))&&o.add(s)}return o}function Gs(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(...wo(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{Je(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&&Mt(c.wires,n,typeof c.id=="string"?c.id:void 0)}return n}function Mt(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 Ul(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 ct(t,n,{includeHelp:!1})).outputs}catch{return}}function Bl(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 ql(e){return Array.isArray(e)&&e.some(t=>typeof t=="string"&&t.length>0)}function Wl(e,t){return t<=0?!1:!("wires"in e)||e.wires===void 0?!0:Array.isArray(e.wires)?!e.wires.some(ql):!1}async function bo(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 Ul(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(!Mt(d.wires,r,d.id))continue;let p=d.wires;if(l!==void 0){let f=Bl(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&&Wl(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 vo(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 zl(e,t){return e.filter(n=>n.type===t).map(n=>n.id)}async function ko(e,t,n){let r=[],o=[...t];for(let s of e){let i;try{i=Je(s.type,"node.type")}catch{continue}let d;try{d=await ct(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],x=zl(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:x});continue}o.find(E=>E.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:x})}}return r}async function Js(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(...vo(r,n));let d=t.baseFlow?Hl(t.baseFlow,e):void 0,l=await bo(r,t.nodeTypes,{...d?{softWarningNodeIds:d}:{}});if(s.push(...l.errors),i.push(...l.warnings),t.nodeTypes){let c=await ko(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 Dl,Ao=U(()=>{"use strict";K();ut();a(Ke,"throwValidationIssues");a(wo,"collectDuplicateNodeIdIssues");a(Ml,"describeLiveNode");a(zs,"collectCrossFlowDuplicateIssues");Dl=new Set(["x","y","w","h","g","id","z"]);a(Ws,"collectNodesMap");a(jl,"nodeContentChanged");a(Hl,"changedNodeIds");a(Gs,"collectFullFlowShapeIssues");a(Mt,"assertValidWiresShape");a(Ul,"resolveNodeOutputCount");a(Bl,"wireCountIssue");a(ql,"portHasTargets");a(Wl,"nodeIsUnwired");a(bo,"collectWireValidation");a(vo,"collectZMismatchIssues");a(zl,"configCandidates");a(ko,"collectConfigWarnings");a(Js,"validateFlowTab")});function Ve(e,t="node"){return typeof e=="string"&&e.trim()?e.trim():t}function dr(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 Vs(e,t){e[Ks]=t}function Kl(e){if(!e)return;let t=e[Ks];return t instanceof Map?t:void 0}function Vl(e){return typeof e!="string"||!e.trim()?new Set:new Set(e.split(",").map(t=>t.trim()).filter(Boolean))}function Xl(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 re(e){let t=Vl(e.include);return Xl(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=Kl(e);return r&&(n.liveFlowLabels=r),n}function Dt(e){return typeof e.name=="string"&&e.name.trim()?e.name.trim():typeof e.label=="string"&&e.label.trim()?e.label.trim():""}function Sn(e){return`"${e.replace(/"/g,"'")}"`}function jt(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 Yl(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 Ql(e,t){let n=Dt(e),r=Dt(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 xo(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&&Yl(d,i)&&o.push({before:d,after:i})}return{added:n,removed:r,modified:o}}function lr(e){return e===1?"node":"nodes"}function Zl(e){return e.length===1&&e[0]?`adds 1 ${Ve(e[0].type)}`:`adds ${e.length} ${lr(e.length)}`}function ec(e){return`deletes ${e} ${lr(e)}`}function tc(e){return`edits ${e} ${lr(e)}`}function nc(e){let t=[];e.removed>0&&t.push(ec(e.removed));let n=e.added??[],r=e.addedCount??n.length;r>0&&t.push(n.length===r?Zl(n):`adds ${r} ${lr(r)}`),(e.modified??0)>0&&t.push(tc(e.modified??0));let o=t.length?t.join(", "):"replaces nodes",s=e.flowLabel?.trim();return s?`Replace ${s}: ${o}`:`Replace flow: ${o}`}function rc(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=Ve(u.type),f=Dt(u);return f?`Add ${p} node ${Sn(f)}`:`Add ${p} node`}if(n[0]){let u=n[0],p=Ve(u.type),f=Dt(u);return f?`Remove ${p} node ${Sn(f)}`:`Remove ${p} node`}let s=r[0];if(!s)return;let i=Ve(s.after.type,Ve(s.before.type)),d=Dt(s.before),l=Dt(s.after);if(Ql(s.before,s.after)&&d&&l)return`Rename ${i} node ${Sn(d)} \u2192 ${Sn(l)}`;let c=l||d;return c?`Edit ${i} ${Sn(c)}`:`Edit ${i}`}function So(e,t){return typeof e?.label=="string"&&e.label.trim()?e.label.trim():typeof t?.label=="string"&&t.label.trim()?t.label.trim():""}function cr(e,t){if(typeof e.flowLabel=="string"&&e.flowLabel.trim())return e.flowLabel.trim();let n=So(e.payload?.flow,e.baseFlow);if(n)return n;let r=e.payload?.flowId;return r&&t?.has(r)?t.get(r)??"":""}function Xs(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 To(e,t){let n=xo(jt(e??{}),jt(t)),r=n.added.length+n.removed.length+n.modified.length,o=So(t,e);if(r===0)return o?`Update ${o}`:"Update flow";if(n.removed.length>0)return nc({...o?{flowLabel:o}:{},removed:n.removed.length,added:n.added,modified:n.modified.length});if(r>Jl)return o?`${r} changes in ${o}`:`${r} changes`;let s=rc(n)??(o?`Update ${o}`:"Update flow");return Xs(s,o)}function oc(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 Po(e,t){return`${e} \xB7 ${ic(t)}`}function sc(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 ic(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=sc(-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,Gl).trim();return t.length>0?t:void 0}function ac(e,t){let n=ae(t);return`${n==="/"?"/ai-access":`${n}/ai-access`}#proposal=${encodeURIComponent(e)}`}function Ro(e,t={}){let n=cr(e,t.liveFlowLabels),r=ur(e.title),o=To(e.baseFlow,n&&!So(e.payload.flow,e.baseFlow)?{...e.payload.flow,label:n}:e.payload.flow),s=xo(jt(e.baseFlow??{}),jt(e.payload.flow)),i=oc(r,s.removed.length)?o:r??o;return Xs(i,n)}function dc(e){return typeof e.storedTitle=="string"&&e.storedTitle?e.storedTitle:ur(e.title)}function oe(e,t={}){let n=Ro(e,t),r=cr(e,t.liveFlowLabels),o=dc(e)??n,s=ac(e.id,t.httpAdminRoot),i=t.req?vn(t.req,s):s;return{...e,title:n,storedTitle:o,flowLabel:r,displayName:Po(n,e.createdAt),reviewUrl:i,reviewPath:s}}function lc(e){let t=xo(jt(e.baseFlow??{}),jt(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 cc(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 ce(e,t={}){let n=oe(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:cc(n.createdBy),changeSummary:lc(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),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 Ys(e,t={}){return e.map(n=>ce(n,t))}var Gl,Jl,Ks,Ae=U(()=>{"use strict";ie();_e();Gl=80,Jl=1;a(Ve,"nodeTypeText");Ks=Symbol.for("ai-access.liveFlowLabels");a(dr,"liveFlowLabelsFromFlows");a(Vs,"setRequestFlowLabels");a(Kl,"getRequestFlowLabels");a(Vl,"parseIncludeQuery");a(Xl,"isTruthyFlag");a(re,"resolveIncludeFromQuery");a(q,"presentOptionsFor");a(Dt,"nodeLabel");a(Sn,"quoteName");a(jt,"collectNodes");a(Yl,"nodeContentChanged");a(Ql,"isRenameOnly");a(xo,"classifyFlowChanges");a(lr,"pluralNodes");a(Zl,"describeAddedClause");a(ec,"describeRemovedClause");a(tc,"describeModifiedClause");a(nc,"buildReplaceFlowTitle");a(rc,"describeOneChange");a(So,"flowLabelHint");a(cr,"resolveFlowLabel");a(Xs,"ensureTitleIncludesFlowName");a(To,"autoGenerateProposalTitle");a(oc,"shouldRefreshDisplayTitle");a(Po,"buildProposalDisplayName");a(sc,"formatUtcOffset");a(ic,"formatProposalSubmittedAt");a(ur,"sanitizeProposalTitle");a(ac,"buildProposalReviewPath");a(Ro,"resolveProposalTitle");a(dc,"storedTitleOf");a(oe,"presentProposal");a(lc,"buildChangeSummary");a(cc,"presentCreatedByForApi");a(ce,"presentProposalForApi");a(Ys,"presentProposalsForApi")});function Qs(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 pc(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]),r=[];for(let o of n)uc.has(o)||JSON.stringify(e[o])!==JSON.stringify(t[o])&&r.push(o);return r.sort()}function Zs(e){let t=Qs(e.baseFlow??{}),n=Qs(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=pc(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 fc(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",mc),r+=1}catch{}return r}function ei(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 Ie(e,t,n){let r=Zs(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=Ro(s),d=n?lt(s.createdBy)===n:lt(s.createdBy)===lt(t.createdBy),l=fc(r,Zs(s)),c=s.expectedRev===t.expectedRev,u=l.nodeIds.length>0;if(!c&&!u)continue;let p={id:s.id,title:i,displayName:Po(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 Ne(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 Q(e){return e>0?{autoClosedCount:e}:{}}var uc,mc,Xe=U(()=>{"use strict";Ge();Ae();uc=new Set(["x","y","w","h","g","id","z"]);a(Qs,"collectNodes");a(pc,"changedFieldKeys");a(Zs,"computeProposalTouches");a(fc,"overlapBetween");mc="Proposal expired: the live flow revision moved past expectedRev. Re-read the flow and resubmit.";a(V,"expireStalePendingProposals");a(ei,"supersedePendingOnFlow");a(Ie,"findConflictingProposals");a(Ne,"conflictWarningFields");a(Q,"autoClosedFields")});var di={};ds(di,{applyFlowNodeOps:()=>pt,applyReplaceTextToValue:()=>ai,buildPatchedFlowTab:()=>Tn,collectSetProps:()=>_o,decodeBase64UrlJson:()=>Pn,findTextOccurrences:()=>ii,interpretPatch:()=>Rn});function si(e){return e===null?"null":Array.isArray(e)?"array":typeof e}function hc(e){if(typeof e!="object"||e===null||Array.isArray(e))throw new y(`add op needs node as an object (got ${si(e)})`,"invalid_node")}async function Tn(e,t,n){return pt(e,t,n.map(r=>({op:"set",nodeId:r.nodeId,set:r.set})))}function ii(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 ai(e,t,n,r,o){if(oi.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=ii(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 gc(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 ${si(r)})`});continue}let o=r;(!o.id||typeof o.id!="string")&&t.push({code:"missing_node_id",error:"add op needs node.id"});try{Je(o.type,"node.type")}catch(s){s instanceof y&&t.push({code:s.code,error:s.message,nodeId:o.id})}"wires"in o&&Mt(o.wires,t,typeof o.id=="string"?o.id:void 0)}else n.op==="set"&&typeof n.set=="object"&&"wires"in n.set&&Mt(n.set.wires,t,n.nodeId);return t}function yc(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 pt(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=gc(n);o.push(...wo([n.flatMap(h=>h.op==="add"?[h.node]:[])],"the batch (add ops)")),Ke(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 qs(r.nodeTypes,h.node.type),ni(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(...vo(c,t));let p=yc(n),f=await bo(c,r.nodeTypes,{softWarningNodeIds:p});u.push(...f.errors),Ke(u);for(let h of n)ni(i,t,h);let m=[...f.warnings];if(r.nodeTypes){let h=await ko(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 ti(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 ni(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(Je(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=ti(e,n.nodeId,t);e.splice(i,1);return}let r=ti(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=ai(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(oi.has(i))throw new y(`Cannot change protected node property '${i}' via GET propose`,"protected_property");if(i==="wires"){let l=[];Mt(d,l,n.nodeId),Ke(l)}s[i]=d}e[r]=s}function ri(e){if(typeof e=="string")return e;if(typeof e=="number"||typeof e=="boolean"||typeof e=="bigint")return String(e)}function _o(e){let t={},n=e.set;if(n&&typeof n=="object"&&!Array.isArray(n))for(let[r,o]of Object.entries(n)){let s=ri(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=ri(o);i!==void 0&&(t[s]=i)}}return t}function Pn(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 wc(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 Lo(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 bc(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 vc(e){let t=e.node;if(t!==void 0)return hc(t),{op:"add",node:t};let n={...e};return delete n.op,{op:"add",node:n}}function kc(e){let t=Lo(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=bc(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 Ac(e){let t=Lo(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 xc(e){let t=typeof e.op=="string"?e.op.toLowerCase():"set";if(t==="add")return vc(e);if(t==="remove"){let n=Lo(e);if(!n)throw new y("remove op needs id","missing_node_id");return{op:"remove",nodeId:n}}if(t==="replace_text")return kc(e);if(t!=="set")throw new y("nodes[].op must be 'add', 'remove', 'set', or 'replace_text'","invalid_patch");return Ac(e)}function Rn(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=wc(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(xc(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 oi,pr=U(()=>{"use strict";K();Ao();ut();oi=new Set(["id","type","z"]);a(si,"describeNonObjectNode");a(hc,"assertAddNodeObject");a(Tn,"buildPatchedFlowTab");a(ii,"findTextOccurrences");a(ai,"applyReplaceTextToValue");a(gc,"collectOpShapeIssues");a(yc,"touchedNodeIdsFromOps");a(pt,"applyFlowNodeOps");a(ti,"requireNodeIndex");a(ni,"applyOneNodeOp");a(ri,"stringifyQueryValue");a(_o,"collectSetProps");a(Pn,"decodeBase64UrlJson");a(wc,"resolveReplaceFlowId");a(Lo,"resolveNodeId");a(bc,"parseOccurrence");a(vc,"parseAddOp");a(kc,"parseReplaceTextOp");a(Ac,"parseSetOp");a(xc,"parseNodeOp");a(Rn,"interpretPatch")});function Ht(e,t){return lt(e.createdBy)===t.id}function Co(e,t){return!t||t.length===0||t.includes(e)}function Sc(e){return typeof e=="string"&&ci.has(e)}function Bt(e,t){let n=Tc[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 fr(e,t){if(Array.isArray(t))throw Bt(e,t);return t}function Pc(e){return typeof e=="number"?Number.isInteger(e)?e:null:typeof e=="string"&&/^-?\d+$/.test(e.trim())?Number(e.trim()):null}function Eo(e){return e==null||e===""}function Rc(e){if(!Eo(e)){if(e==="true"||e==="1"||e===!0)return!0;if(e==="false"||e==="0"||e===!1)return!1;throw Bt("mine",e)}}function Io(e){if(Eo(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(!Sc(o))throw Bt("status",e);n.includes(o)||n.push(o)}}return n.length>0?n:void 0}function li(e,t,n,r){if(Eo(t))return;let o=Pc(t);if(o===null||o<n||o>r)throw Bt(e,t);return o}function mr(e){let t={},n=Rc(fr("mine",e.mine));n!==void 0&&(t.mine=n);let r=Io(e.status);r&&(t.status=r);let o=fr("flowId",e.flowId??e.flow_id??e.flow);typeof o=="string"&&o.length>0&&(t.flowId=o);let s=li("limit",fr("limit",e.limit),1,Ut);s!==void 0&&(t.limit=s);let i=li("offset",fr("offset",e.offset),0,Number.MAX_SAFE_INTEGER);return i!==void 0&&(t.offset=i),t}function ft(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 _c(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),Ut);return{items:e.slice(r,r+o),total:n,limit:o,offset:r}}function qt(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||!Ht(c,t)).filter(c=>Co(c.status,r.status)).map(c=>({...oe(c,n),mine:Ht(c,t)})),l=_c(d,r);return{proposals:l.items,total:l.total,limit:l.limit,offset:l.offset}}function De(e,t,n,r={}){if(!n)throw new y("Missing proposal id","missing_proposal_id");let o=e.get(n);return{...oe(o,r),mine:Ht(o,t)}}function Lc(e){let t={kind:"access-link",linkId:e.id};return typeof e.label=="string"&&e.label&&(t.label=e.label),t}function Wt(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 ne,f}if(!Ht(i,n))throw new Ot(r);if(i.status!=="pending")throw new te(r,i.status);let d=typeof o=="string"&&o.trim()?o.replace(/\0/g,"").trim().slice(0,2e3):"Withdrawn by access link",l=Lc(n),c=e.markWithdrawn(r,l,d),u=oe(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 Cc(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"?{}:Ne(Ie(e,o,n))}function ue(e,t,n,r={},o={}){let s="mine"in n?n:{...oe(n,r),mine:Ht(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={...oe(u,r),mine:Ht(u,t)}}catch{}let d=ce(s,r),l={...d,mine:s.mine,...Q(i)};return o.includeConflicts!==!1&&d.status==="pending"&&Object.assign(l,Cc(e,d,t.id)),l}async function pe(e){let t=await e.getFlows();return{rev:t.rev,flows:t.flows}}async function xe(e,t){if(!t)throw new y("Missing flow id","missing_flow_id");let n=await e.getFlow(t);return{rev:n.rev,flow:$t(t,n.flow)}}function Ec(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 ui(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 Ic(e,t,n,r,o){let{applyFlowNodeOps:s,interpretPatch:i}=await Promise.resolve().then(()=>(pr(),di));ui(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 J(t,p.expectedRev,"submit_proposal");return{flowId:u.flowId,flow:p.flow}}async function Nc(e,t,n,r,o){let s=n.flowId,i=n.flow,d=!i||typeof i!="object"||Array.isArray(i);if(d&&ui(n,r),d&&(Array.isArray(n.nodes)||Array.isArray(r))){let l=await Ic(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 Fc(e,t){try{return{safeExpectedRev:Ee(e.slice(0,200),"expectedRev"),safeFlowId:Ee(t.slice(0,200),"flowId")}}catch{throw new y("expectedRev and payload.flowId must be safe path segments","invalid_path_segment")}}async function Ye(e,t,n,r,o,s={},i){let d=r.type,l=Ec(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 p=await Nc(o,l,c,r.nodes,i),{safeExpectedRev:f,safeFlowId:m}=Fc(l,p.flowId),h=p.flow,g=Buffer.byteLength(JSON.stringify(c),"utf8");if(g>j)throw new X("proposal payload",g,j);Ke(Gs(h,m));let S=Ft(h),E=await o.getFlows();if(E.rev!==f)throw new J(f,E.rev,"submit_proposal");Ke(zs(S,m,E.flows));let I=await o.getFlow(m);if(I.rev!==f)throw new J(f,I.rev,"submit_proposal");let N=Ft(I.flow);i&&await Bs(i,S);let{errors:ot,warnings:me}=await Js(S,{flowId:m,baseFlow:N,...i?{nodeTypes:i}:{}});Ke(ot);let A={kind:"access-link",linkId:n.id};n.label!==void 0&&(A.label=n.label);let v=n.oauthClientId&&n.oauthUser?{kind:"oauth",clientId:n.oauthClientId,user:n.oauthUser}:A,w=ur(r.title)??To(N,S),b=e.create({type:"replace_flow",expectedRev:f,payload:{flowId:m,flow:S},baseFlow:N,title:w,createdBy:v,...me.length>0?{warnings:me}:{}}),k=oe(b,s),L=k.flowLabel||void 0;return t.append("proposal.created",A,{proposalId:b.id,flowId:m,expectedRev:f,title:k.title,displayName:k.displayName,...L?{flowLabel:L}:{},...me.length>0?{warningCount:me.length}:{}}),k}function O(e,t="rest"){return _n(e,t)}var Ut,ci,Tc,y,K=U(()=>{"use strict";ve();or();Ge();er();G();Fe();Ao();ut();Xe();Ae();a(Ht,"isMineProposal");Ut=200,ci=new Set(["pending","approved","rejected","failed","withdrawn","expired","superseded"]);a(Co,"matchesStatusFilter");a(Sc,"isProposalStatus");Tc={status:[...ci].join(", "),limit:`integer 1\u2013${Ut}`,offset:"integer \u2265 0",mine:"true, false, 1, 0",flowId:"one flow tab id"};a(Bt,"invalidFilterError");a(fr,"singleQueryValue");a(Pc,"parseIntegerFilter");a(Eo,"isUnset");a(Rc,"parseMineFilter");a(Io,"parseStatusFilter");a(li,"parseRangeFilter");a(mr,"parseListProposalsQuery");a(ft,"listPageFields");a(_c,"applyListPagination");a(qt,"listAccessibleProposals");a(De,"getAccessibleProposal");a(Lc,"accessLinkActor");a(Wt,"withdrawMyProposal");a(Cc,"conflictsForProposal");a(ue,"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(pe,"listFlows");a(xe,"getFlow");a(Ec,"resolveExpectedRev");a(ui,"assertNodesArrayOrAbsent");a(Ic,"materializePayloadFromNodes");a(Nc,"resolveReplaceFlowPayload");a(Fc,"assertSafeRevAndFlowId");a(Ye,"submitReplaceFlowProposal");a(O,"aiOpsErrorToHttp")});function No(e){return e==="get_link"?86400:2592e3}function Fo(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 mt,Ln=U(()=>{"use strict";a(No,"maxTtlSecondsForKind");a(Fo,"ttlCapErrorMessage");mt="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 P(e,t,n,r,o=Jt,s="error"){return{code:e,status:t,meaning:n,hint:r,channels:o,kind:s}}function Cn(e){return $c.get(e)?.hint}function pi(e){return e.replace(/\\/g,"\\\\").replace(/\|/g,"\\|")}function fi(){let e=a(s=>`| \`${s.code}\` | ${s.status} | ${pi(s.meaning)} | ${pi(s.hint)} | ${s.channels.join(", ")} |`,"row"),t=["| Code | HTTP | Meaning | Next step | Channels |","| --- | --- | --- | --- | --- |"],n=hr.filter(s=>s.kind==="error"),r=hr.filter(s=>s.kind==="warning"),o=hr.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(`
7
- `)}var Jt,zt,Oo,Oc,Gt,hr,$c,gr=U(()=>{"use strict";Ln();Jt=["GET","REST","MCP"],zt=["GET"],Oo=["REST","MCP"],Oc=["GET","REST"],Gt=["admin"];a(P,"e");hr=[P("access_link_missing",401,"No Bearer / path token","Ask the user for a new access link of the right kind"),P("bearer_scheme_required",401,"Auth used Basic (or non-Bearer)","Send `Authorization: Bearer <token>`",Oo),P("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`)"),P("access_link_invalid",401,"Unknown, revoked, or cleared after restart","Ask the user for a new access link"),P("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"),P("ai_features_disabled",403,"Plugin AI features off","Owner enables AI Access / `adminAuth`"),P("auth_required",401,"Admin session required","Sign in to the Node-RED editor",Gt),P("insufficient_permissions",403,"Admin lacks required scope","Need `*` for link mint / approve",Gt),P("invalid_link_kind",400,"Unknown `kind` on create","Use `get_link`, `rest`, or `mcp`",Gt),P("invalid_ttl",400,"`ttlSeconds` is zero, negative, not a number, or over the cap for the kind",mt,Gt),P("access_link_limit",409,"Too many active links","Revoke an existing link first",Gt),P("access_link_not_found",404,"Unknown link id on revoke","List links and use an existing id",Gt),P("link_create_not_supported",403,"Attempted mint under a GET link","Admin mints via `POST /mcp/api/v1/links` only",zt),P("rate_limited",429,"Per-IP or per-token limit","Wait `retryAfter` / `Retry-After`"),P("payload_too_large",413,"Body / proposal over ~1.5MB","Shrink content"),P("invalid_json",400,"Malformed JSON body","Fix Content-Type / body",Oo),P("unsupported_media_type",415,"POST body is not `application/json`","Send `Content-Type: application/json`",Oo),P("url_too_long",414,"GET URL over soft cap (~8KB)","Use chunked drafts or REST/MCP",zt),P("not_found",404,"Unknown path or resource","Re-read docs / discovery"),P("missing_query",400,"Required search `q` missing","Pass `?q=`",Oc),P("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"),P("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"]),P("invalid_diff_format",400,"Unknown `format` on proposal diff","Use `format=summary` (default) or `format=full`"),P("confirm_required",400,"GET withdraw without `confirm=1`","Repeat the request with `confirm=1`",zt),P("missing_expected_rev",400,"Proposal submitted without `expectedRev` / `rev`","Read the flow and pass its `rev`"),P("version_mismatch",409,"Stale `rev` / `expectedRev`","Re-read flows and resubmit"),P("proposal_not_found",404,"Unknown proposal id","List proposals and use an existing id"),P("proposal_not_pending",409,"Action needs a pending proposal","Only pending can withdraw / decide"),P("proposal_not_owned",403,"Withdraw of another token\u2019s proposal","Only the creating token can withdraw"),P("validation_failed",400,"Several validation errors at once (message `N validation errors`)","Fix every entry in errors[] and resubmit."),P("duplicate_node_id",400,"The same node id appears more than once in a full tab or batch","Give every node a unique id"),P("missing_node_id",400,"Node / op without an id","Include a non-empty string id"),P("missing_node_type",400,"Node without a `type`","Set `type` to an installed palette type name"),P("invalid_node_type",400,"`type` is not a string","Use the palette type name exactly"),P("invalid_nodes",400,"`flow.nodes` / `flow.configs` not an array","Send arrays (use `[]` to clear)"),P("invalid_node",400,"Node entry is not an object","Send `{ id, type, \u2026 }` objects"),P("invalid_wires",400,"Bad wires shape, missing target, or more ports than outputs","Wire only to existing node ids / ports"),P("z_mismatch",400,"Node `z` differs from the flow id","Set `z` to the tab id or omit it"),P("node_already_exists",400,"`add` op for an id already in the flow","Use `set` or a new id"),P("protected_property",400,"Tried to change id / type / z","Omit id, type, and z from edits"),P("node_not_found",404,"Unknown node (single error only; several errors \u2192 `validation_failed`)","Re-read the flow"),P("field_not_found",404,"Unknown field on a node","Re-read the node"),P("text_not_found",400,"`replace_text` find had no match","Re-read field; find is exact"),P("ambiguous_match",409,"`replace_text` matched more than once","Pass `occurrence` (1-based) or unique find"),P("unknown_node_type",400,"Proposal uses a type that is not installed","List node-types / palette search; ask user to install"),P("node_type_not_found",404,"Unknown type on node-types detail","List types or search palette"),P("chunk_conflict",409,"Same draft seq re-sent with different data","Re-send identical data or start a new draft",zt),P("missing_chunks",400,"Draft submit with gaps in seq","Upload every seq from 0 through max",zt),P("draft_hash_mismatch",400,"Draft sha256 does not match assembled bytes","Send the hex sha256 of the assembled UTF-8",zt),P("missing_config_node",201,"Warning: node has no (or an unresolved) config node yet","Configure before deploy if needed; see `candidates`",Jt,"warning"),P("unwired_node",201,"Warning: node has outputs but no wires","Wire it, or ignore while scaffolding",Jt,"warning"),P("incomplete_wires",201,"Warning: fewer wire ports than outputs","Add ports if outputs should connect",Jt,"warning"),P("expired",200,"Status: pending proposal auto-closed because the live flow rev moved past `expectedRev`","Re-read the flow and submit a new proposal",Jt,"status"),P("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",Jt,"status"),P("upstream_error",502,"Node-RED runtime/flows error","Retry; check Node-RED logs"),P("internal_error",500,"Unexpected server error","Retry; check Node-RED logs")],$c=new Map(hr.map(e=>[e.code,e]));a(Cn,"registryHint");a(pi,"cell");a(fi,"renderGetLinkErrorTable")});function se(e,t,n={}){return $o(e,t,n)}function Mc(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 Dc(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 Qe(e,t,n,r){return e==="mcp"?t:e==="rest"?n:r}function $o(e,t,n={}){switch(e){case"version_mismatch":return Qe(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 Qe(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 Qe(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 Qe(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 Mc(t,n.cause);case"node_type_not_found":case"unknown_node_type":return Dc(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 Qe(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 Qe(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 Qe(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 Qe(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 _n(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 J?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,expectedRev:e.expectedRev,currentRev:e.currentRev}}:e instanceof z?o={statusCode:e.statusCode,body:{error:e.message,code:e.code,flowId:e.flowId}}:e instanceof ne||e instanceof Ot||e instanceof te||e instanceof X?o={statusCode:e.statusCode,body:{error:e.message,code:e.code}}:(e instanceof Ce||e instanceof be)&&(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=Oe[s];i&&(o.body.error=i)}if(typeof o.body.hint!="string"||!o.body.hint){let i=$o(s,t,{cause:o.body.cause})??Cn(s);i&&(o.body.hint=i)}return o}function mi(e,t,n={}){let o={error:n.error??Oe[t]??"Request failed",code:t,...n.details??{}},s=$o(t,e,{cause:o.cause})??Cn(t);return s&&(o.hint=s),{statusCode:n.statusCode??400,body:o}}var Oe,Fe=U(()=>{"use strict";ve();Ge();G();K();gr();Oe={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(se,"channelErrorHint");a(Mc,"invalidWiresHint");a(Dc,"nodeTypeHint");a(Qe,"pickChannel");a($o,"hintFor");a(_n,"mapErrorForChannel");a(mi,"buildChannelError")});var vm={};ds(vm,{ACCESS_LINK_INVALID_MESSAGE:()=>st,ACCESS_LINK_MISSING_MESSAGE:()=>le,AccessLinkStore:()=>At,EDITOR_SIDEBAR_ASSET_VERSION:()=>Xt,MAX_PROPOSAL_JSON_BYTES:()=>j,SettingsStore:()=>Lt,acceptedMediaTypes:()=>wd,areAiFeaturesEnabled:()=>ge,bindSettingsStore:()=>Jn,createPluginServices:()=>Vr,default:()=>bm,getStorageDir:()=>Hr,isAdminAuthEnabled:()=>W,isMcpStreamRequest:()=>bd,packageVersion:()=>Vt,registerRoutes:()=>vd,renderEditorSidebarJs:()=>yr,renderEditorSidebarLoaderHtml:()=>wi,requireAccessLink:()=>Nt,resolveAiAccessSettings:()=>he});module.exports=Pd(vm);var yd=require("fs");var Un=require("fs"),un=require("path");G();function Bn(e){let t=(0,un.join)(e,".ai-access");return{root:t,proposals:(0,un.join)(t,"proposals"),logs:(0,un.join)(t,"logs"),settings:(0,un.join)(t,"config.json")}}a(Bn,"resolveAiAccessPaths");function Qr(e){(0,Un.mkdirSync)(e,{recursive:!0,mode:Yr});try{(0,Un.chmodSync)(e,Yr)}catch{}}a(Qr,"mkdirSecure");function fs(e){Qr(e.root),Qr(e.proposals),Qr(e.logs)}a(fs,"ensureAiAccessDirs");it();_e();function Yn(e){let t=ae(e);return`auth-tokens${(t==="/"?"":t).replace(/\//g,"-")}`}a(Yn,"editorAuthTokenKey");_e();Fe();function jc(e){if(!e)return!1;let t=e.split(";")[0]?.trim().toLowerCase()??"";return t==="application/json"||t.endsWith("+json")}a(jc,"isJsonContentType");function Hc(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(Hc,"hasRequestBody");function je(e="rest"){return(t,n,r)=>{let o=t.headers["content-type"];if(jc(o)){r();return}if(!o&&!Hc(t)){r();return}let s=mi(e,"unsupported_media_type",{statusCode:415});n.status(s.statusCode).json(s.body)}}a(je,"requireJsonContentType");var Uc="text/plain; charset=utf-8",Bc="text/html; charset=utf-8",qc="default-src 'none'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'";function Wc(e){let t=e.query?.format;return typeof t=="string"?t.toLowerCase():void 0}a(Wc,"formatParam");function zc(e){let t=Wc(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(zc,"chooseGetLinkRepresentation");function hi(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;")}a(hi,"escapeHtml");var Gc=/(?<![A-Za-z0-9_.~\-/])\.\/[A-Za-z0-9._~\-/]+(?:\?[A-Za-z0-9._~\-=&%]+)?/g;function Jc(e){return e.replace(Gc,(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(Jc,"linkifyEscaped");function Kc(e,t){return`<!doctype html>
8
- <html><head><meta charset="utf-8">${t?`<base href="${hi(t)}">`:""}<title>Node-RED AI Access</title></head><body><pre>${Jc(hi(e))}</pre></body></html>
9
- `}a(Kc,"renderGetLinkHtml");function Vc(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(Vc,"getLinkBaseHref");function Xc(e){try{return JSON.stringify(JSON.parse(e),null,2)}catch{return e}}a(Xc,"prettyJson");function gi(){return(e,t,n)=>{let r=zc(e),o=Vc(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",Uc),s(u)):(t.setHeader("Content-Type",Bc),t.setHeader("Content-Security-Policy",qc),s(Kc(l?Xc(u):u,o)))}),n()}}a(gi,"getLinkContentNegotiation");qn();_e();function Yc(e){let t=de(e.settings.httpAdminRoot),n=qe(e.settings.httpAdminRoot),r="0.2.0-beta.4",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(Yc,"buildDiscoveryIndex");function Mo(e){return(t,n)=>{n.status(200).json(Yc(e))}}a(Mo,"discoveryIndexHandler");function yi(){return(e,t)=>{t.status(404).json({error:"Not found",code:"not_found"})}}a(yi,"mcpApiNotFoundHandler");var Kt;function Vt(){return Kt||(Kt="0.2.0-beta.4",Kt)}a(Vt,"packageVersion");var Xt=Vt();function Qc(){return`#red-ui-sidebar-ai-access {
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(`
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 {
10
13
  position: relative;
11
14
  height: 100%;
12
15
  display: flex;
@@ -162,7 +165,7 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
162
165
  border: 0;
163
166
  background: transparent;
164
167
  color: transparent;
165
- }`}a(Qc,"editorSidebarStyles");function Zc(){return`
168
+ }`}a(Ep,"editorSidebarStyles");function Cp(){return`
166
169
  /**
167
170
  * Editor right-hand sidebar tab: AI Access.
168
171
  * Served from /mcp/editor/ai-access-sidebar.js (no-store + ?v= cache-bust).
@@ -460,7 +463,7 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
460
463
  saving = false;
461
464
  });
462
465
  }
463
- `}a(Zc,"editorSidebarScriptPartA");function eu(){return`
466
+ `}a(Cp,"editorSidebarScriptPartA");function Lp(){return`
464
467
  function buildContent() {
465
468
  var content = $('<div></div>', { id: CONTENT_ID, class: 'red-ui-sidebar-ai-access' });
466
469
 
@@ -562,8 +565,39 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
562
565
  },
563
566
  });
564
567
  })();
565
- `}a(eu,"editorSidebarScriptPartB");function tu(){return Zc()+eu()}a(tu,"editorSidebarScriptBody");function yr(){let e=JSON.stringify(Qc());return[`/* @com55/node-red-ai-access editor sidebar v=${Xt} */`,"(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);"," }","})();",tu()].join(`
566
- `)}a(yr,"renderEditorSidebarJs");function wi(e=Xt){return`<script>
568
+ `}a(Lp,"editorSidebarScriptPartB");function Ip(){return Cp()+Lp()}a(Ip,"editorSidebarScriptBody");function Np(){return`(function () {
569
+ if (typeof RED === 'undefined' || typeof BroadcastChannel === 'undefined') return;
570
+ if (window.__com55AiAccessRevealListener) return;
571
+ window.__com55AiAccessRevealListener = true;
572
+ try { if (!window.name) window.name = 'node-red-editor'; } catch (e) {}
573
+ var ch = new BroadcastChannel('node-red-ai-access');
574
+ var REQ_ID_RE = /^[0-9]{10,16}-[0-9]{1,12}$/;
575
+ var lastFocus = 0;
576
+ ch.onmessage = function (ev) {
577
+ var m = ev && ev.data;
578
+ if (!m || m.type !== 'reveal' || typeof m.id !== 'string' || !m.id || m.id.length > 200) return;
579
+ if (typeof m.reqId !== 'string' || !REQ_ID_RE.test(m.reqId)) return;
580
+ if (m.kind !== 'flow' && m.kind !== 'node') return;
581
+ var ok = false;
582
+ try {
583
+ if (m.kind === 'flow') {
584
+ if ((RED.nodes.workspace && RED.nodes.workspace(m.id)) || (RED.nodes.subflow && RED.nodes.subflow(m.id))) {
585
+ RED.workspaces.show(m.id);
586
+ ok = true;
587
+ }
588
+ } else if (RED.nodes.node(m.id) || (RED.nodes.group && RED.nodes.group(m.id))) {
589
+ RED.view.reveal(m.id);
590
+ ok = true;
591
+ }
592
+ } catch (e) { ok = false; }
593
+ ch.postMessage({ type: 'reveal-ack', reqId: m.reqId, ok: ok });
594
+ if (ok && Date.now() - lastFocus > 2000) {
595
+ lastFocus = Date.now();
596
+ try { window.focus(); } catch (e) {}
597
+ }
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>
567
601
  /**
568
602
  * Thin loader for the AI Access editor sidebar.
569
603
  * Logic lives at GET /mcp/editor/ai-access-sidebar.js (Cache-Control: no-store)
@@ -597,8 +631,8 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
597
631
  (document.head || document.documentElement).appendChild(s);
598
632
  })();
599
633
  </script>
600
- `}a(wi,"renderEditorSidebarLoaderHtml");K();Fe();function bi(e){return Oe[e]??"Request failed"}a(bi,"safeErrorText");function nu(e,t){let n=O(t,"rest");if(!n)return!1;let r=n.body.code,o={error:bi(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(nu,"sendMappedFlowError");function vi(e){return async(t,n)=>{try{n.json(await pe(e))}catch(r){if(nu(n,r))return;n.setHeader("X-Content-Type-Options","nosniff"),n.status(502).json({error:bi("upstream_error"),code:"upstream_error"})}}}a(vi,"listFlowsHandler");ie();K();_e();Fe();function Yt(e){return e==null||e===""?{ok:!0,format:"summary"}:e==="summary"||e==="full"?{ok:!0,format:e}:{ok:!1}}a(Yt,"resolveDiffFormat");function ru(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(ru,"compactLineDiff");function ou(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=ru(e.lines),d={key:e.key,kind:"text",beforeBytes:o,afterBytes:s,lines:i.lines};return i.truncated&&(d.truncated=!0),d}a(ou,"formatTextChange");function su(e,t){return e.map(n=>n.kind==="text"?ou(n,t):n)}a(su,"formatPropertyChanges");function ki(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)}
601
- \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(ki,"formatPresenceText");function Ai(e,t){return{summary:e.summary,added:e.added.map(n=>ki(n,t)),removed:e.removed.map(n=>ki(n,t)),modified:e.modified.map(n=>({...n,changes:su(n.changes,t)}))}}a(Ai,"formatSections");function Qt(e,t="summary"){let n=Ai(e,t),r={...e,...n,format:t};return e.vsCurrent&&(r.vsCurrent=Ai(e.vsCurrent,t)),r}a(Qt,"applyDiffFormat");G();ie();gr();function Zt(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;").replaceAll("'","&#39;")}a(Zt,"escapeForDocs");var xi="https://YOUR-NODE-RED/mcp/rpc";function iu(e,t,n,r=xi){return`## Also available (prefer when your client can send headers)
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)
602
636
 
603
637
  **Prefer MCP or REST** whenever the AI client can send custom headers and JSON
604
638
  bodies \u2014 no URL length limits and no chunked drafts. Use this **GET link
@@ -667,7 +701,7 @@ curl -sS -X POST "${r}" \\
667
701
  -H "Accept: application/json, text/event-stream" \\
668
702
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
669
703
  \`\`\`
670
- `}a(iu,"docsAlsoAvailable");function au(e,t){return`# Node-RED AI Access \u2014 self-contained GET link (get_link)${e}
704
+ `}a(Dp,"docsAlsoAvailable");function jp(e,t){return`# Node-RED AI Access \u2014 self-contained GET link (get_link)${e}
671
705
 
672
706
  **Git mental model:** a proposal is like a pull request. Human approve = merge;
673
707
  reject = close; withdraw = you close your own PR. If \`rev\`/\`expectedRev\` does
@@ -742,7 +776,7 @@ fields at the top level** (no \`ok\` / \`proposal\` wrapper). Read
742
776
 
743
777
  Lean by default (no \`payload\` / \`baseFlow\`). Pass
744
778
  \`?include=payload,baseFlow\` or \`?include_payload=true\` to include them.
745
- `}a(au,"docsIntro");function du(){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(du,"docsWorkflow");function lu(){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(lu,"docsUrlTable");function cu(){return`## Propose examples
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
746
780
 
747
781
  **Recommended for small edits to long fields** \u2014 \`replace_text\` (find must be
748
782
  **unique** unless you pass \`occurrence\` as a 1-based index). Creates a normal
@@ -820,11 +854,11 @@ GET ./propose?flow=tab1&rev=abc123&patch=<base64url>
820
854
  Node-level proposes create a normal \`replace_flow\` proposal; owners should
821
855
  approve with deployment type **\`nodes\`** (plugin default) so only changed
822
856
  nodes deploy.
823
- `}a(cu,"docsProposeExamples");function uu(){return`## Chunked drafts (long HTML / large fields)
857
+ `}a(Bp,"docsProposeExamples");function qp(){return`## Chunked drafts (long HTML / large fields)
824
858
 
825
- When the GET URL would exceed **${It} bytes**, do not
859
+ When the GET URL would exceed **${Ht} bytes**, do not
826
860
  stuff everything into \`propose\`. Use drafts (expire after **30 minutes**,
827
- max **${j}** decoded bytes):
861
+ max **${H}** decoded bytes):
828
862
 
829
863
  ### Recommended chunk size
830
864
 
@@ -869,7 +903,7 @@ Or a full \`replace_flow\` document. JSON-mode drafts **may add nodes**, so
869
903
  \`draft/new\` does not require an existing \`node\`/\`field\`. Use JSON mode for
870
904
  structural changes; use field-mode only when you need to replace one whole
871
905
  string field that is too large for a single propose URL.
872
- `}a(uu,"docsDrafts");function pu(){return`## Last resort: content larger than chunked drafts
906
+ `}a(qp,"docsDrafts");function zp(){return`## Last resort: content larger than chunked drafts
873
907
 
874
908
  If the field is still too large for chunked drafts (decoded size over the
875
909
  proposal cap), use this fallback only after drafts fail:
@@ -883,9 +917,9 @@ proposal cap), use this fallback only after drafts fail:
883
917
  **Trade-off:** pasted content is **not** reviewed through the proposal diff.
884
918
  Prefer \`replace_text\` or chunked drafts whenever they fit so the human can
885
919
  still see a readable change on \`/mcp\`.
886
- `}a(pu,"docsLastResortPaste");function fu(){return`## Size cap and REST/MCP nodes payload shapes
920
+ `}a(zp,"docsLastResortPaste");function Wp(){return`## Size cap and REST/MCP nodes payload shapes
887
921
 
888
- The proposal JSON cap is **${j} bytes**
922
+ The proposal JSON cap is **${H} bytes**
889
923
  (\`MAX_PROPOSAL_JSON_BYTES\` / ~1.5MB) on **every** channel: GET propose/drafts,
890
924
  REST \`POST /proposals\`, and MCP \`submit_proposal\`. Oversized bodies return
891
925
  \`413 payload_too_large\` (never truncated).
@@ -929,7 +963,7 @@ Example (canonical):
929
963
  **Alternative (also accepted):** top-level \`nodes\` array as an alias for
930
964
  \`payload.nodes\` (same op shapes). Full-tab replace remains supported via
931
965
  \`payload.flow\` (from \`GET ./flows/<id>\` / REST \`GET /flows/{id}\`).
932
- `}a(fu,"docsRestNodesPayload");function mu(){return`## Known issues
966
+ `}a(Wp,"docsRestNodesPayload");function Gp(){return`## Known issues
933
967
 
934
968
  - **Rate limits are per token (and per IP).** Each access-link token has its own
935
969
  token bucket. Several tokens can each use the full per-token limit at the same
@@ -937,20 +971,20 @@ Example (canonical):
937
971
  - **\`RateLimit-*\` headers describe the per-token bucket** (default
938
972
  \`RateLimit-Policy: 300;w=60\`) on token requests; requests without a token
939
973
  report the per-IP bucket (\`600;w=60\`). Both are enforced.
940
- `}a(mu,"docsKnownIssues");function hu(){return`## Error codes (JSON unless docs)
974
+ `}a(Gp,"docsKnownIssues");function Kp(){return`## Error codes (JSON unless docs)
941
975
 
942
- ${fi()}
976
+ ${ia()}
943
977
 
944
978
  All JSON responses use \`Cache-Control: no-store\`. Never put secrets in query
945
979
  strings you will paste publicly beyond this temporary link.
946
- `}a(hu,"docsErrorsAndAlso");function Si(e){let{link:t,openapiPath:n,restApiBase:r,mcpRpcPath:o,publicOrigin:s}=e,i=Zt(t.expiresAt),d=t.label?` (${Zt(t.label)})`:"",l=Zt(n),c=Zt(r),u=Zt(o);return[au(d,i),iu(c,l,u,s?Zt(`${s}${o}`):xi),du(),lu(),cu(),fu(),uu(),pu(),mu(),hu()].join(`
947
- `)}a(Si,"renderGetLinkDocs");var Oi=require("crypto");G();K();Fe();Xe();Ae();K();function wr(e){return{error:e instanceof Error&&e.message.trim()?e.message:"Internal server error",code:"internal_error"}}a(wr,"internalErrorBody");function gu(e,t){e.headersSent||e.status(500).json(wr(t))}a(gu,"sendInternalError");function $(e,t){if(e.headersSent)return;let n=O(t);if(n){e.status(n.statusCode).json(n.body);return}gu(e,t)}a($,"sendCaughtRouteError");function R(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}$(n,o)})}}a(R,"asyncRoute");var vr=require("crypto");G();ie();var D=class extends Error{static{a(this,"DraftValidationError")}statusCode=400;code;constructor(t,n="draft_invalid"){super(t),this.name="DraftValidationError",this.code=n}},ht=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"}},gt=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}},yu=/^[A-Za-z0-9_-]+={0,2}$/;function Ti(e){if(!yu.test(e)||e.replace(/=+$/,"").length===0)throw new D("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 D("data must be non-empty base64url (A-Z, a-z, 0-9, -, _)","invalid_chunk_data")}if(t.byteLength===0&&e.length>0)throw new D("data must be non-empty base64url (A-Z, a-z, 0-9, -, _)","invalid_chunk_data");return t}a(Ti,"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,vr.randomBytes)(12).toString("hex")),this.maxBytes=t.maxBytes??j,this.ttlMs=t.ttlMs??Ss}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 gt(t,o.proposalId);let s=this.drafts.get(t);if(s?.linkId!==n)throw new ht(t);if(s.expiresAtMs<=r)throw new ht(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 D("seq must be a non-negative integer","invalid_seq");if(typeof o!="string")throw new D("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 D(`Chunk seq ${r} already stored with different data`,"chunk_conflict")}else{let u=Ti(o),p=s.totalBytes+u.byteLength;if(p>this.maxBytes)throw new D(`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 D("Draft has no chunks","draft_empty");let i=[...o.chunks.keys()].sort((m,h)=>m-h).at(-1);if(i===void 0)throw new D("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 D(`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 D(`Missing chunk seqs: ${m}`,"missing_chunks");l.push(h)}let c=l.map(m=>Ti(m)),u=Buffer.concat(c);if(u.byteLength>this.maxBytes)throw new D(`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 D("invalid_sha256: expected 64 hex chars","invalid_sha256");if((0,vr.createHash)("sha256").update(u).digest("hex")!==p)throw new D("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()}};pr();it();ie();var Ze=ln(require("express-rate-limit"),1);var jo=6e4,Pi=600,Ri=300;function Do(e,t){return typeof e=="number"&&Number.isFinite(e)&&e>0?Math.max(1,Math.floor(e)):t}a(Do,"positiveInt");function He(e){if(!e)return{windowMs:jo,maxPerIp:Pi,maxPerToken:Ri};let t=e;return{windowMs:Do(t.windowMs??t.rateLimitWindowMs,jo),maxPerIp:Do(t.maxPerIp??t.rateLimitMaxPerIp,Pi),maxPerToken:Do(t.maxPerToken??t.rateLimitMaxPerToken,Ri)}}a(He,"resolveRateLimitConfig");function wu(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(wu,"retryAfterSeconds");function _i(e,t,n=jo){let r=wu(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(_i,"sendRateLimited");function bu(e){return(t,n,r,o)=>{_i(t,n,e)}}a(bu,"rateLimitedHandler");function kr(e){return`ip:${(0,Ze.ipKeyGenerator)(e.ip??"unknown")}`}a(kr,"clientIpKey");function Ar(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(Ar,"accessTokenRateLimitKey");function Li(e,t,n,r){let o={windowMs:t,max:e,standardHeaders:!0,legacyHeaders:!1,keyGenerator:n,handler:bu(t)};return r&&(o.skip=r),(0,Ze.default)(o)}a(Li,"createLimiter");function Ho(e){let t=He(e);return Li(t.maxPerIp,t.windowMs,kr)}a(Ho,"createAiAccessRateLimiter");function vu(e){let t=He(e);return Li(t.maxPerToken,t.windowMs,n=>Ar(n)??kr(n),n=>Ar(n)==null)}a(vu,"createAiAccessTokenRateLimiter");function Ci(e){let t=He(e),n=Ho(t),r=vu(t);return{ip:n,token:r,stack:[n,r],config:t}}a(Ci,"createAiAccessDualRateLimiters");function Ei(e){let t=He(e()).windowMs,n=He(e()).maxPerIp,r=He(e()).maxPerToken,o=new Ze.MemoryStore,s=new Ze.MemoryStore,i=a(()=>{let h=He(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=((x,S)=>(x.toLowerCase()==="ratelimit-policy"&&typeof S=="string"&&(S=S.replace(/;w=\d+/,`;w=${Math.ceil(t/1e3)}`)),g(x,S)))},"useLiveWindowPolicy"),c=a((h,g)=>(0,Ze.default)({store:g,windowMs:t,limit:a(()=>{let x=He(e());return h?x.maxPerToken:x.maxPerIp},"limit"),standardHeaders:!0,legacyHeaders:!1,keyGenerator:h?x=>Ar(x)??kr(x):kr,skip:a(x=>h&&Ar(x)==null,"skip"),handler:a((x,S,E)=>{_i(x,S,He(e()).windowMs)},"handler")}),"make"),u=c(!1,o),p=c(!0,s);return{ip:a((h,g,x)=>(i(),l(g),u(h,g,x)),"ip"),token:a((h,g,x)=>(i(),l(g),p(h,g,x)),"token")}}a(Ei,"createLiveAiAccessDualRateLimiters");ye();function F(e){e.setHeader("Cache-Control","no-store"),e.setHeader("Pragma","no-cache")}a(F,"setNoStore");function Uo(e){let t=e.originalUrl||e.url||"",n=so(t);n!==t&&(Object.defineProperty(e,"originalUrl",{value:n,writable:!0,configurable:!0}),typeof e.url=="string"&&Object.defineProperty(e,"url",{value:so(e.url),writable:!0,configurable:!0}))}a(Uo,"redactTokenFromRequestUrl");function Ii(e,t){return(n,r,o)=>{F(r);let s=n.originalUrl||n.url||"";if(n.getLinkUrlBytes=Buffer.byteLength(s,"utf8"),Uo(n),!ge(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",Y),r.status(401).json({error:le,code:Le,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"){wn(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",Y),r.status(401).json({error:st,code:yn,hint:c});return}if(l.link.kind!=="get_link"){bn(r,"get_link",l.link.kind);return}n.accessLink=l.link,n.getLinkToken=d,o()}}a(Ii,"requireGetLinkToken");function en(e,t){let n=e.getLinkUrlBytes;return(typeof n=="number"?n:Buffer.byteLength(e.originalUrl||e.url||"","utf8"))>It?(F(t),t.status(414).json({error:`Request URL exceeds ${It} 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:It}),!0):!1}a(en,"rejectOversizedGetUrl");function C(e,t,n,r,o,s){F(e),e.status(t).json({error:n,code:r,...o?{hint:o}:{},...s})}a(C,"jsonError");function Ni(){return(e,t)=>{Uo(e),F(t),t.status(404).json({error:"Not found",code:"not_found"})}}a(Ni,"getLinkNotFoundHandler");var ku="link_create_not_supported";function Fi(){return(e,t)=>{Uo(e),F(t),t.status(403).json({error:"GET access links cannot create tokens",code:ku,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(Fi,"getLinkMintNotSupportedHandler");function _(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(_,"queryString");function Se(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(Se,"draftErrorHint");async function Au(e,t,n){let r;try{r=await e.getFlow(t)}catch(s){let i=O(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(Au,"validateFieldDraftTargets");function xu(e,t,n,r){if(r)try{let o=Pn(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=Se("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(xu,"planDraftMode");async function Su(e,t,n,r){try{let o=await Au(t,n,r);if(o){let s=o.code==="node_not_found"||o.code==="field_not_found"?404:400;return C(e,s,o.message,o.code,Se(o.code)),!0}}catch(o){let s=O(o);return s?(C(e,s.statusCode,s.body.error,s.body.code,Se(s.body.code)),!0):($(e,o),!0)}return!1}a(Su,"rejectInvalidFieldTargets");function $i(e,t){return async(n,r)=>{if(en(n,r))return;F(r);let o=_(n.query,"flow"),s=_(n.query,"rev"),i=_(n.query,"node"),d=_(n.query,"field"),l=_(n.query,"summary"),c=_(n.query,"title")??l,u=_(n.query,"mode"),p=_(n.query,"targets");if(!o||!s){C(r,400,"flow and rev query parameters are required","missing_draft_params",Se("missing_draft_params"));return}if(!n.accessLink){C(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:Se("version_mismatch")});return}}catch(h){let g=O(h);if(g){C(r,g.statusCode,g.body.error,g.body.code,g.body.code==="flow_not_found"?se("flow_not_found","get")??"Use an id from GET ./flows":Se(g.body.code));return}$(r,h);return}let f=xu(i,d,u,p);if(!f.ok){C(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 Su(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:j,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($i,"getLinkDraftNewHandler");function Mi(e){return(t,n)=>{if(en(t,n))return;F(n);let r=t.params.draftId,o=_(t.query,"seq"),s=_(t.query,"data");if(typeof r!="string"||!r){C(n,400,"Missing draft id","missing_draft_id");return}if(!t.accessLink){C(n,401,"Access link required","access_link_missing");return}if(o===void 0||s===void 0){C(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 ht||d instanceof gt||d instanceof D){C(n,d.statusCode,d.message,d.code,Se(d.code),d instanceof gt?{proposalId:d.proposalId}:void 0);return}$(n,d);return}}}a(Mi,"getLinkDraftAppendHandler");function Tu(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 D("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 D("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 D(`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 D("Invalid multi-target draft JSON","invalid_draft_body")}if(e.nodeId&&e.field)return[{nodeId:e.nodeId,set:{[e.field]:t}}];throw new D("Field draft missing node/field","invalid_draft")}a(Tu,"editsFromFieldDraft");function Di(e,t,n,r,o,s){return async(i,d)=>{if(en(i,d))return;F(d);let l=i.params.draftId,c=_(i.query,"sha256"),u=_(i.query,"title");if(typeof l!="string"||!l){C(d,400,"Missing draft id","missing_draft_id");return}if(!i.accessLink){C(d,401,"Access link required","access_link_missing");return}if(!c){C(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 A=Tu(p,f),v=await Tn(r,p.flowId,A);if(v.expectedRev!==p.expectedRev){d.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:p.expectedRev,currentRev:v.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=v.flow,g=v.expectedRev}else{let A=Rn(JSON.parse(f),p.flowId);if(m=A.flowId,A.kind==="replace_flow")h={...A.flow,id:A.flow.id||m,type:A.flow.type??"tab",nodes:A.flow.nodes??[]};else{let v=await pt(r,m,A.ops,{expectedRev:p.expectedRev,...s?{nodeTypes:s}:{}});if(v.expectedRev!==p.expectedRev){d.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:p.expectedRev,currentRev:v.expectedRev,draftDeleted:!1,hint:"Draft was kept. Re-read the flow and start a new draft with the current rev."});return}h=v.flow,g=v.expectedRev}}let x=u??p.title??p.summary,S=q(i,o),E=re(i.query),I=await Ye(t,n,i.accessLink,{type:"replace_flow",expectedRev:g,payload:{flowId:m,flow:h},...x?{title:x}:{}},r,S,s);e.markSubmitted(l,i.accessLink.id,I.id);let N={...ce(I,{...S,include:E}),mine:!0},ot=V(t,I.expectedRev,{flowId:I.payload.flowId}),me=Ie(t,t.get(I.id),i.accessLink.id);d.status(201).json({ok:!0,proposal:N,sha256:(0,Oi.createHash)("sha256").update(Buffer.from(f,"utf8")).digest("hex"),...Ne(me),...Q(ot),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 gt){C(d,p.statusCode,p.message,p.code,Se(p.code),{proposalId:p.proposalId});return}if(p instanceof ht||p instanceof D){C(d,p.statusCode,p.message,p.code,Se(p.code));return}if(p instanceof SyntaxError){C(d,400,"Assembled draft is not valid JSON","invalid_draft_body");return}let f=O(p);if(f){d.status(f.statusCode).json({...f.body,...Se(f.body.code)?{hint:Se(f.body.code)}:{}});return}$(d,p);return}}}a(Di,"getLinkDraftSubmitHandler");K();Fe();gr();Xe();Ae();pr();var Pu={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 Bo(e,t){return se(e,"get",{cause:t})??Pu[e]??Cn(e)}a(Bo,"proposeErrorHint");function Ru(e,t,n){e.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:t,currentRev:n,hint:Bo("version_mismatch")})}a(Ru,"sendVersionMismatch");async function _u(e,t,n,r,o){let s=Rn(Pn(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 pt(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(_u,"buildFromPatch");async function Lu(e,t,n,r,o){let s=await Tn(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(Lu,"buildFromSetProps");async function Cu(e,t,n,r,o,s,i,d){let l=await pt(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(Cu,"buildFromReplaceText");function Eu(e){let t=Object.prototype.hasOwnProperty.call(e.query,"replace");return{flowId:_(e.query,"flow"),rev:_(e.query,"rev"),nodeId:_(e.query,"node"),patchEncoded:_(e.query,"patch"),setProps:_o(e.query),findText:_(e.query,"find"),replaceText:t?_(e.query,"replace")??"":void 0,fieldName:_(e.query,"field"),occurrenceRaw:_(e.query,"occurrence")}}a(Eu,"readProposeQuery");function Te(e,t,n){C(e,400,n,t,Bo(t))}a(Te,"jsonProposeError");function Iu(e,t){if(t===void 0||t==="")return{ok:!0};let n=Number(t);return Number.isFinite(n)?{ok:!0,occurrence:Math.trunc(n)}:(Te(e,"invalid_occurrence","occurrence must be a positive integer"),{ok:!1})}a(Iu,"parseOccurrenceQuery");async function Nu(e,t,n,r,o){if(n.patchEncoded)return _u(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 Te(e,"conflicting_edits","Cannot combine replace_text (find/replace/field) with set.<prop> in one request"),null;if(!n.flowId)return Te(e,"missing_flow_id","flow query parameter is required"),null;if(!n.nodeId)return Te(e,"missing_node_id","node query parameter is required"),null;if(!n.fieldName)return Te(e,"missing_field","field query parameter is required for replace_text"),null;if(n.findText===void 0||n.findText.length===0)return Te(e,"missing_find","find query parameter must be a non-empty string"),null;if(n.replaceText===void 0)return Te(e,"invalid_replace","replace query parameter is required (may be empty)"),null;let i=Iu(e,n.occurrenceRaw);return i.ok?Cu(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?(Te(e,"missing_edits","At least one set.<prop>=value is required"),null):Lu(t,n.flowId,n.nodeId,n.setProps,r):(Te(e,"missing_node_id","node query parameter is required (or pass patch=)"),null):(Te(e,"missing_flow_id","flow query parameter is required"),null)}a(Nu,"buildProposeFromQuery");function ji(e,t,n,r,o){return async(s,i)=>{if(en(s,i))return;F(i);let d=Eu(s),l=_(s.query,"summary"),c=_(s.query,"title")??l;if(!d.rev){Te(i,"missing_expected_rev","rev query parameter is required");return}if(!s.accessLink){C(i,401,"Access link required","access_link_missing");return}try{let u=await Nu(i,n,d,d.rev,o);if(!u)return;if(!u.ok){Ru(i,u.expectedRev,u.currentRev);return}let p=q(s,r),f=re(s.query),m=await Ye(e,t,s.accessLink,{type:"replace_flow",expectedRev:u.expectedRev,payload:{flowId:u.builtFlowId,flow:u.builtFlow},...c?{title:c}:{}},n,p,o),h={...ce(m,{...p,include:f}),mine:!0},g=V(e,m.expectedRev,{flowId:m.payload.flowId}),x=Ie(e,e.get(m.id),s.accessLink.id),S=m.warnings;i.status(201).json({ok:!0,proposal:h,...l?{summary:l}:{},...Ne(x),...Q(g),...S&&S.length>0?{warnings:S}:{},hint:`Waiting for human approval. Tell the user about "${h.displayName}" (${h.reviewUrl}). Poll GET \u2026/proposals/<id> for status.${S&&S.length>0?` Soft warnings (${S.length}) are non-blocking \u2014 configure later if needed.`:""}`})}catch(u){let p=O(u,"get");if(p){let f=Bo(p.body.code,p.body.cause);i.status(p.statusCode).json({...p.body,...f?{hint:f}:{}});return}$(i,u)}}}a(ji,"getLinkProposeHandler");K();Fe();K();var Fu=50,Hi=40,Ou=20;function $u(e){return Buffer.byteLength(JSON.stringify(e),"utf8")}a($u,"nodeBytes");function Mu(e){let t={id:e.id,type:e.type,bytes:$u(e)};return typeof e.name=="string"&&(t.name=e.name),"wires"in e&&(t.wires=e.wires),t}a(Mu,"summarizeNode");async function xr(e,t){let{rev:n,flow:r}=await xe(e,t),o=r,s=Array.isArray(o.nodes)?o.nodes.map(Mu):[],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(xr,"getFlowSummary");function Ui(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(Ui,"collectTabs");async function Bi(e,t,n){if(!t)throw new y("Missing node id","missing_node_id");if(n){let{rev:d,flow:l}=await xe(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 pe(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 Ui(o)){let l=d.id;if(l)try{let u=(await xe(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(Bi,"findNodeInFlows");async function Sr(e,t,n){let r=await Bi(e,t,n);return{rev:r.rev,flowId:r.flowId,node:r.node}}a(Sr,"getNodeById");function Du(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(Du,"parseLineRange");async function Tr(e,t,n,r={}){if(!n)throw new y("Missing field name","missing_field");let o=await Bi(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(`
948
- `),d=i.length,l=Buffer.byteLength(s,"utf8"),c=Du(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(`
949
- `);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(Tr,"getNodeField");function ju(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(ju,"stringFields");function Hu(e,t,n){let r=Math.max(0,t-Hi),o=Math.min(e.length,t+n+Hi),s=e.slice(r,o).replace(/\s+/g," ");return r>0&&(s=`\u2026${s}`),o<e.length&&(s=`${s}\u2026`),s}a(Hu,"snippetAround");function Uu(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(Uu,"lineNumberAtIndex");function qo(e,t,n,r,o){for(let{field:s,value:i}of ju(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(`
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(`
950
984
  `)){let f=i.split(`
951
- `);for(let m=0;m<f.length;m+=1){let g=(f[m]??"").indexOf(n);if(g>=0){let x=0;for(let S=0;S<m;S+=1)x+=(f[S]?.length??0)+1;u(m+1,x+g)}}}if(n.includes(`
952
- `)){let f=0;for(;f<=i.length;){let m=i.indexOf(n,f);if(m<0)break;u(Uu(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,Ou),lineCount:p,snippet:Hu(i,Math.max(0,c),n.length)})}return r.length>=o}a(qo,"searchInNode");function qi(e){return[...e.nodes??[],...e.configs??[]]}a(qi,"nodePool");async function Bu(e,t,n,r,o){let s=await xe(e,t);for(let i of qi(s.flow))if(qo(i,t,n,r,o))return{rev:s.rev,truncated:!0};return{rev:s.rev,truncated:!1}}a(Bu,"searchOneFlow");async function qu(e,t,n,r){let o=await pe(e),s=new Set;for(let i of Ui(o.flows))if(i.id)try{let d=await xe(e,i.id);for(let l of qi(d.flow))if(s.add(l.id),qo(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(qo(d,l,t,n,r))return{rev:o.rev,truncated:!0}}return{rev:o.rev,truncated:!1}}a(qu,"searchAllFlows");async function Pr(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):Fu,o=[],s=n.flowId?await Bu(e,n.flowId,t,o,r):await qu(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(Pr,"searchFlows");function Wi(e){if(e===void 0)return!1;let t=e.toLowerCase();return t==="1"||t==="true"||t==="yes"}a(Wi,"isTruthyQueryFlag");function Wu(e,t,n){switch(e){case"node_not_found":case"flow_not_found":case"field_not_found":return n??se(e,t);default:return se(e,t)}}a(Wu,"layeredReadErrorHint");function yt(e){return Oe[e]??"Request failed"}a(yt,"safeErrorText");function fe(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(fe,"sendJson");function Rr(e,t,n,r){let o=O(t,n);if(!o)return!1;let s=o.body.code,i={error:yt(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=Wu(s,n,r);return d&&(i.hint=d),fe(e,o.statusCode,i),!0}a(Rr,"sendMappedError");function _r(e){fe(e,502,{error:yt("upstream_error"),code:"upstream_error"})}a(_r,"sendUpstreamError");function Lr(e,t="rest"){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||o.length===0){fe(r,400,{error:yt("missing_flow_id"),code:"missing_flow_id"});return}let s=Wi(_(n.query,"summary"));try{if(s){fe(r,200,await xr(e,o));return}fe(r,200,await xe(e,o))}catch(i){if(Rr(r,i,t))return;_r(r)}}}a(Lr,"getFlowOrSummaryHandler");function Cr(e,t="rest"){return async(n,r)=>{let o=n.params.id;if(typeof o!="string"||!o){fe(r,400,{error:yt("missing_node_id"),code:"missing_node_id"});return}let s=_(n.query,"flow");try{fe(r,200,await Sr(e,o,s))}catch(i){if(Rr(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;_r(r)}}}a(Cr,"getNodeHandler");function Er(e,t="rest"){return async(n,r)=>{let o=n.params.id,s=n.params.name;if(typeof o!="string"||!o){fe(r,400,{error:yt("missing_node_id"),code:"missing_node_id"});return}if(typeof s!="string"||!s){fe(r,400,{error:yt("missing_field"),code:"missing_field"});return}let i=_(n.query,"flow"),d=_(n.query,"lines");try{fe(r,200,await Tr(e,o,s,{...i?{flowId:i}:{},...d?{lines:d}:{}}))}catch(l){if(Rr(r,l,t,"Use summary/search to discover node ids and fields."))return;_r(r)}}}a(Er,"getNodeFieldHandler");function Ir(e,t="rest"){return async(n,r)=>{let o=_(n.query,"q");if(!o){fe(r,400,{error:yt("missing_query"),code:"missing_query",hint:t==="get"?"Example: GET \u2026/search?q=TODO&flow=tab1":se("missing_query",t)});return}let s=_(n.query,"flow");try{fe(r,200,await Pr(e,o,s?{flowId:s}:{}))}catch(i){if(Rr(r,i,t))return;_r(r)}}}a(Ir,"searchFlowsHandler");K();ut();var zu={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 En(e){return zu[e]??"Request failed"}a(En,"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 Wo(e,t){let n=O(t);if(!n)return!1;let r=n.body.code,o={error:En(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(Wo,"sendMappedError");function Nr(e){return async(t,n)=>{try{let r=_(t.query,"q"),o=await ir(e.nodeTypes,r?{q:r}:{});et(n,200,o)}catch(r){if(Wo(n,r))return;et(n,502,{error:En("upstream_error"),code:"upstream_error"})}}}a(Nr,"listNodeTypesHandler");function Fr(e){return async(t,n)=>{try{let r=t.params.type,o=typeof r=="string"?decodeURIComponent(r):"",s=_(t.query,"help"),i=!(s==="0"||s==="false"||s==="no"||s==="omit"),d=await ct(e.nodeTypes,o,{includeHelp:i});et(n,200,d)}catch(r){if(Wo(n,r))return;et(n,502,{error:En("upstream_error"),code:"upstream_error"})}}}a(Fr,"getNodeTypeHandler");function Or(e){return async(t,n)=>{try{let r=_(t.query,"q");if(!r){et(n,400,{error:En("missing_query"),code:"missing_query",hint:"Pass ?q= to search the Node-RED catalogue (recommend only)."});return}let o=await ar(e.nodeTypes,r,{...e.catalogueFetcher?{catalogueFetcher:e.catalogueFetcher}:{},...e.catalogueUrl?{catalogueUrl:e.catalogueUrl}:{}});et(n,200,o)}catch(r){if(Wo(n,r))return;et(n,502,{error:En("upstream_error"),code:"upstream_error"})}}}a(Or,"searchPaletteHandler");Xe();ve();function tn(e,t){return e[t]??""}a(tn,"lineAt");function In(e,t,n){let r=e[t];return r?r[n]??0:0}a(In,"cell");function zi(e,t,n,r){let o=e[t];o&&(o[n]=r)}a(zi,"setCell");function Ji(e,t){let n=Gi(e),r=Gi(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)tn(n,u)===tn(r,p)?zi(i,u,p,In(i,u+1,p+1)+1):zi(i,u,p,Math.max(In(i,u+1,p),In(i,u,p+1)));let d=[],l=0,c=0;for(;l<o&&c<s;){let u=tn(n,l),p=tn(r,c);u===p?(d.push({op:"equal",text:u}),l+=1,c+=1):In(i,l+1,c)>=In(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:tn(n,l)}),l+=1;for(;c<s;)d.push({op:"add",text:tn(r,c)}),c+=1;return d}a(Ji,"diffLines");function Gi(e){return e.length===0?[]:e.split(`
953
- `)}a(Gi,"splitLines");Ae();var Xi=new Set(["func","template","info","payload","code","text","format","syntax"]),Gu=new Set(["x","y","w","h","wires","g"]),Ju=new Set(["w","h","g"]);function wt(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(wt,"nodeName");function Ki(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(Ki,"collectNodes");function Dr(e){if(e===void 0)return"undefined";if(e===null||typeof e!="object")return JSON.stringify(e);if(Array.isArray(e))return`[${e.map(Dr).join(",")}]`;let t=e;return`{${Object.keys(t).sort().map(r=>`${JSON.stringify(r)}:${Dr(t[r])}`).join(",")}}`}a(Dr,"stableStringify");function Yi(e,t){return Dr(e)===Dr(t)}a(Yi,"valuesEqual");function jr(e){return typeof e=="string"?e:null}a(jr,"asText");function Ku(e){return/<\/?[a-z][\s\S]*>/i.test(e.slice(0,800))}a(Ku,"looksLikeHtml");function Vu(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:Ku(n)}a(Vu,"isHtmlTemplateField");function Qi(e,t,n){let r=jr(n);if(Xi.has(t)&&r!==null){let o={key:t,kind:"text",value:r};return Vu(e,t,r)&&(o.preview="html"),o}return{key:t,kind:"scalar",value:n===void 0?null:n}}a(Qi,"toPresenceProperty");function Xu(e,t){let n=[];for(let r of[...Object.keys(e)].sort())if(!(Ju.has(r)||r==="id"||r==="z")){if(r==="wires"){let o=Zi(e,t);n.push({key:"wires",kind:"scalar",value:o.length>0?o:e.wires??null});continue}n.push(Qi(e,r,e[r]))}return n}a(Xu,"nodeProperties");function Zi(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?wt(l)??l.type:d})}r.push({port:s,to:i})}),r}a(Zi,"describeWiresStructured");var Yu=new Set(["func","template","code","text"]);function Qu(e,t){let n=[],r=wt(e);r&&n.push({key:"name",kind:"scalar",value:r});let o=Zi(e,t);o.length>0&&n.push({key:"wires",kind:"scalar",value:o});for(let s of Yu){if(!(s in e))continue;let i=jr(e[s]);i===null||i.length===0||n.push(Qi(e,s,i))}return n}a(Qu,"buildKeyFields");function Zu(e,t,n){let r=Ve(e.type),o=wt(e),s=o?` "${o}"`:"",i=t?` in "${t}"`:"",d=n===1?"1 field":`${n} fields`;return`Changes ${r} node${s}${i} (${d})`}a(Zu,"buildModifiedSummary");function ep(e,t,n){let r=Ve(e.type),o=wt(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(ep,"buildPresenceSummary");function $r(e,t,n,r){let o={id:e.id,type:e.type,summary:ep(e,t,n),keyFields:Qu(e,r),properties:Xu(e,r)},s=wt(e);return s!==void 0&&(o.name=s),o}a($r,"toPresenceNode");function tp(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]),r=[];for(let o of[...n].sort()){if(Gu.has(o)||o==="id"||o==="z")continue;let s=e[o],i=t[o];if(Yi(s,i))continue;let d=jr(s),l=jr(i);if(Xi.has(o)&&(d!==null||l!==null)){let c=d??"",u=l??"";r.push({key:o,kind:"text",before:c,after:u,lines:Ji(c,u)});continue}r.push({key:o,kind:"scalar",before:s===void 0?null:s,after:i===void 0?null:i})}return r}a(tp,"compareNodes");function np(e,t){let n=["label","disabled","info","env"],r=[];for(let o of n)Yi(e[o],t[o])||r.push(o);return r}a(np,"compareTabMeta");function Mr(e){return typeof e.label=="string"&&e.label.trim()?e.label.trim():""}a(Mr,"flowLabelOf");function ea(e,t){let n=Ki(t),r=Ki(e),o=Mr(e)||Mr(t),s=Mr(t)||Mr(e),i=[],d=[],l=[];for(let c of e.nodes??[])n.has(c.id)||i.push($r(c,"added",o,r));for(let c of e.configs??[])n.has(c.id)||i.push($r(c,"added",o,r));for(let c of t.nodes??[])r.has(c.id)||d.push($r(c,"removed",s,n));for(let c of t.configs??[])r.has(c.id)||d.push($r(c,"removed",s,n));for(let[c,u]of r){let p=n.get(c);if(!p)continue;let f=tp(p,u);if(f.length>0){let m={id:c,type:u.type||p.type,summary:Zu(u,o||s,f.length),changes:f},h=wt(u)??wt(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:np(t,e),beforeNodeCount:n.size,afterNodeCount:r.size},added:i,removed:d,modified:l}}a(ea,"buildDiffSections");function Vi(e,t,n={}){let r=ea(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(Vi,"buildProposalDiff");async function nn(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 z))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?ea(e.payload.flow,s):void 0;return Vi(e,{rev:e.expectedRev,flow:l},{currentRev:o,compareAgainst:"base",baseRevStale:d,...c?{vsCurrent:c}:{}})}return Vi(e,{rev:o,flow:s},{compareAgainst:"current",baseSnapshotMissing:!0,baseRevStale:d})}a(nn,"computeProposalDiff");Ae();_e();ye();var ta=require("path");function Hr(e){return(0,ta.join)(e.userDir,".ai-access")}a(Hr,"getStorageDir");var rp=["status:read","flows:read","proposals:create","proposals:read-own"];function zo(){return Vt()}a(zo,"readPackageVersion");function op(e,t=Date.now()){let n=Date.parse(e);return Number.isFinite(n)?Math.max(0,Math.ceil((n-t)/1e3)):0}a(op,"secondsRemainingUntil");function sp(e,t=Date.now()){let n=Date.parse(e);return Number.isFinite(n)?Math.max(0,Math.ceil((n-t)/6e4)):0}a(sp,"minutesRemainingUntil");function ip(e,t){let n=de(t),r=qe(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(ip,"baseUrlsForTokenKind");function rn(e,t){let n=Kn(e,t),r=he(e.settings);if(t.auth){let o=t.auth.extra?.user;return{ok:!0,version:zo(),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:zo(),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:sp(t.accessLink.expiresAt,o),secondsRemaining:op(t.accessLink.expiresAt,o),permissions:[...rp],baseUrls:ip(t.accessLink.kind,e.settings.httpAdminRoot)}}}return{ok:!0,version:zo(),adminAuthEnabled:n.adminAuthEnabled,aiAccessEnabled:r.enabled,aiFeaturesEnabled:n.aiFeaturesEnabled,allowWithoutAdminAuth:n.allowWithoutAdminAuth,permission:n.permission,capabilities:n.capabilities,storageDir:Hr(e.settings),defaultLinkTtlSeconds:r.defaultLinkTtlSeconds,maxActiveLinks:r.maxActiveLinks,defaultDeploymentType:r.defaultDeploymentType,caller:{kind:"admin",...n.permission?{permission:n.permission}:{}}}}a(rn,"buildStatus");function na(e,t){let n=t.draftStore??new br,r=t.rateLimiters??Ci(t.rateLimit??e.settings.aiAccess),o=e.httpAdmin,s="/mcp/a/:token",i=[r.ip,Ii(e,t.linkStore),r.token];o.use("/mcp/a",gi()),o.get(s,...i,R((u,p)=>{F(p),p.setHeader("X-Content-Type-Options","nosniff");let f=de(e.settings.httpAdminRoot),m=u.accessLink;if(!m){C(p,401,"Access link required","access_link_missing");return}let h=Si({link:m,openapiPath:`${f}/openapi.yaml`,restApiBase:f,mcpRpcPath:qe(e.settings.httpAdminRoot),publicOrigin:oo(u)});p.status(200).type("text/markdown; charset=utf-8").send(h)})),o.get(`${s}/status`,...i,R((u,p)=>{F(p),p.json(rn(e,u))})),o.get(`${s}/flows`,...i,R(async(u,p)=>{F(p);try{p.json(await pe(t.flowAdapter))}catch(f){let m=O(f);if(m){p.status(m.statusCode).json({...m.body,hint:"Retry after the owner fixes the plugin runtime."});return}C(p,502,f instanceof Error?f.message:String(f),"upstream_error")}})),o.get(`${s}/flows/:id`,...i,R(async(u,p)=>{F(p),await Lr(t.flowAdapter,"get")(u,p)})),o.get(`${s}/nodes/:id`,...i,R(async(u,p)=>{F(p),await Cr(t.flowAdapter,"get")(u,p)})),o.get(`${s}/nodes/:id/field/:name`,...i,R(async(u,p)=>{F(p),await Er(t.flowAdapter,"get")(u,p)})),o.get(`${s}/search`,...i,R(async(u,p)=>{F(p),await Ir(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,R(async(u,p)=>{F(p),await Nr(d)(u,p)})),o.get(`${s}/node-types/:type`,...i,R(async(u,p)=>{F(p),await Fr(d)(u,p)})),o.get(`${s}/palette/search`,...i,R(async(u,p)=>{F(p),await Or(d)(u,p)}));let l=e.settings.httpAdminRoot;ap(o,s,i,t,l),o.get(`${s}/propose`,...i,R(ji(t.proposalStore,t.auditLog,t.flowAdapter,l,t.nodeTypesAdapter))),cp(o,s,i,n,t,l);let c=Fi();return o.all(`${s}/links`,c),o.all(`${s}/links/:id`,c),o.use("/mcp/a",Ni()),n}a(na,"registerGetLinkRoutes");function ap(e,t,n,r,o){e.get(`${t}/proposals`,...n,R(async(s,i)=>{F(i);let d=s.accessLink;if(!d){C(i,401,"Access link required","access_link_missing");return}let l=q(s,o),c=re(s.query),u=mr(s.query),p;try{p=(await pe(r.flowAdapter)).rev}catch{}let f=p?V(r.proposalStore,p):0,m=qt(r.proposalStore,d,l,u);i.json({proposals:m.proposals.map(h=>ue(r.proposalStore,d,h,{...l,include:c},{includeConflicts:!1})),...ft({...m,returned:m.proposals.length}),...Q(f)})})),e.get(`${t}/proposals/:id`,...n,R(async(s,i)=>{F(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){C(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){C(i,401,"Access link required","access_link_missing");return}try{let c=q(s,o),u=re(s.query),p;try{p=(await pe(r.flowAdapter)).rev}catch{}let f=De(r.proposalStore,l,d,c);i.json(ue(r.proposalStore,l,f,{...c,include:u},{...p?{liveRev:p}:{}}))}catch(c){let u=O(c,"get");if(u){i.status(u.statusCode).json({...u.body,hint:u.body.hint??"List proposals with GET \u2026/proposals."});return}$(i,c);return}})),dp(e,t,n,r,o),lp(e,t,n,r,o)}a(ap,"registerGetLinkProposalRoutes");function dp(e,t,n,r,o){e.get(`${t}/proposals/:id/withdraw`,...n,R((s,i)=>{F(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){C(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){C(i,401,"Access link required","access_link_missing");return}let c=s.query.confirm;if(c!=="1"&&c!=="true"){C(i,400,"Withdraw over GET requires confirm=1","confirm_required",se("confirm_required","get"));return}let u=typeof s.query.reason=="string"?s.query.reason:void 0,p=q(s,o),f=re(s.query);try{let m=Wt(r.proposalStore,r.auditLog,l,d,u,p);i.json(ue(r.proposalStore,l,m,{...p,include:f}))}catch(m){let h=O(m,"get");if(h){let g=h.body.code;C(i,h.statusCode,Oe[g]??"Request failed",g,se(g,"get"));return}C(i,500,"Internal server error","internal_error");return}}))}a(dp,"registerGetLinkWithdrawRoute");function lp(e,t,n,r,o){e.get(`${t}/proposals/:id/diff`,...n,R(async(s,i)=>{F(i);let d=s.params.id,l=s.accessLink;if(typeof d!="string"||!d){C(i,400,"Missing proposal id","missing_proposal_id");return}if(!l){C(i,401,"Access link required","access_link_missing");return}try{let c=De(r.proposalStore,l,d,q(s,o)),u=_(s.query,"format"),p=Yt(u==="json"||u==="md"?void 0:u);if(!p.ok){C(i,400,"Invalid diff format","invalid_diff_format","Valid formats: summary, full (default summary).");return}i.json(Qt(await nn(c,r.flowAdapter),p.format))}catch(c){let u=O(c);if(u){i.status(u.statusCode).json(u.body);return}$(i,c);return}}))}a(lp,"registerGetLinkDiffRoute");function cp(e,t,n,r,o,s){e.get(`${t}/draft/new`,...n,R($i(r,o.flowAdapter))),e.get(`${t}/draft/:draftId/append`,...n,R(Mi(r))),e.get(`${t}/draft/:draftId/submit`,...n,R(Di(r,o.proposalStore,o.auditLog,o.flowAdapter,s,o.nodeTypesAdapter)))}a(cp,"registerGetLinkDraftRoutes");G();var ra=require("module");function up(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,ra.createRequire)(e)}a(up,"createPluginRequire");function pp(e){try{let r=up()("@node-red/runtime")._?.adminApi?.httpAdmin;if(r&&typeof r.use=="function"&&r!==e.httpAdmin)return r}catch{}return null}a(pp,"resolveEditorHttpAdmin");function oa(e){let t=[e.httpAdmin],n=pp(e);return n&&t.push(n),t}a(oa,"httpAdminAppsForMcpErrors");var sa=Symbol.for("com55.ai-access.mcpJsonErrorHandler");function fp(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(fp,"mcpRpcMatcher");function Go(e){return e.includes("/mcp")}a(Go,"isMcpPath");function mp(e){return e.path||e.url||e.originalUrl||""}a(mp,"requestPath");function hp(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(hp,"isJsonParseError");function gp(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(gp,"isPayloadTooLarge");function yp(e){let t=fp(e);return(n,r,o,s)=>{if(o.headersSent){s(n);return}let i=mp(r);if(hp(n)&&Go(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(gp(n)&&Go(i)){o.status(413).json({error:`Request body exceeds limit of ${j} bytes`,code:"payload_too_large"});return}if(Go(i)){if(t(i)){o.status(500).json({jsonrpc:"2.0",error:{code:-32603,message:wr(n).error,data:{code:"internal_error"}},id:null});return}o.status(500).json(wr(n));return}s(n)}}a(yp,"mcpJsonErrorHandler");function wp(e,t){let n=e;n[sa]||(n[sa]=!0,e.use(yp(t)))}a(wp,"attachOnce");function ia(e){for(let t of oa(e))wp(t,e.settings.httpAdminRoot)}a(ia,"attachMcpJsonErrorHandlers");Ln();var bt=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"}},vt=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"}},Nn=class extends Error{static{a(this,"AccessLinkValidationError")}code="access_link_invalid";statusCode=400;constructor(t){super(t),this.name="AccessLinkValidationError"}},tt=class extends Error{static{a(this,"AccessLinkTtlError")}code="invalid_ttl";statusCode=400;hint=mt;constructor(t){super(t),this.name="AccessLinkTtlError"}};var kt=require("crypto");Ln();function Jo(e){return e==="get_link"||e==="rest"||e==="mcp"}a(Jo,"isAccessLinkKind");function Ko(e){return e??"get_link"}a(Ko,"resolveAccessLinkKind");function aa(e){return(0,kt.createHash)("sha256").update(e,"utf8").digest("hex")}a(aa,"hashToken");function bp(e,t){let n=Buffer.from(e,"utf8"),r=Buffer.from(t,"utf8");return n.length!==r.length?!1:(0,kt.timingSafeEqual)(n,r)}a(bp,"hashesEqual");function Fn(e){let t={id:e.id,kind:Ko(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(Fn,"toPublic");var At=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,kt.randomBytes)(12).toString("hex")),this.randomToken=t.randomToken??(()=>`aia_${(0,kt.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(Fn)}create(t={}){let n=this.now();if([...this.links.values()].filter(p=>this.isActive(p,n)).length>=this.maxActiveLinks)throw new bt(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 tt("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=Ko(t.kind),d=No(i);if(o>d)throw new tt(Fo(i,d));let l=this.randomId(),c=this.randomToken(),u={id:l,tokenHash:aa(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),{...Fn(u),token:c}}revoke(t){let n=this.links.get(t);if(!n)throw new vt(t);return n.revokedAtMs??=this.now(),Fn(n)}getById(t){if(typeof t!="string"||t.length===0)return null;let n=this.links.get(t);return n?Fn(n):null}validateResult(t){if(typeof t!="string"||t.length===0)return{status:"invalid"};let n=aa(t),r=this.now();for(let o of this.links.values())if(bp(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:Fn(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)}};Ln();ie();ye();function Vo(e,t){return t}a(Vo,"storeForRequest");function la(e){let t=typeof e.user?.username=="string"?e.user.username:void 0;return t?{kind:"admin",username:t}:{kind:"admin"}}a(la,"adminActor");function Ur(e){let t=he(e.settings);return new At({defaultTtlSeconds:t.defaultLinkTtlSeconds,maxActiveLinks:t.maxActiveLinks})}a(Ur,"createConfiguredLinkStore");function ca(e){e.setHeader("Cache-Control","no-store"),e.setHeader("Pragma","no-cache")}a(ca,"noStore");function ua(e,t){return(n,r)=>{let s=Vo(e,t).list();ca(r),r.json({links:s})}}a(ua,"listLinksHandler");function pa(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:mt});return}i.ttlSeconds=s.ttlSeconds}if(typeof s.label=="string"&&(i.label=s.label),s.kind!==void 0){if(!Jo(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=Vo(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",la(r),{linkId:d.id,label:d.label,expiresAt:d.expiresAt,createdAt:d.createdAt,kind:l,...p!==void 0?{ttlSeconds:p}:{}})}if(ca(o),l==="get_link"){let c=Ps(r,e.settings.httpAdminRoot,d.token);o.status(201).json({...d,url:c});return}o.status(201).json({...d})}catch(d){if(d instanceof bt){o.status(d.statusCode).json({error:d.message,code:d.code});return}if(d instanceof tt){o.status(d.statusCode).json({error:d.message,code:d.code,hint:d.hint});return}if(d instanceof Nn){o.status(d.statusCode).json({error:d.message,code:d.code});return}$(o,d);return}}}a(pa,"createLinkHandler");function fa(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=Vo(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",la(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 vt){o.status(i.statusCode).json({error:i.message,code:i.code});return}$(o,i);return}}}a(fa,"revokeLinkHandler");function vp(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(vp,"ttlSecondsFromLink");function kp(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=vp(l);c!==void 0&&(n.ttlSeconds=c)}return{...e,details:n}}a(kp,"enrichAuditLogEntry");function ma(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=>kp(d,t));r.json({logs:i})}}a(ma,"listLogsHandler");var Xo=require("@modelcontextprotocol/sdk/server/mcp.js"),ha=require("@modelcontextprotocol/sdk/server/streamableHttp.js"),T=require("zod");Ge();G();it();K();Fe();ut();Xe();Ae();function ga(){return"0.2.0-beta.4"}a(ga,"pluginVersion");async function ya(e){try{return(await pe(e)).rev}catch{return}}a(ya,"tryLiveRev");function Z(e){return{content:[{type:"text",text:JSON.stringify(e)}]}}a(Z,"jsonResult");function Yo(e){return e===!0?new Set(["payload","baseFlow"]):new Set}a(Yo,"includeSetFromPayloadFlag");function ee(e,t=500){let n=_n(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 Ap=new Set(["list_proposals","list_my_proposals"]),xp=new Set(["status","limit","offset","mine","flowId"]);function Sp(e){return!!e&&typeof e=="object"&&typeof e.safeParseAsync=="function"}a(Sp,"isZodLikeSchema");function Tp(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(Ap.has(e)&&xp.has(s))return Bt(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(Tp,"mcpArgumentError");function wa(e){let t=e,n=t.validateToolInput;typeof n=="function"&&(t.validateToolInput=async(r,o,s)=>{let i=r.inputSchema;if(!Sp(i))return n.call(e,r,o,s);let d=await i.safeParseAsync(o??{});if(d.success)return d.data;let l=_n(Tp(s,o,d.error.issues),"mcp"),c=l?{...l.body,statusCode:l.statusCode}:{};throw new Error(JSON.stringify(c))})}a(wa,"installMcpArgumentErrorMapping");function ba(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 Z(rn(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. Returns { rev, flows }. Call this (or get_flow) before submit_proposal so expectedRev matches."},async()=>{try{return Z(await pe(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:T.z.string().min(1).describe("Flow / tab id"),summary:T.z.boolean().optional().describe("When true, return node list with sizes only (no field contents)")}},async({id:o,summary:s})=>{try{return Z(s?await xr(n.flowAdapter,o):await xe(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:T.z.string().min(1).describe("Node id"),flow:T.z.string().min(1).optional().describe("Optional tab id to scope the lookup")}},async({id:o,flow:s})=>{try{return Z(await Sr(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:T.z.string().min(1).describe("Node id"),field:T.z.string().min(1).describe("Field name (e.g. template, func)"),lines:T.z.string().optional().describe("Optional 1-based line range A or A-B"),flow:T.z.string().min(1).optional().describe("Optional tab id to scope the lookup")}},async({id:o,field:s,lines:i,flow:d})=>{try{return Z(await Tr(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:T.z.string().describe("Substring to find (case-sensitive; non-empty required)"),flow:T.z.string().min(1).optional().describe("Optional tab id")}},async({q:o,flow:s})=>{try{return Z(await Pr(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:T.z.string().optional().describe("Optional case-insensitive filter")}},async({q:o})=>{try{return Z(await ir(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:T.z.string().min(1).describe("Node type id (e.g. inject, template) \u2014 case-sensitive"),includeHelp:T.z.boolean().optional().describe("When false, omit help sections (default true)")}},async({type:o,includeHelp:s})=>{try{return Z(await ct(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:T.z.string().describe("Search string (id / description / keywords; non-empty required)")}},async({q:o})=>{try{return Z(await ar(n.nodeTypesAdapter,o,{...n.catalogueFetcher?{catalogueFetcher:n.catalogueFetcher}:{},...n.catalogueUrl?{catalogueUrl:n.catalogueUrl}:{}}))}catch(s){return ee(s,502)}})}a(ba,"registerReadTools");function va(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. Payload size limit is ${j} 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:T.z.literal("replace_flow").describe("Must be 'replace_flow'"),expectedRev:T.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:T.z.string().min(1).optional().describe("Alias for expectedRev (snake_case); prefer expectedRev"),title:T.z.string().max(80).optional().describe("Optional short human title (\u226480 chars). Auto-generated from changes when omitted."),include_payload:T.z.boolean().optional().describe("When true, include payload and baseFlow in the response (default lean)"),nodes:T.z.array(T.z.record(T.z.string(),T.z.unknown())).optional().describe("Optional node ops (set/add/remove/replace_text). Same as payload.nodes when flow is omitted."),payload:T.z.object({flowId:T.z.string().min(1).describe("Tab id to replace"),flow:T.z.object({id:T.z.string().optional(),type:T.z.string().optional().describe("Usually 'tab' (included by get_flow)"),label:T.z.string().optional(),nodes:T.z.array(T.z.record(T.z.string(),T.z.unknown())).optional().describe("Child nodes for the tab (from get_flow.nodes)"),configs:T.z.array(T.z.record(T.z.string(),T.z.unknown())).optional()}).passthrough().optional().describe("Full tab from get_flow; omit when using nodes ops"),nodes:T.z.array(T.z.record(T.z.string(),T.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 Ye(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={...ce(d,{...s,include:Yo(i.include_payload)}),mine:!0},c=V(n.proposalStore,d.expectedRev,{flowId:d.payload.flowId}),u=Ne(Ie(n.proposalStore,n.proposalStore.get(d.id),o.id));return Z({...l,...u,...Q(c)})}catch(d){return ee(d)}}),Rp(e,n,o,s),_p(e,n,o,s)}a(va,"registerProposalTools");function Pp(e,t,n,r){let o={mine:T.z.boolean().optional().describe("true: only proposals created by this access link; false: only other links\u2019 proposals"),status:T.z.union([T.z.string(),T.z.array(T.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:T.z.string().optional().describe("Filter by flow tab id (payload.flowId)"),limit:T.z.number().int().min(1).max(Ut).optional().describe(`Page size (1\u2013${Ut}). Omit to return all matches.`),offset:T.z.number().int().min(0).optional().describe("Skip this many filtered results (default 0)"),include_payload:T.z.boolean().optional().describe("When true, include payload and baseFlow on each proposal")},s=a(async i=>{try{let d=Yo(i.include_payload),l=await ya(t.flowAdapter),c=l?V(t.proposalStore,l):0,u={};typeof i.mine=="boolean"&&(u.mine=i.mine);let p=Io(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=qt(t.proposalStore,n,r,u);return Z({proposals:f.proposals.map(m=>ue(t.proposalStore,n,m,{...r,include:d},{includeConflicts:!1})),...ft({...f,returned:f.proposals.length}),...Q(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(Pp,"registerListProposalsTools");function Rp(e,t,n,r){Pp(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:T.z.string().min(1).describe("Proposal id (prop-\u2026)"),include_payload:T.z.boolean().optional().describe("When true, include payload and baseFlow")}},async({id:o,include_payload:s})=>{try{let i=await ya(t.flowAdapter),d=De(t.proposalStore,n,o,r);return Z(ue(t.proposalStore,n,d,{...r,include:Yo(s)},{...i?{liveRev:i}:{}}))}catch(i){return ee(i)}})}a(Rp,"registerProposalQueryTools");function _p(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:T.z.string().min(1).describe("Proposal id (prop-\u2026)"),format:T.z.enum(["summary","full"]).optional().describe("Diff payload format (default summary)")}},async({id:o,format:s})=>{try{let i=De(t.proposalStore,n,o),d=Yt(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=Qt(await nn(i,t.flowAdapter),d.format);return Z(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:T.z.string().min(1).describe("Proposal id (prop-\u2026) owned by this access link"),reason:T.z.string().optional().describe("Optional withdraw reason")}},({id:o,reason:s})=>{try{let i=Wt(t.proposalStore,t.auditLog,n,o,s,r);return Z(ue(t.proposalStore,n,i,r))}catch(i){return ee(i)}})}a(_p,"registerProposalActionTools");function Lp(e,t,n){let r=new Xo.McpServer({name:"node-red-ai-access",version:ga()});wa(r);let o=n.accessLink;if(!o)throw new Error("Access link required");return ba(r,e,t,n),va(r,e,t,n,o),r}a(Lp,"createAiMcpServer");function Qo(e,t){return async(n,r,o)=>{try{if(!n.accessLink&&!n.auth){r.setHeader("WWW-Authenticate",Y),r.status(401).json({error:le,code:Le});return}let s=n.accessLink?Lp(e,t,n):Cp(e,t,n),i=new ha.StreamableHTTPServerTransport({enableJsonResponse:!0});await s.connect(i),await i.handleRequest(n,r,n.body),r.on("close",()=>{i.close(),s.close()})}catch(s){if(!r.headersSent){$(r,s);return}o(s instanceof Error?s:new Error(String(s)))}}}a(Qo,"mcpRpcPostHandler");function Cp(e,t,n){let r=new Xo.McpServer({name:"node-red-ai-access",version:ga()});if(wa(r),ba(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:lo(i,s),kind:"mcp",createdAt:new Date().toISOString(),expiresAt:new Date((n.auth?.expiresAt??0)*1e3).toISOString(),createdBy:s,oauthClientId:i,oauthUser:s};va(r,e,t,n,l)}return r}a(Cp,"createOAuthMcpServer");function On(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(On,"mcpRpcMethodNotAllowed");var $n=require("crypto"),sn=require("fs"),ts=require("path"),B=require("@modelcontextprotocol/sdk/server/auth/errors.js"),Ta=require("@modelcontextprotocol/sdk/server/auth/handlers/authorize.js"),ns=require("@modelcontextprotocol/sdk/server/auth/handlers/metadata.js"),Pa=require("@modelcontextprotocol/sdk/server/auth/handlers/register.js"),Ra=require("@modelcontextprotocol/sdk/server/auth/handlers/revoke.js"),_a=require("@modelcontextprotocol/sdk/server/auth/handlers/token.js"),La=require("@modelcontextprotocol/sdk/server/auth/middleware/bearerAuth.js"),Ca=require("@modelcontextprotocol/sdk/server/auth/router.js"),Ea=ln(require("express"),1);G();_e();function $e(e){return e.replace(/[&<>"']/g,t=>({"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;","'":"&#39;"})[t]??t)}a($e,"esc");var ka={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>'},Ep={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."]},Ip='<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>',Np=`
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=`
954
988
  :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}
955
989
  *{box-sizing:border-box}
956
990
  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}
@@ -986,7 +1020,7 @@ form.login input{font:inherit;padding:9px 11px;border-radius:10px;border:1px sol
986
1020
  a{color:var(--focus)}
987
1021
  [hidden]{display:none!important}
988
1022
  @media (max-width:420px){.actions{flex-direction:column-reverse}.secondary{flex:1}}
989
- `;function Aa(e){let t=e.scopes.map(n=>{let[r,o]=Ep[n]??[n,""];return`<li data-scope="${$e(n)}">${ka[n]??ka.read}<div><b>${$e(r)}</b><span>${$e(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>${Np}</style></head><body><main class="card" data-request="${$e(e.requestId)}" data-token-key="${$e(e.tokenKey)}" data-admin-root="${$e(e.adminRoot)}"><div class="head">${Ip}<div class="brand"><b>Node-RED AI Access</b><span>Authorization request</span></div></div><div class="body"><h1><span class="app">${$e(e.redirectHost)}</span> wants to access your Node-RED</h1><p class="sub">Access tokens will be sent to <span class="host">${$e(e.redirectHost)}</span>. App name (provided by the client, unverified): <b>${$e(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="${$e(e.nonce)}">${e.script}</script></body></html>`}a(Aa,"renderConsentPage");var xa=`(function () {
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 () {
990
1024
  var main = document.querySelector('main');
991
1025
  var id = main.getAttribute('data-request');
992
1026
  var key = main.getAttribute('data-token-key');
@@ -1101,8 +1135,31 @@ a{color:var(--focus)}
1101
1135
  allow.addEventListener('click', function () { decide('allow'); });
1102
1136
  deny.addEventListener('click', function () { decide('deny'); });
1103
1137
  retry.addEventListener('click', load);
1138
+ // Auto-retry once the user signs in to the editor in another tab (it writes
1139
+ // the token to localStorage), or when this tab is shown / focused again.
1140
+ // Only while signed out and not while typing in the inline sign-in form.
1141
+ function signedOut() { return !retry.hidden || !login.hidden; }
1142
+ function hasToken() {
1143
+ try { var t = JSON.parse(localStorage.getItem(key) || 'null'); return !!(t && t.access_token); } catch (e) { return false; }
1144
+ }
1145
+ var lastAuto = 0;
1146
+ function typingInLogin() {
1147
+ return !login.hidden && !!($('username').value || $('password').value);
1148
+ }
1149
+ function autoRetry(fromStorage) {
1150
+ if (!signedOut() || !hasToken()) return;
1151
+ // A token written by another tab is definitive; on focus / visibility only,
1152
+ // leave a half-typed inline sign-in alone.
1153
+ if (!fromStorage && typingInLogin()) return;
1154
+ if (Date.now() - lastAuto < 1000) return;
1155
+ lastAuto = Date.now();
1156
+ load();
1157
+ }
1158
+ window.addEventListener('storage', function (ev) { if (ev.key === key && ev.newValue) autoRetry(true); });
1159
+ document.addEventListener('visibilitychange', function () { if (document.visibilityState === 'visible') autoRetry(false); });
1160
+ window.addEventListener('focus', function () { autoRetry(false); });
1104
1161
  load();
1105
- })();`;ye();var nt=a(e=>(0,$n.createHash)("sha256").update(e).digest("hex"),"hash"),St=a(()=>(0,$n.randomBytes)(32).toString("base64url"),"secret"),Fp=36e5,Op=25,$p=5,Mp=500,Sa=a(()=>({clients:[],codes:[],tokens:[]}),"emptyDb"),Br=class{constructor(t,n){this.maxClients=n;this.path=(0,ts.join)(t.root,"oauth.json");try{let r=(0,sn.existsSync)(this.path)?JSON.parse((0,sn.readFileSync)(this.path,"utf8")):Sa();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=Sa()}(0,sn.mkdirSync)(t.root,{recursive:!0,mode:448}),this.prune()}static{a(this,"OAuthStore")}db;path;save(){Rt((0,ts.join)(this.path,".."),this.path,Be(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-Fp).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()>=Op)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:St(),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 os(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(os,"describeOAuthRouteCollision");var ss=a((e,t)=>e.toLowerCase().startsWith(t.toLowerCase()),"startsWithCi");function Zo(e){return e===!1?void 0:`/${typeof e=="string"?e:"/"}`.replace(/\/+/g,"/").replace(/\/$/,"")}a(Zo,"normalizeRoot");function es(e,t){if(e!==void 0){if(!e)return t;if(ss(t,`${e}/`))return t.slice(e.length)}}a(es,"relativePrefix");function Dp(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(Dp,"makeLiveCheck");function jp(e,t){if(!e||!t.length)return[];let n=[];try{e(s=>n.push(s))}catch{return[]}let r=Dp(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=>ss(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(jp,"findHttpInCollisions");var Hp=new Set(["bound dispatch","handle"]),Up=["authorize","token","register","revoke"],Bp=["authorization-server","protected-resource"];function qp(e){let t=e.length;for(;t>0&&e.charCodeAt(t-1)===47;)t--;return e.slice(0,t)}a(qp,"trimTrailingSlashes");function Wp(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(Wp,"dedupe");function Ia(e){let t=Zo(e.httpAdminRoot),n=e.httpNode?Zo(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=es(t,p),m=es(n,p),h=m?jp(e.eachNode,[m]):[],g=new Set(h.map(E=>E.path.toLowerCase())),x=m?rs(e.httpNode,[m]).filter(E=>!(E.source==="route"&&Hp.has(E.layer??"")&&g.has(E.path.toLowerCase()))):[],S=f?rs(e.httpAdmin,[f]):[];return Wp([...S,...x,...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=Zo(m.root),g=qp(m.path);for(let x of p){let S=es(h,x);!S||!l(`${g}${S}`)||f.push({path:x,source:"httpStatic",file:`${g}${S}`})}}},"checkStatic");return u(Up.map(p=>`${r}${p}`),i),u(Bp.map(p=>`${o}${p}`),d),{authorization:i,metadata:d}}a(Ia,"scanOAuthRouteCollisions");function zp(e){let t=a(n=>{try{return e?.[n]}catch{return}},"read");return t("_router")?.stack??t("router")?.stack??[]}a(zp,"routerStack");function Gp(e){let t=e.route?.path;return t?(Array.isArray(t)?t:[t]).map(String):[]}a(Gp,"layerPaths");function Jp(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(Jp,"middlewareHits");function rs(e,t=["/.well-known/oauth-","/mcp/oauth/"]){let n=[];for(let r of zp(e)){if(r.handle?.oauthOwned)continue;let o=Gp(r);for(let s of o)t.some(i=>ss(s,i))&&n.push({path:s,source:"route",layer:r.name??"route"});!o.length&&Jp(r,t)&&n.push({path:t.join(", "),source:"middleware",layer:r.name??"route"}),r.handle?.stack&&n.push(...rs({router:{stack:r.handle.stack}},t))}return n}a(rs,"detectOAuthRouteCollisionDetails");var qr=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(nt(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&&rt(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===nt(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&&rt(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===nt(t)&&o.type==="access"&&o.expires>Date.now());if(!n||n.resource!==rt(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===nt(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=St();return this.clientsStore.data.codes.push({hash:nt(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=rt(this.resource());if(t&&rt(t.href)!==n)throw new B.InvalidGrantError("Resource mismatch");return n}findCode(t,n){let r=this.clientsStore.data.codes.find(o=>o.hash===nt(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=St(),i=Date.now()){let d=St(),l=St(),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:nt(d),type:"access",clientId:t,family:s,scopes:n,resource:r,expires:c+this.accessTtl()*1e3,user:o,rotated:!1},{hash:nt(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 rt(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(rt,"canonicalResource");var on=["read","propose"];function Kp(e){let t=(e??"/").split("/").filter(Boolean).join("/");return t?`/${t}/`:"/"}a(Kp,"consentAdminRoot");var Vp=new Set(["localhost","127.0.0.1","[::1]"]);function xt(e){return typeof e=="string"?e:""}a(xt,"value");function Na(e,t,n,r){let o=a(A=>`${n().replace(/\/$/,"")}${A}`,"abs"),s=a((A,v,w,b)=>{let k=v.url;v.url="/",A(v,w,L=>{v.url=k,b(L)})},"atRoot"),i=new Map,d=a(()=>{let A=Date.now();for(let[v,w]of i)w.expires<=A&&i.delete(v)},"sweep"),l=a(A=>{d();let v=[...i].filter(([,w])=>w.clientId===A);for(;v.length>=$p;)i.delete(v.shift()[0]);return i.size<Mp},"reservePending"),c=a((A,v)=>{A.setHeader("Cache-Control","no-store"),A.setHeader("X-Frame-Options","DENY"),A.setHeader("Referrer-Policy","no-referrer"),A.setHeader("X-Content-Type-Options","nosniff"),A.setHeader("Content-Security-Policy",`default-src 'none'; ${v?`script-src 'nonce-${v}'; `:""}style-src 'unsafe-inline'; connect-src 'self'; form-action 'none'; base-uri 'none'; frame-ancestors 'none'`)},"securityHeaders"),u=Yn(e.settings.httpAdminRoot),p=a(A=>{if(!W(e.settings))return[...on];let v=We(A);return v==="*"?[...on]:v==="read"?["read"]:[]},"grantableFor"),f=a(A=>{let v=A.get("sec-fetch-site");if(v&&v!=="same-origin")return!1;let w=A.get("origin");if(!w)return!0;let b=new Set;try{b.add(new URL(n()).origin)}catch{}let k=A.get("host");return k&&(b.add(`http://${k}`),b.add(`https://${k}`)),b.has(w)},"sameOrigin"),m=a((A,v)=>{d();let w=A.query,b=t.clientsStore.getClient(xt(w.client_id));if(!b){v.status(400).send("Unknown OAuth client");return}let k=xt(w.redirect_uri)||(b.redirect_uris.length===1?b.redirect_uris[0]:"");if(!b.redirect_uris.some(Xr=>(0,Ta.redirectUriMatches)(k,Xr))){v.status(400).send("Unregistered redirect URI");return}if(w.response_type!=="code"||w.code_challenge_method!=="S256"||!/^[A-Za-z0-9_-]{43,128}$/.test(xt(w.code_challenge))){v.status(400).send("Authorization code with PKCE S256 is required");return}let L=[...new Set((xt(w.scope)||"read").split(" ").filter(Boolean))];if(L.some(Xr=>!on.includes(Xr))){v.status(400).send("Unsupported scope");return}let H=xt(w.resource)||r(),M;try{M=rt(H)}catch{v.status(400).send("Invalid resource");return}if(M!==rt(r())){v.status(400).send("Resource mismatch");return}if(!l(b.client_id)){v.status(429).send("Too many pending authorization requests; try again later");return}let Re=St();i.set(Re,{clientId:b.client_id,redirect:k,challenge:xt(w.code_challenge),scopes:L,resource:H,state:xt(w.state),csrf:St(),expires:Date.now()+10*6e4});let Ue=new URL(k).host,Pt=(0,$n.randomBytes)(16).toString("base64");c(v,Pt),v.status(200).type("html").send(Aa({requestId:Re,tokenKey:u,adminRoot:Kp(e.settings.httpAdminRoot),nonce:Pt,redirectHost:Ue,loopbackRedirect:Vp.has(new URL(k).hostname),clientName:b.client_name||"Unnamed client",scopes:L,script:xa}))},"authorizePage"),h=a((A,v,w)=>{c(v),Ea.default.json({limit:"4kb"})(A,v,()=>we(e,"api")(A,v,()=>{try{d();let b=/^\/mcp\/oauth\/consent\/([A-Za-z0-9_-]+)\/?$/.exec(A.path)?.[1]??"",k=i.get(b),L=k&&t.clientsStore.getClient(k.clientId);if(!k||!L){v.status(404).json({error:"Consent request expired or unknown"});return}let H=W(e.settings)&&A.user?.username||"owner",M=k.scopes.filter(Pt=>p(A).includes(Pt));if(!M.length){v.status(403).json({error:"Your Node-RED permissions do not allow granting the requested scopes",grantable:M});return}if(A.method==="GET"){k.approver=H,v.json({client_name:L.client_name??null,redirect_host:new URL(k.redirect).host,requested:k.scopes,grantable:M,user:H,csrf:k.csrf});return}if(A.method!=="POST"){v.status(405).end();return}let Re=A.body??{};if(!f(A)||Re.csrf!==k.csrf||k.approver!==H){v.status(403).json({error:"Consent request verification failed"});return}i.delete(b);let Ue=new URL(k.redirect);if(Re.decision!=="allow")Ue.searchParams.set("error","access_denied");else{try{t.clientsStore.approve(L.client_id)}catch{v.status(409).json({error:"Maximum active OAuth clients reached"});return}let Pt=t.createCode(L,{redirectUri:k.redirect,codeChallenge:k.challenge,scopes:M,resource:new URL(k.resource)},H);Ue.searchParams.set("code",Pt)}k.state&&Ue.searchParams.set("state",k.state),v.json({redirect:Ue.href})}catch(b){w(b)}}))},"consentApi"),g=(0,Pa.clientRegistrationHandler)({clientsStore:t.clientsStore,rateLimit:{windowMs:36e5,max:10,standardHeaders:!0,legacyHeaders:!1}}),x=(0,_a.tokenHandler)({provider:t}),S=(0,Ra.revocationHandler)({provider:t}),E,I="";return{server:a((A,v,w)=>{let b=A.path,k=b.replace(/\/+$/,"").toLowerCase();if(k==="/mcp/oauth/authorize"){A.method==="GET"?m(A,v,w):v.status(405).set("Allow","GET").end();return}if(k.startsWith("/mcp/oauth/consent/")){h(A,v,w);return}if(!["/mcp/oauth/token","/mcp/oauth/register","/mcp/oauth/revoke"].includes(k)){w();return}let L=n();if(L.startsWith("https://")||/^http:\/\/(localhost|127\.0\.0\.1)(:|\/|$)/.test(L)){if(I!==L)try{E=(0,Ca.mcpAuthRouter)({provider:t,issuerUrl:new URL(L),baseUrl:new URL(`${L.replace(/\/$/,"")}/mcp/oauth/`),resourceServerUrl:new URL(r()),scopesSupported:on,clientRegistrationOptions:{rateLimit:{windowMs:36e5,max:10}}}),I=L}catch{E=void 0}if(E){let H=A.url;A.url=A.url.slice(10)||"/",E(A,v,M=>{A.url=H,w(M)});return}}if(b==="/mcp/oauth/token"){s(x,A,v,w);return}if(b==="/mcp/oauth/register"){s(g,A,v,w);return}if(b==="/mcp/oauth/revoke"){s(S,A,v,w);return}w()},"server"),metadata:a((A,v,w)=>{let b=A.path;if(b==="/.well-known/oauth-authorization-server"){s((0,ns.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:on}),A,v,w);return}if(b==="/.well-known/oauth-protected-resource/mcp"||b==="/.well-known/oauth-protected-resource"){s((0,ns.metadataHandler)({resource:rt(r()),authorization_servers:[n()],scopes_supported:on}),A,v,w);return}w()},"metadata"),auth:a((A,v,w)=>{try{let b=v.set.bind(v);v.set=((k,L)=>(k==="WWW-Authenticate"&&typeof L=="string"&&(L=L.replace('scope="read"','scope="read propose"')),typeof k=="string"?b(k,L??""):b(k))),(0,La.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())})(A,v,k=>{v.set=b,w(k)})}catch(b){w(b)}},"auth")}}a(Na,"makeOAuthHandlers");var Mn=require("fs"),an=require("path"),Fa=require("url"),Oa=require("yaml"),Zp={};function Xp(){return typeof __dirname<"u"?__dirname:(0,an.dirname)((0,Fa.fileURLToPath)(Zp.url))}a(Xp,"moduleDir");function $a(){let e=Xp(),t=[(0,an.join)(e,"..","docs","api","openapi.yaml"),(0,an.join)(e,"..","..","docs","api","openapi.yaml"),(0,an.join)(process.cwd(),"docs","api","openapi.yaml")];for(let n of t)if((0,Mn.existsSync)(n))return n;return null}a($a,"resolveOpenApiYamlPath");function Yp(){let e=$a();if(!e)throw new Error("OpenAPI specification file was not found");return(0,Mn.readFileSync)(e,"utf8")}a(Yp,"loadOpenApiYaml");function Ma(){return(e,t)=>{try{let n=Yp();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(Ma,"openApiYamlHandler");var is;function Qp(){let e=$a();if(!e)throw new Error("OpenAPI specification file was not found");return is?.path!==e&&(is={path:e,json:JSON.stringify((0,Oa.parse)((0,Mn.readFileSync)(e,"utf8")))}),is.json}a(Qp,"loadOpenApiJson");function Da(){return(e,t)=>{try{t.status(200).type("application/json").send(Qp())}catch(n){let r=n instanceof Error?n.message:String(n);t.status(500).json({error:r})}}}a(Da,"openApiJsonHandler");function ef(){return`
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`
1106
1163
  var settingsData = null;
1107
1164
  var settingsSaving = false;
1108
1165
  var settingsTimer = null;
@@ -1113,6 +1170,7 @@ a{color:var(--focus)}
1113
1170
  corsAllowedOrigins: 'cors', rateLimitWindowMs: 'rate-window',
1114
1171
  rateLimitMaxPerIp: 'rate-ip', rateLimitMaxPerToken: 'rate-token'
1115
1172
  };
1173
+ var SECRET_PATTERN_RE = /^[A-Za-z0-9_-]{2,40}$/;
1116
1174
  var OAUTH_ACCESS_UNITS = [['minutes', 60], ['hours', 3600]];
1117
1175
  var OAUTH_REFRESH_UNITS = [['hours', 3600], ['days', 86400]];
1118
1176
 
@@ -1180,6 +1238,7 @@ a{color:var(--focus)}
1180
1238
  if (badge) badge.hidden = !locked;
1181
1239
  });
1182
1240
  $('set-check-public-url').disabled = ro;
1241
+ renderSecretPatterns(data);
1183
1242
 
1184
1243
  var o = s.oauth || {};
1185
1244
  var oauthLocked = fieldLocked('oauth');
@@ -1236,7 +1295,95 @@ a{color:var(--focus)}
1236
1295
  return payload;
1237
1296
  }
1238
1297
 
1239
- `}a(ef,"settingsPartForm");function tf(){return`
1298
+ `}a(Om,"settingsPartForm");function $m(){return`
1299
+ function secretPatternsEditable() {
1300
+ return settingsCanEdit() && !fieldLocked('secretNamePatterns');
1301
+ }
1302
+
1303
+ // Chips are built with createElement/textContent only (pattern text is never HTML).
1304
+ function renderSecretPatterns(data) {
1305
+ var list = $('secret-patterns');
1306
+ var s = (data && data.settings) || {};
1307
+ var patterns = Array.isArray(s.secretNamePatterns) ? s.secretNamePatterns : [];
1308
+ var editable = secretPatternsEditable();
1309
+ var source = data && data.sources ? data.sources.secretNamePatterns : 'default';
1310
+ $('secret-patterns-source').textContent = source === 'settings.js' ? '(set in settings.js)'
1311
+ : source === 'disk' ? '(customized)' : '(defaults)';
1312
+ while (list.firstChild) list.removeChild(list.firstChild);
1313
+ if (!patterns.length) {
1314
+ var empty = document.createElement('li');
1315
+ empty.className = 'chip-empty secret-patterns-empty-warning';
1316
+ empty.setAttribute('role', 'alert');
1317
+ empty.textContent = 'Warning: the pattern list is empty, so secrets are no longer redacted by ' +
1318
+ 'name (env vars, password/token fields). Only known token formats are still hidden from AI. ' +
1319
+ 'Use "Reset to defaults" to restore protection.';
1320
+ list.appendChild(empty);
1321
+ }
1322
+ patterns.forEach(function (p) {
1323
+ var li = document.createElement('li');
1324
+ li.className = 'chip';
1325
+ var label = document.createElement('code');
1326
+ label.textContent = String(p);
1327
+ li.appendChild(label);
1328
+ if (editable) {
1329
+ var rm = document.createElement('button');
1330
+ rm.type = 'button';
1331
+ rm.className = 'chip-remove';
1332
+ rm.setAttribute('aria-label', 'Remove ' + String(p));
1333
+ rm.title = 'Remove';
1334
+ rm.textContent = '\xD7';
1335
+ rm.addEventListener('click', function () {
1336
+ saveSecretPatterns(patterns.filter(function (x) { return x !== p; }));
1337
+ });
1338
+ li.appendChild(rm);
1339
+ }
1340
+ list.appendChild(li);
1341
+ });
1342
+ $('set-secret-new').disabled = !editable;
1343
+ $('set-secret-add').disabled = !editable;
1344
+ $('set-secret-reset').disabled = !editable || source !== 'disk';
1345
+ $('lock-secret-patterns').hidden = !fieldLocked('secretNamePatterns');
1346
+ }
1347
+
1348
+ function currentSecretPatterns() {
1349
+ var s = (settingsData && settingsData.settings) || {};
1350
+ return Array.isArray(s.secretNamePatterns) ? s.secretNamePatterns.slice() : [];
1351
+ }
1352
+
1353
+ function saveSecretPatterns(next) {
1354
+ if (!secretPatternsEditable() || settingsSaving) return;
1355
+ persistSettings({ secretNamePatterns: next });
1356
+ }
1357
+
1358
+ function addSecretPattern() {
1359
+ var input = $('set-secret-new');
1360
+ var value = String(input.value || '').trim().toUpperCase();
1361
+ if (!value) return;
1362
+ if (!SECRET_PATTERN_RE.test(value)) {
1363
+ settingsFlash('Pattern must be 2-40 letters, digits, "_" or "-".', false);
1364
+ return;
1365
+ }
1366
+ var list = currentSecretPatterns();
1367
+ if (list.indexOf(value) !== -1) {
1368
+ settingsFlash('Pattern ' + value + ' is already in the list.', false);
1369
+ return;
1370
+ }
1371
+ list.push(value);
1372
+ input.value = '';
1373
+ saveSecretPatterns(list);
1374
+ }
1375
+
1376
+ function resetSecretPatterns() {
1377
+ if (!secretPatternsEditable()) return;
1378
+ openConfirmDialog({
1379
+ title: 'Reset secret name patterns',
1380
+ message: 'Replace the list with the built-in defaults?',
1381
+ confirmLabel: 'Reset',
1382
+ onConfirm: function () { persistSettings({ secretNamePatterns: null }); }
1383
+ });
1384
+ }
1385
+
1386
+ `}a($m,"settingsPartSecrets");function Mm(){return`
1240
1387
  function settingsFlash(msg, ok) {
1241
1388
  var tab = document.querySelector('nav.tabs button[aria-selected="true"]');
1242
1389
  var target = tab && tab.getAttribute('data-tab') === 'oauth' ? 'oauth-flash' : 'settings-flash';
@@ -1324,7 +1471,7 @@ a{color:var(--focus)}
1324
1471
  }
1325
1472
  }
1326
1473
 
1327
- `}a(tf,"settingsPartSave");function nf(){return`
1474
+ `}a(Mm,"settingsPartSave");function Dm(){return`
1328
1475
  function editorNodeHref(c) {
1329
1476
  var base = editorRoot && editorRoot !== '/' ? editorRoot.replace(/\\/$/, '') + '/' : '/';
1330
1477
  if (c.nodeId) return base + '#node/' + encodeURIComponent(c.nodeId);
@@ -1373,9 +1520,14 @@ a{color:var(--focus)}
1373
1520
  box.hidden = !html;
1374
1521
  }
1375
1522
 
1523
+ var oauthStatusGen = 0;
1524
+ var oauthClientsGen = 0;
1376
1525
  async function loadOAuthStatus() {
1526
+ var gen = ++oauthStatusGen;
1377
1527
  try {
1378
- renderOAuthRouteWarning(await api('/oauth/status'));
1528
+ var st = await api('/oauth/status');
1529
+ if (gen !== oauthStatusGen) return; // superseded by a newer refresh
1530
+ renderOAuthRouteWarning(st);
1379
1531
  } catch (e) {
1380
1532
  var box = $('oauth-route-warning');
1381
1533
  box.textContent = 'OAuth route status unavailable: ' + (e.message || String(e));
@@ -1385,8 +1537,10 @@ a{color:var(--focus)}
1385
1537
 
1386
1538
  async function loadOAuthClients() {
1387
1539
  var list = $('oauth-clients');
1540
+ var gen = ++oauthClientsGen;
1388
1541
  try {
1389
1542
  var data = await api('/oauth/clients');
1543
+ if (gen !== oauthClientsGen) return;
1390
1544
  var clients = (data && data.clients) || [];
1391
1545
  if (!clients.length) {
1392
1546
  list.innerHTML = '<p class="muted">No registered OAuth clients.</p>';
@@ -1425,7 +1579,7 @@ a{color:var(--focus)}
1425
1579
  }
1426
1580
  }
1427
1581
 
1428
- `}a(nf,"settingsPartOAuth");function rf(){return`
1582
+ `}a(Dm,"settingsPartOAuth");function jm(){return`
1429
1583
  function loadOAuthTab() {
1430
1584
  loadSettings();
1431
1585
  loadOAuthStatus();
@@ -1438,7 +1592,7 @@ a{color:var(--focus)}
1438
1592
  [sec, oauthSec].forEach(function (host) {
1439
1593
  if (!host) return;
1440
1594
  host.querySelectorAll('input, select').forEach(function (el) {
1441
- if (el.id === 'set-public-url') return;
1595
+ if (el.id === 'set-public-url' || el.getAttribute('data-no-autosave')) return;
1442
1596
  el.addEventListener('change', scheduleSettingsSave);
1443
1597
  });
1444
1598
  });
@@ -1447,13 +1601,18 @@ a{color:var(--focus)}
1447
1601
  $('set-deploy-full-warn').hidden = $('set-deploy').value !== 'full';
1448
1602
  });
1449
1603
  $('set-check-public-url').addEventListener('click', checkPublicUrl);
1604
+ $('set-secret-add').addEventListener('click', addSecretPattern);
1605
+ $('set-secret-new').addEventListener('keydown', function (ev) {
1606
+ if (ev.key === 'Enter') { ev.preventDefault(); addSecretPattern(); }
1607
+ });
1608
+ $('set-secret-reset').addEventListener('click', resetSecretPatterns);
1450
1609
  $('oauth-refresh').addEventListener('click', function () { loadOAuthStatus(); loadOAuthClients(); });
1451
1610
  $('oauth-copy-url').addEventListener('click', function () {
1452
1611
  copyTextToClipboard($('oauth-server-url').textContent || '');
1453
1612
  showToast('Server URL copied.', true);
1454
1613
  });
1455
1614
  })();
1456
- `}a(rf,"settingsPartWire");function ja(){return ef()+tf()+nf()+rf()}a(ja,"pageClientSettingsScript");function sf(){return`
1615
+ `}a(jm,"settingsPartWire");function jd(){return Om()+$m()+Mm()+Dm()+jm()}a(jd,"pageClientSettingsScript");function Um(){return`
1457
1616
  (function () {
1458
1617
  var root = document.getElementById('mcp-root');
1459
1618
  if (!root) return;
@@ -1474,6 +1633,54 @@ a{color:var(--focus)}
1474
1633
  // Decided proposals load in pages (Load more); pending always load in full.
1475
1634
  var DECIDED_PAGE_SIZE = 50;
1476
1635
  var DECIDED_STATUSES = 'approved,rejected,failed,withdrawn,expired,superseded';
1636
+ /*
1637
+ * Per-loader generation counters: a loader bumps its counter before fetching
1638
+ * and drops its response unless it is still the latest, so overlapping
1639
+ * refreshes (manual + auto) never render stale data.
1640
+ */
1641
+ var loaderGen = {};
1642
+ function beginLoad(kind) { loaderGen[kind] = (loaderGen[kind] || 0) + 1; return loaderGen[kind]; }
1643
+ function isLatestLoad(kind, gen) { return loaderGen[kind] === gen; }
1644
+ /** approve / reject requests in flight (auto-refresh waits for them). */
1645
+ var proposalActionsInFlight = 0;
1646
+ var lastRenderSig = {};
1647
+ /** True when \`sig\` equals the last rendered signature for \`kind\` (and records it). */
1648
+ function sameAsLastRender(kind, sig) {
1649
+ if (lastRenderSig[kind] === sig) return true;
1650
+ lastRenderSig[kind] = sig;
1651
+ return false;
1652
+ }
1653
+ /*
1654
+ * Re-render a container while keeping UI state: open <details> (diffs,
1655
+ * raw payload, stale group\u2026) keyed by card id + class + index, the focused
1656
+ * element id, and page / container scroll.
1657
+ */
1658
+ function preserveUiState(container, render) {
1659
+ if (!container || !container.querySelectorAll) { render(); return; }
1660
+ var keyOf = function (d, i) {
1661
+ var card = d.closest ? d.closest('[data-id]') : null;
1662
+ return (card ? card.getAttribute('data-id') : '') + '|' + (d.className || '') + '|' + i;
1663
+ };
1664
+ var indexed = function () {
1665
+ var counts = {};
1666
+ return Array.prototype.map.call(container.querySelectorAll('details'), function (d) {
1667
+ var base = keyOf(d, 0);
1668
+ counts[base] = (counts[base] || 0) + 1;
1669
+ return { el: d, key: keyOf(d, counts[base]) };
1670
+ });
1671
+ };
1672
+ var openState = {};
1673
+ indexed().forEach(function (x) { openState[x.key] = !!x.el.open; });
1674
+ var focusedId = document.activeElement && document.activeElement.id;
1675
+ var sx = window.scrollX || 0, sy = window.scrollY || 0, cs = container.scrollTop || 0;
1676
+ render();
1677
+ indexed().forEach(function (x) {
1678
+ if (Object.prototype.hasOwnProperty.call(openState, x.key)) x.el.open = openState[x.key];
1679
+ });
1680
+ if (focusedId) { var f = document.getElementById(focusedId); if (f && f.focus) f.focus({ preventScroll: true }); }
1681
+ container.scrollTop = cs;
1682
+ if (window.scrollTo) window.scrollTo(sx, sy);
1683
+ }
1477
1684
  var decidedLoaded = 0;
1478
1685
  var decidedTotal = 0;
1479
1686
  var decidedHasMore = false;
@@ -1674,7 +1881,7 @@ a{color:var(--focus)}
1674
1881
  }
1675
1882
  return body;
1676
1883
  }
1677
- `}a(sf,"pageClientScriptPartA");function af(){return`
1884
+ `}a(Um,"pageClientScriptPartA");function Bm(){return`
1678
1885
  // Time helpers \u2014 keep in sync with page-time.ts (always browser-local Intl).
1679
1886
  function titleFromDisplayName(displayName) {
1680
1887
  var s = String(displayName || '');
@@ -1781,7 +1988,7 @@ a{color:var(--focus)}
1781
1988
  btn.disabled = false;
1782
1989
  }, 1600);
1783
1990
  }
1784
- `}a(af,"pageClientScriptPartATimeCopy");function df(){return`
1991
+ `}a(Bm,"pageClientScriptPartATimeCopy");function qm(){return`
1785
1992
  function shortId(value, keep) {
1786
1993
  var s = String(value == null ? '' : value);
1787
1994
  var n = keep || 8;
@@ -1911,7 +2118,7 @@ a{color:var(--focus)}
1911
2118
  }
1912
2119
  return '';
1913
2120
  }
1914
- `}a(df,"pageClientScriptPartAFormat");function lf(){return`
2121
+ `}a(qm,"pageClientScriptPartAFormat");function zm(){return`
1915
2122
  function lookupCachedProposal(id) {
1916
2123
  if (!id) return null;
1917
2124
  return cachedProposals.filter(function (x) { return x.id === id; })[0] || null;
@@ -2032,7 +2239,7 @@ a{color:var(--focus)}
2032
2239
  return k + '=' + String(v);
2033
2240
  }).join(', '));
2034
2241
  }
2035
- `}a(lf,"pageClientScriptPartAAudit");function cf(){return`
2242
+ `}a(zm,"pageClientScriptPartAAudit");function Wm(){return`
2036
2243
  // Hash helpers \u2014 keep in sync with page-hash.ts
2037
2244
  var TAB_IDS = ['status', 'settings', 'links', 'oauth', 'proposals'];
2038
2245
  function hashForTab(tab) {
@@ -2318,8 +2525,9 @@ a{color:var(--focus)}
2318
2525
  }
2319
2526
  }
2320
2527
 
2321
- `}a(cf,"pageClientScriptPartATabs");function uf(){return`
2322
- async function loadLinks() {
2528
+ `}a(Wm,"pageClientScriptPartATabs");function Gm(){return`
2529
+ async function loadLinks(opts) {
2530
+ opts = opts || {};
2323
2531
  if (!aiOn || !canManage) {
2324
2532
  var dashLinksOff = $('dash-links');
2325
2533
  if (dashLinksOff && !aiOn) dashLinksOff.textContent = '\u2014';
@@ -2327,15 +2535,19 @@ a{color:var(--focus)}
2327
2535
  }
2328
2536
  var list = $('links-list');
2329
2537
  var flash = $('links-flash');
2330
- showFlash(flash, '', true);
2538
+ if (!opts.auto) showFlash(flash, '', true);
2539
+ var gen = beginLoad('links');
2331
2540
  try {
2332
2541
  var data = await api('/links');
2542
+ if (!isLatestLoad('links', gen)) return;
2333
2543
  var links = (data && data.links) || [];
2334
2544
  links.forEach(function (l) {
2335
2545
  if (l && l.id) cachedLinksById[l.id] = l;
2336
2546
  });
2337
2547
  var dashLinks = $('dash-links');
2338
2548
  if (dashLinks) dashLinks.textContent = String(links.length);
2549
+ var linksUnchanged = sameAsLastRender('links', JSON.stringify(links));
2550
+ if (opts.auto && linksUnchanged) return;
2339
2551
  if (!links.length) {
2340
2552
  list.innerHTML = '<div class="empty-state"><p class="empty-title">No active access links</p>' +
2341
2553
  '<p class="muted">Create a link above to give an AI temporary access to read flows and submit proposals.</p></div>';
@@ -2387,7 +2599,7 @@ a{color:var(--focus)}
2387
2599
  showToast(e.message || String(e), false);
2388
2600
  }
2389
2601
  }
2390
- `}a(uf,"pageClientScriptPartA2");function pf(){return`
2602
+ `}a(Gm,"pageClientScriptPartA2");function Km(){return`
2391
2603
 
2392
2604
  function normalizeLinkKind(kind) {
2393
2605
  if (kind === 'rest' || kind === 'mcp' || kind === 'get_link') return kind;
@@ -2804,13 +3016,17 @@ a{color:var(--focus)}
2804
3016
  paint();
2805
3017
  }
2806
3018
 
3019
+ function proposalFlowId(p) {
3020
+ return (p && (p.flowId || (p.payload && p.payload.flowId))) || '';
3021
+ }
3022
+
2807
3023
  function flowLabelOf(p) {
2808
3024
  if (p && p.flowLabel) return String(p.flowLabel);
2809
3025
  var flow = (p && p.payload && p.payload.flow) || {};
2810
3026
  var base = (p && p.baseFlow) || {};
2811
3027
  if (flow.label) return String(flow.label);
2812
3028
  if (base.label) return String(base.label);
2813
- var flowId = p && p.payload && p.payload.flowId;
3029
+ var flowId = proposalFlowId(p);
2814
3030
  if (flowId && cachedFlowLabels[flowId]) return String(cachedFlowLabels[flowId]);
2815
3031
  return '';
2816
3032
  }
@@ -2869,6 +3085,7 @@ a{color:var(--focus)}
2869
3085
  * Small add/modify-only edits without warnings stay one-click.
2870
3086
  */
2871
3087
  function needsApproveConfirm(diff, proposal) {
3088
+ if (proposal && Array.isArray(proposal.restoredSecrets) && proposal.restoredSecrets.length) return true;
2872
3089
  if (proposalWarnings(proposal, diff).length > 0) return true;
2873
3090
  if (!diff || !diff.summary) return false;
2874
3091
  var removed = Number(diff.summary.removed) || 0;
@@ -2887,6 +3104,41 @@ a{color:var(--focus)}
2887
3104
  return [];
2888
3105
  }
2889
3106
 
3107
+ /**
3108
+ * Fields where a [REDACTED] placeholder was restored to the live secret,
3109
+ * plus a strong banner when that node's destination or code changed.
3110
+ */
3111
+ var SECRET_WARN_CODES = ['secret_destination_changed', 'secret_node_code_changed',
3112
+ 'secret_wires_changed', 'secret_scope_changed', 'outbound_node_changed', 'secret_tab_changed',
3113
+ 'secret_reference_added'];
3114
+ function renderRestoredSecrets(p) {
3115
+ 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>';
3140
+ }
3141
+
2890
3142
  function renderValidationWarnings(warnings, opts) {
2891
3143
  if (!warnings || !warnings.length) return '';
2892
3144
  opts = opts || {};
@@ -3077,7 +3329,7 @@ a{color:var(--focus)}
3077
3329
  decided: Math.max(decidedTotal, cachedProposals.filter(function (p) { return p.status !== 'pending'; }).length)
3078
3330
  };
3079
3331
  }
3080
- `}a(pf,"pageClientScriptPartB");function ff(){return`
3332
+ `}a(Km,"pageClientScriptPartB");function Jm(){return`
3081
3333
  var DIFF_MODE_KEY = 'ai-access-diff-mode';
3082
3334
  var DIFF_SPLIT_MIN_PX = ${900};
3083
3335
  var PRESENCE_SHORT_TEXT_MAX = ${120};
@@ -3194,7 +3446,7 @@ a{color:var(--focus)}
3194
3446
  '</div>';
3195
3447
  }
3196
3448
 
3197
- `}a(ff,"pageClientScriptPartBDiff");function mf(){return`
3449
+ `}a(Jm,"pageClientScriptPartBDiff");function Vm(){return`
3198
3450
  function isCollapsiblePresenceText(value) {
3199
3451
  var s = String(value == null ? '' : value);
3200
3452
  if (s.indexOf('\\n') >= 0) return true;
@@ -3265,7 +3517,9 @@ a{color:var(--focus)}
3265
3517
  : (' <span class="muted tip-id" title="' + esc(n.id) + '">' + tipId(n.id, 8) + '</span>');
3266
3518
  return '<details class="node-presence node-' + kind + '"' + open + '>' +
3267
3519
  '<summary><span class="presence-summary-text">' + summaryText + '</span>' +
3268
- idHint + '</summary>' +
3520
+ idHint +
3521
+ (kind === 'removed' ? ' ' + editorJumpLink('node', n.id, 'Show in editor', 'editor-jump-small') : '') +
3522
+ '</summary>' +
3269
3523
  '<div class="presence-body">' + body + '</div></details>';
3270
3524
  }
3271
3525
 
@@ -3295,7 +3549,7 @@ a{color:var(--focus)}
3295
3549
  });
3296
3550
  });
3297
3551
  }
3298
- `}a(mf,"pageClientScriptPartBDiffPresence");function hf(){return`
3552
+ `}a(Vm,"pageClientScriptPartBDiffPresence");function Ym(){return`
3299
3553
  function textChangeLineCount(ch) {
3300
3554
  return (ch.lines || []).length;
3301
3555
  }
@@ -3354,7 +3608,7 @@ a{color:var(--focus)}
3354
3608
  '<code class="diff-before" title="before">' + esc(JSON.stringify(ch.before)) + '</code>' +
3355
3609
  ' \u2192 <code class="diff-after" title="after">' + esc(JSON.stringify(ch.after)) + '</code></div>';
3356
3610
  }
3357
- `}a(hf,"pageClientScriptPartBDiffView");function gf(){return`
3611
+ `}a(Ym,"pageClientScriptPartBDiffView");function Xm(){return`
3358
3612
  function renderDiffSections(sections, mode, emptyMsg) {
3359
3613
  var parts = '';
3360
3614
  if (sections.added && sections.added.length) {
@@ -3378,7 +3632,8 @@ a{color:var(--focus)}
3378
3632
  ? ''
3379
3633
  : (' <span class="muted tip-id" title="' + esc(m.id) + '">' + tipId(m.id, 8) + '</span>');
3380
3634
  return '<div class="modified-node">' +
3381
- '<div class="modified-heading">' + heading + idHint + '</div>' +
3635
+ '<div class="modified-heading">' + heading + idHint + ' ' +
3636
+ editorJumpLink('node', m.id, 'Show in editor', 'editor-jump-small') + '</div>' +
3382
3637
  (m.changes || []).map(function (ch) { return renderPropertyChange(ch, mode); }).join('') +
3383
3638
  '</div>';
3384
3639
  }).join('') + '</div>';
@@ -3442,7 +3697,7 @@ a{color:var(--focus)}
3442
3697
  // Full view kept for mode-toggle re-render inside .proposal-changes.
3443
3698
  return renderHumanDiff(diff, mode);
3444
3699
  }
3445
- `}a(gf,"pageClientScriptPartBDiffView2");function yf(){return`
3700
+ `}a(Xm,"pageClientScriptPartBDiffView2");function Zm(){return`
3446
3701
  function updatePendingBadges() {
3447
3702
  var counts = countPendingBuckets();
3448
3703
  var fullText = formatPendingBadgeText(counts.live, counts.stale);
@@ -3558,7 +3813,7 @@ a{color:var(--focus)}
3558
3813
  });
3559
3814
  });
3560
3815
  }
3561
- `}a(yf,"pageClientScriptPartB2");function wf(){return`
3816
+ `}a(Zm,"pageClientScriptPartB2");function Qm(){return`
3562
3817
  function renderDeleteBanner(diff) {
3563
3818
  if (!isLargeDelete(diff)) return '';
3564
3819
  var beforeN = diff.summary.beforeNodeCount || 0;
@@ -3640,7 +3895,7 @@ a{color:var(--focus)}
3640
3895
  loadProposals();
3641
3896
  loadLogs();
3642
3897
  }
3643
- `}a(wf,"pageClientScriptPartB2Actions");function bf(){return`
3898
+ `}a(Qm,"pageClientScriptPartB2Actions");function eh(){return`
3644
3899
  function renderRequestedVia(p) {
3645
3900
  if (!p.createdBy) return '';
3646
3901
  if (p.createdBy.label) {
@@ -3681,7 +3936,11 @@ a{color:var(--focus)}
3681
3936
  timeHtml = timeCell(p.createdAt);
3682
3937
  }
3683
3938
  return '<span class="proposal-meta-line">' +
3684
- (flowLabel ? '<span class="proposal-flow">' + esc(flowLabel) + '</span> \xB7 ' : '') +
3939
+ (flowLabel
3940
+ ? '<span class="proposal-flow">' +
3941
+ (proposalFlowId(p) ? editorJumpLink('flow', proposalFlowId(p), flowLabel) : esc(flowLabel)) +
3942
+ '</span> \xB7 '
3943
+ : '') +
3685
3944
  timeHtml +
3686
3945
  ' \xB7 <span class="proposal-id-meta">id ' + proposalIdChip(p.id) + '</span>' +
3687
3946
  '</span>';
@@ -3700,9 +3959,9 @@ a{color:var(--focus)}
3700
3959
  var deleteBanner = cached ? renderDeleteBanner(cached) : '';
3701
3960
  // Card-level banner even before diff loads (warnings come from list API).
3702
3961
  var cardWarns = proposalWarnings(p, cached);
3703
- var warnBanner = cardWarns.length && !cached
3962
+ var warnBanner = renderRestoredSecrets(p) + (cardWarns.length && !cached
3704
3963
  ? renderValidationWarnings(cardWarns)
3705
- : '';
3964
+ : '');
3706
3965
  var stale = proposalIsStale(p);
3707
3966
  var actions = renderPendingActions(p, stale);
3708
3967
  var changes =
@@ -3728,7 +3987,7 @@ a{color:var(--focus)}
3728
3987
  actions +
3729
3988
  '</article>';
3730
3989
  }
3731
- `}a(bf,"pageClientScriptPartB2Cards");function vf(){return`
3990
+ `}a(eh,"pageClientScriptPartB2Cards");function th(){return`
3732
3991
  function renderStaleItem(p) {
3733
3992
  var cached = proposalDiffCache[p.id];
3734
3993
  var title = titleForProposalCard(p);
@@ -3903,7 +4162,7 @@ a{color:var(--focus)}
3903
4162
  bindProposalActions(list);
3904
4163
  focusProposalFromHash();
3905
4164
  }
3906
- `}a(vf,"pageClientScriptPartB2CardsList");function kf(){return`
4165
+ `}a(th,"pageClientScriptPartB2CardsList");function nh(){return`
3907
4166
  async function refreshFlowLabels() {
3908
4167
  try {
3909
4168
  var data = await api('/flows');
@@ -3921,6 +4180,7 @@ a{color:var(--focus)}
3921
4180
  async function loadProposals(opts) {
3922
4181
  if (!aiOn) return;
3923
4182
  opts = opts || {};
4183
+ var gen = beginLoad('proposals');
3924
4184
  var flash = $('proposals-flash');
3925
4185
  if (!opts.skipRender && !opts.keepFlash) {
3926
4186
  showFlash(flash, '', true);
@@ -3932,23 +4192,30 @@ a{color:var(--focus)}
3932
4192
  var decidedLimit = Math.min(200, Math.max(DECIDED_PAGE_SIZE, decidedLoaded));
3933
4193
  var decidedData = await api('/proposals?status=' + DECIDED_STATUSES +
3934
4194
  '&limit=' + decidedLimit + '&offset=0');
4195
+ if (!isLatestLoad('proposals', gen)) return; // a newer refresh superseded this one
3935
4196
  var decidedList = (decidedData && decidedData.proposals) || [];
3936
4197
  decidedLoaded = decidedList.length;
3937
4198
  decidedTotal = Number(decidedData && decidedData.total) || decidedList.length;
3938
4199
  decidedHasMore = !!(decidedData && decidedData.hasMore);
3939
4200
  cachedProposals = ((pendingData && pendingData.proposals) || []).concat(decidedList);
3940
4201
  cachedProposals.forEach(function (p) {
3941
- var id = p && p.payload && p.payload.flowId;
4202
+ var id = proposalFlowId(p);
3942
4203
  var label = p && p.flowLabel;
3943
4204
  if (id && label) cachedFlowLabels[id] = String(label);
3944
4205
  });
3945
- if (!opts.skipRender) {
3946
- renderProposalsList();
3947
- } else {
4206
+ var sig = JSON.stringify([decidedHasMore, decidedTotal, cachedProposals.map(function (p) {
4207
+ return [p.id, p.status, p.updatedAt, p.baseRevStale, p.flowLabel, p.title];
4208
+ })]);
4209
+ var unchanged = sameAsLastRender('proposals', sig);
4210
+ if (opts.skipRender || (opts.auto && unchanged)) {
3948
4211
  updatePendingBadges();
4212
+ } else if (opts.auto) {
4213
+ preserveUiState($('proposals-list'), renderProposalsList);
4214
+ } else {
4215
+ renderProposalsList();
3949
4216
  }
3950
4217
  } catch (e) {
3951
- showFlash(flash, e.message || String(e), false);
4218
+ if (isLatestLoad('proposals', gen)) showFlash(flash, e.message || String(e), false);
3952
4219
  }
3953
4220
  }
3954
4221
 
@@ -4070,7 +4337,7 @@ a{color:var(--focus)}
4070
4337
  replaceLocationHash('#proposals');
4071
4338
  return true;
4072
4339
  }
4073
- `}a(kf,"pageClientScriptPartB2Focus");function Af(){return`
4340
+ `}a(nh,"pageClientScriptPartB2Focus");function rh(){return`
4074
4341
  function bindDiffModeToggle(host, diff) {
4075
4342
  host.querySelectorAll('[data-diff-mode]').forEach(function (btn) {
4076
4343
  btn.addEventListener('click', function () {
@@ -4182,7 +4449,7 @@ a{color:var(--focus)}
4182
4449
  esc(e.message || String(e)) + '</p>';
4183
4450
  }
4184
4451
  }
4185
- }`}a(Af,"pageClientScriptPartB2Apply");function xf(){return`
4452
+ }`}a(rh,"pageClientScriptPartB2Apply");function oh(){return`
4186
4453
 
4187
4454
  function dialogFocusables(dialog) {
4188
4455
  if (!dialog) return [];
@@ -4386,6 +4653,15 @@ a{color:var(--focus)}
4386
4653
  }
4387
4654
 
4388
4655
  async function doApproveProposal(id, deploymentType) {
4656
+ proposalActionsInFlight += 1;
4657
+ try {
4658
+ return await doApproveProposalInner(id, deploymentType);
4659
+ } finally {
4660
+ proposalActionsInFlight -= 1;
4661
+ }
4662
+ }
4663
+
4664
+ async function doApproveProposalInner(id, deploymentType) {
4389
4665
  var flash = $('proposals-flash');
4390
4666
  var pBefore = cachedProposals.filter(function (x) { return x.id === id; })[0];
4391
4667
  var toastTitle = proposalToastTitle(pBefore);
@@ -4460,6 +4736,15 @@ a{color:var(--focus)}
4460
4736
  }
4461
4737
 
4462
4738
  async function rejectProposal(id, reasonOverride, options) {
4739
+ proposalActionsInFlight += 1;
4740
+ try {
4741
+ return await rejectProposalInner(id, reasonOverride, options);
4742
+ } finally {
4743
+ proposalActionsInFlight -= 1;
4744
+ }
4745
+ }
4746
+
4747
+ async function rejectProposalInner(id, reasonOverride, options) {
4463
4748
  var flash = $('proposals-flash');
4464
4749
  var reason = reasonOverride && String(reasonOverride).trim()
4465
4750
  ? String(reasonOverride).trim()
@@ -4496,7 +4781,7 @@ a{color:var(--focus)}
4496
4781
  throw e;
4497
4782
  }
4498
4783
  }
4499
- `}a(xf,"pageClientScriptPartC");function Sf(){return`
4784
+ `}a(oh,"pageClientScriptPartC");function sh(){return`
4500
4785
  var cachedLogs = [];
4501
4786
 
4502
4787
  function renderLogsTable(logs) {
@@ -4533,10 +4818,12 @@ a{color:var(--focus)}
4533
4818
  renderLogsTable(filtered);
4534
4819
  }
4535
4820
 
4536
- async function loadLogs() {
4821
+ async function loadLogs(opts) {
4537
4822
  if (!aiOn) return;
4823
+ opts = opts || {};
4538
4824
  var flash = $('logs-flash');
4539
- showFlash(flash, '', true);
4825
+ if (!opts.auto) showFlash(flash, '', true);
4826
+ var gen = beginLoad('logs');
4540
4827
  try {
4541
4828
  // Need proposals (+ diffs) so audit rows use the same render-time titles.
4542
4829
  if (!cachedProposals.length) {
@@ -4546,7 +4833,10 @@ a{color:var(--focus)}
4546
4833
  try { await loadLinks(); } catch (e) { /* best-effort for link label lookup */ }
4547
4834
  }
4548
4835
  var data = await api('/logs?limit=100');
4549
- cachedLogs = (data && data.logs) || [];
4836
+ if (!isLatestLoad('logs', gen)) return;
4837
+ var newLogs = (data && data.logs) || [];
4838
+ if (sameAsLastRender('logs', JSON.stringify(newLogs)) && opts.auto) return;
4839
+ cachedLogs = newLogs;
4550
4840
  var filter = $('logs-filter');
4551
4841
  if (filter) {
4552
4842
  var actions = [];
@@ -4573,7 +4863,7 @@ a{color:var(--focus)}
4573
4863
  showFlash(flash, e.message || String(e), false);
4574
4864
  }
4575
4865
  }
4576
- `}a(Sf,"pageClientScriptPartCLogs");function Tf(){return`
4866
+ `}a(sh,"pageClientScriptPartCLogs");function ih(){return`
4577
4867
  function applyStatus(s) {
4578
4868
  aiOn = !!s.aiFeaturesEnabled;
4579
4869
  canManage = !!(s.capabilities && s.capabilities.manageLinks);
@@ -4734,7 +5024,7 @@ a{color:var(--focus)}
4734
5024
  });
4735
5025
  }
4736
5026
  }
4737
- `}a(Tf,"pageClientScriptPartD");function Pf(){return`
5027
+ `}a(ih,"pageClientScriptPartD");function ah(){return`
4738
5028
  async function boot() {
4739
5029
  accessToken = readEditorToken();
4740
5030
  if (!accessToken) {
@@ -4838,9 +5128,126 @@ a{color:var(--focus)}
4838
5128
  loadTabData(tab);
4839
5129
  });
4840
5130
 
5131
+ ${dh()}
5132
+ ${lh()}
4841
5133
  boot();
4842
5134
  })();
4843
- `}a(Pf,"pageClientScriptPartDBoot");function Ha(){return(sf()+af()+df()+lf()+cf()+uf()+pf()+ff()+mf()+hf()+gf()+yf()+wf()+bf()+vf()+kf()+Af()+xf()+Sf()+Tf()+ja()+Pf()).trim()}a(Ha,"pageClientScript");function Rf(){return`
5135
+ `}a(ah,"pageClientScriptPartDBoot");function dh(){return`
5136
+ /*
5137
+ * Jump links to the editor (#flow/<id>, #node/<id>). A click first asks an
5138
+ * already-open editor tab (BroadcastChannel 'node-red-ai-access', answered by
5139
+ * the editor sidebar script) to reveal the item; without an ack within 300 ms
5140
+ * it opens the editor in a named window so later clicks reuse that tab.
5141
+ */
5142
+ var EDITOR_WINDOW_NAME = 'node-red-editor';
5143
+ var REVEAL_CHANNEL = 'node-red-ai-access';
5144
+ function editorHashHref(kind, id) {
5145
+ var base = editorRoot && editorRoot !== '/' ? String(editorRoot).replace(/\\/$/, '') + '/' : '/';
5146
+ return base + '#' + (kind === 'flow' ? 'flow' : 'node') + '/' + encodeURIComponent(String(id));
5147
+ }
5148
+ function editorJumpLink(kind, id, text, extraClass) {
5149
+ if (!id) return esc(text);
5150
+ return '<a class="editor-jump' + (extraClass ? ' ' + extraClass : '') + '" href="' +
5151
+ esc(editorHashHref(kind, id)) + '" target="' + EDITOR_WINDOW_NAME + '" rel="noopener" data-reveal-kind="' +
5152
+ esc(kind) + '" data-reveal-id="' + esc(String(id)) + '" title="Open in the Node-RED editor">' +
5153
+ esc(text) + '</a>';
5154
+ }
5155
+ function revealInEditor(kind, id, href) {
5156
+ var opened = false;
5157
+ function openFallback() {
5158
+ if (opened) return;
5159
+ opened = true;
5160
+ var w = window.open(href, EDITOR_WINDOW_NAME);
5161
+ if (w) { try { w.focus(); } catch (e) { /* ignore */ } }
5162
+ }
5163
+ if (typeof BroadcastChannel === 'undefined') { openFallback(); return; }
5164
+ var ch;
5165
+ try { ch = new BroadcastChannel(REVEAL_CHANNEL); } catch (e) { openFallback(); return; }
5166
+ var reqId = String(Date.now()) + '-' + String(Math.random()).slice(2, 10);
5167
+ var notFound = false;
5168
+ var timer = setTimeout(function () {
5169
+ try { ch.close(); } catch (e) { /* ignore */ }
5170
+ if (notFound && !opened) {
5171
+ opened = true;
5172
+ if (typeof showToast === 'function') {
5173
+ showToast('Not found in the open editor (not deployed yet, or deleted)', false);
5174
+ }
5175
+ return;
5176
+ }
5177
+ openFallback();
5178
+ }, 300);
5179
+ ch.onmessage = function (ev) {
5180
+ var m = ev && ev.data;
5181
+ if (!m || m.type !== 'reveal-ack' || m.reqId !== reqId || opened) return;
5182
+ if (!m.ok) { notFound = true; return; } // another editor tab may still find it
5183
+ opened = true;
5184
+ clearTimeout(timer);
5185
+ try { ch.close(); } catch (e) { /* ignore */ }
5186
+ // Browsers do not let a page focus another tab; tell the user where it went.
5187
+ if (typeof showToast === 'function') showToast('Shown in your open Node-RED editor tab', true);
5188
+ };
5189
+ ch.postMessage({ type: 'reveal', kind: kind, id: String(id), reqId: reqId });
5190
+ }
5191
+ document.addEventListener('click', function (ev) {
5192
+ var t = ev.target;
5193
+ var a = t && t.closest ? t.closest('a[data-reveal-id]') : null;
5194
+ if (!a) return;
5195
+ if (ev.button || ev.metaKey || ev.ctrlKey || ev.shiftKey || ev.altKey) return; // new tab etc.
5196
+ ev.preventDefault();
5197
+ ev.stopPropagation();
5198
+ revealInEditor(a.getAttribute('data-reveal-kind'), a.getAttribute('data-reveal-id'), a.getAttribute('href'));
5199
+ });
5200
+ `}a(dh,"pageClientEditorJumpScript");function lh(){return`
5201
+ /*
5202
+ * Auto-refresh when a tab left open becomes visible / focused again.
5203
+ * Throttled; skipped while a dialog (e.g. the one-time token modal) is open or
5204
+ * the user is typing in a field, so unsaved edits are never clobbered.
5205
+ * Settings forms are never reloaded automatically.
5206
+ */
5207
+ var AUTO_REFRESH_MIN_MS = 5000;
5208
+ var lastAutoRefresh = Date.now();
5209
+ var autoRefreshing = false;
5210
+ function autoRefreshBlocked() {
5211
+ if (document.querySelector('dialog[open]')) return true;
5212
+ var el = document.activeElement;
5213
+ if (el && el !== document.body) {
5214
+ var tag = (el.tagName || '').toLowerCase();
5215
+ if (tag === 'input' || tag === 'textarea' || tag === 'select' || el.isContentEditable) return true;
5216
+ }
5217
+ return false;
5218
+ }
5219
+ async function autoRefresh() {
5220
+ if (document.visibilityState === 'hidden' || autoRefreshing) return;
5221
+ if (Date.now() - lastAutoRefresh < AUTO_REFRESH_MIN_MS || autoRefreshBlocked()) return;
5222
+ if (typeof proposalActionsInFlight === 'number' && proposalActionsInFlight > 0) return;
5223
+ lastAutoRefresh = Date.now();
5224
+ autoRefreshing = true;
5225
+ try {
5226
+ var gen = beginLoad('status');
5227
+ var s = await api('/status');
5228
+ if (!isLatestLoad('status', gen)) return;
5229
+ applyStatus(s);
5230
+ var sel = document.querySelector('[role="tab"][aria-selected="true"]');
5231
+ var tab = sel ? sel.getAttribute('data-tab') : '';
5232
+ if (aiOn) {
5233
+ if (canManage) loadLinks({ auto: true });
5234
+ loadProposals({ keepFlash: true, auto: true });
5235
+ loadLogs({ auto: true });
5236
+ }
5237
+ if (tab === 'oauth') { loadOAuthStatus(); loadOAuthClients(); }
5238
+ document.documentElement.setAttribute('data-auto-refreshed', String(lastAutoRefresh));
5239
+ } catch (e) {
5240
+ // Quiet: the manual Refresh button still reports errors.
5241
+ } finally {
5242
+ autoRefreshing = false;
5243
+ }
5244
+ }
5245
+ document.addEventListener('visibilitychange', function () {
5246
+ if (document.visibilityState === 'visible') autoRefresh();
5247
+ });
5248
+ window.addEventListener('focus', autoRefresh);
5249
+
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`
4844
5251
  :root {
4845
5252
  color-scheme: light;
4846
5253
  --bg: #eef2f5;
@@ -4910,7 +5317,7 @@ h1 {
4910
5317
  text-decoration: none;
4911
5318
  text-align: center;
4912
5319
  }
4913
- `}a(Rf,"pageStylesPartA");function _f(){return`
5320
+ `}a(ch,"pageStylesPartA");function uh(){return`
4914
5321
  nav.tabs {
4915
5322
  position: sticky;
4916
5323
  top: 0;
@@ -5004,7 +5411,7 @@ dd {
5004
5411
  padding: 0.22rem 0.55rem;
5005
5412
  border-radius: 6px;
5006
5413
  cursor: help;
5007
- background: var`}a(_f,"pageStylesPartA2");function Lf(){return`(--accent-soft);
5414
+ background: var`}a(uh,"pageStylesPartA2");function ph(){return`(--accent-soft);
5008
5415
  color: var(--accent);
5009
5416
  font-size: 0.82rem;
5010
5417
  font-weight: 650;
@@ -5195,7 +5602,7 @@ dialog.token-modal { width: min(720px, 94vw); max-width: min(720px, 94vw); max-h
5195
5602
  color: var(--muted);
5196
5603
  font-size: 0.92rem;
5197
5604
  }
5198
- `}a(Lf,"pageStylesPartB");function Cf(){return`
5605
+ `}a(ph,"pageStylesPartB");function fh(){return`
5199
5606
  .logs-toolbar { margin-bottom: 0.65rem; }
5200
5607
  table.audit-table {
5201
5608
  table-layout: fixed;
@@ -5271,7 +5678,7 @@ table.audit-table {
5271
5678
  cursor: help;
5272
5679
  border-bottom: 1px dotted var(--line);
5273
5680
  }
5274
- `}a(Cf,"pageStylesPartC");function Ef(){return`
5681
+ `}a(fh,"pageStylesPartC");function mh(){return`
5275
5682
  .proposal-diff {
5276
5683
  margin: 0.65rem 0;
5277
5684
  padding: 0.55rem 0.65rem;
@@ -5348,7 +5755,7 @@ table.audit-table {
5348
5755
  color: var(--muted);
5349
5756
  margin-bottom: 0.15rem;
5350
5757
  }
5351
- `}a(Ef,"pageStylesPartD");function If(){return`
5758
+ `}a(mh,"pageStylesPartD");function hh(){return`
5352
5759
  .diff-block {
5353
5760
  margin-top: 0.25rem;
5354
5761
  font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
@@ -5430,7 +5837,7 @@ table.audit-table {
5430
5837
  }
5431
5838
  .diff-before { color: var(--danger); word-break: break-word; }
5432
5839
  .diff-after { color: var(--ok); word-break: break-word; }
5433
- `}a(If,"pageStylesPartE");function Nf(){return`
5840
+ `}a(hh,"pageStylesPartE");function gh(){return`
5434
5841
  .proposal-detail {
5435
5842
  scroll-margin-top: 4.5rem;
5436
5843
  }
@@ -5556,7 +5963,7 @@ table.audit-table {
5556
5963
  .audit-table .col-action { width: 5.5rem; min-width: 5.5rem; }
5557
5964
  .audit-table .col-actor { width: 7rem; min-width: 6rem; }
5558
5965
  }
5559
- `}a(Nf,"pageStylesPartF");function Ff(){return`
5966
+ `}a(gh,"pageStylesPartF");function yh(){return`
5560
5967
  .dash-grid {
5561
5968
  display: grid;
5562
5969
  grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
@@ -5657,6 +6064,10 @@ button.id-copy:focus {
5657
6064
  .proposal-plain-summary { margin: 0.15rem 0 0.35rem; color: var(--ink); font-size: 0.95rem; }
5658
6065
  .proposal-meta-line { color: var(--muted); font-size: 0.88rem; }
5659
6066
  .proposal-flow { color: var(--ink); font-weight: 600; }
6067
+ .editor-jump { color: inherit; text-decoration: underline dotted; text-underline-offset: 2px; }
6068
+ .editor-jump:hover { color: var(--accent, #8f0000); text-decoration-style: solid; }
6069
+ .editor-jump-small { font-size: 12px; font-weight: 500; margin-left: 6px; }
6070
+ .editor-jump-small::after { content: ' \\2197'; }
5660
6071
  .proposal-changes { margin: 0.35rem 0 0.55rem; }
5661
6072
  .delete-banner {
5662
6073
  margin: 0 0 0.55rem;
@@ -5668,7 +6079,18 @@ button.id-copy:focus {
5668
6079
  font-weight: 650;
5669
6080
  font-size: 0.92rem;
5670
6081
  }
5671
- .proposal-warnings {
6082
+ .secret-patterns-empty-warning {
6083
+ list-style: none;
6084
+ color: #b42318;
6085
+ font-weight: 600;
6086
+ }
6087
+ .restored-secrets-danger {
6088
+ border-color: #b42318 !important;
6089
+ background: #fef3f2 !important;
6090
+ color: #7a271a;
6091
+ }
6092
+ .restored-secrets-danger .proposal-warnings-title { color: #b42318; font-weight: 700; }
6093
+ .proposal-warnings {
5672
6094
  margin: 0 0 0.55rem;
5673
6095
  padding: 0.55rem 0.7rem;
5674
6096
  background: #fff8e8;
@@ -5773,7 +6195,7 @@ button.id-copy:focus {
5773
6195
  background: var(--code-bg, var(--soft));
5774
6196
  border-radius: 4px;
5775
6197
  }
5776
- `}a(Ff,"pageStylesPartG");function Of(){return`
6198
+ `}a(yh,"pageStylesPartG");function wh(){return`
5777
6199
  .stale-group {
5778
6200
  margin: 1rem 0 0;
5779
6201
  border: 1px solid var(--warn-border);
@@ -5937,7 +6359,7 @@ dialog h3 { margin: 0 0 0.4rem; }
5937
6359
  margin-left: auto;
5938
6360
  }
5939
6361
  .debug-toggle input { width: auto; }
5940
- `}a(Of,"pageStylesPartH");function $f(){return`
6362
+ `}a(wh,"pageStylesPartH");function bh(){return`
5941
6363
  .card {
5942
6364
  background: var(--card);
5943
6365
  border: 1px solid var(--line);
@@ -5974,6 +6396,18 @@ dialog h3 { margin: 0 0 0.4rem; }
5974
6396
  font-size: 0.8rem; color: var(--muted); border: 1px solid var(--line);
5975
6397
  border-radius: 999px; padding: 0.1rem 0.5rem; white-space: nowrap;
5976
6398
  }
6399
+ .chip-list { list-style: none; display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0.5rem 0 0.6rem; padding: 0; }
6400
+ .chip {
6401
+ display: inline-flex; align-items: center; gap: 0.25rem; padding: 0.15rem 0.25rem 0.15rem 0.55rem;
6402
+ border: 1px solid var(--line); border-radius: 999px; background: var(--soft); font-size: 0.85rem;
6403
+ }
6404
+ .chip code { background: none; padding: 0; }
6405
+ .chip-remove {
6406
+ border: 0; background: transparent; color: var(--muted); cursor: pointer; font-size: 1rem;
6407
+ line-height: 1; padding: 0.1rem 0.35rem; border-radius: 999px;
6408
+ }
6409
+ .chip-remove:hover, .chip-remove:focus-visible { color: var(--danger); background: #fdecec; }
6410
+ .chip-empty { font-size: 0.88rem; }
5977
6411
  .notice { padding: 0.6rem 0.8rem; border-radius: 8px; background: var(--soft); border: 1px solid var(--line); }
5978
6412
  .check-result { margin: 0.6rem 0 0; padding: 0.6rem 0.8rem; border-radius: 8px; font-weight: 600; overflow-wrap: anywhere; }
5979
6413
  .check-result.is-ok { color: var(--ok); background: #e8f6ef; border: 1px solid #a8d8b4; }
@@ -5995,7 +6429,7 @@ dialog h3 { margin: 0 0 0.4rem; }
5995
6429
  nav.tabs button { flex: 0 0 auto; }
5996
6430
  .field-line { flex-wrap: wrap; }
5997
6431
  }
5998
- `}a($f,"pageStylesDashboard");function Ua(){return(Rf()+_f()+Lf()+Cf()+Ef()+If()+Nf()+Ff()+Of()+$f()).trim()}a(Ua,"pageStyles");function Mf(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;")}a(Mf,"escapeHtml");function Df(){return`
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`
5999
6433
  <header class="page-header">
6000
6434
  <h1>Node-RED AI Access</h1>
6001
6435
  <p class="lede">
@@ -6026,7 +6460,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6026
6460
  Proposals <span class="tab-badge" id="proposals-pending-badge" hidden>0</span>
6027
6461
  </button>
6028
6462
  <button type="button" data-tab="logs" aria-selected="false">Audit log</button>
6029
- </nav>`}a(Df,"renderMcpPageHeader");function jf(){return`
6463
+ </nav>`}a(kh,"renderMcpPageHeader");function Ah(){return`
6030
6464
  <section class="panel active" id="section-status" aria-label="Overview" hidden>
6031
6465
  <h2>Overview</h2>
6032
6466
  <div id="status-flash" class="flash" hidden></div>
@@ -6070,7 +6504,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6070
6504
  <div class="row" style="margin-top:1rem">
6071
6505
  <button type="button" class="action secondary" id="refresh-status">Refresh</button>
6072
6506
  </div>
6073
- </section>`}a(jf,"renderMcpPageStatusSection");function Hf(){return`
6507
+ </section>`}a(Ah,"renderMcpPageStatusSection");function Sh(){return`
6074
6508
  <section class="panel" id="section-links" aria-label="Access tokens" hidden>
6075
6509
  <h2>Access tokens</h2>
6076
6510
  <p class="muted" id="links-intro">
@@ -6081,7 +6515,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6081
6515
  <div id="links-flash" class="flash" hidden></div>
6082
6516
  <div id="links-manage"></div>
6083
6517
  <div id="links-list"></div>
6084
- </section>`}a(Hf,"renderMcpPageLinksSection");function Pe(e){return`<span class="lock-badge" id="lock-${e}" hidden>Locked by settings.js</span>`}a(Pe,"lockBadge");function Uf(){return`
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`
6085
6519
  <section class="panel" id="section-settings" aria-label="Settings" hidden>
6086
6520
  <h2>Settings</h2>
6087
6521
  <p class="muted">Changes save automatically and apply immediately (no restart).</p>
@@ -6094,13 +6528,13 @@ dialog h3 { margin: 0 0 0.4rem; }
6094
6528
  <label class="switch-label"><input type="checkbox" id="set-enabled" />
6095
6529
  <span><strong>Enable AI Access features</strong>
6096
6530
  <span class="hint">When off, access tokens, proposals, and AI tools are disabled.</span></span></label>
6097
- ${Pe("enabled")}
6531
+ ${be("enabled")}
6098
6532
  </div>
6099
6533
  <div class="form-row toggle-row" id="row-allow-without" hidden>
6100
6534
  <label class="switch-label"><input type="checkbox" id="set-allow-without" />
6101
6535
  <span><strong>Allow AI Access without adminAuth (local-only)</strong>
6102
6536
  <span class="hint danger-text">Not for production. Anyone who can reach the editor admin port can use AI features.</span></span></label>
6103
- ${Pe("allow-without")}
6537
+ ${be("allow-without")}
6104
6538
  </div>
6105
6539
  <div class="form-row">
6106
6540
  <label for="set-deploy">Default deploy type</label>
@@ -6110,7 +6544,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6110
6544
  <option value="flows">Changed flows</option>
6111
6545
  <option value="full">Full redeploy</option>
6112
6546
  </select>
6113
- ${Pe("deploy")}
6547
+ ${be("deploy")}
6114
6548
  </div>
6115
6549
  <p class="hint danger-text" id="set-deploy-full-warn" hidden>Full redeploy restarts the entire runtime.</p>
6116
6550
  </div>
@@ -6119,7 +6553,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6119
6553
  <div class="field-line">
6120
6554
  <input type="number" id="set-ttl" min="1" step="1" class="num" />
6121
6555
  <span class="unit">hours</span>
6122
- ${Pe("ttl")}
6556
+ ${be("ttl")}
6123
6557
  </div>
6124
6558
  </div>
6125
6559
  <div class="form-row">
@@ -6127,7 +6561,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6127
6561
  <div class="field-line">
6128
6562
  <input type="number" id="set-max-links" min="1" step="1" class="num" />
6129
6563
  <span class="unit">tokens</span>
6130
- ${Pe("max-links")}
6564
+ ${be("max-links")}
6131
6565
  </div>
6132
6566
  </div>
6133
6567
  </div>
@@ -6140,7 +6574,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6140
6574
  <div class="field-line">
6141
6575
  <input type="url" id="set-public-url" placeholder="https://nodered.example.com" class="grow" />
6142
6576
  <button type="button" class="action secondary" id="set-check-public-url">Check</button>
6143
- ${Pe("public-url")}
6577
+ ${be("public-url")}
6144
6578
  </div>
6145
6579
  <div id="set-public-url-warning" class="hint warn-text" role="status" hidden></div>
6146
6580
  <div id="set-check-result" class="check-result" aria-live="polite" hidden></div>
@@ -6158,8 +6592,24 @@ dialog h3 { margin: 0 0 0.4rem; }
6158
6592
  <label for="set-cors">Allowed origins <span class="hint">(comma separated)</span></label>
6159
6593
  <div class="field-line">
6160
6594
  <input type="text" id="set-cors" class="grow" placeholder="https://app.example.com" />
6161
- ${Pe("cors")}
6595
+ ${be("cors")}
6596
+ </div>
6597
+ </div>
6598
+ </div>
6599
+
6600
+ <div class="card settings-card" id="secret-redaction-card">
6601
+ <h3>Secret redaction</h3>
6602
+ <p class="hint">Values whose names match these words are shown to AI clients as <code>[REDACTED]</code>: env variables (flow, group, subflow, node) and secret-named node settings. Known token formats (GitHub, AWS, Slack, OpenAI, Google, JWT, private keys, Bearer headers, URL passwords) are always redacted. Matching is case-insensitive on whole words, so <code>KEY</code> matches <code>API_KEY</code> but not <code>KEYBOARD</code>. The dashboard always shows real values, and proposals keep secrets the AI left as <code>[REDACTED]</code>.</p>
6603
+ <div class="form-row">
6604
+ <span class="label" id="secret-patterns-label">Secret name patterns <span class="hint" id="secret-patterns-source"></span></span>
6605
+ <ul class="chip-list" id="secret-patterns" aria-labelledby="secret-patterns-label"></ul>
6606
+ <div class="field-line">
6607
+ <input type="text" id="set-secret-new" class="grow" maxlength="40" placeholder="e.g. PIN or SIGNING_KEY" aria-label="New secret name pattern" data-no-autosave="1" />
6608
+ <button type="button" class="action secondary" id="set-secret-add">Add</button>
6609
+ <button type="button" class="action secondary" id="set-secret-reset">Reset to defaults</button>
6610
+ ${be("secret-patterns")}
6162
6611
  </div>
6612
+ <p class="hint">Letters, digits, <code>_</code> or <code>-</code> (2\u201340 characters). An empty list turns off name-based redaction; token formats are still redacted.</p>
6163
6613
  </div>
6164
6614
  </div>
6165
6615
 
@@ -6170,7 +6620,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6170
6620
  <div class="field-line">
6171
6621
  <input type="number" id="set-rate-window" min="1" step="1" class="num" />
6172
6622
  <span class="unit">ms</span>
6173
- ${Pe("rate-window")}
6623
+ ${be("rate-window")}
6174
6624
  </div>
6175
6625
  </div>
6176
6626
  <div class="form-row">
@@ -6178,7 +6628,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6178
6628
  <div class="field-line">
6179
6629
  <input type="number" id="set-rate-ip" min="1" step="1" class="num" />
6180
6630
  <span class="unit">requests / window</span>
6181
- ${Pe("rate-ip")}
6631
+ ${be("rate-ip")}
6182
6632
  </div>
6183
6633
  </div>
6184
6634
  <div class="form-row">
@@ -6186,13 +6636,13 @@ dialog h3 { margin: 0 0 0.4rem; }
6186
6636
  <div class="field-line">
6187
6637
  <input type="number" id="set-rate-token" min="1" step="1" class="num" />
6188
6638
  <span class="unit">requests / window</span>
6189
- ${Pe("rate-token")}
6639
+ ${be("rate-token")}
6190
6640
  </div>
6191
6641
  </div>
6192
6642
  <p class="hint" id="set-trust-proxy-inline"></p>
6193
6643
  </div>
6194
6644
  </div>
6195
- </section>`}a(Uf,"renderMcpPageSettingsSection");function Bf(){return`
6645
+ </section>`}a(xh,"renderMcpPageSettingsSection");function Th(){return`
6196
6646
  <section class="panel" id="section-oauth" aria-label="OAuth" hidden>
6197
6647
  <h2>MCP OAuth</h2>
6198
6648
  <p class="muted">MCP clients (ChatGPT, Claude, \u2026) can request read or propose access through OAuth.</p>
@@ -6205,7 +6655,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6205
6655
  <label class="switch-label"><input type="checkbox" id="oauth-enabled" />
6206
6656
  <span><strong>Enable MCP OAuth</strong>
6207
6657
  <span class="hint">Also available in the editor sidebar (AI Access tab).</span></span></label>
6208
- ${Pe("oauth")}
6658
+ ${be("oauth")}
6209
6659
  </div>
6210
6660
  <div class="form-row">
6211
6661
  <span class="label">Server URL for MCP clients</span>
@@ -6267,7 +6717,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6267
6717
  <button type="button" class="action secondary" id="oauth-refresh">Refresh</button></div>
6268
6718
  <div id="oauth-clients" aria-live="polite"><p class="muted">Loading\u2026</p></div>
6269
6719
  </div>
6270
- </section>`}a(Bf,"renderMcpPageOAuthSection");function qf(){return`
6720
+ </section>`}a(Th,"renderMcpPageOAuthSection");function Rh(){return`
6271
6721
  <section class="panel" id="section-proposals" aria-label="Proposals" hidden>
6272
6722
  <h2>Proposals</h2>
6273
6723
  <p id="proposals-hint" class="muted"></p>
@@ -6285,7 +6735,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6285
6735
  </div>
6286
6736
  <div id="proposals-flash" class="flash" hidden></div>
6287
6737
  <div id="proposals-list"><p class="muted">Loading\u2026</p></div>
6288
- </section>`}a(qf,"renderMcpPageProposalsSection");function Wf(){return`
6738
+ </section>`}a(Rh,"renderMcpPageProposalsSection");function _h(){return`
6289
6739
  <section class="panel" id="section-logs" aria-label="Audit log" hidden>
6290
6740
  <h2>Audit log</h2>
6291
6741
  <div id="logs-flash" class="flash" hidden></div>
@@ -6301,7 +6751,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6301
6751
  </label>
6302
6752
  </div>
6303
6753
  <div id="logs-list"><p class="muted">Loading\u2026</p></div>
6304
- </section>`}a(Wf,"renderMcpPageLogsSection");function zf(){return`
6754
+ </section>`}a(_h,"renderMcpPageLogsSection");function Ph(){return`
6305
6755
  <dialog id="reject-dialog">
6306
6756
  <form method="dialog" id="reject-form">
6307
6757
  <h3>Reject proposal</h3>
@@ -6343,7 +6793,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6343
6793
  <button type="submit" value="confirm" class="action danger" id="confirm-action-btn">Confirm</button>
6344
6794
  </div>
6345
6795
  </form>
6346
- </dialog>`}a(zf,"renderMcpPageDialogs");function Ba(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>
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>
6347
6797
  <html lang="en">
6348
6798
  <head>
6349
6799
  <meta charset="utf-8" />
@@ -6351,23 +6801,23 @@ dialog h3 { margin: 0 0 0.4rem; }
6351
6801
  <meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate" />
6352
6802
  <meta http-equiv="Pragma" content="no-cache" />
6353
6803
  <title>Node-RED AI Access</title>
6354
- <style>${Ua()}</style>
6804
+ <style>${Ud()}</style>
6355
6805
  </head>
6356
6806
  <body>
6357
6807
  <main
6358
6808
  id="mcp-root"
6359
- data-config="${Mf(JSON.stringify(t))}"
6360
- >${Df()}
6361
- ${jf()}
6362
- ${Uf()}
6363
- ${Hf()}
6364
- ${Bf()}
6365
- ${qf()}
6366
- ${Wf()}
6367
- ${zf()}
6809
+ data-config="${vh(JSON.stringify(t))}"
6810
+ >${kh()}
6811
+ ${Ah()}
6812
+ ${xh()}
6813
+ ${Sh()}
6814
+ ${Th()}
6815
+ ${Rh()}
6816
+ ${_h()}
6817
+ ${Ph()}
6368
6818
  </main>
6369
- <script>${Ha()}</script>
6819
+ <script>${Hd()}</script>
6370
6820
  </body>
6371
- </html>`}a(Ba,"renderMcpPage");Ae();ve();Ge();G();it();K();Fe();Xe();Ae();function Tt(e,t,n,r={}){e.setHeader("X-Content-Type-Options","nosniff"),e.setHeader("Content-Type","application/json; charset=utf-8");let o={error:Oe[n]??"Request failed",code:n,...r},s=se(n,"rest");s&&typeof o.hint!="string"&&(o.hint=s),e.status(t).json(o)}a(Tt,"sendSafeJsonError");function qa(e){let t={kind:"admin"};return typeof e.user?.username=="string"&&(t.username=e.user.username),t}a(qa,"adminActor");function Wa(e,t,n,r,o){return async(s,i)=>{if(!s.accessLink){i.setHeader("WWW-Authenticate",Y),i.status(401).json({error:le,code:Le});return}try{let d=q(s,r),l=re(s.query),c=await Ye(e,t,s.accessLink,s.body??{},n,d,o),u=V(e,c.expectedRev,{flowId:c.payload.flowId}),p=Ie(e,e.get(c.id),s.accessLink.id),f=c.warnings;i.status(201).json({...ce(c,{...d,include:l}),mine:!0,...Ne(p),...Q(u),...f&&f.length>0?{warnings:f}:{}})}catch(d){let l=O(d,"rest");if(l){i.status(l.statusCode).json(l.body);return}$(i,d);return}}}a(Wa,"createProposalHandler");function za(e,t,n){return async(r,o)=>{let s,i;try{let S=await t.getFlows();s=S.rev,i=dr(S.flows)}catch{}let d={...q(r,n),...i?{liveFlowLabels:i}:{}},l=re(r.query),c;try{c=mr(r.query)}catch(S){$(o,S);return}let u=s?V(e,s):0,p=r.accessLink;if(p){let S=qt(e,p,d,c),E=S.proposals.map(I=>{let N=ue(e,p,I,{...d,include:l},{includeConflicts:!1});return N.status!=="pending"||s===void 0?N:{...N,baseRevStale:N.expectedRev!==s}});o.json({proposals:E,...ft({...S,returned:S.proposals.length}),...Q(u)});return}let f={};c.flowId&&(f.flowId=c.flowId);let m=Ys(e.list(f).filter(S=>Co(S.status,c.status)),{...d,include:l}),h=c.offset??0,x=(c.limit!==void 0?m.slice(h,h+c.limit):h>0?m.slice(h):m).map(S=>S.status!=="pending"||s===void 0?S:{...S,baseRevStale:S.expectedRev!==s});o.json({proposals:x,...ft({total:m.length,limit:c.limit??null,offset:h,returned:x.length}),...Q(u)})}}a(za,"listProposalsHandler");function Ga(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=re(r.query),l;if(n)try{l=(await n.getFlows()).rev}catch{}try{if(r.accessLink){let c=De(e,r.accessLink,s,i);o.json(ue(e,r.accessLink,c,{...i,include:d},{...l?{liveRev:l}:{}}));return}l&&V(e,l),o.json(ce(e.get(s),{...i,include:d}))}catch(c){if(c instanceof ne){Tt(o,404,c.code);return}let u=O(c,"rest");if(u){o.status(u.statusCode).json(u.body);return}$(o,c);return}}}a(Ga,"getProposalHandler");function Ja(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,qa(n),i);r.json(oe(d,q(n,t)))}catch(d){if(d instanceof ne||d instanceof te||d instanceof kn||d instanceof X){Tt(r,d.statusCode,d.code);return}$(r,d);return}}}a(Ja,"approveProposalHandler");function Ka(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=oe(e.reject(o,qa(n),i),q(n,t));r.json(d)}catch(d){if(d instanceof ne||d instanceof te||d instanceof X){Tt(r,d.statusCode,d.code);return}$(r,d);return}}}a(Ka,"rejectProposalHandler");function Va(e,t,n){return(r,o)=>{if(!r.accessLink){o.setHeader("WWW-Authenticate",Y),o.status(401).json({error:le,code:Le});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=re(r.query);try{let c=Wt(e,t,r.accessLink,s,i.reason,d);o.json(ue(e,r.accessLink,c,{...d,include:l}))}catch(c){let u=O(c,"rest");if(u){Tt(o,u.statusCode,u.body.code);return}Tt(o,500,"internal_error");return}}}a(Va,"withdrawProposalHandler");function Xa(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?De(e,n.accessLink,o):e.get(o),i=Yt(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=Qt(await nn(s,t),i.format);r.json(d)}catch(s){if(s instanceof ne){Tt(r,404,s.code);return}if(s instanceof z||s instanceof be||s instanceof Ce){Tt(r,s.statusCode,s.code);return}let i=O(s,"rest");if(i){r.status(i.statusCode).json(i.body);return}$(r,s);return}}}a(Xa,"getProposalDiffHandler");ve();ve();or();function Gf(e){return e.endsWith("/")?e.slice(0,-1):e}a(Gf,"trimTrailingSlash");var Wr=class{static{a(this,"HttpAdapter")}kind="http";baseUrl;accessToken;fetchImpl;constructor(t){this.baseUrl=Gf(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 z(n):t.status===409?new J("unknown","unknown",`Admin API ${n}`):t.status===401||t.status===403?new Ce(Qn):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 An(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 z(t);let s=await this.readJson(r,"GET /flows"),i=await this.readJson(o,`GET /flow/${t}`);return{rev:An(s,"GET /flows").rev,flow:$t(t,i)}}async updateFlow(t){let n=t.deploymentType??"nodes",r=await this.getFlows();if(r.rev!==t.expectedRev)throw new J(t.expectedRev,r.rev,"updateFlow");let o=rr(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 J(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}}};ve();or();function Jf(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(Jf,"requireFlowsApi");function Kf(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(Kf,"resolveRev");var zr=class{static{a(this,"RedAdapter")}kind="red";flows;constructor(t){this.flows=Jf(t)}async getFlows(){let t=await this.flows.getFlows({});return An(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 Vf(o)?new z(t):o}return{rev:n.rev,flow:$t(t,r)}}async updateFlow(t){let n=t.deploymentType??"nodes",r=await this.getFlows();if(r.rev!==t.expectedRev)throw new J(t.expectedRev,r.rev,"updateFlow");let o=rr(r.flows,t.flowId,t.flow),s=await this.flows.setFlows({flows:{flows:o,rev:t.expectedRev},deploymentType:n});return{flowId:t.flowId,rev:Kf(s)}}};function Ya(e){let t=e?.runtime?.flows;return!!(t?.getFlows&&t.getFlow&&t.setFlows)}a(Ya,"canUseRedRuntime");function Vf(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(Vf,"isRuntimeFlowNotFound");var Qa=require("module");function Za(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(Za,"isFlowsApi");function Xf(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,Qa.createRequire)(e)}a(Xf,"createPluginRequire");function Yf(e){try{let t=e("@node-red/runtime/lib/settings");return typeof t.available=="function"&&t.available()===!0}catch{return!1}}a(Yf,"isNodeRedRuntimeInitialized");function Qf(){try{let e=Xf();if(!Yf(e))return null;let t=e("@node-red/runtime");return Za(t.flows)?t.flows:null}catch{return null}}a(Qf,"tryRequireInitializedRuntimeFlows");function ed(e){if(e?.runtime&&Za(e.runtime.flows))return e;let t=Qf();return t?{runtime:{flows:t}}:null}a(ed,"resolveRuntimeHost");function td(e={}){let t=ed(e.RED??null);if(t&&Ya(t))return new zr(t);let n=e.adminAuthEnabled===!0;if(e.http?.baseUrl){if(n&&!e.http.accessToken)throw new be("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 Wr(e.http)}throw new be("No usable Node-RED flow adapter: in-process flows API is unavailable and no HTTP Admin API baseUrl was provided")}a(td,"createFlowAdapter");ve();var Jr=require("fs"),nd=require("path");er();G();var Zf=/^[a-z][a-z0-9._-]{0,63}$/;function em(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(em,"sanitizeActor");var Gr=class{constructor(t,n={}){this.paths=t;this.filePath=(0,nd.join)(t.logs,"audit.jsonl"),this.now=n.now??(()=>Date.now()),this.maxAppendBytes=n.maxAppendBytes??cs}static{a(this,"AuditLog")}filePath;now;maxAppendBytes;seq=0;append(t,n,r){let o=Zf.test(t)?t:"unknown";this.seq+=1;let s={id:`log-${this.now()}-${this.seq}`,at:new Date(this.now()).toISOString(),action:o,actor:em(n)};r!==void 0&&(s.details=_s(r));let i=Be(s,this.maxAppendBytes,{kind:"audit"});return ps(this.paths.root,this.filePath,i),s}list(t=100){if(!(0,Jr.existsSync)(this.filePath))return[];let n=(0,Jr.readFileSync)(this.filePath,"utf8").split(`
6372
- `).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()}};Ge();Gn();ie();ut();ve();Ge();Xe();Ae();var tm="Flow revision conflict: the expectedRev is stale. Ask the AI to re-read flows and resubmit a new proposal.";function rd(e){let t=oe(e),n=cr(e)||void 0;return{proposalId:e.id,title:t.title,displayName:t.displayName,flowId:e.payload.flowId,...n?{flowLabel:n}:{}}}a(rd,"auditProposalMeta");var Kr=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=rd(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}),ei(this.proposals,o.payload.flowId,t,l.rev),c}catch(l){if(l instanceof J){let p=this.proposals.markRejected(t,n,tm);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 te(t,o.status);let s=rd(o),i=this.proposals.markRejected(t,n,r.trim()||"Rejected by admin");return this.audit.append("proposal.rejected",n,{...s,reason:i.reason}),i}};ye();function nm(e){let t=a(()=>Promise.reject(new Ce(e)),"fail");return{kind:"http",getFlows:t,getFlow:t,updateFlow:t}}a(nm,"misconfiguredAdapter");function od(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(od,"localAdminBaseUrl");function Vr(e,t={}){let n=Bn(e.settings.userDir);fs(n);let r=t.settingsStore??new Lt(n,e.settings.aiAccess,u=>e.log?.warn?.(u));t.bindSettings!==!1&&Jn(r),Vn(r.getEffective().publicBaseUrl);let o=t.linkStore??Ur(e);o.configureDefaults({defaultTtlSeconds:r.getEffective().defaultLinkTtlSeconds,maxActiveLinks:r.getEffective().maxActiveLinks});let s=new tr(n),i=new Gr(n),d;if(t.flowAdapter)d=t.flowAdapter;else try{d=td({RED:e,http:{baseUrl:od(e)},adminAuthEnabled:W(e.settings)})}catch(u){let p=u instanceof be?u.message:Qn;e.log?.warn?.(`AI Access flow adapter unavailable: ${p}`),d=nm(p)}let l;if(t.nodeTypesAdapter)l=t.nodeTypesAdapter;else if(t.flowAdapter)l=ho();else try{l=Hs({baseUrl:od(e)})}catch(u){let p=u instanceof Error?u.message:String(u);e.log?.warn?.(`AI Access node-types adapter unavailable: ${p}`),l=ho([])}let c=new Kr(s,i,d,()=>he(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(Vr,"createPluginServices");G();ie();_e();qn();var sd=ln(require("dns"),1),id=ln(require("http"),1),ad=ln(require("https"),1),Dn=require("net");ie();function rm(e,t){return e===10||e===172&&t>=16&&t<=31||e===192&&t===168}a(rm,"isPrivateV4");function om(e,t){return e===169&&t===254}a(om,"isLinkLocalV4");function sm(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(sm,"isSpecialUseV4");function im(e){let[t=0,n=0,r=0]=e.split(".").map(Number);return t===127?"loopback":rm(t,n)||om(t,n)||sm(t,n,r)||e==="168.63.129.16"?"blocked":"public"}a(im,"classifyIpv4");function am(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(am,"expandIpv6");function dm(e){let t=am(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(dm,"classifyIpv6");function jn(e){let t=at(e);return t?(0,Dn.isIP)(t)===4?im(t):dm(t):"blocked"}a(jn,"classifyPublicUrlAddress");function lm(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(lm,"isMetadataHostname");async function dd(e,t,n){let r=Ct(e);if(!r)throw new Error("publicBaseUrl is required for Check");let o=new URL(r),s=o.hostname.replace(/^\[|\]$/g,"");if(lm(s))throw new Error("Public URL Check cannot target metadata hosts");let i=at(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&&jn(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 x=jn(g);if(x==="blocked"||x==="loopback"&&!c||c&&x!=="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,Dn.isIP)(i)};let f=await new Promise((g,x)=>{let S=a(()=>x(n.reason instanceof Error?n.reason:new Error(String(n.reason))),"abort");n.throwIfAborted(),n.addEventListener("abort",S,{once:!0}),sd.default.lookup(s,{all:!0},(E,I)=>{n.removeEventListener("abort",S),E?x(E):g(I)})});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 x=at(g.address);if(!x)throw new Error("Public URL Check returned an invalid address");m.some(S=>S.address===x)||m.push({address:x,family:(0,Dn.isIP)(x)})}c&&m.sort((g,x)=>g.family-x.family);let h=m[0];if(!h)throw new Error("Public URL Check hostname has no addresses");return{origin:r,...h,addresses:m}}a(dd,"preparePublicUrlCheck");function cm(e,t,n){let r=e.hostname.replace(/^\[|\]$/g,"");return new Promise((o,s)=>{let d=(e.protocol==="https:"?ad.default:id.default).request(e,{method:"GET",agent:!1,signal:n,headers:{Host:e.host},...(0,Dn.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(cm,"requestPinned");async function ld(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 cm(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(ld,"requestPublicUrlCheck");ye();function um(e){return hn(e.settings.httpAdminRoot)}a(um,"mcpPageUrl");function pm(e){return typeof e=="boolean"||typeof e=="number"||typeof e=="string"?e:Array.isArray(e)?e.join(", "):typeof e=="function"?"custom function":!1}a(pm,"normalizeTrustProxy");function pd(e){if(!e)return"Set a Public URL before enabling OAuth.";let t=new URL(e),n=at(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(pd,"oauthPublicUrlWarning");function fd(e,t,n){let{sources:r,...o}=t.getEffective(),{permission:s}=Kn(e,n);return{ok:!0,adminAuthEnabled:W(e.settings),canEdit:s==="*",permission:s,mcpPageUrl:um(e),apiBase:de(e.settings.httpAdminRoot),settings:o,oauthPublicUrlWarning:pd(o.publicBaseUrl),sources:r,disk:t.getDisk(),trustProxy:pm(n.app.get("trust proxy")),precedence:["settings.js","disk","default"]}}a(fd,"settingsResponse");function md(e,t){return(n,r)=>{r.json(fd(e,t,n))}}a(md,"getSettingsHandler");function cd(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(cd,"parseBooleanField");function ud(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(ud,"parsePositiveIntField");function fm(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(fm,"parseRateLimitField");function mm(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=>pn(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(mm,"parseCorsField");function hd(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[l,c]of Object.entries(t.getEffective().sources))c==="settings.js"&&delete i[l];t.getEffective().sources.oauth==="settings.js"&&delete i.oauth;let d={};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 l=i.oauth,c={};if(l.enabled!==void 0){if(typeof l.enabled!="boolean")throw new Error("oauth.enabled must be a boolean");c.enabled=l.enabled}for(let p of["accessTokenTtlSec","refreshIdleTtlSec","dcrMaxClients"])if(l[p]!==void 0){if(typeof l[p]!="number"||!Number.isInteger(l[p])||l[p]<1||l[p]>31536e3)throw new Error(`oauth.${p} must be a positive integer`);c[p]=l[p]}let u=l.refreshMaxLifetimeSec;if(u!==void 0){if(typeof u!="number"||!Number.isInteger(u)||u<0||u>31536e4)throw new Error("oauth.refreshMaxLifetimeSec must be a non-negative integer");c.refreshMaxLifetimeSec=u}d.oauth=c}cd(i,"enabled",d),cd(i,"allowWithoutAdminAuth",d),ud(i,"defaultLinkTtlSeconds",d),ud(i,"maxActiveLinks",d)}catch(l){o.status(400).json({error:l.message,code:"invalid_body"});return}if(i.defaultDeploymentType!==void 0){let l=i.defaultDeploymentType;if(l!=="nodes"&&l!=="flows"&&l!=="full"){o.status(400).json({error:"defaultDeploymentType must be 'nodes', 'flows', or 'full'",code:"invalid_body"});return}d.defaultDeploymentType=l}try{i.publicBaseUrl!==void 0&&(d.publicBaseUrl=Ct(i.publicBaseUrl)),mm(i,d);for(let l of["rateLimitWindowMs","rateLimitMaxPerIp","rateLimitMaxPerToken"])fm(i,l,d)}catch(l){o.status(400).json({error:l.message,code:"invalid_body"});return}try{let l=t.patch(d);Vn(l.publicBaseUrl),n.configureDefaults({defaultTtlSeconds:l.defaultLinkTtlSeconds,maxActiveLinks:l.maxActiveLinks});let c=fd(e,t,r),u=l.publicBaseUrl?new URL(l.publicBaseUrl):void 0,p=u?at(u.hostname.replace(/^\[|\]$/g,"")):void 0,f=p?jn(p):void 0,m=[];f!==void 0&&f!=="public"&&m.push("Public URL uses a private or non-public IP address.");let h=pd(l.publicBaseUrl);h&&l.publicBaseUrl&&m.push(h),o.json({...c,...m.length?{warnings:m}:{}})}catch(l){if(l instanceof X){o.status(l.statusCode).json({error:l.message,code:l.code});return}$(o,l);return}}}a(hd,"putSettingsHandler");function gd(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"||jn(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 dd(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+de(e.settings.httpAdminRoot);let d=await ld(i,de(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(gd,"checkPublicUrlHandler");ye();it();ye();K();Gn();var hm="com55-ai-access";function wd(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(wd,"acceptedMediaTypes");function bd(e){let t=wd(e.headers.accept);return t.has("text/event-stream")&&!t.has("text/html")}a(bd,"isMcpStreamRequest");function gm(e,t,n){e.httpAdmin.get("/mcp/api/v1",t,n,Mo(e)),e.httpAdmin.get("/mcp/api/v1/",t,n,Mo(e)),e.httpAdmin.get("/mcp/api/v1/openapi.yaml",t,n,Ma()),e.httpAdmin.get("/mcp/api/v1/openapi.json",t,n,Da()),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",Xt),o.type("application/javascript").status(200).send(yr())})}a(gm,"registerMcpShellAndDiscovery");var ym=/^\/mcp\/(?:api\/v1\/proposals|rpc$|a\/[^/]+\/(?:proposals|propose|draft))/;function vd(e,t={}){let n=Vr(e,{...t,linkStore:t.linkStore??Ur(e)}),r=Ei(()=>n.settingsStore.getEffective()),{ip:o,token:s}=r,i=new Br(Bn(e.settings.userDir),()=>n.settingsStore.getEffective().oauth.dcrMaxClients),d=a(()=>n.settingsStore.getEffective().publicBaseUrl,"publicBase"),l=a(()=>`${d().replace(/\/$/,"")}/mcp`,"mcpUrl"),c=new qr(i,l,()=>n.settingsStore.getEffective().oauth.accessTokenTtlSec,()=>n.settingsStore.getEffective().oauth.refreshIdleTtlSec,()=>n.settingsStore.getEffective().oauth.refreshMaxLifetimeSec),u=Na(e,c,d,l);hs(e.httpAdmin,ms(()=>n.settingsStore.getEffective().corsAllowedOrigins));let p=a(w=>(Array.isArray(w)?w:w?[w]:[]).flatMap(b=>{if(typeof b=="string")return[b];let k=b;return k&&typeof k.path=="string"?[{path:k.path,root:typeof k.root=="string"?k.root:void 0}]:[]}),"staticMounts"),f={authorization:[],metadata:[],details:[],httpNodeRootFallback:e.settings.httpNodeRoot===!1||!e.httpNode},m="",h=a(()=>{let w=Ia({httpAdmin:e.httpAdmin,httpNode:e.httpNode,httpAdminRoot:e.settings.httpAdminRoot,httpNodeRoot:e.settings.httpNodeRoot,metadataFallback:f.httpNodeRootFallback,eachNode:e.nodes?M=>e.nodes?.eachNode(M):void 0,staticDirs:p(e.settings.httpStatic),httpStaticRoot:e.settings.httpStaticRoot,fileExists:yd.existsSync}),b=w.authorization,k=w.metadata;f.authorization=b.map(os),f.metadata=k.map(os),f.details=[...b.map(M=>({...M,kind:"authorization"})),...k.map(M=>({...M,kind:"metadata"}))];let L=[...f.authorization.map(M=>`${M} \u2014 OAuth authorization routes disabled`),...f.metadata.map(M=>`${M} \u2014 OAuth metadata disabled`)],H=L.join(`
6373
- `);if(H!==m)for(let M of L)e.log?.warn?.(`AI Access OAuth route collision: ${M}`);m=H},"refreshCollisions");h(),e.events?.on("flows:started",h);let g=Ho({windowMs:6e4,maxPerIp:30}),x=a((w,b,k)=>{if(!n.settingsStore.getEffective().oauth.enabled||f.authorization.length||!w.path.startsWith("/mcp/oauth/")){k();return}let L=w.path.replace(/\/+$/,"").toLowerCase(),H=a(()=>Et(e)(w,b,()=>u.server(w,b,k)),"run");L==="/mcp/oauth/authorize"?o(w,b,()=>g(w,b,H)):L.startsWith("/mcp/oauth/consent/")?o(w,b,H):H()},"oauthServerMiddleware");x.oauthOwned=!0,e.httpAdmin.use(x);let S=a((w,b,k)=>{if(!n.settingsStore.getEffective().oauth.enabled||f.metadata.length){k();return}u.metadata(w,b,k)},"metadataHandler");S.oauthOwned=!0,f.httpNodeRootFallback?e.httpAdmin.use("/mcp",S):e.httpNode?.use(S),e.httpAdmin.get("/mcp/api/v1/oauth/clients",o,s,we(e,"api"),(w,b)=>b.json({clients:i.data.clients.map(k=>({client_id:k.client_id,client_name:k.client_name,redirect_uris:k.redirect_uris,lastUsedAt:k.lastUsedAt}))})),e.httpAdmin.get("/mcp/api/v1/oauth/status",o,s,we(e,"api"),(w,b)=>{let k=!W(e.settings)||We(w)==="*";b.json({...f,details:k?f.details:f.details.map(({file:L,...H})=>H),collisions:[...f.authorization,...f.metadata],oauthEnabled:n.settingsStore.getEffective().oauth.enabled})}),e.httpAdmin.delete("/mcp/api/v1/oauth/clients/:id",o,s,we(e,"api"),gn(e),(w,b)=>b.json({revoked:i.revokeClient(String(w.params.id))})),e.httpAdmin.use((w,b,k)=>{if(!ym.test(w.path)){k();return}n.flowAdapter.getFlows().then(L=>Vs(w,dr(L.flows))).catch(()=>{}).finally(()=>k())}),na(e,{linkStore:n.linkStore,proposalStore:n.proposalStore,auditLog:n.auditLog,flowAdapter: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,b)=>{b.setHeader("Cache-Control","no-store, no-cache, must-revalidate, max-age=0"),b.setHeader("Pragma","no-cache"),b.setHeader("Expires","0"),b.type("html").status(200).send(Ba({apiBase:de(e.settings.httpAdminRoot),mcpRpc:qe(e.settings.httpAdminRoot),editorRoot:ae(e.settings.httpAdminRoot),authTokenKey:Yn(e.settings.httpAdminRoot),dashboardPath:hn(e.settings.httpAdminRoot)}))}),e.httpAdmin.get("/mcp",o,(w,b,k)=>{if(bd(w)){k();return}b.setHeader("Cache-Control","no-store"),b.redirect(302,hn(e.settings.httpAdminRoot))}),gm(e,o,s),e.httpAdmin.get("/mcp/api/v1/status",o,s,vs(e,w=>n.linkStore.validateResult(w)),R((w,b)=>{b.json(rn(e,w))})),e.httpAdmin.get("/mcp/api/v1/settings",o,s,we(e,"api"),R(md(e,n.settingsStore))),e.httpAdmin.put("/mcp/api/v1/settings",o,s,we(e,"api"),gn(e),je("rest"),R(hd(e,n.settingsStore,n.linkStore))),e.httpAdmin.post("/mcp/api/v1/settings/check-public-url",o,s,we(e,"api"),gn(e),je("rest"),R(gd(e)));let E=[o,s,we(e,"api"),Et(e),gn(e)];e.httpAdmin.get("/mcp/api/v1/links",...E,R(ua(e,n.linkStore))),e.httpAdmin.post("/mcp/api/v1/links",...E,R(pa(e,n.linkStore,n.auditLog))),e.httpAdmin.delete("/mcp/api/v1/links/:id",...E,R(fa(e,n.linkStore,n.auditLog)));let I=[o,s,Et(e),bs(e,w=>n.linkStore.validateResult(w))],N=n.flowAdapter,me={nodeTypes:n.nodeTypesAdapter,...n.catalogueFetcher?{catalogueFetcher:n.catalogueFetcher}:{},...n.catalogueUrl?{catalogueUrl:n.catalogueUrl}:{}};e.httpAdmin.get("/mcp/api/v1/flows",...I,R(vi(N))),e.httpAdmin.get("/mcp/api/v1/flows/:id",...I,R(Lr(N))),e.httpAdmin.get("/mcp/api/v1/nodes/:id",...I,R(Cr(N))),e.httpAdmin.get("/mcp/api/v1/nodes/:id/field/:name",...I,R(Er(N))),e.httpAdmin.get("/mcp/api/v1/search",...I,R(Ir(N))),e.httpAdmin.get("/mcp/api/v1/node-types",...I,R(Nr(me))),e.httpAdmin.get("/mcp/api/v1/node-types/:type",...I,R(Fr(me))),e.httpAdmin.get("/mcp/api/v1/palette/search",...I,R(Or(me)));let A=e.settings.httpAdminRoot;e.httpAdmin.get("/mcp/api/v1/proposals",...I,R(za(n.proposalStore,n.flowAdapter,A))),e.httpAdmin.get("/mcp/api/v1/proposals/:id",...I,R(Ga(n.proposalStore,A,n.flowAdapter))),e.httpAdmin.get("/mcp/api/v1/proposals/:id/diff",...I,R(Xa(n.proposalStore,n.flowAdapter))),e.httpAdmin.post("/mcp/api/v1/proposals",o,s,je("rest"),Nt(e,n.linkStore,"rest"),R(Wa(n.proposalStore,n.auditLog,n.flowAdapter,A,n.nodeTypesAdapter))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/approve",...E,je("rest"),R(Ja(n.proposalService,A))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/reject",...E,je("rest"),R(Ka(n.proposalService,A))),e.httpAdmin.post("/mcp/api/v1/proposals/:id/withdraw",o,s,je("rest"),Nt(e,n.linkStore,"rest"),R(Va(n.proposalStore,n.auditLog,A)));let v=[o,s,(w,b,k)=>{let L=w.headers.authorization;if(!n.settingsStore.getEffective().oauth.enabled||L?.toLowerCase().startsWith("bearer aia_")){if(n.settingsStore.getEffective().oauth.enabled){let H=b.setHeader.bind(b);b.setHeader=((M,Re)=>{if(M.toLowerCase()==="www-authenticate"&&typeof Re=="string"&&!Re.includes("resource_metadata=")){let Ue=`${d().replace(/\/$/,"")}${f.httpNodeRootFallback?"/mcp":""}/.well-known/oauth-protected-resource/mcp`;Re+=`, resource_metadata="${Ue}", scope="read propose"`}return H(M,Re)})}Nt(e,n.linkStore,"mcp")(w,b,k);return}Et(e)(w,b,()=>u.auth(w,b,k))}];e.httpAdmin.post("/mcp/rpc",...v,je("mcp"),R(Qo(e,n))),e.httpAdmin.get("/mcp/rpc",...v,On),e.httpAdmin.delete("/mcp/rpc",...v,On),e.httpAdmin.post("/mcp",...v,je("mcp"),R(Qo(e,n))),e.httpAdmin.get("/mcp",...v,On),e.httpAdmin.delete("/mcp",...v,On),e.httpAdmin.get("/mcp/api/v1/logs",o,s,we(e,"api"),Et(e),R(ma(n.auditLog,n.linkStore))),e.httpAdmin.use("/mcp/api/v1",yi()),ia(e)}a(vd,"registerRoutes");function wm(e){e.plugins.registerPlugin(hm,{type:"com55-ai-access",onadd(){vd(e),e.log?.info?.("Node-RED AI Access plugin loaded (dashboard /ai-access, MCP /mcp)")}})}a(wm,"registerAiAccessPlugin");var bm=wm;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});
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});