@com55/node-red-ai-access 0.2.0-beta.5 → 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
@@ -3,13 +3,13 @@
3
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
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
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),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(`
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
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
8
  `),d=s.indexOf(`
9
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 tg={};Os(tg,{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:()=>eg,getStorageDir:()=>co,isAdminAuthEnabled:()=>z,isMcpStreamRequest:()=>bl,packageVersion:()=>rn,registerRoutes:()=>vl,renderEditorSidebarJs:()=>Hr,renderEditorSidebarLoaderHtml:()=>ua,requireAccessLink:()=>Ut,resolveAiAccessSettings:()=>ve});module.exports=Rl(tg);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>
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
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.5",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.5",nn)}a(rn,"packageVersion");var on=rn();function Ep(){return`#red-ui-sidebar-ai-access {
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 {
13
13
  position: relative;
14
14
  height: 100%;
15
15
  display: flex;
@@ -565,7 +565,38 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
565
565
  },
566
566
  });
567
567
  })();
568
- `}a(Lp,"editorSidebarScriptPartB");function Ip(){return Cp()+Lp()}a(Ip,"editorSidebarScriptBody");function 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);"," }","})();",Ip()].join(`
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(`
569
600
  `)}a(Hr,"renderEditorSidebarJs");function ua(e=on){return`<script>
570
601
  /**
571
602
  * Thin loader for the AI Access editor sidebar.
@@ -600,8 +631,8 @@ ${t}`))return{text:t,summary:t};let r=t.split(/\n(?=#{1,3}\s+)/),o="",s,i;for(le
600
631
  (document.head || document.documentElement).appendChild(s);
601
632
  })();
602
633
  </script>
603
- `}a(ua,"renderEditorSidebarLoaderHtml");J();De();nt();function pa(e){return je[e]??"Request failed"}a(pa,"safeErrorText");function Np(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(Np,"sendMappedFlowError");function fa(e){return async(t,n)=>{try{n.json(await ye(Me(t,e)))}catch(r){if(Np(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 Fp(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(Fp,"compactLineDiff");function Op(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=Fp(e.lines),d={key:e.key,kind:"text",beforeBytes:o,afterBytes:s,lines:i.lines};return i.truncated&&(d.truncated=!0),d}a(Op,"formatTextChange");function $p(e,t){return e.map(n=>n.kind==="text"?Op(n,t):n)}a($p,"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)}
604
- \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:$p(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 Mp(e,t,n,r=ga){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)
605
636
 
606
637
  **Prefer MCP or REST** whenever the AI client can send custom headers and JSON
607
638
  bodies \u2014 no URL length limits and no chunked drafts. Use this **GET link
@@ -670,7 +701,7 @@ curl -sS -X POST "${r}" \\
670
701
  -H "Accept: application/json, text/event-stream" \\
671
702
  -d '{"jsonrpc":"2.0","id":1,"method":"tools/list"}'
672
703
  \`\`\`
673
- `}a(Mp,"docsAlsoAvailable");function Dp(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}
674
705
 
675
706
  **Git mental model:** a proposal is like a pull request. Human approve = merge;
676
707
  reject = close; withdraw = you close your own PR. If \`rev\`/\`expectedRev\` does
@@ -745,7 +776,7 @@ fields at the top level** (no \`ok\` / \`proposal\` wrapper). Read
745
776
 
746
777
  Lean by default (no \`payload\` / \`baseFlow\`). Pass
747
778
  \`?include=payload,baseFlow\` or \`?include_payload=true\` to include them.
748
- `}a(Dp,"docsIntro");function jp(){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(jp,"docsWorkflow");function Hp(){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(Hp,"docsUrlTable");function Up(){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
749
780
 
750
781
  **Recommended for small edits to long fields** \u2014 \`replace_text\` (find must be
751
782
  **unique** unless you pass \`occurrence\` as a 1-based index). Creates a normal
@@ -823,7 +854,7 @@ GET ./propose?flow=tab1&rev=abc123&patch=<base64url>
823
854
  Node-level proposes create a normal \`replace_flow\` proposal; owners should
824
855
  approve with deployment type **\`nodes\`** (plugin default) so only changed
825
856
  nodes deploy.
826
- `}a(Up,"docsProposeExamples");function Bp(){return`## Chunked drafts (long HTML / large fields)
857
+ `}a(Bp,"docsProposeExamples");function qp(){return`## Chunked drafts (long HTML / large fields)
827
858
 
828
859
  When the GET URL would exceed **${Ht} bytes**, do not
829
860
  stuff everything into \`propose\`. Use drafts (expire after **30 minutes**,
@@ -872,7 +903,7 @@ Or a full \`replace_flow\` document. JSON-mode drafts **may add nodes**, so
872
903
  \`draft/new\` does not require an existing \`node\`/\`field\`. Use JSON mode for
873
904
  structural changes; use field-mode only when you need to replace one whole
874
905
  string field that is too large for a single propose URL.
875
- `}a(Bp,"docsDrafts");function qp(){return`## Last resort: content larger than chunked drafts
906
+ `}a(qp,"docsDrafts");function zp(){return`## Last resort: content larger than chunked drafts
876
907
 
877
908
  If the field is still too large for chunked drafts (decoded size over the
878
909
  proposal cap), use this fallback only after drafts fail:
@@ -886,7 +917,7 @@ proposal cap), use this fallback only after drafts fail:
886
917
  **Trade-off:** pasted content is **not** reviewed through the proposal diff.
887
918
  Prefer \`replace_text\` or chunked drafts whenever they fit so the human can
888
919
  still see a readable change on \`/mcp\`.
889
- `}a(qp,"docsLastResortPaste");function zp(){return`## Size cap and REST/MCP nodes payload shapes
920
+ `}a(zp,"docsLastResortPaste");function Wp(){return`## Size cap and REST/MCP nodes payload shapes
890
921
 
891
922
  The proposal JSON cap is **${H} bytes**
892
923
  (\`MAX_PROPOSAL_JSON_BYTES\` / ~1.5MB) on **every** channel: GET propose/drafts,
@@ -932,7 +963,7 @@ Example (canonical):
932
963
  **Alternative (also accepted):** top-level \`nodes\` array as an alias for
933
964
  \`payload.nodes\` (same op shapes). Full-tab replace remains supported via
934
965
  \`payload.flow\` (from \`GET ./flows/<id>\` / REST \`GET /flows/{id}\`).
935
- `}a(zp,"docsRestNodesPayload");function Wp(){return`## Known issues
966
+ `}a(Wp,"docsRestNodesPayload");function Gp(){return`## Known issues
936
967
 
937
968
  - **Rate limits are per token (and per IP).** Each access-link token has its own
938
969
  token bucket. Several tokens can each use the full per-token limit at the same
@@ -940,20 +971,20 @@ Example (canonical):
940
971
  - **\`RateLimit-*\` headers describe the per-token bucket** (default
941
972
  \`RateLimit-Policy: 300;w=60\`) on token requests; requests without a token
942
973
  report the per-IP bucket (\`600;w=60\`). Both are enforced.
943
- `}a(Wp,"docsKnownIssues");function Gp(){return`## Error codes (JSON unless docs)
974
+ `}a(Gp,"docsKnownIssues");function Kp(){return`## Error codes (JSON unless docs)
944
975
 
945
976
  ${ia()}
946
977
 
947
978
  All JSON responses use \`Cache-Control: no-store\`. Never put secrets in query
948
979
  strings you will paste publicly beyond this temporary link.
949
- `}a(Gp,"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[Dp(d,i),Mp(c,l,u,s?dn(`${s}${o}`):ga),jp(),Hp(),Up(),zp(),Bp(),qp(),Wp(),Gp()].join(`
950
- `)}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 Kp(e,t){e.headersSent||e.status(500).json(Ur(t))}a(Kp,"sendInternalError");function D(e,t){if(e.headersSent)return;let n=M(t);if(n){e.status(n.statusCode).json(n.body);return}Kp(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}},Jp=/^[A-Za-z0-9_-]+={0,2}$/;function wa(e){if(!Jp.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 Vp(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(Vp,"retryAfterSeconds");function ka(e,t,n=us){let r=Vp(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 Yp(e){return(t,n,r,o)=>{ka(t,n,e)}}a(Yp,"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:Yp(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 Xp(e){let t=Ge(e);return Aa(t.maxPerToken,t.windowMs,n=>Wr(n)??zr(n),n=>Wr(n)==null)}a(Xp,"createAiAccessTokenRateLimiter");function Sa(e){let t=Ge(e),n=ps(t),r=Xp(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 Zp="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:Zp,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 Qp(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(Qp,"validateFieldDraftTargets");function ef(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(ef,"planDraftMode");async function tf(e,t,n,r){try{let o=await Qp(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(tf,"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=ef(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 tf(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 nf(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(nf,"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=nf(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 rf={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})??rf[e]??zn(e)}a(ms,"proposeErrorHint");function of(e,t,n){e.status(409).json({error:"Flow revision mismatch",code:"version_mismatch",expectedRev:t,currentRev:n,hint:ms("version_mismatch")})}a(of,"sendVersionMismatch");async function sf(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(sf,"buildFromPatch");async function af(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(af,"buildFromSetProps");async function df(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(df,"buildFromReplaceText");function lf(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(lf,"readProposeQuery");function Ee(e,t,n){I(e,400,n,t,ms(t))}a(Ee,"jsonProposeError");function cf(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(cf,"parseOccurrenceQuery");async function uf(e,t,n,r,o){if(n.patchEncoded)return sf(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=cf(e,n.occurrenceRaw);return i.ok?df(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):af(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(uf,"buildProposeFromQuery");function Ia(e,t,n,r,o){return async(s,i)=>{if(ln(s,i))return;O(i);let d=lf(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 uf(i,n,d,d.rev,o);if(!u)return;if(!u.ok){of(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 pf=50,Na=40,ff=20;function mf(e){return Buffer.byteLength(JSON.stringify(e),"utf8")}a(mf,"nodeBytes");function hf(e){let t={id:e.id,type:e.type,bytes:mf(e)};return typeof e.name=="string"&&(t.name=e.name),"wires"in e&&(t.wires=e.wires),t}a(hf,"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(hf):[],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 gf(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(gf,"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(`
951
- `),d=i.length,l=Buffer.byteLength(s,"utf8"),c=gf(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(`
952
- `);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 yf(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(yf,"stringFields");function wf(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(wf,"snippetAround");function bf(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(bf,"lineNumberAtIndex");function hs(e,t,n,r,o){for(let{field:s,value:i}of yf(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(`
953
984
  `)){let f=i.split(`
954
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(`
955
- `)){let f=0;for(;f<=i.length;){let m=i.indexOf(n,f);if(m<0)break;u(bf(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,ff),lineCount:p,snippet:wf(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 vf(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(vf,"searchOneFlow");async function kf(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(kf,"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):pf,o=[],s=n.flowId?await vf(e,n.flowId,t,o,r):await kf(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 Af(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(Af,"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=Af(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 Sf={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 Sf[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(`
956
- `)}a(ja,"splitLines");Re();var qa=new Set(["func","template","info","payload","code","text","format","syntax"]),xf=new Set(["x","y","w","h","wires","g"]),Tf=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 Rf(e){return/<\/?[a-z][\s\S]*>/i.test(e.slice(0,800))}a(Rf,"looksLikeHtml");function _f(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:Rf(n)}a(_f,"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 _f(e,t,r)&&(o.preview="html"),o}return{key:t,kind:"scalar",value:n===void 0?null:n}}a(Wa,"toPresenceProperty");function Pf(e,t){let n=[];for(let r of[...Object.keys(e)].sort())if(!(Tf.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(Pf,"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 Ef=new Set(["func","template","code","text"]);function Cf(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 Ef){if(!(s in e))continue;let i=lo(e[s]);i===null||i.length===0||n.push(Wa(e,s,i))}return n}a(Cf,"buildKeyFields");function Lf(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(Lf,"buildModifiedSummary");function If(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(If,"buildPresenceSummary");function so(e,t,n,r){let o={id:e.id,type:e.type,summary:If(e,t,n),keyFields:Cf(e,r),properties:Pf(e,r)},s=Rt(e);return s!==void 0&&(o.name=s),o}a(so,"toPresenceNode");function Nf(e,t){let n=new Set([...Object.keys(e),...Object.keys(t)]),r=[];for(let o of[...n].sort()){if(xf.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(Nf,"compareNodes");function Ff(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(Ff,"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=Nf(p,u);if(f.length>0){let m={id:c,type:u.type||p.type,summary:Lf(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:Ff(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 Of=["status:read","flows:read","proposals:create","proposals:read-own"];function ys(){return rn()}a(ys,"readPackageVersion");function $f(e,t=Date.now()){let n=Date.parse(e);return Number.isFinite(n)?Math.max(0,Math.ceil((n-t)/1e3)):0}a($f,"secondsRemainingUntil");function Mf(e,t=Date.now()){let n=Date.parse(e);return Number.isFinite(n)?Math.max(0,Math.ceil((n-t)/6e4)):0}a(Mf,"minutesRemainingUntil");function Df(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(Df,"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:Mf(t.accessLink.expiresAt,o),secondsRemaining:$f(t.accessLink.expiresAt,o),permissions:[...Of],baseUrls:Df(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;jf(o,s,i,t,l),o.get(`${s}/propose`,...i,_(Ia(t.proposalStore,t.auditLog,t.flowAdapter,l,t.nodeTypesAdapter))),Bf(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 jf(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}})),Hf(e,t,n,r,o),Uf(e,t,n,r,o)}a(jf,"registerGetLinkProposalRoutes");function Hf(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(Hf,"registerGetLinkWithdrawRoute");function Uf(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(Uf,"registerGetLinkDiffRoute");function Bf(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(Bf,"registerGetLinkDraftRoutes");G();var Ya=require("module");function qf(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,Ya.createRequire)(e)}a(qf,"createPluginRequire");function zf(e){try{let r=qf()("@node-red/runtime")._?.adminApi?.httpAdmin;if(r&&typeof r.use=="function"&&r!==e.httpAdmin)return r}catch{}return null}a(zf,"resolveEditorHttpAdmin");function Xa(e){let t=[e.httpAdmin],n=zf(e);return n&&t.push(n),t}a(Xa,"httpAdminAppsForMcpErrors");var Za=Symbol.for("com55.ai-access.mcpJsonErrorHandler");function Wf(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(Wf,"mcpRpcMatcher");function ws(e){return e.includes("/mcp")}a(ws,"isMcpPath");function Gf(e){return e.path||e.url||e.originalUrl||""}a(Gf,"requestPath");function Kf(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(Kf,"isJsonParseError");function Jf(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(Jf,"isPayloadTooLarge");function Vf(e){let t=Wf(e);return(n,r,o,s)=>{if(o.headersSent){s(n);return}let i=Gf(r);if(Kf(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(Jf(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(Vf,"mcpJsonErrorHandler");function Yf(e,t){let n=e;n[Za]||(n[Za]=!0,e.use(Vf(t)))}a(Yf,"attachOnce");function Qa(e){for(let t of Xa(e))Yf(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 Xf(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(Xf,"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(Xf(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 Zf(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(Zf,"ttlSecondsFromLink");function Qf(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=Zf(l);c!==void 0&&(n.ttlSeconds=c)}return{...e,details:n}}a(Qf,"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=>Qf(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 em="ai.redacted",ld=new dd.AsyncLocalStorage;function cd(e,t,n,r){if(!(r<=0))try{e.append(em,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.5"}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 tm=new Set(["list_proposals","list_my_proposals"]),nm=new Set(["status","limit","offset","mine","flowId"]);function rm(e){return!!e&&typeof e=="object"&&typeof e.safeParseAsync=="function"}a(rm,"isZodLikeSchema");function om(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(tm.has(e)&&nm.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(om,"mcpArgumentError");function wd(e){let t=e,n=t.validateToolInput;typeof n=="function"&&(t.validateToolInput=async(r,o,s)=>{let i=r.inputSchema;if(!rm(i))return n.call(e,r,o,s);let d=await i.safeParseAsync(o??{});if(d.success)return d.data;let l=Bn(om(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)}}),im(e,n,o,s),am(e,n,o,s)}a(vd,"registerProposalTools");function sm(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(sm,"registerListProposalsTools");function im(e,t,n,r){sm(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(im,"registerProposalQueryTools");function am(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(am,"registerProposalActionTools");function dm(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(dm,"createAiMcpServer");function lm(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(lm,"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?dm(e,s,n):cm(e,s,n),d=new hd.StreamableHTTPServerTransport({enableJsonResponse:!0});await i.connect(d),await pd(t.auditLog,lm(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 cm(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(cm,"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>'},um={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."]},pm='<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>',fm=`
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=`
957
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}
958
989
  *{box-sizing:border-box}
959
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}
@@ -989,7 +1020,7 @@ form.login input{font:inherit;padding:9px 11px;border-radius:10px;border:1px sol
989
1020
  a{color:var(--focus)}
990
1021
  [hidden]{display:none!important}
991
1022
  @media (max-width:420px){.actions{flex-direction:column-reverse}.secondary{flex:1}}
992
- `;function Ad(e){let t=e.scopes.map(n=>{let[r,o]=um[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>${fm}</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">${pm}<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 () {
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 () {
993
1024
  var main = document.querySelector('main');
994
1025
  var id = main.getAttribute('data-request');
995
1026
  var key = main.getAttribute('data-token-key');
@@ -1104,8 +1135,31 @@ a{color:var(--focus)}
1104
1135
  allow.addEventListener('click', function () { decide('allow'); });
1105
1136
  deny.addEventListener('click', function () { decide('deny'); });
1106
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); });
1107
1161
  load();
1108
- })();`;ue();var dt=a(e=>(0,Yn.createHash)("sha256").update(e).digest("hex"),"hash"),It=a(()=>(0,Yn.randomBytes)(32).toString("base64url"),"secret"),mm=36e5,hm=25,gm=5,ym=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-mm).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()>=hm)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 wm(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(wm,"makeLiveCheck");function bm(e,t){if(!e||!t.length)return[];let n=[];try{e(s=>n.push(s))}catch{return[]}let r=wm(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(bm,"findHttpInCollisions");var vm=new Set(["bound dispatch","handle"]),km=["authorize","token","register","revoke"],Am=["authorization-server","protected-resource"];function Sm(e){let t=e.length;for(;t>0&&e.charCodeAt(t-1)===47;)t--;return e.slice(0,t)}a(Sm,"trimTrailingSlashes");function xm(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(xm,"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?bm(e.eachNode,[m]):[],g=new Set(h.map(C=>C.path.toLowerCase())),k=m?Cs(e.httpNode,[m]).filter(C=>!(C.source==="route"&&vm.has(C.layer??"")&&g.has(C.path.toLowerCase()))):[],T=f?Cs(e.httpAdmin,[f]):[];return xm([...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=Sm(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(km.map(p=>`${r}${p}`),i),u(Am.map(p=>`${o}${p}`),d),{authorization:i,metadata:d}}a(Id,"scanOAuthRouteCollisions");function Tm(e){let t=a(n=>{try{return e?.[n]}catch{return}},"read");return t("_router")?.stack??t("router")?.stack??[]}a(Tm,"routerStack");function Rm(e){let t=e.route?.path;return t?(Array.isArray(t)?t:[t]).map(String):[]}a(Rm,"layerPaths");function _m(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(_m,"middlewareHits");function Cs(e,t=["/.well-known/oauth-","/mcp/oauth/"]){let n=[];for(let r of Tm(e)){if(r.handle?.oauthOwned)continue;let o=Rm(r);for(let s of o)t.some(i=>Is(s,i))&&n.push({path:s,source:"route",layer:r.name??"route"});!o.length&&_m(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 Pm(e){let t=(e??"/").split("/").filter(Boolean).join("/");return t?`/${t}/`:"/"}a(Pm,"consentAdminRoot");var Em=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>=gm;)i.delete(b.shift()[0]);return i.size<ym},"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:Pm(e.settings.httpAdminRoot),nonce:Ft,redirectHost:Ke,loopbackRedirect:Em.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"),Nm={};function Cm(){return typeof __dirname<"u"?__dirname:(0,hn.dirname)((0,Fd.fileURLToPath)(Nm.url))}a(Cm,"moduleDir");function $d(){let e=Cm(),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 Lm(){let e=$d();if(!e)throw new Error("OpenAPI specification file was not found");return(0,Xn.readFileSync)(e,"utf8")}a(Lm,"loadOpenApiYaml");function Md(){return(e,t)=>{try{let n=Lm();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 Im(){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(Im,"loadOpenApiJson");function Dd(){return(e,t)=>{try{t.status(200).type("application/json").send(Im())}catch(n){let r=n instanceof Error?n.message:String(n);t.status(500).json({error:r})}}}a(Dd,"openApiJsonHandler");function Fm(){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`
1109
1163
  var settingsData = null;
1110
1164
  var settingsSaving = false;
1111
1165
  var settingsTimer = null;
@@ -1241,7 +1295,7 @@ a{color:var(--focus)}
1241
1295
  return payload;
1242
1296
  }
1243
1297
 
1244
- `}a(Fm,"settingsPartForm");function Om(){return`
1298
+ `}a(Om,"settingsPartForm");function $m(){return`
1245
1299
  function secretPatternsEditable() {
1246
1300
  return settingsCanEdit() && !fieldLocked('secretNamePatterns');
1247
1301
  }
@@ -1329,7 +1383,7 @@ a{color:var(--focus)}
1329
1383
  });
1330
1384
  }
1331
1385
 
1332
- `}a(Om,"settingsPartSecrets");function $m(){return`
1386
+ `}a($m,"settingsPartSecrets");function Mm(){return`
1333
1387
  function settingsFlash(msg, ok) {
1334
1388
  var tab = document.querySelector('nav.tabs button[aria-selected="true"]');
1335
1389
  var target = tab && tab.getAttribute('data-tab') === 'oauth' ? 'oauth-flash' : 'settings-flash';
@@ -1417,7 +1471,7 @@ a{color:var(--focus)}
1417
1471
  }
1418
1472
  }
1419
1473
 
1420
- `}a($m,"settingsPartSave");function Mm(){return`
1474
+ `}a(Mm,"settingsPartSave");function Dm(){return`
1421
1475
  function editorNodeHref(c) {
1422
1476
  var base = editorRoot && editorRoot !== '/' ? editorRoot.replace(/\\/$/, '') + '/' : '/';
1423
1477
  if (c.nodeId) return base + '#node/' + encodeURIComponent(c.nodeId);
@@ -1466,9 +1520,14 @@ a{color:var(--focus)}
1466
1520
  box.hidden = !html;
1467
1521
  }
1468
1522
 
1523
+ var oauthStatusGen = 0;
1524
+ var oauthClientsGen = 0;
1469
1525
  async function loadOAuthStatus() {
1526
+ var gen = ++oauthStatusGen;
1470
1527
  try {
1471
- 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);
1472
1531
  } catch (e) {
1473
1532
  var box = $('oauth-route-warning');
1474
1533
  box.textContent = 'OAuth route status unavailable: ' + (e.message || String(e));
@@ -1478,8 +1537,10 @@ a{color:var(--focus)}
1478
1537
 
1479
1538
  async function loadOAuthClients() {
1480
1539
  var list = $('oauth-clients');
1540
+ var gen = ++oauthClientsGen;
1481
1541
  try {
1482
1542
  var data = await api('/oauth/clients');
1543
+ if (gen !== oauthClientsGen) return;
1483
1544
  var clients = (data && data.clients) || [];
1484
1545
  if (!clients.length) {
1485
1546
  list.innerHTML = '<p class="muted">No registered OAuth clients.</p>';
@@ -1518,7 +1579,7 @@ a{color:var(--focus)}
1518
1579
  }
1519
1580
  }
1520
1581
 
1521
- `}a(Mm,"settingsPartOAuth");function Dm(){return`
1582
+ `}a(Dm,"settingsPartOAuth");function jm(){return`
1522
1583
  function loadOAuthTab() {
1523
1584
  loadSettings();
1524
1585
  loadOAuthStatus();
@@ -1551,7 +1612,7 @@ a{color:var(--focus)}
1551
1612
  showToast('Server URL copied.', true);
1552
1613
  });
1553
1614
  })();
1554
- `}a(Dm,"settingsPartWire");function jd(){return Fm()+Om()+$m()+Mm()+Dm()}a(jd,"pageClientSettingsScript");function Hm(){return`
1615
+ `}a(jm,"settingsPartWire");function jd(){return Om()+$m()+Mm()+Dm()+jm()}a(jd,"pageClientSettingsScript");function Um(){return`
1555
1616
  (function () {
1556
1617
  var root = document.getElementById('mcp-root');
1557
1618
  if (!root) return;
@@ -1572,6 +1633,54 @@ a{color:var(--focus)}
1572
1633
  // Decided proposals load in pages (Load more); pending always load in full.
1573
1634
  var DECIDED_PAGE_SIZE = 50;
1574
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
+ }
1575
1684
  var decidedLoaded = 0;
1576
1685
  var decidedTotal = 0;
1577
1686
  var decidedHasMore = false;
@@ -1772,7 +1881,7 @@ a{color:var(--focus)}
1772
1881
  }
1773
1882
  return body;
1774
1883
  }
1775
- `}a(Hm,"pageClientScriptPartA");function Um(){return`
1884
+ `}a(Um,"pageClientScriptPartA");function Bm(){return`
1776
1885
  // Time helpers \u2014 keep in sync with page-time.ts (always browser-local Intl).
1777
1886
  function titleFromDisplayName(displayName) {
1778
1887
  var s = String(displayName || '');
@@ -1879,7 +1988,7 @@ a{color:var(--focus)}
1879
1988
  btn.disabled = false;
1880
1989
  }, 1600);
1881
1990
  }
1882
- `}a(Um,"pageClientScriptPartATimeCopy");function Bm(){return`
1991
+ `}a(Bm,"pageClientScriptPartATimeCopy");function qm(){return`
1883
1992
  function shortId(value, keep) {
1884
1993
  var s = String(value == null ? '' : value);
1885
1994
  var n = keep || 8;
@@ -2009,7 +2118,7 @@ a{color:var(--focus)}
2009
2118
  }
2010
2119
  return '';
2011
2120
  }
2012
- `}a(Bm,"pageClientScriptPartAFormat");function qm(){return`
2121
+ `}a(qm,"pageClientScriptPartAFormat");function zm(){return`
2013
2122
  function lookupCachedProposal(id) {
2014
2123
  if (!id) return null;
2015
2124
  return cachedProposals.filter(function (x) { return x.id === id; })[0] || null;
@@ -2130,7 +2239,7 @@ a{color:var(--focus)}
2130
2239
  return k + '=' + String(v);
2131
2240
  }).join(', '));
2132
2241
  }
2133
- `}a(qm,"pageClientScriptPartAAudit");function zm(){return`
2242
+ `}a(zm,"pageClientScriptPartAAudit");function Wm(){return`
2134
2243
  // Hash helpers \u2014 keep in sync with page-hash.ts
2135
2244
  var TAB_IDS = ['status', 'settings', 'links', 'oauth', 'proposals'];
2136
2245
  function hashForTab(tab) {
@@ -2416,8 +2525,9 @@ a{color:var(--focus)}
2416
2525
  }
2417
2526
  }
2418
2527
 
2419
- `}a(zm,"pageClientScriptPartATabs");function Wm(){return`
2420
- async function loadLinks() {
2528
+ `}a(Wm,"pageClientScriptPartATabs");function Gm(){return`
2529
+ async function loadLinks(opts) {
2530
+ opts = opts || {};
2421
2531
  if (!aiOn || !canManage) {
2422
2532
  var dashLinksOff = $('dash-links');
2423
2533
  if (dashLinksOff && !aiOn) dashLinksOff.textContent = '\u2014';
@@ -2425,15 +2535,19 @@ a{color:var(--focus)}
2425
2535
  }
2426
2536
  var list = $('links-list');
2427
2537
  var flash = $('links-flash');
2428
- showFlash(flash, '', true);
2538
+ if (!opts.auto) showFlash(flash, '', true);
2539
+ var gen = beginLoad('links');
2429
2540
  try {
2430
2541
  var data = await api('/links');
2542
+ if (!isLatestLoad('links', gen)) return;
2431
2543
  var links = (data && data.links) || [];
2432
2544
  links.forEach(function (l) {
2433
2545
  if (l && l.id) cachedLinksById[l.id] = l;
2434
2546
  });
2435
2547
  var dashLinks = $('dash-links');
2436
2548
  if (dashLinks) dashLinks.textContent = String(links.length);
2549
+ var linksUnchanged = sameAsLastRender('links', JSON.stringify(links));
2550
+ if (opts.auto && linksUnchanged) return;
2437
2551
  if (!links.length) {
2438
2552
  list.innerHTML = '<div class="empty-state"><p class="empty-title">No active access links</p>' +
2439
2553
  '<p class="muted">Create a link above to give an AI temporary access to read flows and submit proposals.</p></div>';
@@ -2485,7 +2599,7 @@ a{color:var(--focus)}
2485
2599
  showToast(e.message || String(e), false);
2486
2600
  }
2487
2601
  }
2488
- `}a(Wm,"pageClientScriptPartA2");function Gm(){return`
2602
+ `}a(Gm,"pageClientScriptPartA2");function Km(){return`
2489
2603
 
2490
2604
  function normalizeLinkKind(kind) {
2491
2605
  if (kind === 'rest' || kind === 'mcp' || kind === 'get_link') return kind;
@@ -2902,13 +3016,17 @@ a{color:var(--focus)}
2902
3016
  paint();
2903
3017
  }
2904
3018
 
3019
+ function proposalFlowId(p) {
3020
+ return (p && (p.flowId || (p.payload && p.payload.flowId))) || '';
3021
+ }
3022
+
2905
3023
  function flowLabelOf(p) {
2906
3024
  if (p && p.flowLabel) return String(p.flowLabel);
2907
3025
  var flow = (p && p.payload && p.payload.flow) || {};
2908
3026
  var base = (p && p.baseFlow) || {};
2909
3027
  if (flow.label) return String(flow.label);
2910
3028
  if (base.label) return String(base.label);
2911
- var flowId = p && p.payload && p.payload.flowId;
3029
+ var flowId = proposalFlowId(p);
2912
3030
  if (flowId && cachedFlowLabels[flowId]) return String(cachedFlowLabels[flowId]);
2913
3031
  return '';
2914
3032
  }
@@ -3211,7 +3329,7 @@ a{color:var(--focus)}
3211
3329
  decided: Math.max(decidedTotal, cachedProposals.filter(function (p) { return p.status !== 'pending'; }).length)
3212
3330
  };
3213
3331
  }
3214
- `}a(Gm,"pageClientScriptPartB");function Km(){return`
3332
+ `}a(Km,"pageClientScriptPartB");function Jm(){return`
3215
3333
  var DIFF_MODE_KEY = 'ai-access-diff-mode';
3216
3334
  var DIFF_SPLIT_MIN_PX = ${900};
3217
3335
  var PRESENCE_SHORT_TEXT_MAX = ${120};
@@ -3328,7 +3446,7 @@ a{color:var(--focus)}
3328
3446
  '</div>';
3329
3447
  }
3330
3448
 
3331
- `}a(Km,"pageClientScriptPartBDiff");function Jm(){return`
3449
+ `}a(Jm,"pageClientScriptPartBDiff");function Vm(){return`
3332
3450
  function isCollapsiblePresenceText(value) {
3333
3451
  var s = String(value == null ? '' : value);
3334
3452
  if (s.indexOf('\\n') >= 0) return true;
@@ -3399,7 +3517,9 @@ a{color:var(--focus)}
3399
3517
  : (' <span class="muted tip-id" title="' + esc(n.id) + '">' + tipId(n.id, 8) + '</span>');
3400
3518
  return '<details class="node-presence node-' + kind + '"' + open + '>' +
3401
3519
  '<summary><span class="presence-summary-text">' + summaryText + '</span>' +
3402
- idHint + '</summary>' +
3520
+ idHint +
3521
+ (kind === 'removed' ? ' ' + editorJumpLink('node', n.id, 'Show in editor', 'editor-jump-small') : '') +
3522
+ '</summary>' +
3403
3523
  '<div class="presence-body">' + body + '</div></details>';
3404
3524
  }
3405
3525
 
@@ -3429,7 +3549,7 @@ a{color:var(--focus)}
3429
3549
  });
3430
3550
  });
3431
3551
  }
3432
- `}a(Jm,"pageClientScriptPartBDiffPresence");function Vm(){return`
3552
+ `}a(Vm,"pageClientScriptPartBDiffPresence");function Ym(){return`
3433
3553
  function textChangeLineCount(ch) {
3434
3554
  return (ch.lines || []).length;
3435
3555
  }
@@ -3488,7 +3608,7 @@ a{color:var(--focus)}
3488
3608
  '<code class="diff-before" title="before">' + esc(JSON.stringify(ch.before)) + '</code>' +
3489
3609
  ' \u2192 <code class="diff-after" title="after">' + esc(JSON.stringify(ch.after)) + '</code></div>';
3490
3610
  }
3491
- `}a(Vm,"pageClientScriptPartBDiffView");function Ym(){return`
3611
+ `}a(Ym,"pageClientScriptPartBDiffView");function Xm(){return`
3492
3612
  function renderDiffSections(sections, mode, emptyMsg) {
3493
3613
  var parts = '';
3494
3614
  if (sections.added && sections.added.length) {
@@ -3512,7 +3632,8 @@ a{color:var(--focus)}
3512
3632
  ? ''
3513
3633
  : (' <span class="muted tip-id" title="' + esc(m.id) + '">' + tipId(m.id, 8) + '</span>');
3514
3634
  return '<div class="modified-node">' +
3515
- '<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>' +
3516
3637
  (m.changes || []).map(function (ch) { return renderPropertyChange(ch, mode); }).join('') +
3517
3638
  '</div>';
3518
3639
  }).join('') + '</div>';
@@ -3576,7 +3697,7 @@ a{color:var(--focus)}
3576
3697
  // Full view kept for mode-toggle re-render inside .proposal-changes.
3577
3698
  return renderHumanDiff(diff, mode);
3578
3699
  }
3579
- `}a(Ym,"pageClientScriptPartBDiffView2");function Xm(){return`
3700
+ `}a(Xm,"pageClientScriptPartBDiffView2");function Zm(){return`
3580
3701
  function updatePendingBadges() {
3581
3702
  var counts = countPendingBuckets();
3582
3703
  var fullText = formatPendingBadgeText(counts.live, counts.stale);
@@ -3692,7 +3813,7 @@ a{color:var(--focus)}
3692
3813
  });
3693
3814
  });
3694
3815
  }
3695
- `}a(Xm,"pageClientScriptPartB2");function Zm(){return`
3816
+ `}a(Zm,"pageClientScriptPartB2");function Qm(){return`
3696
3817
  function renderDeleteBanner(diff) {
3697
3818
  if (!isLargeDelete(diff)) return '';
3698
3819
  var beforeN = diff.summary.beforeNodeCount || 0;
@@ -3774,7 +3895,7 @@ a{color:var(--focus)}
3774
3895
  loadProposals();
3775
3896
  loadLogs();
3776
3897
  }
3777
- `}a(Zm,"pageClientScriptPartB2Actions");function Qm(){return`
3898
+ `}a(Qm,"pageClientScriptPartB2Actions");function eh(){return`
3778
3899
  function renderRequestedVia(p) {
3779
3900
  if (!p.createdBy) return '';
3780
3901
  if (p.createdBy.label) {
@@ -3815,7 +3936,11 @@ a{color:var(--focus)}
3815
3936
  timeHtml = timeCell(p.createdAt);
3816
3937
  }
3817
3938
  return '<span class="proposal-meta-line">' +
3818
- (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
+ : '') +
3819
3944
  timeHtml +
3820
3945
  ' \xB7 <span class="proposal-id-meta">id ' + proposalIdChip(p.id) + '</span>' +
3821
3946
  '</span>';
@@ -3862,7 +3987,7 @@ a{color:var(--focus)}
3862
3987
  actions +
3863
3988
  '</article>';
3864
3989
  }
3865
- `}a(Qm,"pageClientScriptPartB2Cards");function eh(){return`
3990
+ `}a(eh,"pageClientScriptPartB2Cards");function th(){return`
3866
3991
  function renderStaleItem(p) {
3867
3992
  var cached = proposalDiffCache[p.id];
3868
3993
  var title = titleForProposalCard(p);
@@ -4037,7 +4162,7 @@ a{color:var(--focus)}
4037
4162
  bindProposalActions(list);
4038
4163
  focusProposalFromHash();
4039
4164
  }
4040
- `}a(eh,"pageClientScriptPartB2CardsList");function th(){return`
4165
+ `}a(th,"pageClientScriptPartB2CardsList");function nh(){return`
4041
4166
  async function refreshFlowLabels() {
4042
4167
  try {
4043
4168
  var data = await api('/flows');
@@ -4055,6 +4180,7 @@ a{color:var(--focus)}
4055
4180
  async function loadProposals(opts) {
4056
4181
  if (!aiOn) return;
4057
4182
  opts = opts || {};
4183
+ var gen = beginLoad('proposals');
4058
4184
  var flash = $('proposals-flash');
4059
4185
  if (!opts.skipRender && !opts.keepFlash) {
4060
4186
  showFlash(flash, '', true);
@@ -4066,23 +4192,30 @@ a{color:var(--focus)}
4066
4192
  var decidedLimit = Math.min(200, Math.max(DECIDED_PAGE_SIZE, decidedLoaded));
4067
4193
  var decidedData = await api('/proposals?status=' + DECIDED_STATUSES +
4068
4194
  '&limit=' + decidedLimit + '&offset=0');
4195
+ if (!isLatestLoad('proposals', gen)) return; // a newer refresh superseded this one
4069
4196
  var decidedList = (decidedData && decidedData.proposals) || [];
4070
4197
  decidedLoaded = decidedList.length;
4071
4198
  decidedTotal = Number(decidedData && decidedData.total) || decidedList.length;
4072
4199
  decidedHasMore = !!(decidedData && decidedData.hasMore);
4073
4200
  cachedProposals = ((pendingData && pendingData.proposals) || []).concat(decidedList);
4074
4201
  cachedProposals.forEach(function (p) {
4075
- var id = p && p.payload && p.payload.flowId;
4202
+ var id = proposalFlowId(p);
4076
4203
  var label = p && p.flowLabel;
4077
4204
  if (id && label) cachedFlowLabels[id] = String(label);
4078
4205
  });
4079
- if (!opts.skipRender) {
4080
- renderProposalsList();
4081
- } 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)) {
4082
4211
  updatePendingBadges();
4212
+ } else if (opts.auto) {
4213
+ preserveUiState($('proposals-list'), renderProposalsList);
4214
+ } else {
4215
+ renderProposalsList();
4083
4216
  }
4084
4217
  } catch (e) {
4085
- showFlash(flash, e.message || String(e), false);
4218
+ if (isLatestLoad('proposals', gen)) showFlash(flash, e.message || String(e), false);
4086
4219
  }
4087
4220
  }
4088
4221
 
@@ -4204,7 +4337,7 @@ a{color:var(--focus)}
4204
4337
  replaceLocationHash('#proposals');
4205
4338
  return true;
4206
4339
  }
4207
- `}a(th,"pageClientScriptPartB2Focus");function nh(){return`
4340
+ `}a(nh,"pageClientScriptPartB2Focus");function rh(){return`
4208
4341
  function bindDiffModeToggle(host, diff) {
4209
4342
  host.querySelectorAll('[data-diff-mode]').forEach(function (btn) {
4210
4343
  btn.addEventListener('click', function () {
@@ -4316,7 +4449,7 @@ a{color:var(--focus)}
4316
4449
  esc(e.message || String(e)) + '</p>';
4317
4450
  }
4318
4451
  }
4319
- }`}a(nh,"pageClientScriptPartB2Apply");function rh(){return`
4452
+ }`}a(rh,"pageClientScriptPartB2Apply");function oh(){return`
4320
4453
 
4321
4454
  function dialogFocusables(dialog) {
4322
4455
  if (!dialog) return [];
@@ -4520,6 +4653,15 @@ a{color:var(--focus)}
4520
4653
  }
4521
4654
 
4522
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) {
4523
4665
  var flash = $('proposals-flash');
4524
4666
  var pBefore = cachedProposals.filter(function (x) { return x.id === id; })[0];
4525
4667
  var toastTitle = proposalToastTitle(pBefore);
@@ -4594,6 +4736,15 @@ a{color:var(--focus)}
4594
4736
  }
4595
4737
 
4596
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) {
4597
4748
  var flash = $('proposals-flash');
4598
4749
  var reason = reasonOverride && String(reasonOverride).trim()
4599
4750
  ? String(reasonOverride).trim()
@@ -4630,7 +4781,7 @@ a{color:var(--focus)}
4630
4781
  throw e;
4631
4782
  }
4632
4783
  }
4633
- `}a(rh,"pageClientScriptPartC");function oh(){return`
4784
+ `}a(oh,"pageClientScriptPartC");function sh(){return`
4634
4785
  var cachedLogs = [];
4635
4786
 
4636
4787
  function renderLogsTable(logs) {
@@ -4667,10 +4818,12 @@ a{color:var(--focus)}
4667
4818
  renderLogsTable(filtered);
4668
4819
  }
4669
4820
 
4670
- async function loadLogs() {
4821
+ async function loadLogs(opts) {
4671
4822
  if (!aiOn) return;
4823
+ opts = opts || {};
4672
4824
  var flash = $('logs-flash');
4673
- showFlash(flash, '', true);
4825
+ if (!opts.auto) showFlash(flash, '', true);
4826
+ var gen = beginLoad('logs');
4674
4827
  try {
4675
4828
  // Need proposals (+ diffs) so audit rows use the same render-time titles.
4676
4829
  if (!cachedProposals.length) {
@@ -4680,7 +4833,10 @@ a{color:var(--focus)}
4680
4833
  try { await loadLinks(); } catch (e) { /* best-effort for link label lookup */ }
4681
4834
  }
4682
4835
  var data = await api('/logs?limit=100');
4683
- 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;
4684
4840
  var filter = $('logs-filter');
4685
4841
  if (filter) {
4686
4842
  var actions = [];
@@ -4707,7 +4863,7 @@ a{color:var(--focus)}
4707
4863
  showFlash(flash, e.message || String(e), false);
4708
4864
  }
4709
4865
  }
4710
- `}a(oh,"pageClientScriptPartCLogs");function sh(){return`
4866
+ `}a(sh,"pageClientScriptPartCLogs");function ih(){return`
4711
4867
  function applyStatus(s) {
4712
4868
  aiOn = !!s.aiFeaturesEnabled;
4713
4869
  canManage = !!(s.capabilities && s.capabilities.manageLinks);
@@ -4868,7 +5024,7 @@ a{color:var(--focus)}
4868
5024
  });
4869
5025
  }
4870
5026
  }
4871
- `}a(sh,"pageClientScriptPartD");function ih(){return`
5027
+ `}a(ih,"pageClientScriptPartD");function ah(){return`
4872
5028
  async function boot() {
4873
5029
  accessToken = readEditorToken();
4874
5030
  if (!accessToken) {
@@ -4972,9 +5128,126 @@ a{color:var(--focus)}
4972
5128
  loadTabData(tab);
4973
5129
  });
4974
5130
 
5131
+ ${dh()}
5132
+ ${lh()}
4975
5133
  boot();
4976
5134
  })();
4977
- `}a(ih,"pageClientScriptPartDBoot");function Hd(){return(Hm()+Um()+Bm()+qm()+zm()+Wm()+Gm()+Km()+Jm()+Vm()+Ym()+Xm()+Zm()+Qm()+eh()+th()+nh()+rh()+oh()+sh()+jd()+ih()).trim()}a(Hd,"pageClientScript");function ah(){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`
4978
5251
  :root {
4979
5252
  color-scheme: light;
4980
5253
  --bg: #eef2f5;
@@ -5044,7 +5317,7 @@ h1 {
5044
5317
  text-decoration: none;
5045
5318
  text-align: center;
5046
5319
  }
5047
- `}a(ah,"pageStylesPartA");function dh(){return`
5320
+ `}a(ch,"pageStylesPartA");function uh(){return`
5048
5321
  nav.tabs {
5049
5322
  position: sticky;
5050
5323
  top: 0;
@@ -5138,7 +5411,7 @@ dd {
5138
5411
  padding: 0.22rem 0.55rem;
5139
5412
  border-radius: 6px;
5140
5413
  cursor: help;
5141
- background: var`}a(dh,"pageStylesPartA2");function lh(){return`(--accent-soft);
5414
+ background: var`}a(uh,"pageStylesPartA2");function ph(){return`(--accent-soft);
5142
5415
  color: var(--accent);
5143
5416
  font-size: 0.82rem;
5144
5417
  font-weight: 650;
@@ -5329,7 +5602,7 @@ dialog.token-modal { width: min(720px, 94vw); max-width: min(720px, 94vw); max-h
5329
5602
  color: var(--muted);
5330
5603
  font-size: 0.92rem;
5331
5604
  }
5332
- `}a(lh,"pageStylesPartB");function ch(){return`
5605
+ `}a(ph,"pageStylesPartB");function fh(){return`
5333
5606
  .logs-toolbar { margin-bottom: 0.65rem; }
5334
5607
  table.audit-table {
5335
5608
  table-layout: fixed;
@@ -5405,7 +5678,7 @@ table.audit-table {
5405
5678
  cursor: help;
5406
5679
  border-bottom: 1px dotted var(--line);
5407
5680
  }
5408
- `}a(ch,"pageStylesPartC");function uh(){return`
5681
+ `}a(fh,"pageStylesPartC");function mh(){return`
5409
5682
  .proposal-diff {
5410
5683
  margin: 0.65rem 0;
5411
5684
  padding: 0.55rem 0.65rem;
@@ -5482,7 +5755,7 @@ table.audit-table {
5482
5755
  color: var(--muted);
5483
5756
  margin-bottom: 0.15rem;
5484
5757
  }
5485
- `}a(uh,"pageStylesPartD");function ph(){return`
5758
+ `}a(mh,"pageStylesPartD");function hh(){return`
5486
5759
  .diff-block {
5487
5760
  margin-top: 0.25rem;
5488
5761
  font-family: ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace;
@@ -5564,7 +5837,7 @@ table.audit-table {
5564
5837
  }
5565
5838
  .diff-before { color: var(--danger); word-break: break-word; }
5566
5839
  .diff-after { color: var(--ok); word-break: break-word; }
5567
- `}a(ph,"pageStylesPartE");function fh(){return`
5840
+ `}a(hh,"pageStylesPartE");function gh(){return`
5568
5841
  .proposal-detail {
5569
5842
  scroll-margin-top: 4.5rem;
5570
5843
  }
@@ -5690,7 +5963,7 @@ table.audit-table {
5690
5963
  .audit-table .col-action { width: 5.5rem; min-width: 5.5rem; }
5691
5964
  .audit-table .col-actor { width: 7rem; min-width: 6rem; }
5692
5965
  }
5693
- `}a(fh,"pageStylesPartF");function mh(){return`
5966
+ `}a(gh,"pageStylesPartF");function yh(){return`
5694
5967
  .dash-grid {
5695
5968
  display: grid;
5696
5969
  grid-template-columns: repeat(auto-fit, minmax(10.5rem, 1fr));
@@ -5791,6 +6064,10 @@ button.id-copy:focus {
5791
6064
  .proposal-plain-summary { margin: 0.15rem 0 0.35rem; color: var(--ink); font-size: 0.95rem; }
5792
6065
  .proposal-meta-line { color: var(--muted); font-size: 0.88rem; }
5793
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'; }
5794
6071
  .proposal-changes { margin: 0.35rem 0 0.55rem; }
5795
6072
  .delete-banner {
5796
6073
  margin: 0 0 0.55rem;
@@ -5918,7 +6195,7 @@ button.id-copy:focus {
5918
6195
  background: var(--code-bg, var(--soft));
5919
6196
  border-radius: 4px;
5920
6197
  }
5921
- `}a(mh,"pageStylesPartG");function hh(){return`
6198
+ `}a(yh,"pageStylesPartG");function wh(){return`
5922
6199
  .stale-group {
5923
6200
  margin: 1rem 0 0;
5924
6201
  border: 1px solid var(--warn-border);
@@ -6082,7 +6359,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6082
6359
  margin-left: auto;
6083
6360
  }
6084
6361
  .debug-toggle input { width: auto; }
6085
- `}a(hh,"pageStylesPartH");function gh(){return`
6362
+ `}a(wh,"pageStylesPartH");function bh(){return`
6086
6363
  .card {
6087
6364
  background: var(--card);
6088
6365
  border: 1px solid var(--line);
@@ -6152,7 +6429,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6152
6429
  nav.tabs button { flex: 0 0 auto; }
6153
6430
  .field-line { flex-wrap: wrap; }
6154
6431
  }
6155
- `}a(gh,"pageStylesDashboard");function Ud(){return(ah()+dh()+lh()+ch()+uh()+ph()+fh()+mh()+hh()+gh()).trim()}a(Ud,"pageStyles");function yh(e){return e.replaceAll("&","&amp;").replaceAll("<","&lt;").replaceAll(">","&gt;").replaceAll('"',"&quot;")}a(yh,"escapeHtml");function wh(){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`
6156
6433
  <header class="page-header">
6157
6434
  <h1>Node-RED AI Access</h1>
6158
6435
  <p class="lede">
@@ -6183,7 +6460,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6183
6460
  Proposals <span class="tab-badge" id="proposals-pending-badge" hidden>0</span>
6184
6461
  </button>
6185
6462
  <button type="button" data-tab="logs" aria-selected="false">Audit log</button>
6186
- </nav>`}a(wh,"renderMcpPageHeader");function bh(){return`
6463
+ </nav>`}a(kh,"renderMcpPageHeader");function Ah(){return`
6187
6464
  <section class="panel active" id="section-status" aria-label="Overview" hidden>
6188
6465
  <h2>Overview</h2>
6189
6466
  <div id="status-flash" class="flash" hidden></div>
@@ -6227,7 +6504,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6227
6504
  <div class="row" style="margin-top:1rem">
6228
6505
  <button type="button" class="action secondary" id="refresh-status">Refresh</button>
6229
6506
  </div>
6230
- </section>`}a(bh,"renderMcpPageStatusSection");function vh(){return`
6507
+ </section>`}a(Ah,"renderMcpPageStatusSection");function Sh(){return`
6231
6508
  <section class="panel" id="section-links" aria-label="Access tokens" hidden>
6232
6509
  <h2>Access tokens</h2>
6233
6510
  <p class="muted" id="links-intro">
@@ -6238,7 +6515,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6238
6515
  <div id="links-flash" class="flash" hidden></div>
6239
6516
  <div id="links-manage"></div>
6240
6517
  <div id="links-list"></div>
6241
- </section>`}a(vh,"renderMcpPageLinksSection");function be(e){return`<span class="lock-badge" id="lock-${e}" hidden>Locked by settings.js</span>`}a(be,"lockBadge");function kh(){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`
6242
6519
  <section class="panel" id="section-settings" aria-label="Settings" hidden>
6243
6520
  <h2>Settings</h2>
6244
6521
  <p class="muted">Changes save automatically and apply immediately (no restart).</p>
@@ -6365,7 +6642,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6365
6642
  <p class="hint" id="set-trust-proxy-inline"></p>
6366
6643
  </div>
6367
6644
  </div>
6368
- </section>`}a(kh,"renderMcpPageSettingsSection");function Ah(){return`
6645
+ </section>`}a(xh,"renderMcpPageSettingsSection");function Th(){return`
6369
6646
  <section class="panel" id="section-oauth" aria-label="OAuth" hidden>
6370
6647
  <h2>MCP OAuth</h2>
6371
6648
  <p class="muted">MCP clients (ChatGPT, Claude, \u2026) can request read or propose access through OAuth.</p>
@@ -6440,7 +6717,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6440
6717
  <button type="button" class="action secondary" id="oauth-refresh">Refresh</button></div>
6441
6718
  <div id="oauth-clients" aria-live="polite"><p class="muted">Loading\u2026</p></div>
6442
6719
  </div>
6443
- </section>`}a(Ah,"renderMcpPageOAuthSection");function Sh(){return`
6720
+ </section>`}a(Th,"renderMcpPageOAuthSection");function Rh(){return`
6444
6721
  <section class="panel" id="section-proposals" aria-label="Proposals" hidden>
6445
6722
  <h2>Proposals</h2>
6446
6723
  <p id="proposals-hint" class="muted"></p>
@@ -6458,7 +6735,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6458
6735
  </div>
6459
6736
  <div id="proposals-flash" class="flash" hidden></div>
6460
6737
  <div id="proposals-list"><p class="muted">Loading\u2026</p></div>
6461
- </section>`}a(Sh,"renderMcpPageProposalsSection");function xh(){return`
6738
+ </section>`}a(Rh,"renderMcpPageProposalsSection");function _h(){return`
6462
6739
  <section class="panel" id="section-logs" aria-label="Audit log" hidden>
6463
6740
  <h2>Audit log</h2>
6464
6741
  <div id="logs-flash" class="flash" hidden></div>
@@ -6474,7 +6751,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6474
6751
  </label>
6475
6752
  </div>
6476
6753
  <div id="logs-list"><p class="muted">Loading\u2026</p></div>
6477
- </section>`}a(xh,"renderMcpPageLogsSection");function Th(){return`
6754
+ </section>`}a(_h,"renderMcpPageLogsSection");function Ph(){return`
6478
6755
  <dialog id="reject-dialog">
6479
6756
  <form method="dialog" id="reject-form">
6480
6757
  <h3>Reject proposal</h3>
@@ -6516,7 +6793,7 @@ dialog h3 { margin: 0 0 0.4rem; }
6516
6793
  <button type="submit" value="confirm" class="action danger" id="confirm-action-btn">Confirm</button>
6517
6794
  </div>
6518
6795
  </form>
6519
- </dialog>`}a(Th,"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>
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>
6520
6797
  <html lang="en">
6521
6798
  <head>
6522
6799
  <meta charset="utf-8" />
@@ -6529,18 +6806,18 @@ dialog h3 { margin: 0 0 0.4rem; }
6529
6806
  <body>
6530
6807
  <main
6531
6808
  id="mcp-root"
6532
- data-config="${yh(JSON.stringify(t))}"
6533
- >${wh()}
6534
- ${bh()}
6535
- ${kh()}
6536
- ${vh()}
6809
+ data-config="${vh(JSON.stringify(t))}"
6810
+ >${kh()}
6537
6811
  ${Ah()}
6538
- ${Sh()}
6539
6812
  ${xh()}
6813
+ ${Sh()}
6540
6814
  ${Th()}
6815
+ ${Rh()}
6816
+ ${_h()}
6817
+ ${Ph()}
6541
6818
  </main>
6542
6819
  <script>${Hd()}</script>
6543
6820
  </body>
6544
- </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 Rh(e){return e.endsWith("/")?e.slice(0,-1):e}a(Rh,"trimTrailingSlash");var mo=class{static{a(this,"HttpAdapter")}kind="http";baseUrl;accessToken;fetchImpl;constructor(t){this.baseUrl=Rh(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 _h(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(_h,"requireFlowsApi");function Ph(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(Ph,"resolveRev");var ho=class{static{a(this,"RedAdapter")}kind="red";flows;constructor(t){this.flows=_h(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 Eh(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:Ph(s)}}};function Xd(e){let t=e?.runtime?.flows;return!!(t?.getFlows&&t.getFlow&&t.setFlows)}a(Xd,"canUseRedRuntime");function Eh(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(Eh,"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 Ch(){if(typeof require=="function")return require;let e=(0,eval)("import.meta.url");return(0,Zd.createRequire)(e)}a(Ch,"createPluginRequire");function Lh(e){try{let t=e("@node-red/runtime/lib/settings");return typeof t.available=="function"&&t.available()===!0}catch{return!1}}a(Lh,"isNodeRedRuntimeInitialized");function Ih(){try{let e=Ch();if(!Lh(e))return null;let t=e("@node-red/runtime");return Qd(t.flows)?t.flows:null}catch{return null}}a(Ih,"tryRequireInitializedRuntimeFlows");function el(e){if(e?.runtime&&Qd(e.runtime.flows))return e;let t=Ih();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 Nh=/^[a-z][a-z0-9._-]{0,63}$/;function Fh(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(Fh,"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=Nh.test(t)?t:"unknown";this.seq+=1;let s={id:`log-${this.now()}-${this.seq}`,at:new Date(this.now()).toISOString(),action:o,actor:Fh(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(`
6545
- `).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 Oh="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,Oh);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 $h(e){let t=a(()=>Promise.reject(new Ne(e)),"fail");return{kind:"http",getFlows:t,getFlow:t,updateFlow:t}}a($h,"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=$h(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 Mh(e,t){return e===10||e===172&&t>=16&&t<=31||e===192&&t===168}a(Mh,"isPrivateV4");function Dh(e,t){return e===169&&t===254}a(Dh,"isLinkLocalV4");function jh(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(jh,"isSpecialUseV4");function Hh(e){let[t=0,n=0,r=0]=e.split(".").map(Number);return t===127?"loopback":Mh(t,n)||Dh(t,n)||jh(t,n,r)||e==="168.63.129.16"?"blocked":"public"}a(Hh,"classifyIpv4");function Uh(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(Uh,"expandIpv6");function Bh(e){let t=Uh(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(Bh,"classifyIpv6");function Qn(e){let t=ft(e);return t?(0,Zn.isIP)(t)===4?Hh(t):Bh(t):"blocked"}a(Qn,"classifyPublicUrlAddress");function qh(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(qh,"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(qh(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 zh(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(zh,"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 zh(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 Wh(e){return Rn(e.settings.httpAdminRoot)}a(Wh,"mcpPageUrl");function Gh(e){return typeof e=="boolean"||typeof e=="number"||typeof e=="string"?e:Array.isArray(e)?e.join(", "):typeof e=="function"?"custom function":!1}a(Gh,"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:Wh(e),apiBase:fe(e.settings.httpAdminRoot),settings:o,oauthPublicUrlWarning:pl(o.publicBaseUrl),sources:r,disk:t.getDisk(),trustProxy:Gh(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 Kh(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(Kh,"parseRateLimitField");function Jh(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(Jh,"parseCorsField");function Vh(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(Vh,"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)),Jh(i,d),Vh(i,d,l);for(let c of["rateLimitWindowMs","rateLimitMaxPerIp","rateLimitMaxPerToken"])Kh(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 Yh="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 Xh(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(Xh,"registerMcpShellAndDiscovery");var Zh=/^\/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(`
6546
- `);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(!Zh.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))}),Xh(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 Qh(e){e.plugins.registerPlugin(Yh,{type:"com55-ai-access",onadd(){vl(e),e.log?.info?.("Node-RED AI Access plugin loaded (dashboard /ai-access, MCP /mcp)")}})}a(Qh,"registerAiAccessPlugin");var eg=Qh;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});